@classytic/fluid 0.2.1 → 0.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +149 -62
- package/dist/api-pagination-CJ0vR_w6.d.mts +34 -0
- package/dist/api-pagination-DBTE0yk4.mjs +190 -0
- package/dist/chunk-DQk6qfdC.mjs +18 -0
- package/dist/client/calendar.d.mts +105 -0
- package/dist/client/calendar.mjs +202 -0
- package/dist/client/core.d.mts +1614 -0
- package/dist/client/core.mjs +2779 -0
- package/dist/client/error.d.mts +125 -0
- package/dist/client/error.mjs +166 -0
- package/dist/client/hooks.d.mts +162 -0
- package/dist/client/hooks.mjs +447 -0
- package/dist/client/table.d.mts +84 -0
- package/dist/client/table.mjs +373 -0
- package/dist/client/theme.d.mts +6 -0
- package/dist/client/theme.mjs +65 -0
- package/dist/command.d.mts +134 -0
- package/dist/command.mjs +132 -0
- package/dist/compact.d.mts +359 -0
- package/dist/compact.mjs +892 -0
- package/dist/dashboard.d.mts +778 -0
- package/dist/dashboard.mjs +1617 -0
- package/dist/filter-utils-DqMmy_v-.mjs +72 -0
- package/dist/filter-utils-IZ0GtuPo.d.mts +40 -0
- package/dist/forms.d.mts +1549 -0
- package/dist/forms.mjs +3740 -0
- package/dist/index.d.mts +296 -0
- package/dist/index.mjs +432 -0
- package/dist/layouts.d.mts +215 -0
- package/dist/layouts.mjs +460 -0
- package/dist/search-context-DR7DBs7S.mjs +19 -0
- package/dist/search.d.mts +254 -0
- package/dist/search.mjs +523 -0
- package/dist/sheet-wrapper-CWNCvYMD.mjs +211 -0
- package/dist/use-base-search-BGgWnWaF.d.mts +35 -0
- package/dist/use-debounce-xmZucz5e.mjs +53 -0
- package/dist/use-keyboard-shortcut-Bl6YM5Q7.mjs +82 -0
- package/dist/use-keyboard-shortcut-_mRCh3QO.d.mts +24 -0
- package/dist/use-media-query-BnVNIKT4.mjs +17 -0
- package/dist/use-mobile-BX3SQVo2.mjs +20 -0
- package/dist/use-scroll-detection-CsgsQYvy.mjs +43 -0
- package/dist/utils-CDue7cEt.d.mts +6 -0
- package/dist/utils-DQ5SCVoW.mjs +10 -0
- package/package.json +85 -45
- package/styles.css +2 -2
- package/dist/chunk-GUHK2DTW.js +0 -15
- package/dist/chunk-GUHK2DTW.js.map +0 -1
- package/dist/chunk-H3NFL3GJ.js +0 -57
- package/dist/chunk-H3NFL3GJ.js.map +0 -1
- package/dist/chunk-J2YRTQE4.js +0 -293
- package/dist/chunk-J2YRTQE4.js.map +0 -1
- package/dist/compact.d.ts +0 -217
- package/dist/compact.js +0 -986
- package/dist/compact.js.map +0 -1
- package/dist/dashboard.d.ts +0 -386
- package/dist/dashboard.js +0 -1032
- package/dist/dashboard.js.map +0 -1
- package/dist/index.d.ts +0 -2141
- package/dist/index.js +0 -6460
- package/dist/index.js.map +0 -1
- package/dist/layout.d.ts +0 -25
- package/dist/layout.js +0 -4
- package/dist/layout.js.map +0 -1
- package/dist/search.d.ts +0 -172
- package/dist/search.js +0 -341
- package/dist/search.js.map +0 -1
- package/dist/use-base-search-AS5Z3SAy.d.ts +0 -64
- package/dist/utils-Cbsgs0XP.d.ts +0 -5
|
@@ -0,0 +1,1617 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
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";
|
|
7
|
+
import * as React$1 from "react";
|
|
8
|
+
import React, { Suspense, useState } from "react";
|
|
9
|
+
import { Building2, Check, ChevronLeft, ChevronRight, ChevronsUpDown, CircleUserRound, LogOut, Menu } from "lucide-react";
|
|
10
|
+
import { Button } from "@/components/ui/button";
|
|
11
|
+
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible";
|
|
12
|
+
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
|
+
import { Sheet, SheetContent, SheetDescription, SheetTitle, SheetTrigger } from "@/components/ui/sheet";
|
|
16
|
+
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
|
|
17
|
+
import { Separator } from "@/components/ui/separator";
|
|
18
|
+
import Link from "next/link";
|
|
19
|
+
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
|
+
//#region src/dashboard/nav-utils.ts
|
|
23
|
+
/** Check hidden flag, then canAccess(ctx) */
|
|
24
|
+
function isItemVisible(item, ctx) {
|
|
25
|
+
if (item.hidden) return false;
|
|
26
|
+
if (item.canAccess && !item.canAccess(ctx)) return false;
|
|
27
|
+
return true;
|
|
28
|
+
}
|
|
29
|
+
/** Resolve badgeCount(ctx), falling back to static badge */
|
|
30
|
+
function resolveBadge(item, ctx) {
|
|
31
|
+
if (item.badgeCount) {
|
|
32
|
+
const value = item.badgeCount(ctx);
|
|
33
|
+
if (value !== void 0) return {
|
|
34
|
+
...item,
|
|
35
|
+
badge: value
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
return item;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* resolveNavigation — Filter NavGroup[] by permissions and resolve dynamic badges.
|
|
42
|
+
* For InsetSidebar, FloatingSidebar, and any component using NavGroup[].
|
|
43
|
+
*
|
|
44
|
+
* When `ctx` is omitted, only `hidden` checks apply (backwards-compat).
|
|
45
|
+
*/
|
|
46
|
+
function resolveNavigation(groups, ctx) {
|
|
47
|
+
const result = [];
|
|
48
|
+
for (const group of groups) {
|
|
49
|
+
const filteredItems = [];
|
|
50
|
+
for (const item of group.items) {
|
|
51
|
+
if (ctx !== void 0 && !isItemVisible(item, ctx)) continue;
|
|
52
|
+
if (ctx === void 0 && item.hidden) continue;
|
|
53
|
+
let resolved = ctx !== void 0 ? resolveBadge(item, ctx) : item;
|
|
54
|
+
if (resolved.items && resolved.items.length > 0) {
|
|
55
|
+
const filteredSubs = resolved.items.filter((sub) => {
|
|
56
|
+
if (ctx !== void 0) return isItemVisible(sub, ctx);
|
|
57
|
+
return !sub.hidden;
|
|
58
|
+
}).map((sub) => ctx !== void 0 ? resolveBadge(sub, ctx) : sub);
|
|
59
|
+
resolved = {
|
|
60
|
+
...resolved,
|
|
61
|
+
items: filteredSubs
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
filteredItems.push(resolved);
|
|
65
|
+
}
|
|
66
|
+
if (filteredItems.length > 0) result.push({
|
|
67
|
+
...group,
|
|
68
|
+
items: filteredItems
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
return result;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* resolveMiniNavigation — Filter MiniSidebarGroup[] by permissions.
|
|
75
|
+
*/
|
|
76
|
+
function resolveMiniNavigation(groups, ctx) {
|
|
77
|
+
const result = [];
|
|
78
|
+
for (const group of groups) {
|
|
79
|
+
const filteredItems = group.items.filter((item) => {
|
|
80
|
+
if (ctx !== void 0) return isItemVisible(item, ctx);
|
|
81
|
+
return !item.hidden;
|
|
82
|
+
}).map((item) => ctx !== void 0 ? resolveBadge(item, ctx) : item);
|
|
83
|
+
if (filteredItems.length > 0) result.push({
|
|
84
|
+
...group,
|
|
85
|
+
items: filteredItems
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
return result;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* resolveCategories — Filter category arrays by permissions.
|
|
92
|
+
* Works with DualSidebarCategory[] and TopbarRailCategory[].
|
|
93
|
+
*/
|
|
94
|
+
function resolveCategories(categories, ctx) {
|
|
95
|
+
const result = [];
|
|
96
|
+
for (const category of categories) {
|
|
97
|
+
if (ctx !== void 0 && !isItemVisible(category, ctx)) continue;
|
|
98
|
+
if (ctx === void 0 && category.hidden) continue;
|
|
99
|
+
const filteredItems = category.items.filter((item) => {
|
|
100
|
+
if (ctx !== void 0) return isItemVisible(item, ctx);
|
|
101
|
+
return !item.hidden;
|
|
102
|
+
}).map((item) => ctx !== void 0 ? resolveBadge(item, ctx) : item);
|
|
103
|
+
if (filteredItems.length > 0) result.push({
|
|
104
|
+
...category,
|
|
105
|
+
items: filteredItems
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
return result;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
//#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
|
+
}
|
|
146
|
+
};
|
|
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
|
+
})
|
|
167
|
+
});
|
|
168
|
+
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
|
|
174
|
+
}),
|
|
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
|
+
]
|
|
208
|
+
})]
|
|
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),
|
|
221
|
+
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" })
|
|
225
|
+
]
|
|
226
|
+
}, index);
|
|
227
|
+
})
|
|
228
|
+
})]
|
|
229
|
+
}),
|
|
230
|
+
children && /* @__PURE__ */ jsx("div", {
|
|
231
|
+
className: cn("mt-4 pt-4 border-t", variant === "minimal" && "border-0 pt-2"),
|
|
232
|
+
children
|
|
233
|
+
})
|
|
234
|
+
]
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
//#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
|
+
};
|
|
271
|
+
/**
|
|
272
|
+
* DashboardContent — Safe 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`.
|
|
282
|
+
*
|
|
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
|
+
* ```
|
|
295
|
+
*/
|
|
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
|
+
})
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
//#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
|
+
})] });
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
//#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")
|
|
362
|
+
}),
|
|
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
|
+
}) })
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
//#endregion
|
|
375
|
+
//#region src/dashboard/sidebar-nav.tsx
|
|
376
|
+
/**
|
|
377
|
+
* SidebarNavItem - Single navigation item with optional sub-items.
|
|
378
|
+
*/
|
|
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.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
|
|
479
|
+
})]
|
|
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
|
+
});
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
//#endregion
|
|
538
|
+
//#region src/dashboard/project-switcher.tsx
|
|
539
|
+
function DropdownContent({ items, selected, onSelect, label, renderItem, isMobile = false }) {
|
|
540
|
+
return /* @__PURE__ */ jsx(DropdownMenuContent, {
|
|
541
|
+
className: "w-64 rounded-lg",
|
|
542
|
+
side: isMobile ? "bottom" : "right",
|
|
543
|
+
align: "start",
|
|
544
|
+
sideOffset: 4,
|
|
545
|
+
children: /* @__PURE__ */ jsxs(DropdownMenuGroup, { children: [/* @__PURE__ */ jsx(DropdownMenuLabel, {
|
|
546
|
+
className: "text-xs text-muted-foreground font-normal",
|
|
547
|
+
children: label
|
|
548
|
+
}), items.map((item) => {
|
|
549
|
+
const isSelected = selected.id === item.id;
|
|
550
|
+
if (renderItem) return /* @__PURE__ */ jsx(DropdownMenuItem, {
|
|
551
|
+
onClick: () => onSelect(item),
|
|
552
|
+
className: "cursor-pointer",
|
|
553
|
+
children: renderItem(item, isSelected)
|
|
554
|
+
}, item.id);
|
|
555
|
+
return /* @__PURE__ */ jsx(DropdownMenuItem, {
|
|
556
|
+
onClick: () => onSelect(item),
|
|
557
|
+
className: "gap-2 cursor-pointer",
|
|
558
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
559
|
+
className: "flex-1 min-w-0",
|
|
560
|
+
children: [
|
|
561
|
+
/* @__PURE__ */ jsxs("div", {
|
|
562
|
+
className: "flex items-center justify-between gap-2",
|
|
563
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
564
|
+
className: "font-medium text-sm truncate",
|
|
565
|
+
children: item.name
|
|
566
|
+
}), isSelected && /* @__PURE__ */ jsx(Check, { className: "size-3.5 text-primary shrink-0" })]
|
|
567
|
+
}),
|
|
568
|
+
/* @__PURE__ */ jsxs("div", {
|
|
569
|
+
className: "flex items-center gap-1.5 mt-0.5",
|
|
570
|
+
children: [
|
|
571
|
+
item.code && /* @__PURE__ */ jsx("span", {
|
|
572
|
+
className: "text-xs text-muted-foreground",
|
|
573
|
+
children: item.code
|
|
574
|
+
}),
|
|
575
|
+
item.type && /* @__PURE__ */ jsxs(Fragment, { children: [item.code && /* @__PURE__ */ jsx("span", {
|
|
576
|
+
className: "text-xs text-muted-foreground",
|
|
577
|
+
children: "•"
|
|
578
|
+
}), /* @__PURE__ */ jsx(Badge, {
|
|
579
|
+
variant: "outline",
|
|
580
|
+
className: "text-[10px] h-4 px-1",
|
|
581
|
+
children: item.type
|
|
582
|
+
})] }),
|
|
583
|
+
item.isDefault && /* @__PURE__ */ jsx(Badge, {
|
|
584
|
+
variant: "secondary",
|
|
585
|
+
className: "text-[10px] h-4 px-1",
|
|
586
|
+
children: "Default"
|
|
587
|
+
}),
|
|
588
|
+
item.isActive === false && /* @__PURE__ */ jsx(Badge, {
|
|
589
|
+
variant: "destructive",
|
|
590
|
+
className: "text-[10px] h-4 px-1",
|
|
591
|
+
children: "Inactive"
|
|
592
|
+
})
|
|
593
|
+
]
|
|
594
|
+
}),
|
|
595
|
+
item.subtitle && /* @__PURE__ */ jsx("p", {
|
|
596
|
+
className: "text-[10px] text-muted-foreground mt-0.5 truncate",
|
|
597
|
+
children: item.subtitle
|
|
598
|
+
})
|
|
599
|
+
]
|
|
600
|
+
})
|
|
601
|
+
}, item.id);
|
|
602
|
+
})] })
|
|
603
|
+
});
|
|
604
|
+
}
|
|
605
|
+
function SidebarLoadingSkeleton({ isCollapsed, isMobile }) {
|
|
606
|
+
return /* @__PURE__ */ jsx(SidebarMenu, { children: /* @__PURE__ */ jsx(SidebarMenuItem, { children: /* @__PURE__ */ jsxs(SidebarMenuButton, {
|
|
607
|
+
className: cn("h-10 py-1.5 bg-sidebar-accent/50", isCollapsed && !isMobile && "justify-center"),
|
|
608
|
+
disabled: true,
|
|
609
|
+
children: [/* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-4 rounded-sm shrink-0" }), (!isCollapsed || isMobile) && /* @__PURE__ */ jsxs("div", {
|
|
610
|
+
className: "flex-1 min-w-0 space-y-1",
|
|
611
|
+
children: [/* @__PURE__ */ jsx(Skeleton, { className: "h-3 w-24" }), /* @__PURE__ */ jsx(Skeleton, { className: "h-2.5 w-14" })]
|
|
612
|
+
})]
|
|
613
|
+
}) }) });
|
|
614
|
+
}
|
|
615
|
+
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", {
|
|
617
|
+
className: "flex-1 min-w-0 leading-tight text-left",
|
|
618
|
+
children: [/* @__PURE__ */ jsx("p", {
|
|
619
|
+
className: "text-xs font-medium truncate",
|
|
620
|
+
children: project.name
|
|
621
|
+
}), project.code && /* @__PURE__ */ jsx("p", {
|
|
622
|
+
className: "text-[10px] text-muted-foreground truncate",
|
|
623
|
+
children: project.code
|
|
624
|
+
})]
|
|
625
|
+
})] });
|
|
626
|
+
}
|
|
627
|
+
function StandaloneLoadingSkeleton({ size }) {
|
|
628
|
+
return /* @__PURE__ */ jsxs(Button, {
|
|
629
|
+
variant: "outline",
|
|
630
|
+
size,
|
|
631
|
+
className: cn("gap-2", size === "sm" ? "h-8" : "h-9"),
|
|
632
|
+
disabled: true,
|
|
633
|
+
children: [/* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-4 rounded-sm" }), /* @__PURE__ */ jsx(Skeleton, { className: "h-3 w-20" })]
|
|
634
|
+
});
|
|
635
|
+
}
|
|
636
|
+
function StandaloneProjectDisplay({ project, Icon, size }) {
|
|
637
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
638
|
+
/* @__PURE__ */ jsx(Icon, { className: cn("text-muted-foreground shrink-0", size === "sm" ? "h-3.5 w-3.5" : "h-4 w-4") }),
|
|
639
|
+
/* @__PURE__ */ jsx("span", {
|
|
640
|
+
className: cn("font-medium truncate max-w-[120px]", size === "sm" ? "text-xs" : "text-sm"),
|
|
641
|
+
children: project.name
|
|
642
|
+
}),
|
|
643
|
+
project.code && /* @__PURE__ */ jsxs("span", {
|
|
644
|
+
className: cn("text-muted-foreground truncate", size === "sm" ? "text-[10px]" : "text-xs"),
|
|
645
|
+
children: [
|
|
646
|
+
"(",
|
|
647
|
+
project.code,
|
|
648
|
+
")"
|
|
649
|
+
]
|
|
650
|
+
})
|
|
651
|
+
] });
|
|
652
|
+
}
|
|
653
|
+
/**
|
|
654
|
+
* ProjectSwitcher - Generic project/workspace/branch switcher.
|
|
655
|
+
* Works for any entity that needs switching functionality.
|
|
656
|
+
*
|
|
657
|
+
* @example Sidebar usage (inside SidebarProvider)
|
|
658
|
+
* ```tsx
|
|
659
|
+
* <ProjectSwitcher
|
|
660
|
+
* items={projects}
|
|
661
|
+
* selected={selectedProject}
|
|
662
|
+
* onSelect={setSelectedProject}
|
|
663
|
+
* />
|
|
664
|
+
* ```
|
|
665
|
+
*
|
|
666
|
+
* @example Header/Standalone usage (no SidebarProvider needed)
|
|
667
|
+
* ```tsx
|
|
668
|
+
* <ProjectSwitcher
|
|
669
|
+
* variant="standalone"
|
|
670
|
+
* items={projects}
|
|
671
|
+
* selected={selectedProject}
|
|
672
|
+
* onSelect={setSelectedProject}
|
|
673
|
+
* size="sm"
|
|
674
|
+
* />
|
|
675
|
+
* ```
|
|
676
|
+
*/
|
|
677
|
+
function ProjectSwitcher({ items, selected, onSelect, isLoading = false, label = "Switch Project", icon: Icon = Building2, className, disabled = false, variant = "sidebar", size = "default", renderItem }) {
|
|
678
|
+
const hasMultipleItems = items.length > 1;
|
|
679
|
+
if (variant === "standalone") {
|
|
680
|
+
if (isLoading) return /* @__PURE__ */ jsx(StandaloneLoadingSkeleton, { size });
|
|
681
|
+
if (!selected) return null;
|
|
682
|
+
if (!hasMultipleItems || disabled) return /* @__PURE__ */ jsx(Button, {
|
|
683
|
+
variant: "outline",
|
|
684
|
+
size,
|
|
685
|
+
className: cn("gap-2 cursor-default", size === "sm" ? "h-8" : "h-9", className),
|
|
686
|
+
disabled: true,
|
|
687
|
+
children: /* @__PURE__ */ jsx(StandaloneProjectDisplay, {
|
|
688
|
+
project: selected,
|
|
689
|
+
Icon,
|
|
690
|
+
size
|
|
691
|
+
})
|
|
692
|
+
});
|
|
693
|
+
return /* @__PURE__ */ jsxs(DropdownMenu, { children: [/* @__PURE__ */ jsxs(DropdownMenuTrigger, {
|
|
694
|
+
render: /* @__PURE__ */ jsx(Button, {
|
|
695
|
+
variant: "outline",
|
|
696
|
+
size,
|
|
697
|
+
className: cn("gap-2", size === "sm" ? "h-8" : "h-9", className)
|
|
698
|
+
}),
|
|
699
|
+
children: [/* @__PURE__ */ jsx(StandaloneProjectDisplay, {
|
|
700
|
+
project: selected,
|
|
701
|
+
Icon,
|
|
702
|
+
size
|
|
703
|
+
}), /* @__PURE__ */ jsx(ChevronsUpDown, { className: cn("text-muted-foreground shrink-0", size === "sm" ? "h-3 w-3" : "h-3.5 w-3.5") })]
|
|
704
|
+
}), /* @__PURE__ */ jsx(DropdownContent, {
|
|
705
|
+
items,
|
|
706
|
+
selected,
|
|
707
|
+
onSelect,
|
|
708
|
+
label,
|
|
709
|
+
renderItem
|
|
710
|
+
})] });
|
|
711
|
+
}
|
|
712
|
+
const { isMobile, state } = useSidebar();
|
|
713
|
+
const isCollapsed = state === "collapsed";
|
|
714
|
+
if (isLoading) return /* @__PURE__ */ jsx(SidebarLoadingSkeleton, {
|
|
715
|
+
isCollapsed,
|
|
716
|
+
isMobile
|
|
717
|
+
});
|
|
718
|
+
if (!selected) return null;
|
|
719
|
+
if (!hasMultipleItems || disabled) return /* @__PURE__ */ jsx(SidebarMenu, {
|
|
720
|
+
className,
|
|
721
|
+
children: /* @__PURE__ */ jsx(SidebarMenuItem, { children: /* @__PURE__ */ jsx(SidebarMenuButton, {
|
|
722
|
+
className: cn("h-10 py-1.5 bg-sidebar-accent/50", isCollapsed && !isMobile && "justify-center"),
|
|
723
|
+
tooltip: isCollapsed ? selected.name : void 0,
|
|
724
|
+
disabled: true,
|
|
725
|
+
children: /* @__PURE__ */ jsx(SidebarProjectDisplay, {
|
|
726
|
+
project: selected,
|
|
727
|
+
Icon,
|
|
728
|
+
isCollapsed,
|
|
729
|
+
isMobile
|
|
730
|
+
})
|
|
731
|
+
}) })
|
|
732
|
+
});
|
|
733
|
+
return /* @__PURE__ */ jsx(SidebarMenu, {
|
|
734
|
+
className,
|
|
735
|
+
children: /* @__PURE__ */ jsx(SidebarMenuItem, { children: /* @__PURE__ */ jsxs(DropdownMenu, { children: [/* @__PURE__ */ jsxs(DropdownMenuTrigger, {
|
|
736
|
+
render: /* @__PURE__ */ jsx(SidebarMenuButton, {
|
|
737
|
+
className: cn("h-10 py-1.5 bg-sidebar-accent/50 hover:bg-sidebar-accent transition-colors", isCollapsed && !isMobile && "justify-center"),
|
|
738
|
+
tooltip: isCollapsed ? selected.name : void 0
|
|
739
|
+
}),
|
|
740
|
+
children: [/* @__PURE__ */ jsx(SidebarProjectDisplay, {
|
|
741
|
+
project: selected,
|
|
742
|
+
Icon,
|
|
743
|
+
isCollapsed,
|
|
744
|
+
isMobile
|
|
745
|
+
}), (!isCollapsed || isMobile) && /* @__PURE__ */ jsx(ChevronsUpDown, { className: "h-3 w-3 text-muted-foreground shrink-0" })]
|
|
746
|
+
}), /* @__PURE__ */ jsx(DropdownContent, {
|
|
747
|
+
items,
|
|
748
|
+
selected,
|
|
749
|
+
onSelect,
|
|
750
|
+
label,
|
|
751
|
+
renderItem,
|
|
752
|
+
isMobile
|
|
753
|
+
})] }) })
|
|
754
|
+
});
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
//#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
|
|
794
|
+
/**
|
|
795
|
+
* InsetSidebar - Complete sidebar preset with inset variant.
|
|
796
|
+
* The standard dashboard sidebar with collapsible to icons.
|
|
797
|
+
*/
|
|
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",
|
|
803
|
+
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
|
+
]
|
|
827
|
+
}),
|
|
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
|
+
]
|
|
838
|
+
});
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
//#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"
|
|
859
|
+
};
|
|
860
|
+
/**
|
|
861
|
+
* DualSidebar - A sidebar with icon rail and collapsible expanded panel.
|
|
862
|
+
* Mimics the inset sidebar variant structure for consistent styling.
|
|
863
|
+
*/
|
|
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",
|
|
903
|
+
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"
|
|
911
|
+
}),
|
|
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
|
+
})]
|
|
992
|
+
})
|
|
993
|
+
]
|
|
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
|
+
})] });
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
//#endregion
|
|
1127
|
+
//#region src/dashboard/presets/floating-sidebar.tsx
|
|
1128
|
+
/**
|
|
1129
|
+
* FloatingSidebar - Floating variant sidebar with rounded corners and shadow.
|
|
1130
|
+
* Hovers above the content area like Linear / Notion style.
|
|
1131
|
+
* Collapses to icon-only rail on desktop.
|
|
1132
|
+
*/
|
|
1133
|
+
function FloatingSidebar({ brand, navigation, secondaryNavigation, project, user, headerContent, side = "left", ariaLabel = "Navigation Menu", className, children }) {
|
|
1134
|
+
const { isMobile } = useSidebar();
|
|
1135
|
+
return /* @__PURE__ */ jsxs(Sidebar, {
|
|
1136
|
+
variant: "floating",
|
|
1137
|
+
collapsible: "icon",
|
|
1138
|
+
side,
|
|
1139
|
+
className,
|
|
1140
|
+
children: [
|
|
1141
|
+
isMobile && /* @__PURE__ */ jsxs("span", {
|
|
1142
|
+
className: "sr-only",
|
|
1143
|
+
children: [/* @__PURE__ */ jsx(SheetTitle, { children: ariaLabel }), /* @__PURE__ */ jsx(SheetDescription, { children: "Application navigation menu containing links to different sections" })]
|
|
1144
|
+
}),
|
|
1145
|
+
/* @__PURE__ */ jsxs(SidebarHeader, {
|
|
1146
|
+
className: "gap-1",
|
|
1147
|
+
children: [
|
|
1148
|
+
/* @__PURE__ */ jsx(SidebarBrand, {
|
|
1149
|
+
title: brand.title,
|
|
1150
|
+
icon: brand.icon,
|
|
1151
|
+
href: brand.href
|
|
1152
|
+
}),
|
|
1153
|
+
project && /* @__PURE__ */ jsx(ProjectSwitcher, {
|
|
1154
|
+
items: project.items,
|
|
1155
|
+
selected: project.selected,
|
|
1156
|
+
onSelect: project.onSelect,
|
|
1157
|
+
isLoading: project.isLoading,
|
|
1158
|
+
label: project.label,
|
|
1159
|
+
icon: project.icon
|
|
1160
|
+
}),
|
|
1161
|
+
headerContent
|
|
1162
|
+
]
|
|
1163
|
+
}),
|
|
1164
|
+
/* @__PURE__ */ jsx(SidebarContent, { children: children || /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(SidebarNav, { groups: navigation }), secondaryNavigation && /* @__PURE__ */ jsx(SidebarNav, {
|
|
1165
|
+
groups: secondaryNavigation,
|
|
1166
|
+
className: "mt-auto"
|
|
1167
|
+
})] }) }),
|
|
1168
|
+
user && /* @__PURE__ */ jsx(SidebarFooter, { children: /* @__PURE__ */ jsx(SidebarUserMenu, {
|
|
1169
|
+
user: user.data,
|
|
1170
|
+
menuItems: user.menuItems,
|
|
1171
|
+
onLogout: user.onLogout
|
|
1172
|
+
}) })
|
|
1173
|
+
]
|
|
1174
|
+
});
|
|
1175
|
+
}
|
|
1176
|
+
|
|
1177
|
+
//#endregion
|
|
1178
|
+
//#region src/dashboard/presets/mini-sidebar.tsx
|
|
1179
|
+
/**
|
|
1180
|
+
* MiniSidebar - Collapsible sidebar that shrinks to icon-only rail.
|
|
1181
|
+
* Think VS Code, GitHub, or Figma-style navigation.
|
|
1182
|
+
* Uses the standard shadcn sidebar with `collapsible="icon"`.
|
|
1183
|
+
*/
|
|
1184
|
+
function MiniSidebar({ brand, navigation, secondaryNavigation, user, headerContent, side = "left", showRail = true, ariaLabel = "Navigation Menu", className, children }) {
|
|
1185
|
+
const { isMobile } = useSidebar();
|
|
1186
|
+
return /* @__PURE__ */ jsxs(Sidebar, {
|
|
1187
|
+
collapsible: "icon",
|
|
1188
|
+
side,
|
|
1189
|
+
className,
|
|
1190
|
+
children: [
|
|
1191
|
+
isMobile && /* @__PURE__ */ jsxs("span", {
|
|
1192
|
+
className: "sr-only",
|
|
1193
|
+
children: [/* @__PURE__ */ jsx(SheetTitle, { children: ariaLabel }), /* @__PURE__ */ jsx(SheetDescription, { children: "Application navigation menu" })]
|
|
1194
|
+
}),
|
|
1195
|
+
/* @__PURE__ */ jsxs(SidebarHeader, { children: [/* @__PURE__ */ jsx(SidebarMenu, { children: /* @__PURE__ */ jsx(SidebarMenuItem, { children: /* @__PURE__ */ jsxs(SidebarMenuButton, {
|
|
1196
|
+
size: "lg",
|
|
1197
|
+
tooltip: brand.title,
|
|
1198
|
+
render: /* @__PURE__ */ jsx(Link, { href: brand.href || "/" }),
|
|
1199
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
1200
|
+
className: "flex items-center justify-center h-8 w-8 rounded-lg bg-sidebar-primary text-sidebar-primary-foreground shadow-sm shrink-0",
|
|
1201
|
+
children: brand.icon
|
|
1202
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
1203
|
+
className: "font-semibold tracking-tight truncate",
|
|
1204
|
+
children: brand.title
|
|
1205
|
+
})]
|
|
1206
|
+
}) }) }), headerContent] }),
|
|
1207
|
+
/* @__PURE__ */ jsx(SidebarContent, { children: children || /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(NavGroups, { groups: navigation }), secondaryNavigation && /* @__PURE__ */ jsx(NavGroups, {
|
|
1208
|
+
groups: secondaryNavigation,
|
|
1209
|
+
className: "mt-auto"
|
|
1210
|
+
})] }) }),
|
|
1211
|
+
user && /* @__PURE__ */ jsx(SidebarFooter, { children: /* @__PURE__ */ jsx(SidebarUserMenu, {
|
|
1212
|
+
user: user.data,
|
|
1213
|
+
menuItems: user.menuItems,
|
|
1214
|
+
onLogout: user.onLogout
|
|
1215
|
+
}) }),
|
|
1216
|
+
showRail && /* @__PURE__ */ jsx(SidebarRail, {})
|
|
1217
|
+
]
|
|
1218
|
+
});
|
|
1219
|
+
}
|
|
1220
|
+
function NavGroups({ groups, className }) {
|
|
1221
|
+
return /* @__PURE__ */ jsx(Fragment, { children: groups.map((group, groupIndex) => /* @__PURE__ */ jsxs(SidebarGroup, {
|
|
1222
|
+
className: cn(groupIndex === groups.length - 1 && className),
|
|
1223
|
+
children: [group.title && /* @__PURE__ */ jsx(SidebarGroupLabel, { children: group.title }), /* @__PURE__ */ jsx(SidebarMenu, { children: group.items.map((item, itemIndex) => {
|
|
1224
|
+
const Icon = item.icon;
|
|
1225
|
+
return /* @__PURE__ */ jsx(SidebarMenuItem, { children: /* @__PURE__ */ jsxs(SidebarMenuButton, {
|
|
1226
|
+
isActive: item.isActive,
|
|
1227
|
+
tooltip: item.title,
|
|
1228
|
+
render: /* @__PURE__ */ jsx(Link, { href: item.url }),
|
|
1229
|
+
children: [
|
|
1230
|
+
/* @__PURE__ */ jsx(Icon, { className: "h-4 w-4" }),
|
|
1231
|
+
/* @__PURE__ */ jsx("span", {
|
|
1232
|
+
className: "truncate",
|
|
1233
|
+
children: item.title
|
|
1234
|
+
}),
|
|
1235
|
+
item.badge != null && /* @__PURE__ */ jsx("span", {
|
|
1236
|
+
className: "ml-auto text-xs bg-sidebar-accent text-sidebar-accent-foreground px-1.5 py-0.5 rounded shrink-0",
|
|
1237
|
+
children: item.badge
|
|
1238
|
+
})
|
|
1239
|
+
]
|
|
1240
|
+
}) }, `${groupIndex}-${itemIndex}`);
|
|
1241
|
+
}) })]
|
|
1242
|
+
}, groupIndex)) });
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1245
|
+
//#endregion
|
|
1246
|
+
//#region src/dashboard/presets/topbar-rail.tsx
|
|
1247
|
+
const TOPBAR_HEIGHT = "3rem";
|
|
1248
|
+
const RAIL_WIDTH = "3.5rem";
|
|
1249
|
+
const DRAWER_WIDTH = "14rem";
|
|
1250
|
+
const railVariants = {
|
|
1251
|
+
default: "bg-sidebar",
|
|
1252
|
+
dark: "bg-zinc-900 dark:bg-zinc-950",
|
|
1253
|
+
primary: "bg-primary",
|
|
1254
|
+
muted: "bg-muted"
|
|
1255
|
+
};
|
|
1256
|
+
const railTextVariants = {
|
|
1257
|
+
default: "text-sidebar-foreground",
|
|
1258
|
+
dark: "text-zinc-100",
|
|
1259
|
+
primary: "text-primary-foreground",
|
|
1260
|
+
muted: "text-muted-foreground"
|
|
1261
|
+
};
|
|
1262
|
+
function getUserInitials(name) {
|
|
1263
|
+
if (!name) return "?";
|
|
1264
|
+
const names = name.trim().split(" ");
|
|
1265
|
+
if (names.length === 1) return names[0].slice(0, 2).toUpperCase();
|
|
1266
|
+
return (names[0][0] + names[names.length - 1][0]).toUpperCase();
|
|
1267
|
+
}
|
|
1268
|
+
function TopbarBreadcrumbs({ items }) {
|
|
1269
|
+
return /* @__PURE__ */ jsx(Breadcrumb, { children: /* @__PURE__ */ jsx(BreadcrumbList, { children: items.map((item, index) => /* @__PURE__ */ jsxs(React$1.Fragment, { children: [/* @__PURE__ */ jsx(BreadcrumbItem, {
|
|
1270
|
+
className: item.current ? "" : "hidden md:block",
|
|
1271
|
+
children: item.current ? /* @__PURE__ */ jsx(BreadcrumbPage, { children: item.label }) : /* @__PURE__ */ jsx(BreadcrumbLink, {
|
|
1272
|
+
render: /* @__PURE__ */ jsx(Link, { href: item.href || "#" }),
|
|
1273
|
+
children: item.label
|
|
1274
|
+
})
|
|
1275
|
+
}), index < items.length - 1 && /* @__PURE__ */ jsx(BreadcrumbSeparator, { className: "hidden md:block" })] }, `breadcrumb-${index}`)) }) });
|
|
1276
|
+
}
|
|
1277
|
+
function TopbarUserMenu({ user, menuItems = [], onLogout }) {
|
|
1278
|
+
return /* @__PURE__ */ jsxs(DropdownMenu, { children: [/* @__PURE__ */ jsx(DropdownMenuTrigger, {
|
|
1279
|
+
render: /* @__PURE__ */ jsx(Button, {
|
|
1280
|
+
variant: "ghost",
|
|
1281
|
+
size: "icon",
|
|
1282
|
+
className: "h-8 w-8 rounded-full"
|
|
1283
|
+
}),
|
|
1284
|
+
children: /* @__PURE__ */ jsxs(Avatar, {
|
|
1285
|
+
className: "h-8 w-8",
|
|
1286
|
+
children: [/* @__PURE__ */ jsx(AvatarImage, {
|
|
1287
|
+
src: user.avatar,
|
|
1288
|
+
alt: user.name
|
|
1289
|
+
}), /* @__PURE__ */ jsx(AvatarFallback, {
|
|
1290
|
+
className: "text-xs",
|
|
1291
|
+
children: getUserInitials(user.name)
|
|
1292
|
+
})]
|
|
1293
|
+
})
|
|
1294
|
+
}), /* @__PURE__ */ jsxs(DropdownMenuContent, {
|
|
1295
|
+
className: "w-56",
|
|
1296
|
+
align: "end",
|
|
1297
|
+
sideOffset: 4,
|
|
1298
|
+
children: [
|
|
1299
|
+
/* @__PURE__ */ jsx(DropdownMenuGroup, { children: /* @__PURE__ */ jsx(DropdownMenuLabel, {
|
|
1300
|
+
className: "p-0 font-normal",
|
|
1301
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
1302
|
+
className: "flex items-center gap-2 px-2 py-1.5 text-left text-sm",
|
|
1303
|
+
children: [/* @__PURE__ */ jsxs(Avatar, {
|
|
1304
|
+
className: "h-8 w-8",
|
|
1305
|
+
children: [/* @__PURE__ */ jsx(AvatarImage, {
|
|
1306
|
+
src: user.avatar,
|
|
1307
|
+
alt: user.name
|
|
1308
|
+
}), /* @__PURE__ */ jsx(AvatarFallback, {
|
|
1309
|
+
className: "text-xs",
|
|
1310
|
+
children: getUserInitials(user.name)
|
|
1311
|
+
})]
|
|
1312
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
1313
|
+
className: "grid flex-1 text-left text-sm leading-tight",
|
|
1314
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
1315
|
+
className: "truncate font-semibold",
|
|
1316
|
+
children: user.name
|
|
1317
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
1318
|
+
className: "truncate text-xs text-muted-foreground",
|
|
1319
|
+
children: user.email
|
|
1320
|
+
})]
|
|
1321
|
+
})]
|
|
1322
|
+
})
|
|
1323
|
+
}) }),
|
|
1324
|
+
/* @__PURE__ */ jsx(DropdownMenuSeparator, {}),
|
|
1325
|
+
/* @__PURE__ */ jsx(DropdownMenuGroup, { children: menuItems.map((item, index) => /* @__PURE__ */ jsxs(React$1.Fragment, { children: [item.separator && index > 0 && /* @__PURE__ */ jsx(DropdownMenuSeparator, {}), item.href ? /* @__PURE__ */ jsxs(DropdownMenuItem, {
|
|
1326
|
+
render: /* @__PURE__ */ jsx(Link, { href: item.href }),
|
|
1327
|
+
className: "w-full flex items-center",
|
|
1328
|
+
children: [item.icon && /* @__PURE__ */ jsx(item.icon, { className: "mr-2 h-4 w-4" }), item.label]
|
|
1329
|
+
}) : /* @__PURE__ */ jsxs(DropdownMenuItem, {
|
|
1330
|
+
onClick: item.onClick,
|
|
1331
|
+
className: "w-full flex items-center",
|
|
1332
|
+
children: [item.icon && /* @__PURE__ */ jsx(item.icon, { className: "mr-2 h-4 w-4" }), item.label]
|
|
1333
|
+
})] }, item.label)) }),
|
|
1334
|
+
onLogout && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(DropdownMenuSeparator, {}), /* @__PURE__ */ jsx(DropdownMenuItem, {
|
|
1335
|
+
onClick: onLogout,
|
|
1336
|
+
className: "w-full flex items-center",
|
|
1337
|
+
children: "Log out"
|
|
1338
|
+
})] })
|
|
1339
|
+
]
|
|
1340
|
+
})] });
|
|
1341
|
+
}
|
|
1342
|
+
/**
|
|
1343
|
+
* TopbarRail - Fixed topbar + icon rail + secondary drawer layout.
|
|
1344
|
+
* Common in Linear, Figma, Jira-style SaaS applications.
|
|
1345
|
+
* Does NOT use SidebarProvider — fully custom layout.
|
|
1346
|
+
*/
|
|
1347
|
+
function TopbarRail({ brand, categories, user, breadcrumbs, actions, defaultCategoryId, onCategoryChange, defaultDrawerOpen = false, railVariant = "default", ariaLabel = "Navigation Menu", className, children }) {
|
|
1348
|
+
const [activeCategory, setActiveCategory] = React$1.useState(defaultCategoryId || (categories.length > 0 ? categories[0].id : null));
|
|
1349
|
+
const [isDrawerOpen, setIsDrawerOpen] = React$1.useState(defaultDrawerOpen);
|
|
1350
|
+
const [mobileOpen, setMobileOpen] = React$1.useState(false);
|
|
1351
|
+
useIsMobile();
|
|
1352
|
+
const handleCategorySelect = (id) => {
|
|
1353
|
+
if (activeCategory === id && isDrawerOpen) setIsDrawerOpen(false);
|
|
1354
|
+
else {
|
|
1355
|
+
setActiveCategory(id);
|
|
1356
|
+
onCategoryChange?.(id);
|
|
1357
|
+
setIsDrawerOpen(true);
|
|
1358
|
+
}
|
|
1359
|
+
};
|
|
1360
|
+
const handleMobileCategorySelect = (id) => {
|
|
1361
|
+
setActiveCategory(id);
|
|
1362
|
+
onCategoryChange?.(id);
|
|
1363
|
+
};
|
|
1364
|
+
const activeCategoryData = categories.find((c) => c.id === activeCategory) || null;
|
|
1365
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
1366
|
+
"data-slot": "topbar-rail-layout",
|
|
1367
|
+
className: cn("flex flex-col h-svh", className),
|
|
1368
|
+
children: [/* @__PURE__ */ jsxs("header", {
|
|
1369
|
+
className: "flex shrink-0 items-center border-b bg-background px-4 gap-2",
|
|
1370
|
+
style: { height: TOPBAR_HEIGHT },
|
|
1371
|
+
children: [
|
|
1372
|
+
/* @__PURE__ */ jsxs(Sheet, {
|
|
1373
|
+
open: mobileOpen,
|
|
1374
|
+
onOpenChange: setMobileOpen,
|
|
1375
|
+
children: [/* @__PURE__ */ jsx(SheetTrigger, {
|
|
1376
|
+
render: /* @__PURE__ */ jsx(Button, {
|
|
1377
|
+
variant: "ghost",
|
|
1378
|
+
size: "icon",
|
|
1379
|
+
className: "md:hidden h-8 w-8",
|
|
1380
|
+
"aria-label": "Open navigation"
|
|
1381
|
+
}),
|
|
1382
|
+
children: /* @__PURE__ */ jsx(Menu, { className: "h-5 w-5" })
|
|
1383
|
+
}), /* @__PURE__ */ jsxs(SheetContent, {
|
|
1384
|
+
side: "left",
|
|
1385
|
+
className: "w-72 p-0",
|
|
1386
|
+
children: [
|
|
1387
|
+
/* @__PURE__ */ jsx(SheetTitle, {
|
|
1388
|
+
className: "sr-only",
|
|
1389
|
+
children: ariaLabel
|
|
1390
|
+
}),
|
|
1391
|
+
/* @__PURE__ */ jsx(SheetDescription, {
|
|
1392
|
+
className: "sr-only",
|
|
1393
|
+
children: "Navigation menu with category icons and expanded content"
|
|
1394
|
+
}),
|
|
1395
|
+
/* @__PURE__ */ jsx(MobileNav, {
|
|
1396
|
+
brand,
|
|
1397
|
+
categories,
|
|
1398
|
+
user,
|
|
1399
|
+
activeCategory,
|
|
1400
|
+
onCategorySelect: handleMobileCategorySelect,
|
|
1401
|
+
onClose: () => setMobileOpen(false),
|
|
1402
|
+
railVariant
|
|
1403
|
+
})
|
|
1404
|
+
]
|
|
1405
|
+
})]
|
|
1406
|
+
}),
|
|
1407
|
+
/* @__PURE__ */ jsxs(Link, {
|
|
1408
|
+
href: brand.href || "/",
|
|
1409
|
+
className: "hidden md:flex items-center gap-2 shrink-0",
|
|
1410
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
1411
|
+
className: "flex items-center justify-center h-7 w-7",
|
|
1412
|
+
children: brand.icon
|
|
1413
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
1414
|
+
className: "font-semibold text-sm",
|
|
1415
|
+
children: brand.title
|
|
1416
|
+
})]
|
|
1417
|
+
}),
|
|
1418
|
+
/* @__PURE__ */ jsx(Link, {
|
|
1419
|
+
href: brand.href || "/",
|
|
1420
|
+
className: "md:hidden flex items-center gap-2 shrink-0",
|
|
1421
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
1422
|
+
className: "flex items-center justify-center h-7 w-7",
|
|
1423
|
+
children: brand.icon
|
|
1424
|
+
})
|
|
1425
|
+
}),
|
|
1426
|
+
breadcrumbs && breadcrumbs.length > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Separator, {
|
|
1427
|
+
orientation: "vertical",
|
|
1428
|
+
className: "mx-2 !h-4 !self-auto hidden md:block"
|
|
1429
|
+
}), /* @__PURE__ */ jsx(TopbarBreadcrumbs, { items: breadcrumbs })] }),
|
|
1430
|
+
/* @__PURE__ */ jsx("div", { className: "flex-1" }),
|
|
1431
|
+
actions && /* @__PURE__ */ jsx("div", {
|
|
1432
|
+
className: "flex items-center gap-2",
|
|
1433
|
+
children: actions
|
|
1434
|
+
}),
|
|
1435
|
+
user && /* @__PURE__ */ jsx(TopbarUserMenu, {
|
|
1436
|
+
user: user.data,
|
|
1437
|
+
menuItems: user.menuItems,
|
|
1438
|
+
onLogout: user.onLogout
|
|
1439
|
+
})
|
|
1440
|
+
]
|
|
1441
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
1442
|
+
className: "flex flex-1 overflow-hidden",
|
|
1443
|
+
children: [
|
|
1444
|
+
/* @__PURE__ */ jsx("nav", {
|
|
1445
|
+
className: cn("hidden md:flex flex-col shrink-0 border-r", railVariants[railVariant], railTextVariants[railVariant]),
|
|
1446
|
+
style: {
|
|
1447
|
+
width: RAIL_WIDTH,
|
|
1448
|
+
minWidth: RAIL_WIDTH
|
|
1449
|
+
},
|
|
1450
|
+
"aria-label": ariaLabel,
|
|
1451
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
1452
|
+
className: "flex-1 flex flex-col gap-1 p-2 pt-3",
|
|
1453
|
+
children: categories.map((category) => {
|
|
1454
|
+
const Icon = category.icon;
|
|
1455
|
+
return /* @__PURE__ */ jsxs(Tooltip, { children: [/* @__PURE__ */ jsx(TooltipTrigger, {
|
|
1456
|
+
render: /* @__PURE__ */ jsx("button", {
|
|
1457
|
+
onClick: () => handleCategorySelect(category.id),
|
|
1458
|
+
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
|
+
}),
|
|
1460
|
+
children: /* @__PURE__ */ jsx(Icon, { className: "h-5 w-5" })
|
|
1461
|
+
}), /* @__PURE__ */ jsx(TooltipContent, {
|
|
1462
|
+
side: "right",
|
|
1463
|
+
children: category.name
|
|
1464
|
+
})] }, category.id);
|
|
1465
|
+
})
|
|
1466
|
+
})
|
|
1467
|
+
}),
|
|
1468
|
+
/* @__PURE__ */ jsx("aside", {
|
|
1469
|
+
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
|
+
style: { width: isDrawerOpen ? DRAWER_WIDTH : 0 },
|
|
1471
|
+
children: activeCategoryData && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(SidebarHeader, {
|
|
1472
|
+
className: "h-12 border-b border-sidebar-border px-4 flex items-center shrink-0",
|
|
1473
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
1474
|
+
className: "flex items-center gap-2",
|
|
1475
|
+
children: [/* @__PURE__ */ jsx(activeCategoryData.icon, { className: "h-5 w-5 text-sidebar-foreground" }), /* @__PURE__ */ jsx("span", {
|
|
1476
|
+
className: "font-semibold text-sidebar-foreground text-sm truncate",
|
|
1477
|
+
children: activeCategoryData.name
|
|
1478
|
+
})]
|
|
1479
|
+
})
|
|
1480
|
+
}), /* @__PURE__ */ jsx(SidebarContent, {
|
|
1481
|
+
className: "flex-1 overflow-y-auto",
|
|
1482
|
+
children: /* @__PURE__ */ jsxs(SidebarGroup, {
|
|
1483
|
+
className: "p-2",
|
|
1484
|
+
children: [/* @__PURE__ */ jsx(SidebarGroupLabel, {
|
|
1485
|
+
className: "px-2 text-xs text-sidebar-foreground/60",
|
|
1486
|
+
children: activeCategoryData.name
|
|
1487
|
+
}), /* @__PURE__ */ jsx(SidebarMenu, { children: activeCategoryData.items.map((item, idx) => {
|
|
1488
|
+
const Icon = item.icon;
|
|
1489
|
+
return /* @__PURE__ */ jsx(SidebarMenuItem, { children: /* @__PURE__ */ jsxs(Link, {
|
|
1490
|
+
href: item.url,
|
|
1491
|
+
className: cn("flex items-center gap-2 rounded-md px-2 py-1.5 text-sm transition-colors w-full", "text-sidebar-foreground hover:bg-sidebar-accent hover:text-sidebar-accent-foreground", item.isActive && "bg-sidebar-accent text-sidebar-accent-foreground font-medium"),
|
|
1492
|
+
children: [
|
|
1493
|
+
Icon && /* @__PURE__ */ jsx(Icon, { className: "h-4 w-4" }),
|
|
1494
|
+
/* @__PURE__ */ jsx("span", {
|
|
1495
|
+
className: "truncate",
|
|
1496
|
+
children: item.title
|
|
1497
|
+
}),
|
|
1498
|
+
item.badge != null && /* @__PURE__ */ jsx("span", {
|
|
1499
|
+
className: "ml-auto text-xs bg-sidebar-accent text-sidebar-accent-foreground px-1.5 py-0.5 rounded shrink-0",
|
|
1500
|
+
children: item.badge
|
|
1501
|
+
})
|
|
1502
|
+
]
|
|
1503
|
+
}) }, `${activeCategoryData.id}-${item.title}-${idx}`);
|
|
1504
|
+
}) })]
|
|
1505
|
+
})
|
|
1506
|
+
})] })
|
|
1507
|
+
}),
|
|
1508
|
+
/* @__PURE__ */ jsx("main", {
|
|
1509
|
+
className: "flex-1 overflow-auto min-w-0",
|
|
1510
|
+
children
|
|
1511
|
+
})
|
|
1512
|
+
]
|
|
1513
|
+
})]
|
|
1514
|
+
});
|
|
1515
|
+
}
|
|
1516
|
+
function MobileNav({ brand, categories, user, activeCategory, onCategorySelect, onClose, railVariant }) {
|
|
1517
|
+
const activeCategoryData = categories.find((c) => c.id === activeCategory) || null;
|
|
1518
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
1519
|
+
className: "flex h-full flex-row",
|
|
1520
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
1521
|
+
className: cn("flex flex-col shrink-0", railVariants[railVariant], railTextVariants[railVariant], "border-r border-sidebar-border"),
|
|
1522
|
+
style: {
|
|
1523
|
+
width: RAIL_WIDTH,
|
|
1524
|
+
minWidth: RAIL_WIDTH
|
|
1525
|
+
},
|
|
1526
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
1527
|
+
className: "flex items-center justify-center p-2 h-14",
|
|
1528
|
+
children: /* @__PURE__ */ jsx(Link, {
|
|
1529
|
+
href: brand.href || "/",
|
|
1530
|
+
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"),
|
|
1531
|
+
onClick: onClose,
|
|
1532
|
+
children: brand.icon
|
|
1533
|
+
})
|
|
1534
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
1535
|
+
className: "flex-1 flex flex-col gap-1 p-2",
|
|
1536
|
+
children: categories.map((category) => {
|
|
1537
|
+
const Icon = category.icon;
|
|
1538
|
+
return /* @__PURE__ */ jsx("button", {
|
|
1539
|
+
onClick: () => onCategorySelect(category.id),
|
|
1540
|
+
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
|
+
children: /* @__PURE__ */ jsx(Icon, { className: "h-5 w-5" })
|
|
1542
|
+
}, category.id);
|
|
1543
|
+
})
|
|
1544
|
+
})]
|
|
1545
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
1546
|
+
className: "flex-1 flex flex-col bg-sidebar overflow-hidden",
|
|
1547
|
+
children: activeCategoryData ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1548
|
+
/* @__PURE__ */ jsx(SidebarHeader, {
|
|
1549
|
+
className: "h-14 border-b border-sidebar-border px-4 flex items-center shrink-0",
|
|
1550
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
1551
|
+
className: "flex items-center gap-2",
|
|
1552
|
+
children: [/* @__PURE__ */ jsx(activeCategoryData.icon, { className: "h-5 w-5 text-sidebar-foreground" }), /* @__PURE__ */ jsx("span", {
|
|
1553
|
+
className: "font-semibold text-sidebar-foreground truncate",
|
|
1554
|
+
children: activeCategoryData.name
|
|
1555
|
+
})]
|
|
1556
|
+
})
|
|
1557
|
+
}),
|
|
1558
|
+
/* @__PURE__ */ jsx(SidebarContent, {
|
|
1559
|
+
className: "flex-1 overflow-y-auto",
|
|
1560
|
+
children: /* @__PURE__ */ jsx(SidebarGroup, {
|
|
1561
|
+
className: "p-2",
|
|
1562
|
+
children: /* @__PURE__ */ jsx(SidebarMenu, { children: activeCategoryData.items.map((item, idx) => {
|
|
1563
|
+
const Icon = item.icon;
|
|
1564
|
+
return /* @__PURE__ */ jsx(SidebarMenuItem, { children: /* @__PURE__ */ jsxs(Link, {
|
|
1565
|
+
href: item.url,
|
|
1566
|
+
onClick: onClose,
|
|
1567
|
+
className: cn("flex items-center gap-2 rounded-md px-2 py-1.5 text-sm transition-colors w-full", "text-sidebar-foreground hover:bg-sidebar-accent hover:text-sidebar-accent-foreground", item.isActive && "bg-sidebar-accent text-sidebar-accent-foreground font-medium"),
|
|
1568
|
+
children: [
|
|
1569
|
+
Icon && /* @__PURE__ */ jsx(Icon, { className: "h-4 w-4" }),
|
|
1570
|
+
/* @__PURE__ */ jsx("span", {
|
|
1571
|
+
className: "truncate",
|
|
1572
|
+
children: item.title
|
|
1573
|
+
}),
|
|
1574
|
+
item.badge != null && /* @__PURE__ */ jsx("span", {
|
|
1575
|
+
className: "ml-auto text-xs bg-sidebar-accent text-sidebar-accent-foreground px-1.5 py-0.5 rounded shrink-0",
|
|
1576
|
+
children: item.badge
|
|
1577
|
+
})
|
|
1578
|
+
]
|
|
1579
|
+
}) }, `mobile-${activeCategoryData.id}-${item.title}-${idx}`);
|
|
1580
|
+
}) })
|
|
1581
|
+
})
|
|
1582
|
+
}),
|
|
1583
|
+
user && /* @__PURE__ */ jsx("div", {
|
|
1584
|
+
className: "border-t border-sidebar-border p-3 shrink-0",
|
|
1585
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
1586
|
+
className: "flex items-center gap-2",
|
|
1587
|
+
children: [/* @__PURE__ */ jsxs(Avatar, {
|
|
1588
|
+
className: "h-8 w-8",
|
|
1589
|
+
children: [/* @__PURE__ */ jsx(AvatarImage, {
|
|
1590
|
+
src: user.data.avatar,
|
|
1591
|
+
alt: user.data.name
|
|
1592
|
+
}), /* @__PURE__ */ jsx(AvatarFallback, {
|
|
1593
|
+
className: "text-xs",
|
|
1594
|
+
children: getUserInitials(user.data.name)
|
|
1595
|
+
})]
|
|
1596
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
1597
|
+
className: "grid flex-1 text-left text-sm leading-tight",
|
|
1598
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
1599
|
+
className: "truncate font-semibold text-sm",
|
|
1600
|
+
children: user.data.name
|
|
1601
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
1602
|
+
className: "truncate text-xs text-muted-foreground",
|
|
1603
|
+
children: user.data.email
|
|
1604
|
+
})]
|
|
1605
|
+
})]
|
|
1606
|
+
})
|
|
1607
|
+
})
|
|
1608
|
+
] }) : /* @__PURE__ */ jsx("div", {
|
|
1609
|
+
className: "flex-1 flex items-center justify-center text-sidebar-foreground/60 text-sm",
|
|
1610
|
+
children: "Select a category"
|
|
1611
|
+
})
|
|
1612
|
+
})]
|
|
1613
|
+
});
|
|
1614
|
+
}
|
|
1615
|
+
|
|
1616
|
+
//#endregion
|
|
1617
|
+
export { DashboardContent, DashboardHeader, DashboardPageLayout, DualSidebar, FloatingSidebar, HeaderSection, InsetSidebar, MiniSidebar, PageHeader, ProjectSwitcher, SidebarBrand, SidebarNav, SidebarNavGroup, SidebarNavItem, SidebarUserMenu, TopbarRail, cn, resolveCategories, resolveMiniNavigation, resolveNavigation };
|