@fayz-ai/ui 0.12.0 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/dist/analytics/AnalyticsControls.d.ts +5 -1
  2. package/dist/analytics/AnalyticsControls.d.ts.map +1 -1
  3. package/dist/{chunk-JGJNRNQW.js → chunk-2EFRFYGF.js} +117 -41
  4. package/dist/chunk-2EFRFYGF.js.map +1 -0
  5. package/dist/{chunk-7BMXN7MP.js → chunk-46UZAKFJ.js} +24 -24
  6. package/dist/chunk-46UZAKFJ.js.map +1 -0
  7. package/dist/chunk-5KAGNW45.js +152 -0
  8. package/dist/chunk-5KAGNW45.js.map +1 -0
  9. package/dist/{chunk-7BHG3XGJ.js → chunk-77CUIMOI.js} +201 -119
  10. package/dist/chunk-77CUIMOI.js.map +1 -0
  11. package/dist/chunk-LFZFBN3P.js +142 -0
  12. package/dist/chunk-LFZFBN3P.js.map +1 -0
  13. package/dist/{chunk-ZDRHCKRK.js → chunk-MVH64JFG.js} +1029 -767
  14. package/dist/chunk-MVH64JFG.js.map +1 -0
  15. package/dist/{chunk-22EVFMS3.js → chunk-UZYXFEQS.js} +4 -3
  16. package/dist/chunk-UZYXFEQS.js.map +1 -0
  17. package/dist/dashboard/DashboardCanvas.d.ts.map +1 -1
  18. package/dist/dashboard/DashboardCustomizeMenu.d.ts.map +1 -1
  19. package/dist/dashboard/DashboardRange.d.ts +8 -2
  20. package/dist/dashboard/DashboardRange.d.ts.map +1 -1
  21. package/dist/dashboard/index.js +4 -5
  22. package/dist/icons.js +1 -1
  23. package/dist/index.d.ts +3 -3
  24. package/dist/index.d.ts.map +1 -1
  25. package/dist/index.js +69 -96
  26. package/dist/index.js.map +1 -1
  27. package/dist/layout/AppShell.d.ts +1 -0
  28. package/dist/layout/AppShell.d.ts.map +1 -1
  29. package/dist/layout/ModulePage.d.ts +24 -1
  30. package/dist/layout/ModulePage.d.ts.map +1 -1
  31. package/dist/layout/Sidebar.d.ts.map +1 -1
  32. package/dist/layout/index.d.ts +2 -1
  33. package/dist/layout/index.d.ts.map +1 -1
  34. package/dist/layout/index.js +4 -5
  35. package/dist/layout/rail-flyout.d.ts +10 -0
  36. package/dist/layout/rail-flyout.d.ts.map +1 -1
  37. package/dist/primitives/bulk-action-bar.d.ts +3 -1
  38. package/dist/primitives/bulk-action-bar.d.ts.map +1 -1
  39. package/dist/primitives/header-tabs.d.ts +36 -6
  40. package/dist/primitives/header-tabs.d.ts.map +1 -1
  41. package/dist/primitives/index.d.ts +2 -1
  42. package/dist/primitives/index.d.ts.map +1 -1
  43. package/dist/primitives/index.js +5 -4
  44. package/dist/primitives/list-view.d.ts +3 -1
  45. package/dist/primitives/list-view.d.ts.map +1 -1
  46. package/dist/primitives/page-toolbar.d.ts +61 -0
  47. package/dist/primitives/page-toolbar.d.ts.map +1 -0
  48. package/dist/primitives/search-select.d.ts +3 -1
  49. package/dist/primitives/search-select.d.ts.map +1 -1
  50. package/dist/primitives/tabs.d.ts.map +1 -1
  51. package/dist/stores/index.d.ts +1 -1
  52. package/dist/stores/index.d.ts.map +1 -1
  53. package/dist/stores/page-header.store.d.ts +26 -2
  54. package/dist/stores/page-header.store.d.ts.map +1 -1
  55. package/dist/styles.css +54 -17
  56. package/package.json +5 -5
  57. package/dist/chunk-22EVFMS3.js.map +0 -1
  58. package/dist/chunk-7BHG3XGJ.js.map +0 -1
  59. package/dist/chunk-7BMXN7MP.js.map +0 -1
  60. package/dist/chunk-JGJNRNQW.js.map +0 -1
  61. package/dist/chunk-OKZ6WQB3.js +0 -57
  62. package/dist/chunk-OKZ6WQB3.js.map +0 -1
  63. package/dist/chunk-VAKWUUVC.js +0 -425
  64. package/dist/chunk-VAKWUUVC.js.map +0 -1
  65. package/dist/chunk-ZBQ772DU.js +0 -17
  66. package/dist/chunk-ZBQ772DU.js.map +0 -1
  67. package/dist/chunk-ZDRHCKRK.js.map +0 -1
@@ -1,40 +1,720 @@
1
- import { useHasPageHeaderHost, useCrossModuleBack, useBackHandler, PageHeaderCrumb, usePageHeader, PageHeaderOutlet, Sheet, SheetContent, SheetHeader, SheetTitle, SheetDescription, SheetBody, HeaderTabs, usePageHeaderSlot, useHasPageHeaderOutlet, usePageHeaderHost, SaveBarProvider, SaveBar } from './chunk-VAKWUUVC.js';
2
- import { useLayoutStore } from './chunk-ZBQ772DU.js';
3
- import { cn, Button } from './chunk-OKZ6WQB3.js';
4
- import { ICON_MAP } from './chunk-22EVFMS3.js';
5
- import * as React from 'react';
6
- import { PanelLeftClose, PanelLeft, Search, Bell, Menu, Crown, Repeat, GraduationCap, Newspaper, Clapperboard, Zap, Star, UserCheck, Inbox, Clock3, Ban, Map, ListPlus, CalendarX, CalendarCheck2, Mic, UsersRound, Disc3, ListMusic, Eye, Music, Layers, Banknote, BadgeDollarSign, CalendarDays, Radio, PartyPopper, FileCheck2, Box, Boxes, TreePalm, UserX, UserPlus, ArrowDownRight, ArrowUpRight, Ruler, Warehouse, Wallet, Sparkles, AlertTriangle, Clock, CircleDollarSign, TrendingUp, Receipt, Landmark, TableProperties, SlidersHorizontal, Plug, ArrowLeftRight, ArrowUpCircle, ArrowDownCircle, Wheat, Egg, Apple, LeafyGreen, LayoutTemplate, Heart, PawPrint, Cat, Dog, FolderOpen, ListChecks, List, Shield, Plus, Filter, ChevronDown, Building2, Contact, Handshake, MapPin, UtensilsCrossed, Camera, Tags, Tag, Percent, BookOpenCheck, Globe, MessageCircle, BookOpen, UserCog, Briefcase, ClipboardCheck, ClipboardList, Wrench, Target, ShoppingBag, ShoppingCart, Megaphone, DollarSign, Mail, FileText, BarChart3, Activity, Package, CalendarClock, Calendar, CreditCard, Settings, Users, Home, ChevronRight, ChevronLeft, PanelRightOpen, PanelRightClose, PanelRight, ExternalLink, PiggyBank, LogOut } from 'lucide-react';
7
- import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
1
+ import { cn, useHasPageHeaderHost, usePageHeader, useLayoutStore, useRegisterPageActions, useHasPageHeaderBack, usePageHeaderSlot, useHasPageHeaderOutlet, useHasPageActions, usePageHeaderHost } from './chunk-LFZFBN3P.js';
2
+ import { ICON_MAP } from './chunk-UZYXFEQS.js';
3
+ import * as React7 from 'react';
4
+ import * as DialogPrimitive from '@radix-ui/react-dialog';
5
+ import { X, Loader2, ArrowLeft, Menu, Search, Crown, PanelLeftClose, PanelLeft, Bell, Repeat, GraduationCap, Newspaper, Clapperboard, Zap, Star, UserCheck, Inbox, Clock3, Ban, Map, ListPlus, CalendarX, CalendarCheck2, Mic, UsersRound, Disc3, ListMusic, Eye, Music, Layers, Banknote, BadgeDollarSign, CalendarDays, Radio, PartyPopper, FileCheck2, Box, Boxes, TreePalm, UserX, UserPlus, ArrowDownRight, ArrowUpRight, Ruler, Warehouse, Wallet, Sparkles, AlertTriangle, Clock, CircleDollarSign, TrendingUp, Receipt, Landmark, TableProperties, SlidersHorizontal, Plug, ArrowLeftRight, ArrowUpCircle, ArrowDownCircle, Wheat, Egg, Apple, LeafyGreen, LayoutTemplate, Heart, PawPrint, Cat, Dog, FolderOpen, ListChecks, List, Shield, Plus, Filter, ChevronDown, Building2, Contact, Handshake, MapPin, UtensilsCrossed, Camera, Tags, Tag, Percent, BookOpenCheck, Globe, MessageCircle, BookOpen, UserCog, Briefcase, ClipboardCheck, ClipboardList, Wrench, Target, ShoppingBag, ShoppingCart, Megaphone, DollarSign, Mail, FileText, BarChart3, Activity, Package, CalendarClock, Calendar, CreditCard, Settings, Users, Home, ChevronRight, ChevronLeft, ExternalLink, PiggyBank, LogOut } from 'lucide-react';
6
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
7
+ import { useTranslation, useNavReferrer, routeModule, navHistoryBack } from '@fayz-ai/core';
8
8
  import * as Popover from '@radix-ui/react-popover';
9
9
  import { createPortal } from 'react-dom';
10
- import { useTranslation } from '@fayz-ai/core';
11
10
 
11
+ var STYLE_ID = "saas-sheet-styles";
12
+ if (typeof document !== "undefined" && !document.getElementById(STYLE_ID)) {
13
+ const style = document.createElement("style");
14
+ style.id = STYLE_ID;
15
+ style.textContent = `
16
+ @keyframes saas-sheet-fade-in { from { opacity: 0 } to { opacity: 1 } }
17
+ @keyframes saas-sheet-fade-out { from { opacity: 1 } to { opacity: 0 } }
18
+ @keyframes saas-sheet-slide-in { from { transform: translateX(100%) } to { transform: translateX(0) } }
19
+ @keyframes saas-sheet-slide-out { from { transform: translateX(0) } to { transform: translateX(100%) } }
20
+ .saas-sheet-overlay[data-state="open"] { animation: saas-sheet-fade-in 200ms ease-out }
21
+ .saas-sheet-overlay[data-state="closed"] { animation: saas-sheet-fade-out 150ms ease-in }
22
+ .saas-sheet-content[data-state="open"] { animation: saas-sheet-slide-in 280ms cubic-bezier(0.16, 1, 0.3, 1) }
23
+ .saas-sheet-content[data-state="closed"] { animation: saas-sheet-slide-out 200ms ease-in }
24
+ `;
25
+ document.head.appendChild(style);
26
+ }
27
+ var Sheet = DialogPrimitive.Root;
28
+ var SheetTrigger = DialogPrimitive.Trigger;
29
+ var SheetClose = DialogPrimitive.Close;
30
+ var SheetPortal = DialogPrimitive.Portal;
31
+ var SheetOverlay = React7.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(
32
+ DialogPrimitive.Overlay,
33
+ {
34
+ ref,
35
+ className: cn(
36
+ "saas-sheet-overlay fixed inset-0 z-50 bg-black/40 backdrop-blur-[2px]",
37
+ className
38
+ ),
39
+ ...props
40
+ }
41
+ ));
42
+ SheetOverlay.displayName = "SheetOverlay";
43
+ var SheetContent = React7.forwardRef(({ className, children, width, hideClose, overlay = "blur", ...props }, ref) => /* @__PURE__ */ jsxs(SheetPortal, { children: [
44
+ /* @__PURE__ */ jsx(SheetOverlay, { className: overlay === "none" ? "bg-transparent backdrop-blur-0" : overlay === "dim" ? "backdrop-blur-0" : void 0 }),
45
+ /* @__PURE__ */ jsxs(
46
+ DialogPrimitive.Content,
47
+ {
48
+ ref,
49
+ className: cn(
50
+ "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",
51
+ "w-full",
52
+ width ?? "max-w-md",
53
+ className
54
+ ),
55
+ ...props,
56
+ children: [
57
+ children,
58
+ !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: [
59
+ /* @__PURE__ */ jsx(X, { className: "h-4 w-4" }),
60
+ /* @__PURE__ */ jsx("span", { className: "sr-only", children: "Close" })
61
+ ] })
62
+ ]
63
+ }
64
+ )
65
+ ] }));
66
+ SheetContent.displayName = "SheetContent";
67
+ function SheetHeader({ className, ...props }) {
68
+ return /* @__PURE__ */ jsx(
69
+ "div",
70
+ {
71
+ className: cn("flex flex-col gap-1 px-5 py-4 border-b shrink-0", className),
72
+ ...props
73
+ }
74
+ );
75
+ }
76
+ SheetHeader.displayName = "SheetHeader";
77
+ function SheetFooter({ className, ...props }) {
78
+ return /* @__PURE__ */ jsx(
79
+ "div",
80
+ {
81
+ className: cn("flex items-center gap-2 px-5 py-3 border-t shrink-0", className),
82
+ ...props
83
+ }
84
+ );
85
+ }
86
+ SheetFooter.displayName = "SheetFooter";
87
+ var SheetTitle = React7.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(
88
+ DialogPrimitive.Title,
89
+ {
90
+ ref,
91
+ className: cn("text-sm font-semibold leading-none tracking-tight", className),
92
+ ...props
93
+ }
94
+ ));
95
+ SheetTitle.displayName = "SheetTitle";
96
+ var SheetDescription = React7.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(
97
+ DialogPrimitive.Description,
98
+ {
99
+ ref,
100
+ className: cn("text-xs text-muted-foreground", className),
101
+ ...props
102
+ }
103
+ ));
104
+ SheetDescription.displayName = "SheetDescription";
105
+ function SheetBody({ className, ...props }) {
106
+ return /* @__PURE__ */ jsx(
107
+ "div",
108
+ {
109
+ className: cn("flex-1 overflow-y-auto px-5 py-5", className),
110
+ ...props
111
+ }
112
+ );
113
+ }
114
+ SheetBody.displayName = "SheetBody";
115
+ var SaveBarApiContext = React7.createContext(null);
116
+ var SaveBarStateContext = React7.createContext({ visible: false, saving: false });
117
+ var _saveBarVisible = false;
118
+ var _saveBarSubs = /* @__PURE__ */ new Set();
119
+ function setSaveBarVisibleSignal(v) {
120
+ if (v === _saveBarVisible) return;
121
+ _saveBarVisible = v;
122
+ _saveBarSubs.forEach((fn) => fn());
123
+ }
124
+ function useSaveBarVisible() {
125
+ return React7.useSyncExternalStore(
126
+ (cb) => {
127
+ _saveBarSubs.add(cb);
128
+ return () => {
129
+ _saveBarSubs.delete(cb);
130
+ };
131
+ },
132
+ () => _saveBarVisible,
133
+ () => false
134
+ );
135
+ }
136
+ function SaveBarProvider({ children }) {
137
+ const [state, setState] = React7.useState({ visible: false, saving: false });
138
+ const handlers = React7.useRef({});
139
+ const api = React7.useMemo(() => ({ setState, handlers }), []);
140
+ const backStack = React7.useRef([]);
141
+ const backApi = React7.useMemo(() => ({ stack: backStack }), []);
142
+ React7.useEffect(() => {
143
+ setSaveBarVisibleSignal(state.visible);
144
+ }, [state.visible]);
145
+ React7.useEffect(() => {
146
+ function onKey(e) {
147
+ if (state.visible && !state.saving) {
148
+ if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) {
149
+ e.preventDefault();
150
+ handlers.current.onSave?.();
151
+ } else if (e.key === "Escape") {
152
+ e.preventDefault();
153
+ handlers.current.onDiscard?.();
154
+ }
155
+ return;
156
+ }
157
+ if (e.key === "Escape" && !e.defaultPrevented && !isEditableTarget(e.target)) {
158
+ for (let i = backStack.current.length - 1; i >= 0; i--) {
159
+ const back = backStack.current[i].current;
160
+ if (back) {
161
+ e.preventDefault();
162
+ back();
163
+ return;
164
+ }
165
+ }
166
+ }
167
+ }
168
+ window.addEventListener("keydown", onKey);
169
+ return () => window.removeEventListener("keydown", onKey);
170
+ }, [state.visible, state.saving]);
171
+ return /* @__PURE__ */ jsx(SaveBarApiContext.Provider, { value: api, children: /* @__PURE__ */ jsx(BackNavApiContext.Provider, { value: backApi, children: /* @__PURE__ */ jsx(SaveBarStateContext.Provider, { value: state, children }) }) });
172
+ }
173
+ var BackNavApiContext = React7.createContext(null);
174
+ function isEditableTarget(target) {
175
+ const el = target;
176
+ if (!el || !el.tagName) return false;
177
+ const tag = el.tagName.toLowerCase();
178
+ return tag === "input" || tag === "textarea" || tag === "select" || el.isContentEditable;
179
+ }
180
+ function useBackHandler(onBack) {
181
+ const api = React7.useContext(BackNavApiContext);
182
+ const ref = React7.useRef(onBack);
183
+ React7.useEffect(() => {
184
+ ref.current = onBack;
185
+ });
186
+ React7.useEffect(() => {
187
+ if (!api) return;
188
+ const stack = api.stack.current;
189
+ stack.push(ref);
190
+ return () => {
191
+ const i = stack.indexOf(ref);
192
+ if (i !== -1) stack.splice(i, 1);
193
+ };
194
+ }, [api]);
195
+ }
196
+ function useSaveBar({ dirty, saving = false, onSave, onDiscard, message, saveLabel, discardLabel }) {
197
+ const api = React7.useContext(SaveBarApiContext);
198
+ React7.useEffect(() => {
199
+ if (api) api.handlers.current = { onSave, onDiscard };
200
+ });
201
+ React7.useEffect(() => {
202
+ if (!api) return;
203
+ api.setState({ visible: dirty, saving, message, saveLabel, discardLabel });
204
+ return () => api.setState((s) => ({ ...s, visible: false }));
205
+ }, [api, dirty, saving, message, saveLabel, discardLabel]);
206
+ }
207
+ function SaveBar({ className }) {
208
+ const state = React7.useContext(SaveBarStateContext);
209
+ const api = React7.useContext(SaveBarApiContext);
210
+ const t = useTranslation();
211
+ const [rendered, setRendered] = React7.useState(state.visible);
212
+ const [closing, setClosing] = React7.useState(false);
213
+ React7.useEffect(() => {
214
+ if (state.visible) {
215
+ setRendered(true);
216
+ setClosing(false);
217
+ return;
218
+ }
219
+ if (!rendered) return;
220
+ setClosing(true);
221
+ const id = setTimeout(() => {
222
+ setRendered(false);
223
+ setClosing(false);
224
+ }, 200);
225
+ return () => clearTimeout(id);
226
+ }, [state.visible, rendered]);
227
+ if (!api || !rendered) return null;
228
+ const { saving, message, saveLabel, discardLabel } = state;
229
+ 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(
230
+ "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",
231
+ closing ? "animate-out fade-out slide-out-to-bottom-2" : "animate-in fade-in slide-in-from-bottom-2"
232
+ ), children: [
233
+ /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-2 text-sm font-medium", children: [
234
+ /* @__PURE__ */ jsx("span", { className: "h-2 w-2 shrink-0 rounded-full bg-amber-400" }),
235
+ message ?? t("saveBar.unsaved")
236
+ ] }),
237
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
238
+ /* @__PURE__ */ jsx(
239
+ "button",
240
+ {
241
+ type: "button",
242
+ disabled: saving,
243
+ onClick: () => api.handlers.current.onDiscard?.(),
244
+ 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",
245
+ children: discardLabel ?? t("common.discard")
246
+ }
247
+ ),
248
+ /* @__PURE__ */ jsxs(
249
+ "button",
250
+ {
251
+ type: "button",
252
+ disabled: saving,
253
+ onClick: () => api.handlers.current.onSave?.(),
254
+ 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",
255
+ children: [
256
+ saving && /* @__PURE__ */ jsx(Loader2, { className: "mr-1.5 h-3.5 w-3.5 animate-spin" }),
257
+ saveLabel ?? t("common.save")
258
+ ]
259
+ }
260
+ )
261
+ ] })
262
+ ] }) });
263
+ }
264
+ function useCrossModuleBack(onBack, parentLabel) {
265
+ const currentPath = typeof window !== "undefined" ? (window.location.hash ? window.location.hash.slice(1) : window.location.pathname) || "/" : "/";
266
+ const referrer = useNavReferrer(currentPath);
267
+ const cross = referrer && referrer.label && routeModule(referrer.path) !== routeModule(currentPath) ? referrer : null;
268
+ return {
269
+ onBack: cross ? () => navHistoryBack(onBack) : onBack,
270
+ parentLabel: cross ? cross.label : parentLabel,
271
+ isCrossModule: !!cross
272
+ };
273
+ }
274
+ function PageHeaderCrumb({
275
+ title,
276
+ parentLabel,
277
+ onBack
278
+ }) {
279
+ if (!onBack) {
280
+ return title ? /* @__PURE__ */ jsx("h2", { className: "min-w-0 truncate text-lg font-semibold", children: title }) : null;
281
+ }
282
+ return /* @__PURE__ */ jsxs("nav", { className: "flex min-w-0 items-center gap-1.5 text-sm text-muted-foreground", children: [
283
+ /* @__PURE__ */ jsxs(
284
+ "button",
285
+ {
286
+ type: "button",
287
+ onClick: onBack,
288
+ className: "inline-flex shrink-0 items-center gap-1 transition-colors hover:text-foreground",
289
+ children: [
290
+ /* @__PURE__ */ jsx(ArrowLeft, { className: "h-3.5 w-3.5" }),
291
+ parentLabel ?? "Voltar"
292
+ ]
293
+ }
294
+ ),
295
+ title != null && /* @__PURE__ */ jsxs(Fragment, { children: [
296
+ /* @__PURE__ */ jsx("span", { className: "shrink-0", children: "/" }),
297
+ title === "" ? (
298
+ // Title still waiting on data: a pulsing bar, otherwise the line
299
+ // appears from nowhere and shoves the rest when the record lands.
300
+ /* @__PURE__ */ jsx("span", { className: "inline-block h-4 w-24 animate-pulse rounded bg-muted align-middle" })
301
+ ) : /* @__PURE__ */ jsx("span", { className: "min-w-0 truncate font-medium text-foreground", children: title })
302
+ ] })
303
+ ] });
304
+ }
305
+ function PageHeader({ title, onBack, parentLabel, backLabel }) {
306
+ const hasHost = useHasPageHeaderHost();
307
+ const { onBack: effectiveOnBack, parentLabel: effectiveParent } = useCrossModuleBack(onBack, parentLabel);
308
+ useBackHandler(effectiveOnBack);
309
+ const node = React7.useMemo(() => /* @__PURE__ */ jsx(
310
+ PageHeaderCrumb,
311
+ {
312
+ title,
313
+ parentLabel: effectiveParent ?? backLabel,
314
+ onBack: effectiveOnBack
315
+ }
316
+ ), [effectiveOnBack, effectiveParent, backLabel, title]);
317
+ usePageHeader(hasHost ? node : null, { back: !!effectiveOnBack });
318
+ if (hasHost) return null;
319
+ return /* @__PURE__ */ jsx("div", { className: "mb-4", children: node });
320
+ }
321
+ function TopbarUserMenu({
322
+ user,
323
+ userPlan,
324
+ billingLabel = "Subscription",
325
+ onSignOut,
326
+ onProfile,
327
+ onSettings,
328
+ onBilling,
329
+ userMenuExtras
330
+ }) {
331
+ const [open, setOpen] = React7.useState(false);
332
+ if (!user) return null;
333
+ const initial = (user.fullName?.trim()?.[0] ?? user.email?.[0] ?? "?").toUpperCase();
334
+ const item = (label, onClick, Icon) => onClick ? /* @__PURE__ */ jsxs(
335
+ "button",
336
+ {
337
+ onClick: () => {
338
+ setOpen(false);
339
+ onClick();
340
+ },
341
+ 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",
342
+ children: [
343
+ Icon && /* @__PURE__ */ jsx(Icon, { className: "h-4 w-4 text-muted-foreground" }),
344
+ /* @__PURE__ */ jsx("span", { children: label })
345
+ ]
346
+ },
347
+ label
348
+ ) : null;
349
+ return /* @__PURE__ */ jsxs(Popover.Root, { open, onOpenChange: setOpen, children: [
350
+ /* @__PURE__ */ jsx(Popover.Trigger, { asChild: true, children: /* @__PURE__ */ jsx(
351
+ "button",
352
+ {
353
+ "aria-label": "User menu",
354
+ 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",
355
+ children: user.avatarUrl ? (
356
+ // eslint-disable-next-line @next/next/no-img-element
357
+ /* @__PURE__ */ jsx("img", { src: user.avatarUrl, alt: "", className: "h-full w-full object-cover" })
358
+ ) : initial
359
+ }
360
+ ) }),
361
+ /* @__PURE__ */ jsx(Popover.Portal, { children: /* @__PURE__ */ jsxs(
362
+ Popover.Content,
363
+ {
364
+ align: "end",
365
+ sideOffset: 8,
366
+ 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",
367
+ children: [
368
+ /* @__PURE__ */ jsxs("div", { className: "border-b border-border px-3 py-2", children: [
369
+ /* @__PURE__ */ jsx("p", { className: "truncate text-sm font-medium text-foreground", children: user.fullName || user.email }),
370
+ /* @__PURE__ */ jsx("p", { className: "truncate text-xs text-muted-foreground", children: user.email })
371
+ ] }),
372
+ /* @__PURE__ */ jsxs("div", { className: "mt-1 space-y-0.5", children: [
373
+ item("Perfil", onProfile, UserCog),
374
+ item("Configura\xE7\xF5es", onSettings, Settings),
375
+ onBilling && /* @__PURE__ */ jsxs(
376
+ "button",
377
+ {
378
+ onClick: () => {
379
+ setOpen(false);
380
+ onBilling();
381
+ },
382
+ 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",
383
+ children: [
384
+ /* @__PURE__ */ jsx(CreditCard, { className: "h-4 w-4 text-muted-foreground" }),
385
+ /* @__PURE__ */ jsx("span", { className: "flex-1 text-left", children: billingLabel }),
386
+ userPlan && /* @__PURE__ */ jsxs(
387
+ "span",
388
+ {
389
+ className: userPlan.paid ? "inline-flex items-center gap-1 rounded-full border border-amber-400/40 bg-amber-400/15 px-2 py-0.5 text-[10px] font-semibold text-amber-600 dark:text-amber-300" : "inline-flex items-center gap-1 rounded-full border border-border bg-muted px-2 py-0.5 text-[10px] font-medium text-muted-foreground",
390
+ children: [
391
+ userPlan.paid ? /* @__PURE__ */ jsx(Crown, { className: "h-3 w-3" }) : /* @__PURE__ */ jsx(Sparkles, { className: "h-3 w-3" }),
392
+ userPlan.label
393
+ ]
394
+ }
395
+ )
396
+ ]
397
+ },
398
+ "billing"
399
+ ),
400
+ userMenuExtras?.map((e) => item(e.label, e.onClick)),
401
+ onSignOut && /* @__PURE__ */ jsxs(Fragment, { children: [
402
+ /* @__PURE__ */ jsx("div", { className: "my-1 border-t border-border" }),
403
+ item("Sair", onSignOut, LogOut)
404
+ ] })
405
+ ] })
406
+ ]
407
+ }
408
+ ) })
409
+ ] });
410
+ }
411
+ var ICON_MAP2 = {
412
+ Home,
413
+ Users,
414
+ Settings,
415
+ CreditCard,
416
+ Bell,
417
+ Calendar,
418
+ CalendarClock,
419
+ Package,
420
+ Activity,
421
+ BarChart3,
422
+ FileText,
423
+ Mail,
424
+ DollarSign,
425
+ Megaphone,
426
+ ShoppingCart,
427
+ ShoppingBag,
428
+ Target,
429
+ Wrench,
430
+ ClipboardList,
431
+ ClipboardCheck,
432
+ Briefcase,
433
+ UserCog,
434
+ BookOpen,
435
+ MessageCircle,
436
+ Globe,
437
+ BookOpenCheck,
438
+ Percent,
439
+ Tag,
440
+ Tags,
441
+ Camera,
442
+ UtensilsCrossed,
443
+ Search,
444
+ MapPin,
445
+ Handshake,
446
+ Contact,
447
+ Building2,
448
+ ChevronDown,
449
+ Filter,
450
+ Plus,
451
+ Shield,
452
+ List,
453
+ ListChecks,
454
+ FolderOpen,
455
+ Dog,
456
+ Cat,
457
+ PawPrint,
458
+ Heart,
459
+ LayoutTemplate,
460
+ LeafyGreen,
461
+ Apple,
462
+ Egg,
463
+ Wheat,
464
+ ArrowDownCircle,
465
+ ArrowUpCircle,
466
+ ArrowLeftRight,
467
+ Plug,
468
+ SlidersHorizontal,
469
+ TableProperties,
470
+ Landmark,
471
+ Receipt,
472
+ TrendingUp,
473
+ CircleDollarSign,
474
+ Clock,
475
+ AlertTriangle,
476
+ Sparkles,
477
+ Wallet,
478
+ Warehouse,
479
+ Ruler,
480
+ ArrowUpRight,
481
+ ArrowDownRight,
482
+ UserPlus,
483
+ UserX,
484
+ TreePalm,
485
+ Boxes,
486
+ Box,
487
+ FileCheck2,
488
+ PartyPopper,
489
+ Radio,
490
+ CalendarDays,
491
+ BadgeDollarSign,
492
+ Banknote,
493
+ Layers,
494
+ Music,
495
+ Eye,
496
+ ListMusic,
497
+ Disc3,
498
+ UsersRound,
499
+ Mic,
500
+ CalendarCheck2,
501
+ CalendarX,
502
+ ListPlus,
503
+ Map,
504
+ Ban,
505
+ Clock3,
506
+ Inbox,
507
+ UserCheck,
508
+ Star,
509
+ Zap,
510
+ Clapperboard,
511
+ Newspaper,
512
+ GraduationCap,
513
+ Repeat
514
+ };
515
+ function getIcon(name) {
516
+ return ICON_MAP2[name] ?? Home;
517
+ }
518
+ function NavDropdown({ item, currentPath, onNavigate }) {
519
+ const [open, setOpen] = React7.useState(false);
520
+ const Icon = getIcon(item.icon);
521
+ const isChildActive = item.children?.some(
522
+ (child) => currentPath === child.route || currentPath.startsWith(child.route + "/")
523
+ );
524
+ return /* @__PURE__ */ jsxs(Popover.Root, { open, onOpenChange: setOpen, children: [
525
+ /* @__PURE__ */ jsx(Popover.Trigger, { asChild: true, children: /* @__PURE__ */ jsxs(
526
+ "button",
527
+ {
528
+ "data-nav-active": isChildActive || void 0,
529
+ className: cn(
530
+ "flex items-center gap-1.5 whitespace-nowrap border-b-2 px-3 py-2 text-sm transition-colors",
531
+ isChildActive ? "border-sidebar-accent-foreground font-semibold text-sidebar-foreground" : "border-transparent text-sidebar-muted hover:text-sidebar-foreground"
532
+ ),
533
+ children: [
534
+ /* @__PURE__ */ jsx(Icon, { className: "h-4 w-4" }),
535
+ /* @__PURE__ */ jsx("span", { children: item.label }),
536
+ /* @__PURE__ */ jsx(ChevronDown, { className: cn("h-3 w-3 transition-transform", open && "rotate-180") })
537
+ ]
538
+ }
539
+ ) }),
540
+ /* @__PURE__ */ jsx(Popover.Portal, { children: /* @__PURE__ */ jsx(
541
+ Popover.Content,
542
+ {
543
+ align: "start",
544
+ sideOffset: 4,
545
+ 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",
546
+ children: item.children?.map((child) => {
547
+ const ChildIcon = getIcon(child.icon);
548
+ const isActive = currentPath === child.route || currentPath.startsWith(child.route + "/");
549
+ return /* @__PURE__ */ jsxs(
550
+ "button",
551
+ {
552
+ onClick: () => {
553
+ onNavigate(child.route);
554
+ setOpen(false);
555
+ },
556
+ className: cn(
557
+ "flex w-full items-center gap-2.5 rounded-lg px-3 py-2 text-sm transition-colors",
558
+ isActive ? "bg-muted font-medium text-foreground" : "text-popover-foreground hover:bg-muted/70"
559
+ ),
560
+ children: [
561
+ /* @__PURE__ */ jsx(ChildIcon, { className: "h-4 w-4 text-muted-foreground" }),
562
+ /* @__PURE__ */ jsx("span", { children: child.label })
563
+ ]
564
+ },
565
+ child.id
566
+ );
567
+ })
568
+ }
569
+ ) })
570
+ ] });
571
+ }
572
+ function Topbar({
573
+ navigation,
574
+ logo,
575
+ user,
576
+ onMenuClick,
577
+ orgSwitcher,
578
+ leftContent,
579
+ rightContent,
580
+ currentPath = "/",
581
+ onNavigate,
582
+ searchPlaceholder = "Search...",
583
+ onSignOut,
584
+ onProfile,
585
+ onSettings,
586
+ onBilling,
587
+ userPlan,
588
+ billingLabel,
589
+ userMenuExtras
590
+ }) {
591
+ const setCommandPaletteOpen = useLayoutStore((s) => s.setCommandPaletteOpen);
592
+ const mainNav = navigation.filter((item) => item.section === "main");
593
+ const secondaryNav = navigation.filter((item) => item.section === "secondary");
594
+ const allNav = [...mainNav, ...secondaryNav];
595
+ const handleNavigate = (route) => {
596
+ onNavigate?.(route);
597
+ };
598
+ return /* @__PURE__ */ jsxs("header", { "data-print": "hide", className: "sticky top-0 z-50 w-full shrink-0", children: [
599
+ /* @__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: [
600
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
601
+ onMenuClick && /* @__PURE__ */ jsx(
602
+ "button",
603
+ {
604
+ onClick: onMenuClick,
605
+ 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",
606
+ "aria-label": "Open menu",
607
+ children: /* @__PURE__ */ jsx(Menu, { className: "h-5 w-5" })
608
+ }
609
+ ),
610
+ /* @__PURE__ */ jsx("div", { className: "hidden shrink-0 items-center md:flex", children: logo ?? /* @__PURE__ */ jsx("span", { className: "text-lg font-bold", children: "App" }) })
611
+ ] }),
612
+ /* @__PURE__ */ jsx("div", { className: "mx-8 hidden max-w-sm flex-1 md:flex", children: /* @__PURE__ */ jsxs(
613
+ "button",
614
+ {
615
+ onClick: () => setCommandPaletteOpen(true),
616
+ 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:border-sidebar-foreground/25 hover:bg-sidebar-foreground/10 hover:text-sidebar-foreground",
617
+ children: [
618
+ /* @__PURE__ */ jsx(Search, { className: "absolute left-2.5 top-1/2 h-3.5 w-3.5 -translate-y-1/2 text-sidebar-muted" }),
619
+ /* @__PURE__ */ jsx("span", { className: "text-xs", children: searchPlaceholder }),
620
+ /* @__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" })
621
+ ]
622
+ }
623
+ ) }),
624
+ /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 items-center gap-1", children: [
625
+ orgSwitcher && /* @__PURE__ */ jsx("div", { className: "hidden min-w-0 items-center md:flex", children: orgSwitcher }),
626
+ leftContent,
627
+ rightContent,
628
+ /* @__PURE__ */ jsx(
629
+ TopbarUserMenu,
630
+ {
631
+ user,
632
+ userPlan,
633
+ billingLabel,
634
+ onSignOut,
635
+ onProfile,
636
+ onSettings,
637
+ onBilling,
638
+ userMenuExtras
639
+ }
640
+ )
641
+ ] })
642
+ ] }) }),
643
+ /* @__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) => {
644
+ if (!el) return;
645
+ const active = el.querySelector('[data-nav-active="true"]');
646
+ if (active) {
647
+ const left = active.offsetLeft - el.offsetWidth / 2 + active.offsetWidth / 2;
648
+ el.scrollTo({ left: Math.max(0, left), behavior: "smooth" });
649
+ }
650
+ }, className: "flex items-center gap-0.5 overflow-x-auto scrollbar-none", children: allNav.map((item) => {
651
+ if (item.children && item.children.length > 0) {
652
+ return /* @__PURE__ */ jsx(
653
+ NavDropdown,
654
+ {
655
+ item,
656
+ currentPath,
657
+ onNavigate: handleNavigate
658
+ },
659
+ item.id
660
+ );
661
+ }
662
+ const Icon = getIcon(item.icon);
663
+ const isActive = item.route === "/" ? currentPath === "/" : currentPath === item.route || currentPath.startsWith(item.route + "/");
664
+ return /* @__PURE__ */ jsxs(
665
+ "button",
666
+ {
667
+ "data-nav-active": isActive || void 0,
668
+ onClick: () => handleNavigate(item.route),
669
+ className: cn(
670
+ "flex items-center gap-1.5 whitespace-nowrap border-b-2 px-3 py-2 text-sm transition-colors",
671
+ isActive ? "border-primary font-semibold text-sidebar-foreground" : "border-transparent text-sidebar-muted hover:text-sidebar-foreground"
672
+ ),
673
+ children: [
674
+ /* @__PURE__ */ jsx(Icon, { className: "h-4 w-4" }),
675
+ /* @__PURE__ */ jsx("span", { children: item.label }),
676
+ item.premium && /* @__PURE__ */ jsx(Crown, { className: "h-3 w-3 text-amber-500", "aria-hidden": true }),
677
+ 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 })
678
+ ]
679
+ },
680
+ item.id
681
+ );
682
+ }) }) }) })
683
+ ] });
684
+ }
12
685
  var GUTTER_PX = 8;
13
686
  var CLOSE_DELAY_MS = 140;
14
- var useIsomorphicLayoutEffect = typeof window !== "undefined" ? React.useLayoutEffect : React.useEffect;
687
+ var useIsomorphicLayoutEffect = typeof window !== "undefined" ? React7.useLayoutEffect : React7.useEffect;
15
688
  function iconOf(name) {
16
689
  if (!name) return null;
17
690
  return ICON_MAP[name] ?? null;
18
691
  }
692
+ var RailFlyoutContext = React7.createContext(null);
693
+ var RailFlyoutProvider = RailFlyoutContext.Provider;
694
+ function useRailRow(content) {
695
+ const flyout = React7.useContext(RailFlyoutContext);
696
+ if (!flyout || !content) return {};
697
+ return flyout.rowProps(content);
698
+ }
19
699
  function useRailFlyout({
20
700
  enabled,
21
701
  railRef,
22
702
  activeRoute,
23
703
  onNavigate
24
704
  }) {
25
- const [state, setState] = React.useState(null);
26
- const [pos, setPos] = React.useState({ top: 0, arrow: 20 });
27
- const [animate, setAnimate] = React.useState(false);
28
- const panelRef = React.useRef(null);
29
- const closeTimer = React.useRef(null);
30
- const lastContent = React.useRef(null);
31
- const cancelClose = React.useCallback(() => {
705
+ const [state, setState] = React7.useState(null);
706
+ const [pos, setPos] = React7.useState({ top: 0, arrow: 20 });
707
+ const [animate, setAnimate] = React7.useState(false);
708
+ const panelRef = React7.useRef(null);
709
+ const closeTimer = React7.useRef(null);
710
+ const lastContent = React7.useRef(null);
711
+ const cancelClose = React7.useCallback(() => {
32
712
  if (closeTimer.current) {
33
713
  clearTimeout(closeTimer.current);
34
714
  closeTimer.current = null;
35
715
  }
36
716
  }, []);
37
- const show = React.useCallback(
717
+ const show = React7.useCallback(
38
718
  (anchor, content2) => {
39
719
  if (!enabled) return;
40
720
  cancelClose();
@@ -42,19 +722,19 @@ function useRailFlyout({
42
722
  },
43
723
  [enabled, cancelClose]
44
724
  );
45
- const hide = React.useCallback(() => {
725
+ const hide = React7.useCallback(() => {
46
726
  cancelClose();
47
727
  closeTimer.current = setTimeout(() => setState(null), CLOSE_DELAY_MS);
48
728
  }, [cancelClose]);
49
- const dismiss = React.useCallback(() => {
729
+ const dismiss = React7.useCallback(() => {
50
730
  cancelClose();
51
731
  setState(null);
52
732
  }, [cancelClose]);
53
- React.useEffect(() => () => cancelClose(), [cancelClose]);
54
- React.useEffect(() => {
733
+ React7.useEffect(() => () => cancelClose(), [cancelClose]);
734
+ React7.useEffect(() => {
55
735
  if (!enabled) dismiss();
56
736
  }, [enabled, dismiss]);
57
- const reposition = React.useCallback(() => {
737
+ const reposition = React7.useCallback(() => {
58
738
  const rail = railRef.current;
59
739
  const anchor = state?.anchor;
60
740
  if (!rail || !anchor) return;
@@ -69,7 +749,7 @@ function useRailFlyout({
69
749
  setPos({ top, arrow });
70
750
  }, [railRef, state]);
71
751
  useIsomorphicLayoutEffect(reposition, [reposition, state?.content.key]);
72
- React.useEffect(() => {
752
+ React7.useEffect(() => {
73
753
  if (!state) return;
74
754
  const rail = railRef.current;
75
755
  const onScroll = () => reposition();
@@ -81,7 +761,7 @@ function useRailFlyout({
81
761
  };
82
762
  }, [state, railRef, reposition]);
83
763
  const visible = !!state;
84
- React.useEffect(() => {
764
+ React7.useEffect(() => {
85
765
  if (!visible) {
86
766
  setAnimate(false);
87
767
  return;
@@ -89,7 +769,7 @@ function useRailFlyout({
89
769
  const id = requestAnimationFrame(() => setAnimate(true));
90
770
  return () => cancelAnimationFrame(id);
91
771
  }, [visible]);
92
- React.useEffect(() => {
772
+ React7.useEffect(() => {
93
773
  if (!visible) return;
94
774
  const onKey = (event) => {
95
775
  if (event.key === "Escape") dismiss();
@@ -97,7 +777,7 @@ function useRailFlyout({
97
777
  window.addEventListener("keydown", onKey);
98
778
  return () => window.removeEventListener("keydown", onKey);
99
779
  }, [visible, dismiss]);
100
- const rowProps = React.useCallback(
780
+ const rowProps = React7.useCallback(
101
781
  (content2) => ({
102
782
  onMouseEnter: (event) => show(event.currentTarget, content2),
103
783
  onFocus: (event) => show(event.currentTarget, content2),
@@ -214,8 +894,8 @@ function FlyoutBody({
214
894
  ] });
215
895
  }
216
896
  var RAIL_PUSH_MS = 260;
217
- var useIsomorphicLayoutEffect2 = typeof window !== "undefined" ? React.useLayoutEffect : React.useEffect;
218
- function getIcon(name) {
897
+ var useIsomorphicLayoutEffect2 = typeof window !== "undefined" ? React7.useLayoutEffect : React7.useEffect;
898
+ function getIcon2(name) {
219
899
  return ICON_MAP[name] ?? Home;
220
900
  }
221
901
  function shortcutKey() {
@@ -248,7 +928,7 @@ function NavItem({
248
928
  onNavigate,
249
929
  flyout
250
930
  }) {
251
- const Icon = getIcon(item.icon);
931
+ const Icon = getIcon2(item.icon);
252
932
  const isExternal = !!item.href;
253
933
  const hoverProps = collapsed && flyout ? flyout.rowProps(flyoutContentOf(item)) : void 0;
254
934
  const className = cn(
@@ -314,8 +994,8 @@ function NavGroup({
314
994
  flyout
315
995
  }) {
316
996
  const isChildActive = item.children?.some((c) => c.route === activeRoute);
317
- const [open, setOpen] = React.useState(() => isChildActive ?? false);
318
- const Icon = getIcon(item.icon);
997
+ const [open, setOpen] = React7.useState(() => isChildActive ?? false);
998
+ const Icon = getIcon2(item.icon);
319
999
  if (collapsed) {
320
1000
  const hoverProps = flyout?.rowProps(flyoutContentOf(item));
321
1001
  return /* @__PURE__ */ jsx(
@@ -370,7 +1050,7 @@ function NavGroup({
370
1050
  // along with the labels and land the arrowhead on top of the text.
371
1051
  childConnector ? "relative" : "ml-4 space-y-0.5 border-l border-sidebar-border/50 pl-3"
372
1052
  ), children: item.children?.map((child, childIndex) => {
373
- const ChildIcon = getIcon(child.icon);
1053
+ const ChildIcon = getIcon2(child.icon);
374
1054
  const isActive = child.route === activeRoute;
375
1055
  const childExternal = !!child.href;
376
1056
  const childClass = cn(
@@ -388,7 +1068,7 @@ function NavGroup({
388
1068
  child.premium && /* @__PURE__ */ jsx(Crown, { className: "h-3 w-3 shrink-0 text-amber-500", "aria-hidden": true })
389
1069
  ] });
390
1070
  const row = childExternal ? /* @__PURE__ */ jsx("a", { href: child.href, target: "_blank", rel: "noopener noreferrer", className: childClass, children: childInner }) : /* @__PURE__ */ jsx("button", { onClick: () => onNavigate(child.route), className: childClass, children: childInner });
391
- if (!childConnector) return /* @__PURE__ */ jsx(React.Fragment, { children: row }, child.id);
1071
+ if (!childConnector) return /* @__PURE__ */ jsx(React7.Fragment, { children: row }, child.id);
392
1072
  const activeIndex = item.children.findIndex((c) => c.route === activeRoute);
393
1073
  const onPath = activeIndex >= 0 && childIndex < activeIndex;
394
1074
  return (
@@ -450,9 +1130,9 @@ function NavGroup({
450
1130
  ] });
451
1131
  }
452
1132
  function withCollapsed(slot, collapsed) {
453
- if (!React.isValidElement(slot)) return slot;
1133
+ if (!React7.isValidElement(slot)) return slot;
454
1134
  if (typeof slot.type === "string") return slot;
455
- return React.cloneElement(slot, { collapsed });
1135
+ return React7.cloneElement(slot, { collapsed });
456
1136
  }
457
1137
  function Sidebar({
458
1138
  navigation,
@@ -479,7 +1159,7 @@ function Sidebar({
479
1159
  const currentPath = currentPathProp;
480
1160
  const setCommandPaletteOpen = useLayoutStore((s) => s.setCommandPaletteOpen);
481
1161
  const activeRoute = resolveActiveRoute(navigation, currentPath);
482
- const railRef = React.useRef(null);
1162
+ const railRef = React7.useRef(null);
483
1163
  const flyout = useRailFlyout({
484
1164
  enabled: collapsed,
485
1165
  railRef,
@@ -529,10 +1209,10 @@ function Sidebar({
529
1209
  mainItems.length > 0 && renderSection(mainItems, sectionLabels?.main),
530
1210
  secondaryItems.length > 0 && /* @__PURE__ */ jsx("div", { className: "mt-6", children: renderSection(secondaryItems, sectionLabels?.secondary) })
531
1211
  ] });
532
- const [outgoing, setOutgoing] = React.useState(null);
533
- const lastBodyRef = React.useRef(null);
534
- const lastKeyRef = React.useRef(navKey);
535
- const timerRef = React.useRef(null);
1212
+ const [outgoing, setOutgoing] = React7.useState(null);
1213
+ const lastBodyRef = React7.useRef(null);
1214
+ const lastKeyRef = React7.useRef(navKey);
1215
+ const timerRef = React7.useRef(null);
536
1216
  useIsomorphicLayoutEffect2(() => {
537
1217
  if (lastKeyRef.current !== navKey) {
538
1218
  const previousBody = lastBodyRef.current;
@@ -545,539 +1225,183 @@ function Sidebar({
545
1225
  }
546
1226
  lastBodyRef.current = navBody;
547
1227
  });
548
- React.useEffect(() => () => {
1228
+ React7.useEffect(() => () => {
549
1229
  if (timerRef.current) clearTimeout(timerRef.current);
550
1230
  }, []);
551
- return /* @__PURE__ */ jsxs(
552
- "aside",
553
- {
554
- ref: railRef,
555
- "data-print": "hide",
556
- onBlur: (event) => {
557
- if (!event.currentTarget.contains(event.relatedTarget)) flyout.dismiss();
558
- },
559
- className: cn(
560
- // `relative` anchors the flyout, which hangs off the rail's right edge
561
- // and has to escape the nav's own scroll box to do it.
562
- "relative flex h-full flex-col bg-sidebar text-sidebar-foreground transition-all duration-200",
563
- !borderless && "border-r border-sidebar-border",
564
- // 14rem expanded. It was 16rem, which is the width a rail needs when
565
- // its entries are sentences ("GlossGenius Financing", "Configure your
566
- // payment methods"); these are one or two words, so the extra 2rem was
567
- // paid for by the content column on every screen in every app. The
568
- // labels still truncate, and a long one now truncates 32px earlier —
569
- // which is the trade, taken deliberately.
570
- collapsed ? "w-16" : "w-56"
571
- ),
572
- children: [
573
- /* @__PURE__ */ jsxs(
574
- "div",
575
- {
576
- className: cn(
577
- "flex h-14 items-center justify-between px-4",
578
- !borderless && "border-b border-sidebar-border",
579
- collapsed && "justify-center px-2"
580
- ),
581
- children: [
582
- !collapsed && /* @__PURE__ */ jsxs(Fragment, { children: [
583
- logo ?? /* @__PURE__ */ jsx("span", { className: "text-lg font-bold", children: "App" }),
584
- /* @__PURE__ */ jsx(
585
- "button",
586
- {
587
- onClick: onToggle,
588
- className: "ml-auto rounded-button p-1.5 text-sidebar-muted transition-colors hover:bg-sidebar-accent hover:text-sidebar-accent-foreground",
589
- "aria-label": "Collapse sidebar",
590
- children: /* @__PURE__ */ jsx(PanelLeftClose, { className: "h-4 w-4" })
591
- }
592
- )
593
- ] }),
594
- collapsed && /* @__PURE__ */ jsx(
595
- "button",
596
- {
597
- onClick: onToggle,
598
- className: "rounded-button p-1.5 text-sidebar-muted transition-colors hover:bg-sidebar-accent hover:text-sidebar-accent-foreground",
599
- "aria-label": "Expand sidebar",
600
- children: /* @__PURE__ */ jsx(PanelLeft, { className: "h-4 w-4" })
601
- }
602
- )
603
- ]
604
- }
605
- ),
606
- topContent && /* @__PURE__ */ jsx("div", { className: "px-2 pt-2", children: withCollapsed(topContent, collapsed) }),
607
- /* @__PURE__ */ jsx("div", { className: "px-2 pb-2 pt-2", children: /* @__PURE__ */ jsxs(
608
- "button",
609
- {
610
- onClick: () => setCommandPaletteOpen(true),
611
- "aria-keyshortcuts": "Meta+K Control+K",
612
- className: cn(
613
- // Tonal surface, no outline. On a dark rail a 1px border is a hard
614
- // edge competing with every other hard edge; a lighter fill of the
615
- // same hue reads as "a field" without drawing a box around it — and
616
- // leaves the outline treatment free to mean focus, which it must.
617
- "fz-tap group flex items-center rounded-lg bg-sidebar-accent/40 text-sidebar-muted transition-colors",
618
- "hover:bg-sidebar-accent/70 hover:text-sidebar-foreground",
619
- "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-sidebar-accent-foreground/50",
620
- collapsed ? "h-9 w-9 justify-center" : "h-9 w-full gap-2.5 px-2.5"
621
- ),
622
- "aria-label": searchLabel,
623
- title: collapsed ? searchLabel : void 0,
624
- children: [
625
- /* @__PURE__ */ jsx(Search, { className: "h-4 w-4 shrink-0" }),
626
- !collapsed && /* @__PURE__ */ jsxs(Fragment, { children: [
627
- /* @__PURE__ */ jsx("span", { className: "flex-1 truncate text-left text-[13px]", children: searchLabel }),
628
- /* @__PURE__ */ jsxs("span", { className: "shrink-0 text-[11px] font-medium tabular-nums text-sidebar-muted/60 transition-opacity group-hover:opacity-100 md:opacity-70", children: [
629
- shortcutKey(),
630
- "K"
631
- ] })
632
- ] })
633
- ]
634
- }
635
- ) }),
636
- /* @__PURE__ */ jsxs(
637
- "nav",
638
- {
639
- className: cn(
640
- "relative flex-1 p-2",
641
- // scrollbar-none: the rail still scrolls, but without the native
642
- // scrollbar gutter eating into the nav items.
643
- outgoing ? "overflow-hidden" : "overflow-y-auto scrollbar-none"
644
- ),
645
- children: [
646
- outgoing && /* @__PURE__ */ jsx(
647
- "div",
648
- {
649
- "aria-hidden": true,
650
- className: cn(
651
- "pointer-events-none absolute inset-x-2 top-2 space-y-1",
652
- outgoing.direction === "back" ? "saas-rail-out-back" : "saas-rail-out-forward"
653
- ),
654
- children: outgoing.body
655
- }
656
- ),
657
- /* @__PURE__ */ jsx(
658
- "div",
659
- {
660
- className: cn(
661
- "space-y-1",
662
- outgoing && (outgoing.direction === "back" ? "saas-rail-in-back" : "saas-rail-in-forward")
663
- ),
664
- children: navBody
665
- },
666
- navKey
667
- )
668
- ]
669
- }
670
- ),
671
- 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(
672
- NavItem,
673
- {
674
- item,
675
- collapsed,
676
- isActive: item.route === activeRoute,
677
- onNavigate: handleNavigate,
678
- flyout
679
- },
680
- item.id
681
- )) }),
682
- footerContent && // `empty:hidden` — slot children may all render null (e.g. the
683
- // SeuSiteWidget self-hides when the tenant has no project); without
684
- // it the padded/bordered wrapper shows as a blank block.
685
- /* @__PURE__ */ jsx("div", { className: cn("space-y-2 p-2 empty:hidden", !borderless && "border-t border-sidebar-border"), children: footerContent }),
686
- withCollapsed(orgSwitcher, collapsed),
687
- /* @__PURE__ */ jsx("div", { className: cn("p-2", !borderless && "border-t border-sidebar-border"), children: /* @__PURE__ */ jsxs("div", { className: cn(
688
- "flex items-center rounded-button",
689
- collapsed ? "flex-col gap-1" : "gap-1 px-1"
690
- ), children: [
691
- userMenuSlot,
692
- !collapsed && /* @__PURE__ */ jsx("div", { className: "flex-1" }),
693
- notificationSlot ?? /* @__PURE__ */ jsxs(
694
- "button",
695
- {
696
- className: "fz-tap relative inline-flex shrink-0 items-center justify-center rounded-button p-2 text-sidebar-muted transition-colors hover:bg-sidebar-accent/50 hover:text-sidebar-accent-foreground",
697
- "aria-label": unreadCount > 0 ? `${unreadCount} unread notifications` : "Notifications",
698
- children: [
699
- /* @__PURE__ */ jsx(Bell, { className: "h-4 w-4" }),
700
- unreadCount > 0 && /* @__PURE__ */ jsx("span", { className: "absolute right-1 top-1 h-2 w-2 rounded-full bg-destructive" })
701
- ]
702
- }
703
- )
704
- ] }) }),
705
- flyout.panel
706
- ]
707
- }
708
- );
709
- }
710
- function TopbarUserMenu({
711
- user,
712
- userPlan,
713
- billingLabel = "Subscription",
714
- onSignOut,
715
- onProfile,
716
- onSettings,
717
- onBilling,
718
- userMenuExtras
719
- }) {
720
- const [open, setOpen] = React.useState(false);
721
- if (!user) return null;
722
- const initial = (user.fullName?.trim()?.[0] ?? user.email?.[0] ?? "?").toUpperCase();
723
- const item = (label, onClick, Icon) => onClick ? /* @__PURE__ */ jsxs(
724
- "button",
725
- {
726
- onClick: () => {
727
- setOpen(false);
728
- onClick();
729
- },
730
- 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",
731
- children: [
732
- Icon && /* @__PURE__ */ jsx(Icon, { className: "h-4 w-4 text-muted-foreground" }),
733
- /* @__PURE__ */ jsx("span", { children: label })
734
- ]
735
- },
736
- label
737
- ) : null;
738
- return /* @__PURE__ */ jsxs(Popover.Root, { open, onOpenChange: setOpen, children: [
739
- /* @__PURE__ */ jsx(Popover.Trigger, { asChild: true, children: /* @__PURE__ */ jsx(
740
- "button",
741
- {
742
- "aria-label": "User menu",
743
- 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",
744
- children: user.avatarUrl ? (
745
- // eslint-disable-next-line @next/next/no-img-element
746
- /* @__PURE__ */ jsx("img", { src: user.avatarUrl, alt: "", className: "h-full w-full object-cover" })
747
- ) : initial
748
- }
749
- ) }),
750
- /* @__PURE__ */ jsx(Popover.Portal, { children: /* @__PURE__ */ jsxs(
751
- Popover.Content,
752
- {
753
- align: "end",
754
- sideOffset: 8,
755
- 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",
756
- children: [
757
- /* @__PURE__ */ jsxs("div", { className: "border-b border-border px-3 py-2", children: [
758
- /* @__PURE__ */ jsx("p", { className: "truncate text-sm font-medium text-foreground", children: user.fullName || user.email }),
759
- /* @__PURE__ */ jsx("p", { className: "truncate text-xs text-muted-foreground", children: user.email })
760
- ] }),
761
- /* @__PURE__ */ jsxs("div", { className: "mt-1 space-y-0.5", children: [
762
- item("Perfil", onProfile, UserCog),
763
- item("Configura\xE7\xF5es", onSettings, Settings),
764
- onBilling && /* @__PURE__ */ jsxs(
765
- "button",
766
- {
767
- onClick: () => {
768
- setOpen(false);
769
- onBilling();
770
- },
771
- 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",
772
- children: [
773
- /* @__PURE__ */ jsx(CreditCard, { className: "h-4 w-4 text-muted-foreground" }),
774
- /* @__PURE__ */ jsx("span", { className: "flex-1 text-left", children: billingLabel }),
775
- userPlan && /* @__PURE__ */ jsxs(
776
- "span",
777
- {
778
- className: userPlan.paid ? "inline-flex items-center gap-1 rounded-full border border-amber-400/40 bg-amber-400/15 px-2 py-0.5 text-[10px] font-semibold text-amber-600 dark:text-amber-300" : "inline-flex items-center gap-1 rounded-full border border-border bg-muted px-2 py-0.5 text-[10px] font-medium text-muted-foreground",
779
- children: [
780
- userPlan.paid ? /* @__PURE__ */ jsx(Crown, { className: "h-3 w-3" }) : /* @__PURE__ */ jsx(Sparkles, { className: "h-3 w-3" }),
781
- userPlan.label
782
- ]
783
- }
784
- )
785
- ]
786
- },
787
- "billing"
788
- ),
789
- userMenuExtras?.map((e) => item(e.label, e.onClick)),
790
- onSignOut && /* @__PURE__ */ jsxs(Fragment, { children: [
791
- /* @__PURE__ */ jsx("div", { className: "my-1 border-t border-border" }),
792
- item("Sair", onSignOut, LogOut)
793
- ] })
794
- ] })
795
- ]
796
- }
797
- ) })
798
- ] });
799
- }
800
- var ICON_MAP2 = {
801
- Home: Home,
802
- Users,
803
- Settings: Settings,
804
- CreditCard,
805
- Bell: Bell,
806
- Calendar,
807
- CalendarClock,
808
- Package,
809
- Activity,
810
- BarChart3,
811
- FileText,
812
- Mail,
813
- DollarSign,
814
- Megaphone,
815
- ShoppingCart,
816
- ShoppingBag,
817
- Target,
818
- Wrench,
819
- ClipboardList,
820
- ClipboardCheck,
821
- Briefcase,
822
- UserCog,
823
- BookOpen,
824
- MessageCircle,
825
- Globe,
826
- BookOpenCheck,
827
- Percent,
828
- Tag,
829
- Tags,
830
- Camera,
831
- UtensilsCrossed,
832
- Search: Search,
833
- MapPin,
834
- Handshake,
835
- Contact,
836
- Building2,
837
- ChevronDown: ChevronDown,
838
- Filter,
839
- Plus,
840
- Shield,
841
- List,
842
- ListChecks,
843
- FolderOpen,
844
- Dog,
845
- Cat,
846
- PawPrint,
847
- Heart,
848
- LayoutTemplate,
849
- LeafyGreen,
850
- Apple,
851
- Egg,
852
- Wheat,
853
- ArrowDownCircle,
854
- ArrowUpCircle,
855
- ArrowLeftRight,
856
- Plug,
857
- SlidersHorizontal,
858
- TableProperties,
859
- Landmark,
860
- Receipt,
861
- TrendingUp,
862
- CircleDollarSign,
863
- Clock,
864
- AlertTriangle,
865
- Sparkles,
866
- Wallet,
867
- Warehouse,
868
- Ruler,
869
- ArrowUpRight,
870
- ArrowDownRight,
871
- UserPlus,
872
- UserX,
873
- TreePalm,
874
- Boxes,
875
- Box,
876
- FileCheck2,
877
- PartyPopper,
878
- Radio,
879
- CalendarDays,
880
- BadgeDollarSign,
881
- Banknote,
882
- Layers,
883
- Music,
884
- Eye,
885
- ListMusic,
886
- Disc3,
887
- UsersRound,
888
- Mic,
889
- CalendarCheck2,
890
- CalendarX,
891
- ListPlus,
892
- Map,
893
- Ban,
894
- Clock3,
895
- Inbox,
896
- UserCheck,
897
- Star,
898
- Zap,
899
- Clapperboard,
900
- Newspaper,
901
- GraduationCap,
902
- Repeat
903
- };
904
- function getIcon2(name) {
905
- return ICON_MAP2[name] ?? Home;
906
- }
907
- function NavDropdown({ item, currentPath, onNavigate }) {
908
- const [open, setOpen] = React.useState(false);
909
- const Icon = getIcon2(item.icon);
910
- const isChildActive = item.children?.some(
911
- (child) => currentPath === child.route || currentPath.startsWith(child.route + "/")
912
- );
913
- return /* @__PURE__ */ jsxs(Popover.Root, { open, onOpenChange: setOpen, children: [
914
- /* @__PURE__ */ jsx(Popover.Trigger, { asChild: true, children: /* @__PURE__ */ jsxs(
915
- "button",
1231
+ return (
1232
+ // O handle do flyout desce por contexto para QUALQUER linha da trilha —
1233
+ // inclusive os widgets que os plugins montam no pé dela. Ver useRailRow.
1234
+ /* @__PURE__ */ jsx(RailFlyoutProvider, { value: flyout, children: /* @__PURE__ */ jsxs(
1235
+ "aside",
916
1236
  {
917
- "data-nav-active": isChildActive || void 0,
1237
+ ref: railRef,
1238
+ "data-print": "hide",
1239
+ onBlur: (event) => {
1240
+ if (!event.currentTarget.contains(event.relatedTarget)) flyout.dismiss();
1241
+ },
918
1242
  className: cn(
919
- "flex items-center gap-1.5 whitespace-nowrap border-b-2 px-3 py-2 text-sm transition-colors",
920
- isChildActive ? "border-sidebar-accent-foreground font-semibold text-sidebar-foreground" : "border-transparent text-sidebar-muted hover:text-sidebar-foreground"
1243
+ // `relative` anchors the flyout, which hangs off the rail's right edge
1244
+ // and has to escape the nav's own scroll box to do it.
1245
+ "relative flex h-full flex-col bg-sidebar text-sidebar-foreground transition-all duration-200",
1246
+ !borderless && "border-r border-sidebar-border",
1247
+ // 14rem expanded. It was 16rem, which is the width a rail needs when
1248
+ // its entries are sentences ("GlossGenius Financing", "Configure your
1249
+ // payment methods"); these are one or two words, so the extra 2rem was
1250
+ // paid for by the content column on every screen in every app. The
1251
+ // labels still truncate, and a long one now truncates 32px earlier —
1252
+ // which is the trade, taken deliberately.
1253
+ collapsed ? "w-16" : "w-56"
921
1254
  ),
922
1255
  children: [
923
- /* @__PURE__ */ jsx(Icon, { className: "h-4 w-4" }),
924
- /* @__PURE__ */ jsx("span", { children: item.label }),
925
- /* @__PURE__ */ jsx(ChevronDown, { className: cn("h-3 w-3 transition-transform", open && "rotate-180") })
926
- ]
927
- }
928
- ) }),
929
- /* @__PURE__ */ jsx(Popover.Portal, { children: /* @__PURE__ */ jsx(
930
- Popover.Content,
931
- {
932
- align: "start",
933
- sideOffset: 4,
934
- 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",
935
- children: item.children?.map((child) => {
936
- const ChildIcon = getIcon2(child.icon);
937
- const isActive = currentPath === child.route || currentPath.startsWith(child.route + "/");
938
- return /* @__PURE__ */ jsxs(
1256
+ /* @__PURE__ */ jsxs(
1257
+ "div",
1258
+ {
1259
+ className: cn(
1260
+ "flex h-14 items-center justify-between px-4",
1261
+ !borderless && "border-b border-sidebar-border",
1262
+ collapsed && "justify-center px-2"
1263
+ ),
1264
+ children: [
1265
+ !collapsed && /* @__PURE__ */ jsxs(Fragment, { children: [
1266
+ logo ?? /* @__PURE__ */ jsx("span", { className: "text-lg font-bold", children: "App" }),
1267
+ /* @__PURE__ */ jsx(
1268
+ "button",
1269
+ {
1270
+ onClick: onToggle,
1271
+ className: "ml-auto rounded-button p-1.5 text-sidebar-muted transition-colors hover:bg-sidebar-accent hover:text-sidebar-accent-foreground",
1272
+ "aria-label": "Collapse sidebar",
1273
+ children: /* @__PURE__ */ jsx(PanelLeftClose, { className: "h-4 w-4" })
1274
+ }
1275
+ )
1276
+ ] }),
1277
+ collapsed && /* @__PURE__ */ jsx(
1278
+ "button",
1279
+ {
1280
+ onClick: onToggle,
1281
+ className: "rounded-button p-1.5 text-sidebar-muted transition-colors hover:bg-sidebar-accent hover:text-sidebar-accent-foreground",
1282
+ "aria-label": "Expand sidebar",
1283
+ children: /* @__PURE__ */ jsx(PanelLeft, { className: "h-4 w-4" })
1284
+ }
1285
+ )
1286
+ ]
1287
+ }
1288
+ ),
1289
+ topContent && /* @__PURE__ */ jsx("div", { className: "px-2 pt-2", children: withCollapsed(topContent, collapsed) }),
1290
+ /* @__PURE__ */ jsx("div", { className: "px-2 pb-2 pt-2", children: /* @__PURE__ */ jsxs(
939
1291
  "button",
940
1292
  {
941
- onClick: () => {
942
- onNavigate(child.route);
943
- setOpen(false);
944
- },
1293
+ onClick: () => setCommandPaletteOpen(true),
1294
+ "aria-keyshortcuts": "Meta+K Control+K",
945
1295
  className: cn(
946
- "flex w-full items-center gap-2.5 rounded-lg px-3 py-2 text-sm transition-colors",
947
- isActive ? "bg-muted font-medium text-foreground" : "text-popover-foreground hover:bg-muted/70"
1296
+ // Tonal surface, no outline. On a dark rail a 1px border is a hard
1297
+ // edge competing with every other hard edge; a lighter fill of the
1298
+ // same hue reads as "a field" without drawing a box around it — and
1299
+ // leaves the outline treatment free to mean focus, which it must.
1300
+ "fz-tap group flex items-center rounded-lg bg-sidebar-accent/40 text-sidebar-muted transition-colors",
1301
+ "hover:bg-sidebar-accent/70 hover:text-sidebar-foreground",
1302
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-sidebar-accent-foreground/50",
1303
+ collapsed ? "h-9 w-9 justify-center" : "h-9 w-full gap-2.5 px-2.5"
948
1304
  ),
1305
+ "aria-label": searchLabel,
1306
+ title: collapsed ? searchLabel : void 0,
949
1307
  children: [
950
- /* @__PURE__ */ jsx(ChildIcon, { className: "h-4 w-4 text-muted-foreground" }),
951
- /* @__PURE__ */ jsx("span", { children: child.label })
1308
+ /* @__PURE__ */ jsx(Search, { className: "h-4 w-4 shrink-0" }),
1309
+ !collapsed && /* @__PURE__ */ jsxs(Fragment, { children: [
1310
+ /* @__PURE__ */ jsx("span", { className: "flex-1 truncate text-left text-[13px]", children: searchLabel }),
1311
+ /* @__PURE__ */ jsxs("span", { className: "shrink-0 text-[11px] font-medium tabular-nums text-sidebar-muted/60 transition-opacity group-hover:opacity-100 md:opacity-70", children: [
1312
+ shortcutKey(),
1313
+ "K"
1314
+ ] })
1315
+ ] })
1316
+ ]
1317
+ }
1318
+ ) }),
1319
+ /* @__PURE__ */ jsxs(
1320
+ "nav",
1321
+ {
1322
+ className: cn(
1323
+ "relative flex-1 p-2",
1324
+ // scrollbar-none: the rail still scrolls, but without the native
1325
+ // scrollbar gutter eating into the nav items.
1326
+ outgoing ? "overflow-hidden" : "overflow-y-auto scrollbar-none"
1327
+ ),
1328
+ children: [
1329
+ outgoing && /* @__PURE__ */ jsx(
1330
+ "div",
1331
+ {
1332
+ "aria-hidden": true,
1333
+ className: cn(
1334
+ "pointer-events-none absolute inset-x-2 top-2 space-y-1",
1335
+ outgoing.direction === "back" ? "saas-rail-out-back" : "saas-rail-out-forward"
1336
+ ),
1337
+ children: outgoing.body
1338
+ }
1339
+ ),
1340
+ /* @__PURE__ */ jsx(
1341
+ "div",
1342
+ {
1343
+ className: cn(
1344
+ "space-y-1",
1345
+ outgoing && (outgoing.direction === "back" ? "saas-rail-in-back" : "saas-rail-in-forward")
1346
+ ),
1347
+ children: navBody
1348
+ },
1349
+ navKey
1350
+ )
952
1351
  ]
1352
+ }
1353
+ ),
1354
+ 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(
1355
+ NavItem,
1356
+ {
1357
+ item,
1358
+ collapsed,
1359
+ isActive: item.route === activeRoute,
1360
+ onNavigate: handleNavigate,
1361
+ flyout
953
1362
  },
954
- child.id
955
- );
956
- })
1363
+ item.id
1364
+ )) }),
1365
+ footerContent && // `empty:hidden` — slot children may all render null (e.g. the
1366
+ // SeuSiteWidget self-hides when the tenant has no project); without
1367
+ // it the padded/bordered wrapper shows as a blank block.
1368
+ /* @__PURE__ */ jsx("div", { className: cn("space-y-2 p-2 empty:hidden", !borderless && "border-t border-sidebar-border"), children: footerContent }),
1369
+ withCollapsed(orgSwitcher, collapsed),
1370
+ /* @__PURE__ */ jsx("div", { className: cn("p-2", !borderless && "border-t border-sidebar-border"), children: /* @__PURE__ */ jsxs("div", { className: cn(
1371
+ "flex items-center rounded-button",
1372
+ collapsed ? "flex-col gap-1" : "gap-1 px-1"
1373
+ ), children: [
1374
+ userMenuSlot,
1375
+ !collapsed && /* @__PURE__ */ jsx("div", { className: "flex-1" }),
1376
+ notificationSlot ?? /* @__PURE__ */ jsxs(
1377
+ "button",
1378
+ {
1379
+ className: "fz-tap relative inline-flex shrink-0 items-center justify-center rounded-button p-2 text-sidebar-muted transition-colors hover:bg-sidebar-accent/50 hover:text-sidebar-accent-foreground",
1380
+ "aria-label": unreadCount > 0 ? `${unreadCount} unread notifications` : "Notifications",
1381
+ children: [
1382
+ /* @__PURE__ */ jsx(Bell, { className: "h-4 w-4" }),
1383
+ unreadCount > 0 && /* @__PURE__ */ jsx("span", { className: "absolute right-1 top-1 h-2 w-2 rounded-full bg-destructive" })
1384
+ ]
1385
+ }
1386
+ )
1387
+ ] }) }),
1388
+ flyout.panel
1389
+ ]
957
1390
  }
958
1391
  ) })
959
- ] });
960
- }
961
- function Topbar({
962
- navigation,
963
- logo,
964
- user,
965
- onMenuClick,
966
- orgSwitcher,
967
- leftContent,
968
- rightContent,
969
- currentPath = "/",
970
- onNavigate,
971
- searchPlaceholder = "Search...",
972
- onSignOut,
973
- onProfile,
974
- onSettings,
975
- onBilling,
976
- userPlan,
977
- billingLabel,
978
- userMenuExtras
979
- }) {
980
- const setCommandPaletteOpen = useLayoutStore((s) => s.setCommandPaletteOpen);
981
- const mainNav = navigation.filter((item) => item.section === "main");
982
- const secondaryNav = navigation.filter((item) => item.section === "secondary");
983
- const allNav = [...mainNav, ...secondaryNav];
984
- const handleNavigate = (route) => {
985
- onNavigate?.(route);
986
- };
987
- return /* @__PURE__ */ jsxs("header", { "data-print": "hide", className: "sticky top-0 z-50 w-full shrink-0", children: [
988
- /* @__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: [
989
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
990
- onMenuClick && /* @__PURE__ */ jsx(
991
- "button",
992
- {
993
- onClick: onMenuClick,
994
- 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",
995
- "aria-label": "Open menu",
996
- children: /* @__PURE__ */ jsx(Menu, { className: "h-5 w-5" })
997
- }
998
- ),
999
- /* @__PURE__ */ jsx("div", { className: "hidden shrink-0 items-center md:flex", children: logo ?? /* @__PURE__ */ jsx("span", { className: "text-lg font-bold", children: "App" }) })
1000
- ] }),
1001
- /* @__PURE__ */ jsx("div", { className: "mx-8 hidden max-w-sm flex-1 md:flex", children: /* @__PURE__ */ jsxs(
1002
- "button",
1003
- {
1004
- onClick: () => setCommandPaletteOpen(true),
1005
- 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:border-sidebar-foreground/25 hover:bg-sidebar-foreground/10 hover:text-sidebar-foreground",
1006
- children: [
1007
- /* @__PURE__ */ jsx(Search, { className: "absolute left-2.5 top-1/2 h-3.5 w-3.5 -translate-y-1/2 text-sidebar-muted" }),
1008
- /* @__PURE__ */ jsx("span", { className: "text-xs", children: searchPlaceholder }),
1009
- /* @__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" })
1010
- ]
1011
- }
1012
- ) }),
1013
- /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 items-center gap-1", children: [
1014
- orgSwitcher && /* @__PURE__ */ jsx("div", { className: "hidden min-w-0 items-center md:flex", children: orgSwitcher }),
1015
- leftContent,
1016
- rightContent,
1017
- /* @__PURE__ */ jsx(
1018
- TopbarUserMenu,
1019
- {
1020
- user,
1021
- userPlan,
1022
- billingLabel,
1023
- onSignOut,
1024
- onProfile,
1025
- onSettings,
1026
- onBilling,
1027
- userMenuExtras
1028
- }
1029
- )
1030
- ] })
1031
- ] }) }),
1032
- /* @__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) => {
1033
- if (!el) return;
1034
- const active = el.querySelector('[data-nav-active="true"]');
1035
- if (active) {
1036
- const left = active.offsetLeft - el.offsetWidth / 2 + active.offsetWidth / 2;
1037
- el.scrollTo({ left: Math.max(0, left), behavior: "smooth" });
1038
- }
1039
- }, className: "flex items-center gap-0.5 overflow-x-auto scrollbar-none", children: allNav.map((item) => {
1040
- if (item.children && item.children.length > 0) {
1041
- return /* @__PURE__ */ jsx(
1042
- NavDropdown,
1043
- {
1044
- item,
1045
- currentPath,
1046
- onNavigate: handleNavigate
1047
- },
1048
- item.id
1049
- );
1050
- }
1051
- const Icon = getIcon2(item.icon);
1052
- const isActive = item.route === "/" ? currentPath === "/" : currentPath === item.route || currentPath.startsWith(item.route + "/");
1053
- return /* @__PURE__ */ jsxs(
1054
- "button",
1055
- {
1056
- "data-nav-active": isActive || void 0,
1057
- onClick: () => handleNavigate(item.route),
1058
- className: cn(
1059
- "flex items-center gap-1.5 whitespace-nowrap border-b-2 px-3 py-2 text-sm transition-colors",
1060
- isActive ? "border-primary font-semibold text-sidebar-foreground" : "border-transparent text-sidebar-muted hover:text-sidebar-foreground"
1061
- ),
1062
- children: [
1063
- /* @__PURE__ */ jsx(Icon, { className: "h-4 w-4" }),
1064
- /* @__PURE__ */ jsx("span", { children: item.label }),
1065
- item.premium && /* @__PURE__ */ jsx(Crown, { className: "h-3 w-3 text-amber-500", "aria-hidden": true }),
1066
- 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 })
1067
- ]
1068
- },
1069
- item.id
1070
- );
1071
- }) }) }) })
1072
- ] });
1392
+ );
1073
1393
  }
1074
- var ModuleHeaderSlotContext = React.createContext(null);
1394
+ var ModuleHeaderSlotContext = React7.createContext(null);
1075
1395
  function useModuleHeaderSlot() {
1076
- return React.useContext(ModuleHeaderSlotContext);
1396
+ return React7.useContext(ModuleHeaderSlotContext);
1077
1397
  }
1078
- var ModuleHeaderActionsSlotContext = React.createContext(null);
1398
+ var ModuleHeaderActionsSlotContext = React7.createContext(null);
1079
1399
  function useModuleHeaderActionsSlot() {
1080
- return React.useContext(ModuleHeaderActionsSlotContext);
1400
+ return React7.useContext(ModuleHeaderActionsSlotContext);
1401
+ }
1402
+ var PageRailSlotContext = React7.createContext(null);
1403
+ function usePageRailSlot() {
1404
+ return React7.useContext(PageRailSlotContext);
1081
1405
  }
1082
1406
  function ShellPageTitle({
1083
1407
  title,
@@ -1122,10 +1446,12 @@ function SidebarLayout({
1122
1446
  const pageHeaderSlot = usePageHeaderSlot();
1123
1447
  usePageHeaderHost();
1124
1448
  const setSidebarCollapsed = useLayoutStore((s) => s.setSidebarCollapsed);
1125
- const [mobileMenuOpen, setMobileMenuOpen] = React.useState(false);
1126
- const [headerSlot, setHeaderSlot] = React.useState(null);
1127
- const [actionsSlot, setActionsSlot] = React.useState(null);
1128
- return /* @__PURE__ */ jsx(SaveBarProvider, { children: /* @__PURE__ */ jsx(ModuleHeaderSlotContext.Provider, { value: headerSlot, children: /* @__PURE__ */ jsx(ModuleHeaderActionsSlotContext.Provider, { value: actionsSlot, children: /* @__PURE__ */ jsxs("div", { className: cn(
1449
+ const [mobileMenuOpen, setMobileMenuOpen] = React7.useState(false);
1450
+ const [headerSlot, setHeaderSlot] = React7.useState(null);
1451
+ const [actionsSlot, setActionsSlot] = React7.useState(null);
1452
+ const [pageRailSlot, setPageRailSlot] = React7.useState(null);
1453
+ const hasPageActions = useHasPageActions();
1454
+ return /* @__PURE__ */ jsx(SaveBarProvider, { children: /* @__PURE__ */ jsx(ModuleHeaderSlotContext.Provider, { value: headerSlot, children: /* @__PURE__ */ jsx(ModuleHeaderActionsSlotContext.Provider, { value: actionsSlot, children: /* @__PURE__ */ jsx(PageRailSlotContext.Provider, { value: pageRailSlot, children: /* @__PURE__ */ jsxs("div", { className: cn(
1129
1455
  "shell-enter flex h-screen overflow-hidden bg-background",
1130
1456
  // Frame mode: the inset gap around the framed card takes the sidebar (menu)
1131
1457
  // color, so the white card reads as a panel floating on the same tone as the
@@ -1162,34 +1488,42 @@ function SidebarLayout({
1162
1488
  }
1163
1489
  ) }),
1164
1490
  /* @__PURE__ */ jsxs("div", { className: cn(
1165
- "relative flex flex-1 flex-col overflow-hidden",
1166
- frame && "md:m-2 md:rounded-xl md:border md:border-border md:bg-card"
1491
+ "relative flex flex-1 overflow-hidden",
1492
+ // `content`, not `card`: this is the page's own surface, and cards have
1493
+ // to sit ON it. Painted card, the frame was the same tone as everything
1494
+ // inside it and the page read as one flat sheet.
1495
+ frame && "md:m-2 md:rounded-xl md:border md:border-border md:bg-content"
1167
1496
  ), children: [
1168
- /* @__PURE__ */ jsxs("div", { className: cn(
1169
- "flex min-h-14 shrink-0 items-center justify-between gap-3 border-b border-border bg-card px-4 py-2",
1170
- mobileHeader !== "minimal" && "hidden md:flex"
1171
- ), children: [
1172
- /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-1 items-center gap-3", children: [
1173
- !hasBottomNav && /* @__PURE__ */ jsx(
1174
- "button",
1175
- {
1176
- onClick: () => setMobileMenuOpen(true),
1177
- className: "inline-flex items-center justify-center rounded-md p-2 text-muted-foreground hover:bg-accent md:hidden",
1178
- "aria-label": "Open menu",
1179
- children: /* @__PURE__ */ jsx(Menu, { className: "h-5 w-5" })
1180
- }
1181
- ),
1182
- pageHeaderSlot ?? /* @__PURE__ */ jsx(ShellPageTitle, { title: pageTitle, back: pageBack }),
1183
- topbarStart,
1184
- /* @__PURE__ */ jsx("div", { ref: setHeaderSlot, className: "flex min-w-0 flex-1 items-center" })
1497
+ /* @__PURE__ */ jsx("div", { ref: setPageRailSlot, className: "flex shrink-0 empty:hidden" }),
1498
+ /* @__PURE__ */ jsxs("div", { className: "relative flex min-w-0 flex-1 flex-col overflow-hidden", children: [
1499
+ /* @__PURE__ */ jsxs("div", { className: cn(
1500
+ "flex min-h-14 shrink-0 items-center justify-between gap-3 border-b border-border px-4 py-2",
1501
+ // …unless the page put its actions in it. Hiding the bar on mobile
1502
+ // used to hide the only "Novo" the screen had.
1503
+ mobileHeader !== "minimal" && !hasPageActions && "hidden md:flex"
1504
+ ), children: [
1505
+ /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-1 items-center gap-3", children: [
1506
+ !hasBottomNav && /* @__PURE__ */ jsx(
1507
+ "button",
1508
+ {
1509
+ onClick: () => setMobileMenuOpen(true),
1510
+ className: "inline-flex items-center justify-center rounded-md p-2 text-muted-foreground hover:bg-accent md:hidden",
1511
+ "aria-label": "Open menu",
1512
+ children: /* @__PURE__ */ jsx(Menu, { className: "h-5 w-5" })
1513
+ }
1514
+ ),
1515
+ pageHeaderSlot ?? /* @__PURE__ */ jsx(ShellPageTitle, { title: pageTitle, back: pageBack }),
1516
+ topbarStart,
1517
+ /* @__PURE__ */ jsx("div", { ref: setHeaderSlot, className: "flex min-w-0 flex-1 items-center" })
1518
+ ] }),
1519
+ /* @__PURE__ */ jsxs("div", { className: "flex shrink-0 items-center gap-2", children: [
1520
+ /* @__PURE__ */ jsx("div", { ref: setActionsSlot, className: "flex items-center gap-2" }),
1521
+ topbarEnd
1522
+ ] })
1185
1523
  ] }),
1186
- /* @__PURE__ */ jsxs("div", { className: "flex shrink-0 items-center gap-2", children: [
1187
- /* @__PURE__ */ jsx("div", { ref: setActionsSlot, className: "flex items-center gap-2" }),
1188
- topbarEnd
1189
- ] })
1190
- ] }),
1191
- /* @__PURE__ */ jsx("main", { className: "min-w-0 flex-1 overflow-y-auto pb-16 md:pb-0", children }),
1192
- /* @__PURE__ */ jsx(SaveBar, {})
1524
+ /* @__PURE__ */ jsx("main", { className: "min-w-0 flex-1 overflow-y-auto pb-16 md:pb-0", children }),
1525
+ /* @__PURE__ */ jsx(SaveBar, {})
1526
+ ] })
1193
1527
  ] }),
1194
1528
  mobileMenuOpen && /* @__PURE__ */ jsx(
1195
1529
  MobileOverlay,
@@ -1214,7 +1548,7 @@ function SidebarLayout({
1214
1548
  onClose: () => setMobileMenuOpen(false)
1215
1549
  }
1216
1550
  )
1217
- ] }) }) }) });
1551
+ ] }) }) }) }) });
1218
1552
  }
1219
1553
  function TopbarLayout({
1220
1554
  navigation = [],
@@ -1239,13 +1573,17 @@ function TopbarLayout({
1239
1573
  notificationSlot,
1240
1574
  hasBottomNav = false
1241
1575
  }) {
1242
- const [mobileMenuOpen, setMobileMenuOpen] = React.useState(false);
1576
+ const [mobileMenuOpen, setMobileMenuOpen] = React7.useState(false);
1577
+ const [headerSlot, setHeaderSlot] = React7.useState(null);
1578
+ const [actionsSlot, setActionsSlot] = React7.useState(null);
1579
+ const [pageRailSlot, setPageRailSlot] = React7.useState(null);
1243
1580
  const rawSlot = usePageHeaderSlot();
1244
1581
  const hasOutlet = useHasPageHeaderOutlet();
1582
+ const hasPageActions = useHasPageActions();
1245
1583
  usePageHeaderHost();
1246
1584
  const pageHeaderSlot = hasOutlet ? null : rawSlot;
1247
1585
  const showTitle = !hasOutlet && !!pageTitle;
1248
- return /* @__PURE__ */ jsx(SaveBarProvider, { children: /* @__PURE__ */ jsxs("div", { className: "shell-enter relative flex h-screen flex-col overflow-hidden bg-background", children: [
1586
+ return /* @__PURE__ */ jsx(SaveBarProvider, { children: /* @__PURE__ */ jsx(ModuleHeaderSlotContext.Provider, { value: headerSlot, children: /* @__PURE__ */ jsx(ModuleHeaderActionsSlotContext.Provider, { value: actionsSlot, children: /* @__PURE__ */ jsx(PageRailSlotContext.Provider, { value: pageRailSlot, children: /* @__PURE__ */ jsxs("div", { className: "shell-enter relative flex h-screen flex-col overflow-hidden bg-background", children: [
1249
1587
  /* @__PURE__ */ jsx(
1250
1588
  Topbar,
1251
1589
  {
@@ -1270,9 +1608,18 @@ function TopbarLayout({
1270
1608
  onMenuClick: hasBottomNav ? void 0 : () => setMobileMenuOpen(true)
1271
1609
  }
1272
1610
  ),
1273
- /* @__PURE__ */ jsx("main", { className: cn("flex min-w-0 min-h-0 flex-1 flex-col overflow-hidden", frame && "md:bg-sidebar md:p-2"), children: /* @__PURE__ */ jsxs("div", { className: cn("flex min-w-0 min-h-0 flex-1 flex-col overflow-hidden", frame && "md:rounded-xl md:border md:border-border md:bg-card"), children: [
1274
- (pageHeaderSlot || pageBack || showTitle) && /* @__PURE__ */ jsx("div", { className: "flex min-h-12 shrink-0 items-center gap-3 border-b border-border px-4 py-2", children: pageHeaderSlot ?? /* @__PURE__ */ jsx(ShellPageTitle, { title: showTitle ? pageTitle : void 0, back: pageBack }) }),
1275
- /* @__PURE__ */ jsx("div", { className: "min-h-0 flex-1 overflow-y-auto pb-16 md:pb-0", children })
1611
+ /* @__PURE__ */ jsx("main", { className: cn("flex min-w-0 min-h-0 flex-1 flex-col overflow-hidden", frame && "md:bg-sidebar md:p-2"), children: /* @__PURE__ */ jsxs("div", { className: cn("flex min-w-0 min-h-0 flex-1 overflow-hidden", frame && "md:rounded-xl md:border md:border-border md:bg-content"), children: [
1612
+ /* @__PURE__ */ jsx("div", { ref: setPageRailSlot, className: "flex shrink-0 empty:hidden" }),
1613
+ /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 min-h-0 flex-1 flex-col overflow-hidden", children: [
1614
+ (pageHeaderSlot || pageBack || hasPageActions) && /* @__PURE__ */ jsxs("div", { className: "flex min-h-12 shrink-0 items-center justify-between gap-3 border-b border-border px-4 py-2", children: [
1615
+ /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-1 items-center gap-3", children: [
1616
+ pageHeaderSlot ?? /* @__PURE__ */ jsx(ShellPageTitle, { title: showTitle ? pageTitle : void 0, back: pageBack }),
1617
+ /* @__PURE__ */ jsx("div", { ref: setHeaderSlot, className: "flex min-w-0 flex-1 items-center" })
1618
+ ] }),
1619
+ /* @__PURE__ */ jsx("div", { ref: setActionsSlot, className: "flex shrink-0 items-center gap-2" })
1620
+ ] }),
1621
+ /* @__PURE__ */ jsx("div", { className: "min-h-0 flex-1 overflow-y-auto pb-16 md:pb-0", children })
1622
+ ] })
1276
1623
  ] }) }),
1277
1624
  /* @__PURE__ */ jsx(SaveBar, {}),
1278
1625
  mobileMenuOpen && /* @__PURE__ */ jsx(
@@ -1292,7 +1639,7 @@ function TopbarLayout({
1292
1639
  onClose: () => setMobileMenuOpen(false)
1293
1640
  }
1294
1641
  )
1295
- ] }) });
1642
+ ] }) }) }) }) });
1296
1643
  }
1297
1644
  function DrawerUserRow({ user }) {
1298
1645
  const label = user.fullName || user.email;
@@ -1316,23 +1663,23 @@ function MinimalLayout({
1316
1663
  ] });
1317
1664
  }
1318
1665
  function MobileOverlay({ onClose, onNavigate, ...sidebarProps }) {
1319
- const [visible, setVisible] = React.useState(false);
1320
- React.useEffect(() => {
1666
+ const [visible, setVisible] = React7.useState(false);
1667
+ React7.useEffect(() => {
1321
1668
  const id = requestAnimationFrame(() => setVisible(true));
1322
1669
  return () => cancelAnimationFrame(id);
1323
1670
  }, []);
1324
- const close = React.useCallback(() => {
1671
+ const close = React7.useCallback(() => {
1325
1672
  setVisible(false);
1326
1673
  window.setTimeout(onClose, 250);
1327
1674
  }, [onClose]);
1328
- React.useEffect(() => {
1675
+ React7.useEffect(() => {
1329
1676
  const previous = document.body.style.overflow;
1330
1677
  document.body.style.overflow = "hidden";
1331
1678
  return () => {
1332
1679
  document.body.style.overflow = previous;
1333
1680
  };
1334
1681
  }, []);
1335
- React.useEffect(() => {
1682
+ React7.useEffect(() => {
1336
1683
  const onKey = (e) => {
1337
1684
  if (e.key === "Escape") close();
1338
1685
  };
@@ -1608,13 +1955,13 @@ function SplitWithRightRail({
1608
1955
  maxWidth = 720,
1609
1956
  resizeLabel = "Resize panel"
1610
1957
  }) {
1611
- const [width, setWidth] = React.useState(defaultWidth);
1612
- const [dragging, setDragging] = React.useState(false);
1613
- const clamp = React.useCallback(
1958
+ const [width, setWidth] = React7.useState(defaultWidth);
1959
+ const [dragging, setDragging] = React7.useState(false);
1960
+ const clamp = React7.useCallback(
1614
1961
  (value) => Math.min(maxWidth, Math.max(minWidth, value)),
1615
1962
  [minWidth, maxWidth]
1616
1963
  );
1617
- React.useEffect(() => {
1964
+ React7.useEffect(() => {
1618
1965
  if (!dragging) return;
1619
1966
  const onMove = (event) => {
1620
1967
  event.preventDefault();
@@ -1636,7 +1983,7 @@ function SplitWithRightRail({
1636
1983
  document.body.style.cursor = previousCursor;
1637
1984
  };
1638
1985
  }, [dragging, clamp]);
1639
- React.useEffect(() => {
1986
+ React7.useEffect(() => {
1640
1987
  if (dragging) return;
1641
1988
  onWidthChange?.(width);
1642
1989
  }, [dragging]);
@@ -1715,40 +2062,74 @@ function SplitWithRightRail({
1715
2062
  ] })
1716
2063
  );
1717
2064
  }
1718
- function BootSpinner({ className }) {
2065
+ function HeaderTabs({
2066
+ tabs,
2067
+ value,
2068
+ onChange,
2069
+ className,
2070
+ ...rest
2071
+ }) {
1719
2072
  return /* @__PURE__ */ jsx(
1720
- "span",
2073
+ "div",
1721
2074
  {
1722
- "aria-hidden": true,
2075
+ role: "tablist",
2076
+ "aria-label": rest["aria-label"],
1723
2077
  className: cn(
1724
- "block h-7 w-7 animate-spin rounded-full border-2 border-foreground/20 border-t-foreground/70",
1725
- "motion-reduce:animate-none motion-reduce:border-t-foreground/20 motion-reduce:animate-pulse",
2078
+ "inline-flex max-w-full items-center gap-5 overflow-x-auto scrollbar-none",
1726
2079
  className
1727
- )
2080
+ ),
2081
+ children: tabs.map((tab) => {
2082
+ const active = tab.value === value;
2083
+ const Icon = tab.icon;
2084
+ return /* @__PURE__ */ jsxs(
2085
+ "button",
2086
+ {
2087
+ type: "button",
2088
+ role: "tab",
2089
+ "aria-selected": active,
2090
+ onClick: () => onChange(tab.value),
2091
+ className: cn(
2092
+ "relative flex shrink-0 items-center gap-1.5 whitespace-nowrap pb-1.5 text-sm transition-colors",
2093
+ active ? "font-medium text-foreground" : "text-muted-foreground hover:text-foreground"
2094
+ ),
2095
+ children: [
2096
+ Icon ? /* @__PURE__ */ jsx(Icon, { className: "h-4 w-4 shrink-0" }) : null,
2097
+ /* @__PURE__ */ jsx("span", { children: tab.label }),
2098
+ tab.count != null && /* @__PURE__ */ jsx("span", { className: cn(
2099
+ "ml-0.5 rounded-full px-1.5 text-[11px] tabular-nums",
2100
+ active ? "bg-muted text-foreground" : "bg-muted/70 text-muted-foreground"
2101
+ ), children: tab.count }),
2102
+ tab.badge,
2103
+ active && /* @__PURE__ */ jsx("span", { className: "absolute inset-x-0 bottom-0 h-0.5 rounded-full bg-foreground" })
2104
+ ]
2105
+ },
2106
+ tab.value
2107
+ );
2108
+ })
1728
2109
  }
1729
2110
  );
1730
2111
  }
1731
- function AppShellSkeleton({ label, className }) {
2112
+ function PageTabs({ actions, className, ...props }) {
1732
2113
  return /* @__PURE__ */ jsxs(
1733
2114
  "div",
1734
2115
  {
1735
- role: "status",
1736
- "aria-busy": "true",
1737
- className: cn("flex h-screen items-center justify-center overflow-hidden bg-background", className),
2116
+ className: cn(
2117
+ "-mx-3 -mt-3 mb-4 flex flex-wrap items-end justify-between gap-3 border-b border-border px-3 pt-2 md:-mx-6 md:-mt-4 md:px-6",
2118
+ className
2119
+ ),
1738
2120
  children: [
1739
- /* @__PURE__ */ jsx("span", { className: "sr-only", children: label }),
1740
- /* @__PURE__ */ jsx(BootSpinner, {})
2121
+ /* @__PURE__ */ jsx(HeaderTabs, { ...props, className: "-mb-px" }),
2122
+ actions && /* @__PURE__ */ jsx("div", { className: "flex shrink-0 items-center gap-2 pb-1.5", children: actions })
1741
2123
  ]
1742
2124
  }
1743
2125
  );
1744
2126
  }
1745
- AppShellSkeleton.displayName = "AppShellSkeleton";
1746
- var NavTransitionContext = React.createContext("slide");
2127
+ var NavTransitionContext = React7.createContext("slide");
1747
2128
  function NavTransitionProvider({ value, children }) {
1748
2129
  return /* @__PURE__ */ jsx(NavTransitionContext.Provider, { value, children });
1749
2130
  }
1750
2131
  function useNavTransition() {
1751
- return React.useContext(NavTransitionContext);
2132
+ return React7.useContext(NavTransitionContext);
1752
2133
  }
1753
2134
  function resolveTransitionClass(transition, direction) {
1754
2135
  if (transition === "none") return "";
@@ -1767,8 +2148,14 @@ function PageTransition({
1767
2148
  const animClass = resolveTransitionClass(transition, direction);
1768
2149
  return /* @__PURE__ */ jsx("div", { className: cn(animClass, className), children }, transitionKey);
1769
2150
  }
2151
+ function PageRail({ children }) {
2152
+ const slot = usePageRailSlot();
2153
+ if (slot) return createPortal(children, slot);
2154
+ return /* @__PURE__ */ jsx(Fragment, { children });
2155
+ }
1770
2156
  function PageHeaderActions({ children }) {
1771
2157
  const slot = useModuleHeaderActionsSlot();
2158
+ useRegisterPageActions();
1772
2159
  if (slot) return createPortal(children, slot);
1773
2160
  return /* @__PURE__ */ jsx("div", { className: "mb-4 flex items-center justify-end gap-2", children });
1774
2161
  }
@@ -1777,15 +2164,15 @@ function PremiumBadge({ className }) {
1777
2164
  const tip = t("upgrade.badgeTooltip");
1778
2165
  return /* @__PURE__ */ jsx("span", { title: tip, "aria-label": tip, className: cn("inline-flex shrink-0", className), children: /* @__PURE__ */ jsx(Crown, { className: "h-3.5 w-3.5 text-amber-500", "aria-hidden": true }) });
1779
2166
  }
1780
- var ModuleLayoutContext = React.createContext("rail");
2167
+ var ModuleLayoutContext = React7.createContext("rail");
1781
2168
  function ModuleLayoutProvider({ variant, children }) {
1782
2169
  return /* @__PURE__ */ jsx(ModuleLayoutContext.Provider, { value: variant, children });
1783
2170
  }
1784
2171
  function useModuleLayout() {
1785
- return React.useContext(ModuleLayoutContext);
2172
+ return React7.useContext(ModuleLayoutContext);
1786
2173
  }
1787
- function NavItem2({ item }) {
1788
- const [expanded, setExpanded] = React.useState(true);
2174
+ function RailNavItem({ item }) {
2175
+ const [expanded, setExpanded] = React7.useState(true);
1789
2176
  const Icon = item.icon ? ICON_MAP2[item.icon] ?? null : null;
1790
2177
  const hasChildren = item.children && item.children.length > 0;
1791
2178
  const hasActiveChild = hasChildren && item.children.some((c) => c.active);
@@ -1827,9 +2214,9 @@ function NavItem2({ item }) {
1827
2214
  ] });
1828
2215
  }
1829
2216
  function MobileTabs({ nav }) {
1830
- const scrollRef = React.useRef(null);
1831
- const [expandedId, setExpandedId] = React.useState(null);
1832
- React.useEffect(() => {
2217
+ const scrollRef = React7.useRef(null);
2218
+ const [expandedId, setExpandedId] = React7.useState(null);
2219
+ React7.useEffect(() => {
1833
2220
  const container = scrollRef.current;
1834
2221
  if (!container) return;
1835
2222
  const activeEl = container.querySelector('[data-active="true"]');
@@ -1896,12 +2283,12 @@ function MobileTabs({ nav }) {
1896
2283
  })()
1897
2284
  ] });
1898
2285
  }
1899
- var BackStyleContext = React.createContext("link");
2286
+ var BackStyleContext = React7.createContext("link");
1900
2287
  function BackStyleProvider({ style, children }) {
1901
2288
  return /* @__PURE__ */ jsx(BackStyleContext.Provider, { value: style, children });
1902
2289
  }
1903
2290
  function useBackStyle() {
1904
- return React.useContext(BackStyleContext);
2291
+ return React7.useContext(BackStyleContext);
1905
2292
  }
1906
2293
  function SubpageHeader({ title, subtitle, icon, onBack, parentLabel, actions, backStyle, inline }) {
1907
2294
  const t = useTranslation();
@@ -1997,13 +2384,11 @@ function ShellSubpageHeader({
1997
2384
  /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 items-center gap-2", children: [
1998
2385
  Icon && !onBack && /* @__PURE__ */ jsx(Icon, { className: "h-4 w-4 shrink-0 text-muted-foreground" }),
1999
2386
  /* @__PURE__ */ jsx(PageHeaderCrumb, { title, parentLabel, onBack })
2000
- ] })
2387
+ ] }),
2388
+ { back: !!onBack }
2001
2389
  );
2002
- if (!subtitle && !actions) return null;
2003
- return /* @__PURE__ */ jsxs("div", { className: "mb-5 flex flex-wrap items-start justify-between gap-3", children: [
2004
- subtitle ? /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: subtitle }) : /* @__PURE__ */ jsx("span", {}),
2005
- actions && /* @__PURE__ */ jsx("div", { className: "no-print flex shrink-0 items-center gap-2", children: actions })
2006
- ] });
2390
+ if (!actions) return null;
2391
+ return /* @__PURE__ */ jsx(PageHeaderActions, { children: /* @__PURE__ */ jsx("div", { className: "no-print flex shrink-0 items-center gap-2", children: actions }) });
2007
2392
  }
2008
2393
  function ModuleHeader({ nav }) {
2009
2394
  function defaultChild(item) {
@@ -2026,12 +2411,13 @@ function ModuleHeader({ nav }) {
2026
2411
  if (hasChildren) defaultChild(item)?.onClick?.();
2027
2412
  else item.onClick?.();
2028
2413
  };
2029
- return /* @__PURE__ */ jsx("div", { className: "mb-6 flex flex-wrap items-center justify-between gap-3", children: /* @__PURE__ */ jsx(HeaderTabs, { tabs, value: activeItem?.id ?? nav[0]?.id ?? "", onChange }) });
2414
+ return /* @__PURE__ */ jsx(PageTabs, { tabs, value: activeItem?.id ?? nav[0]?.id ?? "", onChange });
2030
2415
  }
2031
- function ModulePage({ title, subtitle, nav, children, className, headerAction, showHeader = true, sidebarFooter, navVariant, viewKey, direction }) {
2416
+ function ModulePage({ title: _title, subtitle, nav, children, className, headerAction, showHeader = true, sidebarFooter, navVariant, viewKey, direction }) {
2032
2417
  const ctxVariant = useModuleLayout();
2033
2418
  const variant = navVariant ?? ctxVariant;
2034
2419
  const transitionKey = viewKey ?? "view";
2420
+ const onSubpage = useHasPageHeaderBack();
2035
2421
  if (variant === "sidebar") {
2036
2422
  return /* @__PURE__ */ jsxs(Fragment, { children: [
2037
2423
  showHeader && headerAction && /* @__PURE__ */ jsx(PageHeaderActions, { children: headerAction }),
@@ -2040,148 +2426,24 @@ function ModulePage({ title, subtitle, nav, children, className, headerAction, s
2040
2426
  }
2041
2427
  if (variant === "tabs") {
2042
2428
  return /* @__PURE__ */ jsxs(Fragment, { children: [
2043
- /* @__PURE__ */ jsx(ModuleHeader, { nav }),
2429
+ !onSubpage && /* @__PURE__ */ jsx(ModuleHeader, { nav }),
2044
2430
  showHeader && headerAction && /* @__PURE__ */ jsx(PageHeaderActions, { children: headerAction }),
2045
2431
  /* @__PURE__ */ jsx(PageTransition, { transitionKey, direction, className: cn("min-w-0", className), children })
2046
2432
  ] });
2047
2433
  }
2048
- return /* @__PURE__ */ jsxs("div", { className: cn("flex gap-6 -mt-2", className), children: [
2049
- /* @__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: [
2050
- /* @__PURE__ */ jsxs("div", { children: [
2051
- /* @__PURE__ */ jsx("p", { className: "text-xs font-semibold text-muted-foreground uppercase tracking-wider px-2.5 mb-2", children: title }),
2052
- /* @__PURE__ */ jsx("nav", { className: "space-y-0.5", children: nav.map((item) => /* @__PURE__ */ jsx(NavItem2, { item }, item.id)) })
2053
- ] }),
2054
- sidebarFooter && /* @__PURE__ */ jsx("div", { className: "mt-auto", children: sidebarFooter })
2434
+ return /* @__PURE__ */ jsxs("div", { className: cn("min-w-0", className), children: [
2435
+ /* @__PURE__ */ jsx(PageRail, { children: /* @__PURE__ */ jsxs("div", { className: "hidden w-48 shrink-0 flex-col overflow-y-auto border-r border-border p-3 no-print md:flex", children: [
2436
+ /* @__PURE__ */ jsx("nav", { className: "space-y-0.5", children: nav.map((item) => /* @__PURE__ */ jsx(RailNavItem, { item }, item.id)) }),
2437
+ sidebarFooter && /* @__PURE__ */ jsx("div", { className: "mt-auto pt-3", children: sidebarFooter })
2055
2438
  ] }) }),
2056
- /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
2057
- /* @__PURE__ */ jsx(PageHeaderOutlet, {}),
2058
- showHeader && /* @__PURE__ */ jsxs("div", { className: "mb-6 flex items-start justify-between gap-3", children: [
2059
- /* @__PURE__ */ jsxs("div", { children: [
2060
- /* @__PURE__ */ jsx("h1", { className: "text-2xl font-bold", children: title }),
2061
- subtitle && /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: subtitle })
2062
- ] }),
2063
- headerAction
2064
- ] }),
2439
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
2440
+ showHeader && headerAction && /* @__PURE__ */ jsx(PageHeaderActions, { children: headerAction }),
2065
2441
  /* @__PURE__ */ jsx(MobileTabs, { nav }),
2066
2442
  /* @__PURE__ */ jsx(PageTransition, { transitionKey, direction, children })
2067
2443
  ] })
2068
2444
  ] });
2069
2445
  }
2070
- var EMPTY = [];
2071
- var PageTrailContext = React.createContext(EMPTY);
2072
- function PageTrail({
2073
- label,
2074
- href,
2075
- onNavigate,
2076
- children
2077
- }) {
2078
- const parent = React.useContext(PageTrailContext);
2079
- const trail = React.useMemo(
2080
- () => [...parent, { label, href, onNavigate }],
2081
- [parent, label, href, onNavigate]
2082
- );
2083
- return /* @__PURE__ */ jsx(PageTrailContext.Provider, { value: trail, children });
2084
- }
2085
- function usePageTrail() {
2086
- return React.useContext(PageTrailContext);
2087
- }
2088
- function usePageTrailParent(href) {
2089
- const trail = React.useContext(PageTrailContext);
2090
- return React.useMemo(() => {
2091
- const above = href ? trail.filter((c) => c.href !== href) : trail;
2092
- return above[above.length - 1];
2093
- }, [trail, href]);
2094
- }
2095
- function navigateToCrumb(crumb) {
2096
- if (crumb.onNavigate) crumb.onNavigate();
2097
- else window.location.hash = crumb.href;
2098
- }
2099
- var ContentSplitContext = React.createContext(null);
2100
- function ContentSplit({
2101
- children,
2102
- aside,
2103
- asideTitle,
2104
- asideDescription,
2105
- asideWidth = "20rem",
2106
- asideOpen,
2107
- onAsideOpenChange,
2108
- collapsible = false,
2109
- defaultCollapsed = false,
2110
- className
2111
- }) {
2112
- const [internalOpen, setInternalOpen] = React.useState(false);
2113
- const open = asideOpen ?? internalOpen;
2114
- const setOpen = onAsideOpenChange ?? setInternalOpen;
2115
- const [collapsed, setCollapsed] = React.useState(collapsible && defaultCollapsed);
2116
- return /* @__PURE__ */ jsxs(ContentSplitContext.Provider, { value: { open, setOpen }, children: [
2117
- /* @__PURE__ */ jsxs(
2118
- "div",
2119
- {
2120
- className: cn("lg:grid lg:items-start lg:gap-6", className),
2121
- style: { gridTemplateColumns: `minmax(0, 1fr) ${collapsed ? "auto" : asideWidth}` },
2122
- children: [
2123
- /* @__PURE__ */ jsx("div", { className: "min-w-0", children }),
2124
- /* @__PURE__ */ jsx("aside", { className: "hidden lg:block lg:sticky lg:top-4", children: collapsed ? /* @__PURE__ */ jsx(
2125
- Button,
2126
- {
2127
- variant: "outline",
2128
- size: "sm",
2129
- onClick: () => setCollapsed(false),
2130
- className: "h-9 w-9 p-0",
2131
- "aria-label": typeof asideTitle === "string" ? asideTitle : void 0,
2132
- title: typeof asideTitle === "string" ? asideTitle : void 0,
2133
- children: /* @__PURE__ */ jsx(PanelRightOpen, { className: "h-4 w-4" })
2134
- }
2135
- ) : /* @__PURE__ */ jsxs("div", { className: "rounded-card border border-border bg-card", children: [
2136
- /* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between gap-2 border-b border-border px-4 py-3", children: [
2137
- /* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
2138
- /* @__PURE__ */ jsx("p", { className: "text-sm font-semibold text-foreground", children: asideTitle }),
2139
- asideDescription && /* @__PURE__ */ jsx("p", { className: "mt-0.5 text-xs text-muted-foreground", children: asideDescription })
2140
- ] }),
2141
- collapsible && /* @__PURE__ */ jsx(
2142
- "button",
2143
- {
2144
- type: "button",
2145
- onClick: () => setCollapsed(true),
2146
- className: "rounded p-1 text-muted-foreground transition-colors hover:bg-muted/60 hover:text-foreground",
2147
- "aria-label": "collapse",
2148
- children: /* @__PURE__ */ jsx(PanelRightClose, { className: "h-4 w-4" })
2149
- }
2150
- )
2151
- ] }),
2152
- /* @__PURE__ */ jsx("div", { className: "p-4", children: aside })
2153
- ] }) })
2154
- ]
2155
- }
2156
- ),
2157
- /* @__PURE__ */ jsx("div", { className: "lg:hidden", children: /* @__PURE__ */ jsx(Sheet, { open, onOpenChange: setOpen, children: /* @__PURE__ */ jsxs(SheetContent, { width: "max-w-sm", children: [
2158
- /* @__PURE__ */ jsxs(SheetHeader, { children: [
2159
- /* @__PURE__ */ jsx(SheetTitle, { children: asideTitle }),
2160
- asideDescription && /* @__PURE__ */ jsx(SheetDescription, { children: asideDescription })
2161
- ] }),
2162
- /* @__PURE__ */ jsx(SheetBody, { children: aside })
2163
- ] }) }) })
2164
- ] });
2165
- }
2166
- function ContentSplitTrigger({ label, iconOnly, className }) {
2167
- const ctx = React.useContext(ContentSplitContext);
2168
- if (!ctx) return null;
2169
- return /* @__PURE__ */ jsxs(
2170
- Button,
2171
- {
2172
- variant: "outline",
2173
- size: "sm",
2174
- onClick: () => ctx.setOpen(true),
2175
- className: cn("lg:hidden", className),
2176
- "aria-label": typeof label === "string" ? label : void 0,
2177
- children: [
2178
- /* @__PURE__ */ jsx(PanelRight, { className: cn("h-4 w-4", !iconOnly && "mr-1.5") }),
2179
- !iconOnly && label
2180
- ]
2181
- }
2182
- );
2183
- }
2184
2446
 
2185
- export { AppShell, AppShellSkeleton, BackStyleProvider, ContentSplit, ContentSplitTrigger, ModuleLayoutProvider, ModulePage, NavTransitionProvider, PageHeaderActions, PageTrail, PageTransition, Sidebar, SubpageHeader, Topbar, navigateToCrumb, useBackStyle, useModuleHeaderActionsSlot, useModuleHeaderSlot, useModuleLayout, useNavTransition, usePageTrail, usePageTrailParent };
2186
- //# sourceMappingURL=chunk-ZDRHCKRK.js.map
2187
- //# sourceMappingURL=chunk-ZDRHCKRK.js.map
2447
+ export { AppShell, BackStyleProvider, HeaderTabs, ModuleLayoutProvider, ModulePage, NavTransitionProvider, PageHeader, PageHeaderActions, PageHeaderCrumb, PageRail, PageTabs, PageTransition, RailNavItem, SaveBar, SaveBarProvider, Sheet, SheetBody, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetOverlay, SheetPortal, SheetTitle, SheetTrigger, Sidebar, SubpageHeader, Topbar, useBackHandler, useBackStyle, useCrossModuleBack, useModuleHeaderActionsSlot, useModuleHeaderSlot, useModuleLayout, useNavTransition, useRailRow, useSaveBar, useSaveBarVisible };
2448
+ //# sourceMappingURL=chunk-MVH64JFG.js.map
2449
+ //# sourceMappingURL=chunk-MVH64JFG.js.map