@fayz-ai/ui 0.10.2 → 0.11.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 (72) 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/batch.d.ts +19 -0
  4. package/dist/analytics/batch.d.ts.map +1 -0
  5. package/dist/analytics/context.d.ts.map +1 -1
  6. package/dist/analytics/index.d.ts +2 -0
  7. package/dist/analytics/index.d.ts.map +1 -1
  8. package/dist/{chunk-I6CPKAOD.js → chunk-7BMXN7MP.js} +215 -99
  9. package/dist/chunk-7BMXN7MP.js.map +1 -0
  10. package/dist/{chunk-6V5MGYXH.js → chunk-JGJNRNQW.js} +142 -31
  11. package/dist/chunk-JGJNRNQW.js.map +1 -0
  12. package/dist/{chunk-H63TOSQ7.js → chunk-QSHA2Q27.js} +475 -134
  13. package/dist/chunk-QSHA2Q27.js.map +1 -0
  14. package/dist/{chunk-SEIY2F5F.js → chunk-XRDCYOND.js} +231 -41
  15. package/dist/chunk-XRDCYOND.js.map +1 -0
  16. package/dist/chunk-ZBQ772DU.js +17 -0
  17. package/dist/chunk-ZBQ772DU.js.map +1 -0
  18. package/dist/dashboard/ChartWidget.d.ts.map +1 -1
  19. package/dist/dashboard/KpiCard.d.ts.map +1 -1
  20. package/dist/dashboard/SidebarOnboarding.d.ts +16 -11
  21. package/dist/dashboard/SidebarOnboarding.d.ts.map +1 -1
  22. package/dist/dashboard/Sparkline.d.ts +13 -0
  23. package/dist/dashboard/Sparkline.d.ts.map +1 -0
  24. package/dist/dashboard/index.d.ts +1 -0
  25. package/dist/dashboard/index.d.ts.map +1 -1
  26. package/dist/dashboard/index.js +3 -2
  27. package/dist/index.d.ts +2 -2
  28. package/dist/index.d.ts.map +1 -1
  29. package/dist/index.js +234 -115
  30. package/dist/index.js.map +1 -1
  31. package/dist/layout/AppShell.d.ts +4 -1
  32. package/dist/layout/AppShell.d.ts.map +1 -1
  33. package/dist/layout/AppShellSkeleton.d.ts +23 -0
  34. package/dist/layout/AppShellSkeleton.d.ts.map +1 -0
  35. package/dist/layout/ModulePage.d.ts +5 -1
  36. package/dist/layout/ModulePage.d.ts.map +1 -1
  37. package/dist/layout/Sidebar.d.ts +19 -1
  38. package/dist/layout/Sidebar.d.ts.map +1 -1
  39. package/dist/layout/Topbar.d.ts +4 -1
  40. package/dist/layout/Topbar.d.ts.map +1 -1
  41. package/dist/layout/index.d.ts +2 -0
  42. package/dist/layout/index.d.ts.map +1 -1
  43. package/dist/layout/index.js +2 -1
  44. package/dist/layout/page-trail.d.ts +23 -0
  45. package/dist/layout/page-trail.d.ts.map +1 -0
  46. package/dist/layout/rail-flyout.d.ts +39 -0
  47. package/dist/layout/rail-flyout.d.ts.map +1 -0
  48. package/dist/primitives/bulk-action-bar.d.ts +30 -0
  49. package/dist/primitives/bulk-action-bar.d.ts.map +1 -0
  50. package/dist/primitives/checkbox.d.ts +5 -0
  51. package/dist/primitives/checkbox.d.ts.map +1 -1
  52. package/dist/primitives/data-table.d.ts +11 -1
  53. package/dist/primitives/data-table.d.ts.map +1 -1
  54. package/dist/primitives/dropdown.d.ts.map +1 -1
  55. package/dist/primitives/index.d.ts +4 -0
  56. package/dist/primitives/index.d.ts.map +1 -1
  57. package/dist/primitives/index.js +2 -2
  58. package/dist/primitives/list-chrome.d.ts +20 -0
  59. package/dist/primitives/list-chrome.d.ts.map +1 -0
  60. package/dist/primitives/modal.d.ts.map +1 -1
  61. package/dist/primitives/phone-field.d.ts +19 -0
  62. package/dist/primitives/phone-field.d.ts.map +1 -0
  63. package/dist/primitives/search-select.d.ts +6 -1
  64. package/dist/primitives/search-select.d.ts.map +1 -1
  65. package/dist/primitives/thumbnail.d.ts +19 -0
  66. package/dist/primitives/thumbnail.d.ts.map +1 -0
  67. package/dist/styles.css +111 -1
  68. package/package.json +2 -2
  69. package/dist/chunk-6V5MGYXH.js.map +0 -1
  70. package/dist/chunk-H63TOSQ7.js.map +0 -1
  71. package/dist/chunk-I6CPKAOD.js.map +0 -1
  72. package/dist/chunk-SEIY2F5F.js.map +0 -1
@@ -1,38 +1,256 @@
1
- import { useHasPageHeaderHost, useCrossModuleBack, useBackHandler, usePageHeader, PageHeaderCrumb, PageHeaderOutlet, Sheet, SheetContent, SheetHeader, SheetTitle, SheetDescription, SheetBody, HeaderTabs, usePageHeaderSlot, useHasPageHeaderOutlet, usePageHeaderHost, SaveBarProvider, SaveBar } from './chunk-VAKWUUVC.js';
1
+ import { useHasPageHeaderHost, useCrossModuleBack, useBackHandler, PageHeaderCrumb, usePageHeader, PageHeaderOutlet, Sheet, SheetContent, SheetHeader, SheetTitle, SheetDescription, SheetBody, HeaderTabs, usePageHeaderSlot, useHasPageHeaderOutlet, usePageHeaderHost, SaveBarProvider, SaveBar } from './chunk-VAKWUUVC.js';
2
+ import { useLayoutStore } from './chunk-ZBQ772DU.js';
2
3
  import { cn, Button } from './chunk-OKZ6WQB3.js';
3
4
  import { ICON_MAP } from './chunk-22EVFMS3.js';
4
- import * as React3 from 'react';
5
+ import * as React from 'react';
5
6
  import { PanelLeftClose, PanelLeft, Search, Bell, Menu, Crown, Repeat, GraduationCap, Newspaper, Clapperboard, Zap, Star, UserCheck, Inbox, Clock3, Ban, Map, ListPlus, CalendarX, CalendarCheck2, Mic, UsersRound, Disc3, ListMusic, Eye, Music, Layers, Banknote, BadgeDollarSign, CalendarDays, Radio, PartyPopper, FileCheck2, Box, Boxes, TreePalm, UserX, UserPlus, ArrowDownRight, ArrowUpRight, Ruler, Warehouse, Wallet, Sparkles, AlertTriangle, Clock, CircleDollarSign, TrendingUp, Receipt, Landmark, TableProperties, SlidersHorizontal, Plug, ArrowLeftRight, ArrowUpCircle, ArrowDownCircle, Wheat, Egg, Apple, LeafyGreen, LayoutTemplate, Heart, PawPrint, Cat, Dog, FolderOpen, ListChecks, List, Shield, Plus, Filter, ChevronDown, Building2, Contact, Handshake, MapPin, UtensilsCrossed, Camera, Tags, Tag, Percent, BookOpenCheck, Globe, MessageCircle, BookOpen, UserCog, Briefcase, ClipboardCheck, ClipboardList, Wrench, Target, ShoppingBag, ShoppingCart, Megaphone, DollarSign, Mail, FileText, BarChart3, Activity, Package, CalendarClock, Calendar, CreditCard, Settings, Users, Home, ChevronRight, ChevronLeft, PanelRightOpen, PanelRightClose, PanelRight, ExternalLink, PiggyBank, LogOut } from 'lucide-react';
6
- import * as Tooltip from '@radix-ui/react-tooltip';
7
- import { create } from 'zustand';
8
7
  import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
9
8
  import * as Popover from '@radix-ui/react-popover';
10
9
  import { createPortal } from 'react-dom';
11
10
  import { useTranslation } from '@fayz-ai/core';
12
11
 
13
- var useLayoutStore = create((set) => ({
14
- sidebarOpen: true,
15
- sidebarCollapsed: false,
16
- mobileDrawerOpen: false,
17
- commandPaletteOpen: false,
18
- toggleSidebar: () => set((state) => ({ sidebarOpen: !state.sidebarOpen })),
19
- setSidebarCollapsed: (collapsed) => set({ sidebarCollapsed: collapsed }),
20
- toggleMobileDrawer: () => set((state) => ({ mobileDrawerOpen: !state.mobileDrawerOpen })),
21
- setCommandPaletteOpen: (open) => set({ commandPaletteOpen: open })
22
- }));
12
+ var GUTTER_PX = 8;
13
+ var CLOSE_DELAY_MS = 140;
14
+ var useIsomorphicLayoutEffect = typeof window !== "undefined" ? React.useLayoutEffect : React.useEffect;
15
+ function iconOf(name) {
16
+ if (!name) return null;
17
+ return ICON_MAP[name] ?? null;
18
+ }
19
+ function useRailFlyout({
20
+ enabled,
21
+ railRef,
22
+ activeRoute,
23
+ onNavigate
24
+ }) {
25
+ const [state, setState] = React.useState(null);
26
+ const [pos, setPos] = React.useState({ top: 0, arrow: 20 });
27
+ const [animate, setAnimate] = React.useState(false);
28
+ const panelRef = React.useRef(null);
29
+ const closeTimer = React.useRef(null);
30
+ const lastContent = React.useRef(null);
31
+ const cancelClose = React.useCallback(() => {
32
+ if (closeTimer.current) {
33
+ clearTimeout(closeTimer.current);
34
+ closeTimer.current = null;
35
+ }
36
+ }, []);
37
+ const show = React.useCallback(
38
+ (anchor, content2) => {
39
+ if (!enabled) return;
40
+ cancelClose();
41
+ setState({ anchor, content: content2 });
42
+ },
43
+ [enabled, cancelClose]
44
+ );
45
+ const hide = React.useCallback(() => {
46
+ cancelClose();
47
+ closeTimer.current = setTimeout(() => setState(null), CLOSE_DELAY_MS);
48
+ }, [cancelClose]);
49
+ const dismiss = React.useCallback(() => {
50
+ cancelClose();
51
+ setState(null);
52
+ }, [cancelClose]);
53
+ React.useEffect(() => () => cancelClose(), [cancelClose]);
54
+ React.useEffect(() => {
55
+ if (!enabled) dismiss();
56
+ }, [enabled, dismiss]);
57
+ const reposition = React.useCallback(() => {
58
+ const rail = railRef.current;
59
+ const anchor = state?.anchor;
60
+ if (!rail || !anchor) return;
61
+ const railRect = rail.getBoundingClientRect();
62
+ const rect = anchor.getBoundingClientRect();
63
+ const height = panelRef.current?.offsetHeight ?? 0;
64
+ const rowTop = rect.top - railRect.top;
65
+ const rowCenter = rowTop + rect.height / 2;
66
+ const max = Math.max(GUTTER_PX, railRect.height - height - GUTTER_PX);
67
+ const top = Math.min(Math.max(GUTTER_PX, rowTop - 6), max);
68
+ const arrow = Math.min(Math.max(rowCenter - top, 14), Math.max(14, height - 14));
69
+ setPos({ top, arrow });
70
+ }, [railRef, state]);
71
+ useIsomorphicLayoutEffect(reposition, [reposition, state?.content.key]);
72
+ React.useEffect(() => {
73
+ if (!state) return;
74
+ const rail = railRef.current;
75
+ const onScroll = () => reposition();
76
+ rail?.addEventListener("scroll", onScroll, true);
77
+ window.addEventListener("resize", onScroll);
78
+ return () => {
79
+ rail?.removeEventListener("scroll", onScroll, true);
80
+ window.removeEventListener("resize", onScroll);
81
+ };
82
+ }, [state, railRef, reposition]);
83
+ const visible = !!state;
84
+ React.useEffect(() => {
85
+ if (!visible) {
86
+ setAnimate(false);
87
+ return;
88
+ }
89
+ const id = requestAnimationFrame(() => setAnimate(true));
90
+ return () => cancelAnimationFrame(id);
91
+ }, [visible]);
92
+ React.useEffect(() => {
93
+ if (!visible) return;
94
+ const onKey = (event) => {
95
+ if (event.key === "Escape") dismiss();
96
+ };
97
+ window.addEventListener("keydown", onKey);
98
+ return () => window.removeEventListener("keydown", onKey);
99
+ }, [visible, dismiss]);
100
+ const rowProps = React.useCallback(
101
+ (content2) => ({
102
+ onMouseEnter: (event) => show(event.currentTarget, content2),
103
+ onFocus: (event) => show(event.currentTarget, content2),
104
+ onMouseLeave: hide
105
+ }),
106
+ [show, hide]
107
+ );
108
+ if (state) lastContent.current = state.content;
109
+ const content = state?.content ?? lastContent.current;
110
+ const go = (entry) => {
111
+ dismiss();
112
+ if (entry.href) {
113
+ window.open(entry.href, "_blank", "noopener,noreferrer");
114
+ return;
115
+ }
116
+ if (entry.route) onNavigate(entry.route);
117
+ };
118
+ const entries = content?.entries ?? [];
119
+ const hasEntries = entries.length > 0;
120
+ const panel = enabled ? /* @__PURE__ */ jsx(
121
+ "div",
122
+ {
123
+ style: { transform: `translateY(${pos.top}px)`, paddingLeft: GUTTER_PX },
124
+ onMouseEnter: cancelClose,
125
+ onMouseLeave: hide,
126
+ className: cn(
127
+ "absolute left-full top-0 z-50 hidden md:block",
128
+ animate && "motion-safe:transition-[transform,opacity] motion-safe:duration-200 motion-safe:ease-out",
129
+ visible ? "opacity-100" : "pointer-events-none opacity-0"
130
+ ),
131
+ "aria-hidden": !visible,
132
+ children: /* @__PURE__ */ jsxs(
133
+ "div",
134
+ {
135
+ ref: panelRef,
136
+ className: cn(
137
+ // Sidebar tone, sidebar border: the panel is the menu leaning out,
138
+ // not a popover from the page underneath it.
139
+ "relative rounded-lg border border-sidebar-border bg-sidebar p-1 text-sidebar-foreground shadow-xl shadow-black/25",
140
+ hasEntries ? "min-w-[12.5rem] max-w-[16rem]" : "max-w-[15rem]"
141
+ ),
142
+ children: [
143
+ /* @__PURE__ */ jsx(
144
+ "span",
145
+ {
146
+ "aria-hidden": true,
147
+ style: { top: pos.arrow },
148
+ className: "absolute -left-[5px] h-2.5 w-2.5 -translate-y-1/2 rotate-45 border-b border-l border-sidebar-border bg-sidebar"
149
+ }
150
+ ),
151
+ /* @__PURE__ */ jsx("div", { className: "saas-flyout-swap", children: content && /* @__PURE__ */ jsx(FlyoutBody, { content, entries, activeRoute, onGo: go }) }, content?.key)
152
+ ]
153
+ }
154
+ )
155
+ }
156
+ ) : null;
157
+ return { rowProps, panel, dismiss };
158
+ }
159
+ function FlyoutBody({
160
+ content,
161
+ entries,
162
+ activeRoute,
163
+ onGo
164
+ }) {
165
+ const hasEntries = entries.length > 0;
166
+ const headerActive = !!content.route && content.route === activeRoute;
167
+ const headerClickable = !!content.route || !!content.href;
168
+ const header = /* @__PURE__ */ jsxs(
169
+ "div",
170
+ {
171
+ className: cn(
172
+ "flex items-center gap-2 rounded-md px-2 py-1.5 text-[13px] font-semibold",
173
+ hasEntries ? "text-sidebar-foreground" : "whitespace-nowrap",
174
+ headerActive && "text-sidebar-accent-foreground",
175
+ headerClickable && "transition-colors hover:bg-sidebar-accent hover:text-sidebar-accent-foreground"
176
+ ),
177
+ children: [
178
+ /* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate", children: content.label }),
179
+ content.href && /* @__PURE__ */ jsx(ExternalLink, { className: "h-3 w-3 shrink-0 opacity-50", "aria-hidden": true }),
180
+ content.premium && /* @__PURE__ */ jsx(Crown, { className: "h-3 w-3 shrink-0 text-amber-500", "aria-hidden": true }),
181
+ content.badge !== void 0 && /* @__PURE__ */ jsx("span", { className: "rounded-full bg-primary px-1.5 py-0.5 text-[10px] font-semibold text-primary-foreground", children: content.badge }),
182
+ hasEntries && headerClickable && /* @__PURE__ */ jsx(ChevronRight, { className: "h-3 w-3 shrink-0 text-sidebar-muted", "aria-hidden": true })
183
+ ]
184
+ }
185
+ );
186
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
187
+ headerClickable ? /* @__PURE__ */ jsx("button", { type: "button", onClick: () => onGo(content), className: "w-full text-left", children: header }) : header,
188
+ hasEntries && /* @__PURE__ */ jsxs(Fragment, { children: [
189
+ /* @__PURE__ */ jsx("div", { className: "my-1 h-px bg-sidebar-border" }),
190
+ /* @__PURE__ */ jsx("div", { className: "flex flex-col", children: entries.map((entry) => {
191
+ const EntryIcon = iconOf(entry.icon);
192
+ const isActive = !!entry.route && entry.route === activeRoute;
193
+ return /* @__PURE__ */ jsxs(
194
+ "button",
195
+ {
196
+ type: "button",
197
+ onClick: () => onGo(entry),
198
+ className: cn(
199
+ "flex items-center gap-2 rounded-md px-2 py-1.5 text-[12.5px] transition-colors",
200
+ isActive ? "bg-sidebar-accent font-medium text-sidebar-accent-foreground" : "text-sidebar-foreground/75 hover:bg-sidebar-accent/60 hover:text-sidebar-accent-foreground"
201
+ ),
202
+ children: [
203
+ EntryIcon && /* @__PURE__ */ jsx(EntryIcon, { className: "h-3.5 w-3.5 shrink-0 opacity-70" }),
204
+ /* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate text-left", children: entry.label }),
205
+ entry.href && /* @__PURE__ */ jsx(ExternalLink, { className: "h-3 w-3 shrink-0 opacity-50", "aria-hidden": true }),
206
+ entry.premium && /* @__PURE__ */ jsx(Crown, { className: "h-3 w-3 shrink-0 text-amber-500", "aria-hidden": true }),
207
+ entry.badge !== void 0 && /* @__PURE__ */ jsx("span", { className: "rounded-full bg-primary px-1.5 py-0.5 text-[10px] font-semibold text-primary-foreground", children: entry.badge })
208
+ ]
209
+ },
210
+ entry.id
211
+ );
212
+ }) })
213
+ ] })
214
+ ] });
215
+ }
23
216
  var RAIL_PUSH_MS = 260;
24
- var useIsomorphicLayoutEffect = typeof window !== "undefined" ? React3.useLayoutEffect : React3.useEffect;
217
+ var useIsomorphicLayoutEffect2 = typeof window !== "undefined" ? React.useLayoutEffect : React.useEffect;
25
218
  function getIcon(name) {
26
219
  return ICON_MAP[name] ?? Home;
27
220
  }
221
+ function shortcutKey() {
222
+ if (typeof navigator === "undefined") return "\u2318";
223
+ return /Mac|iPhone|iPad/u.test(navigator.platform || navigator.userAgent) ? "\u2318" : "Ctrl ";
224
+ }
225
+ function flyoutContentOf(item) {
226
+ return {
227
+ key: item.id,
228
+ label: item.label,
229
+ route: item.route,
230
+ href: item.href,
231
+ premium: item.premium,
232
+ badge: item.badge,
233
+ entries: item.children?.map((child) => ({
234
+ id: child.id,
235
+ label: child.label,
236
+ icon: child.icon,
237
+ route: child.route,
238
+ href: child.href,
239
+ premium: child.premium,
240
+ badge: child.badge
241
+ }))
242
+ };
243
+ }
28
244
  function NavItem({
29
245
  item,
30
246
  collapsed,
31
247
  isActive,
32
- onNavigate
248
+ onNavigate,
249
+ flyout
33
250
  }) {
34
251
  const Icon = getIcon(item.icon);
35
252
  const isExternal = !!item.href;
253
+ const hoverProps = collapsed && flyout ? flyout.rowProps(flyoutContentOf(item)) : void 0;
36
254
  const className = cn(
37
255
  "flex w-full items-center gap-3 rounded-button px-2.5 py-1.5 text-[13px] font-medium transition-colors",
38
256
  "hover:bg-sidebar-accent/60 hover:text-sidebar-accent-foreground",
@@ -48,27 +266,18 @@ function NavItem({
48
266
  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 })
49
267
  ] })
50
268
  ] });
51
- const content = isExternal ? /* @__PURE__ */ jsx("a", { href: item.href, target: "_blank", rel: "noopener noreferrer", className, children: inner }) : /* @__PURE__ */ jsx("button", { onClick: () => onNavigate(item.route), className, children: inner });
52
- if (collapsed) {
53
- return /* @__PURE__ */ jsxs(Tooltip.Root, { delayDuration: 0, children: [
54
- /* @__PURE__ */ jsx(Tooltip.Trigger, { asChild: true, children: content }),
55
- /* @__PURE__ */ jsx(Tooltip.Portal, { children: /* @__PURE__ */ jsxs(
56
- Tooltip.Content,
57
- {
58
- side: "right",
59
- sideOffset: 8,
60
- className: "fayz-glass-surface z-50 rounded-md bg-popover px-3 py-1.5 text-sm text-popover-foreground shadow-md",
61
- children: [
62
- item.label,
63
- item.premium && /* @__PURE__ */ jsx(Crown, { className: "ml-1.5 inline h-3 w-3 text-amber-500", "aria-hidden": true }),
64
- item.badge !== void 0 && /* @__PURE__ */ jsx("span", { className: "ml-2 rounded-full bg-primary px-1.5 py-0.5 text-xs text-primary-foreground", children: item.badge }),
65
- /* @__PURE__ */ jsx(Tooltip.Arrow, { className: "fill-popover" })
66
- ]
67
- }
68
- ) })
69
- ] });
70
- }
71
- return content;
269
+ return isExternal ? /* @__PURE__ */ jsx("a", { href: item.href, target: "_blank", rel: "noopener noreferrer", className, ...hoverProps, children: inner }) : /* @__PURE__ */ jsx(
270
+ "button",
271
+ {
272
+ onClick: () => {
273
+ flyout?.dismiss();
274
+ onNavigate(item.route);
275
+ },
276
+ className,
277
+ ...hoverProps,
278
+ children: inner
279
+ }
280
+ );
72
281
  }
73
282
  function routeMatchScore(route, path) {
74
283
  if (path === route) return route.length + 1;
@@ -97,30 +306,30 @@ function NavGroup({
97
306
  collapsed,
98
307
  activeRoute,
99
308
  onNavigate,
100
- childConnector
309
+ childConnector,
310
+ flyout
101
311
  }) {
102
312
  const isChildActive = item.children?.some((c) => c.route === activeRoute);
103
- const [open, setOpen] = React3.useState(() => isChildActive ?? false);
313
+ const [open, setOpen] = React.useState(() => isChildActive ?? false);
104
314
  const Icon = getIcon(item.icon);
105
315
  if (collapsed) {
106
- return /* @__PURE__ */ jsxs(Tooltip.Root, { delayDuration: 0, children: [
107
- /* @__PURE__ */ jsx(Tooltip.Trigger, { asChild: true, children: /* @__PURE__ */ jsx(
108
- "button",
109
- {
110
- onClick: () => onNavigate(item.route),
111
- className: cn(
112
- "flex w-full items-center justify-center rounded-button px-2 py-2 text-sm font-medium transition-colors",
113
- "hover:bg-sidebar-accent hover:text-sidebar-accent-foreground",
114
- isChildActive ? "bg-sidebar-accent text-sidebar-accent-foreground" : "text-sidebar-muted"
115
- ),
116
- children: /* @__PURE__ */ jsx(Icon, { className: "h-4 w-4 shrink-0" })
117
- }
118
- ) }),
119
- /* @__PURE__ */ jsx(Tooltip.Portal, { children: /* @__PURE__ */ jsxs(Tooltip.Content, { side: "right", sideOffset: 8, className: "fayz-glass-surface z-50 rounded-md bg-popover px-3 py-1.5 text-sm text-popover-foreground shadow-md", children: [
120
- item.label,
121
- /* @__PURE__ */ jsx(Tooltip.Arrow, { className: "fill-popover" })
122
- ] }) })
123
- ] });
316
+ const hoverProps = flyout?.rowProps(flyoutContentOf(item));
317
+ return /* @__PURE__ */ jsx(
318
+ "button",
319
+ {
320
+ onClick: () => {
321
+ flyout?.dismiss();
322
+ onNavigate(item.route);
323
+ },
324
+ className: cn(
325
+ "flex w-full items-center justify-center rounded-button px-2 py-2 text-sm font-medium transition-colors",
326
+ "hover:bg-sidebar-accent hover:text-sidebar-accent-foreground",
327
+ isChildActive ? "bg-sidebar-accent text-sidebar-accent-foreground" : "text-sidebar-muted"
328
+ ),
329
+ ...hoverProps,
330
+ children: /* @__PURE__ */ jsx(Icon, { className: "h-4 w-4 shrink-0" })
331
+ }
332
+ );
124
333
  }
125
334
  return /* @__PURE__ */ jsxs("div", { children: [
126
335
  /* @__PURE__ */ jsxs(
@@ -173,7 +382,7 @@ function NavGroup({
173
382
  child.premium && /* @__PURE__ */ jsx(Crown, { className: "h-3 w-3 shrink-0 text-amber-500", "aria-hidden": true })
174
383
  ] });
175
384
  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 });
176
- if (!childConnector) return /* @__PURE__ */ jsx(React3.Fragment, { children: row }, child.id);
385
+ if (!childConnector) return /* @__PURE__ */ jsx(React.Fragment, { children: row }, child.id);
177
386
  const activeIndex = item.children.findIndex((c) => c.route === activeRoute);
178
387
  const onPath = activeIndex >= 0 && childIndex < activeIndex;
179
388
  return (
@@ -234,6 +443,11 @@ function NavGroup({
234
443
  }) })
235
444
  ] });
236
445
  }
446
+ function withCollapsed(slot, collapsed) {
447
+ if (!React.isValidElement(slot)) return slot;
448
+ if (typeof slot.type === "string") return slot;
449
+ return React.cloneElement(slot, { collapsed });
450
+ }
237
451
  function Sidebar({
238
452
  navigation,
239
453
  logo,
@@ -244,11 +458,13 @@ function Sidebar({
244
458
  user: _user,
245
459
  orgSwitcher,
246
460
  topContent,
461
+ navTop,
247
462
  footerContent,
248
463
  borderless,
249
464
  userMenuSlot,
250
465
  notificationSlot,
251
466
  unreadCount = 0,
467
+ searchLabel = "Search",
252
468
  sectionLabels,
253
469
  navKey,
254
470
  navDirection = "forward",
@@ -257,6 +473,13 @@ function Sidebar({
257
473
  const currentPath = currentPathProp;
258
474
  const setCommandPaletteOpen = useLayoutStore((s) => s.setCommandPaletteOpen);
259
475
  const activeRoute = resolveActiveRoute(navigation, currentPath);
476
+ const railRef = React.useRef(null);
477
+ const flyout = useRailFlyout({
478
+ enabled: collapsed,
479
+ railRef,
480
+ activeRoute,
481
+ onNavigate: (route) => onNavigate?.(route)
482
+ });
260
483
  const mainItems = navigation.filter((item) => item.section === "main");
261
484
  const secondaryItems = navigation.filter((item) => item.section === "secondary");
262
485
  const settingsItems = navigation.filter((item) => item.section === "settings");
@@ -264,32 +487,43 @@ function Sidebar({
264
487
  onNavigate?.(route);
265
488
  };
266
489
  const sectionHeading = (label) => label && !collapsed ? /* @__PURE__ */ jsx("p", { className: "px-2.5 pb-1 pt-1 text-[10px] font-semibold uppercase tracking-wider text-sidebar-muted", children: label }) : null;
267
- const renderItem = (item) => item.children && item.children.length > 0 ? /* @__PURE__ */ jsx(NavGroup, { item, collapsed, activeRoute, onNavigate: handleNavigate, childConnector: navChildConnector }, item.id) : /* @__PURE__ */ jsx(
490
+ const groupRuns = (items) => {
491
+ const runs = [];
492
+ for (const item of items) {
493
+ const last = runs[runs.length - 1];
494
+ if (last && last.group === item.group) last.items.push(item);
495
+ else runs.push({ group: item.group, items: [item] });
496
+ }
497
+ return runs;
498
+ };
499
+ const renderSection = (items, label) => {
500
+ const runs = groupRuns(items);
501
+ return runs.map((run, i) => /* @__PURE__ */ jsxs("div", { className: i === 0 ? "space-y-1" : "mt-6 space-y-1", children: [
502
+ sectionHeading(run.group ?? label),
503
+ run.items.map(renderItem)
504
+ ] }, run.group ?? `ungrouped-${i}`));
505
+ };
506
+ const renderItem = (item) => item.children && item.children.length > 0 ? /* @__PURE__ */ jsx(NavGroup, { item, collapsed, activeRoute, onNavigate: handleNavigate, childConnector: navChildConnector, flyout }, item.id) : /* @__PURE__ */ jsx(
268
507
  NavItem,
269
508
  {
270
509
  item,
271
510
  collapsed,
272
511
  isActive: item.route === activeRoute,
273
- onNavigate: handleNavigate
512
+ onNavigate: handleNavigate,
513
+ flyout
274
514
  },
275
515
  item.id
276
516
  );
277
517
  const navBody = /* @__PURE__ */ jsxs(Fragment, { children: [
278
- topContent && /* @__PURE__ */ jsx("div", { className: "mb-4 space-y-2", children: React3.isValidElement(topContent) ? React3.cloneElement(topContent, { collapsed }) : topContent }),
279
- mainItems.length > 0 && /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
280
- sectionHeading(sectionLabels?.main),
281
- mainItems.map(renderItem)
282
- ] }),
283
- secondaryItems.length > 0 && /* @__PURE__ */ jsxs("div", { className: "mt-6 space-y-1", children: [
284
- sectionHeading(sectionLabels?.secondary),
285
- secondaryItems.map(renderItem)
286
- ] })
518
+ navTop && /* @__PURE__ */ jsx("div", { className: "mb-2", children: withCollapsed(navTop, collapsed) }),
519
+ mainItems.length > 0 && renderSection(mainItems, sectionLabels?.main),
520
+ secondaryItems.length > 0 && /* @__PURE__ */ jsx("div", { className: "mt-6", children: renderSection(secondaryItems, sectionLabels?.secondary) })
287
521
  ] });
288
- const [outgoing, setOutgoing] = React3.useState(null);
289
- const lastBodyRef = React3.useRef(null);
290
- const lastKeyRef = React3.useRef(navKey);
291
- const timerRef = React3.useRef(null);
292
- useIsomorphicLayoutEffect(() => {
522
+ const [outgoing, setOutgoing] = React.useState(null);
523
+ const lastBodyRef = React.useRef(null);
524
+ const lastKeyRef = React.useRef(navKey);
525
+ const timerRef = React.useRef(null);
526
+ useIsomorphicLayoutEffect2(() => {
293
527
  if (lastKeyRef.current !== navKey) {
294
528
  const previousBody = lastBodyRef.current;
295
529
  lastKeyRef.current = navKey;
@@ -301,15 +535,21 @@ function Sidebar({
301
535
  }
302
536
  lastBodyRef.current = navBody;
303
537
  });
304
- React3.useEffect(() => () => {
538
+ React.useEffect(() => () => {
305
539
  if (timerRef.current) clearTimeout(timerRef.current);
306
540
  }, []);
307
- return /* @__PURE__ */ jsx(Tooltip.Provider, { children: /* @__PURE__ */ jsxs(
541
+ return /* @__PURE__ */ jsxs(
308
542
  "aside",
309
543
  {
544
+ ref: railRef,
310
545
  "data-print": "hide",
546
+ onBlur: (event) => {
547
+ if (!event.currentTarget.contains(event.relatedTarget)) flyout.dismiss();
548
+ },
311
549
  className: cn(
312
- "flex h-full flex-col bg-sidebar text-sidebar-foreground transition-all duration-200",
550
+ // `relative` anchors the flyout, which hangs off the rail's right edge
551
+ // and has to escape the nav's own scroll box to do it.
552
+ "relative flex h-full flex-col bg-sidebar text-sidebar-foreground transition-all duration-200",
313
553
  !borderless && "border-r border-sidebar-border",
314
554
  collapsed ? "w-16" : "w-64"
315
555
  ),
@@ -347,6 +587,36 @@ function Sidebar({
347
587
  ]
348
588
  }
349
589
  ),
590
+ topContent && /* @__PURE__ */ jsx("div", { className: "px-2 pt-2", children: withCollapsed(topContent, collapsed) }),
591
+ /* @__PURE__ */ jsx("div", { className: "px-2 pb-2 pt-2", children: /* @__PURE__ */ jsxs(
592
+ "button",
593
+ {
594
+ onClick: () => setCommandPaletteOpen(true),
595
+ "aria-keyshortcuts": "Meta+K Control+K",
596
+ className: cn(
597
+ // Tonal surface, no outline. On a dark rail a 1px border is a hard
598
+ // edge competing with every other hard edge; a lighter fill of the
599
+ // same hue reads as "a field" without drawing a box around it — and
600
+ // leaves the outline treatment free to mean focus, which it must.
601
+ "fz-tap group flex items-center rounded-lg bg-sidebar-accent/40 text-sidebar-muted transition-colors",
602
+ "hover:bg-sidebar-accent/70 hover:text-sidebar-foreground",
603
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-sidebar-accent-foreground/50",
604
+ collapsed ? "h-9 w-9 justify-center" : "h-9 w-full gap-2.5 px-2.5"
605
+ ),
606
+ "aria-label": searchLabel,
607
+ title: collapsed ? searchLabel : void 0,
608
+ children: [
609
+ /* @__PURE__ */ jsx(Search, { className: "h-4 w-4 shrink-0" }),
610
+ !collapsed && /* @__PURE__ */ jsxs(Fragment, { children: [
611
+ /* @__PURE__ */ jsx("span", { className: "flex-1 truncate text-left text-[13px]", children: searchLabel }),
612
+ /* @__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: [
613
+ shortcutKey(),
614
+ "K"
615
+ ] })
616
+ ] })
617
+ ]
618
+ }
619
+ ) }),
350
620
  /* @__PURE__ */ jsxs(
351
621
  "nav",
352
622
  {
@@ -388,7 +658,8 @@ function Sidebar({
388
658
  item,
389
659
  collapsed,
390
660
  isActive: item.route === activeRoute,
391
- onNavigate: handleNavigate
661
+ onNavigate: handleNavigate,
662
+ flyout
392
663
  },
393
664
  item.id
394
665
  )) }),
@@ -396,26 +667,17 @@ function Sidebar({
396
667
  // SeuSiteWidget self-hides when the tenant has no project); without
397
668
  // it the padded/bordered wrapper shows as a blank block.
398
669
  /* @__PURE__ */ jsx("div", { className: cn("space-y-2 p-2 empty:hidden", !borderless && "border-t border-sidebar-border"), children: footerContent }),
399
- orgSwitcher && React3.isValidElement(orgSwitcher) ? React3.cloneElement(orgSwitcher, { collapsed }) : orgSwitcher,
670
+ withCollapsed(orgSwitcher, collapsed),
400
671
  /* @__PURE__ */ jsx("div", { className: cn("p-2", !borderless && "border-t border-sidebar-border"), children: /* @__PURE__ */ jsxs("div", { className: cn(
401
672
  "flex items-center rounded-button",
402
673
  collapsed ? "flex-col gap-1" : "gap-1 px-1"
403
674
  ), children: [
404
675
  userMenuSlot,
405
676
  !collapsed && /* @__PURE__ */ jsx("div", { className: "flex-1" }),
406
- /* @__PURE__ */ jsx(
407
- "button",
408
- {
409
- onClick: () => setCommandPaletteOpen(true),
410
- className: "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",
411
- "aria-label": "Search",
412
- children: /* @__PURE__ */ jsx(Search, { className: "h-4 w-4" })
413
- }
414
- ),
415
677
  notificationSlot ?? /* @__PURE__ */ jsxs(
416
678
  "button",
417
679
  {
418
- className: "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",
680
+ 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",
419
681
  "aria-label": unreadCount > 0 ? `${unreadCount} unread notifications` : "Notifications",
420
682
  children: [
421
683
  /* @__PURE__ */ jsx(Bell, { className: "h-4 w-4" }),
@@ -423,10 +685,11 @@ function Sidebar({
423
685
  ]
424
686
  }
425
687
  )
426
- ] }) })
688
+ ] }) }),
689
+ flyout.panel
427
690
  ]
428
691
  }
429
- ) });
692
+ );
430
693
  }
431
694
  function TopbarUserMenu({
432
695
  user,
@@ -438,7 +701,7 @@ function TopbarUserMenu({
438
701
  onBilling,
439
702
  userMenuExtras
440
703
  }) {
441
- const [open, setOpen] = React3.useState(false);
704
+ const [open, setOpen] = React.useState(false);
442
705
  if (!user) return null;
443
706
  const initial = (user.fullName?.trim()?.[0] ?? user.email?.[0] ?? "?").toUpperCase();
444
707
  const item = (label, onClick, Icon) => onClick ? /* @__PURE__ */ jsxs(
@@ -626,7 +889,7 @@ function getIcon2(name) {
626
889
  return ICON_MAP2[name] ?? Home;
627
890
  }
628
891
  function NavDropdown({ item, currentPath, onNavigate }) {
629
- const [open, setOpen] = React3.useState(false);
892
+ const [open, setOpen] = React.useState(false);
630
893
  const Icon = getIcon2(item.icon);
631
894
  const isChildActive = item.children?.some(
632
895
  (child) => currentPath === child.route || currentPath.startsWith(child.route + "/")
@@ -717,8 +980,7 @@ function Topbar({
717
980
  children: /* @__PURE__ */ jsx(Menu, { className: "h-5 w-5" })
718
981
  }
719
982
  ),
720
- /* @__PURE__ */ jsx("div", { className: "hidden shrink-0 items-center md:flex", children: logo ?? /* @__PURE__ */ jsx("span", { className: "text-lg font-bold", children: "App" }) }),
721
- orgSwitcher && /* @__PURE__ */ jsx("div", { className: "hidden min-w-0 items-center md:flex", children: orgSwitcher })
983
+ /* @__PURE__ */ jsx("div", { className: "hidden shrink-0 items-center md:flex", children: logo ?? /* @__PURE__ */ jsx("span", { className: "text-lg font-bold", children: "App" }) })
722
984
  ] }),
723
985
  /* @__PURE__ */ jsx("div", { className: "mx-8 hidden max-w-sm flex-1 md:flex", children: /* @__PURE__ */ jsxs(
724
986
  "button",
@@ -732,7 +994,8 @@ function Topbar({
732
994
  ]
733
995
  }
734
996
  ) }),
735
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
997
+ /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 items-center gap-1", children: [
998
+ orgSwitcher && /* @__PURE__ */ jsx("div", { className: "hidden min-w-0 items-center md:flex", children: orgSwitcher }),
736
999
  leftContent,
737
1000
  rightContent,
738
1001
  /* @__PURE__ */ jsx(
@@ -792,13 +1055,13 @@ function Topbar({
792
1055
  }) }) }) })
793
1056
  ] });
794
1057
  }
795
- var ModuleHeaderSlotContext = React3.createContext(null);
1058
+ var ModuleHeaderSlotContext = React.createContext(null);
796
1059
  function useModuleHeaderSlot() {
797
- return React3.useContext(ModuleHeaderSlotContext);
1060
+ return React.useContext(ModuleHeaderSlotContext);
798
1061
  }
799
- var ModuleHeaderActionsSlotContext = React3.createContext(null);
1062
+ var ModuleHeaderActionsSlotContext = React.createContext(null);
800
1063
  function useModuleHeaderActionsSlot() {
801
- return React3.useContext(ModuleHeaderActionsSlotContext);
1064
+ return React.useContext(ModuleHeaderActionsSlotContext);
802
1065
  }
803
1066
  function ShellPageTitle({
804
1067
  title,
@@ -820,6 +1083,7 @@ function SidebarLayout({
820
1083
  userMenuExtras: _userMenuExtras,
821
1084
  orgSwitcher,
822
1085
  sidebarTopContent,
1086
+ sidebarNavTop,
823
1087
  sidebarFooterContent,
824
1088
  frame,
825
1089
  currentPath,
@@ -830,6 +1094,7 @@ function SidebarLayout({
830
1094
  userMenuSlot,
831
1095
  notificationSlot,
832
1096
  unreadCount = 0,
1097
+ searchLabel,
833
1098
  hasBottomNav = false,
834
1099
  mobileHeader = "minimal",
835
1100
  sidebarSectionLabels,
@@ -841,9 +1106,9 @@ function SidebarLayout({
841
1106
  const pageHeaderSlot = usePageHeaderSlot();
842
1107
  usePageHeaderHost();
843
1108
  const setSidebarCollapsed = useLayoutStore((s) => s.setSidebarCollapsed);
844
- const [mobileMenuOpen, setMobileMenuOpen] = React3.useState(false);
845
- const [headerSlot, setHeaderSlot] = React3.useState(null);
846
- const [actionsSlot, setActionsSlot] = React3.useState(null);
1109
+ const [mobileMenuOpen, setMobileMenuOpen] = React.useState(false);
1110
+ const [headerSlot, setHeaderSlot] = React.useState(null);
1111
+ const [actionsSlot, setActionsSlot] = React.useState(null);
847
1112
  return /* @__PURE__ */ jsx(SaveBarProvider, { children: /* @__PURE__ */ jsx(ModuleHeaderSlotContext.Provider, { value: headerSlot, children: /* @__PURE__ */ jsx(ModuleHeaderActionsSlotContext.Provider, { value: actionsSlot, children: /* @__PURE__ */ jsxs("div", { className: cn(
848
1113
  "flex h-screen overflow-hidden bg-background",
849
1114
  // Frame mode: the inset gap around the framed card takes the sidebar (menu)
@@ -867,11 +1132,13 @@ function SidebarLayout({
867
1132
  onBilling,
868
1133
  orgSwitcher,
869
1134
  topContent: sidebarTopContent,
1135
+ navTop: sidebarNavTop,
870
1136
  footerContent: sidebarFooterContent,
871
1137
  borderless: false,
872
1138
  userMenuSlot,
873
1139
  notificationSlot,
874
1140
  unreadCount,
1141
+ searchLabel,
875
1142
  sectionLabels: sidebarSectionLabels,
876
1143
  navKey: sidebarNavKey,
877
1144
  navDirection: sidebarNavDirection,
@@ -922,10 +1189,12 @@ function SidebarLayout({
922
1189
  onBilling,
923
1190
  orgSwitcher,
924
1191
  topContent: sidebarTopContent,
1192
+ navTop: sidebarNavTop,
925
1193
  footerContent: sidebarFooterContent,
926
1194
  userMenuSlot,
927
1195
  notificationSlot,
928
1196
  unreadCount,
1197
+ searchLabel,
929
1198
  onClose: () => setMobileMenuOpen(false)
930
1199
  }
931
1200
  )
@@ -954,7 +1223,7 @@ function TopbarLayout({
954
1223
  notificationSlot,
955
1224
  hasBottomNav = false
956
1225
  }) {
957
- const [mobileMenuOpen, setMobileMenuOpen] = React3.useState(false);
1226
+ const [mobileMenuOpen, setMobileMenuOpen] = React.useState(false);
958
1227
  const rawSlot = usePageHeaderSlot();
959
1228
  const hasOutlet = useHasPageHeaderOutlet();
960
1229
  usePageHeaderHost();
@@ -1031,23 +1300,23 @@ function MinimalLayout({
1031
1300
  ] });
1032
1301
  }
1033
1302
  function MobileOverlay({ onClose, onNavigate, ...sidebarProps }) {
1034
- const [visible, setVisible] = React3.useState(false);
1035
- React3.useEffect(() => {
1303
+ const [visible, setVisible] = React.useState(false);
1304
+ React.useEffect(() => {
1036
1305
  const id = requestAnimationFrame(() => setVisible(true));
1037
1306
  return () => cancelAnimationFrame(id);
1038
1307
  }, []);
1039
- const close = React3.useCallback(() => {
1308
+ const close = React.useCallback(() => {
1040
1309
  setVisible(false);
1041
1310
  window.setTimeout(onClose, 250);
1042
1311
  }, [onClose]);
1043
- React3.useEffect(() => {
1312
+ React.useEffect(() => {
1044
1313
  const previous = document.body.style.overflow;
1045
1314
  document.body.style.overflow = "hidden";
1046
1315
  return () => {
1047
1316
  document.body.style.overflow = previous;
1048
1317
  };
1049
1318
  }, []);
1050
- React3.useEffect(() => {
1319
+ React.useEffect(() => {
1051
1320
  const onKey = (e) => {
1052
1321
  if (e.key === "Escape") close();
1053
1322
  };
@@ -1187,6 +1456,7 @@ function AppShell({
1187
1456
  topbarStart,
1188
1457
  topbarEnd,
1189
1458
  sidebarTopContent,
1459
+ sidebarNavTop,
1190
1460
  sidebarFooterContent,
1191
1461
  bottomNav,
1192
1462
  onBottomNavAction,
@@ -1194,6 +1464,7 @@ function AppShell({
1194
1464
  userMenuSlot,
1195
1465
  notificationSlot,
1196
1466
  unreadCount = 0,
1467
+ searchLabel,
1197
1468
  sidebarSectionLabels,
1198
1469
  sidebarNavKey,
1199
1470
  sidebarNavDirection,
@@ -1234,6 +1505,7 @@ function AppShell({
1234
1505
  userMenuExtras,
1235
1506
  orgSwitcher,
1236
1507
  sidebarTopContent,
1508
+ sidebarNavTop,
1237
1509
  sidebarFooterContent,
1238
1510
  frame: contentFrame,
1239
1511
  currentPath,
@@ -1244,6 +1516,7 @@ function AppShell({
1244
1516
  userMenuSlot,
1245
1517
  notificationSlot,
1246
1518
  unreadCount,
1519
+ searchLabel,
1247
1520
  hasBottomNav,
1248
1521
  mobileHeader,
1249
1522
  sidebarSectionLabels,
@@ -1319,13 +1592,13 @@ function SplitWithRightRail({
1319
1592
  maxWidth = 720,
1320
1593
  resizeLabel = "Resize panel"
1321
1594
  }) {
1322
- const [width, setWidth] = React3.useState(defaultWidth);
1323
- const [dragging, setDragging] = React3.useState(false);
1324
- const clamp = React3.useCallback(
1595
+ const [width, setWidth] = React.useState(defaultWidth);
1596
+ const [dragging, setDragging] = React.useState(false);
1597
+ const clamp = React.useCallback(
1325
1598
  (value) => Math.min(maxWidth, Math.max(minWidth, value)),
1326
1599
  [minWidth, maxWidth]
1327
1600
  );
1328
- React3.useEffect(() => {
1601
+ React.useEffect(() => {
1329
1602
  if (!dragging) return;
1330
1603
  const onMove = (event) => {
1331
1604
  event.preventDefault();
@@ -1347,7 +1620,7 @@ function SplitWithRightRail({
1347
1620
  document.body.style.cursor = previousCursor;
1348
1621
  };
1349
1622
  }, [dragging, clamp]);
1350
- React3.useEffect(() => {
1623
+ React.useEffect(() => {
1351
1624
  if (dragging) return;
1352
1625
  onWidthChange?.(width);
1353
1626
  }, [dragging]);
@@ -1426,12 +1699,40 @@ function SplitWithRightRail({
1426
1699
  ] })
1427
1700
  );
1428
1701
  }
1429
- var NavTransitionContext = React3.createContext("slide");
1702
+ function BootSpinner({ className }) {
1703
+ return /* @__PURE__ */ jsx(
1704
+ "span",
1705
+ {
1706
+ "aria-hidden": true,
1707
+ className: cn(
1708
+ "block h-7 w-7 animate-spin rounded-full border-2 border-sidebar-foreground/20 border-t-sidebar-foreground/70",
1709
+ "motion-reduce:animate-none motion-reduce:border-t-sidebar-foreground/20 motion-reduce:animate-pulse",
1710
+ className
1711
+ )
1712
+ }
1713
+ );
1714
+ }
1715
+ function AppShellSkeleton({ label, className }) {
1716
+ return /* @__PURE__ */ jsxs(
1717
+ "div",
1718
+ {
1719
+ role: "status",
1720
+ "aria-busy": "true",
1721
+ className: cn("flex h-screen items-center justify-center overflow-hidden bg-sidebar", className),
1722
+ children: [
1723
+ /* @__PURE__ */ jsx("span", { className: "sr-only", children: label }),
1724
+ /* @__PURE__ */ jsx(BootSpinner, {})
1725
+ ]
1726
+ }
1727
+ );
1728
+ }
1729
+ AppShellSkeleton.displayName = "AppShellSkeleton";
1730
+ var NavTransitionContext = React.createContext("slide");
1430
1731
  function NavTransitionProvider({ value, children }) {
1431
1732
  return /* @__PURE__ */ jsx(NavTransitionContext.Provider, { value, children });
1432
1733
  }
1433
1734
  function useNavTransition() {
1434
- return React3.useContext(NavTransitionContext);
1735
+ return React.useContext(NavTransitionContext);
1435
1736
  }
1436
1737
  function resolveTransitionClass(transition, direction) {
1437
1738
  if (transition === "none") return "";
@@ -1460,15 +1761,15 @@ function PremiumBadge({ className }) {
1460
1761
  const tip = t("upgrade.badgeTooltip");
1461
1762
  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 }) });
1462
1763
  }
1463
- var ModuleLayoutContext = React3.createContext("rail");
1764
+ var ModuleLayoutContext = React.createContext("rail");
1464
1765
  function ModuleLayoutProvider({ variant, children }) {
1465
1766
  return /* @__PURE__ */ jsx(ModuleLayoutContext.Provider, { value: variant, children });
1466
1767
  }
1467
1768
  function useModuleLayout() {
1468
- return React3.useContext(ModuleLayoutContext);
1769
+ return React.useContext(ModuleLayoutContext);
1469
1770
  }
1470
1771
  function NavItem2({ item }) {
1471
- const [expanded, setExpanded] = React3.useState(true);
1772
+ const [expanded, setExpanded] = React.useState(true);
1472
1773
  const Icon = item.icon ? ICON_MAP2[item.icon] ?? null : null;
1473
1774
  const hasChildren = item.children && item.children.length > 0;
1474
1775
  const hasActiveChild = hasChildren && item.children.some((c) => c.active);
@@ -1510,9 +1811,9 @@ function NavItem2({ item }) {
1510
1811
  ] });
1511
1812
  }
1512
1813
  function MobileTabs({ nav }) {
1513
- const scrollRef = React3.useRef(null);
1514
- const [expandedId, setExpandedId] = React3.useState(null);
1515
- React3.useEffect(() => {
1814
+ const scrollRef = React.useRef(null);
1815
+ const [expandedId, setExpandedId] = React.useState(null);
1816
+ React.useEffect(() => {
1516
1817
  const container = scrollRef.current;
1517
1818
  if (!container) return;
1518
1819
  const activeEl = container.querySelector('[data-active="true"]');
@@ -1579,20 +1880,31 @@ function MobileTabs({ nav }) {
1579
1880
  })()
1580
1881
  ] });
1581
1882
  }
1582
- var BackStyleContext = React3.createContext("link");
1883
+ var BackStyleContext = React.createContext("link");
1583
1884
  function BackStyleProvider({ style, children }) {
1584
1885
  return /* @__PURE__ */ jsx(BackStyleContext.Provider, { value: style, children });
1585
1886
  }
1586
1887
  function useBackStyle() {
1587
- return React3.useContext(BackStyleContext);
1888
+ return React.useContext(BackStyleContext);
1588
1889
  }
1589
- function SubpageHeader({ title, subtitle, icon, onBack, parentLabel, actions, backStyle }) {
1890
+ function SubpageHeader({ title, subtitle, icon, onBack, parentLabel, actions, backStyle, inline }) {
1590
1891
  const t = useTranslation();
1591
1892
  const appStyle = useBackStyle();
1592
- const hasShellBar = useHasPageHeaderHost();
1593
- const { onBack: effectiveOnBack, parentLabel: effectiveParentLabel } = useCrossModuleBack(onBack, parentLabel);
1893
+ const hasShellBar = useHasPageHeaderHost() && !inline;
1894
+ const cross = useCrossModuleBack(onBack, parentLabel);
1895
+ const effectiveOnBack = inline ? onBack : cross.onBack;
1896
+ const effectiveParentLabel = inline ? parentLabel : cross.parentLabel;
1594
1897
  useBackHandler(effectiveOnBack);
1595
1898
  const Icon = icon ? ICON_MAP2[icon] ?? null : null;
1899
+ if (inline && effectiveOnBack) {
1900
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
1901
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center justify-between gap-3", children: [
1902
+ /* @__PURE__ */ jsx(PageHeaderCrumb, { title, parentLabel: effectiveParentLabel, onBack: effectiveOnBack }),
1903
+ actions && /* @__PURE__ */ jsx("div", { className: "no-print", children: actions })
1904
+ ] }),
1905
+ subtitle && /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: subtitle })
1906
+ ] });
1907
+ }
1596
1908
  if (hasShellBar) {
1597
1909
  return /* @__PURE__ */ jsx(
1598
1910
  ShellSubpageHeader,
@@ -1739,7 +2051,36 @@ function ModulePage({ title, subtitle, nav, children, className, headerAction, s
1739
2051
  ] })
1740
2052
  ] });
1741
2053
  }
1742
- var ContentSplitContext = React3.createContext(null);
2054
+ var EMPTY = [];
2055
+ var PageTrailContext = React.createContext(EMPTY);
2056
+ function PageTrail({
2057
+ label,
2058
+ href,
2059
+ onNavigate,
2060
+ children
2061
+ }) {
2062
+ const parent = React.useContext(PageTrailContext);
2063
+ const trail = React.useMemo(
2064
+ () => [...parent, { label, href, onNavigate }],
2065
+ [parent, label, href, onNavigate]
2066
+ );
2067
+ return /* @__PURE__ */ jsx(PageTrailContext.Provider, { value: trail, children });
2068
+ }
2069
+ function usePageTrail() {
2070
+ return React.useContext(PageTrailContext);
2071
+ }
2072
+ function usePageTrailParent(href) {
2073
+ const trail = React.useContext(PageTrailContext);
2074
+ return React.useMemo(() => {
2075
+ const above = href ? trail.filter((c) => c.href !== href) : trail;
2076
+ return above[above.length - 1];
2077
+ }, [trail, href]);
2078
+ }
2079
+ function navigateToCrumb(crumb) {
2080
+ if (crumb.onNavigate) crumb.onNavigate();
2081
+ else window.location.hash = crumb.href;
2082
+ }
2083
+ var ContentSplitContext = React.createContext(null);
1743
2084
  function ContentSplit({
1744
2085
  children,
1745
2086
  aside,
@@ -1752,10 +2093,10 @@ function ContentSplit({
1752
2093
  defaultCollapsed = false,
1753
2094
  className
1754
2095
  }) {
1755
- const [internalOpen, setInternalOpen] = React3.useState(false);
2096
+ const [internalOpen, setInternalOpen] = React.useState(false);
1756
2097
  const open = asideOpen ?? internalOpen;
1757
2098
  const setOpen = onAsideOpenChange ?? setInternalOpen;
1758
- const [collapsed, setCollapsed] = React3.useState(collapsible && defaultCollapsed);
2099
+ const [collapsed, setCollapsed] = React.useState(collapsible && defaultCollapsed);
1759
2100
  return /* @__PURE__ */ jsxs(ContentSplitContext.Provider, { value: { open, setOpen }, children: [
1760
2101
  /* @__PURE__ */ jsxs(
1761
2102
  "div",
@@ -1807,7 +2148,7 @@ function ContentSplit({
1807
2148
  ] });
1808
2149
  }
1809
2150
  function ContentSplitTrigger({ label, iconOnly, className }) {
1810
- const ctx = React3.useContext(ContentSplitContext);
2151
+ const ctx = React.useContext(ContentSplitContext);
1811
2152
  if (!ctx) return null;
1812
2153
  return /* @__PURE__ */ jsxs(
1813
2154
  Button,
@@ -1825,6 +2166,6 @@ function ContentSplitTrigger({ label, iconOnly, className }) {
1825
2166
  );
1826
2167
  }
1827
2168
 
1828
- export { AppShell, BackStyleProvider, ContentSplit, ContentSplitTrigger, ModuleLayoutProvider, ModulePage, NavTransitionProvider, PageHeaderActions, PageTransition, Sidebar, SubpageHeader, Topbar, useBackStyle, useLayoutStore, useModuleHeaderActionsSlot, useModuleHeaderSlot, useModuleLayout, useNavTransition };
1829
- //# sourceMappingURL=chunk-H63TOSQ7.js.map
1830
- //# sourceMappingURL=chunk-H63TOSQ7.js.map
2169
+ export { AppShell, AppShellSkeleton, BackStyleProvider, ContentSplit, ContentSplitTrigger, ModuleLayoutProvider, ModulePage, NavTransitionProvider, PageHeaderActions, PageTrail, PageTransition, Sidebar, SubpageHeader, Topbar, navigateToCrumb, useBackStyle, useModuleHeaderActionsSlot, useModuleHeaderSlot, useModuleLayout, useNavTransition, usePageTrail, usePageTrailParent };
2170
+ //# sourceMappingURL=chunk-QSHA2Q27.js.map
2171
+ //# sourceMappingURL=chunk-QSHA2Q27.js.map