@fayz-ai/ui 0.10.1 → 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.
- 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/index.d.ts +2 -0
- package/dist/analytics/index.d.ts.map +1 -1
- 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-H63TOSQ7.js → chunk-QSHA2Q27.js} +475 -134
- package/dist/chunk-QSHA2Q27.js.map +1 -0
- package/dist/{chunk-SEIY2F5F.js → chunk-XRDCYOND.js} +231 -41
- package/dist/chunk-XRDCYOND.js.map +1 -0
- package/dist/chunk-ZBQ772DU.js +17 -0
- package/dist/chunk-ZBQ772DU.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 +234 -115
- 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 +4 -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/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 +111 -1
- package/dist/tailwind.cjs +7 -2
- package/dist/tailwind.cjs.map +1 -1
- package/dist/tailwind.d.ts.map +1 -1
- package/dist/tailwind.js +7 -2
- package/dist/tailwind.js.map +1 -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,38 +1,256 @@
|
|
|
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
|
"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
|
-
|
|
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;
|
|
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] =
|
|
313
|
+
const [open, setOpen] = React.useState(() => isChildActive ?? false);
|
|
104
314
|
const Icon = getIcon(item.icon);
|
|
105
315
|
if (collapsed) {
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
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(
|
|
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
|
|
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
|
-
|
|
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
|
-
] })
|
|
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] =
|
|
289
|
-
const lastBodyRef =
|
|
290
|
-
const lastKeyRef =
|
|
291
|
-
const timerRef =
|
|
292
|
-
|
|
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
|
-
|
|
538
|
+
React.useEffect(() => () => {
|
|
305
539
|
if (timerRef.current) clearTimeout(timerRef.current);
|
|
306
540
|
}, []);
|
|
307
|
-
return /* @__PURE__ */
|
|
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
|
-
|
|
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
|
-
|
|
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] =
|
|
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] =
|
|
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 =
|
|
1058
|
+
var ModuleHeaderSlotContext = React.createContext(null);
|
|
796
1059
|
function useModuleHeaderSlot() {
|
|
797
|
-
return
|
|
1060
|
+
return React.useContext(ModuleHeaderSlotContext);
|
|
798
1061
|
}
|
|
799
|
-
var ModuleHeaderActionsSlotContext =
|
|
1062
|
+
var ModuleHeaderActionsSlotContext = React.createContext(null);
|
|
800
1063
|
function useModuleHeaderActionsSlot() {
|
|
801
|
-
return
|
|
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] =
|
|
845
|
-
const [headerSlot, setHeaderSlot] =
|
|
846
|
-
const [actionsSlot, setActionsSlot] =
|
|
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] =
|
|
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] =
|
|
1035
|
-
|
|
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 =
|
|
1308
|
+
const close = React.useCallback(() => {
|
|
1040
1309
|
setVisible(false);
|
|
1041
1310
|
window.setTimeout(onClose, 250);
|
|
1042
1311
|
}, [onClose]);
|
|
1043
|
-
|
|
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
|
-
|
|
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] =
|
|
1323
|
-
const [dragging, setDragging] =
|
|
1324
|
-
const clamp =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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
|
|
1769
|
+
return React.useContext(ModuleLayoutContext);
|
|
1469
1770
|
}
|
|
1470
1771
|
function NavItem2({ item }) {
|
|
1471
|
-
const [expanded, setExpanded] =
|
|
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 =
|
|
1514
|
-
const [expandedId, setExpandedId] =
|
|
1515
|
-
|
|
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 =
|
|
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
|
|
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
|
|
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
|
|
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] =
|
|
2096
|
+
const [internalOpen, setInternalOpen] = React.useState(false);
|
|
1756
2097
|
const open = asideOpen ?? internalOpen;
|
|
1757
2098
|
const setOpen = onAsideOpenChange ?? setInternalOpen;
|
|
1758
|
-
const [collapsed, setCollapsed] =
|
|
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 =
|
|
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,
|
|
1829
|
-
//# sourceMappingURL=chunk-
|
|
1830
|
-
//# sourceMappingURL=chunk-
|
|
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
|