@mieweb/ui 0.7.3-dev.15 → 0.7.3-dev.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/brands/index.cjs +12 -12
- package/dist/brands/index.js +3 -3
- package/dist/{chunk-33TQ25IK.js → chunk-2WLP2ERQ.js} +4 -4
- package/dist/{chunk-33TQ25IK.js.map → chunk-2WLP2ERQ.js.map} +1 -1
- package/dist/{chunk-I2AHT7JM.js → chunk-CKZX6IQN.js} +27 -2
- package/dist/chunk-CKZX6IQN.js.map +1 -0
- package/dist/{chunk-MO65V64I.js → chunk-DSSMQH74.js} +2 -2
- package/dist/{chunk-MO65V64I.js.map → chunk-DSSMQH74.js.map} +1 -1
- package/dist/chunk-F57VRLKP.js +708 -0
- package/dist/chunk-F57VRLKP.js.map +1 -0
- package/dist/{chunk-JU62BLDY.cjs → chunk-GJD4OW7P.cjs} +27 -2
- package/dist/chunk-GJD4OW7P.cjs.map +1 -0
- package/dist/{chunk-JDHIBJ5K.cjs → chunk-RBEQQ4GG.cjs} +2 -2
- package/dist/{chunk-JDHIBJ5K.cjs.map → chunk-RBEQQ4GG.cjs.map} +1 -1
- package/dist/{chunk-35HHCXWC.cjs → chunk-SETNPK4P.cjs} +4 -4
- package/dist/{chunk-35HHCXWC.cjs.map → chunk-SETNPK4P.cjs.map} +1 -1
- package/dist/chunk-YVIH26FG.cjs +736 -0
- package/dist/chunk-YVIH26FG.cjs.map +1 -0
- package/dist/components/CustomizableDashboard/index.cjs +43 -0
- package/dist/components/CustomizableDashboard/index.cjs.map +1 -0
- package/dist/components/CustomizableDashboard/index.d.cts +195 -0
- package/dist/components/CustomizableDashboard/index.d.ts +195 -0
- package/dist/components/CustomizableDashboard/index.js +10 -0
- package/dist/components/CustomizableDashboard/index.js.map +1 -0
- package/dist/esheet.cjs +6 -6
- package/dist/esheet.js +3 -3
- package/dist/index.cjs +196 -167
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +20 -19
- package/dist/index.js.map +1 -1
- package/dist/styles.css +1 -1
- package/dist/tailwind-preset.cjs +4 -4
- package/dist/tailwind-preset.js +1 -1
- package/dist/utils/index.cjs +9 -9
- package/dist/utils/index.js +1 -1
- package/package.json +4 -1
- package/dist/chunk-I2AHT7JM.js.map +0 -1
- package/dist/chunk-JU62BLDY.cjs.map +0 -1
|
@@ -0,0 +1,708 @@
|
|
|
1
|
+
import { Switch } from './chunk-AQ564XO7.js';
|
|
2
|
+
import { Sheet, SheetHeader, SheetTitle, SheetDescription, SheetBody, SheetFooter } from './chunk-LMBXJZLG.js';
|
|
3
|
+
import { Button } from './chunk-D2GID3YK.js';
|
|
4
|
+
import { cn } from './chunk-TQONQSJO.js';
|
|
5
|
+
import * as React2 from 'react';
|
|
6
|
+
import { createPortal } from 'react-dom';
|
|
7
|
+
import { useSensors, useSensor, PointerSensor, KeyboardSensor, DndContext, pointerWithin, closestCenter, rectIntersection, useDroppable } from '@dnd-kit/core';
|
|
8
|
+
import { sortableKeyboardCoordinates, arrayMove, SortableContext, verticalListSortingStrategy, useSortable } from '@dnd-kit/sortable';
|
|
9
|
+
import { CSS } from '@dnd-kit/utilities';
|
|
10
|
+
import { SlidersHorizontal, Columns3, Columns2, Square, RotateCcw, GripVertical } from 'lucide-react';
|
|
11
|
+
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
12
|
+
|
|
13
|
+
function DashboardCustomizePanel({
|
|
14
|
+
open,
|
|
15
|
+
onOpenChange,
|
|
16
|
+
widgets,
|
|
17
|
+
hiddenIds,
|
|
18
|
+
onToggleWidget,
|
|
19
|
+
onReset,
|
|
20
|
+
title = "Customize dashboard",
|
|
21
|
+
description = "Choose which widgets appear on this dashboard. Drag a widget by its handle to rearrange it."
|
|
22
|
+
}) {
|
|
23
|
+
const hidden = React2.useMemo(() => new Set(hiddenIds), [hiddenIds]);
|
|
24
|
+
const groups = React2.useMemo(() => {
|
|
25
|
+
const map = /* @__PURE__ */ new Map();
|
|
26
|
+
for (const w of widgets) {
|
|
27
|
+
const key = w.category ?? "";
|
|
28
|
+
const list = map.get(key);
|
|
29
|
+
if (list) list.push(w);
|
|
30
|
+
else map.set(key, [w]);
|
|
31
|
+
}
|
|
32
|
+
return Array.from(map.entries());
|
|
33
|
+
}, [widgets]);
|
|
34
|
+
const visibleCount = widgets.length - hidden.size;
|
|
35
|
+
return /* @__PURE__ */ jsxs(
|
|
36
|
+
Sheet,
|
|
37
|
+
{
|
|
38
|
+
open,
|
|
39
|
+
onOpenChange,
|
|
40
|
+
side: "end",
|
|
41
|
+
"aria-label": title,
|
|
42
|
+
children: [
|
|
43
|
+
/* @__PURE__ */ jsx(SheetHeader, { children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1.5", children: [
|
|
44
|
+
/* @__PURE__ */ jsx(SheetTitle, { children: title }),
|
|
45
|
+
/* @__PURE__ */ jsx(SheetDescription, { children: description })
|
|
46
|
+
] }) }),
|
|
47
|
+
/* @__PURE__ */ jsxs(SheetBody, { className: "flex flex-col gap-5", children: [
|
|
48
|
+
/* @__PURE__ */ jsxs("p", { className: "text-muted-foreground text-xs font-medium", children: [
|
|
49
|
+
visibleCount,
|
|
50
|
+
" of ",
|
|
51
|
+
widgets.length,
|
|
52
|
+
" widgets shown"
|
|
53
|
+
] }),
|
|
54
|
+
groups.map(([category, items]) => /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1", children: [
|
|
55
|
+
category && /* @__PURE__ */ jsx("h3", { className: "text-muted-foreground pb-1 text-xs font-semibold tracking-wide uppercase", children: category }),
|
|
56
|
+
/* @__PURE__ */ jsx("ul", { className: "flex list-none flex-col gap-1 p-0", children: items.map((widget) => {
|
|
57
|
+
const isVisible = !hidden.has(widget.id);
|
|
58
|
+
return /* @__PURE__ */ jsxs(
|
|
59
|
+
"li",
|
|
60
|
+
{
|
|
61
|
+
className: cn(
|
|
62
|
+
"border-border flex items-center gap-3 rounded-lg border p-3 transition-colors",
|
|
63
|
+
isVisible ? "bg-card" : "bg-muted/50"
|
|
64
|
+
),
|
|
65
|
+
children: [
|
|
66
|
+
widget.icon && /* @__PURE__ */ jsx(
|
|
67
|
+
"span",
|
|
68
|
+
{
|
|
69
|
+
className: cn(
|
|
70
|
+
"shrink-0",
|
|
71
|
+
isVisible ? "text-primary-600 dark:text-primary-400" : "text-muted-foreground"
|
|
72
|
+
),
|
|
73
|
+
children: widget.icon
|
|
74
|
+
}
|
|
75
|
+
),
|
|
76
|
+
/* @__PURE__ */ jsxs("span", { className: "flex min-w-0 flex-1 flex-col", children: [
|
|
77
|
+
/* @__PURE__ */ jsx(
|
|
78
|
+
"span",
|
|
79
|
+
{
|
|
80
|
+
className: cn(
|
|
81
|
+
"truncate text-sm font-medium",
|
|
82
|
+
isVisible ? "text-foreground" : "text-muted-foreground"
|
|
83
|
+
),
|
|
84
|
+
children: widget.title
|
|
85
|
+
}
|
|
86
|
+
),
|
|
87
|
+
widget.description && /* @__PURE__ */ jsx("span", { className: "text-muted-foreground truncate text-xs", children: widget.description })
|
|
88
|
+
] }),
|
|
89
|
+
/* @__PURE__ */ jsx(
|
|
90
|
+
Switch,
|
|
91
|
+
{
|
|
92
|
+
checked: isVisible,
|
|
93
|
+
onCheckedChange: (checked) => onToggleWidget(widget.id, checked),
|
|
94
|
+
"aria-label": `Show ${widget.title}`
|
|
95
|
+
}
|
|
96
|
+
)
|
|
97
|
+
]
|
|
98
|
+
},
|
|
99
|
+
widget.id
|
|
100
|
+
);
|
|
101
|
+
}) })
|
|
102
|
+
] }, category || "general"))
|
|
103
|
+
] }),
|
|
104
|
+
onReset && /* @__PURE__ */ jsx(SheetFooter, { children: /* @__PURE__ */ jsx(
|
|
105
|
+
Button,
|
|
106
|
+
{
|
|
107
|
+
variant: "outline",
|
|
108
|
+
size: "sm",
|
|
109
|
+
onClick: onReset,
|
|
110
|
+
leftIcon: /* @__PURE__ */ jsx(RotateCcw, { "aria-hidden": "true", className: "h-4 w-4" }),
|
|
111
|
+
children: "Reset layout"
|
|
112
|
+
}
|
|
113
|
+
) })
|
|
114
|
+
]
|
|
115
|
+
}
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
var COL_IDS = ["column-0", "column-1", "column-2"];
|
|
119
|
+
var DEFAULT_HANDLE_SELECTOR = '[data-slot="dashboard-widget-header"]';
|
|
120
|
+
var HEADER_LAYOUT_CLASSES = [
|
|
121
|
+
"flex",
|
|
122
|
+
"flex-row",
|
|
123
|
+
"flex-nowrap",
|
|
124
|
+
"items-center",
|
|
125
|
+
"[&>:nth-child(2)]:ms-auto"
|
|
126
|
+
];
|
|
127
|
+
function orderStorageKey(base) {
|
|
128
|
+
return `${base}-portlet-order`;
|
|
129
|
+
}
|
|
130
|
+
function layoutStorageKey(base) {
|
|
131
|
+
return `${base}-dashboard-layout`;
|
|
132
|
+
}
|
|
133
|
+
function hiddenStorageKey(base) {
|
|
134
|
+
return `${base}-dashboard-hidden`;
|
|
135
|
+
}
|
|
136
|
+
function loadHidden(base) {
|
|
137
|
+
if (!base) return null;
|
|
138
|
+
try {
|
|
139
|
+
const raw = localStorage.getItem(hiddenStorageKey(base));
|
|
140
|
+
const parsed = raw ? JSON.parse(raw) : null;
|
|
141
|
+
return Array.isArray(parsed) ? parsed.filter((v) => typeof v === "string") : null;
|
|
142
|
+
} catch {
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
function saveHidden(base, ids) {
|
|
147
|
+
if (!base) return;
|
|
148
|
+
try {
|
|
149
|
+
localStorage.setItem(hiddenStorageKey(base), JSON.stringify(ids));
|
|
150
|
+
} catch {
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
function loadLayout(base) {
|
|
154
|
+
if (!base) return null;
|
|
155
|
+
try {
|
|
156
|
+
const raw = localStorage.getItem(layoutStorageKey(base));
|
|
157
|
+
const val = raw ? Number(raw) : null;
|
|
158
|
+
return val === 1 || val === 2 || val === 3 ? val : null;
|
|
159
|
+
} catch {
|
|
160
|
+
return null;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
function saveLayout(base, mode) {
|
|
164
|
+
if (!base) return;
|
|
165
|
+
try {
|
|
166
|
+
localStorage.setItem(layoutStorageKey(base), String(mode));
|
|
167
|
+
} catch {
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
function sanitizeSlot(value) {
|
|
171
|
+
return Array.isArray(value) ? value.filter((v) => typeof v === "string") : [];
|
|
172
|
+
}
|
|
173
|
+
function loadOrder(base) {
|
|
174
|
+
if (!base) return null;
|
|
175
|
+
try {
|
|
176
|
+
const raw = localStorage.getItem(orderStorageKey(base));
|
|
177
|
+
if (!raw) return null;
|
|
178
|
+
const parsed = JSON.parse(raw);
|
|
179
|
+
return [
|
|
180
|
+
sanitizeSlot(parsed[0]),
|
|
181
|
+
sanitizeSlot(parsed[1]),
|
|
182
|
+
sanitizeSlot(parsed[2])
|
|
183
|
+
];
|
|
184
|
+
} catch {
|
|
185
|
+
return null;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
function saveOrder(base, order) {
|
|
189
|
+
if (!base) return;
|
|
190
|
+
try {
|
|
191
|
+
const record = {};
|
|
192
|
+
order.forEach((ids, i) => {
|
|
193
|
+
record[i] = ids;
|
|
194
|
+
});
|
|
195
|
+
localStorage.setItem(orderStorageKey(base), JSON.stringify(record));
|
|
196
|
+
} catch {
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
function mergeColumnOrder(columns, saved) {
|
|
200
|
+
const allIds = new Set(
|
|
201
|
+
[...columns[0], ...columns[1], ...columns[2]].map((p) => p.id)
|
|
202
|
+
);
|
|
203
|
+
const result = [[], [], []];
|
|
204
|
+
const placed = /* @__PURE__ */ new Set();
|
|
205
|
+
for (let i = 0; i < 3; i++) {
|
|
206
|
+
for (const id of saved?.[i] ?? []) {
|
|
207
|
+
if (allIds.has(id) && !placed.has(id)) {
|
|
208
|
+
result[i].push(id);
|
|
209
|
+
placed.add(id);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
for (let i = 0; i < 3; i++) {
|
|
214
|
+
for (const item of columns[i]) {
|
|
215
|
+
if (!placed.has(item.id)) {
|
|
216
|
+
result[i].push(item.id);
|
|
217
|
+
placed.add(item.id);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
return result;
|
|
222
|
+
}
|
|
223
|
+
function buildItemMap(columns) {
|
|
224
|
+
const map = /* @__PURE__ */ new Map();
|
|
225
|
+
for (const col of columns) {
|
|
226
|
+
for (const item of col) map.set(item.id, item);
|
|
227
|
+
}
|
|
228
|
+
return map;
|
|
229
|
+
}
|
|
230
|
+
function findColumn(colOrder, itemId) {
|
|
231
|
+
return colOrder.findIndex((ids) => ids.includes(itemId));
|
|
232
|
+
}
|
|
233
|
+
function moveAcrossColumns(order, activeId, overId) {
|
|
234
|
+
const colIdx = COL_IDS.indexOf(overId);
|
|
235
|
+
const sourceCol = findColumn(order, activeId);
|
|
236
|
+
if (sourceCol === -1) return order;
|
|
237
|
+
const targetCol = colIdx !== -1 ? colIdx : findColumn(order, overId);
|
|
238
|
+
if (targetCol === -1 || sourceCol === targetCol) return order;
|
|
239
|
+
const next = order.map((col) => [...col]);
|
|
240
|
+
next[sourceCol] = next[sourceCol].filter((id) => id !== activeId);
|
|
241
|
+
const overIdx = colIdx !== -1 ? -1 : next[targetCol].indexOf(overId);
|
|
242
|
+
if (overIdx === -1) {
|
|
243
|
+
next[targetCol].push(activeId);
|
|
244
|
+
} else {
|
|
245
|
+
next[targetCol].splice(overIdx, 0, activeId);
|
|
246
|
+
}
|
|
247
|
+
return next;
|
|
248
|
+
}
|
|
249
|
+
function reorderOnDrop(order, activeId, overId, crossedColumns) {
|
|
250
|
+
const isColumnTarget = COL_IDS.indexOf(overId) !== -1;
|
|
251
|
+
if (isColumnTarget || overId === activeId) return order;
|
|
252
|
+
const col = findColumn(order, activeId);
|
|
253
|
+
if (col === -1 || findColumn(order, overId) !== col) return order;
|
|
254
|
+
const oldIdx = order[col].indexOf(activeId);
|
|
255
|
+
const newIdx = order[col].indexOf(overId);
|
|
256
|
+
if (oldIdx === -1 || newIdx === -1) return order;
|
|
257
|
+
if (crossedColumns && newIdx === oldIdx + 1) return order;
|
|
258
|
+
const next = order.map((c) => [...c]);
|
|
259
|
+
next[col] = arrayMove(next[col], oldIdx, newIdx);
|
|
260
|
+
return next;
|
|
261
|
+
}
|
|
262
|
+
function consolidateColumns(order, mode) {
|
|
263
|
+
if (mode >= 3 || requiredColumns(order) <= mode) return order;
|
|
264
|
+
if (mode === 1) return [[...order[0], ...order[1], ...order[2]], [], []];
|
|
265
|
+
return [[...order[0]], [...order[1], ...order[2]], []];
|
|
266
|
+
}
|
|
267
|
+
function requiredColumns(order) {
|
|
268
|
+
return Math.max(
|
|
269
|
+
1,
|
|
270
|
+
...order.map((ids, i) => ids.length > 0 ? i + 1 : 0)
|
|
271
|
+
);
|
|
272
|
+
}
|
|
273
|
+
var collisionDetection = (args) => {
|
|
274
|
+
const pw = pointerWithin(args);
|
|
275
|
+
if (pw.length > 0) return pw;
|
|
276
|
+
const cc = closestCenter(args);
|
|
277
|
+
if (cc.length > 0) return cc;
|
|
278
|
+
return rectIntersection(args);
|
|
279
|
+
};
|
|
280
|
+
function SortablePortlet({
|
|
281
|
+
id,
|
|
282
|
+
handleSelector,
|
|
283
|
+
children
|
|
284
|
+
}) {
|
|
285
|
+
const {
|
|
286
|
+
attributes,
|
|
287
|
+
listeners,
|
|
288
|
+
setNodeRef,
|
|
289
|
+
transform,
|
|
290
|
+
transition,
|
|
291
|
+
isDragging
|
|
292
|
+
} = useSortable({ id });
|
|
293
|
+
const wrapperRef = React2.useRef(null);
|
|
294
|
+
const [headerEl, setHeaderEl] = React2.useState(null);
|
|
295
|
+
const [searched, setSearched] = React2.useState(false);
|
|
296
|
+
const style = {
|
|
297
|
+
transform: CSS.Transform.toString(transform),
|
|
298
|
+
transition,
|
|
299
|
+
opacity: isDragging ? 0.4 : 1
|
|
300
|
+
};
|
|
301
|
+
React2.useEffect(() => {
|
|
302
|
+
const container = wrapperRef.current;
|
|
303
|
+
if (!container) return;
|
|
304
|
+
let rafId = 0;
|
|
305
|
+
const findHeader = () => {
|
|
306
|
+
const header = container.querySelector(handleSelector);
|
|
307
|
+
if (header instanceof HTMLElement) {
|
|
308
|
+
header.classList.add(...HEADER_LAYOUT_CLASSES);
|
|
309
|
+
setHeaderEl(header);
|
|
310
|
+
observer.disconnect();
|
|
311
|
+
} else {
|
|
312
|
+
setHeaderEl(null);
|
|
313
|
+
}
|
|
314
|
+
setSearched(true);
|
|
315
|
+
};
|
|
316
|
+
const observer = new MutationObserver(() => {
|
|
317
|
+
cancelAnimationFrame(rafId);
|
|
318
|
+
rafId = requestAnimationFrame(findHeader);
|
|
319
|
+
});
|
|
320
|
+
findHeader();
|
|
321
|
+
observer.observe(container, { childList: true, subtree: true });
|
|
322
|
+
return () => {
|
|
323
|
+
cancelAnimationFrame(rafId);
|
|
324
|
+
observer.disconnect();
|
|
325
|
+
};
|
|
326
|
+
}, [handleSelector]);
|
|
327
|
+
const handle = /* @__PURE__ */ jsx(
|
|
328
|
+
"button",
|
|
329
|
+
{
|
|
330
|
+
type: "button",
|
|
331
|
+
className: cn(
|
|
332
|
+
"inline-flex h-7 w-6 shrink-0 cursor-grab touch-none items-center justify-center rounded border-none bg-transparent",
|
|
333
|
+
"text-muted-foreground hover:bg-muted hover:text-foreground active:cursor-grabbing",
|
|
334
|
+
"opacity-40 transition-opacity group-hover/portlet:opacity-100 focus-visible:opacity-100",
|
|
335
|
+
"[@media(hover:none)]:opacity-100",
|
|
336
|
+
!headerEl && "bg-card/80 absolute end-2 top-2 z-10 shadow-sm"
|
|
337
|
+
),
|
|
338
|
+
"aria-label": "Drag to reorder",
|
|
339
|
+
...attributes,
|
|
340
|
+
...listeners,
|
|
341
|
+
children: /* @__PURE__ */ jsx(GripVertical, { "aria-hidden": "true", className: "h-4 w-4" })
|
|
342
|
+
}
|
|
343
|
+
);
|
|
344
|
+
return /* @__PURE__ */ jsxs(
|
|
345
|
+
"div",
|
|
346
|
+
{
|
|
347
|
+
ref: (node) => {
|
|
348
|
+
setNodeRef(node);
|
|
349
|
+
wrapperRef.current = node;
|
|
350
|
+
},
|
|
351
|
+
style,
|
|
352
|
+
className: "group/portlet relative min-w-0",
|
|
353
|
+
children: [
|
|
354
|
+
/* @__PURE__ */ jsx("div", { className: "w-full min-w-0", children }),
|
|
355
|
+
headerEl ? createPortal(handle, headerEl) : searched ? handle : null
|
|
356
|
+
]
|
|
357
|
+
}
|
|
358
|
+
);
|
|
359
|
+
}
|
|
360
|
+
function DroppableColumn({
|
|
361
|
+
id,
|
|
362
|
+
items,
|
|
363
|
+
columnIndex,
|
|
364
|
+
handleSelector,
|
|
365
|
+
flatten,
|
|
366
|
+
dragActive
|
|
367
|
+
}) {
|
|
368
|
+
const { setNodeRef, isOver } = useDroppable({ id });
|
|
369
|
+
const ids = items.map((p) => p.id);
|
|
370
|
+
return /* @__PURE__ */ jsx(SortableContext, { items: ids, strategy: verticalListSortingStrategy, children: /* @__PURE__ */ jsxs(
|
|
371
|
+
"div",
|
|
372
|
+
{
|
|
373
|
+
ref: setNodeRef,
|
|
374
|
+
className: cn(
|
|
375
|
+
"flex min-h-16 min-w-0 flex-col gap-3",
|
|
376
|
+
flatten && "max-lg:contents"
|
|
377
|
+
),
|
|
378
|
+
role: "group",
|
|
379
|
+
"aria-label": `Dashboard column ${columnIndex + 1}`,
|
|
380
|
+
children: [
|
|
381
|
+
items.map((item) => /* @__PURE__ */ jsx(
|
|
382
|
+
SortablePortlet,
|
|
383
|
+
{
|
|
384
|
+
id: item.id,
|
|
385
|
+
handleSelector,
|
|
386
|
+
children: item.node
|
|
387
|
+
},
|
|
388
|
+
item.id
|
|
389
|
+
)),
|
|
390
|
+
items.length === 0 && dragActive && /* @__PURE__ */ jsx(
|
|
391
|
+
"div",
|
|
392
|
+
{
|
|
393
|
+
"aria-hidden": "true",
|
|
394
|
+
className: cn(
|
|
395
|
+
"border-border text-muted-foreground flex min-h-32 items-center justify-center rounded-lg border-2 border-dashed text-sm font-medium transition-colors",
|
|
396
|
+
isOver && "border-primary-500 bg-primary-500/10 text-primary-600"
|
|
397
|
+
),
|
|
398
|
+
children: "Drop here"
|
|
399
|
+
}
|
|
400
|
+
)
|
|
401
|
+
]
|
|
402
|
+
}
|
|
403
|
+
) });
|
|
404
|
+
}
|
|
405
|
+
var CustomizableDashboard = React2.forwardRef(function CustomizableDashboard2({
|
|
406
|
+
columns,
|
|
407
|
+
title,
|
|
408
|
+
storageKey,
|
|
409
|
+
ariaLabel,
|
|
410
|
+
toolbarSlot,
|
|
411
|
+
defaultOrder,
|
|
412
|
+
order: controlledOrder,
|
|
413
|
+
onOrderChange,
|
|
414
|
+
defaultLayout = 3,
|
|
415
|
+
layout: controlledLayout,
|
|
416
|
+
onLayoutChange,
|
|
417
|
+
dragHandleSelector = DEFAULT_HANDLE_SELECTOR,
|
|
418
|
+
widgets,
|
|
419
|
+
defaultHiddenIds,
|
|
420
|
+
hiddenIds: controlledHidden,
|
|
421
|
+
onHiddenChange,
|
|
422
|
+
customizeLabel = "Customize",
|
|
423
|
+
className
|
|
424
|
+
}, ref) {
|
|
425
|
+
const itemMap = React2.useMemo(() => buildItemMap(columns), [columns]);
|
|
426
|
+
const [internalLayout, setInternalLayout] = React2.useState(defaultLayout);
|
|
427
|
+
const layoutMode = controlledLayout ?? internalLayout;
|
|
428
|
+
const [colOrder, setColOrder] = React2.useState(
|
|
429
|
+
() => consolidateColumns(
|
|
430
|
+
mergeColumnOrder(columns, controlledOrder ?? defaultOrder ?? null),
|
|
431
|
+
controlledLayout ?? defaultLayout
|
|
432
|
+
)
|
|
433
|
+
);
|
|
434
|
+
const [activeId, setActiveId] = React2.useState(null);
|
|
435
|
+
const [internalHidden, setInternalHidden] = React2.useState(
|
|
436
|
+
() => defaultHiddenIds ?? []
|
|
437
|
+
);
|
|
438
|
+
const hiddenList = controlledHidden ?? internalHidden;
|
|
439
|
+
const hiddenSet = React2.useMemo(() => new Set(hiddenList), [hiddenList]);
|
|
440
|
+
const [panelOpen, setPanelOpen] = React2.useState(false);
|
|
441
|
+
const prevColOrder = React2.useRef(colOrder);
|
|
442
|
+
const layoutModeRef = React2.useRef(layoutMode);
|
|
443
|
+
layoutModeRef.current = layoutMode;
|
|
444
|
+
const colOrderRef = React2.useRef(colOrder);
|
|
445
|
+
colOrderRef.current = colOrder;
|
|
446
|
+
const columnsRef = React2.useRef(columns);
|
|
447
|
+
columnsRef.current = columns;
|
|
448
|
+
const draggingRef = React2.useRef(false);
|
|
449
|
+
React2.useEffect(() => {
|
|
450
|
+
if (!storageKey) return;
|
|
451
|
+
if (controlledLayout === void 0) {
|
|
452
|
+
const savedLayout = loadLayout(storageKey);
|
|
453
|
+
if (savedLayout !== null) setInternalLayout(savedLayout);
|
|
454
|
+
}
|
|
455
|
+
if (controlledOrder === void 0) {
|
|
456
|
+
const savedOrder = loadOrder(storageKey);
|
|
457
|
+
if (savedOrder) {
|
|
458
|
+
setColOrder(mergeColumnOrder(columnsRef.current, savedOrder));
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
if (controlledHidden === void 0) {
|
|
462
|
+
const savedHidden = loadHidden(storageKey);
|
|
463
|
+
if (savedHidden) setInternalHidden(savedHidden);
|
|
464
|
+
}
|
|
465
|
+
}, [storageKey]);
|
|
466
|
+
React2.useEffect(() => {
|
|
467
|
+
if (draggingRef.current) return;
|
|
468
|
+
setColOrder((prev) => {
|
|
469
|
+
const next = consolidateColumns(
|
|
470
|
+
mergeColumnOrder(columns, controlledOrder ?? prev),
|
|
471
|
+
layoutMode
|
|
472
|
+
);
|
|
473
|
+
return JSON.stringify(next) === JSON.stringify(prev) ? prev : next;
|
|
474
|
+
});
|
|
475
|
+
}, [columns, controlledOrder, layoutMode]);
|
|
476
|
+
const setLayout = React2.useCallback(
|
|
477
|
+
(mode) => {
|
|
478
|
+
if (controlledLayout === void 0) setInternalLayout(mode);
|
|
479
|
+
saveLayout(storageKey, mode);
|
|
480
|
+
onLayoutChange?.(mode);
|
|
481
|
+
},
|
|
482
|
+
[controlledLayout, storageKey, onLayoutChange]
|
|
483
|
+
);
|
|
484
|
+
const commitOrder = React2.useCallback(
|
|
485
|
+
(next) => {
|
|
486
|
+
saveOrder(storageKey, next);
|
|
487
|
+
onOrderChange?.(next);
|
|
488
|
+
const occupied = requiredColumns(next);
|
|
489
|
+
if (occupied < layoutModeRef.current) {
|
|
490
|
+
setLayout(occupied);
|
|
491
|
+
}
|
|
492
|
+
},
|
|
493
|
+
[storageKey, onOrderChange, setLayout]
|
|
494
|
+
);
|
|
495
|
+
const handleLayoutChange = React2.useCallback(
|
|
496
|
+
(mode) => {
|
|
497
|
+
setLayout(mode);
|
|
498
|
+
const next = consolidateColumns(colOrderRef.current, mode);
|
|
499
|
+
if (next !== colOrderRef.current) {
|
|
500
|
+
setColOrder(next);
|
|
501
|
+
saveOrder(storageKey, next);
|
|
502
|
+
onOrderChange?.(next);
|
|
503
|
+
}
|
|
504
|
+
},
|
|
505
|
+
[setLayout, storageKey, onOrderChange]
|
|
506
|
+
);
|
|
507
|
+
const sensors = useSensors(
|
|
508
|
+
useSensor(PointerSensor, { activationConstraint: { distance: 5 } }),
|
|
509
|
+
useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates })
|
|
510
|
+
);
|
|
511
|
+
const resolve = (ids) => ids.filter((id) => !hiddenSet.has(id)).map((id) => itemMap.get(id)).filter(Boolean);
|
|
512
|
+
const handleDragStart = React2.useCallback(
|
|
513
|
+
(event) => {
|
|
514
|
+
prevColOrder.current = colOrder;
|
|
515
|
+
draggingRef.current = true;
|
|
516
|
+
setActiveId(String(event.active.id));
|
|
517
|
+
},
|
|
518
|
+
[colOrder]
|
|
519
|
+
);
|
|
520
|
+
const handleDragOver = React2.useCallback((event) => {
|
|
521
|
+
const { active, over } = event;
|
|
522
|
+
if (!over) return;
|
|
523
|
+
const next = moveAcrossColumns(
|
|
524
|
+
colOrderRef.current,
|
|
525
|
+
String(active.id),
|
|
526
|
+
String(over.id)
|
|
527
|
+
);
|
|
528
|
+
if (next !== colOrderRef.current) setColOrder(next);
|
|
529
|
+
}, []);
|
|
530
|
+
const handleDragEnd = React2.useCallback(
|
|
531
|
+
(event) => {
|
|
532
|
+
const { active, over } = event;
|
|
533
|
+
draggingRef.current = false;
|
|
534
|
+
setActiveId(null);
|
|
535
|
+
if (!over) {
|
|
536
|
+
setColOrder(prevColOrder.current);
|
|
537
|
+
return;
|
|
538
|
+
}
|
|
539
|
+
const activeItemId = String(active.id);
|
|
540
|
+
const crossedColumns = findColumn(prevColOrder.current, activeItemId) !== findColumn(colOrderRef.current, activeItemId);
|
|
541
|
+
const next = reorderOnDrop(
|
|
542
|
+
colOrderRef.current,
|
|
543
|
+
activeItemId,
|
|
544
|
+
String(over.id),
|
|
545
|
+
crossedColumns
|
|
546
|
+
);
|
|
547
|
+
if (next !== colOrderRef.current) setColOrder(next);
|
|
548
|
+
if (JSON.stringify(next) !== JSON.stringify(prevColOrder.current)) {
|
|
549
|
+
commitOrder(next);
|
|
550
|
+
}
|
|
551
|
+
},
|
|
552
|
+
[commitOrder]
|
|
553
|
+
);
|
|
554
|
+
const handleDragCancel = React2.useCallback(() => {
|
|
555
|
+
draggingRef.current = false;
|
|
556
|
+
setActiveId(null);
|
|
557
|
+
setColOrder(prevColOrder.current);
|
|
558
|
+
}, []);
|
|
559
|
+
const setHidden = React2.useCallback(
|
|
560
|
+
(ids) => {
|
|
561
|
+
if (controlledHidden === void 0) setInternalHidden(ids);
|
|
562
|
+
saveHidden(storageKey, ids);
|
|
563
|
+
onHiddenChange?.(ids);
|
|
564
|
+
},
|
|
565
|
+
[controlledHidden, storageKey, onHiddenChange]
|
|
566
|
+
);
|
|
567
|
+
const handleToggleWidget = React2.useCallback(
|
|
568
|
+
(id, visible) => {
|
|
569
|
+
setHidden(
|
|
570
|
+
visible ? hiddenList.filter((h) => h !== id) : [...hiddenList, id]
|
|
571
|
+
);
|
|
572
|
+
},
|
|
573
|
+
[hiddenList, setHidden]
|
|
574
|
+
);
|
|
575
|
+
const handleReset = React2.useCallback(() => {
|
|
576
|
+
if (storageKey) {
|
|
577
|
+
try {
|
|
578
|
+
localStorage.removeItem(orderStorageKey(storageKey));
|
|
579
|
+
localStorage.removeItem(layoutStorageKey(storageKey));
|
|
580
|
+
localStorage.removeItem(hiddenStorageKey(storageKey));
|
|
581
|
+
} catch {
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
const next = mergeColumnOrder(columns, defaultOrder ?? null);
|
|
585
|
+
setColOrder(next);
|
|
586
|
+
onOrderChange?.(next);
|
|
587
|
+
if (controlledLayout === void 0) setInternalLayout(defaultLayout);
|
|
588
|
+
onLayoutChange?.(defaultLayout);
|
|
589
|
+
const hidden = defaultHiddenIds ?? [];
|
|
590
|
+
if (controlledHidden === void 0) setInternalHidden(hidden);
|
|
591
|
+
onHiddenChange?.(hidden);
|
|
592
|
+
}, [
|
|
593
|
+
storageKey,
|
|
594
|
+
columns,
|
|
595
|
+
defaultOrder,
|
|
596
|
+
onOrderChange,
|
|
597
|
+
controlledLayout,
|
|
598
|
+
defaultLayout,
|
|
599
|
+
onLayoutChange,
|
|
600
|
+
defaultHiddenIds,
|
|
601
|
+
controlledHidden,
|
|
602
|
+
onHiddenChange
|
|
603
|
+
]);
|
|
604
|
+
const cols = [
|
|
605
|
+
resolve(colOrder[0]),
|
|
606
|
+
resolve(colOrder[1]),
|
|
607
|
+
resolve(colOrder[2])
|
|
608
|
+
];
|
|
609
|
+
const visibleCols = layoutMode === 1 ? [cols[0]] : layoutMode === 2 ? [cols[0], cols[1]] : cols;
|
|
610
|
+
const toolbar = /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-end gap-3 pb-2", children: [
|
|
611
|
+
title && /* @__PURE__ */ jsx("h2", { className: "text-foreground me-auto text-lg font-semibold", children: title }),
|
|
612
|
+
widgets && widgets.length > 0 && /* @__PURE__ */ jsx(
|
|
613
|
+
Button,
|
|
614
|
+
{
|
|
615
|
+
variant: "outline",
|
|
616
|
+
size: "sm",
|
|
617
|
+
onClick: () => setPanelOpen(true),
|
|
618
|
+
"aria-haspopup": "dialog",
|
|
619
|
+
leftIcon: /* @__PURE__ */ jsx(SlidersHorizontal, { "aria-hidden": "true", className: "h-4 w-4" }),
|
|
620
|
+
children: customizeLabel
|
|
621
|
+
}
|
|
622
|
+
),
|
|
623
|
+
/* @__PURE__ */ jsx(
|
|
624
|
+
"div",
|
|
625
|
+
{
|
|
626
|
+
className: "border-border bg-muted flex gap-0.5 rounded-md border p-0.5 max-md:hidden",
|
|
627
|
+
role: "radiogroup",
|
|
628
|
+
"aria-label": "Column layout",
|
|
629
|
+
children: [3, 2, 1].map((mode) => {
|
|
630
|
+
const Icon = mode === 3 ? Columns3 : mode === 2 ? Columns2 : Square;
|
|
631
|
+
return /* @__PURE__ */ jsx(
|
|
632
|
+
"button",
|
|
633
|
+
{
|
|
634
|
+
type: "button",
|
|
635
|
+
className: cn(
|
|
636
|
+
"flex h-7 w-7 items-center justify-center rounded transition-colors",
|
|
637
|
+
layoutMode === mode ? "bg-background text-foreground shadow-sm" : "text-muted-foreground hover:bg-background hover:text-foreground"
|
|
638
|
+
),
|
|
639
|
+
onClick: () => handleLayoutChange(mode),
|
|
640
|
+
role: "radio",
|
|
641
|
+
"aria-checked": layoutMode === mode,
|
|
642
|
+
"aria-label": `${mode} column layout`,
|
|
643
|
+
title: `${mode} column${mode > 1 ? "s" : ""}`,
|
|
644
|
+
children: /* @__PURE__ */ jsx(Icon, { "aria-hidden": "true", className: "h-4 w-4" })
|
|
645
|
+
},
|
|
646
|
+
mode
|
|
647
|
+
);
|
|
648
|
+
})
|
|
649
|
+
}
|
|
650
|
+
)
|
|
651
|
+
] });
|
|
652
|
+
return /* @__PURE__ */ jsxs(
|
|
653
|
+
DndContext,
|
|
654
|
+
{
|
|
655
|
+
sensors,
|
|
656
|
+
collisionDetection,
|
|
657
|
+
onDragStart: handleDragStart,
|
|
658
|
+
onDragOver: handleDragOver,
|
|
659
|
+
onDragEnd: handleDragEnd,
|
|
660
|
+
onDragCancel: handleDragCancel,
|
|
661
|
+
children: [
|
|
662
|
+
toolbarSlot ? createPortal(toolbar, toolbarSlot) : toolbar,
|
|
663
|
+
/* @__PURE__ */ jsx(
|
|
664
|
+
"div",
|
|
665
|
+
{
|
|
666
|
+
ref,
|
|
667
|
+
className: cn(
|
|
668
|
+
"grid items-start gap-3",
|
|
669
|
+
layoutMode === 3 && "grid-cols-1 md:grid-cols-2 lg:grid-cols-3",
|
|
670
|
+
layoutMode === 2 && "grid-cols-1 md:grid-cols-2",
|
|
671
|
+
layoutMode === 1 && "grid-cols-1",
|
|
672
|
+
className
|
|
673
|
+
),
|
|
674
|
+
role: "region",
|
|
675
|
+
"aria-label": ariaLabel ?? "Dashboard",
|
|
676
|
+
children: visibleCols.map((col, i) => /* @__PURE__ */ jsx(
|
|
677
|
+
DroppableColumn,
|
|
678
|
+
{
|
|
679
|
+
id: COL_IDS[i],
|
|
680
|
+
items: col,
|
|
681
|
+
columnIndex: i,
|
|
682
|
+
handleSelector: dragHandleSelector,
|
|
683
|
+
flatten: layoutMode === 3,
|
|
684
|
+
dragActive: activeId !== null
|
|
685
|
+
},
|
|
686
|
+
COL_IDS[i]
|
|
687
|
+
))
|
|
688
|
+
}
|
|
689
|
+
),
|
|
690
|
+
widgets && widgets.length > 0 && /* @__PURE__ */ jsx(
|
|
691
|
+
DashboardCustomizePanel,
|
|
692
|
+
{
|
|
693
|
+
open: panelOpen,
|
|
694
|
+
onOpenChange: setPanelOpen,
|
|
695
|
+
widgets,
|
|
696
|
+
hiddenIds: hiddenList,
|
|
697
|
+
onToggleWidget: handleToggleWidget,
|
|
698
|
+
onReset: handleReset
|
|
699
|
+
}
|
|
700
|
+
)
|
|
701
|
+
]
|
|
702
|
+
}
|
|
703
|
+
);
|
|
704
|
+
});
|
|
705
|
+
|
|
706
|
+
export { CustomizableDashboard, DashboardCustomizePanel, consolidateColumns, mergeColumnOrder, moveAcrossColumns, reorderOnDrop, requiredColumns };
|
|
707
|
+
//# sourceMappingURL=chunk-F57VRLKP.js.map
|
|
708
|
+
//# sourceMappingURL=chunk-F57VRLKP.js.map
|