@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/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