@pactor-app/ui 1.1.0 → 1.2.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/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @pactor-app/ui
2
2
 
3
- [Pactor](https://github.com/426-330/pactor) 的**唯一内置 UI kit**(shadcn 风格):内含 components / interaction / layout 三层(vendored shadcn/ui 组件源码——Base UI 原语(`@base-ui/react`,shadcn 官方默认原语层)+ cva + tailwind-merge + lucide-react;12 组件 props 契约类型、交互核心 `OverlayManager` / `createUiBridge` / `registerInteractionActions` / `UiBridge`、布局类型与 `resolveLayout` 均为本包自有),并提供 `shadcnUiKit` 预设对象与预建样式表。**这是 @pactor-app/app 的内置默认(也是唯一内置)UI kit**。主入口导出三层全部公开 API 与预设,另提供子路径入口:`@pactor-app/ui/components`(DSL 组件适配层)/ `/interaction` / `/layout` / `/ui`(vendored shadcn 原语组件——宿主自写 React UI 用,如槽位内容与自定义组件)。
3
+ [Pactor](https://github.com/426-330/pactor) 的**内置默认 UI 实现**(shadcn 风格):内含 components / interaction / layout 三层(vendored shadcn/ui 组件源码——Base UI 原语(`@base-ui/react`,shadcn 官方默认原语层)+ cva + tailwind-merge + lucide-react;12 组件 props 契约类型、交互核心 `OverlayManager` / `createUiBridge` / `registerInteractionActions` / `UiBridge`、布局类型与 `resolveLayout` 均为本包自有),并提供预建样式表。**这是 @pactor-app/app 的内置默认 UI 实现**。主入口导出三层全部公开 API,另提供子路径入口:`@pactor-app/ui/components`(DSL 组件适配层)/ `/interaction` / `/layout` / `/ui`(vendored shadcn 原语组件——宿主自写 React UI 用,如槽位内容与自定义组件)。
4
4
 
5
5
  ## 安装
6
6
 
@@ -26,33 +26,28 @@ createPactorApplication({
26
26
  }).mount('#root');
27
27
  ```
28
28
 
29
- ## 预设对象 shadcnUiKit
29
+ ## 自定义覆盖点
30
30
 
31
- 显式表达或局部覆盖场景使用 `shadcnUiKit`,一次提供三个槽位:
31
+ 业务可用 `createPactorApplication` 的细粒度选项逐槽位覆盖默认实现:
32
32
 
33
- | 槽位 | |
33
+ | 槽位 | 默认值 |
34
34
  | --- | --- |
35
- | `componentRegistry` | `createShadcnComponentRegistry`(components 层) |
36
- | `layoutRegistry` | `createShadcnLayoutRegistry`(layout 层) |
37
- | `interactionKit` | `{ Provider: ShadcnInteractionProvider, OverlayHost: ShadcnOverlayHost }`(interaction 层) |
35
+ | `componentRegistry` | `createComponentRegistry`(components 层) |
36
+ | `layoutRegistry` | `createLayoutRegistry`(layout 层) |
37
+ | `interactionKit` | `{ Provider: InteractionProvider, OverlayHost: OverlayHost }`(interaction 层) |
38
38
 
39
- ```ts
40
- import { shadcnUiKit } from '@pactor-app/ui';
41
-
42
- createPactorApplication({
43
- appId: 'risk-admin',
44
- api: { baseURL: '/api/runtime' },
45
- ui: shadcnUiKit, // 与不传 ui 的默认行为一致
46
- });
47
- ```
48
-
49
- 每个槽位的取值优先级为:**显式细粒度选项 > `ui` 预设 > 内置 shadcn 默认**。例如只覆盖布局层、其余槽位保持 shadcn 预设:
39
+ 各槽位独立合并,显式传入的选项优先于内置默认。例如只覆盖布局层、其余槽位保持内置默认:
50
40
 
51
41
  ```ts
42
+ import { createLayoutRegistry } from '@pactor-app/ui/layout';
43
+
52
44
  createPactorApplication({
53
45
  appId: 'risk-admin',
54
- ui: shadcnUiKit,
55
- layoutRegistry: createMyLayoutRegistry, // 覆盖预设的布局槽位
46
+ layoutRegistry: () => {
47
+ const registry = createLayoutRegistry();
48
+ registry.register('my-layout', MyLayout);
49
+ return registry;
50
+ },
56
51
  });
57
52
  ```
58
53
 
@@ -70,39 +65,38 @@ import '@pactor-app/ui/styles.css';
70
65
 
71
66
  ## 独立使用(不经 @pactor-app/app)
72
67
 
73
- 以 `createShadcnComponentRegistry()` 创建组件注册表,与 @pactor-app/runtime 直接组合:
68
+ 以 `createComponentRegistry()` 创建组件注册表,与 @pactor-app/runtime 直接组合:
74
69
 
75
70
  ```tsx
76
71
  import { createPageRuntime, PageRuntimeView } from '@pactor-app/runtime';
77
- import { createShadcnComponentRegistry } from '@pactor-app/ui/components';
72
+ import { createComponentRegistry } from '@pactor-app/ui/components';
78
73
  import '@pactor-app/ui/styles.css';
79
74
 
80
75
  const runtime = createPageRuntime(pageDsl, {
81
- components: createShadcnComponentRegistry(),
76
+ components: createComponentRegistry(),
82
77
  });
83
78
 
84
79
  await runtime.initialized;
85
80
  <PageRuntimeView runtime={runtime} />;
86
81
  ```
87
82
 
88
- 布局层(`createShadcnLayoutRegistry` / `registerShadcnLayouts`)与交互层(`ShadcnInteractionProvider` / `ShadcnOverlayHost`)同样可独立组合;交互核心(`OverlayManager` / `createUiBridge` / `registerInteractionActions`)由本包 `@pactor-app/ui/interaction` 导出。
83
+ 布局层(`createLayoutRegistry` / `registerBuiltinLayouts`)预注册五种内置布局:admin / blank / login / landing / chat;与交互层(`InteractionProvider` / `OverlayHost`)同样可独立组合。交互核心(`OverlayManager` / `createUiBridge` / `registerInteractionActions`)由本包 `@pactor-app/ui/interaction` 导出。
89
84
 
90
85
  ## 聚合 re-export
91
86
 
92
- 三层的公开 API 与预设均可直接从本包导入:
87
+ 三层的公开 API 均可直接从本包导入:
93
88
 
94
89
  ```ts
95
90
  import {
96
- createShadcnComponentRegistry,
97
- createShadcnLayoutRegistry,
98
- ShadcnInteractionProvider,
99
- ShadcnOverlayHost,
100
- shadcnUiKit,
91
+ createComponentRegistry,
92
+ createLayoutRegistry,
93
+ InteractionProvider,
94
+ OverlayHost,
101
95
  // ...以及三层其余全部导出
102
96
  } from '@pactor-app/ui';
103
97
  ```
104
98
 
105
- 契约类型(props / layout / interaction)与交互核心均为本包自有定义(自 0.x 的 `unify-ui-shadcn` 变更起;旧 `pactor-ui-antd` / `pactor-ui-radix` 包已删除,等价符号的迁移对照见 docs 的 @pactor-app/ui 章节)。
99
+ 契约类型(props / layout / interaction)与交互核心均为本包自有定义。
106
100
 
107
101
  ## 文档
108
102
 
@@ -1,6 +1,8 @@
1
1
  import {
2
- Icon
3
- } from "./chunk-6JNCDPGS.js";
2
+ Icon,
3
+ Menu,
4
+ Sidebar
5
+ } from "./chunk-HJPHJKVA.js";
4
6
  import {
5
7
  Accordion,
6
8
  AccordionContent,
@@ -13,7 +15,7 @@ import {
13
15
  Popover,
14
16
  PopoverContent,
15
17
  PopoverTrigger
16
- } from "./chunk-GL7IO22L.js";
18
+ } from "./chunk-2LYMCWEJ.js";
17
19
  import {
18
20
  cn
19
21
  } from "./chunk-RQHJBTEU.js";
@@ -62,7 +64,7 @@ function collectDefaultOpenKeys(registry, activeMenuId) {
62
64
  return keys;
63
65
  }
64
66
 
65
- // src/layout/admin/ShadcnAdminLayout.tsx
67
+ // src/layout/admin/AdminLayout.tsx
66
68
  import { filterMenusByPermission } from "@pactor-app/permission";
67
69
  import {
68
70
  resolveRoute
@@ -103,7 +105,7 @@ function useBreakpointBelow(breakpoint) {
103
105
  return breakpoint !== void 0 && matches;
104
106
  }
105
107
 
106
- // src/layout/admin/ShadcnAdminLayout.tsx
108
+ // src/layout/admin/AdminLayout.tsx
107
109
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
108
110
  function buildVisibleMenuTree(registry, permissionChecker) {
109
111
  const tree = registry.tree();
@@ -304,7 +306,7 @@ function MenuTree({
304
306
  }
305
307
  );
306
308
  }
307
- function ShadcnAdminLayout({
309
+ function AdminLayout({
308
310
  config = {},
309
311
  brand,
310
312
  registry,
@@ -611,9 +613,9 @@ function ShadcnAdminLayout({
611
613
  );
612
614
  }
613
615
 
614
- // src/layout/basic/ShadcnBlankLayout.tsx
616
+ // src/layout/basic/BlankLayout.tsx
615
617
  import { jsx as jsx2 } from "react/jsx-runtime";
616
- function ShadcnBlankLayout({ children }) {
618
+ function BlankLayout({ children }) {
617
619
  return /* @__PURE__ */ jsx2(
618
620
  "div",
619
621
  {
@@ -624,17 +626,144 @@ function ShadcnBlankLayout({ children }) {
624
626
  );
625
627
  }
626
628
 
627
- // src/layout/basic/ShadcnLoginLayout.tsx
628
- import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
629
- function ShadcnLoginLayout({
629
+ // src/layout/chat/ChatLayout.tsx
630
+ import { filterMenusByPermission as filterMenusByPermission2 } from "@pactor-app/permission";
631
+ import { useEffect as useEffect3, useMemo as useMemo2, useState as useState3 } from "react";
632
+ import { jsx as jsx3 } from "react/jsx-runtime";
633
+ function ChatLayout({
634
+ brand,
635
+ menu,
636
+ registry,
637
+ bridge,
638
+ route: routeProp,
639
+ permissionChecker,
640
+ activeMenuKey,
641
+ onMenuSelect,
642
+ collapsible,
643
+ sidebar,
644
+ sidebarFooter,
645
+ children
646
+ }) {
647
+ const registryMenus = useMemo2(() => {
648
+ if (menu !== void 0 || registry === void 0) {
649
+ return [];
650
+ }
651
+ const tree = registry.tree();
652
+ const visible = permissionChecker !== void 0 ? filterMenusByPermission2(tree, permissionChecker) : tree;
653
+ return visible.flatMap((item) => menuItemToMenuData(item));
654
+ }, [menu, registry, permissionChecker]);
655
+ const menuItems = menu ?? registryMenus;
656
+ const [bridgePath, setBridgePath] = useState3(
657
+ () => bridge?.getLocation().pathname ?? "/"
658
+ );
659
+ useEffect3(() => {
660
+ if (bridge === void 0) {
661
+ return;
662
+ }
663
+ return bridge.subscribe((location) => {
664
+ setBridgePath(location.pathname);
665
+ });
666
+ }, [bridge]);
667
+ const activeKey = useMemo2(() => {
668
+ if (activeMenuKey !== void 0) {
669
+ return activeMenuKey;
670
+ }
671
+ const path = routeProp?.path ?? bridgePath;
672
+ return menuItems.find((item) => item.path === path)?.key;
673
+ }, [activeMenuKey, routeProp, bridgePath, menuItems]);
674
+ const handleSelect = (key) => {
675
+ const item = menuItems.find((m) => m.key === key);
676
+ if (item === void 0 || item.disabled === true) {
677
+ return;
678
+ }
679
+ if (onMenuSelect !== void 0) {
680
+ onMenuSelect(key, item);
681
+ return;
682
+ }
683
+ if (bridge !== void 0 && item.path !== void 0) {
684
+ bridge.push(item.path);
685
+ }
686
+ };
687
+ const sidebarContent = sidebar !== void 0 && sidebar !== null ? sidebar : menuItems.length > 0 ? /* @__PURE__ */ jsx3(
688
+ Menu,
689
+ {
690
+ items: menuItems.map((item) => chatMenuItemToUiMenuItem(item)),
691
+ selectedKey: activeKey,
692
+ onSelect: handleSelect
693
+ }
694
+ ) : void 0;
695
+ return /* @__PURE__ */ jsx3(
696
+ Sidebar,
697
+ {
698
+ brand,
699
+ content: sidebarContent,
700
+ sidebarFooter,
701
+ collapsible,
702
+ locationKey: routeProp?.path ?? bridgePath,
703
+ children: /* @__PURE__ */ jsx3(
704
+ "div",
705
+ {
706
+ "data-slot": "chat-layout-content",
707
+ className: "flex min-h-0 flex-1 flex-col",
708
+ children
709
+ }
710
+ )
711
+ }
712
+ );
713
+ }
714
+ function chatMenuItemToUiMenuItem(item) {
715
+ return {
716
+ key: item.key,
717
+ label: item.label,
718
+ icon: item.icon,
719
+ badge: item.badge,
720
+ dot: item.dot,
721
+ group: item.group,
722
+ disabled: item.disabled
723
+ };
724
+ }
725
+ function menuItemToMenuData(item) {
726
+ if (item.type === "group") {
727
+ const children = (item.children ?? []).flatMap(
728
+ (child) => menuItemToMenuData(child)
729
+ );
730
+ if (children.length === 0) {
731
+ return [];
732
+ }
733
+ return children.map((child, index) => ({
734
+ ...child,
735
+ group: index === 0 ? item.title : void 0
736
+ }));
737
+ }
738
+ if (item.hidden === true) {
739
+ return [];
740
+ }
741
+ const ext = item;
742
+ return [
743
+ {
744
+ key: item.id,
745
+ label: item.title,
746
+ icon: item.icon,
747
+ path: item.path,
748
+ badge: item.badge,
749
+ dot: ext.dot,
750
+ group: ext.group,
751
+ disabled: item.disabled
752
+ }
753
+ ];
754
+ }
755
+
756
+ // src/layout/basic/LoginLayout.tsx
757
+ import { jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
758
+ function LoginLayout({
630
759
  variant = "center",
631
760
  brand,
632
761
  children
633
762
  }) {
634
763
  const brandBlock = /* @__PURE__ */ jsxs2("div", { "data-slot": "login-brand", className: "flex flex-col items-center gap-2", children: [
635
764
  brand.logo,
636
- /* @__PURE__ */ jsx3("h1", { className: "text-xl font-semibold", children: brand.title }),
637
- brand.description !== void 0 && /* @__PURE__ */ jsx3("div", { className: "text-sm text-muted-foreground", children: brand.description })
765
+ /* @__PURE__ */ jsx4("h1", { className: "text-xl font-semibold", children: brand.title }),
766
+ brand.description !== void 0 && /* @__PURE__ */ jsx4("div", { className: "text-sm text-muted-foreground", children: brand.description })
638
767
  ] });
639
768
  if (variant === "split") {
640
769
  return /* @__PURE__ */ jsxs2(
@@ -644,7 +773,7 @@ function ShadcnLoginLayout({
644
773
  "data-variant": "split",
645
774
  className: "grid min-h-screen grid-cols-2 bg-background text-foreground",
646
775
  children: [
647
- /* @__PURE__ */ jsx3(
776
+ /* @__PURE__ */ jsx4(
648
777
  "div",
649
778
  {
650
779
  "data-slot": "login-brand-panel",
@@ -652,7 +781,7 @@ function ShadcnLoginLayout({
652
781
  children: brandBlock
653
782
  }
654
783
  ),
655
- /* @__PURE__ */ jsx3(
784
+ /* @__PURE__ */ jsx4(
656
785
  "div",
657
786
  {
658
787
  "data-slot": "login-form-panel",
@@ -664,7 +793,7 @@ function ShadcnLoginLayout({
664
793
  }
665
794
  );
666
795
  }
667
- return /* @__PURE__ */ jsx3(
796
+ return /* @__PURE__ */ jsx4(
668
797
  "div",
669
798
  {
670
799
  "data-slot": "login-layout",
@@ -672,18 +801,18 @@ function ShadcnLoginLayout({
672
801
  className: "flex min-h-screen items-center justify-center bg-muted/40 text-foreground",
673
802
  children: /* @__PURE__ */ jsxs2(Card, { "data-slot": "login-card", className: "w-full max-w-sm gap-4 py-6", children: [
674
803
  /* @__PURE__ */ jsxs2(CardHeader, { className: "px-6", children: [
675
- /* @__PURE__ */ jsx3(CardTitle, { className: "sr-only", children: brand.title }),
804
+ /* @__PURE__ */ jsx4(CardTitle, { className: "sr-only", children: brand.title }),
676
805
  brandBlock
677
806
  ] }),
678
- /* @__PURE__ */ jsx3(CardContent, { className: "flex flex-col gap-4 px-6", children })
807
+ /* @__PURE__ */ jsx4(CardContent, { className: "flex flex-col gap-4 px-6", children })
679
808
  ] })
680
809
  }
681
810
  );
682
811
  }
683
812
 
684
- // src/layout/basic/ShadcnLandingLayout.tsx
685
- import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
686
- function ShadcnLandingLayout({
813
+ // src/layout/basic/LandingLayout.tsx
814
+ import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
815
+ function LandingLayout({
687
816
  navbar,
688
817
  footer,
689
818
  children
@@ -694,7 +823,7 @@ function ShadcnLandingLayout({
694
823
  "data-slot": "landing-layout",
695
824
  className: "flex min-h-screen flex-col bg-background text-foreground",
696
825
  children: [
697
- navbar !== void 0 && /* @__PURE__ */ jsx4(
826
+ navbar !== void 0 && /* @__PURE__ */ jsx5(
698
827
  "header",
699
828
  {
700
829
  "data-slot": "landing-navbar",
@@ -702,8 +831,8 @@ function ShadcnLandingLayout({
702
831
  children: navbar
703
832
  }
704
833
  ),
705
- /* @__PURE__ */ jsx4("main", { "data-slot": "landing-content", className: "flex-1", children }),
706
- footer !== void 0 && /* @__PURE__ */ jsx4(
834
+ /* @__PURE__ */ jsx5("main", { "data-slot": "landing-content", className: "flex-1", children }),
835
+ footer !== void 0 && /* @__PURE__ */ jsx5(
707
836
  "footer",
708
837
  {
709
838
  "data-slot": "landing-footer",
@@ -718,15 +847,16 @@ function ShadcnLandingLayout({
718
847
 
719
848
  // src/layout/registry.ts
720
849
  import { Registry } from "@pactor-app/core";
721
- function registerShadcnLayouts(registry) {
722
- registry.register("admin", ShadcnAdminLayout);
723
- registry.register("blank", ShadcnBlankLayout);
724
- registry.register("login", ShadcnLoginLayout);
725
- registry.register("landing", ShadcnLandingLayout);
850
+ function registerBuiltinLayouts(registry) {
851
+ registry.register("admin", AdminLayout);
852
+ registry.register("blank", BlankLayout);
853
+ registry.register("login", LoginLayout);
854
+ registry.register("landing", LandingLayout);
855
+ registry.register("chat", ChatLayout);
726
856
  }
727
- function createShadcnLayoutRegistry() {
857
+ function createLayoutRegistry() {
728
858
  const registry = new Registry("LayoutRegistry");
729
- registerShadcnLayouts(registry);
859
+ registerBuiltinLayouts(registry);
730
860
  return registry;
731
861
  }
732
862
  function resolveLayout(registry, type) {
@@ -735,7 +865,7 @@ function resolveLayout(registry, type) {
735
865
  console.warn(
736
866
  `[pactor-layout] unknown layout type "${type}", fallback to BlankLayout`
737
867
  );
738
- return ShadcnBlankLayout;
868
+ return BlankLayout;
739
869
  }
740
870
  return component;
741
871
  }
@@ -743,11 +873,12 @@ function resolveLayout(registry, type) {
743
873
  export {
744
874
  findMenuChain,
745
875
  collectDefaultOpenKeys,
746
- ShadcnAdminLayout,
747
- ShadcnBlankLayout,
748
- ShadcnLoginLayout,
749
- ShadcnLandingLayout,
750
- registerShadcnLayouts,
751
- createShadcnLayoutRegistry,
876
+ AdminLayout,
877
+ BlankLayout,
878
+ ChatLayout,
879
+ LoginLayout,
880
+ LandingLayout,
881
+ registerBuiltinLayouts,
882
+ createLayoutRegistry,
752
883
  resolveLayout
753
884
  };
@@ -207,6 +207,66 @@ function PopoverContent({
207
207
  ) });
208
208
  }
209
209
 
210
+ // src/ui/tooltip.tsx
211
+ import { Tooltip as TooltipPrimitive } from "@base-ui/react/tooltip";
212
+ import { jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
213
+ function TooltipProvider({
214
+ delay = 0,
215
+ ...props
216
+ }) {
217
+ return /* @__PURE__ */ jsx4(
218
+ TooltipPrimitive.Provider,
219
+ {
220
+ "data-slot": "tooltip-provider",
221
+ delay,
222
+ ...props
223
+ }
224
+ );
225
+ }
226
+ function Tooltip({
227
+ ...props
228
+ }) {
229
+ return /* @__PURE__ */ jsx4(TooltipProvider, { children: /* @__PURE__ */ jsx4(TooltipPrimitive.Root, { "data-slot": "tooltip", ...props }) });
230
+ }
231
+ function TooltipTrigger({
232
+ ...props
233
+ }) {
234
+ return /* @__PURE__ */ jsx4(TooltipPrimitive.Trigger, { "data-slot": "tooltip-trigger", ...props });
235
+ }
236
+ function TooltipContent({
237
+ className,
238
+ side = "top",
239
+ sideOffset = 4,
240
+ align = "center",
241
+ children,
242
+ ...props
243
+ }) {
244
+ return /* @__PURE__ */ jsx4(TooltipPrimitive.Portal, { children: /* @__PURE__ */ jsx4(
245
+ TooltipPrimitive.Positioner,
246
+ {
247
+ align,
248
+ side,
249
+ sideOffset,
250
+ className: "isolate z-50",
251
+ children: /* @__PURE__ */ jsxs2(
252
+ TooltipPrimitive.Popup,
253
+ {
254
+ "data-slot": "tooltip-content",
255
+ className: cn(
256
+ "z-50 w-fit origin-(--transform-origin) rounded-md bg-primary px-3 py-1.5 text-xs text-balance text-primary-foreground 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",
257
+ className
258
+ ),
259
+ ...props,
260
+ children: [
261
+ children,
262
+ /* @__PURE__ */ jsx4(TooltipPrimitive.Arrow, { className: "z-50 size-2.5 rotate-45 rounded-[2px] bg-primary fill-primary data-[side=top]:-bottom-1 data-[side=bottom]:-top-1 data-[side=left]:-right-1 data-[side=right]:-left-1" })
263
+ ]
264
+ }
265
+ )
266
+ }
267
+ ) });
268
+ }
269
+
210
270
  export {
211
271
  Accordion,
212
272
  AccordionItem,
@@ -221,5 +281,9 @@ export {
221
281
  CardFooter,
222
282
  Popover,
223
283
  PopoverTrigger,
224
- PopoverContent
284
+ PopoverContent,
285
+ TooltipProvider,
286
+ Tooltip,
287
+ TooltipTrigger,
288
+ TooltipContent
225
289
  };