@marktiderman/genesis-ui 1.2.0 → 1.3.0
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/{chunk-DGRHPJ3M.js → chunk-44TULYGG.js} +3 -3
- package/dist/{chunk-DGRHPJ3M.js.map → chunk-44TULYGG.js.map} +1 -1
- package/dist/{chunk-AI6CCNWK.js → chunk-6NPEMLBA.js} +2 -2
- package/dist/chunk-6NPEMLBA.js.map +1 -0
- package/dist/{chunk-UKSBLFEK.js → chunk-6VVVGYKS.js} +4 -4
- package/dist/{chunk-UKSBLFEK.js.map → chunk-6VVVGYKS.js.map} +1 -1
- package/dist/chunk-A2OXCNNY.js +65 -0
- package/dist/chunk-A2OXCNNY.js.map +1 -0
- package/dist/{chunk-O4B5ALO7.js → chunk-AUECDWP2.js} +3 -3
- package/dist/{chunk-O4B5ALO7.js.map → chunk-AUECDWP2.js.map} +1 -1
- package/dist/{chunk-FCGFG4RV.js → chunk-D4JTQYP5.js} +3 -3
- package/dist/{chunk-FCGFG4RV.js.map → chunk-D4JTQYP5.js.map} +1 -1
- package/dist/chunk-GCQFVMPI.js +56 -0
- package/dist/chunk-GCQFVMPI.js.map +1 -0
- package/dist/{chunk-LDPPIBIJ.js → chunk-KB5T5LWM.js} +3 -3
- package/dist/{chunk-LDPPIBIJ.js.map → chunk-KB5T5LWM.js.map} +1 -1
- package/dist/{chunk-I6JQHP6D.js → chunk-ZJFSZFPI.js} +4 -2
- package/dist/chunk-ZJFSZFPI.js.map +1 -0
- package/dist/components/data/index.d.ts +186 -20
- package/dist/components/data/index.js +1163 -179
- package/dist/components/data/index.js.map +1 -1
- package/dist/components/ui/button.d.ts +3 -1
- package/dist/components/ui/button.js +1 -1
- package/dist/components/ui/calendar.js +2 -2
- package/dist/components/ui/carousel.js +2 -2
- package/dist/components/ui/command-palette.d.ts +30 -0
- package/dist/components/ui/command-palette.js +6 -0
- package/dist/components/ui/command-palette.js.map +1 -0
- package/dist/components/ui/date-picker.js +3 -3
- package/dist/components/ui/pagination.js +2 -2
- package/dist/hooks/index.d.ts +4 -2
- package/dist/hooks/index.js +8 -3
- package/dist/hooks/use-data-filters.d.ts +1 -1
- package/dist/hooks/use-data-filters.js +2 -2
- package/dist/hooks/use-resource-page.d.ts +2 -1
- package/dist/hooks/use-saved-views.d.ts +28 -0
- package/dist/hooks/use-saved-views.js +3 -0
- package/dist/hooks/use-saved-views.js.map +1 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +20 -19
- package/dist/{navigation-qI86Ldug.d.ts → navigation-CpOEyUSb.d.ts} +4 -0
- package/dist/{use-resource-page-B-CFcikK.d.ts → use-resource-page-BXBv_Xah.d.ts} +9 -1
- package/package.json +5 -1
- package/dist/chunk-AI6CCNWK.js.map +0 -1
- package/dist/chunk-I6JQHP6D.js.map +0 -1
|
@@ -3,31 +3,35 @@ import { Spinner } from '../../chunk-OVU3Z2MT.js';
|
|
|
3
3
|
import { Switch } from '../../chunk-2MXMWBDV.js';
|
|
4
4
|
import { Table, TableHeader, TableRow, TableHead, TableBody, TableCell } from '../../chunk-N2ZFNOOT.js';
|
|
5
5
|
import { Textarea } from '../../chunk-YETMISCE.js';
|
|
6
|
+
import { Progress } from '../../chunk-W5Y3LG5K.js';
|
|
6
7
|
import { Select, SelectTrigger, SelectValue, SelectContent, SelectItem } from '../../chunk-2B3MFHBQ.js';
|
|
7
8
|
import { Separator } from '../../chunk-SXSCHWZU.js';
|
|
8
9
|
import { Sheet, SheetContent, SheetHeader, SheetTitle, SheetDescription } from '../../chunk-SCUT2ZCD.js';
|
|
9
10
|
import { Skeleton } from '../../chunk-QFM4Z2SS.js';
|
|
10
|
-
import {
|
|
11
|
+
import { Input } from '../../chunk-QSXVL5UB.js';
|
|
12
|
+
import { Popover, PopoverTrigger, PopoverContent } from '../../chunk-NJMO3VPA.js';
|
|
11
13
|
import { DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuCheckboxItem, DropdownMenuSeparator, DropdownMenuItem } from '../../chunk-SBJV3K4G.js';
|
|
12
14
|
import { FormField, FormLabel, FormError, FormDescription } from '../../chunk-IP6TMT3D.js';
|
|
13
|
-
import '../../chunk-T5VX5DYO.js';
|
|
14
|
-
import {
|
|
15
|
+
import { Label } from '../../chunk-T5VX5DYO.js';
|
|
16
|
+
import { Card, CardContent, CardHeader, CardTitle } from '../../chunk-FN3FZVUR.js';
|
|
15
17
|
import { Checkbox } from '../../chunk-WZWR2ELB.js';
|
|
16
|
-
import {
|
|
17
|
-
import { Popover, PopoverTrigger, PopoverContent } from '../../chunk-NJMO3VPA.js';
|
|
18
|
-
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, DialogDescription } from '../../chunk-YZEA5TGK.js';
|
|
18
|
+
import { Collapsible, CollapsibleContent } from '../../chunk-KBS4BHME.js';
|
|
19
19
|
import { Badge } from '../../chunk-NPNADZHM.js';
|
|
20
|
-
import { Button } from '../../chunk-
|
|
21
|
-
import { Card, CardContent, CardHeader, CardTitle } from '../../chunk-FN3FZVUR.js';
|
|
22
|
-
import { cn } from '../../chunk-NZ5WBA4R.js';
|
|
20
|
+
import { Button } from '../../chunk-ZJFSZFPI.js';
|
|
23
21
|
import { titleCase, useResourcePage } from '../../chunk-QLCDAFS7.js';
|
|
22
|
+
export { useSavedViews } from '../../chunk-GCQFVMPI.js';
|
|
24
23
|
import '../../chunk-XISSLIMH.js';
|
|
25
|
-
import {
|
|
26
|
-
export {
|
|
24
|
+
import { useViewSettings } from '../../chunk-OZ7ZAYCS.js';
|
|
25
|
+
export { CommandPalette, useCommandPaletteHotkey } from '../../chunk-A2OXCNNY.js';
|
|
26
|
+
import { Command, CommandInput, CommandList, CommandEmpty, CommandGroup, CommandItem, CommandSeparator } from '../../chunk-7OBL3H5P.js';
|
|
27
|
+
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, DialogDescription } from '../../chunk-YZEA5TGK.js';
|
|
28
|
+
import { cn } from '../../chunk-NZ5WBA4R.js';
|
|
29
|
+
import { DefaultLink, defaultNavigate, isPathActive } from '../../chunk-6NPEMLBA.js';
|
|
30
|
+
export { DefaultLink, defaultNavigate, isPathActive } from '../../chunk-6NPEMLBA.js';
|
|
27
31
|
import { useIsMobile } from '../../chunk-M7AU5DDM.js';
|
|
28
|
-
import { Search, LayoutGrid, Table2, List, Settings2, AlertTriangle, RefreshCw, ChevronDown, Check, X, ArrowUpDown, Bookmark, Trash2, BookmarkPlus, Loader2, Columns3, ArrowUp, ArrowDown, ChevronLeft, ChevronRight, ExternalLink, Pencil, ChevronsUpDown, ImageIcon, Upload, AlertCircle, FileIcon, Menu, Sun, Moon, Plus } from 'lucide-react';
|
|
32
|
+
import { Search, LayoutGrid, Table2, List, Settings2, ListFilter, SlidersHorizontal, AlertTriangle, RefreshCw, ChevronDown, Check, X, ArrowUpDown, Bookmark, Trash2, BookmarkPlus, Loader2, Columns3, ArrowUp, ArrowDown, ChevronLeft, ChevronRight, ExternalLink, Pencil, ChevronsUpDown, ImageIcon, Upload, AlertCircle, FileIcon, Menu, PanelLeft, PanelLeftClose, Sun, Moon, Plus } from 'lucide-react';
|
|
29
33
|
import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
|
|
30
|
-
import {
|
|
34
|
+
import { useId, useState, useEffect, useMemo, useCallback, useRef } from 'react';
|
|
31
35
|
import { formatDistanceToNow, format } from 'date-fns';
|
|
32
36
|
import { useFieldArray, Controller, useWatch } from 'react-hook-form';
|
|
33
37
|
import { useResource, useWizard, useResourceForm, buildArrayItemDefaults } from '@marktiderman/genesis-core/hooks';
|
|
@@ -143,9 +147,330 @@ function ViewSettings({
|
|
|
143
147
|
] })
|
|
144
148
|
] });
|
|
145
149
|
}
|
|
146
|
-
|
|
150
|
+
|
|
151
|
+
// src/option-storage.ts
|
|
152
|
+
function readStoredOptions(key) {
|
|
153
|
+
if (typeof window === "undefined") return {};
|
|
154
|
+
try {
|
|
155
|
+
const raw = localStorage.getItem(key);
|
|
156
|
+
if (!raw) return {};
|
|
157
|
+
const parsed = JSON.parse(raw);
|
|
158
|
+
if (!parsed || typeof parsed !== "object") return {};
|
|
159
|
+
return parsed;
|
|
160
|
+
} catch {
|
|
161
|
+
return {};
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
function writeStoredOptions(key, value) {
|
|
165
|
+
if (typeof window === "undefined") return;
|
|
166
|
+
try {
|
|
167
|
+
localStorage.setItem(key, JSON.stringify(value));
|
|
168
|
+
} catch {
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
function pluralizeEntity(entity, count) {
|
|
172
|
+
if (count === 1) return entity;
|
|
173
|
+
return entity.endsWith("s") ? entity : `${entity}s`;
|
|
174
|
+
}
|
|
175
|
+
var OPTION_DEFAULTS = {
|
|
176
|
+
showIcon: true,
|
|
177
|
+
showSubtitle: true,
|
|
178
|
+
showCount: true,
|
|
179
|
+
showViewToggle: true,
|
|
180
|
+
showViewSettings: true,
|
|
181
|
+
showCreate: true,
|
|
182
|
+
showDetailsToggle: true,
|
|
183
|
+
showDetailsHint: true,
|
|
184
|
+
subtitleInDetails: void 0,
|
|
185
|
+
defaultDetailsOpen: false,
|
|
186
|
+
detailsLabel: "Details",
|
|
187
|
+
configurable: void 0
|
|
188
|
+
};
|
|
189
|
+
var BOOLEAN_OPTION_KEYS = [
|
|
190
|
+
"showIcon",
|
|
191
|
+
"showSubtitle",
|
|
192
|
+
"showCount",
|
|
193
|
+
"showViewToggle",
|
|
194
|
+
"showViewSettings",
|
|
195
|
+
"showCreate",
|
|
196
|
+
"showDetailsToggle",
|
|
197
|
+
"showDetailsHint",
|
|
198
|
+
"subtitleInDetails",
|
|
199
|
+
"defaultDetailsOpen",
|
|
200
|
+
"configurable"
|
|
201
|
+
];
|
|
202
|
+
var STRING_OPTION_KEYS = ["detailsLabel"];
|
|
203
|
+
function readStoredOptions2(key) {
|
|
204
|
+
const raw = readStoredOptions(key);
|
|
205
|
+
const sanitized = {};
|
|
206
|
+
for (const optKey of BOOLEAN_OPTION_KEYS) {
|
|
207
|
+
const value = raw[optKey];
|
|
208
|
+
if (typeof value === "boolean") {
|
|
209
|
+
sanitized[optKey] = value;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
for (const optKey of STRING_OPTION_KEYS) {
|
|
213
|
+
const value = raw[optKey];
|
|
214
|
+
if (typeof value === "string") {
|
|
215
|
+
sanitized[optKey] = value;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
return sanitized;
|
|
219
|
+
}
|
|
220
|
+
function PageHeader({
|
|
147
221
|
title,
|
|
222
|
+
subtitle,
|
|
148
223
|
icon: Icon,
|
|
224
|
+
options: optionsProp,
|
|
225
|
+
optionsStorageKey,
|
|
226
|
+
actions,
|
|
227
|
+
details,
|
|
228
|
+
detailsOpen,
|
|
229
|
+
onDetailsOpenChange,
|
|
230
|
+
detailsActive = false,
|
|
231
|
+
detailsHint,
|
|
232
|
+
count,
|
|
233
|
+
totalCount,
|
|
234
|
+
entityName,
|
|
235
|
+
countActive = false,
|
|
236
|
+
viewMode,
|
|
237
|
+
onViewModeChange,
|
|
238
|
+
pageSize,
|
|
239
|
+
onPageSizeChange,
|
|
240
|
+
density,
|
|
241
|
+
onDensityChange,
|
|
242
|
+
createAction
|
|
243
|
+
}) {
|
|
244
|
+
const detailsRegionId = useId();
|
|
245
|
+
const hasDetails = details != null;
|
|
246
|
+
const hasCount = count !== void 0 && totalCount !== void 0;
|
|
247
|
+
const hasViewToggle = Boolean(viewMode && onViewModeChange);
|
|
248
|
+
const hasViewSettings = Boolean(onPageSizeChange && onDensityChange);
|
|
249
|
+
const hasCreate = createAction != null;
|
|
250
|
+
const [userOverrides, setUserOverrides] = useState(
|
|
251
|
+
{}
|
|
252
|
+
);
|
|
253
|
+
const [hydratedKey, setHydratedKey] = useState(
|
|
254
|
+
optionsStorageKey ? void 0 : ""
|
|
255
|
+
);
|
|
256
|
+
useEffect(() => {
|
|
257
|
+
if (!optionsStorageKey) {
|
|
258
|
+
setUserOverrides({});
|
|
259
|
+
setHydratedKey("");
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
setUserOverrides(readStoredOptions2(optionsStorageKey));
|
|
263
|
+
setHydratedKey(optionsStorageKey);
|
|
264
|
+
}, [optionsStorageKey]);
|
|
265
|
+
useEffect(() => {
|
|
266
|
+
if (!optionsStorageKey || hydratedKey !== optionsStorageKey) return;
|
|
267
|
+
writeStoredOptions(optionsStorageKey, userOverrides);
|
|
268
|
+
}, [optionsStorageKey, userOverrides, hydratedKey]);
|
|
269
|
+
const resolved = useMemo(() => {
|
|
270
|
+
const merged = {
|
|
271
|
+
...OPTION_DEFAULTS,
|
|
272
|
+
...optionsProp,
|
|
273
|
+
...userOverrides
|
|
274
|
+
};
|
|
275
|
+
if (merged.subtitleInDetails === void 0) {
|
|
276
|
+
merged.subtitleInDetails = hasDetails;
|
|
277
|
+
}
|
|
278
|
+
if (merged.configurable === void 0) {
|
|
279
|
+
merged.configurable = hasDetails || hasCount || hasViewSettings || hasViewToggle;
|
|
280
|
+
}
|
|
281
|
+
return merged;
|
|
282
|
+
}, [
|
|
283
|
+
optionsProp,
|
|
284
|
+
userOverrides,
|
|
285
|
+
hasDetails,
|
|
286
|
+
hasCount,
|
|
287
|
+
hasViewSettings,
|
|
288
|
+
hasViewToggle
|
|
289
|
+
]);
|
|
290
|
+
const patchOption = useCallback((key, value) => {
|
|
291
|
+
setUserOverrides((prev) => ({ ...prev, [key]: value }));
|
|
292
|
+
}, []);
|
|
293
|
+
const moveSubtitle = Boolean(resolved.subtitleInDetails) && hasDetails;
|
|
294
|
+
const showTitleSubtitle = Boolean(subtitle) && Boolean(resolved.showSubtitle) && !moveSubtitle;
|
|
295
|
+
const [uncontrolledOpen, setUncontrolledOpen] = useState(
|
|
296
|
+
() => optionsProp?.defaultDetailsOpen ?? OPTION_DEFAULTS.defaultDetailsOpen
|
|
297
|
+
);
|
|
298
|
+
const isControlled = detailsOpen !== void 0;
|
|
299
|
+
const open = isControlled ? detailsOpen : uncontrolledOpen;
|
|
300
|
+
const setOpen = (next) => {
|
|
301
|
+
if (!isControlled) setUncontrolledOpen(next);
|
|
302
|
+
onDetailsOpenChange?.(next);
|
|
303
|
+
};
|
|
304
|
+
const detailsLabel = resolved.detailsLabel ?? "Details";
|
|
305
|
+
const entity = entityName || "item";
|
|
306
|
+
const toggles = [
|
|
307
|
+
{ key: "showIcon", label: "Icon", available: Boolean(Icon) },
|
|
308
|
+
{
|
|
309
|
+
key: "showSubtitle",
|
|
310
|
+
label: "Subtitle",
|
|
311
|
+
available: Boolean(subtitle) && !moveSubtitle
|
|
312
|
+
},
|
|
313
|
+
{ key: "showCount", label: "Count", available: hasCount },
|
|
314
|
+
{ key: "showViewToggle", label: "View toggle", available: hasViewToggle },
|
|
315
|
+
{
|
|
316
|
+
key: "showViewSettings",
|
|
317
|
+
label: "Display settings",
|
|
318
|
+
available: hasViewSettings
|
|
319
|
+
},
|
|
320
|
+
{ key: "showCreate", label: "Create action", available: hasCreate },
|
|
321
|
+
{
|
|
322
|
+
key: "showDetailsToggle",
|
|
323
|
+
label: "Filters / details",
|
|
324
|
+
available: hasDetails
|
|
325
|
+
},
|
|
326
|
+
{
|
|
327
|
+
key: "showDetailsHint",
|
|
328
|
+
label: "Collapsed hint",
|
|
329
|
+
available: hasDetails && detailsHint != null
|
|
330
|
+
}
|
|
331
|
+
].filter((t) => t.available);
|
|
332
|
+
const showDetailsToggle = hasDetails && Boolean(resolved.showDetailsToggle);
|
|
333
|
+
const showHint = hasDetails && !open && Boolean(resolved.showDetailsHint) && detailsHint != null;
|
|
334
|
+
const actionsCluster = /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 shrink-0", children: [
|
|
335
|
+
showHint ? /* @__PURE__ */ jsx("span", { className: "hidden max-w-[14rem] truncate text-xs text-muted-foreground sm:inline", children: detailsHint }) : null,
|
|
336
|
+
hasCount && resolved.showCount !== false ? /* @__PURE__ */ jsx(Badge, { variant: "secondary", className: "text-xs", children: countActive ? `${count} of ${totalCount}` : `${totalCount} ${pluralizeEntity(entity, totalCount)}` }) : null,
|
|
337
|
+
hasViewToggle && resolved.showViewToggle !== false ? /* @__PURE__ */ jsx(ViewToggle, { viewMode, onViewModeChange }) : null,
|
|
338
|
+
showDetailsToggle ? /* @__PURE__ */ jsxs(
|
|
339
|
+
Button,
|
|
340
|
+
{
|
|
341
|
+
type: "button",
|
|
342
|
+
variant: "ghost",
|
|
343
|
+
size: "icon",
|
|
344
|
+
className: cn(
|
|
345
|
+
"relative h-8 w-8",
|
|
346
|
+
(open || detailsActive) && "bg-accent text-accent-foreground"
|
|
347
|
+
),
|
|
348
|
+
onClick: () => setOpen(!open),
|
|
349
|
+
"data-testid": "page-header-details-toggle",
|
|
350
|
+
title: open ? `Hide ${detailsLabel}` : `Show ${detailsLabel}`,
|
|
351
|
+
"aria-label": open ? `Hide ${detailsLabel}` : `Show ${detailsLabel}`,
|
|
352
|
+
"aria-expanded": open,
|
|
353
|
+
"aria-controls": detailsRegionId,
|
|
354
|
+
testID: "page-header-details-toggle",
|
|
355
|
+
children: [
|
|
356
|
+
/* @__PURE__ */ jsx(ListFilter, { className: "h-4 w-4" }),
|
|
357
|
+
detailsActive ? /* @__PURE__ */ jsx(
|
|
358
|
+
"span",
|
|
359
|
+
{
|
|
360
|
+
className: "absolute right-1.5 top-1.5 size-1.5 rounded-full bg-primary",
|
|
361
|
+
"aria-hidden": true
|
|
362
|
+
}
|
|
363
|
+
) : null
|
|
364
|
+
]
|
|
365
|
+
}
|
|
366
|
+
) : null,
|
|
367
|
+
hasViewSettings && resolved.showViewSettings !== false ? /* @__PURE__ */ jsx(
|
|
368
|
+
ViewSettings,
|
|
369
|
+
{
|
|
370
|
+
pageSize: pageSize ?? 25,
|
|
371
|
+
onPageSizeChange,
|
|
372
|
+
density: density ?? "comfortable",
|
|
373
|
+
onDensityChange
|
|
374
|
+
}
|
|
375
|
+
) : null,
|
|
376
|
+
hasCreate && resolved.showCreate !== false ? createAction : null,
|
|
377
|
+
actions,
|
|
378
|
+
resolved.configurable && toggles.length > 0 ? /* @__PURE__ */ jsxs(Popover, { children: [
|
|
379
|
+
/* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
|
|
380
|
+
Button,
|
|
381
|
+
{
|
|
382
|
+
type: "button",
|
|
383
|
+
variant: "ghost",
|
|
384
|
+
size: "icon",
|
|
385
|
+
className: "h-8 w-8",
|
|
386
|
+
title: "Header options",
|
|
387
|
+
"aria-label": "Header options",
|
|
388
|
+
"data-testid": "page-header-options",
|
|
389
|
+
testID: "page-header-options",
|
|
390
|
+
children: /* @__PURE__ */ jsx(SlidersHorizontal, { className: "h-4 w-4" })
|
|
391
|
+
}
|
|
392
|
+
) }),
|
|
393
|
+
/* @__PURE__ */ jsxs(PopoverContent, { align: "end", className: "w-64 space-y-3", children: [
|
|
394
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
395
|
+
/* @__PURE__ */ jsx("h4", { className: "text-sm font-semibold", children: "Header options" }),
|
|
396
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "Customize this page header. Saved on this device when a storage key is set." })
|
|
397
|
+
] }),
|
|
398
|
+
/* @__PURE__ */ jsx("ul", { className: "space-y-3", children: toggles.map((t) => {
|
|
399
|
+
const checked = Boolean(resolved[t.key] ?? true);
|
|
400
|
+
return /* @__PURE__ */ jsxs(
|
|
401
|
+
"li",
|
|
402
|
+
{
|
|
403
|
+
className: "flex items-center justify-between gap-3",
|
|
404
|
+
children: [
|
|
405
|
+
/* @__PURE__ */ jsx(
|
|
406
|
+
Label,
|
|
407
|
+
{
|
|
408
|
+
htmlFor: `page-header-opt-${t.key}`,
|
|
409
|
+
className: "text-sm font-normal",
|
|
410
|
+
children: t.label
|
|
411
|
+
}
|
|
412
|
+
),
|
|
413
|
+
/* @__PURE__ */ jsx(
|
|
414
|
+
Switch,
|
|
415
|
+
{
|
|
416
|
+
id: `page-header-opt-${t.key}`,
|
|
417
|
+
checked,
|
|
418
|
+
onCheckedChange: (v) => patchOption(t.key, v)
|
|
419
|
+
}
|
|
420
|
+
)
|
|
421
|
+
]
|
|
422
|
+
},
|
|
423
|
+
t.key
|
|
424
|
+
);
|
|
425
|
+
}) }),
|
|
426
|
+
optionsStorageKey && Object.keys(userOverrides).length > 0 ? /* @__PURE__ */ jsx(
|
|
427
|
+
Button,
|
|
428
|
+
{
|
|
429
|
+
type: "button",
|
|
430
|
+
variant: "ghost",
|
|
431
|
+
size: "sm",
|
|
432
|
+
className: "h-8 w-full text-xs",
|
|
433
|
+
onClick: () => setUserOverrides({}),
|
|
434
|
+
"data-testid": "page-header-options-reset",
|
|
435
|
+
"aria-label": "Reset header options",
|
|
436
|
+
testID: "page-header-options-reset",
|
|
437
|
+
children: "Reset to defaults"
|
|
438
|
+
}
|
|
439
|
+
) : null
|
|
440
|
+
] })
|
|
441
|
+
] }) : null
|
|
442
|
+
] });
|
|
443
|
+
const titleRow = /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between", children: [
|
|
444
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 min-w-0", children: [
|
|
445
|
+
Icon && resolved.showIcon !== false ? /* @__PURE__ */ jsx("div", { className: "flex h-10 w-10 shrink-0 items-center justify-center overflow-hidden rounded-xl bg-primary/10", children: /* @__PURE__ */ jsx(Icon, { className: "h-5 w-5 text-primary" }) }) : null,
|
|
446
|
+
/* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
|
|
447
|
+
/* @__PURE__ */ jsx(
|
|
448
|
+
"h1",
|
|
449
|
+
{
|
|
450
|
+
"data-testid": "page-title",
|
|
451
|
+
className: "truncate font-display text-xl font-bold tracking-tight sm:text-2xl",
|
|
452
|
+
children: title
|
|
453
|
+
}
|
|
454
|
+
),
|
|
455
|
+
showTitleSubtitle ? /* @__PURE__ */ jsx("p", { className: "truncate text-sm text-muted-foreground", children: subtitle }) : null
|
|
456
|
+
] })
|
|
457
|
+
] }),
|
|
458
|
+
actionsCluster
|
|
459
|
+
] });
|
|
460
|
+
if (!hasDetails) {
|
|
461
|
+
return titleRow;
|
|
462
|
+
}
|
|
463
|
+
return /* @__PURE__ */ jsxs(Collapsible, { open, onOpenChange: setOpen, children: [
|
|
464
|
+
titleRow,
|
|
465
|
+
/* @__PURE__ */ jsx(CollapsibleContent, { id: detailsRegionId, className: "mt-3", children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-3 rounded-lg border border-border bg-card/40 p-3 sm:p-4", children: [
|
|
466
|
+
moveSubtitle && subtitle && resolved.showSubtitle !== false ? /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: subtitle }) : null,
|
|
467
|
+
details
|
|
468
|
+
] }) })
|
|
469
|
+
] });
|
|
470
|
+
}
|
|
471
|
+
function DataPageShell({
|
|
472
|
+
title,
|
|
473
|
+
icon,
|
|
149
474
|
entityName,
|
|
150
475
|
subtitle,
|
|
151
476
|
createAction,
|
|
@@ -168,44 +493,57 @@ function DataPageShell({
|
|
|
168
493
|
filters,
|
|
169
494
|
hasActiveFilters = false,
|
|
170
495
|
onClearFilters,
|
|
496
|
+
filtersCollapsible = false,
|
|
497
|
+
defaultFiltersOpen = false,
|
|
498
|
+
filtersOpen,
|
|
499
|
+
onFiltersOpenChange,
|
|
500
|
+
filtersLabel,
|
|
501
|
+
filtersHint,
|
|
502
|
+
headerOptions,
|
|
503
|
+
headerOptionsStorageKey,
|
|
171
504
|
children
|
|
172
505
|
}) {
|
|
173
506
|
const entity = entityName || title.toLowerCase();
|
|
174
|
-
const
|
|
175
|
-
const
|
|
176
|
-
|
|
507
|
+
const collapseFilters = filtersCollapsible && filters != null;
|
|
508
|
+
const mergedOptions = {
|
|
509
|
+
defaultDetailsOpen: defaultFiltersOpen,
|
|
510
|
+
detailsLabel: filtersLabel,
|
|
511
|
+
subtitleInDetails: collapseFilters ? true : void 0,
|
|
512
|
+
...headerOptions
|
|
513
|
+
};
|
|
514
|
+
const sharedHeaderProps = {
|
|
515
|
+
title,
|
|
516
|
+
subtitle,
|
|
517
|
+
icon,
|
|
518
|
+
entityName: entity,
|
|
519
|
+
count,
|
|
520
|
+
totalCount,
|
|
521
|
+
countActive: hasActiveFilters,
|
|
522
|
+
viewMode,
|
|
523
|
+
onViewModeChange,
|
|
524
|
+
pageSize,
|
|
525
|
+
onPageSizeChange,
|
|
526
|
+
density,
|
|
527
|
+
onDensityChange,
|
|
528
|
+
createAction,
|
|
529
|
+
optionsStorageKey: headerOptionsStorageKey
|
|
530
|
+
};
|
|
177
531
|
return /* @__PURE__ */ jsxs("div", { className: "space-y-6", children: [
|
|
178
|
-
/* @__PURE__ */
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
] }),
|
|
193
|
-
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 shrink-0", children: [
|
|
194
|
-
hasCount && /* @__PURE__ */ jsx(Badge, { variant: "secondary", className: "text-xs", children: hasActiveFilters ? `${count} of ${totalCount}` : `${totalCount} ${entity.endsWith("s") ? entity : `${entity}s`}` }),
|
|
195
|
-
hasViewToggle && /* @__PURE__ */ jsx(ViewToggle, { viewMode, onViewModeChange }),
|
|
196
|
-
hasViewSettings && /* @__PURE__ */ jsx(
|
|
197
|
-
ViewSettings,
|
|
198
|
-
{
|
|
199
|
-
pageSize: pageSize ?? 25,
|
|
200
|
-
onPageSizeChange,
|
|
201
|
-
density: density ?? "comfortable",
|
|
202
|
-
onDensityChange
|
|
203
|
-
}
|
|
204
|
-
),
|
|
205
|
-
createAction
|
|
206
|
-
] })
|
|
532
|
+
collapseFilters ? /* @__PURE__ */ jsx(
|
|
533
|
+
PageHeader,
|
|
534
|
+
{
|
|
535
|
+
...sharedHeaderProps,
|
|
536
|
+
details: filters,
|
|
537
|
+
detailsOpen: filtersOpen,
|
|
538
|
+
onDetailsOpenChange: onFiltersOpenChange,
|
|
539
|
+
detailsActive: hasActiveFilters,
|
|
540
|
+
detailsHint: filtersHint,
|
|
541
|
+
options: mergedOptions
|
|
542
|
+
}
|
|
543
|
+
) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
544
|
+
/* @__PURE__ */ jsx(PageHeader, { ...sharedHeaderProps, options: headerOptions }),
|
|
545
|
+
filters
|
|
207
546
|
] }),
|
|
208
|
-
filters,
|
|
209
547
|
isLoading ? /* @__PURE__ */ jsx(
|
|
210
548
|
"div",
|
|
211
549
|
{
|
|
@@ -222,10 +560,10 @@ function DataPageShell({
|
|
|
222
560
|
}
|
|
223
561
|
) : hasError ? /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center justify-center py-16 text-center", children: [
|
|
224
562
|
/* @__PURE__ */ jsx(AlertTriangle, { className: "h-10 w-10 text-destructive mb-3" }),
|
|
225
|
-
/* @__PURE__ */ jsx("p", { className: "text-sm font-medium text-foreground
|
|
226
|
-
/* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground
|
|
563
|
+
/* @__PURE__ */ jsx("p", { className: "mb-1 text-sm font-medium text-foreground", children: "Something went wrong" }),
|
|
564
|
+
/* @__PURE__ */ jsx("p", { className: "mb-4 text-sm text-muted-foreground", children: errorMessage || `Failed to load ${entity}s. Please try again.` }),
|
|
227
565
|
onRetry && /* @__PURE__ */ jsxs(Button, { variant: "outline", size: "sm", onClick: onRetry, children: [
|
|
228
|
-
/* @__PURE__ */ jsx(RefreshCw, { className: "h-4 w-4
|
|
566
|
+
/* @__PURE__ */ jsx(RefreshCw, { className: "mr-1 h-4 w-4" }),
|
|
229
567
|
" Retry"
|
|
230
568
|
] })
|
|
231
569
|
] }) : isEmpty ? /* @__PURE__ */ jsx(
|
|
@@ -647,6 +985,16 @@ function DataGrid({
|
|
|
647
985
|
)
|
|
648
986
|
] });
|
|
649
987
|
}
|
|
988
|
+
var DENSITY_CELL = {
|
|
989
|
+
compact: "py-1 h-8",
|
|
990
|
+
comfortable: "py-2",
|
|
991
|
+
spacious: "py-3"
|
|
992
|
+
};
|
|
993
|
+
var DENSITY_HEAD = {
|
|
994
|
+
compact: "h-8 py-1",
|
|
995
|
+
comfortable: "h-10",
|
|
996
|
+
spacious: "h-12 py-3"
|
|
997
|
+
};
|
|
650
998
|
function getCellClasses(col) {
|
|
651
999
|
const hide = col.hideBelow ? col.hideBelow === "sm" ? "hidden sm:table-cell" : col.hideBelow === "md" ? "hidden md:table-cell" : col.hideBelow === "lg" ? "hidden lg:table-cell" : "hidden xl:table-cell" : "";
|
|
652
1000
|
return cn(col.width, hide);
|
|
@@ -764,8 +1112,11 @@ function DataTable({
|
|
|
764
1112
|
pageSizeOptions = [10, 25, 50, 100],
|
|
765
1113
|
stickyHeader = true,
|
|
766
1114
|
resizeKey,
|
|
767
|
-
stickyFirstColumn
|
|
1115
|
+
stickyFirstColumn,
|
|
1116
|
+
density = "comfortable"
|
|
768
1117
|
}) {
|
|
1118
|
+
const cellDensity = DENSITY_CELL[density];
|
|
1119
|
+
const headDensity = DENSITY_HEAD[density];
|
|
769
1120
|
const [sorts, setSorts] = useState([]);
|
|
770
1121
|
const handleSort = useCallback(
|
|
771
1122
|
(key, shiftKey) => {
|
|
@@ -788,15 +1139,6 @@ function DataTable({
|
|
|
788
1139
|
);
|
|
789
1140
|
const [visibleCols, setVisibleCols] = useState(
|
|
790
1141
|
() => {
|
|
791
|
-
if (columnVisibilityKey) {
|
|
792
|
-
try {
|
|
793
|
-
const saved = localStorage.getItem(
|
|
794
|
-
`dt-vis-${columnVisibilityKey}`
|
|
795
|
-
);
|
|
796
|
-
if (saved) return JSON.parse(saved);
|
|
797
|
-
} catch {
|
|
798
|
-
}
|
|
799
|
-
}
|
|
800
1142
|
const initial = {};
|
|
801
1143
|
columns.forEach((c) => {
|
|
802
1144
|
initial[c.key] = c.defaultVisible !== false;
|
|
@@ -804,14 +1146,40 @@ function DataTable({
|
|
|
804
1146
|
return initial;
|
|
805
1147
|
}
|
|
806
1148
|
);
|
|
1149
|
+
const [visibilityHydrated, setVisibilityHydrated] = useState(false);
|
|
807
1150
|
useEffect(() => {
|
|
808
|
-
if (columnVisibilityKey) {
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
JSON.stringify(visibleCols)
|
|
812
|
-
);
|
|
1151
|
+
if (!columnVisibilityKey) {
|
|
1152
|
+
setVisibilityHydrated(true);
|
|
1153
|
+
return;
|
|
813
1154
|
}
|
|
814
|
-
|
|
1155
|
+
try {
|
|
1156
|
+
const saved = localStorage.getItem(`dt-vis-${columnVisibilityKey}`);
|
|
1157
|
+
if (saved) {
|
|
1158
|
+
const parsed = JSON.parse(saved);
|
|
1159
|
+
setVisibleCols((prev) => {
|
|
1160
|
+
const next = { ...prev };
|
|
1161
|
+
for (const [k, v] of Object.entries(parsed)) {
|
|
1162
|
+
if (k in next || columns.some((c) => c.key === k)) {
|
|
1163
|
+
next[k] = v;
|
|
1164
|
+
}
|
|
1165
|
+
}
|
|
1166
|
+
columns.forEach((c) => {
|
|
1167
|
+
if (!(c.key in next)) next[c.key] = c.defaultVisible !== false;
|
|
1168
|
+
});
|
|
1169
|
+
return next;
|
|
1170
|
+
});
|
|
1171
|
+
}
|
|
1172
|
+
} catch {
|
|
1173
|
+
}
|
|
1174
|
+
setVisibilityHydrated(true);
|
|
1175
|
+
}, [columnVisibilityKey]);
|
|
1176
|
+
useEffect(() => {
|
|
1177
|
+
if (!visibilityHydrated || !columnVisibilityKey) return;
|
|
1178
|
+
localStorage.setItem(
|
|
1179
|
+
`dt-vis-${columnVisibilityKey}`,
|
|
1180
|
+
JSON.stringify(visibleCols)
|
|
1181
|
+
);
|
|
1182
|
+
}, [visibleCols, columnVisibilityKey, visibilityHydrated]);
|
|
815
1183
|
const toggleColumnVisibility = useCallback((key) => {
|
|
816
1184
|
setVisibleCols((prev) => ({ ...prev, [key]: !prev[key] }));
|
|
817
1185
|
}, []);
|
|
@@ -950,7 +1318,7 @@ function DataTable({
|
|
|
950
1318
|
{
|
|
951
1319
|
className: stickyHeader ? "sticky top-0 z-10 bg-background" : "",
|
|
952
1320
|
children: /* @__PURE__ */ jsxs(TableRow, { children: [
|
|
953
|
-
selectable && /* @__PURE__ */ jsx(TableHead, { className: "w-10", children: /* @__PURE__ */ jsx(
|
|
1321
|
+
selectable && /* @__PURE__ */ jsx(TableHead, { className: cn("w-10", headDensity), children: /* @__PURE__ */ jsx(
|
|
954
1322
|
Checkbox,
|
|
955
1323
|
{
|
|
956
1324
|
checked: items.length > 0 && selected?.size === items.length,
|
|
@@ -967,6 +1335,7 @@ function DataTable({
|
|
|
967
1335
|
{
|
|
968
1336
|
className: cn(
|
|
969
1337
|
getCellClasses(col),
|
|
1338
|
+
headDensity,
|
|
970
1339
|
col.sortable && "cursor-pointer select-none",
|
|
971
1340
|
resizableColumns && "relative",
|
|
972
1341
|
isFirstCol && "sticky left-0 bg-background",
|
|
@@ -998,7 +1367,7 @@ function DataTable({
|
|
|
998
1367
|
col.key
|
|
999
1368
|
);
|
|
1000
1369
|
}),
|
|
1001
|
-
renderActions && /* @__PURE__ */ jsx(TableHead, { className: "w-10" })
|
|
1370
|
+
renderActions && /* @__PURE__ */ jsx(TableHead, { className: cn("w-10", headDensity) })
|
|
1002
1371
|
] })
|
|
1003
1372
|
}
|
|
1004
1373
|
),
|
|
@@ -1018,13 +1387,20 @@ function DataTable({
|
|
|
1018
1387
|
"data-state": selected?.has(id) ? "selected" : void 0,
|
|
1019
1388
|
onClick: onRowClick ? () => onRowClick(item) : void 0,
|
|
1020
1389
|
children: [
|
|
1021
|
-
selectable && /* @__PURE__ */ jsx(
|
|
1022
|
-
|
|
1390
|
+
selectable && /* @__PURE__ */ jsx(
|
|
1391
|
+
TableCell,
|
|
1023
1392
|
{
|
|
1024
|
-
|
|
1025
|
-
|
|
1393
|
+
className: cellDensity,
|
|
1394
|
+
onClick: (e) => e.stopPropagation(),
|
|
1395
|
+
children: /* @__PURE__ */ jsx(
|
|
1396
|
+
Checkbox,
|
|
1397
|
+
{
|
|
1398
|
+
checked: selected?.has(id),
|
|
1399
|
+
onCheckedChange: () => onToggleSelect?.(id)
|
|
1400
|
+
}
|
|
1401
|
+
)
|
|
1026
1402
|
}
|
|
1027
|
-
)
|
|
1403
|
+
),
|
|
1028
1404
|
activeColumns.map((col, colIndex) => {
|
|
1029
1405
|
const acc = col.accessor || ((it) => it[col.key]);
|
|
1030
1406
|
const value = acc(item);
|
|
@@ -1036,6 +1412,7 @@ function DataTable({
|
|
|
1036
1412
|
getCellClasses(
|
|
1037
1413
|
col
|
|
1038
1414
|
),
|
|
1415
|
+
cellDensity,
|
|
1039
1416
|
isFirstCol && "sticky left-0 z-[1] bg-background"
|
|
1040
1417
|
),
|
|
1041
1418
|
children: col.editable ? /* @__PURE__ */ jsx(
|
|
@@ -1050,7 +1427,14 @@ function DataTable({
|
|
|
1050
1427
|
col.key
|
|
1051
1428
|
);
|
|
1052
1429
|
}),
|
|
1053
|
-
renderActions && /* @__PURE__ */ jsx(
|
|
1430
|
+
renderActions && /* @__PURE__ */ jsx(
|
|
1431
|
+
TableCell,
|
|
1432
|
+
{
|
|
1433
|
+
className: cellDensity,
|
|
1434
|
+
onClick: (e) => e.stopPropagation(),
|
|
1435
|
+
children: renderActions(item)
|
|
1436
|
+
}
|
|
1437
|
+
)
|
|
1054
1438
|
]
|
|
1055
1439
|
},
|
|
1056
1440
|
id
|
|
@@ -2393,9 +2777,12 @@ function ResourcePage(props) {
|
|
|
2393
2777
|
emptyState,
|
|
2394
2778
|
resourceOptions,
|
|
2395
2779
|
defaultView = "table",
|
|
2396
|
-
onNavigate = defaultNavigate
|
|
2780
|
+
onNavigate = defaultNavigate,
|
|
2781
|
+
viewSettingsKey: viewSettingsKeyProp
|
|
2397
2782
|
} = props;
|
|
2398
2783
|
const isClientMode = !!resourceName;
|
|
2784
|
+
const viewSettingsKey = viewSettingsKeyProp ?? resourceName ?? title.toLowerCase().replace(/\s+/g, "-");
|
|
2785
|
+
const { settings, updateSetting } = useViewSettings(viewSettingsKey);
|
|
2399
2786
|
const resourceHook = useResource(
|
|
2400
2787
|
resourceName ?? "__noop__",
|
|
2401
2788
|
isClientMode ? resourceOptions : { enabled: false }
|
|
@@ -2544,6 +2931,10 @@ function ResourcePage(props) {
|
|
|
2544
2931
|
onRetry: isClientMode ? () => resourceHook.list.refetch() : void 0,
|
|
2545
2932
|
viewMode: page.viewMode,
|
|
2546
2933
|
onViewModeChange: page.setViewMode,
|
|
2934
|
+
pageSize: settings.pageSize,
|
|
2935
|
+
onPageSizeChange: (size) => updateSetting("pageSize", size),
|
|
2936
|
+
density: settings.density,
|
|
2937
|
+
onDensityChange: (d) => updateSetting("density", d),
|
|
2547
2938
|
createAction,
|
|
2548
2939
|
hasActiveFilters: page.hasActiveFilters,
|
|
2549
2940
|
onClearFilters: page.clearFilters,
|
|
@@ -2595,7 +2986,9 @@ function ResourcePage(props) {
|
|
|
2595
2986
|
selectable: !!bulkActions && bulkActions.length > 0,
|
|
2596
2987
|
selected: selectedIds,
|
|
2597
2988
|
onToggleSelect: handleToggleSelect,
|
|
2598
|
-
onToggleAll: handleToggleAll
|
|
2989
|
+
onToggleAll: handleToggleAll,
|
|
2990
|
+
density: settings.density,
|
|
2991
|
+
pageSize: settings.pageSize
|
|
2599
2992
|
}
|
|
2600
2993
|
),
|
|
2601
2994
|
renderCard: (item) => {
|
|
@@ -2743,7 +3136,40 @@ function ResourcePage(props) {
|
|
|
2743
3136
|
}
|
|
2744
3137
|
);
|
|
2745
3138
|
}
|
|
2746
|
-
|
|
3139
|
+
var OPTION_DEFAULTS2 = {
|
|
3140
|
+
showLogo: true,
|
|
3141
|
+
showAppName: true,
|
|
3142
|
+
showThemeToggle: true,
|
|
3143
|
+
showCollapse: true,
|
|
3144
|
+
showUserSlot: true,
|
|
3145
|
+
showFooter: true,
|
|
3146
|
+
showSecondaryNav: true,
|
|
3147
|
+
showMobileBottomNav: true,
|
|
3148
|
+
defaultCollapsed: false,
|
|
3149
|
+
configurable: void 0,
|
|
3150
|
+
density: "comfortable",
|
|
3151
|
+
railWidth: "narrow",
|
|
3152
|
+
scrollRoot: "container"
|
|
3153
|
+
};
|
|
3154
|
+
function readStoredCollapsed(key) {
|
|
3155
|
+
if (typeof window === "undefined") return void 0;
|
|
3156
|
+
try {
|
|
3157
|
+
const raw = localStorage.getItem(`${key}:collapsed`);
|
|
3158
|
+
if (raw === "1") return true;
|
|
3159
|
+
if (raw === "0") return false;
|
|
3160
|
+
return void 0;
|
|
3161
|
+
} catch {
|
|
3162
|
+
return void 0;
|
|
3163
|
+
}
|
|
3164
|
+
}
|
|
3165
|
+
function writeStoredCollapsed(key, collapsed) {
|
|
3166
|
+
if (typeof window === "undefined") return;
|
|
3167
|
+
try {
|
|
3168
|
+
localStorage.setItem(`${key}:collapsed`, collapsed ? "1" : "0");
|
|
3169
|
+
} catch {
|
|
3170
|
+
}
|
|
3171
|
+
}
|
|
3172
|
+
function ThemeToggle({ testId }) {
|
|
2747
3173
|
const [dark, setDark] = useState(false);
|
|
2748
3174
|
useEffect(() => {
|
|
2749
3175
|
const stored = localStorage.getItem("theme");
|
|
@@ -2760,159 +3186,717 @@ function ThemeToggle() {
|
|
|
2760
3186
|
document.documentElement.classList.toggle("dark", next);
|
|
2761
3187
|
localStorage.setItem("theme", next ? "dark" : "light");
|
|
2762
3188
|
}
|
|
2763
|
-
return /* @__PURE__ */ jsx(
|
|
3189
|
+
return /* @__PURE__ */ jsx(
|
|
3190
|
+
Button,
|
|
3191
|
+
{
|
|
3192
|
+
variant: "ghost",
|
|
3193
|
+
size: "icon",
|
|
3194
|
+
onClick: toggle,
|
|
3195
|
+
className: "h-8 w-8",
|
|
3196
|
+
title: "Toggle theme",
|
|
3197
|
+
"aria-label": "Toggle theme",
|
|
3198
|
+
"data-testid": testId,
|
|
3199
|
+
testID: testId,
|
|
3200
|
+
children: dark ? /* @__PURE__ */ jsx(Sun, { className: "h-4 w-4" }) : /* @__PURE__ */ jsx(Moon, { className: "h-4 w-4" })
|
|
3201
|
+
}
|
|
3202
|
+
);
|
|
3203
|
+
}
|
|
3204
|
+
function itemIsActive(item, activePath, isActive) {
|
|
3205
|
+
if (item.end) {
|
|
3206
|
+
return Boolean(activePath && activePath === item.to);
|
|
3207
|
+
}
|
|
3208
|
+
if (isActive(item.to, activePath)) return true;
|
|
3209
|
+
return Boolean(
|
|
3210
|
+
item.children?.some((child) => itemIsActive(child, activePath, isActive))
|
|
3211
|
+
);
|
|
2764
3212
|
}
|
|
2765
|
-
function
|
|
2766
|
-
|
|
3213
|
+
function NavLinkRow({
|
|
3214
|
+
item,
|
|
3215
|
+
collapsed,
|
|
3216
|
+
density,
|
|
2767
3217
|
onNavClick,
|
|
2768
3218
|
linkComponent: LinkComp,
|
|
2769
3219
|
activePath,
|
|
2770
3220
|
isActive
|
|
2771
3221
|
}) {
|
|
2772
|
-
|
|
2773
|
-
|
|
3222
|
+
const active = itemIsActive(item, activePath, isActive);
|
|
3223
|
+
const pad = density === "compact" ? "px-2.5 py-1.5" : "px-3 py-2";
|
|
3224
|
+
if (item.disabled) {
|
|
2774
3225
|
return /* @__PURE__ */ jsxs(
|
|
2775
|
-
|
|
3226
|
+
"span",
|
|
2776
3227
|
{
|
|
2777
|
-
|
|
2778
|
-
onClick: onNavClick,
|
|
2779
|
-
"aria-current": active ? "page" : void 0,
|
|
3228
|
+
title: item.label,
|
|
2780
3229
|
className: cn(
|
|
2781
|
-
"flex items-center gap-3 rounded-lg
|
|
2782
|
-
|
|
3230
|
+
"flex items-center gap-3 rounded-lg text-sm font-medium opacity-40",
|
|
3231
|
+
pad,
|
|
3232
|
+
collapsed && "justify-center px-2"
|
|
2783
3233
|
),
|
|
3234
|
+
"aria-disabled": true,
|
|
2784
3235
|
children: [
|
|
2785
|
-
/* @__PURE__ */ jsx(item.icon, { className: "h-4 w-4" }),
|
|
2786
|
-
item.label
|
|
3236
|
+
/* @__PURE__ */ jsx(item.icon, { className: "h-4 w-4 shrink-0" }),
|
|
3237
|
+
!collapsed ? /* @__PURE__ */ jsx("span", { className: "truncate", children: item.label }) : null
|
|
2787
3238
|
]
|
|
2788
|
-
}
|
|
2789
|
-
item.to
|
|
3239
|
+
}
|
|
2790
3240
|
);
|
|
2791
|
-
}
|
|
3241
|
+
}
|
|
3242
|
+
return /* @__PURE__ */ jsxs(
|
|
3243
|
+
LinkComp,
|
|
3244
|
+
{
|
|
3245
|
+
href: item.to,
|
|
3246
|
+
onClick: onNavClick,
|
|
3247
|
+
title: collapsed ? item.label : void 0,
|
|
3248
|
+
"aria-label": collapsed ? item.label : void 0,
|
|
3249
|
+
"aria-current": active ? "page" : void 0,
|
|
3250
|
+
className: cn(
|
|
3251
|
+
"flex items-center gap-3 rounded-lg text-sm font-medium transition-colors",
|
|
3252
|
+
pad,
|
|
3253
|
+
collapsed && "justify-center px-2",
|
|
3254
|
+
active ? "bg-sidebar-accent text-sidebar-primary" : "text-sidebar-foreground hover:bg-sidebar-accent hover:text-sidebar-accent-foreground"
|
|
3255
|
+
),
|
|
3256
|
+
children: [
|
|
3257
|
+
/* @__PURE__ */ jsx(item.icon, { className: "h-4 w-4 shrink-0" }),
|
|
3258
|
+
!collapsed ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
3259
|
+
/* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate", children: item.label }),
|
|
3260
|
+
item.badge != null ? /* @__PURE__ */ jsx(Badge, { variant: "secondary", className: "h-5 shrink-0 px-1.5 text-[10px]", children: item.badge }) : null
|
|
3261
|
+
] }) : null
|
|
3262
|
+
]
|
|
3263
|
+
}
|
|
3264
|
+
);
|
|
2792
3265
|
}
|
|
2793
|
-
function
|
|
3266
|
+
function NavGroup({
|
|
3267
|
+
item,
|
|
3268
|
+
collapsed,
|
|
3269
|
+
density,
|
|
3270
|
+
onNavClick,
|
|
3271
|
+
linkComponent,
|
|
3272
|
+
activePath,
|
|
3273
|
+
isActive
|
|
3274
|
+
}) {
|
|
3275
|
+
const children = item.children ?? [];
|
|
3276
|
+
const childActive = children.some(
|
|
3277
|
+
(c) => itemIsActive(c, activePath, isActive)
|
|
3278
|
+
);
|
|
3279
|
+
const [open, setOpen] = useState(childActive);
|
|
3280
|
+
useEffect(() => {
|
|
3281
|
+
if (childActive) setOpen(true);
|
|
3282
|
+
}, [childActive]);
|
|
3283
|
+
if (children.length === 0) {
|
|
3284
|
+
return /* @__PURE__ */ jsx(
|
|
3285
|
+
NavLinkRow,
|
|
3286
|
+
{
|
|
3287
|
+
item,
|
|
3288
|
+
collapsed,
|
|
3289
|
+
density,
|
|
3290
|
+
onNavClick,
|
|
3291
|
+
linkComponent,
|
|
3292
|
+
activePath,
|
|
3293
|
+
isActive
|
|
3294
|
+
}
|
|
3295
|
+
);
|
|
3296
|
+
}
|
|
3297
|
+
if (collapsed) {
|
|
3298
|
+
const target = item.to ? { ...item, children: void 0 } : { ...children[0], children: void 0 };
|
|
3299
|
+
return /* @__PURE__ */ jsx(
|
|
3300
|
+
NavLinkRow,
|
|
3301
|
+
{
|
|
3302
|
+
item: target,
|
|
3303
|
+
collapsed: true,
|
|
3304
|
+
density,
|
|
3305
|
+
onNavClick,
|
|
3306
|
+
linkComponent,
|
|
3307
|
+
activePath,
|
|
3308
|
+
isActive
|
|
3309
|
+
}
|
|
3310
|
+
);
|
|
3311
|
+
}
|
|
3312
|
+
const selfExact = Boolean(item.to && activePath && activePath === item.to);
|
|
3313
|
+
const selfActive = item.to ? item.end ? selfExact : isActive(item.to, activePath) : false;
|
|
3314
|
+
const groupHighlight = selfActive || childActive;
|
|
3315
|
+
const pad = density === "compact" ? "px-2.5 py-1.5" : "px-3 py-2";
|
|
3316
|
+
const LinkComp = linkComponent;
|
|
3317
|
+
const parentClass = cn(
|
|
3318
|
+
"flex min-w-0 flex-1 items-center gap-3 rounded-lg text-sm font-medium transition-colors",
|
|
3319
|
+
pad,
|
|
3320
|
+
groupHighlight ? "bg-sidebar-accent/60 text-sidebar-primary" : "text-sidebar-foreground hover:bg-sidebar-accent hover:text-sidebar-accent-foreground"
|
|
3321
|
+
);
|
|
3322
|
+
return /* @__PURE__ */ jsx(Collapsible, { open, onOpenChange: setOpen, children: /* @__PURE__ */ jsxs("div", { className: "space-y-0.5", children: [
|
|
3323
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-0.5", "data-testid": `nav-group-${item.label}`, children: [
|
|
3324
|
+
item.to && !item.disabled ? /* @__PURE__ */ jsxs(
|
|
3325
|
+
LinkComp,
|
|
3326
|
+
{
|
|
3327
|
+
href: item.to,
|
|
3328
|
+
onClick: onNavClick,
|
|
3329
|
+
"aria-current": selfExact ? "page" : void 0,
|
|
3330
|
+
className: parentClass,
|
|
3331
|
+
children: [
|
|
3332
|
+
/* @__PURE__ */ jsx(item.icon, { className: "h-4 w-4 shrink-0" }),
|
|
3333
|
+
/* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate text-left", children: item.label })
|
|
3334
|
+
]
|
|
3335
|
+
}
|
|
3336
|
+
) : /* @__PURE__ */ jsxs(
|
|
3337
|
+
"button",
|
|
3338
|
+
{
|
|
3339
|
+
type: "button",
|
|
3340
|
+
className: parentClass,
|
|
3341
|
+
onClick: () => setOpen((o) => !o),
|
|
3342
|
+
"aria-expanded": open,
|
|
3343
|
+
"aria-disabled": item.disabled || void 0,
|
|
3344
|
+
disabled: item.disabled,
|
|
3345
|
+
children: [
|
|
3346
|
+
/* @__PURE__ */ jsx(item.icon, { className: "h-4 w-4 shrink-0" }),
|
|
3347
|
+
/* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate text-left", children: item.label })
|
|
3348
|
+
]
|
|
3349
|
+
}
|
|
3350
|
+
),
|
|
3351
|
+
/* @__PURE__ */ jsx(
|
|
3352
|
+
"button",
|
|
3353
|
+
{
|
|
3354
|
+
type: "button",
|
|
3355
|
+
className: cn(
|
|
3356
|
+
"shrink-0 rounded-lg p-1.5 text-sidebar-foreground transition-colors hover:bg-sidebar-accent",
|
|
3357
|
+
groupHighlight && "text-sidebar-primary",
|
|
3358
|
+
item.disabled && "pointer-events-none opacity-40"
|
|
3359
|
+
),
|
|
3360
|
+
onClick: () => setOpen((o) => !o),
|
|
3361
|
+
"aria-expanded": open,
|
|
3362
|
+
"aria-label": open ? `Collapse ${item.label}` : `Expand ${item.label}`,
|
|
3363
|
+
"aria-disabled": item.disabled || void 0,
|
|
3364
|
+
disabled: item.disabled,
|
|
3365
|
+
"data-testid": `nav-group-toggle-${item.label}`,
|
|
3366
|
+
children: /* @__PURE__ */ jsx(
|
|
3367
|
+
ChevronDown,
|
|
3368
|
+
{
|
|
3369
|
+
className: cn(
|
|
3370
|
+
"h-3.5 w-3.5 opacity-60 transition-transform",
|
|
3371
|
+
open && "rotate-180"
|
|
3372
|
+
)
|
|
3373
|
+
}
|
|
3374
|
+
)
|
|
3375
|
+
}
|
|
3376
|
+
)
|
|
3377
|
+
] }),
|
|
3378
|
+
/* @__PURE__ */ jsx(CollapsibleContent, { children: /* @__PURE__ */ jsx("div", { className: "ml-3 space-y-0.5 border-l border-sidebar-border pl-2", children: children.map((child) => /* @__PURE__ */ jsx(
|
|
3379
|
+
NavLinkRow,
|
|
3380
|
+
{
|
|
3381
|
+
item: child,
|
|
3382
|
+
collapsed: false,
|
|
3383
|
+
density,
|
|
3384
|
+
onNavClick,
|
|
3385
|
+
linkComponent,
|
|
3386
|
+
activePath,
|
|
3387
|
+
isActive
|
|
3388
|
+
},
|
|
3389
|
+
child.to + child.label
|
|
3390
|
+
)) }) })
|
|
3391
|
+
] }) });
|
|
3392
|
+
}
|
|
3393
|
+
function SidebarNav({
|
|
3394
|
+
navItems,
|
|
3395
|
+
secondaryNavItems,
|
|
3396
|
+
secondaryNavLabel,
|
|
3397
|
+
showSecondary,
|
|
3398
|
+
collapsed,
|
|
3399
|
+
density,
|
|
3400
|
+
onNavClick,
|
|
3401
|
+
linkComponent,
|
|
3402
|
+
activePath,
|
|
3403
|
+
isActive
|
|
3404
|
+
}) {
|
|
3405
|
+
return /* @__PURE__ */ jsxs(
|
|
3406
|
+
"nav",
|
|
3407
|
+
{
|
|
3408
|
+
className: cn(
|
|
3409
|
+
"flex-1 space-y-1 overflow-y-auto",
|
|
3410
|
+
collapsed ? "p-2" : "p-3"
|
|
3411
|
+
),
|
|
3412
|
+
"aria-label": "Main",
|
|
3413
|
+
children: [
|
|
3414
|
+
navItems.map((item) => /* @__PURE__ */ jsx(
|
|
3415
|
+
NavGroup,
|
|
3416
|
+
{
|
|
3417
|
+
item,
|
|
3418
|
+
collapsed,
|
|
3419
|
+
density,
|
|
3420
|
+
onNavClick,
|
|
3421
|
+
linkComponent,
|
|
3422
|
+
activePath,
|
|
3423
|
+
isActive
|
|
3424
|
+
},
|
|
3425
|
+
item.to + item.label
|
|
3426
|
+
)),
|
|
3427
|
+
showSecondary && secondaryNavItems && secondaryNavItems.length > 0 ? /* @__PURE__ */ jsxs(
|
|
3428
|
+
"div",
|
|
3429
|
+
{
|
|
3430
|
+
className: cn(
|
|
3431
|
+
"mt-4 space-y-1 border-t border-sidebar-border pt-4",
|
|
3432
|
+
collapsed && "mt-2 pt-2"
|
|
3433
|
+
),
|
|
3434
|
+
children: [
|
|
3435
|
+
!collapsed ? /* @__PURE__ */ jsx("p", { className: "mb-2 px-3 text-xs font-medium uppercase tracking-wider text-sidebar-foreground/40", children: secondaryNavLabel }) : null,
|
|
3436
|
+
secondaryNavItems.map((item) => /* @__PURE__ */ jsx(
|
|
3437
|
+
NavGroup,
|
|
3438
|
+
{
|
|
3439
|
+
item,
|
|
3440
|
+
collapsed,
|
|
3441
|
+
density,
|
|
3442
|
+
onNavClick,
|
|
3443
|
+
linkComponent,
|
|
3444
|
+
activePath,
|
|
3445
|
+
isActive
|
|
3446
|
+
},
|
|
3447
|
+
item.to + item.label
|
|
3448
|
+
))
|
|
3449
|
+
]
|
|
3450
|
+
}
|
|
3451
|
+
) : null
|
|
3452
|
+
]
|
|
3453
|
+
}
|
|
3454
|
+
);
|
|
3455
|
+
}
|
|
3456
|
+
function SidebarBrand({
|
|
2794
3457
|
appName,
|
|
2795
|
-
appLogo
|
|
3458
|
+
appLogo,
|
|
3459
|
+
logo,
|
|
3460
|
+
showLogo,
|
|
3461
|
+
showAppName,
|
|
3462
|
+
collapsed
|
|
2796
3463
|
}) {
|
|
2797
|
-
|
|
2798
|
-
|
|
2799
|
-
|
|
2800
|
-
|
|
3464
|
+
if (!showLogo && !showAppName) return null;
|
|
3465
|
+
return /* @__PURE__ */ jsxs(
|
|
3466
|
+
"div",
|
|
3467
|
+
{
|
|
3468
|
+
className: cn(
|
|
3469
|
+
"flex min-w-0 items-center gap-2",
|
|
3470
|
+
collapsed && "justify-center"
|
|
3471
|
+
),
|
|
3472
|
+
children: [
|
|
3473
|
+
showLogo ? logo ? /* @__PURE__ */ jsx("div", { className: "flex h-7 w-7 shrink-0 items-center justify-center overflow-hidden", children: logo }) : appLogo ? /* @__PURE__ */ jsx("div", { className: "flex h-7 w-7 shrink-0 items-center justify-center rounded-lg bg-primary", children: /* @__PURE__ */ jsx("span", { className: "text-sm font-bold text-primary-foreground", children: appLogo }) }) : null : null,
|
|
3474
|
+
showAppName && !collapsed ? /* @__PURE__ */ jsx("span", { className: "truncate font-semibold text-sidebar-foreground", children: appName }) : null
|
|
3475
|
+
]
|
|
3476
|
+
}
|
|
3477
|
+
);
|
|
2801
3478
|
}
|
|
2802
3479
|
function AppShell({
|
|
2803
3480
|
appName,
|
|
2804
3481
|
appLogo,
|
|
3482
|
+
logo,
|
|
2805
3483
|
navItems,
|
|
3484
|
+
secondaryNavItems,
|
|
3485
|
+
secondaryNavLabel = "More",
|
|
3486
|
+
mobileNavItems,
|
|
3487
|
+
headerSlot,
|
|
3488
|
+
collapsedHeaderSlot,
|
|
3489
|
+
footer,
|
|
3490
|
+
userSlot,
|
|
2806
3491
|
children,
|
|
2807
|
-
showThemeToggle
|
|
3492
|
+
showThemeToggle: showThemeToggleProp,
|
|
3493
|
+
options: optionsProp,
|
|
3494
|
+
optionsStorageKey,
|
|
3495
|
+
collapsed: collapsedProp,
|
|
3496
|
+
onCollapsedChange,
|
|
2808
3497
|
linkComponent = DefaultLink,
|
|
2809
3498
|
activePath,
|
|
2810
3499
|
isActive = isPathActive
|
|
2811
3500
|
}) {
|
|
2812
3501
|
const [mobileOpen, setMobileOpen] = useState(false);
|
|
2813
|
-
|
|
2814
|
-
|
|
2815
|
-
|
|
2816
|
-
|
|
2817
|
-
|
|
2818
|
-
|
|
2819
|
-
|
|
2820
|
-
|
|
2821
|
-
|
|
2822
|
-
|
|
2823
|
-
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
|
|
2827
|
-
|
|
2828
|
-
|
|
3502
|
+
const [userOverrides, setUserOverrides] = useState(
|
|
3503
|
+
{}
|
|
3504
|
+
);
|
|
3505
|
+
const [hydratedKey, setHydratedKey] = useState(
|
|
3506
|
+
optionsStorageKey ? void 0 : ""
|
|
3507
|
+
);
|
|
3508
|
+
useEffect(() => {
|
|
3509
|
+
setMobileOpen(false);
|
|
3510
|
+
}, [activePath]);
|
|
3511
|
+
const hasSecondary = Boolean(secondaryNavItems && secondaryNavItems.length);
|
|
3512
|
+
const hasUser = userSlot != null;
|
|
3513
|
+
const hasFooter = footer != null;
|
|
3514
|
+
const hasLogo = Boolean(logo || appLogo);
|
|
3515
|
+
useEffect(() => {
|
|
3516
|
+
if (!optionsStorageKey) {
|
|
3517
|
+
setUserOverrides({});
|
|
3518
|
+
setHydratedKey("");
|
|
3519
|
+
return;
|
|
3520
|
+
}
|
|
3521
|
+
setUserOverrides(readStoredOptions(optionsStorageKey));
|
|
3522
|
+
setHydratedKey(optionsStorageKey);
|
|
3523
|
+
}, [optionsStorageKey]);
|
|
3524
|
+
useEffect(() => {
|
|
3525
|
+
if (!optionsStorageKey || hydratedKey !== optionsStorageKey) return;
|
|
3526
|
+
writeStoredOptions(optionsStorageKey, userOverrides);
|
|
3527
|
+
}, [optionsStorageKey, userOverrides, hydratedKey]);
|
|
3528
|
+
const lockedOptions = optionsProp?.lockedOptions;
|
|
3529
|
+
const resolved = useMemo(() => {
|
|
3530
|
+
const merged = {
|
|
3531
|
+
...OPTION_DEFAULTS2,
|
|
3532
|
+
...showThemeToggleProp !== void 0 ? { showThemeToggle: showThemeToggleProp } : {},
|
|
3533
|
+
...optionsProp,
|
|
3534
|
+
...userOverrides
|
|
3535
|
+
};
|
|
3536
|
+
if (merged.configurable === void 0) {
|
|
3537
|
+
merged.configurable = hasSecondary || hasUser || hasFooter || Boolean(merged.showCollapse) || Boolean(merged.showThemeToggle);
|
|
3538
|
+
}
|
|
3539
|
+
if (lockedOptions?.length) {
|
|
3540
|
+
const defaults = OPTION_DEFAULTS2;
|
|
3541
|
+
const devOptions = optionsProp;
|
|
3542
|
+
const target = merged;
|
|
3543
|
+
for (const key of lockedOptions) {
|
|
3544
|
+
target[key] = devOptions?.[key] ?? defaults[key];
|
|
3545
|
+
}
|
|
3546
|
+
}
|
|
3547
|
+
return merged;
|
|
3548
|
+
}, [
|
|
3549
|
+
optionsProp,
|
|
3550
|
+
userOverrides,
|
|
3551
|
+
showThemeToggleProp,
|
|
3552
|
+
hasSecondary,
|
|
3553
|
+
hasUser,
|
|
3554
|
+
hasFooter,
|
|
3555
|
+
lockedOptions
|
|
3556
|
+
]);
|
|
3557
|
+
const patchOption = useCallback(
|
|
3558
|
+
(key, value) => {
|
|
3559
|
+
if (lockedOptions?.includes(key)) return;
|
|
3560
|
+
setUserOverrides((prev) => ({ ...prev, [key]: value }));
|
|
3561
|
+
},
|
|
3562
|
+
[lockedOptions]
|
|
3563
|
+
);
|
|
3564
|
+
const [uncontrolledCollapsed, setUncontrolledCollapsed] = useState(
|
|
3565
|
+
() => optionsProp?.defaultCollapsed ?? OPTION_DEFAULTS2.defaultCollapsed
|
|
3566
|
+
);
|
|
3567
|
+
useEffect(() => {
|
|
3568
|
+
if (!optionsStorageKey || collapsedProp !== void 0) return;
|
|
3569
|
+
const stored = readStoredCollapsed(optionsStorageKey);
|
|
3570
|
+
if (stored !== void 0) setUncontrolledCollapsed(stored);
|
|
3571
|
+
}, [optionsStorageKey, collapsedProp]);
|
|
3572
|
+
const isCollapseControlled = collapsedProp !== void 0;
|
|
3573
|
+
const collapsed = isCollapseControlled ? collapsedProp : uncontrolledCollapsed;
|
|
3574
|
+
const setCollapsed = (next) => {
|
|
3575
|
+
if (!isCollapseControlled) {
|
|
3576
|
+
setUncontrolledCollapsed(next);
|
|
3577
|
+
if (optionsStorageKey) writeStoredCollapsed(optionsStorageKey, next);
|
|
3578
|
+
}
|
|
3579
|
+
onCollapsedChange?.(next);
|
|
3580
|
+
};
|
|
3581
|
+
const density = resolved.density ?? "comfortable";
|
|
3582
|
+
const scrollWindow = resolved.scrollRoot === "window";
|
|
3583
|
+
const railExpanded = resolved.railWidth === "wide" ? "md:w-64" : "md:w-52";
|
|
3584
|
+
const railClass = collapsed ? "md:w-14" : railExpanded;
|
|
3585
|
+
const bottomItems = (mobileNavItems ?? navItems.filter((i) => !i.children || i.children.length === 0)).filter((i) => !i.disabled);
|
|
3586
|
+
const toggles = [
|
|
3587
|
+
{ key: "showLogo", label: "Logo", available: hasLogo },
|
|
3588
|
+
{ key: "showAppName", label: "App name", available: true },
|
|
3589
|
+
{
|
|
3590
|
+
key: "showThemeToggle",
|
|
3591
|
+
label: "Theme toggle",
|
|
3592
|
+
available: true
|
|
3593
|
+
},
|
|
3594
|
+
{ key: "showCollapse", label: "Collapse control", available: true },
|
|
3595
|
+
{
|
|
3596
|
+
key: "showSecondaryNav",
|
|
3597
|
+
label: "Secondary nav",
|
|
3598
|
+
available: hasSecondary
|
|
3599
|
+
},
|
|
3600
|
+
{ key: "showFooter", label: "Footer", available: hasFooter },
|
|
3601
|
+
{ key: "showUserSlot", label: "User block", available: hasUser },
|
|
3602
|
+
{
|
|
3603
|
+
key: "showMobileBottomNav",
|
|
3604
|
+
label: "Mobile bottom nav",
|
|
3605
|
+
available: bottomItems.length > 0
|
|
3606
|
+
}
|
|
3607
|
+
].filter((t) => t.available && !lockedOptions?.includes(t.key));
|
|
3608
|
+
const showTheme = resolved.showThemeToggle !== false;
|
|
3609
|
+
const showCollapseCtrl = resolved.showCollapse !== false;
|
|
3610
|
+
const showSecondary = hasSecondary && resolved.showSecondaryNav !== false;
|
|
3611
|
+
const showFooterSlot = hasFooter && resolved.showFooter !== false;
|
|
3612
|
+
const showUser = hasUser && resolved.showUserSlot !== false;
|
|
3613
|
+
const showMobileBottom = bottomItems.length > 0 && resolved.showMobileBottomNav !== false;
|
|
3614
|
+
const optionsControl = resolved.configurable && toggles.length > 0 ? /* @__PURE__ */ jsxs(Popover, { children: [
|
|
3615
|
+
/* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
|
|
3616
|
+
Button,
|
|
2829
3617
|
{
|
|
2830
|
-
|
|
2831
|
-
|
|
3618
|
+
type: "button",
|
|
3619
|
+
variant: "ghost",
|
|
3620
|
+
size: "icon",
|
|
3621
|
+
className: "h-8 w-8",
|
|
3622
|
+
title: "Sidebar options",
|
|
3623
|
+
"aria-label": "Sidebar options",
|
|
3624
|
+
"data-testid": "app-shell-options",
|
|
3625
|
+
testID: "app-shell-options",
|
|
3626
|
+
children: /* @__PURE__ */ jsx(SlidersHorizontal, { className: "h-4 w-4" })
|
|
2832
3627
|
}
|
|
2833
|
-
),
|
|
3628
|
+
) }),
|
|
3629
|
+
/* @__PURE__ */ jsxs(PopoverContent, { align: "start", side: "top", className: "w-64 space-y-3", children: [
|
|
3630
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
3631
|
+
/* @__PURE__ */ jsx("h4", { className: "text-sm font-semibold", children: "Sidebar options" }),
|
|
3632
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "Customize this sidebar. Saved on this device when a storage key is set." })
|
|
3633
|
+
] }),
|
|
3634
|
+
/* @__PURE__ */ jsx("ul", { className: "space-y-3", children: toggles.map((t) => {
|
|
3635
|
+
const checked = Boolean(resolved[t.key] ?? true);
|
|
3636
|
+
return /* @__PURE__ */ jsxs(
|
|
3637
|
+
"li",
|
|
3638
|
+
{
|
|
3639
|
+
className: "flex items-center justify-between gap-3",
|
|
3640
|
+
children: [
|
|
3641
|
+
/* @__PURE__ */ jsx(
|
|
3642
|
+
Label,
|
|
3643
|
+
{
|
|
3644
|
+
htmlFor: `app-shell-opt-${t.key}`,
|
|
3645
|
+
className: "text-sm font-normal",
|
|
3646
|
+
children: t.label
|
|
3647
|
+
}
|
|
3648
|
+
),
|
|
3649
|
+
/* @__PURE__ */ jsx(
|
|
3650
|
+
Switch,
|
|
3651
|
+
{
|
|
3652
|
+
id: `app-shell-opt-${t.key}`,
|
|
3653
|
+
checked,
|
|
3654
|
+
onCheckedChange: (v) => patchOption(t.key, v)
|
|
3655
|
+
}
|
|
3656
|
+
)
|
|
3657
|
+
]
|
|
3658
|
+
},
|
|
3659
|
+
t.key
|
|
3660
|
+
);
|
|
3661
|
+
}) }),
|
|
3662
|
+
optionsStorageKey && Object.keys(userOverrides).length > 0 ? /* @__PURE__ */ jsx(
|
|
3663
|
+
Button,
|
|
3664
|
+
{
|
|
3665
|
+
type: "button",
|
|
3666
|
+
variant: "ghost",
|
|
3667
|
+
size: "sm",
|
|
3668
|
+
className: "h-8 w-full text-xs",
|
|
3669
|
+
onClick: () => setUserOverrides({}),
|
|
3670
|
+
"data-testid": "app-shell-options-reset",
|
|
3671
|
+
"aria-label": "Reset sidebar options",
|
|
3672
|
+
testID: "app-shell-options-reset",
|
|
3673
|
+
children: "Reset to defaults"
|
|
3674
|
+
}
|
|
3675
|
+
) : null
|
|
3676
|
+
] })
|
|
3677
|
+
] }) : null;
|
|
3678
|
+
const sidebarChrome = (opts) => /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2834
3679
|
/* @__PURE__ */ jsxs(
|
|
2835
|
-
"
|
|
3680
|
+
"div",
|
|
2836
3681
|
{
|
|
2837
3682
|
className: cn(
|
|
2838
|
-
"
|
|
2839
|
-
|
|
3683
|
+
"flex h-14 shrink-0 items-center gap-2 border-b border-sidebar-border",
|
|
3684
|
+
collapsed && !opts.mobile ? "justify-center px-2" : "px-3"
|
|
2840
3685
|
),
|
|
2841
3686
|
children: [
|
|
2842
|
-
/* @__PURE__ */
|
|
2843
|
-
|
|
2844
|
-
|
|
3687
|
+
/* @__PURE__ */ jsx("div", { className: "min-w-0 flex-1", children: /* @__PURE__ */ jsx(
|
|
3688
|
+
SidebarBrand,
|
|
3689
|
+
{
|
|
3690
|
+
appName,
|
|
3691
|
+
appLogo,
|
|
3692
|
+
logo,
|
|
3693
|
+
showLogo: resolved.showLogo !== false,
|
|
3694
|
+
showAppName: resolved.showAppName !== false,
|
|
3695
|
+
collapsed: collapsed && !opts.mobile
|
|
3696
|
+
}
|
|
3697
|
+
) }),
|
|
3698
|
+
opts.mobile ? /* @__PURE__ */ jsx(
|
|
3699
|
+
Button,
|
|
3700
|
+
{
|
|
3701
|
+
variant: "ghost",
|
|
3702
|
+
size: "icon",
|
|
3703
|
+
onClick: () => setMobileOpen(false),
|
|
3704
|
+
"aria-label": "Close navigation",
|
|
3705
|
+
"data-testid": "app-shell-mobile-close",
|
|
3706
|
+
testID: "app-shell-mobile-close",
|
|
3707
|
+
children: /* @__PURE__ */ jsx(X, { className: "h-5 w-5" })
|
|
3708
|
+
}
|
|
3709
|
+
) : /* @__PURE__ */ jsxs("div", { className: "flex shrink-0 items-center gap-0.5", children: [
|
|
3710
|
+
showCollapseCtrl ? /* @__PURE__ */ jsx(
|
|
2845
3711
|
Button,
|
|
2846
3712
|
{
|
|
2847
3713
|
variant: "ghost",
|
|
2848
3714
|
size: "icon",
|
|
2849
|
-
|
|
2850
|
-
|
|
3715
|
+
className: "h-8 w-8",
|
|
3716
|
+
onClick: () => setCollapsed(!collapsed),
|
|
3717
|
+
title: collapsed ? "Expand sidebar" : "Collapse sidebar",
|
|
3718
|
+
"aria-label": collapsed ? "Expand sidebar" : "Collapse sidebar",
|
|
3719
|
+
"aria-pressed": collapsed,
|
|
3720
|
+
"data-testid": "app-shell-collapse",
|
|
3721
|
+
testID: "app-shell-collapse",
|
|
3722
|
+
children: collapsed ? /* @__PURE__ */ jsx(PanelLeft, { className: "h-4 w-4" }) : /* @__PURE__ */ jsx(PanelLeftClose, { className: "h-4 w-4" })
|
|
2851
3723
|
}
|
|
2852
|
-
)
|
|
2853
|
-
|
|
2854
|
-
|
|
2855
|
-
SidebarNav,
|
|
2856
|
-
{
|
|
2857
|
-
navItems,
|
|
2858
|
-
onNavClick: () => setMobileOpen(false),
|
|
2859
|
-
linkComponent,
|
|
2860
|
-
activePath,
|
|
2861
|
-
isActive
|
|
2862
|
-
}
|
|
2863
|
-
)
|
|
3724
|
+
) : null,
|
|
3725
|
+
!collapsed ? optionsControl : null
|
|
3726
|
+
] })
|
|
2864
3727
|
]
|
|
2865
3728
|
}
|
|
2866
3729
|
),
|
|
2867
|
-
/* @__PURE__ */
|
|
2868
|
-
|
|
3730
|
+
collapsed && !opts.mobile ? collapsedHeaderSlot ? /* @__PURE__ */ jsx("div", { className: "shrink-0 border-b border-sidebar-border p-2", children: collapsedHeaderSlot }) : null : headerSlot ? /* @__PURE__ */ jsx("div", { className: "shrink-0 border-b border-sidebar-border", children: headerSlot }) : null,
|
|
3731
|
+
/* @__PURE__ */ jsx(
|
|
3732
|
+
SidebarNav,
|
|
3733
|
+
{
|
|
3734
|
+
navItems,
|
|
3735
|
+
secondaryNavItems,
|
|
3736
|
+
secondaryNavLabel,
|
|
3737
|
+
showSecondary,
|
|
3738
|
+
collapsed: collapsed && !opts.mobile,
|
|
3739
|
+
density,
|
|
3740
|
+
onNavClick: opts.mobile ? () => setMobileOpen(false) : void 0,
|
|
3741
|
+
linkComponent,
|
|
3742
|
+
activePath,
|
|
3743
|
+
isActive
|
|
3744
|
+
}
|
|
3745
|
+
),
|
|
3746
|
+
(showFooterSlot || showUser || showTheme || collapsed && optionsControl) && !opts.mobile ? /* @__PURE__ */ jsxs("div", { className: "mt-auto shrink-0 space-y-2 border-t border-sidebar-border p-2", children: [
|
|
3747
|
+
showFooterSlot && !collapsed ? footer : null,
|
|
3748
|
+
showUser ? /* @__PURE__ */ jsx(
|
|
3749
|
+
"div",
|
|
3750
|
+
{
|
|
3751
|
+
className: cn(
|
|
3752
|
+
collapsed && "flex justify-center [&_[data-shell-user-detail]]:hidden"
|
|
3753
|
+
),
|
|
3754
|
+
children: userSlot
|
|
3755
|
+
}
|
|
3756
|
+
) : null,
|
|
3757
|
+
/* @__PURE__ */ jsxs(
|
|
3758
|
+
"div",
|
|
3759
|
+
{
|
|
3760
|
+
className: cn(
|
|
3761
|
+
"flex items-center gap-1",
|
|
3762
|
+
collapsed ? "flex-col" : "justify-end"
|
|
3763
|
+
),
|
|
3764
|
+
children: [
|
|
3765
|
+
collapsed ? optionsControl : null,
|
|
3766
|
+
showTheme ? /* @__PURE__ */ jsx(ThemeToggle, { testId: "app-shell-theme-toggle-desktop" }) : null
|
|
3767
|
+
]
|
|
3768
|
+
}
|
|
3769
|
+
)
|
|
3770
|
+
] }) : null,
|
|
3771
|
+
opts.mobile && (showFooterSlot || showUser || showTheme) ? /* @__PURE__ */ jsxs("div", { className: "mt-auto shrink-0 space-y-2 border-t border-sidebar-border p-3", children: [
|
|
3772
|
+
showFooterSlot ? footer : null,
|
|
3773
|
+
showUser ? userSlot : null,
|
|
3774
|
+
showTheme ? /* @__PURE__ */ jsx("div", { className: "flex justify-end", children: /* @__PURE__ */ jsx(ThemeToggle, { testId: "app-shell-theme-toggle-mobile-drawer" }) }) : null
|
|
3775
|
+
] }) : null
|
|
3776
|
+
] });
|
|
3777
|
+
return /* @__PURE__ */ jsxs(
|
|
3778
|
+
"div",
|
|
3779
|
+
{
|
|
3780
|
+
className: cn(
|
|
3781
|
+
"flex",
|
|
3782
|
+
scrollWindow ? "min-h-screen" : "h-screen overflow-hidden"
|
|
3783
|
+
),
|
|
3784
|
+
children: [
|
|
2869
3785
|
/* @__PURE__ */ jsx(
|
|
2870
|
-
|
|
3786
|
+
"aside",
|
|
2871
3787
|
{
|
|
2872
|
-
|
|
2873
|
-
|
|
2874
|
-
|
|
2875
|
-
|
|
3788
|
+
className: cn(
|
|
3789
|
+
"hidden flex-col border-r border-sidebar-border bg-sidebar transition-[width] duration-200 md:flex",
|
|
3790
|
+
scrollWindow && "sticky top-0 h-screen",
|
|
3791
|
+
railClass
|
|
3792
|
+
),
|
|
3793
|
+
"data-collapsed": collapsed || void 0,
|
|
3794
|
+
"data-testid": "app-shell-sidebar",
|
|
3795
|
+
children: sidebarChrome({})
|
|
2876
3796
|
}
|
|
2877
3797
|
),
|
|
2878
|
-
/* @__PURE__ */ jsx(
|
|
2879
|
-
|
|
2880
|
-
|
|
2881
|
-
|
|
2882
|
-
|
|
2883
|
-
|
|
2884
|
-
|
|
2885
|
-
|
|
2886
|
-
|
|
3798
|
+
mobileOpen ? /* @__PURE__ */ jsx(
|
|
3799
|
+
"div",
|
|
3800
|
+
{
|
|
3801
|
+
className: "fixed inset-0 z-40 bg-black/50 md:hidden",
|
|
3802
|
+
onClick: () => setMobileOpen(false),
|
|
3803
|
+
"aria-hidden": true
|
|
3804
|
+
}
|
|
3805
|
+
) : null,
|
|
3806
|
+
/* @__PURE__ */ jsx(
|
|
3807
|
+
"aside",
|
|
2887
3808
|
{
|
|
2888
|
-
href: item.to,
|
|
2889
|
-
"aria-current": active ? "page" : void 0,
|
|
2890
3809
|
className: cn(
|
|
2891
|
-
"flex
|
|
2892
|
-
|
|
3810
|
+
"fixed inset-y-0 left-0 z-50 flex w-60 flex-col border-r border-sidebar-border bg-sidebar transition-transform md:hidden",
|
|
3811
|
+
mobileOpen ? "translate-x-0" : "-translate-x-full pointer-events-none"
|
|
3812
|
+
),
|
|
3813
|
+
id: "app-shell-mobile-nav",
|
|
3814
|
+
"aria-hidden": !mobileOpen,
|
|
3815
|
+
inert: !mobileOpen || void 0,
|
|
3816
|
+
children: sidebarChrome({ mobile: true })
|
|
3817
|
+
}
|
|
3818
|
+
),
|
|
3819
|
+
/* @__PURE__ */ jsxs(
|
|
3820
|
+
"div",
|
|
3821
|
+
{
|
|
3822
|
+
className: cn(
|
|
3823
|
+
"flex min-w-0 flex-1 flex-col",
|
|
3824
|
+
!scrollWindow && "overflow-hidden"
|
|
2893
3825
|
),
|
|
2894
3826
|
children: [
|
|
2895
|
-
/* @__PURE__ */
|
|
2896
|
-
|
|
3827
|
+
/* @__PURE__ */ jsxs(
|
|
3828
|
+
"header",
|
|
3829
|
+
{
|
|
3830
|
+
className: cn(
|
|
3831
|
+
"flex h-14 items-center gap-3 border-b px-4 md:hidden",
|
|
3832
|
+
scrollWindow && "sticky top-0 z-20 bg-background"
|
|
3833
|
+
),
|
|
3834
|
+
children: [
|
|
3835
|
+
/* @__PURE__ */ jsx(
|
|
3836
|
+
Button,
|
|
3837
|
+
{
|
|
3838
|
+
variant: "ghost",
|
|
3839
|
+
size: "icon",
|
|
3840
|
+
onClick: () => setMobileOpen(true),
|
|
3841
|
+
"aria-label": "Open navigation",
|
|
3842
|
+
"aria-expanded": mobileOpen,
|
|
3843
|
+
"aria-controls": "app-shell-mobile-nav",
|
|
3844
|
+
"data-testid": "app-shell-mobile-open",
|
|
3845
|
+
testID: "app-shell-mobile-open",
|
|
3846
|
+
children: /* @__PURE__ */ jsx(Menu, { className: "h-5 w-5" })
|
|
3847
|
+
}
|
|
3848
|
+
),
|
|
3849
|
+
/* @__PURE__ */ jsx("span", { className: "flex-1 truncate font-semibold", children: appName }),
|
|
3850
|
+
showTheme ? /* @__PURE__ */ jsx(ThemeToggle, { testId: "app-shell-theme-toggle-mobile-header" }) : null
|
|
3851
|
+
]
|
|
3852
|
+
}
|
|
3853
|
+
),
|
|
3854
|
+
/* @__PURE__ */ jsx(
|
|
3855
|
+
"main",
|
|
3856
|
+
{
|
|
3857
|
+
className: cn(
|
|
3858
|
+
"flex-1",
|
|
3859
|
+
!scrollWindow && "overflow-y-auto",
|
|
3860
|
+
showMobileBottom ? "pb-16 md:pb-0" : "pb-0"
|
|
3861
|
+
),
|
|
3862
|
+
children: /* @__PURE__ */ jsx("div", { className: "min-w-0 p-4 sm:p-6 lg:p-8", children })
|
|
3863
|
+
}
|
|
3864
|
+
),
|
|
3865
|
+
showMobileBottom ? /* @__PURE__ */ jsx(
|
|
3866
|
+
"nav",
|
|
3867
|
+
{
|
|
3868
|
+
className: "fixed bottom-0 left-0 right-0 z-30 flex border-t bg-background md:hidden",
|
|
3869
|
+
"aria-label": "Primary",
|
|
3870
|
+
children: bottomItems.map((item) => {
|
|
3871
|
+
const LinkComp = linkComponent;
|
|
3872
|
+
const active = itemIsActive(item, activePath, isActive);
|
|
3873
|
+
return /* @__PURE__ */ jsxs(
|
|
3874
|
+
LinkComp,
|
|
3875
|
+
{
|
|
3876
|
+
href: item.to,
|
|
3877
|
+
title: item.label,
|
|
3878
|
+
"aria-label": item.label,
|
|
3879
|
+
"aria-current": active ? "page" : void 0,
|
|
3880
|
+
className: cn(
|
|
3881
|
+
"flex flex-1 flex-col items-center gap-1 py-2 text-xs font-medium transition-colors",
|
|
3882
|
+
active ? "text-primary" : "text-muted-foreground"
|
|
3883
|
+
),
|
|
3884
|
+
children: [
|
|
3885
|
+
/* @__PURE__ */ jsx(item.icon, { className: "h-5 w-5" }),
|
|
3886
|
+
/* @__PURE__ */ jsx("span", { className: "max-w-full truncate px-0.5", children: item.label })
|
|
3887
|
+
]
|
|
3888
|
+
},
|
|
3889
|
+
item.to + item.label
|
|
3890
|
+
);
|
|
3891
|
+
})
|
|
3892
|
+
}
|
|
3893
|
+
) : null
|
|
2897
3894
|
]
|
|
2898
|
-
}
|
|
2899
|
-
|
|
2900
|
-
|
|
2901
|
-
|
|
2902
|
-
|
|
2903
|
-
] });
|
|
2904
|
-
}
|
|
2905
|
-
function PageHeader({ title, subtitle, icon: Icon, actions }) {
|
|
2906
|
-
return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between", children: [
|
|
2907
|
-
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 min-w-0", children: [
|
|
2908
|
-
Icon && /* @__PURE__ */ jsx("div", { className: "flex h-10 w-10 shrink-0 items-center justify-center rounded-xl bg-primary/10 overflow-hidden", children: /* @__PURE__ */ jsx(Icon, { className: "h-5 w-5 text-primary" }) }),
|
|
2909
|
-
/* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
|
|
2910
|
-
/* @__PURE__ */ jsx("h1", { className: "text-xl sm:text-2xl font-bold font-display tracking-tight truncate", children: title }),
|
|
2911
|
-
subtitle && /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground truncate", children: subtitle })
|
|
2912
|
-
] })
|
|
2913
|
-
] }),
|
|
2914
|
-
actions && /* @__PURE__ */ jsx("div", { className: "flex items-center gap-2 shrink-0", children: actions })
|
|
2915
|
-
] });
|
|
3895
|
+
}
|
|
3896
|
+
)
|
|
3897
|
+
]
|
|
3898
|
+
}
|
|
3899
|
+
);
|
|
2916
3900
|
}
|
|
2917
3901
|
|
|
2918
3902
|
export { AppShell, BadgeCell, CurrencyCell, DataBulkBar, DataFilters, DataGrid, DataList, DataPageShell, DataTable, DateCell, DetailPanel, EmptyState, FieldArrayRenderer, FileUploadField, FilterCombobox, KanbanBoard, LinkCell, NumberCell, PageHeader, RelationField, ResourceForm, ResourcePage, StatCard, ThumbnailCell, ViewSettings, ViewToggle, WizardForm };
|