@pactor-app/ui 1.0.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.
@@ -20,14 +20,15 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/layout/index.ts
21
21
  var layout_exports = {};
22
22
  __export(layout_exports, {
23
- ShadcnAdminLayout: () => ShadcnAdminLayout,
24
- ShadcnBlankLayout: () => ShadcnBlankLayout,
25
- ShadcnLandingLayout: () => ShadcnLandingLayout,
26
- ShadcnLoginLayout: () => ShadcnLoginLayout,
23
+ AdminLayout: () => AdminLayout,
24
+ BlankLayout: () => BlankLayout,
25
+ ChatLayout: () => ChatLayout,
26
+ LandingLayout: () => LandingLayout,
27
+ LoginLayout: () => LoginLayout,
27
28
  collectDefaultOpenKeys: () => collectDefaultOpenKeys,
28
- createShadcnLayoutRegistry: () => createShadcnLayoutRegistry,
29
+ createLayoutRegistry: () => createLayoutRegistry,
29
30
  findMenuChain: () => findMenuChain,
30
- registerShadcnLayouts: () => registerShadcnLayouts,
31
+ registerBuiltinLayouts: () => registerBuiltinLayouts,
31
32
  resolveLayout: () => resolveLayout
32
33
  });
33
34
  module.exports = __toCommonJS(layout_exports);
@@ -76,7 +77,7 @@ function collectDefaultOpenKeys(registry, activeMenuId) {
76
77
  return keys;
77
78
  }
78
79
 
79
- // src/layout/admin/ShadcnAdminLayout.tsx
80
+ // src/layout/admin/AdminLayout.tsx
80
81
  var import_permission = require("@pactor-app/permission");
81
82
  var import_router = require("@pactor-app/router");
82
83
  var import_runtime = require("@pactor-app/runtime");
@@ -302,7 +303,19 @@ var ICONS = {
302
303
  code: import_lucide_react2.Code,
303
304
  database: import_lucide_react2.Database,
304
305
  cloud: import_lucide_react2.Cloud,
305
- languages: import_lucide_react2.Languages
306
+ languages: import_lucide_react2.Languages,
307
+ // @pactor-app/chat 收敛(spec: chat-icon-convergence):chat 既有图标与
308
+ // 契约图标名(menu.icon / messageActions[].icon 的既有取值)直通
309
+ "chevrons-up-down": import_lucide_react2.ChevronsUpDown,
310
+ "file-text": import_lucide_react2.FileText,
311
+ "panel-left": import_lucide_react2.PanelLeft,
312
+ "panel-right-open": import_lucide_react2.PanelRightOpen,
313
+ square: import_lucide_react2.Square,
314
+ history: import_lucide_react2.History,
315
+ "rotate-ccw": import_lucide_react2.RotateCcw,
316
+ quote: import_lucide_react2.Quote,
317
+ chat: import_lucide_react2.MessageSquare,
318
+ brain: import_lucide_react2.Brain
306
319
  };
307
320
  function normalizeIconName(name) {
308
321
  return name.replace(/([a-z0-9])([A-Z])/g, "$1-$2").replace(/[_\s]+/g, "-").toLowerCase();
@@ -385,7 +398,7 @@ function useBreakpointBelow(breakpoint) {
385
398
  return breakpoint !== void 0 && matches;
386
399
  }
387
400
 
388
- // src/layout/admin/ShadcnAdminLayout.tsx
401
+ // src/layout/admin/AdminLayout.tsx
389
402
  var import_jsx_runtime4 = require("react/jsx-runtime");
390
403
  function buildVisibleMenuTree(registry, permissionChecker) {
391
404
  const tree = registry.tree();
@@ -586,7 +599,7 @@ function MenuTree({
586
599
  }
587
600
  );
588
601
  }
589
- function ShadcnAdminLayout({
602
+ function AdminLayout({
590
603
  config = {},
591
604
  brand,
592
605
  registry,
@@ -893,9 +906,9 @@ function ShadcnAdminLayout({
893
906
  );
894
907
  }
895
908
 
896
- // src/layout/basic/ShadcnBlankLayout.tsx
909
+ // src/layout/basic/BlankLayout.tsx
897
910
  var import_jsx_runtime5 = require("react/jsx-runtime");
898
- function ShadcnBlankLayout({ children }) {
911
+ function BlankLayout({ children }) {
899
912
  return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
900
913
  "div",
901
914
  {
@@ -906,10 +919,598 @@ function ShadcnBlankLayout({ children }) {
906
919
  );
907
920
  }
908
921
 
909
- // src/ui/card.tsx
922
+ // src/layout/chat/ChatLayout.tsx
923
+ var import_permission2 = require("@pactor-app/permission");
924
+ var import_react4 = require("react");
925
+
926
+ // src/ui/tooltip.tsx
927
+ var import_tooltip = require("@base-ui/react/tooltip");
910
928
  var import_jsx_runtime6 = require("react/jsx-runtime");
911
- function Card({ className, ...props }) {
929
+ function TooltipProvider({
930
+ delay = 0,
931
+ ...props
932
+ }) {
912
933
  return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
934
+ import_tooltip.Tooltip.Provider,
935
+ {
936
+ "data-slot": "tooltip-provider",
937
+ delay,
938
+ ...props
939
+ }
940
+ );
941
+ }
942
+ function Tooltip({
943
+ ...props
944
+ }) {
945
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(TooltipProvider, { children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_tooltip.Tooltip.Root, { "data-slot": "tooltip", ...props }) });
946
+ }
947
+ function TooltipTrigger({
948
+ ...props
949
+ }) {
950
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_tooltip.Tooltip.Trigger, { "data-slot": "tooltip-trigger", ...props });
951
+ }
952
+ function TooltipContent({
953
+ className,
954
+ side = "top",
955
+ sideOffset = 4,
956
+ align = "center",
957
+ children,
958
+ ...props
959
+ }) {
960
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_tooltip.Tooltip.Portal, { children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
961
+ import_tooltip.Tooltip.Positioner,
962
+ {
963
+ align,
964
+ side,
965
+ sideOffset,
966
+ className: "isolate z-50",
967
+ children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
968
+ import_tooltip.Tooltip.Popup,
969
+ {
970
+ "data-slot": "tooltip-content",
971
+ className: cn(
972
+ "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",
973
+ className
974
+ ),
975
+ ...props,
976
+ children: [
977
+ children,
978
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_tooltip.Tooltip.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" })
979
+ ]
980
+ }
981
+ )
982
+ }
983
+ ) });
984
+ }
985
+
986
+ // src/components/components/Sidebar/rail-tooltip.tsx
987
+ var import_jsx_runtime7 = require("react/jsx-runtime");
988
+ function RailTooltip({
989
+ label,
990
+ children
991
+ }) {
992
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(Tooltip, { children: [
993
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(TooltipTrigger, { render: children }),
994
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(TooltipContent, { side: "right", sideOffset: 8, children: label })
995
+ ] });
996
+ }
997
+
998
+ // src/components/components/Sidebar/index.tsx
999
+ var import_runtime2 = require("@pactor-app/runtime");
1000
+ var import_lucide_react3 = require("lucide-react");
1001
+ var import_react3 = require("react");
1002
+ var import_jsx_runtime8 = require("react/jsx-runtime");
1003
+ var SidebarDrawerContext = (0, import_react3.createContext)(null);
1004
+ function useSidebarDrawer() {
1005
+ return (0, import_react3.useContext)(SidebarDrawerContext);
1006
+ }
1007
+ var MOBILE_MEDIA_QUERY = "(max-width: 767px)";
1008
+ function useIsMobileViewport() {
1009
+ const [isMobile, setIsMobile] = (0, import_react3.useState)(
1010
+ () => typeof window !== "undefined" && typeof window.matchMedia === "function" && window.matchMedia(MOBILE_MEDIA_QUERY).matches
1011
+ );
1012
+ (0, import_react3.useEffect)(() => {
1013
+ if (typeof window.matchMedia !== "function") {
1014
+ return;
1015
+ }
1016
+ const media = window.matchMedia(MOBILE_MEDIA_QUERY);
1017
+ const onChange = (event) => {
1018
+ setIsMobile(event.matches);
1019
+ };
1020
+ setIsMobile(media.matches);
1021
+ media.addEventListener("change", onChange);
1022
+ return () => media.removeEventListener("change", onChange);
1023
+ }, []);
1024
+ return isMobile;
1025
+ }
1026
+ function useOptionalRenderer() {
1027
+ try {
1028
+ return (0, import_runtime2.useRenderer)();
1029
+ } catch {
1030
+ return null;
1031
+ }
1032
+ }
1033
+ function SidebarContent({ content }) {
1034
+ const renderer = useOptionalRenderer();
1035
+ if (renderer !== null && typeof content === "object" && content !== null && !("$$typeof" in content) && "type" in content) {
1036
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_jsx_runtime8.Fragment, { children: renderer.renderChildren([content]) });
1037
+ }
1038
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_jsx_runtime8.Fragment, { children: content });
1039
+ }
1040
+ function isBrandObject(brand) {
1041
+ return typeof brand === "object" && brand !== null && !("$$typeof" in brand) && "title" in brand;
1042
+ }
1043
+ function Sidebar({
1044
+ brand,
1045
+ items = [],
1046
+ content,
1047
+ sidebarFooter,
1048
+ collapsible,
1049
+ onCollapse,
1050
+ locationKey,
1051
+ drawer = true,
1052
+ className,
1053
+ style,
1054
+ children
1055
+ }) {
1056
+ const { t } = (0, import_runtime2.useI18n)();
1057
+ const isMobile = useIsMobileViewport();
1058
+ const [drawerOpen, setDrawerOpen] = (0, import_react3.useState)(false);
1059
+ const [collapsed, setCollapsed] = (0, import_react3.useState)(false);
1060
+ const collapsedEffective = collapsible === true && collapsed && !isMobile;
1061
+ const close = (0, import_react3.useCallback)(() => setDrawerOpen(false), []);
1062
+ const toggleCollapsed = () => {
1063
+ const next = !collapsed;
1064
+ setCollapsed(next);
1065
+ onCollapse?.(next);
1066
+ };
1067
+ const [trackedLocationKey, setTrackedLocationKey] = (0, import_react3.useState)(locationKey);
1068
+ if (trackedLocationKey !== locationKey) {
1069
+ setTrackedLocationKey(locationKey);
1070
+ setDrawerOpen(false);
1071
+ }
1072
+ (0, import_react3.useEffect)(() => {
1073
+ if (!drawerOpen) {
1074
+ return;
1075
+ }
1076
+ const onKeyDown = (event) => {
1077
+ if (event.key === "Escape") {
1078
+ setDrawerOpen(false);
1079
+ }
1080
+ };
1081
+ window.addEventListener("keydown", onKeyDown);
1082
+ return () => window.removeEventListener("keydown", onKeyDown);
1083
+ }, [drawerOpen]);
1084
+ const brandNode = isBrandObject(brand) ? /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_jsx_runtime8.Fragment, { children: [
1085
+ brand.logo ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1086
+ "img",
1087
+ {
1088
+ "data-slot": "sidebar-brand-logo",
1089
+ src: brand.logo,
1090
+ alt: brand.title,
1091
+ className: "size-6 shrink-0"
1092
+ }
1093
+ ) : null,
1094
+ collapsedEffective ? null : /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "truncate text-sm font-semibold", children: brand.title })
1095
+ ] }) : brand;
1096
+ const collapseTrigger = collapsible && !isMobile ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1097
+ "button",
1098
+ {
1099
+ type: "button",
1100
+ "data-slot": "sidebar-collapse-trigger",
1101
+ "aria-label": collapsedEffective ? t("pactor.layout.expandSidebar") : t("pactor.layout.collapseSidebar"),
1102
+ "aria-expanded": !collapsedEffective,
1103
+ title: collapsedEffective ? t("pactor.layout.expandSidebar") : t("pactor.layout.collapseSidebar"),
1104
+ className: cn(
1105
+ "cursor-pointer flex size-8 shrink-0 items-center justify-center rounded-md text-sidebar-foreground/60 transition-colors hover:bg-sidebar-accent hover:text-sidebar-foreground"
1106
+ ),
1107
+ onClick: toggleCollapsed,
1108
+ children: collapsedEffective ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_lucide_react3.ChevronRight, { "aria-hidden": true, className: "size-4" }) : /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_lucide_react3.ChevronLeft, { "aria-hidden": true, className: "size-4" })
1109
+ }
1110
+ ) : null;
1111
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1112
+ SidebarDrawerContext.Provider,
1113
+ {
1114
+ value: { close, collapsed: collapsedEffective },
1115
+ children: /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
1116
+ "div",
1117
+ {
1118
+ "data-slot": "sidebar-layout",
1119
+ className: cn("flex min-h-screen w-full", className),
1120
+ style,
1121
+ children: [
1122
+ drawer && drawerOpen ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1123
+ "div",
1124
+ {
1125
+ "data-slot": "sidebar-scrim",
1126
+ "aria-hidden": true,
1127
+ className: "fixed inset-0 z-40 bg-black/40 md:hidden",
1128
+ onClick: close
1129
+ }
1130
+ ) : null,
1131
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
1132
+ "aside",
1133
+ {
1134
+ "data-slot": "sidebar",
1135
+ "data-collapsed": collapsedEffective || void 0,
1136
+ "data-open": drawerOpen ? "true" : "false",
1137
+ inert: drawer && isMobile && !drawerOpen ? true : void 0,
1138
+ className: cn(
1139
+ "flex shrink-0 flex-col border-r border-sidebar-border bg-sidebar text-sidebar-foreground transition-[width,transform]",
1140
+ collapsedEffective ? "w-16" : "w-60",
1141
+ // 移动端覆盖抽屉:退出文档流,关闭时移出屏幕
1142
+ drawer && "max-md:fixed max-md:inset-y-0 max-md:left-0 max-md:z-50 max-md:w-60 max-md:-translate-x-full max-md:data-[open=true]:translate-x-0"
1143
+ ),
1144
+ children: [
1145
+ brand !== void 0 || collapseTrigger !== null ? /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
1146
+ "div",
1147
+ {
1148
+ "data-slot": "sidebar-brand",
1149
+ className: cn(
1150
+ "flex h-14 shrink-0 items-center gap-2 border-b border-sidebar-border px-4",
1151
+ // 移动端品牌维持顶栏现状:抽屉内不重复渲染品牌行
1152
+ "max-md:hidden",
1153
+ collapsedEffective && "justify-center px-2"
1154
+ ),
1155
+ children: [
1156
+ collapsedEffective ? null : /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "flex min-w-0 flex-1 items-center gap-2", children: brandNode }),
1157
+ collapseTrigger
1158
+ ]
1159
+ }
1160
+ ) : null,
1161
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1162
+ "nav",
1163
+ {
1164
+ "data-slot": "sidebar-nav",
1165
+ className: "min-h-0 flex-1 overflow-auto p-2",
1166
+ children: content !== void 0 && content !== null ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(SidebarContent, { content }) : items.map((item, index) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1167
+ SidebarEntry,
1168
+ {
1169
+ item,
1170
+ depth: 0,
1171
+ collapsed: collapsedEffective
1172
+ },
1173
+ item.href ?? item.label ?? index
1174
+ ))
1175
+ }
1176
+ ),
1177
+ sidebarFooter !== void 0 && sidebarFooter !== null && !collapsedEffective ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1178
+ "div",
1179
+ {
1180
+ "data-slot": "sidebar-footer",
1181
+ className: "shrink-0 border-t border-sidebar-border p-2",
1182
+ children: sidebarFooter
1183
+ }
1184
+ ) : null
1185
+ ]
1186
+ }
1187
+ ),
1188
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
1189
+ "main",
1190
+ {
1191
+ "data-slot": "sidebar-content",
1192
+ className: "flex min-w-0 flex-1 flex-col",
1193
+ children: [
1194
+ drawer ? /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
1195
+ "div",
1196
+ {
1197
+ "data-slot": "sidebar-topbar",
1198
+ className: "flex h-14 items-center gap-2 border-b border-sidebar-border bg-sidebar px-3 md:hidden",
1199
+ children: [
1200
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1201
+ "button",
1202
+ {
1203
+ type: "button",
1204
+ "data-slot": "sidebar-menu-trigger",
1205
+ "aria-label": t("pactor.layout.openNavigation"),
1206
+ "aria-expanded": drawerOpen,
1207
+ className: "flex size-8 items-center justify-center rounded-md text-sidebar-foreground/60 transition-colors hover:bg-sidebar-accent hover:text-sidebar-foreground",
1208
+ onClick: () => setDrawerOpen(true),
1209
+ children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Icon, { name: "menu", size: 18 })
1210
+ }
1211
+ ),
1212
+ brandNode
1213
+ ]
1214
+ }
1215
+ ) : null,
1216
+ children
1217
+ ]
1218
+ }
1219
+ )
1220
+ ]
1221
+ }
1222
+ )
1223
+ }
1224
+ );
1225
+ }
1226
+ function SidebarEntry({
1227
+ item,
1228
+ depth,
1229
+ collapsed
1230
+ }) {
1231
+ const entryClass = cn(
1232
+ "flex w-full items-center gap-2 rounded-md px-3 py-2 text-left text-sm transition-colors",
1233
+ "text-sidebar-foreground/70 hover:bg-sidebar-accent hover:text-sidebar-accent-foreground",
1234
+ collapsed && "justify-center px-0"
1235
+ );
1236
+ const indentStyle = !collapsed && depth > 0 ? { paddingLeft: 12 + depth * 16 } : void 0;
1237
+ const labelNode = collapsed ? null : item.label;
1238
+ const iconNode = item.icon ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Icon, { name: item.icon, size: "sm" }) : null;
1239
+ const entry = item.href ? /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
1240
+ "a",
1241
+ {
1242
+ "data-slot": "sidebar-item",
1243
+ href: item.href,
1244
+ className: entryClass,
1245
+ style: indentStyle,
1246
+ children: [
1247
+ iconNode,
1248
+ labelNode
1249
+ ]
1250
+ }
1251
+ ) : /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("span", { "data-slot": "sidebar-item", className: entryClass, style: indentStyle, children: [
1252
+ iconNode,
1253
+ labelNode
1254
+ ] });
1255
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { "data-slot": "sidebar-entry", children: [
1256
+ collapsed ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(RailTooltip, { label: item.label, children: entry }) : entry,
1257
+ item.children?.map((child, index) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1258
+ SidebarEntry,
1259
+ {
1260
+ item: child,
1261
+ depth: depth + 1,
1262
+ collapsed
1263
+ },
1264
+ child.href ?? child.label ?? index
1265
+ ))
1266
+ ] });
1267
+ }
1268
+
1269
+ // src/components/components/Menu/index.tsx
1270
+ var import_jsx_runtime9 = require("react/jsx-runtime");
1271
+ function Menu2({
1272
+ items = [],
1273
+ mode = "inline",
1274
+ selectedKey,
1275
+ onSelect,
1276
+ className,
1277
+ style
1278
+ }) {
1279
+ const drawer = useSidebarDrawer();
1280
+ const collapsed = mode === "inline" && drawer?.collapsed === true;
1281
+ let previousGroup;
1282
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1283
+ "nav",
1284
+ {
1285
+ "data-slot": "menu",
1286
+ "data-mode": mode,
1287
+ "data-collapsed": collapsed || void 0,
1288
+ className: cn(
1289
+ mode === "horizontal" ? "flex items-center gap-1" : "flex flex-col gap-0.5 p-2",
1290
+ className
1291
+ ),
1292
+ style,
1293
+ children: items.map((item) => {
1294
+ let groupLabel;
1295
+ if (!collapsed && mode === "inline" && item.group !== void 0 && item.group !== previousGroup) {
1296
+ groupLabel = item.group;
1297
+ }
1298
+ previousGroup = item.group;
1299
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { "data-slot": "menu-group-entry", children: [
1300
+ groupLabel !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1301
+ "div",
1302
+ {
1303
+ "data-slot": "menu-group",
1304
+ className: "px-3 pb-1 pt-3 text-xs font-medium text-muted-foreground first:pt-1",
1305
+ children: groupLabel
1306
+ }
1307
+ ),
1308
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1309
+ MenuEntry,
1310
+ {
1311
+ item,
1312
+ mode,
1313
+ depth: 0,
1314
+ selectedKey,
1315
+ onSelect,
1316
+ collapsed
1317
+ }
1318
+ )
1319
+ ] }, item.key);
1320
+ })
1321
+ }
1322
+ );
1323
+ }
1324
+ function MenuEntry({
1325
+ item,
1326
+ mode,
1327
+ depth,
1328
+ selectedKey,
1329
+ onSelect,
1330
+ collapsed = false
1331
+ }) {
1332
+ const selected = item.key === selectedKey;
1333
+ const button = /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
1334
+ "button",
1335
+ {
1336
+ type: "button",
1337
+ "data-slot": "menu-item",
1338
+ "aria-current": selected ? "page" : void 0,
1339
+ disabled: item.disabled,
1340
+ className: cn(
1341
+ "flex w-full items-center gap-2 rounded-md text-sm transition-colors",
1342
+ mode === "horizontal" ? "px-3 py-2" : "px-3 py-2 text-left",
1343
+ collapsed && "justify-center px-0",
1344
+ selected ? "bg-accent font-medium text-accent-foreground" : "cursor-pointer text-muted-foreground hover:bg-accent/50 hover:text-foreground",
1345
+ item.disabled && "cursor-not-allowed opacity-50 hover:bg-transparent hover:text-muted-foreground"
1346
+ ),
1347
+ style: mode === "inline" && depth > 0 && !collapsed ? { paddingLeft: 12 + depth * 16 } : void 0,
1348
+ onClick: () => {
1349
+ if (!item.disabled) {
1350
+ onSelect?.(item.key);
1351
+ }
1352
+ },
1353
+ children: [
1354
+ item.icon ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Icon, { name: item.icon, size: "sm" }) : null,
1355
+ collapsed ? null : /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "min-w-0 flex-1 truncate text-left", children: item.label }),
1356
+ item.dot && !collapsed ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { "data-slot": "menu-item-dot", className: "size-1.5 shrink-0 rounded-full bg-destructive" }) : null,
1357
+ item.badge !== void 0 && !collapsed ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1358
+ "span",
1359
+ {
1360
+ "data-slot": "menu-item-badge",
1361
+ className: "shrink-0 rounded-full bg-muted px-1.5 py-0.5 text-xs text-muted-foreground",
1362
+ children: item.badge
1363
+ }
1364
+ ) : null
1365
+ ]
1366
+ }
1367
+ );
1368
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { "data-slot": "menu-entry", "data-key": item.key, children: [
1369
+ collapsed ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(RailTooltip, { label: item.label, children: button }) : button,
1370
+ item.children?.map((child) => /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1371
+ MenuEntry,
1372
+ {
1373
+ item: child,
1374
+ mode,
1375
+ depth: depth + 1,
1376
+ selectedKey,
1377
+ onSelect,
1378
+ collapsed
1379
+ },
1380
+ child.key
1381
+ ))
1382
+ ] });
1383
+ }
1384
+
1385
+ // src/layout/chat/ChatLayout.tsx
1386
+ var import_jsx_runtime10 = require("react/jsx-runtime");
1387
+ function ChatLayout({
1388
+ brand,
1389
+ menu,
1390
+ registry,
1391
+ bridge,
1392
+ route: routeProp,
1393
+ permissionChecker,
1394
+ activeMenuKey,
1395
+ onMenuSelect,
1396
+ collapsible,
1397
+ sidebar,
1398
+ sidebarFooter,
1399
+ children
1400
+ }) {
1401
+ const registryMenus = (0, import_react4.useMemo)(() => {
1402
+ if (menu !== void 0 || registry === void 0) {
1403
+ return [];
1404
+ }
1405
+ const tree = registry.tree();
1406
+ const visible = permissionChecker !== void 0 ? (0, import_permission2.filterMenusByPermission)(tree, permissionChecker) : tree;
1407
+ return visible.flatMap((item) => menuItemToMenuData(item));
1408
+ }, [menu, registry, permissionChecker]);
1409
+ const menuItems = menu ?? registryMenus;
1410
+ const [bridgePath, setBridgePath] = (0, import_react4.useState)(
1411
+ () => bridge?.getLocation().pathname ?? "/"
1412
+ );
1413
+ (0, import_react4.useEffect)(() => {
1414
+ if (bridge === void 0) {
1415
+ return;
1416
+ }
1417
+ return bridge.subscribe((location) => {
1418
+ setBridgePath(location.pathname);
1419
+ });
1420
+ }, [bridge]);
1421
+ const activeKey = (0, import_react4.useMemo)(() => {
1422
+ if (activeMenuKey !== void 0) {
1423
+ return activeMenuKey;
1424
+ }
1425
+ const path = routeProp?.path ?? bridgePath;
1426
+ return menuItems.find((item) => item.path === path)?.key;
1427
+ }, [activeMenuKey, routeProp, bridgePath, menuItems]);
1428
+ const handleSelect = (key) => {
1429
+ const item = menuItems.find((m) => m.key === key);
1430
+ if (item === void 0 || item.disabled === true) {
1431
+ return;
1432
+ }
1433
+ if (onMenuSelect !== void 0) {
1434
+ onMenuSelect(key, item);
1435
+ return;
1436
+ }
1437
+ if (bridge !== void 0 && item.path !== void 0) {
1438
+ bridge.push(item.path);
1439
+ }
1440
+ };
1441
+ const sidebarContent = sidebar !== void 0 && sidebar !== null ? sidebar : menuItems.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1442
+ Menu2,
1443
+ {
1444
+ items: menuItems.map((item) => chatMenuItemToUiMenuItem(item)),
1445
+ selectedKey: activeKey,
1446
+ onSelect: handleSelect
1447
+ }
1448
+ ) : void 0;
1449
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1450
+ Sidebar,
1451
+ {
1452
+ brand,
1453
+ content: sidebarContent,
1454
+ sidebarFooter,
1455
+ collapsible,
1456
+ locationKey: routeProp?.path ?? bridgePath,
1457
+ children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1458
+ "div",
1459
+ {
1460
+ "data-slot": "chat-layout-content",
1461
+ className: "flex min-h-0 flex-1 flex-col",
1462
+ children
1463
+ }
1464
+ )
1465
+ }
1466
+ );
1467
+ }
1468
+ function chatMenuItemToUiMenuItem(item) {
1469
+ return {
1470
+ key: item.key,
1471
+ label: item.label,
1472
+ icon: item.icon,
1473
+ badge: item.badge,
1474
+ dot: item.dot,
1475
+ group: item.group,
1476
+ disabled: item.disabled
1477
+ };
1478
+ }
1479
+ function menuItemToMenuData(item) {
1480
+ if (item.type === "group") {
1481
+ const children = (item.children ?? []).flatMap(
1482
+ (child) => menuItemToMenuData(child)
1483
+ );
1484
+ if (children.length === 0) {
1485
+ return [];
1486
+ }
1487
+ return children.map((child, index) => ({
1488
+ ...child,
1489
+ group: index === 0 ? item.title : void 0
1490
+ }));
1491
+ }
1492
+ if (item.hidden === true) {
1493
+ return [];
1494
+ }
1495
+ const ext = item;
1496
+ return [
1497
+ {
1498
+ key: item.id,
1499
+ label: item.title,
1500
+ icon: item.icon,
1501
+ path: item.path,
1502
+ badge: item.badge,
1503
+ dot: ext.dot,
1504
+ group: ext.group,
1505
+ disabled: item.disabled
1506
+ }
1507
+ ];
1508
+ }
1509
+
1510
+ // src/ui/card.tsx
1511
+ var import_jsx_runtime11 = require("react/jsx-runtime");
1512
+ function Card({ className, ...props }) {
1513
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
913
1514
  "div",
914
1515
  {
915
1516
  "data-slot": "card",
@@ -922,7 +1523,7 @@ function Card({ className, ...props }) {
922
1523
  );
923
1524
  }
924
1525
  function CardHeader({ className, ...props }) {
925
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1526
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
926
1527
  "div",
927
1528
  {
928
1529
  "data-slot": "card-header",
@@ -935,7 +1536,7 @@ function CardHeader({ className, ...props }) {
935
1536
  );
936
1537
  }
937
1538
  function CardTitle({ className, ...props }) {
938
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1539
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
939
1540
  "div",
940
1541
  {
941
1542
  "data-slot": "card-title",
@@ -945,7 +1546,7 @@ function CardTitle({ className, ...props }) {
945
1546
  );
946
1547
  }
947
1548
  function CardContent({ className, ...props }) {
948
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1549
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
949
1550
  "div",
950
1551
  {
951
1552
  "data-slot": "card-content",
@@ -955,27 +1556,27 @@ function CardContent({ className, ...props }) {
955
1556
  );
956
1557
  }
957
1558
 
958
- // src/layout/basic/ShadcnLoginLayout.tsx
959
- var import_jsx_runtime7 = require("react/jsx-runtime");
960
- function ShadcnLoginLayout({
1559
+ // src/layout/basic/LoginLayout.tsx
1560
+ var import_jsx_runtime12 = require("react/jsx-runtime");
1561
+ function LoginLayout({
961
1562
  variant = "center",
962
1563
  brand,
963
1564
  children
964
1565
  }) {
965
- const brandBlock = /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { "data-slot": "login-brand", className: "flex flex-col items-center gap-2", children: [
1566
+ const brandBlock = /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { "data-slot": "login-brand", className: "flex flex-col items-center gap-2", children: [
966
1567
  brand.logo,
967
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("h1", { className: "text-xl font-semibold", children: brand.title }),
968
- brand.description !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "text-sm text-muted-foreground", children: brand.description })
1568
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("h1", { className: "text-xl font-semibold", children: brand.title }),
1569
+ brand.description !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "text-sm text-muted-foreground", children: brand.description })
969
1570
  ] });
970
1571
  if (variant === "split") {
971
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
1572
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
972
1573
  "div",
973
1574
  {
974
1575
  "data-slot": "login-layout",
975
1576
  "data-variant": "split",
976
1577
  className: "grid min-h-screen grid-cols-2 bg-background text-foreground",
977
1578
  children: [
978
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1579
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
979
1580
  "div",
980
1581
  {
981
1582
  "data-slot": "login-brand-panel",
@@ -983,7 +1584,7 @@ function ShadcnLoginLayout({
983
1584
  children: brandBlock
984
1585
  }
985
1586
  ),
986
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1587
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
987
1588
  "div",
988
1589
  {
989
1590
  "data-slot": "login-form-panel",
@@ -995,37 +1596,37 @@ function ShadcnLoginLayout({
995
1596
  }
996
1597
  );
997
1598
  }
998
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1599
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
999
1600
  "div",
1000
1601
  {
1001
1602
  "data-slot": "login-layout",
1002
1603
  "data-variant": "center",
1003
1604
  className: "flex min-h-screen items-center justify-center bg-muted/40 text-foreground",
1004
- children: /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(Card, { "data-slot": "login-card", className: "w-full max-w-sm gap-4 py-6", children: [
1005
- /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(CardHeader, { className: "px-6", children: [
1006
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(CardTitle, { className: "sr-only", children: brand.title }),
1605
+ children: /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(Card, { "data-slot": "login-card", className: "w-full max-w-sm gap-4 py-6", children: [
1606
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(CardHeader, { className: "px-6", children: [
1607
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(CardTitle, { className: "sr-only", children: brand.title }),
1007
1608
  brandBlock
1008
1609
  ] }),
1009
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(CardContent, { className: "flex flex-col gap-4 px-6", children })
1610
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(CardContent, { className: "flex flex-col gap-4 px-6", children })
1010
1611
  ] })
1011
1612
  }
1012
1613
  );
1013
1614
  }
1014
1615
 
1015
- // src/layout/basic/ShadcnLandingLayout.tsx
1016
- var import_jsx_runtime8 = require("react/jsx-runtime");
1017
- function ShadcnLandingLayout({
1616
+ // src/layout/basic/LandingLayout.tsx
1617
+ var import_jsx_runtime13 = require("react/jsx-runtime");
1618
+ function LandingLayout({
1018
1619
  navbar,
1019
1620
  footer,
1020
1621
  children
1021
1622
  }) {
1022
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
1623
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
1023
1624
  "div",
1024
1625
  {
1025
1626
  "data-slot": "landing-layout",
1026
1627
  className: "flex min-h-screen flex-col bg-background text-foreground",
1027
1628
  children: [
1028
- navbar !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1629
+ navbar !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1029
1630
  "header",
1030
1631
  {
1031
1632
  "data-slot": "landing-navbar",
@@ -1033,8 +1634,8 @@ function ShadcnLandingLayout({
1033
1634
  children: navbar
1034
1635
  }
1035
1636
  ),
1036
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("main", { "data-slot": "landing-content", className: "flex-1", children }),
1037
- footer !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1637
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("main", { "data-slot": "landing-content", className: "flex-1", children }),
1638
+ footer !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1038
1639
  "footer",
1039
1640
  {
1040
1641
  "data-slot": "landing-footer",
@@ -1049,15 +1650,16 @@ function ShadcnLandingLayout({
1049
1650
 
1050
1651
  // src/layout/registry.ts
1051
1652
  var import_core = require("@pactor-app/core");
1052
- function registerShadcnLayouts(registry) {
1053
- registry.register("admin", ShadcnAdminLayout);
1054
- registry.register("blank", ShadcnBlankLayout);
1055
- registry.register("login", ShadcnLoginLayout);
1056
- registry.register("landing", ShadcnLandingLayout);
1653
+ function registerBuiltinLayouts(registry) {
1654
+ registry.register("admin", AdminLayout);
1655
+ registry.register("blank", BlankLayout);
1656
+ registry.register("login", LoginLayout);
1657
+ registry.register("landing", LandingLayout);
1658
+ registry.register("chat", ChatLayout);
1057
1659
  }
1058
- function createShadcnLayoutRegistry() {
1660
+ function createLayoutRegistry() {
1059
1661
  const registry = new import_core.Registry("LayoutRegistry");
1060
- registerShadcnLayouts(registry);
1662
+ registerBuiltinLayouts(registry);
1061
1663
  return registry;
1062
1664
  }
1063
1665
  function resolveLayout(registry, type) {
@@ -1066,19 +1668,20 @@ function resolveLayout(registry, type) {
1066
1668
  console.warn(
1067
1669
  `[pactor-layout] unknown layout type "${type}", fallback to BlankLayout`
1068
1670
  );
1069
- return ShadcnBlankLayout;
1671
+ return BlankLayout;
1070
1672
  }
1071
1673
  return component;
1072
1674
  }
1073
1675
  // Annotate the CommonJS export names for ESM import in node:
1074
1676
  0 && (module.exports = {
1075
- ShadcnAdminLayout,
1076
- ShadcnBlankLayout,
1077
- ShadcnLandingLayout,
1078
- ShadcnLoginLayout,
1677
+ AdminLayout,
1678
+ BlankLayout,
1679
+ ChatLayout,
1680
+ LandingLayout,
1681
+ LoginLayout,
1079
1682
  collectDefaultOpenKeys,
1080
- createShadcnLayoutRegistry,
1683
+ createLayoutRegistry,
1081
1684
  findMenuChain,
1082
- registerShadcnLayouts,
1685
+ registerBuiltinLayouts,
1083
1686
  resolveLayout
1084
1687
  });