@classytic/fluid 0.4.1 → 0.5.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.
Files changed (51) hide show
  1. package/README.md +21 -1
  2. package/dist/client/calendar.d.mts +1 -2
  3. package/dist/client/calendar.mjs +4 -4
  4. package/dist/client/color-picker.d.mts +94 -0
  5. package/dist/client/color-picker.mjs +392 -0
  6. package/dist/client/core.d.mts +243 -557
  7. package/dist/client/core.mjs +351 -1462
  8. package/dist/client/error.d.mts +41 -41
  9. package/dist/client/error.mjs +35 -35
  10. package/dist/client/gallery.d.mts +175 -0
  11. package/dist/client/gallery.mjs +546 -0
  12. package/dist/client/hooks.d.mts +57 -39
  13. package/dist/client/hooks.mjs +29 -7
  14. package/dist/client/spreadsheet.d.mts +30 -27
  15. package/dist/client/spreadsheet.mjs +80 -80
  16. package/dist/client/table.d.mts +66 -33
  17. package/dist/client/table.mjs +87 -54
  18. package/dist/client/theme.mjs +1 -1
  19. package/dist/command.d.mts +6 -4
  20. package/dist/command.mjs +3 -3
  21. package/dist/compact.d.mts +97 -95
  22. package/dist/compact.mjs +336 -322
  23. package/dist/dashboard.d.mts +614 -422
  24. package/dist/dashboard.mjs +1051 -762
  25. package/dist/{dropdown-wrapper-B86u9Fri.mjs → dropdown-wrapper-B9nRDUlz.mjs} +25 -35
  26. package/dist/forms.d.mts +1037 -972
  27. package/dist/forms.mjs +2849 -2721
  28. package/dist/index.d.mts +218 -152
  29. package/dist/index.mjs +357 -264
  30. package/dist/layouts.d.mts +94 -94
  31. package/dist/layouts.mjs +115 -110
  32. package/dist/phone-input-B9_XPNvv.mjs +429 -0
  33. package/dist/phone-input-CLH_UjQZ.d.mts +31 -0
  34. package/dist/{search-context-DR7DBs7S.mjs → search-context-1g3ZmOvx.mjs} +1 -1
  35. package/dist/search.d.mts +168 -164
  36. package/dist/search.mjs +305 -301
  37. package/dist/{sheet-wrapper-C13Y-Q6w.mjs → sheet-wrapper-B2uxookb.mjs} +1 -1
  38. package/dist/timeline-Bgu1mIe9.d.mts +373 -0
  39. package/dist/timeline-HJtWf4Op.mjs +804 -0
  40. package/dist/{use-base-search-BGgWnWaF.d.mts → use-base-search-DFC4QKYU.d.mts} +1 -1
  41. package/dist/{use-media-query-BnVNIKT4.mjs → use-media-query-ChLfFChU.mjs} +6 -7
  42. package/package.json +10 -2
  43. /package/dist/{api-pagination-CJ0vR_w6.d.mts → api-pagination-C30ser2L.d.mts} +0 -0
  44. /package/dist/{filter-utils-DqMmy_v-.mjs → filter-utils-BGIvtq1R.mjs} +0 -0
  45. /package/dist/{filter-utils-IZ0GtuPo.d.mts → filter-utils-DOFTBWm1.d.mts} +0 -0
  46. /package/dist/{use-debounce-xmZucz5e.mjs → use-debounce-BNoNiEon.mjs} +0 -0
  47. /package/dist/{use-keyboard-shortcut-Bl6YM5Q7.mjs → use-keyboard-shortcut-C_Vk-36P.mjs} +0 -0
  48. /package/dist/{use-keyboard-shortcut-_mRCh3QO.d.mts → use-keyboard-shortcut-Q4CSPzSI.d.mts} +0 -0
  49. /package/dist/{use-mobile-BX3SQVo2.mjs → use-mobile-CnEmFiQx.mjs} +0 -0
  50. /package/dist/{use-scroll-detection-CsgsQYvy.mjs → use-scroll-detection-BKfqkmEC.mjs} +0 -0
  51. /package/dist/{utils-CDue7cEt.d.mts → utils-rqvYP1by.d.mts} +0 -0
@@ -1,25 +1,186 @@
1
1
  "use client";
2
2
 
3
3
  import { t as cn } from "./utils-DQ5SCVoW.mjs";
4
- import { t as useIsMobile } from "./use-mobile-BX3SQVo2.mjs";
5
- import { Fragment, jsx, jsxs } from "react/jsx-runtime";
6
- import { Skeleton } from "@/components/ui/skeleton";
4
+ import { t as useIsMobile } from "./use-mobile-CnEmFiQx.mjs";
7
5
  import * as React$1 from "react";
8
- import React, { Suspense, useState } from "react";
6
+ import React, { Suspense, useEffect, useMemo, useState } from "react";
7
+ import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
9
8
  import { Building2, Check, ChevronLeft, ChevronRight, ChevronsUpDown, CircleUserRound, LogOut, Menu } from "lucide-react";
10
9
  import { Button } from "@/components/ui/button";
10
+ import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
11
+ import { Badge } from "@/components/ui/badge";
12
+ import { Skeleton } from "@/components/ui/skeleton";
13
+ import { usePathname } from "next/navigation";
11
14
  import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible";
12
15
  import { DropdownMenu, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger } from "@/components/ui/dropdown-menu";
13
- import { Badge } from "@/components/ui/badge";
14
- import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
15
16
  import { Sheet, SheetContent, SheetDescription, SheetTitle, SheetTrigger } from "@/components/ui/sheet";
16
17
  import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
17
18
  import { Separator } from "@/components/ui/separator";
19
+ import { Sidebar, SidebarContent, SidebarFooter, SidebarGroup, SidebarGroupLabel, SidebarHeader, SidebarInset, SidebarMenu, SidebarMenuButton, SidebarMenuItem, SidebarMenuSub, SidebarMenuSubButton, SidebarMenuSubItem, SidebarProvider, SidebarRail, SidebarTrigger, useSidebar } from "@/components/ui/sidebar";
18
20
  import Link from "next/link";
19
21
  import { Breadcrumb, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator } from "@/components/ui/breadcrumb";
20
- import { Sidebar, SidebarContent, SidebarFooter, SidebarGroup, SidebarGroupLabel, SidebarHeader, SidebarMenu, SidebarMenuButton, SidebarMenuItem, SidebarMenuSub, SidebarMenuSubButton, SidebarMenuSubItem, SidebarRail, SidebarTrigger, useSidebar } from "@/components/ui/sidebar";
21
22
 
23
+ //#region src/components/client-only.tsx
24
+ /**
25
+ * Renders children only on the client after hydration.
26
+ * Use for components with Base UI auto-generated IDs that cause hydration mismatches.
27
+ */
28
+ function ClientOnly({ children, fallback = null }) {
29
+ const [mounted, setMounted] = useState(false);
30
+ useEffect(() => setMounted(true), []);
31
+ if (!mounted) return fallback;
32
+ return children;
33
+ }
34
+
35
+ //#endregion
36
+ //#region src/hooks/use-dashboard-breadcrumbs.ts
37
+ function humanize(slug) {
38
+ return slug.replace(/[-_]/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
39
+ }
40
+ function useDashboardBreadcrumbs(basePath, options = {}) {
41
+ const pathname = usePathname();
42
+ const { labelOverrides = {}, hideSegments = [], baseLabel = "Home" } = options;
43
+ return useMemo(() => {
44
+ const normalizedBase = basePath.replace(/\/+$/, "");
45
+ const visibleSegments = (pathname.startsWith(normalizedBase) ? pathname.slice(normalizedBase.length) : pathname).split("/").filter(Boolean).filter((s) => !hideSegments.includes(s));
46
+ const crumbs = [{
47
+ label: baseLabel,
48
+ href: normalizedBase || "/"
49
+ }];
50
+ let cumulativePath = normalizedBase;
51
+ for (let i = 0; i < visibleSegments.length; i++) {
52
+ const segment = visibleSegments[i];
53
+ cumulativePath += `/${segment}`;
54
+ const isLast = i === visibleSegments.length - 1;
55
+ crumbs.push({
56
+ label: labelOverrides[segment] ?? humanize(segment),
57
+ href: isLast ? void 0 : cumulativePath,
58
+ current: isLast
59
+ });
60
+ }
61
+ return crumbs;
62
+ }, [
63
+ pathname,
64
+ basePath,
65
+ labelOverrides,
66
+ hideSegments,
67
+ baseLabel
68
+ ]);
69
+ }
70
+
71
+ //#endregion
72
+ //#region src/dashboard/dashboard-content.tsx
73
+ const PADDING = {
74
+ none: "",
75
+ sm: "px-2",
76
+ md: "px-4",
77
+ lg: "px-6 lg:px-8"
78
+ };
79
+ /**
80
+ * DashboardContent — Safe content wrapper for sidebar layouts.
81
+ *
82
+ * Prevents the classic flexbox overflow bug where children with
83
+ * `max-w-*` or grid layouts extend beyond the available space
84
+ * (behind the sidebar) by propagating `min-w-0` through the
85
+ * flex chain.
86
+ *
87
+ * **Important**: `SidebarInset` also needs `min-w-0` for the
88
+ * width constraint to propagate correctly from the flex parent.
89
+ * Pass `className="min-w-0"` to `SidebarInset`.
90
+ *
91
+ * @example
92
+ * ```tsx
93
+ * <SidebarProvider>
94
+ * <AppSidebar />
95
+ * <SidebarInset className="min-w-0">
96
+ * <DashboardContent>
97
+ * <PageHeader items={breadcrumbs} />
98
+ * <MyPageContent />
99
+ * </DashboardContent>
100
+ * </SidebarInset>
101
+ * </SidebarProvider>
102
+ * ```
103
+ */
104
+ function DashboardContent({ children, className, container = true, padding = "md" }) {
105
+ const containerClass = container === true ? "@container/main" : typeof container === "string" ? `@container/${container}` : "";
106
+ return /* @__PURE__ */ jsx("div", {
107
+ className: cn("flex flex-1 flex-col min-w-0", className),
108
+ children: /* @__PURE__ */ jsx("div", {
109
+ className: cn("flex flex-1 flex-col gap-2 min-w-0", containerClass, PADDING[padding]),
110
+ children
111
+ })
112
+ });
113
+ }
114
+
115
+ //#endregion
116
+ //#region src/dashboard/dashboard-header.tsx
117
+ function BreadcrumbNav({ items }) {
118
+ return /* @__PURE__ */ jsx(Breadcrumb, { children: /* @__PURE__ */ jsx(BreadcrumbList, { children: items.map((item, index) => /* @__PURE__ */ jsxs(React$1.Fragment, { children: [/* @__PURE__ */ jsx(BreadcrumbItem, {
119
+ className: item.current ? "" : "hidden md:block",
120
+ children: item.current ? /* @__PURE__ */ jsx(BreadcrumbPage, { children: item.label }) : /* @__PURE__ */ jsx(BreadcrumbLink, {
121
+ render: /* @__PURE__ */ jsx(Link, { href: item.href || "#" }),
122
+ children: item.label
123
+ })
124
+ }), index < items.length - 1 && /* @__PURE__ */ jsx(BreadcrumbSeparator, { className: "hidden md:block" })] }, `breadcrumb-${index}`)) }) });
125
+ }
126
+ /**
127
+ * DashboardHeader - Top header bar for dashboard pages.
128
+ * Includes sidebar trigger, breadcrumbs, and action slots.
129
+ */
130
+ function DashboardHeader({ breadcrumbs, leftContent, rightContent, showSidebarTrigger = true, customTrigger, className, children }) {
131
+ return /* @__PURE__ */ jsxs("header", {
132
+ className: cn("flex h-12 shrink-0 items-center justify-between gap-2 px-4", className),
133
+ children: [/* @__PURE__ */ jsxs("div", {
134
+ className: "flex items-center gap-2",
135
+ children: [
136
+ showSidebarTrigger && !customTrigger && /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(SidebarTrigger, { className: "-ml-1" }), /* @__PURE__ */ jsx(Separator, {
137
+ orientation: "vertical",
138
+ className: "mx-2 !h-4 !self-auto"
139
+ })] }),
140
+ customTrigger,
141
+ children ? children : /* @__PURE__ */ jsxs(Fragment$1, { children: [breadcrumbs && breadcrumbs.length > 0 && /* @__PURE__ */ jsx(BreadcrumbNav, { items: breadcrumbs }), leftContent] })
142
+ ]
143
+ }), rightContent && /* @__PURE__ */ jsx("div", {
144
+ className: "flex items-center gap-2",
145
+ children: rightContent
146
+ })]
147
+ });
148
+ }
149
+
150
+ //#endregion
22
151
  //#region src/dashboard/nav-utils.ts
152
+ /**
153
+ * matchRoute — Check if a pathname matches a URL.
154
+ * Exact match or prefix match with trailing slash boundary.
155
+ */
156
+ function matchRoute(pathname, url) {
157
+ return pathname === url || url !== "/" && pathname.startsWith(url + "/");
158
+ }
159
+ /**
160
+ * findActiveItem — Find the most specific matching item from a flat list.
161
+ * Returns the item with the longest matching URL (avoids `/settings` matching `/settings/users`).
162
+ */
163
+ function findActiveItem(items, pathname) {
164
+ let best = null;
165
+ for (const item of items) if (matchRoute(pathname, item.url) && (!best || item.url.length > best.url.length)) best = item;
166
+ return best;
167
+ }
168
+ /**
169
+ * findActiveCategory — Find the category containing the best-matching item.
170
+ * Returns { category, item } or null.
171
+ */
172
+ function findActiveCategory(categories, pathname) {
173
+ let best = null;
174
+ for (const category of categories) for (const item of category.items) if (matchRoute(pathname, item.url) && (!best || item.url.length > best.length)) best = {
175
+ category,
176
+ item,
177
+ length: item.url.length
178
+ };
179
+ return best ? {
180
+ category: best.category,
181
+ item: best.item
182
+ } : null;
183
+ }
23
184
  /** Check hidden flag, then canAccess(ctx) */
24
185
  function isItemVisible(item, ctx) {
25
186
  if (item.hidden) return false;
@@ -109,429 +270,459 @@ function resolveCategories(categories, ctx) {
109
270
  }
110
271
 
111
272
  //#endregion
112
- //#region src/dashboard/header-section.tsx
113
- const variants = {
114
- default: {
115
- wrapper: "px-6 py-5 rounded-xl shadow-sm border bg-background hover:shadow-md",
116
- title: "text-2xl font-semibold tracking-tight",
117
- description: "text-sm text-muted-foreground",
118
- iconSize: "size-10",
119
- iconInner: "size-5",
120
- spacing: "space-y-1"
121
- },
122
- compact: {
123
- wrapper: "px-4 py-3 rounded-lg border bg-background/50",
124
- title: "text-lg font-semibold",
125
- description: "text-xs text-muted-foreground",
126
- iconSize: "size-8",
127
- iconInner: "size-4",
128
- spacing: "space-y-0.5"
129
- },
130
- hero: {
131
- wrapper: "px-8 py-8 rounded-2xl shadow-lg border bg-gradient-to-br from-background to-muted/20",
132
- title: "text-3xl font-bold tracking-tight bg-gradient-to-r from-foreground to-foreground/70 bg-clip-text text-transparent",
133
- description: "text-base text-muted-foreground mt-2",
134
- iconSize: "size-14",
135
- iconInner: "size-7",
136
- spacing: "space-y-2"
137
- },
138
- minimal: {
139
- wrapper: "px-0 py-2",
140
- title: "text-xl font-medium",
141
- description: "text-sm text-muted-foreground",
142
- iconSize: "size-8",
143
- iconInner: "size-4",
144
- spacing: "space-y-0.5"
145
- }
273
+ //#region src/dashboard/presets/dual-sidebar/constants.ts
274
+ const SIDEBAR_RAIL_WIDTH = "3.5rem";
275
+ const SIDEBAR_PANEL_WIDTH = "13rem";
276
+ const SIDEBAR_WIDTH_EXPANDED = "16.5rem";
277
+ const SIDEBAR_WIDTH_COLLAPSED = "3.5rem";
278
+ const SIDEBAR_PADDING = "0.5rem";
279
+ const RAIL_STYLE = {
280
+ width: SIDEBAR_RAIL_WIDTH,
281
+ minWidth: SIDEBAR_RAIL_WIDTH
146
282
  };
147
- function HeaderSection({ title, description, actions = null, icon: Icon = null, iconClassName, loading = false, variant = "default", className, badge, breadcrumbs, metadata, children }) {
148
- const currentVariant = variants[variant] || variants.default;
149
- const isBadgeObject = (badge) => {
150
- return typeof badge === "object" && badge !== null && "text" in badge;
151
- };
152
- if (loading && !title) return /* @__PURE__ */ jsx("div", {
153
- className: cn(currentVariant.wrapper, "transition-all duration-300", className),
154
- children: /* @__PURE__ */ jsxs("div", {
155
- className: "flex flex-col sm:flex-row sm:items-center sm:justify-between",
156
- children: [/* @__PURE__ */ jsxs("div", {
157
- className: "flex items-center gap-4",
158
- children: [Icon && /* @__PURE__ */ jsx(Skeleton, { className: cn(currentVariant.iconSize, "rounded-lg") }), /* @__PURE__ */ jsxs("div", {
159
- className: currentVariant.spacing,
160
- children: [/* @__PURE__ */ jsx(Skeleton, { className: "h-7 w-48" }), /* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-64 mt-1" })]
161
- })]
162
- }), actions && /* @__PURE__ */ jsxs("div", {
163
- className: "flex gap-2 mt-4 sm:mt-0",
164
- children: [/* @__PURE__ */ jsx(Skeleton, { className: "h-9 w-24" }), /* @__PURE__ */ jsx(Skeleton, { className: "h-9 w-24" })]
165
- })]
166
- })
283
+ const railVariants$1 = {
284
+ default: "bg-sidebar",
285
+ dark: "bg-zinc-900 dark:bg-zinc-950",
286
+ primary: "bg-primary",
287
+ muted: "bg-muted"
288
+ };
289
+ const railTextVariants$1 = {
290
+ default: "text-sidebar-foreground",
291
+ dark: "text-zinc-100",
292
+ primary: "text-primary-foreground",
293
+ muted: "text-muted-foreground"
294
+ };
295
+ const railBorderVariants$1 = {
296
+ default: "border-sidebar-border",
297
+ dark: "border-zinc-700 dark:border-zinc-800",
298
+ primary: "border-primary/30",
299
+ muted: "border-border"
300
+ };
301
+
302
+ //#endregion
303
+ //#region src/dashboard/sidebar-user-menu.tsx
304
+ function getUserInitials$1(name) {
305
+ if (!name) return "?";
306
+ const names = name.trim().split(" ");
307
+ if (names.length === 1) return names[0].slice(0, 2).toUpperCase();
308
+ return (names[0][0] + names[names.length - 1][0]).toUpperCase();
309
+ }
310
+ function UserAvatar({ user, className }) {
311
+ return /* @__PURE__ */ jsxs(Avatar, {
312
+ className: cn("h-8 w-8 rounded-lg", className),
313
+ children: [/* @__PURE__ */ jsx(AvatarImage, {
314
+ src: user.avatar,
315
+ alt: user.name
316
+ }), /* @__PURE__ */ jsx(AvatarFallback, {
317
+ className: "rounded-lg bg-sidebar-primary text-sidebar-primary-foreground",
318
+ children: getUserInitials$1(user.name)
319
+ })]
167
320
  });
321
+ }
322
+ function UserInfo({ user }) {
168
323
  return /* @__PURE__ */ jsxs("div", {
169
- className: cn("transition-all duration-300", currentVariant.wrapper, className),
170
- children: [
171
- breadcrumbs && /* @__PURE__ */ jsx("nav", {
172
- className: "mb-3 text-sm text-muted-foreground",
173
- children: breadcrumbs
324
+ className: "grid flex-1 text-left text-sm leading-tight",
325
+ children: [/* @__PURE__ */ jsx("span", {
326
+ className: "truncate font-semibold",
327
+ children: user.name
328
+ }), /* @__PURE__ */ jsx("span", {
329
+ className: "truncate text-xs text-muted-foreground",
330
+ children: user.email
331
+ })]
332
+ });
333
+ }
334
+ /**
335
+ * SidebarUserMenu - User avatar and dropdown menu for sidebar footer.
336
+ * Accepts handlers for logout and custom menu items.
337
+ */
338
+ function SidebarUserMenu({ user, menuItems = [], onLogout, className }) {
339
+ const { isMobile, state } = useSidebar();
340
+ const isCollapsed = state === "collapsed";
341
+ const defaultItems = [{
342
+ label: "Account",
343
+ icon: CircleUserRound,
344
+ href: "/dashboard/profile"
345
+ }];
346
+ const items = menuItems.length > 0 ? menuItems : defaultItems;
347
+ return /* @__PURE__ */ jsx(SidebarMenu, {
348
+ className,
349
+ children: /* @__PURE__ */ jsx(SidebarMenuItem, { children: /* @__PURE__ */ jsxs(DropdownMenu, { children: [/* @__PURE__ */ jsxs(DropdownMenuTrigger, {
350
+ render: /* @__PURE__ */ jsx(SidebarMenuButton, {
351
+ size: "lg",
352
+ tooltip: isCollapsed ? user.name : void 0,
353
+ className: cn("data-open:bg-sidebar-accent data-open:text-sidebar-accent-foreground", isCollapsed && !isMobile && "justify-center")
174
354
  }),
175
- /* @__PURE__ */ jsxs("div", {
176
- className: "flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4",
177
- children: [/* @__PURE__ */ jsxs("div", {
178
- className: "flex items-start sm:items-center gap-4",
179
- children: [Icon && /* @__PURE__ */ jsx("div", {
180
- className: cn("flex aspect-square items-center justify-center rounded-lg flex-shrink-0", "bg-primary/10 text-primary", currentVariant.iconSize, iconClassName),
181
- children: /* @__PURE__ */ jsx(Icon, { className: currentVariant.iconInner })
182
- }), /* @__PURE__ */ jsxs("div", {
183
- className: cn("flex-1", currentVariant.spacing),
184
- children: [
185
- /* @__PURE__ */ jsxs("div", {
186
- className: "flex items-center gap-2 flex-wrap",
187
- children: [/* @__PURE__ */ jsx("h1", {
188
- className: cn("leading-none", currentVariant.title),
189
- children: title
190
- }), badge && (isBadgeObject(badge) ? /* @__PURE__ */ jsx(Badge, {
191
- variant: badge.variant || "secondary",
192
- className: cn("ml-2", badge.className),
193
- children: badge.text
194
- }) : badge)]
195
- }),
196
- description && /* @__PURE__ */ jsx("p", {
197
- className: currentVariant.description,
198
- children: description
199
- }),
200
- metadata && /* @__PURE__ */ jsx("div", {
201
- className: "flex items-center gap-4 mt-2 text-xs text-muted-foreground",
202
- children: metadata.map((item, index) => /* @__PURE__ */ jsxs("div", {
203
- className: "flex items-center gap-1",
204
- children: [item.icon && /* @__PURE__ */ jsx(item.icon, { className: "size-3" }), /* @__PURE__ */ jsx("span", { children: item.text })]
205
- }, index))
206
- })
207
- ]
355
+ children: [/* @__PURE__ */ jsx(UserAvatar, { user }), (!isCollapsed || isMobile) && /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(UserInfo, { user }), /* @__PURE__ */ jsx(ChevronsUpDown, { className: "ml-auto size-4" })] })]
356
+ }), /* @__PURE__ */ jsxs(DropdownMenuContent, {
357
+ className: "w-(--anchor-width) min-w-56 rounded-lg",
358
+ side: isMobile ? "bottom" : "right",
359
+ align: "end",
360
+ sideOffset: 4,
361
+ children: [
362
+ /* @__PURE__ */ jsx(DropdownMenuGroup, { children: /* @__PURE__ */ jsx(DropdownMenuLabel, {
363
+ className: "p-0 font-normal",
364
+ children: /* @__PURE__ */ jsxs("div", {
365
+ className: "flex items-center gap-2 px-1 py-1.5 text-left text-sm",
366
+ children: [/* @__PURE__ */ jsx(UserAvatar, { user }), /* @__PURE__ */ jsx(UserInfo, { user })]
367
+ })
368
+ }) }),
369
+ /* @__PURE__ */ jsx(DropdownMenuSeparator, {}),
370
+ /* @__PURE__ */ jsx(DropdownMenuGroup, { children: items.map((item, index) => /* @__PURE__ */ jsxs(React$1.Fragment, { children: [item.separator && index > 0 && /* @__PURE__ */ jsx(DropdownMenuSeparator, {}), item.href ? /* @__PURE__ */ jsxs(DropdownMenuItem, {
371
+ render: /* @__PURE__ */ jsx(Link, { href: item.href }),
372
+ className: "w-full flex items-center",
373
+ children: [item.icon && /* @__PURE__ */ jsx(item.icon, { className: "mr-2 h-4 w-4" }), item.label]
374
+ }) : /* @__PURE__ */ jsxs(DropdownMenuItem, {
375
+ onClick: item.onClick,
376
+ className: "w-full flex items-center",
377
+ children: [item.icon && /* @__PURE__ */ jsx(item.icon, { className: "mr-2 h-4 w-4" }), item.label]
378
+ })] }, item.label)) }),
379
+ onLogout && /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(DropdownMenuSeparator, {}), /* @__PURE__ */ jsxs(DropdownMenuItem, {
380
+ onClick: onLogout,
381
+ className: "w-full flex items-center",
382
+ children: [/* @__PURE__ */ jsx(LogOut, { className: "mr-2 h-4 w-4" }), "Log out"]
383
+ })] })
384
+ ]
385
+ })] }) })
386
+ });
387
+ }
388
+
389
+ //#endregion
390
+ //#region src/dashboard/presets/dual-sidebar/dual-sidebar-expanded-panel.tsx
391
+ function DualSidebarExpandedPanel({ activeCategoryData, user, isItemActive, variant, isExpanded, onItemClick, headerContent, footerContent }) {
392
+ const isDesktop = variant === "desktop";
393
+ const panelStyles = isDesktop ? {
394
+ className: cn("flex flex-col bg-sidebar overflow-hidden transition-[width,opacity] duration-200 ease-linear", isExpanded ? "opacity-100" : "opacity-0 w-0"),
395
+ style: { width: isExpanded ? SIDEBAR_PANEL_WIDTH : 0 }
396
+ } : {
397
+ className: "flex-1 flex flex-col bg-sidebar overflow-hidden",
398
+ style: void 0
399
+ };
400
+ if (!activeCategoryData) return /* @__PURE__ */ jsx("div", {
401
+ ...panelStyles,
402
+ style: panelStyles.style,
403
+ children: /* @__PURE__ */ jsx("div", {
404
+ className: "flex-1 flex items-center justify-center text-sidebar-foreground/60 text-sm",
405
+ children: "Select a category"
406
+ })
407
+ });
408
+ return /* @__PURE__ */ jsxs("div", {
409
+ className: panelStyles.className,
410
+ style: panelStyles.style,
411
+ children: [
412
+ /* @__PURE__ */ jsx(SidebarHeader, {
413
+ className: "h-12 border-b border-sidebar-border px-4 flex items-center shrink-0",
414
+ children: /* @__PURE__ */ jsxs("div", {
415
+ className: "flex items-center gap-2",
416
+ children: [/* @__PURE__ */ jsx(activeCategoryData.icon, { className: "h-5 w-5 text-sidebar-foreground" }), /* @__PURE__ */ jsx("span", {
417
+ className: "font-semibold text-sidebar-foreground truncate",
418
+ children: activeCategoryData.name
208
419
  })]
209
- }), actions && /* @__PURE__ */ jsx("div", {
210
- className: "flex flex-wrap gap-2 mt-4 sm:mt-0",
211
- children: actions.map((action, index) => {
212
- const isActionLoading = action.loading ?? false;
213
- const isDisabled = loading || action.disabled || isActionLoading;
214
- const displayText = isActionLoading ? action.loadingText || action.text : action.text;
215
- return /* @__PURE__ */ jsxs(Button, {
216
- onClick: action.onClick,
217
- disabled: isDisabled,
218
- variant: action.variant || "default",
219
- size: action.size || (variant === "compact" ? "sm" : "default"),
220
- className: cn(variant === "hero" && "shadow-md hover:shadow-lg", action.className),
420
+ })
421
+ }),
422
+ headerContent && /* @__PURE__ */ jsx("div", {
423
+ className: "px-3 py-2 border-b border-sidebar-border shrink-0",
424
+ children: headerContent
425
+ }),
426
+ /* @__PURE__ */ jsx(SidebarContent, {
427
+ className: "flex-1 overflow-y-auto",
428
+ children: /* @__PURE__ */ jsxs(SidebarGroup, {
429
+ className: "p-2",
430
+ children: [isDesktop && /* @__PURE__ */ jsxs(SidebarGroupLabel, {
431
+ className: "px-2 text-xs text-sidebar-foreground/60",
432
+ children: [activeCategoryData.name, " Menu"]
433
+ }), /* @__PURE__ */ jsx(SidebarMenu, { children: activeCategoryData.items.map((item, idx) => {
434
+ const Icon = item.icon;
435
+ const keyPrefix = isDesktop ? "" : "mobile-";
436
+ return /* @__PURE__ */ jsx(SidebarMenuItem, { children: /* @__PURE__ */ jsxs(SidebarMenuButton, {
437
+ isActive: isItemActive(item),
438
+ render: /* @__PURE__ */ jsx(Link, {
439
+ href: item.url,
440
+ onClick: onItemClick
441
+ }),
221
442
  children: [
222
- action.icon && action.iconPosition !== "right" && /* @__PURE__ */ jsx(action.icon, { className: "size-4 mr-2" }),
223
- /* @__PURE__ */ jsx("span", { children: displayText }),
224
- action.icon && action.iconPosition === "right" && /* @__PURE__ */ jsx(action.icon, { className: "size-4 ml-2" })
443
+ Icon && /* @__PURE__ */ jsx(Icon, { className: "h-4 w-4" }),
444
+ /* @__PURE__ */ jsx("span", {
445
+ className: "truncate",
446
+ children: item.title
447
+ }),
448
+ item.badge && /* @__PURE__ */ jsx("span", {
449
+ className: "ml-auto text-xs bg-sidebar-accent text-sidebar-accent-foreground px-1.5 py-0.5 rounded shrink-0",
450
+ children: item.badge
451
+ })
225
452
  ]
226
- }, index);
227
- })
228
- })]
453
+ }) }, `${keyPrefix}${activeCategoryData.id}-${item.title}-${idx}`);
454
+ }) })]
455
+ })
229
456
  }),
230
- children && /* @__PURE__ */ jsx("div", {
231
- className: cn("mt-4 pt-4 border-t", variant === "minimal" && "border-0 pt-2"),
232
- children
457
+ footerContent && /* @__PURE__ */ jsx("div", {
458
+ className: "px-3 py-2 border-t border-sidebar-border shrink-0",
459
+ children: footerContent
460
+ }),
461
+ user && /* @__PURE__ */ jsx(SidebarFooter, {
462
+ className: "border-t border-sidebar-border shrink-0",
463
+ children: /* @__PURE__ */ jsx(SidebarUserMenu, {
464
+ user: user.data,
465
+ menuItems: user.menuItems,
466
+ onLogout: user.onLogout
467
+ })
233
468
  })
234
469
  ]
235
470
  });
236
471
  }
237
472
 
238
473
  //#endregion
239
- //#region src/dashboard/page-header.tsx
240
- function PageHeader({ items, className, actions }) {
241
- return /* @__PURE__ */ jsxs("header", {
242
- className: cn("flex h-12 shrink-0 items-center justify-between gap-2", className),
243
- children: [/* @__PURE__ */ jsxs("div", {
244
- className: "flex items-center gap-2",
245
- children: [
246
- /* @__PURE__ */ jsx(SidebarTrigger, { className: "-ml-1" }),
247
- /* @__PURE__ */ jsx(Separator, {
248
- orientation: "vertical",
249
- className: "!self-center h-4"
250
- }),
251
- /* @__PURE__ */ jsx(Breadcrumb, { children: /* @__PURE__ */ jsx(BreadcrumbList, { children: items.map((item, index) => /* @__PURE__ */ jsxs(React.Fragment, { children: [/* @__PURE__ */ jsx(BreadcrumbItem, {
252
- className: item.current ? "" : "hidden md:block",
253
- children: item.current ? /* @__PURE__ */ jsx(BreadcrumbPage, { children: item.label }) : /* @__PURE__ */ jsx(BreadcrumbLink, {
254
- render: /* @__PURE__ */ jsx(Link, { href: item.href || "#" }),
255
- children: item.label
256
- })
257
- }), index < items.length - 1 && /* @__PURE__ */ jsx(BreadcrumbSeparator, { className: "hidden md:block" })] }, `breadcrumb-${index}`)) }) })
258
- ]
259
- }), actions]
260
- });
261
- }
262
-
263
- //#endregion
264
- //#region src/dashboard/dashboard-content.tsx
265
- const PADDING = {
266
- none: "",
267
- sm: "px-2",
268
- md: "px-4",
269
- lg: "px-6 lg:px-8"
270
- };
474
+ //#region src/dashboard/presets/dual-sidebar/rail-tooltip.tsx
271
475
  /**
272
- * DashboardContentSafe content wrapper for sidebar layouts.
273
- *
274
- * Prevents the classic flexbox overflow bug where children with
275
- * `max-w-*` or grid layouts extend beyond the available space
276
- * (behind the sidebar) by propagating `min-w-0` through the
277
- * flex chain.
278
- *
279
- * **Important**: `SidebarInset` also needs `min-w-0` for the
280
- * width constraint to propagate correctly from the flex parent.
281
- * Pass `className="min-w-0"` to `SidebarInset`.
476
+ * RailTooltipPure CSS/Tailwind tooltip for sidebar icon rails.
282
477
  *
283
- * @example
284
- * ```tsx
285
- * <SidebarProvider>
286
- * <AppSidebar />
287
- * <SidebarInset className="min-w-0">
288
- * <DashboardContent>
289
- * <PageHeader items={breadcrumbs} />
290
- * <MyPageContent />
291
- * </DashboardContent>
292
- * </SidebarInset>
293
- * </SidebarProvider>
294
- * ```
478
+ * - SSR-safe: no Base UI, no auto-generated IDs, no hydration mismatch
479
+ * - Accessible: aria-label on wrapper for screen readers
480
+ * - Shows on hover with delay, hides on click/active
481
+ * - Styled to match shadcn's tooltip (bg-foreground, text-background, arrow)
295
482
  */
296
- function DashboardContent({ children, className, container = true, padding = "md" }) {
297
- const containerClass = container === true ? "@container/main" : typeof container === "string" ? `@container/${container}` : "";
298
- return /* @__PURE__ */ jsx("div", {
299
- className: cn("flex flex-1 flex-col min-w-0", className),
300
- children: /* @__PURE__ */ jsx("div", {
301
- className: cn("flex flex-1 flex-col gap-2 min-w-0", containerClass, PADDING[padding]),
302
- children
303
- })
483
+ function RailTooltip({ label, children, className }) {
484
+ return /* @__PURE__ */ jsxs("div", {
485
+ className: cn("group/tip relative", className),
486
+ "aria-label": label,
487
+ children: [children, /* @__PURE__ */ jsxs("div", {
488
+ role: "tooltip",
489
+ "aria-hidden": true,
490
+ className: cn("pointer-events-none absolute left-full top-1/2 -translate-y-1/2 ml-3 z-50", "rounded-md bg-foreground px-3 py-1.5 text-xs font-medium text-background", "shadow-md whitespace-nowrap", "opacity-0 scale-95 origin-left", "transition-[opacity,transform] duration-100", "group-hover/tip:opacity-100 group-hover/tip:scale-100 group-hover/tip:delay-300", "group-active/tip:opacity-0 group-active/tip:scale-95 group-active/tip:delay-0"),
491
+ children: [label, /* @__PURE__ */ jsx("div", {
492
+ className: "absolute right-full top-1/2 -translate-y-1/2 -mr-[3px]",
493
+ children: /* @__PURE__ */ jsx("div", { className: "size-2 rotate-45 bg-foreground rounded-[1px]" })
494
+ })]
495
+ })]
304
496
  });
305
497
  }
306
498
 
307
499
  //#endregion
308
- //#region src/dashboard/dashboard-page-layout.tsx
309
- const defaultFallback = /* @__PURE__ */ jsx("div", {
310
- className: "flex items-center justify-center py-12",
311
- children: /* @__PURE__ */ jsx("div", { className: "h-6 w-6 animate-spin rounded-full border-2 border-primary border-t-transparent" })
312
- });
313
- /**
314
- * DashboardPageLayout — Standard page shell for dashboard pages.
315
- * Combines PageHeader (breadcrumbs + actions) with optional Suspense boundary.
316
- *
317
- * @example
318
- * ```tsx
319
- * <DashboardPageLayout
320
- * breadcrumbs={[
321
- * { label: "Dashboard", href: "/dashboard" },
322
- * { label: "Posts", current: true },
323
- * ]}
324
- * actions={<Button>New Post</Button>}
325
- * fallback={<SkeletonTable rows={5} />}
326
- * >
327
- * <PostsList />
328
- * </DashboardPageLayout>
329
- * ```
330
- */
331
- function DashboardPageLayout({ breadcrumbs, actions, suspense = true, fallback = defaultFallback, className, children }) {
332
- return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(PageHeader, {
333
- items: breadcrumbs,
334
- actions
335
- }), /* @__PURE__ */ jsx("div", {
336
- className: cn("flex-1", className),
337
- children: suspense ? /* @__PURE__ */ jsx(Suspense, {
338
- fallback,
339
- children
340
- }) : children
341
- })] });
500
+ //#region src/dashboard/presets/dual-sidebar/dual-sidebar-icon-rail.tsx
501
+ function DualSidebarIconRail({ brand, categories, activeCategory, railVariant, isExpanded, variant, onCategorySelect, onToggleExpanded, onBrandClick }) {
502
+ const isDarkOrPrimary = railVariant === "dark" || railVariant === "primary";
503
+ const brandButtonClasses = cn("flex items-center justify-center h-10 w-10 rounded-lg shadow-sm transition-colors", isDarkOrPrimary ? "bg-white/10 hover:bg-white/20 text-inherit" : "bg-sidebar-primary text-sidebar-primary-foreground");
504
+ const brandElement = /* @__PURE__ */ jsx(Link, {
505
+ href: brand.href || "/",
506
+ className: brandButtonClasses,
507
+ onClick: onBrandClick,
508
+ children: brand.icon
509
+ });
510
+ return /* @__PURE__ */ jsxs("div", {
511
+ className: cn("flex flex-col shrink-0", variant === "desktop" && "transition-colors", railVariants$1[railVariant], railTextVariants$1[railVariant], variant === "mobile" ? cn("border-r", railBorderVariants$1[railVariant]) : isExpanded && cn("border-r", railBorderVariants$1[railVariant])),
512
+ style: RAIL_STYLE,
513
+ children: [
514
+ /* @__PURE__ */ jsx("div", {
515
+ className: "flex items-center justify-center p-2 h-12",
516
+ children: variant === "desktop" ? /* @__PURE__ */ jsx(RailTooltip, {
517
+ label: brand.title,
518
+ children: brandElement
519
+ }) : brandElement
520
+ }),
521
+ /* @__PURE__ */ jsx("div", {
522
+ className: "flex-1 flex flex-col gap-1 p-2",
523
+ children: categories.map((category) => {
524
+ const Icon = category.icon;
525
+ const button = /* @__PURE__ */ jsx("button", {
526
+ type: "button",
527
+ onClick: () => onCategorySelect(category.id),
528
+ className: cn("flex items-center justify-center h-10 w-10 rounded-lg transition-colors", activeCategory === category.id ? isDarkOrPrimary ? "bg-white/20" : "bg-sidebar-accent text-sidebar-accent-foreground" : isDarkOrPrimary ? "hover:bg-white/10" : "hover:bg-sidebar-accent/50"),
529
+ children: /* @__PURE__ */ jsx(Icon, { className: "h-5 w-5" })
530
+ });
531
+ if (variant === "desktop") return /* @__PURE__ */ jsx(RailTooltip, {
532
+ label: category.name,
533
+ children: button
534
+ }, category.id);
535
+ return /* @__PURE__ */ jsx("div", { children: button }, category.id);
536
+ })
537
+ }),
538
+ variant === "desktop" && onToggleExpanded && /* @__PURE__ */ jsx("div", {
539
+ className: "p-2",
540
+ children: /* @__PURE__ */ jsx(RailTooltip, {
541
+ label: isExpanded ? "Collapse sidebar" : "Expand sidebar",
542
+ children: /* @__PURE__ */ jsx("button", {
543
+ type: "button",
544
+ onClick: onToggleExpanded,
545
+ className: cn("flex items-center justify-center h-10 w-10 rounded-lg transition-colors", isDarkOrPrimary ? "hover:bg-white/10" : "hover:bg-sidebar-accent/50"),
546
+ children: isExpanded ? /* @__PURE__ */ jsx(ChevronLeft, { className: "h-5 w-5" }) : /* @__PURE__ */ jsx(ChevronRight, { className: "h-5 w-5" })
547
+ })
548
+ })
549
+ })
550
+ ]
551
+ });
342
552
  }
343
553
 
344
554
  //#endregion
345
- //#region src/dashboard/sidebar-brand.tsx
346
- /**
347
- * SidebarBrand - Logo and title section for dashboard sidebar.
348
- * Automatically handles collapsed state styling.
349
- */
350
- function SidebarBrand({ title, icon, href = "/", className, tooltip }) {
351
- const { isMobile, state } = useSidebar();
352
- const isCollapsed = state === "collapsed";
353
- return /* @__PURE__ */ jsx(SidebarMenu, {
354
- className,
355
- children: /* @__PURE__ */ jsx(SidebarMenuItem, { children: /* @__PURE__ */ jsxs(SidebarMenuButton, {
356
- size: "lg",
357
- className: "group-data-[collapsible=icon]:p-0!",
358
- tooltip: isCollapsed ? tooltip || title : void 0,
359
- render: /* @__PURE__ */ jsx(Link, {
360
- href,
361
- className: cn("flex items-center text-sidebar-foreground", isCollapsed && !isMobile && "justify-center")
555
+ //#region src/dashboard/presets/dual-sidebar/dual-sidebar-mobile.tsx
556
+ function DualSidebarMobile({ brand, categories, user, activeCategory, activeCategoryData, railVariant, ariaLabel, mobileOpen, isControlledMobile, isItemActive, onMobileOpenChange, onCategorySelect, headerContent, footerContent }) {
557
+ return /* @__PURE__ */ jsxs(Sheet, {
558
+ open: mobileOpen,
559
+ onOpenChange: onMobileOpenChange,
560
+ children: [!isControlledMobile && /* @__PURE__ */ jsx(SheetTrigger, {
561
+ render: /* @__PURE__ */ jsx(Button, {
562
+ variant: "outline",
563
+ size: "icon",
564
+ className: "md:hidden fixed top-2 left-2 z-40 h-9 w-9 bg-background/80 backdrop-blur-sm shadow-sm",
565
+ "aria-label": "Open navigation"
362
566
  }),
363
- children: [/* @__PURE__ */ jsx("div", {
364
- className: cn("rounded-lg flex items-center justify-center bg-sidebar-primary text-sidebar-primary-foreground shadow-sm flex-shrink-0 transition-[width,height]", isCollapsed && !isMobile ? "h-6 w-6" : "h-8 w-8"),
365
- children: icon
366
- }), /* @__PURE__ */ jsx("span", {
367
- className: cn("ml-2 font-semibold tracking-tight truncate", !isMobile && isCollapsed && "hidden"),
368
- children: title
369
- })]
370
- }) })
567
+ children: /* @__PURE__ */ jsx(Menu, { className: "h-5 w-5" })
568
+ }), /* @__PURE__ */ jsxs(SheetContent, {
569
+ side: "left",
570
+ className: "w-72 p-0",
571
+ children: [
572
+ /* @__PURE__ */ jsx(SheetTitle, {
573
+ className: "sr-only",
574
+ children: ariaLabel
575
+ }),
576
+ /* @__PURE__ */ jsx(SheetDescription, {
577
+ className: "sr-only",
578
+ children: "Navigation menu with category icons and expanded content"
579
+ }),
580
+ /* @__PURE__ */ jsxs("div", {
581
+ className: "flex h-full flex-row",
582
+ children: [/* @__PURE__ */ jsx(DualSidebarIconRail, {
583
+ brand,
584
+ categories,
585
+ activeCategory,
586
+ railVariant,
587
+ variant: "mobile",
588
+ onCategorySelect,
589
+ onBrandClick: () => onMobileOpenChange(false)
590
+ }), /* @__PURE__ */ jsx(DualSidebarExpandedPanel, {
591
+ activeCategoryData,
592
+ user,
593
+ isItemActive,
594
+ variant: "mobile",
595
+ onItemClick: () => onMobileOpenChange(false),
596
+ headerContent,
597
+ footerContent
598
+ })]
599
+ })
600
+ ]
601
+ })]
371
602
  });
372
603
  }
373
604
 
374
605
  //#endregion
375
- //#region src/dashboard/sidebar-nav.tsx
606
+ //#region src/dashboard/presets/dual-sidebar/dual-sidebar.tsx
376
607
  /**
377
- * SidebarNavItem - Single navigation item with optional sub-items.
608
+ * DualSidebar - A sidebar with icon rail and collapsible expanded panel.
609
+ * Mimics the inset sidebar variant structure for consistent styling.
378
610
  */
379
- function SidebarNavItem({ item, onClick }) {
380
- const hasSubItems = item.items && item.items.length > 0;
381
- const Icon = item.icon;
382
- const [open, setOpen] = useState(item.defaultOpen ?? item.isActive ?? false);
383
- if (hasSubItems) return /* @__PURE__ */ jsx(SidebarMenuItem, { children: /* @__PURE__ */ jsxs(Collapsible, {
384
- open,
385
- onOpenChange: setOpen,
386
- className: "group/collapsible",
387
- children: [/* @__PURE__ */ jsxs(CollapsibleTrigger, {
388
- render: /* @__PURE__ */ jsx(SidebarMenuButton, {}),
389
- children: [
390
- Icon && /* @__PURE__ */ jsx(Icon, { className: "h-4 w-4" }),
391
- /* @__PURE__ */ jsx("span", { children: item.title }),
392
- item.badge && /* @__PURE__ */ jsx("span", {
393
- className: "ml-auto text-xs bg-sidebar-accent text-sidebar-accent-foreground px-1.5 py-0.5 rounded",
394
- children: item.badge
395
- }),
396
- /* @__PURE__ */ jsx(ChevronRight, { className: "ml-auto h-4 w-4 transition-transform duration-200 group-data-open/collapsible:rotate-90" })
397
- ]
398
- }), /* @__PURE__ */ jsx(CollapsibleContent, { children: /* @__PURE__ */ jsx(SidebarMenuSub, { children: item.items.map((subItem, idx) => /* @__PURE__ */ jsx(SidebarMenuSubItem, { children: /* @__PURE__ */ jsxs(SidebarMenuSubButton, {
399
- render: /* @__PURE__ */ jsx(Link, { href: subItem.url }),
400
- isActive: subItem.isActive,
401
- children: [/* @__PURE__ */ jsx("span", { children: subItem.title }), subItem.badge && /* @__PURE__ */ jsx("span", {
402
- className: "ml-auto text-xs opacity-60",
403
- children: subItem.badge
404
- })]
405
- }) }, `${item.title}-${subItem.title}-${idx}`)) }) })]
406
- }) });
407
- return /* @__PURE__ */ jsx(SidebarMenuItem, { children: /* @__PURE__ */ jsx(Link, {
408
- href: item.url,
409
- className: "w-full",
410
- children: /* @__PURE__ */ jsxs(SidebarMenuButton, {
411
- isActive: item.isActive,
412
- onClick,
413
- children: [
414
- Icon && /* @__PURE__ */ jsx(Icon, { className: "h-4 w-4" }),
415
- /* @__PURE__ */ jsx("span", { children: item.title }),
416
- item.badge && /* @__PURE__ */ jsx("span", {
417
- className: "ml-auto text-xs bg-sidebar-accent text-sidebar-accent-foreground px-1.5 py-0.5 rounded",
418
- children: item.badge
419
- })
420
- ]
421
- })
422
- }) });
423
- }
424
- /**
425
- * SidebarNavGroup - A group of navigation items with optional title.
426
- */
427
- function SidebarNavGroup({ title, items, className, onItemClick }) {
428
- return /* @__PURE__ */ jsxs(SidebarGroup, {
429
- className,
430
- children: [title && /* @__PURE__ */ jsx(SidebarGroupLabel, { children: title }), /* @__PURE__ */ jsx(SidebarMenu, { children: items.map((item, idx) => /* @__PURE__ */ jsx(SidebarNavItem, {
431
- item,
432
- onClick: () => onItemClick?.(item)
433
- }, `${item.title}-${idx}`)) })]
434
- });
435
- }
436
- /**
437
- * SidebarNav - Complete navigation section with multiple groups.
438
- */
439
- function SidebarNav({ groups, className, onItemClick }) {
440
- return /* @__PURE__ */ jsx("div", {
441
- className: cn("flex flex-col gap-2", className),
442
- children: groups.map((group, index) => /* @__PURE__ */ jsx(SidebarNavGroup, {
443
- title: group.title,
444
- items: group.items,
445
- onItemClick
446
- }, group.title || `nav-group-${index}`))
447
- });
448
- }
449
-
450
- //#endregion
451
- //#region src/dashboard/sidebar-user-menu.tsx
452
- function getUserInitials$1(name) {
453
- if (!name) return "?";
454
- const names = name.trim().split(" ");
455
- if (names.length === 1) return names[0].slice(0, 2).toUpperCase();
456
- return (names[0][0] + names[names.length - 1][0]).toUpperCase();
457
- }
458
- function UserAvatar({ user, className }) {
459
- return /* @__PURE__ */ jsxs(Avatar, {
460
- className: cn("h-8 w-8 rounded-lg", className),
461
- children: [/* @__PURE__ */ jsx(AvatarImage, {
462
- src: user.avatar,
463
- alt: user.name
464
- }), /* @__PURE__ */ jsx(AvatarFallback, {
465
- className: "rounded-lg bg-sidebar-primary text-sidebar-primary-foreground",
466
- children: getUserInitials$1(user.name)
467
- })]
468
- });
469
- }
470
- function UserInfo({ user }) {
471
- return /* @__PURE__ */ jsxs("div", {
472
- className: "grid flex-1 text-left text-sm leading-tight",
473
- children: [/* @__PURE__ */ jsx("span", {
474
- className: "truncate font-semibold",
475
- children: user.name
476
- }), /* @__PURE__ */ jsx("span", {
477
- className: "truncate text-xs text-muted-foreground",
478
- children: user.email
611
+ function DualSidebar({ brand, categories, user, defaultCategoryId, activeCategoryId: controlledCategoryId, onCategoryChange, defaultCollapsed = false, collapsed: controlledCollapsed, onCollapsedChange, mobileOpen: controlledMobileOpen, onMobileOpenChange, headerContent, footerContent, railVariant = "default", sidebarStyle = "inset", ariaLabel = "Navigation Menu", className }) {
612
+ const pathname = usePathname();
613
+ const [manualOverride, setManualOverride] = React$1.useState(null);
614
+ const [internalExpanded, setInternalExpanded] = React$1.useState(!defaultCollapsed);
615
+ const [internalMobileOpen, setInternalMobileOpen] = React$1.useState(false);
616
+ const { isMobile } = useSidebar();
617
+ const pathnameMatch = React$1.useMemo(() => findActiveCategory(categories, pathname), [pathname, categories]);
618
+ const manualCategory = manualOverride && manualOverride.forPathname === pathname ? manualOverride.id : null;
619
+ const isControlledCategory = controlledCategoryId !== void 0;
620
+ const activeCategory = isControlledCategory ? controlledCategoryId : manualCategory ?? pathnameMatch?.category.id ?? defaultCategoryId ?? (categories.length > 0 ? categories[0].id : null);
621
+ const setActiveCategory = React$1.useCallback((id) => {
622
+ if (isControlledCategory) onCategoryChange?.(id);
623
+ else {
624
+ setManualOverride({
625
+ id,
626
+ forPathname: pathname
627
+ });
628
+ onCategoryChange?.(id);
629
+ }
630
+ }, [
631
+ isControlledCategory,
632
+ onCategoryChange,
633
+ pathname
634
+ ]);
635
+ const isItemActive = React$1.useCallback((item) => {
636
+ if (item.isActive !== void 0) return item.isActive;
637
+ return item.url === pathnameMatch?.item.url;
638
+ }, [pathnameMatch]);
639
+ const isControlledCollapsed = controlledCollapsed !== void 0;
640
+ const isExpanded = isControlledCollapsed ? !controlledCollapsed : internalExpanded;
641
+ const setExpanded = React$1.useCallback((expanded) => {
642
+ if (isControlledCollapsed) onCollapsedChange?.(!expanded);
643
+ else {
644
+ setInternalExpanded(expanded);
645
+ onCollapsedChange?.(!expanded);
646
+ }
647
+ }, [isControlledCollapsed, onCollapsedChange]);
648
+ const isControlledMobile = controlledMobileOpen !== void 0;
649
+ const mobileOpen = isControlledMobile ? controlledMobileOpen : internalMobileOpen;
650
+ const setMobileOpen = React$1.useCallback((open) => {
651
+ if (isControlledMobile) onMobileOpenChange?.(open);
652
+ else setInternalMobileOpen(open);
653
+ }, [isControlledMobile, onMobileOpenChange]);
654
+ const handleCategorySelect = (id) => {
655
+ setActiveCategory(id);
656
+ if (!isExpanded) setExpanded(true);
657
+ };
658
+ const handleMobileCategorySelect = (id) => {
659
+ setActiveCategory(id);
660
+ };
661
+ const toggleExpanded = () => {
662
+ setExpanded(!isExpanded);
663
+ };
664
+ const activeCategoryData = categories.find((c) => c.id === activeCategory) || null;
665
+ const isFlush = sidebarStyle === "flush";
666
+ const currentWidth = isExpanded ? SIDEBAR_WIDTH_EXPANDED : SIDEBAR_WIDTH_COLLAPSED;
667
+ const totalWidth = isFlush ? currentWidth : `calc(${currentWidth} + ${SIDEBAR_PADDING} * 2)`;
668
+ return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(DualSidebarMobile, {
669
+ brand,
670
+ categories,
671
+ user,
672
+ activeCategory,
673
+ activeCategoryData,
674
+ railVariant,
675
+ ariaLabel,
676
+ mobileOpen,
677
+ isControlledMobile,
678
+ isItemActive,
679
+ onMobileOpenChange: setMobileOpen,
680
+ onCategorySelect: handleMobileCategorySelect,
681
+ headerContent,
682
+ footerContent
683
+ }), /* @__PURE__ */ jsxs("div", {
684
+ className: "group peer text-sidebar-foreground hidden md:block",
685
+ "data-state": "expanded",
686
+ "data-collapsible": "",
687
+ "data-variant": isFlush ? "sidebar" : "inset",
688
+ "data-side": "left",
689
+ "data-slot": "sidebar",
690
+ children: [/* @__PURE__ */ jsx("div", {
691
+ "data-slot": "sidebar-gap",
692
+ className: "relative bg-transparent transition-[width] duration-200 ease-linear",
693
+ style: { width: totalWidth }
694
+ }), /* @__PURE__ */ jsx("div", {
695
+ "data-slot": "sidebar-container",
696
+ className: cn("fixed inset-y-0 left-0 z-10 hidden h-svh transition-[width] duration-200 ease-linear md:flex", className),
697
+ style: {
698
+ width: totalWidth,
699
+ ...isFlush ? {} : { padding: SIDEBAR_PADDING }
700
+ },
701
+ children: /* @__PURE__ */ jsxs("div", {
702
+ "data-sidebar": "sidebar",
703
+ "data-slot": "sidebar-inner",
704
+ className: cn("bg-sidebar flex h-full w-full flex-row overflow-hidden", isFlush ? "border-r border-sidebar-border" : "rounded-lg border border-sidebar-border shadow-sm"),
705
+ children: [/* @__PURE__ */ jsx(DualSidebarIconRail, {
706
+ brand,
707
+ categories,
708
+ activeCategory,
709
+ railVariant,
710
+ isExpanded,
711
+ variant: "desktop",
712
+ onCategorySelect: handleCategorySelect,
713
+ onToggleExpanded: toggleExpanded
714
+ }), /* @__PURE__ */ jsx(DualSidebarExpandedPanel, {
715
+ activeCategoryData,
716
+ user,
717
+ isItemActive,
718
+ variant: "desktop",
719
+ isExpanded,
720
+ headerContent,
721
+ footerContent
722
+ })]
723
+ })
479
724
  })]
480
- });
481
- }
482
- /**
483
- * SidebarUserMenu - User avatar and dropdown menu for sidebar footer.
484
- * Accepts handlers for logout and custom menu items.
485
- */
486
- function SidebarUserMenu({ user, menuItems = [], onLogout, className }) {
487
- const { isMobile, state } = useSidebar();
488
- const isCollapsed = state === "collapsed";
489
- const defaultItems = [{
490
- label: "Account",
491
- icon: CircleUserRound,
492
- href: "/dashboard/profile"
493
- }];
494
- const items = menuItems.length > 0 ? menuItems : defaultItems;
495
- return /* @__PURE__ */ jsx(SidebarMenu, {
496
- className,
497
- children: /* @__PURE__ */ jsx(SidebarMenuItem, { children: /* @__PURE__ */ jsxs(DropdownMenu, { children: [/* @__PURE__ */ jsxs(DropdownMenuTrigger, {
498
- render: /* @__PURE__ */ jsx(SidebarMenuButton, {
499
- size: "lg",
500
- tooltip: isCollapsed ? user.name : void 0,
501
- className: cn("data-open:bg-sidebar-accent data-open:text-sidebar-accent-foreground", isCollapsed && !isMobile && "justify-center")
502
- }),
503
- children: [/* @__PURE__ */ jsx(UserAvatar, { user }), (!isCollapsed || isMobile) && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(UserInfo, { user }), /* @__PURE__ */ jsx(ChevronsUpDown, { className: "ml-auto size-4" })] })]
504
- }), /* @__PURE__ */ jsxs(DropdownMenuContent, {
505
- className: "w-(--anchor-width) min-w-56 rounded-lg",
506
- side: isMobile ? "bottom" : "right",
507
- align: "end",
508
- sideOffset: 4,
509
- children: [
510
- /* @__PURE__ */ jsx(DropdownMenuGroup, { children: /* @__PURE__ */ jsx(DropdownMenuLabel, {
511
- className: "p-0 font-normal",
512
- children: /* @__PURE__ */ jsxs("div", {
513
- className: "flex items-center gap-2 px-1 py-1.5 text-left text-sm",
514
- children: [/* @__PURE__ */ jsx(UserAvatar, { user }), /* @__PURE__ */ jsx(UserInfo, { user })]
515
- })
516
- }) }),
517
- /* @__PURE__ */ jsx(DropdownMenuSeparator, {}),
518
- /* @__PURE__ */ jsx(DropdownMenuGroup, { children: items.map((item, index) => /* @__PURE__ */ jsxs(React$1.Fragment, { children: [item.separator && index > 0 && /* @__PURE__ */ jsx(DropdownMenuSeparator, {}), item.href ? /* @__PURE__ */ jsxs(DropdownMenuItem, {
519
- render: /* @__PURE__ */ jsx(Link, { href: item.href }),
520
- className: "w-full flex items-center",
521
- children: [item.icon && /* @__PURE__ */ jsx(item.icon, { className: "mr-2 h-4 w-4" }), item.label]
522
- }) : /* @__PURE__ */ jsxs(DropdownMenuItem, {
523
- onClick: item.onClick,
524
- className: "w-full flex items-center",
525
- children: [item.icon && /* @__PURE__ */ jsx(item.icon, { className: "mr-2 h-4 w-4" }), item.label]
526
- })] }, item.label)) }),
527
- onLogout && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(DropdownMenuSeparator, {}), /* @__PURE__ */ jsxs(DropdownMenuItem, {
528
- onClick: onLogout,
529
- className: "w-full flex items-center",
530
- children: [/* @__PURE__ */ jsx(LogOut, { className: "mr-2 h-4 w-4" }), "Log out"]
531
- })] })
532
- ]
533
- })] }) })
534
- });
725
+ })] });
535
726
  }
536
727
 
537
728
  //#endregion
@@ -572,7 +763,7 @@ function DropdownContent({ items, selected, onSelect, label, renderItem, isMobil
572
763
  className: "text-xs text-muted-foreground",
573
764
  children: item.code
574
765
  }),
575
- item.type && /* @__PURE__ */ jsxs(Fragment, { children: [item.code && /* @__PURE__ */ jsx("span", {
766
+ item.type && /* @__PURE__ */ jsxs(Fragment$1, { children: [item.code && /* @__PURE__ */ jsx("span", {
576
767
  className: "text-xs text-muted-foreground",
577
768
  children: "•"
578
769
  }), /* @__PURE__ */ jsx(Badge, {
@@ -613,7 +804,7 @@ function SidebarLoadingSkeleton({ isCollapsed, isMobile }) {
613
804
  }) }) });
614
805
  }
615
806
  function SidebarProjectDisplay({ project, Icon, isCollapsed, isMobile }) {
616
- return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Icon, { className: "h-4 w-4 text-muted-foreground shrink-0" }), (!isCollapsed || isMobile) && /* @__PURE__ */ jsxs("div", {
807
+ return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(Icon, { className: "h-4 w-4 text-muted-foreground shrink-0" }), (!isCollapsed || isMobile) && /* @__PURE__ */ jsxs("div", {
617
808
  className: "flex-1 min-w-0 leading-tight text-left",
618
809
  children: [/* @__PURE__ */ jsx("p", {
619
810
  className: "text-xs font-medium truncate",
@@ -634,7 +825,7 @@ function StandaloneLoadingSkeleton({ size }) {
634
825
  });
635
826
  }
636
827
  function StandaloneProjectDisplay({ project, Icon, size }) {
637
- return /* @__PURE__ */ jsxs(Fragment, { children: [
828
+ return /* @__PURE__ */ jsxs(Fragment$1, { children: [
638
829
  /* @__PURE__ */ jsx(Icon, { className: cn("text-muted-foreground shrink-0", size === "sm" ? "h-3.5 w-3.5" : "h-4 w-4") }),
639
830
  /* @__PURE__ */ jsx("span", {
640
831
  className: cn("font-medium truncate max-w-[120px]", size === "sm" ? "text-xs" : "text-sm"),
@@ -755,372 +946,118 @@ function ProjectSwitcher({ items, selected, onSelect, isLoading = false, label =
755
946
  }
756
947
 
757
948
  //#endregion
758
- //#region src/dashboard/dashboard-header.tsx
759
- function BreadcrumbNav({ items }) {
760
- return /* @__PURE__ */ jsx(Breadcrumb, { children: /* @__PURE__ */ jsx(BreadcrumbList, { children: items.map((item, index) => /* @__PURE__ */ jsxs(React$1.Fragment, { children: [/* @__PURE__ */ jsx(BreadcrumbItem, {
761
- className: item.current ? "" : "hidden md:block",
762
- children: item.current ? /* @__PURE__ */ jsx(BreadcrumbPage, { children: item.label }) : /* @__PURE__ */ jsx(BreadcrumbLink, {
763
- render: /* @__PURE__ */ jsx(Link, { href: item.href || "#" }),
764
- children: item.label
765
- })
766
- }), index < items.length - 1 && /* @__PURE__ */ jsx(BreadcrumbSeparator, { className: "hidden md:block" })] }, `breadcrumb-${index}`)) }) });
767
- }
768
- /**
769
- * DashboardHeader - Top header bar for dashboard pages.
770
- * Includes sidebar trigger, breadcrumbs, and action slots.
771
- */
772
- function DashboardHeader({ breadcrumbs, leftContent, rightContent, showSidebarTrigger = true, customTrigger, className, children }) {
773
- return /* @__PURE__ */ jsxs("header", {
774
- className: cn("flex h-12 shrink-0 items-center justify-between gap-2 px-4", className),
775
- children: [/* @__PURE__ */ jsxs("div", {
776
- className: "flex items-center gap-2",
777
- children: [
778
- showSidebarTrigger && !customTrigger && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(SidebarTrigger, { className: "-ml-1" }), /* @__PURE__ */ jsx(Separator, {
779
- orientation: "vertical",
780
- className: "mx-2 !h-4 !self-auto"
781
- })] }),
782
- customTrigger,
783
- children ? children : /* @__PURE__ */ jsxs(Fragment, { children: [breadcrumbs && breadcrumbs.length > 0 && /* @__PURE__ */ jsx(BreadcrumbNav, { items: breadcrumbs }), leftContent] })
784
- ]
785
- }), rightContent && /* @__PURE__ */ jsx("div", {
786
- className: "flex items-center gap-2",
787
- children: rightContent
788
- })]
789
- });
790
- }
791
-
792
- //#endregion
793
- //#region src/dashboard/presets/inset-sidebar.tsx
949
+ //#region src/dashboard/sidebar-brand.tsx
794
950
  /**
795
- * InsetSidebar - Complete sidebar preset with inset variant.
796
- * The standard dashboard sidebar with collapsible to icons.
951
+ * SidebarBrand - Logo and title section for dashboard sidebar.
952
+ * Automatically handles collapsed state styling.
797
953
  */
798
- function InsetSidebar({ brand, navigation, secondaryNavigation, project, user, headerContent, ariaLabel = "Navigation Menu", className, children }) {
799
- const { isMobile } = useSidebar();
800
- return /* @__PURE__ */ jsxs(Sidebar, {
801
- variant: "inset",
802
- collapsible: "icon",
954
+ function SidebarBrand({ title, icon, href = "/", className, tooltip }) {
955
+ const { isMobile, state } = useSidebar();
956
+ const isCollapsed = state === "collapsed";
957
+ return /* @__PURE__ */ jsx(SidebarMenu, {
803
958
  className,
804
- children: [
805
- isMobile && /* @__PURE__ */ jsxs("span", {
806
- className: "sr-only",
807
- children: [/* @__PURE__ */ jsx(SheetTitle, { children: ariaLabel }), /* @__PURE__ */ jsx(SheetDescription, { children: "Application navigation menu containing links to different sections" })]
808
- }),
809
- /* @__PURE__ */ jsxs(SidebarHeader, {
810
- className: "gap-1",
811
- children: [
812
- /* @__PURE__ */ jsx(SidebarBrand, {
813
- title: brand.title,
814
- icon: brand.icon,
815
- href: brand.href
816
- }),
817
- project && /* @__PURE__ */ jsx(ProjectSwitcher, {
818
- items: project.items,
819
- selected: project.selected,
820
- onSelect: project.onSelect,
821
- isLoading: project.isLoading,
822
- label: project.label,
823
- icon: project.icon
824
- }),
825
- headerContent
826
- ]
959
+ children: /* @__PURE__ */ jsx(SidebarMenuItem, { children: /* @__PURE__ */ jsxs(SidebarMenuButton, {
960
+ size: "lg",
961
+ className: "group-data-[collapsible=icon]:p-0!",
962
+ tooltip: isCollapsed ? tooltip || title : void 0,
963
+ render: /* @__PURE__ */ jsx(Link, {
964
+ href,
965
+ className: cn("flex items-center text-sidebar-foreground", isCollapsed && !isMobile && "justify-center")
827
966
  }),
828
- /* @__PURE__ */ jsx(SidebarContent, { children: children || /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(SidebarNav, { groups: navigation }), secondaryNavigation && /* @__PURE__ */ jsx(SidebarNav, {
829
- groups: secondaryNavigation,
830
- className: "mt-auto"
831
- })] }) }),
832
- user && /* @__PURE__ */ jsx(SidebarFooter, { children: /* @__PURE__ */ jsx(SidebarUserMenu, {
833
- user: user.data,
834
- menuItems: user.menuItems,
835
- onLogout: user.onLogout
836
- }) })
837
- ]
967
+ children: [/* @__PURE__ */ jsx("div", {
968
+ className: cn("rounded-lg flex items-center justify-center bg-sidebar-primary text-sidebar-primary-foreground shadow-sm flex-shrink-0 transition-[width,height]", isCollapsed && !isMobile ? "h-6 w-6" : "h-8 w-8"),
969
+ children: icon
970
+ }), /* @__PURE__ */ jsx("span", {
971
+ className: cn("ml-2 font-semibold tracking-tight truncate", !isMobile && isCollapsed && "hidden"),
972
+ children: title
973
+ })]
974
+ }) })
838
975
  });
839
976
  }
840
977
 
841
978
  //#endregion
842
- //#region src/dashboard/presets/dual-sidebar.tsx
843
- const SIDEBAR_RAIL_WIDTH = "3.5rem";
844
- const SIDEBAR_PANEL_WIDTH = "13rem";
845
- const SIDEBAR_WIDTH_EXPANDED = "16.5rem";
846
- const SIDEBAR_WIDTH_COLLAPSED = "3.5rem";
847
- const SIDEBAR_PADDING = "0.5rem";
848
- const railVariants$1 = {
849
- default: "bg-sidebar",
850
- dark: "bg-zinc-900 dark:bg-zinc-950",
851
- primary: "bg-primary",
852
- muted: "bg-muted"
853
- };
854
- const railTextVariants$1 = {
855
- default: "text-sidebar-foreground",
856
- dark: "text-zinc-100",
857
- primary: "text-primary-foreground",
858
- muted: "text-muted-foreground"
979
+ //#region src/dashboard/sidebar-nav.tsx
980
+ const DENSITY_CLASSES = {
981
+ compact: "h-7 text-xs",
982
+ default: "",
983
+ comfortable: "h-10 text-sm [&_svg]:size-5"
859
984
  };
860
985
  /**
861
- * DualSidebar - A sidebar with icon rail and collapsible expanded panel.
862
- * Mimics the inset sidebar variant structure for consistent styling.
986
+ * SidebarNavItem - Single navigation item with optional sub-items.
863
987
  */
864
- function DualSidebar({ brand, categories, user, defaultCategoryId, onCategoryChange, defaultCollapsed = false, mobileOpen: controlledMobileOpen, onMobileOpenChange, railVariant = "default", ariaLabel = "Navigation Menu", className }) {
865
- const [activeCategory, setActiveCategory] = React$1.useState(defaultCategoryId || (categories.length > 0 ? categories[0].id : null));
866
- const [isExpanded, setIsExpanded] = React$1.useState(!defaultCollapsed);
867
- const [internalMobileOpen, setInternalMobileOpen] = React$1.useState(false);
868
- const { isMobile } = useSidebar();
869
- const isControlledMobile = controlledMobileOpen !== void 0;
870
- const mobileOpen = isControlledMobile ? controlledMobileOpen : internalMobileOpen;
871
- const setMobileOpen = React$1.useCallback((open) => {
872
- if (isControlledMobile) onMobileOpenChange?.(open);
873
- else setInternalMobileOpen(open);
874
- }, [isControlledMobile, onMobileOpenChange]);
875
- const handleCategorySelect = (id) => {
876
- setActiveCategory(id);
877
- onCategoryChange?.(id);
878
- if (!isExpanded) setIsExpanded(true);
879
- };
880
- const handleMobileCategorySelect = (id) => {
881
- setActiveCategory(id);
882
- onCategoryChange?.(id);
883
- };
884
- const toggleExpanded = () => {
885
- setIsExpanded(!isExpanded);
886
- };
887
- const activeCategoryData = categories.find((c) => c.id === activeCategory) || null;
888
- const totalWidth = `calc(${isExpanded ? SIDEBAR_WIDTH_EXPANDED : SIDEBAR_WIDTH_COLLAPSED} + ${SIDEBAR_PADDING} * 2)`;
889
- return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs(Sheet, {
890
- open: mobileOpen,
891
- onOpenChange: setMobileOpen,
892
- children: [!isControlledMobile && /* @__PURE__ */ jsx(SheetTrigger, {
893
- render: /* @__PURE__ */ jsx(Button, {
894
- variant: "outline",
895
- size: "icon",
896
- className: "md:hidden fixed top-2 left-2 z-40 h-9 w-9 bg-background/80 backdrop-blur-sm shadow-sm",
897
- "aria-label": "Open navigation"
898
- }),
899
- children: /* @__PURE__ */ jsx(Menu, { className: "h-5 w-5" })
900
- }), /* @__PURE__ */ jsxs(SheetContent, {
901
- side: "left",
902
- className: "w-72 p-0",
988
+ function SidebarNavItem({ item, density = "default", onClick }) {
989
+ const densityClass = DENSITY_CLASSES[density];
990
+ const hasSubItems = item.items && item.items.length > 0;
991
+ const Icon = item.icon;
992
+ const [open, setOpen] = useState(item.defaultOpen ?? item.isActive ?? false);
993
+ if (hasSubItems) return /* @__PURE__ */ jsx(SidebarMenuItem, { children: /* @__PURE__ */ jsxs(Collapsible, {
994
+ open,
995
+ onOpenChange: setOpen,
996
+ className: "group/collapsible",
997
+ children: [/* @__PURE__ */ jsxs(CollapsibleTrigger, {
998
+ render: /* @__PURE__ */ jsx(SidebarMenuButton, { className: densityClass }),
903
999
  children: [
904
- /* @__PURE__ */ jsx(SheetTitle, {
905
- className: "sr-only",
906
- children: ariaLabel
907
- }),
908
- /* @__PURE__ */ jsx(SheetDescription, {
909
- className: "sr-only",
910
- children: "Navigation menu with category icons and expanded content"
1000
+ Icon && /* @__PURE__ */ jsx(Icon, { className: "h-4 w-4" }),
1001
+ /* @__PURE__ */ jsx("span", { children: item.title }),
1002
+ item.badge && /* @__PURE__ */ jsx("span", {
1003
+ className: "ml-auto text-xs bg-sidebar-accent text-sidebar-accent-foreground px-1.5 py-0.5 rounded",
1004
+ children: item.badge
911
1005
  }),
912
- /* @__PURE__ */ jsxs("div", {
913
- className: "flex h-full flex-row",
914
- children: [/* @__PURE__ */ jsxs("div", {
915
- className: cn("flex flex-col shrink-0", railVariants$1[railVariant], railTextVariants$1[railVariant], "border-r border-sidebar-border"),
916
- style: {
917
- width: SIDEBAR_RAIL_WIDTH,
918
- minWidth: SIDEBAR_RAIL_WIDTH
919
- },
920
- children: [/* @__PURE__ */ jsx("div", {
921
- className: "flex items-center justify-center p-2 h-14",
922
- children: /* @__PURE__ */ jsx(Link, {
923
- href: brand.href || "/",
924
- className: cn("flex items-center justify-center h-10 w-10 rounded-lg shadow-sm transition-colors", railVariant === "dark" || railVariant === "primary" ? "bg-white/10 hover:bg-white/20 text-inherit" : "bg-sidebar-primary text-sidebar-primary-foreground"),
925
- onClick: () => setMobileOpen(false),
926
- children: brand.icon
927
- })
928
- }), /* @__PURE__ */ jsx("div", {
929
- className: "flex-1 flex flex-col gap-1 p-2",
930
- children: categories.map((category) => {
931
- const Icon = category.icon;
932
- return /* @__PURE__ */ jsx("button", {
933
- onClick: () => handleMobileCategorySelect(category.id),
934
- className: cn("flex items-center justify-center h-10 w-10 rounded-lg transition-colors", activeCategory === category.id ? railVariant === "dark" || railVariant === "primary" ? "bg-white/20" : "bg-sidebar-accent text-sidebar-accent-foreground" : railVariant === "dark" || railVariant === "primary" ? "hover:bg-white/10" : "hover:bg-sidebar-accent/50"),
935
- children: /* @__PURE__ */ jsx(Icon, { className: "h-5 w-5" })
936
- }, category.id);
937
- })
938
- })]
939
- }), /* @__PURE__ */ jsx("div", {
940
- className: "flex-1 flex flex-col bg-sidebar overflow-hidden",
941
- children: activeCategoryData ? /* @__PURE__ */ jsxs(Fragment, { children: [
942
- /* @__PURE__ */ jsx(SidebarHeader, {
943
- className: "h-14 border-b border-sidebar-border px-4 flex items-center shrink-0",
944
- children: /* @__PURE__ */ jsxs("div", {
945
- className: "flex items-center gap-2",
946
- children: [/* @__PURE__ */ jsx(activeCategoryData.icon, { className: "h-5 w-5 text-sidebar-foreground" }), /* @__PURE__ */ jsx("span", {
947
- className: "font-semibold text-sidebar-foreground truncate",
948
- children: activeCategoryData.name
949
- })]
950
- })
951
- }),
952
- /* @__PURE__ */ jsx(SidebarContent, {
953
- className: "flex-1 overflow-y-auto",
954
- children: /* @__PURE__ */ jsx(SidebarGroup, {
955
- className: "p-2",
956
- children: /* @__PURE__ */ jsx(SidebarMenu, { children: activeCategoryData.items.map((item, idx) => {
957
- const Icon = item.icon;
958
- return /* @__PURE__ */ jsx(SidebarMenuItem, { children: /* @__PURE__ */ jsxs(SidebarMenuButton, {
959
- isActive: item.isActive,
960
- render: /* @__PURE__ */ jsx(Link, {
961
- href: item.url,
962
- onClick: () => setMobileOpen(false)
963
- }),
964
- children: [
965
- Icon && /* @__PURE__ */ jsx(Icon, { className: "h-4 w-4" }),
966
- /* @__PURE__ */ jsx("span", {
967
- className: "truncate",
968
- children: item.title
969
- }),
970
- item.badge && /* @__PURE__ */ jsx("span", {
971
- className: "ml-auto text-xs bg-sidebar-accent text-sidebar-accent-foreground px-1.5 py-0.5 rounded shrink-0",
972
- children: item.badge
973
- })
974
- ]
975
- }) }, `mobile-${activeCategoryData.id}-${item.title}-${idx}`);
976
- }) })
977
- })
978
- }),
979
- user && /* @__PURE__ */ jsx(SidebarFooter, {
980
- className: "border-t border-sidebar-border shrink-0",
981
- children: /* @__PURE__ */ jsx(SidebarUserMenu, {
982
- user: user.data,
983
- menuItems: user.menuItems,
984
- onLogout: user.onLogout
985
- })
986
- })
987
- ] }) : /* @__PURE__ */ jsx("div", {
988
- className: "flex-1 flex items-center justify-center text-sidebar-foreground/60 text-sm",
989
- children: "Select a category"
990
- })
991
- })]
1006
+ /* @__PURE__ */ jsx(ChevronRight, { className: "ml-auto h-4 w-4 transition-transform duration-200 group-data-open/collapsible:rotate-90" })
1007
+ ]
1008
+ }), /* @__PURE__ */ jsx(CollapsibleContent, { children: /* @__PURE__ */ jsx(SidebarMenuSub, { children: item.items.map((subItem, idx) => /* @__PURE__ */ jsx(SidebarMenuSubItem, { children: /* @__PURE__ */ jsxs(SidebarMenuSubButton, {
1009
+ render: /* @__PURE__ */ jsx(Link, { href: subItem.url }),
1010
+ isActive: subItem.isActive,
1011
+ children: [/* @__PURE__ */ jsx("span", { children: subItem.title }), subItem.badge && /* @__PURE__ */ jsx("span", {
1012
+ className: "ml-auto text-xs opacity-60",
1013
+ children: subItem.badge
1014
+ })]
1015
+ }) }, `${item.title}-${subItem.title}-${idx}`)) }) })]
1016
+ }) });
1017
+ return /* @__PURE__ */ jsx(SidebarMenuItem, { children: /* @__PURE__ */ jsx(Link, {
1018
+ href: item.url,
1019
+ className: "w-full",
1020
+ children: /* @__PURE__ */ jsxs(SidebarMenuButton, {
1021
+ isActive: item.isActive,
1022
+ onClick,
1023
+ className: densityClass,
1024
+ children: [
1025
+ Icon && /* @__PURE__ */ jsx(Icon, { className: "h-4 w-4" }),
1026
+ /* @__PURE__ */ jsx("span", { children: item.title }),
1027
+ item.badge && /* @__PURE__ */ jsx("span", {
1028
+ className: "ml-auto text-xs bg-sidebar-accent text-sidebar-accent-foreground px-1.5 py-0.5 rounded",
1029
+ children: item.badge
992
1030
  })
993
1031
  ]
994
- })]
995
- }), /* @__PURE__ */ jsxs("div", {
996
- className: "group peer text-sidebar-foreground hidden md:block",
997
- "data-state": "expanded",
998
- "data-collapsible": "",
999
- "data-variant": "inset",
1000
- "data-side": "left",
1001
- "data-slot": "sidebar",
1002
- children: [/* @__PURE__ */ jsx("div", {
1003
- "data-slot": "sidebar-gap",
1004
- className: "relative bg-transparent transition-[width] duration-200 ease-linear",
1005
- style: { width: totalWidth }
1006
- }), /* @__PURE__ */ jsx("div", {
1007
- "data-slot": "sidebar-container",
1008
- className: cn("fixed inset-y-0 left-0 z-10 hidden h-svh transition-[width] duration-200 ease-linear md:flex", className),
1009
- style: {
1010
- width: totalWidth,
1011
- padding: SIDEBAR_PADDING
1012
- },
1013
- children: /* @__PURE__ */ jsxs("div", {
1014
- "data-sidebar": "sidebar",
1015
- "data-slot": "sidebar-inner",
1016
- className: "bg-sidebar flex h-full w-full flex-row overflow-hidden rounded-lg border border-sidebar-border shadow-sm",
1017
- children: [/* @__PURE__ */ jsxs("div", {
1018
- className: cn("flex flex-col shrink-0 transition-colors", railVariants$1[railVariant], railTextVariants$1[railVariant], isExpanded && "border-r border-sidebar-border"),
1019
- style: {
1020
- width: SIDEBAR_RAIL_WIDTH,
1021
- minWidth: SIDEBAR_RAIL_WIDTH
1022
- },
1023
- children: [
1024
- /* @__PURE__ */ jsx("div", {
1025
- className: "flex items-center justify-center p-2 h-14",
1026
- children: /* @__PURE__ */ jsxs(Tooltip, { children: [/* @__PURE__ */ jsx(TooltipTrigger, {
1027
- render: /* @__PURE__ */ jsx(Link, {
1028
- href: brand.href || "/",
1029
- className: cn("flex items-center justify-center h-10 w-10 rounded-lg shadow-sm transition-colors", railVariant === "dark" || railVariant === "primary" ? "bg-white/10 hover:bg-white/20 text-inherit" : "bg-sidebar-primary text-sidebar-primary-foreground")
1030
- }),
1031
- children: brand.icon
1032
- }), /* @__PURE__ */ jsx(TooltipContent, {
1033
- side: "right",
1034
- children: brand.title
1035
- })] })
1036
- }),
1037
- /* @__PURE__ */ jsx("div", {
1038
- className: "flex-1 flex flex-col gap-1 p-2",
1039
- children: categories.map((category) => {
1040
- const Icon = category.icon;
1041
- return /* @__PURE__ */ jsxs(Tooltip, { children: [/* @__PURE__ */ jsx(TooltipTrigger, {
1042
- render: /* @__PURE__ */ jsx("button", {
1043
- onClick: () => handleCategorySelect(category.id),
1044
- className: cn("flex items-center justify-center h-10 w-10 rounded-lg transition-colors", activeCategory === category.id ? railVariant === "dark" || railVariant === "primary" ? "bg-white/20" : "bg-sidebar-accent text-sidebar-accent-foreground" : railVariant === "dark" || railVariant === "primary" ? "hover:bg-white/10" : "hover:bg-sidebar-accent/50")
1045
- }),
1046
- children: /* @__PURE__ */ jsx(Icon, { className: "h-5 w-5" })
1047
- }), /* @__PURE__ */ jsx(TooltipContent, {
1048
- side: "right",
1049
- children: category.name
1050
- })] }, category.id);
1051
- })
1052
- }),
1053
- /* @__PURE__ */ jsx("div", {
1054
- className: "p-2",
1055
- children: /* @__PURE__ */ jsxs(Tooltip, { children: [/* @__PURE__ */ jsx(TooltipTrigger, {
1056
- render: /* @__PURE__ */ jsx("button", {
1057
- onClick: toggleExpanded,
1058
- className: cn("flex items-center justify-center h-10 w-10 rounded-lg transition-colors", railVariant === "dark" || railVariant === "primary" ? "hover:bg-white/10" : "hover:bg-sidebar-accent/50")
1059
- }),
1060
- children: isExpanded ? /* @__PURE__ */ jsx(ChevronLeft, { className: "h-5 w-5" }) : /* @__PURE__ */ jsx(ChevronRight, { className: "h-5 w-5" })
1061
- }), /* @__PURE__ */ jsx(TooltipContent, {
1062
- side: "right",
1063
- children: isExpanded ? "Collapse sidebar" : "Expand sidebar"
1064
- })] })
1065
- })
1066
- ]
1067
- }), /* @__PURE__ */ jsx("div", {
1068
- className: cn("flex flex-col bg-sidebar overflow-hidden transition-[width,opacity] duration-200 ease-linear", isExpanded ? "opacity-100" : "opacity-0 w-0"),
1069
- style: { width: isExpanded ? SIDEBAR_PANEL_WIDTH : 0 },
1070
- children: activeCategoryData ? /* @__PURE__ */ jsxs(Fragment, { children: [
1071
- /* @__PURE__ */ jsx(SidebarHeader, {
1072
- className: "h-14 border-b border-sidebar-border px-4 flex items-center shrink-0",
1073
- children: /* @__PURE__ */ jsxs("div", {
1074
- className: "flex items-center gap-2",
1075
- children: [/* @__PURE__ */ jsx(activeCategoryData.icon, { className: "h-5 w-5 text-sidebar-foreground" }), /* @__PURE__ */ jsx("span", {
1076
- className: "font-semibold text-sidebar-foreground truncate",
1077
- children: activeCategoryData.name
1078
- })]
1079
- })
1080
- }),
1081
- /* @__PURE__ */ jsx(SidebarContent, {
1082
- className: "flex-1 overflow-y-auto",
1083
- children: /* @__PURE__ */ jsxs(SidebarGroup, {
1084
- className: "p-2",
1085
- children: [/* @__PURE__ */ jsxs(SidebarGroupLabel, {
1086
- className: "px-2 text-xs text-sidebar-foreground/60",
1087
- children: [activeCategoryData.name, " Menu"]
1088
- }), /* @__PURE__ */ jsx(SidebarMenu, { children: activeCategoryData.items.map((item, idx) => {
1089
- const Icon = item.icon;
1090
- return /* @__PURE__ */ jsx(SidebarMenuItem, { children: /* @__PURE__ */ jsxs(SidebarMenuButton, {
1091
- isActive: item.isActive,
1092
- render: /* @__PURE__ */ jsx(Link, { href: item.url }),
1093
- children: [
1094
- Icon && /* @__PURE__ */ jsx(Icon, { className: "h-4 w-4" }),
1095
- /* @__PURE__ */ jsx("span", {
1096
- className: "truncate",
1097
- children: item.title
1098
- }),
1099
- item.badge && /* @__PURE__ */ jsx("span", {
1100
- className: "ml-auto text-xs bg-sidebar-accent text-sidebar-accent-foreground px-1.5 py-0.5 rounded shrink-0",
1101
- children: item.badge
1102
- })
1103
- ]
1104
- }) }, `${activeCategoryData.id}-${item.title}-${idx}`);
1105
- }) })]
1106
- })
1107
- }),
1108
- user && /* @__PURE__ */ jsx(SidebarFooter, {
1109
- className: "border-t border-sidebar-border shrink-0",
1110
- children: /* @__PURE__ */ jsx(SidebarUserMenu, {
1111
- user: user.data,
1112
- menuItems: user.menuItems,
1113
- onLogout: user.onLogout
1114
- })
1115
- })
1116
- ] }) : /* @__PURE__ */ jsx("div", {
1117
- className: "flex-1 flex items-center justify-center text-sidebar-foreground/60 text-sm",
1118
- children: "Select a category"
1119
- })
1120
- })]
1121
- })
1122
- })]
1123
- })] });
1032
+ })
1033
+ }) });
1034
+ }
1035
+ /**
1036
+ * SidebarNavGroup - A group of navigation items with optional title.
1037
+ */
1038
+ function SidebarNavGroup({ title, items, density, className, onItemClick }) {
1039
+ return /* @__PURE__ */ jsxs(SidebarGroup, {
1040
+ className,
1041
+ children: [title && /* @__PURE__ */ jsx(SidebarGroupLabel, { children: title }), /* @__PURE__ */ jsx(SidebarMenu, { children: items.map((item, idx) => /* @__PURE__ */ jsx(SidebarNavItem, {
1042
+ item,
1043
+ density,
1044
+ onClick: () => onItemClick?.(item)
1045
+ }, `${item.title}-${idx}`)) })]
1046
+ });
1047
+ }
1048
+ /**
1049
+ * SidebarNav - Complete navigation section with multiple groups.
1050
+ */
1051
+ function SidebarNav({ groups, density, className, onItemClick }) {
1052
+ return /* @__PURE__ */ jsx("div", {
1053
+ className: cn("flex flex-col gap-2", className),
1054
+ children: groups.map((group, index) => /* @__PURE__ */ jsx(SidebarNavGroup, {
1055
+ title: group.title,
1056
+ items: group.items,
1057
+ density,
1058
+ onItemClick
1059
+ }, group.title || `nav-group-${index}`))
1060
+ });
1124
1061
  }
1125
1062
 
1126
1063
  //#endregion
@@ -1161,8 +1098,62 @@ function FloatingSidebar({ brand, navigation, secondaryNavigation, project, user
1161
1098
  headerContent
1162
1099
  ]
1163
1100
  }),
1164
- /* @__PURE__ */ jsx(SidebarContent, { children: children || /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(SidebarNav, { groups: navigation }), secondaryNavigation && /* @__PURE__ */ jsx(SidebarNav, {
1101
+ /* @__PURE__ */ jsx(SidebarContent, { children: children || /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(SidebarNav, { groups: navigation }), secondaryNavigation && /* @__PURE__ */ jsx(SidebarNav, {
1102
+ groups: secondaryNavigation,
1103
+ className: "mt-auto"
1104
+ })] }) }),
1105
+ user && /* @__PURE__ */ jsx(SidebarFooter, { children: /* @__PURE__ */ jsx(SidebarUserMenu, {
1106
+ user: user.data,
1107
+ menuItems: user.menuItems,
1108
+ onLogout: user.onLogout
1109
+ }) })
1110
+ ]
1111
+ });
1112
+ }
1113
+
1114
+ //#endregion
1115
+ //#region src/dashboard/presets/inset-sidebar.tsx
1116
+ /**
1117
+ * InsetSidebar - Complete sidebar preset with inset variant.
1118
+ * The standard dashboard sidebar with collapsible to icons.
1119
+ */
1120
+ function InsetSidebar({ brand, navigation, secondaryNavigation, project, user, headerContent, sidebarStyle = "inset", density = "default", side = "left", ariaLabel = "Navigation Menu", className, children }) {
1121
+ const { isMobile } = useSidebar();
1122
+ return /* @__PURE__ */ jsxs(Sidebar, {
1123
+ variant: sidebarStyle === "flush" ? "sidebar" : "inset",
1124
+ collapsible: "icon",
1125
+ side,
1126
+ className,
1127
+ children: [
1128
+ isMobile && /* @__PURE__ */ jsxs("span", {
1129
+ className: "sr-only",
1130
+ children: [/* @__PURE__ */ jsx(SheetTitle, { children: ariaLabel }), /* @__PURE__ */ jsx(SheetDescription, { children: "Application navigation menu containing links to different sections" })]
1131
+ }),
1132
+ /* @__PURE__ */ jsxs(SidebarHeader, {
1133
+ className: "gap-1",
1134
+ children: [
1135
+ /* @__PURE__ */ jsx(SidebarBrand, {
1136
+ title: brand.title,
1137
+ icon: brand.icon,
1138
+ href: brand.href
1139
+ }),
1140
+ project && /* @__PURE__ */ jsx(ProjectSwitcher, {
1141
+ items: project.items,
1142
+ selected: project.selected,
1143
+ onSelect: project.onSelect,
1144
+ isLoading: project.isLoading,
1145
+ label: project.label,
1146
+ icon: project.icon
1147
+ }),
1148
+ headerContent
1149
+ ]
1150
+ }),
1151
+ /* @__PURE__ */ jsx(SidebarContent, { children: children || /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(SidebarNav, {
1152
+ groups: navigation,
1153
+ density
1154
+ }), secondaryNavigation && /* @__PURE__ */ jsx(SidebarNav, {
1165
1155
  groups: secondaryNavigation,
1156
+ density,
1166
1157
  className: "mt-auto"
1167
1158
  })] }) }),
1168
1159
  user && /* @__PURE__ */ jsx(SidebarFooter, { children: /* @__PURE__ */ jsx(SidebarUserMenu, {
@@ -1204,7 +1195,7 @@ function MiniSidebar({ brand, navigation, secondaryNavigation, user, headerConte
1204
1195
  children: brand.title
1205
1196
  })]
1206
1197
  }) }) }), headerContent] }),
1207
- /* @__PURE__ */ jsx(SidebarContent, { children: children || /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(NavGroups, { groups: navigation }), secondaryNavigation && /* @__PURE__ */ jsx(NavGroups, {
1198
+ /* @__PURE__ */ jsx(SidebarContent, { children: children || /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(NavGroups, { groups: navigation }), secondaryNavigation && /* @__PURE__ */ jsx(NavGroups, {
1208
1199
  groups: secondaryNavigation,
1209
1200
  className: "mt-auto"
1210
1201
  })] }) }),
@@ -1218,7 +1209,7 @@ function MiniSidebar({ brand, navigation, secondaryNavigation, user, headerConte
1218
1209
  });
1219
1210
  }
1220
1211
  function NavGroups({ groups, className }) {
1221
- return /* @__PURE__ */ jsx(Fragment, { children: groups.map((group, groupIndex) => /* @__PURE__ */ jsxs(SidebarGroup, {
1212
+ return /* @__PURE__ */ jsx(Fragment$1, { children: groups.map((group, groupIndex) => /* @__PURE__ */ jsxs(SidebarGroup, {
1222
1213
  className: cn(groupIndex === groups.length - 1 && className),
1223
1214
  children: [group.title && /* @__PURE__ */ jsx(SidebarGroupLabel, { children: group.title }), /* @__PURE__ */ jsx(SidebarMenu, { children: group.items.map((item, itemIndex) => {
1224
1215
  const Icon = item.icon;
@@ -1259,6 +1250,12 @@ const railTextVariants = {
1259
1250
  primary: "text-primary-foreground",
1260
1251
  muted: "text-muted-foreground"
1261
1252
  };
1253
+ const railBorderVariants = {
1254
+ default: "border-sidebar-border",
1255
+ dark: "border-zinc-700 dark:border-zinc-800",
1256
+ primary: "border-primary/30",
1257
+ muted: "border-border"
1258
+ };
1262
1259
  function getUserInitials(name) {
1263
1260
  if (!name) return "?";
1264
1261
  const names = name.trim().split(" ");
@@ -1331,7 +1328,7 @@ function TopbarUserMenu({ user, menuItems = [], onLogout }) {
1331
1328
  className: "w-full flex items-center",
1332
1329
  children: [item.icon && /* @__PURE__ */ jsx(item.icon, { className: "mr-2 h-4 w-4" }), item.label]
1333
1330
  })] }, item.label)) }),
1334
- onLogout && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(DropdownMenuSeparator, {}), /* @__PURE__ */ jsx(DropdownMenuItem, {
1331
+ onLogout && /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(DropdownMenuSeparator, {}), /* @__PURE__ */ jsx(DropdownMenuItem, {
1335
1332
  onClick: onLogout,
1336
1333
  className: "w-full flex items-center",
1337
1334
  children: "Log out"
@@ -1423,7 +1420,7 @@ function TopbarRail({ brand, categories, user, breadcrumbs, actions, defaultCate
1423
1420
  children: brand.icon
1424
1421
  })
1425
1422
  }),
1426
- breadcrumbs && breadcrumbs.length > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Separator, {
1423
+ breadcrumbs && breadcrumbs.length > 0 && /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(Separator, {
1427
1424
  orientation: "vertical",
1428
1425
  className: "mx-2 !h-4 !self-auto hidden md:block"
1429
1426
  }), /* @__PURE__ */ jsx(TopbarBreadcrumbs, { items: breadcrumbs })] }),
@@ -1442,7 +1439,7 @@ function TopbarRail({ brand, categories, user, breadcrumbs, actions, defaultCate
1442
1439
  className: "flex flex-1 overflow-hidden",
1443
1440
  children: [
1444
1441
  /* @__PURE__ */ jsx("nav", {
1445
- className: cn("hidden md:flex flex-col shrink-0 border-r", railVariants[railVariant], railTextVariants[railVariant]),
1442
+ className: cn("hidden md:flex flex-col shrink-0 border-r", railVariants[railVariant], railTextVariants[railVariant], railBorderVariants[railVariant]),
1446
1443
  style: {
1447
1444
  width: RAIL_WIDTH,
1448
1445
  minWidth: RAIL_WIDTH
@@ -1454,6 +1451,7 @@ function TopbarRail({ brand, categories, user, breadcrumbs, actions, defaultCate
1454
1451
  const Icon = category.icon;
1455
1452
  return /* @__PURE__ */ jsxs(Tooltip, { children: [/* @__PURE__ */ jsx(TooltipTrigger, {
1456
1453
  render: /* @__PURE__ */ jsx("button", {
1454
+ type: "button",
1457
1455
  onClick: () => handleCategorySelect(category.id),
1458
1456
  className: cn("flex items-center justify-center h-10 w-10 rounded-lg transition-colors", activeCategory === category.id ? railVariant === "dark" || railVariant === "primary" ? "bg-white/20" : "bg-sidebar-accent text-sidebar-accent-foreground" : railVariant === "dark" || railVariant === "primary" ? "hover:bg-white/10" : "hover:bg-sidebar-accent/50")
1459
1457
  }),
@@ -1468,7 +1466,7 @@ function TopbarRail({ brand, categories, user, breadcrumbs, actions, defaultCate
1468
1466
  /* @__PURE__ */ jsx("aside", {
1469
1467
  className: cn("hidden md:flex flex-col border-r bg-sidebar overflow-hidden transition-[width,opacity] duration-200 ease-linear", isDrawerOpen ? "opacity-100" : "opacity-0 w-0"),
1470
1468
  style: { width: isDrawerOpen ? DRAWER_WIDTH : 0 },
1471
- children: activeCategoryData && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(SidebarHeader, {
1469
+ children: activeCategoryData && /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(SidebarHeader, {
1472
1470
  className: "h-12 border-b border-sidebar-border px-4 flex items-center shrink-0",
1473
1471
  children: /* @__PURE__ */ jsxs("div", {
1474
1472
  className: "flex items-center gap-2",
@@ -1518,7 +1516,7 @@ function MobileNav({ brand, categories, user, activeCategory, onCategorySelect,
1518
1516
  return /* @__PURE__ */ jsxs("div", {
1519
1517
  className: "flex h-full flex-row",
1520
1518
  children: [/* @__PURE__ */ jsxs("div", {
1521
- className: cn("flex flex-col shrink-0", railVariants[railVariant], railTextVariants[railVariant], "border-r border-sidebar-border"),
1519
+ className: cn("flex flex-col shrink-0", railVariants[railVariant], railTextVariants[railVariant], "border-r", railBorderVariants[railVariant]),
1522
1520
  style: {
1523
1521
  width: RAIL_WIDTH,
1524
1522
  minWidth: RAIL_WIDTH
@@ -1536,6 +1534,7 @@ function MobileNav({ brand, categories, user, activeCategory, onCategorySelect,
1536
1534
  children: categories.map((category) => {
1537
1535
  const Icon = category.icon;
1538
1536
  return /* @__PURE__ */ jsx("button", {
1537
+ type: "button",
1539
1538
  onClick: () => onCategorySelect(category.id),
1540
1539
  className: cn("flex items-center justify-center h-10 w-10 rounded-lg transition-colors", activeCategory === category.id ? railVariant === "dark" || railVariant === "primary" ? "bg-white/20" : "bg-sidebar-accent text-sidebar-accent-foreground" : railVariant === "dark" || railVariant === "primary" ? "hover:bg-white/10" : "hover:bg-sidebar-accent/50"),
1541
1540
  children: /* @__PURE__ */ jsx(Icon, { className: "h-5 w-5" })
@@ -1544,7 +1543,7 @@ function MobileNav({ brand, categories, user, activeCategory, onCategorySelect,
1544
1543
  })]
1545
1544
  }), /* @__PURE__ */ jsx("div", {
1546
1545
  className: "flex-1 flex flex-col bg-sidebar overflow-hidden",
1547
- children: activeCategoryData ? /* @__PURE__ */ jsxs(Fragment, { children: [
1546
+ children: activeCategoryData ? /* @__PURE__ */ jsxs(Fragment$1, { children: [
1548
1547
  /* @__PURE__ */ jsx(SidebarHeader, {
1549
1548
  className: "h-14 border-b border-sidebar-border px-4 flex items-center shrink-0",
1550
1549
  children: /* @__PURE__ */ jsxs("div", {
@@ -1614,4 +1613,294 @@ function MobileNav({ brand, categories, user, activeCategory, onCategorySelect,
1614
1613
  }
1615
1614
 
1616
1615
  //#endregion
1617
- export { DashboardContent, DashboardHeader, DashboardPageLayout, DualSidebar, FloatingSidebar, HeaderSection, InsetSidebar, MiniSidebar, PageHeader, ProjectSwitcher, SidebarBrand, SidebarNav, SidebarNavGroup, SidebarNavItem, SidebarUserMenu, TopbarRail, cn, resolveCategories, resolveMiniNavigation, resolveNavigation };
1616
+ //#region src/dashboard/dashboard-layout.tsx
1617
+ const DualMobileTrigger = React$1.memo(function DualMobileTrigger({ onOpen }) {
1618
+ return /* @__PURE__ */ jsx(Button, {
1619
+ variant: "ghost",
1620
+ size: "icon",
1621
+ className: "md:hidden -ml-1",
1622
+ onClick: onOpen,
1623
+ "aria-label": "Open navigation",
1624
+ children: /* @__PURE__ */ jsx(Menu, { className: "h-5 w-5" })
1625
+ });
1626
+ });
1627
+ function renderSidebar(config, mobileOpen, setMobileOpen) {
1628
+ switch (config.variant) {
1629
+ case "dual": return /* @__PURE__ */ jsx(DualSidebar, {
1630
+ brand: config.brand,
1631
+ categories: config.categories,
1632
+ user: config.user,
1633
+ railVariant: config.railVariant,
1634
+ sidebarStyle: config.sidebarStyle,
1635
+ defaultCategoryId: config.defaultCategoryId,
1636
+ activeCategoryId: config.activeCategoryId,
1637
+ onCategoryChange: config.onCategoryChange,
1638
+ defaultCollapsed: config.defaultCollapsed,
1639
+ collapsed: config.collapsed,
1640
+ onCollapsedChange: config.onCollapsedChange,
1641
+ headerContent: config.headerContent,
1642
+ footerContent: config.footerContent,
1643
+ mobileOpen,
1644
+ onMobileOpenChange: setMobileOpen
1645
+ });
1646
+ case "inset": return /* @__PURE__ */ jsx(InsetSidebar, {
1647
+ brand: config.brand,
1648
+ navigation: config.navigation,
1649
+ secondaryNavigation: config.secondaryNavigation,
1650
+ project: config.project,
1651
+ user: config.user,
1652
+ headerContent: config.headerContent,
1653
+ sidebarStyle: config.sidebarStyle,
1654
+ density: config.density,
1655
+ side: config.side
1656
+ });
1657
+ case "floating": return /* @__PURE__ */ jsx(FloatingSidebar, {
1658
+ brand: config.brand,
1659
+ navigation: config.navigation,
1660
+ secondaryNavigation: config.secondaryNavigation,
1661
+ project: config.project,
1662
+ user: config.user,
1663
+ headerContent: config.headerContent,
1664
+ side: config.side
1665
+ });
1666
+ case "mini": return /* @__PURE__ */ jsx(MiniSidebar, {
1667
+ brand: config.brand,
1668
+ navigation: config.navigation,
1669
+ secondaryNavigation: config.secondaryNavigation,
1670
+ user: config.user,
1671
+ headerContent: config.headerContent,
1672
+ side: config.side,
1673
+ showRail: config.showRail
1674
+ });
1675
+ default: return null;
1676
+ }
1677
+ }
1678
+ function DashboardLayout({ sidebar, breadcrumbs, headerRight, headerLeft, header, stickyHeader = false, headerClassName, className, children }) {
1679
+ const [mobileOpen, setMobileOpen] = React$1.useState(false);
1680
+ const handleMobileOpen = React$1.useCallback(() => setMobileOpen(true), []);
1681
+ const bc = breadcrumbs !== false && breadcrumbs ? breadcrumbs : void 0;
1682
+ const autoBreadcrumbs = useDashboardBreadcrumbs(bc?.basePath ?? "/", bc);
1683
+ const breadcrumbItems = breadcrumbs === false ? void 0 : autoBreadcrumbs;
1684
+ const isDual = sidebar.variant === "dual";
1685
+ const isTopbarRail = sidebar.variant === "topbar-rail";
1686
+ const headerElement = header === false ? null : header ?? /* @__PURE__ */ jsx(DashboardHeader, {
1687
+ breadcrumbs: breadcrumbItems,
1688
+ leftContent: headerLeft,
1689
+ rightContent: headerRight,
1690
+ showSidebarTrigger: !isDual && !isTopbarRail,
1691
+ customTrigger: isDual ? /* @__PURE__ */ jsx(DualMobileTrigger, { onOpen: handleMobileOpen }) : void 0,
1692
+ className: cn(stickyHeader && "sticky top-0 z-10 bg-background/95 backdrop-blur-sm border-b", headerClassName)
1693
+ });
1694
+ if (isTopbarRail && sidebar.variant === "topbar-rail") return /* @__PURE__ */ jsx(ClientOnly, { children: /* @__PURE__ */ jsx(TopbarRail, {
1695
+ brand: sidebar.brand,
1696
+ categories: sidebar.categories,
1697
+ user: sidebar.user,
1698
+ breadcrumbs: breadcrumbItems?.map((b) => ({
1699
+ label: b.label,
1700
+ href: b.href,
1701
+ isCurrentPage: b.current
1702
+ })),
1703
+ actions: headerRight,
1704
+ railVariant: sidebar.railVariant,
1705
+ defaultCategoryId: sidebar.defaultCategoryId,
1706
+ defaultDrawerOpen: sidebar.defaultDrawerOpen,
1707
+ className,
1708
+ children
1709
+ }) });
1710
+ return /* @__PURE__ */ jsxs(SidebarProvider, { children: [renderSidebar(sidebar, mobileOpen, setMobileOpen), /* @__PURE__ */ jsxs(SidebarInset, {
1711
+ className: cn("min-w-0", className),
1712
+ children: [headerElement, /* @__PURE__ */ jsx(DashboardContent, { children })]
1713
+ })] });
1714
+ }
1715
+
1716
+ //#endregion
1717
+ //#region src/dashboard/page-header.tsx
1718
+ function PageHeader({ items, className, actions }) {
1719
+ return /* @__PURE__ */ jsxs("header", {
1720
+ className: cn("flex h-12 shrink-0 items-center justify-between gap-2", className),
1721
+ children: [/* @__PURE__ */ jsxs("div", {
1722
+ className: "flex items-center gap-2",
1723
+ children: [
1724
+ /* @__PURE__ */ jsx(SidebarTrigger, { className: "-ml-1" }),
1725
+ /* @__PURE__ */ jsx(Separator, {
1726
+ orientation: "vertical",
1727
+ className: "!self-center h-4"
1728
+ }),
1729
+ /* @__PURE__ */ jsx(Breadcrumb, { children: /* @__PURE__ */ jsx(BreadcrumbList, { children: items.map((item, index) => /* @__PURE__ */ jsxs(React.Fragment, { children: [/* @__PURE__ */ jsx(BreadcrumbItem, {
1730
+ className: item.current ? "" : "hidden md:block",
1731
+ children: item.current ? /* @__PURE__ */ jsx(BreadcrumbPage, { children: item.label }) : /* @__PURE__ */ jsx(BreadcrumbLink, {
1732
+ render: /* @__PURE__ */ jsx(Link, { href: item.href || "#" }),
1733
+ children: item.label
1734
+ })
1735
+ }), index < items.length - 1 && /* @__PURE__ */ jsx(BreadcrumbSeparator, { className: "hidden md:block" })] }, `breadcrumb-${index}`)) }) })
1736
+ ]
1737
+ }), actions]
1738
+ });
1739
+ }
1740
+
1741
+ //#endregion
1742
+ //#region src/dashboard/dashboard-page-layout.tsx
1743
+ const defaultFallback = /* @__PURE__ */ jsx("div", {
1744
+ className: "flex items-center justify-center py-12",
1745
+ children: /* @__PURE__ */ jsx("div", { className: "h-6 w-6 animate-spin rounded-full border-2 border-primary border-t-transparent" })
1746
+ });
1747
+ /**
1748
+ * DashboardPageLayout — Standard page shell for dashboard pages.
1749
+ * Combines PageHeader (breadcrumbs + actions) with optional Suspense boundary.
1750
+ *
1751
+ * @example
1752
+ * ```tsx
1753
+ * <DashboardPageLayout
1754
+ * breadcrumbs={[
1755
+ * { label: "Dashboard", href: "/dashboard" },
1756
+ * { label: "Posts", current: true },
1757
+ * ]}
1758
+ * actions={<Button>New Post</Button>}
1759
+ * fallback={<SkeletonTable rows={5} />}
1760
+ * >
1761
+ * <PostsList />
1762
+ * </DashboardPageLayout>
1763
+ * ```
1764
+ */
1765
+ function DashboardPageLayout({ breadcrumbs, actions, suspense = true, fallback = defaultFallback, className, children }) {
1766
+ return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(PageHeader, {
1767
+ items: breadcrumbs,
1768
+ actions
1769
+ }), /* @__PURE__ */ jsx("div", {
1770
+ className: cn("flex-1", className),
1771
+ children: suspense ? /* @__PURE__ */ jsx(Suspense, {
1772
+ fallback,
1773
+ children
1774
+ }) : children
1775
+ })] });
1776
+ }
1777
+
1778
+ //#endregion
1779
+ //#region src/dashboard/header-section.tsx
1780
+ const variants = {
1781
+ default: {
1782
+ wrapper: "px-6 py-5 rounded-xl shadow-sm border bg-background hover:shadow-md",
1783
+ title: "text-2xl font-semibold tracking-tight",
1784
+ description: "text-sm text-muted-foreground",
1785
+ iconSize: "size-10",
1786
+ iconInner: "size-5",
1787
+ spacing: "space-y-1"
1788
+ },
1789
+ compact: {
1790
+ wrapper: "px-4 py-3 rounded-lg border bg-background/50",
1791
+ title: "text-lg font-semibold",
1792
+ description: "text-xs text-muted-foreground",
1793
+ iconSize: "size-8",
1794
+ iconInner: "size-4",
1795
+ spacing: "space-y-0.5"
1796
+ },
1797
+ hero: {
1798
+ wrapper: "px-8 py-8 rounded-2xl shadow-lg border bg-gradient-to-br from-background to-muted/20",
1799
+ title: "text-3xl font-bold tracking-tight bg-gradient-to-r from-foreground to-foreground/70 bg-clip-text text-transparent",
1800
+ description: "text-base text-muted-foreground mt-2",
1801
+ iconSize: "size-14",
1802
+ iconInner: "size-7",
1803
+ spacing: "space-y-2"
1804
+ },
1805
+ minimal: {
1806
+ wrapper: "px-0 py-2",
1807
+ title: "text-xl font-medium",
1808
+ description: "text-sm text-muted-foreground",
1809
+ iconSize: "size-8",
1810
+ iconInner: "size-4",
1811
+ spacing: "space-y-0.5"
1812
+ }
1813
+ };
1814
+ function HeaderSection({ title, description, actions = null, icon: Icon = null, iconClassName, loading = false, variant = "default", className, badge, breadcrumbs, metadata, children }) {
1815
+ const currentVariant = variants[variant] || variants.default;
1816
+ const isBadgeObject = (badge) => {
1817
+ return typeof badge === "object" && badge !== null && "text" in badge;
1818
+ };
1819
+ if (loading && !title) return /* @__PURE__ */ jsx("div", {
1820
+ className: cn(currentVariant.wrapper, "transition-all duration-300", className),
1821
+ children: /* @__PURE__ */ jsxs("div", {
1822
+ className: "flex flex-col sm:flex-row sm:items-center sm:justify-between",
1823
+ children: [/* @__PURE__ */ jsxs("div", {
1824
+ className: "flex items-center gap-4",
1825
+ children: [Icon && /* @__PURE__ */ jsx(Skeleton, { className: cn(currentVariant.iconSize, "rounded-lg") }), /* @__PURE__ */ jsxs("div", {
1826
+ className: currentVariant.spacing,
1827
+ children: [/* @__PURE__ */ jsx(Skeleton, { className: "h-7 w-48" }), /* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-64 mt-1" })]
1828
+ })]
1829
+ }), actions && /* @__PURE__ */ jsxs("div", {
1830
+ className: "flex gap-2 mt-4 sm:mt-0",
1831
+ children: [/* @__PURE__ */ jsx(Skeleton, { className: "h-9 w-24" }), /* @__PURE__ */ jsx(Skeleton, { className: "h-9 w-24" })]
1832
+ })]
1833
+ })
1834
+ });
1835
+ return /* @__PURE__ */ jsxs("div", {
1836
+ className: cn("transition-all duration-300", currentVariant.wrapper, className),
1837
+ children: [
1838
+ breadcrumbs && /* @__PURE__ */ jsx("nav", {
1839
+ className: "mb-3 text-sm text-muted-foreground",
1840
+ children: breadcrumbs
1841
+ }),
1842
+ /* @__PURE__ */ jsxs("div", {
1843
+ className: "flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4",
1844
+ children: [/* @__PURE__ */ jsxs("div", {
1845
+ className: "flex items-start sm:items-center gap-4",
1846
+ children: [Icon && /* @__PURE__ */ jsx("div", {
1847
+ className: cn("flex aspect-square items-center justify-center rounded-lg flex-shrink-0", "bg-primary/10 text-primary", currentVariant.iconSize, iconClassName),
1848
+ children: /* @__PURE__ */ jsx(Icon, { className: currentVariant.iconInner })
1849
+ }), /* @__PURE__ */ jsxs("div", {
1850
+ className: cn("flex-1", currentVariant.spacing),
1851
+ children: [
1852
+ /* @__PURE__ */ jsxs("div", {
1853
+ className: "flex items-center gap-2 flex-wrap",
1854
+ children: [/* @__PURE__ */ jsx("h1", {
1855
+ className: cn("leading-none", currentVariant.title),
1856
+ children: title
1857
+ }), badge && (isBadgeObject(badge) ? /* @__PURE__ */ jsx(Badge, {
1858
+ variant: badge.variant || "secondary",
1859
+ className: cn("ml-2", badge.className),
1860
+ children: badge.text
1861
+ }) : badge)]
1862
+ }),
1863
+ description && /* @__PURE__ */ jsx("p", {
1864
+ className: currentVariant.description,
1865
+ children: description
1866
+ }),
1867
+ metadata && /* @__PURE__ */ jsx("div", {
1868
+ className: "flex items-center gap-4 mt-2 text-xs text-muted-foreground",
1869
+ children: metadata.map((item, index) => /* @__PURE__ */ jsxs("div", {
1870
+ className: "flex items-center gap-1",
1871
+ children: [item.icon && /* @__PURE__ */ jsx(item.icon, { className: "size-3" }), /* @__PURE__ */ jsx("span", { children: item.text })]
1872
+ }, index))
1873
+ })
1874
+ ]
1875
+ })]
1876
+ }), actions && /* @__PURE__ */ jsx("div", {
1877
+ className: "flex flex-wrap gap-2 mt-4 sm:mt-0",
1878
+ children: actions.map((action, index) => {
1879
+ const isActionLoading = action.loading ?? false;
1880
+ const isDisabled = loading || action.disabled || isActionLoading;
1881
+ const displayText = isActionLoading ? action.loadingText || action.text : action.text;
1882
+ return /* @__PURE__ */ jsxs(Button, {
1883
+ onClick: action.onClick,
1884
+ disabled: isDisabled,
1885
+ variant: action.variant || "default",
1886
+ size: action.size || (variant === "compact" ? "sm" : "default"),
1887
+ className: cn(variant === "hero" && "shadow-md hover:shadow-lg", action.className),
1888
+ children: [
1889
+ action.icon && action.iconPosition !== "right" && /* @__PURE__ */ jsx(action.icon, { className: "size-4 mr-2" }),
1890
+ /* @__PURE__ */ jsx("span", { children: displayText }),
1891
+ action.icon && action.iconPosition === "right" && /* @__PURE__ */ jsx(action.icon, { className: "size-4 ml-2" })
1892
+ ]
1893
+ }, index);
1894
+ })
1895
+ })]
1896
+ }),
1897
+ children && /* @__PURE__ */ jsx("div", {
1898
+ className: cn("mt-4 pt-4 border-t", variant === "minimal" && "border-0 pt-2"),
1899
+ children
1900
+ })
1901
+ ]
1902
+ });
1903
+ }
1904
+
1905
+ //#endregion
1906
+ export { ClientOnly, DashboardContent, DashboardHeader, DashboardLayout, DashboardPageLayout, DualSidebar, FloatingSidebar, HeaderSection, InsetSidebar, MiniSidebar, PageHeader, ProjectSwitcher, SidebarBrand, SidebarNav, SidebarNavGroup, SidebarNavItem, SidebarUserMenu, TopbarRail, cn, findActiveCategory, findActiveItem, matchRoute, resolveCategories, resolveMiniNavigation, resolveNavigation, useDashboardBreadcrumbs };