@qijenchen/design-system 0.1.0-beta.73 → 0.1.0-beta.75

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 (100) hide show
  1. package/dist/components/AppShell/_demo-helpers.d.ts.map +1 -1
  2. package/dist/components/BulkActionBar/bulk-action-bar.d.ts.map +1 -1
  3. package/dist/components/BulkActionBar/bulk-action-bar.js +1 -1
  4. package/dist/components/BulkActionBar/bulk-action-bar.js.map +1 -1
  5. package/dist/components/DataTable/data-table.d.ts +27 -6
  6. package/dist/components/DataTable/data-table.d.ts.map +1 -1
  7. package/dist/components/DataTable/data-table.js +57 -34
  8. package/dist/components/DataTable/data-table.js.map +1 -1
  9. package/ds-canonical/fork/governance.lock +7 -7
  10. package/ds-canonical/fork/skills/bug-fix-rhythm/SKILL.md +2 -0
  11. package/ds-canonical/fork/skills/code-quality-audit/SKILL.md +2 -0
  12. package/ds-canonical/fork/skills/product-ui-audit/SKILL.md +2 -0
  13. package/ds-canonical/fork/skills/prototype/references/audit-checks.md +1 -0
  14. package/ds-canonical/fork/skills/scan-similar-bugs/SKILL.md +2 -0
  15. package/ds-canonical/fork/skills/visual-audit/SKILL.md +2 -0
  16. package/ds-canonical/fork/skills/visual-audit/references/audit-architecture.md +1 -0
  17. package/ds-canonical/hooks/check_plugin_fork_health.sh +2 -2
  18. package/ds-canonical/hooks/check_story_invariants.sh +26 -0
  19. package/ds-canonical/hooks/lib/_app_shell_primary_header_consistency.sh +31 -4
  20. package/ds-canonical/hooks/session_start_governance_check.sh +1 -1
  21. package/ds-canonical/hooks/tests/test_check_app_shell_primary_header_consistency.sh +46 -2
  22. package/ds-canonical/hooks/tests/test_check_story_invariants.sh +30 -0
  23. package/ds-canonical/skills/design-system-audit/SKILL.md +2 -2
  24. package/llms-full.txt +1 -1
  25. package/llms.txt +1 -1
  26. package/package.json +1 -1
  27. package/src/components/Accordion/accordion.principles.stories.tsx +3 -3
  28. package/src/components/Alert/alert.principles.stories.tsx +5 -5
  29. package/src/components/AppShell/_demo-helpers.tsx +23 -6
  30. package/src/components/AppShell/app-shell.principles.stories.tsx +9 -8
  31. package/src/components/AppShell/app-shell.spec.md +9 -8
  32. package/src/components/AppShell/app-shell.stories.tsx +5 -3
  33. package/src/components/AspectRatio/aspect-ratio.principles.stories.tsx +1 -1
  34. package/src/components/Avatar/avatar.principles.stories.tsx +3 -3
  35. package/src/components/Badge/badge.principles.stories.tsx +3 -3
  36. package/src/components/Breadcrumb/breadcrumb.principles.stories.tsx +3 -3
  37. package/src/components/BulkActionBar/bulk-action-bar.anatomy.stories.tsx +1 -1
  38. package/src/components/BulkActionBar/bulk-action-bar.principles.stories.tsx +3 -3
  39. package/src/components/BulkActionBar/bulk-action-bar.spec.md +4 -2
  40. package/src/components/BulkActionBar/bulk-action-bar.stories.tsx +2 -2
  41. package/src/components/BulkActionBar/bulk-action-bar.tsx +3 -2
  42. package/src/components/Button/button.principles.stories.tsx +3 -3
  43. package/src/components/Calendar/calendar.principles.stories.tsx +3 -3
  44. package/src/components/Carousel/carousel.principles.stories.tsx +2 -2
  45. package/src/components/Chart/chart.principles.stories.tsx +4 -4
  46. package/src/components/Checkbox/checkbox.principles.stories.tsx +2 -2
  47. package/src/components/Chip/chip.principles.stories.tsx +3 -3
  48. package/src/components/Coachmark/coachmark.principles.stories.tsx +3 -3
  49. package/src/components/Combobox/combobox.anatomy.stories.tsx +2 -2
  50. package/src/components/Combobox/combobox.principles.stories.tsx +5 -5
  51. package/src/components/Command/command.principles.stories.tsx +7 -7
  52. package/src/components/DataTable/data-table.principles.stories.tsx +3 -3
  53. package/src/components/DataTable/data-table.spec.md +23 -15
  54. package/src/components/DataTable/data-table.stories.tsx +29 -25
  55. package/src/components/DataTable/data-table.tsx +92 -44
  56. package/src/components/DateGrid/date-grid.principles.stories.tsx +4 -4
  57. package/src/components/DatePicker/date-picker.principles.stories.tsx +5 -5
  58. package/src/components/DescriptionList/description-list.principles.stories.tsx +5 -5
  59. package/src/components/Dialog/dialog.principles.stories.tsx +4 -4
  60. package/src/components/DropdownMenu/dropdown-menu.anatomy.stories.tsx +1 -1
  61. package/src/components/DropdownMenu/dropdown-menu.principles.stories.tsx +5 -5
  62. package/src/components/Empty/empty.principles.stories.tsx +2 -2
  63. package/src/components/Field/field.principles.stories.tsx +4 -4
  64. package/src/components/FileItem/file-item.principles.stories.tsx +5 -5
  65. package/src/components/FileUpload/file-upload.principles.stories.tsx +4 -4
  66. package/src/components/FileViewer/file-viewer.principles.stories.tsx +5 -5
  67. package/src/components/HoverCard/hover-card.principles.stories.tsx +5 -5
  68. package/src/components/Input/input.principles.stories.tsx +4 -4
  69. package/src/components/LinkInput/link-input.principles.stories.tsx +5 -5
  70. package/src/components/Menu/menu-item.principles.stories.tsx +7 -7
  71. package/src/components/Notice/notice.principles.stories.tsx +7 -7
  72. package/src/components/NumberInput/number-input.principles.stories.tsx +4 -4
  73. package/src/components/OverflowIndicator/overflow-indicator.principles.stories.tsx +5 -5
  74. package/src/components/PeoplePicker/people-picker.principles.stories.tsx +3 -3
  75. package/src/components/Popover/popover.principles.stories.tsx +1 -1
  76. package/src/components/ProfileCard/profile-card.principles.stories.tsx +1 -1
  77. package/src/components/ProgressBar/progress-bar.principles.stories.tsx +2 -2
  78. package/src/components/RadioGroup/radio-group.principles.stories.tsx +2 -2
  79. package/src/components/Rating/rating.principles.stories.tsx +3 -3
  80. package/src/components/ScrollArea/scroll-area.principles.stories.tsx +4 -4
  81. package/src/components/Select/select.principles.stories.tsx +5 -5
  82. package/src/components/SelectMenu/select-menu.principles.stories.tsx +8 -8
  83. package/src/components/SelectionControl/selection-item.principles.stories.tsx +7 -7
  84. package/src/components/Separator/separator.principles.stories.tsx +4 -4
  85. package/src/components/Sheet/sheet.principles.stories.tsx +2 -2
  86. package/src/components/Sidebar/sidebar.principles.stories.tsx +4 -4
  87. package/src/components/Sidebar/sidebar.spec.md +1 -1
  88. package/src/components/Skeleton/skeleton.principles.stories.tsx +5 -5
  89. package/src/components/Slider/slider.principles.stories.tsx +3 -3
  90. package/src/components/Steps/steps.principles.stories.tsx +4 -4
  91. package/src/components/Switch/switch.principles.stories.tsx +1 -1
  92. package/src/components/Tabs/tabs.principles.stories.tsx +3 -3
  93. package/src/components/Tag/tag.principles.stories.tsx +3 -3
  94. package/src/components/Textarea/textarea.principles.stories.tsx +2 -2
  95. package/src/components/TimePicker/time-picker.principles.stories.tsx +5 -5
  96. package/src/components/Toast/toast.principles.stories.tsx +2 -2
  97. package/src/components/Tooltip/tooltip.principles.stories.tsx +3 -3
  98. package/src/components/TreeView/tree-view.principles.stories.tsx +5 -5
  99. package/src/components/TreeView/tree-view.stories.tsx +1 -1
  100. package/src/tokens/color/color.spec.md +2 -0
@@ -64,7 +64,7 @@ benchmark:
64
64
  </AppShell>
65
65
  ```
66
66
 
67
- Sub-component:`<AppShellAside title={...} width={400}>`(width consumer 自決 — `number` 或 `{ md, xl }` breakpoint-keyed,clamp `min-width: 240` / `max-width: 640`;**title prop required**,modal mode 走 Sheet → `aria-labelledby` 強制,per `sheet.spec.md:98`)。
67
+ Sub-component:`<AppShellAside title={...} width={400}>`(width consumer 自決 — `number` 或 `{ md, xl }` breakpoint-keyed,clamp `min-width: 240` / `max-width: 640`;**title prop required**,modal mode 走 Sheet → `aria-labelledby` 強制,per `sheet.spec.md`「禁止事項」(無 title → aria-labelledby 強制))
68
68
 
69
69
  ### Hook export:`useAppShell()`(2026-05-21 D2 codify per Phase B codex catch)
70
70
 
@@ -147,7 +147,7 @@ function CustomAside() {
147
147
 
148
148
  **Rule:帳號入口只能出現一次**(視覺 SSOT,同 WorkspaceBrand)。`primary-header` mode 同時放(globalHeader 右 + sidebar footer)= 視覺冗餘 + 入口混淆 → **禁止**;`primary-header` 的 sidebar **不放** user footer(與上方「globalHeader 存在 → sidebar 不重複 chrome 角色」同源邏輯)。
149
149
 
150
- **Responsive 精修( WorkspaceBrand,per breakpoint)**:mobile(<768px)sidebar Sheet 蓋住 globalHeader(帳號入口也在 globalHeader 右)→ Sheet 底補 `<SidebarFooter><UserFooter/>`(consumer `includeUserFooter || isMobile`,同組 primitive、style SSOT);desktop 維持無 footer。對齊世界級 mobile drawer「頂品牌 + 底帳號」慣例(Gmail / GitHub / Material modal nav drawer)。
150
+ **Responsive 精修(mobile-Sheet 子句,per breakpoint;2026-06-18 修正為「鏡像自己桌面的帳號家」)**:mobile(<768px)sidebar 收成 Sheet 蓋住 globalHeader(桌面帳號入口也在 globalHeader 右)→ **帳號入口鏡像桌面位置搬進 Sheet header 右**:在 `<SidebarHeader>` 內放 `<WorkspaceBrand/>`(左)+ `flex-1` + `<AccountMenu/>`(右)= 把整條 globalHeader 原封搬進 Sheet 頂排(SidebarHeader 即 ChromeHeader 基底,與 GlobalHeader 同 primitive → 帳號 avatar 24px + 右緣距 `--layout-space-loose` 由 construction 保證,**非手刻對齊**)。**不在 Sheet 底放 `<SidebarFooter>`**(footer 是 primary-sidebar 的帳號家慣例;primary-header 任何 breakpoint 帳號家都在 header 區)。Consumer 讀 `useSidebar().isMobile` + `useAppShell().layout`(`headerHasAccount = layout === 'primary-header' && isMobile`),desktop 維持無 header/footer。對齊 Material modal navigation drawer 官方「account switcher 放 drawer header 區(與 logo 同區、pinned、最高優先),footer 區放 settings/support 次要項」(<https://m3.material.io/components/navigation-drawer>)+ Gmail / GitHub mobile 選單頂部帳號慣例。**為何不放 footer**:使用者開 Sheet 看到的頂排 = 原本被蓋住的 top bar(brand 左 / 帳號右),空間記憶不斷;把帳號丟到 footer = 誤套 primary-sidebar 慣例(那 mode 桌面帳號本就在底)。Reference:`_demo-helpers.tsx` `AcmeSidebar`。
151
151
 
152
152
  **為什麼 primary-header 帳號入口在「右上」**:GitHub / Gmail / Slack / Atlassian 的全域標頭一律把「自己的帳號」放右上(品牌在左、帳號在右,左右對稱)= global chrome 標準位置,不是 sidebar footer。
153
153
 
@@ -166,14 +166,14 @@ function CustomAside() {
166
166
 
167
167
  **常見誤解:「multi-workspace 就必須 primary-header 派」** — workspace 多寡跟 layout 派别無相關性(world-class 反證:Linear / Notion / Figma 皆 multi-workspace 卻用 primary-sidebar;Gmail 多 account 用 primary-header)。選 mode = 表態「Header 是 local 還是 global」,**不是**「workspace 是 single 還是 multi」。
168
168
 
169
- **Sidebar toggle 按鈕位置**(消費既有 `sidebar.spec.md:308-360` SidebarTrigger pattern,**不發明新 toggle**):
169
+ **Sidebar toggle 按鈕位置**(消費既有 `sidebar.spec.md`「SidebarTrigger 位置(兩種 canonical pattern)」段,**不發明新 toggle**):
170
170
 
171
171
  | Mode | 對應 Sidebar pattern | Toggle 位置 |
172
172
  |---|---|---|
173
173
  | `primary-sidebar` | `sidebar.spec.md` Pattern A(無 global top bar) | 主內容 header 最左 |
174
174
  | `primary-header` | `sidebar.spec.md` Pattern B(有 global top bar) | global top bar 最左 |
175
175
 
176
- **唯一 invariant**(`sidebar.spec.md:310` 既有):trigger 必在 sidebar 任何 state(offcanvas / icon / expanded)下都可見 — 收合後 sidebar 不見了,toggle 不可能留在 sidebar 內(會跟著消失)。兩 mode 結論都落在 **Header 最左**,只是該 Header 是 local toolbar(Pattern A)還是 global bar(Pattern B)。Consumer 直接 `<SidebarTrigger />` 塞 Header 最左,AppShell 不 wrap / 不發明。
176
+ **唯一 invariant**(`sidebar.spec.md`「SidebarTrigger 位置」段既有):trigger 必在 sidebar 任何 state(offcanvas / icon / expanded)下都可見 — 收合後 sidebar 不見了,toggle 不可能留在 sidebar 內(會跟著消失)。兩 mode 結論都落在 **Header 最左**,只是該 Header 是 local toolbar(Pattern A)還是 global bar(Pattern B)。Consumer 直接 `<SidebarTrigger />` 塞 Header 最左,AppShell 不 wrap / 不發明。
177
177
 
178
178
  **層級語意差異**:`primary-sidebar` 的 Header scope = local(當前頁);`primary-header` 的 Header scope = global(整 app)。兩者是不同 product 角色,**不互通**。Consumer 選 mode = 表態 product 是哪派。
179
179
 
@@ -205,7 +205,7 @@ function CustomAside() {
205
205
 
206
206
  **Modal overlay** 行為:
207
207
  - 消費既有 `sheet.spec.md` canonical(從右滑出 + Esc 關 + click-outside 關 + focus trap + restore focus)
208
- - **title prop required**(per `sheet.spec.md:98` 禁無 title — `aria-labelledby` 強制)
208
+ - **title prop required**(per `sheet.spec.md`「禁止事項」禁無 title — `aria-labelledby` 強制)
209
209
  - 跟 Sidebar mobile fallback 同 SSOT
210
210
 
211
211
  **Breakpoint**:消費既有 `useIsNarrowViewport()` hook(`hooks/use-is-narrow-viewport.ts`,`MOBILE_BREAKPOINT = 768`;與 `sidebar.spec.md`「Mobile 行為」段 768px 同值),**不發明新 breakpoint**(非 CSS token — repo 無 `--sidebar-mobile-breakpoint`)。Sidebar + Aside 同步切 Sheet。
@@ -249,7 +249,7 @@ Main 內塞什麼(table / field / card / page header / list)的 layout + spacing
249
249
 
250
250
  - 所有 modal overlay(Dialog / Sheet / Popover / HoverCard)消費既有 `overlay-surface.spec.md` SSOT
251
251
  - Mask 蓋整個 AppShell(包含 sidebar + header + aside + main)
252
- - Z-index:AppShell shell root **不設 z-index**(`app-shell.tsx` root `<div>` 走正常 flow / stacking context,唯一 z-* 是 SkipToMain `focus:z-50`);overlay 走既有 **`z-50` Tailwind utility**(`Sheet.tsx:53/69` SSOT canonical,**不發明 `--z-overlay` token** — repo 內無此 token,Sheet 直接 `z-50`)
252
+ - Z-index:AppShell shell root **不設 z-index**(`app-shell.tsx` root `<div>` 走正常 flow / stacking context,唯一 z-* 是 SkipToMain `focus:z-50`);overlay 走既有 **`z-50` Tailwind utility**(`sheet.tsx` SheetOverlay / SheetContent `z-50` SSOT canonical,**不發明 `--z-overlay` token** — repo 內無此 token,Sheet 直接 `z-50`)
253
253
  - AppShell **不** export `modalOpen` prop,overlay 自管 open / close state
254
254
 
255
255
  ---
@@ -258,7 +258,7 @@ Main 內塞什麼(table / field / card / page header / list)的 layout + spacing
258
258
 
259
259
  | Shortcut | Action | Cite |
260
260
  |---|---|---|
261
- | **`⌘B`(macOS)/ `Ctrl+B`(Windows)** | Toggle sidebar | `sidebar.spec.md:348` SSOT「industry-standard,已內建,不該改」;`sidebar.tsx:63` code key `"b"` |
261
+ | **`⌘B`(macOS)/ `Ctrl+B`(Windows)** | Toggle sidebar | `sidebar.spec.md`「持久化與快捷鍵」SSOT(⌘B / Ctrl+B industry-standard,已內建,不該改);`sidebar.tsx` code key `"b"` |
262
262
  | **`⌘.`(macOS)/ `Ctrl+.`(Windows)** | Toggle aside | Linear convention(新加) |
263
263
  | **Skip-to-main link** | `Tab` 第一站 focus 「Skip to content」link → `main` | A11y WCAG 2.4.1 bypass blocks;對齊 Atlassian Layout skip-link |
264
264
 
@@ -309,7 +309,7 @@ Main 內塞什麼(table / field / card / page header / list)的 layout + spacing
309
309
  | Header height | `--chrome-header-height`(`tokens/uiSize/uiSize.spec.md`)|
310
310
  | Aside width | consumer 自傳 prop(無 token)|
311
311
  | Layout spacing | `layoutSpace` 全 family(`--layout-space-{tight,loose,bottom}`)|
312
- | Z-index | shell root 不設 z-index;overlay 走 Sheet `z-50` Tailwind utility(`sheet.tsx:53/69`,無 `--z-overlay` token)|
312
+ | Z-index | shell root 不設 z-index;overlay 走 Sheet `z-50` Tailwind utility(`sheet.tsx` SheetOverlay / SheetContent,無 `--z-overlay` token)|
313
313
  | Sheet fallback | `sheet.spec.md` SSOT |
314
314
  | Overlay | `overlay-surface.spec.md` SSOT |
315
315
 
@@ -354,3 +354,4 @@ Main 內塞什麼(table / field / card / page header / list)的 layout + spacing
354
354
  > 本節由 `scripts/add-reciprocal-pointers.mjs` 自動維護,列出在 SSOT 語境下指向本 spec 的其他 spec。若要手動補充,寫在本節之前。
355
355
 
356
356
  - `header-canonical.spec.md`
357
+ - `sidebar.spec.md`
@@ -385,9 +385,11 @@ export const PrimarySidebarWithTabs: Story = {
385
385
  * 對齊 GitHub repo header / Slack channel header / Gmail email-list toolbar 2-layer 慣例
386
386
  * - Sidebar 內**不 render WorkspaceBrand**(`includeWorkspaceBrand={false}`),avoid 重複(已在 globalHeader)
387
387
  * - **小螢幕(<768px)responsive**:sidebar 收成 Sheet 打開時蓋住 globalHeader → AcmeSidebar 偵測
388
- * `useSidebar().isMobile` 自動在 Sheet 內補回 WorkspaceBrand + UserFooter 底(同 primary-sidebar style SSOT);
389
- * desktop 維持無 header/footer。見 app-shell.spec.md WorkspaceBrand/帳號入口 SSOT 的「Responsive 精修」子句。
390
- * ( Storybook 縮窄 viewport < 768px 即可見 Sheet 內品牌 + 帳號。)
388
+ * `useSidebar().isMobile` + `useAppShell().layout` 把整條 globalHeader 搬進 Sheet SidebarHeader
389
+ * (brand + 帳號 avatar 右,鏡像桌面 global bar;SidebarHeader = ChromeHeader 基底 → 結構 SSOT、非手刻)。
390
+ * **不放 SidebarFooter**(primary-header 帳號家在 header 右,非 footer footer primary-sidebar 慣例)
391
+ * desktop 維持無 header/footer。見 app-shell.spec.md「帳號入口(Account entry)放置 SSOT」的「Responsive 精修」子句。
392
+ * (在 Storybook 縮窄 viewport < 768px 即可見 Sheet header 一排品牌 + 帳號。)
391
393
  */
392
394
  export const PrimaryHeader: Story = {
393
395
  name: '主標頭佈局 — 全域+本地兩層(GitHub/Gmail/Slack 派)',
@@ -197,7 +197,7 @@ export const RatioChoice: Story = {
197
197
  <p className="text-caption text-fg-muted max-w-[720px] leading-relaxed mb-8">
198
198
  先看內容方向(橫 / 方 / 直),再對到下方的判斷準則即可選定。五個 ratio 的並排視覺對照見{' '}
199
199
  <LinkTo kind="Design System/Components/AspectRatio/設計規格" name="標準 比例 視覺對照">
200
- <span className="text-primary hover:underline font-medium cursor-pointer">設計規格 → 標準 比例 視覺對照</span>
200
+ <span className="text-primary hover:text-primary-hover font-medium cursor-pointer">設計規格 → 標準 比例 視覺對照</span>
201
201
  </LinkTo>
202
202
  ,此處只講「怎麼選」的決策邏輯,不重畫圖。
203
203
  </p>
@@ -47,9 +47,9 @@ export const UsageGuidance: Story = {
47
47
  <div className="prose prose-sm max-w-prose mb-8">
48
48
  <p>Avatar 代表「誰」——人、團隊、組織、專案的視覺身份。適合的真實業務場景(點擊跳轉「展示」頁範例):</p>
49
49
  <ul className="space-y-1">
50
- <li>留言者、指派者、團隊成員列表的人員識別;workspace / 組織 / App 的身份標識 —— 見 <LinkTo kind="Design System/Components/Avatar/展示" name="四模式"><span className="text-primary hover:underline font-medium cursor-pointer">四模式</span></LinkTo></li>
51
- <li>成員沒上傳照片、或頭像圖片載入失敗時,以名字首字 + 色彩維持可辨識 —— 見 <LinkTo kind="Design System/Components/Avatar/展示" name="備援顯示"><span className="text-primary hover:underline font-medium cursor-pointer">備援顯示</span></LinkTo></li>
52
- <li>通訊錄、成員選單、chat 列表等列表項目的主視覺 prefix —— 見 <LinkTo kind="Design System/Components/Avatar/展示" name="情境用例"><span className="text-primary hover:underline font-medium cursor-pointer">情境用例</span></LinkTo></li>
50
+ <li>留言者、指派者、團隊成員列表的人員識別;workspace / 組織 / App 的身份標識 —— 見 <LinkTo kind="Design System/Components/Avatar/展示" name="四模式"><span className="text-primary hover:text-primary-hover font-medium cursor-pointer">四模式</span></LinkTo></li>
51
+ <li>成員沒上傳照片、或頭像圖片載入失敗時,以名字首字 + 色彩維持可辨識 —— 見 <LinkTo kind="Design System/Components/Avatar/展示" name="備援顯示"><span className="text-primary hover:text-primary-hover font-medium cursor-pointer">備援顯示</span></LinkTo></li>
52
+ <li>通訊錄、成員選單、chat 列表等列表項目的主視覺 prefix —— 見 <LinkTo kind="Design System/Components/Avatar/展示" name="情境用例"><span className="text-primary hover:text-primary-hover font-medium cursor-pointer">情境用例</span></LinkTo></li>
53
53
  </ul>
54
54
  <p className="text-fg-muted mt-3">判斷不確定時:對照 spec.md「何時用 / 何時不用」段;若仍不符,改用近親元件(見下方 vs 近親 段)。</p>
55
55
  </div>
@@ -45,13 +45,13 @@ export const UsageGuidance: Story = {
45
45
  <p>適合 Badge 的真實業務場景(點擊跳轉「展示」頁範例):</p>
46
46
  <ul className="space-y-1">
47
47
  <li>
48
- <LinkTo kind="Design System/Components/Badge/展示" name="正圓 vs 膠囊"><span className="text-primary hover:underline font-medium cursor-pointer">正圓 vs 膠囊</span></LinkTo>
48
+ <LinkTo kind="Design System/Components/Badge/展示" name="正圓 vs 膠囊"><span className="text-primary hover:text-primary-hover font-medium cursor-pointer">正圓 vs 膠囊</span></LinkTo>
49
49
  </li>
50
50
  <li>
51
- <LinkTo kind="Design System/Components/Badge/展示" name="圓點模式"><span className="text-primary hover:underline font-medium cursor-pointer">Dot 模式</span></LinkTo>
51
+ <LinkTo kind="Design System/Components/Badge/展示" name="圓點模式"><span className="text-primary hover:text-primary-hover font-medium cursor-pointer">Dot 模式</span></LinkTo>
52
52
  </li>
53
53
  <li>
54
- <LinkTo kind="Design System/Components/Badge/展示" name="數量上限"><span className="text-primary hover:underline font-medium cursor-pointer">Max 上限</span></LinkTo>
54
+ <LinkTo kind="Design System/Components/Badge/展示" name="數量上限"><span className="text-primary hover:text-primary-hover font-medium cursor-pointer">Max 上限</span></LinkTo>
55
55
  </li>
56
56
  </ul>
57
57
  <p className="text-fg-muted mt-3">判斷不確定時:先確認使用者真正需要的是「有沒有新東西」(用 Dot)還是「有多少」(用數字)。若兩者都不貼切,代表這裡可能不該放 Badge——改用文字標籤或其他狀態指示。</p>
@@ -65,15 +65,15 @@ export const UsageGuidance: Story = {
65
65
  <ul className="space-y-1">
66
66
  <li>
67
67
  電商多層分類、檔案管理器等深層頁面,路徑過長時中段折疊、點 ⋯ 可展開中間層 —— 見{' '}
68
- <LinkTo kind="Design System/Components/Breadcrumb/展示" name="可互動省略"><span className="text-primary hover:underline font-medium cursor-pointer">可互動省略</span></LinkTo>
68
+ <LinkTo kind="Design System/Components/Breadcrumb/展示" name="可互動省略"><span className="text-primary hover:text-primary-hover font-medium cursor-pointer">可互動省略</span></LinkTo>
69
69
  </li>
70
70
  <li>
71
71
  路徑由路由 / CMS 資料動態產生,直接傳資料陣列、超過層數自動收合中段 —— 見{' '}
72
- <LinkTo kind="Design System/Components/Breadcrumb/展示" name="宣告式 API + 自動收合"><span className="text-primary hover:underline font-medium cursor-pointer">宣告式 API + 自動收合</span></LinkTo>
72
+ <LinkTo kind="Design System/Components/Breadcrumb/展示" name="宣告式 API + 自動收合"><span className="text-primary hover:text-primary-hover font-medium cursor-pointer">宣告式 API + 自動收合</span></LinkTo>
73
73
  </li>
74
74
  <li>
75
75
  SPA 內點麵包屑要走前端路由、不整頁重載 —— 見{' '}
76
- <LinkTo kind="Design System/Components/Breadcrumb/展示" name="整合 React Router / Next.js Link"><span className="text-primary hover:underline font-medium cursor-pointer">整合 React Router / Next.js Link</span></LinkTo>
76
+ <LinkTo kind="Design System/Components/Breadcrumb/展示" name="整合 React Router / Next.js Link"><span className="text-primary hover:text-primary-hover font-medium cursor-pointer">整合 React Router / Next.js Link</span></LinkTo>
77
77
  </li>
78
78
  </ul>
79
79
  <p className="text-fg-muted mt-3">判斷不確定時:對照 spec.md「何時用 / 何時不用」段;若仍不符,改用相近的其他元件(見下方「跟相近元件怎麼分」的範例)。</p>
@@ -76,7 +76,7 @@ export const Overview: Story = {
76
76
  title={
77
77
  <>
78
78
  已選取本頁全部 50 個。{' '}
79
- <button type="button" className="text-primary hover:underline">
79
+ <button type="button" className="text-primary hover:text-primary-hover">
80
80
  點此選取全部 5370 個項目
81
81
  </button>
82
82
  </>
@@ -40,8 +40,8 @@ export const UsageGuidance: Story = {
40
40
  <div className="prose prose-sm max-w-prose">
41
41
  <p>BulkActionBar 是「選取狀態驅動」的批次操作列,跟 selection state 生命週期綁定。真實業務場景:</p>
42
42
  <ul className="space-y-1">
43
- <li><LinkTo kind="Design System/Components/BulkActionBar/展示" name="基本"><span className="text-primary hover:underline cursor-pointer">基本</span></LinkTo>(收件匣多選郵件後,一次封存 / 加標籤 / 刪除)</li>
44
- <li><LinkTo kind="Design System/Components/BulkActionBar/展示" name="提示擴選整個資料集"><span className="text-primary hover:underline cursor-pointer">提示擴選整個資料集</span></LinkTo>(資料集共 5370 筆、本頁只顯示 50 筆:本頁全選後浮出提示,可一鍵把選取範圍擴大到全部 5370 筆)</li>
43
+ <li><LinkTo kind="Design System/Components/BulkActionBar/展示" name="基本"><span className="text-primary hover:text-primary-hover cursor-pointer">基本</span></LinkTo>(收件匣多選郵件後,一次封存 / 加標籤 / 刪除)</li>
44
+ <li><LinkTo kind="Design System/Components/BulkActionBar/展示" name="提示擴選整個資料集"><span className="text-primary hover:text-primary-hover cursor-pointer">提示擴選整個資料集</span></LinkTo>(資料集共 5370 筆、本頁只顯示 50 筆:本頁全選後浮出提示,可一鍵把選取範圍擴大到全部 5370 筆)</li>
45
45
  </ul>
46
46
  <p className="text-fg-muted mt-3">判斷不確定:對照 spec.md「何時用 / 何時不用」段。</p>
47
47
  </div>
@@ -201,7 +201,7 @@ export const HintBannerRule: Story = {
201
201
  title={
202
202
  <>
203
203
  已選取本頁全部 50 個。{' '}
204
- <button type="button" className="text-primary hover:underline">
204
+ <button type="button" className="text-primary hover:text-primary-hover">
205
205
  點此選取全部 5370 個項目
206
206
  </button>
207
207
  </>
@@ -76,7 +76,7 @@ benchmark:
76
76
  - `gap-2`(8px)+ `<ButtonDivider />`(自帶 mx-1 = 12px 視覺距離)
77
77
  - `px-[var(--layout-space-loose)] py-[var(--layout-space-tight)]`
78
78
  - 自然高度 56md / 68lg(md Button `--field-height-md` 32/36 + `py-[var(--layout-space-tight)]` 12/16 ×2;對齊 SurfaceFooter / DataTable toolbar canonical)
79
- - `selection.length === 0` → 回 null 不佔 layout
79
+ - `selection.length === 0` **且** `totalSelected` 為 0/未設 → 回 null 不佔 layout(反向選取 all 模式 `totalSelected > 0` 但可見列全 excluded 時仍顯示,見「Extend dataset pattern」)
80
80
  - **寬度邊界**:actions 為單行 flex 排列(`gap-2`),無折行、無內建 overflow 收納;寬度受限場景由 consumer 控制 action 數量
81
81
 
82
82
  ### Slot
@@ -170,6 +170,8 @@ interface BulkActionBarLabels {
170
170
 
171
171
  「本頁全選 → hint 點擊 → 擴選整個 dataset」2-step 後,consumer 把 `totalSelected` 設為 dataset 真總數,count 區改顯示該值(否則 fallback `selection.length`)——避免 Alert 顯「已選 5370」但 bar 仍顯「已選 50」的不同步(2026-05-13 ship)。對齊 Gmail / Linear / Notion 全選 dataset hint pattern。
172
172
 
173
+ **可見性與反向選取(DataTable all 模式,2026-06-22)**:顯示判準 = `selection.length > 0 || (totalSelected ?? 0) > 0`。反向選取(`{ mode:'all', excluded }`)下若可見列全被 excluded、`selection`(可見代表)為空但 `totalSelected > 0`(全集仍有選取),bar **仍顯示**(否則「已選 N 個但 bar 消失」矛盾)。對應 `data-table.spec.md`「L2 選取」inverted 模型。
174
+
173
175
  ---
174
176
 
175
177
  ## a11y 預設
@@ -185,7 +187,7 @@ interface BulkActionBarLabels {
185
187
 
186
188
  ## 視覺與動畫
187
189
 
188
- - **出現 / 消失**:`selection.length` 0→>0 直接 mount;>0→0 null 直接 unmount(無 fade 動畫)。inline composition 下自然 reflow;consumer 需固定高度時自擺 placeholder(見「禁止事項」)
190
+ - **出現 / 消失**:有選取(`selection.length > 0` **或** `totalSelected > 0`)直接 mount;歸零回 null 直接 unmount(無 fade 動畫)。inline composition 下自然 reflow;consumer 需固定高度時自擺 placeholder(見「禁止事項」)
189
191
  - **底色**:**無底色 contrast**,跟 page 同色(`bg-canvas` / `bg-surface` 視 placement 繼承)。對齊 Notion / Linear minimalist — 用文字內容切換呈現「mode」,**不**用底色 highlight。**不像 Polaris 那種顯著底色變化** <!-- @benchmark-unverified: see frontmatter benchmark list for canonical DS source URL -->
190
192
  - **邊界**:**無外框邊界**(融入 page chrome)— 恆 **`border-top` border-divider 切割 layout**(bar 是 page 結構,不是 floating overlay,不用 box-shadow 製造「浮層」誤導)。top-toolbar 變體為未來項(見「Size canonical」)
191
193
  - **與 table 的關係**:inline composition — bar 接在 DataTable 下方,toolbar 永遠保留(見「Placement」)
@@ -83,7 +83,7 @@ export const WithExtendDatasetHint: Story = {
83
83
  <button
84
84
  type="button"
85
85
  onClick={() => { setSelection([]); setAllSelected(false) }}
86
- className="text-primary hover:underline focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring rounded-sm"
86
+ className="text-primary hover:text-primary-hover focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring rounded-sm"
87
87
  >
88
88
  清除選取項目
89
89
  </button>
@@ -94,7 +94,7 @@ export const WithExtendDatasetHint: Story = {
94
94
  <button
95
95
  type="button"
96
96
  onClick={() => setAllSelected(true)}
97
- className="text-primary hover:underline focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring rounded-sm"
97
+ className="text-primary hover:text-primary-hover focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring rounded-sm"
98
98
  >
99
99
  點此選取全部 {TOTAL} 個項目
100
100
  </button>
@@ -82,8 +82,9 @@ const BulkActionBar = React.forwardRef<HTMLDivElement, BulkActionBarProps>(
82
82
  [labelsOverride]
83
83
  )
84
84
 
85
- // selection.length === 0 自動藏(對齊 spec 禁止事項 #3)
86
- if (selection.length === 0) return null
85
+ // selection.length === 0 自動藏(對齊 spec 禁止事項 #3)
86
+ // 反向選取(DataTable all 模式)例外:visible 全被 excluded 但 totalSelected>0 仍有選取 → 顯示(2026-06-22)
87
+ if (selection.length === 0 && (totalSelected ?? 0) === 0) return null
87
88
 
88
89
  return (
89
90
  <div
@@ -51,13 +51,13 @@ export const UsageGuidance: Story = {
51
51
  <p>適合 Button 的真實業務場景(點擊跳轉「展示」頁範例):</p>
52
52
  <ul className="space-y-1">
53
53
  <li>
54
- <LinkTo kind="Design System/Components/Button/展示" name="危險 語意"><span className="text-primary hover:underline font-medium cursor-pointer">Danger 語意</span></LinkTo>
54
+ <LinkTo kind="Design System/Components/Button/展示" name="危險 語意"><span className="text-primary hover:text-primary-hover font-medium cursor-pointer">Danger 語意</span></LinkTo>
55
55
  </li>
56
56
  <li>
57
- <LinkTo kind="Design System/Components/Button/展示" name="滑鼠移過 / 鍵盤聚焦"><span className="text-primary hover:underline font-medium cursor-pointer">滑鼠移過 / 鍵盤聚焦</span></LinkTo>
57
+ <LinkTo kind="Design System/Components/Button/展示" name="滑鼠移過 / 鍵盤聚焦"><span className="text-primary hover:text-primary-hover font-medium cursor-pointer">滑鼠移過 / 鍵盤聚焦</span></LinkTo>
58
58
  </li>
59
59
  <li>
60
- <LinkTo kind="Design System/Components/Button/展示" name="純圖示加 Tooltip"><span className="text-primary hover:underline font-medium cursor-pointer">純圖示加 Tooltip</span></LinkTo>
60
+ <LinkTo kind="Design System/Components/Button/展示" name="純圖示加 Tooltip"><span className="text-primary hover:text-primary-hover font-medium cursor-pointer">純圖示加 Tooltip</span></LinkTo>
61
61
  </li>
62
62
  </ul>
63
63
  <p className="text-fg-muted mt-3">判斷不確定時:對照 spec.md「何時用 / 何時不用」段;若仍不符,改用近親元件(見 <code>Vs*Rule</code> stories)。</p>
@@ -41,13 +41,13 @@ export const UsageGuidance: Story = {
41
41
  <p>適合 Calendar 的真實業務場景(點擊跳轉「展示」頁範例):</p>
42
42
  <ul className="space-y-1">
43
43
  <li>
44
- <LinkTo kind="Design System/Components/Calendar/展示" name="團隊行事曆"><span className="text-primary hover:underline font-medium cursor-pointer">團隊行事曆</span></LinkTo>
44
+ <LinkTo kind="Design System/Components/Calendar/展示" name="團隊行事曆"><span className="text-primary hover:text-primary-hover font-medium cursor-pointer">團隊行事曆</span></LinkTo>
45
45
  </li>
46
46
  <li>
47
- <LinkTo kind="Design System/Components/Calendar/展示" name="內容發佈月曆"><span className="text-primary hover:underline font-medium cursor-pointer">內容發佈月曆</span></LinkTo>
47
+ <LinkTo kind="Design System/Components/Calendar/展示" name="內容發佈月曆"><span className="text-primary hover:text-primary-hover font-medium cursor-pointer">內容發佈月曆</span></LinkTo>
48
48
  </li>
49
49
  <li>
50
- <LinkTo kind="Design System/Components/Calendar/展示" name="空行事曆"><span className="text-primary hover:underline font-medium cursor-pointer">空行事曆</span></LinkTo>
50
+ <LinkTo kind="Design System/Components/Calendar/展示" name="空行事曆"><span className="text-primary hover:text-primary-hover font-medium cursor-pointer">空行事曆</span></LinkTo>
51
51
  </li>
52
52
  </ul>
53
53
  <p className="text-fg-muted mt-3">判斷不確定時:對照 spec.md「何時用 / 何時不用」段;若仍不符,改用近親元件(見下方「vs 近親」)。</p>
@@ -72,8 +72,8 @@ export const UsageGuidance: Story = {
72
72
  <div className="prose prose-sm max-w-prose mb-8">
73
73
  <p>適合 Carousel 的真實業務場景(點擊跳轉「展示」頁範例):</p>
74
74
  <ul className="space-y-1">
75
- <li><LinkTo kind="Design System/Components/Carousel/展示" name="首頁主視覺橫幅"><span className="text-primary hover:underline font-medium cursor-pointer">首頁 Hero Banner</span></LinkTo></li>
76
- <li><LinkTo kind="Design System/Components/Carousel/展示" name="商品圖片輪播"><span className="text-primary hover:underline font-medium cursor-pointer">商品圖片輪播</span></LinkTo></li>
75
+ <li><LinkTo kind="Design System/Components/Carousel/展示" name="首頁主視覺橫幅"><span className="text-primary hover:text-primary-hover font-medium cursor-pointer">首頁 Hero Banner</span></LinkTo></li>
76
+ <li><LinkTo kind="Design System/Components/Carousel/展示" name="商品圖片輪播"><span className="text-primary hover:text-primary-hover font-medium cursor-pointer">商品圖片輪播</span></LinkTo></li>
77
77
  </ul>
78
78
  <p className="text-fg-muted mt-3">判斷不確定時:對照 spec.md「何時用 / 何時不用」段;若仍不符,改用近親元件(見下方 vs 近親 段)。</p>
79
79
  </div>
@@ -149,16 +149,16 @@ export const UsageGuidance: Story = {
149
149
  <p>適合 Chart 的真實業務場景(點擊跳轉「展示」頁範例):</p>
150
150
  <ul className="space-y-1">
151
151
  <li>
152
- <LinkTo kind="Design System/Components/Chart/展示" name="長條 Chart — 月營收"><span className="text-primary hover:underline font-medium cursor-pointer">長條 Chart — 月營收</span></LinkTo>
152
+ <LinkTo kind="Design System/Components/Chart/展示" name="長條 Chart — 月營收"><span className="text-primary hover:text-primary-hover font-medium cursor-pointer">長條 Chart — 月營收</span></LinkTo>
153
153
  </li>
154
154
  <li>
155
- <LinkTo kind="Design System/Components/Chart/展示" name="折線 Chart — 伺服器回應時間"><span className="text-primary hover:underline font-medium cursor-pointer">折線 Chart — 伺服器回應時間</span></LinkTo>
155
+ <LinkTo kind="Design System/Components/Chart/展示" name="折線 Chart — 伺服器回應時間"><span className="text-primary hover:text-primary-hover font-medium cursor-pointer">折線 Chart — 伺服器回應時間</span></LinkTo>
156
156
  </li>
157
157
  <li>
158
- <LinkTo kind="Design System/Components/Chart/展示" name="環圈 Chart — 流量來源分布"><span className="text-primary hover:underline font-medium cursor-pointer">環圈 Chart — 流量來源分布</span></LinkTo>
158
+ <LinkTo kind="Design System/Components/Chart/展示" name="環圈 Chart — 流量來源分布"><span className="text-primary hover:text-primary-hover font-medium cursor-pointer">環圈 Chart — 流量來源分布</span></LinkTo>
159
159
  </li>
160
160
  <li>
161
- <LinkTo kind="Design System/Components/Chart/展示" name="堆疊 面積 — 部門支出"><span className="text-primary hover:underline font-medium cursor-pointer">堆疊 面積 — 部門支出</span></LinkTo>
161
+ <LinkTo kind="Design System/Components/Chart/展示" name="堆疊 面積 — 部門支出"><span className="text-primary hover:text-primary-hover font-medium cursor-pointer">堆疊 面積 — 部門支出</span></LinkTo>
162
162
  </li>
163
163
  </ul>
164
164
  <p className="text-fg-muted mt-3">不確定該不該用 Chart 時:先確認資料是否真的需要視覺化(單一數字、2-3 個小數字或可排序篩選的清單,通常用文字、描述清單或表格更清楚);若資料量夠多且要看趨勢、比例或分布,才用 Chart。</p>
@@ -52,10 +52,10 @@ export const UsageGuidance: Story = {
52
52
  >
53
53
  <ul className="space-y-1">
54
54
  <li>
55
- <LinkTo kind="Design System/Components/Checkbox/展示" name="直式群組"><span className="text-primary hover:underline font-medium cursor-pointer">商品類別的複選清單(直式群組)</span></LinkTo>
55
+ <LinkTo kind="Design System/Components/Checkbox/展示" name="直式群組"><span className="text-primary hover:text-primary-hover font-medium cursor-pointer">商品類別的複選清單(直式群組)</span></LinkTo>
56
56
  </li>
57
57
  <li>
58
- <LinkTo kind="Design System/Components/Checkbox/展示" name="水平排列"><span className="text-primary hover:underline font-medium cursor-pointer">篩選列的少量選項複選(水平排列)</span></LinkTo>
58
+ <LinkTo kind="Design System/Components/Checkbox/展示" name="水平排列"><span className="text-primary hover:text-primary-hover font-medium cursor-pointer">篩選列的少量選項複選(水平排列)</span></LinkTo>
59
59
  </li>
60
60
  </ul>
61
61
  </Rule>
@@ -51,13 +51,13 @@ export const UsageGuidance: Story = {
51
51
  <p>適合 Chip 的真實業務場景(點擊跳轉「展示」頁範例):</p>
52
52
  <ul className="space-y-1">
53
53
  <li>
54
- <LinkTo kind="Design System/Components/Chip/展示" name="預設"><span className="text-primary hover:underline font-medium cursor-pointer">技術文章列表的語言標籤濾鏡(多選)</span></LinkTo>
54
+ <LinkTo kind="Design System/Components/Chip/展示" name="預設"><span className="text-primary hover:text-primary-hover font-medium cursor-pointer">技術文章列表的語言標籤濾鏡(多選)</span></LinkTo>
55
55
  </li>
56
56
  <li>
57
- <LinkTo kind="Design System/Components/Chip/展示" name="帶 Badge"><span className="text-primary hover:underline font-medium cursor-pointer">任務列表的狀態篩選 — Badge 顯示各狀態筆數</span></LinkTo>
57
+ <LinkTo kind="Design System/Components/Chip/展示" name="帶 Badge"><span className="text-primary hover:text-primary-hover font-medium cursor-pointer">任務列表的狀態篩選 — Badge 顯示各狀態筆數</span></LinkTo>
58
58
  </li>
59
59
  <li>
60
- <LinkTo kind="Design System/Components/Chip/展示" name="單選"><span className="text-primary hover:underline font-medium cursor-pointer">程式語言擇一的單選濾鏡(type="single")</span></LinkTo>
60
+ <LinkTo kind="Design System/Components/Chip/展示" name="單選"><span className="text-primary hover:text-primary-hover font-medium cursor-pointer">程式語言擇一的單選濾鏡(type="single")</span></LinkTo>
61
61
  </li>
62
62
  </ul>
63
63
  <p className="text-fg-muted mt-3">判斷不確定時:對照 spec.md「何時用 / 何時不用」段;若仍不符,改用近親元件(見下方「vs 近親元件」段)。</p>
@@ -70,9 +70,9 @@ export const UsageGuidance: Story = {
70
70
  <div className="prose prose-sm max-w-prose mb-8">
71
71
  <p>適合 Coachmark 的真實業務場景(點擊跳轉「展示」頁範例):</p>
72
72
  <ul className="space-y-1">
73
- <li><LinkTo kind="Design System/Components/Coachmark/展示" name="單步驟新功能介紹"><span className="text-primary hover:underline font-medium cursor-pointer">首次推出 AI 助理時 anchor 到入口按鈕介紹(單步驟)</span></LinkTo></li>
74
- <li><LinkTo kind="Design System/Components/Coachmark/展示" name="多步 新手導覽"><span className="text-primary hover:underline font-medium cursor-pointer">新用戶首登的三步功能導覽(多步 Onboarding)</span></LinkTo></li>
75
- <li><LinkTo kind="Design System/Components/Coachmark/展示" name="多步提示"><span className="text-primary hover:underline font-medium cursor-pointer">進階快捷鍵的漸進式提示(多步 Tips)</span></LinkTo></li>
73
+ <li><LinkTo kind="Design System/Components/Coachmark/展示" name="單步驟新功能介紹"><span className="text-primary hover:text-primary-hover font-medium cursor-pointer">首次推出 AI 助理時 anchor 到入口按鈕介紹(單步驟)</span></LinkTo></li>
74
+ <li><LinkTo kind="Design System/Components/Coachmark/展示" name="多步 新手導覽"><span className="text-primary hover:text-primary-hover font-medium cursor-pointer">新用戶首登的三步功能導覽(多步 Onboarding)</span></LinkTo></li>
75
+ <li><LinkTo kind="Design System/Components/Coachmark/展示" name="多步提示"><span className="text-primary hover:text-primary-hover font-medium cursor-pointer">進階快捷鍵的漸進式提示(多步 Tips)</span></LinkTo></li>
76
76
  </ul>
77
77
  <p className="text-fg-muted mt-3">判斷不確定時:對照 spec.md「何時用 / 何時不用」段;若仍不符,改用近親元件(見下方 vs 近親 段)。</p>
78
78
  </div>
@@ -850,7 +850,7 @@ export const StateBehavior = {
850
850
  <button
851
851
  type="button"
852
852
  onClick={() => setClearV(['electronics', 'food', 'lifestyle'])}
853
- className="text-caption text-primary cursor-pointer hover:underline"
853
+ className="text-caption text-primary cursor-pointer hover:text-primary-hover"
854
854
  >
855
855
  重設
856
856
  </button>
@@ -876,7 +876,7 @@ export const StateBehavior = {
876
876
  <button
877
877
  type="button"
878
878
  onClick={() => setDismissV(['electronics', 'food', 'lifestyle'])}
879
- className="text-caption text-primary cursor-pointer hover:underline"
879
+ className="text-caption text-primary cursor-pointer hover:text-primary-hover"
880
880
  >
881
881
  重設
882
882
  </button>
@@ -57,19 +57,19 @@ export const UsageGuidance: Story = {
57
57
  <p>適合 Combobox 的真實業務場景(點擊跳轉「展示」頁範例):</p>
58
58
  <ul className="space-y-1">
59
59
  <li>
60
- <LinkTo kind="Design System/Components/Combobox/展示" name="四模式"><span className="text-primary hover:underline font-medium cursor-pointer">四模式</span></LinkTo>
60
+ <LinkTo kind="Design System/Components/Combobox/展示" name="四模式"><span className="text-primary hover:text-primary-hover font-medium cursor-pointer">四模式</span></LinkTo>
61
61
  </li>
62
62
  <li>
63
- <LinkTo kind="Design System/Components/Combobox/展示" name="尺寸與 Button 對齊"><span className="text-primary hover:underline font-medium cursor-pointer">尺寸與 Button 對齊</span></LinkTo>
63
+ <LinkTo kind="Design System/Components/Combobox/展示" name="尺寸與 Button 對齊"><span className="text-primary hover:text-primary-hover font-medium cursor-pointer">尺寸與 Button 對齊</span></LinkTo>
64
64
  </li>
65
65
  <li>
66
- <LinkTo kind="Design System/Components/Combobox/展示" name="單行 vs 換行"><span className="text-primary hover:underline font-medium cursor-pointer">單行 vs 換行</span></LinkTo>
66
+ <LinkTo kind="Design System/Components/Combobox/展示" name="單行 vs 換行"><span className="text-primary hover:text-primary-hover font-medium cursor-pointer">單行 vs 換行</span></LinkTo>
67
67
  </li>
68
68
  <li>
69
- <LinkTo kind="Design System/Components/Combobox/展示" name="搜尋"><span className="text-primary hover:underline font-medium cursor-pointer">搜尋</span></LinkTo>
69
+ <LinkTo kind="Design System/Components/Combobox/展示" name="搜尋"><span className="text-primary hover:text-primary-hover font-medium cursor-pointer">搜尋</span></LinkTo>
70
70
  </li>
71
71
  <li>
72
- <LinkTo kind="Design System/Components/Combobox/展示" name="DataTable 整合"><span className="text-primary hover:underline font-medium cursor-pointer">DataTable 整合</span></LinkTo>
72
+ <LinkTo kind="Design System/Components/Combobox/展示" name="DataTable 整合"><span className="text-primary hover:text-primary-hover font-medium cursor-pointer">DataTable 整合</span></LinkTo>
73
73
  </li>
74
74
  </ul>
75
75
  <p className="text-fg-muted mt-3">判斷不確定時:對照 spec.md「何時用 / 何時不用」段;若仍不符,改用近親元件(見 <code>Vs*Rule</code> stories)。</p>
@@ -24,16 +24,16 @@ export const UsageGuidance: Story = {
24
24
  <p>適合 Command 的真實業務場景(點擊跳轉「展示」頁範例):</p>
25
25
  <ul className="space-y-1">
26
26
  <li>
27
- <LinkTo kind="Design System/Internal/Command/展示" name="全域指令面板"><span className="text-primary hover:underline font-medium cursor-pointer">全域指令面板(⌘K)— Linear / VS Code 式跨頁搜尋與快速動作</span></LinkTo>
27
+ <LinkTo kind="Design System/Internal/Command/展示" name="全域指令面板"><span className="text-primary hover:text-primary-hover font-medium cursor-pointer">全域指令面板(⌘K)— Linear / VS Code 式跨頁搜尋與快速動作</span></LinkTo>
28
28
  </li>
29
29
  <li>
30
- <LinkTo kind="Design System/Internal/Command/展示" name="行內搜尋清單"><span className="text-primary hover:underline font-medium cursor-pointer">行內搜尋清單 — Gmail 式 sidebar 信件資料夾過濾</span></LinkTo>
30
+ <LinkTo kind="Design System/Internal/Command/展示" name="行內搜尋清單"><span className="text-primary hover:text-primary-hover font-medium cursor-pointer">行內搜尋清單 — Gmail 式 sidebar 信件資料夾過濾</span></LinkTo>
31
31
  </li>
32
32
  <li>
33
- <LinkTo kind="Design System/Internal/Command/展示" name="外觀切換器"><span className="text-primary hover:underline font-medium cursor-pointer">外觀切換器 — 選中立即執行的純動作清單</span></LinkTo>
33
+ <LinkTo kind="Design System/Internal/Command/展示" name="外觀切換器"><span className="text-primary hover:text-primary-hover font-medium cursor-pointer">外觀切換器 — 選中立即執行的純動作清單</span></LinkTo>
34
34
  </li>
35
35
  <li>
36
- <LinkTo kind="Design System/Internal/Command/展示" name="無結果狀態"><span className="text-primary hover:underline font-medium cursor-pointer">搜尋無結果時的空狀態文案(CommandEmpty)</span></LinkTo>
36
+ <LinkTo kind="Design System/Internal/Command/展示" name="無結果狀態"><span className="text-primary hover:text-primary-hover font-medium cursor-pointer">搜尋無結果時的空狀態文案(CommandEmpty)</span></LinkTo>
37
37
  </li>
38
38
  </ul>
39
39
  <p className="text-fg-muted mt-3">判斷不確定時:對照 spec.md「何時用 / 何時不用」段;若仍不符,改用近親元件(見下方「vs 近親」段)。</p>
@@ -64,9 +64,9 @@ export const CompositionRules: Story = {
64
64
  <h4>Pattern 1 — SelectMenu 內嵌 Command(searchable form input)</h4>
65
65
  <p>需要「搜尋 + 選值寫回 form」→ 用 <code>SelectMenu</code>(內部已組合 <code>Popover + Command</code>),<strong>不要</strong>自己組合:</p>
66
66
  <ul>
67
- <li><LinkTo kind="Design System/Components/Combobox/展示" name="四模式"><span className="text-primary hover:underline font-medium cursor-pointer">Combobox</span></LinkTo>——可搜尋多選(如商品類別多選標籤)</li>
68
- <li><LinkTo kind="Design System/Components/Select/展示" name="搜尋"><span className="text-primary hover:underline font-medium cursor-pointer">Select(searchable)</span></LinkTo>——可搜尋單選(如從長國家清單打字篩選)</li>
69
- <li><LinkTo kind="Design System/Components/PeoplePicker/展示" name="單人"><span className="text-primary hover:underline font-medium cursor-pointer">PeoplePicker</span></LinkTo>——人員搜尋(如指派任務負責人)</li>
67
+ <li><LinkTo kind="Design System/Components/Combobox/展示" name="四模式"><span className="text-primary hover:text-primary-hover font-medium cursor-pointer">Combobox</span></LinkTo>——可搜尋多選(如商品類別多選標籤)</li>
68
+ <li><LinkTo kind="Design System/Components/Select/展示" name="搜尋"><span className="text-primary hover:text-primary-hover font-medium cursor-pointer">Select(searchable)</span></LinkTo>——可搜尋單選(如從長國家清單打字篩選)</li>
69
+ <li><LinkTo kind="Design System/Components/PeoplePicker/展示" name="單人"><span className="text-primary hover:text-primary-hover font-medium cursor-pointer">PeoplePicker</span></LinkTo>——人員搜尋(如指派任務負責人)</li>
70
70
  </ul>
71
71
 
72
72
  <h4>Pattern 2 — Popover + Command 組成 Command Palette(Cmd+K)</h4>
@@ -71,13 +71,13 @@ export const UsageGuidance: Story = {
71
71
  <p>適合 DataTable 的真實業務場景(點擊跳轉「展示」頁範例):</p>
72
72
  <ul className="space-y-1">
73
73
  <li>
74
- <LinkTo kind="Design System/Components/DataTable/展示" name="數字靠右對齊"><span className="text-primary hover:underline font-medium cursor-pointer">商品庫存後台 — 單價 / 金額數字靠右對齊</span></LinkTo>
74
+ <LinkTo kind="Design System/Components/DataTable/展示" name="數字靠右對齊"><span className="text-primary hover:text-primary-hover font-medium cursor-pointer">商品庫存後台 — 單價 / 金額數字靠右對齊</span></LinkTo>
75
75
  </li>
76
76
  <li>
77
- <LinkTo kind="Design System/Components/DataTable/展示" name="自動行高"><span className="text-primary hover:underline font-medium cursor-pointer">商品備註長短不一 — 自動行高撐開列高</span></LinkTo>
77
+ <LinkTo kind="Design System/Components/DataTable/展示" name="自動行高"><span className="text-primary hover:text-primary-hover font-medium cursor-pointer">商品備註長短不一 — 自動行高撐開列高</span></LinkTo>
78
78
  </li>
79
79
  <li>
80
- <LinkTo kind="Design System/Components/DataTable/展示" name="空狀態"><span className="text-primary hover:underline font-medium cursor-pointer">目錄尚無商品 — 空狀態引導新增</span></LinkTo>
80
+ <LinkTo kind="Design System/Components/DataTable/展示" 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">判斷不確定時:對照 spec.md「何時用 / 何時不用」段;若仍不符,改用近親元件(見 <code>Vs*Rule</code> stories)。</p>
@@ -263,18 +263,24 @@ DataTable 的 row selection layer。提供 controlled/uncontrolled state + 視
263
263
 
264
264
  **世界級對照**:Material DataGrid `rowSelectionModel` / Polaris IndexTable `selectedResources` / Linear / Notion 全 controlled-first + uncontrolled fallback。AG Grid 的 imperative `gridRef.api` 不採(違背 React idiom + 既有 Field/Switch/Checkbox controllable 慣例)。 <!-- @benchmark-unverified: see frontmatter benchmark list for canonical DS source URL -->
265
265
 
266
- ### 一、State 模式
266
+ ### 一、State 模式(discriminated union,2026-06-22 支援反向選取 inverted)
267
267
 
268
268
  ```ts
269
- selection?: string[] // controlled
270
- defaultSelection?: string[] // uncontrolled
271
- onSelectionChange?: (ids: string[]) => void
272
- selectable?: boolean | 'single' | 'multi' // default 'multi'
269
+ // 選取模型:include(列舉)/ all(反向,全集 − excluded)
270
+ type DataTableSelection =
271
+ | { mode: 'include'; ids: string[] } // 只選 ids 列(預設)
272
+ | { mode: 'all'; excluded: string[] } // 全資料集(filter 後)選取,扣掉 excluded
273
+
274
+ selection?: string[] | DataTableSelection // controlled;傳 string[] = include shorthand(向後相容)
275
+ defaultSelection?: string[] | DataTableSelection // uncontrolled
276
+ onSelectionChange?: (next: DataTableSelection) => void // 一律 emit union
277
+ totalCount?: number // 全集筆數 M(server-side / filter 後);all 模式計數用
278
+ selectable?: boolean | 'single' | 'multi' // default 'multi';single 永遠 include
273
279
  isRowSelectable?: (row: TData) => boolean
274
280
  preserveSelectionOnFilter?: boolean // default false
275
281
  ```
276
282
 
277
- 對齊 `useControllableState` idiom(Field / Switch / Checkbox 已用)。
283
+ 對齊 `useControllableState` idiom(Field / Switch / Checkbox 已用)+ MUI X DataGrid v8 `rowSelectionModel { type:'include'|'exclude', ids }` / AG Grid `selectAll + toggledNodes` 反向選取共識。**計數(consumer)**:`mode==='all' ? totalCount − excluded.length : ids.length`。**向後相容**:傳 `string[]` 自動正規化為 `{ mode:'include' }`;但 `onSelectionChange` 一律 emit union(consumer 讀取端需處理兩 mode)
278
284
 
279
285
  ### 二、Checkbox column
280
286
 
@@ -283,15 +289,16 @@ preserveSelectionOnFilter?: boolean // default false
283
289
  - **顯示時機**:**always visible**(對齊 Linear 2024 / Polaris / Material consensus,不允 hover-show) <!-- @benchmark-unverified: see frontmatter benchmark list for canonical DS source URL -->
284
290
  - **Header tri-state**:none / indeterminate / all,使用既有 Checkbox `indeterminate` prop
285
291
 
286
- ### 三、全選邏輯(2-step pattern)
292
+ ### 三、全選邏輯(2-step pattern + 反向選取 inverted)
287
293
 
288
- 對齊 ref 圖 + Linear / Gmail / Notion canonical: <!-- @benchmark-unverified: see frontmatter benchmark list for canonical DS source URL -->
294
+ 對齊 ref 圖 + Linear / Gmail / Notion 2-step + MUI X v8 / AG Grid inverted: <!-- @benchmark-unverified: see frontmatter benchmark list for canonical DS source URL -->
289
295
 
290
- 1. Header checkbox click(none → all)→ 選**目前可見** rows(filter 後 visible-only)
291
- 2. 全頁可見已選 → BulkActionBar 顯示 hint:「已選取本頁 N 個。**點此選取全部 M 個**」
292
- 3. 點 hint → 擴 dataset 全選,hint 改:「已選取全部 M 個。**清除選取項目**」
296
+ 1. Header checkbox click(none → all)→ 選**目前可見** rows(filter 後 visible-only)= `{ mode:'include', ids:[…visible] }`
297
+ 2. 全頁可見已選 → BulkActionBar hint:「已選取本頁 N 個。**點此選取全部 M 個**」
298
+ 3. 點 hint → consumer `setSelection({ mode:'all', excluded:[] })` 擴 dataset 全選,hint 改:「已選取全部 M 個。**清除選取項目**」
299
+ 4. **反向選取(inverted)**:all 模式下取消勾選某幾筆 → 加進 `excluded`(`選取 = 全集 − excluded`);再勾回 → 移出 `excluded`。對 10k 筆只載 50 筆**不需列舉其餘 ID**,任意 toggle 順序封閉、O(1)。count = `totalCount − excluded.length`,hint 顯示「已選取全部 M 個(排除 K 個)」。
293
300
 
294
- 不直接擴 dataset(避免誤觸大量資料)
301
+ 不**一鍵**直接擴 dataset(避免誤觸大量資料,必先 2-step);擴選後的反向扣除由 inverted 模型自動處理。
295
302
 
296
303
  ### 四、互動
297
304
 
@@ -308,9 +315,10 @@ preserveSelectionOnFilter?: boolean // default false
308
315
 
309
316
  ### 六、Selection × filter / sort 互動
310
317
 
311
- - **filter 套用 → filtered-out 的 selected rows 預設清掉**(對齊 Material / AG Grid / Polaris / GitHub / Gmail consensus) <!-- @benchmark-unverified: see frontmatter benchmark list for canonical DS source URL -->
312
- - **opt-in `preserveSelectionOnFilter={true}`** productivity scope(Linear / Airtable 用法),保留 hidden selected,BulkActionBar 顯示「{visible} selected ({hidden} hidden by filter)
313
- - sort 套用selection 全保留(sort 不影響可見性)
318
+ - **`include` 模式**:filter 套用 → filtered-out 的 selected rows 預設清掉(對齊 Material / AG Grid / Polaris / GitHub / Gmail consensus) <!-- @benchmark-unverified: see frontmatter benchmark list for canonical DS source URL -->
319
+ - **`all`(反向)模式**:語意 = 「全部**符合當前 filter**的列 excluded」→ filter 變動時 selection set filter **自然重算**(M 跟著變),`excluded` 保留不清( filter 掉的 excluded 列無害,回到該 filter 時仍排除);**不**套用上面的 include-mode 清除。consumer 計數用更新後的 `totalCount`。
320
+ - **opt-in `preserveSelectionOnFilter={true}`**(僅 include 模式) productivity scope(Linear / Airtable 用法),保留 hidden selected,BulkActionBar 顯示「{visible} selected ({hidden} hidden by filter)」
321
+ - sort 套用 → selection 全保留(sort 不影響可見性,兩 mode 同)
314
322
 
315
323
  ### 七、BulkActionBar 整合(inline composition canonical)
316
324