@fayz-ai/ui 0.15.0 → 0.17.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 (78) hide show
  1. package/dist/analytics/context.d.ts +15 -1
  2. package/dist/analytics/context.d.ts.map +1 -1
  3. package/dist/analytics/index.d.ts +1 -1
  4. package/dist/analytics/index.d.ts.map +1 -1
  5. package/dist/{chunk-J7F3Z52K.js → chunk-DFOAKWYB.js} +4 -4
  6. package/dist/chunk-DFOAKWYB.js.map +1 -0
  7. package/dist/{chunk-KD2V3TAX.js → chunk-E44WOPCR.js} +61 -49
  8. package/dist/chunk-E44WOPCR.js.map +1 -0
  9. package/dist/{chunk-FFJ2C5YH.js → chunk-HKGTF2YA.js} +41 -16
  10. package/dist/chunk-HKGTF2YA.js.map +1 -0
  11. package/dist/{chunk-NA3GE4H3.js → chunk-OBIU37AX.js} +365 -252
  12. package/dist/chunk-OBIU37AX.js.map +1 -0
  13. package/dist/{chunk-SLWTKHNY.js → chunk-Z2ITFPGL.js} +380 -83
  14. package/dist/chunk-Z2ITFPGL.js.map +1 -0
  15. package/dist/{chunk-5KMAEQA3.js → chunk-ZLPYNHXH.js} +56 -21
  16. package/dist/chunk-ZLPYNHXH.js.map +1 -0
  17. package/dist/dashboard/OnboardingWidget.d.ts +7 -1
  18. package/dist/dashboard/OnboardingWidget.d.ts.map +1 -1
  19. package/dist/dashboard/SidebarOnboarding.d.ts +7 -1
  20. package/dist/dashboard/SidebarOnboarding.d.ts.map +1 -1
  21. package/dist/dashboard/index.js +3 -3
  22. package/dist/hooks/useIsWide.d.ts +16 -0
  23. package/dist/hooks/useIsWide.d.ts.map +1 -0
  24. package/dist/index.d.ts +5 -3
  25. package/dist/index.d.ts.map +1 -1
  26. package/dist/index.js +278 -10
  27. package/dist/index.js.map +1 -1
  28. package/dist/layout/AppShell.d.ts +25 -2
  29. package/dist/layout/AppShell.d.ts.map +1 -1
  30. package/dist/layout/ModulePage.d.ts +4 -0
  31. package/dist/layout/ModulePage.d.ts.map +1 -1
  32. package/dist/layout/SaveBar.d.ts.map +1 -1
  33. package/dist/layout/Sidebar.d.ts.map +1 -1
  34. package/dist/layout/Topbar.d.ts +5 -1
  35. package/dist/layout/Topbar.d.ts.map +1 -1
  36. package/dist/layout/index.d.ts +1 -1
  37. package/dist/layout/index.d.ts.map +1 -1
  38. package/dist/layout/index.js +3 -3
  39. package/dist/primitives/checkbox.d.ts +4 -0
  40. package/dist/primitives/checkbox.d.ts.map +1 -1
  41. package/dist/primitives/index.d.ts +3 -0
  42. package/dist/primitives/index.d.ts.map +1 -1
  43. package/dist/primitives/index.js +4 -4
  44. package/dist/primitives/list-view.d.ts +5 -1
  45. package/dist/primitives/list-view.d.ts.map +1 -1
  46. package/dist/primitives/modal.d.ts.map +1 -1
  47. package/dist/primitives/page-actions.d.ts +55 -0
  48. package/dist/primitives/page-actions.d.ts.map +1 -0
  49. package/dist/primitives/page-header.d.ts +5 -2
  50. package/dist/primitives/page-header.d.ts.map +1 -1
  51. package/dist/primitives/page-toolbar.d.ts.map +1 -1
  52. package/dist/primitives/refresh-button.d.ts.map +1 -1
  53. package/dist/primitives/row-actions.d.ts +11 -0
  54. package/dist/primitives/row-actions.d.ts.map +1 -0
  55. package/dist/primitives/whatsapp-icon.d.ts +7 -0
  56. package/dist/primitives/whatsapp-icon.d.ts.map +1 -0
  57. package/dist/stores/index.d.ts +1 -1
  58. package/dist/stores/index.d.ts.map +1 -1
  59. package/dist/stores/layout.store.d.ts +1 -0
  60. package/dist/stores/layout.store.d.ts.map +1 -1
  61. package/dist/stores/page-header.store.d.ts +39 -5
  62. package/dist/stores/page-header.store.d.ts.map +1 -1
  63. package/dist/styles.css +188 -3
  64. package/dist/tour/TourSpotlight.d.ts +29 -0
  65. package/dist/tour/TourSpotlight.d.ts.map +1 -0
  66. package/dist/tour/index.d.ts +4 -0
  67. package/dist/tour/index.d.ts.map +1 -0
  68. package/dist/tour/tour.store.d.ts +22 -0
  69. package/dist/tour/tour.store.d.ts.map +1 -0
  70. package/dist/tour/types.d.ts +28 -0
  71. package/dist/tour/types.d.ts.map +1 -0
  72. package/package.json +2 -2
  73. package/dist/chunk-5KMAEQA3.js.map +0 -1
  74. package/dist/chunk-FFJ2C5YH.js.map +0 -1
  75. package/dist/chunk-J7F3Z52K.js.map +0 -1
  76. package/dist/chunk-KD2V3TAX.js.map +0 -1
  77. package/dist/chunk-NA3GE4H3.js.map +0 -1
  78. package/dist/chunk-SLWTKHNY.js.map +0 -1
@@ -1,9 +1,9 @@
1
- import { useHasPageHeaderHost, usePageHeader, useLayoutStore, useRegisterPageActions, useHasPageHeaderBack, usePageHeaderSlot, useHasPageHeaderOutlet, useHasPageActions, usePageHeaderHost } from './chunk-FFJ2C5YH.js';
1
+ import { useHasPageHeaderHost, usePageHeader, PageTitlePortal, useLayoutStore, useHasPageHeaderBack, useRegisterPageActions, usePageHeaderClaimed, usePageTitleSlotHost, useHasPageHeaderOutlet, useHasPageActions, usePageHeaderHost } from './chunk-HKGTF2YA.js';
2
2
  import { cn } from './chunk-OKZ6WQB3.js';
3
3
  import { ICON_MAP } from './chunk-4GK6IHOI.js';
4
- import * as React7 from 'react';
4
+ import * as React2 from 'react';
5
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, PiggyBank, Wallet, MessageCircle, Settings, Calendar, ArrowLeftRight, Receipt, UserCog, LogOut } from 'lucide-react';
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
7
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
8
8
  import { useTranslation, useNavReferrer, routeModule, navHistoryBack } from '@fayz-ai/core';
9
9
  import * as Popover from '@radix-ui/react-popover';
@@ -29,19 +29,19 @@ var Sheet = DialogPrimitive.Root;
29
29
  var SheetTrigger = DialogPrimitive.Trigger;
30
30
  var SheetClose = DialogPrimitive.Close;
31
31
  var SheetPortal = DialogPrimitive.Portal;
32
- var SheetOverlay = React7.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(
32
+ var SheetOverlay = React2.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(
33
33
  DialogPrimitive.Overlay,
34
34
  {
35
35
  ref,
36
36
  className: cn(
37
- "saas-sheet-overlay fixed inset-0 z-50 bg-black/40 backdrop-blur-[2px]",
37
+ "saas-sheet-overlay fixed inset-0 z-[100] bg-black/40 backdrop-blur-[2px]",
38
38
  className
39
39
  ),
40
40
  ...props
41
41
  }
42
42
  ));
43
43
  SheetOverlay.displayName = "SheetOverlay";
44
- var SheetContent = React7.forwardRef(({ className, children, width, hideClose, overlay = "blur", onPointerDownOutside, onInteractOutside, ...props }, ref) => /* @__PURE__ */ jsxs(SheetPortal, { children: [
44
+ var SheetContent = React2.forwardRef(({ className, children, width, hideClose, overlay = "blur", onPointerDownOutside, onInteractOutside, ...props }, ref) => /* @__PURE__ */ jsxs(SheetPortal, { children: [
45
45
  /* @__PURE__ */ jsx(SheetOverlay, { className: overlay === "none" ? "bg-transparent backdrop-blur-0" : overlay === "dim" ? "backdrop-blur-0" : void 0 }),
46
46
  /* @__PURE__ */ jsxs(
47
47
  DialogPrimitive.Content,
@@ -62,7 +62,7 @@ var SheetContent = React7.forwardRef(({ className, children, width, hideClose, o
62
62
  onInteractOutside?.(e);
63
63
  },
64
64
  className: cn(
65
- "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",
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
66
  "w-full",
67
67
  width ?? "max-w-md",
68
68
  className
@@ -99,7 +99,7 @@ function SheetFooter({ className, ...props }) {
99
99
  );
100
100
  }
101
101
  SheetFooter.displayName = "SheetFooter";
102
- var SheetTitle = React7.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(
102
+ var SheetTitle = React2.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(
103
103
  DialogPrimitive.Title,
104
104
  {
105
105
  ref,
@@ -108,7 +108,7 @@ var SheetTitle = React7.forwardRef(({ className, ...props }, ref) => /* @__PURE_
108
108
  }
109
109
  ));
110
110
  SheetTitle.displayName = "SheetTitle";
111
- var SheetDescription = React7.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(
111
+ var SheetDescription = React2.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(
112
112
  DialogPrimitive.Description,
113
113
  {
114
114
  ref,
@@ -127,8 +127,8 @@ function SheetBody({ className, ...props }) {
127
127
  );
128
128
  }
129
129
  SheetBody.displayName = "SheetBody";
130
- var SaveBarApiContext = React7.createContext(null);
131
- var SaveBarStateContext = React7.createContext({ visible: false, saving: false });
130
+ var SaveBarApiContext = React2.createContext(null);
131
+ var SaveBarStateContext = React2.createContext({ visible: false, saving: false });
132
132
  var _saveBarVisible = false;
133
133
  var _saveBarSubs = /* @__PURE__ */ new Set();
134
134
  function setSaveBarVisibleSignal(v) {
@@ -137,7 +137,7 @@ function setSaveBarVisibleSignal(v) {
137
137
  _saveBarSubs.forEach((fn) => fn());
138
138
  }
139
139
  function useSaveBarVisible() {
140
- return React7.useSyncExternalStore(
140
+ return React2.useSyncExternalStore(
141
141
  (cb) => {
142
142
  _saveBarSubs.add(cb);
143
143
  return () => {
@@ -149,15 +149,15 @@ function useSaveBarVisible() {
149
149
  );
150
150
  }
151
151
  function SaveBarProvider({ children }) {
152
- const [state, setState] = React7.useState({ visible: false, saving: false });
153
- const handlers = React7.useRef({});
154
- const api = React7.useMemo(() => ({ setState, handlers }), []);
155
- const backStack = React7.useRef([]);
156
- const backApi = React7.useMemo(() => ({ stack: backStack }), []);
157
- React7.useEffect(() => {
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(() => {
158
158
  setSaveBarVisibleSignal(state.visible);
159
159
  }, [state.visible]);
160
- React7.useEffect(() => {
160
+ React2.useEffect(() => {
161
161
  function onKey(e) {
162
162
  if (state.visible && !state.saving) {
163
163
  if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) {
@@ -185,7 +185,7 @@ function SaveBarProvider({ children }) {
185
185
  }, [state.visible, state.saving]);
186
186
  return /* @__PURE__ */ jsx(SaveBarApiContext.Provider, { value: api, children: /* @__PURE__ */ jsx(BackNavApiContext.Provider, { value: backApi, children: /* @__PURE__ */ jsx(SaveBarStateContext.Provider, { value: state, children }) }) });
187
187
  }
188
- var BackNavApiContext = React7.createContext(null);
188
+ var BackNavApiContext = React2.createContext(null);
189
189
  function isEditableTarget(target) {
190
190
  const el = target;
191
191
  if (!el || !el.tagName) return false;
@@ -193,12 +193,12 @@ function isEditableTarget(target) {
193
193
  return tag === "input" || tag === "textarea" || tag === "select" || el.isContentEditable;
194
194
  }
195
195
  function useBackHandler(onBack) {
196
- const api = React7.useContext(BackNavApiContext);
197
- const ref = React7.useRef(onBack);
198
- React7.useEffect(() => {
196
+ const api = React2.useContext(BackNavApiContext);
197
+ const ref = React2.useRef(onBack);
198
+ React2.useEffect(() => {
199
199
  ref.current = onBack;
200
200
  });
201
- React7.useEffect(() => {
201
+ React2.useEffect(() => {
202
202
  if (!api) return;
203
203
  const stack = api.stack.current;
204
204
  stack.push(ref);
@@ -209,23 +209,23 @@ function useBackHandler(onBack) {
209
209
  }, [api]);
210
210
  }
211
211
  function useSaveBar({ dirty, saving = false, onSave, onDiscard, message, saveLabel, discardLabel }) {
212
- const api = React7.useContext(SaveBarApiContext);
213
- React7.useEffect(() => {
212
+ const api = React2.useContext(SaveBarApiContext);
213
+ React2.useEffect(() => {
214
214
  if (api) api.handlers.current = { onSave, onDiscard };
215
215
  });
216
- React7.useEffect(() => {
216
+ React2.useEffect(() => {
217
217
  if (!api) return;
218
218
  api.setState({ visible: dirty, saving, message, saveLabel, discardLabel });
219
219
  return () => api.setState((s) => ({ ...s, visible: false }));
220
220
  }, [api, dirty, saving, message, saveLabel, discardLabel]);
221
221
  }
222
222
  function SaveBar({ className }) {
223
- const state = React7.useContext(SaveBarStateContext);
224
- const api = React7.useContext(SaveBarApiContext);
223
+ const state = React2.useContext(SaveBarStateContext);
224
+ const api = React2.useContext(SaveBarApiContext);
225
225
  const t = useTranslation();
226
- const [rendered, setRendered] = React7.useState(state.visible);
227
- const [closing, setClosing] = React7.useState(false);
228
- React7.useEffect(() => {
226
+ const [rendered, setRendered] = React2.useState(state.visible);
227
+ const [closing, setClosing] = React2.useState(false);
228
+ React2.useEffect(() => {
229
229
  if (state.visible) {
230
230
  setRendered(true);
231
231
  setClosing(false);
@@ -241,40 +241,52 @@ function SaveBar({ className }) {
241
241
  }, [state.visible, rendered]);
242
242
  if (!api || !rendered) return null;
243
243
  const { saving, message, saveLabel, discardLabel } = state;
244
- 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(
245
- "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",
246
- closing ? "animate-out fade-out slide-out-to-bottom-2" : "animate-in fade-in slide-in-from-bottom-2"
247
- ), children: [
248
- /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-2 text-sm font-medium", children: [
249
- /* @__PURE__ */ jsx("span", { className: "h-2 w-2 shrink-0 rounded-full bg-amber-400" }),
250
- message ?? t("saveBar.unsaved")
251
- ] }),
252
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
253
- /* @__PURE__ */ jsx(
254
- "button",
255
- {
256
- type: "button",
257
- disabled: saving,
258
- onClick: () => api.handlers.current.onDiscard?.(),
259
- 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",
260
- children: discardLabel ?? t("common.discard")
261
- }
244
+ return /* @__PURE__ */ jsx(
245
+ "div",
246
+ {
247
+ className: cn(
248
+ // Above the tab bar (z-50), below a full-screen sheet (z-60), and lifted
249
+ // clear of the bar's height so its actions are not sitting under it.
250
+ "pointer-events-none fixed inset-x-0 z-[55] flex justify-center px-4",
251
+ "bottom-[calc(var(--fz-bottom-bar,0px)+1.25rem)]",
252
+ className
262
253
  ),
263
- /* @__PURE__ */ jsxs(
264
- "button",
265
- {
266
- type: "button",
267
- disabled: saving,
268
- onClick: () => api.handlers.current.onSave?.(),
269
- 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",
270
- children: [
271
- saving && /* @__PURE__ */ jsx(Loader2, { className: "mr-1.5 h-3.5 w-3.5 animate-spin" }),
272
- saveLabel ?? t("common.save")
273
- ]
274
- }
275
- )
276
- ] })
277
- ] }) });
254
+ children: /* @__PURE__ */ jsxs("div", { className: cn(
255
+ "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",
256
+ closing ? "animate-out fade-out slide-out-to-bottom-2" : "animate-in fade-in slide-in-from-bottom-2"
257
+ ), children: [
258
+ /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-2 text-sm font-medium", children: [
259
+ /* @__PURE__ */ jsx("span", { className: "h-2 w-2 shrink-0 rounded-full bg-amber-400" }),
260
+ message ?? t("saveBar.unsaved")
261
+ ] }),
262
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
263
+ /* @__PURE__ */ jsx(
264
+ "button",
265
+ {
266
+ type: "button",
267
+ disabled: saving,
268
+ onClick: () => api.handlers.current.onDiscard?.(),
269
+ 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",
270
+ children: discardLabel ?? t("common.discard")
271
+ }
272
+ ),
273
+ /* @__PURE__ */ jsxs(
274
+ "button",
275
+ {
276
+ type: "button",
277
+ disabled: saving,
278
+ onClick: () => api.handlers.current.onSave?.(),
279
+ 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",
280
+ children: [
281
+ saving && /* @__PURE__ */ jsx(Loader2, { className: "mr-1.5 h-3.5 w-3.5 animate-spin" }),
282
+ saveLabel ?? t("common.save")
283
+ ]
284
+ }
285
+ )
286
+ ] })
287
+ ] })
288
+ }
289
+ );
278
290
  }
279
291
  function useCrossModuleBack(onBack, parentLabel) {
280
292
  const currentPath = typeof window !== "undefined" ? (window.location.hash ? window.location.hash.slice(1) : window.location.pathname) || "/" : "/";
@@ -289,10 +301,14 @@ function useCrossModuleBack(onBack, parentLabel) {
289
301
  function PageHeaderCrumb({
290
302
  title,
291
303
  parentLabel,
292
- onBack
304
+ onBack,
305
+ headingLevel = "h2"
293
306
  }) {
307
+ const Heading = headingLevel;
308
+ const asHeading = typeof title === "string" && title !== "";
294
309
  if (!onBack) {
295
- return title ? /* @__PURE__ */ jsx("h2", { className: "min-w-0 truncate text-lg font-semibold", children: title }) : null;
310
+ if (!title) return null;
311
+ return asHeading ? /* @__PURE__ */ jsx(Heading, { className: "min-w-0 truncate text-lg font-semibold", children: title }) : /* @__PURE__ */ jsx("span", { className: "min-w-0 truncate text-lg font-semibold", children: title });
296
312
  }
297
313
  return /* @__PURE__ */ jsxs("nav", { className: "flex min-w-0 items-center gap-1.5 text-sm text-muted-foreground", children: [
298
314
  /* @__PURE__ */ jsxs(
@@ -313,6 +329,11 @@ function PageHeaderCrumb({
313
329
  // Title still waiting on data: a pulsing bar, otherwise the line
314
330
  // appears from nowhere and shoves the rest when the record lands.
315
331
  /* @__PURE__ */ jsx("span", { className: "inline-block h-4 w-24 animate-pulse rounded bg-muted align-middle" })
332
+ ) : asHeading ? (
333
+ // A heading, not a span, when it IS the screen's name: a subpage
334
+ // that only ever renders a breadcrumb was leaving the page with no
335
+ // heading for a screen reader to land on.
336
+ /* @__PURE__ */ jsx(Heading, { className: "min-w-0 truncate font-medium text-foreground", children: title })
316
337
  ) : /* @__PURE__ */ jsx("span", { className: "min-w-0 truncate font-medium text-foreground", children: title })
317
338
  ] })
318
339
  ] });
@@ -321,7 +342,7 @@ function PageHeader({ title, onBack, parentLabel, backLabel }) {
321
342
  const hasHost = useHasPageHeaderHost();
322
343
  const { onBack: effectiveOnBack, parentLabel: effectiveParent } = useCrossModuleBack(onBack, parentLabel);
323
344
  useBackHandler(effectiveOnBack);
324
- const node = React7.useMemo(() => /* @__PURE__ */ jsx(
345
+ const node = React2.useMemo(() => /* @__PURE__ */ jsx(
325
346
  PageHeaderCrumb,
326
347
  {
327
348
  title,
@@ -329,21 +350,21 @@ function PageHeader({ title, onBack, parentLabel, backLabel }) {
329
350
  onBack: effectiveOnBack
330
351
  }
331
352
  ), [effectiveOnBack, effectiveParent, backLabel, title]);
332
- usePageHeader(hasHost ? node : null, { back: !!effectiveOnBack });
333
- if (hasHost) return null;
353
+ usePageHeader(hasHost, { back: !!effectiveOnBack });
354
+ if (hasHost) return /* @__PURE__ */ jsx(PageTitlePortal, { children: node });
334
355
  return /* @__PURE__ */ jsx("div", { className: "mb-4", children: node });
335
356
  }
336
357
  var GUTTER_PX = 8;
337
358
  var CLOSE_DELAY_MS = 140;
338
- var useIsomorphicLayoutEffect = typeof window !== "undefined" ? React7.useLayoutEffect : React7.useEffect;
359
+ var useIsomorphicLayoutEffect = typeof window !== "undefined" ? React2.useLayoutEffect : React2.useEffect;
339
360
  function iconOf(name) {
340
361
  if (!name) return null;
341
362
  return ICON_MAP[name] ?? null;
342
363
  }
343
- var RailFlyoutContext = React7.createContext(null);
364
+ var RailFlyoutContext = React2.createContext(null);
344
365
  var RailFlyoutProvider = RailFlyoutContext.Provider;
345
366
  function useRailRow(content) {
346
- const flyout = React7.useContext(RailFlyoutContext);
367
+ const flyout = React2.useContext(RailFlyoutContext);
347
368
  if (!flyout || !content) return {};
348
369
  return flyout.rowProps(content);
349
370
  }
@@ -353,19 +374,19 @@ function useRailFlyout({
353
374
  activeRoute,
354
375
  onNavigate
355
376
  }) {
356
- const [state, setState] = React7.useState(null);
357
- const [pos, setPos] = React7.useState({ top: 0, arrow: 20 });
358
- const [animate, setAnimate] = React7.useState(false);
359
- const panelRef = React7.useRef(null);
360
- const closeTimer = React7.useRef(null);
361
- const lastContent = React7.useRef(null);
362
- const cancelClose = React7.useCallback(() => {
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(() => {
363
384
  if (closeTimer.current) {
364
385
  clearTimeout(closeTimer.current);
365
386
  closeTimer.current = null;
366
387
  }
367
388
  }, []);
368
- const show = React7.useCallback(
389
+ const show = React2.useCallback(
369
390
  (anchor, content2) => {
370
391
  if (!enabled) return;
371
392
  cancelClose();
@@ -373,19 +394,19 @@ function useRailFlyout({
373
394
  },
374
395
  [enabled, cancelClose]
375
396
  );
376
- const hide = React7.useCallback(() => {
397
+ const hide = React2.useCallback(() => {
377
398
  cancelClose();
378
399
  closeTimer.current = setTimeout(() => setState(null), CLOSE_DELAY_MS);
379
400
  }, [cancelClose]);
380
- const dismiss = React7.useCallback(() => {
401
+ const dismiss = React2.useCallback(() => {
381
402
  cancelClose();
382
403
  setState(null);
383
404
  }, [cancelClose]);
384
- React7.useEffect(() => () => cancelClose(), [cancelClose]);
385
- React7.useEffect(() => {
405
+ React2.useEffect(() => () => cancelClose(), [cancelClose]);
406
+ React2.useEffect(() => {
386
407
  if (!enabled) dismiss();
387
408
  }, [enabled, dismiss]);
388
- const reposition = React7.useCallback(() => {
409
+ const reposition = React2.useCallback(() => {
389
410
  const rail = railRef.current;
390
411
  const anchor = state?.anchor;
391
412
  if (!rail || !anchor) return;
@@ -400,7 +421,7 @@ function useRailFlyout({
400
421
  setPos({ top, arrow });
401
422
  }, [railRef, state]);
402
423
  useIsomorphicLayoutEffect(reposition, [reposition, state?.content.key]);
403
- React7.useEffect(() => {
424
+ React2.useEffect(() => {
404
425
  if (!state) return;
405
426
  const rail = railRef.current;
406
427
  const onScroll = () => reposition();
@@ -412,7 +433,7 @@ function useRailFlyout({
412
433
  };
413
434
  }, [state, railRef, reposition]);
414
435
  const visible = !!state;
415
- React7.useEffect(() => {
436
+ React2.useEffect(() => {
416
437
  if (!visible) {
417
438
  setAnimate(false);
418
439
  return;
@@ -420,7 +441,7 @@ function useRailFlyout({
420
441
  const id = requestAnimationFrame(() => setAnimate(true));
421
442
  return () => cancelAnimationFrame(id);
422
443
  }, [visible]);
423
- React7.useEffect(() => {
444
+ React2.useEffect(() => {
424
445
  if (!visible) return;
425
446
  const onKey = (event) => {
426
447
  if (event.key === "Escape") dismiss();
@@ -428,7 +449,7 @@ function useRailFlyout({
428
449
  window.addEventListener("keydown", onKey);
429
450
  return () => window.removeEventListener("keydown", onKey);
430
451
  }, [visible, dismiss]);
431
- const rowProps = React7.useCallback(
452
+ const rowProps = React2.useCallback(
432
453
  (content2) => ({
433
454
  onMouseEnter: (event) => show(event.currentTarget, content2),
434
455
  onFocus: (event) => show(event.currentTarget, content2),
@@ -545,7 +566,7 @@ function FlyoutBody({
545
566
  ] });
546
567
  }
547
568
  var RAIL_PUSH_MS = 260;
548
- var useIsomorphicLayoutEffect2 = typeof window !== "undefined" ? React7.useLayoutEffect : React7.useEffect;
569
+ var useIsomorphicLayoutEffect2 = typeof window !== "undefined" ? React2.useLayoutEffect : React2.useEffect;
549
570
  function getIcon(name) {
550
571
  return ICON_MAP[name] ?? Home;
551
572
  }
@@ -601,6 +622,7 @@ function NavItem({
601
622
  item.badge !== void 0 && /* @__PURE__ */ jsx("span", { className: cn("rounded-full bg-primary px-2 py-0.5 text-xs font-semibold text-primary-foreground", !isExternal && !item.premium && "ml-auto"), children: item.badge })
602
623
  ] })
603
624
  ] });
625
+ const anchor = `nav.${item.route}`;
604
626
  return isExternal ? /* @__PURE__ */ jsx("a", { href: item.href, target: "_blank", rel: "noopener noreferrer", className, ...hoverProps, children: inner }) : /* @__PURE__ */ jsx(
605
627
  "button",
606
628
  {
@@ -609,6 +631,7 @@ function NavItem({
609
631
  onNavigate(item.route);
610
632
  },
611
633
  className,
634
+ "data-tour": anchor,
612
635
  ...hoverProps,
613
636
  children: inner
614
637
  }
@@ -645,7 +668,7 @@ function NavGroup({
645
668
  flyout
646
669
  }) {
647
670
  const isChildActive = item.children?.some((c) => c.route === activeRoute);
648
- const [open, setOpen] = React7.useState(() => isChildActive ?? false);
671
+ const [open, setOpen] = React2.useState(() => isChildActive ?? false);
649
672
  const Icon = getIcon(item.icon);
650
673
  if (collapsed) {
651
674
  const hoverProps = flyout?.rowProps(flyoutContentOf(item));
@@ -656,6 +679,7 @@ function NavGroup({
656
679
  flyout?.dismiss();
657
680
  onNavigate(item.route);
658
681
  },
682
+ "data-tour": `nav.${item.route}`,
659
683
  className: cn(
660
684
  "flex w-full items-center justify-center rounded-button px-2 py-2 text-sm font-medium transition-colors",
661
685
  "hover:bg-sidebar-accent hover:text-sidebar-accent-foreground",
@@ -719,7 +743,7 @@ function NavGroup({
719
743
  child.premium && /* @__PURE__ */ jsx(Crown, { className: "h-3 w-3 shrink-0 text-amber-500", "aria-hidden": true })
720
744
  ] });
721
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 });
722
- if (!childConnector) return /* @__PURE__ */ jsx(React7.Fragment, { children: row }, child.id);
746
+ if (!childConnector) return /* @__PURE__ */ jsx(React2.Fragment, { children: row }, child.id);
723
747
  const activeIndex = item.children.findIndex((c) => c.route === activeRoute);
724
748
  const onPath = activeIndex >= 0 && childIndex < activeIndex;
725
749
  return (
@@ -781,9 +805,9 @@ function NavGroup({
781
805
  ] });
782
806
  }
783
807
  function withCollapsed(slot, collapsed) {
784
- if (!React7.isValidElement(slot)) return slot;
808
+ if (!React2.isValidElement(slot)) return slot;
785
809
  if (typeof slot.type === "string") return slot;
786
- return React7.cloneElement(slot, { collapsed });
810
+ return React2.cloneElement(slot, { collapsed });
787
811
  }
788
812
  function Sidebar({
789
813
  navigation,
@@ -812,7 +836,7 @@ function Sidebar({
812
836
  const currentPath = currentPathProp;
813
837
  const setCommandPaletteOpen = useLayoutStore((s) => s.setCommandPaletteOpen);
814
838
  const activeRoute = resolveActiveRoute(navigation, currentPath);
815
- const railRef = React7.useRef(null);
839
+ const railRef = React2.useRef(null);
816
840
  const flyout = useRailFlyout({
817
841
  enabled: collapsed,
818
842
  railRef,
@@ -861,10 +885,10 @@ function Sidebar({
861
885
  mainItems.length > 0 && renderSection(mainItems, sectionLabels?.main),
862
886
  secondaryItems.length > 0 && /* @__PURE__ */ jsx("div", { className: "mt-6", children: renderSection(secondaryItems, sectionLabels?.secondary) })
863
887
  ] });
864
- const [outgoing, setOutgoing] = React7.useState(null);
865
- const lastBodyRef = React7.useRef(null);
866
- const lastKeyRef = React7.useRef(navKey);
867
- const timerRef = React7.useRef(null);
888
+ const [outgoing, setOutgoing] = React2.useState(null);
889
+ const lastBodyRef = React2.useRef(null);
890
+ const lastKeyRef = React2.useRef(navKey);
891
+ const timerRef = React2.useRef(null);
868
892
  useIsomorphicLayoutEffect2(() => {
869
893
  if (lastKeyRef.current !== navKey) {
870
894
  const previousBody = lastBodyRef.current;
@@ -877,7 +901,7 @@ function Sidebar({
877
901
  }
878
902
  lastBodyRef.current = navBody;
879
903
  });
880
- React7.useEffect(() => () => {
904
+ React2.useEffect(() => () => {
881
905
  if (timerRef.current) clearTimeout(timerRef.current);
882
906
  }, []);
883
907
  return (
@@ -1022,34 +1046,41 @@ function Sidebar({
1022
1046
  // it the padded/bordered wrapper shows as a blank block.
1023
1047
  /* @__PURE__ */ jsx("div", { className: cn("space-y-2 p-2 empty:hidden", !borderless && "border-t border-sidebar-border"), children: footerContent }),
1024
1048
  withCollapsed(orgSwitcher, collapsed),
1025
- /* @__PURE__ */ jsx("div", { className: cn("p-2", !borderless && "border-t border-sidebar-border"), children: /* @__PURE__ */ jsxs("div", { className: cn(
1026
- "flex items-center rounded-button",
1027
- collapsed ? "flex-col gap-1" : "gap-1 px-1"
1028
- ), children: [
1029
- userMenuSlot,
1030
- !collapsed && /* @__PURE__ */ jsx("div", { className: "flex-1" }),
1031
- notificationSlot ?? /* @__PURE__ */ jsxs(
1032
- "button",
1033
- {
1034
- 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",
1035
- "aria-label": unreadCount > 0 ? `${unreadCount} unread notifications` : "Notifications",
1036
- children: [
1037
- /* @__PURE__ */ jsx(Bell, { className: "h-4 w-4" }),
1038
- unreadCount > 0 && /* @__PURE__ */ jsx("span", { className: "absolute right-1 top-1 h-2 w-2 rounded-full bg-destructive" })
1039
- ]
1040
- }
1041
- ),
1042
- !drawer && /* @__PURE__ */ jsx(
1043
- "button",
1044
- {
1045
- onClick: onToggle,
1046
- className: "fz-tap 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",
1047
- "aria-label": collapsed ? "Expand sidebar" : "Collapse sidebar",
1048
- title: collapsed ? "Expand sidebar" : "Collapse sidebar",
1049
- children: collapsed ? /* @__PURE__ */ jsx(PanelLeft, { className: "h-4 w-4" }) : /* @__PURE__ */ jsx(PanelLeftClose, { className: "h-4 w-4" })
1050
- }
1051
- )
1052
- ] }) }),
1049
+ /* @__PURE__ */ jsx(
1050
+ "div",
1051
+ {
1052
+ className: cn("py-2 pr-2", !borderless && "border-t border-sidebar-border"),
1053
+ style: drawer ? { paddingBottom: "calc(0.5rem + env(safe-area-inset-bottom))" } : void 0,
1054
+ children: /* @__PURE__ */ jsxs("div", { className: cn(
1055
+ "flex items-center rounded-button",
1056
+ collapsed ? "flex-col gap-1" : "gap-1"
1057
+ ), children: [
1058
+ userMenuSlot,
1059
+ !collapsed && /* @__PURE__ */ jsx("div", { className: "flex-1" }),
1060
+ notificationSlot ?? /* @__PURE__ */ jsxs(
1061
+ "button",
1062
+ {
1063
+ 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",
1064
+ "aria-label": unreadCount > 0 ? `${unreadCount} unread notifications` : "Notifications",
1065
+ children: [
1066
+ /* @__PURE__ */ jsx(Bell, { className: "h-4 w-4" }),
1067
+ unreadCount > 0 && /* @__PURE__ */ jsx("span", { className: "absolute right-1 top-1 h-2 w-2 rounded-full bg-destructive" })
1068
+ ]
1069
+ }
1070
+ ),
1071
+ !drawer && /* @__PURE__ */ jsx(
1072
+ "button",
1073
+ {
1074
+ onClick: onToggle,
1075
+ className: "fz-tap 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",
1076
+ "aria-label": collapsed ? "Expand sidebar" : "Collapse sidebar",
1077
+ title: collapsed ? "Expand sidebar" : "Collapse sidebar",
1078
+ children: collapsed ? /* @__PURE__ */ jsx(PanelLeft, { className: "h-4 w-4" }) : /* @__PURE__ */ jsx(PanelLeftClose, { className: "h-4 w-4" })
1079
+ }
1080
+ )
1081
+ ] })
1082
+ }
1083
+ ),
1053
1084
  flyout.panel
1054
1085
  ]
1055
1086
  }
@@ -1066,7 +1097,7 @@ function TopbarUserMenu({
1066
1097
  onBilling,
1067
1098
  userMenuExtras
1068
1099
  }) {
1069
- const [open, setOpen] = React7.useState(false);
1100
+ const [open, setOpen] = React2.useState(false);
1070
1101
  if (!user) return null;
1071
1102
  const initial = (user.fullName?.trim()?.[0] ?? user.email?.[0] ?? "?").toUpperCase();
1072
1103
  const item = (label, onClick, Icon) => onClick ? /* @__PURE__ */ jsxs(
@@ -1150,7 +1181,7 @@ function getIcon2(name) {
1150
1181
  return ICON_MAP[name] ?? Home;
1151
1182
  }
1152
1183
  function NavDropdown({ item, currentPath, onNavigate }) {
1153
- const [open, setOpen] = React7.useState(false);
1184
+ const [open, setOpen] = React2.useState(false);
1154
1185
  const Icon = getIcon2(item.icon);
1155
1186
  const isChildActive = item.children?.some(
1156
1187
  (child) => currentPath === child.route || currentPath.startsWith(child.route + "/")
@@ -1208,6 +1239,7 @@ function Topbar({
1208
1239
  logo,
1209
1240
  user,
1210
1241
  onMenuClick,
1242
+ hideMobileChrome,
1211
1243
  orgSwitcher,
1212
1244
  leftContent,
1213
1245
  rightContent,
@@ -1231,7 +1263,7 @@ function Topbar({
1231
1263
  onNavigate?.(route);
1232
1264
  };
1233
1265
  return /* @__PURE__ */ jsxs("header", { "data-print": "hide", className: "sticky top-0 z-50 w-full shrink-0", children: [
1234
- /* @__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: [
1266
+ /* @__PURE__ */ jsx("div", { className: cn("border-b border-sidebar-border bg-sidebar text-sidebar-foreground", hideMobileChrome && "hidden md:block"), children: /* @__PURE__ */ jsxs("div", { className: "flex h-14 w-full items-center justify-between px-4 md:px-6", children: [
1235
1267
  /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 items-center gap-3", children: [
1236
1268
  onMenuClick && /* @__PURE__ */ jsx(
1237
1269
  "button",
@@ -1316,24 +1348,27 @@ function Topbar({
1316
1348
  }) }) }) })
1317
1349
  ] });
1318
1350
  }
1319
- var ModuleHeaderSlotContext = React7.createContext(null);
1351
+ var ModuleHeaderSlotContext = React2.createContext(null);
1320
1352
  function useModuleHeaderSlot() {
1321
- return React7.useContext(ModuleHeaderSlotContext);
1353
+ return React2.useContext(ModuleHeaderSlotContext);
1322
1354
  }
1323
- var ModuleHeaderActionsSlotContext = React7.createContext(null);
1355
+ var ModuleHeaderActionsSlotContext = React2.createContext(null);
1324
1356
  function useModuleHeaderActionsSlot() {
1325
- return React7.useContext(ModuleHeaderActionsSlotContext);
1357
+ return React2.useContext(ModuleHeaderActionsSlotContext);
1326
1358
  }
1327
- var PageRailSlotContext = React7.createContext(null);
1359
+ var PageRailSlotContext = React2.createContext(null);
1328
1360
  function usePageRailSlot() {
1329
- return React7.useContext(PageRailSlotContext);
1361
+ return React2.useContext(PageRailSlotContext);
1362
+ }
1363
+ function bottomNavReachesAll(items) {
1364
+ return !!items?.some((i) => "kind" in i && i.kind === "more");
1330
1365
  }
1331
1366
  function ShellPageTitle({
1332
1367
  title,
1333
1368
  back
1334
1369
  }) {
1335
1370
  if (!title && !back) return null;
1336
- return /* @__PURE__ */ jsx(PageHeaderCrumb, { title, parentLabel: back?.label, onBack: back?.onBack });
1371
+ return /* @__PURE__ */ jsx(PageHeaderCrumb, { title, parentLabel: back?.label, onBack: back?.onBack, headingLevel: "h1" });
1337
1372
  }
1338
1373
  function SidebarLayout({
1339
1374
  navigation = [],
@@ -1362,6 +1397,7 @@ function SidebarLayout({
1362
1397
  unreadCount = 0,
1363
1398
  searchLabel,
1364
1399
  hasBottomNav = false,
1400
+ barOwnsNav = false,
1365
1401
  mobileHeader = "minimal",
1366
1402
  sidebarSectionLabels,
1367
1403
  sidebarNavKey,
@@ -1370,16 +1406,19 @@ function SidebarLayout({
1370
1406
  railOpen
1371
1407
  }) {
1372
1408
  const sidebarCollapsed = useLayoutStore((s) => s.sidebarCollapsed);
1373
- const pageHeaderSlot = usePageHeaderSlot();
1409
+ const pageHeaderClaimed = usePageHeaderClaimed();
1410
+ const setTitleSlot = usePageTitleSlotHost();
1374
1411
  usePageHeaderHost();
1375
1412
  const setSidebarCollapsed = useLayoutStore((s) => s.setSidebarCollapsed);
1376
- const [mobileMenuOpen, setMobileMenuOpen] = React7.useState(false);
1377
- const [headerSlot, setHeaderSlot] = React7.useState(null);
1378
- const [actionsSlot, setActionsSlot] = React7.useState(null);
1379
- const [pageRailSlot, setPageRailSlot] = React7.useState(null);
1413
+ const mobileMenuOpen = useLayoutStore((st) => st.mobileDrawerOpen);
1414
+ const setMobileMenuOpen = useLayoutStore((st) => st.setMobileDrawerOpen);
1415
+ const [headerSlot, setHeaderSlot] = React2.useState(null);
1416
+ const [actionsSlot, setActionsSlot] = React2.useState(null);
1417
+ const [pageRailSlot, setPageRailSlot] = React2.useState(null);
1380
1418
  const hasPageActions = useHasPageActions();
1381
1419
  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(
1382
- "shell-enter flex h-screen overflow-hidden bg-background",
1420
+ "shell-enter fz-viewport-h flex overflow-hidden bg-background",
1421
+ hasBottomNav && "fz-has-bottom-bar",
1383
1422
  // Frame mode: the inset gap around the framed card takes the sidebar (menu)
1384
1423
  // color, so the white card reads as a panel floating on the same tone as the
1385
1424
  // left menu. The card's own md:m-2 margin reveals this behind it.
@@ -1437,7 +1476,7 @@ function SidebarLayout({
1437
1476
  mobileHeader !== "minimal" && !hasPageActions && "hidden md:flex"
1438
1477
  ), children: [
1439
1478
  /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-1 items-center gap-3", children: [
1440
- !hasBottomNav && /* @__PURE__ */ jsx(
1479
+ !barOwnsNav && /* @__PURE__ */ jsx(
1441
1480
  "button",
1442
1481
  {
1443
1482
  onClick: () => setMobileMenuOpen(true),
@@ -1446,7 +1485,8 @@ function SidebarLayout({
1446
1485
  children: /* @__PURE__ */ jsx(Menu, { className: "h-5 w-5" })
1447
1486
  }
1448
1487
  ),
1449
- pageHeaderSlot ?? /* @__PURE__ */ jsx(ShellPageTitle, { title: pageTitle, back: pageBack }),
1488
+ !pageHeaderClaimed && /* @__PURE__ */ jsx(ShellPageTitle, { title: pageTitle, back: pageBack }),
1489
+ /* @__PURE__ */ jsx("div", { ref: setTitleSlot, className: "flex min-w-0 items-center empty:hidden" }),
1450
1490
  topbarStart,
1451
1491
  /* @__PURE__ */ jsx("div", { ref: setHeaderSlot, className: "flex min-w-0 flex-1 items-center" })
1452
1492
  ] }),
@@ -1499,6 +1539,7 @@ function TopbarLayout({
1499
1539
  billingLabel,
1500
1540
  userMenuExtras,
1501
1541
  userMenuSlot,
1542
+ drawerUserMenuSlot,
1502
1543
  orgSwitcher,
1503
1544
  frame,
1504
1545
  currentPath,
@@ -1507,19 +1548,23 @@ function TopbarLayout({
1507
1548
  pageTitle,
1508
1549
  pageBack,
1509
1550
  notificationSlot,
1510
- hasBottomNav = false
1551
+ unreadCount = 0,
1552
+ hasBottomNav = false,
1553
+ barOwnsNav = false
1511
1554
  }) {
1512
- const [mobileMenuOpen, setMobileMenuOpen] = React7.useState(false);
1513
- const [headerSlot, setHeaderSlot] = React7.useState(null);
1514
- const [actionsSlot, setActionsSlot] = React7.useState(null);
1515
- const [pageRailSlot, setPageRailSlot] = React7.useState(null);
1516
- const rawSlot = usePageHeaderSlot();
1555
+ const mobileMenuOpen = useLayoutStore((st) => st.mobileDrawerOpen);
1556
+ const setMobileMenuOpen = useLayoutStore((st) => st.setMobileDrawerOpen);
1557
+ const [headerSlot, setHeaderSlot] = React2.useState(null);
1558
+ const [actionsSlot, setActionsSlot] = React2.useState(null);
1559
+ const [pageRailSlot, setPageRailSlot] = React2.useState(null);
1560
+ const rawClaimed = usePageHeaderClaimed();
1561
+ const setTitleSlot = usePageTitleSlotHost();
1517
1562
  const hasOutlet = useHasPageHeaderOutlet();
1518
1563
  const hasPageActions = useHasPageActions();
1519
1564
  usePageHeaderHost();
1520
- const pageHeaderSlot = hasOutlet ? null : rawSlot;
1565
+ const pageHeaderClaimed = hasOutlet ? false : rawClaimed;
1521
1566
  const showTitle = !hasOutlet && !!pageTitle;
1522
- 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: [
1567
+ 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("shell-enter fz-viewport-h relative flex flex-col overflow-hidden bg-background", hasBottomNav && "fz-has-bottom-bar"), children: [
1523
1568
  /* @__PURE__ */ jsx(
1524
1569
  Topbar,
1525
1570
  {
@@ -1542,19 +1587,30 @@ function TopbarLayout({
1542
1587
  topbarEnd,
1543
1588
  notificationSlot
1544
1589
  ] }) : void 0,
1545
- onMenuClick: hasBottomNav ? void 0 : () => setMobileMenuOpen(true)
1590
+ onMenuClick: barOwnsNav ? void 0 : () => setMobileMenuOpen(true),
1591
+ hideMobileChrome: barOwnsNav
1546
1592
  }
1547
1593
  ),
1548
1594
  /* @__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: [
1549
1595
  /* @__PURE__ */ jsx("div", { ref: setPageRailSlot, className: "flex shrink-0 empty:hidden" }),
1550
1596
  /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 min-h-0 flex-1 flex-col overflow-hidden", children: [
1551
- (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: [
1552
- /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-1 items-center gap-3", children: [
1553
- pageHeaderSlot ?? /* @__PURE__ */ jsx(ShellPageTitle, { title: showTitle ? pageTitle : void 0, back: pageBack }),
1554
- /* @__PURE__ */ jsx("div", { ref: setHeaderSlot, className: "flex min-w-0 flex-1 items-center" })
1555
- ] }),
1556
- /* @__PURE__ */ jsx("div", { ref: setActionsSlot, className: "flex shrink-0 items-center gap-2" })
1557
- ] }),
1597
+ (pageHeaderClaimed || pageBack || hasPageActions || barOwnsNav) && /* @__PURE__ */ jsxs(
1598
+ "div",
1599
+ {
1600
+ className: cn(
1601
+ "flex min-h-12 shrink-0 items-center justify-between gap-3 border-b border-border px-4 py-2",
1602
+ !(pageHeaderClaimed || pageBack || hasPageActions) && "md:hidden"
1603
+ ),
1604
+ children: [
1605
+ /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-1 items-center gap-3", children: [
1606
+ !pageHeaderClaimed && /* @__PURE__ */ jsx(ShellPageTitle, { title: showTitle ? pageTitle : void 0, back: pageBack }),
1607
+ /* @__PURE__ */ jsx("div", { ref: setTitleSlot, className: "flex min-w-0 items-center empty:hidden" }),
1608
+ /* @__PURE__ */ jsx("div", { ref: setHeaderSlot, className: "flex min-w-0 flex-1 items-center" })
1609
+ ] }),
1610
+ /* @__PURE__ */ jsx("div", { ref: setActionsSlot, className: "flex shrink-0 items-center gap-2" })
1611
+ ]
1612
+ }
1613
+ ),
1558
1614
  /* @__PURE__ */ jsx("div", { className: "min-h-0 flex-1 overflow-y-auto pb-16 md:pb-0", children })
1559
1615
  ] })
1560
1616
  ] }) }),
@@ -1572,7 +1628,9 @@ function TopbarLayout({
1572
1628
  onSettings,
1573
1629
  onBilling,
1574
1630
  orgSwitcher,
1575
- userMenuSlot: user ? /* @__PURE__ */ jsx(DrawerUserRow, { user }) : void 0,
1631
+ userMenuSlot: drawerUserMenuSlot ?? (user ? /* @__PURE__ */ jsx(DrawerUserRow, { user }) : void 0),
1632
+ notificationSlot,
1633
+ unreadCount,
1576
1634
  onClose: () => setMobileMenuOpen(false)
1577
1635
  }
1578
1636
  )
@@ -1589,9 +1647,10 @@ function MinimalLayout({
1589
1647
  logo,
1590
1648
  user: _user,
1591
1649
  children,
1592
- headerEnd
1650
+ headerEnd,
1651
+ hasBottomNav = false
1593
1652
  }) {
1594
- return /* @__PURE__ */ jsxs("div", { className: "shell-enter flex h-screen flex-col", children: [
1653
+ return /* @__PURE__ */ jsxs("div", { className: cn("shell-enter fz-viewport-h flex flex-col", hasBottomNav && "fz-has-bottom-bar"), children: [
1595
1654
  /* @__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: [
1596
1655
  /* @__PURE__ */ jsx("div", { className: "flex items-center gap-3", children: logo ?? /* @__PURE__ */ jsx("span", { className: "text-lg font-bold", children: "App" }) }),
1597
1656
  headerEnd && /* @__PURE__ */ jsx("div", { className: "flex items-center gap-2", children: headerEnd })
@@ -1600,23 +1659,23 @@ function MinimalLayout({
1600
1659
  ] });
1601
1660
  }
1602
1661
  function MobileOverlay({ onClose, onNavigate, ...sidebarProps }) {
1603
- const [visible, setVisible] = React7.useState(false);
1604
- React7.useEffect(() => {
1662
+ const [visible, setVisible] = React2.useState(false);
1663
+ React2.useEffect(() => {
1605
1664
  const id = requestAnimationFrame(() => setVisible(true));
1606
1665
  return () => cancelAnimationFrame(id);
1607
1666
  }, []);
1608
- const close = React7.useCallback(() => {
1667
+ const close = React2.useCallback(() => {
1609
1668
  setVisible(false);
1610
1669
  window.setTimeout(onClose, 250);
1611
1670
  }, [onClose]);
1612
- React7.useEffect(() => {
1671
+ React2.useEffect(() => {
1613
1672
  const previous = document.body.style.overflow;
1614
1673
  document.body.style.overflow = "hidden";
1615
1674
  return () => {
1616
1675
  document.body.style.overflow = previous;
1617
1676
  };
1618
1677
  }, []);
1619
- React7.useEffect(() => {
1678
+ React2.useEffect(() => {
1620
1679
  const onKey = (e) => {
1621
1680
  if (e.key === "Escape") close();
1622
1681
  };
@@ -1659,19 +1718,7 @@ function MobileOverlay({ onClose, onNavigate, ...sidebarProps }) {
1659
1718
  )
1660
1719
  ] });
1661
1720
  }
1662
- var BOTTOM_NAV_ICONS = {
1663
- Home: Home,
1664
- Receipt,
1665
- ArrowLeftRight,
1666
- Calendar,
1667
- CreditCard: CreditCard,
1668
- Settings: Settings,
1669
- Menu: Menu,
1670
- MessageCircle,
1671
- Wallet,
1672
- PiggyBank,
1673
- Plus
1674
- };
1721
+ var bottomNavIcon = (name, fallback = Home) => (name ? ICON_MAP[name] : void 0) ?? fallback;
1675
1722
  function FloatingProfileAvatar({ user, onClick }) {
1676
1723
  if (!user) return null;
1677
1724
  const label = user.fullName || user.email;
@@ -1697,43 +1744,80 @@ function MobileBottomNav({
1697
1744
  items,
1698
1745
  currentPath,
1699
1746
  onNavigate,
1700
- onAction
1747
+ onAction,
1748
+ onMore,
1749
+ onAssistant,
1750
+ assistantOpen,
1751
+ moreOpen,
1752
+ retracted
1701
1753
  }) {
1702
1754
  const path = currentPath ?? "";
1703
- return /* @__PURE__ */ jsx("nav", { className: "fixed inset-x-0 bottom-0 z-50 flex h-16 items-center justify-around border-t border-border bg-background md:hidden", children: items.map((item) => {
1704
- if (item.kind === "action") {
1705
- const ActionIcon = BOTTOM_NAV_ICONS[item.icon] ?? Plus;
1706
- return /* @__PURE__ */ jsx("div", { className: "flex flex-1 items-start justify-center", children: /* @__PURE__ */ jsx(
1707
- "button",
1708
- {
1709
- type: "button",
1710
- onClick: () => onAction?.(item.id),
1711
- "aria-label": item.label ?? "Add",
1712
- className: "-mt-6 flex h-14 w-14 items-center justify-center rounded-full bg-primary text-primary-foreground shadow-lg shadow-primary/30 ring-4 ring-background transition-transform active:scale-95",
1713
- children: /* @__PURE__ */ jsx(ActionIcon, { className: "h-6 w-6" })
1755
+ return /* @__PURE__ */ jsx(
1756
+ "nav",
1757
+ {
1758
+ "aria-label": "Navega\xE7\xE3o principal",
1759
+ "aria-hidden": retracted || void 0,
1760
+ className: cn(
1761
+ "fz-safe-b fixed inset-x-0 bottom-0 z-50 flex min-h-16 items-stretch justify-around border-t border-border bg-background md:hidden",
1762
+ "motion-safe:transition-transform motion-safe:duration-200 motion-safe:ease-out",
1763
+ retracted && "pointer-events-none translate-y-full"
1764
+ ),
1765
+ children: items.map((item, i) => {
1766
+ if ("kind" in item && item.kind === "action") {
1767
+ const ActionIcon = bottomNavIcon(item.icon, Plus);
1768
+ return /* @__PURE__ */ jsx("div", { className: "flex flex-1 items-start justify-center", children: /* @__PURE__ */ jsx(
1769
+ "button",
1770
+ {
1771
+ type: "button",
1772
+ onClick: () => onAction?.(item.id),
1773
+ "aria-label": item.label ?? "Add",
1774
+ className: "-mt-6 flex h-14 w-14 items-center justify-center rounded-full bg-primary text-primary-foreground shadow-lg shadow-primary/30 ring-4 ring-background transition-transform active:scale-95",
1775
+ children: /* @__PURE__ */ jsx(ActionIcon, { className: "h-6 w-6" })
1776
+ }
1777
+ ) }, item.id);
1714
1778
  }
1715
- ) }, item.id);
1779
+ const isMore = "kind" in item && item.kind === "more";
1780
+ const isAssistant = "kind" in item && item.kind === "assistant";
1781
+ const label = isMore ? item.label ?? "Mais" : isAssistant ? item.label ?? "IA" : item.label;
1782
+ const Icon = bottomNavIcon(
1783
+ item.icon,
1784
+ isMore ? Menu : isAssistant ? Sparkles : Home
1785
+ );
1786
+ const route = isMore || isAssistant ? void 0 : item.route;
1787
+ const isActive = isMore ? !!moreOpen : isAssistant ? !!assistantOpen : route === "/" ? path === "/" || path === "" : !!route && (path === route || path.startsWith(route + "/"));
1788
+ return /* @__PURE__ */ jsxs(
1789
+ "button",
1790
+ {
1791
+ type: "button",
1792
+ onClick: () => isMore ? onMore?.() : isAssistant ? onAssistant?.() : onNavigate?.(route),
1793
+ "aria-current": isActive && route ? "page" : void 0,
1794
+ "aria-expanded": isMore || isAssistant ? isActive : void 0,
1795
+ className: cn(
1796
+ // min-h-[44px] is the floor a finger needs; the bar was letting
1797
+ // content decide and landing around 40.
1798
+ "relative flex min-h-[44px] flex-1 flex-col items-center justify-center gap-0.5 px-1 py-2 transition-colors",
1799
+ isActive ? "text-primary" : "text-muted-foreground"
1800
+ ),
1801
+ children: [
1802
+ /* @__PURE__ */ jsx(
1803
+ "span",
1804
+ {
1805
+ "aria-hidden": true,
1806
+ className: cn(
1807
+ "absolute inset-x-3 top-0 h-0.5 rounded-full transition-opacity",
1808
+ isActive ? "bg-primary opacity-100" : "opacity-0"
1809
+ )
1810
+ }
1811
+ ),
1812
+ /* @__PURE__ */ jsx(Icon, { className: "h-5 w-5 shrink-0" }),
1813
+ /* @__PURE__ */ jsx("span", { className: "max-w-full truncate text-[11px] leading-tight", children: label })
1814
+ ]
1815
+ },
1816
+ isMore ? "more" : isAssistant ? "assistant" : `${route}-${i}`
1817
+ );
1818
+ })
1716
1819
  }
1717
- const isActive = item.route === "/" ? path === "/" || path === "" : path === item.route || path.startsWith(item.route + "/");
1718
- const Icon = BOTTOM_NAV_ICONS[item.icon] ?? Home;
1719
- return /* @__PURE__ */ jsxs(
1720
- "button",
1721
- {
1722
- type: "button",
1723
- onClick: () => onNavigate?.(item.route),
1724
- className: cn(
1725
- "flex flex-1 flex-col items-center justify-center gap-0.5 px-1 py-1 transition-colors",
1726
- isActive ? "text-primary" : "text-muted-foreground"
1727
- ),
1728
- "aria-current": isActive ? "page" : void 0,
1729
- children: [
1730
- /* @__PURE__ */ jsx(Icon, { className: "h-5 w-5" }),
1731
- /* @__PURE__ */ jsx("span", { className: "text-xs", children: item.label })
1732
- ]
1733
- },
1734
- item.route
1735
- );
1736
- }) });
1820
+ );
1737
1821
  }
1738
1822
  function AppShell({
1739
1823
  variant,
@@ -1762,8 +1846,11 @@ function AppShell({
1762
1846
  sidebarFooterContent,
1763
1847
  bottomNav,
1764
1848
  onBottomNavAction,
1849
+ onAssistantToggle,
1850
+ assistantOpen,
1765
1851
  mobileHeader = "minimal",
1766
1852
  userMenuSlot,
1853
+ drawerUserMenuSlot,
1767
1854
  notificationSlot,
1768
1855
  unreadCount = 0,
1769
1856
  searchLabel,
@@ -1780,13 +1867,26 @@ function AppShell({
1780
1867
  rightRailResizeLabel
1781
1868
  }) {
1782
1869
  const hasBottomNav = !!bottomNav?.length;
1870
+ const barOwnsNav = bottomNavReachesAll(bottomNav);
1871
+ const pageClaimedBack = useHasPageHeaderBack();
1872
+ const barRetracted = !!pageBack || pageClaimedBack;
1873
+ const mobileDrawerOpen = useLayoutStore((st) => st.mobileDrawerOpen);
1874
+ const setMobileDrawerOpen = useLayoutStore((st) => st.setMobileDrawerOpen);
1875
+ React2.useEffect(() => {
1876
+ setMobileDrawerOpen(false);
1877
+ }, [currentPath]);
1783
1878
  const bottomBar = hasBottomNav ? /* @__PURE__ */ jsx(
1784
1879
  MobileBottomNav,
1785
1880
  {
1786
1881
  items: bottomNav,
1787
1882
  currentPath,
1788
1883
  onNavigate,
1789
- onAction: onBottomNavAction
1884
+ onAction: onBottomNavAction,
1885
+ onMore: () => setMobileDrawerOpen(!mobileDrawerOpen),
1886
+ moreOpen: mobileDrawerOpen,
1887
+ onAssistant: onAssistantToggle,
1888
+ assistantOpen,
1889
+ retracted: barRetracted
1790
1890
  }
1791
1891
  ) : null;
1792
1892
  const floatingAvatar = mobileHeader === "transparent" && user ? /* @__PURE__ */ jsx(FloatingProfileAvatar, { user, onClick: onProfile }) : null;
@@ -1820,7 +1920,8 @@ function AppShell({
1820
1920
  notificationSlot,
1821
1921
  unreadCount,
1822
1922
  searchLabel,
1823
- hasBottomNav,
1923
+ hasBottomNav: hasBottomNav && !barRetracted,
1924
+ barOwnsNav,
1824
1925
  mobileHeader,
1825
1926
  sidebarSectionLabels,
1826
1927
  sidebarNavKey,
@@ -1847,6 +1948,7 @@ function AppShell({
1847
1948
  billingLabel,
1848
1949
  userMenuExtras,
1849
1950
  userMenuSlot,
1951
+ drawerUserMenuSlot,
1850
1952
  orgSwitcher,
1851
1953
  frame: contentFrame,
1852
1954
  currentPath,
@@ -1855,13 +1957,14 @@ function AppShell({
1855
1957
  pageTitle,
1856
1958
  pageBack,
1857
1959
  notificationSlot,
1858
- hasBottomNav,
1960
+ hasBottomNav: hasBottomNav && !barRetracted,
1961
+ barOwnsNav,
1859
1962
  children
1860
1963
  }
1861
1964
  );
1862
1965
  break;
1863
1966
  case "minimal":
1864
- layoutElement = /* @__PURE__ */ jsx(MinimalLayout, { logo, user, headerEnd: topbarEnd, children });
1967
+ layoutElement = /* @__PURE__ */ jsx(MinimalLayout, { logo, user, headerEnd: topbarEnd, hasBottomNav: hasBottomNav && !barRetracted, children });
1865
1968
  break;
1866
1969
  default: {
1867
1970
  return null;
@@ -1897,13 +2000,13 @@ function SplitWithRightRail({
1897
2000
  maxWidth = 720,
1898
2001
  resizeLabel = "Resize panel"
1899
2002
  }) {
1900
- const [width, setWidth] = React7.useState(defaultWidth);
1901
- const [dragging, setDragging] = React7.useState(false);
1902
- const clamp = React7.useCallback(
2003
+ const [width, setWidth] = React2.useState(defaultWidth);
2004
+ const [dragging, setDragging] = React2.useState(false);
2005
+ const clamp = React2.useCallback(
1903
2006
  (value) => Math.min(maxWidth, Math.max(minWidth, value)),
1904
2007
  [minWidth, maxWidth]
1905
2008
  );
1906
- React7.useEffect(() => {
2009
+ React2.useEffect(() => {
1907
2010
  if (!dragging) return;
1908
2011
  const onMove = (event) => {
1909
2012
  event.preventDefault();
@@ -1925,7 +2028,7 @@ function SplitWithRightRail({
1925
2028
  document.body.style.cursor = previousCursor;
1926
2029
  };
1927
2030
  }, [dragging, clamp]);
1928
- React7.useEffect(() => {
2031
+ React2.useEffect(() => {
1929
2032
  if (dragging) return;
1930
2033
  onWidthChange?.(width);
1931
2034
  }, [dragging]);
@@ -1935,7 +2038,7 @@ function SplitWithRightRail({
1935
2038
  // chrome, next to the menu — painting them page-white put a bright strip
1936
2039
  // between the dark chrome and the rail, which read as a seam rather than
1937
2040
  // as space.
1938
- /* @__PURE__ */ jsxs("div", { className: cn("flex h-screen w-full overflow-hidden bg-sidebar", !frame && "md:bg-background"), children: [
2041
+ /* @__PURE__ */ jsxs("div", { className: cn("fz-viewport-h flex w-full overflow-hidden bg-sidebar", !frame && "md:bg-background"), children: [
1939
2042
  /* @__PURE__ */ jsx("div", { className: "min-w-0 flex-1", children }),
1940
2043
  /* @__PURE__ */ jsxs(
1941
2044
  "div",
@@ -2075,12 +2178,12 @@ function PageTabs({ actions, className, ...props }) {
2075
2178
  }
2076
2179
  );
2077
2180
  }
2078
- var NavTransitionContext = React7.createContext("slide");
2181
+ var NavTransitionContext = React2.createContext("slide");
2079
2182
  function NavTransitionProvider({ value, children }) {
2080
2183
  return /* @__PURE__ */ jsx(NavTransitionContext.Provider, { value, children });
2081
2184
  }
2082
2185
  function useNavTransition() {
2083
- return React7.useContext(NavTransitionContext);
2186
+ return React2.useContext(NavTransitionContext);
2084
2187
  }
2085
2188
  function resolveTransitionClass(transition, direction) {
2086
2189
  if (transition === "none") return "";
@@ -2115,15 +2218,15 @@ function PremiumBadge({ className }) {
2115
2218
  const tip = t("upgrade.badgeTooltip");
2116
2219
  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 }) });
2117
2220
  }
2118
- var ModuleLayoutContext = React7.createContext("rail");
2221
+ var ModuleLayoutContext = React2.createContext("rail");
2119
2222
  function ModuleLayoutProvider({ variant, children }) {
2120
2223
  return /* @__PURE__ */ jsx(ModuleLayoutContext.Provider, { value: variant, children });
2121
2224
  }
2122
2225
  function useModuleLayout() {
2123
- return React7.useContext(ModuleLayoutContext);
2226
+ return React2.useContext(ModuleLayoutContext);
2124
2227
  }
2125
2228
  function RailNavItem({ item }) {
2126
- const [expanded, setExpanded] = React7.useState(true);
2229
+ const [expanded, setExpanded] = React2.useState(true);
2127
2230
  const Icon = item.icon ? ICON_MAP[item.icon] ?? null : null;
2128
2231
  const hasChildren = item.children && item.children.length > 0;
2129
2232
  const hasActiveChild = hasChildren && item.children.some((c) => c.active);
@@ -2164,10 +2267,14 @@ function RailNavItem({ item }) {
2164
2267
  )) })
2165
2268
  ] });
2166
2269
  }
2270
+ function defaultChildOf(item) {
2271
+ const kids = item.children ?? [];
2272
+ 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];
2273
+ }
2167
2274
  function MobileTabs({ nav }) {
2168
- const scrollRef = React7.useRef(null);
2169
- const [expandedId, setExpandedId] = React7.useState(null);
2170
- React7.useEffect(() => {
2275
+ const scrollRef = React2.useRef(null);
2276
+ const [expandedId, setExpandedId] = React2.useState(null);
2277
+ React2.useEffect(() => {
2171
2278
  const container = scrollRef.current;
2172
2279
  if (!container) return;
2173
2280
  const activeEl = container.querySelector('[data-active="true"]');
@@ -2189,6 +2296,7 @@ function MobileTabs({ nav }) {
2189
2296
  "data-active": isActive || void 0,
2190
2297
  onClick: () => {
2191
2298
  if (hasChildren) {
2299
+ if (!isExpanded) defaultChildOf(item)?.onClick?.();
2192
2300
  setExpandedId(isExpanded ? null : item.id);
2193
2301
  } else {
2194
2302
  item.onClick?.();
@@ -2196,11 +2304,15 @@ function MobileTabs({ nav }) {
2196
2304
  }
2197
2305
  },
2198
2306
  className: cn(
2199
- "flex items-center gap-1.5 whitespace-nowrap rounded-full px-3 py-1.5 text-xs font-medium transition-colors shrink-0",
2307
+ // This strip is the most-tapped control in most modules and it
2308
+ // was 28px tall — a third under the comfortable minimum. Height
2309
+ // and text step up on touch; the desktop rail is unaffected.
2310
+ "flex shrink-0 items-center gap-1.5 whitespace-nowrap rounded-full px-4 text-sm font-medium transition-colors",
2311
+ "min-h-[2.75rem] active:scale-[0.98]",
2200
2312
  isActive ? "bg-primary text-primary-foreground" : "bg-muted text-muted-foreground hover:text-foreground"
2201
2313
  ),
2202
2314
  children: [
2203
- Icon && /* @__PURE__ */ jsx(Icon, { className: "h-3 w-3" }),
2315
+ Icon && /* @__PURE__ */ jsx(Icon, { className: "h-3.5 w-3.5" }),
2204
2316
  item.label,
2205
2317
  item.premium && /* @__PURE__ */ jsx(PremiumBadge, {}),
2206
2318
  hasChildren && /* @__PURE__ */ jsx(ChevronDown, { className: cn("h-2.5 w-2.5 transition-transform", isExpanded && "rotate-180") })
@@ -2234,12 +2346,12 @@ function MobileTabs({ nav }) {
2234
2346
  })()
2235
2347
  ] });
2236
2348
  }
2237
- var BackStyleContext = React7.createContext("link");
2349
+ var BackStyleContext = React2.createContext("link");
2238
2350
  function BackStyleProvider({ style, children }) {
2239
2351
  return /* @__PURE__ */ jsx(BackStyleContext.Provider, { value: style, children });
2240
2352
  }
2241
2353
  function useBackStyle() {
2242
- return React7.useContext(BackStyleContext);
2354
+ return React2.useContext(BackStyleContext);
2243
2355
  }
2244
2356
  function SubpageHeader({ title, subtitle, icon, onBack, parentLabel, actions, backStyle, inline }) {
2245
2357
  const t = useTranslation();
@@ -2331,15 +2443,16 @@ function ShellSubpageHeader({
2331
2443
  parentLabel,
2332
2444
  actions
2333
2445
  }) {
2334
- usePageHeader(
2335
- /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 items-center gap-2", children: [
2336
- Icon && !onBack && /* @__PURE__ */ jsx(Icon, { className: "h-4 w-4 shrink-0 text-muted-foreground" }),
2337
- /* @__PURE__ */ jsx(PageHeaderCrumb, { title, parentLabel, onBack })
2338
- ] }),
2339
- { back: !!onBack }
2340
- );
2341
- if (!actions) return null;
2342
- return /* @__PURE__ */ jsx(PageHeaderActions, { children: /* @__PURE__ */ jsx("div", { className: "no-print flex shrink-0 items-center gap-2", children: actions }) });
2446
+ usePageHeader(true, { back: !!onBack });
2447
+ const crumb = /* @__PURE__ */ jsx(PageTitlePortal, { children: /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 items-center gap-2", children: [
2448
+ Icon && !onBack && /* @__PURE__ */ jsx(Icon, { className: "h-4 w-4 shrink-0 text-muted-foreground" }),
2449
+ /* @__PURE__ */ jsx(PageHeaderCrumb, { title, parentLabel, onBack })
2450
+ ] }) });
2451
+ if (!actions) return crumb;
2452
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
2453
+ crumb,
2454
+ /* @__PURE__ */ jsx(PageHeaderActions, { children: /* @__PURE__ */ jsx("div", { className: "no-print flex shrink-0 items-center gap-2", children: actions }) })
2455
+ ] });
2343
2456
  }
2344
2457
  function ModuleHeader({ nav }) {
2345
2458
  function defaultChild(item) {
@@ -2389,12 +2502,12 @@ function ModulePage({ title: _title, subtitle, nav, children, className, headerA
2389
2502
  ] }) }),
2390
2503
  /* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
2391
2504
  showHeader && headerAction && /* @__PURE__ */ jsx(PageHeaderActions, { children: headerAction }),
2392
- /* @__PURE__ */ jsx(MobileTabs, { nav }),
2505
+ !onSubpage && /* @__PURE__ */ jsx(MobileTabs, { nav }),
2393
2506
  /* @__PURE__ */ jsx(PageTransition, { transitionKey, direction, children })
2394
2507
  ] })
2395
2508
  ] });
2396
2509
  }
2397
2510
 
2398
- 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 };
2399
- //# sourceMappingURL=chunk-NA3GE4H3.js.map
2400
- //# sourceMappingURL=chunk-NA3GE4H3.js.map
2511
+ 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, bottomNavReachesAll, useBackHandler, useBackStyle, useCrossModuleBack, useModuleHeaderActionsSlot, useModuleHeaderSlot, useModuleLayout, useNavTransition, useRailRow, useSaveBar, useSaveBarVisible };
2512
+ //# sourceMappingURL=chunk-OBIU37AX.js.map
2513
+ //# sourceMappingURL=chunk-OBIU37AX.js.map