@fayz-ai/ui 0.21.0 → 0.23.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 (96) hide show
  1. package/dist/analytics/AnalyticsCard.d.ts.map +1 -1
  2. package/dist/analytics/AnalyticsControls.d.ts.map +1 -1
  3. package/dist/analytics/DateRangeControl.d.ts +14 -0
  4. package/dist/analytics/DateRangeControl.d.ts.map +1 -0
  5. package/dist/analytics/StatementTable.d.ts +22 -0
  6. package/dist/analytics/StatementTable.d.ts.map +1 -0
  7. package/dist/analytics/context.d.ts +8 -0
  8. package/dist/analytics/context.d.ts.map +1 -1
  9. package/dist/analytics/derive.d.ts +13 -0
  10. package/dist/analytics/derive.d.ts.map +1 -1
  11. package/dist/analytics/index.d.ts +7 -1
  12. package/dist/analytics/index.d.ts.map +1 -1
  13. package/dist/analytics/statement.d.ts +58 -0
  14. package/dist/analytics/statement.d.ts.map +1 -0
  15. package/dist/{chunk-E44WOPCR.js → chunk-HO2P2XB4.js} +428 -58
  16. package/dist/chunk-HO2P2XB4.js.map +1 -0
  17. package/dist/{chunk-P3YTL3MF.js → chunk-L4TXDKMN.js} +5 -5
  18. package/dist/chunk-L4TXDKMN.js.map +1 -0
  19. package/dist/{chunk-AIUHVTVK.js → chunk-OWN3GFDG.js} +5 -3
  20. package/dist/chunk-OWN3GFDG.js.map +1 -0
  21. package/dist/{chunk-EGHEU3U5.js → chunk-RRNMNFLF.js} +492 -789
  22. package/dist/chunk-RRNMNFLF.js.map +1 -0
  23. package/dist/chunk-UP7WVKSU.js +631 -0
  24. package/dist/chunk-UP7WVKSU.js.map +1 -0
  25. package/dist/{chunk-OYH7CEMB.js → chunk-XEUIYSW2.js} +330 -366
  26. package/dist/chunk-XEUIYSW2.js.map +1 -0
  27. package/dist/{chunk-MBGTFL4I.js → chunk-XKFYX3DG.js} +39 -8
  28. package/dist/chunk-XKFYX3DG.js.map +1 -0
  29. package/dist/dashboard/OnboardingWidget.d.ts +4 -1
  30. package/dist/dashboard/OnboardingWidget.d.ts.map +1 -1
  31. package/dist/dashboard/SidebarOnboarding.d.ts +11 -1
  32. package/dist/dashboard/SidebarOnboarding.d.ts.map +1 -1
  33. package/dist/dashboard/index.js +4 -4
  34. package/dist/icons.js +1 -1
  35. package/dist/index.d.ts +3 -3
  36. package/dist/index.d.ts.map +1 -1
  37. package/dist/index.js +1109 -166
  38. package/dist/index.js.map +1 -1
  39. package/dist/layout/AppShell.d.ts +7 -1
  40. package/dist/layout/AppShell.d.ts.map +1 -1
  41. package/dist/layout/ModulePage.d.ts.map +1 -1
  42. package/dist/layout/SaveBar.d.ts.map +1 -1
  43. package/dist/layout/ShellSearch.d.ts +53 -0
  44. package/dist/layout/ShellSearch.d.ts.map +1 -0
  45. package/dist/layout/Sidebar.d.ts +10 -6
  46. package/dist/layout/Sidebar.d.ts.map +1 -1
  47. package/dist/layout/Takeover.d.ts.map +1 -1
  48. package/dist/layout/index.d.ts +1 -0
  49. package/dist/layout/index.d.ts.map +1 -1
  50. package/dist/layout/index.js +4 -4
  51. package/dist/primitives/data-table.d.ts.map +1 -1
  52. package/dist/primitives/date-range-chip.d.ts +32 -0
  53. package/dist/primitives/date-range-chip.d.ts.map +1 -0
  54. package/dist/primitives/header-tabs.d.ts +7 -1
  55. package/dist/primitives/header-tabs.d.ts.map +1 -1
  56. package/dist/primitives/index.d.ts +2 -1
  57. package/dist/primitives/index.d.ts.map +1 -1
  58. package/dist/primitives/index.js +5 -5
  59. package/dist/primitives/list-view.d.ts +43 -1
  60. package/dist/primitives/list-view.d.ts.map +1 -1
  61. package/dist/primitives/modal.d.ts +2 -0
  62. package/dist/primitives/modal.d.ts.map +1 -1
  63. package/dist/primitives/page-actions.d.ts +14 -0
  64. package/dist/primitives/page-actions.d.ts.map +1 -1
  65. package/dist/primitives/page-toolbar.d.ts +16 -1
  66. package/dist/primitives/page-toolbar.d.ts.map +1 -1
  67. package/dist/primitives/search-select.d.ts +3 -1
  68. package/dist/primitives/search-select.d.ts.map +1 -1
  69. package/dist/primitives/segmented-control.d.ts.map +1 -1
  70. package/dist/primitives/stock-entry-form.d.ts +4 -1
  71. package/dist/primitives/stock-entry-form.d.ts.map +1 -1
  72. package/dist/stores/layout.store.d.ts +20 -1
  73. package/dist/stores/layout.store.d.ts.map +1 -1
  74. package/dist/styles.css +133 -0
  75. package/dist/tour/PracticeOverlay.d.ts +12 -0
  76. package/dist/tour/PracticeOverlay.d.ts.map +1 -0
  77. package/dist/tour/TourSpotlight.d.ts.map +1 -1
  78. package/dist/tour/confetti.d.ts +17 -0
  79. package/dist/tour/confetti.d.ts.map +1 -0
  80. package/dist/tour/index.d.ts +3 -0
  81. package/dist/tour/index.d.ts.map +1 -1
  82. package/dist/tour/practice.store.d.ts +28 -0
  83. package/dist/tour/practice.store.d.ts.map +1 -0
  84. package/dist/tour/tour.store.d.ts +4 -0
  85. package/dist/tour/tour.store.d.ts.map +1 -1
  86. package/dist/tour/types.d.ts +17 -0
  87. package/dist/tour/types.d.ts.map +1 -1
  88. package/package.json +2 -2
  89. package/dist/chunk-AIUHVTVK.js.map +0 -1
  90. package/dist/chunk-E44WOPCR.js.map +0 -1
  91. package/dist/chunk-EGHEU3U5.js.map +0 -1
  92. package/dist/chunk-HKGTF2YA.js +0 -117
  93. package/dist/chunk-HKGTF2YA.js.map +0 -1
  94. package/dist/chunk-MBGTFL4I.js.map +0 -1
  95. package/dist/chunk-OYH7CEMB.js.map +0 -1
  96. package/dist/chunk-P3YTL3MF.js.map +0 -1
@@ -1,134 +1,15 @@
1
- import { useHasPageHeaderHost, usePageHeader, PageTitlePortal, useLayoutStore, useHasPageHeaderBack, useRegisterPageActions, usePageHeaderClaimed, usePageTitleSlotHost, useHasPageHeaderOutlet, useHasPageActions, usePageHeaderHost } from './chunk-HKGTF2YA.js';
2
1
  import { cn } from './chunk-OKZ6WQB3.js';
3
- import { ICON_MAP } from './chunk-AIUHVTVK.js';
4
- import * as React2 from 'react';
5
- import * as DialogPrimitive from '@radix-ui/react-dialog';
6
- import { X, Loader2, ArrowLeft, Search, Bell, PanelLeft, PanelLeftClose, Menu, Crown, ChevronDown, ChevronRight, ChevronLeft, ExternalLink, CreditCard, Sparkles, Home, Plus, UserCog, Settings, LogOut } from 'lucide-react';
7
- import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
2
+ import { ICON_MAP } from './chunk-OWN3GFDG.js';
3
+ import * as React from 'react';
4
+ import { Loader2, ArrowLeft, Search, CornerDownLeft, X, Bell, PanelLeft, PanelLeftClose, Menu, Crown, ExternalLink, CreditCard, Sparkles, ChevronDown, Home, ChevronRight, Plus, UserCog, Settings, LogOut } from 'lucide-react';
8
5
  import { useTranslation, useNavReferrer, routeModule, navHistoryBack } from '@fayz-ai/core';
9
- import * as Popover from '@radix-ui/react-popover';
6
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
10
7
  import { createPortal } from 'react-dom';
8
+ import { create } from 'zustand';
9
+ import * as Popover from '@radix-ui/react-popover';
11
10
 
12
- var STYLE_ID = "saas-sheet-styles";
13
- if (typeof document !== "undefined" && !document.getElementById(STYLE_ID)) {
14
- const style = document.createElement("style");
15
- style.id = STYLE_ID;
16
- style.textContent = `
17
- @keyframes saas-sheet-fade-in { from { opacity: 0 } to { opacity: 1 } }
18
- @keyframes saas-sheet-fade-out { from { opacity: 1 } to { opacity: 0 } }
19
- @keyframes saas-sheet-slide-in { from { transform: translateX(100%) } to { transform: translateX(0) } }
20
- @keyframes saas-sheet-slide-out { from { transform: translateX(0) } to { transform: translateX(100%) } }
21
- .saas-sheet-overlay[data-state="open"] { animation: saas-sheet-fade-in 200ms ease-out }
22
- .saas-sheet-overlay[data-state="closed"] { animation: saas-sheet-fade-out 150ms ease-in }
23
- .saas-sheet-content[data-state="open"] { animation: saas-sheet-slide-in 280ms cubic-bezier(0.16, 1, 0.3, 1) }
24
- .saas-sheet-content[data-state="closed"] { animation: saas-sheet-slide-out 200ms ease-in }
25
- `;
26
- document.head.appendChild(style);
27
- }
28
- var Sheet = DialogPrimitive.Root;
29
- var SheetTrigger = DialogPrimitive.Trigger;
30
- var SheetClose = DialogPrimitive.Close;
31
- var SheetPortal = DialogPrimitive.Portal;
32
- var SheetOverlay = React2.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(
33
- DialogPrimitive.Overlay,
34
- {
35
- ref,
36
- className: cn(
37
- "saas-sheet-overlay fixed inset-0 z-[100] bg-black/40 backdrop-blur-[2px]",
38
- className
39
- ),
40
- ...props
41
- }
42
- ));
43
- SheetOverlay.displayName = "SheetOverlay";
44
- var SheetContent = React2.forwardRef(({ className, children, width, hideClose, overlay = "blur", onPointerDownOutside, onInteractOutside, ...props }, ref) => /* @__PURE__ */ jsxs(SheetPortal, { children: [
45
- /* @__PURE__ */ jsx(SheetOverlay, { className: overlay === "none" ? "bg-transparent backdrop-blur-0" : overlay === "dim" ? "backdrop-blur-0" : void 0 }),
46
- /* @__PURE__ */ jsxs(
47
- DialogPrimitive.Content,
48
- {
49
- ref,
50
- onPointerDownOutside: (e) => {
51
- const target = e.target;
52
- if (target.closest("[data-modal-passthrough]") || target.closest("[data-radix-popper-content-wrapper]")) {
53
- e.preventDefault();
54
- }
55
- onPointerDownOutside?.(e);
56
- },
57
- onInteractOutside: (e) => {
58
- const target = e.target;
59
- if (target.closest("[data-modal-passthrough]") || target.closest("[data-radix-popper-content-wrapper]")) {
60
- e.preventDefault();
61
- }
62
- onInteractOutside?.(e);
63
- },
64
- className: cn(
65
- "fayz-glass-surface saas-sheet-content fixed inset-y-0 right-0 z-[100] flex flex-col border-l bg-card shadow-2xl outline-none",
66
- "w-full",
67
- width ?? "max-w-md",
68
- className
69
- ),
70
- ...props,
71
- children: [
72
- children,
73
- !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: [
74
- /* @__PURE__ */ jsx(X, { className: "h-4 w-4" }),
75
- /* @__PURE__ */ jsx("span", { className: "sr-only", children: "Close" })
76
- ] })
77
- ]
78
- }
79
- )
80
- ] }));
81
- SheetContent.displayName = "SheetContent";
82
- function SheetHeader({ className, ...props }) {
83
- return /* @__PURE__ */ jsx(
84
- "div",
85
- {
86
- className: cn("flex flex-col gap-1 px-5 py-4 border-b shrink-0", className),
87
- ...props
88
- }
89
- );
90
- }
91
- SheetHeader.displayName = "SheetHeader";
92
- function SheetFooter({ className, ...props }) {
93
- return /* @__PURE__ */ jsx(
94
- "div",
95
- {
96
- className: cn("flex items-center gap-2 px-5 py-3 border-t shrink-0", className),
97
- ...props
98
- }
99
- );
100
- }
101
- SheetFooter.displayName = "SheetFooter";
102
- var SheetTitle = React2.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(
103
- DialogPrimitive.Title,
104
- {
105
- ref,
106
- className: cn("text-sm font-semibold leading-none tracking-tight", className),
107
- ...props
108
- }
109
- ));
110
- SheetTitle.displayName = "SheetTitle";
111
- var SheetDescription = React2.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(
112
- DialogPrimitive.Description,
113
- {
114
- ref,
115
- className: cn("text-xs text-muted-foreground", className),
116
- ...props
117
- }
118
- ));
119
- SheetDescription.displayName = "SheetDescription";
120
- function SheetBody({ className, ...props }) {
121
- return /* @__PURE__ */ jsx(
122
- "div",
123
- {
124
- className: cn("flex-1 overflow-y-auto px-5 py-5", className),
125
- ...props
126
- }
127
- );
128
- }
129
- SheetBody.displayName = "SheetBody";
130
- var SaveBarApiContext = React2.createContext(null);
131
- var SaveBarStateContext = React2.createContext({ visible: false, saving: false });
11
+ var SaveBarApiContext = React.createContext(null);
12
+ var SaveBarStateContext = React.createContext({ visible: false, saving: false });
132
13
  var _saveBarVisible = false;
133
14
  var _saveBarSubs = /* @__PURE__ */ new Set();
134
15
  function setSaveBarVisibleSignal(v) {
@@ -137,7 +18,7 @@ function setSaveBarVisibleSignal(v) {
137
18
  _saveBarSubs.forEach((fn) => fn());
138
19
  }
139
20
  function useSaveBarVisible() {
140
- return React2.useSyncExternalStore(
21
+ return React.useSyncExternalStore(
141
22
  (cb) => {
142
23
  _saveBarSubs.add(cb);
143
24
  return () => {
@@ -149,15 +30,15 @@ function useSaveBarVisible() {
149
30
  );
150
31
  }
151
32
  function SaveBarProvider({ children }) {
152
- const [state, setState] = React2.useState({ visible: false, saving: false });
153
- const handlers = React2.useRef({});
154
- const api = React2.useMemo(() => ({ setState, handlers }), []);
155
- const backStack = React2.useRef([]);
156
- const backApi = React2.useMemo(() => ({ stack: backStack }), []);
157
- React2.useEffect(() => {
33
+ const [state, setState] = React.useState({ visible: false, saving: false });
34
+ const handlers = React.useRef({});
35
+ const api = React.useMemo(() => ({ setState, handlers }), []);
36
+ const backStack = React.useRef([]);
37
+ const backApi = React.useMemo(() => ({ stack: backStack }), []);
38
+ React.useEffect(() => {
158
39
  setSaveBarVisibleSignal(state.visible);
159
40
  }, [state.visible]);
160
- React2.useEffect(() => {
41
+ React.useEffect(() => {
161
42
  function onKey(e) {
162
43
  if (state.visible && !state.saving) {
163
44
  if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) {
@@ -185,7 +66,7 @@ function SaveBarProvider({ children }) {
185
66
  }, [state.visible, state.saving]);
186
67
  return /* @__PURE__ */ jsx(SaveBarApiContext.Provider, { value: api, children: /* @__PURE__ */ jsx(BackNavApiContext.Provider, { value: backApi, children: /* @__PURE__ */ jsx(SaveBarStateContext.Provider, { value: state, children }) }) });
187
68
  }
188
- var BackNavApiContext = React2.createContext(null);
69
+ var BackNavApiContext = React.createContext(null);
189
70
  function isEditableTarget(target) {
190
71
  const el = target;
191
72
  if (!el || !el.tagName) return false;
@@ -193,12 +74,12 @@ function isEditableTarget(target) {
193
74
  return tag === "input" || tag === "textarea" || tag === "select" || el.isContentEditable;
194
75
  }
195
76
  function useBackHandler(onBack) {
196
- const api = React2.useContext(BackNavApiContext);
197
- const ref = React2.useRef(onBack);
198
- React2.useEffect(() => {
77
+ const api = React.useContext(BackNavApiContext);
78
+ const ref = React.useRef(onBack);
79
+ React.useEffect(() => {
199
80
  ref.current = onBack;
200
81
  });
201
- React2.useEffect(() => {
82
+ React.useEffect(() => {
202
83
  if (!api) return;
203
84
  const stack = api.stack.current;
204
85
  stack.push(ref);
@@ -209,23 +90,23 @@ function useBackHandler(onBack) {
209
90
  }, [api]);
210
91
  }
211
92
  function useSaveBar({ dirty, alwaysVisible, saving = false, onSave, onDiscard, message, saveLabel, discardLabel }) {
212
- const api = React2.useContext(SaveBarApiContext);
213
- React2.useEffect(() => {
93
+ const api = React.useContext(SaveBarApiContext);
94
+ React.useEffect(() => {
214
95
  if (api) api.handlers.current = { onSave, onDiscard };
215
96
  });
216
- React2.useEffect(() => {
97
+ React.useEffect(() => {
217
98
  if (!api) return;
218
99
  api.setState({ visible: dirty || !!alwaysVisible, dirty, saving, message, saveLabel, discardLabel });
219
100
  return () => api.setState((s) => ({ ...s, visible: false }));
220
101
  }, [api, dirty, alwaysVisible, saving, message, saveLabel, discardLabel]);
221
102
  }
222
103
  function SaveBar({ className }) {
223
- const state = React2.useContext(SaveBarStateContext);
224
- const api = React2.useContext(SaveBarApiContext);
104
+ const state = React.useContext(SaveBarStateContext);
105
+ const api = React.useContext(SaveBarApiContext);
225
106
  const t = useTranslation();
226
- const [rendered, setRendered] = React2.useState(state.visible);
227
- const [closing, setClosing] = React2.useState(false);
228
- React2.useEffect(() => {
107
+ const [rendered, setRendered] = React.useState(state.visible);
108
+ const [closing, setClosing] = React.useState(false);
109
+ React.useEffect(() => {
229
110
  if (state.visible) {
230
111
  setRendered(true);
231
112
  setClosing(false);
@@ -264,6 +145,7 @@ function SaveBar({ className }) {
264
145
  "button",
265
146
  {
266
147
  type: "button",
148
+ "data-tour": "savebar.discard",
267
149
  disabled: saving,
268
150
  onClick: () => api.handlers.current.onDiscard?.(),
269
151
  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",
@@ -274,6 +156,7 @@ function SaveBar({ className }) {
274
156
  "button",
275
157
  {
276
158
  type: "button",
159
+ "data-tour": "savebar.save",
277
160
  disabled: saving,
278
161
  onClick: () => api.handlers.current.onSave?.(),
279
162
  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",
@@ -288,6 +171,98 @@ function SaveBar({ className }) {
288
171
  }
289
172
  );
290
173
  }
174
+ var usePageHeaderStore = create((set, get) => ({
175
+ shellSlot: null,
176
+ setShellSlot: (element) => set({ shellSlot: element }),
177
+ outletSlot: null,
178
+ setOutletSlot: (element) => set({ outletSlot: element }),
179
+ claimed: false,
180
+ owner: null,
181
+ hasBack: false,
182
+ hosts: 0,
183
+ outlets: 0,
184
+ actions: 0,
185
+ claim: (owner, hasBack = false) => set({ owner, claimed: true, hasBack }),
186
+ release: (owner) => {
187
+ if (get().owner === owner) set({ owner: null, claimed: false, hasBack: false });
188
+ },
189
+ addHost: () => set((s) => ({ hosts: s.hosts + 1 })),
190
+ removeHost: () => set((s) => ({ hosts: Math.max(0, s.hosts - 1) })),
191
+ addOutlet: () => set((s) => ({ outlets: s.outlets + 1 })),
192
+ removeOutlet: () => set((s) => ({ outlets: Math.max(0, s.outlets - 1) })),
193
+ addActions: () => set((s) => ({ actions: s.actions + 1 })),
194
+ removeActions: () => set((s) => ({ actions: Math.max(0, s.actions - 1) }))
195
+ }));
196
+ function useRegisterPageActions() {
197
+ const add = usePageHeaderStore((s) => s.addActions);
198
+ const remove = usePageHeaderStore((s) => s.removeActions);
199
+ React.useEffect(() => {
200
+ add();
201
+ return remove;
202
+ }, [add, remove]);
203
+ }
204
+ function useHasPageActions() {
205
+ return usePageHeaderStore((s) => s.actions > 0);
206
+ }
207
+ function usePageHeaderClaimed() {
208
+ return usePageHeaderStore((s) => s.claimed);
209
+ }
210
+ function usePageTitleSlot() {
211
+ return usePageHeaderStore((s) => s.outletSlot ?? s.shellSlot);
212
+ }
213
+ function usePageTitleSlotHost() {
214
+ return usePageHeaderStore((s) => s.setShellSlot);
215
+ }
216
+ function usePageHeaderHost() {
217
+ const addHost = usePageHeaderStore((s) => s.addHost);
218
+ const removeHost = usePageHeaderStore((s) => s.removeHost);
219
+ React.useEffect(() => {
220
+ addHost();
221
+ return removeHost;
222
+ }, [addHost, removeHost]);
223
+ }
224
+ function useHasPageHeaderHost() {
225
+ return usePageHeaderStore((s) => s.hosts > 0 || s.outlets > 0);
226
+ }
227
+ function useHasPageHeaderOutlet() {
228
+ return usePageHeaderStore((s) => s.outlets > 0);
229
+ }
230
+ function PageHeaderOutlet({ className }) {
231
+ const setOutletSlot = usePageHeaderStore((s) => s.setOutletSlot);
232
+ const claimed = usePageHeaderStore((s) => s.claimed);
233
+ const addOutlet = usePageHeaderStore((s) => s.addOutlet);
234
+ const removeOutlet = usePageHeaderStore((s) => s.removeOutlet);
235
+ React.useEffect(() => {
236
+ addOutlet();
237
+ return removeOutlet;
238
+ }, [addOutlet, removeOutlet]);
239
+ return React.createElement("div", {
240
+ ref: setOutletSlot,
241
+ className: `${className ?? "mb-4"}${claimed ? "" : " hidden"}`
242
+ });
243
+ }
244
+ function usePageHeader(active, options) {
245
+ const id = React.useId();
246
+ const claim = usePageHeaderStore((s) => s.claim);
247
+ const release = usePageHeaderStore((s) => s.release);
248
+ const back = options?.back ?? false;
249
+ React.useEffect(() => {
250
+ if (!active) {
251
+ release(id);
252
+ return;
253
+ }
254
+ claim(id, back);
255
+ return () => release(id);
256
+ }, [id, active, back, claim, release]);
257
+ }
258
+ function useHasPageHeaderBack() {
259
+ return usePageHeaderStore((s) => s.hasBack);
260
+ }
261
+ function PageTitlePortal({ children }) {
262
+ const slot = usePageTitleSlot();
263
+ if (!slot) return React.createElement(React.Fragment, null, children);
264
+ return createPortal(children, slot);
265
+ }
291
266
  function useCrossModuleBack(onBack, parentLabel) {
292
267
  const currentPath = typeof window !== "undefined" ? (window.location.hash ? window.location.hash.slice(1) : window.location.pathname) || "/" : "/";
293
268
  const referrer = useNavReferrer(currentPath);
@@ -342,7 +317,7 @@ function PageHeader({ title, onBack, parentLabel, backLabel }) {
342
317
  const hasHost = useHasPageHeaderHost();
343
318
  const { onBack: effectiveOnBack, parentLabel: effectiveParent } = useCrossModuleBack(onBack, parentLabel);
344
319
  useBackHandler(effectiveOnBack);
345
- const node = React2.useMemo(() => /* @__PURE__ */ jsx(
320
+ const node = React.useMemo(() => /* @__PURE__ */ jsx(
346
321
  PageHeaderCrumb,
347
322
  {
348
323
  title,
@@ -354,17 +329,154 @@ function PageHeader({ title, onBack, parentLabel, backLabel }) {
354
329
  if (hasHost) return /* @__PURE__ */ jsx(PageTitlePortal, { children: node });
355
330
  return /* @__PURE__ */ jsx("div", { className: "mb-4", children: node });
356
331
  }
332
+ var useLayoutStore = create((set) => ({
333
+ sidebarOpen: true,
334
+ sidebarCollapsed: false,
335
+ mobileDrawerOpen: false,
336
+ drawerKeepOpenOnce: false,
337
+ commandPaletteOpen: false,
338
+ commandPaletteQuery: "",
339
+ toggleSidebar: () => set((state) => ({ sidebarOpen: !state.sidebarOpen })),
340
+ setSidebarCollapsed: (collapsed) => set({ sidebarCollapsed: collapsed }),
341
+ toggleMobileDrawer: () => set((state) => ({ mobileDrawerOpen: !state.mobileDrawerOpen })),
342
+ setMobileDrawerOpen: (open) => set({ mobileDrawerOpen: open }),
343
+ keepDrawerOpenOnce: () => set({ drawerKeepOpenOnce: true }),
344
+ // Opening search closes the mobile drawer. The palette is a full-screen
345
+ // surface on a phone, and leaving the drawer mounted behind it means the user
346
+ // dismisses the palette onto a menu they had already navigated away from.
347
+ // Handled here so every entry point behaves the same — the rail's search
348
+ // field, the topbar button and the ⌘K shortcut.
349
+ setCommandPaletteOpen: (open, query) => set(open ? { commandPaletteOpen: true, mobileDrawerOpen: false, commandPaletteQuery: query ?? "" } : { commandPaletteOpen: false, commandPaletteQuery: "" })
350
+ }));
351
+ function shortcutKey() {
352
+ if (typeof navigator === "undefined") return "\u2318";
353
+ return /Mac|iPhone|iPad/u.test(navigator.platform || navigator.userAgent) ? "\u2318" : "Ctrl ";
354
+ }
355
+ function ShellSearch({
356
+ label = "Search",
357
+ tone = "bar",
358
+ collapsed = false,
359
+ value,
360
+ onValueChange,
361
+ searchAllLabel = "Search everywhere",
362
+ className
363
+ }) {
364
+ const setCommandPaletteOpen = useLayoutStore((s) => s.setCommandPaletteOpen);
365
+ const rail = tone === "rail";
366
+ const contextual = !!onValueChange && !collapsed;
367
+ const query = value ?? "";
368
+ const inputRef = React.useRef(null);
369
+ const escalate = () => setCommandPaletteOpen(true, query);
370
+ const surface = rail ? "bg-sidebar-accent/40 text-sidebar-muted hover:bg-sidebar-accent/70 hover:text-sidebar-foreground" : "bg-accent/40 text-muted-foreground hover:bg-accent/70 hover:text-foreground";
371
+ const ring = rail ? "focus-within:bg-sidebar-accent/70 focus-within:ring-1 focus-within:ring-sidebar-accent-foreground/25" : "focus-within:bg-accent/70 focus-within:ring-1 focus-within:ring-ring/40";
372
+ if (!contextual) {
373
+ return /* @__PURE__ */ jsxs(
374
+ "button",
375
+ {
376
+ onClick: escalate,
377
+ "aria-keyshortcuts": "Meta+K Control+K",
378
+ className: cn(
379
+ // Tonal surface, no outline. A 1px border here is a hard edge
380
+ // competing with every other hard edge; a lighter fill of the same
381
+ // hue reads as "a field" without drawing a box — and leaves the
382
+ // outline treatment free to mean focus, which it must.
383
+ "fz-tap group flex items-center rounded-lg transition-colors",
384
+ collapsed ? "h-9 w-9 justify-center" : "h-9 w-full gap-2.5 px-2.5",
385
+ surface,
386
+ "focus-visible:outline-none focus-visible:ring-2",
387
+ rail ? "focus-visible:ring-sidebar-accent-foreground/50" : "focus-visible:ring-ring/50",
388
+ className
389
+ ),
390
+ "aria-label": label,
391
+ title: collapsed ? label : void 0,
392
+ children: [
393
+ /* @__PURE__ */ jsx(Search, { className: "h-4 w-4 shrink-0" }),
394
+ !collapsed && /* @__PURE__ */ jsxs(Fragment, { children: [
395
+ /* @__PURE__ */ jsx("span", { className: "flex-1 truncate text-left text-[13px]", children: label }),
396
+ /* @__PURE__ */ jsxs("span", { className: "shrink-0 text-[11px] font-medium tabular-nums opacity-70 transition-opacity group-hover:opacity-100", children: [
397
+ shortcutKey(),
398
+ "K"
399
+ ] })
400
+ ] })
401
+ ]
402
+ }
403
+ );
404
+ }
405
+ return /* @__PURE__ */ jsxs("div", { className: cn("flex flex-col gap-1", className), children: [
406
+ /* @__PURE__ */ jsxs(
407
+ "div",
408
+ {
409
+ className: cn(
410
+ "group flex h-9 w-full items-center gap-2.5 rounded-lg px-2.5 transition-colors",
411
+ surface,
412
+ "focus-within:outline-none",
413
+ ring
414
+ ),
415
+ children: [
416
+ /* @__PURE__ */ jsx(Search, { className: "h-4 w-4 shrink-0" }),
417
+ /* @__PURE__ */ jsx(
418
+ "input",
419
+ {
420
+ ref: inputRef,
421
+ type: "search",
422
+ value: query,
423
+ onChange: (e) => onValueChange(e.target.value),
424
+ onKeyDown: (e) => {
425
+ if (e.key === "Enter") {
426
+ e.preventDefault();
427
+ escalate();
428
+ }
429
+ if (e.key === "Escape" && query) {
430
+ e.preventDefault();
431
+ onValueChange("");
432
+ }
433
+ },
434
+ placeholder: label,
435
+ "aria-label": label,
436
+ "aria-keyshortcuts": "Meta+K Control+K",
437
+ className: cn(
438
+ "min-w-0 flex-1 border-none bg-transparent p-0 text-[13px] outline-none",
439
+ // `search` inputs paint their own clear button in WebKit, on top of
440
+ // ours and in the browser's grey.
441
+ "[&::-webkit-search-cancel-button]:appearance-none",
442
+ rail ? "text-sidebar-foreground placeholder:text-sidebar-muted" : "text-foreground placeholder:text-muted-foreground"
443
+ )
444
+ }
445
+ ),
446
+ !query && /* @__PURE__ */ jsxs("span", { className: "shrink-0 text-[11px] font-medium tabular-nums opacity-70 transition-opacity group-hover:opacity-100", children: [
447
+ shortcutKey(),
448
+ "K"
449
+ ] })
450
+ ]
451
+ }
452
+ ),
453
+ query && /* @__PURE__ */ jsxs(
454
+ "button",
455
+ {
456
+ onClick: escalate,
457
+ className: cn(
458
+ "fz-tap flex w-full items-center gap-2 rounded-lg py-1.5 pl-9 pr-2.5 text-left text-[12px] transition-colors",
459
+ rail ? "text-sidebar-muted hover:bg-sidebar-accent/50 hover:text-sidebar-foreground" : "text-muted-foreground hover:bg-accent/60 hover:text-foreground"
460
+ ),
461
+ children: [
462
+ /* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate", children: searchAllLabel }),
463
+ /* @__PURE__ */ jsx(CornerDownLeft, { className: "h-3.5 w-3.5 shrink-0 opacity-60", "aria-hidden": true })
464
+ ]
465
+ }
466
+ )
467
+ ] });
468
+ }
357
469
  var GUTTER_PX = 8;
358
470
  var CLOSE_DELAY_MS = 140;
359
- var useIsomorphicLayoutEffect = typeof window !== "undefined" ? React2.useLayoutEffect : React2.useEffect;
471
+ var useIsomorphicLayoutEffect = typeof window !== "undefined" ? React.useLayoutEffect : React.useEffect;
360
472
  function iconOf(name) {
361
473
  if (!name) return null;
362
474
  return ICON_MAP[name] ?? null;
363
475
  }
364
- var RailFlyoutContext = React2.createContext(null);
476
+ var RailFlyoutContext = React.createContext(null);
365
477
  var RailFlyoutProvider = RailFlyoutContext.Provider;
366
478
  function useRailRow(content) {
367
- const flyout = React2.useContext(RailFlyoutContext);
479
+ const flyout = React.useContext(RailFlyoutContext);
368
480
  if (!flyout || !content) return {};
369
481
  return flyout.rowProps(content);
370
482
  }
@@ -374,19 +486,19 @@ function useRailFlyout({
374
486
  activeRoute,
375
487
  onNavigate
376
488
  }) {
377
- const [state, setState] = React2.useState(null);
378
- const [pos, setPos] = React2.useState({ top: 0, arrow: 20 });
379
- const [animate, setAnimate] = React2.useState(false);
380
- const panelRef = React2.useRef(null);
381
- const closeTimer = React2.useRef(null);
382
- const lastContent = React2.useRef(null);
383
- const cancelClose = React2.useCallback(() => {
489
+ const [state, setState] = React.useState(null);
490
+ const [pos, setPos] = React.useState({ top: 0, arrow: 20 });
491
+ const [animate, setAnimate] = React.useState(false);
492
+ const panelRef = React.useRef(null);
493
+ const closeTimer = React.useRef(null);
494
+ const lastContent = React.useRef(null);
495
+ const cancelClose = React.useCallback(() => {
384
496
  if (closeTimer.current) {
385
497
  clearTimeout(closeTimer.current);
386
498
  closeTimer.current = null;
387
499
  }
388
500
  }, []);
389
- const show = React2.useCallback(
501
+ const show = React.useCallback(
390
502
  (anchor, content2) => {
391
503
  if (!enabled) return;
392
504
  cancelClose();
@@ -394,19 +506,19 @@ function useRailFlyout({
394
506
  },
395
507
  [enabled, cancelClose]
396
508
  );
397
- const hide = React2.useCallback(() => {
509
+ const hide = React.useCallback(() => {
398
510
  cancelClose();
399
511
  closeTimer.current = setTimeout(() => setState(null), CLOSE_DELAY_MS);
400
512
  }, [cancelClose]);
401
- const dismiss = React2.useCallback(() => {
513
+ const dismiss = React.useCallback(() => {
402
514
  cancelClose();
403
515
  setState(null);
404
516
  }, [cancelClose]);
405
- React2.useEffect(() => () => cancelClose(), [cancelClose]);
406
- React2.useEffect(() => {
517
+ React.useEffect(() => () => cancelClose(), [cancelClose]);
518
+ React.useEffect(() => {
407
519
  if (!enabled) dismiss();
408
520
  }, [enabled, dismiss]);
409
- const reposition = React2.useCallback(() => {
521
+ const reposition = React.useCallback(() => {
410
522
  const rail = railRef.current;
411
523
  const anchor = state?.anchor;
412
524
  if (!rail || !anchor) return;
@@ -421,7 +533,7 @@ function useRailFlyout({
421
533
  setPos({ top, arrow });
422
534
  }, [railRef, state]);
423
535
  useIsomorphicLayoutEffect(reposition, [reposition, state?.content.key]);
424
- React2.useEffect(() => {
536
+ React.useEffect(() => {
425
537
  if (!state) return;
426
538
  const rail = railRef.current;
427
539
  const onScroll = () => reposition();
@@ -433,7 +545,7 @@ function useRailFlyout({
433
545
  };
434
546
  }, [state, railRef, reposition]);
435
547
  const visible = !!state;
436
- React2.useEffect(() => {
548
+ React.useEffect(() => {
437
549
  if (!visible) {
438
550
  setAnimate(false);
439
551
  return;
@@ -441,7 +553,7 @@ function useRailFlyout({
441
553
  const id = requestAnimationFrame(() => setAnimate(true));
442
554
  return () => cancelAnimationFrame(id);
443
555
  }, [visible]);
444
- React2.useEffect(() => {
556
+ React.useEffect(() => {
445
557
  if (!visible) return;
446
558
  const onKey = (event) => {
447
559
  if (event.key === "Escape") dismiss();
@@ -449,7 +561,7 @@ function useRailFlyout({
449
561
  window.addEventListener("keydown", onKey);
450
562
  return () => window.removeEventListener("keydown", onKey);
451
563
  }, [visible, dismiss]);
452
- const rowProps = React2.useCallback(
564
+ const rowProps = React.useCallback(
453
565
  (content2) => ({
454
566
  onMouseEnter: (event) => show(event.currentTarget, content2),
455
567
  onFocus: (event) => show(event.currentTarget, content2),
@@ -566,14 +678,10 @@ function FlyoutBody({
566
678
  ] });
567
679
  }
568
680
  var RAIL_PUSH_MS = 260;
569
- var useIsomorphicLayoutEffect2 = typeof window !== "undefined" ? React2.useLayoutEffect : React2.useEffect;
681
+ var useIsomorphicLayoutEffect2 = typeof window !== "undefined" ? React.useLayoutEffect : React.useEffect;
570
682
  function getIcon(name) {
571
683
  return ICON_MAP[name] ?? Home;
572
684
  }
573
- function shortcutKey() {
574
- if (typeof navigator === "undefined") return "\u2318";
575
- return /Mac|iPhone|iPad/u.test(navigator.platform || navigator.userAgent) ? "\u2318" : "Ctrl ";
576
- }
577
685
  function flyoutContentOf(item) {
578
686
  return {
579
687
  key: item.id,
@@ -668,7 +776,7 @@ function NavGroup({
668
776
  flyout
669
777
  }) {
670
778
  const isChildActive = item.children?.some((c) => c.route === activeRoute);
671
- const [open, setOpen] = React2.useState(() => isChildActive ?? false);
779
+ const [open, setOpen] = React.useState(() => isChildActive ?? false);
672
780
  const Icon = getIcon(item.icon);
673
781
  if (collapsed) {
674
782
  const hoverProps = flyout?.rowProps(flyoutContentOf(item));
@@ -742,8 +850,14 @@ function NavGroup({
742
850
  childExternal && /* @__PURE__ */ jsx(ExternalLink, { className: "h-3 w-3 shrink-0 opacity-50", "aria-hidden": true }),
743
851
  child.premium && /* @__PURE__ */ jsx(Crown, { className: "h-3 w-3 shrink-0 text-amber-500", "aria-hidden": true })
744
852
  ] });
745
- 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 });
746
- if (!childConnector) return /* @__PURE__ */ jsx(React2.Fragment, { children: row }, child.id);
853
+ const row = childExternal ? /* @__PURE__ */ jsx("a", { href: child.href, target: "_blank", rel: "noopener noreferrer", className: childClass, children: childInner }) : (
854
+ // `nav.<route>` on group children too, matching the top-level rows
855
+ // above — without it a guided tour cannot point at "Cardápio",
856
+ // "Reservas" or any other row that lives under a group (the normal
857
+ // case in an app that groups its rail).
858
+ /* @__PURE__ */ jsx("button", { "data-tour": `nav.${child.route}`, onClick: () => onNavigate(child.route), className: childClass, children: childInner })
859
+ );
860
+ if (!childConnector) return /* @__PURE__ */ jsx(React.Fragment, { children: row }, child.id);
747
861
  const activeIndex = item.children.findIndex((c) => c.route === activeRoute);
748
862
  const onPath = activeIndex >= 0 && childIndex < activeIndex;
749
863
  return (
@@ -805,12 +919,24 @@ function NavGroup({
805
919
  ] });
806
920
  }
807
921
  function withCollapsed(slot, collapsed) {
808
- if (!React2.isValidElement(slot)) return slot;
922
+ if (!React.isValidElement(slot)) return slot;
809
923
  if (typeof slot.type === "string") return slot;
810
- return React2.cloneElement(slot, { collapsed });
811
- }
924
+ return React.cloneElement(slot, { collapsed });
925
+ }
926
+ var SKELETON_ROWS = [
927
+ { width: 56, group: false },
928
+ { width: 68, group: true },
929
+ { width: 48, group: false },
930
+ { width: 60, group: true },
931
+ { width: 52, group: false }
932
+ ];
812
933
  function Sidebar({
813
934
  navigation,
935
+ navLoading = false,
936
+ navError,
937
+ navErrorLabel,
938
+ navRetryLabel,
939
+ onNavRetry,
814
940
  logo,
815
941
  collapsed,
816
942
  onToggle,
@@ -835,9 +961,8 @@ function Sidebar({
835
961
  drawer
836
962
  }) {
837
963
  const currentPath = currentPathProp;
838
- const setCommandPaletteOpen = useLayoutStore((s) => s.setCommandPaletteOpen);
839
964
  const activeRoute = resolveActiveRoute(navigation, currentPath);
840
- const railRef = React2.useRef(null);
965
+ const railRef = React.useRef(null);
841
966
  const flyout = useRailFlyout({
842
967
  enabled: collapsed,
843
968
  railRef,
@@ -886,10 +1011,10 @@ function Sidebar({
886
1011
  mainItems.length > 0 && renderSection(mainItems, sectionLabels?.main),
887
1012
  secondaryItems.length > 0 && /* @__PURE__ */ jsx("div", { className: "mt-6", children: renderSection(secondaryItems, sectionLabels?.secondary) })
888
1013
  ] });
889
- const [outgoing, setOutgoing] = React2.useState(null);
890
- const lastBodyRef = React2.useRef(null);
891
- const lastKeyRef = React2.useRef(navKey);
892
- const timerRef = React2.useRef(null);
1014
+ const [outgoing, setOutgoing] = React.useState(null);
1015
+ const lastBodyRef = React.useRef(null);
1016
+ const lastKeyRef = React.useRef(navKey);
1017
+ const timerRef = React.useRef(null);
893
1018
  useIsomorphicLayoutEffect2(() => {
894
1019
  if (lastKeyRef.current !== navKey) {
895
1020
  const previousBody = lastBodyRef.current;
@@ -902,7 +1027,7 @@ function Sidebar({
902
1027
  }
903
1028
  lastBodyRef.current = navBody;
904
1029
  });
905
- React2.useEffect(() => () => {
1030
+ React.useEffect(() => () => {
906
1031
  if (timerRef.current) clearTimeout(timerRef.current);
907
1032
  }, []);
908
1033
  return (
@@ -927,8 +1052,10 @@ function Sidebar({
927
1052
  // paid for by the content column on every screen in every app. The
928
1053
  // labels still truncate, and a long one now truncates 32px earlier —
929
1054
  // which is the trade, taken deliberately.
930
- collapsed ? "w-16" : "w-56"
1055
+ // A largura da GAVETA vai em `style`, logo abaixo — ver o porquê lá.
1056
+ collapsed ? "w-16" : !drawer && "w-56"
931
1057
  ),
1058
+ style: drawer && !collapsed ? { width: "min(18rem, 85vw)" } : void 0,
932
1059
  children: [
933
1060
  /* @__PURE__ */ jsxs("div", { className: cn("shrink-0", !borderless && "border-b border-sidebar-border"), children: [
934
1061
  /* @__PURE__ */ jsxs(
@@ -940,7 +1067,13 @@ function Sidebar({
940
1067
  // h-14 quando a marca é a única coisa aqui e a linha tem de bater
941
1068
  // com o cabeçalho da página.
942
1069
  identity ? "h-12" : "h-14",
943
- collapsed && "justify-center"
1070
+ collapsed && "justify-center",
1071
+ // `logo === null` é uma opinião, não a falta de uma: quer dizer
1072
+ // "esta casa não é marcada pelo produto". A linha da marca então
1073
+ // não existe, e o rail abre direto no seletor de conta — o ícone da
1074
+ // casa, o nome e o chevron, numa linha só. `undefined` continua
1075
+ // caindo no rótulo padrão, que é o que um app sem logo sempre teve.
1076
+ logo === null && !topContent && "hidden"
944
1077
  ),
945
1078
  children: [
946
1079
  topContent ? /* @__PURE__ */ jsx("div", { className: cn("min-w-0", collapsed ? "flex justify-center" : "flex-1"), children: withCollapsed(topContent, collapsed) }) : !collapsed && /* @__PURE__ */ jsx("div", { className: "min-w-0 flex-1 px-1.5", children: logo ?? /* @__PURE__ */ jsx("span", { className: "text-lg font-bold", children: "App" }) }),
@@ -948,60 +1081,68 @@ function Sidebar({
948
1081
  "button",
949
1082
  {
950
1083
  onClick: onToggle,
951
- className: "fz-tap shrink-0 rounded-button p-1.5 text-sidebar-muted transition-colors hover:bg-sidebar-accent hover:text-sidebar-accent-foreground",
1084
+ className: "fz-tap flex h-9 w-9 shrink-0 items-center justify-center rounded-button text-sidebar-muted transition-colors hover:bg-sidebar-accent hover:text-sidebar-accent-foreground",
952
1085
  "aria-label": "Close menu",
953
- children: /* @__PURE__ */ jsx(X, { className: "h-4 w-4" })
1086
+ children: /* @__PURE__ */ jsx(X, { className: "h-5 w-5" })
954
1087
  }
955
1088
  )
956
1089
  ]
957
1090
  }
958
1091
  ),
959
- identity && /* @__PURE__ */ jsx("div", { className: cn("px-2 pb-2", collapsed && "flex justify-center"), children: withCollapsed(identity, collapsed) })
960
- ] }),
961
- navTop ? /* @__PURE__ */ jsx("div", { className: "px-2 pb-2 pt-2", children: withCollapsed(navTop, collapsed) }) : (
962
- /* Search above the first destination. It used to be a 16px magnifier
963
- tucked beside the avatar at the very bottom, which is where you put
964
- something nobody is meant to find: the fastest way through an admin
965
- with thirty screens was the one control shaped like an afterthought.
966
- Here it reads as what it is — a field — and says which key opens it.
967
- Clicking it opens the same palette ⌘K does; it is a button, not an
968
- input, because typing happens there. */
969
- /* @__PURE__ */ jsx("div", { className: cn("px-2 pb-2 pt-2", collapsed && "flex justify-center"), children: /* @__PURE__ */ jsxs(
970
- "button",
1092
+ identity && // Sem a linha da marca acima, a conta encosta no topo da janela. O ar
1093
+ // que a marca ocupava continua sendo necessário: é o que separa o
1094
+ // conteúdo do rail da borda do navegador.
1095
+ /* @__PURE__ */ jsx(
1096
+ "div",
971
1097
  {
972
- onClick: () => setCommandPaletteOpen(true),
973
- "aria-keyshortcuts": "Meta+K Control+K",
974
1098
  className: cn(
975
- // Tonal surface, no outline. On a dark rail a 1px border is a hard
976
- // edge competing with every other hard edge; a lighter fill of the
977
- // same hue reads as "a field" without drawing a box around it — and
978
- // leaves the outline treatment free to mean focus, which it must.
979
- "fz-tap group flex items-center rounded-lg bg-sidebar-accent/40 text-sidebar-muted transition-colors",
980
- "hover:bg-sidebar-accent/70 hover:text-sidebar-foreground",
981
- "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-sidebar-accent-foreground/50",
982
- collapsed ? "h-9 w-9 justify-center" : "h-9 w-full gap-2.5 px-2.5"
1099
+ "px-2 pb-2",
1100
+ logo === null && !topContent && "pt-3",
1101
+ collapsed && "flex justify-center"
983
1102
  ),
984
- "aria-label": searchLabel,
985
- title: collapsed ? searchLabel : void 0,
986
- children: [
987
- /* @__PURE__ */ jsx(Search, { className: "h-4 w-4 shrink-0" }),
988
- !collapsed && /* @__PURE__ */ jsxs(Fragment, { children: [
989
- /* @__PURE__ */ jsx("span", { className: "flex-1 truncate text-left text-[13px]", children: searchLabel }),
990
- /* @__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: [
991
- shortcutKey(),
992
- "K"
993
- ] })
994
- ] })
995
- ]
1103
+ children: withCollapsed(identity, collapsed)
996
1104
  }
997
- ) })
998
- ),
1105
+ )
1106
+ ] }),
1107
+ navTop ? /* @__PURE__ */ jsx("div", { className: cn("px-2 pb-2 pt-2", collapsed && "flex justify-center"), children: withCollapsed(navTop, collapsed) }) : /* @__PURE__ */ jsx("div", { className: cn("px-2 pb-2 pt-2", collapsed && "flex justify-center"), children: /* @__PURE__ */ jsx(ShellSearch, { tone: "rail", collapsed, label: searchLabel }) }),
999
1108
  beforeNav && /* @__PURE__ */ jsx("div", { className: cn("px-2 pb-1 empty:hidden", collapsed && "flex justify-center"), children: withCollapsed(beforeNav, collapsed) }),
1109
+ navError && !navLoading && /* @__PURE__ */ jsxs("div", { className: "flex-1 px-3 py-4", children: [
1110
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-medium text-sidebar-foreground", children: navErrorLabel ?? "N\xE3o foi poss\xEDvel carregar o menu" }),
1111
+ /* @__PURE__ */ jsx("p", { className: "mt-1 text-xs leading-relaxed text-sidebar-muted", children: navError }),
1112
+ onNavRetry && /* @__PURE__ */ jsx(
1113
+ "button",
1114
+ {
1115
+ type: "button",
1116
+ onClick: onNavRetry,
1117
+ className: "fz-tap mt-3 rounded-button bg-sidebar-accent px-2.5 py-1.5 text-xs font-medium text-sidebar-accent-foreground transition-colors hover:brightness-110",
1118
+ children: navRetryLabel ?? "Tentar de novo"
1119
+ }
1120
+ )
1121
+ ] }),
1122
+ navLoading && /* @__PURE__ */ jsx(
1123
+ "nav",
1124
+ {
1125
+ "aria-busy": true,
1126
+ "aria-label": "Carregando menu",
1127
+ className: "fz-rail-skeleton flex-1 space-y-1 p-2",
1128
+ children: SKELETON_ROWS.map((row, i) => /* @__PURE__ */ jsxs("div", { className: "flex h-9 items-center gap-3 rounded-button px-2.5", children: [
1129
+ /* @__PURE__ */ jsx("span", { className: "h-4 w-4 shrink-0 rounded bg-sidebar-foreground/10" }),
1130
+ !collapsed && /* @__PURE__ */ jsxs(Fragment, { children: [
1131
+ /* @__PURE__ */ jsx("span", { className: "h-3 rounded bg-sidebar-foreground/10", style: { width: `${row.width}%` } }),
1132
+ row.group && /* @__PURE__ */ jsx("span", { className: "ml-auto h-3 w-3 shrink-0 rounded bg-sidebar-foreground/[0.07]" })
1133
+ ] })
1134
+ ] }, i))
1135
+ }
1136
+ ),
1000
1137
  /* @__PURE__ */ jsxs(
1001
1138
  "nav",
1002
1139
  {
1140
+ hidden: navLoading || !!navError,
1003
1141
  className: cn(
1004
1142
  "relative flex-1 p-2",
1143
+ // Entra em fade: o rail é a primeira coisa que alguém lê depois de
1144
+ // escolher a conta, e aparecer de uma vez lê como sobressalto.
1145
+ "duration-300 animate-in fade-in",
1005
1146
  // scrollbar-none: the rail still scrolls, but without the native
1006
1147
  // scrollbar gutter eating into the nav items.
1007
1148
  outgoing ? "overflow-hidden" : "overflow-y-auto scrollbar-none"
@@ -1104,7 +1245,7 @@ function TopbarUserMenu({
1104
1245
  onBilling,
1105
1246
  userMenuExtras
1106
1247
  }) {
1107
- const [open, setOpen] = React2.useState(false);
1248
+ const [open, setOpen] = React.useState(false);
1108
1249
  if (!user) return null;
1109
1250
  const initial = (user.fullName?.trim()?.[0] ?? user.email?.[0] ?? "?").toUpperCase();
1110
1251
  const item = (label, onClick, Icon) => onClick ? /* @__PURE__ */ jsxs(
@@ -1188,7 +1329,7 @@ function getIcon2(name) {
1188
1329
  return ICON_MAP[name] ?? Home;
1189
1330
  }
1190
1331
  function NavDropdown({ item, currentPath, onNavigate }) {
1191
- const [open, setOpen] = React2.useState(false);
1332
+ const [open, setOpen] = React.useState(false);
1192
1333
  const Icon = getIcon2(item.icon);
1193
1334
  const isChildActive = item.children?.some(
1194
1335
  (child) => currentPath === child.route || currentPath.startsWith(child.route + "/")
@@ -1355,17 +1496,17 @@ function Topbar({
1355
1496
  }) }) }) })
1356
1497
  ] });
1357
1498
  }
1358
- var ModuleHeaderSlotContext = React2.createContext(null);
1499
+ var ModuleHeaderSlotContext = React.createContext(null);
1359
1500
  function useModuleHeaderSlot() {
1360
- return React2.useContext(ModuleHeaderSlotContext);
1501
+ return React.useContext(ModuleHeaderSlotContext);
1361
1502
  }
1362
- var ModuleHeaderActionsSlotContext = React2.createContext(null);
1503
+ var ModuleHeaderActionsSlotContext = React.createContext(null);
1363
1504
  function useModuleHeaderActionsSlot() {
1364
- return React2.useContext(ModuleHeaderActionsSlotContext);
1505
+ return React.useContext(ModuleHeaderActionsSlotContext);
1365
1506
  }
1366
- var PageRailSlotContext = React2.createContext(null);
1507
+ var PageRailSlotContext = React.createContext(null);
1367
1508
  function usePageRailSlot() {
1368
- return React2.useContext(PageRailSlotContext);
1509
+ return React.useContext(PageRailSlotContext);
1369
1510
  }
1370
1511
  function bottomNavReachesAll(items) {
1371
1512
  return !!items?.some((i) => "kind" in i && i.kind === "more");
@@ -1379,6 +1520,11 @@ function ShellPageTitle({
1379
1520
  }
1380
1521
  function SidebarLayout({
1381
1522
  navigation = [],
1523
+ navLoading = false,
1524
+ navError,
1525
+ navErrorLabel,
1526
+ navRetryLabel,
1527
+ onNavRetry,
1382
1528
  logo,
1383
1529
  user,
1384
1530
  children,
@@ -1421,9 +1567,9 @@ function SidebarLayout({
1421
1567
  const setSidebarCollapsed = useLayoutStore((s) => s.setSidebarCollapsed);
1422
1568
  const mobileMenuOpen = useLayoutStore((st) => st.mobileDrawerOpen);
1423
1569
  const setMobileMenuOpen = useLayoutStore((st) => st.setMobileDrawerOpen);
1424
- const [headerSlot, setHeaderSlot] = React2.useState(null);
1425
- const [actionsSlot, setActionsSlot] = React2.useState(null);
1426
- const [pageRailSlot, setPageRailSlot] = React2.useState(null);
1570
+ const [headerSlot, setHeaderSlot] = React.useState(null);
1571
+ const [actionsSlot, setActionsSlot] = React.useState(null);
1572
+ const [pageRailSlot, setPageRailSlot] = React.useState(null);
1427
1573
  const hasPageActions = useHasPageActions();
1428
1574
  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(
1429
1575
  "shell-enter fz-viewport-h flex overflow-hidden bg-background",
@@ -1437,6 +1583,11 @@ function SidebarLayout({
1437
1583
  Sidebar,
1438
1584
  {
1439
1585
  navigation,
1586
+ navLoading,
1587
+ navError,
1588
+ navErrorLabel,
1589
+ navRetryLabel,
1590
+ onNavRetry,
1440
1591
  logo,
1441
1592
  collapsed: sidebarCollapsed,
1442
1593
  onToggle: () => setSidebarCollapsed(!sidebarCollapsed),
@@ -1464,55 +1615,73 @@ function SidebarLayout({
1464
1615
  navChildConnector: sidebarNavChildConnector
1465
1616
  }
1466
1617
  ) }),
1467
- /* @__PURE__ */ jsxs("div", { className: cn(
1468
- "relative flex flex-1 overflow-hidden",
1469
- // `content`, not `card`: this is the page's own surface, and cards have
1470
- // to sit ON it. Painted card, the frame was the same tone as everything
1471
- // inside it and the page read as one flat sheet.
1472
- frame && "md:my-2 md:ml-2 md:rounded-xl md:border md:border-border md:bg-content",
1473
- // With a companion rail open, the page and the rail are ONE card: the
1474
- // right edge opens and the rail closes it. Two cards with a strip of
1475
- // chrome between them made the assistant read as a separate window
1476
- // parked next to the app rather than as a column of it — the rail's own
1477
- // left border is then the single seam between the two.
1478
- frame && (railOpen ? "md:rounded-r-none md:border-r-0" : "md:mr-2"),
1479
- immersive && "md:overflow-hidden"
1480
- ), children: [
1481
- /* @__PURE__ */ jsx("div", { ref: setPageRailSlot, className: "flex shrink-0 empty:hidden" }),
1482
- /* @__PURE__ */ jsxs("div", { className: "relative flex min-w-0 flex-1 flex-col overflow-hidden", children: [
1483
- /* @__PURE__ */ jsxs("div", { className: cn(
1484
- "flex shrink-0 items-center justify-between gap-3 px-4 py-2",
1485
- "min-h-14 border-b border-border",
1486
- // …unless the page put its actions in it. Hiding the bar on mobile
1487
- // used to hide the only "Novo" the screen had.
1488
- mobileHeader !== "minimal" && !hasPageActions && "hidden md:flex",
1489
- // Immersive pages draw their own window bar. Keeping ours would be two
1490
- // bars stating one thing — but only on desktop: on a phone this band
1491
- // is where the hamburger lives, and the page's bar sits under it.
1492
- immersive && "md:hidden"
1493
- ), children: [
1494
- /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-1 items-center gap-3", children: [
1495
- !barOwnsNav && /* @__PURE__ */ jsx(
1496
- "button",
1497
- {
1498
- onClick: () => setMobileMenuOpen(true),
1499
- className: "inline-flex items-center justify-center rounded-md p-2 text-muted-foreground hover:bg-accent md:hidden",
1500
- "aria-label": "Open menu",
1501
- children: /* @__PURE__ */ jsx(Menu, { className: "h-5 w-5" })
1502
- }
1503
- ),
1504
- !pageHeaderClaimed && /* @__PURE__ */ jsx(ShellPageTitle, { title: pageTitle, back: pageBack }),
1505
- /* @__PURE__ */ jsx("div", { ref: setTitleSlot, className: "flex min-w-0 items-center empty:hidden" }),
1506
- topbarStart,
1507
- /* @__PURE__ */ jsx("div", { ref: setHeaderSlot, className: "flex min-w-0 flex-1 items-center" })
1508
- ] }),
1509
- /* @__PURE__ */ jsxs("div", { className: "flex shrink-0 items-center gap-2", children: [
1510
- /* @__PURE__ */ jsx("div", { ref: setActionsSlot, className: "flex items-center gap-2" }),
1511
- topbarEnd
1512
- ] })
1618
+ /* @__PURE__ */ jsxs("div", { className: "relative flex min-w-0 flex-1 flex-col overflow-hidden", children: [
1619
+ /* @__PURE__ */ jsxs("div", { className: cn(
1620
+ "flex shrink-0 items-center justify-between gap-3 px-4 py-2",
1621
+ "min-h-14 border-b border-border",
1622
+ // Sobre a moldura, os tokens do menu. O que a barra mostra (título,
1623
+ // abas do módulo, botões) é escrito com `foreground`/`border`/`muted`
1624
+ // comuns: num app de menu escuro isso seria texto escuro sobre preto.
1625
+ // Remapear as variáveis aqui converte a barra inteira de uma vez, sem
1626
+ // cada página saber em que superfície caiu. A linha de baixo sai: a
1627
+ // borda de cima do cartão é a costura.
1628
+ // Altura TRAVADA em 56px na moldura, não mínima: esta faixa e a do
1629
+ // assistente são desenhadas em subárvores diferentes, e o que alinha
1630
+ // o topo dos dois cartões é as duas terem exatamente a mesma altura.
1631
+ // Com `min-h` bastava um controle mais alto de um lado para o cartão
1632
+ // daquele lado descer sozinho.
1633
+ frame && "fz-on-band md:h-14 md:border-b-0 md:text-sidebar-foreground md:[--accent-foreground:var(--sidebar-accent-foreground)] md:[--accent:var(--sidebar-accent)] md:[--background:var(--sidebar)] md:[--border:var(--sidebar-border)] md:[--card:var(--sidebar)] md:[--foreground:var(--sidebar-foreground)] md:[--muted-foreground:var(--sidebar-muted)] md:[--muted:var(--sidebar-accent)]",
1634
+ // …unless the page put its actions in it. Hiding the bar on mobile
1635
+ // used to hide the only "Novo" the screen had.
1636
+ mobileHeader !== "minimal" && !hasPageActions && "hidden md:flex",
1637
+ // Immersive pages draw their own window bar. Keeping ours would be two
1638
+ // bars stating one thing — but only on desktop: on a phone this band
1639
+ // is where the hamburger lives, and the page's bar sits under it.
1640
+ immersive && "md:hidden"
1641
+ ), children: [
1642
+ /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-1 items-center gap-3", children: [
1643
+ !barOwnsNav && /* @__PURE__ */ jsx(
1644
+ "button",
1645
+ {
1646
+ onClick: () => setMobileMenuOpen(true),
1647
+ className: "inline-flex items-center justify-center rounded-md p-2 text-muted-foreground hover:bg-accent md:hidden",
1648
+ "aria-label": "Open menu",
1649
+ children: /* @__PURE__ */ jsx(Menu, { className: "h-5 w-5" })
1650
+ }
1651
+ ),
1652
+ !pageHeaderClaimed && /* @__PURE__ */ jsx(ShellPageTitle, { title: pageTitle, back: pageBack }),
1653
+ /* @__PURE__ */ jsx("div", { ref: setTitleSlot, className: "flex min-w-0 items-center empty:hidden" }),
1654
+ topbarStart,
1655
+ /* @__PURE__ */ jsx("div", { ref: setHeaderSlot, className: "flex min-w-0 flex-1 items-center" })
1513
1656
  ] }),
1514
- /* @__PURE__ */ jsx("main", { className: "min-w-0 flex-1 overflow-y-auto pb-16 md:pb-0", children }),
1515
- /* @__PURE__ */ jsx(SaveBar, {})
1657
+ /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 shrink-0 items-center justify-end gap-2 md:flex-1", children: [
1658
+ /* @__PURE__ */ jsx("div", { ref: setActionsSlot, className: "flex items-center gap-2", style: { "--primary": "var(--primary-on-rail)", "--primary-foreground": "var(--primary-on-rail-foreground)" } }),
1659
+ topbarEnd
1660
+ ] })
1661
+ ] }),
1662
+ /* @__PURE__ */ jsxs("div", { "data-frame": frame || void 0, className: cn(
1663
+ "relative flex min-w-0 flex-1 overflow-hidden",
1664
+ // `content`, not `card`: this is the page's own surface, and cards have
1665
+ // to sit ON it. Painted card, the frame was the same tone as everything
1666
+ // inside it and the page read as one flat sheet.
1667
+ "fz-page-surface",
1668
+ frame && "md:mb-2 md:ml-2 md:rounded-xl md:border md:border-border md:bg-content",
1669
+ // Sem barra no desktop (páginas imersivas desenham a sua) o cartão
1670
+ // ficaria colado no alto da janela: a folga de cima volta.
1671
+ frame && immersive && "md:mt-2",
1672
+ // With a companion rail open, the page and the rail are ONE card: the
1673
+ // right edge opens and the rail closes it. Two cards with a strip of
1674
+ // chrome between them made the assistant read as a separate window
1675
+ // parked next to the app rather than as a column of it — the rail's own
1676
+ // left border is then the single seam between the two.
1677
+ frame && (railOpen ? "md:rounded-r-none md:border-r-0" : "md:mr-2"),
1678
+ immersive && "md:overflow-hidden"
1679
+ ), children: [
1680
+ /* @__PURE__ */ jsx("div", { ref: setPageRailSlot, className: "flex shrink-0 empty:hidden" }),
1681
+ /* @__PURE__ */ jsxs("div", { className: "relative flex min-w-0 flex-1 flex-col overflow-hidden", children: [
1682
+ /* @__PURE__ */ jsx("main", { className: "min-w-0 flex-1 overflow-y-auto pb-16 md:pb-0", children }),
1683
+ /* @__PURE__ */ jsx(SaveBar, {})
1684
+ ] })
1516
1685
  ] })
1517
1686
  ] }),
1518
1687
  mobileMenuOpen && /* @__PURE__ */ jsx(
@@ -1571,9 +1740,9 @@ function TopbarLayout({
1571
1740
  }) {
1572
1741
  const mobileMenuOpen = useLayoutStore((st) => st.mobileDrawerOpen);
1573
1742
  const setMobileMenuOpen = useLayoutStore((st) => st.setMobileDrawerOpen);
1574
- const [headerSlot, setHeaderSlot] = React2.useState(null);
1575
- const [actionsSlot, setActionsSlot] = React2.useState(null);
1576
- const [pageRailSlot, setPageRailSlot] = React2.useState(null);
1743
+ const [headerSlot, setHeaderSlot] = React.useState(null);
1744
+ const [actionsSlot, setActionsSlot] = React.useState(null);
1745
+ const [pageRailSlot, setPageRailSlot] = React.useState(null);
1577
1746
  const rawClaimed = usePageHeaderClaimed();
1578
1747
  const setTitleSlot = usePageTitleSlotHost();
1579
1748
  const hasOutlet = useHasPageHeaderOutlet();
@@ -1608,7 +1777,7 @@ function TopbarLayout({
1608
1777
  hideMobileChrome: barOwnsNav
1609
1778
  }
1610
1779
  ),
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: [
1780
+ /* @__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", { "data-frame": frame || void 0, className: cn("fz-page-surface 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
1781
  /* @__PURE__ */ jsx("div", { ref: setPageRailSlot, className: "flex shrink-0 empty:hidden" }),
1613
1782
  /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 min-h-0 flex-1 flex-col overflow-hidden", children: [
1614
1783
  (pageHeaderClaimed || pageBack || hasPageActions || barOwnsNav) && /* @__PURE__ */ jsxs(
@@ -1624,7 +1793,7 @@ function TopbarLayout({
1624
1793
  /* @__PURE__ */ jsx("div", { ref: setTitleSlot, className: "flex min-w-0 items-center empty:hidden" }),
1625
1794
  /* @__PURE__ */ jsx("div", { ref: setHeaderSlot, className: "flex min-w-0 flex-1 items-center" })
1626
1795
  ] }),
1627
- /* @__PURE__ */ jsx("div", { ref: setActionsSlot, className: "flex shrink-0 items-center gap-2" })
1796
+ /* @__PURE__ */ jsx("div", { ref: setActionsSlot, className: "flex shrink-0 items-center gap-2", style: { "--primary": "var(--primary-on-rail)", "--primary-foreground": "var(--primary-on-rail-foreground)" } })
1628
1797
  ]
1629
1798
  }
1630
1799
  ),
@@ -1669,30 +1838,30 @@ function MinimalLayout({
1669
1838
  }) {
1670
1839
  return /* @__PURE__ */ jsxs("div", { className: cn("shell-enter fz-viewport-h flex flex-col", hasBottomNav && "fz-has-bottom-bar"), children: [
1671
1840
  /* @__PURE__ */ jsxs("header", { className: "flex h-14 shrink-0 items-center justify-between border-b border-border bg-card px-4 md:px-6", children: [
1672
- /* @__PURE__ */ jsx("div", { className: "flex items-center gap-3", children: logo ?? /* @__PURE__ */ jsx("span", { className: "text-lg font-bold", children: "App" }) }),
1841
+ logo === null ? null : /* @__PURE__ */ jsx("div", { className: "flex items-center gap-3", children: logo ?? /* @__PURE__ */ jsx("span", { className: "text-lg font-bold", children: "App" }) }),
1673
1842
  headerEnd && /* @__PURE__ */ jsx("div", { className: "flex items-center gap-2", children: headerEnd })
1674
1843
  ] }),
1675
1844
  /* @__PURE__ */ jsx("main", { className: "min-w-0 flex-1 overflow-y-auto pb-16 md:pb-0", children })
1676
1845
  ] });
1677
1846
  }
1678
1847
  function MobileOverlay({ onClose, onNavigate, ...sidebarProps }) {
1679
- const [visible, setVisible] = React2.useState(false);
1680
- React2.useEffect(() => {
1848
+ const [visible, setVisible] = React.useState(false);
1849
+ React.useEffect(() => {
1681
1850
  const id = requestAnimationFrame(() => setVisible(true));
1682
1851
  return () => cancelAnimationFrame(id);
1683
1852
  }, []);
1684
- const close = React2.useCallback(() => {
1853
+ const close = React.useCallback(() => {
1685
1854
  setVisible(false);
1686
1855
  window.setTimeout(onClose, 250);
1687
1856
  }, [onClose]);
1688
- React2.useEffect(() => {
1857
+ React.useEffect(() => {
1689
1858
  const previous = document.body.style.overflow;
1690
1859
  document.body.style.overflow = "hidden";
1691
1860
  return () => {
1692
1861
  document.body.style.overflow = previous;
1693
1862
  };
1694
1863
  }, []);
1695
- React2.useEffect(() => {
1864
+ React.useEffect(() => {
1696
1865
  const onKey = (e) => {
1697
1866
  if (e.key === "Escape") close();
1698
1867
  };
@@ -1721,6 +1890,7 @@ function MobileOverlay({ onClose, onNavigate, ...sidebarProps }) {
1721
1890
  Sidebar,
1722
1891
  {
1723
1892
  ...sidebarProps,
1893
+ navTop: sidebarProps.navTop ? /* @__PURE__ */ jsx("div", { onClickCapture: () => useLayoutStore.getState().keepDrawerOpenOnce(), children: sidebarProps.navTop }) : void 0,
1724
1894
  collapsed: false,
1725
1895
  onToggle: close,
1726
1896
  drawer: true,
@@ -1840,6 +2010,13 @@ function AppShell({
1840
2010
  variant,
1841
2011
  contentFrame,
1842
2012
  navigation = [],
2013
+ // Declarados em AppShellProps desde sempre e nunca desestruturados aqui: o
2014
+ // esqueleto do rail so existia no caminho que nao e o que o app usa.
2015
+ navLoading = false,
2016
+ navError = null,
2017
+ navErrorLabel,
2018
+ navRetryLabel,
2019
+ onNavRetry,
1843
2020
  logo,
1844
2021
  user,
1845
2022
  children,
@@ -1891,7 +2068,11 @@ function AppShell({
1891
2068
  const barRetracted = !!pageBack || pageClaimedBack;
1892
2069
  const mobileDrawerOpen = useLayoutStore((st) => st.mobileDrawerOpen);
1893
2070
  const setMobileDrawerOpen = useLayoutStore((st) => st.setMobileDrawerOpen);
1894
- React2.useEffect(() => {
2071
+ React.useEffect(() => {
2072
+ if (useLayoutStore.getState().drawerKeepOpenOnce) {
2073
+ useLayoutStore.setState({ drawerKeepOpenOnce: false });
2074
+ return;
2075
+ }
1895
2076
  setMobileDrawerOpen(false);
1896
2077
  }, [currentPath]);
1897
2078
  const bottomBar = hasBottomNav ? /* @__PURE__ */ jsx(
@@ -1916,6 +2097,11 @@ function AppShell({
1916
2097
  SidebarLayout,
1917
2098
  {
1918
2099
  navigation,
2100
+ navLoading,
2101
+ navError,
2102
+ navErrorLabel,
2103
+ navRetryLabel,
2104
+ onNavRetry,
1919
2105
  logo,
1920
2106
  user,
1921
2107
  onNavigate,
@@ -2021,13 +2207,13 @@ function SplitWithRightRail({
2021
2207
  maxWidth = 720,
2022
2208
  resizeLabel = "Resize panel"
2023
2209
  }) {
2024
- const [width, setWidth] = React2.useState(defaultWidth);
2025
- const [dragging, setDragging] = React2.useState(false);
2026
- const clamp = React2.useCallback(
2210
+ const [width, setWidth] = React.useState(defaultWidth);
2211
+ const [dragging, setDragging] = React.useState(false);
2212
+ const clamp = React.useCallback(
2027
2213
  (value) => Math.min(maxWidth, Math.max(minWidth, value)),
2028
2214
  [minWidth, maxWidth]
2029
2215
  );
2030
- React2.useEffect(() => {
2216
+ React.useEffect(() => {
2031
2217
  if (!dragging) return;
2032
2218
  const onMove = (event) => {
2033
2219
  event.preventDefault();
@@ -2049,7 +2235,7 @@ function SplitWithRightRail({
2049
2235
  document.body.style.cursor = previousCursor;
2050
2236
  };
2051
2237
  }, [dragging, clamp]);
2052
- React2.useEffect(() => {
2238
+ React.useEffect(() => {
2053
2239
  if (dragging) return;
2054
2240
  onWidthChange?.(width);
2055
2241
  }, [dragging]);
@@ -2132,490 +2318,7 @@ function SplitWithRightRail({
2132
2318
  ] })
2133
2319
  );
2134
2320
  }
2135
- function HeaderTabs({
2136
- tabs,
2137
- value,
2138
- onChange,
2139
- className,
2140
- fill,
2141
- ...rest
2142
- }) {
2143
- return /* @__PURE__ */ jsx(
2144
- "div",
2145
- {
2146
- role: "tablist",
2147
- "aria-label": rest["aria-label"],
2148
- className: cn(
2149
- "inline-flex max-w-full gap-5 overflow-x-auto scrollbar-none",
2150
- fill ? "items-stretch" : "items-center",
2151
- className
2152
- ),
2153
- children: tabs.map((tab) => {
2154
- const active = tab.value === value;
2155
- const Icon = tab.icon;
2156
- const box = cn(
2157
- "relative flex shrink-0 items-center gap-1.5 whitespace-nowrap text-sm transition-colors",
2158
- fill ? "h-full" : "pb-1.5",
2159
- active ? "font-medium text-foreground" : "text-muted-foreground hover:text-foreground",
2160
- tab.dim && "opacity-50 hover:opacity-100"
2161
- );
2162
- const inner = /* @__PURE__ */ jsxs(Fragment, { children: [
2163
- Icon ? /* @__PURE__ */ jsx(Icon, { className: "h-4 w-4 shrink-0" }) : null,
2164
- /* @__PURE__ */ jsx("span", { children: tab.label }),
2165
- tab.count != null && /* @__PURE__ */ jsx("span", { className: cn(
2166
- "ml-0.5 rounded-full px-1.5 text-[11px] tabular-nums",
2167
- active ? "bg-muted text-foreground" : "bg-muted/70 text-muted-foreground"
2168
- ), children: tab.count }),
2169
- tab.badge
2170
- ] });
2171
- const underline = active ? /* @__PURE__ */ jsx("span", { className: "absolute inset-x-0 bottom-0 h-0.5 rounded-full bg-foreground" }) : null;
2172
- if (!tab.trailing) {
2173
- return /* @__PURE__ */ jsxs(
2174
- "button",
2175
- {
2176
- type: "button",
2177
- role: "tab",
2178
- "aria-selected": active,
2179
- onClick: () => onChange(tab.value),
2180
- className: box,
2181
- children: [
2182
- inner,
2183
- underline
2184
- ]
2185
- },
2186
- tab.value
2187
- );
2188
- }
2189
- return /* @__PURE__ */ jsxs("div", { className: box, children: [
2190
- /* @__PURE__ */ jsx(
2191
- "button",
2192
- {
2193
- type: "button",
2194
- role: "tab",
2195
- "aria-selected": active,
2196
- onClick: () => onChange(tab.value),
2197
- className: "flex items-center gap-1.5 whitespace-nowrap",
2198
- children: inner
2199
- }
2200
- ),
2201
- tab.trailing,
2202
- underline
2203
- ] }, tab.value);
2204
- })
2205
- }
2206
- );
2207
- }
2208
- function PageTabs({ actions, className, ...props }) {
2209
- return /* @__PURE__ */ jsxs(
2210
- "div",
2211
- {
2212
- className: cn(
2213
- "-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",
2214
- className
2215
- ),
2216
- children: [
2217
- /* @__PURE__ */ jsx(HeaderTabs, { ...props, className: "-mb-px" }),
2218
- actions && /* @__PURE__ */ jsx("div", { className: "ml-auto flex shrink-0 items-center gap-2 pb-1.5", children: actions })
2219
- ]
2220
- }
2221
- );
2222
- }
2223
- var SIZE = {
2224
- sm: { container: "h-[26px]", segment: "px-2.5 text-xs" },
2225
- md: { container: "h-[30px]", segment: "px-3 text-sm" }
2226
- };
2227
- function SegmentedControl({
2228
- options,
2229
- value,
2230
- onChange,
2231
- size = "sm",
2232
- className,
2233
- ...rest
2234
- }) {
2235
- const items = options.map((o) => typeof o === "string" ? { value: o, label: o } : o);
2236
- return /* @__PURE__ */ jsx(
2237
- "div",
2238
- {
2239
- role: "tablist",
2240
- "aria-label": rest["aria-label"],
2241
- className: cn(
2242
- "inline-flex items-center rounded-button border border-border bg-card p-0.5 shadow-button",
2243
- SIZE[size].container,
2244
- className
2245
- ),
2246
- children: items.map((item) => {
2247
- const active = item.value === value;
2248
- return /* @__PURE__ */ jsx(
2249
- "button",
2250
- {
2251
- type: "button",
2252
- role: "tab",
2253
- "aria-selected": active,
2254
- onClick: () => onChange(item.value),
2255
- className: cn(
2256
- "flex h-full items-center rounded-button font-semibold transition-colors",
2257
- SIZE[size].segment,
2258
- active ? "bg-foreground text-background" : "text-muted-foreground hover:bg-muted/60"
2259
- ),
2260
- children: item.label
2261
- },
2262
- item.value
2263
- );
2264
- })
2265
- }
2266
- );
2267
- }
2268
- var NavTransitionContext = React2.createContext("slide");
2269
- function NavTransitionProvider({ value, children }) {
2270
- return /* @__PURE__ */ jsx(NavTransitionContext.Provider, { value, children });
2271
- }
2272
- function useNavTransition() {
2273
- return React2.useContext(NavTransitionContext);
2274
- }
2275
- function resolveTransitionClass(transition, direction) {
2276
- if (transition === "none") return "";
2277
- if (transition === "fade") return "saas-page-enter";
2278
- if (direction === "back") return "saas-nav-back";
2279
- if (direction === "forward") return "saas-nav-forward";
2280
- return "saas-page-enter";
2281
- }
2282
- function PageTransition({
2283
- transitionKey,
2284
- direction,
2285
- className,
2286
- children
2287
- }) {
2288
- const transition = useNavTransition();
2289
- const animClass = resolveTransitionClass(transition, direction);
2290
- return /* @__PURE__ */ jsx("div", { className: cn(animClass, className), children }, transitionKey);
2291
- }
2292
- function PageRail({ children }) {
2293
- const slot = usePageRailSlot();
2294
- if (slot) return createPortal(children, slot);
2295
- return /* @__PURE__ */ jsx(Fragment, { children });
2296
- }
2297
- function PageHeaderActions({ children }) {
2298
- const slot = useModuleHeaderActionsSlot();
2299
- useRegisterPageActions();
2300
- if (slot) return createPortal(children, slot);
2301
- return /* @__PURE__ */ jsx("div", { className: "mb-4 flex items-center justify-end gap-2", children });
2302
- }
2303
- function PremiumBadge({ className }) {
2304
- const t = useTranslation();
2305
- const tip = t("upgrade.badgeTooltip");
2306
- 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 }) });
2307
- }
2308
- var ModuleLayoutContext = React2.createContext("rail");
2309
- function ModuleLayoutProvider({ variant, children }) {
2310
- return /* @__PURE__ */ jsx(ModuleLayoutContext.Provider, { value: variant, children });
2311
- }
2312
- function useModuleLayout() {
2313
- return React2.useContext(ModuleLayoutContext);
2314
- }
2315
- function RailNavItem({ item }) {
2316
- const [expanded, setExpanded] = React2.useState(true);
2317
- const Icon = item.icon ? ICON_MAP[item.icon] ?? null : null;
2318
- const hasChildren = item.children && item.children.length > 0;
2319
- const hasActiveChild = hasChildren && item.children.some((c) => c.active);
2320
- const isActive = item.active || hasActiveChild;
2321
- return /* @__PURE__ */ jsxs("div", { children: [
2322
- /* @__PURE__ */ jsxs(
2323
- "button",
2324
- {
2325
- type: "button",
2326
- onClick: () => hasChildren ? setExpanded(!expanded) : item.onClick?.(),
2327
- className: cn(
2328
- "flex items-center gap-2 w-full rounded-md px-2.5 py-1.5 text-sm transition-colors",
2329
- isActive ? "bg-primary/10 text-primary font-medium" : "text-muted-foreground hover:text-foreground hover:bg-muted/50"
2330
- ),
2331
- children: [
2332
- Icon && /* @__PURE__ */ jsx(Icon, { className: "h-4 w-4 shrink-0" }),
2333
- /* @__PURE__ */ jsx("span", { className: "flex-1 text-left truncate", children: item.label }),
2334
- item.premium && /* @__PURE__ */ jsx(PremiumBadge, { className: "ml-1" }),
2335
- hasChildren && (expanded ? /* @__PURE__ */ jsx(ChevronDown, { className: "h-3.5 w-3.5 shrink-0 opacity-50" }) : /* @__PURE__ */ jsx(ChevronRight, { className: "h-3.5 w-3.5 shrink-0 opacity-50" }))
2336
- ]
2337
- }
2338
- ),
2339
- hasChildren && expanded && /* @__PURE__ */ jsx("div", { className: "ml-4 mt-0.5 space-y-0.5 border-l pl-2", children: item.children.map((child) => /* @__PURE__ */ jsxs(
2340
- "button",
2341
- {
2342
- type: "button",
2343
- onClick: child.onClick,
2344
- className: cn(
2345
- "flex items-center w-full rounded-md px-2.5 py-1 text-xs transition-colors",
2346
- child.active ? "text-primary font-medium bg-primary/5" : "text-muted-foreground hover:text-foreground hover:bg-muted/50"
2347
- ),
2348
- children: [
2349
- /* @__PURE__ */ jsx("span", { className: "flex-1 text-left truncate", children: child.label }),
2350
- child.premium && /* @__PURE__ */ jsx(PremiumBadge, { className: "ml-1" })
2351
- ]
2352
- },
2353
- child.id
2354
- )) })
2355
- ] });
2356
- }
2357
- function defaultChildOf(item) {
2358
- const kids = (item.children ?? []).filter((c) => c.kind !== "action");
2359
- return kids.find((c) => c.defaultChild) ?? kids.find((c) => /(^|-)list$/i.test(c.id)) ?? kids.find((c) => !/(^|-)(new|novo|nova|create)$/i.test(c.id)) ?? kids[0];
2360
- }
2361
- function MobileTabs({ nav }) {
2362
- const scrollRef = React2.useRef(null);
2363
- const [expandedId, setExpandedId] = React2.useState(null);
2364
- React2.useEffect(() => {
2365
- const container = scrollRef.current;
2366
- if (!container) return;
2367
- const activeEl = container.querySelector('[data-active="true"]');
2368
- if (activeEl) {
2369
- const left = activeEl.offsetLeft - container.offsetWidth / 2 + activeEl.offsetWidth / 2;
2370
- container.scrollTo({ left: Math.max(0, left), behavior: "smooth" });
2371
- }
2372
- }, [nav]);
2373
- return /* @__PURE__ */ jsxs("div", { className: "md:hidden -mx-1 mb-4 no-print", children: [
2374
- /* @__PURE__ */ jsx("div", { ref: scrollRef, className: "flex gap-1 px-1 pb-2 overflow-x-auto scrollbar-none", children: nav.map((item) => {
2375
- const Icon = item.icon ? ICON_MAP[item.icon] ?? null : null;
2376
- const hasChildren = item.children && item.children.length > 0;
2377
- const isActive = item.active || item.children?.some((c) => c.active);
2378
- const isExpanded = expandedId === item.id;
2379
- return /* @__PURE__ */ jsxs(
2380
- "button",
2381
- {
2382
- type: "button",
2383
- "data-active": isActive || void 0,
2384
- onClick: () => {
2385
- if (hasChildren) {
2386
- if (!isExpanded) defaultChildOf(item)?.onClick?.();
2387
- setExpandedId(isExpanded ? null : item.id);
2388
- } else {
2389
- item.onClick?.();
2390
- setExpandedId(null);
2391
- }
2392
- },
2393
- className: cn(
2394
- // This strip is the most-tapped control in most modules and it
2395
- // was 28px tall — a third under the comfortable minimum. Height
2396
- // and text step up on touch; the desktop rail is unaffected.
2397
- "flex shrink-0 items-center gap-1.5 whitespace-nowrap rounded-full px-4 text-sm font-medium transition-colors",
2398
- "min-h-[2.75rem] active:scale-[0.98]",
2399
- isActive ? "bg-primary text-primary-foreground" : "bg-muted text-muted-foreground hover:text-foreground"
2400
- ),
2401
- children: [
2402
- Icon && /* @__PURE__ */ jsx(Icon, { className: "h-3.5 w-3.5" }),
2403
- item.label,
2404
- item.premium && /* @__PURE__ */ jsx(PremiumBadge, {}),
2405
- hasChildren && /* @__PURE__ */ jsx(ChevronDown, { className: cn("h-2.5 w-2.5 transition-transform", isExpanded && "rotate-180") })
2406
- ]
2407
- },
2408
- item.id
2409
- );
2410
- }) }),
2411
- expandedId && (() => {
2412
- const item = nav.find((n) => n.id === expandedId);
2413
- const shown = (item?.children ?? []).filter((c) => c.kind !== "action");
2414
- if (shown.length < 2) return null;
2415
- return /* @__PURE__ */ jsx("div", { className: "flex gap-1 px-2 pb-2 animate-in fade-in-0 slide-in-from-top-1", children: shown.map((child) => /* @__PURE__ */ jsxs(
2416
- "button",
2417
- {
2418
- type: "button",
2419
- onClick: () => {
2420
- child.onClick?.();
2421
- setExpandedId(null);
2422
- },
2423
- className: cn(
2424
- "whitespace-nowrap rounded-full px-2.5 py-1 text-[11px] font-medium transition-colors shrink-0",
2425
- child.active ? "bg-foreground/10 text-foreground" : "text-muted-foreground hover:text-foreground"
2426
- ),
2427
- children: [
2428
- child.label,
2429
- child.premium && /* @__PURE__ */ jsx(PremiumBadge, {})
2430
- ]
2431
- },
2432
- child.id
2433
- )) });
2434
- })()
2435
- ] });
2436
- }
2437
- var BackStyleContext = React2.createContext("link");
2438
- function BackStyleProvider({ style, children }) {
2439
- return /* @__PURE__ */ jsx(BackStyleContext.Provider, { value: style, children });
2440
- }
2441
- function useBackStyle() {
2442
- return React2.useContext(BackStyleContext);
2443
- }
2444
- function SubpageHeader({ title, subtitle, icon, onBack, parentLabel, actions, backStyle, inline }) {
2445
- const t = useTranslation();
2446
- const appStyle = useBackStyle();
2447
- const hasShellBar = useHasPageHeaderHost() && !inline;
2448
- const cross = useCrossModuleBack(onBack, parentLabel);
2449
- const effectiveOnBack = inline ? onBack : cross.onBack;
2450
- const effectiveParentLabel = inline ? parentLabel : cross.parentLabel;
2451
- useBackHandler(effectiveOnBack);
2452
- const Icon = icon ? ICON_MAP[icon] ?? null : null;
2453
- if (inline && effectiveOnBack) {
2454
- return /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
2455
- /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center justify-between gap-3", children: [
2456
- /* @__PURE__ */ jsx(PageHeaderCrumb, { title, parentLabel: effectiveParentLabel, onBack: effectiveOnBack }),
2457
- actions && /* @__PURE__ */ jsx("div", { className: "no-print", children: actions })
2458
- ] }),
2459
- subtitle && /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: subtitle })
2460
- ] });
2461
- }
2462
- if (hasShellBar) {
2463
- return /* @__PURE__ */ jsx(
2464
- ShellSubpageHeader,
2465
- {
2466
- title,
2467
- subtitle,
2468
- icon: Icon,
2469
- onBack: effectiveOnBack,
2470
- parentLabel: effectiveParentLabel,
2471
- actions
2472
- }
2473
- );
2474
- }
2475
- const style = !effectiveOnBack ? "icon" : backStyle ?? appStyle;
2476
- const effectiveStyle = style !== "icon" && !effectiveParentLabel ? "icon" : style;
2477
- const titleBlock = /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-4", children: [
2478
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
2479
- effectiveStyle === "icon" && effectiveOnBack && /* @__PURE__ */ jsx(
2480
- "button",
2481
- {
2482
- onClick: effectiveOnBack,
2483
- className: "flex h-8 w-8 items-center justify-center rounded-lg border hover:bg-muted bg-card shadow-button active:shadow-button-inset transition-colors",
2484
- children: /* @__PURE__ */ jsx(ChevronLeft, { className: "h-4 w-4" })
2485
- }
2486
- ),
2487
- Icon && /* @__PURE__ */ jsx("div", { className: "flex h-10 w-10 items-center justify-center rounded-xl bg-muted", children: /* @__PURE__ */ jsx(Icon, { className: "h-5 w-5 text-muted-foreground" }) }),
2488
- /* @__PURE__ */ jsxs("div", { children: [
2489
- /* @__PURE__ */ jsx("h2", { className: "text-xl font-bold", children: title }),
2490
- subtitle && /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: subtitle })
2491
- ] })
2492
- ] }),
2493
- actions && /* @__PURE__ */ jsx("div", { className: "no-print", children: actions })
2494
- ] });
2495
- if (effectiveStyle === "breadcrumb") {
2496
- return /* @__PURE__ */ jsxs("div", { className: "space-y-4 mb-6", children: [
2497
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-4", children: [
2498
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-sm text-muted-foreground", children: [
2499
- /* @__PURE__ */ jsx("button", { onClick: effectiveOnBack, className: "hover:text-foreground transition-colors", children: effectiveParentLabel }),
2500
- /* @__PURE__ */ jsx(ChevronRight, { className: "h-3.5 w-3.5" }),
2501
- /* @__PURE__ */ jsx("span", { className: "text-foreground font-medium", children: title })
2502
- ] }),
2503
- actions && /* @__PURE__ */ jsx("div", { className: "no-print", children: actions })
2504
- ] }),
2505
- subtitle && /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground -mt-2", children: subtitle })
2506
- ] });
2507
- }
2508
- if (effectiveStyle === "link") {
2509
- return /* @__PURE__ */ jsxs("div", { className: "space-y-3 mb-6", children: [
2510
- /* @__PURE__ */ jsxs(
2511
- "button",
2512
- {
2513
- onClick: effectiveOnBack,
2514
- className: "inline-flex items-center gap-1.5 text-sm text-muted-foreground hover:text-foreground transition-colors",
2515
- children: [
2516
- /* @__PURE__ */ jsx(ChevronLeft, { className: "h-4 w-4" }),
2517
- t("crud.detail.backTo", { entities: effectiveParentLabel })
2518
- ]
2519
- }
2520
- ),
2521
- titleBlock
2522
- ] });
2523
- }
2524
- return /* @__PURE__ */ jsx("div", { className: "space-y-4 mb-6", children: titleBlock });
2525
- }
2526
- function ShellSubpageHeader({
2527
- title,
2528
- subtitle,
2529
- icon: Icon,
2530
- onBack,
2531
- parentLabel,
2532
- actions
2533
- }) {
2534
- usePageHeader(true, { back: !!onBack });
2535
- const crumb = /* @__PURE__ */ jsx(PageTitlePortal, { children: /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 items-center gap-2", children: [
2536
- Icon && !onBack && /* @__PURE__ */ jsx(Icon, { className: "h-4 w-4 shrink-0 text-muted-foreground" }),
2537
- /* @__PURE__ */ jsx(PageHeaderCrumb, { title, parentLabel, onBack })
2538
- ] }) });
2539
- if (!actions) return crumb;
2540
- return /* @__PURE__ */ jsxs(Fragment, { children: [
2541
- crumb,
2542
- /* @__PURE__ */ jsx(PageHeaderActions, { children: /* @__PURE__ */ jsx("div", { className: "no-print flex shrink-0 items-center gap-2", children: actions }) })
2543
- ] });
2544
- }
2545
- function ModuleHeader({ nav }) {
2546
- function defaultChild(item) {
2547
- const kids2 = (item.children ?? []).filter((c) => c.kind !== "action");
2548
- return kids2.find((c) => /list/i.test(c.id) || /^list$/i.test(c.label)) ?? kids2[kids2.length - 1];
2549
- }
2550
- const activeItem = nav.find((item) => item.active || item.children?.some((c) => c.active));
2551
- const tabs = nav.map((item) => ({
2552
- value: item.id,
2553
- label: item.label,
2554
- icon: item.icon ? ICON_MAP[item.icon] ?? void 0 : void 0,
2555
- // Plan-gated views keep their upgrade marker — dropping it would hide the
2556
- // only signal that the tab costs an upgrade.
2557
- badge: item.premium ? /* @__PURE__ */ jsx(PremiumBadge, {}) : void 0
2558
- }));
2559
- const onChange = (id) => {
2560
- const item = nav.find((n) => n.id === id);
2561
- if (!item) return;
2562
- const hasChildren = item.children && item.children.length > 0;
2563
- if (hasChildren) defaultChild(item)?.onClick?.();
2564
- else item.onClick?.();
2565
- };
2566
- const kids = (activeItem?.children ?? []).filter((c) => c.kind !== "action");
2567
- const activeKid = kids.find((c) => c.active)?.id ?? kids[0]?.id;
2568
- const childControl = kids.length > 1 ? /* @__PURE__ */ jsx(
2569
- SegmentedControl,
2570
- {
2571
- size: "sm",
2572
- "aria-label": activeItem?.label,
2573
- options: kids.map((c) => ({ value: c.id, label: c.label })),
2574
- value: activeKid ?? "",
2575
- onChange: (id) => kids.find((c) => c.id === id)?.onClick?.()
2576
- }
2577
- ) : void 0;
2578
- return /* @__PURE__ */ jsx(
2579
- PageTabs,
2580
- {
2581
- tabs,
2582
- value: activeItem?.id ?? nav[0]?.id ?? "",
2583
- onChange,
2584
- actions: childControl
2585
- }
2586
- );
2587
- }
2588
- function ModulePage({ title: _title, subtitle, nav, children, className, headerAction, showHeader = true, sidebarFooter, navVariant, viewKey, direction }) {
2589
- const ctxVariant = useModuleLayout();
2590
- const variant = navVariant ?? ctxVariant;
2591
- const transitionKey = viewKey ?? "view";
2592
- const onSubpage = useHasPageHeaderBack();
2593
- if (variant === "sidebar") {
2594
- return /* @__PURE__ */ jsxs(Fragment, { children: [
2595
- showHeader && headerAction && /* @__PURE__ */ jsx(PageHeaderActions, { children: headerAction }),
2596
- /* @__PURE__ */ jsx(PageTransition, { transitionKey, direction, className: cn("min-w-0", className), children })
2597
- ] });
2598
- }
2599
- if (variant === "tabs") {
2600
- return /* @__PURE__ */ jsxs(Fragment, { children: [
2601
- !onSubpage && /* @__PURE__ */ jsx(ModuleHeader, { nav }),
2602
- showHeader && headerAction && /* @__PURE__ */ jsx(PageHeaderActions, { children: headerAction }),
2603
- /* @__PURE__ */ jsx(PageTransition, { transitionKey, direction, className: cn("min-w-0", className), children })
2604
- ] });
2605
- }
2606
- return /* @__PURE__ */ jsxs("div", { className: cn("min-w-0", className), children: [
2607
- /* @__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: [
2608
- /* @__PURE__ */ jsx("nav", { className: "space-y-0.5", children: nav.map((item) => /* @__PURE__ */ jsx(RailNavItem, { item }, item.id)) }),
2609
- sidebarFooter && /* @__PURE__ */ jsx("div", { className: "mt-auto pt-3", children: sidebarFooter })
2610
- ] }) }),
2611
- /* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
2612
- showHeader && headerAction && /* @__PURE__ */ jsx(PageHeaderActions, { children: headerAction }),
2613
- !onSubpage && /* @__PURE__ */ jsx(MobileTabs, { nav }),
2614
- /* @__PURE__ */ jsx(PageTransition, { transitionKey, direction, children })
2615
- ] })
2616
- ] });
2617
- }
2618
2321
 
2619
- export { AppShell, BackStyleProvider, HeaderTabs, ModuleLayoutProvider, ModulePage, NavTransitionProvider, PageHeader, PageHeaderActions, PageHeaderCrumb, PageRail, PageTabs, PageTransition, RailNavItem, SaveBar, SaveBarProvider, SegmentedControl, Sheet, SheetBody, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetOverlay, SheetPortal, SheetTitle, SheetTrigger, Sidebar, SubpageHeader, Topbar, bottomNavReachesAll, useBackHandler, useBackStyle, useCrossModuleBack, useModuleHeaderActionsSlot, useModuleHeaderSlot, useModuleLayout, useNavTransition, useRailRow, useSaveBar, useSaveBarVisible };
2620
- //# sourceMappingURL=chunk-EGHEU3U5.js.map
2621
- //# sourceMappingURL=chunk-EGHEU3U5.js.map
2322
+ export { AppShell, PageHeader, PageHeaderCrumb, PageHeaderOutlet, PageTitlePortal, SaveBar, SaveBarProvider, ShellSearch, Sidebar, Topbar, bottomNavReachesAll, useBackHandler, useCrossModuleBack, useHasPageActions, useHasPageHeaderBack, useHasPageHeaderHost, useHasPageHeaderOutlet, useLayoutStore, useModuleHeaderActionsSlot, useModuleHeaderSlot, usePageHeader, usePageHeaderClaimed, usePageHeaderHost, usePageRailSlot, useRailRow, useRegisterPageActions, useSaveBar, useSaveBarVisible };
2323
+ //# sourceMappingURL=chunk-RRNMNFLF.js.map
2324
+ //# sourceMappingURL=chunk-RRNMNFLF.js.map