Skip to content

Commit ba9d26c

Browse files
committed
Pack dashboard cards without row gaps
1 parent 22897a7 commit ba9d26c

3 files changed

Lines changed: 85 additions & 27 deletions

File tree

src/dashboard-layout.ts

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
const dashboardSelector = '[data-layout="dashboard"]'
2+
const wideDashboard = window.matchMedia("(min-width: 1280px)")
3+
const verticalGap = 20
4+
const observedItems = new Set<HTMLElement>()
5+
const pendingFrames = new WeakMap<HTMLElement, number>()
6+
7+
function dashboardGrids() {
8+
return Array.from(document.querySelectorAll<HTMLElement>(dashboardSelector))
9+
}
10+
11+
function resetGrid(grid: HTMLElement) {
12+
grid.style.removeProperty("grid-auto-rows")
13+
grid.style.removeProperty("grid-auto-flow")
14+
grid.style.removeProperty("row-gap")
15+
for (const child of Array.from(grid.children)) {
16+
if (!(child instanceof HTMLElement)) continue
17+
child.style.removeProperty("align-self")
18+
child.style.removeProperty("grid-row-end")
19+
}
20+
}
21+
22+
function measureGrid(grid: HTMLElement) {
23+
if (!grid.isConnected) return
24+
if (!wideDashboard.matches) {
25+
resetGrid(grid)
26+
return
27+
}
28+
29+
grid.style.gridAutoRows = "1px"
30+
grid.style.gridAutoFlow = "dense"
31+
grid.style.rowGap = "0px"
32+
33+
for (const child of Array.from(grid.children)) {
34+
if (!(child instanceof HTMLElement)) continue
35+
child.style.alignSelf = "start"
36+
const height = Math.ceil(child.getBoundingClientRect().height)
37+
child.style.gridRowEnd = `span ${Math.max(1, height + verticalGap)}`
38+
}
39+
}
40+
41+
function scheduleGrid(grid: HTMLElement) {
42+
const pending = pendingFrames.get(grid)
43+
if (pending !== undefined) cancelAnimationFrame(pending)
44+
pendingFrames.set(
45+
grid,
46+
requestAnimationFrame(() => {
47+
pendingFrames.delete(grid)
48+
measureGrid(grid)
49+
}),
50+
)
51+
}
52+
53+
const resizeObserver = new ResizeObserver((entries) => {
54+
const grids = new Set<HTMLElement>()
55+
for (const entry of entries) {
56+
const grid = entry.target.parentElement
57+
if (grid instanceof HTMLElement && grid.matches(dashboardSelector)) grids.add(grid)
58+
}
59+
for (const grid of grids) scheduleGrid(grid)
60+
})
61+
62+
function syncDashboardLayout() {
63+
const currentItems = new Set<HTMLElement>()
64+
for (const grid of dashboardGrids()) {
65+
for (const child of Array.from(grid.children)) {
66+
if (!(child instanceof HTMLElement)) continue
67+
currentItems.add(child)
68+
if (!observedItems.has(child)) resizeObserver.observe(child)
69+
}
70+
scheduleGrid(grid)
71+
}
72+
73+
for (const item of observedItems) {
74+
if (currentItems.has(item)) continue
75+
resizeObserver.unobserve(item)
76+
observedItems.delete(item)
77+
}
78+
for (const item of currentItems) observedItems.add(item)
79+
}
80+
81+
const mutationObserver = new MutationObserver(() => syncDashboardLayout())
82+
mutationObserver.observe(document.body, { childList: true, subtree: true })
83+
wideDashboard.addEventListener("change", () => syncDashboardLayout())
84+
queueMicrotask(syncDashboardLayout)

src/layout-fixes.css

Lines changed: 0 additions & 26 deletions
This file was deleted.

src/main.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { StrictMode } from "react"
22
import { createRoot } from "react-dom/client"
33
import { App } from "@/App"
44
import "@/index.css"
5-
import "@/layout-fixes.css"
5+
import "@/dashboard-layout"
66

77
createRoot(document.getElementById("root")!).render(
88
<StrictMode>

0 commit comments

Comments
 (0)