@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.
package/dist/index.cjs ADDED
@@ -0,0 +1,2626 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ Alert: () => Alert2,
34
+ Button: () => Button2,
35
+ Card: () => Card2,
36
+ Flex: () => Flex,
37
+ Form: () => Form,
38
+ Grid: () => Grid,
39
+ Input: () => Input2,
40
+ OVERLAY_CANCEL_PAYLOAD: () => OVERLAY_CANCEL_PAYLOAD,
41
+ OverlayManager: () => OverlayManager,
42
+ Page: () => Page,
43
+ Select: () => Select2,
44
+ ShadcnAdminLayout: () => ShadcnAdminLayout,
45
+ ShadcnBlankLayout: () => ShadcnBlankLayout,
46
+ ShadcnInteractionProvider: () => ShadcnInteractionProvider,
47
+ ShadcnLandingLayout: () => ShadcnLandingLayout,
48
+ ShadcnLoginLayout: () => ShadcnLoginLayout,
49
+ ShadcnOverlayHost: () => ShadcnOverlayHost,
50
+ Statistic: () => Statistic,
51
+ Table: () => Table2,
52
+ Tabs: () => Tabs2,
53
+ collectDefaultOpenKeys: () => collectDefaultOpenKeys,
54
+ createShadcnComponentRegistry: () => createShadcnComponentRegistry,
55
+ createShadcnLayoutRegistry: () => createShadcnLayoutRegistry,
56
+ createUiBridge: () => createUiBridge,
57
+ findMenuChain: () => findMenuChain,
58
+ registerInteractionActions: () => registerInteractionActions,
59
+ registerShadcnComponents: () => registerShadcnComponents,
60
+ registerShadcnLayouts: () => registerShadcnLayouts,
61
+ resolveLayout: () => resolveLayout,
62
+ shadcnComponents: () => shadcnComponents,
63
+ shadcnUiKit: () => shadcnUiKit,
64
+ useFormContext: () => useFormContext
65
+ });
66
+ module.exports = __toCommonJS(index_exports);
67
+
68
+ // src/components/register.ts
69
+ var import_core = require("@pactor-app/core");
70
+
71
+ // src/ui/alert.tsx
72
+ var import_class_variance_authority = require("class-variance-authority");
73
+
74
+ // src/lib/utils.ts
75
+ var import_clsx = require("clsx");
76
+ var import_tailwind_merge = require("tailwind-merge");
77
+ function cn(...inputs) {
78
+ return (0, import_tailwind_merge.twMerge)((0, import_clsx.clsx)(inputs));
79
+ }
80
+
81
+ // src/ui/alert.tsx
82
+ var import_jsx_runtime = require("react/jsx-runtime");
83
+ var alertVariants = (0, import_class_variance_authority.cva)(
84
+ "relative grid w-full grid-cols-[0_1fr] items-start gap-y-0.5 rounded-lg border border-border px-4 py-3 text-sm has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr] has-[>svg]:gap-x-3 [&>svg]:size-4 [&>svg]:translate-y-0.5 [&>svg]:text-current",
85
+ {
86
+ variants: {
87
+ variant: {
88
+ default: "bg-card text-card-foreground",
89
+ destructive: "border-destructive/50 text-destructive bg-card [&>svg]:text-current",
90
+ success: "border-success/50 text-success bg-card [&>svg]:text-current",
91
+ warning: "border-warning/50 text-warning bg-card [&>svg]:text-current",
92
+ info: "border-info/50 text-info bg-card [&>svg]:text-current"
93
+ }
94
+ },
95
+ defaultVariants: {
96
+ variant: "default"
97
+ }
98
+ }
99
+ );
100
+ function Alert({
101
+ className,
102
+ variant,
103
+ ...props
104
+ }) {
105
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
106
+ "div",
107
+ {
108
+ "data-slot": "alert",
109
+ role: "alert",
110
+ className: cn(alertVariants({ variant }), className),
111
+ ...props
112
+ }
113
+ );
114
+ }
115
+ function AlertTitle({ className, ...props }) {
116
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
117
+ "div",
118
+ {
119
+ "data-slot": "alert-title",
120
+ className: cn(
121
+ "col-start-2 line-clamp-1 min-h-4 font-medium tracking-tight",
122
+ className
123
+ ),
124
+ ...props
125
+ }
126
+ );
127
+ }
128
+ function AlertDescription({
129
+ className,
130
+ ...props
131
+ }) {
132
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
133
+ "div",
134
+ {
135
+ "data-slot": "alert-description",
136
+ className: cn(
137
+ "col-start-2 grid justify-items-start gap-1 text-sm text-muted-foreground [&_p]:leading-relaxed",
138
+ className
139
+ ),
140
+ ...props
141
+ }
142
+ );
143
+ }
144
+
145
+ // src/components/components/Alert/index.tsx
146
+ var import_jsx_runtime2 = require("react/jsx-runtime");
147
+ var variantMap = {
148
+ success: "success",
149
+ info: "info",
150
+ warning: "warning",
151
+ error: "destructive"
152
+ };
153
+ function Alert2({ message, description, variant = "info" }) {
154
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Alert, { variant: variantMap[variant], children: [
155
+ message ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(AlertTitle, { children: message }) : null,
156
+ description ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(AlertDescription, { children: description }) : null
157
+ ] });
158
+ }
159
+
160
+ // src/components/components/Button/index.tsx
161
+ var import_lucide_react = require("lucide-react");
162
+
163
+ // src/ui/button.tsx
164
+ var import_button = require("@base-ui/react/button");
165
+ var import_class_variance_authority2 = require("class-variance-authority");
166
+ var React = __toESM(require("react"), 1);
167
+ var import_jsx_runtime3 = require("react/jsx-runtime");
168
+ var buttonVariants = (0, import_class_variance_authority2.cva)(
169
+ "inline-flex shrink-0 cursor-pointer items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium outline-none transition-colors focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
170
+ {
171
+ variants: {
172
+ variant: {
173
+ default: "bg-primary text-primary-foreground hover:bg-primary/90",
174
+ destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90 focus-visible:ring-destructive/20",
175
+ outline: "border border-border bg-background hover:bg-accent hover:text-accent-foreground",
176
+ secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
177
+ ghost: "hover:bg-accent hover:text-accent-foreground",
178
+ link: "text-primary underline-offset-4 hover:underline"
179
+ },
180
+ size: {
181
+ default: "h-9 px-4 py-2",
182
+ sm: "h-8 gap-1.5 rounded-md px-3 text-xs",
183
+ lg: "h-10 rounded-md px-6",
184
+ icon: "size-9"
185
+ }
186
+ },
187
+ defaultVariants: {
188
+ variant: "default",
189
+ size: "default"
190
+ }
191
+ }
192
+ );
193
+ function Button({
194
+ className,
195
+ variant,
196
+ size,
197
+ asChild = false,
198
+ children,
199
+ ...props
200
+ }) {
201
+ if (asChild && React.isValidElement(children)) {
202
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
203
+ import_button.Button,
204
+ {
205
+ "data-slot": "button",
206
+ className: cn(buttonVariants({ variant, size, className })),
207
+ render: children,
208
+ ...props
209
+ }
210
+ );
211
+ }
212
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
213
+ import_button.Button,
214
+ {
215
+ "data-slot": "button",
216
+ className: cn(buttonVariants({ variant, size, className })),
217
+ ...props,
218
+ children
219
+ }
220
+ );
221
+ }
222
+
223
+ // src/components/components/Button/index.tsx
224
+ var import_jsx_runtime4 = require("react/jsx-runtime");
225
+ var variantMap2 = {
226
+ default: "outline",
227
+ primary: "default",
228
+ dashed: "outline",
229
+ link: "link",
230
+ text: "ghost"
231
+ };
232
+ var sizeMap = {
233
+ small: "sm",
234
+ middle: "default",
235
+ large: "lg"
236
+ };
237
+ function Button2({
238
+ text,
239
+ variant = "default",
240
+ danger,
241
+ size = "middle",
242
+ loading,
243
+ disabled,
244
+ submit,
245
+ onClick,
246
+ children
247
+ }) {
248
+ const textual = variant === "link" || variant === "text";
249
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
250
+ Button,
251
+ {
252
+ type: submit ? "submit" : "button",
253
+ variant: danger && !textual ? "destructive" : variantMap2[variant],
254
+ size: sizeMap[size],
255
+ disabled: disabled ?? loading,
256
+ "aria-busy": loading || void 0,
257
+ onClick: () => onClick?.(),
258
+ className: cn(
259
+ variant === "dashed" && "border-dashed",
260
+ danger && textual && "text-destructive hover:text-destructive"
261
+ ),
262
+ children: [
263
+ loading ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_lucide_react.Loader2, { "aria-hidden": true, className: "animate-spin" }) : null,
264
+ text ?? children
265
+ ]
266
+ }
267
+ );
268
+ }
269
+
270
+ // src/ui/card.tsx
271
+ var import_jsx_runtime5 = require("react/jsx-runtime");
272
+ function Card({ className, ...props }) {
273
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
274
+ "div",
275
+ {
276
+ "data-slot": "card",
277
+ className: cn(
278
+ "flex flex-col gap-6 rounded-xl border border-border bg-card py-6 text-card-foreground shadow-sm",
279
+ className
280
+ ),
281
+ ...props
282
+ }
283
+ );
284
+ }
285
+ function CardHeader({ className, ...props }) {
286
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
287
+ "div",
288
+ {
289
+ "data-slot": "card-header",
290
+ className: cn(
291
+ "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",
292
+ className
293
+ ),
294
+ ...props
295
+ }
296
+ );
297
+ }
298
+ function CardTitle({ className, ...props }) {
299
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
300
+ "div",
301
+ {
302
+ "data-slot": "card-title",
303
+ className: cn("leading-none font-semibold", className),
304
+ ...props
305
+ }
306
+ );
307
+ }
308
+ function CardContent({ className, ...props }) {
309
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
310
+ "div",
311
+ {
312
+ "data-slot": "card-content",
313
+ className: cn("px-6", className),
314
+ ...props
315
+ }
316
+ );
317
+ }
318
+
319
+ // src/components/components/Card/index.tsx
320
+ var import_jsx_runtime6 = require("react/jsx-runtime");
321
+ function Card2({ title, bordered = true, children }) {
322
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
323
+ Card,
324
+ {
325
+ className: cn("gap-4 py-4", !bordered && "border-transparent shadow-none"),
326
+ children: [
327
+ title ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(CardHeader, { className: "px-4", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(CardTitle, { children: title }) }) : null,
328
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(CardContent, { className: "px-4", children })
329
+ ]
330
+ }
331
+ );
332
+ }
333
+
334
+ // src/components/components/Flex/index.tsx
335
+ var import_jsx_runtime7 = require("react/jsx-runtime");
336
+ var gapClass = {
337
+ small: "gap-2",
338
+ middle: "gap-4",
339
+ large: "gap-6"
340
+ };
341
+ var justifyClass = {
342
+ start: "justify-start",
343
+ center: "justify-center",
344
+ end: "justify-end",
345
+ "space-between": "justify-between",
346
+ "space-around": "justify-around",
347
+ "space-evenly": "justify-evenly"
348
+ };
349
+ var alignClass = {
350
+ start: "items-start",
351
+ center: "items-center",
352
+ end: "items-end",
353
+ baseline: "items-baseline",
354
+ stretch: "items-stretch"
355
+ };
356
+ function Flex({
357
+ direction = "horizontal",
358
+ gap,
359
+ justify,
360
+ align,
361
+ children
362
+ }) {
363
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
364
+ "div",
365
+ {
366
+ "data-slot": "flex",
367
+ className: cn(
368
+ "flex",
369
+ direction === "vertical" && "flex-col",
370
+ typeof gap === "string" && gapClass[gap],
371
+ justify && justifyClass[justify],
372
+ align && alignClass[align]
373
+ ),
374
+ style: typeof gap === "number" ? { gap } : void 0,
375
+ children
376
+ }
377
+ );
378
+ }
379
+
380
+ // src/components/components/Form/index.tsx
381
+ var import_runtime = require("@pactor-app/runtime");
382
+ var import_react2 = require("react");
383
+
384
+ // src/components/components/Form/context.ts
385
+ var import_react = require("react");
386
+ var FormReactContext = (0, import_react.createContext)(null);
387
+ var FormContextProvider = FormReactContext.Provider;
388
+ function useFormContext() {
389
+ return (0, import_react.useContext)(FormReactContext);
390
+ }
391
+ function formItemClass(layout) {
392
+ switch (layout) {
393
+ case "horizontal":
394
+ return "mb-4 flex items-baseline gap-2";
395
+ case "inline":
396
+ return "flex items-center gap-2";
397
+ default:
398
+ return "mb-4 flex flex-col gap-1.5";
399
+ }
400
+ }
401
+
402
+ // src/components/components/Form/index.tsx
403
+ var import_jsx_runtime8 = require("react/jsx-runtime");
404
+ function isEmpty(value) {
405
+ return value === void 0 || value === null || value === "";
406
+ }
407
+ function Form({
408
+ name = "default",
409
+ layout = "vertical",
410
+ initialValues,
411
+ children,
412
+ onSubmit,
413
+ onValuesChange
414
+ }) {
415
+ const { context } = (0, import_runtime.useRenderer)();
416
+ const [values, setValues] = (0, import_react2.useState)(() => ({
417
+ ...initialValues
418
+ }));
419
+ const [errors, setErrors] = (0, import_react2.useState)({});
420
+ const fieldRulesRef = (0, import_react2.useRef)(/* @__PURE__ */ new Map());
421
+ const mergedInitialFieldsRef = (0, import_react2.useRef)(/* @__PURE__ */ new Set());
422
+ const setValuesBatch = (0, import_react2.useCallback)((partial) => {
423
+ setValues((prev) => ({ ...prev, ...partial }));
424
+ }, []);
425
+ (0, import_react2.useEffect)(() => {
426
+ context.forms.register(name, { setValues: setValuesBatch });
427
+ return () => {
428
+ context.forms.unregister(name);
429
+ };
430
+ }, [context, name, setValuesBatch]);
431
+ const formContext = (0, import_react2.useMemo)(
432
+ () => ({
433
+ inForm: true,
434
+ layout,
435
+ values,
436
+ errors,
437
+ setValue(name2, value) {
438
+ const next = { ...values, [name2]: value };
439
+ setValues(next);
440
+ setErrors((prev) => {
441
+ if (!(name2 in prev)) {
442
+ return prev;
443
+ }
444
+ const rest = { ...prev };
445
+ delete rest[name2];
446
+ return rest;
447
+ });
448
+ onValuesChange?.({ [name2]: value }, next);
449
+ },
450
+ setValues: setValuesBatch,
451
+ registerField(name2, rules, initialValue) {
452
+ fieldRulesRef.current.set(name2, rules);
453
+ if (initialValue !== void 0 && !mergedInitialFieldsRef.current.has(name2)) {
454
+ mergedInitialFieldsRef.current.add(name2);
455
+ setValues((prev) => ({ ...prev, [name2]: initialValue }));
456
+ }
457
+ },
458
+ unregisterField(name2) {
459
+ fieldRulesRef.current.delete(name2);
460
+ }
461
+ }),
462
+ [layout, values, errors, onValuesChange, setValuesBatch]
463
+ );
464
+ const handleSubmit = (event) => {
465
+ event.preventDefault();
466
+ const nextErrors = {};
467
+ for (const [name2, rules] of fieldRulesRef.current) {
468
+ for (const rule of rules ?? []) {
469
+ if (rule.required && isEmpty(values[name2])) {
470
+ nextErrors[name2] = rule.message ?? context.i18n.t("pactor.form.required");
471
+ break;
472
+ }
473
+ }
474
+ }
475
+ setErrors(nextErrors);
476
+ if (Object.keys(nextErrors).length > 0) {
477
+ return;
478
+ }
479
+ context.state.set("form", values);
480
+ onSubmit?.(values);
481
+ };
482
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(FormContextProvider, { value: formContext, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
483
+ "form",
484
+ {
485
+ "data-slot": "form",
486
+ className: cn(layout === "inline" && "flex flex-wrap items-center gap-3"),
487
+ onSubmit: handleSubmit,
488
+ children
489
+ }
490
+ ) });
491
+ }
492
+
493
+ // src/components/components/Grid/index.tsx
494
+ var import_react3 = require("react");
495
+ var import_jsx_runtime9 = require("react/jsx-runtime");
496
+ function Grid({ columns = 1, gap, children }) {
497
+ const cols = Math.max(1, columns);
498
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
499
+ "div",
500
+ {
501
+ "data-slot": "grid",
502
+ "data-columns": cols,
503
+ className: "grid",
504
+ style: {
505
+ gridTemplateColumns: `repeat(${cols}, minmax(0, 1fr))`,
506
+ ...gap !== void 0 ? { gap } : {}
507
+ },
508
+ children: import_react3.Children.map(children, (child, index) => /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { "data-slot": "grid-item", children: child }, index))
509
+ }
510
+ );
511
+ }
512
+
513
+ // src/components/components/Input/index.tsx
514
+ var import_react4 = require("react");
515
+
516
+ // src/ui/input.tsx
517
+ var import_jsx_runtime10 = require("react/jsx-runtime");
518
+ function Input({ className, type, ...props }) {
519
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
520
+ "input",
521
+ {
522
+ type,
523
+ "data-slot": "input",
524
+ className: cn(
525
+ "flex h-9 w-full min-w-0 rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-xs transition-colors outline-none placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50",
526
+ className
527
+ ),
528
+ ...props
529
+ }
530
+ );
531
+ }
532
+
533
+ // src/components/components/Input/index.tsx
534
+ var import_jsx_runtime11 = require("react/jsx-runtime");
535
+ function Input2({
536
+ name,
537
+ label,
538
+ rules,
539
+ value,
540
+ type = "text",
541
+ placeholder,
542
+ disabled,
543
+ onChange
544
+ }) {
545
+ const form = useFormContext();
546
+ const controlId = (0, import_react4.useId)();
547
+ const inForm = form?.inForm === true && typeof name === "string" && name !== "";
548
+ (0, import_react4.useEffect)(() => {
549
+ if (inForm && form && name) {
550
+ form.registerField(name, rules, value);
551
+ return () => form.unregisterField(name);
552
+ }
553
+ return void 0;
554
+ });
555
+ if (inForm && form && name) {
556
+ const fieldValue = form.values[name];
557
+ const error = form.errors[name];
558
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { "data-slot": "form-item", className: formItemClass(form.layout), children: [
559
+ label ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
560
+ "label",
561
+ {
562
+ htmlFor: controlId,
563
+ className: "shrink-0 text-sm leading-none font-medium whitespace-nowrap",
564
+ children: label
565
+ }
566
+ ) : null,
567
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
568
+ Input,
569
+ {
570
+ id: controlId,
571
+ type,
572
+ value: typeof fieldValue === "string" ? fieldValue : "",
573
+ placeholder,
574
+ disabled,
575
+ onChange: (event) => {
576
+ form.setValue(name, event.target.value);
577
+ onChange?.(event.target.value);
578
+ }
579
+ }
580
+ ),
581
+ error ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { "data-slot": "form-error", className: "text-sm text-destructive", children: error }) : null
582
+ ] });
583
+ }
584
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
585
+ Input,
586
+ {
587
+ type,
588
+ value: value ?? "",
589
+ placeholder,
590
+ disabled,
591
+ onChange: (event) => onChange?.(event.target.value)
592
+ }
593
+ );
594
+ }
595
+
596
+ // src/components/components/Page/index.tsx
597
+ var import_jsx_runtime12 = require("react/jsx-runtime");
598
+ function Page({ title, children }) {
599
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { "data-slot": "page", className: "p-6", children: [
600
+ title ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("h2", { "data-slot": "page-title", className: "mb-5 text-xl font-semibold", children: title }) : null,
601
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { "data-slot": "page-content", className: "space-y-5", children })
602
+ ] });
603
+ }
604
+
605
+ // src/components/components/Select/index.tsx
606
+ var import_runtime2 = require("@pactor-app/runtime");
607
+ var import_react5 = require("react");
608
+
609
+ // src/ui/select.tsx
610
+ var import_select = require("@base-ui/react/select");
611
+ var import_lucide_react2 = require("lucide-react");
612
+ var import_jsx_runtime13 = require("react/jsx-runtime");
613
+ var Select = import_select.Select.Root;
614
+ function SelectValue({
615
+ ...props
616
+ }) {
617
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_select.Select.Value, { "data-slot": "select-value", ...props });
618
+ }
619
+ function SelectTrigger({
620
+ className,
621
+ size = "default",
622
+ children,
623
+ ...props
624
+ }) {
625
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
626
+ import_select.Select.Trigger,
627
+ {
628
+ "data-slot": "select-trigger",
629
+ "data-size": size,
630
+ className: cn(
631
+ "flex w-fit cursor-pointer items-center justify-between gap-2 rounded-md border border-input bg-transparent px-3 py-2 text-sm whitespace-nowrap shadow-xs transition-colors outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 data-[size=default]:h-9 data-[size=sm]:h-8 data-[placeholder]:text-muted-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
632
+ className
633
+ ),
634
+ ...props,
635
+ children: [
636
+ children,
637
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
638
+ import_select.Select.Icon,
639
+ {
640
+ render: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_lucide_react2.ChevronDownIcon, { className: "size-4 opacity-50" })
641
+ }
642
+ )
643
+ ]
644
+ }
645
+ );
646
+ }
647
+ function SelectContent({
648
+ className,
649
+ children,
650
+ side = "bottom",
651
+ sideOffset = 4,
652
+ align = "center",
653
+ alignItemWithTrigger = false,
654
+ ...props
655
+ }) {
656
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_select.Select.Portal, { children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
657
+ import_select.Select.Positioner,
658
+ {
659
+ side,
660
+ sideOffset,
661
+ align,
662
+ alignItemWithTrigger,
663
+ className: "isolate z-50",
664
+ children: /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
665
+ import_select.Select.Popup,
666
+ {
667
+ "data-slot": "select-content",
668
+ className: cn(
669
+ "relative z-50 max-h-96 min-w-[8rem] overflow-x-hidden overflow-y-auto rounded-md border border-border bg-popover text-popover-foreground shadow-md 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",
670
+ className
671
+ ),
672
+ ...props,
673
+ children: [
674
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(SelectScrollUpButton, {}),
675
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_select.Select.List, { className: "p-1", children }),
676
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(SelectScrollDownButton, {})
677
+ ]
678
+ }
679
+ )
680
+ }
681
+ ) });
682
+ }
683
+ function SelectItem({
684
+ className,
685
+ children,
686
+ ...props
687
+ }) {
688
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
689
+ import_select.Select.Item,
690
+ {
691
+ "data-slot": "select-item",
692
+ className: cn(
693
+ "relative flex w-full cursor-pointer items-center gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm outline-none select-none focus:bg-accent focus:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
694
+ className
695
+ ),
696
+ ...props,
697
+ children: [
698
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "absolute right-2 flex size-3.5 items-center justify-center", children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_select.Select.ItemIndicator, { children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_lucide_react2.CheckIcon, { className: "size-4" }) }) }),
699
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_select.Select.ItemText, { children })
700
+ ]
701
+ }
702
+ );
703
+ }
704
+ function SelectScrollUpButton({
705
+ className,
706
+ ...props
707
+ }) {
708
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
709
+ import_select.Select.ScrollUpArrow,
710
+ {
711
+ "data-slot": "select-scroll-up-button",
712
+ className: cn(
713
+ "flex cursor-default items-center justify-center py-1",
714
+ className
715
+ ),
716
+ ...props,
717
+ children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_lucide_react2.ChevronUpIcon, { className: "size-4" })
718
+ }
719
+ );
720
+ }
721
+ function SelectScrollDownButton({
722
+ className,
723
+ ...props
724
+ }) {
725
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
726
+ import_select.Select.ScrollDownArrow,
727
+ {
728
+ "data-slot": "select-scroll-down-button",
729
+ className: cn(
730
+ "flex cursor-default items-center justify-center py-1",
731
+ className
732
+ ),
733
+ ...props,
734
+ children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_lucide_react2.ChevronDownIcon, { className: "size-4" })
735
+ }
736
+ );
737
+ }
738
+
739
+ // src/components/components/Select/index.tsx
740
+ var import_jsx_runtime14 = require("react/jsx-runtime");
741
+ function Select2({
742
+ name,
743
+ label,
744
+ rules,
745
+ options = [],
746
+ value,
747
+ placeholder,
748
+ disabled,
749
+ allowClear,
750
+ onChange
751
+ }) {
752
+ const form = useFormContext();
753
+ const { t } = (0, import_runtime2.useI18n)();
754
+ const controlId = (0, import_react5.useId)();
755
+ const inForm = form?.inForm === true && typeof name === "string" && name !== "";
756
+ (0, import_react5.useEffect)(() => {
757
+ if (inForm && form && name) {
758
+ form.registerField(name, rules, value);
759
+ return () => form.unregisterField(name);
760
+ }
761
+ return void 0;
762
+ });
763
+ const current = inForm && form && name ? form.values[name] : value;
764
+ const selectedValue = current === void 0 || current === null || current === "" ? null : current;
765
+ const handleChange = (next) => {
766
+ if (inForm && form && name) {
767
+ form.setValue(name, next ?? void 0);
768
+ }
769
+ onChange?.(next ?? void 0);
770
+ };
771
+ const select = /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("span", { className: "inline-flex items-center gap-1", children: [
772
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(
773
+ Select,
774
+ {
775
+ items: options,
776
+ value: selectedValue,
777
+ onValueChange: handleChange,
778
+ disabled,
779
+ name: inForm ? name : void 0,
780
+ children: [
781
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(SelectTrigger, { id: controlId, children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(SelectValue, { placeholder }) }),
782
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(SelectContent, { children: options.map((option) => /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(SelectItem, { value: option.value, children: option.label }, String(option.value))) })
783
+ ]
784
+ }
785
+ ),
786
+ allowClear && selectedValue !== null && !disabled ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
787
+ "button",
788
+ {
789
+ type: "button",
790
+ "aria-label": t("pactor.select.clear"),
791
+ className: "cursor-pointer text-muted-foreground hover:text-foreground",
792
+ onClick: () => {
793
+ if (inForm && form && name) {
794
+ form.setValue(name, void 0);
795
+ }
796
+ onChange?.(void 0);
797
+ },
798
+ children: "\xD7"
799
+ }
800
+ ) : null
801
+ ] });
802
+ if (inForm && form && name) {
803
+ const error = form.errors[name];
804
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { "data-slot": "form-item", className: formItemClass(form.layout), children: [
805
+ label ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
806
+ "label",
807
+ {
808
+ htmlFor: controlId,
809
+ className: "shrink-0 text-sm leading-none font-medium whitespace-nowrap",
810
+ children: label
811
+ }
812
+ ) : null,
813
+ select,
814
+ error ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { "data-slot": "form-error", className: "text-sm text-destructive", children: error }) : null
815
+ ] });
816
+ }
817
+ return select;
818
+ }
819
+
820
+ // src/components/components/Statistic/index.tsx
821
+ var import_jsx_runtime15 = require("react/jsx-runtime");
822
+ function Statistic({ title, value, precision, suffix }) {
823
+ const display = typeof value === "number" && precision !== void 0 ? value.toFixed(precision) : value;
824
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { "data-slot": "statistic", children: [
825
+ title ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { "data-slot": "statistic-title", className: "text-sm text-muted-foreground", children: title }) : null,
826
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { "data-slot": "statistic-value", className: "text-2xl font-bold tabular-nums", children: [
827
+ display,
828
+ suffix ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "ml-1 text-sm font-normal text-muted-foreground", children: suffix }) : null
829
+ ] })
830
+ ] });
831
+ }
832
+
833
+ // src/components/components/Table/index.tsx
834
+ var import_runtime3 = require("@pactor-app/runtime");
835
+ var import_react6 = require("react");
836
+
837
+ // src/ui/table.tsx
838
+ var import_jsx_runtime16 = require("react/jsx-runtime");
839
+ function Table({ className, ...props }) {
840
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
841
+ "div",
842
+ {
843
+ "data-slot": "table-container",
844
+ className: "relative w-full overflow-x-auto",
845
+ children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
846
+ "table",
847
+ {
848
+ "data-slot": "table",
849
+ className: cn("w-full caption-bottom text-sm", className),
850
+ ...props
851
+ }
852
+ )
853
+ }
854
+ );
855
+ }
856
+ function TableHeader({ className, ...props }) {
857
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
858
+ "thead",
859
+ {
860
+ "data-slot": "table-header",
861
+ className: cn("[&_tr]:border-b [&_tr]:border-border", className),
862
+ ...props
863
+ }
864
+ );
865
+ }
866
+ function TableBody({ className, ...props }) {
867
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
868
+ "tbody",
869
+ {
870
+ "data-slot": "table-body",
871
+ className: cn("[&_tr:last-child]:border-0", className),
872
+ ...props
873
+ }
874
+ );
875
+ }
876
+ function TableRow({ className, ...props }) {
877
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
878
+ "tr",
879
+ {
880
+ "data-slot": "table-row",
881
+ className: cn(
882
+ "border-b border-border transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted",
883
+ className
884
+ ),
885
+ ...props
886
+ }
887
+ );
888
+ }
889
+ function TableHead({ className, ...props }) {
890
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
891
+ "th",
892
+ {
893
+ "data-slot": "table-head",
894
+ className: cn(
895
+ "h-10 px-2 text-left align-middle font-medium whitespace-nowrap text-foreground [&:has([role=checkbox])]:pr-0",
896
+ className
897
+ ),
898
+ ...props
899
+ }
900
+ );
901
+ }
902
+ function TableCell({ className, ...props }) {
903
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
904
+ "td",
905
+ {
906
+ "data-slot": "table-cell",
907
+ className: cn(
908
+ "p-2 align-middle whitespace-nowrap [&:has([role=checkbox])]:pr-0",
909
+ className
910
+ ),
911
+ ...props
912
+ }
913
+ );
914
+ }
915
+
916
+ // src/components/components/Table/index.tsx
917
+ var import_jsx_runtime17 = require("react/jsx-runtime");
918
+ function renderCellValue(value) {
919
+ if (value === void 0 || value === null) {
920
+ return "";
921
+ }
922
+ if (typeof value === "object") {
923
+ return JSON.stringify(value);
924
+ }
925
+ return String(value);
926
+ }
927
+ function Table2({
928
+ columns = [],
929
+ dataSource = [],
930
+ rowKey = "id",
931
+ loading,
932
+ pagination,
933
+ onChange
934
+ }) {
935
+ const { renderChildren } = (0, import_runtime3.useRenderer)();
936
+ const { t } = (0, import_runtime3.useI18n)();
937
+ const [current, setCurrent] = (0, import_react6.useState)(1);
938
+ const pageSize = pagination === false ? void 0 : pagination?.pageSize;
939
+ const total = dataSource.length;
940
+ const pageCount = pageSize !== void 0 && pageSize > 0 ? Math.max(1, Math.ceil(total / pageSize)) : 1;
941
+ const pageRows = pageSize !== void 0 && pageSize > 0 ? dataSource.slice((current - 1) * pageSize, current * pageSize) : dataSource;
942
+ const goToPage = (page) => {
943
+ setCurrent(page);
944
+ onChange?.({ current: page, pageSize, total });
945
+ };
946
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { "data-slot": "dsl-table", className: "relative", children: [
947
+ loading ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
948
+ "div",
949
+ {
950
+ "data-slot": "table-loading",
951
+ className: "absolute inset-0 z-10 flex items-center justify-center bg-background/60 text-sm text-muted-foreground",
952
+ children: t("pactor.table.loading")
953
+ }
954
+ ) : null,
955
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(Table, { children: [
956
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(TableHeader, { children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(TableRow, { className: "hover:bg-transparent", children: columns.map((column, index) => /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
957
+ TableHead,
958
+ {
959
+ style: column.width !== void 0 ? { width: column.width } : void 0,
960
+ children: column.title
961
+ },
962
+ column.dataIndex ?? String(index)
963
+ )) }) }),
964
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(TableBody, { children: pageRows.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(TableRow, { className: "hover:bg-transparent", children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(TableCell, { colSpan: Math.max(1, columns.length), children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
965
+ "div",
966
+ {
967
+ "data-slot": "table-empty",
968
+ className: "py-8 text-center text-muted-foreground",
969
+ children: t("pactor.table.empty")
970
+ }
971
+ ) }) }) : pageRows.map((row, rowIndex) => {
972
+ const key = row[rowKey];
973
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
974
+ TableRow,
975
+ {
976
+ children: columns.map((column, columnIndex) => /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(TableCell, { children: column.cell ? renderChildren([column.cell], { row, rowIndex }) : renderCellValue(
977
+ column.dataIndex === void 0 ? void 0 : row[column.dataIndex]
978
+ ) }, column.dataIndex ?? String(columnIndex)))
979
+ },
980
+ key === void 0 || key === null ? rowIndex : String(key)
981
+ );
982
+ }) })
983
+ ] }),
984
+ pageSize !== void 0 && pageSize > 0 ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { "data-slot": "table-pagination", className: "mt-3 flex justify-end gap-1", children: Array.from({ length: pageCount }, (_, index) => index + 1).map(
985
+ (page) => /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
986
+ Button,
987
+ {
988
+ type: "button",
989
+ size: "sm",
990
+ variant: page === current ? "default" : "outline",
991
+ "aria-current": page === current ? "page" : void 0,
992
+ onClick: () => goToPage(page),
993
+ children: page
994
+ },
995
+ page
996
+ )
997
+ ) }) : null
998
+ ] });
999
+ }
1000
+
1001
+ // src/components/components/Tabs/index.tsx
1002
+ var import_runtime4 = require("@pactor-app/runtime");
1003
+
1004
+ // src/ui/tabs.tsx
1005
+ var import_tabs = require("@base-ui/react/tabs");
1006
+ var import_jsx_runtime18 = require("react/jsx-runtime");
1007
+ function Tabs({
1008
+ className,
1009
+ ...props
1010
+ }) {
1011
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
1012
+ import_tabs.Tabs.Root,
1013
+ {
1014
+ "data-slot": "tabs",
1015
+ className: cn("flex flex-col gap-2", className),
1016
+ ...props
1017
+ }
1018
+ );
1019
+ }
1020
+ function TabsList({
1021
+ className,
1022
+ ...props
1023
+ }) {
1024
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
1025
+ import_tabs.Tabs.List,
1026
+ {
1027
+ "data-slot": "tabs-list",
1028
+ className: cn(
1029
+ "inline-flex h-9 w-fit items-center justify-center rounded-lg bg-muted p-[3px] text-muted-foreground",
1030
+ className
1031
+ ),
1032
+ ...props
1033
+ }
1034
+ );
1035
+ }
1036
+ function TabsTrigger({
1037
+ className,
1038
+ ...props
1039
+ }) {
1040
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
1041
+ import_tabs.Tabs.Tab,
1042
+ {
1043
+ "data-slot": "tabs-trigger",
1044
+ className: cn(
1045
+ "inline-flex h-[calc(100%-1px)] flex-1 cursor-pointer items-center justify-center gap-1.5 rounded-md border border-transparent px-2 py-1 text-sm font-medium whitespace-nowrap text-foreground transition-colors focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 focus-visible:outline-1 disabled:pointer-events-none disabled:opacity-50 data-active:border-border data-active:bg-background data-active:shadow-sm [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
1046
+ className
1047
+ ),
1048
+ ...props
1049
+ }
1050
+ );
1051
+ }
1052
+ function TabsContent({
1053
+ className,
1054
+ ...props
1055
+ }) {
1056
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
1057
+ import_tabs.Tabs.Panel,
1058
+ {
1059
+ "data-slot": "tabs-content",
1060
+ className: cn("flex-1 outline-none", className),
1061
+ ...props
1062
+ }
1063
+ );
1064
+ }
1065
+
1066
+ // src/components/components/Tabs/index.tsx
1067
+ var import_jsx_runtime19 = require("react/jsx-runtime");
1068
+ function Tabs2({ items = [], activeKey, onChange }) {
1069
+ const { renderChildren } = (0, import_runtime4.useRenderer)();
1070
+ return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(
1071
+ Tabs,
1072
+ {
1073
+ value: activeKey,
1074
+ defaultValue: activeKey === void 0 ? items[0]?.key : void 0,
1075
+ onValueChange: (key) => onChange?.(key),
1076
+ children: [
1077
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(TabsList, { children: items.map((item) => /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(TabsTrigger, { value: item.key, children: item.label }, item.key)) }),
1078
+ items.map((item) => /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(TabsContent, { value: item.key, children: renderChildren(item.children) }, item.key))
1079
+ ]
1080
+ }
1081
+ );
1082
+ }
1083
+
1084
+ // src/components/register.ts
1085
+ var shadcnComponents = {
1086
+ Page,
1087
+ Grid,
1088
+ Flex,
1089
+ Card: Card2,
1090
+ Button: Button2,
1091
+ Alert: Alert2,
1092
+ Statistic,
1093
+ Tabs: Tabs2,
1094
+ Table: Table2,
1095
+ Form,
1096
+ Input: Input2,
1097
+ Select: Select2
1098
+ };
1099
+ function registerShadcnComponents(registry) {
1100
+ for (const [type, component] of Object.entries(shadcnComponents)) {
1101
+ registry.register(type, component);
1102
+ }
1103
+ }
1104
+ function createShadcnComponentRegistry() {
1105
+ const registry = new import_core.Registry("ComponentRegistry");
1106
+ registerShadcnComponents(registry);
1107
+ return registry;
1108
+ }
1109
+
1110
+ // src/layout/admin/menu-chain.ts
1111
+ function findMenuChain(registry, menuId) {
1112
+ const walk = (menus, ancestors) => {
1113
+ for (const menu of menus) {
1114
+ const chain = [...ancestors, menu];
1115
+ if (menu.id === menuId) {
1116
+ return chain;
1117
+ }
1118
+ if (menu.children !== void 0) {
1119
+ const hit = walk(menu.children, chain);
1120
+ if (hit !== null) {
1121
+ return hit;
1122
+ }
1123
+ }
1124
+ }
1125
+ return null;
1126
+ };
1127
+ return walk(registry.tree(), []) ?? [];
1128
+ }
1129
+
1130
+ // src/layout/admin/menu-items.ts
1131
+ function collectDefaultOpenKeys(registry, activeMenuId) {
1132
+ const keys = [];
1133
+ const walk = (menus) => {
1134
+ for (const menu of menus) {
1135
+ if (menu.type === "group" && menu.defaultOpen === true) {
1136
+ keys.push(menu.id);
1137
+ }
1138
+ if (menu.children !== void 0) {
1139
+ walk(menu.children);
1140
+ }
1141
+ }
1142
+ };
1143
+ walk(registry.tree());
1144
+ if (activeMenuId !== void 0) {
1145
+ for (const menu of findMenuChain(registry, activeMenuId)) {
1146
+ if (menu.type === "group" && !keys.includes(menu.id)) {
1147
+ keys.push(menu.id);
1148
+ }
1149
+ }
1150
+ }
1151
+ return keys;
1152
+ }
1153
+
1154
+ // src/layout/admin/ShadcnAdminLayout.tsx
1155
+ var import_permission = require("@pactor-app/permission");
1156
+ var import_router = require("@pactor-app/router");
1157
+ var import_runtime5 = require("@pactor-app/runtime");
1158
+ var import_react8 = require("react");
1159
+
1160
+ // src/ui/accordion.tsx
1161
+ var import_accordion = require("@base-ui/react/accordion");
1162
+ var import_lucide_react3 = require("lucide-react");
1163
+ var import_jsx_runtime20 = require("react/jsx-runtime");
1164
+ function toArray(value) {
1165
+ if (value === void 0) {
1166
+ return void 0;
1167
+ }
1168
+ return Array.isArray(value) ? value : [value];
1169
+ }
1170
+ function Accordion({
1171
+ type = "single",
1172
+ value,
1173
+ defaultValue,
1174
+ onValueChange,
1175
+ ...props
1176
+ }) {
1177
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
1178
+ import_accordion.Accordion.Root,
1179
+ {
1180
+ "data-slot": "accordion",
1181
+ multiple: type === "multiple",
1182
+ value: toArray(value),
1183
+ defaultValue: toArray(defaultValue),
1184
+ onValueChange: onValueChange === void 0 ? void 0 : (next) => onValueChange(type === "multiple" ? next : next[0] ?? ""),
1185
+ ...props
1186
+ }
1187
+ );
1188
+ }
1189
+ function AccordionItem({
1190
+ className,
1191
+ ...props
1192
+ }) {
1193
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
1194
+ import_accordion.Accordion.Item,
1195
+ {
1196
+ "data-slot": "accordion-item",
1197
+ className: cn("border-b border-border last:border-b-0", className),
1198
+ ...props
1199
+ }
1200
+ );
1201
+ }
1202
+ function AccordionTrigger({
1203
+ className,
1204
+ children,
1205
+ ...props
1206
+ }) {
1207
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_accordion.Accordion.Header, { className: "flex", children: /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(
1208
+ import_accordion.Accordion.Trigger,
1209
+ {
1210
+ "data-slot": "accordion-trigger",
1211
+ className: cn(
1212
+ "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",
1213
+ className
1214
+ ),
1215
+ ...props,
1216
+ children: [
1217
+ children,
1218
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_lucide_react3.ChevronDownIcon, { className: "pointer-events-none size-4 shrink-0 translate-y-0.5 text-muted-foreground transition-transform duration-200" })
1219
+ ]
1220
+ }
1221
+ ) });
1222
+ }
1223
+ function AccordionContent({
1224
+ className,
1225
+ children,
1226
+ ...props
1227
+ }) {
1228
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
1229
+ import_accordion.Accordion.Panel,
1230
+ {
1231
+ "data-slot": "accordion-content",
1232
+ className: "overflow-hidden text-sm data-open:animate-accordion-down data-closed:animate-accordion-up",
1233
+ ...props,
1234
+ children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { className: cn("pt-0 pb-4", className), children })
1235
+ }
1236
+ );
1237
+ }
1238
+
1239
+ // src/ui/popover.tsx
1240
+ var import_popover = require("@base-ui/react/popover");
1241
+ var import_jsx_runtime21 = require("react/jsx-runtime");
1242
+ function Popover({
1243
+ ...props
1244
+ }) {
1245
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_popover.Popover.Root, { "data-slot": "popover", ...props });
1246
+ }
1247
+ function PopoverTrigger({
1248
+ ...props
1249
+ }) {
1250
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_popover.Popover.Trigger, { "data-slot": "popover-trigger", ...props });
1251
+ }
1252
+ function PopoverContent({
1253
+ className,
1254
+ align = "center",
1255
+ side = "bottom",
1256
+ sideOffset = 4,
1257
+ anchor,
1258
+ ...props
1259
+ }) {
1260
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_popover.Popover.Portal, { children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
1261
+ import_popover.Popover.Positioner,
1262
+ {
1263
+ align,
1264
+ side,
1265
+ sideOffset,
1266
+ anchor,
1267
+ className: "isolate z-50",
1268
+ children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
1269
+ import_popover.Popover.Popup,
1270
+ {
1271
+ "data-slot": "popover-content",
1272
+ className: cn(
1273
+ "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",
1274
+ className
1275
+ ),
1276
+ ...props
1277
+ }
1278
+ )
1279
+ }
1280
+ ) });
1281
+ }
1282
+
1283
+ // src/layout/admin/use-media-query.ts
1284
+ var import_react7 = require("react");
1285
+ var BREAKPOINT_PX = {
1286
+ xs: 480,
1287
+ sm: 576,
1288
+ md: 768,
1289
+ lg: 992,
1290
+ xl: 1200,
1291
+ xxl: 1600
1292
+ };
1293
+ function useMediaQuery(query) {
1294
+ const [matches, setMatches] = (0, import_react7.useState)(
1295
+ () => typeof window !== "undefined" && typeof window.matchMedia === "function" ? window.matchMedia(query).matches : false
1296
+ );
1297
+ (0, import_react7.useEffect)(() => {
1298
+ if (typeof window === "undefined" || typeof window.matchMedia !== "function") {
1299
+ return;
1300
+ }
1301
+ const mql = window.matchMedia(query);
1302
+ const onChange = (event) => setMatches(event.matches);
1303
+ setMatches(mql.matches);
1304
+ mql.addEventListener("change", onChange);
1305
+ return () => mql.removeEventListener("change", onChange);
1306
+ }, [query]);
1307
+ return matches;
1308
+ }
1309
+ function useBreakpointBelow(breakpoint) {
1310
+ const matches = useMediaQuery(
1311
+ breakpoint ? `(max-width: ${BREAKPOINT_PX[breakpoint] - 1}px)` : "(min-width: 0px)"
1312
+ );
1313
+ return breakpoint !== void 0 && matches;
1314
+ }
1315
+
1316
+ // src/layout/admin/ShadcnAdminLayout.tsx
1317
+ var import_jsx_runtime22 = require("react/jsx-runtime");
1318
+ function buildVisibleMenuTree(registry, permissionChecker) {
1319
+ const tree = registry.tree();
1320
+ return permissionChecker === void 0 ? [...tree] : (0, import_permission.filterMenusByPermission)(tree, permissionChecker);
1321
+ }
1322
+ function navigateMenuItem(menu, bridge) {
1323
+ if (menu === void 0 || menu.type === "group" || menu.path === void 0) {
1324
+ return;
1325
+ }
1326
+ if (menu.external === true || menu.type === "link") {
1327
+ window.open(menu.path, menu.target ?? "_blank");
1328
+ return;
1329
+ }
1330
+ bridge.push(menu.path);
1331
+ }
1332
+ 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";
1333
+ function CollapsedGroupFlyout({
1334
+ menu,
1335
+ registry,
1336
+ bridge,
1337
+ activeMenuId,
1338
+ defaultOpenKeys
1339
+ }) {
1340
+ const [open, setOpen] = (0, import_react8.useState)(false);
1341
+ const closeTimer = (0, import_react8.useRef)(void 0);
1342
+ const openNow = () => {
1343
+ window.clearTimeout(closeTimer.current);
1344
+ setOpen(true);
1345
+ };
1346
+ const closeLater = () => {
1347
+ window.clearTimeout(closeTimer.current);
1348
+ closeTimer.current = window.setTimeout(() => setOpen(false), 200);
1349
+ };
1350
+ (0, import_react8.useEffect)(() => () => window.clearTimeout(closeTimer.current), []);
1351
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(Popover, { open, onOpenChange: setOpen, children: [
1352
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
1353
+ PopoverTrigger,
1354
+ {
1355
+ render: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
1356
+ "button",
1357
+ {
1358
+ type: "button",
1359
+ "data-slot": "menu-item",
1360
+ className: cn(
1361
+ menuItemClass,
1362
+ "mx-auto size-10 justify-center px-0 font-semibold"
1363
+ ),
1364
+ disabled: menu.disabled,
1365
+ title: menu.title,
1366
+ onMouseEnter: openNow,
1367
+ onMouseLeave: closeLater,
1368
+ children: menu.title.slice(0, 1)
1369
+ }
1370
+ )
1371
+ }
1372
+ ),
1373
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(
1374
+ PopoverContent,
1375
+ {
1376
+ "data-slot": "menu-flyout",
1377
+ side: "right",
1378
+ align: "start",
1379
+ sideOffset: 8,
1380
+ className: "max-h-[70vh] w-40 overflow-auto p-1",
1381
+ initialFocus: false,
1382
+ onMouseEnter: openNow,
1383
+ onMouseLeave: closeLater,
1384
+ onClick: (event) => {
1385
+ if (event.target.closest("[data-slot=menu-item]")) {
1386
+ setOpen(false);
1387
+ }
1388
+ },
1389
+ children: [
1390
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("div", { className: "px-3 pt-2 pb-1 text-xs font-semibold text-muted-foreground", children: menu.title }),
1391
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
1392
+ MenuTree,
1393
+ {
1394
+ menus: menu.children ?? [],
1395
+ registry,
1396
+ bridge,
1397
+ activeMenuId,
1398
+ defaultOpenKeys
1399
+ }
1400
+ )
1401
+ ]
1402
+ }
1403
+ )
1404
+ ] });
1405
+ }
1406
+ function MenuTree({
1407
+ menus,
1408
+ registry,
1409
+ bridge,
1410
+ activeMenuId,
1411
+ defaultOpenKeys,
1412
+ collapsed = false
1413
+ }) {
1414
+ if (collapsed) {
1415
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("div", { "data-slot": "menu", "data-collapsed": "true", className: "flex flex-col gap-1", children: menus.map((menu) => {
1416
+ if (menu.type === "group") {
1417
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
1418
+ CollapsedGroupFlyout,
1419
+ {
1420
+ menu,
1421
+ registry,
1422
+ bridge,
1423
+ activeMenuId,
1424
+ defaultOpenKeys
1425
+ },
1426
+ menu.id
1427
+ );
1428
+ }
1429
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
1430
+ "button",
1431
+ {
1432
+ type: "button",
1433
+ "data-slot": "menu-item",
1434
+ "data-active": menu.id === activeMenuId || void 0,
1435
+ className: cn(
1436
+ menuItemClass,
1437
+ "mx-auto size-10 justify-center px-0 font-semibold",
1438
+ menu.id === activeMenuId && "bg-accent font-semibold text-accent-foreground"
1439
+ ),
1440
+ disabled: menu.disabled,
1441
+ title: menu.title,
1442
+ onClick: () => navigateMenuItem(registry.findById(menu.id), bridge),
1443
+ children: menu.title.slice(0, 1)
1444
+ },
1445
+ menu.id
1446
+ );
1447
+ }) });
1448
+ }
1449
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
1450
+ Accordion,
1451
+ {
1452
+ type: "multiple",
1453
+ defaultValue: defaultOpenKeys,
1454
+ "data-slot": "menu",
1455
+ className: "flex flex-col gap-0.5 px-2",
1456
+ children: menus.map((menu) => {
1457
+ if (menu.type === "group") {
1458
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(
1459
+ AccordionItem,
1460
+ {
1461
+ value: menu.id,
1462
+ "data-slot": "menu-group",
1463
+ className: "border-b-0",
1464
+ children: [
1465
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
1466
+ AccordionTrigger,
1467
+ {
1468
+ "data-slot": "menu-group-trigger",
1469
+ className: cn(menuItemClass, "py-2 font-medium"),
1470
+ disabled: menu.disabled,
1471
+ title: menu.title,
1472
+ children: menu.title
1473
+ }
1474
+ ),
1475
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(AccordionContent, { "data-slot": "menu-group-content", className: "pb-1", children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("div", { className: "flex flex-col gap-0.5 pl-3", children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
1476
+ MenuTree,
1477
+ {
1478
+ menus: menu.children ?? [],
1479
+ registry,
1480
+ bridge,
1481
+ activeMenuId,
1482
+ defaultOpenKeys
1483
+ }
1484
+ ) }) })
1485
+ ]
1486
+ },
1487
+ menu.id
1488
+ );
1489
+ }
1490
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
1491
+ "button",
1492
+ {
1493
+ type: "button",
1494
+ "data-slot": "menu-item",
1495
+ "data-active": menu.id === activeMenuId || void 0,
1496
+ className: cn(
1497
+ menuItemClass,
1498
+ menu.id === activeMenuId && "bg-accent font-semibold text-accent-foreground"
1499
+ ),
1500
+ disabled: menu.disabled,
1501
+ title: menu.title,
1502
+ onClick: () => navigateMenuItem(registry.findById(menu.id), bridge),
1503
+ children: menu.title
1504
+ },
1505
+ menu.id
1506
+ );
1507
+ })
1508
+ }
1509
+ );
1510
+ }
1511
+ function ShadcnAdminLayout({
1512
+ config = {},
1513
+ brand,
1514
+ registry,
1515
+ bridge,
1516
+ route: routeProp,
1517
+ permissionChecker,
1518
+ headerExtra,
1519
+ children
1520
+ }) {
1521
+ const { t } = (0, import_runtime5.useI18n)();
1522
+ const mode = config.mode ?? "top-side";
1523
+ const showHeader = config.header ?? true;
1524
+ const showSidebar = (config.sidebar ?? true) && mode !== "top-bottom";
1525
+ const showTabs = config.tabs ?? false;
1526
+ const showBreadcrumb = config.breadcrumb ?? false;
1527
+ const showFooter = config.footer ?? false;
1528
+ const [collapsed, setCollapsed] = (0, import_react8.useState)(config.collapsed ?? false);
1529
+ const broken = useBreakpointBelow(config.breakpoint);
1530
+ const effectiveCollapsed = collapsed || broken;
1531
+ const [bridgeRoute, setBridgeRoute] = (0, import_react8.useState)(
1532
+ () => (0, import_router.resolveRoute)(registry, bridge.getLocation())
1533
+ );
1534
+ const [tabs, setTabs] = (0, import_react8.useState)([]);
1535
+ const tabsRef = (0, import_react8.useRef)(tabs);
1536
+ tabsRef.current = tabs;
1537
+ (0, import_react8.useEffect)(() => {
1538
+ const handleLocation = (location) => {
1539
+ const next = (0, import_router.resolveRoute)(registry, location);
1540
+ setBridgeRoute(next);
1541
+ if (next?.menuId !== void 0) {
1542
+ const menu = registry.findById(next.menuId);
1543
+ if (menu !== void 0 && menu.type === "page" && menu.path !== void 0) {
1544
+ setTabs((prev) => {
1545
+ if (prev.some((tab) => tab.key === location.pathname)) {
1546
+ return prev;
1547
+ }
1548
+ return [...prev, { key: location.pathname, title: menu.title }];
1549
+ });
1550
+ }
1551
+ }
1552
+ };
1553
+ handleLocation(bridge.getLocation());
1554
+ return bridge.subscribe(handleLocation);
1555
+ }, [registry, bridge]);
1556
+ const route = routeProp ?? bridgeRoute;
1557
+ const activeMenuId = route?.activeMenu;
1558
+ const visibleMenus = (0, import_react8.useMemo)(
1559
+ () => buildVisibleMenuTree(registry, permissionChecker),
1560
+ [registry, permissionChecker]
1561
+ );
1562
+ const defaultOpenKeysRef = (0, import_react8.useRef)(null);
1563
+ if (defaultOpenKeysRef.current === null) {
1564
+ defaultOpenKeysRef.current = collectDefaultOpenKeys(registry, activeMenuId);
1565
+ }
1566
+ const defaultOpenKeys = defaultOpenKeysRef.current;
1567
+ const activeTopId = (0, import_react8.useMemo)(() => {
1568
+ if (activeMenuId !== void 0) {
1569
+ const chain = findMenuChain(registry, activeMenuId);
1570
+ if (chain.length > 0) {
1571
+ return chain[0].id;
1572
+ }
1573
+ }
1574
+ return void 0;
1575
+ }, [registry, activeMenuId]);
1576
+ const [selectedTopId, setSelectedTopId] = (0, import_react8.useState)();
1577
+ const secondaryTopId = selectedTopId ?? activeTopId ?? visibleMenus.find((m) => m.type === "group")?.id ?? visibleMenus[0]?.id;
1578
+ const secondaryTop = visibleMenus.find((m) => m.id === secondaryTopId);
1579
+ const hasSecondary = (secondaryTop?.children?.length ?? 0) > 0;
1580
+ const breadcrumbItems = (0, import_react8.useMemo)(() => {
1581
+ if (!showBreadcrumb || activeMenuId === void 0) {
1582
+ return [];
1583
+ }
1584
+ return findMenuChain(registry, activeMenuId).map((menu) => menu.title);
1585
+ }, [showBreadcrumb, registry, activeMenuId]);
1586
+ const currentPath = route?.path;
1587
+ const closeTab = (targetKey) => {
1588
+ const remaining = tabsRef.current.filter((tab) => tab.key !== targetKey);
1589
+ setTabs(remaining);
1590
+ if (targetKey === currentPath && remaining.length > 0) {
1591
+ bridge.push(remaining[remaining.length - 1].key);
1592
+ }
1593
+ };
1594
+ const fixedHeader = config.fixedHeader ?? true;
1595
+ const fixedSider = config.fixedSider ?? true;
1596
+ const header = showHeader ? /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(
1597
+ "header",
1598
+ {
1599
+ "data-slot": "admin-header",
1600
+ className: cn(
1601
+ "flex h-14 items-center gap-4 border-b border-border bg-background px-6",
1602
+ fixedHeader && "sticky top-0 z-20"
1603
+ ),
1604
+ children: [
1605
+ mode !== "side-full" && /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(import_jsx_runtime22.Fragment, { children: [
1606
+ brand.logo,
1607
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { "data-slot": "admin-brand", className: "text-lg font-semibold", children: brand.title })
1608
+ ] }),
1609
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("div", { className: "flex-1" }),
1610
+ headerExtra
1611
+ ]
1612
+ }
1613
+ ) : null;
1614
+ const primaryColumn = /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
1615
+ "div",
1616
+ {
1617
+ "data-slot": "admin-primary-col",
1618
+ className: "flex w-12 shrink-0 flex-col items-center gap-1 border-r border-border py-2",
1619
+ children: visibleMenus.map((menu) => /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
1620
+ "button",
1621
+ {
1622
+ type: "button",
1623
+ "data-slot": "admin-primary-item",
1624
+ "data-active": menu.id === secondaryTopId || menu.id === activeTopId || void 0,
1625
+ className: cn(
1626
+ menuItemClass,
1627
+ "size-10 justify-center px-0 font-semibold",
1628
+ (menu.id === secondaryTopId || menu.id === activeTopId) && "bg-accent text-accent-foreground"
1629
+ ),
1630
+ disabled: menu.disabled,
1631
+ title: menu.title,
1632
+ onClick: () => {
1633
+ if (menu.type === "group") {
1634
+ setSelectedTopId(menu.id);
1635
+ } else {
1636
+ navigateMenuItem(menu, bridge);
1637
+ }
1638
+ },
1639
+ children: menu.title.slice(0, 1)
1640
+ },
1641
+ menu.id
1642
+ ))
1643
+ }
1644
+ );
1645
+ const showTrigger = config.collapsible && mode !== "two-column";
1646
+ const sidebar = showSidebar ? /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(
1647
+ "aside",
1648
+ {
1649
+ "data-slot": "admin-sidebar",
1650
+ "data-collapsed": effectiveCollapsed || void 0,
1651
+ className: cn(
1652
+ "shrink-0 overflow-auto border-r border-border bg-background py-2",
1653
+ effectiveCollapsed ? "w-16 px-1.5" : "w-52",
1654
+ fixedSider && (mode === "side-full" ? "sticky top-0 h-screen" : "sticky top-14 h-[calc(100vh-3.5rem)]")
1655
+ ),
1656
+ style: mode === "two-column" && !hasSecondary ? { width: 56 } : void 0,
1657
+ children: [
1658
+ mode === "side-full" && /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(
1659
+ "div",
1660
+ {
1661
+ "data-slot": "admin-sidebar-brand",
1662
+ className: "flex items-center gap-2 border-b border-border px-4 py-4 font-semibold",
1663
+ children: [
1664
+ brand.logo,
1665
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { children: brand.title })
1666
+ ]
1667
+ }
1668
+ ),
1669
+ mode === "two-column" ? /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { "data-slot": "admin-two-col", className: "flex h-full", children: [
1670
+ primaryColumn,
1671
+ hasSecondary && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("div", { "data-slot": "admin-secondary-col", className: "min-w-0 flex-1", children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
1672
+ MenuTree,
1673
+ {
1674
+ menus: secondaryTop?.children ?? [],
1675
+ registry,
1676
+ bridge,
1677
+ activeMenuId,
1678
+ defaultOpenKeys
1679
+ }
1680
+ ) })
1681
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
1682
+ MenuTree,
1683
+ {
1684
+ menus: visibleMenus,
1685
+ registry,
1686
+ bridge,
1687
+ activeMenuId,
1688
+ defaultOpenKeys,
1689
+ collapsed: effectiveCollapsed
1690
+ }
1691
+ ),
1692
+ showTrigger && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
1693
+ "button",
1694
+ {
1695
+ type: "button",
1696
+ "data-slot": "admin-collapse-trigger",
1697
+ "aria-label": effectiveCollapsed ? t("pactor.layout.expandSidebar") : t("pactor.layout.collapseSidebar"),
1698
+ "aria-expanded": !effectiveCollapsed,
1699
+ 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",
1700
+ onClick: () => setCollapsed((value) => !value),
1701
+ children: effectiveCollapsed ? "\xBB" : "\xAB"
1702
+ }
1703
+ )
1704
+ ]
1705
+ }
1706
+ ) : null;
1707
+ const body = /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { "data-slot": "admin-body", className: "flex min-w-0 flex-1 flex-col", children: [
1708
+ showTabs && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
1709
+ "div",
1710
+ {
1711
+ "data-slot": "admin-tabs",
1712
+ role: "tablist",
1713
+ className: "flex items-center gap-1 border-b border-border bg-background px-4 pt-2",
1714
+ children: tabs.map((tab, index) => /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(
1715
+ "div",
1716
+ {
1717
+ "data-slot": "admin-tab",
1718
+ "data-active": tab.key === currentPath || void 0,
1719
+ role: "presentation",
1720
+ className: cn(
1721
+ "inline-flex items-center gap-1 rounded-t-md border border-b-0 border-border px-3 py-1.5 text-sm",
1722
+ tab.key === currentPath ? "bg-background font-medium text-foreground" : "bg-muted text-muted-foreground"
1723
+ ),
1724
+ children: [
1725
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
1726
+ "button",
1727
+ {
1728
+ type: "button",
1729
+ role: "tab",
1730
+ "aria-selected": tab.key === currentPath,
1731
+ className: "cursor-pointer",
1732
+ onClick: () => bridge.push(tab.key),
1733
+ children: tab.title
1734
+ }
1735
+ ),
1736
+ index > 0 && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
1737
+ "button",
1738
+ {
1739
+ type: "button",
1740
+ "data-slot": "admin-tab-close",
1741
+ "aria-label": t("pactor.layout.closeTab", { title: tab.title }),
1742
+ className: "cursor-pointer text-muted-foreground hover:text-foreground",
1743
+ onClick: () => closeTab(tab.key),
1744
+ children: "\xD7"
1745
+ }
1746
+ )
1747
+ ]
1748
+ },
1749
+ tab.key
1750
+ ))
1751
+ }
1752
+ ),
1753
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(
1754
+ "main",
1755
+ {
1756
+ "data-slot": "admin-content",
1757
+ className: "flex-1 bg-muted/40 p-6",
1758
+ children: [
1759
+ showBreadcrumb && breadcrumbItems.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
1760
+ "nav",
1761
+ {
1762
+ "data-slot": "admin-breadcrumb",
1763
+ "aria-label": t("pactor.layout.breadcrumb"),
1764
+ className: "mb-4",
1765
+ children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("ol", { className: "flex items-center gap-2 text-sm text-muted-foreground", children: breadcrumbItems.map((title, index) => /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("li", { className: "flex items-center gap-2", children: [
1766
+ index > 0 && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { "aria-hidden": true, children: "/" }),
1767
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { "data-slot": "breadcrumb-link", children: title })
1768
+ ] }, index)) })
1769
+ }
1770
+ ),
1771
+ children
1772
+ ]
1773
+ }
1774
+ )
1775
+ ] });
1776
+ const footer = showFooter ? /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
1777
+ "footer",
1778
+ {
1779
+ "data-slot": "admin-footer",
1780
+ className: "bg-muted p-4 text-center text-sm text-muted-foreground",
1781
+ children: brand.title
1782
+ }
1783
+ ) : null;
1784
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
1785
+ "div",
1786
+ {
1787
+ "data-slot": "admin-layout",
1788
+ "data-mode": mode,
1789
+ "data-fixed-header": fixedHeader || void 0,
1790
+ "data-fixed-sider": fixedSider || void 0,
1791
+ className: "flex min-h-screen flex-col bg-background text-foreground",
1792
+ children: mode === "side-full" ? /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { "data-slot": "admin-side-full", className: "flex min-h-screen flex-1", children: [
1793
+ sidebar,
1794
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(
1795
+ "div",
1796
+ {
1797
+ "data-slot": "admin-side-full-main",
1798
+ className: "flex min-w-0 flex-1 flex-col",
1799
+ children: [
1800
+ header,
1801
+ body,
1802
+ footer
1803
+ ]
1804
+ }
1805
+ )
1806
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(import_jsx_runtime22.Fragment, { children: [
1807
+ header,
1808
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { "data-slot": "admin-main", className: "flex min-h-0 flex-1", children: [
1809
+ sidebar,
1810
+ body
1811
+ ] }),
1812
+ footer
1813
+ ] })
1814
+ }
1815
+ );
1816
+ }
1817
+
1818
+ // src/layout/basic/ShadcnBlankLayout.tsx
1819
+ var import_jsx_runtime23 = require("react/jsx-runtime");
1820
+ function ShadcnBlankLayout({ children }) {
1821
+ return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
1822
+ "div",
1823
+ {
1824
+ "data-slot": "blank-layout",
1825
+ className: "min-h-screen bg-background text-foreground",
1826
+ children
1827
+ }
1828
+ );
1829
+ }
1830
+
1831
+ // src/layout/basic/ShadcnLoginLayout.tsx
1832
+ var import_jsx_runtime24 = require("react/jsx-runtime");
1833
+ function ShadcnLoginLayout({
1834
+ variant = "center",
1835
+ brand,
1836
+ children
1837
+ }) {
1838
+ const brandBlock = /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { "data-slot": "login-brand", className: "flex flex-col items-center gap-2", children: [
1839
+ brand.logo,
1840
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("h1", { className: "text-xl font-semibold", children: brand.title }),
1841
+ brand.description !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("div", { className: "text-sm text-muted-foreground", children: brand.description })
1842
+ ] });
1843
+ if (variant === "split") {
1844
+ return /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(
1845
+ "div",
1846
+ {
1847
+ "data-slot": "login-layout",
1848
+ "data-variant": "split",
1849
+ className: "grid min-h-screen grid-cols-2 bg-background text-foreground",
1850
+ children: [
1851
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
1852
+ "div",
1853
+ {
1854
+ "data-slot": "login-brand-panel",
1855
+ className: "flex items-center justify-center bg-muted/40 p-10",
1856
+ children: brandBlock
1857
+ }
1858
+ ),
1859
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
1860
+ "div",
1861
+ {
1862
+ "data-slot": "login-form-panel",
1863
+ className: "flex items-center justify-center p-10",
1864
+ children
1865
+ }
1866
+ )
1867
+ ]
1868
+ }
1869
+ );
1870
+ }
1871
+ return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
1872
+ "div",
1873
+ {
1874
+ "data-slot": "login-layout",
1875
+ "data-variant": "center",
1876
+ className: "flex min-h-screen items-center justify-center bg-muted/40 text-foreground",
1877
+ children: /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(Card, { "data-slot": "login-card", className: "w-full max-w-sm gap-4 py-6", children: [
1878
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(CardHeader, { className: "px-6", children: [
1879
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(CardTitle, { className: "sr-only", children: brand.title }),
1880
+ brandBlock
1881
+ ] }),
1882
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(CardContent, { className: "flex flex-col gap-4 px-6", children })
1883
+ ] })
1884
+ }
1885
+ );
1886
+ }
1887
+
1888
+ // src/layout/basic/ShadcnLandingLayout.tsx
1889
+ var import_jsx_runtime25 = require("react/jsx-runtime");
1890
+ function ShadcnLandingLayout({
1891
+ navbar,
1892
+ footer,
1893
+ children
1894
+ }) {
1895
+ return /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)(
1896
+ "div",
1897
+ {
1898
+ "data-slot": "landing-layout",
1899
+ className: "flex min-h-screen flex-col bg-background text-foreground",
1900
+ children: [
1901
+ navbar !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
1902
+ "header",
1903
+ {
1904
+ "data-slot": "landing-navbar",
1905
+ className: "border-b border-border bg-background",
1906
+ children: navbar
1907
+ }
1908
+ ),
1909
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("main", { "data-slot": "landing-content", className: "flex-1", children }),
1910
+ footer !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
1911
+ "footer",
1912
+ {
1913
+ "data-slot": "landing-footer",
1914
+ className: "border-t border-border text-sm text-muted-foreground",
1915
+ children: footer
1916
+ }
1917
+ )
1918
+ ]
1919
+ }
1920
+ );
1921
+ }
1922
+
1923
+ // src/layout/registry.ts
1924
+ var import_core2 = require("@pactor-app/core");
1925
+ function registerShadcnLayouts(registry) {
1926
+ registry.register("admin", ShadcnAdminLayout);
1927
+ registry.register("blank", ShadcnBlankLayout);
1928
+ registry.register("login", ShadcnLoginLayout);
1929
+ registry.register("landing", ShadcnLandingLayout);
1930
+ }
1931
+ function createShadcnLayoutRegistry() {
1932
+ const registry = new import_core2.Registry("LayoutRegistry");
1933
+ registerShadcnLayouts(registry);
1934
+ return registry;
1935
+ }
1936
+ function resolveLayout(registry, type) {
1937
+ const component = registry.get(type);
1938
+ if (component === void 0) {
1939
+ console.warn(
1940
+ `[pactor-layout] unknown layout type "${type}", fallback to BlankLayout`
1941
+ );
1942
+ return ShadcnBlankLayout;
1943
+ }
1944
+ return component;
1945
+ }
1946
+
1947
+ // src/interaction/manager.ts
1948
+ var OVERLAY_CANCEL_PAYLOAD = { success: false };
1949
+ var OverlayManager = class {
1950
+ constructor() {
1951
+ this.pending = [];
1952
+ this.listeners = /* @__PURE__ */ new Set();
1953
+ this.counter = 0;
1954
+ /** 缓存的堆栈快照:变化时才更换引用,供 useSyncExternalStore 使用 */
1955
+ this.snapshot = Object.freeze([]);
1956
+ }
1957
+ /**
1958
+ * 打开 overlay:入栈、通知,返回 Promise——close 时以 payload
1959
+ * resolve;取消关闭(无 payload)resolve `{ success: false }`。
1960
+ * `options.parentScope` 记录打开时刻的父作用域浅快照。
1961
+ */
1962
+ open(config, options = {}) {
1963
+ if (!config || config.type !== "modal" && config.type !== "drawer") {
1964
+ return Promise.reject(
1965
+ new Error(
1966
+ '[pactor-interaction] overlay.open \u9700\u8981 type \u4E3A "modal" \u6216 "drawer" \u7684\u914D\u7F6E'
1967
+ )
1968
+ );
1969
+ }
1970
+ this.counter += 1;
1971
+ const entry = { ...config, id: `overlay-${this.counter}` };
1972
+ return new Promise((resolve) => {
1973
+ this.pending.push({
1974
+ entry,
1975
+ parentScope: options.parentScope ? { ...options.parentScope } : void 0,
1976
+ resolve
1977
+ });
1978
+ this.commit();
1979
+ });
1980
+ }
1981
+ /**
1982
+ * 关闭 overlay 并以 payload resolve 对应的 open Promise:
1983
+ * - `close(id, payload)`:关闭指定条目,原样 resolve payload
1984
+ * (payload 缺省为取消语义 `{ success: false }`)
1985
+ * - `close(payload)`:关闭栈顶,原样 resolve payload
1986
+ * - `close()`:关闭栈顶,取消语义
1987
+ * 未命中任何条目时为受控 no-op,不抛错。
1988
+ */
1989
+ close(idOrPayload, payload) {
1990
+ let index;
1991
+ let resolvedPayload;
1992
+ if (typeof idOrPayload === "string") {
1993
+ index = this.pending.findIndex((p) => p.entry.id === idOrPayload);
1994
+ if (index === -1) {
1995
+ return;
1996
+ }
1997
+ resolvedPayload = payload;
1998
+ } else {
1999
+ index = this.pending.length - 1;
2000
+ if (index === -1) {
2001
+ return;
2002
+ }
2003
+ resolvedPayload = idOrPayload === void 0 ? payload : idOrPayload;
2004
+ }
2005
+ const [target] = this.pending.splice(index, 1);
2006
+ this.commit();
2007
+ target?.resolve(
2008
+ resolvedPayload === void 0 ? OVERLAY_CANCEL_PAYLOAD : resolvedPayload
2009
+ );
2010
+ }
2011
+ /** 当前堆栈快照(打开顺序,栈顶在末尾);引用在堆栈不变时保持稳定 */
2012
+ stack() {
2013
+ return this.snapshot;
2014
+ }
2015
+ /** 打开时记录的父作用域浅快照(只读约定);未知 id 返回 undefined */
2016
+ parentScopeOf(id) {
2017
+ return this.pending.find((p) => p.entry.id === id)?.parentScope;
2018
+ }
2019
+ /** 订阅堆栈变化(open / close 时通知),返回退订函数 */
2020
+ subscribe(listener) {
2021
+ this.listeners.add(listener);
2022
+ return () => {
2023
+ this.listeners.delete(listener);
2024
+ };
2025
+ }
2026
+ /** 重建快照并通知订阅者 */
2027
+ commit() {
2028
+ this.snapshot = Object.freeze(this.pending.map((p) => p.entry));
2029
+ for (const listener of [...this.listeners]) {
2030
+ listener(this.snapshot);
2031
+ }
2032
+ }
2033
+ };
2034
+
2035
+ // src/interaction/actions.ts
2036
+ var import_runtime6 = require("@pactor-app/runtime");
2037
+ function createUiBridge() {
2038
+ return {};
2039
+ }
2040
+ function registerInteractionActions(actions, manager, ui) {
2041
+ const register = (name, handler) => {
2042
+ if (!actions.has(name)) {
2043
+ actions.register(name, handler);
2044
+ }
2045
+ };
2046
+ register("overlay.open", async (action, ctx) => {
2047
+ const overlay = action.params?.["overlay"];
2048
+ if (!isRecord(overlay)) {
2049
+ return failure("[pactor-interaction] overlay.open \u7F3A\u5C11 overlay \u914D\u7F6E\u53C2\u6570");
2050
+ }
2051
+ const { content, ...rest } = overlay;
2052
+ const resolved = (0, import_runtime6.resolveDeep)(rest, ctx.scope);
2053
+ const config = { ...resolved, content };
2054
+ const data = await manager.open(config, {
2055
+ parentScope: { ...ctx.scope }
2056
+ });
2057
+ return { ok: true, data };
2058
+ });
2059
+ register("overlay.close", (action) => {
2060
+ const id = action.params?.["id"];
2061
+ const payload = action.params?.["payload"];
2062
+ if (typeof id === "string" && id) {
2063
+ manager.close(id, payload);
2064
+ } else if (payload !== void 0) {
2065
+ manager.close(payload);
2066
+ } else {
2067
+ manager.close();
2068
+ }
2069
+ return { ok: true };
2070
+ });
2071
+ register("confirm", async (action) => {
2072
+ const modal = ui.modal;
2073
+ if (!modal) {
2074
+ return failure(
2075
+ "[pactor-interaction] confirm \u52A8\u4F5C\u9700\u8981 <InteractionProvider> \u6CE8\u5165 modal \u5B9E\u4F8B"
2076
+ );
2077
+ }
2078
+ const params = action.params ?? {};
2079
+ const data = await new Promise((resolve) => {
2080
+ modal.confirm({
2081
+ title: params["title"],
2082
+ content: params["content"],
2083
+ onOk: () => resolve(true),
2084
+ onCancel: () => resolve(false)
2085
+ });
2086
+ });
2087
+ return { ok: true, data };
2088
+ });
2089
+ register("message", (action) => {
2090
+ const message = ui.message;
2091
+ if (!message) {
2092
+ return failure(
2093
+ "[pactor-interaction] message \u52A8\u4F5C\u9700\u8981 <InteractionProvider> \u6CE8\u5165 message \u5B9E\u4F8B"
2094
+ );
2095
+ }
2096
+ const params = action.params ?? {};
2097
+ const type = params["type"];
2098
+ const text = params["text"];
2099
+ if (type === "success" || type === "error" || type === "warning") {
2100
+ message[type](text);
2101
+ } else {
2102
+ message.info(text);
2103
+ }
2104
+ return { ok: true };
2105
+ });
2106
+ }
2107
+ function failure(message) {
2108
+ return { ok: false, error: new Error(message) };
2109
+ }
2110
+ function isRecord(value) {
2111
+ return typeof value === "object" && value !== null && !Array.isArray(value);
2112
+ }
2113
+
2114
+ // src/interaction/provider.tsx
2115
+ var import_react9 = require("react");
2116
+ var import_react_dom = require("react-dom");
2117
+
2118
+ // src/ui/dialog.tsx
2119
+ var import_dialog = require("@base-ui/react/dialog");
2120
+ var import_lucide_react4 = require("lucide-react");
2121
+ var import_jsx_runtime26 = require("react/jsx-runtime");
2122
+ function Dialog({
2123
+ ...props
2124
+ }) {
2125
+ return /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(import_dialog.Dialog.Root, { "data-slot": "dialog", ...props });
2126
+ }
2127
+ function DialogPortal({
2128
+ ...props
2129
+ }) {
2130
+ return /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(import_dialog.Dialog.Portal, { "data-slot": "dialog-portal", ...props });
2131
+ }
2132
+ function DialogClose({
2133
+ ...props
2134
+ }) {
2135
+ return /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(import_dialog.Dialog.Close, { "data-slot": "dialog-close", ...props });
2136
+ }
2137
+ function DialogOverlay({
2138
+ className,
2139
+ ...props
2140
+ }) {
2141
+ return /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
2142
+ import_dialog.Dialog.Backdrop,
2143
+ {
2144
+ "data-slot": "dialog-overlay",
2145
+ className: cn(
2146
+ "fixed inset-0 z-50 bg-black/50 data-open:animate-in data-open:fade-in-0 data-closed:animate-out data-closed:fade-out-0",
2147
+ className
2148
+ ),
2149
+ ...props
2150
+ }
2151
+ );
2152
+ }
2153
+ function DialogContent({
2154
+ className,
2155
+ children,
2156
+ showCloseButton = true,
2157
+ ...props
2158
+ }) {
2159
+ return /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)(DialogPortal, { "data-slot": "dialog-portal", children: [
2160
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(DialogOverlay, {}),
2161
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)(
2162
+ import_dialog.Dialog.Popup,
2163
+ {
2164
+ "data-slot": "dialog-content",
2165
+ className: cn(
2166
+ "fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border border-border bg-background p-6 shadow-lg 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 sm:max-w-lg",
2167
+ className
2168
+ ),
2169
+ ...props,
2170
+ children: [
2171
+ children,
2172
+ showCloseButton && /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)(
2173
+ import_dialog.Dialog.Close,
2174
+ {
2175
+ "data-slot": "dialog-close",
2176
+ render: /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
2177
+ "button",
2178
+ {
2179
+ type: "button",
2180
+ className: "absolute top-4 right-4 cursor-pointer rounded-xs opacity-70 transition-opacity hover:opacity-100 focus-visible:ring-[3px] focus-visible:ring-ring/50 focus-visible:outline-none disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4"
2181
+ }
2182
+ ),
2183
+ children: [
2184
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(import_lucide_react4.XIcon, {}),
2185
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("span", { className: "sr-only", children: "Close" })
2186
+ ]
2187
+ }
2188
+ )
2189
+ ]
2190
+ }
2191
+ )
2192
+ ] });
2193
+ }
2194
+ function DialogHeader({ className, ...props }) {
2195
+ return /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
2196
+ "div",
2197
+ {
2198
+ "data-slot": "dialog-header",
2199
+ className: cn("flex flex-col gap-2 text-center sm:text-left", className),
2200
+ ...props
2201
+ }
2202
+ );
2203
+ }
2204
+ function DialogFooter({ className, ...props }) {
2205
+ return /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
2206
+ "div",
2207
+ {
2208
+ "data-slot": "dialog-footer",
2209
+ className: cn(
2210
+ "flex flex-col-reverse gap-2 sm:flex-row sm:justify-end",
2211
+ className
2212
+ ),
2213
+ ...props
2214
+ }
2215
+ );
2216
+ }
2217
+ function DialogTitle({
2218
+ className,
2219
+ ...props
2220
+ }) {
2221
+ return /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
2222
+ import_dialog.Dialog.Title,
2223
+ {
2224
+ "data-slot": "dialog-title",
2225
+ className: cn("text-lg leading-none font-semibold", className),
2226
+ ...props
2227
+ }
2228
+ );
2229
+ }
2230
+ function DialogDescription({
2231
+ className,
2232
+ ...props
2233
+ }) {
2234
+ return /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
2235
+ import_dialog.Dialog.Description,
2236
+ {
2237
+ "data-slot": "dialog-description",
2238
+ className: cn("text-sm text-muted-foreground", className),
2239
+ ...props
2240
+ }
2241
+ );
2242
+ }
2243
+
2244
+ // src/interaction/provider.tsx
2245
+ var import_jsx_runtime27 = require("react/jsx-runtime");
2246
+ var TOAST_DURATION = 3e3;
2247
+ var toastTypeClass = {
2248
+ success: "border-l-4 border-l-success",
2249
+ error: "border-l-4 border-l-destructive",
2250
+ info: "border-l-4 border-l-info",
2251
+ warning: "border-l-4 border-l-warning"
2252
+ };
2253
+ function ShadcnInteractionProvider({
2254
+ bridge,
2255
+ children
2256
+ }) {
2257
+ const [confirmState, setConfirmState] = (0, import_react9.useState)(null);
2258
+ const [toasts, setToasts] = (0, import_react9.useState)([]);
2259
+ const { modal, message } = (0, import_react9.useMemo)(() => {
2260
+ const pushToast = (type, content) => {
2261
+ const id = Date.now() + Math.random();
2262
+ setToasts((prev) => [...prev, { id, type, content }]);
2263
+ setTimeout(() => {
2264
+ setToasts((prev) => prev.filter((toast) => toast.id !== id));
2265
+ }, TOAST_DURATION);
2266
+ };
2267
+ const modalImpl = {
2268
+ confirm(config) {
2269
+ setConfirmState({ ...config });
2270
+ }
2271
+ };
2272
+ const messageImpl = {
2273
+ success: (content) => pushToast("success", content),
2274
+ error: (content) => pushToast("error", content),
2275
+ info: (content) => pushToast("info", content),
2276
+ warning: (content) => pushToast("warning", content)
2277
+ };
2278
+ return { modal: modalImpl, message: messageImpl };
2279
+ }, []);
2280
+ (0, import_react9.useEffect)(() => {
2281
+ bridge.modal = modal;
2282
+ bridge.message = message;
2283
+ return () => {
2284
+ bridge.modal = void 0;
2285
+ bridge.message = void 0;
2286
+ };
2287
+ }, [bridge, modal, message]);
2288
+ const handleOk = () => {
2289
+ const state = confirmState;
2290
+ setConfirmState(null);
2291
+ state?.onOk?.();
2292
+ };
2293
+ const handleCancel = () => {
2294
+ const state = confirmState;
2295
+ setConfirmState(null);
2296
+ state?.onCancel?.();
2297
+ };
2298
+ return /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(import_jsx_runtime27.Fragment, { children: [
2299
+ children,
2300
+ typeof document !== "undefined" && (0, import_react_dom.createPortal)(
2301
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(import_jsx_runtime27.Fragment, { children: [
2302
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
2303
+ Dialog,
2304
+ {
2305
+ open: confirmState !== null,
2306
+ onOpenChange: (open) => {
2307
+ if (!open) {
2308
+ handleCancel();
2309
+ }
2310
+ },
2311
+ children: /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(DialogContent, { "data-slot": "confirm-dialog", className: "sm:max-w-sm", children: [
2312
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(DialogHeader, { children: [
2313
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(DialogTitle, { children: confirmState?.title ?? "" }),
2314
+ confirmState?.content !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
2315
+ DialogDescription,
2316
+ {
2317
+ render: /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("div", { children: confirmState.content })
2318
+ }
2319
+ )
2320
+ ] }),
2321
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(DialogFooter, { children: [
2322
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(Button, { type: "button", variant: "outline", onClick: handleCancel, children: "\u53D6\u6D88" }),
2323
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(Button, { type: "button", onClick: handleOk, children: "\u786E\u5B9A" })
2324
+ ] })
2325
+ ] })
2326
+ }
2327
+ ),
2328
+ toasts.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
2329
+ "div",
2330
+ {
2331
+ "data-slot": "toast-container",
2332
+ className: "fixed top-4 right-4 z-[100] flex w-80 flex-col gap-2",
2333
+ children: toasts.map((toast) => /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
2334
+ "div",
2335
+ {
2336
+ "data-slot": "toast",
2337
+ "data-type": toast.type,
2338
+ role: "status",
2339
+ className: cn(
2340
+ "rounded-md border border-border bg-popover px-4 py-3 text-sm text-popover-foreground shadow-md",
2341
+ toastTypeClass[toast.type]
2342
+ ),
2343
+ children: toast.content
2344
+ },
2345
+ toast.id
2346
+ ))
2347
+ }
2348
+ )
2349
+ ] }),
2350
+ document.body
2351
+ )
2352
+ ] });
2353
+ }
2354
+
2355
+ // src/interaction/host.tsx
2356
+ var import_lucide_react5 = require("lucide-react");
2357
+ var import_runtime7 = require("@pactor-app/runtime");
2358
+ var import_react10 = require("react");
2359
+ var import_jsx_runtime28 = require("react/jsx-runtime");
2360
+ function toCssWidth(width) {
2361
+ if (width === void 0) {
2362
+ return void 0;
2363
+ }
2364
+ return typeof width === "number" ? `${width}px` : width;
2365
+ }
2366
+ function ShadcnOverlayHost({ manager, resolvePage }) {
2367
+ const renderer = (0, import_runtime7.useRenderer)();
2368
+ const stack = (0, import_react10.useSyncExternalStore)(
2369
+ (onChange) => manager.subscribe(onChange),
2370
+ () => manager.stack()
2371
+ );
2372
+ (0, import_react10.useSyncExternalStore)(
2373
+ (onChange) => {
2374
+ const offState = renderer.context.state.subscribe(onChange);
2375
+ const offData = renderer.context.data.subscribe(onChange);
2376
+ return () => {
2377
+ offState();
2378
+ offData();
2379
+ };
2380
+ },
2381
+ () => `${renderer.context.state.getVersion()}:${renderer.context.data.getVersion()}`
2382
+ );
2383
+ return /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(import_jsx_runtime28.Fragment, { children: stack.map((entry) => /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
2384
+ OverlayEntryView,
2385
+ {
2386
+ entry,
2387
+ manager,
2388
+ renderer,
2389
+ resolvePage
2390
+ },
2391
+ entry.id
2392
+ )) });
2393
+ }
2394
+ function OverlayEntryView({
2395
+ entry,
2396
+ manager,
2397
+ renderer,
2398
+ resolvePage
2399
+ }) {
2400
+ const handleCancel = () => manager.close(entry.id);
2401
+ let body = null;
2402
+ if (entry.content) {
2403
+ body = renderer.renderChildren([entry.content], {
2404
+ params: entry.params ?? {},
2405
+ parent: manager.parentScopeOf(entry.id)
2406
+ });
2407
+ } else if (entry.page) {
2408
+ body = /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
2409
+ PageOverlayBody,
2410
+ {
2411
+ entry,
2412
+ renderer,
2413
+ resolvePage
2414
+ }
2415
+ );
2416
+ }
2417
+ if (entry.type === "drawer") {
2418
+ return /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(ShadcnDrawerView, { entry, onCancel: handleCancel, children: body });
2419
+ }
2420
+ return /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
2421
+ Dialog,
2422
+ {
2423
+ open: true,
2424
+ onOpenChange: (open) => {
2425
+ if (!open) {
2426
+ handleCancel();
2427
+ }
2428
+ },
2429
+ children: /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)(
2430
+ DialogContent,
2431
+ {
2432
+ "data-slot": "overlay-modal",
2433
+ className: cn(entry.width !== void 0 && "sm:max-w-none"),
2434
+ style: entry.width === void 0 ? void 0 : { width: toCssWidth(entry.width) },
2435
+ showCloseButton: false,
2436
+ children: [
2437
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)(DialogHeader, { children: [
2438
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(DialogTitle, { children: entry.title ?? "" }),
2439
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
2440
+ DialogClose,
2441
+ {
2442
+ render: /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
2443
+ "button",
2444
+ {
2445
+ type: "button",
2446
+ "data-slot": "overlay-modal-close",
2447
+ "aria-label": "\u5173\u95ED",
2448
+ className: "absolute top-4 right-4 cursor-pointer rounded-xs opacity-70 transition-opacity hover:opacity-100 focus-visible:ring-[3px] focus-visible:ring-ring/50 focus-visible:outline-none",
2449
+ children: /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(import_lucide_react5.XIcon, { className: "size-4" })
2450
+ }
2451
+ )
2452
+ }
2453
+ )
2454
+ ] }),
2455
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("div", { "data-slot": "overlay-modal-body", children: body })
2456
+ ]
2457
+ }
2458
+ )
2459
+ }
2460
+ );
2461
+ }
2462
+ function ShadcnDrawerView({
2463
+ entry,
2464
+ onCancel,
2465
+ children
2466
+ }) {
2467
+ (0, import_react10.useEffect)(() => {
2468
+ const handleKeyDown = (event) => {
2469
+ if (event.key === "Escape") {
2470
+ onCancel();
2471
+ }
2472
+ };
2473
+ document.addEventListener("keydown", handleKeyDown);
2474
+ return () => {
2475
+ document.removeEventListener("keydown", handleKeyDown);
2476
+ };
2477
+ }, [onCancel]);
2478
+ const panelStyle = entry.width === void 0 ? void 0 : { width: toCssWidth(entry.width) };
2479
+ return /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("div", { "data-slot": "drawer-root", children: [
2480
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
2481
+ "div",
2482
+ {
2483
+ "data-slot": "drawer-mask",
2484
+ "data-testid": "drawer-mask",
2485
+ className: "fixed inset-0 z-50 bg-black/50",
2486
+ onClick: onCancel
2487
+ }
2488
+ ),
2489
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)(
2490
+ "div",
2491
+ {
2492
+ "data-slot": "drawer-panel",
2493
+ role: "dialog",
2494
+ "aria-modal": "true",
2495
+ "aria-label": entry.title,
2496
+ style: panelStyle,
2497
+ className: "fixed inset-y-0 right-0 z-50 flex w-96 max-w-full flex-col border-l border-border bg-background shadow-lg",
2498
+ children: [
2499
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)(
2500
+ "div",
2501
+ {
2502
+ "data-slot": "drawer-header",
2503
+ className: "flex items-center justify-between border-b border-border px-6 py-4",
2504
+ children: [
2505
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("span", { className: "text-lg font-semibold", children: entry.title ?? "" }),
2506
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
2507
+ "button",
2508
+ {
2509
+ type: "button",
2510
+ "data-slot": "drawer-close",
2511
+ "aria-label": "\u5173\u95ED",
2512
+ className: "cursor-pointer rounded-xs opacity-70 transition-opacity hover:opacity-100 focus-visible:ring-[3px] focus-visible:ring-ring/50 focus-visible:outline-none",
2513
+ onClick: onCancel,
2514
+ children: /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(import_lucide_react5.XIcon, { className: "size-4" })
2515
+ }
2516
+ )
2517
+ ]
2518
+ }
2519
+ ),
2520
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("div", { "data-slot": "drawer-body", className: "flex-1 overflow-auto p-6", children })
2521
+ ]
2522
+ }
2523
+ )
2524
+ ] });
2525
+ }
2526
+ function PageOverlayBody({
2527
+ entry,
2528
+ renderer,
2529
+ resolvePage
2530
+ }) {
2531
+ const [child, setChild] = (0, import_react10.useState)(null);
2532
+ const [error, setError] = (0, import_react10.useState)(null);
2533
+ (0, import_react10.useEffect)(() => {
2534
+ if (!resolvePage) {
2535
+ setError(
2536
+ new Error(
2537
+ `[@pactor-app/ui] page overlay\uFF08pageId: "${entry.page?.pageId}"\uFF09\u9700\u8981 ShadcnOverlayHost \u6CE8\u5165 resolvePage`
2538
+ )
2539
+ );
2540
+ return;
2541
+ }
2542
+ let cancelled = false;
2543
+ let created = null;
2544
+ void (async () => {
2545
+ try {
2546
+ const pageDsl = await resolvePage(entry.page?.pageId ?? "");
2547
+ if (cancelled) {
2548
+ return;
2549
+ }
2550
+ const host = renderer.context;
2551
+ created = (0, import_runtime7.createPageRuntime)(pageDsl, {
2552
+ app: host.app,
2553
+ user: host.user,
2554
+ components: host.components,
2555
+ services: host.services,
2556
+ actions: host.actions,
2557
+ permission: host.permission,
2558
+ capabilities: host.capabilities,
2559
+ http: host.http,
2560
+ route: { params: entry.params ?? {} }
2561
+ });
2562
+ setChild(created);
2563
+ } catch (cause) {
2564
+ if (!cancelled) {
2565
+ setError(cause instanceof Error ? cause : new Error(String(cause)));
2566
+ }
2567
+ }
2568
+ })();
2569
+ return () => {
2570
+ cancelled = true;
2571
+ void created?.destroy();
2572
+ };
2573
+ }, [entry.id]);
2574
+ if (error) {
2575
+ return /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("div", { role: "alert", children: error.message });
2576
+ }
2577
+ if (!child) {
2578
+ return null;
2579
+ }
2580
+ return /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(import_runtime7.PageRuntimeView, { runtime: child });
2581
+ }
2582
+
2583
+ // src/index.ts
2584
+ var shadcnUiKit = {
2585
+ componentRegistry: createShadcnComponentRegistry,
2586
+ layoutRegistry: createShadcnLayoutRegistry,
2587
+ interactionKit: {
2588
+ Provider: ShadcnInteractionProvider,
2589
+ OverlayHost: ShadcnOverlayHost
2590
+ }
2591
+ };
2592
+ // Annotate the CommonJS export names for ESM import in node:
2593
+ 0 && (module.exports = {
2594
+ Alert,
2595
+ Button,
2596
+ Card,
2597
+ Flex,
2598
+ Form,
2599
+ Grid,
2600
+ Input,
2601
+ OVERLAY_CANCEL_PAYLOAD,
2602
+ OverlayManager,
2603
+ Page,
2604
+ Select,
2605
+ ShadcnAdminLayout,
2606
+ ShadcnBlankLayout,
2607
+ ShadcnInteractionProvider,
2608
+ ShadcnLandingLayout,
2609
+ ShadcnLoginLayout,
2610
+ ShadcnOverlayHost,
2611
+ Statistic,
2612
+ Table,
2613
+ Tabs,
2614
+ collectDefaultOpenKeys,
2615
+ createShadcnComponentRegistry,
2616
+ createShadcnLayoutRegistry,
2617
+ createUiBridge,
2618
+ findMenuChain,
2619
+ registerInteractionActions,
2620
+ registerShadcnComponents,
2621
+ registerShadcnLayouts,
2622
+ resolveLayout,
2623
+ shadcnComponents,
2624
+ shadcnUiKit,
2625
+ useFormContext
2626
+ });