@pactor-app/ui 1.2.0 → 1.3.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
@@ -61,6 +61,8 @@ import '@pactor-app/ui/styles.css';
61
61
 
62
62
  主题契约为 shadcn 语义令牌:`--background` / `--foreground` / `--card` / `--popover` / `--primary` / `--secondary` / `--muted` / `--accent` / `--destructive` / `--border` / `--input` / `--ring` / `--radius`(另补充 `--success` / `--warning` / `--info` 供 Alert 四态),定义在 `:root`,组件工具类经 `var()` 运行时读取。换肤即覆盖变量;内置暗色皮肤经 `<html data-pactor-theme="dark">`(或任意容器)切换,与全仓约定一致。
63
63
 
64
+ **皮肤令牌契约(CSS 皮肤文件)**:组件全部颜色/阴影/圆角经令牌解析(无硬编码颜色),宿主在 `styles.css` 之后加载一个普通 CSS 文件即可完整换肤,运行时替换样式表节点即切换皮肤,无需重建。语义令牌之上还有组件级令牌(命名 `--<component>-<part>-<state>`,默认值经 `var()` 回退到语义令牌)覆盖 Switch/Slider/Radio/Progress/Pagination/Menu 系 hover 与选中/Bubble/Calendar/卡片阴影/弹层阴影色/模态遮罩;页面背景经 `--background`(纯色)+ `--background-image`(渐变/图片,默认 `none`)承接,字体族经 `--font-sans`。完整令牌清单与示例见 docs《主题与换肤》指南(docs/src/content/docs/guides/theming.md)。
65
+
64
66
  **preflight 开启(shadcn 官方基线)**:样式表为完整 `tailwindcss`(含 preflight)+ `tw-animate-css` 动画层 + 官方 base layer 的预编译产物,与 [shadcn 官方主题脚手架](https://ui.shadcn.com/docs/theming)一致——导入 `styles.css` 即接受 shadcn 基线全局样式(box-sizing / 边距归零 / 元素字体继承等),宿主的自定义皮肤 CSS 须在其后导入。令牌组含 chart-1..5 与 sidebar 系列;radius 派生为基于 `--radius` 的乘法比例(0.6/0.8/1/1.4/1.8/2.2/2.6)。
65
67
 
66
68
  ## 独立使用(不经 @pactor-app/app)
@@ -1,5 +1,4 @@
1
1
  import {
2
- Button,
3
2
  Dialog,
4
3
  DialogClose,
5
4
  DialogContent,
@@ -12,10 +11,11 @@ import {
12
11
  DrawerContent,
13
12
  DrawerHeader,
14
13
  DrawerTitle
15
- } from "./chunk-F3H23KYG.js";
14
+ } from "./chunk-F42SGQNW.js";
16
15
  import {
16
+ Button,
17
17
  cn
18
- } from "./chunk-RQHJBTEU.js";
18
+ } from "./chunk-WKJUCGV4.js";
19
19
 
20
20
  // src/interaction/manager.ts
21
21
  var OVERLAY_CANCEL_PAYLOAD = { success: false };
@@ -1,11 +1,22 @@
1
1
  import {
2
+ SidebarContent,
3
+ SidebarFooter,
4
+ SidebarHeader,
5
+ SidebarInset,
6
+ SidebarMenu,
7
+ SidebarMenuButton,
8
+ SidebarMenuItem,
9
+ SidebarProvider,
10
+ SidebarRoot,
2
11
  Tooltip,
3
12
  TooltipContent,
4
- TooltipTrigger
5
- } from "./chunk-2LYMCWEJ.js";
13
+ TooltipTrigger,
14
+ useIsMobile,
15
+ useSidebar
16
+ } from "./chunk-L4Z7MNYY.js";
6
17
  import {
7
18
  cn
8
- } from "./chunk-RQHJBTEU.js";
19
+ } from "./chunk-WKJUCGV4.js";
9
20
 
10
21
  // src/components/components/Icon/index.tsx
11
22
  import {
@@ -269,37 +280,21 @@ function RailTooltip({
269
280
 
270
281
  // src/components/components/Sidebar/index.tsx
271
282
  import { useI18n, useRenderer } from "@pactor-app/runtime";
272
- import { ChevronLeft as ChevronLeft2, ChevronRight as ChevronRight2 } from "lucide-react";
273
283
  import {
274
- createContext,
275
- useCallback,
276
- useContext,
277
284
  useEffect,
278
- useState
285
+ useRef
279
286
  } from "react";
280
287
  import { Fragment, jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
281
- var SidebarDrawerContext = createContext(null);
282
288
  function useSidebarDrawer() {
283
- return useContext(SidebarDrawerContext);
284
- }
285
- var MOBILE_MEDIA_QUERY = "(max-width: 767px)";
286
- function useIsMobileViewport() {
287
- const [isMobile, setIsMobile] = useState(
288
- () => typeof window !== "undefined" && typeof window.matchMedia === "function" && window.matchMedia(MOBILE_MEDIA_QUERY).matches
289
- );
290
- useEffect(() => {
291
- if (typeof window.matchMedia !== "function") {
292
- return;
293
- }
294
- const media = window.matchMedia(MOBILE_MEDIA_QUERY);
295
- const onChange = (event) => {
296
- setIsMobile(event.matches);
289
+ try {
290
+ const ctx = useSidebar();
291
+ return {
292
+ close: () => ctx.setOpenMobile(false),
293
+ collapsed: ctx.state === "collapsed"
297
294
  };
298
- setIsMobile(media.matches);
299
- media.addEventListener("change", onChange);
300
- return () => media.removeEventListener("change", onChange);
301
- }, []);
302
- return isMobile;
295
+ } catch {
296
+ return null;
297
+ }
303
298
  }
304
299
  function useOptionalRenderer() {
305
300
  try {
@@ -308,7 +303,7 @@ function useOptionalRenderer() {
308
303
  return null;
309
304
  }
310
305
  }
311
- function SidebarContent({ content }) {
306
+ function SidebarContentSlot({ content }) {
312
307
  const renderer = useOptionalRenderer();
313
308
  if (renderer !== null && typeof content === "object" && content !== null && !("$$typeof" in content) && "type" in content) {
314
309
  return /* @__PURE__ */ jsx3(Fragment, { children: renderer.renderChildren([content]) });
@@ -318,6 +313,51 @@ function SidebarContent({ content }) {
318
313
  function isBrandObject(brand) {
319
314
  return typeof brand === "object" && brand !== null && !("$$typeof" in brand) && "title" in brand;
320
315
  }
316
+ function BrandNode({ brand, collapsed }) {
317
+ if (brand === void 0 || brand === null) {
318
+ return null;
319
+ }
320
+ if (isBrandObject(brand)) {
321
+ return /* @__PURE__ */ jsxs2(Fragment, { children: [
322
+ brand.logo ? /* @__PURE__ */ jsx3(
323
+ "img",
324
+ {
325
+ "data-slot": "sidebar-brand-logo",
326
+ src: brand.logo,
327
+ alt: brand.title,
328
+ className: "size-6 shrink-0"
329
+ }
330
+ ) : null,
331
+ collapsed ? null : /* @__PURE__ */ jsx3("span", { className: "truncate text-sm font-semibold", children: brand.title })
332
+ ] });
333
+ }
334
+ return /* @__PURE__ */ jsx3(Fragment, { children: brand });
335
+ }
336
+ function SidebarNavItems({ items }) {
337
+ return /* @__PURE__ */ jsx3(SidebarMenu, { children: items.map((item, index) => /* @__PURE__ */ jsx3(SidebarNavEntry, { item, depth: 0 }, item.href ?? item.label ?? index)) });
338
+ }
339
+ function SidebarNavEntry({ item, depth }) {
340
+ const iconNode = item.icon ? /* @__PURE__ */ jsx3(Icon, { name: item.icon, size: "sm" }) : null;
341
+ const labelNode = item.label;
342
+ const button = item.href ? /* @__PURE__ */ jsx3(SidebarMenuButton, { asChild: true, tooltip: item.label, children: /* @__PURE__ */ jsxs2("a", { href: item.href, children: [
343
+ iconNode,
344
+ /* @__PURE__ */ jsx3("span", { children: labelNode })
345
+ ] }) }) : /* @__PURE__ */ jsxs2(SidebarMenuButton, { tooltip: item.label, children: [
346
+ iconNode,
347
+ /* @__PURE__ */ jsx3("span", { children: labelNode })
348
+ ] });
349
+ return /* @__PURE__ */ jsxs2(SidebarMenuItem, { children: [
350
+ button,
351
+ item.children && item.children.length > 0 && /* @__PURE__ */ jsx3("div", { className: cn("ml-4"), children: item.children.map((child, index) => /* @__PURE__ */ jsx3(
352
+ SidebarNavEntry,
353
+ {
354
+ item: child,
355
+ depth: depth + 1
356
+ },
357
+ child.href ?? child.label ?? index
358
+ )) })
359
+ ] });
360
+ }
321
361
  function Sidebar({
322
362
  brand,
323
363
  items = [],
@@ -330,217 +370,120 @@ function Sidebar({
330
370
  className,
331
371
  style,
332
372
  children
373
+ }) {
374
+ return /* @__PURE__ */ jsx3(
375
+ SidebarProvider,
376
+ {
377
+ defaultOpen: true,
378
+ className,
379
+ style,
380
+ children: /* @__PURE__ */ jsx3(
381
+ SidebarInner,
382
+ {
383
+ brand,
384
+ items,
385
+ content,
386
+ sidebarFooter,
387
+ collapsible,
388
+ onCollapse,
389
+ locationKey,
390
+ drawer,
391
+ children
392
+ }
393
+ )
394
+ }
395
+ );
396
+ }
397
+ function SidebarInner({
398
+ brand,
399
+ items = [],
400
+ content,
401
+ sidebarFooter,
402
+ collapsible,
403
+ onCollapse,
404
+ locationKey,
405
+ drawer = true,
406
+ children
333
407
  }) {
334
408
  const { t } = useI18n();
335
- const isMobile = useIsMobileViewport();
336
- const [drawerOpen, setDrawerOpen] = useState(false);
337
- const [collapsed, setCollapsed] = useState(false);
338
- const collapsedEffective = collapsible === true && collapsed && !isMobile;
339
- const close = useCallback(() => setDrawerOpen(false), []);
340
- const toggleCollapsed = () => {
341
- const next = !collapsed;
342
- setCollapsed(next);
343
- onCollapse?.(next);
344
- };
345
- const [trackedLocationKey, setTrackedLocationKey] = useState(locationKey);
346
- if (trackedLocationKey !== locationKey) {
347
- setTrackedLocationKey(locationKey);
348
- setDrawerOpen(false);
349
- }
409
+ const isMobile = useIsMobile();
410
+ const { state, setOpenMobile, toggleSidebar } = useSidebar();
411
+ const collapsed = state === "collapsed";
412
+ const prevCollapsed = useRef(collapsed);
350
413
  useEffect(() => {
351
- if (!drawerOpen) {
352
- return;
414
+ if (prevCollapsed.current !== collapsed) {
415
+ prevCollapsed.current = collapsed;
416
+ onCollapse?.(collapsed);
353
417
  }
354
- const onKeyDown = (event) => {
355
- if (event.key === "Escape") {
356
- setDrawerOpen(false);
357
- }
358
- };
359
- window.addEventListener("keydown", onKeyDown);
360
- return () => window.removeEventListener("keydown", onKeyDown);
361
- }, [drawerOpen]);
362
- const brandNode = isBrandObject(brand) ? /* @__PURE__ */ jsxs2(Fragment, { children: [
363
- brand.logo ? /* @__PURE__ */ jsx3(
364
- "img",
418
+ }, [collapsed, onCollapse]);
419
+ useEffect(() => {
420
+ if (locationKey !== void 0) {
421
+ setOpenMobile(false);
422
+ }
423
+ }, [locationKey, setOpenMobile]);
424
+ const collapsedEffective = collapsible === true && collapsed && !isMobile;
425
+ const brandNode = /* @__PURE__ */ jsx3(BrandNode, { brand, collapsed: collapsedEffective });
426
+ const sidebarContent = content !== void 0 && content !== null ? /* @__PURE__ */ jsx3(SidebarContentSlot, { content }) : items.length > 0 ? /* @__PURE__ */ jsx3(SidebarNavItems, { items }) : null;
427
+ return /* @__PURE__ */ jsxs2(Fragment, { children: [
428
+ /* @__PURE__ */ jsxs2(
429
+ SidebarRoot,
365
430
  {
366
- "data-slot": "sidebar-brand-logo",
367
- src: brand.logo,
368
- alt: brand.title,
369
- className: "size-6 shrink-0"
431
+ collapsible: collapsible ? "icon" : "offcanvas",
432
+ children: [
433
+ (brand !== void 0 || collapsible) && /* @__PURE__ */ jsx3(SidebarHeader, { children: /* @__PURE__ */ jsxs2(
434
+ "div",
435
+ {
436
+ "data-slot": "sidebar-brand",
437
+ className: cn(
438
+ "flex h-14 shrink-0 items-center gap-2 px-2",
439
+ collapsedEffective && "justify-center px-0"
440
+ ),
441
+ children: [
442
+ collapsedEffective ? null : /* @__PURE__ */ jsx3("div", { className: "flex min-w-0 flex-1 items-center gap-2", children: brandNode }),
443
+ collapsible && !isMobile && /* @__PURE__ */ jsx3(
444
+ "button",
445
+ {
446
+ type: "button",
447
+ "data-slot": "sidebar-collapse-trigger",
448
+ "aria-label": collapsedEffective ? t("pactor.layout.expandSidebar") : t("pactor.layout.collapseSidebar"),
449
+ "aria-expanded": !collapsedEffective,
450
+ className: "flex size-8 shrink-0 cursor-pointer items-center justify-center rounded-md text-sidebar-foreground/60 transition-colors hover:bg-sidebar-accent hover:text-sidebar-foreground",
451
+ onClick: toggleSidebar,
452
+ children: /* @__PURE__ */ jsx3(Icon, { name: collapsedEffective ? "chevron-right" : "chevron-left", size: 16 })
453
+ }
454
+ )
455
+ ]
456
+ }
457
+ ) }),
458
+ /* @__PURE__ */ jsx3(SidebarContent, { children: sidebarContent }),
459
+ sidebarFooter !== void 0 && sidebarFooter !== null && !collapsedEffective && /* @__PURE__ */ jsx3(SidebarFooter, { children: sidebarFooter })
460
+ ]
370
461
  }
371
- ) : null,
372
- collapsedEffective ? null : /* @__PURE__ */ jsx3("span", { className: "truncate text-sm font-semibold", children: brand.title })
373
- ] }) : brand;
374
- const collapseTrigger = collapsible && !isMobile ? /* @__PURE__ */ jsx3(
375
- "button",
376
- {
377
- type: "button",
378
- "data-slot": "sidebar-collapse-trigger",
379
- "aria-label": collapsedEffective ? t("pactor.layout.expandSidebar") : t("pactor.layout.collapseSidebar"),
380
- "aria-expanded": !collapsedEffective,
381
- title: collapsedEffective ? t("pactor.layout.expandSidebar") : t("pactor.layout.collapseSidebar"),
382
- className: cn(
383
- "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"
384
- ),
385
- onClick: toggleCollapsed,
386
- children: collapsedEffective ? /* @__PURE__ */ jsx3(ChevronRight2, { "aria-hidden": true, className: "size-4" }) : /* @__PURE__ */ jsx3(ChevronLeft2, { "aria-hidden": true, className: "size-4" })
387
- }
388
- ) : null;
389
- return /* @__PURE__ */ jsx3(
390
- SidebarDrawerContext.Provider,
391
- {
392
- value: { close, collapsed: collapsedEffective },
393
- children: /* @__PURE__ */ jsxs2(
462
+ ),
463
+ /* @__PURE__ */ jsxs2(SidebarInset, { children: [
464
+ drawer && /* @__PURE__ */ jsxs2(
394
465
  "div",
395
466
  {
396
- "data-slot": "sidebar-layout",
397
- className: cn("flex min-h-screen w-full", className),
398
- style,
467
+ "data-slot": "sidebar-topbar",
468
+ className: "flex h-14 items-center gap-2 border-b border-sidebar-border bg-sidebar px-3 md:hidden",
399
469
  children: [
400
- drawer && drawerOpen ? /* @__PURE__ */ jsx3(
401
- "div",
470
+ /* @__PURE__ */ jsx3(
471
+ "button",
402
472
  {
403
- "data-slot": "sidebar-scrim",
404
- "aria-hidden": true,
405
- className: "fixed inset-0 z-40 bg-black/40 md:hidden",
406
- onClick: close
407
- }
408
- ) : null,
409
- /* @__PURE__ */ jsxs2(
410
- "aside",
411
- {
412
- "data-slot": "sidebar",
413
- "data-collapsed": collapsedEffective || void 0,
414
- "data-open": drawerOpen ? "true" : "false",
415
- inert: drawer && isMobile && !drawerOpen ? true : void 0,
416
- className: cn(
417
- "flex shrink-0 flex-col border-r border-sidebar-border bg-sidebar text-sidebar-foreground transition-[width,transform]",
418
- collapsedEffective ? "w-16" : "w-60",
419
- // 移动端覆盖抽屉:退出文档流,关闭时移出屏幕
420
- 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"
421
- ),
422
- children: [
423
- brand !== void 0 || collapseTrigger !== null ? /* @__PURE__ */ jsxs2(
424
- "div",
425
- {
426
- "data-slot": "sidebar-brand",
427
- className: cn(
428
- "flex h-14 shrink-0 items-center gap-2 border-b border-sidebar-border px-4",
429
- // 移动端品牌维持顶栏现状:抽屉内不重复渲染品牌行
430
- "max-md:hidden",
431
- collapsedEffective && "justify-center px-2"
432
- ),
433
- children: [
434
- collapsedEffective ? null : /* @__PURE__ */ jsx3("div", { className: "flex min-w-0 flex-1 items-center gap-2", children: brandNode }),
435
- collapseTrigger
436
- ]
437
- }
438
- ) : null,
439
- /* @__PURE__ */ jsx3(
440
- "nav",
441
- {
442
- "data-slot": "sidebar-nav",
443
- className: "min-h-0 flex-1 overflow-auto p-2",
444
- children: content !== void 0 && content !== null ? /* @__PURE__ */ jsx3(SidebarContent, { content }) : items.map((item, index) => /* @__PURE__ */ jsx3(
445
- SidebarEntry,
446
- {
447
- item,
448
- depth: 0,
449
- collapsed: collapsedEffective
450
- },
451
- item.href ?? item.label ?? index
452
- ))
453
- }
454
- ),
455
- sidebarFooter !== void 0 && sidebarFooter !== null && !collapsedEffective ? /* @__PURE__ */ jsx3(
456
- "div",
457
- {
458
- "data-slot": "sidebar-footer",
459
- className: "shrink-0 border-t border-sidebar-border p-2",
460
- children: sidebarFooter
461
- }
462
- ) : null
463
- ]
473
+ type: "button",
474
+ "data-slot": "sidebar-menu-trigger",
475
+ "aria-label": t("pactor.layout.openNavigation"),
476
+ className: "flex size-8 items-center justify-center rounded-md text-sidebar-foreground/60 transition-colors hover:bg-sidebar-accent hover:text-sidebar-foreground",
477
+ onClick: toggleSidebar,
478
+ children: /* @__PURE__ */ jsx3(Icon, { name: "menu", size: 18 })
464
479
  }
465
480
  ),
466
- /* @__PURE__ */ jsxs2(
467
- "main",
468
- {
469
- "data-slot": "sidebar-content",
470
- className: "flex min-w-0 flex-1 flex-col",
471
- children: [
472
- drawer ? /* @__PURE__ */ jsxs2(
473
- "div",
474
- {
475
- "data-slot": "sidebar-topbar",
476
- className: "flex h-14 items-center gap-2 border-b border-sidebar-border bg-sidebar px-3 md:hidden",
477
- children: [
478
- /* @__PURE__ */ jsx3(
479
- "button",
480
- {
481
- type: "button",
482
- "data-slot": "sidebar-menu-trigger",
483
- "aria-label": t("pactor.layout.openNavigation"),
484
- "aria-expanded": drawerOpen,
485
- className: "flex size-8 items-center justify-center rounded-md text-sidebar-foreground/60 transition-colors hover:bg-sidebar-accent hover:text-sidebar-foreground",
486
- onClick: () => setDrawerOpen(true),
487
- children: /* @__PURE__ */ jsx3(Icon, { name: "menu", size: 18 })
488
- }
489
- ),
490
- brandNode
491
- ]
492
- }
493
- ) : null,
494
- children
495
- ]
496
- }
497
- )
481
+ brandNode
498
482
  ]
499
483
  }
500
- )
501
- }
502
- );
503
- }
504
- function SidebarEntry({
505
- item,
506
- depth,
507
- collapsed
508
- }) {
509
- const entryClass = cn(
510
- "flex w-full items-center gap-2 rounded-md px-3 py-2 text-left text-sm transition-colors",
511
- "text-sidebar-foreground/70 hover:bg-sidebar-accent hover:text-sidebar-accent-foreground",
512
- collapsed && "justify-center px-0"
513
- );
514
- const indentStyle = !collapsed && depth > 0 ? { paddingLeft: 12 + depth * 16 } : void 0;
515
- const labelNode = collapsed ? null : item.label;
516
- const iconNode = item.icon ? /* @__PURE__ */ jsx3(Icon, { name: item.icon, size: "sm" }) : null;
517
- const entry = item.href ? /* @__PURE__ */ jsxs2(
518
- "a",
519
- {
520
- "data-slot": "sidebar-item",
521
- href: item.href,
522
- className: entryClass,
523
- style: indentStyle,
524
- children: [
525
- iconNode,
526
- labelNode
527
- ]
528
- }
529
- ) : /* @__PURE__ */ jsxs2("span", { "data-slot": "sidebar-item", className: entryClass, style: indentStyle, children: [
530
- iconNode,
531
- labelNode
532
- ] });
533
- return /* @__PURE__ */ jsxs2("div", { "data-slot": "sidebar-entry", children: [
534
- collapsed ? /* @__PURE__ */ jsx3(RailTooltip, { label: item.label, children: entry }) : entry,
535
- item.children?.map((child, index) => /* @__PURE__ */ jsx3(
536
- SidebarEntry,
537
- {
538
- item: child,
539
- depth: depth + 1,
540
- collapsed
541
- },
542
- child.href ?? child.label ?? index
543
- ))
484
+ ),
485
+ children
486
+ ] })
544
487
  ] });
545
488
  }
546
489
 
@@ -619,7 +562,7 @@ function MenuEntry({
619
562
  "flex w-full items-center gap-2 rounded-md text-sm transition-colors",
620
563
  mode === "horizontal" ? "px-3 py-2" : "px-3 py-2 text-left",
621
564
  collapsed && "justify-center px-0",
622
- selected ? "bg-accent font-medium text-accent-foreground" : "cursor-pointer text-muted-foreground hover:bg-accent/50 hover:text-foreground",
565
+ selected ? "bg-(--menu-active-background) font-medium text-(--menu-active-foreground)" : "cursor-pointer text-muted-foreground hover:bg-(--menu-hover-background)/50 hover:text-foreground",
623
566
  item.disabled && "cursor-not-allowed opacity-50 hover:bg-transparent hover:text-muted-foreground"
624
567
  ),
625
568
  style: mode === "inline" && depth > 0 && !collapsed ? { paddingLeft: 12 + depth * 16 } : void 0,