@petrarca/sonnet-shell 0.1.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/LICENSE.md +190 -0
- package/dist/auth/index.d.ts +117 -0
- package/dist/auth/index.js +305 -0
- package/dist/auth/index.js.map +1 -0
- package/dist/index.d.ts +597 -0
- package/dist/index.js +1194 -0
- package/dist/index.js.map +1 -0
- package/package.json +51 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1194 @@
|
|
|
1
|
+
// src/AppShell.tsx
|
|
2
|
+
import { useState, useEffect as useEffect3, useCallback as useCallback2, useRef as useRef2, useMemo as useMemo3 } from "react";
|
|
3
|
+
import { Outlet, useLocation as useLocation2, useNavigate as useNavigate2 } from "react-router-dom";
|
|
4
|
+
import { TooltipProvider } from "@petrarca/sonnet-ui";
|
|
5
|
+
import { ScrollArea as ScrollArea2 } from "@petrarca/sonnet-ui";
|
|
6
|
+
import {
|
|
7
|
+
Sheet,
|
|
8
|
+
SheetContent,
|
|
9
|
+
SheetHeader,
|
|
10
|
+
SheetTitle,
|
|
11
|
+
SheetDescription,
|
|
12
|
+
SheetBody
|
|
13
|
+
} from "@petrarca/sonnet-ui";
|
|
14
|
+
|
|
15
|
+
// src/ConfirmDialog.tsx
|
|
16
|
+
import { AlertTriangle } from "lucide-react";
|
|
17
|
+
import { Button } from "@petrarca/sonnet-ui";
|
|
18
|
+
import {
|
|
19
|
+
Dialog,
|
|
20
|
+
DialogContent,
|
|
21
|
+
DialogDescription,
|
|
22
|
+
DialogFooter,
|
|
23
|
+
DialogHeader,
|
|
24
|
+
DialogTitle
|
|
25
|
+
} from "@petrarca/sonnet-ui";
|
|
26
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
27
|
+
function ConfirmDialog({
|
|
28
|
+
open,
|
|
29
|
+
title,
|
|
30
|
+
description,
|
|
31
|
+
confirmLabel = "Confirm",
|
|
32
|
+
cancelLabel = "Cancel",
|
|
33
|
+
variant = "default",
|
|
34
|
+
onConfirm,
|
|
35
|
+
onCancel
|
|
36
|
+
}) {
|
|
37
|
+
return /* @__PURE__ */ jsx(Dialog, { open, onOpenChange: (isOpen) => !isOpen && onCancel(), children: /* @__PURE__ */ jsxs(DialogContent, { children: [
|
|
38
|
+
/* @__PURE__ */ jsxs(DialogHeader, { children: [
|
|
39
|
+
/* @__PURE__ */ jsx(DialogTitle, { children: title }),
|
|
40
|
+
description && /* @__PURE__ */ jsx(DialogDescription, { children: description })
|
|
41
|
+
] }),
|
|
42
|
+
variant === "destructive" && /* @__PURE__ */ jsxs("div", { className: "flex gap-2 items-start", children: [
|
|
43
|
+
/* @__PURE__ */ jsx(
|
|
44
|
+
AlertTriangle,
|
|
45
|
+
{
|
|
46
|
+
size: 20,
|
|
47
|
+
className: "text-destructive flex-shrink-0 mt-0.5"
|
|
48
|
+
}
|
|
49
|
+
),
|
|
50
|
+
/* @__PURE__ */ jsx("p", { className: "flex-1 text-sm text-muted-foreground", children: "This action cannot be undone." })
|
|
51
|
+
] }),
|
|
52
|
+
/* @__PURE__ */ jsxs(DialogFooter, { children: [
|
|
53
|
+
/* @__PURE__ */ jsx(Button, { variant: "outline", onClick: onCancel, children: cancelLabel }),
|
|
54
|
+
/* @__PURE__ */ jsx(Button, { variant, onClick: onConfirm, children: confirmLabel })
|
|
55
|
+
] })
|
|
56
|
+
] }) });
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// src/TopBar.tsx
|
|
60
|
+
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
61
|
+
function TopBar({ children }) {
|
|
62
|
+
return /* @__PURE__ */ jsx2("header", { className: "h-12 shrink-0 border-b border-blue-200 flex items-center justify-between px-3 bg-blue-100/70 z-20", children });
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// src/IconRail.tsx
|
|
66
|
+
import { cn } from "@petrarca/sonnet-core";
|
|
67
|
+
import { Button as Button2 } from "@petrarca/sonnet-ui";
|
|
68
|
+
import { Separator } from "@petrarca/sonnet-ui";
|
|
69
|
+
import { Tooltip, TooltipContent, TooltipTrigger } from "@petrarca/sonnet-ui";
|
|
70
|
+
|
|
71
|
+
// src/shellModules.ts
|
|
72
|
+
import { createContext, useContext } from "react";
|
|
73
|
+
var ShellModulesContext = createContext(null);
|
|
74
|
+
function useShellModules() {
|
|
75
|
+
const registry = useContext(ShellModulesContext);
|
|
76
|
+
if (!registry) {
|
|
77
|
+
throw new Error("useShellModules must be used within an AppShell");
|
|
78
|
+
}
|
|
79
|
+
return registry;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// src/IconRail.tsx
|
|
83
|
+
import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
84
|
+
function IconRail({
|
|
85
|
+
activeServiceId,
|
|
86
|
+
activeSidePaneModuleId,
|
|
87
|
+
onServiceSelect
|
|
88
|
+
}) {
|
|
89
|
+
const { mainModules, bottomModules } = useShellModules();
|
|
90
|
+
const isActive = (id) => activeServiceId === id || activeSidePaneModuleId === id;
|
|
91
|
+
return /* @__PURE__ */ jsxs2("nav", { className: "w-12 shrink-0 border-r bg-muted/30 flex flex-col items-center py-2 gap-1", children: [
|
|
92
|
+
/* @__PURE__ */ jsx3("div", { className: "flex-1 flex flex-col items-center gap-1 overflow-y-auto", children: mainModules.map((service) => /* @__PURE__ */ jsx3(
|
|
93
|
+
RailIcon,
|
|
94
|
+
{
|
|
95
|
+
service,
|
|
96
|
+
isActive: isActive(service.id),
|
|
97
|
+
onClick: () => onServiceSelect(service.id)
|
|
98
|
+
},
|
|
99
|
+
service.id
|
|
100
|
+
)) }),
|
|
101
|
+
/* @__PURE__ */ jsx3(Separator, { className: "w-6 my-1" }),
|
|
102
|
+
/* @__PURE__ */ jsx3("div", { className: "flex flex-col items-center gap-1", children: bottomModules.map((service) => /* @__PURE__ */ jsx3(
|
|
103
|
+
RailIcon,
|
|
104
|
+
{
|
|
105
|
+
service,
|
|
106
|
+
isActive: isActive(service.id),
|
|
107
|
+
onClick: () => onServiceSelect(service.id)
|
|
108
|
+
},
|
|
109
|
+
service.id
|
|
110
|
+
)) })
|
|
111
|
+
] });
|
|
112
|
+
}
|
|
113
|
+
function RailIcon({ service, isActive, onClick }) {
|
|
114
|
+
const Icon = service.icon;
|
|
115
|
+
return /* @__PURE__ */ jsxs2(Tooltip, { delayDuration: 0, children: [
|
|
116
|
+
/* @__PURE__ */ jsx3(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxs2(
|
|
117
|
+
Button2,
|
|
118
|
+
{
|
|
119
|
+
variant: "ghost",
|
|
120
|
+
size: "compact",
|
|
121
|
+
onClick,
|
|
122
|
+
className: cn(
|
|
123
|
+
"h-9 w-9 rounded-lg transition-colors",
|
|
124
|
+
isActive ? "bg-accent text-accent-foreground" : "text-muted-foreground hover:text-foreground"
|
|
125
|
+
),
|
|
126
|
+
children: [
|
|
127
|
+
/* @__PURE__ */ jsx3(Icon, { className: "h-[18px] w-[18px]" }),
|
|
128
|
+
/* @__PURE__ */ jsx3("span", { className: "sr-only", children: service.label })
|
|
129
|
+
]
|
|
130
|
+
}
|
|
131
|
+
) }),
|
|
132
|
+
/* @__PURE__ */ jsx3(TooltipContent, { side: "right", sideOffset: 8, children: service.label })
|
|
133
|
+
] });
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// src/SubNavPanel.tsx
|
|
137
|
+
import { useMemo as useMemo2 } from "react";
|
|
138
|
+
import { useLocation, Link } from "react-router-dom";
|
|
139
|
+
import { ChevronsLeft, ChevronsRight } from "lucide-react";
|
|
140
|
+
import { cn as cn2 } from "@petrarca/sonnet-core";
|
|
141
|
+
import { ScrollArea } from "@petrarca/sonnet-ui";
|
|
142
|
+
import { Tooltip as Tooltip2, TooltipContent as TooltipContent2, TooltipTrigger as TooltipTrigger2 } from "@petrarca/sonnet-ui";
|
|
143
|
+
|
|
144
|
+
// src/hooks.ts
|
|
145
|
+
import { useEffect, useMemo, useRef } from "react";
|
|
146
|
+
|
|
147
|
+
// src/api.ts
|
|
148
|
+
import { toast } from "sonner";
|
|
149
|
+
import { warnLog as warnLog2 } from "@petrarca/sonnet-core";
|
|
150
|
+
|
|
151
|
+
// src/eventBus.ts
|
|
152
|
+
import { devLog, warnLog } from "@petrarca/sonnet-core";
|
|
153
|
+
var listeners = /* @__PURE__ */ new Map();
|
|
154
|
+
function on(key, handler) {
|
|
155
|
+
let set = listeners.get(key);
|
|
156
|
+
if (!set) {
|
|
157
|
+
set = /* @__PURE__ */ new Set();
|
|
158
|
+
listeners.set(key, set);
|
|
159
|
+
}
|
|
160
|
+
set.add(handler);
|
|
161
|
+
devLog(`[shell:event] on "${key}" (${set.size} handler(s))`);
|
|
162
|
+
return () => {
|
|
163
|
+
set.delete(handler);
|
|
164
|
+
if (set.size === 0) listeners.delete(key);
|
|
165
|
+
devLog(`[shell:event] off "${key}" (${set.size} handler(s))`);
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
function once(key, handler) {
|
|
169
|
+
const unsubscribe = on(key, (payload) => {
|
|
170
|
+
unsubscribe();
|
|
171
|
+
handler(payload);
|
|
172
|
+
});
|
|
173
|
+
return unsubscribe;
|
|
174
|
+
}
|
|
175
|
+
function emit(key, payload) {
|
|
176
|
+
const set = listeners.get(key);
|
|
177
|
+
const count = set?.size ?? 0;
|
|
178
|
+
devLog(`[shell:event] emit "${key}"`, payload, `(${count} handler(s))`);
|
|
179
|
+
if (!set || count === 0) return;
|
|
180
|
+
for (const handler of set) {
|
|
181
|
+
Promise.resolve().then(() => {
|
|
182
|
+
try {
|
|
183
|
+
handler(payload);
|
|
184
|
+
} catch (err) {
|
|
185
|
+
warnLog(`[shell:event] handler error "${key}":`, err);
|
|
186
|
+
}
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// src/api.ts
|
|
192
|
+
var notification = {
|
|
193
|
+
success(message) {
|
|
194
|
+
toast.success(message);
|
|
195
|
+
},
|
|
196
|
+
error(message) {
|
|
197
|
+
toast.error(message);
|
|
198
|
+
},
|
|
199
|
+
warning(message) {
|
|
200
|
+
toast.warning(message);
|
|
201
|
+
},
|
|
202
|
+
info(message) {
|
|
203
|
+
toast.info(message);
|
|
204
|
+
},
|
|
205
|
+
promise(promise, opts) {
|
|
206
|
+
return toast.promise(promise, opts);
|
|
207
|
+
},
|
|
208
|
+
dismiss(id) {
|
|
209
|
+
toast.dismiss(id);
|
|
210
|
+
}
|
|
211
|
+
};
|
|
212
|
+
var _dialogConfirm = null;
|
|
213
|
+
function initDialog(confirmFn) {
|
|
214
|
+
_dialogConfirm = confirmFn;
|
|
215
|
+
}
|
|
216
|
+
var dialog = {
|
|
217
|
+
/**
|
|
218
|
+
* Show a confirmation dialog. Returns true if confirmed, false if cancelled.
|
|
219
|
+
*/
|
|
220
|
+
confirm(opts) {
|
|
221
|
+
if (_dialogConfirm) {
|
|
222
|
+
return _dialogConfirm(opts);
|
|
223
|
+
}
|
|
224
|
+
warnLog2("dialog.confirm: shell not initialized yet.");
|
|
225
|
+
return Promise.resolve(false);
|
|
226
|
+
}
|
|
227
|
+
};
|
|
228
|
+
var _navigate = null;
|
|
229
|
+
var _openCommandMenu = null;
|
|
230
|
+
var _lookupFeature = null;
|
|
231
|
+
function initNavigation(navigateFn, openCommandMenuFn) {
|
|
232
|
+
_navigate = navigateFn;
|
|
233
|
+
_openCommandMenu = openCommandMenuFn;
|
|
234
|
+
}
|
|
235
|
+
function initFeatureNav(lookupFn) {
|
|
236
|
+
_lookupFeature = lookupFn;
|
|
237
|
+
}
|
|
238
|
+
var navigation = {
|
|
239
|
+
/** Navigate to a path within the shell. */
|
|
240
|
+
goTo(path) {
|
|
241
|
+
if (_navigate) {
|
|
242
|
+
_navigate(path);
|
|
243
|
+
} else {
|
|
244
|
+
warnLog2("navigation.goTo: shell not initialized yet.");
|
|
245
|
+
}
|
|
246
|
+
},
|
|
247
|
+
/**
|
|
248
|
+
* Navigate to a feature by its stable identifier (e.g. "auditing.audit-events").
|
|
249
|
+
* The path is resolved from the module registry at call time, so it stays
|
|
250
|
+
* correct even if routes are reorganised.
|
|
251
|
+
*/
|
|
252
|
+
goToFeature(featureId) {
|
|
253
|
+
if (!_navigate || !_lookupFeature) {
|
|
254
|
+
warnLog2("navigation.goToFeature: shell not initialized yet.");
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
const path = _lookupFeature(featureId);
|
|
258
|
+
if (path) {
|
|
259
|
+
_navigate(path);
|
|
260
|
+
} else {
|
|
261
|
+
warnLog2("navigation.goToFeature: unknown feature id '{}'.", featureId);
|
|
262
|
+
}
|
|
263
|
+
},
|
|
264
|
+
/** Open the Cmd+K command menu. */
|
|
265
|
+
openCommandMenu() {
|
|
266
|
+
if (_openCommandMenu) {
|
|
267
|
+
_openCommandMenu();
|
|
268
|
+
} else {
|
|
269
|
+
warnLog2("navigation.openCommandMenu: shell not initialized yet.");
|
|
270
|
+
}
|
|
271
|
+
},
|
|
272
|
+
/** Go back in browser history. */
|
|
273
|
+
back() {
|
|
274
|
+
window.history.back();
|
|
275
|
+
}
|
|
276
|
+
};
|
|
277
|
+
var _panelOpen = null;
|
|
278
|
+
var _panelClose = null;
|
|
279
|
+
function initPanel(openFn, closeFn) {
|
|
280
|
+
_panelOpen = openFn;
|
|
281
|
+
_panelClose = closeFn;
|
|
282
|
+
}
|
|
283
|
+
var panel = {
|
|
284
|
+
/** Open the shell panel with content rendered inside. */
|
|
285
|
+
open(opts) {
|
|
286
|
+
if (_panelOpen) {
|
|
287
|
+
_panelOpen(opts);
|
|
288
|
+
} else {
|
|
289
|
+
warnLog2("panel.open: shell not initialized yet.");
|
|
290
|
+
}
|
|
291
|
+
},
|
|
292
|
+
/** Close the panel. */
|
|
293
|
+
close() {
|
|
294
|
+
if (_panelClose) {
|
|
295
|
+
_panelClose();
|
|
296
|
+
} else {
|
|
297
|
+
warnLog2("panel.close: shell not initialized yet.");
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
};
|
|
301
|
+
var _sidePaneOpen = null;
|
|
302
|
+
var _sidePaneClose = null;
|
|
303
|
+
var _sidePaneIsOpen = null;
|
|
304
|
+
var _sidePaneActiveModuleId = null;
|
|
305
|
+
function initSidePane(openFn, closeFn, isOpenFn, activeModuleIdFn) {
|
|
306
|
+
_sidePaneOpen = openFn;
|
|
307
|
+
_sidePaneClose = closeFn;
|
|
308
|
+
_sidePaneIsOpen = isOpenFn;
|
|
309
|
+
_sidePaneActiveModuleId = activeModuleIdFn;
|
|
310
|
+
}
|
|
311
|
+
var sidePane = {
|
|
312
|
+
/** Open the side pane with the given content and sizing options. */
|
|
313
|
+
open(opts) {
|
|
314
|
+
if (_sidePaneOpen) {
|
|
315
|
+
_sidePaneOpen(opts);
|
|
316
|
+
} else {
|
|
317
|
+
warnLog2("sidePane.open: shell not initialized yet.");
|
|
318
|
+
}
|
|
319
|
+
},
|
|
320
|
+
/** Close the side pane. */
|
|
321
|
+
close() {
|
|
322
|
+
if (_sidePaneClose) {
|
|
323
|
+
_sidePaneClose();
|
|
324
|
+
} else {
|
|
325
|
+
warnLog2("sidePane.close: shell not initialized yet.");
|
|
326
|
+
}
|
|
327
|
+
},
|
|
328
|
+
/**
|
|
329
|
+
* Toggle the side pane.
|
|
330
|
+
*
|
|
331
|
+
* If the pane is closed, opens it with opts.
|
|
332
|
+
* If the pane is open with the same moduleId, closes it.
|
|
333
|
+
* If the pane is open with a different moduleId, replaces it with opts.
|
|
334
|
+
*/
|
|
335
|
+
toggle(opts) {
|
|
336
|
+
if (!_sidePaneIsOpen || !_sidePaneActiveModuleId || !_sidePaneOpen || !_sidePaneClose) {
|
|
337
|
+
warnLog2("sidePane.toggle: shell not initialized yet.");
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
if (_sidePaneIsOpen() && _sidePaneActiveModuleId() === opts.moduleId) {
|
|
341
|
+
_sidePaneClose();
|
|
342
|
+
} else {
|
|
343
|
+
_sidePaneOpen(opts);
|
|
344
|
+
}
|
|
345
|
+
},
|
|
346
|
+
/** Returns true when the pane is currently open. */
|
|
347
|
+
isOpen() {
|
|
348
|
+
return _sidePaneIsOpen ? _sidePaneIsOpen() : false;
|
|
349
|
+
},
|
|
350
|
+
/** Returns the moduleId of the currently open pane, or null. */
|
|
351
|
+
activeModuleId() {
|
|
352
|
+
return _sidePaneActiveModuleId ? _sidePaneActiveModuleId() : null;
|
|
353
|
+
}
|
|
354
|
+
};
|
|
355
|
+
var _fullscreenEnter = null;
|
|
356
|
+
var _fullscreenExit = null;
|
|
357
|
+
function initFullscreen(enterFn, exitFn) {
|
|
358
|
+
_fullscreenEnter = enterFn;
|
|
359
|
+
_fullscreenExit = exitFn;
|
|
360
|
+
}
|
|
361
|
+
var fullscreen = {
|
|
362
|
+
/** Enter fullscreen mode, rendering content over all shell chrome. */
|
|
363
|
+
enter(opts) {
|
|
364
|
+
if (_fullscreenEnter) {
|
|
365
|
+
_fullscreenEnter(opts);
|
|
366
|
+
} else {
|
|
367
|
+
warnLog2("fullscreen.enter: shell not initialized yet.");
|
|
368
|
+
}
|
|
369
|
+
},
|
|
370
|
+
/** Exit fullscreen mode, restoring the normal shell layout. */
|
|
371
|
+
exit() {
|
|
372
|
+
if (_fullscreenExit) {
|
|
373
|
+
_fullscreenExit();
|
|
374
|
+
} else {
|
|
375
|
+
warnLog2("fullscreen.exit: shell not initialized yet.");
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
};
|
|
379
|
+
var events = {
|
|
380
|
+
/** Subscribe to an event. Returns an unsubscribe function. */
|
|
381
|
+
on(key, handler) {
|
|
382
|
+
return on(key, handler);
|
|
383
|
+
},
|
|
384
|
+
/** Subscribe, auto-unsubscribing after the first emission. */
|
|
385
|
+
once(key, handler) {
|
|
386
|
+
return once(key, handler);
|
|
387
|
+
},
|
|
388
|
+
/** Emit an event. Handlers run asynchronously (microtask). */
|
|
389
|
+
emit(key, payload) {
|
|
390
|
+
emit(key, payload);
|
|
391
|
+
}
|
|
392
|
+
};
|
|
393
|
+
|
|
394
|
+
// src/hooks.ts
|
|
395
|
+
function useExtensionPoint(name) {
|
|
396
|
+
const { getExtensionPoint } = useShellModules();
|
|
397
|
+
return useMemo(() => getExtensionPoint(name), [getExtensionPoint, name]);
|
|
398
|
+
}
|
|
399
|
+
function useMainModules() {
|
|
400
|
+
const { mainModules } = useShellModules();
|
|
401
|
+
return mainModules;
|
|
402
|
+
}
|
|
403
|
+
function useShellEvent(key, handler) {
|
|
404
|
+
const handlerRef = useRef(handler);
|
|
405
|
+
handlerRef.current = handler;
|
|
406
|
+
useEffect(() => {
|
|
407
|
+
return events.on(key, (payload) => handlerRef.current(payload));
|
|
408
|
+
}, [key]);
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
// src/SubNavPanel.tsx
|
|
412
|
+
import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
413
|
+
function useMergedNavigation(service) {
|
|
414
|
+
const contributions = useExtensionPoint(`${service.id}.nav`);
|
|
415
|
+
return useMemo2(() => {
|
|
416
|
+
if (contributions.length === 0) return service.navigation;
|
|
417
|
+
const contributed = {
|
|
418
|
+
id: `${service.id}-contributed`,
|
|
419
|
+
links: contributions.map((c) => ({
|
|
420
|
+
id: c.extensionPoint + "." + c.label.toLowerCase().replace(/\s+/g, "-"),
|
|
421
|
+
label: c.label,
|
|
422
|
+
path: c.path
|
|
423
|
+
}))
|
|
424
|
+
};
|
|
425
|
+
return [...service.navigation, contributed];
|
|
426
|
+
}, [service, contributions]);
|
|
427
|
+
}
|
|
428
|
+
function SubNavPanel({
|
|
429
|
+
service,
|
|
430
|
+
collapsed,
|
|
431
|
+
onToggleCollapse
|
|
432
|
+
}) {
|
|
433
|
+
const { pathname } = useLocation();
|
|
434
|
+
const mergedNavigation = useMergedNavigation(service);
|
|
435
|
+
if (collapsed) {
|
|
436
|
+
return /* @__PURE__ */ jsx4("aside", { className: "shrink-0 border-r bg-background flex flex-col items-center pt-2.5", children: /* @__PURE__ */ jsxs3(Tooltip2, { children: [
|
|
437
|
+
/* @__PURE__ */ jsx4(TooltipTrigger2, { asChild: true, children: /* @__PURE__ */ jsx4(
|
|
438
|
+
"button",
|
|
439
|
+
{
|
|
440
|
+
className: "text-muted-foreground/50 hover:text-muted-foreground transition-colors p-1",
|
|
441
|
+
onClick: onToggleCollapse,
|
|
442
|
+
children: /* @__PURE__ */ jsx4(ChevronsRight, { className: "h-4 w-4" })
|
|
443
|
+
}
|
|
444
|
+
) }),
|
|
445
|
+
/* @__PURE__ */ jsx4(TooltipContent2, { side: "right", children: "Expand navigation" })
|
|
446
|
+
] }) });
|
|
447
|
+
}
|
|
448
|
+
return /* @__PURE__ */ jsxs3("aside", { className: "w-52 shrink-0 border-r bg-background flex flex-col", children: [
|
|
449
|
+
/* @__PURE__ */ jsxs3("div", { className: "h-10 shrink-0 flex items-center justify-between px-4", children: [
|
|
450
|
+
/* @__PURE__ */ jsx4("span", { className: "heading-compact text-muted-foreground uppercase tracking-wider", children: service.label }),
|
|
451
|
+
/* @__PURE__ */ jsxs3(Tooltip2, { children: [
|
|
452
|
+
/* @__PURE__ */ jsx4(TooltipTrigger2, { asChild: true, children: /* @__PURE__ */ jsx4(
|
|
453
|
+
"button",
|
|
454
|
+
{
|
|
455
|
+
className: "text-muted-foreground/40 hover:text-muted-foreground transition-colors p-0.5",
|
|
456
|
+
onClick: onToggleCollapse,
|
|
457
|
+
children: /* @__PURE__ */ jsx4(ChevronsLeft, { className: "h-3.5 w-3.5" })
|
|
458
|
+
}
|
|
459
|
+
) }),
|
|
460
|
+
/* @__PURE__ */ jsx4(TooltipContent2, { side: "right", children: "Collapse navigation" })
|
|
461
|
+
] })
|
|
462
|
+
] }),
|
|
463
|
+
/* @__PURE__ */ jsx4(ScrollArea, { className: "flex-1", children: /* @__PURE__ */ jsx4("nav", { className: "px-2 pb-4", children: mergedNavigation.map((group, gi) => /* @__PURE__ */ jsxs3("div", { className: cn2(gi > 0 && "mt-4"), children: [
|
|
464
|
+
group.heading && /* @__PURE__ */ jsx4("h6", { className: "heading-meta px-2 mb-1", children: group.heading }),
|
|
465
|
+
/* @__PURE__ */ jsx4("ul", { className: "space-y-0.5", children: group.links.map((link) => {
|
|
466
|
+
const isActive = pathname === link.path;
|
|
467
|
+
return /* @__PURE__ */ jsx4("li", { children: /* @__PURE__ */ jsx4(
|
|
468
|
+
Link,
|
|
469
|
+
{
|
|
470
|
+
to: link.path,
|
|
471
|
+
className: cn2(
|
|
472
|
+
"flex items-center rounded-md px-2 py-1.5 text-sm transition-colors",
|
|
473
|
+
isActive ? "bg-accent text-accent-foreground font-medium" : "text-muted-foreground hover:text-foreground hover:bg-accent/50"
|
|
474
|
+
),
|
|
475
|
+
children: link.label
|
|
476
|
+
}
|
|
477
|
+
) }, link.path);
|
|
478
|
+
}) })
|
|
479
|
+
] }, group.id)) }) })
|
|
480
|
+
] });
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
// src/SidePane.tsx
|
|
484
|
+
import { X } from "lucide-react";
|
|
485
|
+
import { cn as cn3 } from "@petrarca/sonnet-core";
|
|
486
|
+
import { useResizablePanel } from "@petrarca/sonnet-core/hooks";
|
|
487
|
+
|
|
488
|
+
// src/sidePaneState.ts
|
|
489
|
+
import { createContext as createContext2, useContext as useContext2 } from "react";
|
|
490
|
+
var SidePaneContext = createContext2(null);
|
|
491
|
+
function useSidePaneState() {
|
|
492
|
+
const ctx = useContext2(SidePaneContext);
|
|
493
|
+
if (!ctx) {
|
|
494
|
+
throw new Error("useSidePaneState must be used within an AppShell");
|
|
495
|
+
}
|
|
496
|
+
return ctx;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
// src/SidePane.tsx
|
|
500
|
+
import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
501
|
+
var DEFAULT_WIDTH = 320;
|
|
502
|
+
var DEFAULT_MIN = 240;
|
|
503
|
+
var DEFAULT_MAX = 800;
|
|
504
|
+
function SidePane() {
|
|
505
|
+
const { pane, close } = useSidePaneState();
|
|
506
|
+
const { panelWidth, handlePointerDown, handleDoubleClick, separatorProps } = useResizablePanel({
|
|
507
|
+
defaultWidth: pane?.defaultWidth ?? DEFAULT_WIDTH,
|
|
508
|
+
minWidth: pane?.minWidth ?? DEFAULT_MIN,
|
|
509
|
+
maxWidth: pane?.maxWidth ?? DEFAULT_MAX,
|
|
510
|
+
direction: "right-edge"
|
|
511
|
+
});
|
|
512
|
+
if (!pane) return null;
|
|
513
|
+
return /* @__PURE__ */ jsxs4(
|
|
514
|
+
"div",
|
|
515
|
+
{
|
|
516
|
+
className: "relative flex shrink-0 flex-col border-r bg-background",
|
|
517
|
+
style: { width: panelWidth },
|
|
518
|
+
children: [
|
|
519
|
+
/* @__PURE__ */ jsx5("div", { className: "flex h-10 shrink-0 items-center justify-end border-b px-2", children: /* @__PURE__ */ jsx5(
|
|
520
|
+
"button",
|
|
521
|
+
{
|
|
522
|
+
onClick: close,
|
|
523
|
+
className: cn3(
|
|
524
|
+
"flex h-6 w-6 items-center justify-center rounded text-muted-foreground",
|
|
525
|
+
"hover:bg-accent hover:text-foreground transition-colors",
|
|
526
|
+
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
527
|
+
),
|
|
528
|
+
"aria-label": "Close side pane",
|
|
529
|
+
children: /* @__PURE__ */ jsx5(X, { className: "h-3.5 w-3.5" })
|
|
530
|
+
}
|
|
531
|
+
) }),
|
|
532
|
+
/* @__PURE__ */ jsx5("div", { className: "flex-1 overflow-auto", children: pane.content }),
|
|
533
|
+
/* @__PURE__ */ jsx5(
|
|
534
|
+
"div",
|
|
535
|
+
{
|
|
536
|
+
...separatorProps,
|
|
537
|
+
onPointerDown: handlePointerDown,
|
|
538
|
+
onDoubleClick: handleDoubleClick,
|
|
539
|
+
className: cn3(
|
|
540
|
+
"absolute right-0 top-0 h-full w-1 cursor-col-resize",
|
|
541
|
+
"hover:bg-primary/40 active:bg-primary/60 transition-colors"
|
|
542
|
+
)
|
|
543
|
+
}
|
|
544
|
+
)
|
|
545
|
+
]
|
|
546
|
+
}
|
|
547
|
+
);
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
// src/CommandMenu.tsx
|
|
551
|
+
import { useEffect as useEffect2, useCallback } from "react";
|
|
552
|
+
import { useNavigate } from "react-router-dom";
|
|
553
|
+
import {
|
|
554
|
+
CommandDialog,
|
|
555
|
+
CommandEmpty,
|
|
556
|
+
CommandGroup,
|
|
557
|
+
CommandInput,
|
|
558
|
+
CommandItem,
|
|
559
|
+
CommandList,
|
|
560
|
+
CommandSeparator
|
|
561
|
+
} from "@petrarca/sonnet-ui";
|
|
562
|
+
import { Fragment, jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
563
|
+
function CommandMenu({ open, onOpenChange }) {
|
|
564
|
+
const navigate = useNavigate();
|
|
565
|
+
const { mainModules, bottomModules } = useShellModules();
|
|
566
|
+
useEffect2(() => {
|
|
567
|
+
const handleKeyDown = (e) => {
|
|
568
|
+
if (e.key === "k" && (e.metaKey || e.ctrlKey)) {
|
|
569
|
+
e.preventDefault();
|
|
570
|
+
onOpenChange(!open);
|
|
571
|
+
}
|
|
572
|
+
};
|
|
573
|
+
document.addEventListener("keydown", handleKeyDown);
|
|
574
|
+
return () => document.removeEventListener("keydown", handleKeyDown);
|
|
575
|
+
}, [open, onOpenChange]);
|
|
576
|
+
const handleSelect = useCallback(
|
|
577
|
+
(path) => {
|
|
578
|
+
navigate(path);
|
|
579
|
+
onOpenChange(false);
|
|
580
|
+
},
|
|
581
|
+
[navigate, onOpenChange]
|
|
582
|
+
);
|
|
583
|
+
const renderNavGroup = (service) => {
|
|
584
|
+
if (service.navigation.length === 0) return null;
|
|
585
|
+
const Icon = service.icon;
|
|
586
|
+
return /* @__PURE__ */ jsx6(CommandGroup, { heading: service.label, children: service.navigation.flatMap(
|
|
587
|
+
(group) => group.links.map((link) => /* @__PURE__ */ jsxs5(
|
|
588
|
+
CommandItem,
|
|
589
|
+
{
|
|
590
|
+
value: `${service.label} ${group.heading ?? ""} ${link.label}`,
|
|
591
|
+
onSelect: () => handleSelect(link.path),
|
|
592
|
+
children: [
|
|
593
|
+
/* @__PURE__ */ jsx6(Icon, { className: "mr-2 h-4 w-4 text-muted-foreground" }),
|
|
594
|
+
/* @__PURE__ */ jsx6("span", { children: link.label }),
|
|
595
|
+
group.heading && /* @__PURE__ */ jsx6("span", { className: "ml-auto text-xs text-muted-foreground", children: group.heading })
|
|
596
|
+
]
|
|
597
|
+
},
|
|
598
|
+
link.path
|
|
599
|
+
))
|
|
600
|
+
) }, `nav-${service.id}`);
|
|
601
|
+
};
|
|
602
|
+
const renderCommandGroup = (service) => {
|
|
603
|
+
if (!service.commands?.length) return null;
|
|
604
|
+
const Icon = service.icon;
|
|
605
|
+
return /* @__PURE__ */ jsx6(
|
|
606
|
+
CommandGroup,
|
|
607
|
+
{
|
|
608
|
+
heading: service.commands[0].group ?? service.label,
|
|
609
|
+
children: service.commands.map((cmd) => {
|
|
610
|
+
const CmdIcon = cmd.icon ?? Icon;
|
|
611
|
+
return /* @__PURE__ */ jsxs5(
|
|
612
|
+
CommandItem,
|
|
613
|
+
{
|
|
614
|
+
value: `${service.label} ${cmd.label}`,
|
|
615
|
+
onSelect: () => {
|
|
616
|
+
cmd.action();
|
|
617
|
+
onOpenChange(false);
|
|
618
|
+
},
|
|
619
|
+
children: [
|
|
620
|
+
/* @__PURE__ */ jsx6(CmdIcon, { className: "mr-2 h-4 w-4 text-muted-foreground" }),
|
|
621
|
+
/* @__PURE__ */ jsx6("span", { children: cmd.label })
|
|
622
|
+
]
|
|
623
|
+
},
|
|
624
|
+
cmd.id
|
|
625
|
+
);
|
|
626
|
+
})
|
|
627
|
+
},
|
|
628
|
+
`cmd-${service.id}`
|
|
629
|
+
);
|
|
630
|
+
};
|
|
631
|
+
const allModules = [...mainModules, ...bottomModules];
|
|
632
|
+
return /* @__PURE__ */ jsxs5(CommandDialog, { open, onOpenChange, children: [
|
|
633
|
+
/* @__PURE__ */ jsx6(CommandInput, { placeholder: "Search pages, services, actions..." }),
|
|
634
|
+
/* @__PURE__ */ jsxs5(CommandList, { children: [
|
|
635
|
+
/* @__PURE__ */ jsx6(CommandEmpty, { children: "No results found." }),
|
|
636
|
+
mainModules.map(renderNavGroup),
|
|
637
|
+
/* @__PURE__ */ jsx6(CommandSeparator, {}),
|
|
638
|
+
bottomModules.map(renderNavGroup),
|
|
639
|
+
allModules.some((m) => m.commands?.length) && /* @__PURE__ */ jsxs5(Fragment, { children: [
|
|
640
|
+
/* @__PURE__ */ jsx6(CommandSeparator, {}),
|
|
641
|
+
allModules.map(renderCommandGroup)
|
|
642
|
+
] })
|
|
643
|
+
] })
|
|
644
|
+
] });
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
// src/shellConfig.ts
|
|
648
|
+
import { createContext as createContext3, useContext as useContext3 } from "react";
|
|
649
|
+
var ShellConfigContext = createContext3(null);
|
|
650
|
+
function useShellConfig() {
|
|
651
|
+
const config = useContext3(ShellConfigContext);
|
|
652
|
+
if (!config) {
|
|
653
|
+
throw new Error("useShellConfig must be used within a ShellConfigProvider");
|
|
654
|
+
}
|
|
655
|
+
return config;
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
// src/AppShell.tsx
|
|
659
|
+
import { Fragment as Fragment2, jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
660
|
+
var PANEL_WIDTH_CLASS = {
|
|
661
|
+
narrow: "w-[480px]",
|
|
662
|
+
default: "w-[640px]",
|
|
663
|
+
wide: "w-[800px]",
|
|
664
|
+
half: "w-[50vw]",
|
|
665
|
+
full: "w-screen",
|
|
666
|
+
// legacy aliases
|
|
667
|
+
lg: "w-[640px]",
|
|
668
|
+
"1/3": "w-[33vw]",
|
|
669
|
+
"1/2": "w-[50vw]"
|
|
670
|
+
};
|
|
671
|
+
function isSidePaneModule(service) {
|
|
672
|
+
return !!service.sidePane && service.navigation.length === 0;
|
|
673
|
+
}
|
|
674
|
+
function selectSidePaneModule(service, current, setSidePaneState, navigate) {
|
|
675
|
+
if (current?.moduleId === service.id) {
|
|
676
|
+
setSidePaneState(null);
|
|
677
|
+
} else {
|
|
678
|
+
setSidePaneState({
|
|
679
|
+
moduleId: service.id,
|
|
680
|
+
content: null,
|
|
681
|
+
defaultWidth: service.sidePane?.defaultWidth,
|
|
682
|
+
minWidth: service.sidePane?.minWidth,
|
|
683
|
+
maxWidth: service.sidePane?.maxWidth
|
|
684
|
+
});
|
|
685
|
+
navigate(service.basePath);
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
function selectNavModule(service, navigate, setSubNavCollapsed) {
|
|
689
|
+
setSubNavCollapsed(false);
|
|
690
|
+
const target = service.navigation[0]?.links[0]?.path ?? service.basePath;
|
|
691
|
+
navigate(target);
|
|
692
|
+
}
|
|
693
|
+
function useShellApiInit(deps) {
|
|
694
|
+
const stableRefs = useRef2(deps);
|
|
695
|
+
useEffect3(() => {
|
|
696
|
+
const r = stableRefs.current;
|
|
697
|
+
initNavigation(r.navigate, r.openCommandMenu);
|
|
698
|
+
initFeatureNav(r.featureLookup);
|
|
699
|
+
initPanel(r.handlePanelOpen, r.handlePanelClose);
|
|
700
|
+
initFullscreen(r.handleFullscreenEnter, r.handleFullscreenExit);
|
|
701
|
+
initSidePane(
|
|
702
|
+
r.handleSidePaneOpen,
|
|
703
|
+
r.handleSidePaneClose,
|
|
704
|
+
r.handleSidePaneIsOpen,
|
|
705
|
+
r.handleSidePaneActiveModuleId
|
|
706
|
+
);
|
|
707
|
+
initDialog(
|
|
708
|
+
(opts) => new Promise((resolve) => r.setDialogState({ opts, resolve }))
|
|
709
|
+
);
|
|
710
|
+
}, []);
|
|
711
|
+
}
|
|
712
|
+
function useActiveService(modules) {
|
|
713
|
+
const location = useLocation2();
|
|
714
|
+
const serviceByBasePath = useMemo3(
|
|
715
|
+
() => new Map(
|
|
716
|
+
modules.filter((m) => !m.hidden && m.basePath !== "/").map((m) => [m.basePath, m.id])
|
|
717
|
+
),
|
|
718
|
+
[modules]
|
|
719
|
+
);
|
|
720
|
+
const resolveServiceFromPath = useCallback2(
|
|
721
|
+
(pathname) => {
|
|
722
|
+
for (const [basePath, id] of serviceByBasePath) {
|
|
723
|
+
if (pathname.startsWith(basePath)) return id;
|
|
724
|
+
}
|
|
725
|
+
return null;
|
|
726
|
+
},
|
|
727
|
+
[serviceByBasePath]
|
|
728
|
+
);
|
|
729
|
+
const [selectedServiceId, setSelectedServiceId] = useState(
|
|
730
|
+
() => resolveServiceFromPath(location.pathname)
|
|
731
|
+
);
|
|
732
|
+
useEffect3(
|
|
733
|
+
() => setSelectedServiceId(resolveServiceFromPath(location.pathname)),
|
|
734
|
+
[location.pathname, resolveServiceFromPath]
|
|
735
|
+
);
|
|
736
|
+
const activeService = selectedServiceId ? modules.find((s) => s.id === selectedServiceId) ?? null : null;
|
|
737
|
+
return { activeService, selectedServiceId, setSelectedServiceId };
|
|
738
|
+
}
|
|
739
|
+
function useSidePaneState2() {
|
|
740
|
+
const [sidePaneState, setSidePaneState] = useState(
|
|
741
|
+
null
|
|
742
|
+
);
|
|
743
|
+
const handleOpen = useCallback2(
|
|
744
|
+
(opts) => setSidePaneState(opts),
|
|
745
|
+
[]
|
|
746
|
+
);
|
|
747
|
+
const handleClose = useCallback2(() => setSidePaneState(null), []);
|
|
748
|
+
const handleIsOpen = useCallback2(
|
|
749
|
+
() => sidePaneState !== null,
|
|
750
|
+
[sidePaneState]
|
|
751
|
+
);
|
|
752
|
+
const handleActiveModuleId = useCallback2(
|
|
753
|
+
() => sidePaneState?.moduleId ?? null,
|
|
754
|
+
[sidePaneState]
|
|
755
|
+
);
|
|
756
|
+
return {
|
|
757
|
+
sidePaneState,
|
|
758
|
+
setSidePaneState,
|
|
759
|
+
handleOpen,
|
|
760
|
+
handleClose,
|
|
761
|
+
handleIsOpen,
|
|
762
|
+
handleActiveModuleId
|
|
763
|
+
};
|
|
764
|
+
}
|
|
765
|
+
function usePanelState() {
|
|
766
|
+
const [panelState, setPanelState] = useState(null);
|
|
767
|
+
const panelOnCloseRef = useRef2(void 0);
|
|
768
|
+
const handleOpen = useCallback2(
|
|
769
|
+
(opts) => setPanelState(opts),
|
|
770
|
+
[]
|
|
771
|
+
);
|
|
772
|
+
const handleClose = useCallback2(() => {
|
|
773
|
+
panelOnCloseRef.current = panelState?.onClose;
|
|
774
|
+
setPanelState(null);
|
|
775
|
+
}, [panelState]);
|
|
776
|
+
const panelWidth = PANEL_WIDTH_CLASS[panelState?.width ?? "default"] ?? PANEL_WIDTH_CLASS.default;
|
|
777
|
+
const panelOffsetTop = panelState?.coverage === "full" ? "0px" : "3rem";
|
|
778
|
+
return {
|
|
779
|
+
panelState,
|
|
780
|
+
panelOnCloseRef,
|
|
781
|
+
handleOpen,
|
|
782
|
+
handleClose,
|
|
783
|
+
panelWidth,
|
|
784
|
+
panelOffsetTop
|
|
785
|
+
};
|
|
786
|
+
}
|
|
787
|
+
function useDialogState() {
|
|
788
|
+
const [dialogState, setDialogState] = useState(null);
|
|
789
|
+
const handleConfirm = useCallback2(() => {
|
|
790
|
+
dialogState?.resolve(true);
|
|
791
|
+
setDialogState(null);
|
|
792
|
+
}, [dialogState]);
|
|
793
|
+
const handleCancel = useCallback2(() => {
|
|
794
|
+
dialogState?.resolve(false);
|
|
795
|
+
setDialogState(null);
|
|
796
|
+
}, [dialogState]);
|
|
797
|
+
return { dialogState, setDialogState, handleConfirm, handleCancel };
|
|
798
|
+
}
|
|
799
|
+
function useFeatureLookup(modules) {
|
|
800
|
+
return useMemo3(() => {
|
|
801
|
+
const map = /* @__PURE__ */ new Map();
|
|
802
|
+
for (const m of modules) {
|
|
803
|
+
for (const group of m.navigation) {
|
|
804
|
+
for (const link of group.links) {
|
|
805
|
+
if (link.id) map.set(link.id, link.path);
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
return (featureId) => map.get(featureId) ?? null;
|
|
810
|
+
}, [modules]);
|
|
811
|
+
}
|
|
812
|
+
function AppShell({ registry }) {
|
|
813
|
+
const navigate = useNavigate2();
|
|
814
|
+
const config = useShellConfig();
|
|
815
|
+
const { modules } = registry;
|
|
816
|
+
const { activeService, setSelectedServiceId } = useActiveService(modules);
|
|
817
|
+
const [subNavCollapsed, setSubNavCollapsed] = useState(false);
|
|
818
|
+
const [commandMenuOpen, setCommandMenuOpen] = useState(false);
|
|
819
|
+
const openCommandMenu = useCallback2(() => setCommandMenuOpen(true), []);
|
|
820
|
+
const [fullscreenState, setFullscreenState] = useState(null);
|
|
821
|
+
const handleFullscreenEnter = useCallback2(
|
|
822
|
+
(opts) => setFullscreenState(opts),
|
|
823
|
+
[]
|
|
824
|
+
);
|
|
825
|
+
const handleFullscreenExit = useCallback2(() => setFullscreenState(null), []);
|
|
826
|
+
const sidePane2 = useSidePaneState2();
|
|
827
|
+
const panel2 = usePanelState();
|
|
828
|
+
const dialog2 = useDialogState();
|
|
829
|
+
const featureLookup = useFeatureLookup(modules);
|
|
830
|
+
useShellApiInit({
|
|
831
|
+
navigate,
|
|
832
|
+
openCommandMenu,
|
|
833
|
+
handlePanelOpen: panel2.handleOpen,
|
|
834
|
+
handlePanelClose: panel2.handleClose,
|
|
835
|
+
handleFullscreenEnter,
|
|
836
|
+
handleFullscreenExit,
|
|
837
|
+
handleSidePaneOpen: sidePane2.handleOpen,
|
|
838
|
+
handleSidePaneClose: sidePane2.handleClose,
|
|
839
|
+
handleSidePaneIsOpen: sidePane2.handleIsOpen,
|
|
840
|
+
handleSidePaneActiveModuleId: sidePane2.handleActiveModuleId,
|
|
841
|
+
setDialogState: dialog2.setDialogState,
|
|
842
|
+
featureLookup
|
|
843
|
+
});
|
|
844
|
+
const handleServiceSelect = useCallback2(
|
|
845
|
+
(serviceId) => {
|
|
846
|
+
const service = modules.find((s) => s.id === serviceId);
|
|
847
|
+
if (!service) return;
|
|
848
|
+
if (isSidePaneModule(service)) {
|
|
849
|
+
selectSidePaneModule(
|
|
850
|
+
service,
|
|
851
|
+
sidePane2.sidePaneState,
|
|
852
|
+
sidePane2.setSidePaneState,
|
|
853
|
+
navigate
|
|
854
|
+
);
|
|
855
|
+
} else {
|
|
856
|
+
selectNavModule(service, navigate, setSubNavCollapsed);
|
|
857
|
+
}
|
|
858
|
+
setSelectedServiceId(serviceId);
|
|
859
|
+
},
|
|
860
|
+
[
|
|
861
|
+
modules,
|
|
862
|
+
sidePane2.sidePaneState,
|
|
863
|
+
sidePane2.setSidePaneState,
|
|
864
|
+
navigate,
|
|
865
|
+
setSelectedServiceId
|
|
866
|
+
]
|
|
867
|
+
);
|
|
868
|
+
const sidePaneContextValue = useMemo3(
|
|
869
|
+
() => ({
|
|
870
|
+
pane: sidePane2.sidePaneState,
|
|
871
|
+
open: sidePane2.handleOpen,
|
|
872
|
+
close: sidePane2.handleClose,
|
|
873
|
+
toggle: (opts) => {
|
|
874
|
+
if (sidePane2.sidePaneState?.moduleId === opts.moduleId) {
|
|
875
|
+
sidePane2.setSidePaneState(null);
|
|
876
|
+
} else {
|
|
877
|
+
sidePane2.setSidePaneState(opts);
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
}),
|
|
881
|
+
[sidePane2]
|
|
882
|
+
);
|
|
883
|
+
return /* @__PURE__ */ jsx7(ShellModulesContext.Provider, { value: registry, children: /* @__PURE__ */ jsx7(SidePaneContext.Provider, { value: sidePaneContextValue, children: /* @__PURE__ */ jsx7(TooltipProvider, { children: /* @__PURE__ */ jsxs6("div", { className: "flex h-screen w-screen flex-col overflow-hidden", children: [
|
|
884
|
+
/* @__PURE__ */ jsx7(TopBar, { children: config.topBar }),
|
|
885
|
+
/* @__PURE__ */ jsx7(
|
|
886
|
+
CommandMenu,
|
|
887
|
+
{
|
|
888
|
+
open: commandMenuOpen,
|
|
889
|
+
onOpenChange: setCommandMenuOpen
|
|
890
|
+
}
|
|
891
|
+
),
|
|
892
|
+
/* @__PURE__ */ jsxs6("div", { className: "flex flex-1 overflow-hidden", children: [
|
|
893
|
+
/* @__PURE__ */ jsx7(
|
|
894
|
+
IconRail,
|
|
895
|
+
{
|
|
896
|
+
activeServiceId: activeService?.id ?? null,
|
|
897
|
+
activeSidePaneModuleId: sidePane2.sidePaneState?.moduleId ?? null,
|
|
898
|
+
onServiceSelect: handleServiceSelect
|
|
899
|
+
}
|
|
900
|
+
),
|
|
901
|
+
activeService && activeService.navigation.length > 0 && /* @__PURE__ */ jsx7(
|
|
902
|
+
SubNavPanel,
|
|
903
|
+
{
|
|
904
|
+
service: activeService,
|
|
905
|
+
collapsed: subNavCollapsed,
|
|
906
|
+
onToggleCollapse: () => setSubNavCollapsed((c) => !c)
|
|
907
|
+
}
|
|
908
|
+
),
|
|
909
|
+
/* @__PURE__ */ jsx7(SidePane, {}),
|
|
910
|
+
/* @__PURE__ */ jsx7("div", { className: "flex-1 min-w-0 overflow-hidden", children: /* @__PURE__ */ jsx7(ScrollArea2, { className: "h-full", children: /* @__PURE__ */ jsx7("main", { className: "p-6", children: /* @__PURE__ */ jsx7(Outlet, {}) }) }) })
|
|
911
|
+
] }),
|
|
912
|
+
dialog2.dialogState && /* @__PURE__ */ jsx7(
|
|
913
|
+
ConfirmDialog,
|
|
914
|
+
{
|
|
915
|
+
open: true,
|
|
916
|
+
title: dialog2.dialogState.opts.title,
|
|
917
|
+
description: dialog2.dialogState.opts.description,
|
|
918
|
+
confirmLabel: dialog2.dialogState.opts.confirmLabel,
|
|
919
|
+
cancelLabel: dialog2.dialogState.opts.cancelLabel,
|
|
920
|
+
variant: dialog2.dialogState.opts.variant,
|
|
921
|
+
onConfirm: dialog2.handleConfirm,
|
|
922
|
+
onCancel: dialog2.handleCancel
|
|
923
|
+
}
|
|
924
|
+
),
|
|
925
|
+
fullscreenState && /* @__PURE__ */ jsx7("div", { className: "fixed inset-0 z-50 bg-background overflow-auto", children: fullscreenState.content }),
|
|
926
|
+
/* @__PURE__ */ jsx7(
|
|
927
|
+
SlideOverPanel,
|
|
928
|
+
{
|
|
929
|
+
panelState: panel2.panelState,
|
|
930
|
+
panelWidth: panel2.panelWidth,
|
|
931
|
+
panelOffsetTop: panel2.panelOffsetTop,
|
|
932
|
+
panelOnCloseRef: panel2.panelOnCloseRef,
|
|
933
|
+
onClose: panel2.handleClose
|
|
934
|
+
}
|
|
935
|
+
)
|
|
936
|
+
] }) }) }) });
|
|
937
|
+
}
|
|
938
|
+
function SlideOverPanel({
|
|
939
|
+
panelState,
|
|
940
|
+
panelWidth,
|
|
941
|
+
panelOffsetTop,
|
|
942
|
+
panelOnCloseRef,
|
|
943
|
+
onClose
|
|
944
|
+
}) {
|
|
945
|
+
return /* @__PURE__ */ jsx7(
|
|
946
|
+
Sheet,
|
|
947
|
+
{
|
|
948
|
+
open: panelState !== null,
|
|
949
|
+
onOpenChange: (open) => {
|
|
950
|
+
if (!open) onClose();
|
|
951
|
+
},
|
|
952
|
+
children: /* @__PURE__ */ jsx7(
|
|
953
|
+
SheetContent,
|
|
954
|
+
{
|
|
955
|
+
side: "right",
|
|
956
|
+
className: `${panelWidth} max-w-[90vw]`,
|
|
957
|
+
offsetTop: panelOffsetTop,
|
|
958
|
+
onCloseAutoFocus: (e) => {
|
|
959
|
+
if (panelOnCloseRef.current) {
|
|
960
|
+
e.preventDefault();
|
|
961
|
+
panelOnCloseRef.current();
|
|
962
|
+
}
|
|
963
|
+
},
|
|
964
|
+
...!panelState?.description && {
|
|
965
|
+
"aria-describedby": void 0
|
|
966
|
+
},
|
|
967
|
+
children: panelState && /* @__PURE__ */ jsx7(PanelContent, { state: panelState })
|
|
968
|
+
}
|
|
969
|
+
)
|
|
970
|
+
}
|
|
971
|
+
);
|
|
972
|
+
}
|
|
973
|
+
function PanelContent({ state }) {
|
|
974
|
+
return /* @__PURE__ */ jsxs6(Fragment2, { children: [
|
|
975
|
+
state.title ? /* @__PURE__ */ jsxs6(SheetHeader, { children: [
|
|
976
|
+
/* @__PURE__ */ jsx7(SheetTitle, { children: state.title }),
|
|
977
|
+
state.description && /* @__PURE__ */ jsx7(SheetDescription, { children: state.description })
|
|
978
|
+
] }) : /* @__PURE__ */ jsx7(SheetTitle, { className: "sr-only", children: "Panel" }),
|
|
979
|
+
/* @__PURE__ */ jsx7(SheetBody, { children: state.content })
|
|
980
|
+
] });
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
// src/RootLayout.tsx
|
|
984
|
+
import { Toaster } from "sonner";
|
|
985
|
+
|
|
986
|
+
// src/ShellConfigProvider.tsx
|
|
987
|
+
import { jsx as jsx8 } from "react/jsx-runtime";
|
|
988
|
+
function ShellConfigProvider({
|
|
989
|
+
config,
|
|
990
|
+
children
|
|
991
|
+
}) {
|
|
992
|
+
return /* @__PURE__ */ jsx8(ShellConfigContext.Provider, { value: config, children });
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
// src/RootLayout.tsx
|
|
996
|
+
import { jsx as jsx9, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
997
|
+
function RootLayout({ config, registry }) {
|
|
998
|
+
return /* @__PURE__ */ jsxs7(ShellConfigProvider, { config, children: [
|
|
999
|
+
/* @__PURE__ */ jsx9(Toaster, { position: "top-right", richColors: true, closeButton: true }),
|
|
1000
|
+
/* @__PURE__ */ jsx9(AppShell, { registry })
|
|
1001
|
+
] });
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
// src/PlaceholderPage.tsx
|
|
1005
|
+
import { useLocation as useLocation3 } from "react-router-dom";
|
|
1006
|
+
import { jsx as jsx10, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
1007
|
+
function PlaceholderPage() {
|
|
1008
|
+
const { pathname } = useLocation3();
|
|
1009
|
+
return /* @__PURE__ */ jsxs8("div", { children: [
|
|
1010
|
+
/* @__PURE__ */ jsx10("h1", { className: "mb-1", children: pathToTitle(pathname) }),
|
|
1011
|
+
/* @__PURE__ */ jsx10("p", { className: "text-sm text-muted-foreground mb-6", children: pathname }),
|
|
1012
|
+
/* @__PURE__ */ jsx10("div", { className: "rounded-lg border-2 border-dashed border-border p-12 flex items-center justify-center", children: /* @__PURE__ */ jsx10("span", { className: "text-sm text-muted-foreground", children: "Content area \u2014 not yet implemented" }) })
|
|
1013
|
+
] });
|
|
1014
|
+
}
|
|
1015
|
+
function pathToTitle(path) {
|
|
1016
|
+
const last = path.split("/").filter(Boolean).pop();
|
|
1017
|
+
if (!last) return "Dashboard";
|
|
1018
|
+
return last.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
// src/SearchTrigger.tsx
|
|
1022
|
+
import { Search } from "lucide-react";
|
|
1023
|
+
import { Button as Button3 } from "@petrarca/sonnet-ui";
|
|
1024
|
+
import { jsx as jsx11, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
1025
|
+
function SearchTrigger() {
|
|
1026
|
+
return /* @__PURE__ */ jsxs9(
|
|
1027
|
+
Button3,
|
|
1028
|
+
{
|
|
1029
|
+
variant: "outline",
|
|
1030
|
+
size: "sm",
|
|
1031
|
+
className: "h-7 gap-2 text-xs text-muted-foreground w-52 justify-start",
|
|
1032
|
+
onClick: () => navigation.openCommandMenu(),
|
|
1033
|
+
children: [
|
|
1034
|
+
/* @__PURE__ */ jsx11(Search, { className: "h-3.5 w-3.5" }),
|
|
1035
|
+
/* @__PURE__ */ jsx11("span", { children: "Search..." }),
|
|
1036
|
+
/* @__PURE__ */ jsxs9("kbd", { className: "ml-auto pointer-events-none inline-flex h-5 select-none items-center gap-1 rounded border bg-muted px-1.5 font-mono text-[10px] font-medium text-muted-foreground", children: [
|
|
1037
|
+
/* @__PURE__ */ jsx11("span", { className: "text-xs", children: "\u2318" }),
|
|
1038
|
+
"K"
|
|
1039
|
+
] })
|
|
1040
|
+
]
|
|
1041
|
+
}
|
|
1042
|
+
);
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
// src/UserMenu.tsx
|
|
1046
|
+
import { LogOut, User } from "lucide-react";
|
|
1047
|
+
import { Button as Button4 } from "@petrarca/sonnet-ui";
|
|
1048
|
+
import { Avatar, AvatarFallback } from "@petrarca/sonnet-ui";
|
|
1049
|
+
import {
|
|
1050
|
+
DropdownMenu,
|
|
1051
|
+
DropdownMenuContent,
|
|
1052
|
+
DropdownMenuItem,
|
|
1053
|
+
DropdownMenuLabel,
|
|
1054
|
+
DropdownMenuSeparator,
|
|
1055
|
+
DropdownMenuTrigger
|
|
1056
|
+
} from "@petrarca/sonnet-ui";
|
|
1057
|
+
import { jsx as jsx12, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
1058
|
+
function UserMenu({ user, onSignOut }) {
|
|
1059
|
+
return /* @__PURE__ */ jsxs10(DropdownMenu, { children: [
|
|
1060
|
+
/* @__PURE__ */ jsx12(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsx12(Button4, { variant: "ghost", size: "compact", className: "rounded-full", children: /* @__PURE__ */ jsx12(Avatar, { className: "h-7 w-7", children: /* @__PURE__ */ jsx12(AvatarFallback, { className: "text-xs", children: user.initials }) }) }) }),
|
|
1061
|
+
/* @__PURE__ */ jsxs10(DropdownMenuContent, { align: "end", className: "w-48", children: [
|
|
1062
|
+
/* @__PURE__ */ jsx12(DropdownMenuLabel, { className: "font-normal", children: /* @__PURE__ */ jsxs10("div", { className: "flex flex-col gap-1", children: [
|
|
1063
|
+
/* @__PURE__ */ jsx12("p", { className: "text-sm font-medium", children: user.name }),
|
|
1064
|
+
/* @__PURE__ */ jsx12("p", { className: "text-xs text-muted-foreground", children: user.email })
|
|
1065
|
+
] }) }),
|
|
1066
|
+
/* @__PURE__ */ jsx12(DropdownMenuSeparator, {}),
|
|
1067
|
+
/* @__PURE__ */ jsxs10(DropdownMenuItem, { children: [
|
|
1068
|
+
/* @__PURE__ */ jsx12(User, { className: "mr-2 h-4 w-4" }),
|
|
1069
|
+
"Profile"
|
|
1070
|
+
] }),
|
|
1071
|
+
/* @__PURE__ */ jsx12(DropdownMenuSeparator, {}),
|
|
1072
|
+
/* @__PURE__ */ jsxs10(DropdownMenuItem, { onSelect: onSignOut, children: [
|
|
1073
|
+
/* @__PURE__ */ jsx12(LogOut, { className: "mr-2 h-4 w-4" }),
|
|
1074
|
+
"Sign out"
|
|
1075
|
+
] })
|
|
1076
|
+
] })
|
|
1077
|
+
] });
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
// src/createModuleRegistry.ts
|
|
1081
|
+
function createModuleRegistry(modules) {
|
|
1082
|
+
const visible = modules.filter((m) => !m.hidden);
|
|
1083
|
+
const mainModules = visible.filter((m) => !m.pinBottom);
|
|
1084
|
+
const bottomModules = visible.filter((m) => m.pinBottom);
|
|
1085
|
+
const allRoutes = modules.flatMap((m) => m.routes);
|
|
1086
|
+
const contributionsByPoint = /* @__PURE__ */ new Map();
|
|
1087
|
+
for (const m of modules) {
|
|
1088
|
+
for (const c of m.contributions ?? []) {
|
|
1089
|
+
const list = contributionsByPoint.get(c.extensionPoint) ?? [];
|
|
1090
|
+
list.push(c);
|
|
1091
|
+
contributionsByPoint.set(c.extensionPoint, list);
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
1094
|
+
for (const list of contributionsByPoint.values()) {
|
|
1095
|
+
list.sort((a, b) => a.order - b.order);
|
|
1096
|
+
}
|
|
1097
|
+
function getExtensionPoint(name) {
|
|
1098
|
+
return contributionsByPoint.get(name) ?? [];
|
|
1099
|
+
}
|
|
1100
|
+
return { modules, mainModules, bottomModules, allRoutes, getExtensionPoint };
|
|
1101
|
+
}
|
|
1102
|
+
|
|
1103
|
+
// src/OverviewCard.tsx
|
|
1104
|
+
import { cn as cn4 } from "@petrarca/sonnet-core";
|
|
1105
|
+
import { jsx as jsx13, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
1106
|
+
function FeatureLink({
|
|
1107
|
+
feature,
|
|
1108
|
+
children
|
|
1109
|
+
}) {
|
|
1110
|
+
return /* @__PURE__ */ jsx13(
|
|
1111
|
+
"span",
|
|
1112
|
+
{
|
|
1113
|
+
role: "link",
|
|
1114
|
+
tabIndex: 0,
|
|
1115
|
+
onClick: (e) => {
|
|
1116
|
+
e.stopPropagation();
|
|
1117
|
+
navigation.goToFeature(feature);
|
|
1118
|
+
},
|
|
1119
|
+
onKeyDown: (e) => {
|
|
1120
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
1121
|
+
e.preventDefault();
|
|
1122
|
+
e.stopPropagation();
|
|
1123
|
+
navigation.goToFeature(feature);
|
|
1124
|
+
}
|
|
1125
|
+
},
|
|
1126
|
+
className: "underline underline-offset-2 hover:text-foreground transition-colors cursor-pointer",
|
|
1127
|
+
children
|
|
1128
|
+
}
|
|
1129
|
+
);
|
|
1130
|
+
}
|
|
1131
|
+
function OverviewCard({
|
|
1132
|
+
icon: Icon,
|
|
1133
|
+
title,
|
|
1134
|
+
description,
|
|
1135
|
+
feature,
|
|
1136
|
+
className
|
|
1137
|
+
}) {
|
|
1138
|
+
return /* @__PURE__ */ jsxs11(
|
|
1139
|
+
"button",
|
|
1140
|
+
{
|
|
1141
|
+
onClick: feature ? () => navigation.goToFeature(feature) : void 0,
|
|
1142
|
+
disabled: !feature,
|
|
1143
|
+
className: cn4(
|
|
1144
|
+
"rounded-lg border bg-card p-5 text-left flex flex-col gap-3",
|
|
1145
|
+
"disabled:cursor-default",
|
|
1146
|
+
feature && "hover:bg-accent/50 transition-colors",
|
|
1147
|
+
className
|
|
1148
|
+
),
|
|
1149
|
+
children: [
|
|
1150
|
+
/* @__PURE__ */ jsx13("div", { className: "h-9 w-9 rounded-md bg-blue-50 flex items-center justify-center shrink-0", children: /* @__PURE__ */ jsx13(Icon, { className: "h-5 w-5 text-blue-600" }) }),
|
|
1151
|
+
/* @__PURE__ */ jsxs11("div", { children: [
|
|
1152
|
+
/* @__PURE__ */ jsx13("p", { className: "text-sm font-medium mb-1", children: title }),
|
|
1153
|
+
/* @__PURE__ */ jsx13("p", { className: "text-xs text-muted-foreground leading-relaxed", children: description })
|
|
1154
|
+
] })
|
|
1155
|
+
]
|
|
1156
|
+
}
|
|
1157
|
+
);
|
|
1158
|
+
}
|
|
1159
|
+
export {
|
|
1160
|
+
AppShell,
|
|
1161
|
+
CommandMenu,
|
|
1162
|
+
ConfirmDialog,
|
|
1163
|
+
FeatureLink,
|
|
1164
|
+
IconRail,
|
|
1165
|
+
OverviewCard,
|
|
1166
|
+
PlaceholderPage,
|
|
1167
|
+
RootLayout,
|
|
1168
|
+
SearchTrigger,
|
|
1169
|
+
ShellConfigProvider,
|
|
1170
|
+
SidePane,
|
|
1171
|
+
SidePaneContext,
|
|
1172
|
+
SubNavPanel,
|
|
1173
|
+
TopBar,
|
|
1174
|
+
UserMenu,
|
|
1175
|
+
createModuleRegistry,
|
|
1176
|
+
dialog,
|
|
1177
|
+
events,
|
|
1178
|
+
fullscreen,
|
|
1179
|
+
initDialog,
|
|
1180
|
+
initNavigation,
|
|
1181
|
+
initPanel,
|
|
1182
|
+
initSidePane,
|
|
1183
|
+
navigation,
|
|
1184
|
+
notification,
|
|
1185
|
+
panel,
|
|
1186
|
+
sidePane,
|
|
1187
|
+
useExtensionPoint,
|
|
1188
|
+
useMainModules,
|
|
1189
|
+
useShellConfig,
|
|
1190
|
+
useShellEvent,
|
|
1191
|
+
useShellModules,
|
|
1192
|
+
useSidePaneState
|
|
1193
|
+
};
|
|
1194
|
+
//# sourceMappingURL=index.js.map
|