@fayz-ai/ui 0.22.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-7G3O54CC.js → chunk-L4TXDKMN.js} +5 -5
- package/dist/chunk-L4TXDKMN.js.map +1 -0
- package/dist/{chunk-DHNUZF2X.js → chunk-OWN3GFDG.js} +4 -3
- package/dist/chunk-OWN3GFDG.js.map +1 -0
- package/dist/{chunk-PQUS22JE.js → chunk-RRNMNFLF.js} +483 -794
- 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-ZSOT7KTE.js → chunk-XEUIYSW2.js} +321 -361
- package/dist/chunk-XEUIYSW2.js.map +1 -0
- package/dist/{chunk-SUSW4LSW.js → chunk-XKFYX3DG.js} +5 -5
- package/dist/{chunk-SUSW4LSW.js.map → chunk-XKFYX3DG.js.map} +1 -1
- package/dist/dashboard/index.js +4 -4
- package/dist/icons.js +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +672 -87
- 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/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.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/segmented-control.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 +58 -0
- package/package.json +2 -2
- package/dist/chunk-7G3O54CC.js.map +0 -1
- package/dist/chunk-DHNUZF2X.js.map +0 -1
- package/dist/chunk-E44WOPCR.js.map +0 -1
- package/dist/chunk-HKGTF2YA.js +0 -117
- package/dist/chunk-HKGTF2YA.js.map +0 -1
- package/dist/chunk-PQUS22JE.js.map +0 -1
- package/dist/chunk-ZSOT7KTE.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);
|
|
@@ -290,6 +171,98 @@ function SaveBar({ className }) {
|
|
|
290
171
|
}
|
|
291
172
|
);
|
|
292
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
|
+
}
|
|
293
266
|
function useCrossModuleBack(onBack, parentLabel) {
|
|
294
267
|
const currentPath = typeof window !== "undefined" ? (window.location.hash ? window.location.hash.slice(1) : window.location.pathname) || "/" : "/";
|
|
295
268
|
const referrer = useNavReferrer(currentPath);
|
|
@@ -344,7 +317,7 @@ function PageHeader({ title, onBack, parentLabel, backLabel }) {
|
|
|
344
317
|
const hasHost = useHasPageHeaderHost();
|
|
345
318
|
const { onBack: effectiveOnBack, parentLabel: effectiveParent } = useCrossModuleBack(onBack, parentLabel);
|
|
346
319
|
useBackHandler(effectiveOnBack);
|
|
347
|
-
const node =
|
|
320
|
+
const node = React.useMemo(() => /* @__PURE__ */ jsx(
|
|
348
321
|
PageHeaderCrumb,
|
|
349
322
|
{
|
|
350
323
|
title,
|
|
@@ -356,17 +329,154 @@ function PageHeader({ title, onBack, parentLabel, backLabel }) {
|
|
|
356
329
|
if (hasHost) return /* @__PURE__ */ jsx(PageTitlePortal, { children: node });
|
|
357
330
|
return /* @__PURE__ */ jsx("div", { className: "mb-4", children: node });
|
|
358
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
|
+
}
|
|
359
469
|
var GUTTER_PX = 8;
|
|
360
470
|
var CLOSE_DELAY_MS = 140;
|
|
361
|
-
var useIsomorphicLayoutEffect = typeof window !== "undefined" ?
|
|
471
|
+
var useIsomorphicLayoutEffect = typeof window !== "undefined" ? React.useLayoutEffect : React.useEffect;
|
|
362
472
|
function iconOf(name) {
|
|
363
473
|
if (!name) return null;
|
|
364
474
|
return ICON_MAP[name] ?? null;
|
|
365
475
|
}
|
|
366
|
-
var RailFlyoutContext =
|
|
476
|
+
var RailFlyoutContext = React.createContext(null);
|
|
367
477
|
var RailFlyoutProvider = RailFlyoutContext.Provider;
|
|
368
478
|
function useRailRow(content) {
|
|
369
|
-
const flyout =
|
|
479
|
+
const flyout = React.useContext(RailFlyoutContext);
|
|
370
480
|
if (!flyout || !content) return {};
|
|
371
481
|
return flyout.rowProps(content);
|
|
372
482
|
}
|
|
@@ -376,19 +486,19 @@ function useRailFlyout({
|
|
|
376
486
|
activeRoute,
|
|
377
487
|
onNavigate
|
|
378
488
|
}) {
|
|
379
|
-
const [state, setState] =
|
|
380
|
-
const [pos, setPos] =
|
|
381
|
-
const [animate, setAnimate] =
|
|
382
|
-
const panelRef =
|
|
383
|
-
const closeTimer =
|
|
384
|
-
const lastContent =
|
|
385
|
-
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(() => {
|
|
386
496
|
if (closeTimer.current) {
|
|
387
497
|
clearTimeout(closeTimer.current);
|
|
388
498
|
closeTimer.current = null;
|
|
389
499
|
}
|
|
390
500
|
}, []);
|
|
391
|
-
const show =
|
|
501
|
+
const show = React.useCallback(
|
|
392
502
|
(anchor, content2) => {
|
|
393
503
|
if (!enabled) return;
|
|
394
504
|
cancelClose();
|
|
@@ -396,19 +506,19 @@ function useRailFlyout({
|
|
|
396
506
|
},
|
|
397
507
|
[enabled, cancelClose]
|
|
398
508
|
);
|
|
399
|
-
const hide =
|
|
509
|
+
const hide = React.useCallback(() => {
|
|
400
510
|
cancelClose();
|
|
401
511
|
closeTimer.current = setTimeout(() => setState(null), CLOSE_DELAY_MS);
|
|
402
512
|
}, [cancelClose]);
|
|
403
|
-
const dismiss =
|
|
513
|
+
const dismiss = React.useCallback(() => {
|
|
404
514
|
cancelClose();
|
|
405
515
|
setState(null);
|
|
406
516
|
}, [cancelClose]);
|
|
407
|
-
|
|
408
|
-
|
|
517
|
+
React.useEffect(() => () => cancelClose(), [cancelClose]);
|
|
518
|
+
React.useEffect(() => {
|
|
409
519
|
if (!enabled) dismiss();
|
|
410
520
|
}, [enabled, dismiss]);
|
|
411
|
-
const reposition =
|
|
521
|
+
const reposition = React.useCallback(() => {
|
|
412
522
|
const rail = railRef.current;
|
|
413
523
|
const anchor = state?.anchor;
|
|
414
524
|
if (!rail || !anchor) return;
|
|
@@ -423,7 +533,7 @@ function useRailFlyout({
|
|
|
423
533
|
setPos({ top, arrow });
|
|
424
534
|
}, [railRef, state]);
|
|
425
535
|
useIsomorphicLayoutEffect(reposition, [reposition, state?.content.key]);
|
|
426
|
-
|
|
536
|
+
React.useEffect(() => {
|
|
427
537
|
if (!state) return;
|
|
428
538
|
const rail = railRef.current;
|
|
429
539
|
const onScroll = () => reposition();
|
|
@@ -435,7 +545,7 @@ function useRailFlyout({
|
|
|
435
545
|
};
|
|
436
546
|
}, [state, railRef, reposition]);
|
|
437
547
|
const visible = !!state;
|
|
438
|
-
|
|
548
|
+
React.useEffect(() => {
|
|
439
549
|
if (!visible) {
|
|
440
550
|
setAnimate(false);
|
|
441
551
|
return;
|
|
@@ -443,7 +553,7 @@ function useRailFlyout({
|
|
|
443
553
|
const id = requestAnimationFrame(() => setAnimate(true));
|
|
444
554
|
return () => cancelAnimationFrame(id);
|
|
445
555
|
}, [visible]);
|
|
446
|
-
|
|
556
|
+
React.useEffect(() => {
|
|
447
557
|
if (!visible) return;
|
|
448
558
|
const onKey = (event) => {
|
|
449
559
|
if (event.key === "Escape") dismiss();
|
|
@@ -451,7 +561,7 @@ function useRailFlyout({
|
|
|
451
561
|
window.addEventListener("keydown", onKey);
|
|
452
562
|
return () => window.removeEventListener("keydown", onKey);
|
|
453
563
|
}, [visible, dismiss]);
|
|
454
|
-
const rowProps =
|
|
564
|
+
const rowProps = React.useCallback(
|
|
455
565
|
(content2) => ({
|
|
456
566
|
onMouseEnter: (event) => show(event.currentTarget, content2),
|
|
457
567
|
onFocus: (event) => show(event.currentTarget, content2),
|
|
@@ -568,14 +678,10 @@ function FlyoutBody({
|
|
|
568
678
|
] });
|
|
569
679
|
}
|
|
570
680
|
var RAIL_PUSH_MS = 260;
|
|
571
|
-
var useIsomorphicLayoutEffect2 = typeof window !== "undefined" ?
|
|
681
|
+
var useIsomorphicLayoutEffect2 = typeof window !== "undefined" ? React.useLayoutEffect : React.useEffect;
|
|
572
682
|
function getIcon(name) {
|
|
573
683
|
return ICON_MAP[name] ?? Home;
|
|
574
684
|
}
|
|
575
|
-
function shortcutKey() {
|
|
576
|
-
if (typeof navigator === "undefined") return "\u2318";
|
|
577
|
-
return /Mac|iPhone|iPad/u.test(navigator.platform || navigator.userAgent) ? "\u2318" : "Ctrl ";
|
|
578
|
-
}
|
|
579
685
|
function flyoutContentOf(item) {
|
|
580
686
|
return {
|
|
581
687
|
key: item.id,
|
|
@@ -670,7 +776,7 @@ function NavGroup({
|
|
|
670
776
|
flyout
|
|
671
777
|
}) {
|
|
672
778
|
const isChildActive = item.children?.some((c) => c.route === activeRoute);
|
|
673
|
-
const [open, setOpen] =
|
|
779
|
+
const [open, setOpen] = React.useState(() => isChildActive ?? false);
|
|
674
780
|
const Icon = getIcon(item.icon);
|
|
675
781
|
if (collapsed) {
|
|
676
782
|
const hoverProps = flyout?.rowProps(flyoutContentOf(item));
|
|
@@ -751,7 +857,7 @@ function NavGroup({
|
|
|
751
857
|
// case in an app that groups its rail).
|
|
752
858
|
/* @__PURE__ */ jsx("button", { "data-tour": `nav.${child.route}`, onClick: () => onNavigate(child.route), className: childClass, children: childInner })
|
|
753
859
|
);
|
|
754
|
-
if (!childConnector) return /* @__PURE__ */ jsx(
|
|
860
|
+
if (!childConnector) return /* @__PURE__ */ jsx(React.Fragment, { children: row }, child.id);
|
|
755
861
|
const activeIndex = item.children.findIndex((c) => c.route === activeRoute);
|
|
756
862
|
const onPath = activeIndex >= 0 && childIndex < activeIndex;
|
|
757
863
|
return (
|
|
@@ -813,12 +919,24 @@ function NavGroup({
|
|
|
813
919
|
] });
|
|
814
920
|
}
|
|
815
921
|
function withCollapsed(slot, collapsed) {
|
|
816
|
-
if (!
|
|
922
|
+
if (!React.isValidElement(slot)) return slot;
|
|
817
923
|
if (typeof slot.type === "string") return slot;
|
|
818
|
-
return
|
|
819
|
-
}
|
|
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
|
+
];
|
|
820
933
|
function Sidebar({
|
|
821
934
|
navigation,
|
|
935
|
+
navLoading = false,
|
|
936
|
+
navError,
|
|
937
|
+
navErrorLabel,
|
|
938
|
+
navRetryLabel,
|
|
939
|
+
onNavRetry,
|
|
822
940
|
logo,
|
|
823
941
|
collapsed,
|
|
824
942
|
onToggle,
|
|
@@ -843,9 +961,8 @@ function Sidebar({
|
|
|
843
961
|
drawer
|
|
844
962
|
}) {
|
|
845
963
|
const currentPath = currentPathProp;
|
|
846
|
-
const setCommandPaletteOpen = useLayoutStore((s) => s.setCommandPaletteOpen);
|
|
847
964
|
const activeRoute = resolveActiveRoute(navigation, currentPath);
|
|
848
|
-
const railRef =
|
|
965
|
+
const railRef = React.useRef(null);
|
|
849
966
|
const flyout = useRailFlyout({
|
|
850
967
|
enabled: collapsed,
|
|
851
968
|
railRef,
|
|
@@ -894,10 +1011,10 @@ function Sidebar({
|
|
|
894
1011
|
mainItems.length > 0 && renderSection(mainItems, sectionLabels?.main),
|
|
895
1012
|
secondaryItems.length > 0 && /* @__PURE__ */ jsx("div", { className: "mt-6", children: renderSection(secondaryItems, sectionLabels?.secondary) })
|
|
896
1013
|
] });
|
|
897
|
-
const [outgoing, setOutgoing] =
|
|
898
|
-
const lastBodyRef =
|
|
899
|
-
const lastKeyRef =
|
|
900
|
-
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);
|
|
901
1018
|
useIsomorphicLayoutEffect2(() => {
|
|
902
1019
|
if (lastKeyRef.current !== navKey) {
|
|
903
1020
|
const previousBody = lastBodyRef.current;
|
|
@@ -910,7 +1027,7 @@ function Sidebar({
|
|
|
910
1027
|
}
|
|
911
1028
|
lastBodyRef.current = navBody;
|
|
912
1029
|
});
|
|
913
|
-
|
|
1030
|
+
React.useEffect(() => () => {
|
|
914
1031
|
if (timerRef.current) clearTimeout(timerRef.current);
|
|
915
1032
|
}, []);
|
|
916
1033
|
return (
|
|
@@ -935,8 +1052,10 @@ function Sidebar({
|
|
|
935
1052
|
// paid for by the content column on every screen in every app. The
|
|
936
1053
|
// labels still truncate, and a long one now truncates 32px earlier —
|
|
937
1054
|
// which is the trade, taken deliberately.
|
|
938
|
-
|
|
1055
|
+
// A largura da GAVETA vai em `style`, logo abaixo — ver o porquê lá.
|
|
1056
|
+
collapsed ? "w-16" : !drawer && "w-56"
|
|
939
1057
|
),
|
|
1058
|
+
style: drawer && !collapsed ? { width: "min(18rem, 85vw)" } : void 0,
|
|
940
1059
|
children: [
|
|
941
1060
|
/* @__PURE__ */ jsxs("div", { className: cn("shrink-0", !borderless && "border-b border-sidebar-border"), children: [
|
|
942
1061
|
/* @__PURE__ */ jsxs(
|
|
@@ -948,7 +1067,13 @@ function Sidebar({
|
|
|
948
1067
|
// h-14 quando a marca é a única coisa aqui e a linha tem de bater
|
|
949
1068
|
// com o cabeçalho da página.
|
|
950
1069
|
identity ? "h-12" : "h-14",
|
|
951
|
-
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"
|
|
952
1077
|
),
|
|
953
1078
|
children: [
|
|
954
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" }) }),
|
|
@@ -956,60 +1081,68 @@ function Sidebar({
|
|
|
956
1081
|
"button",
|
|
957
1082
|
{
|
|
958
1083
|
onClick: onToggle,
|
|
959
|
-
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",
|
|
960
1085
|
"aria-label": "Close menu",
|
|
961
|
-
children: /* @__PURE__ */ jsx(X, { className: "h-
|
|
1086
|
+
children: /* @__PURE__ */ jsx(X, { className: "h-5 w-5" })
|
|
962
1087
|
}
|
|
963
1088
|
)
|
|
964
1089
|
]
|
|
965
1090
|
}
|
|
966
1091
|
),
|
|
967
|
-
identity &&
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
/*
|
|
971
|
-
|
|
972
|
-
something nobody is meant to find: the fastest way through an admin
|
|
973
|
-
with thirty screens was the one control shaped like an afterthought.
|
|
974
|
-
Here it reads as what it is — a field — and says which key opens it.
|
|
975
|
-
Clicking it opens the same palette ⌘K does; it is a button, not an
|
|
976
|
-
input, because typing happens there. */
|
|
977
|
-
/* @__PURE__ */ jsx("div", { className: cn("px-2 pb-2 pt-2", collapsed && "flex justify-center"), children: /* @__PURE__ */ jsxs(
|
|
978
|
-
"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",
|
|
979
1097
|
{
|
|
980
|
-
onClick: () => setCommandPaletteOpen(true),
|
|
981
|
-
"aria-keyshortcuts": "Meta+K Control+K",
|
|
982
1098
|
className: cn(
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
// leaves the outline treatment free to mean focus, which it must.
|
|
987
|
-
"fz-tap group flex items-center rounded-lg bg-sidebar-accent/40 text-sidebar-muted transition-colors",
|
|
988
|
-
"hover:bg-sidebar-accent/70 hover:text-sidebar-foreground",
|
|
989
|
-
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-sidebar-accent-foreground/50",
|
|
990
|
-
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"
|
|
991
1102
|
),
|
|
992
|
-
|
|
993
|
-
title: collapsed ? searchLabel : void 0,
|
|
994
|
-
children: [
|
|
995
|
-
/* @__PURE__ */ jsx(Search, { className: "h-4 w-4 shrink-0" }),
|
|
996
|
-
!collapsed && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
997
|
-
/* @__PURE__ */ jsx("span", { className: "flex-1 truncate text-left text-[13px]", children: searchLabel }),
|
|
998
|
-
/* @__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: [
|
|
999
|
-
shortcutKey(),
|
|
1000
|
-
"K"
|
|
1001
|
-
] })
|
|
1002
|
-
] })
|
|
1003
|
-
]
|
|
1103
|
+
children: withCollapsed(identity, collapsed)
|
|
1004
1104
|
}
|
|
1005
|
-
)
|
|
1006
|
-
),
|
|
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 }) }),
|
|
1007
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
|
+
),
|
|
1008
1137
|
/* @__PURE__ */ jsxs(
|
|
1009
1138
|
"nav",
|
|
1010
1139
|
{
|
|
1140
|
+
hidden: navLoading || !!navError,
|
|
1011
1141
|
className: cn(
|
|
1012
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",
|
|
1013
1146
|
// scrollbar-none: the rail still scrolls, but without the native
|
|
1014
1147
|
// scrollbar gutter eating into the nav items.
|
|
1015
1148
|
outgoing ? "overflow-hidden" : "overflow-y-auto scrollbar-none"
|
|
@@ -1112,7 +1245,7 @@ function TopbarUserMenu({
|
|
|
1112
1245
|
onBilling,
|
|
1113
1246
|
userMenuExtras
|
|
1114
1247
|
}) {
|
|
1115
|
-
const [open, setOpen] =
|
|
1248
|
+
const [open, setOpen] = React.useState(false);
|
|
1116
1249
|
if (!user) return null;
|
|
1117
1250
|
const initial = (user.fullName?.trim()?.[0] ?? user.email?.[0] ?? "?").toUpperCase();
|
|
1118
1251
|
const item = (label, onClick, Icon) => onClick ? /* @__PURE__ */ jsxs(
|
|
@@ -1196,7 +1329,7 @@ function getIcon2(name) {
|
|
|
1196
1329
|
return ICON_MAP[name] ?? Home;
|
|
1197
1330
|
}
|
|
1198
1331
|
function NavDropdown({ item, currentPath, onNavigate }) {
|
|
1199
|
-
const [open, setOpen] =
|
|
1332
|
+
const [open, setOpen] = React.useState(false);
|
|
1200
1333
|
const Icon = getIcon2(item.icon);
|
|
1201
1334
|
const isChildActive = item.children?.some(
|
|
1202
1335
|
(child) => currentPath === child.route || currentPath.startsWith(child.route + "/")
|
|
@@ -1363,17 +1496,17 @@ function Topbar({
|
|
|
1363
1496
|
}) }) }) })
|
|
1364
1497
|
] });
|
|
1365
1498
|
}
|
|
1366
|
-
var ModuleHeaderSlotContext =
|
|
1499
|
+
var ModuleHeaderSlotContext = React.createContext(null);
|
|
1367
1500
|
function useModuleHeaderSlot() {
|
|
1368
|
-
return
|
|
1501
|
+
return React.useContext(ModuleHeaderSlotContext);
|
|
1369
1502
|
}
|
|
1370
|
-
var ModuleHeaderActionsSlotContext =
|
|
1503
|
+
var ModuleHeaderActionsSlotContext = React.createContext(null);
|
|
1371
1504
|
function useModuleHeaderActionsSlot() {
|
|
1372
|
-
return
|
|
1505
|
+
return React.useContext(ModuleHeaderActionsSlotContext);
|
|
1373
1506
|
}
|
|
1374
|
-
var PageRailSlotContext =
|
|
1507
|
+
var PageRailSlotContext = React.createContext(null);
|
|
1375
1508
|
function usePageRailSlot() {
|
|
1376
|
-
return
|
|
1509
|
+
return React.useContext(PageRailSlotContext);
|
|
1377
1510
|
}
|
|
1378
1511
|
function bottomNavReachesAll(items) {
|
|
1379
1512
|
return !!items?.some((i) => "kind" in i && i.kind === "more");
|
|
@@ -1387,6 +1520,11 @@ function ShellPageTitle({
|
|
|
1387
1520
|
}
|
|
1388
1521
|
function SidebarLayout({
|
|
1389
1522
|
navigation = [],
|
|
1523
|
+
navLoading = false,
|
|
1524
|
+
navError,
|
|
1525
|
+
navErrorLabel,
|
|
1526
|
+
navRetryLabel,
|
|
1527
|
+
onNavRetry,
|
|
1390
1528
|
logo,
|
|
1391
1529
|
user,
|
|
1392
1530
|
children,
|
|
@@ -1429,9 +1567,9 @@ function SidebarLayout({
|
|
|
1429
1567
|
const setSidebarCollapsed = useLayoutStore((s) => s.setSidebarCollapsed);
|
|
1430
1568
|
const mobileMenuOpen = useLayoutStore((st) => st.mobileDrawerOpen);
|
|
1431
1569
|
const setMobileMenuOpen = useLayoutStore((st) => st.setMobileDrawerOpen);
|
|
1432
|
-
const [headerSlot, setHeaderSlot] =
|
|
1433
|
-
const [actionsSlot, setActionsSlot] =
|
|
1434
|
-
const [pageRailSlot, setPageRailSlot] =
|
|
1570
|
+
const [headerSlot, setHeaderSlot] = React.useState(null);
|
|
1571
|
+
const [actionsSlot, setActionsSlot] = React.useState(null);
|
|
1572
|
+
const [pageRailSlot, setPageRailSlot] = React.useState(null);
|
|
1435
1573
|
const hasPageActions = useHasPageActions();
|
|
1436
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(
|
|
1437
1575
|
"shell-enter fz-viewport-h flex overflow-hidden bg-background",
|
|
@@ -1445,6 +1583,11 @@ function SidebarLayout({
|
|
|
1445
1583
|
Sidebar,
|
|
1446
1584
|
{
|
|
1447
1585
|
navigation,
|
|
1586
|
+
navLoading,
|
|
1587
|
+
navError,
|
|
1588
|
+
navErrorLabel,
|
|
1589
|
+
navRetryLabel,
|
|
1590
|
+
onNavRetry,
|
|
1448
1591
|
logo,
|
|
1449
1592
|
collapsed: sidebarCollapsed,
|
|
1450
1593
|
onToggle: () => setSidebarCollapsed(!sidebarCollapsed),
|
|
@@ -1472,55 +1615,73 @@ function SidebarLayout({
|
|
|
1472
1615
|
navChildConnector: sidebarNavChildConnector
|
|
1473
1616
|
}
|
|
1474
1617
|
) }),
|
|
1475
|
-
/* @__PURE__ */ jsxs("div", { className:
|
|
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
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
/* @__PURE__ */ jsx("div", { ref: setTitleSlot, className: "flex min-w-0 items-center empty:hidden" }),
|
|
1514
|
-
topbarStart,
|
|
1515
|
-
/* @__PURE__ */ jsx("div", { ref: setHeaderSlot, className: "flex min-w-0 flex-1 items-center" })
|
|
1516
|
-
] }),
|
|
1517
|
-
/* @__PURE__ */ jsxs("div", { className: "flex shrink-0 items-center gap-2", children: [
|
|
1518
|
-
/* @__PURE__ */ jsx("div", { ref: setActionsSlot, className: "flex items-center gap-2" }),
|
|
1519
|
-
topbarEnd
|
|
1520
|
-
] })
|
|
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" })
|
|
1521
1656
|
] }),
|
|
1522
|
-
/* @__PURE__ */
|
|
1523
|
-
|
|
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
|
+
] })
|
|
1524
1685
|
] })
|
|
1525
1686
|
] }),
|
|
1526
1687
|
mobileMenuOpen && /* @__PURE__ */ jsx(
|
|
@@ -1579,9 +1740,9 @@ function TopbarLayout({
|
|
|
1579
1740
|
}) {
|
|
1580
1741
|
const mobileMenuOpen = useLayoutStore((st) => st.mobileDrawerOpen);
|
|
1581
1742
|
const setMobileMenuOpen = useLayoutStore((st) => st.setMobileDrawerOpen);
|
|
1582
|
-
const [headerSlot, setHeaderSlot] =
|
|
1583
|
-
const [actionsSlot, setActionsSlot] =
|
|
1584
|
-
const [pageRailSlot, setPageRailSlot] =
|
|
1743
|
+
const [headerSlot, setHeaderSlot] = React.useState(null);
|
|
1744
|
+
const [actionsSlot, setActionsSlot] = React.useState(null);
|
|
1745
|
+
const [pageRailSlot, setPageRailSlot] = React.useState(null);
|
|
1585
1746
|
const rawClaimed = usePageHeaderClaimed();
|
|
1586
1747
|
const setTitleSlot = usePageTitleSlotHost();
|
|
1587
1748
|
const hasOutlet = useHasPageHeaderOutlet();
|
|
@@ -1616,7 +1777,7 @@ function TopbarLayout({
|
|
|
1616
1777
|
hideMobileChrome: barOwnsNav
|
|
1617
1778
|
}
|
|
1618
1779
|
),
|
|
1619
|
-
/* @__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: [
|
|
1620
1781
|
/* @__PURE__ */ jsx("div", { ref: setPageRailSlot, className: "flex shrink-0 empty:hidden" }),
|
|
1621
1782
|
/* @__PURE__ */ jsxs("div", { className: "flex min-w-0 min-h-0 flex-1 flex-col overflow-hidden", children: [
|
|
1622
1783
|
(pageHeaderClaimed || pageBack || hasPageActions || barOwnsNav) && /* @__PURE__ */ jsxs(
|
|
@@ -1632,7 +1793,7 @@ function TopbarLayout({
|
|
|
1632
1793
|
/* @__PURE__ */ jsx("div", { ref: setTitleSlot, className: "flex min-w-0 items-center empty:hidden" }),
|
|
1633
1794
|
/* @__PURE__ */ jsx("div", { ref: setHeaderSlot, className: "flex min-w-0 flex-1 items-center" })
|
|
1634
1795
|
] }),
|
|
1635
|
-
/* @__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)" } })
|
|
1636
1797
|
]
|
|
1637
1798
|
}
|
|
1638
1799
|
),
|
|
@@ -1677,30 +1838,30 @@ function MinimalLayout({
|
|
|
1677
1838
|
}) {
|
|
1678
1839
|
return /* @__PURE__ */ jsxs("div", { className: cn("shell-enter fz-viewport-h flex flex-col", hasBottomNav && "fz-has-bottom-bar"), children: [
|
|
1679
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: [
|
|
1680
|
-
/* @__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" }) }),
|
|
1681
1842
|
headerEnd && /* @__PURE__ */ jsx("div", { className: "flex items-center gap-2", children: headerEnd })
|
|
1682
1843
|
] }),
|
|
1683
1844
|
/* @__PURE__ */ jsx("main", { className: "min-w-0 flex-1 overflow-y-auto pb-16 md:pb-0", children })
|
|
1684
1845
|
] });
|
|
1685
1846
|
}
|
|
1686
1847
|
function MobileOverlay({ onClose, onNavigate, ...sidebarProps }) {
|
|
1687
|
-
const [visible, setVisible] =
|
|
1688
|
-
|
|
1848
|
+
const [visible, setVisible] = React.useState(false);
|
|
1849
|
+
React.useEffect(() => {
|
|
1689
1850
|
const id = requestAnimationFrame(() => setVisible(true));
|
|
1690
1851
|
return () => cancelAnimationFrame(id);
|
|
1691
1852
|
}, []);
|
|
1692
|
-
const close =
|
|
1853
|
+
const close = React.useCallback(() => {
|
|
1693
1854
|
setVisible(false);
|
|
1694
1855
|
window.setTimeout(onClose, 250);
|
|
1695
1856
|
}, [onClose]);
|
|
1696
|
-
|
|
1857
|
+
React.useEffect(() => {
|
|
1697
1858
|
const previous = document.body.style.overflow;
|
|
1698
1859
|
document.body.style.overflow = "hidden";
|
|
1699
1860
|
return () => {
|
|
1700
1861
|
document.body.style.overflow = previous;
|
|
1701
1862
|
};
|
|
1702
1863
|
}, []);
|
|
1703
|
-
|
|
1864
|
+
React.useEffect(() => {
|
|
1704
1865
|
const onKey = (e) => {
|
|
1705
1866
|
if (e.key === "Escape") close();
|
|
1706
1867
|
};
|
|
@@ -1729,6 +1890,7 @@ function MobileOverlay({ onClose, onNavigate, ...sidebarProps }) {
|
|
|
1729
1890
|
Sidebar,
|
|
1730
1891
|
{
|
|
1731
1892
|
...sidebarProps,
|
|
1893
|
+
navTop: sidebarProps.navTop ? /* @__PURE__ */ jsx("div", { onClickCapture: () => useLayoutStore.getState().keepDrawerOpenOnce(), children: sidebarProps.navTop }) : void 0,
|
|
1732
1894
|
collapsed: false,
|
|
1733
1895
|
onToggle: close,
|
|
1734
1896
|
drawer: true,
|
|
@@ -1848,6 +2010,13 @@ function AppShell({
|
|
|
1848
2010
|
variant,
|
|
1849
2011
|
contentFrame,
|
|
1850
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,
|
|
1851
2020
|
logo,
|
|
1852
2021
|
user,
|
|
1853
2022
|
children,
|
|
@@ -1899,7 +2068,11 @@ function AppShell({
|
|
|
1899
2068
|
const barRetracted = !!pageBack || pageClaimedBack;
|
|
1900
2069
|
const mobileDrawerOpen = useLayoutStore((st) => st.mobileDrawerOpen);
|
|
1901
2070
|
const setMobileDrawerOpen = useLayoutStore((st) => st.setMobileDrawerOpen);
|
|
1902
|
-
|
|
2071
|
+
React.useEffect(() => {
|
|
2072
|
+
if (useLayoutStore.getState().drawerKeepOpenOnce) {
|
|
2073
|
+
useLayoutStore.setState({ drawerKeepOpenOnce: false });
|
|
2074
|
+
return;
|
|
2075
|
+
}
|
|
1903
2076
|
setMobileDrawerOpen(false);
|
|
1904
2077
|
}, [currentPath]);
|
|
1905
2078
|
const bottomBar = hasBottomNav ? /* @__PURE__ */ jsx(
|
|
@@ -1924,6 +2097,11 @@ function AppShell({
|
|
|
1924
2097
|
SidebarLayout,
|
|
1925
2098
|
{
|
|
1926
2099
|
navigation,
|
|
2100
|
+
navLoading,
|
|
2101
|
+
navError,
|
|
2102
|
+
navErrorLabel,
|
|
2103
|
+
navRetryLabel,
|
|
2104
|
+
onNavRetry,
|
|
1927
2105
|
logo,
|
|
1928
2106
|
user,
|
|
1929
2107
|
onNavigate,
|
|
@@ -2029,13 +2207,13 @@ function SplitWithRightRail({
|
|
|
2029
2207
|
maxWidth = 720,
|
|
2030
2208
|
resizeLabel = "Resize panel"
|
|
2031
2209
|
}) {
|
|
2032
|
-
const [width, setWidth] =
|
|
2033
|
-
const [dragging, setDragging] =
|
|
2034
|
-
const clamp =
|
|
2210
|
+
const [width, setWidth] = React.useState(defaultWidth);
|
|
2211
|
+
const [dragging, setDragging] = React.useState(false);
|
|
2212
|
+
const clamp = React.useCallback(
|
|
2035
2213
|
(value) => Math.min(maxWidth, Math.max(minWidth, value)),
|
|
2036
2214
|
[minWidth, maxWidth]
|
|
2037
2215
|
);
|
|
2038
|
-
|
|
2216
|
+
React.useEffect(() => {
|
|
2039
2217
|
if (!dragging) return;
|
|
2040
2218
|
const onMove = (event) => {
|
|
2041
2219
|
event.preventDefault();
|
|
@@ -2057,7 +2235,7 @@ function SplitWithRightRail({
|
|
|
2057
2235
|
document.body.style.cursor = previousCursor;
|
|
2058
2236
|
};
|
|
2059
2237
|
}, [dragging, clamp]);
|
|
2060
|
-
|
|
2238
|
+
React.useEffect(() => {
|
|
2061
2239
|
if (dragging) return;
|
|
2062
2240
|
onWidthChange?.(width);
|
|
2063
2241
|
}, [dragging]);
|
|
@@ -2140,496 +2318,7 @@ function SplitWithRightRail({
|
|
|
2140
2318
|
] })
|
|
2141
2319
|
);
|
|
2142
2320
|
}
|
|
2143
|
-
function HeaderTabs({
|
|
2144
|
-
tabs,
|
|
2145
|
-
value,
|
|
2146
|
-
onChange,
|
|
2147
|
-
className,
|
|
2148
|
-
fill,
|
|
2149
|
-
anchorPrefix,
|
|
2150
|
-
...rest
|
|
2151
|
-
}) {
|
|
2152
|
-
return /* @__PURE__ */ jsx(
|
|
2153
|
-
"div",
|
|
2154
|
-
{
|
|
2155
|
-
role: "tablist",
|
|
2156
|
-
"aria-label": rest["aria-label"],
|
|
2157
|
-
className: cn(
|
|
2158
|
-
"inline-flex max-w-full gap-5 overflow-x-auto scrollbar-none",
|
|
2159
|
-
fill ? "items-stretch" : "items-center",
|
|
2160
|
-
className
|
|
2161
|
-
),
|
|
2162
|
-
children: tabs.map((tab) => {
|
|
2163
|
-
const active = tab.value === value;
|
|
2164
|
-
const Icon = tab.icon;
|
|
2165
|
-
const box = cn(
|
|
2166
|
-
"relative flex shrink-0 items-center gap-1.5 whitespace-nowrap text-sm transition-colors",
|
|
2167
|
-
fill ? "h-full" : "pb-1.5",
|
|
2168
|
-
active ? "font-medium text-foreground" : "text-muted-foreground hover:text-foreground",
|
|
2169
|
-
tab.dim && "opacity-50 hover:opacity-100"
|
|
2170
|
-
);
|
|
2171
|
-
const inner = /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2172
|
-
Icon ? /* @__PURE__ */ jsx(Icon, { className: "h-4 w-4 shrink-0" }) : null,
|
|
2173
|
-
/* @__PURE__ */ jsx("span", { children: tab.label }),
|
|
2174
|
-
tab.count != null && /* @__PURE__ */ jsx("span", { className: cn(
|
|
2175
|
-
"ml-0.5 rounded-full px-1.5 text-[11px] tabular-nums",
|
|
2176
|
-
active ? "bg-muted text-foreground" : "bg-muted/70 text-muted-foreground"
|
|
2177
|
-
), children: tab.count }),
|
|
2178
|
-
tab.badge
|
|
2179
|
-
] });
|
|
2180
|
-
const underline = active ? /* @__PURE__ */ jsx("span", { className: "absolute inset-x-0 bottom-0 h-0.5 rounded-full bg-foreground" }) : null;
|
|
2181
|
-
if (!tab.trailing) {
|
|
2182
|
-
return /* @__PURE__ */ jsxs(
|
|
2183
|
-
"button",
|
|
2184
|
-
{
|
|
2185
|
-
type: "button",
|
|
2186
|
-
role: "tab",
|
|
2187
|
-
"aria-selected": active,
|
|
2188
|
-
"data-tour": anchorPrefix ? `${anchorPrefix}.${tab.value}` : void 0,
|
|
2189
|
-
onClick: () => onChange(tab.value),
|
|
2190
|
-
className: box,
|
|
2191
|
-
children: [
|
|
2192
|
-
inner,
|
|
2193
|
-
underline
|
|
2194
|
-
]
|
|
2195
|
-
},
|
|
2196
|
-
tab.value
|
|
2197
|
-
);
|
|
2198
|
-
}
|
|
2199
|
-
return /* @__PURE__ */ jsxs("div", { className: box, children: [
|
|
2200
|
-
/* @__PURE__ */ jsx(
|
|
2201
|
-
"button",
|
|
2202
|
-
{
|
|
2203
|
-
type: "button",
|
|
2204
|
-
role: "tab",
|
|
2205
|
-
"aria-selected": active,
|
|
2206
|
-
"data-tour": anchorPrefix ? `${anchorPrefix}.${tab.value}` : void 0,
|
|
2207
|
-
onClick: () => onChange(tab.value),
|
|
2208
|
-
className: "flex items-center gap-1.5 whitespace-nowrap",
|
|
2209
|
-
children: inner
|
|
2210
|
-
}
|
|
2211
|
-
),
|
|
2212
|
-
tab.trailing,
|
|
2213
|
-
underline
|
|
2214
|
-
] }, tab.value);
|
|
2215
|
-
})
|
|
2216
|
-
}
|
|
2217
|
-
);
|
|
2218
|
-
}
|
|
2219
|
-
function PageTabs({ actions, className, ...props }) {
|
|
2220
|
-
return /* @__PURE__ */ jsxs(
|
|
2221
|
-
"div",
|
|
2222
|
-
{
|
|
2223
|
-
className: cn(
|
|
2224
|
-
"-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",
|
|
2225
|
-
className
|
|
2226
|
-
),
|
|
2227
|
-
children: [
|
|
2228
|
-
/* @__PURE__ */ jsx(HeaderTabs, { ...props, className: "-mb-px" }),
|
|
2229
|
-
actions && /* @__PURE__ */ jsx("div", { className: "ml-auto flex shrink-0 items-center gap-2 pb-1.5", children: actions })
|
|
2230
|
-
]
|
|
2231
|
-
}
|
|
2232
|
-
);
|
|
2233
|
-
}
|
|
2234
|
-
var SIZE = {
|
|
2235
|
-
sm: { container: "h-[26px]", segment: "px-2.5 text-xs" },
|
|
2236
|
-
md: { container: "h-[30px]", segment: "px-3 text-sm" }
|
|
2237
|
-
};
|
|
2238
|
-
function SegmentedControl({
|
|
2239
|
-
options,
|
|
2240
|
-
value,
|
|
2241
|
-
onChange,
|
|
2242
|
-
size = "sm",
|
|
2243
|
-
className,
|
|
2244
|
-
...rest
|
|
2245
|
-
}) {
|
|
2246
|
-
const items = options.map((o) => typeof o === "string" ? { value: o, label: o } : o);
|
|
2247
|
-
return /* @__PURE__ */ jsx(
|
|
2248
|
-
"div",
|
|
2249
|
-
{
|
|
2250
|
-
role: "tablist",
|
|
2251
|
-
"aria-label": rest["aria-label"],
|
|
2252
|
-
className: cn(
|
|
2253
|
-
"inline-flex items-center rounded-button border border-border bg-card p-0.5 shadow-button",
|
|
2254
|
-
SIZE[size].container,
|
|
2255
|
-
className
|
|
2256
|
-
),
|
|
2257
|
-
children: items.map((item) => {
|
|
2258
|
-
const active = item.value === value;
|
|
2259
|
-
return /* @__PURE__ */ jsx(
|
|
2260
|
-
"button",
|
|
2261
|
-
{
|
|
2262
|
-
type: "button",
|
|
2263
|
-
role: "tab",
|
|
2264
|
-
"aria-selected": active,
|
|
2265
|
-
onClick: () => onChange(item.value),
|
|
2266
|
-
className: cn(
|
|
2267
|
-
"flex h-full items-center rounded-button font-semibold transition-colors",
|
|
2268
|
-
SIZE[size].segment,
|
|
2269
|
-
active ? "bg-foreground text-background" : "text-muted-foreground hover:bg-muted/60"
|
|
2270
|
-
),
|
|
2271
|
-
children: item.label
|
|
2272
|
-
},
|
|
2273
|
-
item.value
|
|
2274
|
-
);
|
|
2275
|
-
})
|
|
2276
|
-
}
|
|
2277
|
-
);
|
|
2278
|
-
}
|
|
2279
|
-
var NavTransitionContext = React2.createContext("slide");
|
|
2280
|
-
function NavTransitionProvider({ value, children }) {
|
|
2281
|
-
return /* @__PURE__ */ jsx(NavTransitionContext.Provider, { value, children });
|
|
2282
|
-
}
|
|
2283
|
-
function useNavTransition() {
|
|
2284
|
-
return React2.useContext(NavTransitionContext);
|
|
2285
|
-
}
|
|
2286
|
-
function resolveTransitionClass(transition, direction) {
|
|
2287
|
-
if (transition === "none") return "";
|
|
2288
|
-
if (transition === "fade") return "saas-page-enter";
|
|
2289
|
-
if (direction === "back") return "saas-nav-back";
|
|
2290
|
-
if (direction === "forward") return "saas-nav-forward";
|
|
2291
|
-
return "saas-page-enter";
|
|
2292
|
-
}
|
|
2293
|
-
function PageTransition({
|
|
2294
|
-
transitionKey,
|
|
2295
|
-
direction,
|
|
2296
|
-
className,
|
|
2297
|
-
children
|
|
2298
|
-
}) {
|
|
2299
|
-
const transition = useNavTransition();
|
|
2300
|
-
const animClass = resolveTransitionClass(transition, direction);
|
|
2301
|
-
return /* @__PURE__ */ jsx("div", { className: cn(animClass, className), children }, transitionKey);
|
|
2302
|
-
}
|
|
2303
|
-
function PageRail({ children }) {
|
|
2304
|
-
const slot = usePageRailSlot();
|
|
2305
|
-
if (slot) return createPortal(children, slot);
|
|
2306
|
-
return /* @__PURE__ */ jsx(Fragment, { children });
|
|
2307
|
-
}
|
|
2308
|
-
function PageHeaderActions({ children }) {
|
|
2309
|
-
const slot = useModuleHeaderActionsSlot();
|
|
2310
|
-
useRegisterPageActions();
|
|
2311
|
-
if (slot) return createPortal(children, slot);
|
|
2312
|
-
return /* @__PURE__ */ jsx("div", { className: "mb-4 flex items-center justify-end gap-2", children });
|
|
2313
|
-
}
|
|
2314
|
-
function PremiumBadge({ className }) {
|
|
2315
|
-
const t = useTranslation();
|
|
2316
|
-
const tip = t("upgrade.badgeTooltip");
|
|
2317
|
-
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 }) });
|
|
2318
|
-
}
|
|
2319
|
-
var ModuleLayoutContext = React2.createContext("rail");
|
|
2320
|
-
function ModuleLayoutProvider({ variant, children }) {
|
|
2321
|
-
return /* @__PURE__ */ jsx(ModuleLayoutContext.Provider, { value: variant, children });
|
|
2322
|
-
}
|
|
2323
|
-
function useModuleLayout() {
|
|
2324
|
-
return React2.useContext(ModuleLayoutContext);
|
|
2325
|
-
}
|
|
2326
|
-
function RailNavItem({ item }) {
|
|
2327
|
-
const [expanded, setExpanded] = React2.useState(true);
|
|
2328
|
-
const Icon = item.icon ? ICON_MAP[item.icon] ?? null : null;
|
|
2329
|
-
const hasChildren = item.children && item.children.length > 0;
|
|
2330
|
-
const hasActiveChild = hasChildren && item.children.some((c) => c.active);
|
|
2331
|
-
const isActive = item.active || hasActiveChild;
|
|
2332
|
-
return /* @__PURE__ */ jsxs("div", { children: [
|
|
2333
|
-
/* @__PURE__ */ jsxs(
|
|
2334
|
-
"button",
|
|
2335
|
-
{
|
|
2336
|
-
type: "button",
|
|
2337
|
-
"data-tour": `modnav.${item.id}`,
|
|
2338
|
-
onClick: () => hasChildren ? setExpanded(!expanded) : item.onClick?.(),
|
|
2339
|
-
className: cn(
|
|
2340
|
-
"flex items-center gap-2 w-full rounded-md px-2.5 py-1.5 text-sm transition-colors",
|
|
2341
|
-
isActive ? "bg-primary/10 text-primary font-medium" : "text-muted-foreground hover:text-foreground hover:bg-muted/50"
|
|
2342
|
-
),
|
|
2343
|
-
children: [
|
|
2344
|
-
Icon && /* @__PURE__ */ jsx(Icon, { className: "h-4 w-4 shrink-0" }),
|
|
2345
|
-
/* @__PURE__ */ jsx("span", { className: "flex-1 text-left truncate", children: item.label }),
|
|
2346
|
-
item.premium && /* @__PURE__ */ jsx(PremiumBadge, { className: "ml-1" }),
|
|
2347
|
-
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" }))
|
|
2348
|
-
]
|
|
2349
|
-
}
|
|
2350
|
-
),
|
|
2351
|
-
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(
|
|
2352
|
-
"button",
|
|
2353
|
-
{
|
|
2354
|
-
type: "button",
|
|
2355
|
-
"data-tour": `modnav.${child.id}`,
|
|
2356
|
-
onClick: child.onClick,
|
|
2357
|
-
className: cn(
|
|
2358
|
-
"flex items-center w-full rounded-md px-2.5 py-1 text-xs transition-colors",
|
|
2359
|
-
child.active ? "text-primary font-medium bg-primary/5" : "text-muted-foreground hover:text-foreground hover:bg-muted/50"
|
|
2360
|
-
),
|
|
2361
|
-
children: [
|
|
2362
|
-
/* @__PURE__ */ jsx("span", { className: "flex-1 text-left truncate", children: child.label }),
|
|
2363
|
-
child.premium && /* @__PURE__ */ jsx(PremiumBadge, { className: "ml-1" })
|
|
2364
|
-
]
|
|
2365
|
-
},
|
|
2366
|
-
child.id
|
|
2367
|
-
)) })
|
|
2368
|
-
] });
|
|
2369
|
-
}
|
|
2370
|
-
function defaultChildOf(item) {
|
|
2371
|
-
const kids = (item.children ?? []).filter((c) => c.kind !== "action");
|
|
2372
|
-
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];
|
|
2373
|
-
}
|
|
2374
|
-
function MobileTabs({ nav }) {
|
|
2375
|
-
const scrollRef = React2.useRef(null);
|
|
2376
|
-
const [expandedId, setExpandedId] = React2.useState(null);
|
|
2377
|
-
React2.useEffect(() => {
|
|
2378
|
-
const container = scrollRef.current;
|
|
2379
|
-
if (!container) return;
|
|
2380
|
-
const activeEl = container.querySelector('[data-active="true"]');
|
|
2381
|
-
if (activeEl) {
|
|
2382
|
-
const left = activeEl.offsetLeft - container.offsetWidth / 2 + activeEl.offsetWidth / 2;
|
|
2383
|
-
container.scrollTo({ left: Math.max(0, left), behavior: "smooth" });
|
|
2384
|
-
}
|
|
2385
|
-
}, [nav]);
|
|
2386
|
-
return /* @__PURE__ */ jsxs("div", { className: "md:hidden -mx-1 mb-4 no-print", children: [
|
|
2387
|
-
/* @__PURE__ */ jsx("div", { ref: scrollRef, className: "flex gap-1 px-1 pb-2 overflow-x-auto scrollbar-none", children: nav.map((item) => {
|
|
2388
|
-
const Icon = item.icon ? ICON_MAP[item.icon] ?? null : null;
|
|
2389
|
-
const hasChildren = item.children && item.children.length > 0;
|
|
2390
|
-
const isActive = item.active || item.children?.some((c) => c.active);
|
|
2391
|
-
const isExpanded = expandedId === item.id;
|
|
2392
|
-
return /* @__PURE__ */ jsxs(
|
|
2393
|
-
"button",
|
|
2394
|
-
{
|
|
2395
|
-
type: "button",
|
|
2396
|
-
"data-active": isActive || void 0,
|
|
2397
|
-
onClick: () => {
|
|
2398
|
-
if (hasChildren) {
|
|
2399
|
-
if (!isExpanded) defaultChildOf(item)?.onClick?.();
|
|
2400
|
-
setExpandedId(isExpanded ? null : item.id);
|
|
2401
|
-
} else {
|
|
2402
|
-
item.onClick?.();
|
|
2403
|
-
setExpandedId(null);
|
|
2404
|
-
}
|
|
2405
|
-
},
|
|
2406
|
-
className: cn(
|
|
2407
|
-
// This strip is the most-tapped control in most modules and it
|
|
2408
|
-
// was 28px tall — a third under the comfortable minimum. Height
|
|
2409
|
-
// and text step up on touch; the desktop rail is unaffected.
|
|
2410
|
-
"flex shrink-0 items-center gap-1.5 whitespace-nowrap rounded-full px-4 text-sm font-medium transition-colors",
|
|
2411
|
-
"min-h-[2.75rem] active:scale-[0.98]",
|
|
2412
|
-
isActive ? "bg-primary text-primary-foreground" : "bg-muted text-muted-foreground hover:text-foreground"
|
|
2413
|
-
),
|
|
2414
|
-
children: [
|
|
2415
|
-
Icon && /* @__PURE__ */ jsx(Icon, { className: "h-3.5 w-3.5" }),
|
|
2416
|
-
item.label,
|
|
2417
|
-
item.premium && /* @__PURE__ */ jsx(PremiumBadge, {}),
|
|
2418
|
-
hasChildren && /* @__PURE__ */ jsx(ChevronDown, { className: cn("h-2.5 w-2.5 transition-transform", isExpanded && "rotate-180") })
|
|
2419
|
-
]
|
|
2420
|
-
},
|
|
2421
|
-
item.id
|
|
2422
|
-
);
|
|
2423
|
-
}) }),
|
|
2424
|
-
expandedId && (() => {
|
|
2425
|
-
const item = nav.find((n) => n.id === expandedId);
|
|
2426
|
-
const shown = (item?.children ?? []).filter((c) => c.kind !== "action");
|
|
2427
|
-
if (shown.length < 2) return null;
|
|
2428
|
-
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(
|
|
2429
|
-
"button",
|
|
2430
|
-
{
|
|
2431
|
-
type: "button",
|
|
2432
|
-
onClick: () => {
|
|
2433
|
-
child.onClick?.();
|
|
2434
|
-
setExpandedId(null);
|
|
2435
|
-
},
|
|
2436
|
-
className: cn(
|
|
2437
|
-
"whitespace-nowrap rounded-full px-2.5 py-1 text-[11px] font-medium transition-colors shrink-0",
|
|
2438
|
-
child.active ? "bg-foreground/10 text-foreground" : "text-muted-foreground hover:text-foreground"
|
|
2439
|
-
),
|
|
2440
|
-
children: [
|
|
2441
|
-
child.label,
|
|
2442
|
-
child.premium && /* @__PURE__ */ jsx(PremiumBadge, {})
|
|
2443
|
-
]
|
|
2444
|
-
},
|
|
2445
|
-
child.id
|
|
2446
|
-
)) });
|
|
2447
|
-
})()
|
|
2448
|
-
] });
|
|
2449
|
-
}
|
|
2450
|
-
var BackStyleContext = React2.createContext("link");
|
|
2451
|
-
function BackStyleProvider({ style, children }) {
|
|
2452
|
-
return /* @__PURE__ */ jsx(BackStyleContext.Provider, { value: style, children });
|
|
2453
|
-
}
|
|
2454
|
-
function useBackStyle() {
|
|
2455
|
-
return React2.useContext(BackStyleContext);
|
|
2456
|
-
}
|
|
2457
|
-
function SubpageHeader({ title, subtitle, icon, onBack, parentLabel, actions, backStyle, inline }) {
|
|
2458
|
-
const t = useTranslation();
|
|
2459
|
-
const appStyle = useBackStyle();
|
|
2460
|
-
const hasShellBar = useHasPageHeaderHost() && !inline;
|
|
2461
|
-
const cross = useCrossModuleBack(onBack, parentLabel);
|
|
2462
|
-
const effectiveOnBack = inline ? onBack : cross.onBack;
|
|
2463
|
-
const effectiveParentLabel = inline ? parentLabel : cross.parentLabel;
|
|
2464
|
-
useBackHandler(effectiveOnBack);
|
|
2465
|
-
const Icon = icon ? ICON_MAP[icon] ?? null : null;
|
|
2466
|
-
if (inline && effectiveOnBack) {
|
|
2467
|
-
return /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
|
|
2468
|
-
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center justify-between gap-3", children: [
|
|
2469
|
-
/* @__PURE__ */ jsx(PageHeaderCrumb, { title, parentLabel: effectiveParentLabel, onBack: effectiveOnBack }),
|
|
2470
|
-
actions && /* @__PURE__ */ jsx("div", { className: "no-print", children: actions })
|
|
2471
|
-
] }),
|
|
2472
|
-
subtitle && /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: subtitle })
|
|
2473
|
-
] });
|
|
2474
|
-
}
|
|
2475
|
-
if (hasShellBar) {
|
|
2476
|
-
return /* @__PURE__ */ jsx(
|
|
2477
|
-
ShellSubpageHeader,
|
|
2478
|
-
{
|
|
2479
|
-
title,
|
|
2480
|
-
subtitle,
|
|
2481
|
-
icon: Icon,
|
|
2482
|
-
onBack: effectiveOnBack,
|
|
2483
|
-
parentLabel: effectiveParentLabel,
|
|
2484
|
-
actions
|
|
2485
|
-
}
|
|
2486
|
-
);
|
|
2487
|
-
}
|
|
2488
|
-
const style = !effectiveOnBack ? "icon" : backStyle ?? appStyle;
|
|
2489
|
-
const effectiveStyle = style !== "icon" && !effectiveParentLabel ? "icon" : style;
|
|
2490
|
-
const titleBlock = /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-4", children: [
|
|
2491
|
-
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
|
|
2492
|
-
effectiveStyle === "icon" && effectiveOnBack && /* @__PURE__ */ jsx(
|
|
2493
|
-
"button",
|
|
2494
|
-
{
|
|
2495
|
-
onClick: effectiveOnBack,
|
|
2496
|
-
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",
|
|
2497
|
-
children: /* @__PURE__ */ jsx(ChevronLeft, { className: "h-4 w-4" })
|
|
2498
|
-
}
|
|
2499
|
-
),
|
|
2500
|
-
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" }) }),
|
|
2501
|
-
/* @__PURE__ */ jsxs("div", { children: [
|
|
2502
|
-
/* @__PURE__ */ jsx("h2", { className: "text-xl font-bold", children: title }),
|
|
2503
|
-
subtitle && /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: subtitle })
|
|
2504
|
-
] })
|
|
2505
|
-
] }),
|
|
2506
|
-
actions && /* @__PURE__ */ jsx("div", { className: "no-print", children: actions })
|
|
2507
|
-
] });
|
|
2508
|
-
if (effectiveStyle === "breadcrumb") {
|
|
2509
|
-
return /* @__PURE__ */ jsxs("div", { className: "space-y-4 mb-6", children: [
|
|
2510
|
-
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-4", children: [
|
|
2511
|
-
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-sm text-muted-foreground", children: [
|
|
2512
|
-
/* @__PURE__ */ jsx("button", { onClick: effectiveOnBack, className: "hover:text-foreground transition-colors", children: effectiveParentLabel }),
|
|
2513
|
-
/* @__PURE__ */ jsx(ChevronRight, { className: "h-3.5 w-3.5" }),
|
|
2514
|
-
/* @__PURE__ */ jsx("span", { className: "text-foreground font-medium", children: title })
|
|
2515
|
-
] }),
|
|
2516
|
-
actions && /* @__PURE__ */ jsx("div", { className: "no-print", children: actions })
|
|
2517
|
-
] }),
|
|
2518
|
-
subtitle && /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground -mt-2", children: subtitle })
|
|
2519
|
-
] });
|
|
2520
|
-
}
|
|
2521
|
-
if (effectiveStyle === "link") {
|
|
2522
|
-
return /* @__PURE__ */ jsxs("div", { className: "space-y-3 mb-6", children: [
|
|
2523
|
-
/* @__PURE__ */ jsxs(
|
|
2524
|
-
"button",
|
|
2525
|
-
{
|
|
2526
|
-
onClick: effectiveOnBack,
|
|
2527
|
-
className: "inline-flex items-center gap-1.5 text-sm text-muted-foreground hover:text-foreground transition-colors",
|
|
2528
|
-
children: [
|
|
2529
|
-
/* @__PURE__ */ jsx(ChevronLeft, { className: "h-4 w-4" }),
|
|
2530
|
-
t("crud.detail.backTo", { entities: effectiveParentLabel })
|
|
2531
|
-
]
|
|
2532
|
-
}
|
|
2533
|
-
),
|
|
2534
|
-
titleBlock
|
|
2535
|
-
] });
|
|
2536
|
-
}
|
|
2537
|
-
return /* @__PURE__ */ jsx("div", { className: "space-y-4 mb-6", children: titleBlock });
|
|
2538
|
-
}
|
|
2539
|
-
function ShellSubpageHeader({
|
|
2540
|
-
title,
|
|
2541
|
-
subtitle,
|
|
2542
|
-
icon: Icon,
|
|
2543
|
-
onBack,
|
|
2544
|
-
parentLabel,
|
|
2545
|
-
actions
|
|
2546
|
-
}) {
|
|
2547
|
-
usePageHeader(true, { back: !!onBack });
|
|
2548
|
-
const crumb = /* @__PURE__ */ jsx(PageTitlePortal, { children: /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 items-center gap-2", children: [
|
|
2549
|
-
Icon && !onBack && /* @__PURE__ */ jsx(Icon, { className: "h-4 w-4 shrink-0 text-muted-foreground" }),
|
|
2550
|
-
/* @__PURE__ */ jsx(PageHeaderCrumb, { title, parentLabel, onBack })
|
|
2551
|
-
] }) });
|
|
2552
|
-
if (!actions) return crumb;
|
|
2553
|
-
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2554
|
-
crumb,
|
|
2555
|
-
/* @__PURE__ */ jsx(PageHeaderActions, { children: /* @__PURE__ */ jsx("div", { className: "no-print flex shrink-0 items-center gap-2", children: actions }) })
|
|
2556
|
-
] });
|
|
2557
|
-
}
|
|
2558
|
-
function ModuleHeader({ nav }) {
|
|
2559
|
-
function defaultChild(item) {
|
|
2560
|
-
const kids2 = (item.children ?? []).filter((c) => c.kind !== "action");
|
|
2561
|
-
return kids2.find((c) => /list/i.test(c.id) || /^list$/i.test(c.label)) ?? kids2[kids2.length - 1];
|
|
2562
|
-
}
|
|
2563
|
-
const activeItem = nav.find((item) => item.active || item.children?.some((c) => c.active));
|
|
2564
|
-
const tabs = nav.map((item) => ({
|
|
2565
|
-
value: item.id,
|
|
2566
|
-
label: item.label,
|
|
2567
|
-
icon: item.icon ? ICON_MAP[item.icon] ?? void 0 : void 0,
|
|
2568
|
-
// Plan-gated views keep their upgrade marker — dropping it would hide the
|
|
2569
|
-
// only signal that the tab costs an upgrade.
|
|
2570
|
-
badge: item.premium ? /* @__PURE__ */ jsx(PremiumBadge, {}) : void 0
|
|
2571
|
-
}));
|
|
2572
|
-
const onChange = (id) => {
|
|
2573
|
-
const item = nav.find((n) => n.id === id);
|
|
2574
|
-
if (!item) return;
|
|
2575
|
-
const hasChildren = item.children && item.children.length > 0;
|
|
2576
|
-
if (hasChildren) defaultChild(item)?.onClick?.();
|
|
2577
|
-
else item.onClick?.();
|
|
2578
|
-
};
|
|
2579
|
-
const kids = (activeItem?.children ?? []).filter((c) => c.kind !== "action");
|
|
2580
|
-
const activeKid = kids.find((c) => c.active)?.id ?? kids[0]?.id;
|
|
2581
|
-
const childControl = kids.length > 1 ? /* @__PURE__ */ jsx(
|
|
2582
|
-
SegmentedControl,
|
|
2583
|
-
{
|
|
2584
|
-
size: "sm",
|
|
2585
|
-
"aria-label": activeItem?.label,
|
|
2586
|
-
options: kids.map((c) => ({ value: c.id, label: c.label })),
|
|
2587
|
-
value: activeKid ?? "",
|
|
2588
|
-
onChange: (id) => kids.find((c) => c.id === id)?.onClick?.()
|
|
2589
|
-
}
|
|
2590
|
-
) : void 0;
|
|
2591
|
-
return /* @__PURE__ */ jsx(
|
|
2592
|
-
PageTabs,
|
|
2593
|
-
{
|
|
2594
|
-
tabs,
|
|
2595
|
-
value: activeItem?.id ?? nav[0]?.id ?? "",
|
|
2596
|
-
onChange,
|
|
2597
|
-
actions: childControl,
|
|
2598
|
-
anchorPrefix: "modnav"
|
|
2599
|
-
}
|
|
2600
|
-
);
|
|
2601
|
-
}
|
|
2602
|
-
function ModulePage({ title: _title, subtitle, nav, children, className, headerAction, showHeader = true, sidebarFooter, navVariant, viewKey, direction }) {
|
|
2603
|
-
const ctxVariant = useModuleLayout();
|
|
2604
|
-
const variant = navVariant ?? ctxVariant;
|
|
2605
|
-
const transitionKey = viewKey ?? "view";
|
|
2606
|
-
const onSubpage = useHasPageHeaderBack();
|
|
2607
|
-
if (variant === "sidebar") {
|
|
2608
|
-
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2609
|
-
showHeader && headerAction && /* @__PURE__ */ jsx(PageHeaderActions, { children: headerAction }),
|
|
2610
|
-
/* @__PURE__ */ jsx(PageTransition, { transitionKey, direction, className: cn("min-w-0", className), children })
|
|
2611
|
-
] });
|
|
2612
|
-
}
|
|
2613
|
-
if (variant === "tabs") {
|
|
2614
|
-
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2615
|
-
!onSubpage && /* @__PURE__ */ jsx(ModuleHeader, { nav }),
|
|
2616
|
-
showHeader && headerAction && /* @__PURE__ */ jsx(PageHeaderActions, { children: headerAction }),
|
|
2617
|
-
/* @__PURE__ */ jsx(PageTransition, { transitionKey, direction, className: cn("min-w-0", className), children })
|
|
2618
|
-
] });
|
|
2619
|
-
}
|
|
2620
|
-
return /* @__PURE__ */ jsxs("div", { className: cn("min-w-0", className), children: [
|
|
2621
|
-
/* @__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: [
|
|
2622
|
-
/* @__PURE__ */ jsx("nav", { className: "space-y-0.5", children: nav.map((item) => /* @__PURE__ */ jsx(RailNavItem, { item }, item.id)) }),
|
|
2623
|
-
sidebarFooter && /* @__PURE__ */ jsx("div", { className: "mt-auto pt-3", children: sidebarFooter })
|
|
2624
|
-
] }) }),
|
|
2625
|
-
/* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
|
|
2626
|
-
showHeader && headerAction && /* @__PURE__ */ jsx(PageHeaderActions, { children: headerAction }),
|
|
2627
|
-
!onSubpage && /* @__PURE__ */ jsx(MobileTabs, { nav }),
|
|
2628
|
-
/* @__PURE__ */ jsx(PageTransition, { transitionKey, direction, children })
|
|
2629
|
-
] })
|
|
2630
|
-
] });
|
|
2631
|
-
}
|
|
2632
2321
|
|
|
2633
|
-
export { AppShell,
|
|
2634
|
-
//# sourceMappingURL=chunk-
|
|
2635
|
-
//# 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
|