@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,746 @@
1
+ import {
2
+ Accordion,
3
+ AccordionContent,
4
+ AccordionItem,
5
+ AccordionTrigger,
6
+ Popover,
7
+ PopoverContent,
8
+ PopoverTrigger
9
+ } from "./chunk-SNFDI77B.js";
10
+ import {
11
+ Card,
12
+ CardContent,
13
+ CardHeader,
14
+ CardTitle
15
+ } from "./chunk-57NRUZ5G.js";
16
+ import {
17
+ cn
18
+ } from "./chunk-RQHJBTEU.js";
19
+
20
+ // src/layout/admin/menu-chain.ts
21
+ function findMenuChain(registry, menuId) {
22
+ const walk = (menus, ancestors) => {
23
+ for (const menu of menus) {
24
+ const chain = [...ancestors, menu];
25
+ if (menu.id === menuId) {
26
+ return chain;
27
+ }
28
+ if (menu.children !== void 0) {
29
+ const hit = walk(menu.children, chain);
30
+ if (hit !== null) {
31
+ return hit;
32
+ }
33
+ }
34
+ }
35
+ return null;
36
+ };
37
+ return walk(registry.tree(), []) ?? [];
38
+ }
39
+
40
+ // src/layout/admin/menu-items.ts
41
+ function collectDefaultOpenKeys(registry, activeMenuId) {
42
+ const keys = [];
43
+ const walk = (menus) => {
44
+ for (const menu of menus) {
45
+ if (menu.type === "group" && menu.defaultOpen === true) {
46
+ keys.push(menu.id);
47
+ }
48
+ if (menu.children !== void 0) {
49
+ walk(menu.children);
50
+ }
51
+ }
52
+ };
53
+ walk(registry.tree());
54
+ if (activeMenuId !== void 0) {
55
+ for (const menu of findMenuChain(registry, activeMenuId)) {
56
+ if (menu.type === "group" && !keys.includes(menu.id)) {
57
+ keys.push(menu.id);
58
+ }
59
+ }
60
+ }
61
+ return keys;
62
+ }
63
+
64
+ // src/layout/admin/ShadcnAdminLayout.tsx
65
+ import { filterMenusByPermission } from "@pactor-app/permission";
66
+ import {
67
+ resolveRoute
68
+ } from "@pactor-app/router";
69
+ import { useI18n } from "@pactor-app/runtime";
70
+ import { useEffect as useEffect2, useMemo, useRef, useState as useState2 } from "react";
71
+
72
+ // src/layout/admin/use-media-query.ts
73
+ import { useEffect, useState } from "react";
74
+ var BREAKPOINT_PX = {
75
+ xs: 480,
76
+ sm: 576,
77
+ md: 768,
78
+ lg: 992,
79
+ xl: 1200,
80
+ xxl: 1600
81
+ };
82
+ function useMediaQuery(query) {
83
+ const [matches, setMatches] = useState(
84
+ () => typeof window !== "undefined" && typeof window.matchMedia === "function" ? window.matchMedia(query).matches : false
85
+ );
86
+ useEffect(() => {
87
+ if (typeof window === "undefined" || typeof window.matchMedia !== "function") {
88
+ return;
89
+ }
90
+ const mql = window.matchMedia(query);
91
+ const onChange = (event) => setMatches(event.matches);
92
+ setMatches(mql.matches);
93
+ mql.addEventListener("change", onChange);
94
+ return () => mql.removeEventListener("change", onChange);
95
+ }, [query]);
96
+ return matches;
97
+ }
98
+ function useBreakpointBelow(breakpoint) {
99
+ const matches = useMediaQuery(
100
+ breakpoint ? `(max-width: ${BREAKPOINT_PX[breakpoint] - 1}px)` : "(min-width: 0px)"
101
+ );
102
+ return breakpoint !== void 0 && matches;
103
+ }
104
+
105
+ // src/layout/admin/ShadcnAdminLayout.tsx
106
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
107
+ function buildVisibleMenuTree(registry, permissionChecker) {
108
+ const tree = registry.tree();
109
+ return permissionChecker === void 0 ? [...tree] : filterMenusByPermission(tree, permissionChecker);
110
+ }
111
+ function navigateMenuItem(menu, bridge) {
112
+ if (menu === void 0 || menu.type === "group" || menu.path === void 0) {
113
+ return;
114
+ }
115
+ if (menu.external === true || menu.type === "link") {
116
+ window.open(menu.path, menu.target ?? "_blank");
117
+ return;
118
+ }
119
+ bridge.push(menu.path);
120
+ }
121
+ 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";
122
+ function CollapsedGroupFlyout({
123
+ menu,
124
+ registry,
125
+ bridge,
126
+ activeMenuId,
127
+ defaultOpenKeys
128
+ }) {
129
+ const [open, setOpen] = useState2(false);
130
+ const closeTimer = useRef(void 0);
131
+ const openNow = () => {
132
+ window.clearTimeout(closeTimer.current);
133
+ setOpen(true);
134
+ };
135
+ const closeLater = () => {
136
+ window.clearTimeout(closeTimer.current);
137
+ closeTimer.current = window.setTimeout(() => setOpen(false), 200);
138
+ };
139
+ useEffect2(() => () => window.clearTimeout(closeTimer.current), []);
140
+ return /* @__PURE__ */ jsxs(Popover, { open, onOpenChange: setOpen, children: [
141
+ /* @__PURE__ */ jsx(
142
+ PopoverTrigger,
143
+ {
144
+ render: /* @__PURE__ */ jsx(
145
+ "button",
146
+ {
147
+ type: "button",
148
+ "data-slot": "menu-item",
149
+ className: cn(
150
+ menuItemClass,
151
+ "mx-auto size-10 justify-center px-0 font-semibold"
152
+ ),
153
+ disabled: menu.disabled,
154
+ title: menu.title,
155
+ onMouseEnter: openNow,
156
+ onMouseLeave: closeLater,
157
+ children: menu.title.slice(0, 1)
158
+ }
159
+ )
160
+ }
161
+ ),
162
+ /* @__PURE__ */ jsxs(
163
+ PopoverContent,
164
+ {
165
+ "data-slot": "menu-flyout",
166
+ side: "right",
167
+ align: "start",
168
+ sideOffset: 8,
169
+ className: "max-h-[70vh] w-40 overflow-auto p-1",
170
+ initialFocus: false,
171
+ onMouseEnter: openNow,
172
+ onMouseLeave: closeLater,
173
+ onClick: (event) => {
174
+ if (event.target.closest("[data-slot=menu-item]")) {
175
+ setOpen(false);
176
+ }
177
+ },
178
+ children: [
179
+ /* @__PURE__ */ jsx("div", { className: "px-3 pt-2 pb-1 text-xs font-semibold text-muted-foreground", children: menu.title }),
180
+ /* @__PURE__ */ jsx(
181
+ MenuTree,
182
+ {
183
+ menus: menu.children ?? [],
184
+ registry,
185
+ bridge,
186
+ activeMenuId,
187
+ defaultOpenKeys
188
+ }
189
+ )
190
+ ]
191
+ }
192
+ )
193
+ ] });
194
+ }
195
+ function MenuTree({
196
+ menus,
197
+ registry,
198
+ bridge,
199
+ activeMenuId,
200
+ defaultOpenKeys,
201
+ collapsed = false
202
+ }) {
203
+ if (collapsed) {
204
+ return /* @__PURE__ */ jsx("div", { "data-slot": "menu", "data-collapsed": "true", className: "flex flex-col gap-1", children: menus.map((menu) => {
205
+ if (menu.type === "group") {
206
+ return /* @__PURE__ */ jsx(
207
+ CollapsedGroupFlyout,
208
+ {
209
+ menu,
210
+ registry,
211
+ bridge,
212
+ activeMenuId,
213
+ defaultOpenKeys
214
+ },
215
+ menu.id
216
+ );
217
+ }
218
+ return /* @__PURE__ */ jsx(
219
+ "button",
220
+ {
221
+ type: "button",
222
+ "data-slot": "menu-item",
223
+ "data-active": menu.id === activeMenuId || void 0,
224
+ className: cn(
225
+ menuItemClass,
226
+ "mx-auto size-10 justify-center px-0 font-semibold",
227
+ menu.id === activeMenuId && "bg-accent font-semibold text-accent-foreground"
228
+ ),
229
+ disabled: menu.disabled,
230
+ title: menu.title,
231
+ onClick: () => navigateMenuItem(registry.findById(menu.id), bridge),
232
+ children: menu.title.slice(0, 1)
233
+ },
234
+ menu.id
235
+ );
236
+ }) });
237
+ }
238
+ return /* @__PURE__ */ jsx(
239
+ Accordion,
240
+ {
241
+ type: "multiple",
242
+ defaultValue: defaultOpenKeys,
243
+ "data-slot": "menu",
244
+ className: "flex flex-col gap-0.5 px-2",
245
+ children: menus.map((menu) => {
246
+ if (menu.type === "group") {
247
+ return /* @__PURE__ */ jsxs(
248
+ AccordionItem,
249
+ {
250
+ value: menu.id,
251
+ "data-slot": "menu-group",
252
+ className: "border-b-0",
253
+ children: [
254
+ /* @__PURE__ */ jsx(
255
+ AccordionTrigger,
256
+ {
257
+ "data-slot": "menu-group-trigger",
258
+ className: cn(menuItemClass, "py-2 font-medium"),
259
+ disabled: menu.disabled,
260
+ title: menu.title,
261
+ children: menu.title
262
+ }
263
+ ),
264
+ /* @__PURE__ */ jsx(AccordionContent, { "data-slot": "menu-group-content", className: "pb-1", children: /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-0.5 pl-3", children: /* @__PURE__ */ jsx(
265
+ MenuTree,
266
+ {
267
+ menus: menu.children ?? [],
268
+ registry,
269
+ bridge,
270
+ activeMenuId,
271
+ defaultOpenKeys
272
+ }
273
+ ) }) })
274
+ ]
275
+ },
276
+ menu.id
277
+ );
278
+ }
279
+ return /* @__PURE__ */ jsx(
280
+ "button",
281
+ {
282
+ type: "button",
283
+ "data-slot": "menu-item",
284
+ "data-active": menu.id === activeMenuId || void 0,
285
+ className: cn(
286
+ menuItemClass,
287
+ menu.id === activeMenuId && "bg-accent font-semibold text-accent-foreground"
288
+ ),
289
+ disabled: menu.disabled,
290
+ title: menu.title,
291
+ onClick: () => navigateMenuItem(registry.findById(menu.id), bridge),
292
+ children: menu.title
293
+ },
294
+ menu.id
295
+ );
296
+ })
297
+ }
298
+ );
299
+ }
300
+ function ShadcnAdminLayout({
301
+ config = {},
302
+ brand,
303
+ registry,
304
+ bridge,
305
+ route: routeProp,
306
+ permissionChecker,
307
+ headerExtra,
308
+ children
309
+ }) {
310
+ const { t } = useI18n();
311
+ const mode = config.mode ?? "top-side";
312
+ const showHeader = config.header ?? true;
313
+ const showSidebar = (config.sidebar ?? true) && mode !== "top-bottom";
314
+ const showTabs = config.tabs ?? false;
315
+ const showBreadcrumb = config.breadcrumb ?? false;
316
+ const showFooter = config.footer ?? false;
317
+ const [collapsed, setCollapsed] = useState2(config.collapsed ?? false);
318
+ const broken = useBreakpointBelow(config.breakpoint);
319
+ const effectiveCollapsed = collapsed || broken;
320
+ const [bridgeRoute, setBridgeRoute] = useState2(
321
+ () => resolveRoute(registry, bridge.getLocation())
322
+ );
323
+ const [tabs, setTabs] = useState2([]);
324
+ const tabsRef = useRef(tabs);
325
+ tabsRef.current = tabs;
326
+ useEffect2(() => {
327
+ const handleLocation = (location) => {
328
+ const next = resolveRoute(registry, location);
329
+ setBridgeRoute(next);
330
+ if (next?.menuId !== void 0) {
331
+ const menu = registry.findById(next.menuId);
332
+ if (menu !== void 0 && menu.type === "page" && menu.path !== void 0) {
333
+ setTabs((prev) => {
334
+ if (prev.some((tab) => tab.key === location.pathname)) {
335
+ return prev;
336
+ }
337
+ return [...prev, { key: location.pathname, title: menu.title }];
338
+ });
339
+ }
340
+ }
341
+ };
342
+ handleLocation(bridge.getLocation());
343
+ return bridge.subscribe(handleLocation);
344
+ }, [registry, bridge]);
345
+ const route = routeProp ?? bridgeRoute;
346
+ const activeMenuId = route?.activeMenu;
347
+ const visibleMenus = useMemo(
348
+ () => buildVisibleMenuTree(registry, permissionChecker),
349
+ [registry, permissionChecker]
350
+ );
351
+ const defaultOpenKeysRef = useRef(null);
352
+ if (defaultOpenKeysRef.current === null) {
353
+ defaultOpenKeysRef.current = collectDefaultOpenKeys(registry, activeMenuId);
354
+ }
355
+ const defaultOpenKeys = defaultOpenKeysRef.current;
356
+ const activeTopId = useMemo(() => {
357
+ if (activeMenuId !== void 0) {
358
+ const chain = findMenuChain(registry, activeMenuId);
359
+ if (chain.length > 0) {
360
+ return chain[0].id;
361
+ }
362
+ }
363
+ return void 0;
364
+ }, [registry, activeMenuId]);
365
+ const [selectedTopId, setSelectedTopId] = useState2();
366
+ const secondaryTopId = selectedTopId ?? activeTopId ?? visibleMenus.find((m) => m.type === "group")?.id ?? visibleMenus[0]?.id;
367
+ const secondaryTop = visibleMenus.find((m) => m.id === secondaryTopId);
368
+ const hasSecondary = (secondaryTop?.children?.length ?? 0) > 0;
369
+ const breadcrumbItems = useMemo(() => {
370
+ if (!showBreadcrumb || activeMenuId === void 0) {
371
+ return [];
372
+ }
373
+ return findMenuChain(registry, activeMenuId).map((menu) => menu.title);
374
+ }, [showBreadcrumb, registry, activeMenuId]);
375
+ const currentPath = route?.path;
376
+ const closeTab = (targetKey) => {
377
+ const remaining = tabsRef.current.filter((tab) => tab.key !== targetKey);
378
+ setTabs(remaining);
379
+ if (targetKey === currentPath && remaining.length > 0) {
380
+ bridge.push(remaining[remaining.length - 1].key);
381
+ }
382
+ };
383
+ const fixedHeader = config.fixedHeader ?? true;
384
+ const fixedSider = config.fixedSider ?? true;
385
+ const header = showHeader ? /* @__PURE__ */ jsxs(
386
+ "header",
387
+ {
388
+ "data-slot": "admin-header",
389
+ className: cn(
390
+ "flex h-14 items-center gap-4 border-b border-border bg-background px-6",
391
+ fixedHeader && "sticky top-0 z-20"
392
+ ),
393
+ children: [
394
+ mode !== "side-full" && /* @__PURE__ */ jsxs(Fragment, { children: [
395
+ brand.logo,
396
+ /* @__PURE__ */ jsx("span", { "data-slot": "admin-brand", className: "text-lg font-semibold", children: brand.title })
397
+ ] }),
398
+ /* @__PURE__ */ jsx("div", { className: "flex-1" }),
399
+ headerExtra
400
+ ]
401
+ }
402
+ ) : null;
403
+ const primaryColumn = /* @__PURE__ */ jsx(
404
+ "div",
405
+ {
406
+ "data-slot": "admin-primary-col",
407
+ className: "flex w-12 shrink-0 flex-col items-center gap-1 border-r border-border py-2",
408
+ children: visibleMenus.map((menu) => /* @__PURE__ */ jsx(
409
+ "button",
410
+ {
411
+ type: "button",
412
+ "data-slot": "admin-primary-item",
413
+ "data-active": menu.id === secondaryTopId || menu.id === activeTopId || void 0,
414
+ className: cn(
415
+ menuItemClass,
416
+ "size-10 justify-center px-0 font-semibold",
417
+ (menu.id === secondaryTopId || menu.id === activeTopId) && "bg-accent text-accent-foreground"
418
+ ),
419
+ disabled: menu.disabled,
420
+ title: menu.title,
421
+ onClick: () => {
422
+ if (menu.type === "group") {
423
+ setSelectedTopId(menu.id);
424
+ } else {
425
+ navigateMenuItem(menu, bridge);
426
+ }
427
+ },
428
+ children: menu.title.slice(0, 1)
429
+ },
430
+ menu.id
431
+ ))
432
+ }
433
+ );
434
+ const showTrigger = config.collapsible && mode !== "two-column";
435
+ const sidebar = showSidebar ? /* @__PURE__ */ jsxs(
436
+ "aside",
437
+ {
438
+ "data-slot": "admin-sidebar",
439
+ "data-collapsed": effectiveCollapsed || void 0,
440
+ className: cn(
441
+ "shrink-0 overflow-auto border-r border-border bg-background py-2",
442
+ effectiveCollapsed ? "w-16 px-1.5" : "w-52",
443
+ fixedSider && (mode === "side-full" ? "sticky top-0 h-screen" : "sticky top-14 h-[calc(100vh-3.5rem)]")
444
+ ),
445
+ style: mode === "two-column" && !hasSecondary ? { width: 56 } : void 0,
446
+ children: [
447
+ mode === "side-full" && /* @__PURE__ */ jsxs(
448
+ "div",
449
+ {
450
+ "data-slot": "admin-sidebar-brand",
451
+ className: "flex items-center gap-2 border-b border-border px-4 py-4 font-semibold",
452
+ children: [
453
+ brand.logo,
454
+ /* @__PURE__ */ jsx("span", { children: brand.title })
455
+ ]
456
+ }
457
+ ),
458
+ mode === "two-column" ? /* @__PURE__ */ jsxs("div", { "data-slot": "admin-two-col", className: "flex h-full", children: [
459
+ primaryColumn,
460
+ hasSecondary && /* @__PURE__ */ jsx("div", { "data-slot": "admin-secondary-col", className: "min-w-0 flex-1", children: /* @__PURE__ */ jsx(
461
+ MenuTree,
462
+ {
463
+ menus: secondaryTop?.children ?? [],
464
+ registry,
465
+ bridge,
466
+ activeMenuId,
467
+ defaultOpenKeys
468
+ }
469
+ ) })
470
+ ] }) : /* @__PURE__ */ jsx(
471
+ MenuTree,
472
+ {
473
+ menus: visibleMenus,
474
+ registry,
475
+ bridge,
476
+ activeMenuId,
477
+ defaultOpenKeys,
478
+ collapsed: effectiveCollapsed
479
+ }
480
+ ),
481
+ showTrigger && /* @__PURE__ */ jsx(
482
+ "button",
483
+ {
484
+ type: "button",
485
+ "data-slot": "admin-collapse-trigger",
486
+ "aria-label": effectiveCollapsed ? t("pactor.layout.expandSidebar") : t("pactor.layout.collapseSidebar"),
487
+ "aria-expanded": !effectiveCollapsed,
488
+ 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",
489
+ onClick: () => setCollapsed((value) => !value),
490
+ children: effectiveCollapsed ? "\xBB" : "\xAB"
491
+ }
492
+ )
493
+ ]
494
+ }
495
+ ) : null;
496
+ const body = /* @__PURE__ */ jsxs("div", { "data-slot": "admin-body", className: "flex min-w-0 flex-1 flex-col", children: [
497
+ showTabs && /* @__PURE__ */ jsx(
498
+ "div",
499
+ {
500
+ "data-slot": "admin-tabs",
501
+ role: "tablist",
502
+ className: "flex items-center gap-1 border-b border-border bg-background px-4 pt-2",
503
+ children: tabs.map((tab, index) => /* @__PURE__ */ jsxs(
504
+ "div",
505
+ {
506
+ "data-slot": "admin-tab",
507
+ "data-active": tab.key === currentPath || void 0,
508
+ role: "presentation",
509
+ className: cn(
510
+ "inline-flex items-center gap-1 rounded-t-md border border-b-0 border-border px-3 py-1.5 text-sm",
511
+ tab.key === currentPath ? "bg-background font-medium text-foreground" : "bg-muted text-muted-foreground"
512
+ ),
513
+ children: [
514
+ /* @__PURE__ */ jsx(
515
+ "button",
516
+ {
517
+ type: "button",
518
+ role: "tab",
519
+ "aria-selected": tab.key === currentPath,
520
+ className: "cursor-pointer",
521
+ onClick: () => bridge.push(tab.key),
522
+ children: tab.title
523
+ }
524
+ ),
525
+ index > 0 && /* @__PURE__ */ jsx(
526
+ "button",
527
+ {
528
+ type: "button",
529
+ "data-slot": "admin-tab-close",
530
+ "aria-label": t("pactor.layout.closeTab", { title: tab.title }),
531
+ className: "cursor-pointer text-muted-foreground hover:text-foreground",
532
+ onClick: () => closeTab(tab.key),
533
+ children: "\xD7"
534
+ }
535
+ )
536
+ ]
537
+ },
538
+ tab.key
539
+ ))
540
+ }
541
+ ),
542
+ /* @__PURE__ */ jsxs(
543
+ "main",
544
+ {
545
+ "data-slot": "admin-content",
546
+ className: "flex-1 bg-muted/40 p-6",
547
+ children: [
548
+ showBreadcrumb && breadcrumbItems.length > 0 && /* @__PURE__ */ jsx(
549
+ "nav",
550
+ {
551
+ "data-slot": "admin-breadcrumb",
552
+ "aria-label": t("pactor.layout.breadcrumb"),
553
+ className: "mb-4",
554
+ children: /* @__PURE__ */ jsx("ol", { className: "flex items-center gap-2 text-sm text-muted-foreground", children: breadcrumbItems.map((title, index) => /* @__PURE__ */ jsxs("li", { className: "flex items-center gap-2", children: [
555
+ index > 0 && /* @__PURE__ */ jsx("span", { "aria-hidden": true, children: "/" }),
556
+ /* @__PURE__ */ jsx("span", { "data-slot": "breadcrumb-link", children: title })
557
+ ] }, index)) })
558
+ }
559
+ ),
560
+ children
561
+ ]
562
+ }
563
+ )
564
+ ] });
565
+ const footer = showFooter ? /* @__PURE__ */ jsx(
566
+ "footer",
567
+ {
568
+ "data-slot": "admin-footer",
569
+ className: "bg-muted p-4 text-center text-sm text-muted-foreground",
570
+ children: brand.title
571
+ }
572
+ ) : null;
573
+ return /* @__PURE__ */ jsx(
574
+ "div",
575
+ {
576
+ "data-slot": "admin-layout",
577
+ "data-mode": mode,
578
+ "data-fixed-header": fixedHeader || void 0,
579
+ "data-fixed-sider": fixedSider || void 0,
580
+ className: "flex min-h-screen flex-col bg-background text-foreground",
581
+ children: mode === "side-full" ? /* @__PURE__ */ jsxs("div", { "data-slot": "admin-side-full", className: "flex min-h-screen flex-1", children: [
582
+ sidebar,
583
+ /* @__PURE__ */ jsxs(
584
+ "div",
585
+ {
586
+ "data-slot": "admin-side-full-main",
587
+ className: "flex min-w-0 flex-1 flex-col",
588
+ children: [
589
+ header,
590
+ body,
591
+ footer
592
+ ]
593
+ }
594
+ )
595
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
596
+ header,
597
+ /* @__PURE__ */ jsxs("div", { "data-slot": "admin-main", className: "flex min-h-0 flex-1", children: [
598
+ sidebar,
599
+ body
600
+ ] }),
601
+ footer
602
+ ] })
603
+ }
604
+ );
605
+ }
606
+
607
+ // src/layout/basic/ShadcnBlankLayout.tsx
608
+ import { jsx as jsx2 } from "react/jsx-runtime";
609
+ function ShadcnBlankLayout({ children }) {
610
+ return /* @__PURE__ */ jsx2(
611
+ "div",
612
+ {
613
+ "data-slot": "blank-layout",
614
+ className: "min-h-screen bg-background text-foreground",
615
+ children
616
+ }
617
+ );
618
+ }
619
+
620
+ // src/layout/basic/ShadcnLoginLayout.tsx
621
+ import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
622
+ function ShadcnLoginLayout({
623
+ variant = "center",
624
+ brand,
625
+ children
626
+ }) {
627
+ const brandBlock = /* @__PURE__ */ jsxs2("div", { "data-slot": "login-brand", className: "flex flex-col items-center gap-2", children: [
628
+ brand.logo,
629
+ /* @__PURE__ */ jsx3("h1", { className: "text-xl font-semibold", children: brand.title }),
630
+ brand.description !== void 0 && /* @__PURE__ */ jsx3("div", { className: "text-sm text-muted-foreground", children: brand.description })
631
+ ] });
632
+ if (variant === "split") {
633
+ return /* @__PURE__ */ jsxs2(
634
+ "div",
635
+ {
636
+ "data-slot": "login-layout",
637
+ "data-variant": "split",
638
+ className: "grid min-h-screen grid-cols-2 bg-background text-foreground",
639
+ children: [
640
+ /* @__PURE__ */ jsx3(
641
+ "div",
642
+ {
643
+ "data-slot": "login-brand-panel",
644
+ className: "flex items-center justify-center bg-muted/40 p-10",
645
+ children: brandBlock
646
+ }
647
+ ),
648
+ /* @__PURE__ */ jsx3(
649
+ "div",
650
+ {
651
+ "data-slot": "login-form-panel",
652
+ className: "flex items-center justify-center p-10",
653
+ children
654
+ }
655
+ )
656
+ ]
657
+ }
658
+ );
659
+ }
660
+ return /* @__PURE__ */ jsx3(
661
+ "div",
662
+ {
663
+ "data-slot": "login-layout",
664
+ "data-variant": "center",
665
+ className: "flex min-h-screen items-center justify-center bg-muted/40 text-foreground",
666
+ children: /* @__PURE__ */ jsxs2(Card, { "data-slot": "login-card", className: "w-full max-w-sm gap-4 py-6", children: [
667
+ /* @__PURE__ */ jsxs2(CardHeader, { className: "px-6", children: [
668
+ /* @__PURE__ */ jsx3(CardTitle, { className: "sr-only", children: brand.title }),
669
+ brandBlock
670
+ ] }),
671
+ /* @__PURE__ */ jsx3(CardContent, { className: "flex flex-col gap-4 px-6", children })
672
+ ] })
673
+ }
674
+ );
675
+ }
676
+
677
+ // src/layout/basic/ShadcnLandingLayout.tsx
678
+ import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
679
+ function ShadcnLandingLayout({
680
+ navbar,
681
+ footer,
682
+ children
683
+ }) {
684
+ return /* @__PURE__ */ jsxs3(
685
+ "div",
686
+ {
687
+ "data-slot": "landing-layout",
688
+ className: "flex min-h-screen flex-col bg-background text-foreground",
689
+ children: [
690
+ navbar !== void 0 && /* @__PURE__ */ jsx4(
691
+ "header",
692
+ {
693
+ "data-slot": "landing-navbar",
694
+ className: "border-b border-border bg-background",
695
+ children: navbar
696
+ }
697
+ ),
698
+ /* @__PURE__ */ jsx4("main", { "data-slot": "landing-content", className: "flex-1", children }),
699
+ footer !== void 0 && /* @__PURE__ */ jsx4(
700
+ "footer",
701
+ {
702
+ "data-slot": "landing-footer",
703
+ className: "border-t border-border text-sm text-muted-foreground",
704
+ children: footer
705
+ }
706
+ )
707
+ ]
708
+ }
709
+ );
710
+ }
711
+
712
+ // src/layout/registry.ts
713
+ import { Registry } from "@pactor-app/core";
714
+ function registerShadcnLayouts(registry) {
715
+ registry.register("admin", ShadcnAdminLayout);
716
+ registry.register("blank", ShadcnBlankLayout);
717
+ registry.register("login", ShadcnLoginLayout);
718
+ registry.register("landing", ShadcnLandingLayout);
719
+ }
720
+ function createShadcnLayoutRegistry() {
721
+ const registry = new Registry("LayoutRegistry");
722
+ registerShadcnLayouts(registry);
723
+ return registry;
724
+ }
725
+ function resolveLayout(registry, type) {
726
+ const component = registry.get(type);
727
+ if (component === void 0) {
728
+ console.warn(
729
+ `[pactor-layout] unknown layout type "${type}", fallback to BlankLayout`
730
+ );
731
+ return ShadcnBlankLayout;
732
+ }
733
+ return component;
734
+ }
735
+
736
+ export {
737
+ findMenuChain,
738
+ collectDefaultOpenKeys,
739
+ ShadcnAdminLayout,
740
+ ShadcnBlankLayout,
741
+ ShadcnLoginLayout,
742
+ ShadcnLandingLayout,
743
+ registerShadcnLayouts,
744
+ createShadcnLayoutRegistry,
745
+ resolveLayout
746
+ };