feat: Display dashboard as a 2-columns grid on big screens (#1000)

* feat: display 2 columns grid in dashboard for wide screens

* fix: update sortable controller to consider X/Y coordinates

* fix: lint issues + missing variable init
This commit is contained in:
Alessio Cappa
2026-02-16 13:45:49 +01:00
committed by GitHub
parent 1995c62ddf
commit 23087c1e98
3 changed files with 34 additions and 21 deletions

View File

@@ -12,6 +12,7 @@ export default class extends Controller {
this.draggedElement = null;
this.placeholder = null;
this.touchStartY = 0;
this.currentTouchX = 0;
this.currentTouchY = 0;
this.isTouching = false;
this.keyboardGrabbedElement = null;
@@ -37,7 +38,7 @@ export default class extends Controller {
event.preventDefault();
event.dataTransfer.dropEffect = "move";
const afterElement = this.getDragAfterElement(event.clientY);
const afterElement = this.getDragAfterElement(event.clientX, event.clientY);
const container = this.element;
this.clearPlaceholders();
@@ -53,7 +54,7 @@ export default class extends Controller {
event.preventDefault();
event.stopPropagation();
const afterElement = this.getDragAfterElement(event.clientY);
const afterElement = this.getDragAfterElement(event.clientX, event.clientY);
const container = this.element;
if (afterElement == null) {
@@ -81,7 +82,9 @@ export default class extends Controller {
if (section.getAttribute("draggable") === "false") return;
this.pendingSection = section;
this.touchStartX = event.touches[0].clientX;
this.touchStartY = event.touches[0].clientY;
this.currentTouchX = this.touchStartX;
this.currentTouchY = this.touchStartY;
this.holdActivated = false;
@@ -110,9 +113,10 @@ export default class extends Controller {
if (!this.holdActivated || !this.isTouching || !this.draggedElement) return;
event.preventDefault();
this.currentTouchX = event.touches[0].clientX;
this.currentTouchY = event.touches[0].clientY;
const afterElement = this.getDragAfterElement(this.currentTouchY);
const afterElement = this.getDragAfterElement(this.currentTouchX, this.currentTouchY);
this.clearPlaceholders();
if (afterElement == null) {
@@ -130,7 +134,7 @@ export default class extends Controller {
return;
}
const afterElement = this.getDragAfterElement(this.currentTouchY);
const afterElement = this.getDragAfterElement(this.currentTouchX, this.currentTouchY);
const container = this.element;
if (afterElement == null) {
@@ -240,23 +244,32 @@ export default class extends Controller {
}
}
getDragAfterElement(y) {
const draggableElements = [
...this.sectionTargets.filter((section) => section !== this.draggedElement),
];
getDragAfterElement(pointerX, pointerY) {
const draggableElements = this.sectionTargets.filter(
(section) => section !== this.draggedElement,
);
return draggableElements.reduce(
(closest, child) => {
const box = child.getBoundingClientRect();
const offset = y - box.top - box.height / 2;
if (draggableElements.length === 0) return null;
if (offset < 0 && offset > closest.offset) {
return { offset: offset, element: child };
}
return closest;
},
{ offset: Number.NEGATIVE_INFINITY },
).element;
let closest = null;
let minDistance = Number.POSITIVE_INFINITY;
draggableElements.forEach((child) => {
const rect = child.getBoundingClientRect();
const centerX = rect.left + rect.width / 2;
const centerY = rect.top + rect.height / 2;
const dx = pointerX - centerX;
const dy = pointerY - centerY;
const distance = Math.sqrt(dx * dx + dy * dy);
if (distance < minDistance) {
minDistance = distance;
closest = child;
}
});
return closest;
}
showPlaceholder(element, position) {

View File

@@ -126,7 +126,7 @@ end %>
<% end %>
<%# SHARED - Main content %>
<%= tag.main class: class_names("grow overflow-y-auto px-3 lg:px-10 w-full mx-auto max-w-5xl"), data: { app_layout_target: "content", viewport_target: "content" } do %>
<%= tag.main class: class_names("grow overflow-y-auto px-3 lg:px-10 w-full mx-auto"), data: { app_layout_target: "content", viewport_target: "content" } do %>
<% unless intro_mode %>
<div class="hidden lg:flex gap-2 items-center justify-between mb-6 sticky top-0 z-10 -mx-3 lg:-mx-10 px-3 lg:px-10 py-4 bg-surface border-b border-tertiary">
<div class="flex items-center gap-2">

View File

@@ -27,7 +27,7 @@
</div>
<% end %>
<div class="w-full space-y-6 pb-6 lg:pb-12" data-controller="dashboard-sortable" data-action="dragover->dashboard-sortable#dragOver drop->dashboard-sortable#drop" role="list" aria-label="Dashboard sections">
<div class="grid grid-cols-1 2xl:grid-cols-2 gap-6 pb-6 lg:pb-12" data-controller="dashboard-sortable" data-action="dragover->dashboard-sortable#dragOver drop->dashboard-sortable#drop" role="list" aria-label="Dashboard sections">
<% if Current.family.accounts.any? %>
<% @dashboard_sections.each do |section| %>
<% next unless section[:visible] %>