@pactor-app/ui 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.
@@ -0,0 +1,938 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/layout/index.ts
21
+ var layout_exports = {};
22
+ __export(layout_exports, {
23
+ ShadcnAdminLayout: () => ShadcnAdminLayout,
24
+ ShadcnBlankLayout: () => ShadcnBlankLayout,
25
+ ShadcnLandingLayout: () => ShadcnLandingLayout,
26
+ ShadcnLoginLayout: () => ShadcnLoginLayout,
27
+ collectDefaultOpenKeys: () => collectDefaultOpenKeys,
28
+ createShadcnLayoutRegistry: () => createShadcnLayoutRegistry,
29
+ findMenuChain: () => findMenuChain,
30
+ registerShadcnLayouts: () => registerShadcnLayouts,
31
+ resolveLayout: () => resolveLayout
32
+ });
33
+ module.exports = __toCommonJS(layout_exports);
34
+
35
+ // src/layout/admin/menu-chain.ts
36
+ function findMenuChain(registry, menuId) {
37
+ const walk = (menus, ancestors) => {
38
+ for (const menu of menus) {
39
+ const chain = [...ancestors, menu];
40
+ if (menu.id === menuId) {
41
+ return chain;
42
+ }
43
+ if (menu.children !== void 0) {
44
+ const hit = walk(menu.children, chain);
45
+ if (hit !== null) {
46
+ return hit;
47
+ }
48
+ }
49
+ }
50
+ return null;
51
+ };
52
+ return walk(registry.tree(), []) ?? [];
53
+ }
54
+
55
+ // src/layout/admin/menu-items.ts
56
+ function collectDefaultOpenKeys(registry, activeMenuId) {
57
+ const keys = [];
58
+ const walk = (menus) => {
59
+ for (const menu of menus) {
60
+ if (menu.type === "group" && menu.defaultOpen === true) {
61
+ keys.push(menu.id);
62
+ }
63
+ if (menu.children !== void 0) {
64
+ walk(menu.children);
65
+ }
66
+ }
67
+ };
68
+ walk(registry.tree());
69
+ if (activeMenuId !== void 0) {
70
+ for (const menu of findMenuChain(registry, activeMenuId)) {
71
+ if (menu.type === "group" && !keys.includes(menu.id)) {
72
+ keys.push(menu.id);
73
+ }
74
+ }
75
+ }
76
+ return keys;
77
+ }
78
+
79
+ // src/layout/admin/ShadcnAdminLayout.tsx
80
+ var import_permission = require("@pactor-app/permission");
81
+ var import_router = require("@pactor-app/router");
82
+ var import_runtime = require("@pactor-app/runtime");
83
+ var import_react2 = require("react");
84
+
85
+ // src/lib/utils.ts
86
+ var import_clsx = require("clsx");
87
+ var import_tailwind_merge = require("tailwind-merge");
88
+ function cn(...inputs) {
89
+ return (0, import_tailwind_merge.twMerge)((0, import_clsx.clsx)(inputs));
90
+ }
91
+
92
+ // src/ui/accordion.tsx
93
+ var import_accordion = require("@base-ui/react/accordion");
94
+ var import_lucide_react = require("lucide-react");
95
+ var import_jsx_runtime = require("react/jsx-runtime");
96
+ function toArray(value) {
97
+ if (value === void 0) {
98
+ return void 0;
99
+ }
100
+ return Array.isArray(value) ? value : [value];
101
+ }
102
+ function Accordion({
103
+ type = "single",
104
+ value,
105
+ defaultValue,
106
+ onValueChange,
107
+ ...props
108
+ }) {
109
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
110
+ import_accordion.Accordion.Root,
111
+ {
112
+ "data-slot": "accordion",
113
+ multiple: type === "multiple",
114
+ value: toArray(value),
115
+ defaultValue: toArray(defaultValue),
116
+ onValueChange: onValueChange === void 0 ? void 0 : (next) => onValueChange(type === "multiple" ? next : next[0] ?? ""),
117
+ ...props
118
+ }
119
+ );
120
+ }
121
+ function AccordionItem({
122
+ className,
123
+ ...props
124
+ }) {
125
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
126
+ import_accordion.Accordion.Item,
127
+ {
128
+ "data-slot": "accordion-item",
129
+ className: cn("border-b border-border last:border-b-0", className),
130
+ ...props
131
+ }
132
+ );
133
+ }
134
+ function AccordionTrigger({
135
+ className,
136
+ children,
137
+ ...props
138
+ }) {
139
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_accordion.Accordion.Header, { className: "flex", children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
140
+ import_accordion.Accordion.Trigger,
141
+ {
142
+ "data-slot": "accordion-trigger",
143
+ className: cn(
144
+ "flex flex-1 cursor-pointer items-center justify-between gap-4 rounded-md py-4 text-left text-sm font-medium transition-colors outline-none hover:bg-accent hover:text-accent-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 [&[aria-expanded=true]>svg]:rotate-180",
145
+ className
146
+ ),
147
+ ...props,
148
+ children: [
149
+ children,
150
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.ChevronDownIcon, { className: "pointer-events-none size-4 shrink-0 translate-y-0.5 text-muted-foreground transition-transform duration-200" })
151
+ ]
152
+ }
153
+ ) });
154
+ }
155
+ function AccordionContent({
156
+ className,
157
+ children,
158
+ ...props
159
+ }) {
160
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
161
+ import_accordion.Accordion.Panel,
162
+ {
163
+ "data-slot": "accordion-content",
164
+ className: "overflow-hidden text-sm data-open:animate-accordion-down data-closed:animate-accordion-up",
165
+ ...props,
166
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: cn("pt-0 pb-4", className), children })
167
+ }
168
+ );
169
+ }
170
+
171
+ // src/ui/popover.tsx
172
+ var import_popover = require("@base-ui/react/popover");
173
+ var import_jsx_runtime2 = require("react/jsx-runtime");
174
+ function Popover({
175
+ ...props
176
+ }) {
177
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_popover.Popover.Root, { "data-slot": "popover", ...props });
178
+ }
179
+ function PopoverTrigger({
180
+ ...props
181
+ }) {
182
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_popover.Popover.Trigger, { "data-slot": "popover-trigger", ...props });
183
+ }
184
+ function PopoverContent({
185
+ className,
186
+ align = "center",
187
+ side = "bottom",
188
+ sideOffset = 4,
189
+ anchor,
190
+ ...props
191
+ }) {
192
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_popover.Popover.Portal, { children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
193
+ import_popover.Popover.Positioner,
194
+ {
195
+ align,
196
+ side,
197
+ sideOffset,
198
+ anchor,
199
+ className: "isolate z-50",
200
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
201
+ import_popover.Popover.Popup,
202
+ {
203
+ "data-slot": "popover-content",
204
+ className: cn(
205
+ "z-50 w-72 rounded-md border border-border bg-popover p-4 text-popover-foreground shadow-md outline-none duration-200 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95",
206
+ className
207
+ ),
208
+ ...props
209
+ }
210
+ )
211
+ }
212
+ ) });
213
+ }
214
+
215
+ // src/layout/admin/use-media-query.ts
216
+ var import_react = require("react");
217
+ var BREAKPOINT_PX = {
218
+ xs: 480,
219
+ sm: 576,
220
+ md: 768,
221
+ lg: 992,
222
+ xl: 1200,
223
+ xxl: 1600
224
+ };
225
+ function useMediaQuery(query) {
226
+ const [matches, setMatches] = (0, import_react.useState)(
227
+ () => typeof window !== "undefined" && typeof window.matchMedia === "function" ? window.matchMedia(query).matches : false
228
+ );
229
+ (0, import_react.useEffect)(() => {
230
+ if (typeof window === "undefined" || typeof window.matchMedia !== "function") {
231
+ return;
232
+ }
233
+ const mql = window.matchMedia(query);
234
+ const onChange = (event) => setMatches(event.matches);
235
+ setMatches(mql.matches);
236
+ mql.addEventListener("change", onChange);
237
+ return () => mql.removeEventListener("change", onChange);
238
+ }, [query]);
239
+ return matches;
240
+ }
241
+ function useBreakpointBelow(breakpoint) {
242
+ const matches = useMediaQuery(
243
+ breakpoint ? `(max-width: ${BREAKPOINT_PX[breakpoint] - 1}px)` : "(min-width: 0px)"
244
+ );
245
+ return breakpoint !== void 0 && matches;
246
+ }
247
+
248
+ // src/layout/admin/ShadcnAdminLayout.tsx
249
+ var import_jsx_runtime3 = require("react/jsx-runtime");
250
+ function buildVisibleMenuTree(registry, permissionChecker) {
251
+ const tree = registry.tree();
252
+ return permissionChecker === void 0 ? [...tree] : (0, import_permission.filterMenusByPermission)(tree, permissionChecker);
253
+ }
254
+ function navigateMenuItem(menu, bridge) {
255
+ if (menu === void 0 || menu.type === "group" || menu.path === void 0) {
256
+ return;
257
+ }
258
+ if (menu.external === true || menu.type === "link") {
259
+ window.open(menu.path, menu.target ?? "_blank");
260
+ return;
261
+ }
262
+ bridge.push(menu.path);
263
+ }
264
+ var menuItemClass = "flex w-full cursor-pointer items-center rounded-md px-3 py-2 text-left text-sm text-muted-foreground transition-colors hover:bg-accent hover:text-accent-foreground disabled:pointer-events-none disabled:opacity-50";
265
+ function CollapsedGroupFlyout({
266
+ menu,
267
+ registry,
268
+ bridge,
269
+ activeMenuId,
270
+ defaultOpenKeys
271
+ }) {
272
+ const [open, setOpen] = (0, import_react2.useState)(false);
273
+ const closeTimer = (0, import_react2.useRef)(void 0);
274
+ const openNow = () => {
275
+ window.clearTimeout(closeTimer.current);
276
+ setOpen(true);
277
+ };
278
+ const closeLater = () => {
279
+ window.clearTimeout(closeTimer.current);
280
+ closeTimer.current = window.setTimeout(() => setOpen(false), 200);
281
+ };
282
+ (0, import_react2.useEffect)(() => () => window.clearTimeout(closeTimer.current), []);
283
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Popover, { open, onOpenChange: setOpen, children: [
284
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
285
+ PopoverTrigger,
286
+ {
287
+ render: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
288
+ "button",
289
+ {
290
+ type: "button",
291
+ "data-slot": "menu-item",
292
+ className: cn(
293
+ menuItemClass,
294
+ "mx-auto size-10 justify-center px-0 font-semibold"
295
+ ),
296
+ disabled: menu.disabled,
297
+ title: menu.title,
298
+ onMouseEnter: openNow,
299
+ onMouseLeave: closeLater,
300
+ children: menu.title.slice(0, 1)
301
+ }
302
+ )
303
+ }
304
+ ),
305
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
306
+ PopoverContent,
307
+ {
308
+ "data-slot": "menu-flyout",
309
+ side: "right",
310
+ align: "start",
311
+ sideOffset: 8,
312
+ className: "max-h-[70vh] w-40 overflow-auto p-1",
313
+ initialFocus: false,
314
+ onMouseEnter: openNow,
315
+ onMouseLeave: closeLater,
316
+ onClick: (event) => {
317
+ if (event.target.closest("[data-slot=menu-item]")) {
318
+ setOpen(false);
319
+ }
320
+ },
321
+ children: [
322
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "px-3 pt-2 pb-1 text-xs font-semibold text-muted-foreground", children: menu.title }),
323
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
324
+ MenuTree,
325
+ {
326
+ menus: menu.children ?? [],
327
+ registry,
328
+ bridge,
329
+ activeMenuId,
330
+ defaultOpenKeys
331
+ }
332
+ )
333
+ ]
334
+ }
335
+ )
336
+ ] });
337
+ }
338
+ function MenuTree({
339
+ menus,
340
+ registry,
341
+ bridge,
342
+ activeMenuId,
343
+ defaultOpenKeys,
344
+ collapsed = false
345
+ }) {
346
+ if (collapsed) {
347
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { "data-slot": "menu", "data-collapsed": "true", className: "flex flex-col gap-1", children: menus.map((menu) => {
348
+ if (menu.type === "group") {
349
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
350
+ CollapsedGroupFlyout,
351
+ {
352
+ menu,
353
+ registry,
354
+ bridge,
355
+ activeMenuId,
356
+ defaultOpenKeys
357
+ },
358
+ menu.id
359
+ );
360
+ }
361
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
362
+ "button",
363
+ {
364
+ type: "button",
365
+ "data-slot": "menu-item",
366
+ "data-active": menu.id === activeMenuId || void 0,
367
+ className: cn(
368
+ menuItemClass,
369
+ "mx-auto size-10 justify-center px-0 font-semibold",
370
+ menu.id === activeMenuId && "bg-accent font-semibold text-accent-foreground"
371
+ ),
372
+ disabled: menu.disabled,
373
+ title: menu.title,
374
+ onClick: () => navigateMenuItem(registry.findById(menu.id), bridge),
375
+ children: menu.title.slice(0, 1)
376
+ },
377
+ menu.id
378
+ );
379
+ }) });
380
+ }
381
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
382
+ Accordion,
383
+ {
384
+ type: "multiple",
385
+ defaultValue: defaultOpenKeys,
386
+ "data-slot": "menu",
387
+ className: "flex flex-col gap-0.5 px-2",
388
+ children: menus.map((menu) => {
389
+ if (menu.type === "group") {
390
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
391
+ AccordionItem,
392
+ {
393
+ value: menu.id,
394
+ "data-slot": "menu-group",
395
+ className: "border-b-0",
396
+ children: [
397
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
398
+ AccordionTrigger,
399
+ {
400
+ "data-slot": "menu-group-trigger",
401
+ className: cn(menuItemClass, "py-2 font-medium"),
402
+ disabled: menu.disabled,
403
+ title: menu.title,
404
+ children: menu.title
405
+ }
406
+ ),
407
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(AccordionContent, { "data-slot": "menu-group-content", className: "pb-1", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "flex flex-col gap-0.5 pl-3", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
408
+ MenuTree,
409
+ {
410
+ menus: menu.children ?? [],
411
+ registry,
412
+ bridge,
413
+ activeMenuId,
414
+ defaultOpenKeys
415
+ }
416
+ ) }) })
417
+ ]
418
+ },
419
+ menu.id
420
+ );
421
+ }
422
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
423
+ "button",
424
+ {
425
+ type: "button",
426
+ "data-slot": "menu-item",
427
+ "data-active": menu.id === activeMenuId || void 0,
428
+ className: cn(
429
+ menuItemClass,
430
+ menu.id === activeMenuId && "bg-accent font-semibold text-accent-foreground"
431
+ ),
432
+ disabled: menu.disabled,
433
+ title: menu.title,
434
+ onClick: () => navigateMenuItem(registry.findById(menu.id), bridge),
435
+ children: menu.title
436
+ },
437
+ menu.id
438
+ );
439
+ })
440
+ }
441
+ );
442
+ }
443
+ function ShadcnAdminLayout({
444
+ config = {},
445
+ brand,
446
+ registry,
447
+ bridge,
448
+ route: routeProp,
449
+ permissionChecker,
450
+ headerExtra,
451
+ children
452
+ }) {
453
+ const { t } = (0, import_runtime.useI18n)();
454
+ const mode = config.mode ?? "top-side";
455
+ const showHeader = config.header ?? true;
456
+ const showSidebar = (config.sidebar ?? true) && mode !== "top-bottom";
457
+ const showTabs = config.tabs ?? false;
458
+ const showBreadcrumb = config.breadcrumb ?? false;
459
+ const showFooter = config.footer ?? false;
460
+ const [collapsed, setCollapsed] = (0, import_react2.useState)(config.collapsed ?? false);
461
+ const broken = useBreakpointBelow(config.breakpoint);
462
+ const effectiveCollapsed = collapsed || broken;
463
+ const [bridgeRoute, setBridgeRoute] = (0, import_react2.useState)(
464
+ () => (0, import_router.resolveRoute)(registry, bridge.getLocation())
465
+ );
466
+ const [tabs, setTabs] = (0, import_react2.useState)([]);
467
+ const tabsRef = (0, import_react2.useRef)(tabs);
468
+ tabsRef.current = tabs;
469
+ (0, import_react2.useEffect)(() => {
470
+ const handleLocation = (location) => {
471
+ const next = (0, import_router.resolveRoute)(registry, location);
472
+ setBridgeRoute(next);
473
+ if (next?.menuId !== void 0) {
474
+ const menu = registry.findById(next.menuId);
475
+ if (menu !== void 0 && menu.type === "page" && menu.path !== void 0) {
476
+ setTabs((prev) => {
477
+ if (prev.some((tab) => tab.key === location.pathname)) {
478
+ return prev;
479
+ }
480
+ return [...prev, { key: location.pathname, title: menu.title }];
481
+ });
482
+ }
483
+ }
484
+ };
485
+ handleLocation(bridge.getLocation());
486
+ return bridge.subscribe(handleLocation);
487
+ }, [registry, bridge]);
488
+ const route = routeProp ?? bridgeRoute;
489
+ const activeMenuId = route?.activeMenu;
490
+ const visibleMenus = (0, import_react2.useMemo)(
491
+ () => buildVisibleMenuTree(registry, permissionChecker),
492
+ [registry, permissionChecker]
493
+ );
494
+ const defaultOpenKeysRef = (0, import_react2.useRef)(null);
495
+ if (defaultOpenKeysRef.current === null) {
496
+ defaultOpenKeysRef.current = collectDefaultOpenKeys(registry, activeMenuId);
497
+ }
498
+ const defaultOpenKeys = defaultOpenKeysRef.current;
499
+ const activeTopId = (0, import_react2.useMemo)(() => {
500
+ if (activeMenuId !== void 0) {
501
+ const chain = findMenuChain(registry, activeMenuId);
502
+ if (chain.length > 0) {
503
+ return chain[0].id;
504
+ }
505
+ }
506
+ return void 0;
507
+ }, [registry, activeMenuId]);
508
+ const [selectedTopId, setSelectedTopId] = (0, import_react2.useState)();
509
+ const secondaryTopId = selectedTopId ?? activeTopId ?? visibleMenus.find((m) => m.type === "group")?.id ?? visibleMenus[0]?.id;
510
+ const secondaryTop = visibleMenus.find((m) => m.id === secondaryTopId);
511
+ const hasSecondary = (secondaryTop?.children?.length ?? 0) > 0;
512
+ const breadcrumbItems = (0, import_react2.useMemo)(() => {
513
+ if (!showBreadcrumb || activeMenuId === void 0) {
514
+ return [];
515
+ }
516
+ return findMenuChain(registry, activeMenuId).map((menu) => menu.title);
517
+ }, [showBreadcrumb, registry, activeMenuId]);
518
+ const currentPath = route?.path;
519
+ const closeTab = (targetKey) => {
520
+ const remaining = tabsRef.current.filter((tab) => tab.key !== targetKey);
521
+ setTabs(remaining);
522
+ if (targetKey === currentPath && remaining.length > 0) {
523
+ bridge.push(remaining[remaining.length - 1].key);
524
+ }
525
+ };
526
+ const fixedHeader = config.fixedHeader ?? true;
527
+ const fixedSider = config.fixedSider ?? true;
528
+ const header = showHeader ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
529
+ "header",
530
+ {
531
+ "data-slot": "admin-header",
532
+ className: cn(
533
+ "flex h-14 items-center gap-4 border-b border-border bg-background px-6",
534
+ fixedHeader && "sticky top-0 z-20"
535
+ ),
536
+ children: [
537
+ mode !== "side-full" && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
538
+ brand.logo,
539
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { "data-slot": "admin-brand", className: "text-lg font-semibold", children: brand.title })
540
+ ] }),
541
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "flex-1" }),
542
+ headerExtra
543
+ ]
544
+ }
545
+ ) : null;
546
+ const primaryColumn = /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
547
+ "div",
548
+ {
549
+ "data-slot": "admin-primary-col",
550
+ className: "flex w-12 shrink-0 flex-col items-center gap-1 border-r border-border py-2",
551
+ children: visibleMenus.map((menu) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
552
+ "button",
553
+ {
554
+ type: "button",
555
+ "data-slot": "admin-primary-item",
556
+ "data-active": menu.id === secondaryTopId || menu.id === activeTopId || void 0,
557
+ className: cn(
558
+ menuItemClass,
559
+ "size-10 justify-center px-0 font-semibold",
560
+ (menu.id === secondaryTopId || menu.id === activeTopId) && "bg-accent text-accent-foreground"
561
+ ),
562
+ disabled: menu.disabled,
563
+ title: menu.title,
564
+ onClick: () => {
565
+ if (menu.type === "group") {
566
+ setSelectedTopId(menu.id);
567
+ } else {
568
+ navigateMenuItem(menu, bridge);
569
+ }
570
+ },
571
+ children: menu.title.slice(0, 1)
572
+ },
573
+ menu.id
574
+ ))
575
+ }
576
+ );
577
+ const showTrigger = config.collapsible && mode !== "two-column";
578
+ const sidebar = showSidebar ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
579
+ "aside",
580
+ {
581
+ "data-slot": "admin-sidebar",
582
+ "data-collapsed": effectiveCollapsed || void 0,
583
+ className: cn(
584
+ "shrink-0 overflow-auto border-r border-border bg-background py-2",
585
+ effectiveCollapsed ? "w-16 px-1.5" : "w-52",
586
+ fixedSider && (mode === "side-full" ? "sticky top-0 h-screen" : "sticky top-14 h-[calc(100vh-3.5rem)]")
587
+ ),
588
+ style: mode === "two-column" && !hasSecondary ? { width: 56 } : void 0,
589
+ children: [
590
+ mode === "side-full" && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
591
+ "div",
592
+ {
593
+ "data-slot": "admin-sidebar-brand",
594
+ className: "flex items-center gap-2 border-b border-border px-4 py-4 font-semibold",
595
+ children: [
596
+ brand.logo,
597
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: brand.title })
598
+ ]
599
+ }
600
+ ),
601
+ mode === "two-column" ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { "data-slot": "admin-two-col", className: "flex h-full", children: [
602
+ primaryColumn,
603
+ hasSecondary && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { "data-slot": "admin-secondary-col", className: "min-w-0 flex-1", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
604
+ MenuTree,
605
+ {
606
+ menus: secondaryTop?.children ?? [],
607
+ registry,
608
+ bridge,
609
+ activeMenuId,
610
+ defaultOpenKeys
611
+ }
612
+ ) })
613
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
614
+ MenuTree,
615
+ {
616
+ menus: visibleMenus,
617
+ registry,
618
+ bridge,
619
+ activeMenuId,
620
+ defaultOpenKeys,
621
+ collapsed: effectiveCollapsed
622
+ }
623
+ ),
624
+ showTrigger && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
625
+ "button",
626
+ {
627
+ type: "button",
628
+ "data-slot": "admin-collapse-trigger",
629
+ "aria-label": effectiveCollapsed ? t("pactor.layout.expandSidebar") : t("pactor.layout.collapseSidebar"),
630
+ "aria-expanded": !effectiveCollapsed,
631
+ className: "sticky bottom-0 flex w-full cursor-pointer items-center justify-center border-t border-border bg-background py-2 text-muted-foreground transition-colors hover:bg-accent hover:text-foreground",
632
+ onClick: () => setCollapsed((value) => !value),
633
+ children: effectiveCollapsed ? "\xBB" : "\xAB"
634
+ }
635
+ )
636
+ ]
637
+ }
638
+ ) : null;
639
+ const body = /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { "data-slot": "admin-body", className: "flex min-w-0 flex-1 flex-col", children: [
640
+ showTabs && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
641
+ "div",
642
+ {
643
+ "data-slot": "admin-tabs",
644
+ role: "tablist",
645
+ className: "flex items-center gap-1 border-b border-border bg-background px-4 pt-2",
646
+ children: tabs.map((tab, index) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
647
+ "div",
648
+ {
649
+ "data-slot": "admin-tab",
650
+ "data-active": tab.key === currentPath || void 0,
651
+ role: "presentation",
652
+ className: cn(
653
+ "inline-flex items-center gap-1 rounded-t-md border border-b-0 border-border px-3 py-1.5 text-sm",
654
+ tab.key === currentPath ? "bg-background font-medium text-foreground" : "bg-muted text-muted-foreground"
655
+ ),
656
+ children: [
657
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
658
+ "button",
659
+ {
660
+ type: "button",
661
+ role: "tab",
662
+ "aria-selected": tab.key === currentPath,
663
+ className: "cursor-pointer",
664
+ onClick: () => bridge.push(tab.key),
665
+ children: tab.title
666
+ }
667
+ ),
668
+ index > 0 && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
669
+ "button",
670
+ {
671
+ type: "button",
672
+ "data-slot": "admin-tab-close",
673
+ "aria-label": t("pactor.layout.closeTab", { title: tab.title }),
674
+ className: "cursor-pointer text-muted-foreground hover:text-foreground",
675
+ onClick: () => closeTab(tab.key),
676
+ children: "\xD7"
677
+ }
678
+ )
679
+ ]
680
+ },
681
+ tab.key
682
+ ))
683
+ }
684
+ ),
685
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
686
+ "main",
687
+ {
688
+ "data-slot": "admin-content",
689
+ className: "flex-1 bg-muted/40 p-6",
690
+ children: [
691
+ showBreadcrumb && breadcrumbItems.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
692
+ "nav",
693
+ {
694
+ "data-slot": "admin-breadcrumb",
695
+ "aria-label": t("pactor.layout.breadcrumb"),
696
+ className: "mb-4",
697
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("ol", { className: "flex items-center gap-2 text-sm text-muted-foreground", children: breadcrumbItems.map((title, index) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("li", { className: "flex items-center gap-2", children: [
698
+ index > 0 && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { "aria-hidden": true, children: "/" }),
699
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { "data-slot": "breadcrumb-link", children: title })
700
+ ] }, index)) })
701
+ }
702
+ ),
703
+ children
704
+ ]
705
+ }
706
+ )
707
+ ] });
708
+ const footer = showFooter ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
709
+ "footer",
710
+ {
711
+ "data-slot": "admin-footer",
712
+ className: "bg-muted p-4 text-center text-sm text-muted-foreground",
713
+ children: brand.title
714
+ }
715
+ ) : null;
716
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
717
+ "div",
718
+ {
719
+ "data-slot": "admin-layout",
720
+ "data-mode": mode,
721
+ "data-fixed-header": fixedHeader || void 0,
722
+ "data-fixed-sider": fixedSider || void 0,
723
+ className: "flex min-h-screen flex-col bg-background text-foreground",
724
+ children: mode === "side-full" ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { "data-slot": "admin-side-full", className: "flex min-h-screen flex-1", children: [
725
+ sidebar,
726
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
727
+ "div",
728
+ {
729
+ "data-slot": "admin-side-full-main",
730
+ className: "flex min-w-0 flex-1 flex-col",
731
+ children: [
732
+ header,
733
+ body,
734
+ footer
735
+ ]
736
+ }
737
+ )
738
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
739
+ header,
740
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { "data-slot": "admin-main", className: "flex min-h-0 flex-1", children: [
741
+ sidebar,
742
+ body
743
+ ] }),
744
+ footer
745
+ ] })
746
+ }
747
+ );
748
+ }
749
+
750
+ // src/layout/basic/ShadcnBlankLayout.tsx
751
+ var import_jsx_runtime4 = require("react/jsx-runtime");
752
+ function ShadcnBlankLayout({ children }) {
753
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
754
+ "div",
755
+ {
756
+ "data-slot": "blank-layout",
757
+ className: "min-h-screen bg-background text-foreground",
758
+ children
759
+ }
760
+ );
761
+ }
762
+
763
+ // src/ui/card.tsx
764
+ var import_jsx_runtime5 = require("react/jsx-runtime");
765
+ function Card({ className, ...props }) {
766
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
767
+ "div",
768
+ {
769
+ "data-slot": "card",
770
+ className: cn(
771
+ "flex flex-col gap-6 rounded-xl border border-border bg-card py-6 text-card-foreground shadow-sm",
772
+ className
773
+ ),
774
+ ...props
775
+ }
776
+ );
777
+ }
778
+ function CardHeader({ className, ...props }) {
779
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
780
+ "div",
781
+ {
782
+ "data-slot": "card-header",
783
+ className: cn(
784
+ "grid auto-rows-min grid-rows-[auto_auto] items-start gap-1.5 px-6 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6",
785
+ className
786
+ ),
787
+ ...props
788
+ }
789
+ );
790
+ }
791
+ function CardTitle({ className, ...props }) {
792
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
793
+ "div",
794
+ {
795
+ "data-slot": "card-title",
796
+ className: cn("leading-none font-semibold", className),
797
+ ...props
798
+ }
799
+ );
800
+ }
801
+ function CardContent({ className, ...props }) {
802
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
803
+ "div",
804
+ {
805
+ "data-slot": "card-content",
806
+ className: cn("px-6", className),
807
+ ...props
808
+ }
809
+ );
810
+ }
811
+
812
+ // src/layout/basic/ShadcnLoginLayout.tsx
813
+ var import_jsx_runtime6 = require("react/jsx-runtime");
814
+ function ShadcnLoginLayout({
815
+ variant = "center",
816
+ brand,
817
+ children
818
+ }) {
819
+ const brandBlock = /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { "data-slot": "login-brand", className: "flex flex-col items-center gap-2", children: [
820
+ brand.logo,
821
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("h1", { className: "text-xl font-semibold", children: brand.title }),
822
+ brand.description !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "text-sm text-muted-foreground", children: brand.description })
823
+ ] });
824
+ if (variant === "split") {
825
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
826
+ "div",
827
+ {
828
+ "data-slot": "login-layout",
829
+ "data-variant": "split",
830
+ className: "grid min-h-screen grid-cols-2 bg-background text-foreground",
831
+ children: [
832
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
833
+ "div",
834
+ {
835
+ "data-slot": "login-brand-panel",
836
+ className: "flex items-center justify-center bg-muted/40 p-10",
837
+ children: brandBlock
838
+ }
839
+ ),
840
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
841
+ "div",
842
+ {
843
+ "data-slot": "login-form-panel",
844
+ className: "flex items-center justify-center p-10",
845
+ children
846
+ }
847
+ )
848
+ ]
849
+ }
850
+ );
851
+ }
852
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
853
+ "div",
854
+ {
855
+ "data-slot": "login-layout",
856
+ "data-variant": "center",
857
+ className: "flex min-h-screen items-center justify-center bg-muted/40 text-foreground",
858
+ children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(Card, { "data-slot": "login-card", className: "w-full max-w-sm gap-4 py-6", children: [
859
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(CardHeader, { className: "px-6", children: [
860
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(CardTitle, { className: "sr-only", children: brand.title }),
861
+ brandBlock
862
+ ] }),
863
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(CardContent, { className: "flex flex-col gap-4 px-6", children })
864
+ ] })
865
+ }
866
+ );
867
+ }
868
+
869
+ // src/layout/basic/ShadcnLandingLayout.tsx
870
+ var import_jsx_runtime7 = require("react/jsx-runtime");
871
+ function ShadcnLandingLayout({
872
+ navbar,
873
+ footer,
874
+ children
875
+ }) {
876
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
877
+ "div",
878
+ {
879
+ "data-slot": "landing-layout",
880
+ className: "flex min-h-screen flex-col bg-background text-foreground",
881
+ children: [
882
+ navbar !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
883
+ "header",
884
+ {
885
+ "data-slot": "landing-navbar",
886
+ className: "border-b border-border bg-background",
887
+ children: navbar
888
+ }
889
+ ),
890
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("main", { "data-slot": "landing-content", className: "flex-1", children }),
891
+ footer !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
892
+ "footer",
893
+ {
894
+ "data-slot": "landing-footer",
895
+ className: "border-t border-border text-sm text-muted-foreground",
896
+ children: footer
897
+ }
898
+ )
899
+ ]
900
+ }
901
+ );
902
+ }
903
+
904
+ // src/layout/registry.ts
905
+ var import_core = require("@pactor-app/core");
906
+ function registerShadcnLayouts(registry) {
907
+ registry.register("admin", ShadcnAdminLayout);
908
+ registry.register("blank", ShadcnBlankLayout);
909
+ registry.register("login", ShadcnLoginLayout);
910
+ registry.register("landing", ShadcnLandingLayout);
911
+ }
912
+ function createShadcnLayoutRegistry() {
913
+ const registry = new import_core.Registry("LayoutRegistry");
914
+ registerShadcnLayouts(registry);
915
+ return registry;
916
+ }
917
+ function resolveLayout(registry, type) {
918
+ const component = registry.get(type);
919
+ if (component === void 0) {
920
+ console.warn(
921
+ `[pactor-layout] unknown layout type "${type}", fallback to BlankLayout`
922
+ );
923
+ return ShadcnBlankLayout;
924
+ }
925
+ return component;
926
+ }
927
+ // Annotate the CommonJS export names for ESM import in node:
928
+ 0 && (module.exports = {
929
+ ShadcnAdminLayout,
930
+ ShadcnBlankLayout,
931
+ ShadcnLandingLayout,
932
+ ShadcnLoginLayout,
933
+ collectDefaultOpenKeys,
934
+ createShadcnLayoutRegistry,
935
+ findMenuChain,
936
+ registerShadcnLayouts,
937
+ resolveLayout
938
+ });