@fayz-ai/ui 0.10.2 → 0.12.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.
- package/dist/analytics/AnalyticsCard.d.ts.map +1 -1
- package/dist/analytics/AnalyticsControls.d.ts.map +1 -1
- package/dist/analytics/batch.d.ts +19 -0
- package/dist/analytics/batch.d.ts.map +1 -0
- package/dist/analytics/context.d.ts.map +1 -1
- package/dist/analytics/derive.d.ts.map +1 -1
- package/dist/analytics/index.d.ts +2 -0
- package/dist/analytics/index.d.ts.map +1 -1
- package/dist/{chunk-SEIY2F5F.js → chunk-7BHG3XGJ.js} +317 -86
- package/dist/chunk-7BHG3XGJ.js.map +1 -0
- package/dist/{chunk-I6CPKAOD.js → chunk-7BMXN7MP.js} +215 -99
- package/dist/chunk-7BMXN7MP.js.map +1 -0
- package/dist/{chunk-6V5MGYXH.js → chunk-JGJNRNQW.js} +142 -31
- package/dist/chunk-JGJNRNQW.js.map +1 -0
- package/dist/chunk-ZBQ772DU.js +17 -0
- package/dist/chunk-ZBQ772DU.js.map +1 -0
- package/dist/{chunk-H63TOSQ7.js → chunk-ZDRHCKRK.js} +499 -142
- package/dist/chunk-ZDRHCKRK.js.map +1 -0
- package/dist/dashboard/ChartWidget.d.ts.map +1 -1
- package/dist/dashboard/KpiCard.d.ts.map +1 -1
- package/dist/dashboard/SidebarOnboarding.d.ts +16 -11
- package/dist/dashboard/SidebarOnboarding.d.ts.map +1 -1
- package/dist/dashboard/Sparkline.d.ts +13 -0
- package/dist/dashboard/Sparkline.d.ts.map +1 -0
- package/dist/dashboard/index.d.ts +1 -0
- package/dist/dashboard/index.d.ts.map +1 -1
- package/dist/dashboard/index.js +3 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +237 -117
- package/dist/index.js.map +1 -1
- package/dist/layout/AppShell.d.ts +4 -1
- package/dist/layout/AppShell.d.ts.map +1 -1
- package/dist/layout/AppShellSkeleton.d.ts +23 -0
- package/dist/layout/AppShellSkeleton.d.ts.map +1 -0
- package/dist/layout/ModulePage.d.ts +5 -1
- package/dist/layout/ModulePage.d.ts.map +1 -1
- package/dist/layout/Sidebar.d.ts +19 -1
- package/dist/layout/Sidebar.d.ts.map +1 -1
- package/dist/layout/Topbar.d.ts +4 -1
- package/dist/layout/Topbar.d.ts.map +1 -1
- package/dist/layout/index.d.ts +2 -0
- package/dist/layout/index.d.ts.map +1 -1
- package/dist/layout/index.js +2 -1
- package/dist/layout/page-trail.d.ts +23 -0
- package/dist/layout/page-trail.d.ts.map +1 -0
- package/dist/layout/rail-flyout.d.ts +39 -0
- package/dist/layout/rail-flyout.d.ts.map +1 -0
- package/dist/primitives/bulk-action-bar.d.ts +30 -0
- package/dist/primitives/bulk-action-bar.d.ts.map +1 -0
- package/dist/primitives/checkbox.d.ts +5 -0
- package/dist/primitives/checkbox.d.ts.map +1 -1
- package/dist/primitives/data-table.d.ts +11 -1
- package/dist/primitives/data-table.d.ts.map +1 -1
- package/dist/primitives/dropdown.d.ts.map +1 -1
- package/dist/primitives/index.d.ts +5 -0
- package/dist/primitives/index.d.ts.map +1 -1
- package/dist/primitives/index.js +2 -2
- package/dist/primitives/list-chrome.d.ts +20 -0
- package/dist/primitives/list-chrome.d.ts.map +1 -0
- package/dist/primitives/modal.d.ts.map +1 -1
- package/dist/primitives/phone-field.d.ts +19 -0
- package/dist/primitives/phone-field.d.ts.map +1 -0
- package/dist/primitives/refresh-button.d.ts +18 -0
- package/dist/primitives/refresh-button.d.ts.map +1 -0
- package/dist/primitives/search-select.d.ts +6 -1
- package/dist/primitives/search-select.d.ts.map +1 -1
- package/dist/primitives/thumbnail.d.ts +19 -0
- package/dist/primitives/thumbnail.d.ts.map +1 -0
- package/dist/styles.css +298 -1
- package/package.json +2 -2
- package/dist/chunk-6V5MGYXH.js.map +0 -1
- package/dist/chunk-H63TOSQ7.js.map +0 -1
- package/dist/chunk-I6CPKAOD.js.map +0 -1
- package/dist/chunk-SEIY2F5F.js.map +0 -1
|
@@ -1,40 +1,262 @@
|
|
|
1
|
-
import { useHasPageHeaderHost, useCrossModuleBack, useBackHandler,
|
|
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
|
|
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
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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
|
|
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
|
+
// 36px rows. The rail was condensed to 28px while it listed every
|
|
256
|
+
// destination flat; with the destinations folded into collapsible groups it
|
|
257
|
+
// shows a handful of rows at rest, and at that length the tight rhythm read
|
|
258
|
+
// as cramped rather than as dense.
|
|
259
|
+
"flex w-full items-center gap-3 rounded-button px-2.5 py-2 text-[13px] font-medium transition-colors",
|
|
38
260
|
"hover:bg-sidebar-accent/60 hover:text-sidebar-accent-foreground",
|
|
39
261
|
isActive ? "bg-sidebar-accent font-semibold text-sidebar-accent-foreground shadow-sm" : "text-sidebar-foreground/80",
|
|
40
262
|
collapsed && "justify-center px-2"
|
|
@@ -48,27 +270,18 @@ function NavItem({
|
|
|
48
270
|
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
271
|
] })
|
|
50
272
|
] });
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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;
|
|
273
|
+
return isExternal ? /* @__PURE__ */ jsx("a", { href: item.href, target: "_blank", rel: "noopener noreferrer", className, ...hoverProps, children: inner }) : /* @__PURE__ */ jsx(
|
|
274
|
+
"button",
|
|
275
|
+
{
|
|
276
|
+
onClick: () => {
|
|
277
|
+
flyout?.dismiss();
|
|
278
|
+
onNavigate(item.route);
|
|
279
|
+
},
|
|
280
|
+
className,
|
|
281
|
+
...hoverProps,
|
|
282
|
+
children: inner
|
|
283
|
+
}
|
|
284
|
+
);
|
|
72
285
|
}
|
|
73
286
|
function routeMatchScore(route, path) {
|
|
74
287
|
if (path === route) return route.length + 1;
|
|
@@ -97,30 +310,30 @@ function NavGroup({
|
|
|
97
310
|
collapsed,
|
|
98
311
|
activeRoute,
|
|
99
312
|
onNavigate,
|
|
100
|
-
childConnector
|
|
313
|
+
childConnector,
|
|
314
|
+
flyout
|
|
101
315
|
}) {
|
|
102
316
|
const isChildActive = item.children?.some((c) => c.route === activeRoute);
|
|
103
|
-
const [open, setOpen] =
|
|
317
|
+
const [open, setOpen] = React.useState(() => isChildActive ?? false);
|
|
104
318
|
const Icon = getIcon(item.icon);
|
|
105
319
|
if (collapsed) {
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
] });
|
|
320
|
+
const hoverProps = flyout?.rowProps(flyoutContentOf(item));
|
|
321
|
+
return /* @__PURE__ */ jsx(
|
|
322
|
+
"button",
|
|
323
|
+
{
|
|
324
|
+
onClick: () => {
|
|
325
|
+
flyout?.dismiss();
|
|
326
|
+
onNavigate(item.route);
|
|
327
|
+
},
|
|
328
|
+
className: cn(
|
|
329
|
+
"flex w-full items-center justify-center rounded-button px-2 py-2 text-sm font-medium transition-colors",
|
|
330
|
+
"hover:bg-sidebar-accent hover:text-sidebar-accent-foreground",
|
|
331
|
+
isChildActive ? "bg-sidebar-accent text-sidebar-accent-foreground" : "text-sidebar-muted"
|
|
332
|
+
),
|
|
333
|
+
...hoverProps,
|
|
334
|
+
children: /* @__PURE__ */ jsx(Icon, { className: "h-4 w-4 shrink-0" })
|
|
335
|
+
}
|
|
336
|
+
);
|
|
124
337
|
}
|
|
125
338
|
return /* @__PURE__ */ jsxs("div", { children: [
|
|
126
339
|
/* @__PURE__ */ jsxs(
|
|
@@ -128,7 +341,9 @@ function NavGroup({
|
|
|
128
341
|
{
|
|
129
342
|
onClick: () => setOpen(!open),
|
|
130
343
|
className: cn(
|
|
131
|
-
|
|
344
|
+
// Same metrics as NavItem — a group header and a plain link are peers
|
|
345
|
+
// in the rail and must share a baseline.
|
|
346
|
+
"flex w-full items-center gap-3 rounded-button px-2.5 py-2 text-[13px] font-medium transition-colors",
|
|
132
347
|
"hover:bg-sidebar-accent/60 hover:text-sidebar-accent-foreground",
|
|
133
348
|
isChildActive ? "text-sidebar-accent-foreground" : "text-sidebar-foreground/80"
|
|
134
349
|
),
|
|
@@ -173,7 +388,7 @@ function NavGroup({
|
|
|
173
388
|
child.premium && /* @__PURE__ */ jsx(Crown, { className: "h-3 w-3 shrink-0 text-amber-500", "aria-hidden": true })
|
|
174
389
|
] });
|
|
175
390
|
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(
|
|
391
|
+
if (!childConnector) return /* @__PURE__ */ jsx(React.Fragment, { children: row }, child.id);
|
|
177
392
|
const activeIndex = item.children.findIndex((c) => c.route === activeRoute);
|
|
178
393
|
const onPath = activeIndex >= 0 && childIndex < activeIndex;
|
|
179
394
|
return (
|
|
@@ -234,6 +449,11 @@ function NavGroup({
|
|
|
234
449
|
}) })
|
|
235
450
|
] });
|
|
236
451
|
}
|
|
452
|
+
function withCollapsed(slot, collapsed) {
|
|
453
|
+
if (!React.isValidElement(slot)) return slot;
|
|
454
|
+
if (typeof slot.type === "string") return slot;
|
|
455
|
+
return React.cloneElement(slot, { collapsed });
|
|
456
|
+
}
|
|
237
457
|
function Sidebar({
|
|
238
458
|
navigation,
|
|
239
459
|
logo,
|
|
@@ -244,11 +464,13 @@ function Sidebar({
|
|
|
244
464
|
user: _user,
|
|
245
465
|
orgSwitcher,
|
|
246
466
|
topContent,
|
|
467
|
+
navTop,
|
|
247
468
|
footerContent,
|
|
248
469
|
borderless,
|
|
249
470
|
userMenuSlot,
|
|
250
471
|
notificationSlot,
|
|
251
472
|
unreadCount = 0,
|
|
473
|
+
searchLabel = "Search",
|
|
252
474
|
sectionLabels,
|
|
253
475
|
navKey,
|
|
254
476
|
navDirection = "forward",
|
|
@@ -257,39 +479,61 @@ function Sidebar({
|
|
|
257
479
|
const currentPath = currentPathProp;
|
|
258
480
|
const setCommandPaletteOpen = useLayoutStore((s) => s.setCommandPaletteOpen);
|
|
259
481
|
const activeRoute = resolveActiveRoute(navigation, currentPath);
|
|
482
|
+
const railRef = React.useRef(null);
|
|
483
|
+
const flyout = useRailFlyout({
|
|
484
|
+
enabled: collapsed,
|
|
485
|
+
railRef,
|
|
486
|
+
activeRoute,
|
|
487
|
+
onNavigate: (route) => onNavigate?.(route)
|
|
488
|
+
});
|
|
260
489
|
const mainItems = navigation.filter((item) => item.section === "main");
|
|
261
490
|
const secondaryItems = navigation.filter((item) => item.section === "secondary");
|
|
262
491
|
const settingsItems = navigation.filter((item) => item.section === "settings");
|
|
263
492
|
const handleNavigate = (route) => {
|
|
264
493
|
onNavigate?.(route);
|
|
265
494
|
};
|
|
266
|
-
const sectionHeading = (label) => label && !collapsed ? /* @__PURE__ */ jsx("p", { className: "px-2.5 pb-
|
|
267
|
-
const
|
|
495
|
+
const sectionHeading = (label) => label && !collapsed ? /* @__PURE__ */ jsx("p", { className: "px-2.5 pb-0.5 pt-1 text-[10px] font-semibold uppercase tracking-wider text-sidebar-muted", children: label }) : null;
|
|
496
|
+
const groupRuns = (items) => {
|
|
497
|
+
const runs = [];
|
|
498
|
+
for (const item of items) {
|
|
499
|
+
const last = runs[runs.length - 1];
|
|
500
|
+
if (last && last.group === item.group) last.items.push(item);
|
|
501
|
+
else runs.push({ group: item.group, items: [item] });
|
|
502
|
+
}
|
|
503
|
+
return runs;
|
|
504
|
+
};
|
|
505
|
+
const renderSection = (items, label) => {
|
|
506
|
+
const runs = groupRuns(items);
|
|
507
|
+
return runs.map((run, i) => (
|
|
508
|
+
// 4px between rows, 20px between runs (a run being a `group` heading's
|
|
509
|
+
// worth of entries — see NavigationItem.group).
|
|
510
|
+
/* @__PURE__ */ jsxs("div", { className: i === 0 ? "space-y-1" : "mt-5 space-y-1", children: [
|
|
511
|
+
sectionHeading(run.group ?? label),
|
|
512
|
+
run.items.map(renderItem)
|
|
513
|
+
] }, run.group ?? `ungrouped-${i}`)
|
|
514
|
+
));
|
|
515
|
+
};
|
|
516
|
+
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
517
|
NavItem,
|
|
269
518
|
{
|
|
270
519
|
item,
|
|
271
520
|
collapsed,
|
|
272
521
|
isActive: item.route === activeRoute,
|
|
273
|
-
onNavigate: handleNavigate
|
|
522
|
+
onNavigate: handleNavigate,
|
|
523
|
+
flyout
|
|
274
524
|
},
|
|
275
525
|
item.id
|
|
276
526
|
);
|
|
277
527
|
const navBody = /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
278
|
-
|
|
279
|
-
mainItems.length > 0 &&
|
|
280
|
-
|
|
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
|
-
] })
|
|
528
|
+
navTop && /* @__PURE__ */ jsx("div", { className: "mb-2", children: withCollapsed(navTop, collapsed) }),
|
|
529
|
+
mainItems.length > 0 && renderSection(mainItems, sectionLabels?.main),
|
|
530
|
+
secondaryItems.length > 0 && /* @__PURE__ */ jsx("div", { className: "mt-6", children: renderSection(secondaryItems, sectionLabels?.secondary) })
|
|
287
531
|
] });
|
|
288
|
-
const [outgoing, setOutgoing] =
|
|
289
|
-
const lastBodyRef =
|
|
290
|
-
const lastKeyRef =
|
|
291
|
-
const timerRef =
|
|
292
|
-
|
|
532
|
+
const [outgoing, setOutgoing] = React.useState(null);
|
|
533
|
+
const lastBodyRef = React.useRef(null);
|
|
534
|
+
const lastKeyRef = React.useRef(navKey);
|
|
535
|
+
const timerRef = React.useRef(null);
|
|
536
|
+
useIsomorphicLayoutEffect2(() => {
|
|
293
537
|
if (lastKeyRef.current !== navKey) {
|
|
294
538
|
const previousBody = lastBodyRef.current;
|
|
295
539
|
lastKeyRef.current = navKey;
|
|
@@ -301,17 +545,29 @@ function Sidebar({
|
|
|
301
545
|
}
|
|
302
546
|
lastBodyRef.current = navBody;
|
|
303
547
|
});
|
|
304
|
-
|
|
548
|
+
React.useEffect(() => () => {
|
|
305
549
|
if (timerRef.current) clearTimeout(timerRef.current);
|
|
306
550
|
}, []);
|
|
307
|
-
return /* @__PURE__ */
|
|
551
|
+
return /* @__PURE__ */ jsxs(
|
|
308
552
|
"aside",
|
|
309
553
|
{
|
|
554
|
+
ref: railRef,
|
|
310
555
|
"data-print": "hide",
|
|
556
|
+
onBlur: (event) => {
|
|
557
|
+
if (!event.currentTarget.contains(event.relatedTarget)) flyout.dismiss();
|
|
558
|
+
},
|
|
311
559
|
className: cn(
|
|
312
|
-
|
|
560
|
+
// `relative` anchors the flyout, which hangs off the rail's right edge
|
|
561
|
+
// and has to escape the nav's own scroll box to do it.
|
|
562
|
+
"relative flex h-full flex-col bg-sidebar text-sidebar-foreground transition-all duration-200",
|
|
313
563
|
!borderless && "border-r border-sidebar-border",
|
|
314
|
-
|
|
564
|
+
// 14rem expanded. It was 16rem, which is the width a rail needs when
|
|
565
|
+
// its entries are sentences ("GlossGenius Financing", "Configure your
|
|
566
|
+
// payment methods"); these are one or two words, so the extra 2rem was
|
|
567
|
+
// paid for by the content column on every screen in every app. The
|
|
568
|
+
// labels still truncate, and a long one now truncates 32px earlier —
|
|
569
|
+
// which is the trade, taken deliberately.
|
|
570
|
+
collapsed ? "w-16" : "w-56"
|
|
315
571
|
),
|
|
316
572
|
children: [
|
|
317
573
|
/* @__PURE__ */ jsxs(
|
|
@@ -347,6 +603,36 @@ function Sidebar({
|
|
|
347
603
|
]
|
|
348
604
|
}
|
|
349
605
|
),
|
|
606
|
+
topContent && /* @__PURE__ */ jsx("div", { className: "px-2 pt-2", children: withCollapsed(topContent, collapsed) }),
|
|
607
|
+
/* @__PURE__ */ jsx("div", { className: "px-2 pb-2 pt-2", children: /* @__PURE__ */ jsxs(
|
|
608
|
+
"button",
|
|
609
|
+
{
|
|
610
|
+
onClick: () => setCommandPaletteOpen(true),
|
|
611
|
+
"aria-keyshortcuts": "Meta+K Control+K",
|
|
612
|
+
className: cn(
|
|
613
|
+
// Tonal surface, no outline. On a dark rail a 1px border is a hard
|
|
614
|
+
// edge competing with every other hard edge; a lighter fill of the
|
|
615
|
+
// same hue reads as "a field" without drawing a box around it — and
|
|
616
|
+
// leaves the outline treatment free to mean focus, which it must.
|
|
617
|
+
"fz-tap group flex items-center rounded-lg bg-sidebar-accent/40 text-sidebar-muted transition-colors",
|
|
618
|
+
"hover:bg-sidebar-accent/70 hover:text-sidebar-foreground",
|
|
619
|
+
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-sidebar-accent-foreground/50",
|
|
620
|
+
collapsed ? "h-9 w-9 justify-center" : "h-9 w-full gap-2.5 px-2.5"
|
|
621
|
+
),
|
|
622
|
+
"aria-label": searchLabel,
|
|
623
|
+
title: collapsed ? searchLabel : void 0,
|
|
624
|
+
children: [
|
|
625
|
+
/* @__PURE__ */ jsx(Search, { className: "h-4 w-4 shrink-0" }),
|
|
626
|
+
!collapsed && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
627
|
+
/* @__PURE__ */ jsx("span", { className: "flex-1 truncate text-left text-[13px]", children: searchLabel }),
|
|
628
|
+
/* @__PURE__ */ jsxs("span", { className: "shrink-0 text-[11px] font-medium tabular-nums text-sidebar-muted/60 transition-opacity group-hover:opacity-100 md:opacity-70", children: [
|
|
629
|
+
shortcutKey(),
|
|
630
|
+
"K"
|
|
631
|
+
] })
|
|
632
|
+
] })
|
|
633
|
+
]
|
|
634
|
+
}
|
|
635
|
+
) }),
|
|
350
636
|
/* @__PURE__ */ jsxs(
|
|
351
637
|
"nav",
|
|
352
638
|
{
|
|
@@ -388,7 +674,8 @@ function Sidebar({
|
|
|
388
674
|
item,
|
|
389
675
|
collapsed,
|
|
390
676
|
isActive: item.route === activeRoute,
|
|
391
|
-
onNavigate: handleNavigate
|
|
677
|
+
onNavigate: handleNavigate,
|
|
678
|
+
flyout
|
|
392
679
|
},
|
|
393
680
|
item.id
|
|
394
681
|
)) }),
|
|
@@ -396,26 +683,17 @@ function Sidebar({
|
|
|
396
683
|
// SeuSiteWidget self-hides when the tenant has no project); without
|
|
397
684
|
// it the padded/bordered wrapper shows as a blank block.
|
|
398
685
|
/* @__PURE__ */ jsx("div", { className: cn("space-y-2 p-2 empty:hidden", !borderless && "border-t border-sidebar-border"), children: footerContent }),
|
|
399
|
-
|
|
686
|
+
withCollapsed(orgSwitcher, collapsed),
|
|
400
687
|
/* @__PURE__ */ jsx("div", { className: cn("p-2", !borderless && "border-t border-sidebar-border"), children: /* @__PURE__ */ jsxs("div", { className: cn(
|
|
401
688
|
"flex items-center rounded-button",
|
|
402
689
|
collapsed ? "flex-col gap-1" : "gap-1 px-1"
|
|
403
690
|
), children: [
|
|
404
691
|
userMenuSlot,
|
|
405
692
|
!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
693
|
notificationSlot ?? /* @__PURE__ */ jsxs(
|
|
416
694
|
"button",
|
|
417
695
|
{
|
|
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",
|
|
696
|
+
className: "fz-tap relative inline-flex shrink-0 items-center justify-center rounded-button p-2 text-sidebar-muted transition-colors hover:bg-sidebar-accent/50 hover:text-sidebar-accent-foreground",
|
|
419
697
|
"aria-label": unreadCount > 0 ? `${unreadCount} unread notifications` : "Notifications",
|
|
420
698
|
children: [
|
|
421
699
|
/* @__PURE__ */ jsx(Bell, { className: "h-4 w-4" }),
|
|
@@ -423,10 +701,11 @@ function Sidebar({
|
|
|
423
701
|
]
|
|
424
702
|
}
|
|
425
703
|
)
|
|
426
|
-
] }) })
|
|
704
|
+
] }) }),
|
|
705
|
+
flyout.panel
|
|
427
706
|
]
|
|
428
707
|
}
|
|
429
|
-
)
|
|
708
|
+
);
|
|
430
709
|
}
|
|
431
710
|
function TopbarUserMenu({
|
|
432
711
|
user,
|
|
@@ -438,7 +717,7 @@ function TopbarUserMenu({
|
|
|
438
717
|
onBilling,
|
|
439
718
|
userMenuExtras
|
|
440
719
|
}) {
|
|
441
|
-
const [open, setOpen] =
|
|
720
|
+
const [open, setOpen] = React.useState(false);
|
|
442
721
|
if (!user) return null;
|
|
443
722
|
const initial = (user.fullName?.trim()?.[0] ?? user.email?.[0] ?? "?").toUpperCase();
|
|
444
723
|
const item = (label, onClick, Icon) => onClick ? /* @__PURE__ */ jsxs(
|
|
@@ -626,7 +905,7 @@ function getIcon2(name) {
|
|
|
626
905
|
return ICON_MAP2[name] ?? Home;
|
|
627
906
|
}
|
|
628
907
|
function NavDropdown({ item, currentPath, onNavigate }) {
|
|
629
|
-
const [open, setOpen] =
|
|
908
|
+
const [open, setOpen] = React.useState(false);
|
|
630
909
|
const Icon = getIcon2(item.icon);
|
|
631
910
|
const isChildActive = item.children?.some(
|
|
632
911
|
(child) => currentPath === child.route || currentPath.startsWith(child.route + "/")
|
|
@@ -717,8 +996,7 @@ function Topbar({
|
|
|
717
996
|
children: /* @__PURE__ */ jsx(Menu, { className: "h-5 w-5" })
|
|
718
997
|
}
|
|
719
998
|
),
|
|
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 })
|
|
999
|
+
/* @__PURE__ */ jsx("div", { className: "hidden shrink-0 items-center md:flex", children: logo ?? /* @__PURE__ */ jsx("span", { className: "text-lg font-bold", children: "App" }) })
|
|
722
1000
|
] }),
|
|
723
1001
|
/* @__PURE__ */ jsx("div", { className: "mx-8 hidden max-w-sm flex-1 md:flex", children: /* @__PURE__ */ jsxs(
|
|
724
1002
|
"button",
|
|
@@ -732,7 +1010,8 @@ function Topbar({
|
|
|
732
1010
|
]
|
|
733
1011
|
}
|
|
734
1012
|
) }),
|
|
735
|
-
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
|
|
1013
|
+
/* @__PURE__ */ jsxs("div", { className: "flex min-w-0 items-center gap-1", children: [
|
|
1014
|
+
orgSwitcher && /* @__PURE__ */ jsx("div", { className: "hidden min-w-0 items-center md:flex", children: orgSwitcher }),
|
|
736
1015
|
leftContent,
|
|
737
1016
|
rightContent,
|
|
738
1017
|
/* @__PURE__ */ jsx(
|
|
@@ -792,13 +1071,13 @@ function Topbar({
|
|
|
792
1071
|
}) }) }) })
|
|
793
1072
|
] });
|
|
794
1073
|
}
|
|
795
|
-
var ModuleHeaderSlotContext =
|
|
1074
|
+
var ModuleHeaderSlotContext = React.createContext(null);
|
|
796
1075
|
function useModuleHeaderSlot() {
|
|
797
|
-
return
|
|
1076
|
+
return React.useContext(ModuleHeaderSlotContext);
|
|
798
1077
|
}
|
|
799
|
-
var ModuleHeaderActionsSlotContext =
|
|
1078
|
+
var ModuleHeaderActionsSlotContext = React.createContext(null);
|
|
800
1079
|
function useModuleHeaderActionsSlot() {
|
|
801
|
-
return
|
|
1080
|
+
return React.useContext(ModuleHeaderActionsSlotContext);
|
|
802
1081
|
}
|
|
803
1082
|
function ShellPageTitle({
|
|
804
1083
|
title,
|
|
@@ -820,6 +1099,7 @@ function SidebarLayout({
|
|
|
820
1099
|
userMenuExtras: _userMenuExtras,
|
|
821
1100
|
orgSwitcher,
|
|
822
1101
|
sidebarTopContent,
|
|
1102
|
+
sidebarNavTop,
|
|
823
1103
|
sidebarFooterContent,
|
|
824
1104
|
frame,
|
|
825
1105
|
currentPath,
|
|
@@ -830,6 +1110,7 @@ function SidebarLayout({
|
|
|
830
1110
|
userMenuSlot,
|
|
831
1111
|
notificationSlot,
|
|
832
1112
|
unreadCount = 0,
|
|
1113
|
+
searchLabel,
|
|
833
1114
|
hasBottomNav = false,
|
|
834
1115
|
mobileHeader = "minimal",
|
|
835
1116
|
sidebarSectionLabels,
|
|
@@ -841,11 +1122,11 @@ function SidebarLayout({
|
|
|
841
1122
|
const pageHeaderSlot = usePageHeaderSlot();
|
|
842
1123
|
usePageHeaderHost();
|
|
843
1124
|
const setSidebarCollapsed = useLayoutStore((s) => s.setSidebarCollapsed);
|
|
844
|
-
const [mobileMenuOpen, setMobileMenuOpen] =
|
|
845
|
-
const [headerSlot, setHeaderSlot] =
|
|
846
|
-
const [actionsSlot, setActionsSlot] =
|
|
1125
|
+
const [mobileMenuOpen, setMobileMenuOpen] = React.useState(false);
|
|
1126
|
+
const [headerSlot, setHeaderSlot] = React.useState(null);
|
|
1127
|
+
const [actionsSlot, setActionsSlot] = React.useState(null);
|
|
847
1128
|
return /* @__PURE__ */ jsx(SaveBarProvider, { children: /* @__PURE__ */ jsx(ModuleHeaderSlotContext.Provider, { value: headerSlot, children: /* @__PURE__ */ jsx(ModuleHeaderActionsSlotContext.Provider, { value: actionsSlot, children: /* @__PURE__ */ jsxs("div", { className: cn(
|
|
848
|
-
"flex h-screen overflow-hidden bg-background",
|
|
1129
|
+
"shell-enter flex h-screen overflow-hidden bg-background",
|
|
849
1130
|
// Frame mode: the inset gap around the framed card takes the sidebar (menu)
|
|
850
1131
|
// color, so the white card reads as a panel floating on the same tone as the
|
|
851
1132
|
// left menu. The card's own md:m-2 margin reveals this behind it.
|
|
@@ -867,11 +1148,13 @@ function SidebarLayout({
|
|
|
867
1148
|
onBilling,
|
|
868
1149
|
orgSwitcher,
|
|
869
1150
|
topContent: sidebarTopContent,
|
|
1151
|
+
navTop: sidebarNavTop,
|
|
870
1152
|
footerContent: sidebarFooterContent,
|
|
871
|
-
borderless:
|
|
1153
|
+
borderless: frame,
|
|
872
1154
|
userMenuSlot,
|
|
873
1155
|
notificationSlot,
|
|
874
1156
|
unreadCount,
|
|
1157
|
+
searchLabel,
|
|
875
1158
|
sectionLabels: sidebarSectionLabels,
|
|
876
1159
|
navKey: sidebarNavKey,
|
|
877
1160
|
navDirection: sidebarNavDirection,
|
|
@@ -922,10 +1205,12 @@ function SidebarLayout({
|
|
|
922
1205
|
onBilling,
|
|
923
1206
|
orgSwitcher,
|
|
924
1207
|
topContent: sidebarTopContent,
|
|
1208
|
+
navTop: sidebarNavTop,
|
|
925
1209
|
footerContent: sidebarFooterContent,
|
|
926
1210
|
userMenuSlot,
|
|
927
1211
|
notificationSlot,
|
|
928
1212
|
unreadCount,
|
|
1213
|
+
searchLabel,
|
|
929
1214
|
onClose: () => setMobileMenuOpen(false)
|
|
930
1215
|
}
|
|
931
1216
|
)
|
|
@@ -954,13 +1239,13 @@ function TopbarLayout({
|
|
|
954
1239
|
notificationSlot,
|
|
955
1240
|
hasBottomNav = false
|
|
956
1241
|
}) {
|
|
957
|
-
const [mobileMenuOpen, setMobileMenuOpen] =
|
|
1242
|
+
const [mobileMenuOpen, setMobileMenuOpen] = React.useState(false);
|
|
958
1243
|
const rawSlot = usePageHeaderSlot();
|
|
959
1244
|
const hasOutlet = useHasPageHeaderOutlet();
|
|
960
1245
|
usePageHeaderHost();
|
|
961
1246
|
const pageHeaderSlot = hasOutlet ? null : rawSlot;
|
|
962
1247
|
const showTitle = !hasOutlet && !!pageTitle;
|
|
963
|
-
return /* @__PURE__ */ jsx(SaveBarProvider, { children: /* @__PURE__ */ jsxs("div", { className: "relative flex h-screen flex-col overflow-hidden bg-background", children: [
|
|
1248
|
+
return /* @__PURE__ */ jsx(SaveBarProvider, { children: /* @__PURE__ */ jsxs("div", { className: "shell-enter relative flex h-screen flex-col overflow-hidden bg-background", children: [
|
|
964
1249
|
/* @__PURE__ */ jsx(
|
|
965
1250
|
Topbar,
|
|
966
1251
|
{
|
|
@@ -1022,7 +1307,7 @@ function MinimalLayout({
|
|
|
1022
1307
|
children,
|
|
1023
1308
|
headerEnd
|
|
1024
1309
|
}) {
|
|
1025
|
-
return /* @__PURE__ */ jsxs("div", { className: "flex h-screen flex-col", children: [
|
|
1310
|
+
return /* @__PURE__ */ jsxs("div", { className: "shell-enter flex h-screen flex-col", children: [
|
|
1026
1311
|
/* @__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: [
|
|
1027
1312
|
/* @__PURE__ */ jsx("div", { className: "flex items-center gap-3", children: logo ?? /* @__PURE__ */ jsx("span", { className: "text-lg font-bold", children: "App" }) }),
|
|
1028
1313
|
headerEnd && /* @__PURE__ */ jsx("div", { className: "flex items-center gap-2", children: headerEnd })
|
|
@@ -1031,23 +1316,23 @@ function MinimalLayout({
|
|
|
1031
1316
|
] });
|
|
1032
1317
|
}
|
|
1033
1318
|
function MobileOverlay({ onClose, onNavigate, ...sidebarProps }) {
|
|
1034
|
-
const [visible, setVisible] =
|
|
1035
|
-
|
|
1319
|
+
const [visible, setVisible] = React.useState(false);
|
|
1320
|
+
React.useEffect(() => {
|
|
1036
1321
|
const id = requestAnimationFrame(() => setVisible(true));
|
|
1037
1322
|
return () => cancelAnimationFrame(id);
|
|
1038
1323
|
}, []);
|
|
1039
|
-
const close =
|
|
1324
|
+
const close = React.useCallback(() => {
|
|
1040
1325
|
setVisible(false);
|
|
1041
1326
|
window.setTimeout(onClose, 250);
|
|
1042
1327
|
}, [onClose]);
|
|
1043
|
-
|
|
1328
|
+
React.useEffect(() => {
|
|
1044
1329
|
const previous = document.body.style.overflow;
|
|
1045
1330
|
document.body.style.overflow = "hidden";
|
|
1046
1331
|
return () => {
|
|
1047
1332
|
document.body.style.overflow = previous;
|
|
1048
1333
|
};
|
|
1049
1334
|
}, []);
|
|
1050
|
-
|
|
1335
|
+
React.useEffect(() => {
|
|
1051
1336
|
const onKey = (e) => {
|
|
1052
1337
|
if (e.key === "Escape") close();
|
|
1053
1338
|
};
|
|
@@ -1187,6 +1472,7 @@ function AppShell({
|
|
|
1187
1472
|
topbarStart,
|
|
1188
1473
|
topbarEnd,
|
|
1189
1474
|
sidebarTopContent,
|
|
1475
|
+
sidebarNavTop,
|
|
1190
1476
|
sidebarFooterContent,
|
|
1191
1477
|
bottomNav,
|
|
1192
1478
|
onBottomNavAction,
|
|
@@ -1194,6 +1480,7 @@ function AppShell({
|
|
|
1194
1480
|
userMenuSlot,
|
|
1195
1481
|
notificationSlot,
|
|
1196
1482
|
unreadCount = 0,
|
|
1483
|
+
searchLabel,
|
|
1197
1484
|
sidebarSectionLabels,
|
|
1198
1485
|
sidebarNavKey,
|
|
1199
1486
|
sidebarNavDirection,
|
|
@@ -1234,6 +1521,7 @@ function AppShell({
|
|
|
1234
1521
|
userMenuExtras,
|
|
1235
1522
|
orgSwitcher,
|
|
1236
1523
|
sidebarTopContent,
|
|
1524
|
+
sidebarNavTop,
|
|
1237
1525
|
sidebarFooterContent,
|
|
1238
1526
|
frame: contentFrame,
|
|
1239
1527
|
currentPath,
|
|
@@ -1244,6 +1532,7 @@ function AppShell({
|
|
|
1244
1532
|
userMenuSlot,
|
|
1245
1533
|
notificationSlot,
|
|
1246
1534
|
unreadCount,
|
|
1535
|
+
searchLabel,
|
|
1247
1536
|
hasBottomNav,
|
|
1248
1537
|
mobileHeader,
|
|
1249
1538
|
sidebarSectionLabels,
|
|
@@ -1319,13 +1608,13 @@ function SplitWithRightRail({
|
|
|
1319
1608
|
maxWidth = 720,
|
|
1320
1609
|
resizeLabel = "Resize panel"
|
|
1321
1610
|
}) {
|
|
1322
|
-
const [width, setWidth] =
|
|
1323
|
-
const [dragging, setDragging] =
|
|
1324
|
-
const clamp =
|
|
1611
|
+
const [width, setWidth] = React.useState(defaultWidth);
|
|
1612
|
+
const [dragging, setDragging] = React.useState(false);
|
|
1613
|
+
const clamp = React.useCallback(
|
|
1325
1614
|
(value) => Math.min(maxWidth, Math.max(minWidth, value)),
|
|
1326
1615
|
[minWidth, maxWidth]
|
|
1327
1616
|
);
|
|
1328
|
-
|
|
1617
|
+
React.useEffect(() => {
|
|
1329
1618
|
if (!dragging) return;
|
|
1330
1619
|
const onMove = (event) => {
|
|
1331
1620
|
event.preventDefault();
|
|
@@ -1347,7 +1636,7 @@ function SplitWithRightRail({
|
|
|
1347
1636
|
document.body.style.cursor = previousCursor;
|
|
1348
1637
|
};
|
|
1349
1638
|
}, [dragging, clamp]);
|
|
1350
|
-
|
|
1639
|
+
React.useEffect(() => {
|
|
1351
1640
|
if (dragging) return;
|
|
1352
1641
|
onWidthChange?.(width);
|
|
1353
1642
|
}, [dragging]);
|
|
@@ -1426,12 +1715,40 @@ function SplitWithRightRail({
|
|
|
1426
1715
|
] })
|
|
1427
1716
|
);
|
|
1428
1717
|
}
|
|
1429
|
-
|
|
1718
|
+
function BootSpinner({ className }) {
|
|
1719
|
+
return /* @__PURE__ */ jsx(
|
|
1720
|
+
"span",
|
|
1721
|
+
{
|
|
1722
|
+
"aria-hidden": true,
|
|
1723
|
+
className: cn(
|
|
1724
|
+
"block h-7 w-7 animate-spin rounded-full border-2 border-foreground/20 border-t-foreground/70",
|
|
1725
|
+
"motion-reduce:animate-none motion-reduce:border-t-foreground/20 motion-reduce:animate-pulse",
|
|
1726
|
+
className
|
|
1727
|
+
)
|
|
1728
|
+
}
|
|
1729
|
+
);
|
|
1730
|
+
}
|
|
1731
|
+
function AppShellSkeleton({ label, className }) {
|
|
1732
|
+
return /* @__PURE__ */ jsxs(
|
|
1733
|
+
"div",
|
|
1734
|
+
{
|
|
1735
|
+
role: "status",
|
|
1736
|
+
"aria-busy": "true",
|
|
1737
|
+
className: cn("flex h-screen items-center justify-center overflow-hidden bg-background", className),
|
|
1738
|
+
children: [
|
|
1739
|
+
/* @__PURE__ */ jsx("span", { className: "sr-only", children: label }),
|
|
1740
|
+
/* @__PURE__ */ jsx(BootSpinner, {})
|
|
1741
|
+
]
|
|
1742
|
+
}
|
|
1743
|
+
);
|
|
1744
|
+
}
|
|
1745
|
+
AppShellSkeleton.displayName = "AppShellSkeleton";
|
|
1746
|
+
var NavTransitionContext = React.createContext("slide");
|
|
1430
1747
|
function NavTransitionProvider({ value, children }) {
|
|
1431
1748
|
return /* @__PURE__ */ jsx(NavTransitionContext.Provider, { value, children });
|
|
1432
1749
|
}
|
|
1433
1750
|
function useNavTransition() {
|
|
1434
|
-
return
|
|
1751
|
+
return React.useContext(NavTransitionContext);
|
|
1435
1752
|
}
|
|
1436
1753
|
function resolveTransitionClass(transition, direction) {
|
|
1437
1754
|
if (transition === "none") return "";
|
|
@@ -1460,15 +1777,15 @@ function PremiumBadge({ className }) {
|
|
|
1460
1777
|
const tip = t("upgrade.badgeTooltip");
|
|
1461
1778
|
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
1779
|
}
|
|
1463
|
-
var ModuleLayoutContext =
|
|
1780
|
+
var ModuleLayoutContext = React.createContext("rail");
|
|
1464
1781
|
function ModuleLayoutProvider({ variant, children }) {
|
|
1465
1782
|
return /* @__PURE__ */ jsx(ModuleLayoutContext.Provider, { value: variant, children });
|
|
1466
1783
|
}
|
|
1467
1784
|
function useModuleLayout() {
|
|
1468
|
-
return
|
|
1785
|
+
return React.useContext(ModuleLayoutContext);
|
|
1469
1786
|
}
|
|
1470
1787
|
function NavItem2({ item }) {
|
|
1471
|
-
const [expanded, setExpanded] =
|
|
1788
|
+
const [expanded, setExpanded] = React.useState(true);
|
|
1472
1789
|
const Icon = item.icon ? ICON_MAP2[item.icon] ?? null : null;
|
|
1473
1790
|
const hasChildren = item.children && item.children.length > 0;
|
|
1474
1791
|
const hasActiveChild = hasChildren && item.children.some((c) => c.active);
|
|
@@ -1510,9 +1827,9 @@ function NavItem2({ item }) {
|
|
|
1510
1827
|
] });
|
|
1511
1828
|
}
|
|
1512
1829
|
function MobileTabs({ nav }) {
|
|
1513
|
-
const scrollRef =
|
|
1514
|
-
const [expandedId, setExpandedId] =
|
|
1515
|
-
|
|
1830
|
+
const scrollRef = React.useRef(null);
|
|
1831
|
+
const [expandedId, setExpandedId] = React.useState(null);
|
|
1832
|
+
React.useEffect(() => {
|
|
1516
1833
|
const container = scrollRef.current;
|
|
1517
1834
|
if (!container) return;
|
|
1518
1835
|
const activeEl = container.querySelector('[data-active="true"]');
|
|
@@ -1579,20 +1896,31 @@ function MobileTabs({ nav }) {
|
|
|
1579
1896
|
})()
|
|
1580
1897
|
] });
|
|
1581
1898
|
}
|
|
1582
|
-
var BackStyleContext =
|
|
1899
|
+
var BackStyleContext = React.createContext("link");
|
|
1583
1900
|
function BackStyleProvider({ style, children }) {
|
|
1584
1901
|
return /* @__PURE__ */ jsx(BackStyleContext.Provider, { value: style, children });
|
|
1585
1902
|
}
|
|
1586
1903
|
function useBackStyle() {
|
|
1587
|
-
return
|
|
1904
|
+
return React.useContext(BackStyleContext);
|
|
1588
1905
|
}
|
|
1589
|
-
function SubpageHeader({ title, subtitle, icon, onBack, parentLabel, actions, backStyle }) {
|
|
1906
|
+
function SubpageHeader({ title, subtitle, icon, onBack, parentLabel, actions, backStyle, inline }) {
|
|
1590
1907
|
const t = useTranslation();
|
|
1591
1908
|
const appStyle = useBackStyle();
|
|
1592
|
-
const hasShellBar = useHasPageHeaderHost();
|
|
1593
|
-
const
|
|
1909
|
+
const hasShellBar = useHasPageHeaderHost() && !inline;
|
|
1910
|
+
const cross = useCrossModuleBack(onBack, parentLabel);
|
|
1911
|
+
const effectiveOnBack = inline ? onBack : cross.onBack;
|
|
1912
|
+
const effectiveParentLabel = inline ? parentLabel : cross.parentLabel;
|
|
1594
1913
|
useBackHandler(effectiveOnBack);
|
|
1595
1914
|
const Icon = icon ? ICON_MAP2[icon] ?? null : null;
|
|
1915
|
+
if (inline && effectiveOnBack) {
|
|
1916
|
+
return /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
|
|
1917
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center justify-between gap-3", children: [
|
|
1918
|
+
/* @__PURE__ */ jsx(PageHeaderCrumb, { title, parentLabel: effectiveParentLabel, onBack: effectiveOnBack }),
|
|
1919
|
+
actions && /* @__PURE__ */ jsx("div", { className: "no-print", children: actions })
|
|
1920
|
+
] }),
|
|
1921
|
+
subtitle && /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: subtitle })
|
|
1922
|
+
] });
|
|
1923
|
+
}
|
|
1596
1924
|
if (hasShellBar) {
|
|
1597
1925
|
return /* @__PURE__ */ jsx(
|
|
1598
1926
|
ShellSubpageHeader,
|
|
@@ -1739,7 +2067,36 @@ function ModulePage({ title, subtitle, nav, children, className, headerAction, s
|
|
|
1739
2067
|
] })
|
|
1740
2068
|
] });
|
|
1741
2069
|
}
|
|
1742
|
-
var
|
|
2070
|
+
var EMPTY = [];
|
|
2071
|
+
var PageTrailContext = React.createContext(EMPTY);
|
|
2072
|
+
function PageTrail({
|
|
2073
|
+
label,
|
|
2074
|
+
href,
|
|
2075
|
+
onNavigate,
|
|
2076
|
+
children
|
|
2077
|
+
}) {
|
|
2078
|
+
const parent = React.useContext(PageTrailContext);
|
|
2079
|
+
const trail = React.useMemo(
|
|
2080
|
+
() => [...parent, { label, href, onNavigate }],
|
|
2081
|
+
[parent, label, href, onNavigate]
|
|
2082
|
+
);
|
|
2083
|
+
return /* @__PURE__ */ jsx(PageTrailContext.Provider, { value: trail, children });
|
|
2084
|
+
}
|
|
2085
|
+
function usePageTrail() {
|
|
2086
|
+
return React.useContext(PageTrailContext);
|
|
2087
|
+
}
|
|
2088
|
+
function usePageTrailParent(href) {
|
|
2089
|
+
const trail = React.useContext(PageTrailContext);
|
|
2090
|
+
return React.useMemo(() => {
|
|
2091
|
+
const above = href ? trail.filter((c) => c.href !== href) : trail;
|
|
2092
|
+
return above[above.length - 1];
|
|
2093
|
+
}, [trail, href]);
|
|
2094
|
+
}
|
|
2095
|
+
function navigateToCrumb(crumb) {
|
|
2096
|
+
if (crumb.onNavigate) crumb.onNavigate();
|
|
2097
|
+
else window.location.hash = crumb.href;
|
|
2098
|
+
}
|
|
2099
|
+
var ContentSplitContext = React.createContext(null);
|
|
1743
2100
|
function ContentSplit({
|
|
1744
2101
|
children,
|
|
1745
2102
|
aside,
|
|
@@ -1752,10 +2109,10 @@ function ContentSplit({
|
|
|
1752
2109
|
defaultCollapsed = false,
|
|
1753
2110
|
className
|
|
1754
2111
|
}) {
|
|
1755
|
-
const [internalOpen, setInternalOpen] =
|
|
2112
|
+
const [internalOpen, setInternalOpen] = React.useState(false);
|
|
1756
2113
|
const open = asideOpen ?? internalOpen;
|
|
1757
2114
|
const setOpen = onAsideOpenChange ?? setInternalOpen;
|
|
1758
|
-
const [collapsed, setCollapsed] =
|
|
2115
|
+
const [collapsed, setCollapsed] = React.useState(collapsible && defaultCollapsed);
|
|
1759
2116
|
return /* @__PURE__ */ jsxs(ContentSplitContext.Provider, { value: { open, setOpen }, children: [
|
|
1760
2117
|
/* @__PURE__ */ jsxs(
|
|
1761
2118
|
"div",
|
|
@@ -1807,7 +2164,7 @@ function ContentSplit({
|
|
|
1807
2164
|
] });
|
|
1808
2165
|
}
|
|
1809
2166
|
function ContentSplitTrigger({ label, iconOnly, className }) {
|
|
1810
|
-
const ctx =
|
|
2167
|
+
const ctx = React.useContext(ContentSplitContext);
|
|
1811
2168
|
if (!ctx) return null;
|
|
1812
2169
|
return /* @__PURE__ */ jsxs(
|
|
1813
2170
|
Button,
|
|
@@ -1825,6 +2182,6 @@ function ContentSplitTrigger({ label, iconOnly, className }) {
|
|
|
1825
2182
|
);
|
|
1826
2183
|
}
|
|
1827
2184
|
|
|
1828
|
-
export { AppShell, BackStyleProvider, ContentSplit, ContentSplitTrigger, ModuleLayoutProvider, ModulePage, NavTransitionProvider, PageHeaderActions, PageTransition, Sidebar, SubpageHeader, Topbar,
|
|
1829
|
-
//# sourceMappingURL=chunk-
|
|
1830
|
-
//# sourceMappingURL=chunk-
|
|
2185
|
+
export { AppShell, AppShellSkeleton, BackStyleProvider, ContentSplit, ContentSplitTrigger, ModuleLayoutProvider, ModulePage, NavTransitionProvider, PageHeaderActions, PageTrail, PageTransition, Sidebar, SubpageHeader, Topbar, navigateToCrumb, useBackStyle, useModuleHeaderActionsSlot, useModuleHeaderSlot, useModuleLayout, useNavTransition, usePageTrail, usePageTrailParent };
|
|
2186
|
+
//# sourceMappingURL=chunk-ZDRHCKRK.js.map
|
|
2187
|
+
//# sourceMappingURL=chunk-ZDRHCKRK.js.map
|