@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.
@@ -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");
@@ -397,7 +398,7 @@ function useBreakpointBelow(breakpoint) {
397
398
  return breakpoint !== void 0 && matches;
398
399
  }
399
400
 
400
- // src/layout/admin/ShadcnAdminLayout.tsx
401
+ // src/layout/admin/AdminLayout.tsx
401
402
  var import_jsx_runtime4 = require("react/jsx-runtime");
402
403
  function buildVisibleMenuTree(registry, permissionChecker) {
403
404
  const tree = registry.tree();
@@ -598,7 +599,7 @@ function MenuTree({
598
599
  }
599
600
  );
600
601
  }
601
- function ShadcnAdminLayout({
602
+ function AdminLayout({
602
603
  config = {},
603
604
  brand,
604
605
  registry,
@@ -905,9 +906,9 @@ function ShadcnAdminLayout({
905
906
  );
906
907
  }
907
908
 
908
- // src/layout/basic/ShadcnBlankLayout.tsx
909
+ // src/layout/basic/BlankLayout.tsx
909
910
  var import_jsx_runtime5 = require("react/jsx-runtime");
910
- function ShadcnBlankLayout({ children }) {
911
+ function BlankLayout({ children }) {
911
912
  return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
912
913
  "div",
913
914
  {
@@ -918,10 +919,598 @@ function ShadcnBlankLayout({ children }) {
918
919
  );
919
920
  }
920
921
 
921
- // 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");
922
928
  var import_jsx_runtime6 = require("react/jsx-runtime");
923
- function Card({ className, ...props }) {
929
+ function TooltipProvider({
930
+ delay = 0,
931
+ ...props
932
+ }) {
924
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)(
925
1514
  "div",
926
1515
  {
927
1516
  "data-slot": "card",
@@ -934,7 +1523,7 @@ function Card({ className, ...props }) {
934
1523
  );
935
1524
  }
936
1525
  function CardHeader({ className, ...props }) {
937
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1526
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
938
1527
  "div",
939
1528
  {
940
1529
  "data-slot": "card-header",
@@ -947,7 +1536,7 @@ function CardHeader({ className, ...props }) {
947
1536
  );
948
1537
  }
949
1538
  function CardTitle({ className, ...props }) {
950
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1539
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
951
1540
  "div",
952
1541
  {
953
1542
  "data-slot": "card-title",
@@ -957,7 +1546,7 @@ function CardTitle({ className, ...props }) {
957
1546
  );
958
1547
  }
959
1548
  function CardContent({ className, ...props }) {
960
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1549
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
961
1550
  "div",
962
1551
  {
963
1552
  "data-slot": "card-content",
@@ -967,27 +1556,27 @@ function CardContent({ className, ...props }) {
967
1556
  );
968
1557
  }
969
1558
 
970
- // src/layout/basic/ShadcnLoginLayout.tsx
971
- var import_jsx_runtime7 = require("react/jsx-runtime");
972
- function ShadcnLoginLayout({
1559
+ // src/layout/basic/LoginLayout.tsx
1560
+ var import_jsx_runtime12 = require("react/jsx-runtime");
1561
+ function LoginLayout({
973
1562
  variant = "center",
974
1563
  brand,
975
1564
  children
976
1565
  }) {
977
- 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: [
978
1567
  brand.logo,
979
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("h1", { className: "text-xl font-semibold", children: brand.title }),
980
- 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 })
981
1570
  ] });
982
1571
  if (variant === "split") {
983
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
1572
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
984
1573
  "div",
985
1574
  {
986
1575
  "data-slot": "login-layout",
987
1576
  "data-variant": "split",
988
1577
  className: "grid min-h-screen grid-cols-2 bg-background text-foreground",
989
1578
  children: [
990
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1579
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
991
1580
  "div",
992
1581
  {
993
1582
  "data-slot": "login-brand-panel",
@@ -995,7 +1584,7 @@ function ShadcnLoginLayout({
995
1584
  children: brandBlock
996
1585
  }
997
1586
  ),
998
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1587
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
999
1588
  "div",
1000
1589
  {
1001
1590
  "data-slot": "login-form-panel",
@@ -1007,37 +1596,37 @@ function ShadcnLoginLayout({
1007
1596
  }
1008
1597
  );
1009
1598
  }
1010
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1599
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1011
1600
  "div",
1012
1601
  {
1013
1602
  "data-slot": "login-layout",
1014
1603
  "data-variant": "center",
1015
1604
  className: "flex min-h-screen items-center justify-center bg-muted/40 text-foreground",
1016
- children: /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(Card, { "data-slot": "login-card", className: "w-full max-w-sm gap-4 py-6", children: [
1017
- /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(CardHeader, { className: "px-6", children: [
1018
- /* @__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 }),
1019
1608
  brandBlock
1020
1609
  ] }),
1021
- /* @__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 })
1022
1611
  ] })
1023
1612
  }
1024
1613
  );
1025
1614
  }
1026
1615
 
1027
- // src/layout/basic/ShadcnLandingLayout.tsx
1028
- var import_jsx_runtime8 = require("react/jsx-runtime");
1029
- function ShadcnLandingLayout({
1616
+ // src/layout/basic/LandingLayout.tsx
1617
+ var import_jsx_runtime13 = require("react/jsx-runtime");
1618
+ function LandingLayout({
1030
1619
  navbar,
1031
1620
  footer,
1032
1621
  children
1033
1622
  }) {
1034
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
1623
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
1035
1624
  "div",
1036
1625
  {
1037
1626
  "data-slot": "landing-layout",
1038
1627
  className: "flex min-h-screen flex-col bg-background text-foreground",
1039
1628
  children: [
1040
- navbar !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1629
+ navbar !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1041
1630
  "header",
1042
1631
  {
1043
1632
  "data-slot": "landing-navbar",
@@ -1045,8 +1634,8 @@ function ShadcnLandingLayout({
1045
1634
  children: navbar
1046
1635
  }
1047
1636
  ),
1048
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("main", { "data-slot": "landing-content", className: "flex-1", children }),
1049
- 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)(
1050
1639
  "footer",
1051
1640
  {
1052
1641
  "data-slot": "landing-footer",
@@ -1061,15 +1650,16 @@ function ShadcnLandingLayout({
1061
1650
 
1062
1651
  // src/layout/registry.ts
1063
1652
  var import_core = require("@pactor-app/core");
1064
- function registerShadcnLayouts(registry) {
1065
- registry.register("admin", ShadcnAdminLayout);
1066
- registry.register("blank", ShadcnBlankLayout);
1067
- registry.register("login", ShadcnLoginLayout);
1068
- 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);
1069
1659
  }
1070
- function createShadcnLayoutRegistry() {
1660
+ function createLayoutRegistry() {
1071
1661
  const registry = new import_core.Registry("LayoutRegistry");
1072
- registerShadcnLayouts(registry);
1662
+ registerBuiltinLayouts(registry);
1073
1663
  return registry;
1074
1664
  }
1075
1665
  function resolveLayout(registry, type) {
@@ -1078,19 +1668,20 @@ function resolveLayout(registry, type) {
1078
1668
  console.warn(
1079
1669
  `[pactor-layout] unknown layout type "${type}", fallback to BlankLayout`
1080
1670
  );
1081
- return ShadcnBlankLayout;
1671
+ return BlankLayout;
1082
1672
  }
1083
1673
  return component;
1084
1674
  }
1085
1675
  // Annotate the CommonJS export names for ESM import in node:
1086
1676
  0 && (module.exports = {
1087
- ShadcnAdminLayout,
1088
- ShadcnBlankLayout,
1089
- ShadcnLandingLayout,
1090
- ShadcnLoginLayout,
1677
+ AdminLayout,
1678
+ BlankLayout,
1679
+ ChatLayout,
1680
+ LandingLayout,
1681
+ LoginLayout,
1091
1682
  collectDefaultOpenKeys,
1092
- createShadcnLayoutRegistry,
1683
+ createLayoutRegistry,
1093
1684
  findMenuChain,
1094
- registerShadcnLayouts,
1685
+ registerBuiltinLayouts,
1095
1686
  resolveLayout
1096
1687
  });