@qijenchen/design-system 0.1.0-beta.74 → 0.1.0-beta.76
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/CLAUDE.md +1 -1
- package/dist/components/AppShell/app-shell.d.ts +2 -2
- package/dist/components/AppShell/app-shell.js.map +1 -1
- package/dist/components/Avatar/avatar.js.map +1 -1
- package/dist/components/BulkActionBar/bulk-action-bar.d.ts.map +1 -1
- package/dist/components/BulkActionBar/bulk-action-bar.js +1 -1
- package/dist/components/BulkActionBar/bulk-action-bar.js.map +1 -1
- package/dist/components/Button/button.d.ts.map +1 -1
- package/dist/components/Button/button.js.map +1 -1
- package/dist/components/Chart/chart.d.ts +1 -1
- package/dist/components/Chart/chart.js.map +1 -1
- package/dist/components/Checkbox/checkbox.d.ts +1 -1
- package/dist/components/Checkbox/checkbox.js +1 -1
- package/dist/components/Checkbox/checkbox.js.map +1 -1
- package/dist/components/Combobox/combobox.d.ts +1 -1
- package/dist/components/Combobox/combobox.d.ts.map +1 -1
- package/dist/components/Combobox/combobox.js +13 -10
- package/dist/components/Combobox/combobox.js.map +1 -1
- package/dist/components/Command/command.d.ts +1 -1
- package/dist/components/Command/command.js +3 -3
- package/dist/components/Command/command.js.map +1 -1
- package/dist/components/DataTable/cell-registry.d.ts.map +1 -1
- package/dist/components/DataTable/cell-registry.js +2 -2
- package/dist/components/DataTable/cell-registry.js.map +1 -1
- package/dist/components/DataTable/data-table.d.ts +27 -6
- package/dist/components/DataTable/data-table.d.ts.map +1 -1
- package/dist/components/DataTable/data-table.js +57 -34
- package/dist/components/DataTable/data-table.js.map +1 -1
- package/dist/components/DatePicker/date-picker.d.ts.map +1 -1
- package/dist/components/DatePicker/date-picker.js +2 -2
- package/dist/components/DatePicker/date-picker.js.map +1 -1
- package/dist/components/DescriptionList/description-list.d.ts +1 -1
- package/dist/components/DescriptionList/description-list.js +2 -2
- package/dist/components/DescriptionList/description-list.js.map +1 -1
- package/dist/components/Empty/empty.d.ts +2 -0
- package/dist/components/Empty/empty.d.ts.map +1 -1
- package/dist/components/Empty/empty.js.map +1 -1
- package/dist/components/Field/field-wrapper.js +4 -4
- package/dist/components/Field/field-wrapper.js.map +1 -1
- package/dist/components/OverflowIndicator/overflow-indicator.d.ts +1 -1
- package/dist/components/OverflowIndicator/overflow-indicator.js +2 -2
- package/dist/components/OverflowIndicator/overflow-indicator.js.map +1 -1
- package/dist/components/PeoplePicker/people-picker.js +2 -2
- package/dist/components/PeoplePicker/people-picker.js.map +1 -1
- package/dist/components/ProfileCard/profile-card.d.ts +1 -1
- package/dist/components/ProfileCard/profile-card.js +2 -1
- package/dist/components/ProfileCard/profile-card.js.map +1 -1
- package/dist/components/Rating/rating.js.map +1 -1
- package/dist/components/Select/select.js +4 -4
- package/dist/components/Select/select.js.map +1 -1
- package/dist/components/Textarea/textarea.d.ts +1 -1
- package/dist/components/Textarea/textarea.js +2 -2
- package/dist/components/Textarea/textarea.js.map +1 -1
- package/dist/components/TimePicker/time-picker.d.ts.map +1 -1
- package/dist/components/TimePicker/time-picker.js +14 -23
- package/dist/components/TimePicker/time-picker.js.map +1 -1
- package/dist/components/TreeView/tree-view.d.ts +1 -1
- package/dist/components/TreeView/tree-view.js +1 -1
- package/dist/components/TreeView/tree-view.js.map +1 -1
- package/ds-canonical/fork/governance.lock +1 -1
- package/ds-canonical/fork/preamble.md +2 -2
- package/ds-canonical/hooks/check_field_controls_contracts.sh +30 -3
- package/ds-canonical/hooks/check_story_invariants.sh +26 -0
- package/ds-canonical/hooks/tests/test_check_story_invariants.sh +30 -0
- package/ds-canonical/references/props-naming.md +15 -1
- package/ds-canonical/rules/ui-development.md +2 -2
- package/llms-full.txt +7 -2
- package/llms.txt +3 -3
- package/package.json +1 -1
- package/src/components/Accordion/accordion.principles.stories.tsx +3 -3
- package/src/components/Alert/alert.anatomy.stories.tsx +4 -4
- package/src/components/Alert/alert.principles.stories.tsx +5 -5
- package/src/components/AppShell/app-shell.principles.stories.tsx +6 -6
- package/src/components/AppShell/app-shell.spec.md +4 -4
- package/src/components/AppShell/app-shell.tsx +2 -2
- package/src/components/AspectRatio/aspect-ratio.principles.stories.tsx +1 -1
- package/src/components/Avatar/avatar.principles.stories.tsx +3 -3
- package/src/components/Avatar/avatar.tsx +1 -1
- package/src/components/Badge/badge.principles.stories.tsx +3 -3
- package/src/components/Breadcrumb/breadcrumb.principles.stories.tsx +3 -3
- package/src/components/Breadcrumb/breadcrumb.spec.md +11 -1
- package/src/components/BulkActionBar/bulk-action-bar.anatomy.stories.tsx +1 -1
- package/src/components/BulkActionBar/bulk-action-bar.principles.stories.tsx +3 -3
- package/src/components/BulkActionBar/bulk-action-bar.spec.md +4 -2
- package/src/components/BulkActionBar/bulk-action-bar.stories.tsx +2 -2
- package/src/components/BulkActionBar/bulk-action-bar.tsx +3 -2
- package/src/components/Button/button.principles.stories.tsx +3 -3
- package/src/components/Button/button.tsx +0 -10
- package/src/components/Calendar/calendar.anatomy.stories.tsx +1 -1
- package/src/components/Calendar/calendar.principles.stories.tsx +3 -3
- package/src/components/Carousel/carousel.principles.stories.tsx +2 -2
- package/src/components/Chart/chart.principles.stories.tsx +4 -4
- package/src/components/Chart/chart.tsx +1 -1
- package/src/components/Checkbox/checkbox.principles.stories.tsx +2 -2
- package/src/components/Checkbox/checkbox.tsx +1 -1
- package/src/components/Chip/chip.principles.stories.tsx +3 -3
- package/src/components/Coachmark/coachmark.anatomy.stories.tsx +1 -1
- package/src/components/Coachmark/coachmark.principles.stories.tsx +3 -3
- package/src/components/Coachmark/coachmark.spec.md +2 -2
- package/src/components/Combobox/combobox.anatomy.stories.tsx +14 -14
- package/src/components/Combobox/combobox.principles.stories.tsx +6 -6
- package/src/components/Combobox/combobox.spec.md +1 -1
- package/src/components/Combobox/combobox.tsx +25 -14
- package/src/components/Command/command.anatomy.stories.tsx +2 -0
- package/src/components/Command/command.principles.stories.tsx +7 -7
- package/src/components/Command/command.tsx +2 -2
- package/src/components/DataTable/cell-registry.tsx +6 -2
- package/src/components/DataTable/data-table-filter-panel.tsx +3 -3
- package/src/components/DataTable/data-table.anatomy.stories.tsx +1 -1
- package/src/components/DataTable/data-table.css +1 -1
- package/src/components/DataTable/data-table.principles.stories.tsx +3 -3
- package/src/components/DataTable/data-table.spec.md +25 -17
- package/src/components/DataTable/data-table.stories.tsx +29 -25
- package/src/components/DataTable/data-table.tsx +92 -44
- package/src/components/DateGrid/date-grid.anatomy.stories.tsx +1 -1
- package/src/components/DateGrid/date-grid.principles.stories.tsx +4 -4
- package/src/components/DateGrid/date-grid.spec.md +1 -1
- package/src/components/DatePicker/date-picker.anatomy.stories.tsx +15 -11
- package/src/components/DatePicker/date-picker.principles.stories.tsx +5 -5
- package/src/components/DatePicker/date-picker.spec.md +1 -1
- package/src/components/DatePicker/date-picker.tsx +9 -6
- package/src/components/DescriptionList/description-list.principles.stories.tsx +5 -5
- package/src/components/DescriptionList/description-list.tsx +1 -1
- package/src/components/Dialog/dialog.anatomy.stories.tsx +1 -1
- package/src/components/Dialog/dialog.principles.stories.tsx +4 -4
- package/src/components/DropdownMenu/dropdown-menu.anatomy.stories.tsx +1 -1
- package/src/components/DropdownMenu/dropdown-menu.principles.stories.tsx +5 -5
- package/src/components/DropdownMenu/dropdown-menu.spec.md +1 -1
- package/src/components/Empty/empty.principles.stories.tsx +2 -2
- package/src/components/Empty/empty.tsx +2 -0
- package/src/components/Field/field-controls.spec.md +9 -6
- package/src/components/Field/field-wrapper.tsx +4 -4
- package/src/components/Field/field.principles.stories.tsx +4 -4
- package/src/components/FileItem/file-item.principles.stories.tsx +6 -5
- package/src/components/FileUpload/file-upload.principles.stories.tsx +6 -6
- package/src/components/FileUpload/file-upload.spec.md +1 -1
- package/src/components/FileViewer/file-viewer.principles.stories.tsx +5 -5
- package/src/components/HoverCard/hover-card.principles.stories.tsx +6 -6
- package/src/components/Input/input.anatomy.stories.tsx +3 -3
- package/src/components/Input/input.principles.stories.tsx +4 -4
- package/src/components/LinkInput/link-input.anatomy.stories.tsx +3 -3
- package/src/components/LinkInput/link-input.principles.stories.tsx +5 -5
- package/src/components/Menu/menu-item.principles.stories.tsx +7 -7
- package/src/components/Notice/notice.anatomy.stories.tsx +1 -1
- package/src/components/Notice/notice.principles.stories.tsx +7 -7
- package/src/components/NumberInput/number-input.anatomy.stories.tsx +8 -7
- package/src/components/NumberInput/number-input.principles.stories.tsx +4 -4
- package/src/components/NumberInput/number-input.spec.md +1 -1
- package/src/components/OverflowIndicator/overflow-indicator.principles.stories.tsx +5 -5
- package/src/components/OverflowIndicator/overflow-indicator.tsx +1 -1
- package/src/components/PeoplePicker/people-picker.principles.stories.tsx +3 -3
- package/src/components/PeoplePicker/people-picker.spec.md +3 -3
- package/src/components/PeoplePicker/people-picker.tsx +6 -6
- package/src/components/Popover/popover.principles.stories.tsx +5 -5
- package/src/components/ProfileCard/profile-card.anatomy.stories.tsx +1 -1
- package/src/components/ProfileCard/profile-card.principles.stories.tsx +1 -1
- package/src/components/ProfileCard/profile-card.tsx +1 -1
- package/src/components/ProgressBar/progress-bar.principles.stories.tsx +2 -2
- package/src/components/ProgressBar/progress-bar.spec.md +1 -1
- package/src/components/RadioGroup/radio-group.principles.stories.tsx +2 -2
- package/src/components/Rating/rating.anatomy.stories.tsx +2 -2
- package/src/components/Rating/rating.principles.stories.tsx +3 -3
- package/src/components/Rating/rating.spec.md +1 -1
- package/src/components/Rating/rating.tsx +1 -1
- package/src/components/ScrollArea/scroll-area.principles.stories.tsx +4 -4
- package/src/components/Select/select.anatomy.stories.tsx +18 -18
- package/src/components/Select/select.principles.stories.tsx +5 -5
- package/src/components/Select/select.spec.md +1 -1
- package/src/components/Select/select.tsx +7 -7
- package/src/components/SelectMenu/select-menu.anatomy.stories.tsx +1 -1
- package/src/components/SelectMenu/select-menu.principles.stories.tsx +8 -8
- package/src/components/SelectionControl/selection-item.principles.stories.tsx +7 -7
- package/src/components/Separator/separator.principles.stories.tsx +4 -4
- package/src/components/Sheet/sheet.principles.stories.tsx +2 -2
- package/src/components/Sidebar/sidebar.principles.stories.tsx +4 -4
- package/src/components/Sidebar/sidebar.spec.md +2 -2
- package/src/components/Skeleton/skeleton.principles.stories.tsx +5 -5
- package/src/components/Slider/slider.anatomy.stories.tsx +1 -1
- package/src/components/Slider/slider.principles.stories.tsx +3 -3
- package/src/components/Steps/steps.principles.stories.tsx +4 -4
- package/src/components/Steps/steps.spec.md +2 -2
- package/src/components/Switch/switch.principles.stories.tsx +1 -1
- package/src/components/Tabs/tabs.principles.stories.tsx +3 -3
- package/src/components/Tabs/tabs.spec.md +1 -1
- package/src/components/Tag/tag.principles.stories.tsx +3 -3
- package/src/components/Textarea/textarea.principles.stories.tsx +2 -2
- package/src/components/Textarea/textarea.tsx +3 -3
- package/src/components/TimePicker/time-picker.principles.stories.tsx +5 -5
- package/src/components/TimePicker/time-picker.spec.md +1 -1
- package/src/components/TimePicker/time-picker.tsx +11 -12
- package/src/components/Toast/toast.principles.stories.tsx +2 -2
- package/src/components/Tooltip/tooltip.principles.stories.tsx +3 -3
- package/src/components/TreeView/tree-view.principles.stories.tsx +5 -5
- package/src/components/TreeView/tree-view.stories.tsx +1 -1
- package/src/components/TreeView/tree-view.tsx +1 -1
- package/src/patterns/element-anatomy/item-anatomy.spec.md +1 -1
- package/src/patterns/element-anatomy/item-anatomy.stories.tsx +1 -1
- package/src/patterns/overlay-surface/overlay-surface.spec.md +1 -0
- package/src/patterns/resize-handle/resize-handle.spec.md +1 -1
- package/src/tokens/color/color.spec.md +2 -0
- package/src/tokens/color/semantic.css +1 -1
- package/src/tokens/uiSize/uiSize.css +5 -0
- package/src/tokens/uiSize/uiSize.spec.md +17 -3
|
@@ -26,19 +26,19 @@ export const UsageGuidance: Story = {
|
|
|
26
26
|
<p>適合 Notice 的真實業務場景(點擊跳轉「展示」頁範例):</p>
|
|
27
27
|
<ul className="space-y-1">
|
|
28
28
|
<li>
|
|
29
|
-
<LinkTo kind="Design System/Internal/Notice/展示" name="部署成功 橫幅"><span className="text-primary hover:
|
|
29
|
+
<LinkTo kind="Design System/Internal/Notice/展示" name="部署成功 橫幅"><span className="text-primary hover:text-primary-hover font-medium cursor-pointer">CI/CD 部署成功橫幅 — 附「查看部署紀錄」動作</span></LinkTo>
|
|
30
30
|
</li>
|
|
31
31
|
<li>
|
|
32
|
-
<LinkTo kind="Design System/Internal/Notice/展示" name="工作區付款失敗"><span className="text-primary hover:
|
|
32
|
+
<LinkTo kind="Design System/Internal/Notice/展示" name="工作區付款失敗"><span className="text-primary hover:text-primary-hover font-medium cursor-pointer">工作區付款失敗 — 附「更新付款方式」補救動作</span></LinkTo>
|
|
33
33
|
</li>
|
|
34
34
|
<li>
|
|
35
|
-
<LinkTo kind="Design System/Internal/Notice/展示" name="行內通知變體對照"><span className="text-primary hover:
|
|
35
|
+
<LinkTo kind="Design System/Internal/Notice/展示" name="行內通知變體對照"><span className="text-primary hover:text-primary-hover font-medium cursor-pointer">方案限制 / 空間不足 / 連線失敗等行內提示(五種語意變體)</span></LinkTo>
|
|
36
36
|
</li>
|
|
37
37
|
<li>
|
|
38
|
-
<LinkTo kind="Design System/Internal/Notice/展示" name="Toast 樣式"><span className="text-primary hover:
|
|
38
|
+
<LinkTo kind="Design System/Internal/Notice/展示" name="Toast 樣式"><span className="text-primary hover:text-primary-hover font-medium cursor-pointer">「變更已儲存」「已複製連結」Toast 短暫提示</span></LinkTo>
|
|
39
39
|
</li>
|
|
40
40
|
<li>
|
|
41
|
-
<LinkTo kind="Design System/Internal/Notice/展示" name="中性 純文字"><span className="text-primary hover:
|
|
41
|
+
<LinkTo kind="Design System/Internal/Notice/展示" name="中性 純文字"><span className="text-primary hover:text-primary-hover font-medium cursor-pointer">成員邀請待加入提醒 — 中性純文字</span></LinkTo>
|
|
42
42
|
</li>
|
|
43
43
|
</ul>
|
|
44
44
|
<p className="text-fg-muted mt-3">判斷不確定時:對照 spec.md「何時用 / 何時不用」段;若仍不符,改用近親元件(見下方 vs 近親段落)。</p>
|
|
@@ -74,7 +74,7 @@ export const CompositionRules: Story = {
|
|
|
74
74
|
<div>
|
|
75
75
|
<Section title="Pattern 1 — Alert(persistent inline announcement)">
|
|
76
76
|
<div className="prose prose-sm max-w-prose">
|
|
77
|
-
<p>需要「持續顯示直到 user 主動 dismiss」→ <LinkTo kind="Design System/Components/Alert/展示" name="低調單行"><span className="text-primary hover:
|
|
77
|
+
<p>需要「持續顯示直到 user 主動 dismiss」→ <LinkTo kind="Design System/Components/Alert/展示" name="低調單行"><span className="text-primary hover:text-primary-hover font-medium cursor-pointer">Alert 低調單行</span></LinkTo>。Alert 內部消費 Notice + 加上 dismiss button + live region ARIA(error/warning → role="alert" assertive;info/success/neutral → role="status" polite)。</p>
|
|
78
78
|
<ul>
|
|
79
79
|
<li>典型場景:付款失敗 banner / workspace 警告 / 重要通知 user 必看</li>
|
|
80
80
|
<li>對齊世界級:Polaris <code>Banner</code> / Material <code>Banner</code> / Atlassian <code>InlineMessage</code> — 共識用「persistent + dismissible」</li>
|
|
@@ -84,7 +84,7 @@ export const CompositionRules: Story = {
|
|
|
84
84
|
|
|
85
85
|
<Section title="Pattern 2 — Toast(auto-dismiss floating announcement)">
|
|
86
86
|
<div className="prose prose-sm max-w-prose">
|
|
87
|
-
<p>需要「短暫提示,使用者不需 acknowledge」→ <LinkTo kind="Design System/Components/Toast/展示" name="有標題與描述"><span className="text-primary hover:
|
|
87
|
+
<p>需要「短暫提示,使用者不需 acknowledge」→ <LinkTo kind="Design System/Components/Toast/展示" name="有標題與描述"><span className="text-primary hover:text-primary-hover font-medium cursor-pointer">有 Title + Description</span></LinkTo>。Toast 基於 Sonner + 消費 Notice layout,自動 4 秒消失(可調)。</p>
|
|
88
88
|
<ul>
|
|
89
89
|
<li>典型場景:儲存成功 / 複製到剪貼簿 / 非關鍵操作回饋</li>
|
|
90
90
|
<li>對齊世界級:Material <code>Snackbar</code> / Polaris <code>Toast</code> / Sonner — 共識用「auto-dismiss + 浮動 + 不阻塞 task」</li>
|
|
@@ -45,11 +45,12 @@ const TOKEN_MAP: Record<ModeKey, Record<StateKey, ColorSpec>> = {
|
|
|
45
45
|
disabled: { bg: 'transparent', text: '--fg-disabled', border: 'transparent' },
|
|
46
46
|
},
|
|
47
47
|
readonly: {
|
|
48
|
+
// readonly cva = `bg-readonly border border-transparent`(field-wrapper.tsx:109)—— 鎖定,無 hover / focus / error bg 轉換
|
|
48
49
|
default: { bg: '--bg-readonly', text: '--foreground', border: 'transparent' },
|
|
49
|
-
hover: { bg: '--bg-
|
|
50
|
-
focus: { bg: '--bg-
|
|
51
|
-
error: { bg: '--bg-
|
|
52
|
-
disabled: { bg: '--bg-
|
|
50
|
+
hover: { bg: '--bg-readonly', text: '--foreground', border: 'transparent' },
|
|
51
|
+
focus: { bg: '--bg-readonly', text: '--foreground', border: 'transparent' },
|
|
52
|
+
error: { bg: '--bg-readonly', text: '--foreground', border: 'transparent' },
|
|
53
|
+
disabled: { bg: '--bg-readonly', text: '--foreground', border: 'transparent' },
|
|
53
54
|
},
|
|
54
55
|
disabled: {
|
|
55
56
|
default: { bg: '--bg-disabled', text: '--fg-disabled', border: 'transparent' },
|
|
@@ -70,9 +71,9 @@ interface SizeSpec {
|
|
|
70
71
|
}
|
|
71
72
|
|
|
72
73
|
const SIZE_SPECS: Record<SizeKey, SizeSpec> = {
|
|
73
|
-
sm: { heightToken: 'h-field-sm', height: '28px', pxToken: 'px-
|
|
74
|
-
md: { heightToken: 'h-field-md', height: '32px', pxToken: 'px-
|
|
75
|
-
lg: { heightToken: 'h-field-lg', height: '36px', pxToken: 'px-
|
|
74
|
+
sm: { heightToken: 'h-field-sm', height: '28px', pxToken: 'px-[var(--field-px)]', px: 12, gapToken: 'gap-2', gap: 8, fontToken: 'text-body', font: '14px', icon: 16, actionHover: 18 },
|
|
75
|
+
md: { heightToken: 'h-field-md', height: '32px', pxToken: 'px-[var(--field-px)]', px: 12, gapToken: 'gap-2', gap: 8, fontToken: 'text-body', font: '14px', icon: 16, actionHover: 18 },
|
|
76
|
+
lg: { heightToken: 'h-field-lg', height: '36px', pxToken: 'px-[var(--field-px)]', px: 12, gapToken: 'gap-2', gap: 8, fontToken: 'text-body-lg', font: '16px', icon: 20, actionHover: 22 },
|
|
76
77
|
}
|
|
77
78
|
|
|
78
79
|
/* ═══════════════════════════════════════════════════════════════════════════
|
|
@@ -50,10 +50,10 @@ export const UsageGuidance: Story = {
|
|
|
50
50
|
<div className="prose prose-sm max-w-prose mb-8">
|
|
51
51
|
<p>適合 NumberInput 的真實業務場景(點擊跳轉「展示」頁範例):</p>
|
|
52
52
|
<ul className="space-y-1">
|
|
53
|
-
<li><LinkTo kind="Design System/Components/NumberInput/展示" name="四模式"><span className="text-primary hover:
|
|
54
|
-
<li><LinkTo kind="Design System/Components/NumberInput/展示" name="尺寸與 Button 對齊"><span className="text-primary hover:
|
|
55
|
-
<li><LinkTo kind="Design System/Components/NumberInput/展示" name="格式化選項"><span className="text-primary hover:
|
|
56
|
-
<li><LinkTo kind="Design System/Components/NumberInput/展示" name="DataTable 整合"><span className="text-primary hover:
|
|
53
|
+
<li><LinkTo kind="Design System/Components/NumberInput/展示" name="四模式"><span className="text-primary hover:text-primary-hover font-medium cursor-pointer">四模式</span></LinkTo></li>
|
|
54
|
+
<li><LinkTo kind="Design System/Components/NumberInput/展示" name="尺寸與 Button 對齊"><span className="text-primary hover:text-primary-hover font-medium cursor-pointer">尺寸與 Button 對齊</span></LinkTo></li>
|
|
55
|
+
<li><LinkTo kind="Design System/Components/NumberInput/展示" name="格式化選項"><span className="text-primary hover:text-primary-hover font-medium cursor-pointer">格式化選項</span></LinkTo></li>
|
|
56
|
+
<li><LinkTo kind="Design System/Components/NumberInput/展示" name="DataTable 整合"><span className="text-primary hover:text-primary-hover font-medium cursor-pointer">DataTable 整合</span></LinkTo></li>
|
|
57
57
|
</ul>
|
|
58
58
|
<p className="text-fg-muted mt-3">判斷不確定時:對照 spec.md「何時用 / 何時不用」段;若仍不符,改用近親元件(見下方 vs 近親 段)。</p>
|
|
59
59
|
</div>
|
|
@@ -124,7 +124,7 @@ col.accessor('price', {
|
|
|
124
124
|
|
|
125
125
|
## A11y 預設
|
|
126
126
|
|
|
127
|
-
**ARIA / Pattern**:ARIA / keyboard 機制**僅 edit mode 適用**——edit mode 渲染 native `<input>` element(預設 a11y),且把 `...props`(含 `aria-label` / `aria-describedby` 等)轉發到該 input
|
|
127
|
+
**ARIA / Pattern**:ARIA / keyboard 機制**僅 edit mode 適用**——edit mode 渲染 native `<input>` element(預設 a11y),且把 `...props`(含 `aria-label` / `aria-describedby` 等)轉發到該 input;該 native input 自身消費 `fieldCtx` 設定 `aria-invalid` / `aria-required` / `aria-describedby` / `aria-errormessage`(非由 Field wrapper 注入,對齊 Select / Combobox trigger 自設機制)。`display` / `readonly` / `disabled` 三 mode 渲染的是無互動純展示文字(`<div>` + `<span>`,不轉發 `aria-*`),螢幕報讀僅得格式化後的靜態值(對齊 Carbon read-only / Stripe display 慣例)。
|
|
128
128
|
|
|
129
129
|
**Keyboard 行為**:
|
|
130
130
|
|
|
@@ -24,16 +24,16 @@ export const UsageGuidance: Story = {
|
|
|
24
24
|
<p>適合 OverflowIndicator 的真實業務場景(點擊跳轉「展示」頁範例):</p>
|
|
25
25
|
<ul className="space-y-1">
|
|
26
26
|
<li>
|
|
27
|
-
<LinkTo kind="Design System/Internal/OverflowIndicator/展示" name="Combobox 標籤溢出"><span className="text-primary hover:
|
|
27
|
+
<LinkTo kind="Design System/Internal/OverflowIndicator/展示" name="Combobox 標籤溢出"><span className="text-primary hover:text-primary-hover font-medium cursor-pointer">Combobox 標籤溢出</span></LinkTo>
|
|
28
28
|
</li>
|
|
29
29
|
<li>
|
|
30
|
-
<LinkTo kind="Design System/Internal/OverflowIndicator/展示" name="人員頭像 疊合 +N"><span className="text-primary hover:
|
|
30
|
+
<LinkTo kind="Design System/Internal/OverflowIndicator/展示" name="人員頭像 疊合 +N"><span className="text-primary hover:text-primary-hover font-medium cursor-pointer">PR reviewer 頭像疊合 +N(只顯前 3 位)</span></LinkTo>
|
|
31
31
|
</li>
|
|
32
32
|
<li>
|
|
33
|
-
<LinkTo kind="Design System/Internal/OverflowIndicator/展示" name="Breadcrumb 中段收合"><span className="text-primary hover:
|
|
33
|
+
<LinkTo kind="Design System/Internal/OverflowIndicator/展示" name="Breadcrumb 中段收合"><span className="text-primary hover:text-primary-hover font-medium cursor-pointer">Breadcrumb 中段收合</span></LinkTo>
|
|
34
34
|
</li>
|
|
35
35
|
<li>
|
|
36
|
-
<LinkTo kind="Design System/Internal/OverflowIndicator/展示" name="DataTable 人員欄位"><span className="text-primary hover:
|
|
36
|
+
<LinkTo kind="Design System/Internal/OverflowIndicator/展示" name="DataTable 人員欄位"><span className="text-primary hover:text-primary-hover font-medium cursor-pointer">DataTable 人員欄位</span></LinkTo>
|
|
37
37
|
</li>
|
|
38
38
|
</ul>
|
|
39
39
|
<p className="text-fg-muted mt-3">判斷不確定時:對照 spec.md「何時用 / 何時不用」段;若仍不符,改用近親元件(見下方 vs 近親段落)。</p>
|
|
@@ -62,7 +62,7 @@ export const CompositionRules: Story = {
|
|
|
62
62
|
<p>OverflowIndicator 是顯示「還有 N 個沒列出來」的 `+N` 小標籤,由下列幾種元件在內部使用(顯示前幾項 + 把剩下的折成 `+N`),不直接放在頁面程式碼裡:</p>
|
|
63
63
|
|
|
64
64
|
<h4>Pattern 1 — Combobox 標籤溢出(單行模式)</h4>
|
|
65
|
-
<p>多選標籤太多、單行放不下 → <LinkTo kind="Design System/Components/Combobox/展示" name="四模式"><span className="text-primary hover:
|
|
65
|
+
<p>多選標籤太多、單行放不下 → <LinkTo kind="Design System/Components/Combobox/展示" name="四模式"><span className="text-primary hover:text-primary-hover font-medium cursor-pointer">Combobox</span></LinkTo> 內部使用 OverflowIndicator;hover `+N` 展開完整清單。對齊 GitHub 多標籤 / Linear 多指派人的做法。</p>
|
|
66
66
|
|
|
67
67
|
<h4>Pattern 2 — 人員頭像疊合 +N</h4>
|
|
68
68
|
<p>一群人只顯示前幾位、其餘折成 `+M`。目前由列表元件自行把頭像疊合再放上 OverflowIndicator(形狀用圓形對齊頭像);未來規劃中的 Avatar 群組元件會把這段組合收進去。對齊 Slack 工作區成員預覽 / Linear 團隊成員的做法。</p>
|
|
@@ -151,7 +151,7 @@ OverflowIndicator.displayName = 'OverflowIndicator'
|
|
|
151
151
|
// Phase 2 fill needed: purpose descriptions + when rationale + world-class refs
|
|
152
152
|
export const overflowIndicatorMeta = {
|
|
153
153
|
component: 'OverflowIndicator',
|
|
154
|
-
family:
|
|
154
|
+
family: 'self-contained', // 對齊 overflow-indicator.spec.md frontmatter family: self-contained(SSOT)
|
|
155
155
|
variants: {
|
|
156
156
|
|
|
157
157
|
},
|
|
@@ -58,13 +58,13 @@ export const UsageGuidance: Story = {
|
|
|
58
58
|
>
|
|
59
59
|
<ul className="space-y-1">
|
|
60
60
|
<li>
|
|
61
|
-
<LinkTo kind="Design System/Components/PeoplePicker/展示" name="單人"><span className="text-primary hover:
|
|
61
|
+
<LinkTo kind="Design System/Components/PeoplePicker/展示" name="單人"><span className="text-primary hover:text-primary-hover font-medium cursor-pointer">單人</span></LinkTo>
|
|
62
62
|
</li>
|
|
63
63
|
<li>
|
|
64
|
-
<LinkTo kind="Design System/Components/PeoplePicker/展示" name="多人"><span className="text-primary hover:
|
|
64
|
+
<LinkTo kind="Design System/Components/PeoplePicker/展示" name="多人"><span className="text-primary hover:text-primary-hover font-medium cursor-pointer">多人</span></LinkTo>
|
|
65
65
|
</li>
|
|
66
66
|
<li>
|
|
67
|
-
<LinkTo kind="Design System/Components/PeoplePicker/展示" name="尺寸"><span className="text-primary hover:
|
|
67
|
+
<LinkTo kind="Design System/Components/PeoplePicker/展示" name="尺寸"><span className="text-primary hover:text-primary-hover font-medium cursor-pointer">尺寸</span></LinkTo>
|
|
68
68
|
</li>
|
|
69
69
|
</ul>
|
|
70
70
|
</Rule>
|
|
@@ -147,7 +147,7 @@ PeoplePicker 永遠支援搜尋(內部使用 `Command` / cmdk)——因為
|
|
|
147
147
|
|
|
148
148
|
| 共享項 | SSOT owner |
|
|
149
149
|
|---|---|
|
|
150
|
-
| Avatar 左 inset 距 trigger.left = 12px 固定(不隨 size/density 漂移)| 本 spec L94 + `people-picker.tsx:362` `!px-
|
|
150
|
+
| Avatar 左 inset 距 trigger.left = 12px 固定(不隨 size/density 漂移)| 本 spec L94 + `people-picker.tsx:362` `!px-[var(--field-px)]` inject(form context)/ `!px-[var(--table-cell-px)]`(naked variant table cell)|
|
|
151
151
|
| Avatar+人名視覺 | `person-display.tsx:137-152` `PersonDisplay`(共享 renderer)|
|
|
152
152
|
| 人名按空間 ellipsis | `person-display.tsx:150` `truncate flex-1 min-w-0` |
|
|
153
153
|
| Placeholder ellipsis(inline-search active 時)| `field-wrapper.tsx:244` `bareInputStyles` 含 `truncate` |
|
|
@@ -172,7 +172,7 @@ PeoplePicker 永遠支援搜尋(內部使用 `Command` / cmdk)——因為
|
|
|
172
172
|
|
|
173
173
|
**改 PeoplePicker 視覺前必查本表,動 trigger render / placeholder / overflowShape / tagWrapperClassName 前先 cite 對應 row。** Hook `check_peoplepicker_ssot_drift.sh` 機械強制。
|
|
174
174
|
|
|
175
|
-
**Avatar 左 inset SSOT(GitHub people picker idiom)**:single trigger / multi stack 第一個 avatar 距 trigger.left = **12px 固定**(不隨 size / density 漂移)。實作:PeoplePicker form context + 有 tag → `cn(className, '!px-
|
|
175
|
+
**Avatar 左 inset SSOT(GitHub people picker idiom)**:single trigger / multi stack 第一個 avatar 距 trigger.left = **12px 固定**(不隨 size / density 漂移)。實作:PeoplePicker form context + 有 tag → `cn(className, '!px-[var(--field-px)]')` inject 12px override 到 Combobox Field wrapper,**覆蓋** Combobox `tagPadding[size]` density-dependent calc 公式(該公式在 default lg + 全 comfortable 共 4/6 組合漂離 12px,故報廢改固定值;推導史詳 git log 2026-05-13);`tagAreaPaddingLeftPx={undefined}` 不再 +8 magic。Cell context naked variant `!px-[var(--table-cell-px)]` 已是 12px 不 inject。對齊 GitHub picker / Material Autocomplete renderTags / Ant Select tagRender 共享 fixed-inset canonical(不 scale with size)。 <!-- @benchmark-unverified: see frontmatter benchmark list for canonical DS source URL -->
|
|
176
176
|
|
|
177
177
|
**判準**:
|
|
178
178
|
- **選人 → PeoplePicker**(不用 Select / Combobox 代替,失去 Avatar 視覺)
|
|
@@ -249,7 +249,7 @@ PeoplePicker 是 **composite 元件**(內部 wrap `<Select>`(single)/ `<Combobox
|
|
|
249
249
|
|
|
250
250
|
## 邊界案例
|
|
251
251
|
|
|
252
|
-
- **Disabled**:`disabled` → `resolvedMode='disabled'`(`useResolvedFieldMode`),走獨立 static-display 分支(`people-picker.tsx`「readonly / disabled」段)——渲染靜態 `<div>` 包 `MultiPersonDisplay` / `PersonDisplay` + `ItemSuffix` ChevronDown 類型身份 indicator(2026-06-
|
|
252
|
+
- **Disabled**:`disabled` → `resolvedMode='disabled'`(`useResolvedFieldMode`),走獨立 static-display 分支(`people-picker.tsx`「readonly / disabled」段)——渲染靜態 `<div>` 包 `MultiPersonDisplay` / `PersonDisplay` + `ItemSuffix` ChevronDown 類型身份 indicator(2026-06-26:disabled 保留 chevron、readonly 不顯示;naked variant 依 `showDisplayEndIcon`),**不** wrap Select / Combobox、無 dismiss X、無 inline-search input。token 走 M24 state precedence(`text-fg-disabled`,含 chevron)。
|
|
253
253
|
- **Loading(async people fetch)**:目前 PeoplePicker **未暴露 `loading` prop**(`PeoplePickerProps` 無此欄位,內部也未 forward 給 wrapped Select / Combobox)。consumer 若需 loading 態,在 fetch 完成前自行控制 `people=[]`(走 emptyText 空態)。底層 Select / Combobox / SelectMenu 各有自己的 loading 機制,但尚未經 PeoplePicker API 層轉發。
|
|
254
254
|
- **Empty(no search results)**:`emptyText` 預設「沒有符合的人員」(本 spec L72 已 codify);無 creatable mode(人員不可建立)。
|
|
255
255
|
- **Empty(no value selected)**:single mode → trigger 顯 placeholder「請選擇人員」;multi mode `value=[]` → trigger 同 placeholder(無 avatar stack 渲);詳「Trigger display SSOT canonical table」B-D 段。
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// @benchmark-unverified-blanket: file-level retraction per M22 (d) — claims herein not individually URL-cited; treat as unverified visual/usage rumor unless retrofit per-claim. Hook escape preserved.
|
|
2
2
|
// @placeholder-vocabulary-allow: 1-cycle backward-compat — `placeholder` 已加(trigger empty SSOT),`emptyPlaceholder={emptyText}` forward 仍保留讓既有 consumer 不被 silent break;Combobox line 760 `placeholder ?? emptyPlaceholder` fallback → placeholder 永遠 takes precedence。Future cycle 移除 emptyPlaceholder forward(per field-controls.spec.md 共享 contract b)。
|
|
3
|
-
// @cell-metric-escape-allow: comment describes RETIRED `tagAreaPaddingLeftPx={8}` magic — current code is surface-guarded (`surface === 'form'` only injects `!px-
|
|
3
|
+
// @cell-metric-escape-allow: comment describes RETIRED `tagAreaPaddingLeftPx={8}` magic — current code is surface-guarded (`surface === 'form'` only injects `!px-[var(--field-px)]`; table-cell context untouched, lets naked `!px-[var(--table-cell-px)]` SSOT take over). Hook regex grep'd the comment word, not the live code path. Per (a) fix 2026-05-13 user-approved Path a.
|
|
4
4
|
import * as React from 'react'
|
|
5
5
|
import { ChevronDown } from 'lucide-react'
|
|
6
6
|
import { cn } from '@/lib/utils'
|
|
@@ -189,8 +189,8 @@ const PeoplePicker = React.forwardRef<HTMLDivElement, PeoplePickerProps>(functio
|
|
|
189
189
|
? <MultiPersonDisplay value={value as PersonValue[]} size={size} measured />
|
|
190
190
|
: <PersonDisplay value={value as PersonValue} size={size} />}
|
|
191
191
|
</span>
|
|
192
|
-
{/* 2026-06-
|
|
193
|
-
{(resolvedVariant === 'naked' ? showDisplayEndIcon :
|
|
192
|
+
{/* 2026-06-26 類型身份 indicator:edit 顯示 / readonly 不顯示 / disabled 保留(fg-disabled,對齊原生 <select disabled>);naked cell 依 showDisplayEndIcon */}
|
|
193
|
+
{(resolvedVariant === 'naked' ? showDisplayEndIcon : resolvedMode === 'disabled') && (
|
|
194
194
|
<ItemSuffix className="pointer-events-none">
|
|
195
195
|
<ChevronDown size={ICON_SIZE[size as 'sm' | 'md' | 'lg']} className={cn('shrink-0', resolvedMode === 'disabled' ? 'text-fg-disabled' : 'text-fg-muted')} aria-hidden />
|
|
196
196
|
</ItemSuffix>
|
|
@@ -363,12 +363,12 @@ const PeoplePicker = React.forwardRef<HTMLDivElement, PeoplePickerProps>(functio
|
|
|
363
363
|
// 撤掉 `tagAreaPaddingLeftPx={8}` magic — Combobox `tagPadding[size]` 是 density-dependent
|
|
364
364
|
// calc 公式(`(field-height - icon-size) / 2`),只在 md size + default density 才 = 4px;
|
|
365
365
|
// 其他 size/density 漂 6px / 8px → 4+8=12 spec 公式不成立。
|
|
366
|
-
// (a) fix:form context + 有 tag → 改 inject `!px-
|
|
366
|
+
// (a) fix:form context + 有 tag → 改 inject `!px-[var(--field-px)]`(固定 12px)直接 override `tagPadding[size]`,
|
|
367
367
|
// 達成 GitHub PeoplePicker fixed 12px inset(對齊 cell context 同 13px from cell.left 含 1px border)。
|
|
368
|
-
// - form + 有 tag → `!px-
|
|
368
|
+
// - form + 有 tag → `!px-[var(--field-px)]`(12px 固定 inset)+ tagAreaPaddingLeftPx undefined → field.padL=12 ✓
|
|
369
369
|
// - table-cell + 有 tag → naked variant `!px-[var(--table-cell-px)]` 已是 12px,不 inject ✓
|
|
370
370
|
// - isEmpty → 不 inject,走 Combobox 預設文字 inset(`tagPadding[size]` 公式自然 vertical center)
|
|
371
|
-
className={cn(className, !isEmpty && surface === 'form' && '!px-
|
|
371
|
+
className={cn(className, !isEmpty && surface === 'form' && '!px-[var(--field-px)]')}
|
|
372
372
|
aria-label={ariaLabel}
|
|
373
373
|
// 2026-05-15 Bug 1 fix(Claude+Codex Step 5 比稿 consensus,user verbatim「就 A」):per-length 動態
|
|
374
374
|
// wrapper class — length=1 降階單人視覺需要 width constraint chain(`flex-1 min-w-0 overflow-hidden`),
|
|
@@ -77,7 +77,7 @@ export const UsageGuidance: Story = {
|
|
|
77
77
|
<p>適合 Popover 的真實業務場景(點擊跳轉「展示」頁範例):</p>
|
|
78
78
|
<ul className="space-y-1">
|
|
79
79
|
<li>
|
|
80
|
-
<LinkTo kind="Design System/Components/Popover/展示" name="篩選面板"><span className="text-primary hover:
|
|
80
|
+
<LinkTo kind="Design System/Components/Popover/展示" name="篩選面板"><span className="text-primary hover:text-primary-hover font-medium cursor-pointer">篩選面板</span></LinkTo>
|
|
81
81
|
</li>
|
|
82
82
|
</ul>
|
|
83
83
|
<p className="text-fg-muted mt-3">判斷不確定時:往下看下方「Popover 與近親元件的分界」三組對照(對上 Dialog、DropdownMenu、Tooltip),確認該用哪個元件。</p>
|
|
@@ -253,7 +253,7 @@ export const VisualAlignmentRule: Story = {
|
|
|
253
253
|
<div>
|
|
254
254
|
<Rule
|
|
255
255
|
title="Popover 與 Dialog 共用 overlay-surface 視覺語言"
|
|
256
|
-
note="bg-surface-raised / border-border / rounded-lg / elevation-200 完全一致。Header / Body / Footer 內 padding 來自 overlay-surface pattern 主檔(px-loose py-tight)。差異:(1) Popover 是 non-modal 無 overlay 遮罩,(2) Popover 鎖 layout-space=md(header 精簡)
|
|
256
|
+
note="bg-surface-raised / border-border / rounded-lg / elevation-200 完全一致。Header / Body / Footer 內 padding 來自 overlay-surface pattern 主檔(px-loose py-tight)。差異:(1) Popover 是 non-modal 無 overlay 遮罩,(2) Popover 鎖 layout-space=md(header 精簡);Dialog 不鎖 layout-space,全繼承 page(md page → 16 / lg page → 24)。兩者 ui-size 都繼承 page"
|
|
257
257
|
>
|
|
258
258
|
<Popover>
|
|
259
259
|
<PopoverTrigger asChild>
|
|
@@ -285,10 +285,10 @@ export const VisualAlignmentRule: Story = {
|
|
|
285
285
|
</Rule>
|
|
286
286
|
|
|
287
287
|
<Rule
|
|
288
|
-
title="為什麼
|
|
289
|
-
note="
|
|
288
|
+
title="為什麼 layout-space 鎖 md"
|
|
289
|
+
note="Popover 只鎖 layout-space=md(header / footer padding 永遠精簡),內部控件的 ui-size 繼承 page(對齊 trigger)。頁面 layout-space 大(lg)時,Popover 若 header / footer 跟著放大會失去「輕量補充 UI」的定位 — 使用者會誤以為是主 modal。鎖 layout-space=md 讓 Popover chrome 永遠比主頁面緊湊,保持「這是補充不是主流程」的視覺暗示"
|
|
290
290
|
>
|
|
291
|
-
<Label>頁面
|
|
291
|
+
<Label>頁面 layout-space = lg,Popover header / footer 仍是 md(內部控件 ui-size 繼承 page)— 輕量感不被放大破壞</Label>
|
|
292
292
|
</Rule>
|
|
293
293
|
</div>
|
|
294
294
|
),
|
|
@@ -498,7 +498,7 @@ export const Accessibility = {
|
|
|
498
498
|
render: () => (
|
|
499
499
|
<div className="max-w-3xl text-body text-fg-secondary">
|
|
500
500
|
<h3 className="text-h5 text-foreground mb-2">無障礙設計</h3>
|
|
501
|
-
<p className="whitespace-pre-line">{"詳 `profile-card.spec.md` 「A11y 預設」段。摘要:\n\n> 命名對齊 DS 設計準則(2026-05-18,per # 命名與語言一致性 )。本節原標題「無障礙」,改「A11y 預設」與其他 spec 一致。\n\n- Trigger 整合 :Avatar 作為 HoverCard trigger 時, onFocus / onBlur 與 mouseenter/leave 同時觸發由 Radix HoverCard 管理——鍵盤使用者 Tab 到 avatar 可自動顯示 card,Escape 關閉\n- Focus 順序 :Radix HoverCard 預設不抓取 focus 進入浮層——HoverCardContent 每次 render 把浮層內所有 tabIndex>=0 節點 set tabindex=-1(@radix-ui/react-hover-card
|
|
501
|
+
<p className="whitespace-pre-line">{"詳 `profile-card.spec.md` 「A11y 預設」段。摘要:\n\n> 命名對齊 DS 設計準則(2026-05-18,per # 命名與語言一致性 )。本節原標題「無障礙」,改「A11y 預設」與其他 spec 一致。\n\n- Trigger 整合 :Avatar 作為 HoverCard trigger 時, onFocus / onBlur 與 mouseenter/leave 同時觸發由 Radix HoverCard 管理——鍵盤使用者 Tab 到 avatar 可自動顯示 card,Escape 關閉\n- Focus 順序 :Radix HoverCard 預設不抓取 focus 進入浮層——HoverCardContent 每次 render 把浮層內所有 tabIndex>=0 節點 set tabindex=-1(@radix-ui/react-hover-card 的 getTabbableNodes + setAttribute('tabindex','-1');dist 行號隨安裝版本浮動,不 pin),刻意把 action / view more 移出 tab order。hover-integrated 路徑下鍵盤使用者 Tab 只停在 trigger(Avatar),無法 Tab 聚焦浮層內 action / view more(與 Popover 的 focus trap 截然不同)。需要鍵盤可達 action 改用 Popover / Dialog\n- Live region 語意 :ProfileCard 是展示內容,非 announcement,不套 aria-live \n- DL 語意 :Info Fields 使用 DescriptionList(<dl>/<dt>/<dd>),screen reader 讀「term X, description Y」(詳 description-list.spec.md「無障礙」段)\n- CTA button aria-label :icon-only action button 必帶 aria-label (「傳訊息給 {name}」),不只 icon 視覺\n- 色彩對比 :Status badge / Avatar status 圓點均過 WCAG AA,不依賴單一色彩載體(搭配文字標籤)"}</p>
|
|
502
502
|
</div>
|
|
503
503
|
),
|
|
504
504
|
}
|
|
@@ -87,7 +87,7 @@ export const UsageGuidance: Story = {
|
|
|
87
87
|
<p>適合 ProfileCard 的真實業務場景(點擊跳轉「展示」頁範例):</p>
|
|
88
88
|
<ul className="space-y-1">
|
|
89
89
|
<li>
|
|
90
|
-
<LinkTo kind="Design System/Internal/ProfileCard/展示" name="懸停展開 ProfileCard"><span className="text-primary hover:
|
|
90
|
+
<LinkTo kind="Design System/Internal/ProfileCard/展示" name="懸停展開 ProfileCard"><span className="text-primary hover:text-primary-hover font-medium cursor-pointer">懸停留言者 / PR reviewer / 成員列表的頭像,展開人員詳情卡</span></LinkTo>
|
|
91
91
|
</li>
|
|
92
92
|
</ul>
|
|
93
93
|
<p>判斷時對照 spec.md「何時用 / 何時不用」段落。</p>
|
|
@@ -314,7 +314,7 @@ export const nameCardMeta = {
|
|
|
314
314
|
sizes: {
|
|
315
315
|
|
|
316
316
|
},
|
|
317
|
-
states: [
|
|
317
|
+
states: [], // 唯讀 person-info template,無自身互動 state(hover/active/focus/disabled 屬子 Button/Avatar)
|
|
318
318
|
tokens: {
|
|
319
319
|
bg: ['bg-muted'],
|
|
320
320
|
fg: ['text-foreground'],
|
|
@@ -61,11 +61,11 @@ export const UsageGuidance: Story = {
|
|
|
61
61
|
<p>適合 ProgressBar 的真實業務場景(點擊跳轉「展示」頁範例):</p>
|
|
62
62
|
<ul className="space-y-1">
|
|
63
63
|
<li>
|
|
64
|
-
<LinkTo kind="Design System/Components/ProgressBar/展示" name="批次任務進度"><span className="text-primary hover:
|
|
64
|
+
<LinkTo kind="Design System/Components/ProgressBar/展示" name="批次任務進度"><span className="text-primary hover:text-primary-hover font-medium cursor-pointer">批次任務進度</span></LinkTo>
|
|
65
65
|
<span className="text-fg-secondary"> — CSV 匯入 1,250 筆客戶資料、批次寄送:總量已知,單一 prominent 進度條讓使用者掌握整體完成比例</span>
|
|
66
66
|
</li>
|
|
67
67
|
<li>
|
|
68
|
-
<LinkTo kind="Design System/Components/ProgressBar/展示" name="DataTable 儲存格內進度"><span className="text-primary hover:
|
|
68
|
+
<LinkTo kind="Design System/Components/ProgressBar/展示" name="DataTable 儲存格內進度"><span className="text-primary hover:text-primary-hover font-medium cursor-pointer">DataTable 儲存格內進度</span></LinkTo>
|
|
69
69
|
<span className="text-fg-secondary"> — 列表中每列各自的配額使用率 / 任務完成度,4px 細線不搶走主要欄位的閱讀重量</span>
|
|
70
70
|
</li>
|
|
71
71
|
</ul>
|
|
@@ -72,7 +72,7 @@ ProgressBar 是**量化 linear 進度** primitive——consumer 必須能回答
|
|
|
72
72
|
| **視覺** | 水平直線從左往右填充 | 圓弧從 12 點順時針填充 / indeterminate 整體旋轉 |
|
|
73
73
|
| **典型情境** | 批次處理 / 表單 wizard / 頁面級大區塊 / quota(檔案上傳 → 走 FileItem) | Button loading / Field loading / cell 局部 / inline 小 % |
|
|
74
74
|
| **可量化時機** | **必須能量化**(100% 結束可預期) | determinate 能量化 / indeterminate 不需要 |
|
|
75
|
-
| **a11y** | `role="progressbar"` + `aria-valuenow` | `role="progressbar"`(有 value) / `role="status"`(無 value) |
|
|
75
|
+
| **a11y** | `role="progressbar"` + `aria-valuenow` | `role="progressbar"`(有 value) / `role="status"`(無 value 且有 label/aria-label;否則 aria-hidden 交父層 aria-busy) |
|
|
76
76
|
| **終止條件** | 到 100% 或 status 變 success / error | 任務完成(卸載或 aria-busy 移除) |
|
|
77
77
|
| **typical 時長** | 秒級到分鐘級(值得顯示比例) | 秒級(短暫等待) / 秒到分鐘(determinate) |
|
|
78
78
|
|
|
@@ -46,8 +46,8 @@ export const UsageGuidance: Story = {
|
|
|
46
46
|
<div className="prose prose-sm max-w-prose mb-8">
|
|
47
47
|
<p>適合 RadioGroup 的真實業務場景(點擊跳轉「展示」頁範例):</p>
|
|
48
48
|
<ul className="space-y-1">
|
|
49
|
-
<li><LinkTo kind="Design System/Components/RadioGroup/展示" name="直式群組"><span className="text-primary hover:
|
|
50
|
-
<li><LinkTo kind="Design System/Components/RadioGroup/展示" name="水平排列"><span className="text-primary hover:
|
|
49
|
+
<li><LinkTo kind="Design System/Components/RadioGroup/展示" name="直式群組"><span className="text-primary hover:text-primary-hover font-medium cursor-pointer">直式群組</span></LinkTo><span className="text-fg-secondary"> — 訂閱方案選擇(月付 / 年付 / 終身):需要對比價格與說明的決策節點,直式 + description 完整閱讀</span></li>
|
|
50
|
+
<li><LinkTo kind="Design System/Components/RadioGroup/展示" name="水平排列"><span className="text-primary hover:text-primary-hover font-medium cursor-pointer">水平排列</span></LinkTo><span className="text-fg-secondary"> — 外觀主題切換(淺色 / 深色 / 系統):2-3 個短 label、不需描述文字,橫排省垂直空間</span></li>
|
|
51
51
|
</ul>
|
|
52
52
|
<p className="text-fg-muted mt-3">判斷不確定時:回頭看「何時用 / 何時不用」;若仍不符,改用近親元件(見下方「vs 近親元件」)。</p>
|
|
53
53
|
</div>
|
|
@@ -124,7 +124,7 @@ const Tab = ({ active, onClick, children }: { active: boolean; onClick: () => vo
|
|
|
124
124
|
type="button"
|
|
125
125
|
onClick={onClick}
|
|
126
126
|
className={`px-2.5 py-1 text-[12px] font-mono rounded-md cursor-pointer transition-colors ${
|
|
127
|
-
active ? 'bg-primary text-
|
|
127
|
+
active ? 'bg-primary text-on-emphasis font-semibold' : 'bg-neutral-hover text-fg-secondary hover:bg-neutral-active'
|
|
128
128
|
}`}
|
|
129
129
|
>
|
|
130
130
|
{children}
|
|
@@ -237,7 +237,7 @@ const InspectorInner = () => {
|
|
|
237
237
|
<PropRow label="aria-valuemax">{readOnly || disabled ? '—' : '5'}</PropRow>
|
|
238
238
|
<PropRow label="aria-valuetext">{readOnly || disabled ? '—' : `${value} of 5 stars`}</PropRow>
|
|
239
239
|
<PropRow label="aria-label">{readOnly ? '必填' : 'Field 內免填 · standalone 必填'}</PropRow>
|
|
240
|
-
<PropRow label="aria-labelledby">
|
|
240
|
+
<PropRow label="aria-labelledby">Field 內自動指向 FieldLabel(所有模式,含 readonly/disabled)</PropRow>
|
|
241
241
|
</div>
|
|
242
242
|
</div>
|
|
243
243
|
</div>
|
|
@@ -58,15 +58,15 @@ export const UsageGuidance: Story = {
|
|
|
58
58
|
<p>適合 Rating 的真實業務場景(點擊跳轉「展示」頁範例):</p>
|
|
59
59
|
<ul className="space-y-1">
|
|
60
60
|
<li>
|
|
61
|
-
<LinkTo kind="Design System/Components/Rating/展示" name="商品列表平均分"><span className="text-primary hover:
|
|
61
|
+
<LinkTo kind="Design System/Components/Rating/展示" name="商品列表平均分"><span className="text-primary hover:text-primary-hover font-medium cursor-pointer">商品列表平均分</span></LinkTo>
|
|
62
62
|
<span className="text-fg-secondary"> — 電商搜尋結果每張商品卡的 readOnly 平均分 + 評論數(4.7、12,843 則)</span>
|
|
63
63
|
</li>
|
|
64
64
|
<li>
|
|
65
|
-
<LinkTo kind="Design System/Components/Rating/展示" name="送出評分流程"><span className="text-primary hover:
|
|
65
|
+
<LinkTo kind="Design System/Components/Rating/展示" name="送出評分流程"><span className="text-primary hover:text-primary-hover font-medium cursor-pointer">送出評分流程</span></LinkTo>
|
|
66
66
|
<span className="text-fg-secondary"> — 訂單完成 / 用餐後使用者親自給分:hover 預覽 + click 送出(interactive)</span>
|
|
67
67
|
</li>
|
|
68
68
|
<li>
|
|
69
|
-
<LinkTo kind="Design System/Components/Rating/展示" name="包在 Field 內"><span className="text-primary hover:
|
|
69
|
+
<LinkTo kind="Design System/Components/Rating/展示" name="包在 Field 內"><span className="text-primary hover:text-primary-hover font-medium cursor-pointer">包在 Field 內</span></LinkTo>
|
|
70
70
|
<span className="text-fg-secondary"> — 售後問卷表單中作為欄位:label + 驗證跟其他 Field 控制項一致</span>
|
|
71
71
|
</li>
|
|
72
72
|
</ul>
|
|
@@ -170,7 +170,7 @@ Star icon 渲染時明確設 `stroke="none"`(Lucide Star 預設 `stroke="current
|
|
|
170
170
|
## A11y 預設
|
|
171
171
|
|
|
172
172
|
- **interactive**:`role="slider"` + `aria-valuenow={value}` + `aria-valuemin={0}` + `aria-valuemax={max}` + `aria-valuetext={`{value} of {max} stars`}` + `tabIndex={0}`,鍵盤 Arrow Left/Right/Up/Down ± step(precision=half 時 step=0.5,否則 step=1);Home = 0;End = max(完整 WAI-ARIA slider keyboard pattern)
|
|
173
|
-
- **readOnly**:`role="img"` + `aria-label
|
|
173
|
+
- **readOnly**:`role="img"` + accessible name。`Field` 內由 `aria-labelledby` 自動指向 `FieldLabel`(免填 `aria-label`);standalone(無 Field)時 `aria-label` 必填,例:`aria-label="平均評分 4.7 星,共 5 星"`。無 tabIndex
|
|
174
174
|
- **disabled**:`aria-disabled="true"` + `pointer-events-none`
|
|
175
175
|
- **單顆星** `aria-hidden`:內部點擊目標是 `<span role="presentation" aria-hidden>`(非 interactive element,避免與外層 `role="slider"` 形成 axe nested-interactive 違規,2026-04-25 修正;含 half-precision 的兩個 hover zone)都不干擾螢幕閱讀器,父層 role 獨自表達語意
|
|
176
176
|
|
|
@@ -17,7 +17,7 @@ import { useFieldContext, useResolvedFieldSize, useResolvedFieldDisabled, useRes
|
|
|
17
17
|
* ── 視覺 ──
|
|
18
18
|
* 填色用 `var(--warning)`(yellow-6,世界級黃星 convention;與 warning 語意共用色相
|
|
19
19
|
* 但語境不同,評分 = UX convention color 非 status)。
|
|
20
|
-
* 空色用 `var(--
|
|
20
|
+
* 空色用 `var(--divider)`(neutral-4 借 divider semantic alias;灰色;與 disabled/empty 同級)。
|
|
21
21
|
*
|
|
22
22
|
* ── 互動 ──
|
|
23
23
|
* interactive(預設):hover 預覽、click 設值、keyboard Left/Right 改值
|
|
@@ -93,16 +93,16 @@ export const UsageGuidance: Story = {
|
|
|
93
93
|
<p>適合 ScrollArea 的真實業務場景(點擊跳轉「展示」頁範例):</p>
|
|
94
94
|
<ul className="space-y-1">
|
|
95
95
|
<li>
|
|
96
|
-
<LinkTo kind="Design System/Components/ScrollArea/展示" name="垂直捲動 — Linear 議題清單"><span className="text-primary hover:
|
|
96
|
+
<LinkTo kind="Design System/Components/ScrollArea/展示" name="垂直捲動 — Linear 議題清單"><span className="text-primary hover:text-primary-hover font-medium cursor-pointer">垂直捲動 — Linear issue 清單</span></LinkTo>
|
|
97
97
|
</li>
|
|
98
98
|
<li>
|
|
99
|
-
<LinkTo kind="Design System/Components/ScrollArea/展示" name="垂直捲動 — Notion 側欄 導覽"><span className="text-primary hover:
|
|
99
|
+
<LinkTo kind="Design System/Components/ScrollArea/展示" name="垂直捲動 — Notion 側欄 導覽"><span className="text-primary hover:text-primary-hover font-medium cursor-pointer">垂直捲動 — Notion sidebar 導覽</span></LinkTo>
|
|
100
100
|
</li>
|
|
101
101
|
<li>
|
|
102
|
-
<LinkTo kind="Design System/Components/ScrollArea/展示" name="水平捲動 — Stripe 寬欄位商品表"><span className="text-primary hover:
|
|
102
|
+
<LinkTo kind="Design System/Components/ScrollArea/展示" name="水平捲動 — Stripe 寬欄位商品表"><span className="text-primary hover:text-primary-hover font-medium cursor-pointer">水平捲動 — Stripe 寬欄位商品表</span></LinkTo>
|
|
103
103
|
</li>
|
|
104
104
|
<li>
|
|
105
|
-
<LinkTo kind="Design System/Components/ScrollArea/展示" name="雙向捲動 — GitHub PR 大型檢閱表"><span className="text-primary hover:
|
|
105
|
+
<LinkTo kind="Design System/Components/ScrollArea/展示" name="雙向捲動 — GitHub PR 大型檢閱表"><span className="text-primary hover:text-primary-hover font-medium cursor-pointer">雙向捲動 — GitHub PR 大型檢閱表</span></LinkTo>
|
|
106
106
|
</li>
|
|
107
107
|
</ul>
|
|
108
108
|
<p className="text-fg-muted mt-3">判斷不確定時:對照 spec.md「何時用 / 何時不用」段;若仍不符,改用近親元件(見 <code>Vs*Rule</code> stories)。</p>
|
|
@@ -47,11 +47,11 @@ const TOKEN_MAP: Record<ModeKey, Record<StateKey, ColorSpec>> = {
|
|
|
47
47
|
},
|
|
48
48
|
// readonly wrapper 為靜態 bg-readonly(field-wrapper.tsx 無 hover/focus 樣式;mode prop 勝 disabled)
|
|
49
49
|
readonly: {
|
|
50
|
-
default: { bg: '--bg-readonly', text: '--foreground', border: 'transparent', icon: '
|
|
51
|
-
hover: { bg: '--bg-readonly', text: '--foreground', border: 'transparent', icon: '
|
|
52
|
-
focus: { bg: '--bg-readonly', text: '--foreground', border: 'transparent', icon: '
|
|
53
|
-
error: { bg: '--bg-readonly', text: '--foreground', border: 'transparent', icon: '
|
|
54
|
-
disabled: { bg: '--bg-readonly', text: '--foreground', border: 'transparent', icon: '
|
|
50
|
+
default: { bg: '--bg-readonly', text: '--foreground', border: 'transparent', icon: '—' },
|
|
51
|
+
hover: { bg: '--bg-readonly', text: '--foreground', border: 'transparent', icon: '—' },
|
|
52
|
+
focus: { bg: '--bg-readonly', text: '--foreground', border: 'transparent', icon: '—' },
|
|
53
|
+
error: { bg: '--bg-readonly', text: '--foreground', border: 'transparent', icon: '—' },
|
|
54
|
+
disabled: { bg: '--bg-readonly', text: '--foreground', border: 'transparent', icon: '—' },
|
|
55
55
|
},
|
|
56
56
|
disabled: {
|
|
57
57
|
default: { bg: '--bg-disabled', text: '--fg-disabled', border: 'transparent', icon: '--fg-disabled' },
|
|
@@ -86,7 +86,7 @@ interface SizeSpec {
|
|
|
86
86
|
const SIZE_SPECS: Record<SizeKey, SizeSpec> = {
|
|
87
87
|
sm: {
|
|
88
88
|
heightToken: 'h-field-sm', height: '28px',
|
|
89
|
-
pxToken: 'px-
|
|
89
|
+
pxToken: 'px-[var(--field-px)]', px: '12px',
|
|
90
90
|
gapToken: 'gap-2', gap: '8px',
|
|
91
91
|
fontToken: 'text-body', font: '14px',
|
|
92
92
|
icon: 16,
|
|
@@ -95,7 +95,7 @@ const SIZE_SPECS: Record<SizeKey, SizeSpec> = {
|
|
|
95
95
|
},
|
|
96
96
|
md: {
|
|
97
97
|
heightToken: 'h-field-md', height: '32px',
|
|
98
|
-
pxToken: 'px-
|
|
98
|
+
pxToken: 'px-[var(--field-px)]', px: '12px',
|
|
99
99
|
gapToken: 'gap-2', gap: '8px',
|
|
100
100
|
fontToken: 'text-body', font: '14px',
|
|
101
101
|
icon: 16,
|
|
@@ -104,7 +104,7 @@ const SIZE_SPECS: Record<SizeKey, SizeSpec> = {
|
|
|
104
104
|
},
|
|
105
105
|
lg: {
|
|
106
106
|
heightToken: 'h-field-lg', height: '36px',
|
|
107
|
-
pxToken: 'px-
|
|
107
|
+
pxToken: 'px-[var(--field-px)]', px: '12px',
|
|
108
108
|
gapToken: 'gap-2', gap: '8px',
|
|
109
109
|
fontToken: 'text-body-lg', font: '16px',
|
|
110
110
|
icon: 20,
|
|
@@ -271,7 +271,7 @@ export const Overview = {
|
|
|
271
271
|
<span className="rounded px-2 py-1 text-[11px] font-mono border border-dashed"
|
|
272
272
|
style={{ borderColor: 'var(--color-turquoise-6)', backgroundColor: 'var(--color-turquoise-1)', color: 'var(--color-turquoise-6)' }}>Tag</span>
|
|
273
273
|
</div>
|
|
274
|
-
<span className="text-[10px] text-fg-muted font-mono">chevron
|
|
274
|
+
<span className="text-[10px] text-fg-muted font-mono">chevron:readonly 不顯示 / disabled 保留(類型身份 indicator)· 不可開選單 · tagPadding 置中</span>
|
|
275
275
|
</div>
|
|
276
276
|
</div>
|
|
277
277
|
</div>
|
|
@@ -437,7 +437,7 @@ const InspectorInner = () => {
|
|
|
437
437
|
{ c: Z.gap, l: '元素間距' },
|
|
438
438
|
{ c: Z.select, l: 'Select 文字' },
|
|
439
439
|
...(clearable && value ? [{ c: Z.action, l: 'Clear' }] : []),
|
|
440
|
-
{ c: Z.icon, l: 'Chevron' },
|
|
440
|
+
...(mode !== 'readonly' ? [{ c: Z.icon, l: 'Chevron' }] : []),
|
|
441
441
|
].map(({ c, l }) => (
|
|
442
442
|
<span key={l} className="inline-flex items-center gap-1">
|
|
443
443
|
<span className="w-2.5 h-2.5 rounded-md" style={{ background: c.bg, border: `1px dashed ${c.border}` }} />
|
|
@@ -452,7 +452,7 @@ const InspectorInner = () => {
|
|
|
452
452
|
{hasStartIcon && <BpZone w={32} color={Z.gap} label={s.gapToken} sub={s.gap} />}
|
|
453
453
|
<BpZone w={80} color={Z.select} label="flex-1" sub="select 文字" />
|
|
454
454
|
{clearable && value && <BpZone w={36} color={Z.action} label={`${s.icon}px`} sub="clear" />}
|
|
455
|
-
<BpZone w={36} color={Z.icon} label={`${s.icon}px`} sub="chevron" />
|
|
455
|
+
{mode !== 'readonly' && <BpZone w={36} color={Z.icon} label={`${s.icon}px`} sub="chevron" />}
|
|
456
456
|
<BpZone w={44} color={Z.pad} label={s.pxToken} sub={s.px} />
|
|
457
457
|
</div>
|
|
458
458
|
<div className="ml-3 flex items-center" style={{ height: 52 }}>
|
|
@@ -475,7 +475,7 @@ const InspectorInner = () => {
|
|
|
475
475
|
{ c: Z.select, l: 'Select (hidden)' },
|
|
476
476
|
{ c: Z.gap, l: 'Spacer' },
|
|
477
477
|
...(clearable && value ? [{ c: Z.action, l: 'Clear' }] : []),
|
|
478
|
-
{ c: Z.icon, l: 'Chevron' },
|
|
478
|
+
...(mode !== 'readonly' ? [{ c: Z.icon, l: 'Chevron' }] : []),
|
|
479
479
|
].map(({ c, l }) => (
|
|
480
480
|
<span key={l} className="inline-flex items-center gap-1">
|
|
481
481
|
<span className="w-2.5 h-2.5 rounded-md" style={{ background: c.bg, border: `1px dashed ${c.border}` }} />
|
|
@@ -490,7 +490,7 @@ const InspectorInner = () => {
|
|
|
490
490
|
<BpZone w={40} color={Z.select} label="select" sub="hidden" />
|
|
491
491
|
<BpZone w={36} color={Z.gap} label="flex-1" sub="spacer" />
|
|
492
492
|
{clearable && value && <BpZone w={36} color={Z.action} label={`${s.icon}px`} sub="clear" />}
|
|
493
|
-
<BpZone w={36} color={Z.icon} label={`${s.icon}px`} sub="chevron" />
|
|
493
|
+
{mode !== 'readonly' && <BpZone w={36} color={Z.icon} label={`${s.icon}px`} sub="chevron" />}
|
|
494
494
|
<BpZone w={44} color={Z.pad} label="12px" sub="pR fixed" />
|
|
495
495
|
</div>
|
|
496
496
|
<div className="ml-3 flex items-center" style={{ height: 52 }}>
|
|
@@ -520,7 +520,7 @@ const InspectorInner = () => {
|
|
|
520
520
|
<PropRow label="Fill"><TokenValue value={colors.bg} /></PropRow>
|
|
521
521
|
<PropRow label="Text"><TokenValue value={colors.text} /></PropRow>
|
|
522
522
|
<PropRow label="Stroke"><TokenValue value={colors.border} /></PropRow>
|
|
523
|
-
<PropRow label="Icon"><TokenValue value={colors.icon} /></PropRow>
|
|
523
|
+
{mode !== 'readonly' && <PropRow label="Icon"><TokenValue value={colors.icon} /></PropRow>}
|
|
524
524
|
</div>
|
|
525
525
|
|
|
526
526
|
{/* LAYOUT */}
|
|
@@ -530,14 +530,14 @@ const InspectorInner = () => {
|
|
|
530
530
|
{isTag ? (
|
|
531
531
|
<>
|
|
532
532
|
<PropRow label="tagPadding" dot={Z.pad.text}><TkVal token="calc()" value={s.tagPaddingFormula} /></PropRow>
|
|
533
|
-
<PropRow label="右側 pR"><TkVal token="
|
|
533
|
+
<PropRow label="右側 pR"><TkVal token="var(--field-px)" value="12px" /></PropRow>
|
|
534
534
|
<PropRow label="Tag 高度" dot={Z.tag.text}>{s.tagHeight}</PropRow>
|
|
535
535
|
</>
|
|
536
536
|
) : (
|
|
537
537
|
<PropRow label="左右內距" dot={Z.pad.text}><TkVal token={s.pxToken} value={s.px} /></PropRow>
|
|
538
538
|
)}
|
|
539
539
|
<PropRow label="元素間距" dot={Z.gap.text}><TkVal token={s.gapToken} value={s.gap} /></PropRow>
|
|
540
|
-
<PropRow label="Icon 尺寸" dot={Z.icon.text}>{s.icon}px</PropRow>
|
|
540
|
+
{mode !== 'readonly' && <PropRow label="Icon 尺寸" dot={Z.icon.text}>{s.icon}px</PropRow>}
|
|
541
541
|
</div>
|
|
542
542
|
|
|
543
543
|
{/* TYPOGRAPHY */}
|
|
@@ -722,7 +722,7 @@ export const SizeMatrix = {
|
|
|
722
722
|
{/* ── tag mode additional tokens ── */}
|
|
723
723
|
<div className="flex flex-col gap-2">
|
|
724
724
|
<span className="text-caption font-medium text-fg-secondary">tag 模式額外 token</span>
|
|
725
|
-
<Desc>tag 模式的 padding 用 calc() 公式置中 Tag:px = (field-height - tag-height) / 2。右側 paddingRight 固定 12px
|
|
725
|
+
<Desc>tag 模式的 padding 用 calc() 公式置中 Tag:px = (field-height - tag-height) / 2。右側 paddingRight 固定 12px(--field-px token)。</Desc>
|
|
726
726
|
<div className="overflow-x-auto">
|
|
727
727
|
<table className="border-collapse text-caption">
|
|
728
728
|
<thead><tr>
|
|
@@ -752,7 +752,7 @@ export const SizeMatrix = {
|
|
|
752
752
|
<Td>paddingRight</Td>
|
|
753
753
|
{SIZES.map((sz) => (
|
|
754
754
|
<Td key={sz} mono>
|
|
755
|
-
<div className="text-fg-secondary">
|
|
755
|
+
<div className="text-fg-secondary">var(--field-px)</div>
|
|
756
756
|
<div className="text-fg-muted text-[10px]">12px(固定)</div>
|
|
757
757
|
</Td>
|
|
758
758
|
))}
|