@clayer/dashboard-layout 1.0.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/index.js ADDED
@@ -0,0 +1,1393 @@
1
+ // src/dashboard-layout.tsx
2
+ import {
3
+ Fragment,
4
+ useCallback,
5
+ useEffect,
6
+ useMemo,
7
+ useRef,
8
+ useState
9
+ } from "react";
10
+ import { useTheme } from "@clayer/theme";
11
+ import { cn } from "@clayer/utils";
12
+ import { Fragment as Fragment2, jsx, jsxs } from "react/jsx-runtime";
13
+ var defaultSidebar = {
14
+ collapsible: true,
15
+ defaultCollapsed: false,
16
+ width: 280,
17
+ collapsedWidth: 80,
18
+ showTooltipsWhenCollapsed: true,
19
+ closeOnNavigateMobile: true
20
+ };
21
+ var densityClasses = {
22
+ compact: {
23
+ header: "h-14",
24
+ content: "p-4",
25
+ navItem: "min-h-9 px-2.5 py-2 text-sm",
26
+ childItem: "min-h-8 px-3 py-1.5 text-sm"
27
+ },
28
+ comfortable: {
29
+ header: "h-16",
30
+ content: "p-4 lg:p-6",
31
+ navItem: "min-h-10 px-3 py-2 text-sm",
32
+ childItem: "min-h-9 px-3 py-2 text-sm"
33
+ },
34
+ spacious: {
35
+ header: "h-20",
36
+ content: "p-5 lg:p-8",
37
+ navItem: "min-h-11 px-4 py-2.5 text-base",
38
+ childItem: "min-h-10 px-4 py-2 text-sm"
39
+ }
40
+ };
41
+ var maxWidthClasses = {
42
+ none: "max-w-none",
43
+ "screen-xl": "max-w-screen-xl",
44
+ "screen-2xl": "max-w-screen-2xl",
45
+ "7xl": "max-w-7xl"
46
+ };
47
+ var layoutPaddingClasses = {
48
+ none: "p-0",
49
+ sm: "p-0 lg:p-2",
50
+ md: "p-0 lg:p-3",
51
+ lg: "p-0 lg:p-4"
52
+ };
53
+ var layoutRadiusClasses = {
54
+ none: "rounded-none",
55
+ sm: "rounded-none lg:rounded-md",
56
+ md: "rounded-none lg:rounded-lg",
57
+ lg: "rounded-none lg:rounded-xl",
58
+ xl: "rounded-none lg:rounded-2xl"
59
+ };
60
+ var layoutShadowClasses = {
61
+ none: "shadow-none",
62
+ sm: "shadow-sm",
63
+ md: "shadow-md",
64
+ lg: "shadow-xl"
65
+ };
66
+ var userMenuRadiusClasses = {
67
+ none: "rounded-none",
68
+ sm: "rounded-md",
69
+ md: "rounded-lg",
70
+ lg: "rounded-xl",
71
+ full: "rounded-full"
72
+ };
73
+ var userMenuPaddingClasses = {
74
+ none: "p-0",
75
+ sm: "p-1.5",
76
+ md: "p-2"
77
+ };
78
+ var userMenuBackgroundClasses = {
79
+ transparent: "bg-transparent",
80
+ background: "bg-background",
81
+ muted: "bg-muted/60"
82
+ };
83
+ var userMenuShadowClasses = {
84
+ none: "shadow-none",
85
+ sm: "shadow-sm",
86
+ md: "shadow-md"
87
+ };
88
+ var defaultLayoutConfig = {
89
+ padding: "none",
90
+ bordered: false,
91
+ radius: "none",
92
+ shadow: "none",
93
+ borders: {
94
+ sidebar: true,
95
+ header: true,
96
+ footer: true,
97
+ mobileBottomNav: true
98
+ },
99
+ background: {
100
+ root: "bg-background",
101
+ shell: "bg-background",
102
+ sidebar: "bg-sidebar",
103
+ header: "bg-background/85",
104
+ breadcrumb: "bg-background/70",
105
+ main: "bg-muted/30",
106
+ content: void 0,
107
+ footer: "bg-background",
108
+ mobileBottomNav: "bg-background"
109
+ },
110
+ backgroundComponents: {}
111
+ };
112
+ function resolveSurfaceBackgroundClass({
113
+ value,
114
+ hasComponent
115
+ }) {
116
+ if (hasComponent || !value) {
117
+ return void 0;
118
+ }
119
+ if (value === "transparent") {
120
+ return "bg-transparent";
121
+ }
122
+ if (value === "inherit") {
123
+ return "bg-inherit";
124
+ }
125
+ return value;
126
+ }
127
+ function SurfaceBackground({ component }) {
128
+ if (!component) {
129
+ return null;
130
+ }
131
+ if (typeof component === "object" && component !== null && !Array.isArray(component) && "content" in component) {
132
+ return /* @__PURE__ */ jsx(
133
+ "div",
134
+ {
135
+ className: cn(
136
+ "absolute inset-0 z-0 h-full w-full",
137
+ !component.interactive && "pointer-events-none",
138
+ component.className
139
+ ),
140
+ style: component.style,
141
+ "aria-hidden": !component.interactive,
142
+ children: component.content
143
+ }
144
+ );
145
+ }
146
+ return /* @__PURE__ */ jsx("div", { className: "pointer-events-none absolute inset-0 z-0 h-full w-full", "aria-hidden": true, children: component });
147
+ }
148
+ function useControllableState({
149
+ value,
150
+ defaultValue,
151
+ onChange
152
+ }) {
153
+ const [internalValue, setInternalValue] = useState(defaultValue);
154
+ const isControlled = value !== void 0;
155
+ const currentValue = isControlled ? value : internalValue;
156
+ const setValue = useCallback(
157
+ (nextValue) => {
158
+ if (!isControlled) {
159
+ setInternalValue(nextValue);
160
+ }
161
+ onChange?.(nextValue);
162
+ },
163
+ [isControlled, onChange]
164
+ );
165
+ return [currentValue, setValue];
166
+ }
167
+ function defaultIsItemActive(item, currentPath) {
168
+ if (!currentPath) {
169
+ return false;
170
+ }
171
+ return item.exact ? currentPath === item.href : currentPath === item.href || currentPath.startsWith(`${item.href}/`);
172
+ }
173
+ function DefaultLink({ href, children, className, onClick, external, "aria-current": ariaCurrent }) {
174
+ return /* @__PURE__ */ jsx(
175
+ "a",
176
+ {
177
+ href,
178
+ className,
179
+ onClick,
180
+ target: external ? "_blank" : void 0,
181
+ rel: external ? "noreferrer" : void 0,
182
+ "aria-current": ariaCurrent,
183
+ children
184
+ }
185
+ );
186
+ }
187
+ function getThemeStyles(styles) {
188
+ return styles;
189
+ }
190
+ function Badge({ children, className }) {
191
+ if (!children) {
192
+ return null;
193
+ }
194
+ return /* @__PURE__ */ jsx(
195
+ "span",
196
+ {
197
+ className: cn(
198
+ "ml-auto rounded-full bg-[hsl(var(--cx-sidebar-foreground)/0.12)] px-2 py-0.5 text-[11px] font-medium leading-5",
199
+ className
200
+ ),
201
+ children
202
+ }
203
+ );
204
+ }
205
+ function NavIcon({ icon: Icon, className }) {
206
+ if (!Icon) {
207
+ return /* @__PURE__ */ jsx("span", { className: cn("grid size-5 shrink-0 place-items-center leading-none", className), "aria-hidden": true, children: /* @__PURE__ */ jsx("span", { className: "size-2 rounded-full bg-current opacity-60" }) });
208
+ }
209
+ return /* @__PURE__ */ jsx("span", { className: cn("grid size-5 shrink-0 place-items-center leading-none", className), "aria-hidden": true, children: /* @__PURE__ */ jsx(Icon, { className: "block size-4 shrink-0 leading-none", "aria-hidden": true }) });
210
+ }
211
+ function SidebarLink({
212
+ item,
213
+ active,
214
+ collapsed,
215
+ closeMobile,
216
+ onExpandPersist,
217
+ onCollapsedTooltipChange,
218
+ renderLink,
219
+ styles,
220
+ density
221
+ }) {
222
+ const handleClick = () => {
223
+ onCollapsedTooltipChange?.(null);
224
+ onExpandPersist?.();
225
+ item.onClick?.();
226
+ closeMobile?.();
227
+ };
228
+ const handleTooltipShow = (element) => {
229
+ if (!collapsed) {
230
+ return;
231
+ }
232
+ const rect = element.getBoundingClientRect();
233
+ onCollapsedTooltipChange?.({
234
+ label: item.label,
235
+ top: rect.top + rect.height / 2,
236
+ left: rect.right + 12
237
+ });
238
+ };
239
+ const className = cn(
240
+ "group relative flex w-full items-center gap-3 rounded-lg focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
241
+ active ? "bg-sidebar-active text-white shadow-sm" : "text-sidebar-foreground/78 transition-colors hover:bg-[hsl(var(--cx-sidebar-foreground)/0.08)] hover:text-sidebar-foreground motion-reduce:transition-none",
242
+ densityClasses[density].navItem,
243
+ item.disabled && "pointer-events-none opacity-45",
244
+ collapsed && "justify-center px-2",
245
+ styles?.sidebarNavItem,
246
+ active && styles?.sidebarNavItemActive
247
+ );
248
+ return /* @__PURE__ */ jsx(
249
+ "li",
250
+ {
251
+ onMouseEnter: (event) => handleTooltipShow(event.currentTarget),
252
+ onMouseLeave: () => onCollapsedTooltipChange?.(null),
253
+ onFocus: (event) => handleTooltipShow(event.currentTarget),
254
+ onBlur: () => onCollapsedTooltipChange?.(null),
255
+ children: renderLink({
256
+ href: item.href,
257
+ external: item.external,
258
+ onClick: handleClick,
259
+ className,
260
+ "aria-current": active ? "page" : void 0,
261
+ children: /* @__PURE__ */ jsxs(Fragment2, { children: [
262
+ /* @__PURE__ */ jsx(NavIcon, { icon: item.icon, className: styles?.sidebarNavItemIcon }),
263
+ /* @__PURE__ */ jsx("span", { className: cn("min-w-0 flex-1 truncate", collapsed && "sr-only", styles?.sidebarNavItemLabel), children: item.label }),
264
+ !collapsed ? /* @__PURE__ */ jsx(Badge, { className: styles?.sidebarNavItemBadge, children: item.badge }) : null
265
+ ] })
266
+ })
267
+ }
268
+ );
269
+ }
270
+ function SidebarGroup({
271
+ item,
272
+ collapsed,
273
+ currentPath,
274
+ isItemActive,
275
+ closeMobile,
276
+ onExpandPersist,
277
+ onCollapsedTooltipChange,
278
+ renderLink,
279
+ styles,
280
+ density
281
+ }) {
282
+ const hasActiveChild = item.children.some((child) => isItemActive(child, currentPath));
283
+ const [open, setOpen] = useState(item.defaultOpen ?? hasActiveChild);
284
+ useEffect(() => {
285
+ if (hasActiveChild) {
286
+ setOpen(true);
287
+ }
288
+ }, [hasActiveChild]);
289
+ const handleTooltipShow = (element) => {
290
+ if (!collapsed) {
291
+ return;
292
+ }
293
+ const rect = element.getBoundingClientRect();
294
+ onCollapsedTooltipChange?.({
295
+ label: item.label,
296
+ top: rect.top + rect.height / 2,
297
+ left: rect.right + 12
298
+ });
299
+ };
300
+ return /* @__PURE__ */ jsxs(
301
+ "li",
302
+ {
303
+ className: cn("space-y-1", styles?.sidebarGroup),
304
+ onMouseLeave: () => onCollapsedTooltipChange?.(null),
305
+ onBlur: () => onCollapsedTooltipChange?.(null),
306
+ children: [
307
+ /* @__PURE__ */ jsxs(
308
+ "button",
309
+ {
310
+ type: "button",
311
+ onClick: () => {
312
+ onCollapsedTooltipChange?.(null);
313
+ onExpandPersist?.();
314
+ setOpen((value) => !value);
315
+ },
316
+ onMouseEnter: (event) => handleTooltipShow(event.currentTarget),
317
+ onFocus: (event) => handleTooltipShow(event.currentTarget),
318
+ "aria-expanded": open,
319
+ className: cn(
320
+ "group relative flex w-full items-center gap-3 rounded-lg focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
321
+ hasActiveChild ? "bg-sidebar-active text-white shadow-sm" : "text-sidebar-foreground/78 transition-colors hover:bg-[hsl(var(--cx-sidebar-foreground)/0.08)] hover:text-sidebar-foreground motion-reduce:transition-none",
322
+ densityClasses[density].navItem,
323
+ collapsed && "justify-center px-2",
324
+ styles?.sidebarGroupTrigger,
325
+ hasActiveChild && styles?.sidebarNavItemActive
326
+ ),
327
+ children: [
328
+ /* @__PURE__ */ jsx(NavIcon, { icon: item.icon, className: styles?.sidebarNavItemIcon }),
329
+ /* @__PURE__ */ jsx("span", { className: cn("min-w-0 flex-1 truncate text-left", collapsed && "sr-only", styles?.sidebarNavItemLabel), children: item.label }),
330
+ !collapsed ? /* @__PURE__ */ jsx(Badge, { className: styles?.sidebarNavItemBadge, children: item.badge }) : null,
331
+ !collapsed ? /* @__PURE__ */ jsx("span", { className: cn("text-xs transition-transform", open && "rotate-90"), children: "\u203A" }) : null
332
+ ]
333
+ }
334
+ ),
335
+ !collapsed && open ? /* @__PURE__ */ jsx(
336
+ "ul",
337
+ {
338
+ className: cn(
339
+ "ml-5 space-y-1 border-l border-[hsl(var(--cx-sidebar-foreground)/0.12)] pl-3",
340
+ styles?.sidebarGroupContent
341
+ ),
342
+ children: item.children.filter((child) => !child.hidden).map((child) => /* @__PURE__ */ jsx(
343
+ SidebarLink,
344
+ {
345
+ item: child,
346
+ active: isItemActive(child, currentPath),
347
+ collapsed: false,
348
+ closeMobile,
349
+ onExpandPersist,
350
+ onCollapsedTooltipChange,
351
+ renderLink,
352
+ styles: { ...styles, sidebarNavItem: cn(styles?.sidebarNavItem, styles?.sidebarGroupChildItem) },
353
+ density
354
+ },
355
+ child.href
356
+ ))
357
+ }
358
+ ) : null
359
+ ]
360
+ }
361
+ );
362
+ }
363
+ function SidebarNav({
364
+ items,
365
+ collapsed,
366
+ currentPath,
367
+ isItemActive,
368
+ closeMobile,
369
+ onExpandPersist,
370
+ onCollapsedTooltipChange,
371
+ renderLink,
372
+ styles,
373
+ density
374
+ }) {
375
+ return /* @__PURE__ */ jsx(
376
+ "nav",
377
+ {
378
+ className: cn(
379
+ "min-h-0 flex-1 overflow-y-auto overflow-x-hidden px-3 py-4",
380
+ styles?.sidebarNav
381
+ ),
382
+ "aria-label": "Sidebar navigation",
383
+ children: /* @__PURE__ */ jsx("ul", { className: "space-y-1", children: items.filter((item) => item.type === "divider" || !item.hidden).map((item, index) => {
384
+ if (item.type === "divider") {
385
+ return /* @__PURE__ */ jsx("li", { className: cn("px-3 py-3", styles?.sidebarNavSection), children: item.label && !collapsed ? /* @__PURE__ */ jsx("span", { className: "text-xs font-semibold uppercase tracking-wide text-sidebar-foreground/45", children: item.label }) : /* @__PURE__ */ jsx("span", { className: "block border-t border-[hsl(var(--cx-sidebar-foreground)/0.12)]" }) }, `${item.label ?? "divider"}-${index}`);
386
+ }
387
+ if (item.type === "group") {
388
+ return /* @__PURE__ */ jsx(
389
+ SidebarGroup,
390
+ {
391
+ item,
392
+ collapsed,
393
+ currentPath,
394
+ isItemActive,
395
+ closeMobile,
396
+ onExpandPersist,
397
+ onCollapsedTooltipChange,
398
+ renderLink,
399
+ styles,
400
+ density
401
+ },
402
+ item.label
403
+ );
404
+ }
405
+ return /* @__PURE__ */ jsx(
406
+ SidebarLink,
407
+ {
408
+ item,
409
+ active: isItemActive(item, currentPath),
410
+ collapsed,
411
+ closeMobile,
412
+ onExpandPersist,
413
+ onCollapsedTooltipChange,
414
+ renderLink,
415
+ styles,
416
+ density
417
+ },
418
+ item.href
419
+ );
420
+ }) })
421
+ }
422
+ );
423
+ }
424
+ function DashboardSidebar({
425
+ logo,
426
+ sidebarHeader,
427
+ sidebarBottomPanel,
428
+ sidebarFooter,
429
+ items,
430
+ collapsed,
431
+ onCollapsedChange,
432
+ sidebar,
433
+ currentPath,
434
+ isItemActive,
435
+ closeMobile,
436
+ renderLink,
437
+ styles,
438
+ density,
439
+ layout,
440
+ mobile
441
+ }) {
442
+ const effectiveCollapsed = collapsed;
443
+ const width = mobile ? sidebar.width : effectiveCollapsed ? sidebar.collapsedWidth : sidebar.width;
444
+ const [collapsedTooltip, setCollapsedTooltip] = useState(null);
445
+ const fallbackBottomPanel = sidebarFooter ? {
446
+ content: sidebarFooter
447
+ } : void 0;
448
+ const bottomPanel = sidebarBottomPanel ?? fallbackBottomPanel;
449
+ const showBottomPanel = bottomPanel && (mobile || !effectiveCollapsed);
450
+ const [bottomPanelCollapsed, setBottomPanelCollapsed] = useControllableState({
451
+ value: bottomPanel?.collapsed,
452
+ defaultValue: bottomPanel?.defaultCollapsed ?? false,
453
+ onChange: bottomPanel?.onCollapsedChange
454
+ });
455
+ const isSidebarCompact = !mobile && effectiveCollapsed;
456
+ const isPanelBodyCollapsed = Boolean(bottomPanel?.collapsible && bottomPanelCollapsed && !isSidebarCompact);
457
+ const bottomPanelContent = isSidebarCompact ? bottomPanel?.collapsedContent : bottomPanel?.content;
458
+ const bottomPanelHeight = isPanelBodyCollapsed ? bottomPanel?.collapsedHeight : bottomPanel?.height;
459
+ const bottomPanelStyle = bottomPanelHeight ? { height: bottomPanelHeight } : void 0;
460
+ const handleExpandPersist = () => {
461
+ if (!mobile && collapsed) {
462
+ setCollapsedTooltip(null);
463
+ onCollapsedChange(false);
464
+ }
465
+ };
466
+ const handleSidebarToggle = () => {
467
+ if (mobile) {
468
+ return;
469
+ }
470
+ setCollapsedTooltip(null);
471
+ onCollapsedChange(!effectiveCollapsed);
472
+ };
473
+ return /* @__PURE__ */ jsxs(
474
+ "aside",
475
+ {
476
+ className: cn(
477
+ "relative flex h-full min-h-0 shrink-0 flex-col overflow-hidden text-sidebar-foreground transition-[width] duration-200 ease-out motion-reduce:transition-none",
478
+ resolveSurfaceBackgroundClass({
479
+ value: layout.background.sidebar,
480
+ hasComponent: Boolean(layout.backgroundComponents.sidebar)
481
+ }),
482
+ layout.borders.sidebar && "border-r border-[hsl(var(--cx-sidebar-foreground)/0.12)]",
483
+ !mobile && "hidden lg:flex",
484
+ styles?.sidebar
485
+ ),
486
+ style: { width },
487
+ children: [
488
+ /* @__PURE__ */ jsx(SurfaceBackground, { component: layout.backgroundComponents.sidebar }),
489
+ /* @__PURE__ */ jsxs("div", { className: cn("relative z-10 flex min-h-0 flex-1 flex-col", styles?.sidebarInner), children: [
490
+ /* @__PURE__ */ jsxs(
491
+ "div",
492
+ {
493
+ className: cn(
494
+ "flex h-16 shrink-0 items-center gap-3 border-b border-[hsl(var(--cx-sidebar-foreground)/0.12)] px-4",
495
+ collapsed && !mobile && "justify-center px-2",
496
+ styles?.sidebarHeader
497
+ ),
498
+ children: [
499
+ /* @__PURE__ */ jsx("div", { className: cn("min-w-0 flex-1", effectiveCollapsed && !mobile && "sr-only", styles?.sidebarLogo), children: sidebarHeader ?? logo }),
500
+ sidebar.collapsible && !mobile ? /* @__PURE__ */ jsx(
501
+ "button",
502
+ {
503
+ type: "button",
504
+ onClick: handleSidebarToggle,
505
+ className: cn(
506
+ "grid size-9 place-items-center rounded-lg text-sidebar-foreground/70 hover:bg-[hsl(var(--cx-sidebar-foreground)/0.08)] hover:text-sidebar-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
507
+ styles?.sidebarToggle
508
+ ),
509
+ "aria-label": effectiveCollapsed ? "Expand sidebar" : "Collapse sidebar",
510
+ children: effectiveCollapsed ? "\u203A" : "\u2039"
511
+ }
512
+ ) : null
513
+ ]
514
+ }
515
+ ),
516
+ /* @__PURE__ */ jsx(
517
+ SidebarNav,
518
+ {
519
+ items,
520
+ collapsed: effectiveCollapsed && !mobile,
521
+ currentPath,
522
+ isItemActive,
523
+ closeMobile,
524
+ onExpandPersist: handleExpandPersist,
525
+ onCollapsedTooltipChange: sidebar.showTooltipsWhenCollapsed && effectiveCollapsed ? setCollapsedTooltip : void 0,
526
+ renderLink,
527
+ styles,
528
+ density
529
+ }
530
+ ),
531
+ showBottomPanel && bottomPanelContent ? /* @__PURE__ */ jsxs(
532
+ "div",
533
+ {
534
+ className: cn(
535
+ bottomPanel.sticky !== false && "sticky bottom-0",
536
+ "shrink-0 overflow-hidden bg-transparent p-4",
537
+ bottomPanel.bordered !== false && "border-t border-[hsl(var(--cx-sidebar-foreground)/0.12)]",
538
+ effectiveCollapsed && !mobile && "px-2",
539
+ styles?.sidebarFooter,
540
+ bottomPanel.className
541
+ ),
542
+ style: bottomPanelStyle,
543
+ children: [
544
+ !isSidebarCompact && (bottomPanel.title || bottomPanel.collapsible) ? /* @__PURE__ */ jsxs("div", { className: "mb-3 flex items-center justify-between gap-3", children: [
545
+ bottomPanel.title ? /* @__PURE__ */ jsx("div", { className: "min-w-0 truncate font-semibold text-sidebar-foreground", children: bottomPanel.title }) : /* @__PURE__ */ jsx("span", {}),
546
+ bottomPanel.collapsible ? /* @__PURE__ */ jsx(
547
+ "button",
548
+ {
549
+ type: "button",
550
+ onClick: () => setBottomPanelCollapsed(!bottomPanelCollapsed),
551
+ className: "grid size-7 shrink-0 place-items-center rounded-md text-sidebar-foreground/70 hover:bg-[hsl(var(--cx-sidebar-foreground)/0.08)] hover:text-sidebar-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
552
+ "aria-expanded": !bottomPanelCollapsed,
553
+ "aria-label": bottomPanel.collapseButtonLabel ?? (bottomPanelCollapsed ? "Expand sidebar panel" : "Collapse sidebar panel"),
554
+ children: /* @__PURE__ */ jsx("span", { className: cn("transition-transform", bottomPanelCollapsed ? "-rotate-90" : "rotate-90"), children: "\u203A" })
555
+ }
556
+ ) : null
557
+ ] }) : null,
558
+ !isPanelBodyCollapsed ? /* @__PURE__ */ jsx("div", { className: "min-h-0 overflow-hidden", children: bottomPanelContent }) : null
559
+ ]
560
+ }
561
+ ) : null
562
+ ] }),
563
+ sidebar.showTooltipsWhenCollapsed && effectiveCollapsed && collapsedTooltip ? /* @__PURE__ */ jsx(
564
+ "span",
565
+ {
566
+ className: "pointer-events-none fixed z-[70] -translate-y-1/2 whitespace-nowrap rounded-md bg-foreground px-2 py-1 text-xs text-background shadow-lg",
567
+ style: { top: collapsedTooltip.top, left: collapsedTooltip.left },
568
+ children: collapsedTooltip.label
569
+ }
570
+ ) : null
571
+ ]
572
+ }
573
+ );
574
+ }
575
+ function ActionButton({ action, styles }) {
576
+ const Icon = action.icon;
577
+ const isIcon = action.type === "icon";
578
+ return /* @__PURE__ */ jsxs(
579
+ "button",
580
+ {
581
+ type: "button",
582
+ onClick: action.onClick,
583
+ className: cn(
584
+ "relative inline-flex items-center justify-center gap-2 rounded-lg border border-border bg-background text-sm font-medium text-foreground transition-colors hover:bg-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
585
+ isIcon ? "size-9" : "h-9 px-3",
586
+ action.type === "button" && action.variant === "solid" && "border-primary bg-primary text-primary-foreground hover:bg-primary/90",
587
+ action.type === "button" && action.variant === "ghost" && "border-transparent bg-transparent hover:bg-muted",
588
+ action.type === "button" && action.variant === "soft" && "border-transparent bg-primary/10 text-primary hover:bg-primary/15",
589
+ action.type === "button" && action.tone === "danger" && "border-danger text-danger hover:bg-danger/10",
590
+ styles?.headerActionButton
591
+ ),
592
+ "aria-label": action.label,
593
+ children: [
594
+ Icon ? /* @__PURE__ */ jsx(Icon, { className: "size-4", "aria-hidden": true }) : null,
595
+ !isIcon ? /* @__PURE__ */ jsx("span", { children: action.label }) : null,
596
+ isIcon && action.badge ? /* @__PURE__ */ jsx("span", { className: "absolute -right-1 -top-1 rounded-full bg-danger px-1.5 text-[10px] font-semibold text-white", children: action.badge }) : null
597
+ ]
598
+ }
599
+ );
600
+ }
601
+ function DropdownAction({ action, styles }) {
602
+ const [open, setOpen] = useState(false);
603
+ const Icon = action.icon;
604
+ return /* @__PURE__ */ jsxs("div", { className: "relative", children: [
605
+ /* @__PURE__ */ jsxs(
606
+ "button",
607
+ {
608
+ type: "button",
609
+ onClick: () => setOpen((value) => !value),
610
+ "aria-expanded": open,
611
+ className: cn("inline-flex h-9 items-center gap-2 rounded-lg border border-border bg-background px-3 text-sm font-medium hover:bg-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring", styles?.headerActionButton),
612
+ children: [
613
+ Icon ? /* @__PURE__ */ jsx(Icon, { className: "size-4", "aria-hidden": true }) : null,
614
+ action.label
615
+ ]
616
+ }
617
+ ),
618
+ open ? /* @__PURE__ */ jsx("div", { className: "absolute right-0 top-11 z-50 w-52 rounded-lg border border-border bg-background p-1 shadow-lg", children: action.items.map((item) => {
619
+ const ItemIcon = item.icon;
620
+ return /* @__PURE__ */ jsxs(
621
+ "button",
622
+ {
623
+ type: "button",
624
+ onClick: () => {
625
+ item.onClick?.();
626
+ setOpen(false);
627
+ },
628
+ className: "flex w-full items-center gap-2 rounded-md px-3 py-2 text-left text-sm hover:bg-muted",
629
+ children: [
630
+ ItemIcon ? /* @__PURE__ */ jsx(ItemIcon, { className: "size-4", "aria-hidden": true }) : null,
631
+ item.label
632
+ ]
633
+ },
634
+ item.label
635
+ );
636
+ }) }) : null
637
+ ] });
638
+ }
639
+ function HeaderActions({
640
+ actions,
641
+ headerActions,
642
+ styles
643
+ }) {
644
+ return /* @__PURE__ */ jsxs("div", { className: cn("flex items-center gap-2", styles?.headerActions), children: [
645
+ actions?.map((action, index) => {
646
+ if (action.type === "custom") {
647
+ return /* @__PURE__ */ jsx(Fragment, { children: action.render }, index);
648
+ }
649
+ if (action.type === "dropdown") {
650
+ return /* @__PURE__ */ jsx(DropdownAction, { action, styles }, `${action.label}-${index}`);
651
+ }
652
+ return /* @__PURE__ */ jsx(ActionButton, { action, styles }, `${action.label}-${index}`);
653
+ }),
654
+ headerActions
655
+ ] });
656
+ }
657
+ function DashboardBreadcrumbs({
658
+ breadcrumbs,
659
+ renderLink,
660
+ styles
661
+ }) {
662
+ if (!breadcrumbs?.length) {
663
+ return null;
664
+ }
665
+ return /* @__PURE__ */ jsx("nav", { className: cn("flex min-w-0 items-center gap-1 overflow-x-auto whitespace-nowrap text-sm text-muted-foreground", styles?.headerBreadcrumbs), "aria-label": "Breadcrumbs", children: breadcrumbs.map((item, index) => {
666
+ const isLast = index === breadcrumbs.length - 1;
667
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
668
+ index > 0 ? /* @__PURE__ */ jsx("span", { "aria-hidden": true, children: "/" }) : null,
669
+ item.href && !isLast ? renderLink({ href: item.href, className: "hover:text-foreground", children: item.label }) : /* @__PURE__ */ jsx("span", { className: cn(isLast && "text-foreground"), children: item.label })
670
+ ] }, `${item.label}-${index}`);
671
+ }) });
672
+ }
673
+ function DashboardUserMenu({
674
+ user,
675
+ config,
676
+ styles,
677
+ placement = "bottom",
678
+ triggerClassName,
679
+ menuClassName
680
+ }) {
681
+ const [open, setOpen] = useState(false);
682
+ const [avatarFailed, setAvatarFailed] = useState(false);
683
+ const initials = user?.initials ?? user?.name?.split(" ").map((part) => part[0]).join("").slice(0, 2).toUpperCase() ?? "U";
684
+ const UserIcon = user?.icon;
685
+ const userMenuConfig = {
686
+ bordered: false,
687
+ background: "transparent",
688
+ radius: "lg",
689
+ padding: "sm",
690
+ shadow: "none",
691
+ showRole: true,
692
+ ...config
693
+ };
694
+ if (!user) {
695
+ return null;
696
+ }
697
+ return /* @__PURE__ */ jsxs("div", { className: cn("relative", styles?.headerUserMenu), children: [
698
+ /* @__PURE__ */ jsxs(
699
+ "button",
700
+ {
701
+ type: "button",
702
+ onClick: () => setOpen((value) => !value),
703
+ "aria-expanded": open,
704
+ className: cn(
705
+ "flex items-center gap-2 transition-colors hover:bg-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
706
+ userMenuConfig.bordered && "border border-border",
707
+ userMenuBackgroundClasses[userMenuConfig.background],
708
+ userMenuRadiusClasses[userMenuConfig.radius],
709
+ userMenuPaddingClasses[userMenuConfig.padding],
710
+ userMenuShadowClasses[userMenuConfig.shadow],
711
+ triggerClassName
712
+ ),
713
+ children: [
714
+ user.avatarUrl && !avatarFailed ? /* @__PURE__ */ jsx(
715
+ "img",
716
+ {
717
+ src: user.avatarUrl,
718
+ alt: "",
719
+ className: "size-8 rounded-full object-cover",
720
+ onError: () => setAvatarFailed(true)
721
+ }
722
+ ) : /* @__PURE__ */ jsx("span", { className: "grid size-8 place-items-center rounded-full bg-primary text-xs font-semibold text-primary-foreground", children: initials }),
723
+ /* @__PURE__ */ jsxs("span", { className: "hidden min-w-0 text-left lg:block", children: [
724
+ /* @__PURE__ */ jsxs("span", { className: "flex max-w-36 items-center gap-1.5", children: [
725
+ /* @__PURE__ */ jsx("span", { className: "truncate text-sm font-medium", children: user.name }),
726
+ UserIcon ? /* @__PURE__ */ jsx(UserIcon, { className: "size-3.5 shrink-0 text-primary", "aria-hidden": true }) : null
727
+ ] }),
728
+ userMenuConfig.showRole ? /* @__PURE__ */ jsx("span", { className: "block max-w-36 truncate text-xs text-muted-foreground", children: user.role ?? user.email }) : null
729
+ ] })
730
+ ]
731
+ }
732
+ ),
733
+ open ? /* @__PURE__ */ jsxs(
734
+ "div",
735
+ {
736
+ className: cn(
737
+ "z-50 w-64 rounded-lg border border-border bg-background p-2 shadow-lg",
738
+ placement === "top" ? "absolute right-0 bottom-12" : "absolute right-0 top-12",
739
+ menuClassName
740
+ ),
741
+ children: [
742
+ /* @__PURE__ */ jsxs("div", { className: "px-3 py-2", children: [
743
+ /* @__PURE__ */ jsx("p", { className: "truncate text-sm font-medium", children: user.name }),
744
+ /* @__PURE__ */ jsx("p", { className: "truncate text-xs text-muted-foreground", children: user.email })
745
+ ] }),
746
+ /* @__PURE__ */ jsx("div", { className: "my-1 border-t border-border" }),
747
+ user.menuItems?.map((item, index) => {
748
+ if (item.type === "divider") {
749
+ return /* @__PURE__ */ jsx("div", { className: "my-1 border-t border-border" }, index);
750
+ }
751
+ const Icon = item.icon;
752
+ return item.href ? /* @__PURE__ */ jsxs("a", { href: item.href, className: "flex items-center gap-2 rounded-md px-3 py-2 text-sm hover:bg-muted", children: [
753
+ Icon ? /* @__PURE__ */ jsx(Icon, { className: "size-4", "aria-hidden": true }) : null,
754
+ item.label
755
+ ] }, item.label) : /* @__PURE__ */ jsxs(
756
+ "button",
757
+ {
758
+ type: "button",
759
+ onClick: () => {
760
+ item.onClick?.();
761
+ setOpen(false);
762
+ },
763
+ className: "flex w-full items-center gap-2 rounded-md px-3 py-2 text-left text-sm hover:bg-muted",
764
+ children: [
765
+ Icon ? /* @__PURE__ */ jsx(Icon, { className: "size-4", "aria-hidden": true }) : null,
766
+ item.label
767
+ ]
768
+ },
769
+ item.label
770
+ );
771
+ })
772
+ ]
773
+ }
774
+ ) : null
775
+ ] });
776
+ }
777
+ function DashboardHeader({
778
+ header,
779
+ user,
780
+ mobile,
781
+ mobileOpen,
782
+ setMobileOpen,
783
+ renderLink,
784
+ headerActions,
785
+ styles,
786
+ density,
787
+ layout,
788
+ breadcrumbsVisible
789
+ }) {
790
+ const heightStyle = header?.height ? { height: header.height } : void 0;
791
+ const showMobileBottomNav = Boolean(mobile?.bottomNav);
792
+ const showMobileHeaderToggle = header?.showSidebarToggle !== false && !showMobileBottomNav;
793
+ return /* @__PURE__ */ jsxs(
794
+ "header",
795
+ {
796
+ className: cn(
797
+ "relative z-30 backdrop-blur-xl",
798
+ resolveSurfaceBackgroundClass({
799
+ value: layout.background.header,
800
+ hasComponent: Boolean(layout.backgroundComponents.header)
801
+ }),
802
+ layout.borders.header && "border-b border-border",
803
+ header?.sticky !== false && "sticky top-0",
804
+ styles?.header
805
+ ),
806
+ children: [
807
+ /* @__PURE__ */ jsx(SurfaceBackground, { component: layout.backgroundComponents.header }),
808
+ /* @__PURE__ */ jsxs(
809
+ "div",
810
+ {
811
+ className: cn("relative z-10 flex items-center justify-between gap-4 px-4 lg:px-6", densityClasses[density].header, styles?.headerInner),
812
+ style: heightStyle,
813
+ children: [
814
+ /* @__PURE__ */ jsxs("div", { className: cn("flex min-w-0 items-center gap-3", styles?.headerLeft), children: [
815
+ showMobileHeaderToggle ? /* @__PURE__ */ jsx(
816
+ "button",
817
+ {
818
+ type: "button",
819
+ onClick: () => setMobileOpen(!mobileOpen),
820
+ className: "grid size-9 place-items-center rounded-lg border border-border bg-background text-foreground hover:bg-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring lg:hidden",
821
+ "aria-label": "Open navigation",
822
+ "aria-expanded": mobileOpen,
823
+ children: "\u2630"
824
+ }
825
+ ) : null,
826
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
827
+ header?.title ? /* @__PURE__ */ jsx("h1", { className: cn("truncate text-lg font-semibold", styles?.headerTitle), children: header.title }) : null,
828
+ header?.subtitle ? /* @__PURE__ */ jsx("p", { className: cn("truncate text-sm text-muted-foreground", styles?.headerSubtitle), children: header.subtitle }) : null
829
+ ] })
830
+ ] }),
831
+ /* @__PURE__ */ jsxs("div", { className: "hidden shrink-0 items-center gap-2 lg:flex", children: [
832
+ /* @__PURE__ */ jsx(HeaderActions, { actions: header?.actions, headerActions, styles }),
833
+ /* @__PURE__ */ jsx(DashboardUserMenu, { user, config: header?.userMenu, styles })
834
+ ] })
835
+ ]
836
+ }
837
+ ),
838
+ header?.showBreadcrumbs ? /* @__PURE__ */ jsxs(
839
+ "div",
840
+ {
841
+ className: cn(
842
+ "relative overflow-hidden border-t border-border/70 transition-all duration-200 ease-out motion-reduce:transition-none",
843
+ resolveSurfaceBackgroundClass({
844
+ value: layout.background.breadcrumb,
845
+ hasComponent: Boolean(layout.backgroundComponents.breadcrumb)
846
+ }),
847
+ breadcrumbsVisible ? "max-h-12 opacity-100 translate-y-0" : "max-h-0 -translate-y-1 opacity-0"
848
+ ),
849
+ "aria-hidden": !breadcrumbsVisible,
850
+ children: [
851
+ /* @__PURE__ */ jsx(SurfaceBackground, { component: layout.backgroundComponents.breadcrumb }),
852
+ /* @__PURE__ */ jsx("div", { className: "relative z-10 px-4 py-2 lg:px-6", children: /* @__PURE__ */ jsx(DashboardBreadcrumbs, { breadcrumbs: header.breadcrumbs, renderLink, styles }) })
853
+ ]
854
+ }
855
+ ) : null
856
+ ]
857
+ }
858
+ );
859
+ }
860
+ function DashboardContent({
861
+ children,
862
+ content,
863
+ styles,
864
+ density,
865
+ layout
866
+ }) {
867
+ const padded = content?.padded !== false;
868
+ const maxWidth = content?.maxWidth ?? "none";
869
+ return /* @__PURE__ */ jsxs(
870
+ "div",
871
+ {
872
+ className: cn(
873
+ "relative",
874
+ resolveSurfaceBackgroundClass({
875
+ value: layout.background.main,
876
+ hasComponent: Boolean(layout.backgroundComponents.main)
877
+ }),
878
+ resolveSurfaceBackgroundClass({
879
+ value: layout.background.content,
880
+ hasComponent: Boolean(layout.backgroundComponents.content)
881
+ }),
882
+ padded && densityClasses[density].content,
883
+ styles?.content
884
+ ),
885
+ children: [
886
+ /* @__PURE__ */ jsx(SurfaceBackground, { component: layout.backgroundComponents.main }),
887
+ /* @__PURE__ */ jsx(SurfaceBackground, { component: layout.backgroundComponents.content }),
888
+ /* @__PURE__ */ jsx(
889
+ "div",
890
+ {
891
+ className: cn(
892
+ "relative z-10",
893
+ maxWidthClasses[maxWidth],
894
+ content?.centered && "mx-auto",
895
+ styles?.pageContainer
896
+ ),
897
+ children
898
+ }
899
+ )
900
+ ]
901
+ }
902
+ );
903
+ }
904
+ function MobileDrawer({
905
+ open,
906
+ setOpen,
907
+ sidebarProps,
908
+ styles
909
+ }) {
910
+ const panelRef = useRef(null);
911
+ const [rendered, setRendered] = useState(open);
912
+ useEffect(() => {
913
+ if (open) {
914
+ setRendered(true);
915
+ return;
916
+ }
917
+ const timeoutId = window.setTimeout(() => {
918
+ setRendered(false);
919
+ }, 220);
920
+ return () => window.clearTimeout(timeoutId);
921
+ }, [open]);
922
+ useEffect(() => {
923
+ if (!open) {
924
+ return;
925
+ }
926
+ const handleKeyDown = (event) => {
927
+ if (event.key === "Escape") {
928
+ setOpen(false);
929
+ }
930
+ };
931
+ document.addEventListener("keydown", handleKeyDown);
932
+ return () => document.removeEventListener("keydown", handleKeyDown);
933
+ }, [open, setOpen]);
934
+ if (!rendered) {
935
+ return null;
936
+ }
937
+ return /* @__PURE__ */ jsxs("div", { className: "fixed inset-0 z-50 lg:hidden", role: "dialog", "aria-modal": "true", children: [
938
+ /* @__PURE__ */ jsx(
939
+ "button",
940
+ {
941
+ className: cn(
942
+ "absolute inset-0 bg-black/50 transition-opacity duration-200 ease-out motion-reduce:transition-none",
943
+ open ? "opacity-100" : "opacity-0"
944
+ ),
945
+ type: "button",
946
+ "aria-label": "Close navigation",
947
+ onClick: () => setOpen(false)
948
+ }
949
+ ),
950
+ /* @__PURE__ */ jsx(
951
+ "div",
952
+ {
953
+ ref: panelRef,
954
+ className: cn(
955
+ "absolute inset-y-0 left-0 flex max-w-[86vw] shadow-2xl transition-transform duration-200 ease-out motion-reduce:transition-none",
956
+ open ? "translate-x-0" : "-translate-x-full",
957
+ styles?.mobileDrawer
958
+ ),
959
+ children: /* @__PURE__ */ jsx(DashboardSidebar, { ...sidebarProps, mobile: true, closeMobile: () => setOpen(false), collapsed: false })
960
+ }
961
+ )
962
+ ] });
963
+ }
964
+ function MobileBottomNav({
965
+ items,
966
+ currentPath,
967
+ isItemActive,
968
+ renderLink,
969
+ user,
970
+ userMenuConfig,
971
+ mobileOpen,
972
+ setMobileOpen,
973
+ utilityItems,
974
+ styles,
975
+ layout
976
+ }) {
977
+ const resolvedUtilityItems = utilityItems?.length ? utilityItems : [{ type: "sidebar-toggle" }, { type: "user-menu" }];
978
+ if (!items?.length && !resolvedUtilityItems.length) {
979
+ return null;
980
+ }
981
+ const mobileUserMenuConfig = {
982
+ ...userMenuConfig,
983
+ bordered: false,
984
+ background: "transparent",
985
+ padding: "none",
986
+ shadow: "none",
987
+ showRole: false
988
+ };
989
+ const itemBaseClassName = "group flex h-full min-h-[4.5rem] w-full min-w-[4.75rem] flex-col items-center justify-center gap-1 px-2.5 py-2 text-[0.7rem] font-medium leading-none transition-colors";
990
+ const itemShellClassName = "relative flex min-w-[4.75rem] flex-1 basis-0 items-stretch before:absolute before:bottom-3 before:left-0 before:top-3 before:w-px before:bg-border/70 before:content-[''] first:before:hidden";
991
+ const iconWrapClassName = "grid size-6 place-items-center text-current transition-transform duration-200 motion-reduce:transition-none";
992
+ return /* @__PURE__ */ jsxs(
993
+ "nav",
994
+ {
995
+ className: cn(
996
+ "fixed inset-x-0 bottom-0 z-40 pb-[env(safe-area-inset-bottom)] lg:hidden",
997
+ resolveSurfaceBackgroundClass({
998
+ value: layout.background.mobileBottomNav,
999
+ hasComponent: Boolean(layout.backgroundComponents.mobileBottomNav)
1000
+ }),
1001
+ styles?.mobileBottomNav
1002
+ ),
1003
+ "aria-label": "Mobile navigation",
1004
+ children: [
1005
+ /* @__PURE__ */ jsx(SurfaceBackground, { component: layout.backgroundComponents.mobileBottomNav }),
1006
+ /* @__PURE__ */ jsx("div", { className: "relative z-10 w-full", children: /* @__PURE__ */ jsx(
1007
+ "div",
1008
+ {
1009
+ className: cn(
1010
+ "flex min-h-[5.25rem] w-full items-stretch bg-background/95 backdrop-blur-2xl",
1011
+ layout.borders.mobileBottomNav && "border-t border-border/80"
1012
+ ),
1013
+ children: /* @__PURE__ */ jsxs("ul", { className: "flex min-w-0 flex-1 items-stretch overflow-x-auto overflow-y-hidden", children: [
1014
+ items?.map((item) => {
1015
+ const active = isItemActive(item, currentPath);
1016
+ const Icon = item.icon;
1017
+ return /* @__PURE__ */ jsx("li", { className: itemShellClassName, children: renderLink({
1018
+ href: item.href,
1019
+ external: item.external,
1020
+ "aria-current": active ? "page" : void 0,
1021
+ className: cn(
1022
+ itemBaseClassName,
1023
+ active ? "text-primary" : "text-muted-foreground hover:text-foreground"
1024
+ ),
1025
+ children: /* @__PURE__ */ jsxs(Fragment2, { children: [
1026
+ /* @__PURE__ */ jsx(
1027
+ "span",
1028
+ {
1029
+ className: cn(
1030
+ "h-0.5 w-8 rounded-full transition-all duration-200 motion-reduce:transition-none",
1031
+ active ? "bg-primary opacity-100" : "bg-transparent opacity-0"
1032
+ )
1033
+ }
1034
+ ),
1035
+ Icon ? /* @__PURE__ */ jsx("span", { className: cn(iconWrapClassName, active && "-translate-y-px"), "aria-hidden": true, children: /* @__PURE__ */ jsx(Icon, { className: "size-[1.3rem]", "aria-hidden": true }) }) : /* @__PURE__ */ jsx("span", { className: cn(iconWrapClassName, active && "-translate-y-px"), "aria-hidden": true, children: /* @__PURE__ */ jsx("span", { className: "size-2.5 rounded-full bg-current" }) }),
1036
+ /* @__PURE__ */ jsx("span", { className: "max-w-16 truncate leading-none", children: item.label })
1037
+ ] })
1038
+ }) }, item.href);
1039
+ }),
1040
+ resolvedUtilityItems.map((item, index) => {
1041
+ if (item.type === "custom") {
1042
+ return /* @__PURE__ */ jsx("li", { className: itemShellClassName, children: item.render }, item.key);
1043
+ }
1044
+ if (item.type === "user-menu") {
1045
+ return user ? /* @__PURE__ */ jsx("li", { className: itemShellClassName, children: /* @__PURE__ */ jsx(
1046
+ DashboardUserMenu,
1047
+ {
1048
+ user,
1049
+ config: mobileUserMenuConfig,
1050
+ styles,
1051
+ placement: "top",
1052
+ menuClassName: "fixed bottom-[calc(env(safe-area-inset-bottom)+6.25rem)] right-3 z-[90] w-[min(16rem,calc(100vw-1.5rem))] max-w-[calc(100vw-1.5rem)] rounded-2xl border border-border/80 bg-background/98 shadow-[0_24px_60px_-24px_rgba(15,23,42,0.55)] backdrop-blur-xl",
1053
+ triggerClassName: cn(
1054
+ itemBaseClassName,
1055
+ "text-foreground hover:text-foreground"
1056
+ )
1057
+ }
1058
+ ) }, `mobile-user-${index}`) : null;
1059
+ }
1060
+ const Icon = item.icon;
1061
+ return /* @__PURE__ */ jsx("li", { className: itemShellClassName, children: /* @__PURE__ */ jsxs(
1062
+ "button",
1063
+ {
1064
+ type: "button",
1065
+ onClick: () => setMobileOpen(!mobileOpen),
1066
+ className: cn(
1067
+ itemBaseClassName,
1068
+ "text-foreground hover:text-foreground"
1069
+ ),
1070
+ "aria-label": item.label ?? "Open navigation",
1071
+ "aria-expanded": mobileOpen,
1072
+ children: [
1073
+ /* @__PURE__ */ jsx("span", { className: iconWrapClassName, "aria-hidden": true, children: Icon ? /* @__PURE__ */ jsx(Icon, { className: "size-[1.3rem]", "aria-hidden": true }) : "\u2630" }),
1074
+ /* @__PURE__ */ jsx("span", { className: "max-w-16 truncate leading-none", children: item.label ?? "Menu" })
1075
+ ]
1076
+ }
1077
+ ) }, `mobile-sidebar-toggle-${index}`);
1078
+ })
1079
+ ] })
1080
+ }
1081
+ ) })
1082
+ ]
1083
+ }
1084
+ );
1085
+ }
1086
+ function DashboardLayout({
1087
+ children,
1088
+ logo,
1089
+ sidebarItems = [],
1090
+ currentPath,
1091
+ isItemActive = defaultIsItemActive,
1092
+ renderLink = DefaultLink,
1093
+ user,
1094
+ header,
1095
+ sidebar: sidebarConfig,
1096
+ mobile,
1097
+ content,
1098
+ layout: layoutConfig,
1099
+ headerActions,
1100
+ sidebarBottomPanel,
1101
+ sidebarFooter,
1102
+ sidebarHeader,
1103
+ footer,
1104
+ variant = "classic",
1105
+ density = "comfortable",
1106
+ styles,
1107
+ className,
1108
+ style
1109
+ }) {
1110
+ const theme = useTheme();
1111
+ const themeStyles = getThemeStyles(theme.layout?.DashboardLayout);
1112
+ const mergedStyles = useMemo(() => ({ ...themeStyles, ...styles }), [themeStyles, styles]);
1113
+ const layout = useMemo(
1114
+ () => ({
1115
+ ...defaultLayoutConfig,
1116
+ ...layoutConfig,
1117
+ borders: {
1118
+ ...defaultLayoutConfig.borders,
1119
+ ...layoutConfig?.borders
1120
+ },
1121
+ background: {
1122
+ ...defaultLayoutConfig.background,
1123
+ ...layoutConfig?.background
1124
+ },
1125
+ backgroundComponents: {
1126
+ ...defaultLayoutConfig.backgroundComponents,
1127
+ ...layoutConfig?.backgroundComponents
1128
+ }
1129
+ }),
1130
+ [layoutConfig]
1131
+ );
1132
+ const sidebar = { ...defaultSidebar, ...sidebarConfig };
1133
+ const [collapsed, setCollapsed] = useControllableState({
1134
+ value: sidebarConfig?.collapsed,
1135
+ defaultValue: sidebar.defaultCollapsed,
1136
+ onChange: sidebarConfig?.onCollapsedChange
1137
+ });
1138
+ const [mobileOpen, setMobileOpen] = useState(false);
1139
+ const [breadcrumbsVisible, setBreadcrumbsVisible] = useState(true);
1140
+ const lastMainScrollTopRef = useRef(0);
1141
+ const breadcrumbScrollIntentRef = useRef(0);
1142
+ const mobileOpenRef = useRef(mobileOpen);
1143
+ const mobileDrawerHistoryEntryActiveRef = useRef(false);
1144
+ const mobileDrawerClosingFromHistoryRef = useRef(false);
1145
+ const skipNextDrawerPopstateRef = useRef(false);
1146
+ const showBottomNav = Boolean(mobile?.bottomNav && (mobile.bottomNavItems?.length || mobile.utilityItems?.length));
1147
+ const breadcrumbBehavior = header?.breadcrumbBehavior ?? "hide-on-scroll";
1148
+ useEffect(() => {
1149
+ mobileOpenRef.current = mobileOpen;
1150
+ }, [mobileOpen]);
1151
+ useEffect(() => {
1152
+ setBreadcrumbsVisible(true);
1153
+ lastMainScrollTopRef.current = 0;
1154
+ breadcrumbScrollIntentRef.current = 0;
1155
+ }, [currentPath]);
1156
+ useEffect(() => {
1157
+ if (mobile?.drawer === false || typeof window === "undefined") {
1158
+ return;
1159
+ }
1160
+ const handlePopState = () => {
1161
+ if (skipNextDrawerPopstateRef.current) {
1162
+ skipNextDrawerPopstateRef.current = false;
1163
+ return;
1164
+ }
1165
+ if (mobileDrawerHistoryEntryActiveRef.current && mobileOpenRef.current) {
1166
+ mobileDrawerClosingFromHistoryRef.current = true;
1167
+ mobileDrawerHistoryEntryActiveRef.current = false;
1168
+ setMobileOpen(false);
1169
+ }
1170
+ };
1171
+ window.addEventListener("popstate", handlePopState);
1172
+ return () => window.removeEventListener("popstate", handlePopState);
1173
+ }, [mobile?.drawer]);
1174
+ useEffect(() => {
1175
+ if (mobile?.drawer === false || typeof window === "undefined") {
1176
+ return;
1177
+ }
1178
+ if (mobileOpen) {
1179
+ if (!mobileDrawerHistoryEntryActiveRef.current) {
1180
+ window.history.pushState({ ...window.history.state ?? {}, __clayerMobileDrawer: true }, "", window.location.href);
1181
+ mobileDrawerHistoryEntryActiveRef.current = true;
1182
+ }
1183
+ return;
1184
+ }
1185
+ if (mobileDrawerClosingFromHistoryRef.current) {
1186
+ mobileDrawerClosingFromHistoryRef.current = false;
1187
+ return;
1188
+ }
1189
+ if (mobileDrawerHistoryEntryActiveRef.current) {
1190
+ mobileDrawerHistoryEntryActiveRef.current = false;
1191
+ skipNextDrawerPopstateRef.current = true;
1192
+ window.history.back();
1193
+ }
1194
+ }, [mobile?.drawer, mobileOpen]);
1195
+ const handleMainScroll = useCallback(
1196
+ (event) => {
1197
+ if (!header?.showBreadcrumbs || breadcrumbBehavior === "static") {
1198
+ return;
1199
+ }
1200
+ const nextScrollTop = event.currentTarget.scrollTop;
1201
+ const maxScrollTop = Math.max(0, event.currentTarget.scrollHeight - event.currentTarget.clientHeight);
1202
+ const previousScrollTop = lastMainScrollTopRef.current;
1203
+ const delta = nextScrollTop - previousScrollTop;
1204
+ const clampedNextScrollTop = Math.max(0, nextScrollTop);
1205
+ const distanceFromBottom = Math.max(0, maxScrollTop - clampedNextScrollTop);
1206
+ if (Math.abs(delta) < 2) {
1207
+ lastMainScrollTopRef.current = clampedNextScrollTop;
1208
+ return;
1209
+ }
1210
+ if (clampedNextScrollTop < 16) {
1211
+ if (!breadcrumbsVisible) {
1212
+ setBreadcrumbsVisible(true);
1213
+ }
1214
+ lastMainScrollTopRef.current = clampedNextScrollTop;
1215
+ breadcrumbScrollIntentRef.current = 0;
1216
+ return;
1217
+ }
1218
+ const isSameDirection = Math.sign(delta) === Math.sign(breadcrumbScrollIntentRef.current) || breadcrumbScrollIntentRef.current === 0;
1219
+ breadcrumbScrollIntentRef.current = isSameDirection ? breadcrumbScrollIntentRef.current + delta : delta;
1220
+ const hideThreshold = 18;
1221
+ const showThreshold = 14;
1222
+ const minHideScrollTop = 56;
1223
+ const showResetScrollTop = 28;
1224
+ const bottomIgnoreZone = 24;
1225
+ if (distanceFromBottom < bottomIgnoreZone && delta > 0) {
1226
+ lastMainScrollTopRef.current = clampedNextScrollTop;
1227
+ breadcrumbScrollIntentRef.current = Math.max(0, breadcrumbScrollIntentRef.current);
1228
+ return;
1229
+ }
1230
+ if (breadcrumbsVisible && clampedNextScrollTop > minHideScrollTop && breadcrumbScrollIntentRef.current > hideThreshold) {
1231
+ setBreadcrumbsVisible(false);
1232
+ breadcrumbScrollIntentRef.current = 0;
1233
+ } else if (!breadcrumbsVisible && (clampedNextScrollTop < showResetScrollTop || breadcrumbScrollIntentRef.current < -showThreshold)) {
1234
+ setBreadcrumbsVisible(true);
1235
+ breadcrumbScrollIntentRef.current = 0;
1236
+ }
1237
+ lastMainScrollTopRef.current = clampedNextScrollTop;
1238
+ },
1239
+ [breadcrumbBehavior, breadcrumbsVisible, header?.showBreadcrumbs]
1240
+ );
1241
+ const sidebarProps = {
1242
+ logo,
1243
+ sidebarHeader,
1244
+ sidebarBottomPanel,
1245
+ sidebarFooter,
1246
+ items: sidebarItems,
1247
+ collapsed,
1248
+ onCollapsedChange: setCollapsed,
1249
+ sidebar,
1250
+ currentPath,
1251
+ isItemActive,
1252
+ renderLink,
1253
+ styles: mergedStyles,
1254
+ density,
1255
+ layout
1256
+ };
1257
+ return /* @__PURE__ */ jsxs(
1258
+ "div",
1259
+ {
1260
+ className: cn(
1261
+ "relative h-screen overflow-hidden text-foreground",
1262
+ resolveSurfaceBackgroundClass({
1263
+ value: layout.background.root,
1264
+ hasComponent: Boolean(layout.backgroundComponents.root)
1265
+ }),
1266
+ layoutPaddingClasses[layout.padding],
1267
+ mergedStyles.root,
1268
+ className
1269
+ ),
1270
+ style,
1271
+ children: [
1272
+ /* @__PURE__ */ jsx(SurfaceBackground, { component: layout.backgroundComponents.root }),
1273
+ /* @__PURE__ */ jsxs(
1274
+ "div",
1275
+ {
1276
+ className: cn(
1277
+ "relative z-10 flex h-full min-h-0 overflow-hidden",
1278
+ resolveSurfaceBackgroundClass({
1279
+ value: layout.background.shell,
1280
+ hasComponent: Boolean(layout.backgroundComponents.shell)
1281
+ }),
1282
+ layoutRadiusClasses[layout.radius],
1283
+ layoutShadowClasses[layout.shadow],
1284
+ layout.bordered && "border border-border",
1285
+ variant === "floating" && !layoutConfig && "rounded-none lg:rounded-xl lg:border lg:border-border",
1286
+ mergedStyles.shell
1287
+ ),
1288
+ children: [
1289
+ /* @__PURE__ */ jsx(SurfaceBackground, { component: layout.backgroundComponents.shell }),
1290
+ /* @__PURE__ */ jsx(DashboardSidebar, { ...sidebarProps }),
1291
+ /* @__PURE__ */ jsxs("div", { className: cn("relative z-10 flex min-h-0 min-w-0 flex-1 flex-col", mergedStyles.main), children: [
1292
+ mobile?.showHeader !== false ? /* @__PURE__ */ jsx(
1293
+ DashboardHeader,
1294
+ {
1295
+ header,
1296
+ user,
1297
+ mobile,
1298
+ mobileOpen,
1299
+ setMobileOpen,
1300
+ renderLink,
1301
+ headerActions,
1302
+ styles: mergedStyles,
1303
+ density,
1304
+ layout,
1305
+ breadcrumbsVisible: breadcrumbBehavior === "static" ? true : breadcrumbsVisible
1306
+ }
1307
+ ) : null,
1308
+ /* @__PURE__ */ jsx(
1309
+ "main",
1310
+ {
1311
+ className: cn("relative min-h-0 min-w-0 flex-1 overflow-y-auto", showBottomNav && "pb-20 lg:pb-0", mergedStyles.main),
1312
+ onScroll: handleMainScroll,
1313
+ children: /* @__PURE__ */ jsx("div", { className: "relative z-10", children: /* @__PURE__ */ jsx(DashboardContent, { content, styles: mergedStyles, density, layout, children }) })
1314
+ }
1315
+ ),
1316
+ footer ? /* @__PURE__ */ jsxs(
1317
+ "footer",
1318
+ {
1319
+ className: cn(
1320
+ "relative px-4 py-3",
1321
+ resolveSurfaceBackgroundClass({
1322
+ value: layout.background.footer,
1323
+ hasComponent: Boolean(layout.backgroundComponents.footer)
1324
+ }),
1325
+ layout.borders.footer && "border-t border-border",
1326
+ mergedStyles.footer
1327
+ ),
1328
+ children: [
1329
+ /* @__PURE__ */ jsx(SurfaceBackground, { component: layout.backgroundComponents.footer }),
1330
+ /* @__PURE__ */ jsx("div", { className: "relative z-10", children: footer })
1331
+ ]
1332
+ }
1333
+ ) : null
1334
+ ] })
1335
+ ]
1336
+ }
1337
+ ),
1338
+ mobile?.drawer !== false ? /* @__PURE__ */ jsx(MobileDrawer, { open: mobileOpen, setOpen: setMobileOpen, sidebarProps, styles: mergedStyles }) : null,
1339
+ mobile?.bottomNav ? /* @__PURE__ */ jsx(
1340
+ MobileBottomNav,
1341
+ {
1342
+ items: mobile.bottomNavItems,
1343
+ currentPath,
1344
+ isItemActive,
1345
+ renderLink,
1346
+ user,
1347
+ userMenuConfig: header?.userMenu,
1348
+ mobileOpen,
1349
+ setMobileOpen,
1350
+ utilityItems: mobile.utilityItems,
1351
+ styles: mergedStyles,
1352
+ layout
1353
+ }
1354
+ ) : null
1355
+ ]
1356
+ }
1357
+ );
1358
+ }
1359
+ function DashboardShell({ className, ...props }) {
1360
+ return /* @__PURE__ */ jsx("div", { className: cn("flex min-h-screen overflow-hidden bg-inherit text-foreground", className), ...props });
1361
+ }
1362
+ function DashboardMain({ className, ...props }) {
1363
+ return /* @__PURE__ */ jsx("main", { className: cn("min-w-0 flex-1 overflow-y-auto bg-transparent", className), ...props });
1364
+ }
1365
+ function DashboardFooter({ className, ...props }) {
1366
+ return /* @__PURE__ */ jsx("footer", { className: cn("border-t border-border bg-transparent px-4 py-3", className), ...props });
1367
+ }
1368
+ function DashboardActionGroup({ className, ...props }) {
1369
+ return /* @__PURE__ */ jsx("div", { className: cn("flex items-center gap-2", className), ...props });
1370
+ }
1371
+ function SidebarNavItem({ className, ...props }) {
1372
+ return /* @__PURE__ */ jsx("li", { className: cn("list-none", className), ...props });
1373
+ }
1374
+ function SidebarNavGroup({ className, ...props }) {
1375
+ return /* @__PURE__ */ jsx("li", { className: cn("list-none space-y-1", className), ...props });
1376
+ }
1377
+ export {
1378
+ DashboardActionGroup,
1379
+ DashboardBreadcrumbs,
1380
+ DashboardContent,
1381
+ DashboardFooter,
1382
+ DashboardHeader,
1383
+ DashboardLayout,
1384
+ DashboardMain,
1385
+ MobileBottomNav as DashboardMobileNav,
1386
+ DashboardShell,
1387
+ DashboardSidebar,
1388
+ DashboardUserMenu,
1389
+ SidebarNav,
1390
+ SidebarNavGroup,
1391
+ SidebarNavItem
1392
+ };
1393
+ //# sourceMappingURL=index.js.map