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