@fayz-ai/ui 0.1.7 → 0.2.1

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 (67) hide show
  1. package/LICENSE +21 -0
  2. package/dist/chunk-22J2RTMN.js +1397 -0
  3. package/dist/chunk-22J2RTMN.js.map +1 -0
  4. package/dist/chunk-2P7T6QQK.cjs +206 -0
  5. package/dist/chunk-2P7T6QQK.cjs.map +1 -0
  6. package/dist/chunk-7TBDPNLY.cjs +649 -0
  7. package/dist/chunk-7TBDPNLY.cjs.map +1 -0
  8. package/dist/chunk-CZ6X6PZT.js +1237 -0
  9. package/dist/chunk-CZ6X6PZT.js.map +1 -0
  10. package/dist/chunk-D6SXPF4F.cjs +397 -0
  11. package/dist/chunk-D6SXPF4F.cjs.map +1 -0
  12. package/dist/chunk-DNYVEOAX.js +11 -0
  13. package/dist/chunk-DNYVEOAX.js.map +1 -0
  14. package/dist/chunk-E526FHZ4.js +169 -0
  15. package/dist/chunk-E526FHZ4.js.map +1 -0
  16. package/dist/chunk-GE6XUREM.cjs +1277 -0
  17. package/dist/chunk-GE6XUREM.cjs.map +1 -0
  18. package/dist/chunk-GFH5HOCO.js +605 -0
  19. package/dist/chunk-GFH5HOCO.js.map +1 -0
  20. package/dist/chunk-IWBXYV4U.js +203 -0
  21. package/dist/chunk-IWBXYV4U.js.map +1 -0
  22. package/dist/chunk-MYZT2OG7.js +350 -0
  23. package/dist/chunk-MYZT2OG7.js.map +1 -0
  24. package/dist/chunk-O5F4LLSL.js +445 -0
  25. package/dist/chunk-O5F4LLSL.js.map +1 -0
  26. package/dist/chunk-TDGSY5W6.cjs +474 -0
  27. package/dist/chunk-TDGSY5W6.cjs.map +1 -0
  28. package/dist/chunk-VVVZO2LE.cjs +13 -0
  29. package/dist/chunk-VVVZO2LE.cjs.map +1 -0
  30. package/dist/chunk-W4XXXJRW.cjs +196 -0
  31. package/dist/chunk-W4XXXJRW.cjs.map +1 -0
  32. package/dist/chunk-YB5ZJKNG.cjs +1484 -0
  33. package/dist/chunk-YB5ZJKNG.cjs.map +1 -0
  34. package/dist/dashboard/index.cjs +102 -0
  35. package/dist/dashboard/index.cjs.map +1 -0
  36. package/dist/dashboard/index.js +5 -0
  37. package/dist/dashboard/index.js.map +1 -0
  38. package/dist/index.cjs +569 -4288
  39. package/dist/index.cjs.map +1 -1
  40. package/dist/index.js +15 -4136
  41. package/dist/index.js.map +1 -1
  42. package/dist/layout/ModulePage.d.ts.map +1 -1
  43. package/dist/layout/Topbar.d.ts.map +1 -1
  44. package/dist/layout/index.cjs +90 -0
  45. package/dist/layout/index.cjs.map +1 -0
  46. package/dist/layout/index.js +5 -0
  47. package/dist/layout/index.js.map +1 -0
  48. package/dist/layout/useCrossModuleBack.d.ts +16 -0
  49. package/dist/layout/useCrossModuleBack.d.ts.map +1 -0
  50. package/dist/preset.cjs +18 -0
  51. package/dist/preset.cjs.map +1 -0
  52. package/dist/preset.js +3 -0
  53. package/dist/preset.js.map +1 -0
  54. package/dist/primitives/breadcrumb.d.ts.map +1 -1
  55. package/dist/primitives/index.cjs +347 -0
  56. package/dist/primitives/index.cjs.map +1 -0
  57. package/dist/primitives/index.js +6 -0
  58. package/dist/primitives/index.js.map +1 -0
  59. package/dist/theme/index.cjs +41 -0
  60. package/dist/theme/index.cjs.map +1 -0
  61. package/dist/theme/index.js +4 -0
  62. package/dist/theme/index.js.map +1 -0
  63. package/package.json +10 -10
  64. package/src/layout/ModulePage.tsx +15 -8
  65. package/src/layout/Topbar.tsx +5 -1
  66. package/src/layout/useCrossModuleBack.ts +30 -0
  67. package/src/primitives/breadcrumb.tsx +7 -3
package/dist/index.js CHANGED
@@ -1,3546 +1,17 @@
1
- import { clsx } from 'clsx';
2
- import { twMerge } from 'tailwind-merge';
3
- import * as React10 from 'react';
4
- import { useState, useRef, useEffect, useMemo, useCallback } from 'react';
5
- import { Slot } from '@radix-ui/react-slot';
6
- import { cva } from 'class-variance-authority';
7
- import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
8
- import * as DialogPrimitive from '@radix-ui/react-dialog';
9
- import * as LucideIcons from 'lucide-react';
10
- import { X, ChevronRight, Check, Circle, ChevronDown, ChevronUp, Workflow, Star, MessageSquare, LayoutDashboard, Award, Zap, Mic, UsersRound, Disc3, ListMusic, Eye, Music, Layers, Banknote, CalendarDays, Radio, PartyPopper, Wheat, Egg, Apple, LeafyGreen, LayoutTemplate, Heart, PawPrint, Cat, Dog, Search, List, Plus, Filter, Building2, Contact, Handshake, MapPin, UtensilsCrossed, Camera, Tag, Percent, Globe, MessageCircle, BookOpen, UserCog, Briefcase, ClipboardList, Wrench, Target, ShoppingCart, Megaphone, DollarSign, Mail, FileText, BarChart3, Package, Calendar, Bell, CreditCard, Settings, Users, Home, ArrowUp, ArrowDown, ArrowUpDown, Loader2, ArrowLeft, Clock, ChevronLeft, CheckCircle2, AlertTriangle, XCircle, Info, PanelLeftClose, PanelLeft, Menu, SlidersHorizontal, RotateCcw, TreePalm, UserPlus, ArrowDownRight, ArrowUpRight, Ruler, Warehouse, Wallet, Sparkles, CircleDollarSign, TrendingUp, Receipt, Landmark, ArrowUpCircle, ArrowDownCircle, TrendingDown, EyeOff } from 'lucide-react';
11
- import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu';
12
- import { useReactTable, getSortedRowModel, getCoreRowModel, flexRender } from '@tanstack/react-table';
13
- import * as SelectPrimitive from '@radix-ui/react-select';
14
- import { Toaster } from 'sonner';
15
- export { toast } from 'sonner';
16
- import { hashRouterAdapter, useTranslation, getCurrentLocale, usePluginRuntime, getDashboardWidgets, resolvePluginComponent } from '@fayz-ai/core';
17
- import * as TabsPrimitive from '@radix-ui/react-tabs';
18
- import * as SeparatorPrimitive from '@radix-ui/react-separator';
19
- import * as Tooltip2 from '@radix-ui/react-tooltip';
20
- import { createPortal } from 'react-dom';
21
- import * as Popover2 from '@radix-ui/react-popover';
22
- import { create } from 'zustand';
1
+ export { Badge, Breadcrumb, Checkbox, ConfirmDialog, CurrencyInput, DatePicker, Input, ListView, Modal, ModalBody, ModalClose, ModalContent, ModalDescription, ModalFooter, ModalHeader, ModalOverlay, ModalPortal, ModalTitle, ModalTrigger, Popover, PopoverContent, PopoverTrigger, SearchSelect, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, Separator, Sheet, SheetBody, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetOverlay, SheetPortal, SheetTitle, SheetTrigger, Skeleton, TableSkeleton, Tabs, TabsContent, TabsList, TabsTrigger, TimePicker, ToastProvider, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, badgeVariants, toast } from './chunk-22J2RTMN.js';
2
+ export { AppShell, BackStyleProvider, ICON_MAP, ModuleLayoutProvider, ModulePage, NavTransitionProvider, PageHeaderActions, PageTransition, Sidebar, SubpageHeader, Topbar, useBackStyle, useLayoutStore, useModuleHeaderActionsSlot, useModuleHeaderSlot, useModuleLayout, useNavTransition } from './chunk-CZ6X6PZT.js';
3
+ export { SaveBar, SaveBarProvider, useBackHandler, useSaveBar } from './chunk-E526FHZ4.js';
4
+ export { ThemeProvider, createFayzTheme, createTheme, darkTheme, fayzThemePresets, lightTheme, useTheme, useThemeStore } from './chunk-O5F4LLSL.js';
5
+ export { ChartWidget, DEFAULT_RANGE_OPTIONS, DashboardCanvas, DashboardCustomizeMenu, DashboardGrid, DashboardNavigateProvider, DashboardRangeControl, DashboardRangeProvider, KpiCard, OnboardingWidget, TableWidget, WidgetGrid, defineChartWidget, defineCustomWidget, defineKpiWidget, defineOnboardingWidget, defineTableWidget, formatKpiValue, renderIcon, resolveDashboardLayout, useDashboardNavigate, useDashboardPreferences, useDashboardRange } from './chunk-GFH5HOCO.js';
6
+ export { Button, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, DataTable, Dropdown, DropdownCheckboxItem, DropdownContent, DropdownGroup, DropdownItem, DropdownLabel, DropdownPortal, DropdownRadioGroup, DropdownRadioItem, DropdownSeparator, DropdownShortcut, DropdownSub, DropdownSubContent, DropdownSubTrigger, DropdownTrigger, SegmentedControl, buttonVariants } from './chunk-MYZT2OG7.js';
7
+ import { cn } from './chunk-DNYVEOAX.js';
8
+ export { cn } from './chunk-DNYVEOAX.js';
9
+ import './chunk-IWBXYV4U.js';
10
+ import * as React from 'react';
23
11
  import * as AvatarPrimitive from '@radix-ui/react-avatar';
24
- import { ResponsiveContainer, PieChart, Tooltip as Tooltip$1, Pie, Cell, Legend, BarChart, CartesianGrid, XAxis, YAxis, Bar, AreaChart, Area, LineChart, Line } from 'recharts';
12
+ import { jsx } from 'react/jsx-runtime';
25
13
 
26
- // src/utils/cn.ts
27
- function cn(...inputs) {
28
- return twMerge(clsx(inputs));
29
- }
30
- var buttonVariants = cva(
31
- "inline-flex items-center justify-center gap-1.5 whitespace-nowrap rounded-button text-sm font-semibold ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
32
- {
33
- variants: {
34
- variant: {
35
- // Polaris primary — near-black brand with the stronger primary bevel
36
- default: "bg-primary text-primary-foreground border border-primary shadow-button-primary hover:bg-primary/90 active:shadow-button-inset",
37
- destructive: "bg-destructive text-destructive-foreground border border-destructive shadow-button-primary hover:bg-destructive/90 active:shadow-button-inset",
38
- // Default/secondary surface — white with hairline + bevel
39
- outline: "border border-border bg-card text-foreground shadow-button hover:bg-muted active:shadow-button-inset",
40
- secondary: "border border-border bg-card text-foreground shadow-button hover:bg-muted active:shadow-button-inset",
41
- ghost: "hover:bg-muted hover:text-foreground",
42
- link: "text-info underline-offset-4 hover:underline"
43
- },
44
- size: {
45
- // Polaris button sizes: md 30px, sm 26px, lg 36px
46
- default: "h-[30px] px-3 py-1",
47
- sm: "h-[26px] rounded-button px-2.5 text-xs",
48
- lg: "h-9 rounded-button px-4 text-sm",
49
- icon: "h-[30px] w-[30px]"
50
- }
51
- },
52
- defaultVariants: {
53
- variant: "default",
54
- size: "default"
55
- }
56
- }
57
- );
58
- var Button = React10.forwardRef(
59
- ({ className, variant, size, asChild = false, ...props }, ref) => {
60
- const Comp = asChild ? Slot : "button";
61
- return /* @__PURE__ */ jsx(
62
- Comp,
63
- {
64
- className: cn(buttonVariants({ variant, size, className })),
65
- ref,
66
- ...props
67
- }
68
- );
69
- }
70
- );
71
- Button.displayName = "Button";
72
- var Input = React10.forwardRef(
73
- ({ className, type, error, description, id, ...props }, ref) => {
74
- const inputId = id ?? React10.useId();
75
- const descriptionId = description ? `${inputId}-description` : void 0;
76
- const errorId = error ? `${inputId}-error` : void 0;
77
- return /* @__PURE__ */ jsxs("div", { className: "w-full", children: [
78
- /* @__PURE__ */ jsx(
79
- "input",
80
- {
81
- type,
82
- id: inputId,
83
- className: cn(
84
- "flex h-8 w-full rounded-input border border-input bg-card px-2 py-1 text-sm text-foreground shadow-[inset_0_1px_0_rgb(0_0_0_/0.06)] ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
85
- error && "border-destructive focus-visible:ring-destructive",
86
- className
87
- ),
88
- ref,
89
- "aria-invalid": error ? true : void 0,
90
- "aria-describedby": [descriptionId, errorId].filter(Boolean).join(" ") || void 0,
91
- ...props
92
- }
93
- ),
94
- description && !error && /* @__PURE__ */ jsx("p", { id: descriptionId, className: "mt-1.5 text-xs text-muted-foreground", children: description }),
95
- error && /* @__PURE__ */ jsx("p", { id: errorId, className: "mt-1.5 text-xs text-destructive", children: error })
96
- ] });
97
- }
98
- );
99
- Input.displayName = "Input";
100
- var badgeVariants = cva(
101
- "inline-flex items-center gap-1.5 rounded px-2 py-0.5 text-xs font-medium leading-[18px] transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
102
- {
103
- variants: {
104
- variant: {
105
- default: "bg-muted text-muted-foreground",
106
- secondary: "bg-muted text-muted-foreground",
107
- info: "bg-info-soft text-info-soft-foreground",
108
- success: "bg-success-soft text-success-soft-foreground",
109
- warning: "bg-warning-soft text-warning-soft-foreground",
110
- destructive: "bg-destructive-soft text-destructive-soft-foreground",
111
- magic: "bg-magic-soft text-magic-soft-foreground",
112
- strong: "bg-primary text-primary-foreground",
113
- outline: "border border-border text-foreground"
114
- }
115
- },
116
- defaultVariants: {
117
- variant: "default"
118
- }
119
- }
120
- );
121
- var VARIANTS_WITH_DOT = /* @__PURE__ */ new Set([
122
- "info",
123
- "success",
124
- "warning",
125
- "destructive",
126
- "magic"
127
- ]);
128
- var DOT_COLOR = {
129
- info: "hsl(var(--info))",
130
- success: "hsl(var(--success))",
131
- warning: "hsl(var(--warning))",
132
- destructive: "hsl(var(--destructive))",
133
- magic: "hsl(var(--magic))"
134
- };
135
- function Badge({ className, variant, dot, children, ...props }) {
136
- const v = variant ?? "default";
137
- const showDot = dot ?? VARIANTS_WITH_DOT.has(v);
138
- return /* @__PURE__ */ jsxs("div", { className: cn(badgeVariants({ variant }), className), ...props, children: [
139
- showDot && /* @__PURE__ */ jsx(
140
- "span",
141
- {
142
- className: "inline-block size-1.5 rounded-full",
143
- style: { background: DOT_COLOR[v] ?? "currentColor" }
144
- }
145
- ),
146
- children
147
- ] });
148
- }
149
- var Card = React10.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(
150
- "div",
151
- {
152
- ref,
153
- className: cn(
154
- "fayz-glass-surface rounded-card border bg-card text-card-foreground shadow-sm",
155
- className
156
- ),
157
- ...props
158
- }
159
- ));
160
- Card.displayName = "Card";
161
- var CardHeader = React10.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(
162
- "div",
163
- {
164
- ref,
165
- className: cn("flex flex-col space-y-1 p-4", className),
166
- ...props
167
- }
168
- ));
169
- CardHeader.displayName = "CardHeader";
170
- var CardTitle = React10.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(
171
- "h3",
172
- {
173
- ref,
174
- className: cn(
175
- "text-sm font-semibold leading-tight tracking-tight",
176
- className
177
- ),
178
- ...props
179
- }
180
- ));
181
- CardTitle.displayName = "CardTitle";
182
- var CardDescription = React10.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(
183
- "p",
184
- {
185
- ref,
186
- className: cn("text-xs text-muted-foreground", className),
187
- ...props
188
- }
189
- ));
190
- CardDescription.displayName = "CardDescription";
191
- var CardContent = React10.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx("div", { ref, className: cn("p-4 pt-0", className), ...props }));
192
- CardContent.displayName = "CardContent";
193
- var CardFooter = React10.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(
194
- "div",
195
- {
196
- ref,
197
- className: cn("flex items-center p-4 pt-0", className),
198
- ...props
199
- }
200
- ));
201
- CardFooter.displayName = "CardFooter";
202
- var SIZE_MAP = {
203
- sm: "sm:max-w-sm",
204
- md: "sm:max-w-md",
205
- lg: "sm:max-w-lg",
206
- xl: "sm:max-w-xl",
207
- "2xl": "sm:max-w-2xl",
208
- "3xl": "sm:max-w-3xl",
209
- full: "sm:max-w-[calc(100vw-4rem)]"
210
- };
211
- var Modal = DialogPrimitive.Root;
212
- var ModalTrigger = DialogPrimitive.Trigger;
213
- var ModalClose = DialogPrimitive.Close;
214
- var ModalPortal = DialogPrimitive.Portal;
215
- var ModalOverlay = React10.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(
216
- DialogPrimitive.Overlay,
217
- {
218
- ref,
219
- className: cn(
220
- "fayz-modal-overlay saas-mdl-ov fixed inset-0 z-50",
221
- "flex items-center justify-center p-0 sm:p-6",
222
- className
223
- ),
224
- ...props
225
- }
226
- ));
227
- ModalOverlay.displayName = "ModalOverlay";
228
- var ModalContent = React10.forwardRef(({ className, children, size = "lg", hideClose, noPadding, onPointerDownOutside, onInteractOutside, onCloseAutoFocus, ...props }, ref) => /* @__PURE__ */ jsx(ModalPortal, { children: /* @__PURE__ */ jsx(ModalOverlay, { children: /* @__PURE__ */ jsxs(
229
- DialogPrimitive.Content,
230
- {
231
- ref,
232
- onCloseAutoFocus: (e) => {
233
- e.preventDefault();
234
- onCloseAutoFocus?.(e);
235
- },
236
- onPointerDownOutside: (e) => {
237
- const target = e.target;
238
- if (target.closest("[data-modal-passthrough]") || target.closest("[data-radix-popper-content-wrapper]")) {
239
- e.preventDefault();
240
- }
241
- onPointerDownOutside?.(e);
242
- },
243
- onInteractOutside: (e) => {
244
- const target = e.target;
245
- if (target.closest("[data-modal-passthrough]") || target.closest("[data-radix-popper-content-wrapper]")) {
246
- e.preventDefault();
247
- }
248
- onInteractOutside?.(e);
249
- },
250
- className: cn(
251
- "fayz-glass-surface fayz-modal-content saas-mdl-ct relative z-50 flex flex-col w-full outline-none",
252
- // Mobile fullscreen
253
- "h-full rounded-none border-0",
254
- // Desktop centered card
255
- "sm:h-auto sm:max-h-[85vh] sm:rounded-2xl sm:border",
256
- SIZE_MAP[size],
257
- noPadding ? "" : "p-6 gap-4",
258
- className
259
- ),
260
- ...props,
261
- children: [
262
- children,
263
- !hideClose && /* @__PURE__ */ jsxs(DialogPrimitive.Close, { className: "absolute right-4 top-4 z-10 flex h-7 w-7 items-center justify-center rounded-full text-muted-foreground transition-colors hover:bg-muted hover:text-foreground focus:outline-none focus:ring-2 focus:ring-ring", children: [
264
- /* @__PURE__ */ jsx(X, { className: "h-4 w-4" }),
265
- /* @__PURE__ */ jsx("span", { className: "sr-only", children: "Close" })
266
- ] })
267
- ]
268
- }
269
- ) }) }));
270
- ModalContent.displayName = "ModalContent";
271
- function ModalHeader({ className, ...props }) {
272
- return /* @__PURE__ */ jsx("div", { className: cn("flex flex-col space-y-1.5 text-center sm:text-left shrink-0", className), ...props });
273
- }
274
- function ModalBody({ className, ...props }) {
275
- return /* @__PURE__ */ jsx("div", { className: cn("flex-1 overflow-y-auto min-h-0", className), ...props });
276
- }
277
- function ModalFooter({ className, ...props }) {
278
- return /* @__PURE__ */ jsx("div", { className: cn("flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2 shrink-0", className), ...props });
279
- }
280
- var ModalTitle = React10.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(DialogPrimitive.Title, { ref, className: cn("text-lg font-semibold leading-none tracking-tight", className), ...props }));
281
- ModalTitle.displayName = "ModalTitle";
282
- var ModalDescription = React10.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(DialogPrimitive.Description, { ref, className: cn("text-sm text-muted-foreground", className), ...props }));
283
- ModalDescription.displayName = "ModalDescription";
284
- var Dropdown = DropdownMenuPrimitive.Root;
285
- var DropdownTrigger = DropdownMenuPrimitive.Trigger;
286
- var DropdownGroup = DropdownMenuPrimitive.Group;
287
- var DropdownPortal = DropdownMenuPrimitive.Portal;
288
- var DropdownSub = DropdownMenuPrimitive.Sub;
289
- var DropdownRadioGroup = DropdownMenuPrimitive.RadioGroup;
290
- var DropdownSubTrigger = React10.forwardRef(({ className, inset, children, ...props }, ref) => /* @__PURE__ */ jsxs(
291
- DropdownMenuPrimitive.SubTrigger,
292
- {
293
- ref,
294
- className: cn(
295
- "flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-muted data-[state=open]:bg-muted",
296
- inset && "pl-8",
297
- className
298
- ),
299
- ...props,
300
- children: [
301
- children,
302
- /* @__PURE__ */ jsx(ChevronRight, { className: "ml-auto h-4 w-4" })
303
- ]
304
- }
305
- ));
306
- DropdownSubTrigger.displayName = DropdownMenuPrimitive.SubTrigger.displayName;
307
- var DropdownSubContent = React10.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(
308
- DropdownMenuPrimitive.SubContent,
309
- {
310
- ref,
311
- className: cn(
312
- "fayz-glass-surface z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
313
- className
314
- ),
315
- ...props
316
- }
317
- ));
318
- DropdownSubContent.displayName = DropdownMenuPrimitive.SubContent.displayName;
319
- var DropdownContent = React10.forwardRef(({ className, sideOffset = 4, ...props }, ref) => /* @__PURE__ */ jsx(DropdownMenuPrimitive.Portal, { children: /* @__PURE__ */ jsx(
320
- DropdownMenuPrimitive.Content,
321
- {
322
- ref,
323
- sideOffset,
324
- className: cn(
325
- "fayz-glass-surface z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
326
- className
327
- ),
328
- ...props
329
- }
330
- ) }));
331
- DropdownContent.displayName = DropdownMenuPrimitive.Content.displayName;
332
- var DropdownItem = React10.forwardRef(({ className, inset, ...props }, ref) => /* @__PURE__ */ jsx(
333
- DropdownMenuPrimitive.Item,
334
- {
335
- ref,
336
- className: cn(
337
- "relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-muted data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
338
- inset && "pl-8",
339
- className
340
- ),
341
- ...props
342
- }
343
- ));
344
- DropdownItem.displayName = DropdownMenuPrimitive.Item.displayName;
345
- var DropdownCheckboxItem = React10.forwardRef(({ className, children, checked, ...props }, ref) => /* @__PURE__ */ jsxs(
346
- DropdownMenuPrimitive.CheckboxItem,
347
- {
348
- ref,
349
- className: cn(
350
- "relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-muted data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
351
- className
352
- ),
353
- checked,
354
- ...props,
355
- children: [
356
- /* @__PURE__ */ jsx("span", { className: "absolute left-2 flex h-3.5 w-3.5 items-center justify-center", children: /* @__PURE__ */ jsx(DropdownMenuPrimitive.ItemIndicator, { children: /* @__PURE__ */ jsx(Check, { className: "h-4 w-4" }) }) }),
357
- children
358
- ]
359
- }
360
- ));
361
- DropdownCheckboxItem.displayName = DropdownMenuPrimitive.CheckboxItem.displayName;
362
- var DropdownRadioItem = React10.forwardRef(({ className, children, ...props }, ref) => /* @__PURE__ */ jsxs(
363
- DropdownMenuPrimitive.RadioItem,
364
- {
365
- ref,
366
- className: cn(
367
- "relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-muted data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
368
- className
369
- ),
370
- ...props,
371
- children: [
372
- /* @__PURE__ */ jsx("span", { className: "absolute left-2 flex h-3.5 w-3.5 items-center justify-center", children: /* @__PURE__ */ jsx(DropdownMenuPrimitive.ItemIndicator, { children: /* @__PURE__ */ jsx(Circle, { className: "h-2 w-2 fill-current" }) }) }),
373
- children
374
- ]
375
- }
376
- ));
377
- DropdownRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName;
378
- var DropdownLabel = React10.forwardRef(({ className, inset, ...props }, ref) => /* @__PURE__ */ jsx(
379
- DropdownMenuPrimitive.Label,
380
- {
381
- ref,
382
- className: cn(
383
- "px-2 py-1.5 text-sm font-semibold",
384
- inset && "pl-8",
385
- className
386
- ),
387
- ...props
388
- }
389
- ));
390
- DropdownLabel.displayName = DropdownMenuPrimitive.Label.displayName;
391
- var DropdownSeparator = React10.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(
392
- DropdownMenuPrimitive.Separator,
393
- {
394
- ref,
395
- className: cn("-mx-1 my-1 h-px bg-muted", className),
396
- ...props
397
- }
398
- ));
399
- DropdownSeparator.displayName = DropdownMenuPrimitive.Separator.displayName;
400
- var DropdownShortcut = ({
401
- className,
402
- ...props
403
- }) => {
404
- return /* @__PURE__ */ jsx(
405
- "span",
406
- {
407
- className: cn("ml-auto text-xs tracking-widest opacity-60", className),
408
- ...props
409
- }
410
- );
411
- };
412
- DropdownShortcut.displayName = "DropdownShortcut";
413
- function DataTable({
414
- columns,
415
- data,
416
- loading = false,
417
- skeletonRows = 3,
418
- emptyMessage = "No results.",
419
- onRowClick,
420
- variant = "card",
421
- compact = false
422
- }) {
423
- const isCard = variant === "card";
424
- const wrapperCls = isCard ? "rounded-lg border bg-card shadow-sm overflow-x-auto" : "rounded-lg border overflow-x-auto";
425
- const headerCls = isCard ? "border-b bg-muted/50" : "border-b bg-muted/30";
426
- const thCls = compact ? "px-3 py-1.5 text-left text-[11px] font-medium text-muted-foreground" : "px-4 py-2.5 text-left font-medium text-muted-foreground";
427
- const tdCls = compact ? "px-3 py-1.5 align-middle text-xs" : "px-4 py-3 align-middle";
428
- const tableCls = compact ? "w-full text-xs min-w-max" : "w-full text-sm min-w-max";
429
- const rowHoverCls = isCard ? "hover:bg-muted/30" : "hover:bg-muted/20";
430
- const [sorting, setSorting] = React10.useState([]);
431
- const table = useReactTable({
432
- data,
433
- columns,
434
- getCoreRowModel: getCoreRowModel(),
435
- getSortedRowModel: getSortedRowModel(),
436
- onSortingChange: setSorting,
437
- state: { sorting }
438
- });
439
- if (loading) {
440
- return /* @__PURE__ */ jsx("div", { className: wrapperCls, children: /* @__PURE__ */ jsxs("table", { className: tableCls, children: [
441
- /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsx("tr", { className: headerCls, children: columns.map((_, i) => /* @__PURE__ */ jsx("th", { className: thCls, children: /* @__PURE__ */ jsx("div", { className: "h-3.5 w-20 animate-pulse rounded bg-muted" }) }, i)) }) }),
442
- /* @__PURE__ */ jsx("tbody", { children: Array.from({ length: skeletonRows }).map((_, i) => /* @__PURE__ */ jsx("tr", { className: "border-b last:border-0", children: columns.map((_2, j) => /* @__PURE__ */ jsx("td", { className: tdCls, children: /* @__PURE__ */ jsx("div", { className: "h-3.5 w-full animate-pulse rounded bg-muted" }) }, j)) }, i)) })
443
- ] }) });
444
- }
445
- return /* @__PURE__ */ jsx("div", { className: wrapperCls, children: /* @__PURE__ */ jsxs("table", { className: tableCls, children: [
446
- /* @__PURE__ */ jsx("thead", { children: table.getHeaderGroups().map((headerGroup) => /* @__PURE__ */ jsx("tr", { className: headerCls, children: headerGroup.headers.map((header) => {
447
- const sorted = header.column.getIsSorted();
448
- return /* @__PURE__ */ jsx(
449
- "th",
450
- {
451
- className: thCls,
452
- children: header.isPlaceholder ? null : header.column.getCanSort() ? /* @__PURE__ */ jsxs(
453
- "button",
454
- {
455
- className: "flex items-center gap-1 hover:text-foreground transition-colors",
456
- onClick: header.column.getToggleSortingHandler(),
457
- children: [
458
- flexRender(header.column.columnDef.header, header.getContext()),
459
- sorted === "asc" ? /* @__PURE__ */ jsx(ArrowUp, { className: "h-3 w-3" }) : sorted === "desc" ? /* @__PURE__ */ jsx(ArrowDown, { className: "h-3 w-3" }) : /* @__PURE__ */ jsx(ArrowUpDown, { className: "h-3 w-3 opacity-40" })
460
- ]
461
- }
462
- ) : flexRender(header.column.columnDef.header, header.getContext())
463
- },
464
- header.id
465
- );
466
- }) }, headerGroup.id)) }),
467
- /* @__PURE__ */ jsx("tbody", { children: table.getRowModel().rows?.length ? table.getRowModel().rows.map((row) => /* @__PURE__ */ jsx(
468
- "tr",
469
- {
470
- className: cn(
471
- `border-b last:border-0 transition-colors ${rowHoverCls}`,
472
- onRowClick && "cursor-pointer"
473
- ),
474
- onClick: () => onRowClick?.(row.original),
475
- children: row.getVisibleCells().map((cell) => /* @__PURE__ */ jsx("td", { className: tdCls, children: flexRender(cell.column.columnDef.cell, cell.getContext()) }, cell.id))
476
- },
477
- row.id
478
- )) : /* @__PURE__ */ jsx("tr", { children: /* @__PURE__ */ jsx("td", { colSpan: columns.length, className: "h-24 text-center text-muted-foreground", children: emptyMessage }) }) })
479
- ] }) });
480
- }
481
- var COLOR_CLASSES = {
482
- primary: "border-primary bg-primary text-primary-foreground",
483
- success: "border-success bg-success text-white dark:border-success dark:bg-success",
484
- warning: "border-warning bg-warning text-white",
485
- destructive: "border-destructive bg-destructive text-destructive-foreground"
486
- };
487
- var Checkbox = React10.forwardRef(
488
- ({ checked = false, onChange, disabled, className, color = "primary", id, ...props }, ref) => {
489
- return /* @__PURE__ */ jsx(
490
- "button",
491
- {
492
- ref,
493
- id,
494
- type: "button",
495
- role: "checkbox",
496
- "aria-checked": checked,
497
- disabled,
498
- onClick: () => onChange?.(!checked),
499
- className: cn(
500
- "peer inline-flex h-[18px] w-[18px] shrink-0 items-center justify-center rounded-[5px] border-2 transition-all duration-150",
501
- "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background",
502
- "disabled:cursor-not-allowed disabled:opacity-50",
503
- checked ? `${COLOR_CLASSES[color]} shadow-sm` : "border-muted-foreground/40 bg-background hover:border-muted-foreground/60",
504
- className
505
- ),
506
- ...props,
507
- children: checked && /* @__PURE__ */ jsx(Check, { className: "h-3.5 w-3.5", strokeWidth: 3 })
508
- }
509
- );
510
- }
511
- );
512
- Checkbox.displayName = "Checkbox";
513
- var STYLE_ID = "saas-sheet-styles";
514
- if (typeof document !== "undefined" && !document.getElementById(STYLE_ID)) {
515
- const style = document.createElement("style");
516
- style.id = STYLE_ID;
517
- style.textContent = `
518
- @keyframes saas-sheet-fade-in { from { opacity: 0 } to { opacity: 1 } }
519
- @keyframes saas-sheet-fade-out { from { opacity: 1 } to { opacity: 0 } }
520
- @keyframes saas-sheet-slide-in { from { transform: translateX(100%) } to { transform: translateX(0) } }
521
- @keyframes saas-sheet-slide-out { from { transform: translateX(0) } to { transform: translateX(100%) } }
522
- .saas-sheet-overlay[data-state="open"] { animation: saas-sheet-fade-in 200ms ease-out }
523
- .saas-sheet-overlay[data-state="closed"] { animation: saas-sheet-fade-out 150ms ease-in }
524
- .saas-sheet-content[data-state="open"] { animation: saas-sheet-slide-in 280ms cubic-bezier(0.16, 1, 0.3, 1) }
525
- .saas-sheet-content[data-state="closed"] { animation: saas-sheet-slide-out 200ms ease-in }
526
- `;
527
- document.head.appendChild(style);
528
- }
529
- var Sheet = DialogPrimitive.Root;
530
- var SheetTrigger = DialogPrimitive.Trigger;
531
- var SheetClose = DialogPrimitive.Close;
532
- var SheetPortal = DialogPrimitive.Portal;
533
- var SheetOverlay = React10.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(
534
- DialogPrimitive.Overlay,
535
- {
536
- ref,
537
- className: cn(
538
- "saas-sheet-overlay fixed inset-0 z-50 bg-black/40 backdrop-blur-[2px]",
539
- className
540
- ),
541
- ...props
542
- }
543
- ));
544
- SheetOverlay.displayName = "SheetOverlay";
545
- var SheetContent = React10.forwardRef(({ className, children, width, hideClose, overlay = "blur", ...props }, ref) => /* @__PURE__ */ jsxs(SheetPortal, { children: [
546
- /* @__PURE__ */ jsx(SheetOverlay, { className: overlay === "none" ? "bg-transparent backdrop-blur-0" : overlay === "dim" ? "backdrop-blur-0" : void 0 }),
547
- /* @__PURE__ */ jsxs(
548
- DialogPrimitive.Content,
549
- {
550
- ref,
551
- className: cn(
552
- "fayz-glass-surface saas-sheet-content fixed inset-y-0 right-0 z-50 flex flex-col border-l bg-card shadow-2xl outline-none",
553
- "w-full",
554
- width ?? "max-w-md",
555
- className
556
- ),
557
- ...props,
558
- children: [
559
- children,
560
- !hideClose && /* @__PURE__ */ jsxs(DialogPrimitive.Close, { className: "absolute right-4 top-4 z-10 flex h-7 w-7 items-center justify-center rounded-full text-muted-foreground transition-colors hover:bg-muted hover:text-foreground focus:outline-none focus:ring-2 focus:ring-ring", children: [
561
- /* @__PURE__ */ jsx(X, { className: "h-4 w-4" }),
562
- /* @__PURE__ */ jsx("span", { className: "sr-only", children: "Close" })
563
- ] })
564
- ]
565
- }
566
- )
567
- ] }));
568
- SheetContent.displayName = "SheetContent";
569
- function SheetHeader({ className, ...props }) {
570
- return /* @__PURE__ */ jsx(
571
- "div",
572
- {
573
- className: cn("flex flex-col gap-1 px-5 py-4 border-b shrink-0", className),
574
- ...props
575
- }
576
- );
577
- }
578
- SheetHeader.displayName = "SheetHeader";
579
- function SheetFooter({ className, ...props }) {
580
- return /* @__PURE__ */ jsx(
581
- "div",
582
- {
583
- className: cn("flex items-center gap-2 px-5 py-3 border-t shrink-0", className),
584
- ...props
585
- }
586
- );
587
- }
588
- SheetFooter.displayName = "SheetFooter";
589
- var SheetTitle = React10.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(
590
- DialogPrimitive.Title,
591
- {
592
- ref,
593
- className: cn("text-sm font-semibold leading-none tracking-tight", className),
594
- ...props
595
- }
596
- ));
597
- SheetTitle.displayName = "SheetTitle";
598
- var SheetDescription = React10.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(
599
- DialogPrimitive.Description,
600
- {
601
- ref,
602
- className: cn("text-xs text-muted-foreground", className),
603
- ...props
604
- }
605
- ));
606
- SheetDescription.displayName = "SheetDescription";
607
- function SheetBody({ className, ...props }) {
608
- return /* @__PURE__ */ jsx(
609
- "div",
610
- {
611
- className: cn("flex-1 overflow-y-auto px-5 py-5", className),
612
- ...props
613
- }
614
- );
615
- }
616
- SheetBody.displayName = "SheetBody";
617
- var Select = SelectPrimitive.Root;
618
- var SelectGroup = SelectPrimitive.Group;
619
- var SelectValue = SelectPrimitive.Value;
620
- var SelectTrigger = React10.forwardRef(({ className, children, ...props }, ref) => /* @__PURE__ */ jsxs(
621
- SelectPrimitive.Trigger,
622
- {
623
- ref,
624
- className: cn(
625
- "flex h-10 w-full items-center justify-between rounded-input border border-input bg-background px-3 py-2 text-sm shadow-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1",
626
- className
627
- ),
628
- ...props,
629
- children: [
630
- children,
631
- /* @__PURE__ */ jsx(SelectPrimitive.Icon, { asChild: true, children: /* @__PURE__ */ jsx(ChevronDown, { className: "h-4 w-4 opacity-50" }) })
632
- ]
633
- }
634
- ));
635
- SelectTrigger.displayName = SelectPrimitive.Trigger.displayName;
636
- var SelectScrollUpButton = React10.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(
637
- SelectPrimitive.ScrollUpButton,
638
- {
639
- ref,
640
- className: cn(
641
- "flex cursor-default items-center justify-center py-1",
642
- className
643
- ),
644
- ...props,
645
- children: /* @__PURE__ */ jsx(ChevronUp, { className: "h-4 w-4" })
646
- }
647
- ));
648
- SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName;
649
- var SelectScrollDownButton = React10.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(
650
- SelectPrimitive.ScrollDownButton,
651
- {
652
- ref,
653
- className: cn(
654
- "flex cursor-default items-center justify-center py-1",
655
- className
656
- ),
657
- ...props,
658
- children: /* @__PURE__ */ jsx(ChevronDown, { className: "h-4 w-4" })
659
- }
660
- ));
661
- SelectScrollDownButton.displayName = SelectPrimitive.ScrollDownButton.displayName;
662
- var SelectContent = React10.forwardRef(({ className, children, position = "popper", ...props }, ref) => /* @__PURE__ */ jsx(SelectPrimitive.Portal, { children: /* @__PURE__ */ jsxs(
663
- SelectPrimitive.Content,
664
- {
665
- ref,
666
- className: cn(
667
- "fayz-glass-surface relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-card border bg-popover text-popover-foreground shadow-md",
668
- position === "popper" && "data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
669
- className
670
- ),
671
- position,
672
- ...props,
673
- children: [
674
- /* @__PURE__ */ jsx(SelectScrollUpButton, {}),
675
- /* @__PURE__ */ jsx(
676
- SelectPrimitive.Viewport,
677
- {
678
- className: cn(
679
- "p-1",
680
- position === "popper" && "h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]"
681
- ),
682
- children
683
- }
684
- ),
685
- /* @__PURE__ */ jsx(SelectScrollDownButton, {})
686
- ]
687
- }
688
- ) }));
689
- SelectContent.displayName = SelectPrimitive.Content.displayName;
690
- var SelectLabel = React10.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(
691
- SelectPrimitive.Label,
692
- {
693
- ref,
694
- className: cn("py-1.5 pl-8 pr-2 text-sm font-semibold", className),
695
- ...props
696
- }
697
- ));
698
- SelectLabel.displayName = SelectPrimitive.Label.displayName;
699
- var SelectItem = React10.forwardRef(({ className, children, ...props }, ref) => /* @__PURE__ */ jsxs(
700
- SelectPrimitive.Item,
701
- {
702
- ref,
703
- className: cn(
704
- "relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none data-[highlighted]:bg-muted focus:bg-muted data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
705
- className
706
- ),
707
- ...props,
708
- children: [
709
- /* @__PURE__ */ jsx("span", { className: "absolute left-2 flex h-3.5 w-3.5 items-center justify-center", children: /* @__PURE__ */ jsx(SelectPrimitive.ItemIndicator, { children: /* @__PURE__ */ jsx(Check, { className: "h-4 w-4" }) }) }),
710
- /* @__PURE__ */ jsx(SelectPrimitive.ItemText, { children })
711
- ]
712
- }
713
- ));
714
- SelectItem.displayName = SelectPrimitive.Item.displayName;
715
- var SelectSeparator = React10.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(
716
- SelectPrimitive.Separator,
717
- {
718
- ref,
719
- className: cn("-mx-1 my-1 h-px bg-muted", className),
720
- ...props
721
- }
722
- ));
723
- SelectSeparator.displayName = SelectPrimitive.Separator.displayName;
724
- var SaveBarApiContext = React10.createContext(null);
725
- var SaveBarStateContext = React10.createContext({ visible: false, saving: false });
726
- var _saveBarVisible = false;
727
- var _saveBarSubs = /* @__PURE__ */ new Set();
728
- function setSaveBarVisibleSignal(v) {
729
- if (v === _saveBarVisible) return;
730
- _saveBarVisible = v;
731
- _saveBarSubs.forEach((fn) => fn());
732
- }
733
- function useSaveBarVisible() {
734
- return React10.useSyncExternalStore(
735
- (cb) => {
736
- _saveBarSubs.add(cb);
737
- return () => {
738
- _saveBarSubs.delete(cb);
739
- };
740
- },
741
- () => _saveBarVisible,
742
- () => false
743
- );
744
- }
745
- function SaveBarProvider({ children }) {
746
- const [state, setState] = React10.useState({ visible: false, saving: false });
747
- const handlers = React10.useRef({});
748
- const api = React10.useMemo(() => ({ setState, handlers }), []);
749
- const backStack = React10.useRef([]);
750
- const backApi = React10.useMemo(() => ({ stack: backStack }), []);
751
- React10.useEffect(() => {
752
- setSaveBarVisibleSignal(state.visible);
753
- }, [state.visible]);
754
- React10.useEffect(() => {
755
- function onKey(e) {
756
- if (state.visible && !state.saving) {
757
- if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) {
758
- e.preventDefault();
759
- handlers.current.onSave?.();
760
- } else if (e.key === "Escape") {
761
- e.preventDefault();
762
- handlers.current.onDiscard?.();
763
- }
764
- return;
765
- }
766
- if (e.key === "Escape" && !e.defaultPrevented && !isEditableTarget(e.target)) {
767
- for (let i = backStack.current.length - 1; i >= 0; i--) {
768
- const back = backStack.current[i].current;
769
- if (back) {
770
- e.preventDefault();
771
- back();
772
- return;
773
- }
774
- }
775
- }
776
- }
777
- window.addEventListener("keydown", onKey);
778
- return () => window.removeEventListener("keydown", onKey);
779
- }, [state.visible, state.saving]);
780
- return /* @__PURE__ */ jsx(SaveBarApiContext.Provider, { value: api, children: /* @__PURE__ */ jsx(BackNavApiContext.Provider, { value: backApi, children: /* @__PURE__ */ jsx(SaveBarStateContext.Provider, { value: state, children }) }) });
781
- }
782
- var BackNavApiContext = React10.createContext(null);
783
- function isEditableTarget(target) {
784
- const el = target;
785
- if (!el || !el.tagName) return false;
786
- const tag = el.tagName.toLowerCase();
787
- return tag === "input" || tag === "textarea" || tag === "select" || el.isContentEditable;
788
- }
789
- function useBackHandler(onBack) {
790
- const api = React10.useContext(BackNavApiContext);
791
- const ref = React10.useRef(onBack);
792
- React10.useEffect(() => {
793
- ref.current = onBack;
794
- });
795
- React10.useEffect(() => {
796
- if (!api) return;
797
- const stack = api.stack.current;
798
- stack.push(ref);
799
- return () => {
800
- const i = stack.indexOf(ref);
801
- if (i !== -1) stack.splice(i, 1);
802
- };
803
- }, [api]);
804
- }
805
- function useSaveBar({ dirty, saving = false, onSave, onDiscard, message, saveLabel, discardLabel }) {
806
- const api = React10.useContext(SaveBarApiContext);
807
- React10.useEffect(() => {
808
- if (api) api.handlers.current = { onSave, onDiscard };
809
- });
810
- React10.useEffect(() => {
811
- if (!api) return;
812
- api.setState({ visible: dirty, saving, message, saveLabel, discardLabel });
813
- return () => api.setState((s) => ({ ...s, visible: false }));
814
- }, [api, dirty, saving, message, saveLabel, discardLabel]);
815
- }
816
- function SaveBar({ className }) {
817
- const state = React10.useContext(SaveBarStateContext);
818
- const api = React10.useContext(SaveBarApiContext);
819
- const t = useTranslation();
820
- const [rendered, setRendered] = React10.useState(state.visible);
821
- const [closing, setClosing] = React10.useState(false);
822
- React10.useEffect(() => {
823
- if (state.visible) {
824
- setRendered(true);
825
- setClosing(false);
826
- return;
827
- }
828
- if (!rendered) return;
829
- setClosing(true);
830
- const id = setTimeout(() => {
831
- setRendered(false);
832
- setClosing(false);
833
- }, 200);
834
- return () => clearTimeout(id);
835
- }, [state.visible, rendered]);
836
- if (!api || !rendered) return null;
837
- const { saving, message, saveLabel, discardLabel } = state;
838
- return /* @__PURE__ */ jsx("div", { className: cn("pointer-events-none fixed inset-x-0 bottom-5 z-40 flex justify-center px-4", className), children: /* @__PURE__ */ jsxs("div", { className: cn(
839
- "pointer-events-auto flex items-center gap-5 rounded-full bg-foreground py-2 pl-5 pr-2 text-background shadow-xl ring-1 ring-black/5 duration-200",
840
- closing ? "animate-out fade-out slide-out-to-bottom-2" : "animate-in fade-in slide-in-from-bottom-2"
841
- ), children: [
842
- /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-2 text-sm font-medium", children: [
843
- /* @__PURE__ */ jsx("span", { className: "h-2 w-2 shrink-0 rounded-full bg-amber-400" }),
844
- message ?? t("saveBar.unsaved")
845
- ] }),
846
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
847
- /* @__PURE__ */ jsx(
848
- "button",
849
- {
850
- type: "button",
851
- disabled: saving,
852
- onClick: () => api.handlers.current.onDiscard?.(),
853
- className: "rounded-full px-3 py-1.5 text-sm font-medium text-background/70 transition-colors hover:bg-background/10 hover:text-background disabled:opacity-50",
854
- children: discardLabel ?? t("common.discard")
855
- }
856
- ),
857
- /* @__PURE__ */ jsxs(
858
- "button",
859
- {
860
- type: "button",
861
- disabled: saving,
862
- onClick: () => api.handlers.current.onSave?.(),
863
- className: "inline-flex items-center rounded-full bg-background px-4 py-1.5 text-sm font-semibold text-foreground transition-colors hover:bg-background/90 disabled:opacity-60",
864
- children: [
865
- saving && /* @__PURE__ */ jsx(Loader2, { className: "mr-1.5 h-3.5 w-3.5 animate-spin" }),
866
- saveLabel ?? t("common.save")
867
- ]
868
- }
869
- )
870
- ] })
871
- ] }) });
872
- }
873
- function ToastProvider({
874
- position = "bottom-center",
875
- theme = "system"
876
- }) {
877
- const saveBarVisible = useSaveBarVisible();
878
- const bottomOffset = position.startsWith("bottom") && saveBarVisible ? 84 : 24;
879
- return /* @__PURE__ */ jsx(
880
- Toaster,
881
- {
882
- position,
883
- theme,
884
- offset: bottomOffset,
885
- closeButton: true,
886
- richColors: true,
887
- toastOptions: {
888
- classNames: {
889
- // Format harmonized with the SaveBar island: soft ring + xl shadow,
890
- // generous rounded-2xl radius (full-pill looks wrong on multi-line
891
- // toasts). Colors stay semantic (richColors).
892
- toast: "group !gap-3 !rounded-3xl !border !px-4 !py-3 !shadow-xl !ring-1 !ring-black/5",
893
- title: "!text-sm !font-semibold",
894
- description: "!text-xs !mt-0.5",
895
- closeButton: "!opacity-0 group-hover:!opacity-60 hover:!opacity-100 !transition-opacity"
896
- }
897
- }
898
- }
899
- );
900
- }
901
- var Tabs = TabsPrimitive.Root;
902
- var TabsList = React10.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(
903
- TabsPrimitive.List,
904
- {
905
- ref,
906
- className: cn(
907
- "inline-flex h-10 items-center justify-center rounded-md bg-muted p-1 text-muted-foreground",
908
- className
909
- ),
910
- ...props
911
- }
912
- ));
913
- TabsList.displayName = TabsPrimitive.List.displayName;
914
- var TabsTrigger = React10.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(
915
- TabsPrimitive.Trigger,
916
- {
917
- ref,
918
- className: cn(
919
- "inline-flex items-center justify-center whitespace-nowrap rounded-sm px-3 py-1.5 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow-sm",
920
- className
921
- ),
922
- ...props
923
- }
924
- ));
925
- TabsTrigger.displayName = TabsPrimitive.Trigger.displayName;
926
- var TabsContent = React10.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(
927
- TabsPrimitive.Content,
928
- {
929
- ref,
930
- className: cn(
931
- "mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
932
- className
933
- ),
934
- ...props
935
- }
936
- ));
937
- TabsContent.displayName = TabsPrimitive.Content.displayName;
938
- var Separator3 = React10.forwardRef(
939
- ({ className, orientation = "horizontal", decorative = true, ...props }, ref) => /* @__PURE__ */ jsx(
940
- SeparatorPrimitive.Root,
941
- {
942
- ref,
943
- decorative,
944
- orientation,
945
- className: cn(
946
- "shrink-0 bg-border",
947
- orientation === "horizontal" ? "h-[1px] w-full" : "h-full w-[1px]",
948
- className
949
- ),
950
- ...props
951
- }
952
- )
953
- );
954
- Separator3.displayName = SeparatorPrimitive.Root.displayName;
955
- function Breadcrumb({ parent, current, onBack }) {
956
- useBackHandler(onBack);
957
- return /* @__PURE__ */ jsxs("nav", { className: "flex items-center gap-1.5 text-sm text-muted-foreground", children: [
958
- /* @__PURE__ */ jsxs(
959
- "button",
960
- {
961
- type: "button",
962
- onClick: onBack,
963
- className: "inline-flex items-center gap-1 hover:text-foreground transition-colors",
964
- children: [
965
- /* @__PURE__ */ jsx(ArrowLeft, { className: "h-3.5 w-3.5" }),
966
- parent
967
- ]
968
- }
969
- ),
970
- current !== void 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
971
- /* @__PURE__ */ jsx("span", { children: "/" }),
972
- current ? /* @__PURE__ */ jsx("span", { className: "text-foreground font-medium truncate max-w-[200px]", children: current }) : /* @__PURE__ */ jsx("span", { className: "inline-block h-4 w-24 rounded bg-muted animate-pulse align-middle" })
973
- ] })
974
- ] });
975
- }
976
- var TooltipProvider = Tooltip2.Provider;
977
- var Tooltip = Tooltip2.Root;
978
- var TooltipTrigger = Tooltip2.Trigger;
979
- var TooltipContent = React10.forwardRef(({ className, sideOffset = 4, ...props }, ref) => /* @__PURE__ */ jsx(
980
- Tooltip2.Content,
981
- {
982
- ref,
983
- sideOffset,
984
- className: cn(
985
- "fayz-glass-surface z-50 overflow-hidden rounded-md border bg-popover px-3 py-1.5 text-sm text-popover-foreground shadow-md animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
986
- className
987
- ),
988
- ...props
989
- }
990
- ));
991
- TooltipContent.displayName = Tooltip2.Content.displayName;
992
- function formatTime12(h, m) {
993
- const period = h >= 12 ? "pm" : "am";
994
- const h12 = h % 12 || 12;
995
- return `${h12}:${String(m).padStart(2, "0")}${period}`;
996
- }
997
- function formatTime24(h, m) {
998
- return `${String(h).padStart(2, "0")}:${String(m).padStart(2, "0")}`;
999
- }
1000
- function TimePicker({ value, onChange, interval = 15, min = "00:00", max = "23:45", use12h = false, readOnly, className, defaultOpen, onOpenChange }) {
1001
- const [open, setOpen] = useState(defaultOpen ?? false);
1002
- const ref = useRef(null);
1003
- const triggerRef = useRef(null);
1004
- const listRef = useRef(null);
1005
- const [pos, setPos] = useState({ top: 0, left: 0, width: 0 });
1006
- useEffect(() => {
1007
- if (!open || !triggerRef.current) return;
1008
- const rect = triggerRef.current.getBoundingClientRect();
1009
- setPos({ top: rect.bottom + 4, left: rect.left, width: Math.max(rect.width, 120) });
1010
- }, [open]);
1011
- useEffect(() => {
1012
- if (!open) return;
1013
- const handler = (e) => {
1014
- const target = e.target;
1015
- if (ref.current?.contains(target)) return;
1016
- if (listRef.current?.contains(target)) return;
1017
- setOpen(false);
1018
- onOpenChange?.(false);
1019
- };
1020
- document.addEventListener("mousedown", handler);
1021
- return () => document.removeEventListener("mousedown", handler);
1022
- }, [open]);
1023
- useEffect(() => {
1024
- if (!open || !listRef.current || !value) return;
1025
- const el = listRef.current.querySelector('[data-selected="true"]');
1026
- if (el) el.scrollIntoView({ block: "center" });
1027
- }, [open, value]);
1028
- const slots = useMemo(() => {
1029
- const [minH, minM] = min.split(":").map(Number);
1030
- const [maxH, maxM] = max.split(":").map(Number);
1031
- const minTotal = minH * 60 + minM;
1032
- const maxTotal = maxH * 60 + maxM;
1033
- const result = [];
1034
- for (let t = minTotal; t <= maxTotal; t += interval) {
1035
- const h = Math.floor(t / 60);
1036
- const m = t % 60;
1037
- result.push({
1038
- value: formatTime24(h, m),
1039
- label: use12h ? formatTime12(h, m) : formatTime24(h, m)
1040
- });
1041
- }
1042
- return result;
1043
- }, [interval, min, max, use12h]);
1044
- const displayValue = useMemo(() => {
1045
- if (!value) return "";
1046
- const [h, m] = value.split(":").map(Number);
1047
- return use12h ? formatTime12(h, m) : formatTime24(h, m);
1048
- }, [value, use12h]);
1049
- function handleKeyDown(e) {
1050
- if (readOnly) return;
1051
- if (e.key === "ArrowDown" || e.key === "ArrowUp") {
1052
- e.preventDefault();
1053
- const idx = slots.findIndex((s) => s.value === value);
1054
- const next = e.key === "ArrowDown" ? Math.min(idx + 1, slots.length - 1) : Math.max(idx - 1, 0);
1055
- if (slots[next]) onChange(slots[next].value);
1056
- } else if (e.key === "Enter" || e.key === "Escape") {
1057
- setOpen(false);
1058
- }
1059
- }
1060
- return /* @__PURE__ */ jsxs("div", { ref, className: `relative ${className ?? ""}`, children: [
1061
- /* @__PURE__ */ jsxs(
1062
- "button",
1063
- {
1064
- ref: triggerRef,
1065
- type: "button",
1066
- onClick: () => !readOnly && setOpen((p) => !p),
1067
- onKeyDown: handleKeyDown,
1068
- disabled: readOnly,
1069
- className: `flex items-center gap-2 rounded-lg border px-3 py-2 text-sm w-full text-left transition-colors ${readOnly ? "bg-muted/40 text-muted-foreground cursor-default" : "bg-background hover:bg-muted/30"}`,
1070
- children: [
1071
- /* @__PURE__ */ jsx(Clock, { className: "h-3.5 w-3.5 text-muted-foreground shrink-0" }),
1072
- /* @__PURE__ */ jsx("span", { children: displayValue || "--:--" })
1073
- ]
1074
- }
1075
- ),
1076
- open && !readOnly && createPortal(
1077
- /* @__PURE__ */ jsx(
1078
- "div",
1079
- {
1080
- ref: listRef,
1081
- "data-modal-passthrough": true,
1082
- style: { position: "fixed", top: pos.top, left: pos.left, width: pos.width, zIndex: 9999, pointerEvents: "auto" },
1083
- className: "fayz-glass-surface min-w-[120px] max-h-48 overflow-y-auto rounded-xl border bg-popover shadow-xl py-1",
1084
- children: slots.map((slot) => {
1085
- const selected = slot.value === value;
1086
- return /* @__PURE__ */ jsx(
1087
- "button",
1088
- {
1089
- type: "button",
1090
- "data-selected": selected,
1091
- onClick: () => {
1092
- onChange(slot.value);
1093
- setOpen(false);
1094
- },
1095
- className: `block w-full px-3 py-1.5 text-sm text-left transition-colors ${selected ? "bg-primary/10 text-primary font-medium" : "hover:bg-muted/50"}`,
1096
- children: slot.label
1097
- },
1098
- slot.value
1099
- );
1100
- })
1101
- }
1102
- ),
1103
- document.body
1104
- )
1105
- ] });
1106
- }
1107
- function Skeleton({
1108
- className,
1109
- ...props
1110
- }) {
1111
- return /* @__PURE__ */ jsx(
1112
- "div",
1113
- {
1114
- className: cn("animate-pulse rounded-md bg-muted", className),
1115
- ...props
1116
- }
1117
- );
1118
- }
1119
- function TableSkeleton({ columns = 4, rows = 2 }) {
1120
- return /* @__PURE__ */ jsxs("div", { className: "rounded-lg border overflow-hidden", children: [
1121
- /* @__PURE__ */ jsx("div", { className: "border-b bg-muted/30 flex gap-4 px-4 py-2.5", children: Array.from({ length: columns }).map((_, i) => /* @__PURE__ */ jsx(Skeleton, { className: "h-3 flex-1 rounded bg-muted-foreground/10" }, i)) }),
1122
- /* @__PURE__ */ jsx("div", { className: "divide-y", children: Array.from({ length: rows }).map((_, r) => /* @__PURE__ */ jsx("div", { className: "flex items-center gap-4 px-4 py-3.5", children: Array.from({ length: columns }).map((_2, c) => /* @__PURE__ */ jsx(
1123
- Skeleton,
1124
- {
1125
- className: cn(
1126
- "h-3 rounded",
1127
- c === 0 ? "flex-[2]" : "flex-1"
1128
- )
1129
- },
1130
- c
1131
- )) }, r)) })
1132
- ] });
1133
- }
1134
- function ListView({
1135
- columns,
1136
- data,
1137
- loading = false,
1138
- searchPlaceholder = "Search...",
1139
- search: controlledSearch,
1140
- onSearchChange,
1141
- searchDebounce = 300,
1142
- tags,
1143
- allTagLabel = "All",
1144
- activeTag,
1145
- onTagChange,
1146
- newLabel,
1147
- onNew,
1148
- onRowClick,
1149
- emptyIcon: EmptyIcon,
1150
- emptyMessage = "No results found.",
1151
- emptyActionLabel,
1152
- onEmptyAction,
1153
- total,
1154
- page = 1,
1155
- pageSize = 25,
1156
- onPageChange,
1157
- toolbar,
1158
- className
1159
- }) {
1160
- const [internalSearch, setInternalSearch] = useState(controlledSearch ?? "");
1161
- const debounceRef = useRef();
1162
- useEffect(() => {
1163
- if (controlledSearch !== void 0) setInternalSearch(controlledSearch);
1164
- }, [controlledSearch]);
1165
- const handleSearchChange = useCallback((value) => {
1166
- setInternalSearch(value);
1167
- if (searchDebounce === 0) {
1168
- onSearchChange?.(value);
1169
- } else {
1170
- if (debounceRef.current) clearTimeout(debounceRef.current);
1171
- debounceRef.current = setTimeout(() => onSearchChange?.(value), searchDebounce);
1172
- }
1173
- }, [onSearchChange, searchDebounce]);
1174
- const totalItems = total ?? data.length;
1175
- const totalPages = Math.max(1, Math.ceil(totalItems / pageSize));
1176
- const showPagination = onPageChange && totalItems > pageSize;
1177
- const showSearch = !!onSearchChange;
1178
- const showTags = tags && tags.length > 0 && onTagChange;
1179
- const showToolbar = showSearch || onNew || showTags;
1180
- return /* @__PURE__ */ jsxs("div", { className: cn("space-y-4", className), children: [
1181
- showToolbar && /* @__PURE__ */ jsxs("div", { className: "flex flex-col sm:flex-row gap-3 sm:items-center sm:justify-between", children: [
1182
- showSearch && /* @__PURE__ */ jsxs("div", { className: "relative flex-1 max-w-sm", children: [
1183
- /* @__PURE__ */ jsx(Search, { className: "absolute left-2.5 top-1/2 -translate-y-1/2 h-3.5 w-3.5 text-muted-foreground" }),
1184
- /* @__PURE__ */ jsx(
1185
- "input",
1186
- {
1187
- type: "text",
1188
- placeholder: searchPlaceholder,
1189
- value: internalSearch,
1190
- onChange: (e) => handleSearchChange(e.target.value),
1191
- className: "w-full rounded-input border border-input bg-card shadow-[inset_0_1px_0_rgb(0_0_0_/0.06)] pl-8 pr-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring"
1192
- }
1193
- )
1194
- ] }),
1195
- onNew && newLabel && /* @__PURE__ */ jsxs(Button, { variant: "default", size: "default", onClick: onNew, className: "shrink-0", children: [
1196
- /* @__PURE__ */ jsx(Plus, { className: "h-3.5 w-3.5" }),
1197
- newLabel
1198
- ] })
1199
- ] }),
1200
- showTags && /* @__PURE__ */ jsxs("div", { className: "flex gap-1.5 flex-wrap", children: [
1201
- /* @__PURE__ */ jsx(
1202
- "button",
1203
- {
1204
- onClick: () => onTagChange(void 0),
1205
- className: cn(
1206
- "rounded-full px-2.5 py-1 text-xs font-medium transition-colors",
1207
- !activeTag ? "bg-primary text-primary-foreground" : "bg-muted/50 text-muted-foreground hover:bg-muted"
1208
- ),
1209
- children: allTagLabel
1210
- }
1211
- ),
1212
- tags.map((tag) => /* @__PURE__ */ jsx(
1213
- "button",
1214
- {
1215
- onClick: () => onTagChange(tag.value),
1216
- className: cn(
1217
- "rounded-full px-2.5 py-1 text-xs font-medium transition-colors",
1218
- activeTag === tag.value ? "bg-primary text-primary-foreground" : "bg-muted/50 text-muted-foreground hover:bg-muted"
1219
- ),
1220
- children: tag.label
1221
- },
1222
- tag.value
1223
- ))
1224
- ] }),
1225
- toolbar,
1226
- loading ? /* @__PURE__ */ jsx(DataTable, { columns, data: [], loading: true, variant: "card" }) : data.length === 0 ? /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center justify-center py-12 text-center rounded-lg border-2 border-dashed border-muted", children: [
1227
- EmptyIcon && /* @__PURE__ */ jsx(EmptyIcon, { className: "h-8 w-8 text-muted-foreground/30 mb-2" }),
1228
- /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: emptyMessage }),
1229
- emptyActionLabel && onEmptyAction && /* @__PURE__ */ jsx("button", { onClick: onEmptyAction, className: "text-xs text-primary hover:underline mt-1", children: emptyActionLabel })
1230
- ] }) : /* @__PURE__ */ jsx(
1231
- DataTable,
1232
- {
1233
- columns,
1234
- data,
1235
- onRowClick,
1236
- variant: "card"
1237
- }
1238
- ),
1239
- showPagination && /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between px-1", children: [
1240
- /* @__PURE__ */ jsxs("p", { className: "text-xs text-muted-foreground", children: [
1241
- (page - 1) * pageSize + 1,
1242
- "\u2013",
1243
- Math.min(page * pageSize, totalItems),
1244
- " of ",
1245
- totalItems
1246
- ] }),
1247
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
1248
- /* @__PURE__ */ jsx(
1249
- "button",
1250
- {
1251
- onClick: () => onPageChange(page - 1),
1252
- disabled: page <= 1,
1253
- className: "flex h-7 w-7 items-center justify-center rounded-md border hover:bg-muted bg-card shadow-button active:shadow-button-inset transition-colors disabled:opacity-30 disabled:cursor-not-allowed",
1254
- children: /* @__PURE__ */ jsx(ChevronLeft, { className: "h-3.5 w-3.5" })
1255
- }
1256
- ),
1257
- /* @__PURE__ */ jsxs("span", { className: "text-xs text-muted-foreground px-2", children: [
1258
- page,
1259
- " / ",
1260
- totalPages
1261
- ] }),
1262
- /* @__PURE__ */ jsx(
1263
- "button",
1264
- {
1265
- onClick: () => onPageChange(page + 1),
1266
- disabled: page >= totalPages,
1267
- className: "flex h-7 w-7 items-center justify-center rounded-md border hover:bg-muted bg-card shadow-button active:shadow-button-inset transition-colors disabled:opacity-30 disabled:cursor-not-allowed",
1268
- children: /* @__PURE__ */ jsx(ChevronRight, { className: "h-3.5 w-3.5" })
1269
- }
1270
- )
1271
- ] })
1272
- ] })
1273
- ] });
1274
- }
1275
- var DAY_LABELS = {
1276
- en: ["S", "M", "T", "W", "T", "F", "S"],
1277
- "pt-BR": ["D", "S", "T", "Q", "Q", "S", "S"]
1278
- };
1279
- function toDateLocale(locale) {
1280
- if (locale === "pt-BR") return "pt-BR";
1281
- return "en-US";
1282
- }
1283
- function isSameDay(a, b) {
1284
- return a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate();
1285
- }
1286
- function isToday(d) {
1287
- return isSameDay(d, /* @__PURE__ */ new Date());
1288
- }
1289
- function formatDate(date, locale) {
1290
- return date.toLocaleDateString(toDateLocale(locale), { month: "short", day: "numeric", year: "numeric" });
1291
- }
1292
- function toDateStr(d) {
1293
- return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
1294
- }
1295
- function parseDate(s) {
1296
- const [y, m, d] = s.split("-").map(Number);
1297
- return new Date(y, m - 1, d);
1298
- }
1299
- function DatePicker({ value, onChange, className, defaultOpen, onOpenChange }) {
1300
- const t = useTranslation();
1301
- const locale = getCurrentLocale();
1302
- const [open, setOpen] = useState(defaultOpen ?? false);
1303
- const ref = useRef(null);
1304
- const triggerRef = useRef(null);
1305
- const dropdownRef = useRef(null);
1306
- const selectedDate = value ? parseDate(value) : /* @__PURE__ */ new Date();
1307
- const [viewMonth, setViewMonth] = useState(new Date(selectedDate.getFullYear(), selectedDate.getMonth(), 1));
1308
- const [pos, setPos] = useState({ top: 0, left: 0 });
1309
- const dayLabels = DAY_LABELS[locale] ?? DAY_LABELS.en;
1310
- const dateLocale = toDateLocale(locale);
1311
- useEffect(() => {
1312
- if (!open || !triggerRef.current) return;
1313
- const rect = triggerRef.current.getBoundingClientRect();
1314
- const dropW = 260;
1315
- let left = rect.left;
1316
- if (left + dropW > window.innerWidth - 12) left = window.innerWidth - dropW - 12;
1317
- if (left < 8) left = 8;
1318
- setPos({ top: rect.bottom + 4, left });
1319
- }, [open]);
1320
- useEffect(() => {
1321
- if (!open) return;
1322
- const handler = (e) => {
1323
- const target = e.target;
1324
- if (ref.current?.contains(target)) return;
1325
- if (dropdownRef.current?.contains(target)) return;
1326
- setOpen(false);
1327
- onOpenChange?.(false);
1328
- };
1329
- document.addEventListener("mousedown", handler);
1330
- return () => document.removeEventListener("mousedown", handler);
1331
- }, [open, onOpenChange]);
1332
- useEffect(() => {
1333
- if (value) {
1334
- const d = parseDate(value);
1335
- setViewMonth(new Date(d.getFullYear(), d.getMonth(), 1));
1336
- }
1337
- }, [value]);
1338
- const monthLabel = viewMonth.toLocaleDateString(dateLocale, { month: "long", year: "numeric" });
1339
- const weeks = useMemo(() => {
1340
- const year = viewMonth.getFullYear();
1341
- const month = viewMonth.getMonth();
1342
- const firstDay = new Date(year, month, 1);
1343
- const startOffset = firstDay.getDay();
1344
- const daysInMonth = new Date(year, month + 1, 0).getDate();
1345
- const prevMonthDays = new Date(year, month, 0).getDate();
1346
- const cells = [];
1347
- for (let i = startOffset - 1; i >= 0; i--) {
1348
- cells.push({ date: new Date(year, month - 1, prevMonthDays - i), inMonth: false });
1349
- }
1350
- for (let d = 1; d <= daysInMonth; d++) {
1351
- cells.push({ date: new Date(year, month, d), inMonth: true });
1352
- }
1353
- while (cells.length % 7 !== 0) {
1354
- cells.push({ date: new Date(year, month + 1, cells.length - startOffset - daysInMonth + 1), inMonth: false });
1355
- }
1356
- const rows = [];
1357
- for (let i = 0; i < cells.length; i += 7) rows.push(cells.slice(i, i + 7));
1358
- return rows;
1359
- }, [viewMonth]);
1360
- function handleSelect(date) {
1361
- onChange(toDateStr(date));
1362
- setOpen(false);
1363
- }
1364
- return /* @__PURE__ */ jsxs("div", { ref, className: `relative ${className ?? ""}`, children: [
1365
- /* @__PURE__ */ jsxs(
1366
- "button",
1367
- {
1368
- ref: triggerRef,
1369
- type: "button",
1370
- onClick: () => setOpen((p) => !p),
1371
- className: "flex items-center gap-2 rounded-input border border-input bg-card shadow-[inset_0_1px_0_rgb(0_0_0_/0.06)] px-3 py-2 text-sm hover:bg-muted/30 transition-colors w-full text-left",
1372
- children: [
1373
- /* @__PURE__ */ jsx(Calendar, { className: "h-3.5 w-3.5 text-muted-foreground shrink-0" }),
1374
- /* @__PURE__ */ jsx("span", { children: value ? formatDate(selectedDate, locale) : t("common.selectDate") })
1375
- ]
1376
- }
1377
- ),
1378
- open && createPortal(
1379
- /* @__PURE__ */ jsxs(
1380
- "div",
1381
- {
1382
- ref: dropdownRef,
1383
- "data-modal-passthrough": true,
1384
- style: { position: "fixed", top: pos.top, left: pos.left, zIndex: 9999, pointerEvents: "auto" },
1385
- className: "fayz-glass-surface rounded-xl border bg-popover shadow-xl p-3 w-[260px]",
1386
- children: [
1387
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between mb-2", children: [
1388
- /* @__PURE__ */ jsx("span", { className: "text-sm font-medium capitalize", children: monthLabel }),
1389
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-0.5", children: [
1390
- /* @__PURE__ */ jsx(
1391
- "button",
1392
- {
1393
- type: "button",
1394
- onClick: () => setViewMonth(new Date(viewMonth.getFullYear(), viewMonth.getMonth() - 1, 1)),
1395
- className: "flex h-7 w-7 items-center justify-center rounded-full hover:bg-muted transition-colors",
1396
- children: /* @__PURE__ */ jsx(ChevronLeft, { className: "h-4 w-4" })
1397
- }
1398
- ),
1399
- /* @__PURE__ */ jsx(
1400
- "button",
1401
- {
1402
- type: "button",
1403
- onClick: () => setViewMonth(new Date(viewMonth.getFullYear(), viewMonth.getMonth() + 1, 1)),
1404
- className: "flex h-7 w-7 items-center justify-center rounded-full hover:bg-muted transition-colors",
1405
- children: /* @__PURE__ */ jsx(ChevronRight, { className: "h-4 w-4" })
1406
- }
1407
- )
1408
- ] })
1409
- ] }),
1410
- /* @__PURE__ */ jsx("div", { className: "grid grid-cols-7 mb-1", children: dayLabels.map((l, i) => /* @__PURE__ */ jsx("div", { className: "flex h-8 items-center justify-center text-[11px] font-medium text-muted-foreground", children: l }, i)) }),
1411
- weeks.map((week, wi) => /* @__PURE__ */ jsx("div", { className: "grid grid-cols-7", children: week.map(({ date, inMonth }, di) => {
1412
- const sel = value && isSameDay(date, selectedDate);
1413
- const today = isToday(date);
1414
- return /* @__PURE__ */ jsx(
1415
- "button",
1416
- {
1417
- type: "button",
1418
- onClick: () => handleSelect(date),
1419
- className: `flex h-8 w-8 items-center justify-center rounded-full text-xs mx-auto transition-colors ${sel ? "bg-primary text-primary-foreground font-semibold" : today ? "bg-primary/10 text-primary font-semibold" : inMonth ? "hover:bg-muted text-foreground" : "text-muted-foreground/40 hover:bg-muted/50"}`,
1420
- children: date.getDate()
1421
- },
1422
- di
1423
- );
1424
- }) }, wi)),
1425
- /* @__PURE__ */ jsx("div", { className: "mt-2 pt-2 border-t", children: /* @__PURE__ */ jsx(
1426
- "button",
1427
- {
1428
- type: "button",
1429
- onClick: () => {
1430
- handleSelect(/* @__PURE__ */ new Date());
1431
- },
1432
- className: "text-xs text-primary font-medium hover:underline",
1433
- children: t("agenda.calendar.today")
1434
- }
1435
- ) })
1436
- ]
1437
- }
1438
- ),
1439
- document.body
1440
- )
1441
- ] });
1442
- }
1443
- function SearchSelect({
1444
- value,
1445
- displayValue,
1446
- onChange,
1447
- onSearch,
1448
- placeholder = "Search...",
1449
- label,
1450
- required,
1451
- debounce = 250,
1452
- minChars = 1,
1453
- allowCreate,
1454
- createLabel = "Add",
1455
- onCreate,
1456
- renderOption,
1457
- loadOnOpen,
1458
- className,
1459
- disabled
1460
- }) {
1461
- const [open, setOpen] = React10.useState(false);
1462
- const [query, setQuery] = React10.useState("");
1463
- const [results, setResults] = React10.useState([]);
1464
- const [loading, setLoading] = React10.useState(false);
1465
- const [creating, setCreating] = React10.useState(false);
1466
- const [selectedLabel, setSelectedLabel] = React10.useState(displayValue ?? "");
1467
- const containerRef = React10.useRef(null);
1468
- const inputRef = React10.useRef(null);
1469
- const timerRef = React10.useRef();
1470
- React10.useEffect(() => {
1471
- function handler(e) {
1472
- if (containerRef.current && !containerRef.current.contains(e.target)) {
1473
- setOpen(false);
1474
- }
1475
- }
1476
- document.addEventListener("mousedown", handler);
1477
- return () => document.removeEventListener("mousedown", handler);
1478
- }, []);
1479
- React10.useEffect(() => {
1480
- if (displayValue !== void 0) setSelectedLabel(displayValue);
1481
- }, [displayValue]);
1482
- React10.useEffect(() => {
1483
- if (open && loadOnOpen && query.length === 0 && results.length === 0 && !loading) {
1484
- setLoading(true);
1485
- Promise.resolve(onSearch("")).then(setResults).catch(() => setResults([])).finally(() => setLoading(false));
1486
- }
1487
- }, [open, loadOnOpen]);
1488
- function handleQueryChange(q) {
1489
- setQuery(q);
1490
- if (timerRef.current) clearTimeout(timerRef.current);
1491
- if (q.length < minChars && !(loadOnOpen && q.length === 0)) {
1492
- setResults([]);
1493
- setLoading(false);
1494
- return;
1495
- }
1496
- setLoading(true);
1497
- timerRef.current = setTimeout(async () => {
1498
- try {
1499
- const res = await onSearch(q);
1500
- setResults(res);
1501
- } catch {
1502
- setResults([]);
1503
- }
1504
- setLoading(false);
1505
- }, debounce);
1506
- }
1507
- function handleSelect(option) {
1508
- onChange(option.id, option);
1509
- setSelectedLabel(option.label);
1510
- setOpen(false);
1511
- setQuery("");
1512
- setResults([]);
1513
- }
1514
- function handleClear() {
1515
- onChange("", null);
1516
- setSelectedLabel("");
1517
- setQuery("");
1518
- setResults([]);
1519
- }
1520
- async function handleCreate(name) {
1521
- if (!onCreate) return;
1522
- setCreating(true);
1523
- setSelectedLabel(name);
1524
- setOpen(false);
1525
- setQuery("");
1526
- setResults([]);
1527
- try {
1528
- await onCreate(name);
1529
- } finally {
1530
- setCreating(false);
1531
- }
1532
- }
1533
- function handleOpen() {
1534
- if (disabled || creating) return;
1535
- setOpen(true);
1536
- setQuery("");
1537
- setTimeout(() => inputRef.current?.focus(), 0);
1538
- }
1539
- const groups = React10.useMemo(() => {
1540
- const map = /* @__PURE__ */ new Map();
1541
- for (const opt of results) {
1542
- const group = opt.group ?? "";
1543
- if (!map.has(group)) map.set(group, []);
1544
- map.get(group).push(opt);
1545
- }
1546
- return map;
1547
- }, [results]);
1548
- const hasValue = !!value && !!selectedLabel;
1549
- const showDropdown = open && query.length >= minChars;
1550
- return /* @__PURE__ */ jsxs("div", { ref: containerRef, className: cn("relative", className), children: [
1551
- label && /* @__PURE__ */ jsxs("label", { className: "text-xs font-medium text-muted-foreground", children: [
1552
- label,
1553
- required && " *"
1554
- ] }),
1555
- /* @__PURE__ */ jsxs(
1556
- "div",
1557
- {
1558
- className: cn(
1559
- "mt-1 flex items-center gap-2 rounded-lg border bg-background transition-colors",
1560
- open ? "ring-2 ring-primary/20" : "hover:bg-muted/30",
1561
- disabled && "opacity-50 cursor-not-allowed"
1562
- ),
1563
- children: [
1564
- /* @__PURE__ */ jsx(Search, { className: "h-3.5 w-3.5 text-muted-foreground ml-3 shrink-0" }),
1565
- open ? /* @__PURE__ */ jsx(
1566
- "input",
1567
- {
1568
- ref: inputRef,
1569
- type: "text",
1570
- value: query,
1571
- onChange: (e) => handleQueryChange(e.target.value),
1572
- placeholder,
1573
- className: "flex-1 bg-transparent py-2 pr-3 text-sm outline-none",
1574
- onKeyDown: (e) => {
1575
- if (e.key === "Escape") setOpen(false);
1576
- }
1577
- }
1578
- ) : /* @__PURE__ */ jsx(
1579
- "button",
1580
- {
1581
- type: "button",
1582
- onClick: handleOpen,
1583
- className: "flex-1 text-left py-2 pr-3 text-sm outline-none",
1584
- disabled: disabled || creating,
1585
- children: creating ? /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1.5 text-muted-foreground", children: [
1586
- /* @__PURE__ */ jsx(Loader2, { className: "h-3 w-3 animate-spin" }),
1587
- /* @__PURE__ */ jsx("span", { className: "font-medium text-foreground", children: selectedLabel })
1588
- ] }) : hasValue ? /* @__PURE__ */ jsx("span", { className: "font-medium", children: selectedLabel }) : /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: placeholder })
1589
- }
1590
- ),
1591
- hasValue && !open && /* @__PURE__ */ jsx("button", { onClick: handleClear, className: "pr-3 text-muted-foreground hover:text-foreground", children: /* @__PURE__ */ jsx(X, { className: "h-3 w-3" }) }),
1592
- !hasValue && !open && /* @__PURE__ */ jsx(ChevronDown, { className: "h-3 w-3 text-muted-foreground mr-3 shrink-0" })
1593
- ]
1594
- }
1595
- ),
1596
- showDropdown && /* @__PURE__ */ jsx("div", { className: "fayz-glass-surface absolute top-full left-0 right-0 z-50 mt-1 rounded-lg border bg-popover shadow-lg overflow-hidden", children: loading ? /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-center gap-2 px-4 py-6 text-xs text-muted-foreground", children: [
1597
- /* @__PURE__ */ jsx(Loader2, { className: "h-3.5 w-3.5 animate-spin" }),
1598
- " Searching..."
1599
- ] }) : results.length === 0 ? /* @__PURE__ */ jsxs("div", { className: "px-4 py-4 text-center", children: [
1600
- /* @__PURE__ */ jsxs("p", { className: "text-xs text-muted-foreground", children: [
1601
- 'No results for "',
1602
- query,
1603
- '"'
1604
- ] }),
1605
- allowCreate && onCreate && /* @__PURE__ */ jsxs(
1606
- "button",
1607
- {
1608
- onClick: () => handleCreate(query),
1609
- className: "mt-2 text-xs text-primary hover:underline",
1610
- children: [
1611
- "+ ",
1612
- createLabel,
1613
- ' "',
1614
- query,
1615
- '"'
1616
- ]
1617
- }
1618
- )
1619
- ] }) : /* @__PURE__ */ jsxs("div", { className: "max-h-64 overflow-auto py-1", children: [
1620
- [...groups.entries()].map(([group, options]) => /* @__PURE__ */ jsxs(React10.Fragment, { children: [
1621
- group && /* @__PURE__ */ jsx("div", { className: "sticky top-0 bg-muted/50 px-3 py-1 text-[10px] font-semibold uppercase tracking-wider text-muted-foreground", children: group }),
1622
- options.map((opt) => /* @__PURE__ */ jsx(
1623
- "button",
1624
- {
1625
- onClick: () => handleSelect(opt),
1626
- disabled: opt.disabled,
1627
- className: cn(
1628
- "w-full text-left px-3 py-2 hover:bg-muted/50 transition-colors flex items-center gap-2.5",
1629
- opt.disabled && "opacity-40 cursor-not-allowed",
1630
- opt.id === value && "bg-muted/30"
1631
- ),
1632
- children: renderOption ? renderOption(opt) : /* @__PURE__ */ jsxs(Fragment, { children: [
1633
- opt.icon,
1634
- /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
1635
- /* @__PURE__ */ jsx("p", { className: "text-sm truncate", children: opt.label }),
1636
- opt.subtitle && /* @__PURE__ */ jsx("p", { className: "text-[10px] text-muted-foreground truncate", children: opt.subtitle })
1637
- ] })
1638
- ] })
1639
- },
1640
- opt.id
1641
- ))
1642
- ] }, group)),
1643
- allowCreate && onCreate && /* @__PURE__ */ jsxs(
1644
- "button",
1645
- {
1646
- onClick: () => handleCreate(query),
1647
- className: "w-full text-left px-3 py-2 text-xs text-primary hover:bg-muted/50 transition-colors border-t",
1648
- children: [
1649
- "+ ",
1650
- createLabel,
1651
- ' "',
1652
- query,
1653
- '"'
1654
- ]
1655
- }
1656
- )
1657
- ] }) })
1658
- ] });
1659
- }
1660
- function CurrencyInput({
1661
- value,
1662
- onChange,
1663
- symbol = "R$",
1664
- locale = "pt-BR",
1665
- currencyCode = "BRL",
1666
- label,
1667
- placeholder,
1668
- className,
1669
- disabled
1670
- }) {
1671
- const inputRef = React10.useRef(null);
1672
- const [focused, setFocused] = React10.useState(false);
1673
- const format = React10.useCallback((v) => {
1674
- return new Intl.NumberFormat(locale, {
1675
- minimumFractionDigits: 2,
1676
- maximumFractionDigits: 2
1677
- }).format(v);
1678
- }, [locale]);
1679
- const displayValue = format(value);
1680
- function handleKeyDown(e) {
1681
- if (disabled) return;
1682
- if (["Tab", "Escape", "ArrowLeft", "ArrowRight", "Home", "End"].includes(e.key)) return;
1683
- e.preventDefault();
1684
- if (e.key === "Backspace" || e.key === "Delete") {
1685
- const cents = Math.floor(Math.round(value * 100) / 10);
1686
- onChange(cents / 100);
1687
- return;
1688
- }
1689
- if (!/^\d$/.test(e.key)) return;
1690
- const currentCents = Math.round(value * 100);
1691
- const newCents = currentCents * 10 + parseInt(e.key, 10);
1692
- if (newCents > 99999999999) return;
1693
- onChange(newCents / 100);
1694
- }
1695
- function handlePaste(e) {
1696
- e.preventDefault();
1697
- const text = e.clipboardData.getData("text");
1698
- const cleaned = text.replace(/[^\d.,]/g, "");
1699
- const normalized = cleaned.replace(",", ".");
1700
- const parsed = parseFloat(normalized);
1701
- if (!isNaN(parsed)) {
1702
- onChange(Math.round(parsed * 100) / 100);
1703
- }
1704
- }
1705
- return /* @__PURE__ */ jsxs("div", { className, children: [
1706
- label && /* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-muted-foreground", children: label }),
1707
- /* @__PURE__ */ jsxs(
1708
- "div",
1709
- {
1710
- className: cn(
1711
- "flex items-center rounded-lg border border-input bg-background ring-offset-background transition-colors cursor-text",
1712
- label && "mt-1",
1713
- focused && "border-ring ring-2 ring-ring ring-offset-2",
1714
- disabled && "opacity-50 cursor-not-allowed"
1715
- ),
1716
- onClick: () => inputRef.current?.focus(),
1717
- children: [
1718
- /* @__PURE__ */ jsx("span", { className: "pl-3 pr-1 text-sm font-medium text-muted-foreground select-none", children: symbol }),
1719
- /* @__PURE__ */ jsx(
1720
- "input",
1721
- {
1722
- ref: inputRef,
1723
- type: "text",
1724
- inputMode: "numeric",
1725
- value: displayValue,
1726
- onKeyDown: handleKeyDown,
1727
- onPaste: handlePaste,
1728
- onFocus: () => setFocused(true),
1729
- onBlur: () => setFocused(false),
1730
- placeholder: placeholder ?? format(0),
1731
- disabled,
1732
- readOnly: false,
1733
- className: "flex-1 bg-transparent px-2 py-2 text-sm outline-none text-right tabular-nums caret-primary selection:bg-primary/20",
1734
- onChange: () => {
1735
- }
1736
- }
1737
- )
1738
- ]
1739
- }
1740
- )
1741
- ] });
1742
- }
1743
- var VARIANT_CONFIG = {
1744
- default: {
1745
- icon: Info,
1746
- iconBg: "bg-info/10",
1747
- iconColor: "text-info",
1748
- buttonVariant: "default"
1749
- },
1750
- destructive: {
1751
- icon: XCircle,
1752
- iconBg: "bg-destructive/10",
1753
- iconColor: "text-destructive",
1754
- buttonVariant: "destructive"
1755
- },
1756
- warning: {
1757
- icon: AlertTriangle,
1758
- iconBg: "bg-warning/10",
1759
- iconColor: "text-warning",
1760
- buttonVariant: "destructive"
1761
- },
1762
- success: {
1763
- icon: CheckCircle2,
1764
- iconBg: "bg-success/10",
1765
- iconColor: "text-success",
1766
- buttonVariant: "default"
1767
- }
1768
- };
1769
- function ConfirmDialog({
1770
- open,
1771
- onConfirm,
1772
- onCancel,
1773
- title,
1774
- description,
1775
- confirmLabel = "Confirm",
1776
- cancelLabel = "Cancel",
1777
- variant = "default",
1778
- loading = false
1779
- }) {
1780
- const config = VARIANT_CONFIG[variant];
1781
- const Icon2 = config.icon;
1782
- React10.useEffect(() => {
1783
- if (!open) return;
1784
- function handleKey(e) {
1785
- if (e.key === "Escape") onCancel();
1786
- }
1787
- document.addEventListener("keydown", handleKey);
1788
- return () => document.removeEventListener("keydown", handleKey);
1789
- }, [open, onCancel]);
1790
- if (!open) return null;
1791
- return createPortal(
1792
- /* @__PURE__ */ jsxs("div", { className: "fixed inset-0 z-[60] flex items-center justify-center", children: [
1793
- /* @__PURE__ */ jsx(
1794
- "div",
1795
- {
1796
- className: "absolute inset-0 bg-black/50 backdrop-blur-[2px] animate-fade-in",
1797
- onClick: onCancel
1798
- }
1799
- ),
1800
- /* @__PURE__ */ jsx(
1801
- "div",
1802
- {
1803
- className: "relative z-10 w-full max-w-sm mx-4 rounded-modal border border-border bg-card shadow-lg animate-zoom-in",
1804
- role: "alertdialog",
1805
- "aria-modal": "true",
1806
- "aria-labelledby": "confirm-title",
1807
- "aria-describedby": description ? "confirm-desc" : void 0,
1808
- children: /* @__PURE__ */ jsxs("div", { className: "p-6", children: [
1809
- /* @__PURE__ */ jsxs("div", { className: "flex gap-4", children: [
1810
- /* @__PURE__ */ jsx("div", { className: cn("flex h-10 w-10 items-center justify-center rounded-full shrink-0", config.iconBg), children: /* @__PURE__ */ jsx(Icon2, { className: cn("h-5 w-5", config.iconColor) }) }),
1811
- /* @__PURE__ */ jsxs("div", { className: "min-w-0 pt-0.5", children: [
1812
- /* @__PURE__ */ jsx("h3", { id: "confirm-title", className: "text-sm font-semibold text-foreground", children: title }),
1813
- description && /* @__PURE__ */ jsx("p", { id: "confirm-desc", className: "text-xs text-muted-foreground mt-1 leading-relaxed", children: description })
1814
- ] })
1815
- ] }),
1816
- /* @__PURE__ */ jsxs("div", { className: "flex justify-end gap-2 mt-5", children: [
1817
- /* @__PURE__ */ jsx(Button, { variant: "outline", size: "sm", onClick: onCancel, disabled: loading, children: cancelLabel }),
1818
- /* @__PURE__ */ jsx(Button, { variant: config.buttonVariant, size: "sm", onClick: onConfirm, disabled: loading, children: loading ? "Processing..." : confirmLabel })
1819
- ] })
1820
- ] })
1821
- }
1822
- )
1823
- ] }),
1824
- document.body
1825
- );
1826
- }
1827
- var Popover = Popover2.Root;
1828
- var PopoverTrigger = Popover2.Trigger;
1829
- var PopoverContent = React10.forwardRef(({ className, align = "start", sideOffset = 4, ...props }, ref) => /* @__PURE__ */ jsx(Popover2.Portal, { children: /* @__PURE__ */ jsx(
1830
- Popover2.Content,
1831
- {
1832
- ref,
1833
- align,
1834
- sideOffset,
1835
- className: cn(
1836
- "fayz-glass-surface z-[110] w-72 rounded-xl border bg-popover p-0 text-popover-foreground shadow-lg outline-none",
1837
- "data-[state=open]:animate-zoom-in data-[state=closed]:animate-zoom-out",
1838
- className
1839
- ),
1840
- ...props
1841
- }
1842
- ) }));
1843
- PopoverContent.displayName = Popover2.Content.displayName;
1844
- var SIZE = {
1845
- sm: { container: "h-[26px]", segment: "px-2.5 text-xs" },
1846
- md: { container: "h-[30px]", segment: "px-3 text-sm" }
1847
- };
1848
- function SegmentedControl({
1849
- options,
1850
- value,
1851
- onChange,
1852
- size = "sm",
1853
- className,
1854
- ...rest
1855
- }) {
1856
- const items = options.map((o) => typeof o === "string" ? { value: o, label: o } : o);
1857
- return /* @__PURE__ */ jsx(
1858
- "div",
1859
- {
1860
- role: "tablist",
1861
- "aria-label": rest["aria-label"],
1862
- className: cn(
1863
- "inline-flex items-center rounded-button border border-border bg-card p-0.5 shadow-button",
1864
- SIZE[size].container,
1865
- className
1866
- ),
1867
- children: items.map((item) => {
1868
- const active = item.value === value;
1869
- return /* @__PURE__ */ jsx(
1870
- "button",
1871
- {
1872
- type: "button",
1873
- role: "tab",
1874
- "aria-selected": active,
1875
- onClick: () => onChange(item.value),
1876
- className: cn(
1877
- "flex h-full items-center rounded-button font-semibold transition-colors",
1878
- SIZE[size].segment,
1879
- active ? "bg-foreground text-background" : "text-muted-foreground hover:bg-muted/60"
1880
- ),
1881
- children: item.label
1882
- },
1883
- item.value
1884
- );
1885
- })
1886
- }
1887
- );
1888
- }
1889
- var useLayoutStore = create((set) => ({
1890
- sidebarOpen: true,
1891
- sidebarCollapsed: false,
1892
- mobileDrawerOpen: false,
1893
- commandPaletteOpen: false,
1894
- toggleSidebar: () => set((state) => ({ sidebarOpen: !state.sidebarOpen })),
1895
- setSidebarCollapsed: (collapsed) => set({ sidebarCollapsed: collapsed }),
1896
- toggleMobileDrawer: () => set((state) => ({ mobileDrawerOpen: !state.mobileDrawerOpen })),
1897
- setCommandPaletteOpen: (open) => set({ commandPaletteOpen: open })
1898
- }));
1899
- var ICON_MAP = {
1900
- Home,
1901
- Users,
1902
- Settings,
1903
- CreditCard,
1904
- Bell,
1905
- Calendar: Calendar,
1906
- Package,
1907
- BarChart3,
1908
- FileText,
1909
- Mail,
1910
- DollarSign,
1911
- Megaphone,
1912
- ShoppingCart,
1913
- Target,
1914
- Wrench,
1915
- ClipboardList,
1916
- Briefcase,
1917
- UserCog,
1918
- BookOpen,
1919
- MessageCircle,
1920
- Globe,
1921
- Percent,
1922
- Tag,
1923
- Camera,
1924
- UtensilsCrossed,
1925
- MapPin,
1926
- Handshake,
1927
- Contact,
1928
- Building2,
1929
- Filter,
1930
- Plus: Plus,
1931
- List: List,
1932
- Search: Search,
1933
- Dog,
1934
- Cat,
1935
- PawPrint,
1936
- Heart,
1937
- LayoutTemplate,
1938
- LeafyGreen,
1939
- Apple,
1940
- Egg,
1941
- Wheat,
1942
- PartyPopper,
1943
- Radio,
1944
- CalendarDays,
1945
- Banknote,
1946
- Layers,
1947
- Music,
1948
- Eye,
1949
- ListMusic,
1950
- Disc3,
1951
- UsersRound,
1952
- Mic,
1953
- Zap,
1954
- Award,
1955
- LayoutDashboard,
1956
- MessageSquare,
1957
- Star,
1958
- Workflow
1959
- };
1960
- function getIcon(name) {
1961
- return ICON_MAP[name] ?? Home;
1962
- }
1963
- function NavItem({
1964
- item,
1965
- collapsed,
1966
- isActive,
1967
- onNavigate
1968
- }) {
1969
- const Icon2 = getIcon(item.icon);
1970
- const content = /* @__PURE__ */ jsxs(
1971
- "button",
1972
- {
1973
- onClick: () => onNavigate(item.route),
1974
- className: cn(
1975
- "flex w-full items-center gap-3 rounded-md px-2.5 py-1.5 text-[13px] font-medium transition-colors",
1976
- "hover:bg-sidebar-accent/60 hover:text-sidebar-accent-foreground",
1977
- isActive ? "bg-sidebar-accent font-semibold text-sidebar-accent-foreground shadow-sm" : "text-sidebar-foreground/80",
1978
- collapsed && "justify-center px-2"
1979
- ),
1980
- children: [
1981
- /* @__PURE__ */ jsx(Icon2, { className: "h-4 w-4 shrink-0" }),
1982
- !collapsed && /* @__PURE__ */ jsxs(Fragment, { children: [
1983
- /* @__PURE__ */ jsx("span", { className: "flex-1 truncate text-left", children: item.label }),
1984
- item.badge !== void 0 && /* @__PURE__ */ jsx("span", { className: "ml-auto rounded-full bg-primary px-2 py-0.5 text-xs font-semibold text-primary-foreground", children: item.badge })
1985
- ] })
1986
- ]
1987
- }
1988
- );
1989
- if (collapsed) {
1990
- return /* @__PURE__ */ jsxs(Tooltip2.Root, { delayDuration: 0, children: [
1991
- /* @__PURE__ */ jsx(Tooltip2.Trigger, { asChild: true, children: content }),
1992
- /* @__PURE__ */ jsx(Tooltip2.Portal, { children: /* @__PURE__ */ jsxs(
1993
- Tooltip2.Content,
1994
- {
1995
- side: "right",
1996
- sideOffset: 8,
1997
- className: "fayz-glass-surface z-50 rounded-md bg-popover px-3 py-1.5 text-sm text-popover-foreground shadow-md",
1998
- children: [
1999
- item.label,
2000
- item.badge !== void 0 && /* @__PURE__ */ jsx("span", { className: "ml-2 rounded-full bg-primary px-1.5 py-0.5 text-xs text-primary-foreground", children: item.badge }),
2001
- /* @__PURE__ */ jsx(Tooltip2.Arrow, { className: "fill-popover" })
2002
- ]
2003
- }
2004
- ) })
2005
- ] });
2006
- }
2007
- return content;
2008
- }
2009
- function NavGroup({
2010
- item,
2011
- collapsed,
2012
- currentPath,
2013
- onNavigate
2014
- }) {
2015
- const [open, setOpen] = React10.useState(
2016
- () => item.children?.some((c) => currentPath === c.route || currentPath.startsWith(c.route + "/")) ?? false
2017
- );
2018
- const Icon2 = getIcon(item.icon);
2019
- const isChildActive = item.children?.some(
2020
- (c) => currentPath === c.route || currentPath.startsWith(c.route + "/")
2021
- );
2022
- if (collapsed) {
2023
- return /* @__PURE__ */ jsxs(Tooltip2.Root, { delayDuration: 0, children: [
2024
- /* @__PURE__ */ jsx(Tooltip2.Trigger, { asChild: true, children: /* @__PURE__ */ jsx(
2025
- "button",
2026
- {
2027
- onClick: () => onNavigate(item.route),
2028
- className: cn(
2029
- "flex w-full items-center justify-center rounded-md px-2 py-2 text-sm font-medium transition-colors",
2030
- "hover:bg-sidebar-accent hover:text-sidebar-accent-foreground",
2031
- isChildActive ? "bg-sidebar-accent text-sidebar-accent-foreground" : "text-sidebar-muted"
2032
- ),
2033
- children: /* @__PURE__ */ jsx(Icon2, { className: "h-4 w-4 shrink-0" })
2034
- }
2035
- ) }),
2036
- /* @__PURE__ */ jsx(Tooltip2.Portal, { children: /* @__PURE__ */ jsxs(Tooltip2.Content, { side: "right", sideOffset: 8, className: "fayz-glass-surface z-50 rounded-md bg-popover px-3 py-1.5 text-sm text-popover-foreground shadow-md", children: [
2037
- item.label,
2038
- /* @__PURE__ */ jsx(Tooltip2.Arrow, { className: "fill-popover" })
2039
- ] }) })
2040
- ] });
2041
- }
2042
- return /* @__PURE__ */ jsxs("div", { children: [
2043
- /* @__PURE__ */ jsxs(
2044
- "button",
2045
- {
2046
- onClick: () => setOpen(!open),
2047
- className: cn(
2048
- "flex w-full items-center gap-3 rounded-md px-3 py-2 text-sm font-medium transition-colors",
2049
- "hover:bg-sidebar-accent hover:text-sidebar-accent-foreground",
2050
- isChildActive ? "text-sidebar-accent-foreground" : "text-sidebar-muted"
2051
- ),
2052
- children: [
2053
- /* @__PURE__ */ jsx(Icon2, { className: "h-4 w-4 shrink-0" }),
2054
- /* @__PURE__ */ jsx("span", { className: "flex-1 truncate text-left", children: item.label }),
2055
- /* @__PURE__ */ jsx(ChevronDown, { className: cn("h-3.5 w-3.5 shrink-0 transition-transform", open && "rotate-180") })
2056
- ]
2057
- }
2058
- ),
2059
- open && /* @__PURE__ */ jsx("div", { className: "ml-4 mt-0.5 space-y-0.5 border-l border-sidebar-border/50 pl-3", children: item.children?.map((child) => {
2060
- const ChildIcon = getIcon(child.icon);
2061
- const isActive = currentPath === child.route || currentPath.startsWith(child.route + "/");
2062
- return /* @__PURE__ */ jsxs(
2063
- "button",
2064
- {
2065
- onClick: () => onNavigate(child.route),
2066
- className: cn(
2067
- "flex w-full items-center gap-2.5 rounded-md px-2 py-1.5 text-sm transition-colors",
2068
- isActive ? "font-medium text-sidebar-accent-foreground" : "text-sidebar-muted hover:text-sidebar-accent-foreground"
2069
- ),
2070
- children: [
2071
- /* @__PURE__ */ jsx(ChildIcon, { className: "h-3.5 w-3.5 shrink-0" }),
2072
- /* @__PURE__ */ jsx("span", { className: "truncate", children: child.label })
2073
- ]
2074
- },
2075
- child.id
2076
- );
2077
- }) })
2078
- ] });
2079
- }
2080
- function Sidebar({
2081
- navigation,
2082
- logo,
2083
- collapsed,
2084
- onToggle,
2085
- onNavigate,
2086
- currentPath: currentPathProp = "/",
2087
- user: _user,
2088
- orgSwitcher,
2089
- topContent,
2090
- footerContent,
2091
- borderless,
2092
- userMenuSlot,
2093
- notificationSlot,
2094
- unreadCount = 0
2095
- }) {
2096
- const currentPath = currentPathProp;
2097
- const setCommandPaletteOpen = useLayoutStore((s) => s.setCommandPaletteOpen);
2098
- const mainItems = navigation.filter((item) => item.section === "main");
2099
- const secondaryItems = navigation.filter((item) => item.section === "secondary");
2100
- const settingsItems = navigation.filter((item) => item.section === "settings");
2101
- const handleNavigate = (route) => {
2102
- onNavigate?.(route);
2103
- };
2104
- return /* @__PURE__ */ jsx(Tooltip2.Provider, { children: /* @__PURE__ */ jsxs(
2105
- "aside",
2106
- {
2107
- "data-print": "hide",
2108
- className: cn(
2109
- "flex h-full flex-col bg-sidebar text-sidebar-foreground transition-all duration-200",
2110
- !borderless && "border-r border-sidebar-border",
2111
- collapsed ? "w-16" : "w-64"
2112
- ),
2113
- children: [
2114
- /* @__PURE__ */ jsxs(
2115
- "div",
2116
- {
2117
- className: cn(
2118
- "flex h-14 items-center justify-between px-4",
2119
- !borderless && "border-b border-sidebar-border",
2120
- collapsed && "justify-center px-2"
2121
- ),
2122
- children: [
2123
- !collapsed && /* @__PURE__ */ jsxs(Fragment, { children: [
2124
- logo ?? /* @__PURE__ */ jsx("span", { className: "text-lg font-bold", children: "App" }),
2125
- /* @__PURE__ */ jsx(
2126
- "button",
2127
- {
2128
- onClick: onToggle,
2129
- className: "ml-auto rounded-md p-1.5 text-sidebar-muted transition-colors hover:bg-sidebar-accent hover:text-sidebar-accent-foreground",
2130
- "aria-label": "Collapse sidebar",
2131
- children: /* @__PURE__ */ jsx(PanelLeftClose, { className: "h-4 w-4" })
2132
- }
2133
- )
2134
- ] }),
2135
- collapsed && /* @__PURE__ */ jsx(
2136
- "button",
2137
- {
2138
- onClick: onToggle,
2139
- className: "rounded-md p-1.5 text-sidebar-muted transition-colors hover:bg-sidebar-accent hover:text-sidebar-accent-foreground",
2140
- "aria-label": "Expand sidebar",
2141
- children: /* @__PURE__ */ jsx(PanelLeft, { className: "h-4 w-4" })
2142
- }
2143
- )
2144
- ]
2145
- }
2146
- ),
2147
- /* @__PURE__ */ jsxs("nav", { className: "flex-1 space-y-1 overflow-y-auto p-2", children: [
2148
- topContent && /* @__PURE__ */ jsx("div", { className: "mb-4 space-y-2", children: topContent }),
2149
- mainItems.length > 0 && /* @__PURE__ */ jsx("div", { className: "space-y-1", children: mainItems.map(
2150
- (item) => item.children && item.children.length > 0 ? /* @__PURE__ */ jsx(NavGroup, { item, collapsed, currentPath, onNavigate: handleNavigate }, item.id) : /* @__PURE__ */ jsx(
2151
- NavItem,
2152
- {
2153
- item,
2154
- collapsed,
2155
- isActive: currentPath === item.route || currentPath.startsWith(item.route + "/"),
2156
- onNavigate: handleNavigate
2157
- },
2158
- item.id
2159
- )
2160
- ) }),
2161
- secondaryItems.length > 0 && /* @__PURE__ */ jsx("div", { className: "mt-6 space-y-1", children: secondaryItems.map(
2162
- (item) => item.children && item.children.length > 0 ? /* @__PURE__ */ jsx(NavGroup, { item, collapsed, currentPath, onNavigate: handleNavigate }, item.id) : /* @__PURE__ */ jsx(
2163
- NavItem,
2164
- {
2165
- item,
2166
- collapsed,
2167
- isActive: currentPath === item.route || currentPath.startsWith(item.route + "/"),
2168
- onNavigate: handleNavigate
2169
- },
2170
- item.id
2171
- )
2172
- ) })
2173
- ] }),
2174
- settingsItems.length > 0 && /* @__PURE__ */ jsx("div", { className: cn("p-2 space-y-1", !borderless && "border-t border-sidebar-border"), children: settingsItems.map((item) => /* @__PURE__ */ jsx(
2175
- NavItem,
2176
- {
2177
- item,
2178
- collapsed,
2179
- isActive: currentPath === item.route || currentPath.startsWith(item.route + "/"),
2180
- onNavigate: handleNavigate
2181
- },
2182
- item.id
2183
- )) }),
2184
- footerContent && /* @__PURE__ */ jsx("div", { className: cn("p-2", !borderless && "border-t border-sidebar-border"), children: /* @__PURE__ */ jsx("div", { className: "space-y-2", children: footerContent }) }),
2185
- orgSwitcher && React10.isValidElement(orgSwitcher) ? React10.cloneElement(orgSwitcher, { collapsed }) : orgSwitcher,
2186
- /* @__PURE__ */ jsx("div", { className: cn("p-2", !borderless && "border-t border-sidebar-border"), children: /* @__PURE__ */ jsxs("div", { className: cn(
2187
- "flex items-center rounded-md",
2188
- collapsed ? "flex-col gap-1" : "gap-1 px-1"
2189
- ), children: [
2190
- userMenuSlot,
2191
- !collapsed && /* @__PURE__ */ jsx("div", { className: "flex-1" }),
2192
- /* @__PURE__ */ jsx(
2193
- "button",
2194
- {
2195
- onClick: () => setCommandPaletteOpen(true),
2196
- className: "inline-flex shrink-0 items-center justify-center rounded-md p-2 text-sidebar-muted transition-colors hover:bg-sidebar-accent/50 hover:text-sidebar-accent-foreground",
2197
- "aria-label": "Search",
2198
- children: /* @__PURE__ */ jsx(Search, { className: "h-4 w-4" })
2199
- }
2200
- ),
2201
- notificationSlot ?? /* @__PURE__ */ jsxs(
2202
- "button",
2203
- {
2204
- className: "relative inline-flex shrink-0 items-center justify-center rounded-md p-2 text-sidebar-muted transition-colors hover:bg-sidebar-accent/50 hover:text-sidebar-accent-foreground",
2205
- "aria-label": unreadCount > 0 ? `${unreadCount} unread notifications` : "Notifications",
2206
- children: [
2207
- /* @__PURE__ */ jsx(Bell, { className: "h-4 w-4" }),
2208
- unreadCount > 0 && /* @__PURE__ */ jsx("span", { className: "absolute right-1 top-1 h-2 w-2 rounded-full bg-destructive" })
2209
- ]
2210
- }
2211
- )
2212
- ] }) })
2213
- ]
2214
- }
2215
- ) });
2216
- }
2217
- function TopbarUserMenu({
2218
- user,
2219
- onSignOut,
2220
- onProfile,
2221
- onSettings,
2222
- onBilling,
2223
- userMenuExtras
2224
- }) {
2225
- const [open, setOpen] = React10.useState(false);
2226
- if (!user) return null;
2227
- const initial = (user.fullName?.trim()?.[0] ?? user.email?.[0] ?? "?").toUpperCase();
2228
- const item = (label, onClick, Icon2) => onClick ? /* @__PURE__ */ jsxs(
2229
- "button",
2230
- {
2231
- onClick: () => {
2232
- setOpen(false);
2233
- onClick();
2234
- },
2235
- className: "flex w-full items-center gap-2.5 rounded-lg px-3 py-2 text-sm text-popover-foreground transition-colors hover:bg-muted/70",
2236
- children: [
2237
- Icon2 && /* @__PURE__ */ jsx(Icon2, { className: "h-4 w-4 text-muted-foreground" }),
2238
- /* @__PURE__ */ jsx("span", { children: label })
2239
- ]
2240
- },
2241
- label
2242
- ) : null;
2243
- return /* @__PURE__ */ jsxs(Popover2.Root, { open, onOpenChange: setOpen, children: [
2244
- /* @__PURE__ */ jsx(Popover2.Trigger, { asChild: true, children: /* @__PURE__ */ jsx(
2245
- "button",
2246
- {
2247
- "aria-label": "User menu",
2248
- className: "ml-1 flex h-9 w-9 items-center justify-center overflow-hidden rounded-full bg-primary text-xs font-semibold text-primary-foreground ring-2 ring-transparent transition hover:ring-sidebar-border",
2249
- children: user.avatarUrl ? (
2250
- // eslint-disable-next-line @next/next/no-img-element
2251
- /* @__PURE__ */ jsx("img", { src: user.avatarUrl, alt: "", className: "h-full w-full object-cover" })
2252
- ) : initial
2253
- }
2254
- ) }),
2255
- /* @__PURE__ */ jsx(Popover2.Portal, { children: /* @__PURE__ */ jsxs(
2256
- Popover2.Content,
2257
- {
2258
- align: "end",
2259
- sideOffset: 8,
2260
- className: "fayz-glass-surface z-50 min-w-[220px] rounded-xl border border-border bg-popover p-1.5 shadow-lg animate-in fade-in-0 zoom-in-95",
2261
- children: [
2262
- /* @__PURE__ */ jsxs("div", { className: "border-b border-border px-3 py-2", children: [
2263
- /* @__PURE__ */ jsx("p", { className: "truncate text-sm font-medium text-foreground", children: user.fullName || user.email }),
2264
- /* @__PURE__ */ jsx("p", { className: "truncate text-xs text-muted-foreground", children: user.email })
2265
- ] }),
2266
- /* @__PURE__ */ jsxs("div", { className: "mt-1 space-y-0.5", children: [
2267
- item("Perfil", onProfile, UserCog),
2268
- item("Configura\xE7\xF5es", onSettings, Settings),
2269
- item("Cobran\xE7a", onBilling, CreditCard),
2270
- userMenuExtras?.map((e) => item(e.label, e.onClick)),
2271
- onSignOut && /* @__PURE__ */ jsxs(Fragment, { children: [
2272
- /* @__PURE__ */ jsx("div", { className: "my-1 border-t border-border" }),
2273
- item("Sair", onSignOut)
2274
- ] })
2275
- ] })
2276
- ]
2277
- }
2278
- ) })
2279
- ] });
2280
- }
2281
- var ICON_MAP2 = {
2282
- Home: Home,
2283
- Users: Users,
2284
- Settings: Settings,
2285
- CreditCard: CreditCard,
2286
- Bell: Bell,
2287
- Calendar: Calendar,
2288
- Package: Package,
2289
- BarChart3: BarChart3,
2290
- FileText: FileText,
2291
- Mail: Mail,
2292
- DollarSign: DollarSign,
2293
- Megaphone: Megaphone,
2294
- ShoppingCart: ShoppingCart,
2295
- Target: Target,
2296
- Wrench: Wrench,
2297
- ClipboardList: ClipboardList,
2298
- Briefcase: Briefcase,
2299
- UserCog: UserCog,
2300
- BookOpen: BookOpen,
2301
- MessageCircle: MessageCircle,
2302
- Globe: Globe,
2303
- Percent: Percent,
2304
- Tag: Tag,
2305
- Camera: Camera,
2306
- UtensilsCrossed: UtensilsCrossed,
2307
- Search: Search,
2308
- MapPin: MapPin,
2309
- Handshake: Handshake,
2310
- Contact: Contact,
2311
- Building2: Building2,
2312
- ChevronDown: ChevronDown,
2313
- Filter: Filter,
2314
- Plus: Plus,
2315
- List: List,
2316
- Dog: Dog,
2317
- Cat: Cat,
2318
- PawPrint: PawPrint,
2319
- Heart: Heart,
2320
- LayoutTemplate: LayoutTemplate,
2321
- LeafyGreen: LeafyGreen,
2322
- Apple: Apple,
2323
- Egg: Egg,
2324
- Wheat: Wheat,
2325
- ArrowDownCircle,
2326
- ArrowUpCircle,
2327
- Landmark,
2328
- Receipt,
2329
- TrendingUp,
2330
- CircleDollarSign,
2331
- Clock: Clock,
2332
- AlertTriangle: AlertTriangle,
2333
- Sparkles,
2334
- Wallet,
2335
- Warehouse,
2336
- Ruler,
2337
- ArrowUpRight,
2338
- ArrowDownRight,
2339
- UserPlus,
2340
- TreePalm,
2341
- PartyPopper: PartyPopper,
2342
- Radio: Radio,
2343
- CalendarDays: CalendarDays,
2344
- Banknote: Banknote,
2345
- Layers: Layers,
2346
- Music: Music,
2347
- Eye: Eye,
2348
- ListMusic: ListMusic,
2349
- Disc3: Disc3,
2350
- UsersRound: UsersRound,
2351
- Mic: Mic
2352
- };
2353
- function getIcon2(name) {
2354
- return ICON_MAP2[name] ?? Home;
2355
- }
2356
- function NavDropdown({ item, currentPath, onNavigate }) {
2357
- const [open, setOpen] = React10.useState(false);
2358
- const Icon2 = getIcon2(item.icon);
2359
- const isChildActive = item.children?.some(
2360
- (child) => currentPath === child.route || currentPath.startsWith(child.route + "/")
2361
- );
2362
- return /* @__PURE__ */ jsxs(Popover2.Root, { open, onOpenChange: setOpen, children: [
2363
- /* @__PURE__ */ jsx(Popover2.Trigger, { asChild: true, children: /* @__PURE__ */ jsxs(
2364
- "button",
2365
- {
2366
- "data-nav-active": isChildActive || void 0,
2367
- className: cn(
2368
- "flex items-center gap-1.5 whitespace-nowrap border-b-2 px-3 py-2 text-sm transition-colors",
2369
- isChildActive ? "border-sidebar-accent-foreground font-semibold text-sidebar-foreground" : "border-transparent text-sidebar-muted hover:text-sidebar-foreground"
2370
- ),
2371
- children: [
2372
- /* @__PURE__ */ jsx(Icon2, { className: "h-4 w-4" }),
2373
- /* @__PURE__ */ jsx("span", { children: item.label }),
2374
- /* @__PURE__ */ jsx(ChevronDown, { className: cn("h-3 w-3 transition-transform", open && "rotate-180") })
2375
- ]
2376
- }
2377
- ) }),
2378
- /* @__PURE__ */ jsx(Popover2.Portal, { children: /* @__PURE__ */ jsx(
2379
- Popover2.Content,
2380
- {
2381
- align: "start",
2382
- sideOffset: 4,
2383
- className: "fayz-glass-surface z-50 min-w-[200px] rounded-xl border border-border bg-popover p-1.5 shadow-lg animate-in fade-in-0 zoom-in-95",
2384
- children: item.children?.map((child) => {
2385
- const ChildIcon = getIcon2(child.icon);
2386
- const isActive = currentPath === child.route || currentPath.startsWith(child.route + "/");
2387
- return /* @__PURE__ */ jsxs(
2388
- "button",
2389
- {
2390
- onClick: () => {
2391
- onNavigate(child.route);
2392
- setOpen(false);
2393
- },
2394
- className: cn(
2395
- "flex w-full items-center gap-2.5 rounded-lg px-3 py-2 text-sm transition-colors",
2396
- isActive ? "bg-muted font-medium text-foreground" : "text-popover-foreground hover:bg-muted/70"
2397
- ),
2398
- children: [
2399
- /* @__PURE__ */ jsx(ChildIcon, { className: "h-4 w-4 text-muted-foreground" }),
2400
- /* @__PURE__ */ jsx("span", { children: child.label })
2401
- ]
2402
- },
2403
- child.id
2404
- );
2405
- })
2406
- }
2407
- ) })
2408
- ] });
2409
- }
2410
- function Topbar({
2411
- navigation,
2412
- logo,
2413
- user,
2414
- onMenuClick,
2415
- leftContent,
2416
- rightContent,
2417
- currentPath = "/",
2418
- onNavigate,
2419
- searchPlaceholder = "Search...",
2420
- onSignOut,
2421
- onProfile,
2422
- onSettings,
2423
- onBilling,
2424
- userMenuExtras
2425
- }) {
2426
- const setCommandPaletteOpen = useLayoutStore((s) => s.setCommandPaletteOpen);
2427
- const mainNav = navigation.filter((item) => item.section === "main");
2428
- const secondaryNav = navigation.filter((item) => item.section === "secondary");
2429
- const allNav = [...mainNav, ...secondaryNav];
2430
- const handleNavigate = (route) => {
2431
- onNavigate?.(route);
2432
- };
2433
- return /* @__PURE__ */ jsxs("header", { "data-print": "hide", className: "sticky top-0 z-50 w-full shrink-0", children: [
2434
- /* @__PURE__ */ jsx("div", { className: "border-b border-sidebar-border bg-sidebar text-sidebar-foreground", children: /* @__PURE__ */ jsxs("div", { className: "flex h-14 w-full items-center justify-between px-4 md:px-6", children: [
2435
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
2436
- /* @__PURE__ */ jsx(
2437
- "button",
2438
- {
2439
- onClick: onMenuClick,
2440
- className: "inline-flex items-center justify-center rounded-md p-2 text-sidebar-foreground/70 hover:bg-sidebar/30 hover:text-sidebar-foreground md:hidden",
2441
- "aria-label": "Open menu",
2442
- children: /* @__PURE__ */ jsx(Menu, { className: "h-5 w-5" })
2443
- }
2444
- ),
2445
- /* @__PURE__ */ jsx("div", { className: "hidden shrink-0 items-center md:flex", children: logo ?? /* @__PURE__ */ jsx("span", { className: "text-lg font-bold", children: "App" }) })
2446
- ] }),
2447
- /* @__PURE__ */ jsx("div", { className: "mx-8 hidden max-w-sm flex-1 md:flex", children: /* @__PURE__ */ jsxs(
2448
- "button",
2449
- {
2450
- onClick: () => setCommandPaletteOpen(true),
2451
- className: "relative flex h-8 w-full items-center rounded-md border border-sidebar-border bg-sidebar-accent px-3 pl-8 text-sm text-sidebar-muted shadow-sm transition-colors hover:bg-card",
2452
- children: [
2453
- /* @__PURE__ */ jsx(Search, { className: "absolute left-2.5 top-1/2 h-3.5 w-3.5 -translate-y-1/2 text-sidebar-muted" }),
2454
- /* @__PURE__ */ jsx("span", { className: "text-xs", children: searchPlaceholder }),
2455
- /* @__PURE__ */ jsx("kbd", { className: "ml-auto hidden rounded border border-sidebar-border bg-sidebar-accent px-1.5 py-0.5 text-[10px] font-medium text-sidebar-muted sm:inline-block", children: "\u2318K" })
2456
- ]
2457
- }
2458
- ) }),
2459
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
2460
- leftContent,
2461
- rightContent,
2462
- /* @__PURE__ */ jsx(
2463
- TopbarUserMenu,
2464
- {
2465
- user,
2466
- onSignOut,
2467
- onProfile,
2468
- onSettings,
2469
- onBilling,
2470
- userMenuExtras
2471
- }
2472
- )
2473
- ] })
2474
- ] }) }),
2475
- /* @__PURE__ */ jsx("div", { className: "hidden md:block bg-sidebar", children: /* @__PURE__ */ jsx("div", { className: "flex h-11 w-full items-center px-4 md:px-6", children: /* @__PURE__ */ jsx("nav", { ref: (el) => {
2476
- if (!el) return;
2477
- const active = el.querySelector('[data-nav-active="true"]');
2478
- if (active) {
2479
- const left = active.offsetLeft - el.offsetWidth / 2 + active.offsetWidth / 2;
2480
- el.scrollTo({ left: Math.max(0, left), behavior: "smooth" });
2481
- }
2482
- }, className: "flex items-center gap-0.5 overflow-x-auto scrollbar-hide", children: allNav.map((item) => {
2483
- if (item.children && item.children.length > 0) {
2484
- return /* @__PURE__ */ jsx(
2485
- NavDropdown,
2486
- {
2487
- item,
2488
- currentPath,
2489
- onNavigate: handleNavigate
2490
- },
2491
- item.id
2492
- );
2493
- }
2494
- const Icon2 = getIcon2(item.icon);
2495
- const isActive = item.route === "/" ? currentPath === "/" : currentPath === item.route || currentPath.startsWith(item.route + "/");
2496
- return /* @__PURE__ */ jsxs(
2497
- "button",
2498
- {
2499
- "data-nav-active": isActive || void 0,
2500
- onClick: () => handleNavigate(item.route),
2501
- className: cn(
2502
- "flex items-center gap-1.5 whitespace-nowrap border-b-2 px-3 py-2 text-sm transition-colors",
2503
- isActive ? "border-primary font-semibold text-sidebar-foreground" : "border-transparent text-sidebar-muted hover:text-sidebar-foreground"
2504
- ),
2505
- children: [
2506
- /* @__PURE__ */ jsx(Icon2, { className: "h-4 w-4" }),
2507
- /* @__PURE__ */ jsx("span", { children: item.label }),
2508
- item.badge !== void 0 && /* @__PURE__ */ jsx("span", { className: "rounded-full bg-primary px-1.5 py-0.5 text-[10px] font-semibold text-primary-foreground", children: item.badge })
2509
- ]
2510
- },
2511
- item.id
2512
- );
2513
- }) }) }) })
2514
- ] });
2515
- }
2516
- var ModuleHeaderSlotContext = React10.createContext(null);
2517
- function useModuleHeaderSlot() {
2518
- return React10.useContext(ModuleHeaderSlotContext);
2519
- }
2520
- var ModuleHeaderActionsSlotContext = React10.createContext(null);
2521
- function useModuleHeaderActionsSlot() {
2522
- return React10.useContext(ModuleHeaderActionsSlotContext);
2523
- }
2524
- function SidebarLayout({
2525
- navigation = [],
2526
- logo,
2527
- user,
2528
- children,
2529
- onNavigate,
2530
- onSignOut,
2531
- onProfile,
2532
- onSettings,
2533
- onBilling,
2534
- userMenuExtras: _userMenuExtras,
2535
- orgSwitcher,
2536
- sidebarTopContent,
2537
- sidebarFooterContent,
2538
- frame,
2539
- currentPath,
2540
- pageTitle,
2541
- topbarStart,
2542
- topbarEnd,
2543
- userMenuSlot,
2544
- notificationSlot,
2545
- unreadCount = 0
2546
- }) {
2547
- const sidebarCollapsed = useLayoutStore((s) => s.sidebarCollapsed);
2548
- const setSidebarCollapsed = useLayoutStore((s) => s.setSidebarCollapsed);
2549
- const [mobileMenuOpen, setMobileMenuOpen] = React10.useState(false);
2550
- const [headerSlot, setHeaderSlot] = React10.useState(null);
2551
- const [actionsSlot, setActionsSlot] = React10.useState(null);
2552
- return /* @__PURE__ */ jsx(SaveBarProvider, { children: /* @__PURE__ */ jsx(ModuleHeaderSlotContext.Provider, { value: headerSlot, children: /* @__PURE__ */ jsx(ModuleHeaderActionsSlotContext.Provider, { value: actionsSlot, children: /* @__PURE__ */ jsxs("div", { className: "flex h-screen overflow-hidden bg-background", children: [
2553
- /* @__PURE__ */ jsx("div", { className: "hidden md:flex", children: /* @__PURE__ */ jsx(
2554
- Sidebar,
2555
- {
2556
- navigation,
2557
- logo,
2558
- collapsed: sidebarCollapsed,
2559
- onToggle: () => setSidebarCollapsed(!sidebarCollapsed),
2560
- onNavigate,
2561
- currentPath,
2562
- user,
2563
- onSignOut,
2564
- onProfile,
2565
- onSettings,
2566
- onBilling,
2567
- orgSwitcher,
2568
- topContent: sidebarTopContent,
2569
- footerContent: sidebarFooterContent,
2570
- borderless: false,
2571
- userMenuSlot,
2572
- notificationSlot,
2573
- unreadCount
2574
- }
2575
- ) }),
2576
- /* @__PURE__ */ jsxs("div", { className: cn(
2577
- "relative flex flex-1 flex-col overflow-hidden",
2578
- frame && "md:m-2 md:rounded-xl md:border md:border-border md:bg-card"
2579
- ), children: [
2580
- /* @__PURE__ */ jsxs("div", { className: "flex min-h-14 shrink-0 items-center justify-between gap-3 border-b border-border bg-card px-4 py-2", children: [
2581
- /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-1 items-center gap-3", children: [
2582
- /* @__PURE__ */ jsx(
2583
- "button",
2584
- {
2585
- onClick: () => setMobileMenuOpen(true),
2586
- className: "inline-flex items-center justify-center rounded-md p-2 text-muted-foreground hover:bg-accent md:hidden",
2587
- "aria-label": "Open menu",
2588
- children: /* @__PURE__ */ jsx(Menu, { className: "h-5 w-5" })
2589
- }
2590
- ),
2591
- pageTitle && /* @__PURE__ */ jsx("h2", { className: "font-semibold text-lg", children: pageTitle }),
2592
- topbarStart,
2593
- /* @__PURE__ */ jsx("div", { ref: setHeaderSlot, className: "flex min-w-0 flex-1 items-center" })
2594
- ] }),
2595
- /* @__PURE__ */ jsxs("div", { className: "flex shrink-0 items-center gap-2", children: [
2596
- /* @__PURE__ */ jsx("div", { ref: setActionsSlot, className: "flex items-center gap-2" }),
2597
- topbarEnd
2598
- ] })
2599
- ] }),
2600
- /* @__PURE__ */ jsx("main", { className: "flex-1 overflow-y-auto", children }),
2601
- /* @__PURE__ */ jsx(SaveBar, {})
2602
- ] }),
2603
- mobileMenuOpen && /* @__PURE__ */ jsx(
2604
- MobileOverlay,
2605
- {
2606
- navigation,
2607
- logo,
2608
- user,
2609
- currentPath,
2610
- onNavigate,
2611
- onSignOut,
2612
- onProfile,
2613
- onSettings,
2614
- onBilling,
2615
- orgSwitcher,
2616
- topContent: sidebarTopContent,
2617
- footerContent: sidebarFooterContent,
2618
- userMenuSlot,
2619
- notificationSlot,
2620
- unreadCount,
2621
- onClose: () => setMobileMenuOpen(false)
2622
- }
2623
- )
2624
- ] }) }) }) });
2625
- }
2626
- function TopbarLayout({
2627
- navigation = [],
2628
- logo,
2629
- user,
2630
- children,
2631
- onNavigate,
2632
- onSignOut,
2633
- onProfile,
2634
- onSettings,
2635
- onBilling,
2636
- userMenuExtras,
2637
- frame,
2638
- currentPath,
2639
- topbarStart,
2640
- topbarEnd
2641
- }) {
2642
- const [mobileMenuOpen, setMobileMenuOpen] = React10.useState(false);
2643
- return /* @__PURE__ */ jsx(SaveBarProvider, { children: /* @__PURE__ */ jsxs("div", { className: "relative flex h-screen flex-col overflow-hidden bg-background", children: [
2644
- /* @__PURE__ */ jsx(
2645
- Topbar,
2646
- {
2647
- navigation,
2648
- logo,
2649
- user,
2650
- currentPath,
2651
- onNavigate,
2652
- onSignOut,
2653
- onProfile,
2654
- onSettings,
2655
- onBilling,
2656
- userMenuExtras,
2657
- leftContent: topbarStart,
2658
- rightContent: topbarEnd,
2659
- onMenuClick: () => setMobileMenuOpen(true)
2660
- }
2661
- ),
2662
- /* @__PURE__ */ jsx("main", { className: cn("flex-1 overflow-y-auto", frame && "md:p-2"), children: /* @__PURE__ */ jsx("div", { className: cn("min-h-full", frame && "md:rounded-xl md:border md:border-border md:bg-card"), children }) }),
2663
- /* @__PURE__ */ jsx(SaveBar, {}),
2664
- mobileMenuOpen && /* @__PURE__ */ jsx(
2665
- MobileOverlay,
2666
- {
2667
- navigation,
2668
- logo,
2669
- user,
2670
- currentPath,
2671
- onNavigate,
2672
- onSignOut,
2673
- onProfile,
2674
- onSettings,
2675
- onBilling,
2676
- userMenuSlot: user ? /* @__PURE__ */ jsx(DrawerUserRow, { user }) : void 0,
2677
- onClose: () => setMobileMenuOpen(false)
2678
- }
2679
- )
2680
- ] }) });
2681
- }
2682
- function DrawerUserRow({ user }) {
2683
- const label = user.fullName || user.email;
2684
- return /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 items-center gap-2 px-1", children: [
2685
- user.avatarUrl ? /* @__PURE__ */ jsx("img", { src: user.avatarUrl, alt: "", className: "h-7 w-7 shrink-0 rounded-full object-cover" }) : /* @__PURE__ */ jsx("span", { className: "flex h-7 w-7 shrink-0 items-center justify-center rounded-full bg-primary text-xs font-semibold text-primary-foreground", children: (label || "?").charAt(0).toUpperCase() }),
2686
- /* @__PURE__ */ jsx("span", { className: "truncate text-sm font-medium text-sidebar-foreground", children: label })
2687
- ] });
2688
- }
2689
- function MinimalLayout({
2690
- logo,
2691
- user: _user,
2692
- children,
2693
- headerEnd
2694
- }) {
2695
- return /* @__PURE__ */ jsxs("div", { className: "flex h-screen flex-col", children: [
2696
- /* @__PURE__ */ jsxs("header", { className: "flex h-14 shrink-0 items-center justify-between border-b border-border bg-card px-4 md:px-6", children: [
2697
- /* @__PURE__ */ jsx("div", { className: "flex items-center gap-3", children: logo ?? /* @__PURE__ */ jsx("span", { className: "text-lg font-bold", children: "App" }) }),
2698
- headerEnd && /* @__PURE__ */ jsx("div", { className: "flex items-center gap-2", children: headerEnd })
2699
- ] }),
2700
- /* @__PURE__ */ jsx("main", { className: "flex-1 overflow-y-auto", children })
2701
- ] });
2702
- }
2703
- function MobileOverlay({ onClose, onNavigate, ...sidebarProps }) {
2704
- const [visible, setVisible] = React10.useState(false);
2705
- React10.useEffect(() => {
2706
- const id = requestAnimationFrame(() => setVisible(true));
2707
- return () => cancelAnimationFrame(id);
2708
- }, []);
2709
- const close = React10.useCallback(() => {
2710
- setVisible(false);
2711
- window.setTimeout(onClose, 250);
2712
- }, [onClose]);
2713
- React10.useEffect(() => {
2714
- const previous = document.body.style.overflow;
2715
- document.body.style.overflow = "hidden";
2716
- return () => {
2717
- document.body.style.overflow = previous;
2718
- };
2719
- }, []);
2720
- React10.useEffect(() => {
2721
- const onKey = (e) => {
2722
- if (e.key === "Escape") close();
2723
- };
2724
- window.addEventListener("keydown", onKey);
2725
- return () => window.removeEventListener("keydown", onKey);
2726
- }, [close]);
2727
- return /* @__PURE__ */ jsxs("div", { className: "fixed inset-0 z-[60] md:hidden", children: [
2728
- /* @__PURE__ */ jsx(
2729
- "div",
2730
- {
2731
- className: cn(
2732
- "absolute inset-0 bg-black/50 transition-opacity duration-300 ease-out",
2733
- visible ? "opacity-100" : "opacity-0"
2734
- ),
2735
- onClick: close
2736
- }
2737
- ),
2738
- /* @__PURE__ */ jsx(
2739
- "div",
2740
- {
2741
- className: cn(
2742
- "absolute inset-y-0 left-0 shadow-2xl transition-transform duration-300 ease-out will-change-transform",
2743
- visible ? "translate-x-0" : "-translate-x-full"
2744
- ),
2745
- children: /* @__PURE__ */ jsx(
2746
- Sidebar,
2747
- {
2748
- ...sidebarProps,
2749
- collapsed: false,
2750
- onToggle: close,
2751
- borderless: true,
2752
- onNavigate: (route) => {
2753
- onNavigate?.(route);
2754
- close();
2755
- }
2756
- }
2757
- )
2758
- }
2759
- )
2760
- ] });
2761
- }
2762
- function AppShell({
2763
- variant,
2764
- contentFrame,
2765
- navigation = [],
2766
- logo,
2767
- user,
2768
- children,
2769
- onNavigate,
2770
- onSignOut,
2771
- onProfile,
2772
- onSettings,
2773
- onBilling,
2774
- pageTitle,
2775
- currentPath,
2776
- userMenuExtras,
2777
- orgSwitcher,
2778
- topbarStart,
2779
- topbarEnd,
2780
- sidebarTopContent,
2781
- sidebarFooterContent,
2782
- userMenuSlot,
2783
- notificationSlot,
2784
- unreadCount = 0
2785
- }) {
2786
- switch (variant) {
2787
- case "sidebar":
2788
- return /* @__PURE__ */ jsx(
2789
- SidebarLayout,
2790
- {
2791
- navigation,
2792
- logo,
2793
- user,
2794
- onNavigate,
2795
- onSignOut,
2796
- onProfile,
2797
- onSettings,
2798
- onBilling,
2799
- userMenuExtras,
2800
- orgSwitcher,
2801
- sidebarTopContent,
2802
- sidebarFooterContent,
2803
- frame: contentFrame,
2804
- currentPath,
2805
- pageTitle,
2806
- topbarStart,
2807
- topbarEnd,
2808
- userMenuSlot,
2809
- notificationSlot,
2810
- unreadCount,
2811
- children
2812
- }
2813
- );
2814
- case "topbar":
2815
- return /* @__PURE__ */ jsx(
2816
- TopbarLayout,
2817
- {
2818
- navigation,
2819
- logo,
2820
- user,
2821
- onNavigate,
2822
- onSignOut,
2823
- onProfile,
2824
- onSettings,
2825
- onBilling,
2826
- userMenuExtras,
2827
- frame: contentFrame,
2828
- currentPath,
2829
- topbarStart,
2830
- topbarEnd,
2831
- children
2832
- }
2833
- );
2834
- case "minimal":
2835
- return /* @__PURE__ */ jsx(MinimalLayout, { logo, user, headerEnd: topbarEnd, children });
2836
- default: {
2837
- return null;
2838
- }
2839
- }
2840
- }
2841
- var NavTransitionContext = React10.createContext("slide");
2842
- function NavTransitionProvider({ value, children }) {
2843
- return /* @__PURE__ */ jsx(NavTransitionContext.Provider, { value, children });
2844
- }
2845
- function useNavTransition() {
2846
- return React10.useContext(NavTransitionContext);
2847
- }
2848
- function resolveTransitionClass(transition, direction) {
2849
- if (transition === "none") return "";
2850
- if (transition === "fade") return "saas-page-enter";
2851
- if (direction === "back") return "saas-nav-back";
2852
- if (direction === "forward") return "saas-nav-forward";
2853
- return "saas-page-enter";
2854
- }
2855
- function PageTransition({
2856
- transitionKey,
2857
- direction,
2858
- className,
2859
- children
2860
- }) {
2861
- const transition = useNavTransition();
2862
- const animClass = resolveTransitionClass(transition, direction);
2863
- return /* @__PURE__ */ jsx("div", { className: cn(animClass, className), children }, transitionKey);
2864
- }
2865
- function PageHeaderActions({ children }) {
2866
- const slot = useModuleHeaderActionsSlot();
2867
- if (slot) return createPortal(children, slot);
2868
- return /* @__PURE__ */ jsx("div", { className: "mb-4 flex items-center justify-end gap-2", children });
2869
- }
2870
- var ModuleLayoutContext = React10.createContext("rail");
2871
- function ModuleLayoutProvider({ variant, children }) {
2872
- return /* @__PURE__ */ jsx(ModuleLayoutContext.Provider, { value: variant, children });
2873
- }
2874
- function useModuleLayout() {
2875
- return React10.useContext(ModuleLayoutContext);
2876
- }
2877
- function NavItem2({ item }) {
2878
- const [expanded, setExpanded] = React10.useState(true);
2879
- const Icon2 = item.icon ? ICON_MAP2[item.icon] ?? null : null;
2880
- const hasChildren = item.children && item.children.length > 0;
2881
- const hasActiveChild = hasChildren && item.children.some((c) => c.active);
2882
- const isActive = item.active || hasActiveChild;
2883
- return /* @__PURE__ */ jsxs("div", { children: [
2884
- /* @__PURE__ */ jsxs(
2885
- "button",
2886
- {
2887
- type: "button",
2888
- onClick: () => hasChildren ? setExpanded(!expanded) : item.onClick?.(),
2889
- className: cn(
2890
- "flex items-center gap-2 w-full rounded-md px-2.5 py-1.5 text-sm transition-colors",
2891
- isActive ? "bg-primary/10 text-primary font-medium" : "text-muted-foreground hover:text-foreground hover:bg-muted/50"
2892
- ),
2893
- children: [
2894
- Icon2 && /* @__PURE__ */ jsx(Icon2, { className: "h-4 w-4 shrink-0" }),
2895
- /* @__PURE__ */ jsx("span", { className: "flex-1 text-left truncate", children: item.label }),
2896
- hasChildren && (expanded ? /* @__PURE__ */ jsx(ChevronDown, { className: "h-3.5 w-3.5 shrink-0 opacity-50" }) : /* @__PURE__ */ jsx(ChevronRight, { className: "h-3.5 w-3.5 shrink-0 opacity-50" }))
2897
- ]
2898
- }
2899
- ),
2900
- hasChildren && expanded && /* @__PURE__ */ jsx("div", { className: "ml-4 mt-0.5 space-y-0.5 border-l pl-2", children: item.children.map((child) => /* @__PURE__ */ jsx(
2901
- "button",
2902
- {
2903
- type: "button",
2904
- onClick: child.onClick,
2905
- className: cn(
2906
- "flex items-center w-full rounded-md px-2.5 py-1 text-xs transition-colors",
2907
- child.active ? "text-primary font-medium bg-primary/5" : "text-muted-foreground hover:text-foreground hover:bg-muted/50"
2908
- ),
2909
- children: child.label
2910
- },
2911
- child.id
2912
- )) })
2913
- ] });
2914
- }
2915
- function MobileTabs({ nav }) {
2916
- const scrollRef = React10.useRef(null);
2917
- const [expandedId, setExpandedId] = React10.useState(null);
2918
- React10.useEffect(() => {
2919
- const container = scrollRef.current;
2920
- if (!container) return;
2921
- const activeEl = container.querySelector('[data-active="true"]');
2922
- if (activeEl) {
2923
- const left = activeEl.offsetLeft - container.offsetWidth / 2 + activeEl.offsetWidth / 2;
2924
- container.scrollTo({ left: Math.max(0, left), behavior: "smooth" });
2925
- }
2926
- }, [nav]);
2927
- return /* @__PURE__ */ jsxs("div", { className: "md:hidden -mx-1 mb-4 no-print", children: [
2928
- /* @__PURE__ */ jsx("div", { ref: scrollRef, className: "flex gap-1 px-1 pb-2 overflow-x-auto scrollbar-hide", children: nav.map((item) => {
2929
- const Icon2 = item.icon ? ICON_MAP2[item.icon] ?? null : null;
2930
- const hasChildren = item.children && item.children.length > 0;
2931
- const isActive = item.active || item.children?.some((c) => c.active);
2932
- const isExpanded = expandedId === item.id;
2933
- return /* @__PURE__ */ jsxs(
2934
- "button",
2935
- {
2936
- type: "button",
2937
- "data-active": isActive || void 0,
2938
- onClick: () => {
2939
- if (hasChildren) {
2940
- setExpandedId(isExpanded ? null : item.id);
2941
- } else {
2942
- item.onClick?.();
2943
- setExpandedId(null);
2944
- }
2945
- },
2946
- className: cn(
2947
- "flex items-center gap-1.5 whitespace-nowrap rounded-full px-3 py-1.5 text-xs font-medium transition-colors shrink-0",
2948
- isActive ? "bg-primary text-primary-foreground" : "bg-muted text-muted-foreground hover:text-foreground"
2949
- ),
2950
- children: [
2951
- Icon2 && /* @__PURE__ */ jsx(Icon2, { className: "h-3 w-3" }),
2952
- item.label,
2953
- hasChildren && /* @__PURE__ */ jsx(ChevronDown, { className: cn("h-2.5 w-2.5 transition-transform", isExpanded && "rotate-180") })
2954
- ]
2955
- },
2956
- item.id
2957
- );
2958
- }) }),
2959
- expandedId && (() => {
2960
- const item = nav.find((n) => n.id === expandedId);
2961
- if (!item?.children) return null;
2962
- return /* @__PURE__ */ jsx("div", { className: "flex gap-1 px-2 pb-2 animate-in fade-in-0 slide-in-from-top-1", children: item.children.map((child) => /* @__PURE__ */ jsx(
2963
- "button",
2964
- {
2965
- type: "button",
2966
- onClick: () => {
2967
- child.onClick?.();
2968
- setExpandedId(null);
2969
- },
2970
- className: cn(
2971
- "whitespace-nowrap rounded-full px-2.5 py-1 text-[11px] font-medium transition-colors shrink-0",
2972
- child.active ? "bg-foreground/10 text-foreground" : "text-muted-foreground hover:text-foreground"
2973
- ),
2974
- children: child.label
2975
- },
2976
- child.id
2977
- )) });
2978
- })()
2979
- ] });
2980
- }
2981
- var BackStyleContext = React10.createContext("link");
2982
- function BackStyleProvider({ style, children }) {
2983
- return /* @__PURE__ */ jsx(BackStyleContext.Provider, { value: style, children });
2984
- }
2985
- function useBackStyle() {
2986
- return React10.useContext(BackStyleContext);
2987
- }
2988
- function SubpageHeader({ title, subtitle, icon, onBack, parentLabel, actions, backStyle }) {
2989
- const t = useTranslation();
2990
- const appStyle = useBackStyle();
2991
- useBackHandler(onBack);
2992
- const Icon2 = icon ? ICON_MAP2[icon] ?? null : null;
2993
- const style = !onBack ? "icon" : backStyle ?? appStyle;
2994
- const effectiveStyle = style !== "icon" && !parentLabel ? "icon" : style;
2995
- const titleBlock = /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-4", children: [
2996
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
2997
- effectiveStyle === "icon" && onBack && /* @__PURE__ */ jsx(
2998
- "button",
2999
- {
3000
- onClick: onBack,
3001
- className: "flex h-8 w-8 items-center justify-center rounded-lg border hover:bg-muted bg-card shadow-button active:shadow-button-inset transition-colors",
3002
- children: /* @__PURE__ */ jsx(ChevronLeft, { className: "h-4 w-4" })
3003
- }
3004
- ),
3005
- Icon2 && /* @__PURE__ */ jsx("div", { className: "flex h-10 w-10 items-center justify-center rounded-xl bg-muted", children: /* @__PURE__ */ jsx(Icon2, { className: "h-5 w-5 text-muted-foreground" }) }),
3006
- /* @__PURE__ */ jsxs("div", { children: [
3007
- /* @__PURE__ */ jsx("h2", { className: "text-xl font-bold", children: title }),
3008
- subtitle && /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: subtitle })
3009
- ] })
3010
- ] }),
3011
- actions && /* @__PURE__ */ jsx("div", { className: "no-print", children: actions })
3012
- ] });
3013
- if (effectiveStyle === "breadcrumb") {
3014
- return /* @__PURE__ */ jsxs("div", { className: "space-y-4 mb-6", children: [
3015
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-4", children: [
3016
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-sm text-muted-foreground", children: [
3017
- /* @__PURE__ */ jsx("button", { onClick: onBack, className: "hover:text-foreground transition-colors", children: parentLabel }),
3018
- /* @__PURE__ */ jsx(ChevronRight, { className: "h-3.5 w-3.5" }),
3019
- /* @__PURE__ */ jsx("span", { className: "text-foreground font-medium", children: title })
3020
- ] }),
3021
- actions && /* @__PURE__ */ jsx("div", { className: "no-print", children: actions })
3022
- ] }),
3023
- subtitle && /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground -mt-2", children: subtitle })
3024
- ] });
3025
- }
3026
- if (effectiveStyle === "link") {
3027
- return /* @__PURE__ */ jsxs("div", { className: "space-y-3 mb-6", children: [
3028
- /* @__PURE__ */ jsxs(
3029
- "button",
3030
- {
3031
- onClick: onBack,
3032
- className: "inline-flex items-center gap-1.5 text-sm text-muted-foreground hover:text-foreground transition-colors",
3033
- children: [
3034
- /* @__PURE__ */ jsx(ChevronLeft, { className: "h-4 w-4" }),
3035
- t("crud.detail.backTo", { entities: parentLabel })
3036
- ]
3037
- }
3038
- ),
3039
- titleBlock
3040
- ] });
3041
- }
3042
- return /* @__PURE__ */ jsx("div", { className: "space-y-4 mb-6", children: titleBlock });
3043
- }
3044
- function ModuleHeader({ nav }) {
3045
- const slot = useModuleHeaderSlot();
3046
- function defaultChild(item) {
3047
- const kids = item.children ?? [];
3048
- return kids.find((c) => /list/i.test(c.id) || /^list$/i.test(c.label)) ?? kids[kids.length - 1];
3049
- }
3050
- const content = /* @__PURE__ */ jsx("div", { className: "flex w-full min-w-0 items-center gap-4", children: /* @__PURE__ */ jsx("div", { className: "flex min-w-0 items-center gap-0.5 overflow-x-auto scrollbar-hide", children: nav.map((item) => {
3051
- const Icon2 = item.icon ? ICON_MAP2[item.icon] ?? null : null;
3052
- const hasChildren = item.children && item.children.length > 0;
3053
- const isActive = item.active || item.children?.some((c) => c.active);
3054
- return /* @__PURE__ */ jsxs(
3055
- "button",
3056
- {
3057
- type: "button",
3058
- onClick: () => hasChildren ? defaultChild(item)?.onClick?.() : item.onClick?.(),
3059
- className: cn(
3060
- "flex shrink-0 items-center gap-1.5 whitespace-nowrap border-b-2 px-2.5 py-1.5 text-sm font-medium transition-colors",
3061
- isActive ? "border-primary text-foreground" : "border-transparent text-muted-foreground hover:text-foreground"
3062
- ),
3063
- children: [
3064
- Icon2 && /* @__PURE__ */ jsx(Icon2, { className: "h-4 w-4" }),
3065
- item.label
3066
- ]
3067
- },
3068
- item.id
3069
- );
3070
- }) }) });
3071
- if (slot) return createPortal(content, slot);
3072
- return /* @__PURE__ */ jsx("div", { className: "mb-5 border-b border-border pb-2", children: content });
3073
- }
3074
- function ModulePage({ title, subtitle, nav, children, className, headerAction, showHeader = true, sidebarFooter, navVariant, viewKey, direction }) {
3075
- const ctxVariant = useModuleLayout();
3076
- const variant = navVariant ?? ctxVariant;
3077
- const transitionKey = viewKey ?? "view";
3078
- if (variant === "tabs") {
3079
- return /* @__PURE__ */ jsxs(Fragment, { children: [
3080
- /* @__PURE__ */ jsx(ModuleHeader, { nav }),
3081
- showHeader && headerAction && /* @__PURE__ */ jsx(PageHeaderActions, { children: headerAction }),
3082
- /* @__PURE__ */ jsx(PageTransition, { transitionKey, direction, className: cn("min-w-0", className), children })
3083
- ] });
3084
- }
3085
- return /* @__PURE__ */ jsxs("div", { className: cn("flex gap-6 -mt-2", className), children: [
3086
- /* @__PURE__ */ jsx("div", { className: "w-48 shrink-0 hidden md:block no-print", children: /* @__PURE__ */ jsxs("div", { className: cn(sidebarFooter && "sticky top-4 flex flex-col"), style: sidebarFooter ? { height: "calc(100vh - 8rem)" } : void 0, children: [
3087
- /* @__PURE__ */ jsxs("div", { children: [
3088
- /* @__PURE__ */ jsx("p", { className: "text-xs font-semibold text-muted-foreground uppercase tracking-wider px-2.5 mb-2", children: title }),
3089
- /* @__PURE__ */ jsx("nav", { className: "space-y-0.5", children: nav.map((item) => /* @__PURE__ */ jsx(NavItem2, { item }, item.id)) })
3090
- ] }),
3091
- sidebarFooter && /* @__PURE__ */ jsx("div", { className: "mt-auto", children: sidebarFooter })
3092
- ] }) }),
3093
- /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
3094
- showHeader && /* @__PURE__ */ jsxs("div", { className: "mb-6 flex items-start justify-between gap-3", children: [
3095
- /* @__PURE__ */ jsxs("div", { children: [
3096
- /* @__PURE__ */ jsx("h1", { className: "text-2xl font-bold", children: title }),
3097
- subtitle && /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: subtitle })
3098
- ] }),
3099
- headerAction
3100
- ] }),
3101
- /* @__PURE__ */ jsx(MobileTabs, { nav }),
3102
- /* @__PURE__ */ jsx(PageTransition, { transitionKey, direction, children })
3103
- ] })
3104
- ] });
3105
- }
3106
- var lightTheme = {
3107
- name: "light",
3108
- colors: {
3109
- primary: "0 0% 18.8%",
3110
- primaryForeground: "0 0% 100%",
3111
- secondary: "0 0% 96.9%",
3112
- secondaryForeground: "0 0% 18.8%",
3113
- accent: "0 0% 94.5%",
3114
- accentForeground: "0 0% 18.8%",
3115
- background: "0 0% 94.5%",
3116
- foreground: "0 0% 18.8%",
3117
- card: "0 0% 100%",
3118
- cardForeground: "0 0% 18.8%",
3119
- muted: "0 0% 96.9%",
3120
- mutedForeground: "0 0% 38%",
3121
- border: "0 0% 89%",
3122
- input: "0 0% 54.1%",
3123
- ring: "214 100% 41.4%",
3124
- popover: "0 0% 100%",
3125
- popoverForeground: "0 0% 18.8%",
3126
- destructive: "354 92% 41%",
3127
- destructiveForeground: "0 0% 100%",
3128
- success: "167 95% 25.1%",
3129
- successForeground: "0 0% 100%",
3130
- warning: "43 100% 50%",
3131
- warningForeground: "0 0% 18.8%",
3132
- sidebar: "0 0% 92.2%",
3133
- sidebarForeground: "0 0% 18.8%",
3134
- sidebarBorder: "0 0% 86.3%",
3135
- sidebarAccent: "0 0% 100%",
3136
- sidebarAccentForeground: "0 0% 18.8%",
3137
- sidebarMuted: "0 0% 38%",
3138
- content: "0 0% 94.5%"
3139
- },
3140
- perception: {
3141
- buttonRadius: "0.5rem",
3142
- cardRadius: "0.5rem",
3143
- inputRadius: "0.5rem",
3144
- modalRadius: "1rem",
3145
- fontFamily: "Inter, ui-sans-serif, system-ui, -apple-system, sans-serif",
3146
- fontFamilyMono: "JetBrains Mono, ui-monospace, SFMono-Regular, monospace",
3147
- shadowSm: "0 1px 0 rgba(0, 0, 0, 0.05), 0 1px 3px 0 rgba(26, 26, 26, 0.06), 0 1px 2px -1px rgba(26, 26, 26, 0.06)",
3148
- shadowMd: "0 1px 1px -1px rgba(26, 26, 26, 0.07), 0 4px 8px -2px rgba(26, 26, 26, 0.10)",
3149
- shadowLg: "0 4px 12px -2px rgba(26, 26, 26, 0.15), 0 1px 3px rgba(26, 26, 26, 0.08)",
3150
- shadowButton: "inset 0 -1px 0 rgba(0,0,0,0.10), inset 0 1px 0 rgba(255,255,255,0.50), 0 1px 0 rgba(0,0,0,0.05)",
3151
- shadowButtonPrimary: "inset 0 -1px 0 rgba(0,0,0,0.20), inset 0 1px 0 rgba(255,255,255,0.20), 0 1px 1.5px rgba(0,0,0,0.20)",
3152
- shadowButtonInset: "inset 0 2px 1px -1px rgba(0,0,0,0.20), inset 0 0 0 1px rgba(0,0,0,0.05)",
3153
- surfaceBackdropFilter: "none",
3154
- buttonBackground: "hsl(var(--card))",
3155
- buttonBackgroundHover: "hsl(var(--muted))",
3156
- buttonBorder: "hsl(var(--border))",
3157
- buttonPrimaryBackground: "hsl(var(--primary))",
3158
- buttonPrimaryBackgroundHover: "hsl(var(--primary) / 0.9)",
3159
- buttonPrimaryBorder: "hsl(var(--primary))",
3160
- buttonBackdropFilter: "none"
3161
- }
3162
- };
3163
- var darkTheme = {
3164
- name: "dark",
3165
- colors: {
3166
- primary: "0 0% 100%",
3167
- primaryForeground: "0 0% 10.2%",
3168
- secondary: "0 0% 18.8%",
3169
- secondaryForeground: "0 0% 89%",
3170
- accent: "0 0% 22.7%",
3171
- accentForeground: "0 0% 100%",
3172
- background: "0 0% 10.2%",
3173
- foreground: "0 0% 89%",
3174
- card: "0 0% 14.9%",
3175
- cardForeground: "0 0% 89%",
3176
- muted: "0 0% 18.8%",
3177
- mutedForeground: "0 0% 64.7%",
3178
- border: "0 0% 22.7%",
3179
- input: "0 0% 32.2%",
3180
- ring: "217 100% 68.4%",
3181
- popover: "0 0% 18.8%",
3182
- popoverForeground: "0 0% 89%",
3183
- destructive: "7 100% 44.9%",
3184
- destructiveForeground: "0 0% 100%",
3185
- success: "156 71% 42.5%",
3186
- successForeground: "0 0% 0%",
3187
- warning: "41 100% 65.1%",
3188
- warningForeground: "0 0% 10.2%",
3189
- sidebar: "0 0% 10.2%",
3190
- sidebarForeground: "0 0% 89%",
3191
- sidebarBorder: "0 0% 16.5%",
3192
- sidebarAccent: "0 0% 18.8%",
3193
- sidebarAccentForeground: "0 0% 100%",
3194
- sidebarMuted: "0 0% 64.7%",
3195
- content: "0 0% 10.2%"
3196
- },
3197
- perception: {
3198
- buttonRadius: "0.5rem",
3199
- cardRadius: "0.5rem",
3200
- inputRadius: "0.5rem",
3201
- modalRadius: "1rem",
3202
- fontFamily: "Inter, ui-sans-serif, system-ui, -apple-system, sans-serif",
3203
- fontFamilyMono: "JetBrains Mono, ui-monospace, SFMono-Regular, monospace",
3204
- shadowSm: "0 1px 0 rgba(0, 0, 0, 0.4), 0 1px 3px 0 rgba(0, 0, 0, 0.35), 0 1px 2px -1px rgba(0, 0, 0, 0.35)",
3205
- shadowMd: "0 1px 1px -1px rgba(0, 0, 0, 0.5), 0 4px 8px -2px rgba(0, 0, 0, 0.6)",
3206
- shadowLg: "0 4px 12px -2px rgba(0, 0, 0, 0.6), 0 1px 3px rgba(0, 0, 0, 0.4)",
3207
- shadowButton: "inset 0 -1px 0 rgba(0,0,0,0.40), inset 0 1px 0 rgba(255,255,255,0.08), 0 1px 0 rgba(0,0,0,0.30)",
3208
- shadowButtonPrimary: "inset 0 -1px 0 rgba(0,0,0,0.20), inset 0 1px 0 rgba(255,255,255,0.40), 0 1px 1.5px rgba(0,0,0,0.40)",
3209
- shadowButtonInset: "inset 0 2px 1px -1px rgba(0,0,0,0.50), inset 0 0 0 1px rgba(0,0,0,0.20)",
3210
- surfaceBackdropFilter: "none",
3211
- buttonBackground: "hsl(var(--card))",
3212
- buttonBackgroundHover: "hsl(var(--muted))",
3213
- buttonBorder: "hsl(var(--border))",
3214
- buttonPrimaryBackground: "hsl(var(--primary))",
3215
- buttonPrimaryBackgroundHover: "hsl(var(--primary) / 0.9)",
3216
- buttonPrimaryBorder: "hsl(var(--primary))",
3217
- buttonBackdropFilter: "none"
3218
- }
3219
- };
3220
- var colorVarMap = {
3221
- primary: "--primary",
3222
- primaryForeground: "--primary-foreground",
3223
- secondary: "--secondary",
3224
- secondaryForeground: "--secondary-foreground",
3225
- accent: "--accent",
3226
- accentForeground: "--accent-foreground",
3227
- background: "--background",
3228
- foreground: "--foreground",
3229
- card: "--card",
3230
- cardForeground: "--card-foreground",
3231
- muted: "--muted",
3232
- mutedForeground: "--muted-foreground",
3233
- border: "--border",
3234
- input: "--input",
3235
- ring: "--ring",
3236
- popover: "--popover",
3237
- popoverForeground: "--popover-foreground",
3238
- destructive: "--destructive",
3239
- destructiveForeground: "--destructive-foreground",
3240
- success: "--success",
3241
- successForeground: "--success-foreground",
3242
- warning: "--warning",
3243
- warningForeground: "--warning-foreground",
3244
- sidebar: "--sidebar",
3245
- sidebarForeground: "--sidebar-foreground",
3246
- sidebarBorder: "--sidebar-border",
3247
- sidebarAccent: "--sidebar-accent",
3248
- sidebarAccentForeground: "--sidebar-accent-foreground",
3249
- sidebarMuted: "--sidebar-muted",
3250
- content: "--content"
3251
- };
3252
- var perceptionVarMap = {
3253
- buttonRadius: "--button-radius",
3254
- cardRadius: "--card-radius",
3255
- inputRadius: "--input-radius",
3256
- modalRadius: "--modal-radius",
3257
- fontFamily: "--font-family",
3258
- fontFamilyMono: "--font-family-mono",
3259
- shadowSm: "--shadow-sm",
3260
- shadowMd: "--shadow-md",
3261
- shadowLg: "--shadow-lg",
3262
- shadowButton: "--shadow-button",
3263
- shadowButtonPrimary: "--shadow-button-primary",
3264
- shadowButtonInset: "--shadow-button-inset",
3265
- surfaceBackdropFilter: "--surface-backdrop-filter",
3266
- modalBackground: "--modal-bg",
3267
- modalBorder: "--modal-border",
3268
- modalOverlayBackground: "--modal-overlay-bg",
3269
- modalOverlayBackdropFilter: "--modal-overlay-backdrop-filter",
3270
- modalShadow: "--modal-shadow",
3271
- glassEdgeGradient: "--glass-edge-gradient",
3272
- glassPrimaryEdgeGradient: "--glass-primary-edge-gradient",
3273
- glassInnerHighlight: "--glass-inner-highlight",
3274
- fieldBackground: "--field-bg",
3275
- fieldBorder: "--field-border",
3276
- fieldShadow: "--field-shadow",
3277
- buttonBackground: "--button-bg",
3278
- buttonBackgroundHover: "--button-hover-bg",
3279
- buttonBorder: "--button-border",
3280
- buttonPrimaryBackground: "--button-primary-bg",
3281
- buttonPrimaryBackgroundHover: "--button-primary-hover-bg",
3282
- buttonPrimaryBorder: "--button-primary-border",
3283
- buttonBackdropFilter: "--button-backdrop-filter"
3284
- };
3285
- function applyTheme(theme, element) {
3286
- const target = document.documentElement;
3287
- for (const [key, cssVar] of Object.entries(colorVarMap)) {
3288
- const value = theme.colors[key];
3289
- if (value) {
3290
- target.style.setProperty(cssVar, value);
3291
- }
3292
- }
3293
- for (const [key, cssVar] of Object.entries(perceptionVarMap)) {
3294
- const value = theme.perception[key];
3295
- if (value) {
3296
- target.style.setProperty(cssVar, value);
3297
- }
3298
- }
3299
- }
3300
- var STORAGE_KEY = "fayz-ui:theme-mode";
3301
- function getSystemPreference() {
3302
- if (typeof window === "undefined") return "light";
3303
- return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
3304
- }
3305
- function getSavedMode() {
3306
- if (typeof window === "undefined") return "light";
3307
- const saved = localStorage.getItem(STORAGE_KEY);
3308
- if (saved === "light" || saved === "dark" || saved === "system") return saved;
3309
- return "light";
3310
- }
3311
- function resolveMode(mode) {
3312
- return mode === "system" ? getSystemPreference() : mode;
3313
- }
3314
- function buildTheme(base, overrides) {
3315
- if (!overrides) return base;
3316
- const isDark = base.name === "dark";
3317
- const colors = { ...base.colors };
3318
- if (overrides.brand) {
3319
- colors.primary = overrides.brand;
3320
- colors.primaryForeground = "0 0% 100%";
3321
- colors.ring = overrides.brand;
3322
- const parts = overrides.brand.split(" ");
3323
- if (parts.length >= 3) {
3324
- const hue = (parseFloat(parts[0]) - 50 + 360) % 360;
3325
- colors.accent = `${hue} ${parts[1]} ${parts[2]}`;
3326
- colors.accentForeground = "0 0% 100%";
3327
- }
3328
- }
3329
- const colorOverrides = isDark && overrides.darkColors ? overrides.darkColors : overrides.colors;
3330
- if (colorOverrides) {
3331
- for (const [key, value] of Object.entries(colorOverrides)) {
3332
- if (value !== void 0) {
3333
- colors[key] = value;
3334
- }
3335
- }
3336
- }
3337
- return {
3338
- name: overrides.name ?? base.name,
3339
- colors,
3340
- perception: {
3341
- ...base.perception,
3342
- ...overrides.perception ?? {}
3343
- }
3344
- };
3345
- }
3346
- var STORE_KEY = "__fayz_ui_theme_store__";
3347
- function createThemeStore() {
3348
- if (typeof window !== "undefined" && window[STORE_KEY]) {
3349
- return window[STORE_KEY];
3350
- }
3351
- const store = create((set, get) => ({
3352
- mode: getSavedMode(),
3353
- resolvedMode: "light",
3354
- overrides: null,
3355
- setMode: (mode) => {
3356
- const resolved = resolveMode(mode);
3357
- set({ mode, resolvedMode: resolved });
3358
- if (typeof window !== "undefined") {
3359
- localStorage.setItem(STORAGE_KEY, mode);
3360
- }
3361
- const { overrides } = get();
3362
- const baseTheme = resolved === "dark" ? darkTheme : lightTheme;
3363
- const theme = buildTheme(baseTheme, overrides);
3364
- if (typeof document !== "undefined") {
3365
- document.documentElement.classList.add("theme-transitioning");
3366
- applyTheme(theme);
3367
- document.documentElement.classList.toggle("dark", resolved === "dark");
3368
- setTimeout(() => {
3369
- document.documentElement.classList.remove("theme-transitioning");
3370
- }, 450);
3371
- } else {
3372
- applyTheme(theme);
3373
- }
3374
- },
3375
- setOverrides: (overrides) => {
3376
- set({ overrides });
3377
- const { resolvedMode } = get();
3378
- const baseTheme = resolvedMode === "dark" ? darkTheme : lightTheme;
3379
- const theme = buildTheme(baseTheme, overrides);
3380
- applyTheme(theme);
3381
- },
3382
- // Legacy compat
3383
- setCustomTheme: (_theme) => {
3384
- },
3385
- initialize: () => {
3386
- const { mode, setMode } = get();
3387
- setMode(mode);
3388
- if (typeof window !== "undefined") {
3389
- window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change", () => {
3390
- const current = get();
3391
- if (current.mode === "system") {
3392
- current.setMode("system");
3393
- }
3394
- });
3395
- }
3396
- }
3397
- }));
3398
- if (typeof window !== "undefined") {
3399
- window[STORE_KEY] = store;
3400
- }
3401
- return store;
3402
- }
3403
- var useThemeStore = createThemeStore();
3404
-
3405
- // src/theme/presets.ts
3406
- function mergeThemeOptions(base, overrides = {}) {
3407
- return {
3408
- ...base,
3409
- ...overrides,
3410
- colors: {
3411
- ...base.colors ?? {},
3412
- ...overrides.colors ?? {}
3413
- },
3414
- darkColors: {
3415
- ...base.darkColors ?? {},
3416
- ...overrides.darkColors ?? {}
3417
- },
3418
- perception: {
3419
- ...base.perception ?? {},
3420
- ...overrides.perception ?? {}
3421
- }
3422
- };
3423
- }
3424
- var fayzThemePresets = {
3425
- classic_admin: {
3426
- name: "classic_admin",
3427
- colors: lightTheme.colors,
3428
- darkColors: darkTheme.colors,
3429
- perception: lightTheme.perception
3430
- },
3431
- liquid_glass: {
3432
- name: "liquid_glass",
3433
- colors: {
3434
- background: "260 38% 96%",
3435
- content: "260 36% 96.5%",
3436
- card: "0 0% 100% / 0.84",
3437
- popover: "0 0% 100% / 0.96",
3438
- secondary: "260 34% 93% / 0.82",
3439
- muted: "260 30% 91% / 0.78",
3440
- border: "260 26% 76% / 0.58",
3441
- input: "260 24% 68% / 0.62"
3442
- },
3443
- darkColors: {
3444
- background: "260 28% 9%",
3445
- content: "260 28% 10%",
3446
- card: "260 24% 16% / 0.72",
3447
- popover: "260 24% 14% / 0.9",
3448
- secondary: "260 20% 22% / 0.72",
3449
- muted: "260 18% 20% / 0.68",
3450
- border: "260 20% 35% / 0.58",
3451
- input: "260 20% 38% / 0.68"
3452
- },
3453
- perception: {
3454
- buttonRadius: "9999px",
3455
- cardRadius: "1.25rem",
3456
- inputRadius: "1rem",
3457
- modalRadius: "1.5rem",
3458
- surfaceBackdropFilter: "blur(16px) saturate(1.24) brightness(1.03)",
3459
- modalBackground: "linear-gradient(145deg, rgb(255 255 255 / 0.82), rgb(249 247 255 / 0.72) 48%, rgb(255 255 255 / 0.64))",
3460
- modalBorder: "rgb(255 255 255 / 0.74)",
3461
- modalOverlayBackground: "rgb(26 20 38 / 0.22)",
3462
- modalOverlayBackdropFilter: "blur(5px) saturate(1.08)",
3463
- modalShadow: "0 32px 82px -46px rgb(31 18 61 / 0.38), 0 1px 4px rgb(31 18 61 / 0.08), inset 0 1px 0 rgb(255 255 255 / 0.78), inset 0 0 0 1px rgb(255 255 255 / 0.28)",
3464
- glassEdgeGradient: "linear-gradient(145deg, rgb(255 255 255 / 0.96) 0%, rgb(255 255 255 / 0.44) 24%, rgb(171 151 211 / 0.34) 48%, rgb(45 35 76 / 0.22) 62%, rgb(255 255 255 / 0.7) 100%)",
3465
- glassPrimaryEdgeGradient: "linear-gradient(145deg, rgb(255 255 255 / 0.74) 0%, rgb(255 255 255 / 0.2) 28%, rgb(45 35 76 / 0.34) 58%, rgb(255 255 255 / 0.5) 100%)",
3466
- glassInnerHighlight: "inset 0 1px 0 rgb(255 255 255 / 0.72), inset 1px 0 0 rgb(255 255 255 / 0.28), inset 0 -1px 0 rgb(45 35 76 / 0.1)",
3467
- fieldBackground: "linear-gradient(135deg, rgb(255 255 255 / 0.9) 0%, rgb(250 248 255 / 0.82) 46%, rgb(242 238 252 / 0.74) 100%)",
3468
- fieldBorder: "rgb(174 158 205 / 0.58)",
3469
- fieldShadow: "inset 0 1px 0 rgb(255 255 255 / 0.8), inset 0 -1px 0 rgb(45 35 76 / 0.08), 0 12px 28px -24px rgb(31 18 61 / 0.28)",
3470
- shadowSm: "0 10px 28px -20px rgb(31 18 61 / 0.34), inset 0 1px 0 rgb(255 255 255 / 0.54)",
3471
- shadowMd: "0 22px 54px -32px rgb(31 18 61 / 0.42), inset 0 1px 0 rgb(255 255 255 / 0.58)",
3472
- shadowLg: "0 34px 88px -42px rgb(31 18 61 / 0.48), inset 0 1px 0 rgb(255 255 255 / 0.62)",
3473
- shadowButton: "inset 0 1px 0 rgb(255 255 255 / 0.82), inset 0 -1px 0 rgb(31 18 61 / 0.08), 0 14px 30px -18px rgb(31 18 61 / 0.56)",
3474
- shadowButtonPrimary: "inset 0 1px 0 rgb(255 255 255 / 0.5), inset 0 -1px 0 rgb(31 18 61 / 0.26), 0 18px 36px -18px rgb(31 18 61 / 0.86)",
3475
- shadowButtonInset: "inset 0 2px 4px rgb(31 18 61 / 0.22), inset 0 0 0 1px rgb(255 255 255 / 0.24)",
3476
- buttonBackground: "linear-gradient(135deg, rgb(255 255 255 / 0.92) 0%, rgb(247 242 255 / 0.74) 48%, rgb(229 220 248 / 0.64) 100%)",
3477
- buttonBackgroundHover: "linear-gradient(135deg, rgb(255 255 255 / 0.98) 0%, rgb(249 244 255 / 0.86) 46%, rgb(231 222 250 / 0.78) 100%)",
3478
- buttonBorder: "rgb(194 178 221 / 0.86)",
3479
- buttonPrimaryBackground: "linear-gradient(135deg, rgb(255 255 255 / 0.28) 0%, hsl(var(--primary) / 0.94) 36%, hsl(278 42% 58% / 0.88) 100%)",
3480
- buttonPrimaryBackgroundHover: "linear-gradient(135deg, rgb(255 255 255 / 0.36) 0%, hsl(var(--primary) / 1) 34%, hsl(278 52% 62% / 0.94) 100%)",
3481
- buttonPrimaryBorder: "rgb(255 255 255 / 0.38)",
3482
- buttonBackdropFilter: "blur(18px) saturate(1.45)"
3483
- }
3484
- }
3485
- };
3486
- function createFayzTheme(preset = "classic_admin", overrides = {}) {
3487
- return mergeThemeOptions(fayzThemePresets[preset] ?? fayzThemePresets.classic_admin, overrides);
3488
- }
3489
-
3490
- // src/theme/index.ts
3491
- function ThemeProvider({ children, overrides, defaultMode }) {
3492
- const initialize = useThemeStore((s) => s.initialize);
3493
- const setMode = useThemeStore((s) => s.setMode);
3494
- const setOverrides = useThemeStore((s) => s.setOverrides);
3495
- React10.useEffect(() => {
3496
- if (defaultMode) {
3497
- setMode(defaultMode);
3498
- }
3499
- if (overrides !== void 0) {
3500
- setOverrides(overrides);
3501
- }
3502
- initialize();
3503
- }, []);
3504
- return children;
3505
- }
3506
- function useTheme() {
3507
- const mode = useThemeStore((s) => s.mode);
3508
- const resolvedMode = useThemeStore((s) => s.resolvedMode);
3509
- const overrides = useThemeStore((s) => s.overrides);
3510
- const setMode = useThemeStore((s) => s.setMode);
3511
- const setOverrides = useThemeStore((s) => s.setOverrides);
3512
- return {
3513
- mode,
3514
- resolvedMode,
3515
- isDark: resolvedMode === "dark",
3516
- overrides,
3517
- setMode,
3518
- setOverrides
3519
- };
3520
- }
3521
- function createTheme(overrides) {
3522
- const colors = { ...lightTheme.colors, ...overrides.colors };
3523
- if (overrides.brand) {
3524
- colors.primary = overrides.brand;
3525
- colors.primaryForeground = "0 0% 100%";
3526
- colors.ring = overrides.brand;
3527
- const parts = overrides.brand.split(" ");
3528
- if (parts.length >= 3) {
3529
- const hue = (parseFloat(parts[0]) - 50 + 360) % 360;
3530
- colors.accent = `${hue} ${parts[1]} ${parts[2]}`;
3531
- colors.accentForeground = "0 0% 100%";
3532
- }
3533
- }
3534
- return {
3535
- name: overrides.name ?? lightTheme.name,
3536
- colors,
3537
- perception: {
3538
- ...lightTheme.perception,
3539
- ...overrides.perception
3540
- }
3541
- };
3542
- }
3543
- var Avatar = React10.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(
14
+ var Avatar = React.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(
3544
15
  AvatarPrimitive.Root,
3545
16
  {
3546
17
  ref,
@@ -3552,7 +23,7 @@ var Avatar = React10.forwardRef(({ className, ...props }, ref) => /* @__PURE__ *
3552
23
  }
3553
24
  ));
3554
25
  Avatar.displayName = AvatarPrimitive.Root.displayName;
3555
- var AvatarImage = React10.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(
26
+ var AvatarImage = React.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(
3556
27
  AvatarPrimitive.Image,
3557
28
  {
3558
29
  ref,
@@ -3561,7 +32,7 @@ var AvatarImage = React10.forwardRef(({ className, ...props }, ref) => /* @__PUR
3561
32
  }
3562
33
  ));
3563
34
  AvatarImage.displayName = AvatarPrimitive.Image.displayName;
3564
- var AvatarFallback = React10.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(
35
+ var AvatarFallback = React.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(
3565
36
  AvatarPrimitive.Fallback,
3566
37
  {
3567
38
  ref,
@@ -3573,599 +44,7 @@ var AvatarFallback = React10.forwardRef(({ className, ...props }, ref) => /* @__
3573
44
  }
3574
45
  ));
3575
46
  AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName;
3576
- var SPAN_CLASS = {
3577
- 1: "",
3578
- 2: "sm:col-span-2",
3579
- 3: "sm:col-span-2 lg:col-span-3",
3580
- 4: "sm:col-span-2 lg:col-span-4"
3581
- };
3582
- function DashboardGrid({ items, className }) {
3583
- return /* @__PURE__ */ jsx("div", { className: cn("grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4", className), children: items.map((item) => /* @__PURE__ */ jsx("div", { className: SPAN_CLASS[Math.min(Math.max(item.span ?? 1, 1), 4)] ?? "", children: item.node }, item.id)) });
3584
- }
3585
-
3586
- // src/dashboard/layout.ts
3587
- function resolveDashboardLayout(registered, appConfig, userPrefs, forceVisibleByDefault = false) {
3588
- const overrides = /* @__PURE__ */ new Map();
3589
- const merge = (cfg) => {
3590
- for (const w of cfg?.widgets ?? []) {
3591
- overrides.set(w.id, { ...overrides.get(w.id), ...w });
3592
- }
3593
- };
3594
- merge(appConfig);
3595
- merge(userPrefs);
3596
- return registered.map((def, index) => {
3597
- const o = overrides.get(def.id);
3598
- const baseVisible = forceVisibleByDefault ? true : def.defaultVisible ?? true;
3599
- const visible = o?.visible ?? baseVisible;
3600
- const order = o?.order ?? def.defaultOrder ?? index;
3601
- const span = Math.min(Math.max(o?.span ?? def.span ?? 1, 1), 4);
3602
- return { def, visible, order, span };
3603
- }).filter((x) => x.visible).sort((a, b) => a.order - b.order).map(({ def, span, order }) => ({ def, span, order }));
3604
- }
3605
- var KEY = (surface, scope) => `fayz.dashboard.prefs.${scope}.${surface}`;
3606
- function read(key) {
3607
- if (typeof window === "undefined") return null;
3608
- try {
3609
- const raw = window.localStorage.getItem(key);
3610
- return raw ? JSON.parse(raw) : null;
3611
- } catch {
3612
- return null;
3613
- }
3614
- }
3615
- function useDashboardPreferences(surface, scope = "home") {
3616
- const key = KEY(surface, scope);
3617
- const [prefs, setPrefs] = React10.useState(() => read(key));
3618
- React10.useEffect(() => {
3619
- setPrefs(read(key));
3620
- }, [key]);
3621
- const save = React10.useCallback((next) => {
3622
- setPrefs(next);
3623
- if (typeof window !== "undefined") {
3624
- try {
3625
- window.localStorage.setItem(key, JSON.stringify(next));
3626
- } catch {
3627
- }
3628
- }
3629
- }, [key]);
3630
- const setVisible = React10.useCallback((widgetId, visible) => {
3631
- setPrefs((cur) => {
3632
- const widgets = [...cur?.widgets ?? []];
3633
- const i = widgets.findIndex((w) => w.id === widgetId);
3634
- if (i >= 0) widgets[i] = { ...widgets[i], visible };
3635
- else widgets.push({ id: widgetId, visible });
3636
- const next = { ...cur, widgets };
3637
- if (typeof window !== "undefined") {
3638
- try {
3639
- window.localStorage.setItem(key, JSON.stringify(next));
3640
- } catch {
3641
- }
3642
- }
3643
- return next;
3644
- });
3645
- }, [key]);
3646
- const reset = React10.useCallback(() => {
3647
- setPrefs(null);
3648
- if (typeof window !== "undefined") {
3649
- try {
3650
- window.localStorage.removeItem(key);
3651
- } catch {
3652
- }
3653
- }
3654
- }, [key]);
3655
- return { prefs, save, setVisible, reset };
3656
- }
3657
- var DEFAULT_RANGE_OPTIONS = ["7d", "30d", "90d"];
3658
- var DashboardRangeContext = React10.createContext(null);
3659
- function useDashboardRange() {
3660
- return React10.useContext(DashboardRangeContext);
3661
- }
3662
- function DashboardRangeProvider({
3663
- options = DEFAULT_RANGE_OPTIONS,
3664
- defaultRange,
3665
- onChange,
3666
- children
3667
- }) {
3668
- const [range, setRangeState] = React10.useState(defaultRange ?? options[1] ?? options[0] ?? "30d");
3669
- const setRange = React10.useCallback((r) => {
3670
- setRangeState(r);
3671
- onChange?.(r);
3672
- }, [onChange]);
3673
- const value = React10.useMemo(() => ({ range, setRange, options }), [range, setRange, options]);
3674
- return /* @__PURE__ */ jsx(DashboardRangeContext.Provider, { value, children });
3675
- }
3676
- function DashboardRangeControl({ className }) {
3677
- const ctx = useDashboardRange();
3678
- if (!ctx) return null;
3679
- return /* @__PURE__ */ jsx(
3680
- SegmentedControl,
3681
- {
3682
- options: ctx.options,
3683
- value: ctx.range,
3684
- onChange: ctx.setRange,
3685
- className,
3686
- "aria-label": "Time range"
3687
- }
3688
- );
3689
- }
3690
- function DashboardCustomizeMenu({ widgets, visibleIds, onToggle, onReset, label }) {
3691
- const t = useTranslation();
3692
- if (widgets.length === 0) return null;
3693
- return /* @__PURE__ */ jsxs(Dropdown, { children: [
3694
- /* @__PURE__ */ jsx(DropdownTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(Button, { variant: "outline", size: "sm", className: "gap-1.5", children: [
3695
- /* @__PURE__ */ jsx(SlidersHorizontal, { className: "h-3.5 w-3.5" }),
3696
- label ?? "Customize"
3697
- ] }) }),
3698
- /* @__PURE__ */ jsxs(DropdownContent, { align: "end", className: "max-h-[70vh] w-60 overflow-y-auto", children: [
3699
- /* @__PURE__ */ jsx(DropdownLabel, { children: label ?? "Show widgets" }),
3700
- /* @__PURE__ */ jsx(DropdownSeparator, {}),
3701
- widgets.map((w) => /* @__PURE__ */ jsx(
3702
- DropdownCheckboxItem,
3703
- {
3704
- checked: visibleIds.has(w.id),
3705
- onCheckedChange: (checked) => onToggle(w.id, Boolean(checked)),
3706
- onSelect: (e) => e.preventDefault(),
3707
- children: t(w.title)
3708
- },
3709
- w.id
3710
- )),
3711
- /* @__PURE__ */ jsx(DropdownSeparator, {}),
3712
- /* @__PURE__ */ jsxs(DropdownItem, { onSelect: () => onReset(), children: [
3713
- /* @__PURE__ */ jsx(RotateCcw, { className: "mr-2 h-3.5 w-3.5" }),
3714
- "Reset to defaults"
3715
- ] })
3716
- ] })
3717
- ] });
3718
- }
3719
- function HoverHide({ id, onHide, children }) {
3720
- if (!onHide) return /* @__PURE__ */ jsx(Fragment, { children });
3721
- return /* @__PURE__ */ jsxs("div", { className: "group relative h-full", children: [
3722
- children,
3723
- /* @__PURE__ */ jsx(
3724
- "button",
3725
- {
3726
- type: "button",
3727
- onClick: () => onHide(id),
3728
- title: "Hide widget",
3729
- "aria-label": "Hide widget",
3730
- className: "absolute right-2 top-2 z-10 inline-flex h-6 w-6 items-center justify-center rounded-md border border-border bg-background/90 text-muted-foreground opacity-0 shadow-sm transition-opacity hover:text-foreground group-hover:opacity-100",
3731
- children: /* @__PURE__ */ jsx(EyeOff, { className: "h-3.5 w-3.5" })
3732
- }
3733
- )
3734
- ] });
3735
- }
3736
- function toGridItems(laidOut, onHide) {
3737
- return laidOut.map(({ def, span }) => {
3738
- const Component = resolvePluginComponent(def);
3739
- const node = Component ? /* @__PURE__ */ jsx(Component, { ...def.props ?? {} }) : null;
3740
- return { id: def.id, span, node: /* @__PURE__ */ jsx(HoverHide, { id: def.id, onHide, children: node }) };
3741
- });
3742
- }
3743
- var DashboardNavigateContext = React10.createContext(
3744
- (path) => hashRouterAdapter().navigate(path)
3745
- );
3746
- var DashboardNavigateProvider = DashboardNavigateContext.Provider;
3747
- function useDashboardNavigate() {
3748
- return React10.useContext(DashboardNavigateContext);
3749
- }
3750
- function WidgetGrid({ widgets, appLayout, surface }) {
3751
- const { prefs, setVisible } = useDashboardPreferences(surface);
3752
- const laidOut = resolveDashboardLayout(widgets, appLayout, prefs ?? void 0);
3753
- return /* @__PURE__ */ jsx(DashboardGrid, { items: toGridItems(laidOut, (id) => setVisible(id, false)) });
3754
- }
3755
- function DashboardCanvas({
3756
- surface,
3757
- domain,
3758
- title,
3759
- subtitle,
3760
- showHeader = true,
3761
- appLayout,
3762
- range,
3763
- customizable = true,
3764
- customizeLabel,
3765
- initialVisibility,
3766
- onNavigate,
3767
- className
3768
- }) {
3769
- const runtime = usePluginRuntime();
3770
- const widgets = getDashboardWidgets(runtime, { surface, domain });
3771
- const rangeOpts = range ? range === true ? {} : range : null;
3772
- const forceAll = (initialVisibility ?? (surface === "plugin-home" ? "all" : "default")) === "all";
3773
- const { prefs, setVisible, reset } = useDashboardPreferences(surface, domain ?? "home");
3774
- const laidOut = resolveDashboardLayout(widgets, appLayout, prefs ?? void 0, forceAll);
3775
- const visibleIds = React10.useMemo(() => new Set(laidOut.map((w) => w.def.id)), [laidOut]);
3776
- const hasControls = Boolean(rangeOpts) || customizable && widgets.length > 0;
3777
- const headerBlock = showHeader && (title || subtitle) ? /* @__PURE__ */ jsxs("div", { children: [
3778
- title ? /* @__PURE__ */ jsx("h1", { className: "text-2xl font-semibold tracking-tight", children: title }) : null,
3779
- subtitle ? /* @__PURE__ */ jsx("p", { className: "mt-1 text-sm text-muted-foreground", children: subtitle }) : null
3780
- ] }) : null;
3781
- const topRow = headerBlock || hasControls ? /* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between gap-4", children: [
3782
- headerBlock ?? /* @__PURE__ */ jsx("span", {}),
3783
- hasControls ? /* @__PURE__ */ jsxs("div", { className: "flex shrink-0 items-center gap-2", children: [
3784
- customizable && widgets.length > 0 ? /* @__PURE__ */ jsx(
3785
- DashboardCustomizeMenu,
3786
- {
3787
- widgets,
3788
- visibleIds,
3789
- onToggle: setVisible,
3790
- onReset: reset,
3791
- label: customizeLabel
3792
- }
3793
- ) : null,
3794
- rangeOpts ? /* @__PURE__ */ jsx(DashboardRangeControl, {}) : null
3795
- ] }) : null
3796
- ] }) : null;
3797
- const content = /* @__PURE__ */ jsxs("div", { className: className ?? "space-y-6 p-6", children: [
3798
- topRow,
3799
- widgets.length === 0 ? /* @__PURE__ */ jsxs(Card, { children: [
3800
- /* @__PURE__ */ jsxs(CardHeader, { children: [
3801
- /* @__PURE__ */ jsx(CardTitle, { children: "Welcome!" }),
3802
- /* @__PURE__ */ jsx(CardDescription, { children: "Your dashboard will show key metrics and information here." })
3803
- ] }),
3804
- /* @__PURE__ */ jsx(CardContent, { children: /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: "Enable plugins or register dashboard widgets to populate this view." }) })
3805
- ] }) : laidOut.length === 0 ? /* @__PURE__ */ jsx(Card, { children: /* @__PURE__ */ jsx(CardContent, { className: "p-6 text-sm text-muted-foreground", children: "All widgets are hidden. Use \u201CCustomize\u201D to choose what to show." }) }) : /* @__PURE__ */ jsx(DashboardGrid, { items: toGridItems(laidOut) })
3806
- ] });
3807
- const navWrapped = onNavigate ? /* @__PURE__ */ jsx(DashboardNavigateProvider, { value: onNavigate, children: content }) : content;
3808
- return rangeOpts ? /* @__PURE__ */ jsx(DashboardRangeProvider, { options: rangeOpts.options, defaultRange: rangeOpts.default, onChange: rangeOpts.onChange, children: navWrapped }) : navWrapped;
3809
- }
3810
- function renderIcon(icon, className = "h-4 w-4") {
3811
- if (!icon) return null;
3812
- if (typeof icon === "string") {
3813
- const Comp = LucideIcons[icon];
3814
- return Comp ? React10.createElement(Comp, { className }) : null;
3815
- }
3816
- return React10.createElement(icon, { className });
3817
- }
3818
- function formatKpiValue(value, format, currency) {
3819
- const locale = currency?.locale ?? "pt-BR";
3820
- switch (format) {
3821
- case "currency":
3822
- return new Intl.NumberFormat(locale, { style: "currency", currency: currency?.code ?? "BRL", maximumFractionDigits: 0 }).format(value);
3823
- case "percent":
3824
- return `${new Intl.NumberFormat(locale, { maximumFractionDigits: 1 }).format(value)}%`;
3825
- case "duration":
3826
- return `${Math.round(value)} min`;
3827
- default:
3828
- return new Intl.NumberFormat(locale, { maximumFractionDigits: 1 }).format(value);
3829
- }
3830
- }
3831
- function deriveTrend(current, previous, explicit) {
3832
- if (explicit) return explicit;
3833
- if (previous == null) return void 0;
3834
- return current > previous ? "up" : current < previous ? "down" : "neutral";
3835
- }
3836
- function TrendChip({ trend, delta, invert }) {
3837
- if (trend === "neutral") return null;
3838
- const good = invert ? trend === "down" : trend === "up";
3839
- const Icon2 = trend === "up" ? TrendingUp : TrendingDown;
3840
- return /* @__PURE__ */ jsxs("span", { className: cn(
3841
- "inline-flex items-center gap-0.5 text-xs font-medium",
3842
- good ? "text-emerald-600" : "text-rose-600"
3843
- ), children: [
3844
- /* @__PURE__ */ jsx(Icon2, { className: "h-3 w-3" }),
3845
- delta != null ? `${delta > 0 ? "+" : ""}${delta}%` : null
3846
- ] });
3847
- }
3848
- function trendColor(trend, invert) {
3849
- if (!trend || trend === "neutral") return "hsl(var(--info))";
3850
- const good = invert ? trend === "down" : trend === "up";
3851
- return good ? "#10b981" : "#f43f5e";
3852
- }
3853
- function Sparkline({ data, color }) {
3854
- const gid = React10.useId();
3855
- if (!data || data.length < 2) return null;
3856
- const W = 96, H = 32, P = 3;
3857
- const min = Math.min(...data);
3858
- const max = Math.max(...data);
3859
- const span = max - min || 1;
3860
- const step = (W - P * 2) / (data.length - 1);
3861
- const pts = data.map((d, i) => {
3862
- const x = P + i * step;
3863
- const y = P + (H - P * 2) * (1 - (d - min) / span);
3864
- return [x, y];
3865
- });
3866
- const line = pts.map(([x, y], i) => `${i === 0 ? "M" : "L"}${x.toFixed(1)},${y.toFixed(1)}`).join(" ");
3867
- const area = `${line} L${pts[pts.length - 1][0].toFixed(1)},${H} L${pts[0][0].toFixed(1)},${H} Z`;
3868
- return /* @__PURE__ */ jsxs("svg", { viewBox: `0 0 ${W} ${H}`, className: "h-8 w-24 shrink-0 overflow-visible", preserveAspectRatio: "none", "aria-hidden": true, children: [
3869
- /* @__PURE__ */ jsx("defs", { children: /* @__PURE__ */ jsxs("linearGradient", { id: gid, x1: "0", y1: "0", x2: "0", y2: "1", children: [
3870
- /* @__PURE__ */ jsx("stop", { offset: "0%", stopColor: color, stopOpacity: "0.22" }),
3871
- /* @__PURE__ */ jsx("stop", { offset: "100%", stopColor: color, stopOpacity: "0" })
3872
- ] }) }),
3873
- /* @__PURE__ */ jsx("path", { d: area, fill: `url(#${gid})` }),
3874
- /* @__PURE__ */ jsx("path", { d: line, fill: "none", stroke: color, strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round", vectorEffect: "non-scaling-stroke" })
3875
- ] });
3876
- }
3877
- function Gauge({ value, goal, color, children }) {
3878
- const pct = goal > 0 ? Math.min(1, Math.max(0, value / goal)) : 0;
3879
- const R = 40, CX = 50, CY = 46, SW = 9;
3880
- const len = Math.PI * R;
3881
- const d = `M ${CX - R} ${CY} A ${R} ${R} 0 0 1 ${CX + R} ${CY}`;
3882
- return /* @__PURE__ */ jsxs("div", { className: "relative mx-auto w-full max-w-[160px]", children: [
3883
- /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 100 52", className: "w-full", "aria-hidden": true, children: [
3884
- /* @__PURE__ */ jsx("path", { d, fill: "none", stroke: "hsl(var(--muted))", strokeWidth: SW, strokeLinecap: "round" }),
3885
- /* @__PURE__ */ jsx(
3886
- "path",
3887
- {
3888
- d,
3889
- fill: "none",
3890
- stroke: color,
3891
- strokeWidth: SW,
3892
- strokeLinecap: "round",
3893
- strokeDasharray: len,
3894
- strokeDashoffset: len * (1 - pct)
3895
- }
3896
- )
3897
- ] }),
3898
- /* @__PURE__ */ jsx("div", { className: "absolute inset-x-0 bottom-0 text-center text-2xl font-semibold tracking-tight text-foreground", children })
3899
- ] });
3900
- }
3901
- function KpiCard(props) {
3902
- const { label, icon, sub, format, currency, invertTrend } = props;
3903
- const [computed, setComputed] = React10.useState(null);
3904
- const [errored, setErrored] = React10.useState(false);
3905
- React10.useEffect(() => {
3906
- if (!props.compute) return;
3907
- let cancelled = false;
3908
- props.compute().then((v) => {
3909
- if (!cancelled) setComputed(v);
3910
- }).catch(() => {
3911
- if (!cancelled) setErrored(true);
3912
- });
3913
- return () => {
3914
- cancelled = true;
3915
- };
3916
- }, [props.compute]);
3917
- const isAsync = Boolean(props.compute);
3918
- const loading = props.loading || isAsync && computed === null && !errored;
3919
- let display = props.value;
3920
- let current = props.current;
3921
- let previous = props.previous;
3922
- let trend = props.trend;
3923
- let unit;
3924
- let spark = props.spark;
3925
- if (isAsync && computed) {
3926
- display = formatKpiValue(computed.value, format, currency);
3927
- current = computed.value;
3928
- previous = computed.previousValue;
3929
- trend = computed.trend;
3930
- unit = computed.unit;
3931
- spark = computed.spark ?? spark;
3932
- } else if (display == null && props.amount != null) {
3933
- display = formatKpiValue(props.amount, format, currency);
3934
- current = current ?? props.amount;
3935
- }
3936
- const resolvedTrend = current != null ? deriveTrend(current, previous, trend) : trend;
3937
- const delta = current != null && previous != null && previous !== 0 ? Math.round((current - previous) / previous * 100) : null;
3938
- const accent = props.accent ?? trendColor(resolvedTrend, invertTrend);
3939
- const valueText = /* @__PURE__ */ jsxs(Fragment, { children: [
3940
- display,
3941
- unit ? /* @__PURE__ */ jsx("span", { className: "ml-1 text-sm font-normal text-muted-foreground", children: unit }) : null
3942
- ] });
3943
- const trendChip = resolvedTrend ? /* @__PURE__ */ jsx(TrendChip, { trend: resolvedTrend, delta, invert: invertTrend }) : null;
3944
- const isGauge = props.goal != null && current != null;
3945
- const hasSpark = Boolean(spark && spark.length >= 2);
3946
- let body;
3947
- if (errored) {
3948
- body = /* @__PURE__ */ jsx("span", { className: "text-sm text-muted-foreground", children: "\u2014" });
3949
- } else if (loading) {
3950
- body = isGauge ? /* @__PURE__ */ jsx("div", { className: "mx-auto h-16 w-32 animate-pulse rounded bg-muted" }) : /* @__PURE__ */ jsx("div", { className: "h-7 w-20 animate-pulse rounded bg-muted" });
3951
- } else if (isGauge) {
3952
- body = /* @__PURE__ */ jsxs("div", { children: [
3953
- /* @__PURE__ */ jsx(Gauge, { value: current, goal: props.goal, color: accent, children: valueText }),
3954
- /* @__PURE__ */ jsxs("div", { className: "mt-1 flex items-center justify-center gap-2 text-xs text-muted-foreground", children: [
3955
- trendChip,
3956
- /* @__PURE__ */ jsx("span", { children: sub ?? `out of ${formatKpiValue(props.goal, format, currency)}` })
3957
- ] })
3958
- ] });
3959
- } else if (hasSpark) {
3960
- body = /* @__PURE__ */ jsxs("div", { className: "flex items-end justify-between gap-3", children: [
3961
- /* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
3962
- /* @__PURE__ */ jsx("div", { className: "text-2xl font-semibold tracking-tight text-foreground", children: valueText }),
3963
- trendChip ? /* @__PURE__ */ jsx("div", { className: "mt-0.5", children: trendChip }) : null
3964
- ] }),
3965
- /* @__PURE__ */ jsx(Sparkline, { data: spark, color: accent })
3966
- ] });
3967
- } else {
3968
- body = /* @__PURE__ */ jsxs("div", { className: "flex items-baseline justify-between gap-2", children: [
3969
- /* @__PURE__ */ jsx("span", { className: "text-2xl font-semibold tracking-tight text-foreground", children: valueText }),
3970
- trendChip ? /* @__PURE__ */ jsx("span", { className: "mb-0.5", children: trendChip }) : null
3971
- ] });
3972
- }
3973
- return /* @__PURE__ */ jsx(Card, { children: /* @__PURE__ */ jsxs(CardContent, { className: "p-4", children: [
3974
- /* @__PURE__ */ jsxs("div", { className: "mb-2 flex items-center justify-between gap-2", children: [
3975
- /* @__PURE__ */ jsx("span", { className: "text-sm text-muted-foreground", children: label }),
3976
- /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: renderIcon(icon) })
3977
- ] }),
3978
- body,
3979
- sub && !isGauge ? /* @__PURE__ */ jsx("p", { className: "mt-0.5 text-xs text-muted-foreground", children: sub }) : null
3980
- ] }) });
3981
- }
3982
- var PALETTE = [
3983
- "hsl(var(--info))",
3984
- "hsl(var(--magic))",
3985
- "hsl(var(--success))",
3986
- "hsl(var(--warning))",
3987
- "hsl(var(--destructive))"
3988
- ];
3989
- var AXIS = "hsl(var(--muted-foreground))";
3990
- var GRID = "hsl(var(--border))";
3991
- function ChartWidget({
3992
- type,
3993
- data,
3994
- series,
3995
- categoryKey = "name",
3996
- title,
3997
- icon,
3998
- headerAction,
3999
- height = 240,
4000
- className
4001
- }) {
4002
- const color = (i, explicit) => explicit ?? PALETTE[i % PALETTE.length];
4003
- return /* @__PURE__ */ jsxs(Card, { className, children: [
4004
- (title || headerAction) && /* @__PURE__ */ jsxs(CardHeader, { className: "flex-row items-center justify-between space-y-0", children: [
4005
- title ? /* @__PURE__ */ jsxs(CardTitle, { className: "flex items-center gap-2", children: [
4006
- renderIcon(icon),
4007
- title
4008
- ] }) : /* @__PURE__ */ jsx("span", {}),
4009
- headerAction
4010
- ] }),
4011
- /* @__PURE__ */ jsx(CardContent, { className: "p-4 pt-2", children: /* @__PURE__ */ jsx(ResponsiveContainer, { width: "100%", height, children: type === "pie" ? /* @__PURE__ */ jsxs(PieChart, { children: [
4012
- /* @__PURE__ */ jsx(Tooltip$1, {}),
4013
- /* @__PURE__ */ jsx(Pie, { data, dataKey: series[0]?.dataKey ?? "value", nameKey: categoryKey, innerRadius: 50, outerRadius: 80, paddingAngle: 2, children: data.map((_, i) => /* @__PURE__ */ jsx(Cell, { fill: color(i, series[0]?.color) }, i)) }),
4014
- /* @__PURE__ */ jsx(Legend, {})
4015
- ] }) : type === "bar" ? /* @__PURE__ */ jsxs(BarChart, { data, children: [
4016
- /* @__PURE__ */ jsx(CartesianGrid, { strokeDasharray: "3 3", stroke: GRID, vertical: false }),
4017
- /* @__PURE__ */ jsx(XAxis, { dataKey: categoryKey, stroke: AXIS, fontSize: 12, tickLine: false, axisLine: false }),
4018
- /* @__PURE__ */ jsx(YAxis, { stroke: AXIS, fontSize: 12, tickLine: false, axisLine: false, width: 36 }),
4019
- /* @__PURE__ */ jsx(Tooltip$1, { cursor: { fill: GRID, opacity: 0.3 } }),
4020
- series.length > 1 && /* @__PURE__ */ jsx(Legend, {}),
4021
- series.map((s, i) => /* @__PURE__ */ jsx(Bar, { dataKey: s.dataKey, name: s.label ?? s.dataKey, fill: color(i, s.color), radius: [4, 4, 0, 0] }, s.dataKey))
4022
- ] }) : type === "area" ? /* @__PURE__ */ jsxs(AreaChart, { data, children: [
4023
- /* @__PURE__ */ jsx(CartesianGrid, { strokeDasharray: "3 3", stroke: GRID, vertical: false }),
4024
- /* @__PURE__ */ jsx(XAxis, { dataKey: categoryKey, stroke: AXIS, fontSize: 12, tickLine: false, axisLine: false }),
4025
- /* @__PURE__ */ jsx(YAxis, { stroke: AXIS, fontSize: 12, tickLine: false, axisLine: false, width: 36 }),
4026
- /* @__PURE__ */ jsx(Tooltip$1, {}),
4027
- series.length > 1 && /* @__PURE__ */ jsx(Legend, {}),
4028
- series.map((s, i) => /* @__PURE__ */ jsx(Area, { dataKey: s.dataKey, name: s.label ?? s.dataKey, stroke: color(i, s.color), fill: color(i, s.color), fillOpacity: 0.15 }, s.dataKey))
4029
- ] }) : /* @__PURE__ */ jsxs(LineChart, { data, children: [
4030
- /* @__PURE__ */ jsx(CartesianGrid, { strokeDasharray: "3 3", stroke: GRID, vertical: false }),
4031
- /* @__PURE__ */ jsx(XAxis, { dataKey: categoryKey, stroke: AXIS, fontSize: 12, tickLine: false, axisLine: false }),
4032
- /* @__PURE__ */ jsx(YAxis, { stroke: AXIS, fontSize: 12, tickLine: false, axisLine: false, width: 36 }),
4033
- /* @__PURE__ */ jsx(Tooltip$1, {}),
4034
- series.length > 1 && /* @__PURE__ */ jsx(Legend, {}),
4035
- series.map((s, i) => /* @__PURE__ */ jsx(Line, { dataKey: s.dataKey, name: s.label ?? s.dataKey, stroke: color(i, s.color), strokeWidth: 2, dot: false }, s.dataKey))
4036
- ] }) }) })
4037
- ] });
4038
- }
4039
- function TableWidget({
4040
- title,
4041
- icon,
4042
- columns,
4043
- data,
4044
- loading,
4045
- emptyMessage,
4046
- onRowClick,
4047
- headerAction,
4048
- className
4049
- }) {
4050
- return /* @__PURE__ */ jsxs(Card, { className, children: [
4051
- (title || headerAction) && /* @__PURE__ */ jsxs(CardHeader, { className: "flex-row items-center justify-between space-y-0", children: [
4052
- title ? /* @__PURE__ */ jsxs(CardTitle, { className: "flex items-center gap-2", children: [
4053
- renderIcon(icon),
4054
- title
4055
- ] }) : /* @__PURE__ */ jsx("span", {}),
4056
- headerAction
4057
- ] }),
4058
- /* @__PURE__ */ jsx(CardContent, { className: "p-0", children: /* @__PURE__ */ jsx(
4059
- DataTable,
4060
- {
4061
- columns,
4062
- data,
4063
- loading,
4064
- emptyMessage,
4065
- onRowClick,
4066
- variant: "flat",
4067
- compact: true
4068
- }
4069
- ) })
4070
- ] });
4071
- }
4072
- function OnboardingWidget({
4073
- title = "Getting started",
4074
- subtitle,
4075
- steps,
4076
- onNavigate,
4077
- hideWhenComplete = true,
4078
- className
4079
- }) {
4080
- const [completed, setCompleted] = React10.useState(
4081
- () => Object.fromEntries(steps.map((s) => [s.id, Boolean(s.done)]))
4082
- );
4083
- React10.useEffect(() => {
4084
- let cancelled = false;
4085
- Promise.all(steps.map(async (s) => {
4086
- if (s.done != null) return [s.id, s.done];
4087
- if (!s.check) return [s.id, false];
4088
- try {
4089
- return [s.id, await s.check()];
4090
- } catch {
4091
- return [s.id, false];
4092
- }
4093
- })).then((entries) => {
4094
- if (!cancelled) setCompleted(Object.fromEntries(entries));
4095
- });
4096
- return () => {
4097
- cancelled = true;
4098
- };
4099
- }, [steps]);
4100
- const doneCount = steps.filter((s) => completed[s.id]).length;
4101
- const allDone = doneCount === steps.length;
4102
- if (allDone && hideWhenComplete) return null;
4103
- const runAction = (action) => {
4104
- if (!action) return;
4105
- if (typeof action === "string") onNavigate?.(action);
4106
- else action();
4107
- };
4108
- return /* @__PURE__ */ jsxs(Card, { className, children: [
4109
- /* @__PURE__ */ jsxs(CardHeader, { children: [
4110
- /* @__PURE__ */ jsx(CardTitle, { children: title }),
4111
- subtitle ? /* @__PURE__ */ jsx(CardDescription, { children: subtitle }) : null,
4112
- /* @__PURE__ */ jsxs("div", { className: "mt-2 flex items-center gap-3", children: [
4113
- /* @__PURE__ */ jsx("div", { className: "h-1.5 flex-1 overflow-hidden rounded-full bg-muted", children: /* @__PURE__ */ jsx(
4114
- "div",
4115
- {
4116
- className: "h-full rounded-full bg-primary transition-all",
4117
- style: { width: `${steps.length ? doneCount / steps.length * 100 : 0}%` }
4118
- }
4119
- ) }),
4120
- /* @__PURE__ */ jsxs("span", { className: "text-xs font-medium text-muted-foreground", children: [
4121
- doneCount,
4122
- "/",
4123
- steps.length
4124
- ] })
4125
- ] })
4126
- ] }),
4127
- /* @__PURE__ */ jsx(CardContent, { className: "space-y-1 pt-0", children: steps.map((step) => {
4128
- const isDone = completed[step.id];
4129
- return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 rounded-lg px-2 py-2.5 hover:bg-muted/50", children: [
4130
- /* @__PURE__ */ jsx("span", { className: cn(
4131
- "flex h-7 w-7 shrink-0 items-center justify-center rounded-full",
4132
- isDone ? "bg-emerald-100 text-emerald-600" : "bg-muted text-muted-foreground"
4133
- ), children: isDone ? /* @__PURE__ */ jsx(Check, { className: "h-4 w-4" }) : renderIcon(step.icon, "h-4 w-4") ?? /* @__PURE__ */ jsx(Circle, { className: "h-4 w-4" }) }),
4134
- /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
4135
- /* @__PURE__ */ jsx("p", { className: cn("text-sm font-medium", isDone && "text-muted-foreground line-through"), children: step.title }),
4136
- step.description ? /* @__PURE__ */ jsx("p", { className: "truncate text-xs text-muted-foreground", children: step.description }) : null
4137
- ] }),
4138
- !isDone && step.action ? /* @__PURE__ */ jsx(Button, { size: "sm", variant: "outline", onClick: () => runAction(step.action), children: step.actionLabel ?? "Start" }) : null
4139
- ] }, step.id);
4140
- }) })
4141
- ] });
4142
- }
4143
-
4144
- // src/dashboard/define.ts
4145
- function build(kind, defaultSpan, input) {
4146
- return {
4147
- kind,
4148
- span: input.span ?? defaultSpan,
4149
- defaultVisible: input.defaultVisible ?? true,
4150
- id: input.id,
4151
- title: input.title,
4152
- description: input.description,
4153
- icon: input.icon,
4154
- domain: input.domain,
4155
- defaultOrder: input.defaultOrder,
4156
- surfaces: input.surfaces,
4157
- verticalId: input.verticalId,
4158
- permission: input.permission,
4159
- component: input.component,
4160
- props: input.props
4161
- };
4162
- }
4163
- var defineKpiWidget = (input) => build("kpi", 1, input);
4164
- var defineChartWidget = (input) => build("chart", 2, input);
4165
- var defineTableWidget = (input) => build("table", 4, input);
4166
- var defineOnboardingWidget = (input) => build("onboarding", 2, input);
4167
- var defineCustomWidget = (input) => build("custom", 2, input);
4168
47
 
4169
- export { AppShell, Avatar, AvatarFallback, AvatarImage, BackStyleProvider, Badge, Breadcrumb, Button, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, ChartWidget, Checkbox, ConfirmDialog, CurrencyInput, DEFAULT_RANGE_OPTIONS, DashboardCanvas, DashboardCustomizeMenu, DashboardGrid, DashboardNavigateProvider, DashboardRangeControl, DashboardRangeProvider, DataTable, DatePicker, Dropdown, DropdownCheckboxItem, DropdownContent, DropdownGroup, DropdownItem, DropdownLabel, DropdownPortal, DropdownRadioGroup, DropdownRadioItem, DropdownSeparator, DropdownShortcut, DropdownSub, DropdownSubContent, DropdownSubTrigger, DropdownTrigger, ICON_MAP, Input, KpiCard, ListView, Modal, ModalBody, ModalClose, ModalContent, ModalDescription, ModalFooter, ModalHeader, ModalOverlay, ModalPortal, ModalTitle, ModalTrigger, ModuleLayoutProvider, ModulePage, NavTransitionProvider, OnboardingWidget, PageHeaderActions, PageTransition, Popover, PopoverContent, PopoverTrigger, SaveBar, SaveBarProvider, SearchSelect, SegmentedControl, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, Separator3 as Separator, Sheet, SheetBody, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetOverlay, SheetPortal, SheetTitle, SheetTrigger, Sidebar, Skeleton, SubpageHeader, TableSkeleton, TableWidget, Tabs, TabsContent, TabsList, TabsTrigger, ThemeProvider, TimePicker, ToastProvider, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, Topbar, WidgetGrid, badgeVariants, buttonVariants, cn, createFayzTheme, createTheme, darkTheme, defineChartWidget, defineCustomWidget, defineKpiWidget, defineOnboardingWidget, defineTableWidget, fayzThemePresets, formatKpiValue, lightTheme, renderIcon, resolveDashboardLayout, useBackHandler, useBackStyle, useDashboardNavigate, useDashboardPreferences, useDashboardRange, useLayoutStore, useModuleHeaderActionsSlot, useModuleHeaderSlot, useModuleLayout, useNavTransition, useSaveBar, useTheme, useThemeStore };
48
+ export { Avatar, AvatarFallback, AvatarImage };
4170
49
  //# sourceMappingURL=index.js.map
4171
50
  //# sourceMappingURL=index.js.map