@qijenchen/design-system 0.1.0-beta.3

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.
Files changed (119) hide show
  1. package/package.json +93 -0
  2. package/src/README.md +32 -0
  3. package/src/components/Accordion/accordion.tsx +104 -0
  4. package/src/components/Alert/alert.tsx +188 -0
  5. package/src/components/AppShell/_demo-helpers.tsx +198 -0
  6. package/src/components/AppShell/app-shell.tsx +364 -0
  7. package/src/components/AspectRatio/aspect-ratio.tsx +58 -0
  8. package/src/components/Avatar/avatar.tsx +368 -0
  9. package/src/components/Badge/badge.tsx +104 -0
  10. package/src/components/Breadcrumb/breadcrumb.tsx +609 -0
  11. package/src/components/BulkActionBar/bulk-action-bar.tsx +156 -0
  12. package/src/components/Button/button-group.tsx +96 -0
  13. package/src/components/Button/button.tsx +539 -0
  14. package/src/components/Calendar/calendar.tsx +411 -0
  15. package/src/components/Carousel/carousel.tsx +371 -0
  16. package/src/components/Chart/chart.tsx +376 -0
  17. package/src/components/Checkbox/checkbox-group.tsx +94 -0
  18. package/src/components/Checkbox/checkbox.tsx +237 -0
  19. package/src/components/Chip/chip.tsx +359 -0
  20. package/src/components/CircularProgress/circular-progress.tsx +204 -0
  21. package/src/components/Coachmark/coachmark.tsx +255 -0
  22. package/src/components/Combobox/combobox.tsx +826 -0
  23. package/src/components/Command/command.tsx +187 -0
  24. package/src/components/DataTable/active-editor-controller.ts +72 -0
  25. package/src/components/DataTable/cell-registry.tsx +520 -0
  26. package/src/components/DataTable/column-types.ts +180 -0
  27. package/src/components/DataTable/data-table-column-visibility-panel.tsx +261 -0
  28. package/src/components/DataTable/data-table-filter-panel.tsx +813 -0
  29. package/src/components/DataTable/data-table-interaction-layer.tsx +483 -0
  30. package/src/components/DataTable/data-table-sort-manager.tsx +210 -0
  31. package/src/components/DataTable/data-table.css +165 -0
  32. package/src/components/DataTable/data-table.tsx +2924 -0
  33. package/src/components/DataTable/filter-operators.ts +225 -0
  34. package/src/components/DataTable/filter-tree.ts +313 -0
  35. package/src/components/DataTable/lib/column-meta.ts +79 -0
  36. package/src/components/DateGrid/date-grid.tsx +209 -0
  37. package/src/components/DatePicker/date-picker.tsx +1114 -0
  38. package/src/components/DescriptionList/description-list.tsx +141 -0
  39. package/src/components/Dialog/dialog.tsx +267 -0
  40. package/src/components/DropdownMenu/dropdown-menu.tsx +475 -0
  41. package/src/components/Empty/empty.tsx +108 -0
  42. package/src/components/Field/field-context.ts +136 -0
  43. package/src/components/Field/field-types.ts +52 -0
  44. package/src/components/Field/field-wrapper.tsx +348 -0
  45. package/src/components/Field/field.tsx +535 -0
  46. package/src/components/FieldControlGroup/field-control-group.tsx +136 -0
  47. package/src/components/FileItem/file-item.tsx +322 -0
  48. package/src/components/FileUpload/file-upload.tsx +326 -0
  49. package/src/components/FileViewer/file-viewer-types.ts +76 -0
  50. package/src/components/FileViewer/file-viewer.tsx +1065 -0
  51. package/src/components/FileViewer/image-renderer.tsx +256 -0
  52. package/src/components/HoverCard/hover-card.tsx +79 -0
  53. package/src/components/Input/input.tsx +233 -0
  54. package/src/components/LinkInput/link-input.tsx +304 -0
  55. package/src/components/Menu/menu-item.tsx +334 -0
  56. package/src/components/NameCard/name-card.tsx +319 -0
  57. package/src/components/Notice/notice.tsx +196 -0
  58. package/src/components/NumberInput/number-input.tsx +203 -0
  59. package/src/components/OverflowIndicator/overflow-indicator.tsx +156 -0
  60. package/src/components/PeoplePicker/avatar-stack-overflow.ts +100 -0
  61. package/src/components/PeoplePicker/people-picker-helpers.ts +76 -0
  62. package/src/components/PeoplePicker/people-picker.tsx +455 -0
  63. package/src/components/PeoplePicker/person-display.tsx +358 -0
  64. package/src/components/Popover/popover.tsx +183 -0
  65. package/src/components/ProgressBar/progress-bar.tsx +157 -0
  66. package/src/components/README.md +58 -0
  67. package/src/components/RadioGroup/radio-group.tsx +261 -0
  68. package/src/components/Rating/rating.tsx +295 -0
  69. package/src/components/ScrollArea/scroll-area.tsx +110 -0
  70. package/src/components/SegmentedControl/segmented-control.tsx +304 -0
  71. package/src/components/Select/select.tsx +658 -0
  72. package/src/components/SelectMenu/select-menu.tsx +430 -0
  73. package/src/components/SelectionControl/selection-item.tsx +261 -0
  74. package/src/components/Separator/separator.tsx +48 -0
  75. package/src/components/Sheet/sheet.tsx +240 -0
  76. package/src/components/Sidebar/sidebar.tsx +1280 -0
  77. package/src/components/Skeleton/skeleton.tsx +35 -0
  78. package/src/components/Slider/slider.tsx +158 -0
  79. package/src/components/Steps/steps.tsx +850 -0
  80. package/src/components/Switch/switch.tsx +285 -0
  81. package/src/components/Tabs/tabs.tsx +515 -0
  82. package/src/components/Tag/tag.tsx +246 -0
  83. package/src/components/Textarea/textarea.tsx +280 -0
  84. package/src/components/TimePicker/time-columns.tsx +260 -0
  85. package/src/components/TimePicker/time-picker.tsx +419 -0
  86. package/src/components/Toast/toast.tsx +129 -0
  87. package/src/components/Tooltip/tooltip.tsx +68 -0
  88. package/src/components/TreeView/tree-view.tsx +1031 -0
  89. package/src/hooks/use-controllable.ts +40 -0
  90. package/src/hooks/use-is-narrow-viewport.ts +19 -0
  91. package/src/hooks/use-is-touch-device.ts +21 -0
  92. package/src/hooks/use-overflow-items.ts +256 -0
  93. package/src/index.ts +85 -0
  94. package/src/lib/README.md +82 -0
  95. package/src/lib/drag-visual.ts +272 -0
  96. package/src/lib/i18n/README.md +60 -0
  97. package/src/lib/i18n/i18n-context.tsx +129 -0
  98. package/src/lib/multi-select-ordering.ts +61 -0
  99. package/src/lib/utils.ts +93 -0
  100. package/src/patterns/README.md +67 -0
  101. package/src/patterns/element-anatomy/item-anatomy.tsx +744 -0
  102. package/src/patterns/header-canonical/chrome-header.tsx +175 -0
  103. package/src/patterns/header-canonical/header-canonical.css +27 -0
  104. package/src/patterns/horizontal-overflow/horizontal-overflow.tsx +217 -0
  105. package/src/patterns/overlay-surface/overlay-surface.tsx +191 -0
  106. package/src/patterns/resize-handle/resize-handle.tsx +188 -0
  107. package/src/stories-helpers/anatomy/anatomy-utils.tsx +64 -0
  108. package/src/tokens/README.md +53 -0
  109. package/src/tokens/color/primitives.css +429 -0
  110. package/src/tokens/color/semantic.css +539 -0
  111. package/src/tokens/elevation/overlay-geometry.ts +13 -0
  112. package/src/tokens/layoutSpace/layoutSpace.css +36 -0
  113. package/src/tokens/motion/motion.css +30 -0
  114. package/src/tokens/motion/motion.ts +17 -0
  115. package/src/tokens/opacity/opacity.css +23 -0
  116. package/src/tokens/radius/radius.css +19 -0
  117. package/src/tokens/typography/typography.css +118 -0
  118. package/src/tokens/uiSize/icon-size.ts +52 -0
  119. package/src/tokens/uiSize/uiSize.css +125 -0
@@ -0,0 +1,129 @@
1
+ import * as React from 'react'
2
+ import { Toaster as SonnerToaster, toast as sonnerToast } from 'sonner'
3
+ import { Notice, useInverseTheme, type NoticeVariant } from '@/design-system/components/Notice/notice'
4
+ import { Button } from '@/design-system/components/Button/button'
5
+
6
+ /**
7
+ * Toast — 非阻斷式浮動通知
8
+ *
9
+ * ── Container 三層結構(所有 variant 統一) ──
10
+ *
11
+ * 1. Shadow wrapper: rounded-lg + elevation-200(永遠在頁面 theme 解析)
12
+ * 2. Bg layer: bg-{color}(有色相 variant 在頁面 theme 解析)
13
+ * 3. Theme layer: data-theme + text-foreground(content token re-resolve)
14
+ *
15
+ * neutral/success(inverse): bg + theme 同層(bg-surface-raised 需要跟 data-theme 一起翻轉)
16
+ * info/error(dark): bg 在 outer,theme 在 inner
17
+ * warning(light always): bg 在 outer,theme="light" 在 inner
18
+ */
19
+
20
+ // code-quality-allow: dead-export — public API surface — consumer-exposed for future use
21
+ export type ToastVariant = NoticeVariant
22
+
23
+ export interface ToastOptions {
24
+ variant?: ToastVariant
25
+ title: string
26
+ description?: string
27
+ action?: { label: string; onClick: () => void }
28
+ duration?: number
29
+ }
30
+
31
+ function ToastInner({
32
+ id,
33
+ variant = 'neutral',
34
+ action,
35
+ ...rest
36
+ }: ToastOptions & { id: string | number }) {
37
+ const inverseTheme = useInverseTheme()
38
+ const isInverse = variant === 'neutral' || variant === 'success'
39
+ const dismiss = () => sonnerToast.dismiss(id)
40
+
41
+ const actionButton = action ? (
42
+ <Button variant="tertiary" size="xs" onClick={action.onClick}>{action.label}</Button>
43
+ ) : undefined
44
+
45
+ // ── Live region 由 outer Toast wrapper 擁有(WAI-ARIA + spec L59-60 canonical) ──
46
+ // Notice(inner layout)不再帶 role,避免 nested live region。
47
+ // - error / warning → role="alert" + aria-live="assertive"(中斷朗讀)
48
+ // - info / success / neutral → role="status" + aria-live="polite"(空閒朗讀)
49
+ const isCritical = variant === 'error' || variant === 'warning'
50
+ const liveRole = isCritical ? 'alert' : 'status'
51
+ const liveLevel = isCritical ? 'assertive' : 'polite'
52
+
53
+ // ── 1. Shadow wrapper(統一,永遠在頁面 theme) ──
54
+ // ── 2+3. Bg + theme layer ──
55
+
56
+ if (isInverse) {
57
+ // bg-surface-raised 需要跟 data-theme 同層翻轉
58
+ return (
59
+ <div role={liveRole} aria-live={liveLevel} className="rounded-lg overflow-hidden" style={{ boxShadow: 'var(--elevation-200)' }}>
60
+ <div data-theme={inverseTheme} className="bg-surface-raised text-foreground">
61
+ <Notice variant={variant} iconClassName={variant === 'success' ? 'text-success' : undefined}
62
+ endContent={actionButton} onDismiss={dismiss} {...rest} />
63
+ </div>
64
+ </div>
65
+ )
66
+ }
67
+
68
+ const bg = variant === 'warning' ? 'bg-warning' : variant === 'error' ? 'bg-error' : 'bg-info'
69
+ const theme = variant === 'warning' ? 'light' : 'dark'
70
+
71
+ return (
72
+ <div role={liveRole} aria-live={liveLevel} className="rounded-lg overflow-hidden" style={{ boxShadow: 'var(--elevation-200)' }}>
73
+ <div className={bg}>
74
+ <div data-theme={theme} className="text-foreground">
75
+ <Notice variant={variant} endContent={actionButton} onDismiss={dismiss} {...rest} />
76
+ </div>
77
+ </div>
78
+ </div>
79
+ )
80
+ }
81
+
82
+ export function toast(options: ToastOptions) {
83
+ const { duration = 4000, ...rest } = options
84
+ return sonnerToast.custom((id) => <ToastInner id={id} {...rest} />, { duration })
85
+ }
86
+
87
+ export interface ToasterProps {
88
+ position?: 'top-left' | 'top-right' | 'top-center' | 'bottom-left' | 'bottom-right' | 'bottom-center'
89
+ }
90
+
91
+ // shadcn canonical:forwardRef + displayName 統一。Sonner Toaster portal-renders
92
+ // 到 document body,ref 對 Toaster 本身無實際 DOM 節點可接(portal 逃逸),
93
+ // 但保留 forwardRef 簽名以符合 DS 統一 API(consumer 可 typecheck 傳 ref,
94
+ // 與其他 DS 元件一致)。
95
+ const Toaster = React.forwardRef<HTMLDivElement, ToasterProps>(
96
+ ({ position = 'bottom-right', ...props }, _ref) => {
97
+ return (
98
+ <SonnerToaster
99
+ position={position}
100
+ toastOptions={{ unstyled: true, className: 'w-[360px]' }}
101
+ {...props}
102
+ />
103
+ )
104
+ },
105
+ )
106
+ Toaster.displayName = 'Toaster'
107
+
108
+ // Story auto-compile metadata — Phase 1 mechanical migration(2026-04-24)
109
+ // Phase 2 fill needed: purpose descriptions + when rationale + world-class refs
110
+ export const toastMeta = {
111
+ component: 'Toast',
112
+ family: null, // non-family composite / overlay / layout
113
+ variants: {
114
+ neutral: { when: '非狀態通知;一般 announcement / tip' },
115
+ info: { when: '資訊性提示(新版本 / 同步完成)' },
116
+ success: { when: '成功確認(已寄送 / 付款完成)' },
117
+ warning: { when: '可恢復警告(未儲存 / 連線不穩)' },
118
+ error: { when: '錯誤(匯入失敗 / 權限不足);action prop 可加重試' },
119
+ },
120
+ sizes: {},
121
+ states: ['default', 'hover', 'active', 'focus-visible', 'disabled'],
122
+ tokens: {
123
+ bg: ['bg-error', 'bg-info', 'bg-surface-raised', 'bg-warning'],
124
+ fg: ['text-foreground'],
125
+ ring: [],
126
+ },
127
+ } as const
128
+
129
+ export { Toaster }
@@ -0,0 +1,68 @@
1
+ import * as React from "react"
2
+ import * as TooltipPrimitive from "@radix-ui/react-tooltip"
3
+
4
+ import { cn } from "@/lib/utils"
5
+ import { OVERLAY_SIDE_OFFSET, OVERLAY_COLLISION_PADDING } from "@/design-system/tokens/elevation/overlay-geometry"
6
+ import { HOVER_DELAY_PLAIN_MS } from "@/design-system/tokens/motion/motion"
7
+
8
+ // 2026-05-18 ship per user 拍板 #3A:Tooltip Provider 預設 delayDuration 對齊 motion token SSOT。
9
+ // Radix 預設 700ms 過保守(被 Material 150-200 / MUI 100 / Atlassian 300 集體驗證),DS 統一用
10
+ // `--hover-delay-plain` (500ms,JS mirror `HOVER_DELAY_PLAIN_MS`)。Consumer 仍可 per-instance override。
11
+ // 2026-05-21 D5 Phase B codex 抓 comment 200 vs token 500 drift → 註解 500ms 對齊 motion.css:27 SSOT。
12
+ const TooltipProvider = ({ delayDuration = HOVER_DELAY_PLAIN_MS, ...props }: React.ComponentProps<typeof TooltipPrimitive.Provider>) => (
13
+ <TooltipPrimitive.Provider delayDuration={delayDuration} {...props} />
14
+ )
15
+
16
+ const Tooltip = ({ delayDuration = HOVER_DELAY_PLAIN_MS, ...props }: React.ComponentProps<typeof TooltipPrimitive.Root>) => (
17
+ <TooltipPrimitive.Root delayDuration={delayDuration} {...props} />
18
+ )
19
+
20
+ const TooltipTrigger = TooltipPrimitive.Trigger
21
+
22
+ const TooltipContent = React.forwardRef<
23
+ React.ElementRef<typeof TooltipPrimitive.Content>,
24
+ React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>
25
+ >(({ className, sideOffset = OVERLAY_SIDE_OFFSET, collisionPadding = OVERLAY_COLLISION_PADDING, style, children, ...props }, ref) => (
26
+ // collisionPadding default 8px:避免 tooltip 貼 viewport 邊(Radix avoidCollisions 預設 true 但 padding 0 會貼邊)
27
+ // 對齊 HoverCard canonical 避免 viewport edge clipping
28
+ <TooltipPrimitive.Portal>
29
+ <TooltipPrimitive.Content
30
+ ref={ref}
31
+ sideOffset={sideOffset}
32
+ collisionPadding={collisionPadding}
33
+ className={cn(
34
+ "z-50 overflow-hidden rounded-md px-3 py-2 text-body font-normal text-on-emphasis bg-tooltip max-w-[280px]",
35
+ "animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 motion-reduce:animate-none motion-reduce:zoom-in-100",
36
+ "data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
37
+ "origin-[var(--radix-tooltip-content-transform-origin)]",
38
+ className
39
+ )}
40
+ style={{ boxShadow: 'var(--elevation-200)', ...style }}
41
+ {...props}
42
+ >
43
+ <div data-theme="dark" className="contents">{children}</div>
44
+ </TooltipPrimitive.Content>
45
+ </TooltipPrimitive.Portal>
46
+ ))
47
+ TooltipContent.displayName = TooltipPrimitive.Content.displayName
48
+
49
+ // Story auto-compile metadata — Phase 1 mechanical migration(2026-04-24)
50
+ // Phase 2 fill needed: purpose descriptions + when rationale + world-class refs
51
+ export const tooltipMeta = {
52
+ component: 'Tooltip',
53
+ family: null, // non-family composite / overlay / layout
54
+ variants: {
55
+
56
+ },
57
+ sizes: {
58
+
59
+ },
60
+ states: ['default', 'hover', 'active', 'focus-visible', 'disabled'],
61
+ tokens: {
62
+ bg: [],
63
+ fg: [],
64
+ ring: [],
65
+ },
66
+ } as const
67
+
68
+ export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }