@qijenchen/design-system 0.1.0-beta.75 → 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.
Files changed (132) hide show
  1. package/CLAUDE.md +1 -1
  2. package/dist/components/AppShell/app-shell.d.ts +2 -2
  3. package/dist/components/AppShell/app-shell.js.map +1 -1
  4. package/dist/components/Avatar/avatar.js.map +1 -1
  5. package/dist/components/Button/button.d.ts.map +1 -1
  6. package/dist/components/Button/button.js.map +1 -1
  7. package/dist/components/Chart/chart.d.ts +1 -1
  8. package/dist/components/Chart/chart.js.map +1 -1
  9. package/dist/components/Checkbox/checkbox.d.ts +1 -1
  10. package/dist/components/Checkbox/checkbox.js +1 -1
  11. package/dist/components/Checkbox/checkbox.js.map +1 -1
  12. package/dist/components/Combobox/combobox.d.ts +1 -1
  13. package/dist/components/Combobox/combobox.d.ts.map +1 -1
  14. package/dist/components/Combobox/combobox.js +13 -10
  15. package/dist/components/Combobox/combobox.js.map +1 -1
  16. package/dist/components/Command/command.d.ts +1 -1
  17. package/dist/components/Command/command.js +3 -3
  18. package/dist/components/Command/command.js.map +1 -1
  19. package/dist/components/DataTable/cell-registry.d.ts.map +1 -1
  20. package/dist/components/DataTable/cell-registry.js +2 -2
  21. package/dist/components/DataTable/cell-registry.js.map +1 -1
  22. package/dist/components/DatePicker/date-picker.d.ts.map +1 -1
  23. package/dist/components/DatePicker/date-picker.js +2 -2
  24. package/dist/components/DatePicker/date-picker.js.map +1 -1
  25. package/dist/components/DescriptionList/description-list.d.ts +1 -1
  26. package/dist/components/DescriptionList/description-list.js +2 -2
  27. package/dist/components/DescriptionList/description-list.js.map +1 -1
  28. package/dist/components/Empty/empty.d.ts +2 -0
  29. package/dist/components/Empty/empty.d.ts.map +1 -1
  30. package/dist/components/Empty/empty.js.map +1 -1
  31. package/dist/components/Field/field-wrapper.js +4 -4
  32. package/dist/components/Field/field-wrapper.js.map +1 -1
  33. package/dist/components/OverflowIndicator/overflow-indicator.d.ts +1 -1
  34. package/dist/components/OverflowIndicator/overflow-indicator.js +2 -2
  35. package/dist/components/OverflowIndicator/overflow-indicator.js.map +1 -1
  36. package/dist/components/PeoplePicker/people-picker.js +2 -2
  37. package/dist/components/PeoplePicker/people-picker.js.map +1 -1
  38. package/dist/components/ProfileCard/profile-card.d.ts +1 -1
  39. package/dist/components/ProfileCard/profile-card.js +2 -1
  40. package/dist/components/ProfileCard/profile-card.js.map +1 -1
  41. package/dist/components/Rating/rating.js.map +1 -1
  42. package/dist/components/Select/select.js +4 -4
  43. package/dist/components/Select/select.js.map +1 -1
  44. package/dist/components/Textarea/textarea.d.ts +1 -1
  45. package/dist/components/Textarea/textarea.js +2 -2
  46. package/dist/components/Textarea/textarea.js.map +1 -1
  47. package/dist/components/TimePicker/time-picker.d.ts.map +1 -1
  48. package/dist/components/TimePicker/time-picker.js +14 -23
  49. package/dist/components/TimePicker/time-picker.js.map +1 -1
  50. package/dist/components/TreeView/tree-view.d.ts +1 -1
  51. package/dist/components/TreeView/tree-view.js +1 -1
  52. package/dist/components/TreeView/tree-view.js.map +1 -1
  53. package/ds-canonical/fork/governance.lock +1 -1
  54. package/ds-canonical/fork/preamble.md +2 -2
  55. package/ds-canonical/hooks/check_field_controls_contracts.sh +30 -3
  56. package/ds-canonical/references/props-naming.md +15 -1
  57. package/ds-canonical/rules/ui-development.md +2 -2
  58. package/llms-full.txt +7 -2
  59. package/llms.txt +3 -3
  60. package/package.json +1 -1
  61. package/src/components/Alert/alert.anatomy.stories.tsx +4 -4
  62. package/src/components/AppShell/app-shell.spec.md +4 -4
  63. package/src/components/AppShell/app-shell.tsx +2 -2
  64. package/src/components/Avatar/avatar.tsx +1 -1
  65. package/src/components/Breadcrumb/breadcrumb.spec.md +11 -1
  66. package/src/components/Button/button.tsx +0 -10
  67. package/src/components/Calendar/calendar.anatomy.stories.tsx +1 -1
  68. package/src/components/Chart/chart.tsx +1 -1
  69. package/src/components/Checkbox/checkbox.tsx +1 -1
  70. package/src/components/Coachmark/coachmark.anatomy.stories.tsx +1 -1
  71. package/src/components/Coachmark/coachmark.spec.md +2 -2
  72. package/src/components/Combobox/combobox.anatomy.stories.tsx +12 -12
  73. package/src/components/Combobox/combobox.principles.stories.tsx +1 -1
  74. package/src/components/Combobox/combobox.spec.md +1 -1
  75. package/src/components/Combobox/combobox.tsx +25 -14
  76. package/src/components/Command/command.anatomy.stories.tsx +2 -0
  77. package/src/components/Command/command.tsx +2 -2
  78. package/src/components/DataTable/cell-registry.tsx +6 -2
  79. package/src/components/DataTable/data-table-filter-panel.tsx +3 -3
  80. package/src/components/DataTable/data-table.anatomy.stories.tsx +1 -1
  81. package/src/components/DataTable/data-table.css +1 -1
  82. package/src/components/DataTable/data-table.spec.md +2 -2
  83. package/src/components/DateGrid/date-grid.anatomy.stories.tsx +1 -1
  84. package/src/components/DateGrid/date-grid.spec.md +1 -1
  85. package/src/components/DatePicker/date-picker.anatomy.stories.tsx +15 -11
  86. package/src/components/DatePicker/date-picker.spec.md +1 -1
  87. package/src/components/DatePicker/date-picker.tsx +9 -6
  88. package/src/components/DescriptionList/description-list.tsx +1 -1
  89. package/src/components/Dialog/dialog.anatomy.stories.tsx +1 -1
  90. package/src/components/DropdownMenu/dropdown-menu.spec.md +1 -1
  91. package/src/components/Empty/empty.tsx +2 -0
  92. package/src/components/Field/field-controls.spec.md +9 -6
  93. package/src/components/Field/field-wrapper.tsx +4 -4
  94. package/src/components/FileItem/file-item.principles.stories.tsx +1 -0
  95. package/src/components/FileUpload/file-upload.principles.stories.tsx +2 -2
  96. package/src/components/FileUpload/file-upload.spec.md +1 -1
  97. package/src/components/HoverCard/hover-card.principles.stories.tsx +1 -1
  98. package/src/components/Input/input.anatomy.stories.tsx +3 -3
  99. package/src/components/LinkInput/link-input.anatomy.stories.tsx +3 -3
  100. package/src/components/Notice/notice.anatomy.stories.tsx +1 -1
  101. package/src/components/NumberInput/number-input.anatomy.stories.tsx +8 -7
  102. package/src/components/NumberInput/number-input.spec.md +1 -1
  103. package/src/components/OverflowIndicator/overflow-indicator.tsx +1 -1
  104. package/src/components/PeoplePicker/people-picker.spec.md +3 -3
  105. package/src/components/PeoplePicker/people-picker.tsx +6 -6
  106. package/src/components/Popover/popover.principles.stories.tsx +4 -4
  107. package/src/components/ProfileCard/profile-card.anatomy.stories.tsx +1 -1
  108. package/src/components/ProfileCard/profile-card.tsx +1 -1
  109. package/src/components/ProgressBar/progress-bar.spec.md +1 -1
  110. package/src/components/Rating/rating.anatomy.stories.tsx +2 -2
  111. package/src/components/Rating/rating.spec.md +1 -1
  112. package/src/components/Rating/rating.tsx +1 -1
  113. package/src/components/Select/select.anatomy.stories.tsx +18 -18
  114. package/src/components/Select/select.spec.md +1 -1
  115. package/src/components/Select/select.tsx +7 -7
  116. package/src/components/SelectMenu/select-menu.anatomy.stories.tsx +1 -1
  117. package/src/components/Sidebar/sidebar.spec.md +2 -2
  118. package/src/components/Slider/slider.anatomy.stories.tsx +1 -1
  119. package/src/components/Steps/steps.spec.md +2 -2
  120. package/src/components/Tabs/tabs.principles.stories.tsx +1 -1
  121. package/src/components/Tabs/tabs.spec.md +1 -1
  122. package/src/components/Textarea/textarea.tsx +3 -3
  123. package/src/components/TimePicker/time-picker.spec.md +1 -1
  124. package/src/components/TimePicker/time-picker.tsx +11 -12
  125. package/src/components/TreeView/tree-view.tsx +1 -1
  126. package/src/patterns/element-anatomy/item-anatomy.spec.md +1 -1
  127. package/src/patterns/element-anatomy/item-anatomy.stories.tsx +1 -1
  128. package/src/patterns/overlay-surface/overlay-surface.spec.md +1 -0
  129. package/src/patterns/resize-handle/resize-handle.spec.md +1 -1
  130. package/src/tokens/color/semantic.css +1 -1
  131. package/src/tokens/uiSize/uiSize.css +5 -0
  132. package/src/tokens/uiSize/uiSize.spec.md +17 -3
package/llms-full.txt CHANGED
@@ -1,6 +1,6 @@
1
1
  # @qijenchen/design-system — 完整設計參考(llms-full)
2
2
 
3
- > 全 component / pattern 的 variants / sizes / 禁止事項。build-time 從 spec.md frontmatter 生成,禁手改。v0.1.0-beta.75
3
+ > 全 component / pattern 的 variants / sizes / 禁止事項。build-time 從 spec.md frontmatter 生成,禁手改。v0.1.0-beta.76
4
4
 
5
5
  # Components
6
6
 
@@ -21,7 +21,7 @@ Source(AI 讀此看官方範例): src/components/Alert/
21
21
  - **warning**: 警告但非阻斷(方案即將到期、需更新付款方式);最高頻使用
22
22
  - **error**: 錯誤但非阻斷(系統錯誤但可重試、API 失敗摘要);搭配 aria-live=assertive
23
23
 
24
- ## app-shell(family composite)
24
+ ## AppShell(family composite)
25
25
 
26
26
  Storybook: https://ajenchen-design-system.netlify.app
27
27
  Source(AI 讀此看官方範例): src/components/AppShell/
@@ -52,6 +52,11 @@ Source(AI 讀此看官方範例): src/components/Badge/
52
52
  Storybook: https://ajenchen-design-system.netlify.app
53
53
  Source(AI 讀此看官方範例): src/components/Breadcrumb/
54
54
 
55
+ ### Sizes
56
+ - **sm**: Dialog / panel / drawer 內;text-body(14)+ icon 16,建議配 text-h4(20)title。對齊 BREADCRUMB_TEXT_CLASS.sm + BREADCRUMB_ICON_SIZE.sm(breadcrumb.tsx:124-136)
57
+ - **md**: 預設 — 一般頁面 header;text-body(14)+ icon 16,建議配 text-h3(24)title
58
+ - **lg**: Detail page hero / landing;text-body-lg(16)+ icon 20,建議配 text-h2(32)title
59
+
55
60
  ## BulkActionBar(family composite)
56
61
 
57
62
  Storybook: https://ajenchen-design-system.netlify.app
package/llms.txt CHANGED
@@ -1,7 +1,7 @@
1
1
  # @qijenchen/design-system
2
2
 
3
3
  > World-class React design system(Radix/shadcn + Tailwind v4 + 自訂 design token)。
4
- > 54 components + 4 public patterns + design tokens。v0.1.0-beta.75
4
+ > 54 components + 4 public patterns + design tokens。v0.1.0-beta.76
5
5
 
6
6
  本檔由 source(spec.md frontmatter + Storybook index)build-time 自動生成,**禁手改**(CI --check drift gate 守)。
7
7
  每元件 / pattern 的完整 variants / sizes / 禁止事項 全文見 [llms-full.txt](./llms-full.txt)。
@@ -10,11 +10,11 @@
10
10
  ## Components
11
11
  - [Accordion](https://ajenchen-design-system.netlify.app): 見 Storybook / spec — src:components/Accordion
12
12
  - [Alert](https://ajenchen-design-system.netlify.app): variants:neutral/info/success/warning/error — src:components/Alert
13
- - [app-shell](https://ajenchen-design-system.netlify.app): 見 Storybook / spec — src:components/AppShell
13
+ - [AppShell](https://ajenchen-design-system.netlify.app): 見 Storybook / spec — src:components/AppShell
14
14
  - [AspectRatio](https://ajenchen-design-system.netlify.app): 見 Storybook / spec — src:components/AspectRatio
15
15
  - [Avatar](https://ajenchen-design-system.netlify.app): 見 Storybook / spec — src:components/Avatar
16
16
  - [Badge](https://ajenchen-design-system.netlify.app): variants:critical/high/medium/low — src:components/Badge
17
- - [Breadcrumb](https://ajenchen-design-system.netlify.app): 見 Storybook / spec — src:components/Breadcrumb
17
+ - [Breadcrumb](https://ajenchen-design-system.netlify.app): sizes:sm/md/lg — src:components/Breadcrumb
18
18
  - [BulkActionBar](https://ajenchen-design-system.netlify.app): 見 Storybook / spec — src:components/BulkActionBar
19
19
  - [Button](https://ajenchen-design-system.netlify.app): variants:primary/secondary/tertiary/text/link;sizes:xs/sm/md/lg — src:components/Button
20
20
  - [Calendar](https://ajenchen-design-system.netlify.app): 見 Storybook / spec — src:components/Calendar
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@qijenchen/design-system",
3
- "version": "0.1.0-beta.75",
3
+ "version": "0.1.0-beta.76",
4
4
  "private": false,
5
5
  "description": "World-class design system — components, patterns, tokens, hooks (single source of truth for team distribution).",
6
6
  "type": "module",
@@ -128,10 +128,10 @@ export const ColorMatrix: Story = {
128
128
  <thead><tr><Th>Variant</Th><Th>Subtle</Th><Th>Solid</Th></tr></thead>
129
129
  <tbody>
130
130
  <tr><Td mono>neutral</Td><Td mono>bg-muted + border-border;無 status icon(VARIANT_ICON.neutral = null)</Td><Td>bg-surface-raised + data-theme inverse(跟頁面反)</Td></tr>
131
- <tr><Td mono>info</Td><Td mono>bg-info-subtle + border-info-hover + text-info-text</Td><Td mono>bg-info + data-theme="dark"(藍底白字)</Td></tr>
132
- <tr><Td mono>success</Td><Td mono>bg-success-subtle + border-success-hover + text-success-text</Td><Td mono>bg-success + data-theme="dark"(綠底白字)</Td></tr>
133
- <tr><Td mono>warning</Td><Td mono>bg-warning-subtle + border-warning-hover + text-warning-text</Td><Td mono>bg-warning + data-theme="light"(黃底深字)</Td></tr>
134
- <tr><Td mono>error</Td><Td mono>bg-error-subtle + border-error-hover + text-error-text</Td><Td mono>bg-error + data-theme="dark"(橘底白字)</Td></tr>
131
+ <tr><Td mono>info</Td><Td mono>bg-info-subtle + border-[var(--info-text)] + text-info-text</Td><Td mono>bg-info + data-theme="dark"(藍底白字)</Td></tr>
132
+ <tr><Td mono>success</Td><Td mono>bg-success-subtle + border-[var(--success-text)] + text-success-text</Td><Td mono>bg-success + data-theme="dark"(綠底白字)</Td></tr>
133
+ <tr><Td mono>warning</Td><Td mono>bg-warning-subtle + border-[var(--warning-text)] + text-warning-text</Td><Td mono>bg-warning + data-theme="light"(黃底深字)</Td></tr>
134
+ <tr><Td mono>error</Td><Td mono>bg-error-subtle + border-[var(--error-text)] + text-error-text</Td><Td mono>bg-error + data-theme="dark"(橘底白字)</Td></tr>
135
135
  </tbody>
136
136
  </table>
137
137
  </div>
@@ -1,5 +1,5 @@
1
1
  ---
2
- pattern: app-shell
2
+ component: AppShell
3
3
  family: composite # 組合 sidebar+header+main+aside,非 Family 1-4 row element
4
4
  scope: web service page-level layout primitive (sidebar + header + main + aside composition)
5
5
  benchmark:
@@ -58,7 +58,7 @@ benchmark:
58
58
  asideOpen={open} onAsideOpenChange={setOpen} // ⌘. / Ctrl+. toggle (本 AppShell own)
59
59
  sidebar={<Sidebar>...</Sidebar>} // SSOT in components/Sidebar/sidebar.spec.md
60
60
  header={<ChromeHeader>...</ChromeHeader>} // SSOT in patterns/header-canonical/header-canonical.spec.md
61
- aside={<AppShellAside title="Detail">...</AppShellAside>} // 本 pattern own;modal mode 必 title(Sheet a11y)
61
+ aside={<AppShellAside title="Detail">...</AppShellAside>} // 本 composite own;modal mode 必 title(Sheet a11y)
62
62
  >
63
63
  {children} {/* <main> landmark, padding=0,內容遵循 layoutSpace 6 條規則 */}
64
64
  </AppShell>
@@ -288,7 +288,7 @@ Main 內塞什麼(table / field / card / page header / list)的 layout + spacing
288
288
 
289
289
  ## Future extension(目前不定義)
290
290
 
291
- **Multi-sidebar**(Notion / Linear 雙側欄派):**尚未實作**。目前 `sidebar` prop 僅 `React.ReactNode`(單一 slot,無 array signature、無取首位邏輯、無 dev warning)。未來若支援會擴充為 array,SSOT 放 `sidebar.spec.md`,不在本 pattern(per user 2026-05-19「AppShell 不該 customize Sidebar」)。
291
+ **Multi-sidebar**(Notion / Linear 雙側欄派):**尚未實作**。目前 `sidebar` prop 僅 `React.ReactNode`(單一 slot,無 array signature、無取首位邏輯、無 dev warning)。未來若支援會擴充為 array,SSOT 放 `sidebar.spec.md`,不在本 composite(per user 2026-05-19「AppShell 不該 customize Sidebar」)。
292
292
 
293
293
  **Footer**:不 expose slot(per user 2026-05-19「不用 footer」)。Web service 通常不用 footer,consumer 若需要自貼 Main 底。
294
294
 
@@ -342,7 +342,7 @@ Main 內塞什麼(table / field / card / page header / list)的 layout + spacing
342
342
 
343
343
  ## 相關 spec(per codex Layer B 比稿修正 path/case)
344
344
 
345
- - `components/Sidebar/sidebar.spec.md` — sidebar 視覺 + 鍵盤 + mobile + SidebarTrigger Pattern A/B SSOT(本 pattern 必消費)
345
+ - `components/Sidebar/sidebar.spec.md` — sidebar 視覺 + 鍵盤 + mobile + SidebarTrigger Pattern A/B SSOT(本 composite 必消費)
346
346
  - `patterns/header-canonical/header-canonical.spec.md` — header 視覺契約 SSOT
347
347
  - `patterns/overlay-surface/overlay-surface.spec.md` — Aside modal mode 上層 SSOT
348
348
  - `components/Sheet/sheet.spec.md` — modal fallback SSOT(`aria-labelledby` 強制 + `z-50`)
@@ -2,14 +2,14 @@
2
2
  /**
3
3
  * AppShell — web service page-level layout primitive。
4
4
  *
5
- * 組合 Sidebar + ChromeHeader + Aside + main 成完整 page shell。SSOT 邊界:本 pattern only
5
+ * 組合 Sidebar + ChromeHeader + Aside + main 成完整 page shell。SSOT 邊界:本 composite component only
6
6
  * own slot composition + layout mode + Aside responsive mode;不 own sidebar / header /
7
7
  * sheet 視覺(各自 spec own)。
8
8
  *
9
9
  * 對齊 Mantine AppShell compound API + Ant Layout slot 模式 + Material 3 standard/modal
10
10
  * drawer canonical(per spec.md frontmatter cite)。
11
11
  *
12
- * Spec SSOT:`patterns/app-shell/app-shell.spec.md`
12
+ * Spec SSOT:`components/AppShell/app-shell.spec.md`
13
13
  */
14
14
 
15
15
  import * as React from 'react'
@@ -277,7 +277,7 @@ const AvatarInner = React.forwardRef<HTMLDivElement, AvatarProps>(
277
277
  style={{
278
278
  boxShadow: `0 0 0 2px var(--surface-raised, var(--canvas))`,
279
279
  }}
280
- aria-label={`${badgeCount} unread`}
280
+ aria-label={`${badgeCount} unread`} // i18n-allow: DS default(未覆寫英文 'N unread')
281
281
  />
282
282
  )}
283
283
  </div>
@@ -2,9 +2,19 @@
2
2
  component: Breadcrumb
3
3
  family: composite
4
4
  variants: {}
5
- sizes: {}
5
+ sizes:
6
+ sm:
7
+ when: "Dialog / panel / drawer 內;text-body(14)+ icon 16,建議配 text-h4(20)title。對齊 BREADCRUMB_TEXT_CLASS.sm + BREADCRUMB_ICON_SIZE.sm(breadcrumb.tsx:124-136)"
8
+ world-class: ["Atlassian Breadcrumb", "Ant Design Breadcrumb"]
9
+ md:
10
+ when: "預設 — 一般頁面 header;text-body(14)+ icon 16,建議配 text-h3(24)title"
11
+ world-class: ["Ant Design Breadcrumb default", "MUI Breadcrumbs", "Polaris Breadcrumbs"]
12
+ lg:
13
+ when: "Detail page hero / landing;text-body-lg(16)+ icon 20,建議配 text-h2(32)title"
14
+ world-class: ["Carbon Breadcrumb", "Material 3 breadcrumb"]
6
15
  traits:
7
16
  - hasInteractiveStates
17
+ - hasSizes
8
18
  - isStructural
9
19
  benchmark:
10
20
  - Ant Design Breadcrumb: github.com/ant-design/ant-design/tree/master/components/breadcrumb
@@ -313,16 +313,6 @@ export interface ButtonProps
313
313
  fullWidth?: boolean
314
314
  }
315
315
 
316
- /**
317
- * Icon-only padding — calc `(field-height - icon-size) / 2` per size。
318
- *
319
- * 設計:startIcon 到左邊距離 = padding = `(height - icon) / 2`。
320
- * 純 icon-only 時 width = 2*padding + icon = height → **自然正方形**,不需要 aspect-square。
321
- * 有 suffix(badge / endIcon)時 width = 2*pad + icon + gap + suffix > height → **自然長方形**。
322
- * StartIcon 到左邊距離始終不變,形狀自動適應內容。
323
- *
324
- * 用 CSS var 讓 density 切換時 padding 自動跟著算(field-height 會變)。
325
- */
326
316
  // IconOnly 用 padding-free + aspect-square + flex-center 的 Polaris/Atlassian idiom
327
317
  // (M17 SSOT 必可傳播 — 取代 4 個 size 的 magic-number 公式):
328
318
  // - aspect-square 鎖 width=height(來自 h-field-X)
@@ -124,7 +124,7 @@ export const ColorMatrix: Story = {
124
124
  <tr>
125
125
  <Td>Today cell(日期數字)</Td>
126
126
  <Td mono>bg-info · text-on-emphasis · rounded-full · min-w-6 h-6 px-2 · text-body font-medium</Td>
127
- <Td>info-filled pill,固定 h-6 + min-w-6 做圓形 badge(對齊 Google Calendar today pill)</Td>
127
+ <Td>info-filled pill,固定 h-6 + min-w-6 pill badge(對齊 Google Calendar today pill)</Td>
128
128
  </tr>
129
129
  <tr>
130
130
  <Td>Outside day cell</Td>
@@ -15,7 +15,7 @@ import { cn } from '@/lib/utils'
15
15
  * 預設建議使用 --chart-1..5(本 DS 提供的 5 色類別 token)
16
16
  *
17
17
  * ── 視覺 token ──
18
- * Tooltip: bg-surface-raised / border-border / shadow-[elevation-200] / rounded-md
18
+ * Tooltip: bg-surface-raised / border-border / shadow-[var(--elevation-200)] / rounded-md
19
19
  * Legend text: text-fg-secondary / text-caption
20
20
  * Grid: stroke-divider
21
21
  * Axis tick: text-fg-muted / text-caption
@@ -281,7 +281,7 @@ export const checkboxMeta = {
281
281
  // 2026-06-10 修 stale meta:iconSize 對齊 checkIconSize 真值(L49 sm/md=12, lg=16;deep-audit A.1b 抓 metadata drift)
282
282
  sm: { fieldHeight: 28, iconSize: 12, typography: 'body' },
283
283
  md: { fieldHeight: 32, iconSize: 12, typography: 'body' },
284
- lg: { fieldHeight: 36, iconSize: 16, typography: 'body' },
284
+ lg: { fieldHeight: 36, iconSize: 16, typography: 'body-lg' },
285
285
  },
286
286
  states: ['default', 'hover', 'focus-visible', 'disabled'],
287
287
  tokens: {
@@ -167,7 +167,7 @@ export const Inspector: Story = {
167
167
  <tr><Td>Previous / Skip variant</Td><Td mono>tertiary</Td></tr>
168
168
  <tr><Td>Next variant</Td><Td mono>primary</Td></tr>
169
169
  <tr><Td>sideOffset</Td><Td mono>8px(對齊 Popover DS 設計準則)</Td></tr>
170
- <tr><Td>Density</Td><Td mono>繼承 Popover(鎖 md)</Td></tr>
170
+ <tr><Td>Layout-space</Td><Td mono>繼承 Popover(鎖 layout-space md;ui-size 繼承 page)</Td></tr>
171
171
  </tbody>
172
172
  </table>
173
173
  </div>
@@ -193,7 +193,7 @@ Previous(可選)→ Skip(可選)→ Next / Done。對齊 Ant Tour / Intercom con
193
193
  - ❌ **不用 Coachmark 做確認框**——確認破壞性動作必須阻斷流程,改用 `Dialog`
194
194
  - ❌ **Media 區不放互動元件**——按鈕 / 輸入 / checkbox 一律走 footer。Media 是視覺說明不是互動區
195
195
  - ❌ **Description 不寫超過 3 行**——Coachmark 是快速說明,超過 3 行改用 Dialog + 完整 body 或連結到說明文件
196
- - ❌ **不強迫完成 tour**——永遠提供退出機制(Esc / header Close / 第一步 Skip)。沒有退出的 onboarding 讓使用者感到被綁架,反而降低完成率
196
+ - ❌ **不強迫完成 tour**——永遠提供退出機制(Esc / header Close / 第一步 Skip)。沒有退出的 onboarding 讓使用者感到被綁架,反而降低完成率。註:header Close 僅在傳 `kind`(有 header)時才 render;無 `kind` 的 multi-step tour 第 2+ 步退出靠 Esc(永遠可用),multi-step 務必傳 `kind` 以提供 header Close 退出入口
197
197
  - ❌ **單步驟 CTA 不叫 "Next"**——沒有下一步就不用 Next 字眼,用 `'知道了' / 'Got it' / 'Start'`
198
198
  - ❌ **有 Prev 時不同時顯示 Skip**——使用者投入進度後兩個退出路徑衝突(見 CTA 語義表)
199
199
  - ❌ **不自包視覺 token**——bg / shadow / radius / padding 一律繼承 Popover,改視覺就改 Popover
@@ -212,7 +212,7 @@ Previous(可選)→ Skip(可選)→ Next / Done。對齊 Ant Tour / Intercom con
212
212
 
213
213
  ## 邊界狀態
214
214
 
215
- disabled / density 繼承 Popover(density 鎖 md,見 `../Popover/popover.spec.md`);empty(title + description 都不傳)則 Body 不渲染(已於「Props 結構」規定);loading 由 consumer 決定。
215
+ disabled / layout-space 繼承 Popover(鎖 `data-layout-space="md"` 保持 header py-tight 精簡;ui-size / 控件大小繼承 page density),SSOT 見 `tokens/density/density.spec.md` L47;empty(title + description 都不傳)則 Body 不渲染(已於「Props 結構」規定);loading 由 consumer 決定。
216
216
 
217
217
  - **步數 > 5**:元件不機械限制 — `step.total` 照實顯示(如「6 of 8」);≤ 5 是設計建議(見 Multi-step Tour),超過應改靜態 onboarding 頁
218
218
  - **無 `image`**:整個 Media 區不渲染(非 skeleton 佔位,見「Props 結構」)
@@ -39,15 +39,15 @@ const TOKEN_MAP: Record<ModeKey, Record<StateKey, ColorSpec>> = {
39
39
  },
40
40
  readonly: {
41
41
  default: { bg: '--bg-readonly', text: '--foreground', border: 'transparent', icon: '—' },
42
- hover: { bg: '--bg-disabled', text: '--foreground', border: 'transparent', icon: '—' },
43
- focus: { bg: '--bg-disabled', text: '--foreground', border: 'transparent', icon: '—' },
44
- disabled: { bg: '--bg-disabled', text: '--foreground', border: 'transparent', icon: '—' },
42
+ hover: { bg: '--bg-readonly', text: '--foreground', border: 'transparent', icon: '—' },
43
+ focus: { bg: '--bg-readonly', text: '--foreground', border: 'transparent', icon: '—' },
44
+ disabled: { bg: '--bg-readonly', text: '--foreground', border: 'transparent', icon: '—' },
45
45
  },
46
46
  disabled: {
47
- default: { bg: '--bg-disabled', text: '--fg-disabled', border: 'transparent', icon: '' },
48
- hover: { bg: '--bg-disabled', text: '--fg-disabled', border: 'transparent', icon: '' },
49
- focus: { bg: '--bg-disabled', text: '--fg-disabled', border: 'transparent', icon: '' },
50
- disabled: { bg: '--bg-disabled', text: '--fg-disabled', border: 'transparent', icon: '' },
47
+ default: { bg: '--bg-disabled', text: '--fg-disabled', border: 'transparent', icon: '--fg-disabled' },
48
+ hover: { bg: '--bg-disabled', text: '--fg-disabled', border: 'transparent', icon: '--fg-disabled' },
49
+ focus: { bg: '--bg-disabled', text: '--fg-disabled', border: 'transparent', icon: '--fg-disabled' },
50
+ disabled: { bg: '--bg-disabled', text: '--fg-disabled', border: 'transparent', icon: '--fg-disabled' },
51
51
  },
52
52
  }
53
53
 
@@ -270,7 +270,7 @@ export const Overview = {
270
270
  <div className="flex flex-col gap-4">
271
271
  <div className="flex flex-col gap-1">
272
272
  <H3>結構(Anatomy)— readonly / disabled</H3>
273
- <Desc>Tag 沒有 dismiss 按鈕、沒有 clear;ChevronDown 保留為類型身份 indicator(pointer-events-none,readonly fg-muted / disabled fg-disabled)。溢出行為與 edit 相同(+N 指示器)。</Desc>
273
+ <Desc>Tag 沒有 dismiss 按鈕、沒有 clear;readonly 不顯示 ChevronDown(純值、不可開下拉),disabled 保留為類型身份 indicator(pointer-events-none,fg-disabled)。溢出行為與 edit 相同(+N 指示器)。</Desc>
274
274
  </div>
275
275
  <div className="flex gap-8">
276
276
  <div className="flex flex-col gap-2 items-start">
@@ -285,7 +285,7 @@ export const Overview = {
285
285
  style={{ borderColor: `var(--${s.color})`, backgroundColor: `var(--${s.color}-subtle)`, color: `var(--${s.color})` }}>{s.name}</span>
286
286
  ))}
287
287
  </div>
288
- <span className="text-[10px] text-fg-muted font-mono">無 dismiss · 無 clear · chevron=類型 indicator · tagPadding</span>
288
+ <span className="text-[10px] text-fg-muted font-mono">無 dismiss · 無 clear · chevron:readonly 不顯示 / disabled 保留 · tagPadding</span>
289
289
  </div>
290
290
  <div className="flex flex-col gap-2 items-start">
291
291
  <span className="text-[11px] text-fg-secondary font-medium">空值</span>
@@ -466,7 +466,7 @@ const InspectorInner = () => {
466
466
  <PropRow label="Fill"><TokenValue value={colors.bg} /></PropRow>
467
467
  <PropRow label="Text"><TokenValue value={colors.text} /></PropRow>
468
468
  <PropRow label="Stroke"><TokenValue value={colors.border} /></PropRow>
469
- {isEdit && <PropRow label="Icon"><TokenValue value={colors.icon} /></PropRow>}
469
+ {mode !== 'readonly' && <PropRow label="Icon"><TokenValue value={colors.icon} /></PropRow>}
470
470
  </div>
471
471
 
472
472
  {/* LAYOUT */}
@@ -479,7 +479,7 @@ const InspectorInner = () => {
479
479
  }
480
480
  </PropRow>
481
481
  <PropRow label="tagPadding" dot={Z.pad.text}><TkVal token="calc()" value={s.tagPaddingCalc} /></PropRow>
482
- <PropRow label="右側內距">0.75rem (12px)</PropRow>
482
+ <PropRow label="右側內距">var(--field-px) (12px)</PropRow>
483
483
  <PropRow label="Tag 間距" dot={Z.gap.text}>{s.tagGap}</PropRow>
484
484
  <PropRow label="Icon 尺寸" dot={Z.icon.text}>{s.icon}px</PropRow>
485
485
  <PropRow label="Tag 高度" dot={Z.tag.text}>{s.tagHeight} ({s.tagSize})</PropRow>
@@ -677,7 +677,7 @@ export const SizeMatrix = {
677
677
  {SIZES.map((sz) => (
678
678
  <Td key={sz} mono>
679
679
  <div className="text-fg-secondary">paddingRight</div>
680
- <div className="text-fg-muted text-[10px]">0.75rem (12px)</div>
680
+ <div className="text-fg-muted text-[10px]">var(--field-px) (12px)</div>
681
681
  </Td>
682
682
  ))}
683
683
  </tr>
@@ -195,7 +195,7 @@ export const TagOperationRule: Story = {
195
195
 
196
196
  <Rule
197
197
  title="readonly / disabled 的 Tag 沒有任何互動"
198
- note="沒有 dismiss X、沒有 clear;ChevronDown 保留為類型身份 indicator(不可開啟)。Tag 變純顯示,溢出規則仍然套用(+N 指示器可 hover 查看完整)"
198
+ note="沒有 dismiss X、沒有 clear;readonly 不顯示 ChevronDown(純值、不可開啟),disabled 才保留為類型身份 indicator。Tag 變純顯示,溢出規則仍然套用(+N 指示器可 hover 查看完整)"
199
199
  >
200
200
  <Combobox mode="readonly" options={categoryOptions} value={ro} />
201
201
  <Label>↑ 不可移除、不可新增、不可清空——整個 field 變「顯示」</Label>
@@ -116,7 +116,7 @@ value 軸 controlled-only;open 軸方向相反 — **uncontrolled-only**:`defaul
116
116
  ## readonly / disabled 的 Tag
117
117
 
118
118
  - 沒有 dismiss 按鈕——不可操作
119
- - ChevronDown 保留——類型身份 indicator(2026-06-10;`pointer-events-none` 不可開啟新增、readonly `fg-muted` / disabled `fg-disabled`、naked cell 依 `showDisplayEndIcon`)
119
+ - ChevronDown:**readonly 不顯示**(純值、不可開下拉)/ **disabled 保留**(類型身份 indicator,`fg-disabled`,`pointer-events-none`)/ naked cell 依 `showDisplayEndIcon`(2026-06-26)
120
120
  - 沒有 clear 按鈕——不可清除
121
121
  - 溢出行為與 edit 模式相同(+N 指示器)
122
122
 
@@ -448,7 +448,7 @@ export interface ComboboxProps {
448
448
  * **2026-05-13 v2 deprecate path**:原 PeoplePicker pass `{8}` 假設「Combobox tagPadding=4px,4+8=12」
449
449
  * 但 `tagPadding[size]` 是 density-dependent calc `(field-height - icon-size) / 2`,只在 md size +
450
450
  * default density 才 = 4px;其他 size/density 漂 6/8px → 4+8=12 公式破。改 PeoplePicker 直接 inject
451
- * `!px-3` className 到 Combobox Field wrapper(per people-picker.spec.md:94 v2),`tagAreaPaddingLeftPx`
451
+ * `!px-[var(--field-px)]` className 到 Combobox Field wrapper(per people-picker.spec.md:94 v2),`tagAreaPaddingLeftPx`
452
452
  * 走 undefined。Future 仍保留此 prop 給其他 consumer 精準調整 padding,但 PeoplePicker 已不再用。
453
453
  */
454
454
  tagAreaPaddingLeftPx?: number
@@ -488,6 +488,7 @@ function ReadonlyMultiSelect({
488
488
  const variant = variantProp ?? 'default'
489
489
  const sz = size ?? 'md'
490
490
  const iconSize = sz === 'lg' ? 20 : 16
491
+ const tagHeight = sz === 'sm' ? 20 : 24
491
492
  const containerRef = React.useRef<HTMLDivElement>(null)
492
493
  const hasTags = (value?.length ?? 0) > 0
493
494
 
@@ -527,8 +528,10 @@ function ReadonlyMultiSelect({
527
528
  // 2026-05-18 #6A Round 1 Step 1/4(per user 拍板「決策6選a」+ codex M31 Step 5 verdict cite combobox.tsx:451):
528
529
  // readonly/disabled path 對齊 L293 display wrapper 已 ship 的 overflow-hidden fix。
529
530
  // M10 propagation:原 overflow-visible 讓 readonly tag 越界蓋 indicator,跟 display 不對稱。
530
- wrap ? 'flex-wrap py-1' : 'overflow-hidden', className)}
531
- style={{ gap: GAP, ...(wrap ? { height: 'auto' } : undefined) }} data-field-mode={resolvedMode}
531
+ // 2026-06-27 對齊 edit path(L598-617):wrap 時 items-start + chevron self-start/tagHeight 鎖第一行;
532
+ // paddingRight: var(--field-px) re-assert 右緣 12px(tagPadding 對稱 calc 會吃掉右緣,跟 edit 一致)
533
+ wrap ? 'flex-wrap items-start py-1' : 'overflow-hidden', className)}
534
+ style={{ gap: GAP, paddingRight: 'var(--field-px)', ...(wrap ? { height: 'auto' } : undefined) }} data-field-mode={resolvedMode}
532
535
  aria-disabled={resolvedMode === 'disabled' ? true : undefined}>
533
536
  {hasTags ? (
534
537
  <ComboboxTagStack value={value} options={options} tagSize={sz} wrap={wrap}
@@ -536,9 +539,9 @@ function ReadonlyMultiSelect({
536
539
  ) : (
537
540
  <span className="text-fg-muted">{EMPTY_DISPLAY}</span>
538
541
  )}
539
- {/* 2026-06-10 類型身份 indicator 規則:readonly/disabled 保留 chevron(naked cell 依 showDisplayEndIcon);disabled → fg-disabled */}
540
- {(variant === 'naked' ? !!showDisplayEndIcon : true) && (
541
- <ItemSuffix className="pointer-events-none">
542
+ {/* 2026-06-26 類型身份 indicator:edit 顯示 / readonly 不顯示(純值、不可開下拉) / disabled 保留(fg-disabled,對齊原生 <select disabled>);naked cell 依 showDisplayEndIcon */}
543
+ {(variant === 'naked' ? !!showDisplayEndIcon : resolvedMode === 'disabled') && (
544
+ <ItemSuffix className={cn('pointer-events-none', wrap && 'self-start')} style={wrap ? { height: tagHeight } : undefined}>
542
545
  <ChevronDown size={iconSize} className={cn('shrink-0', resolvedMode === 'disabled' ? 'text-fg-disabled' : 'text-fg-muted')} aria-hidden />
543
546
  </ItemSuffix>
544
547
  )}
@@ -575,14 +578,18 @@ function NativeCombobox({
575
578
  const handleRemove = (v: string) => onChange?.(value.filter(x => x !== v))
576
579
  const handleAdd = (v: string) => { if (!value.includes(v)) onChange?.([...value, v]) }
577
580
 
581
+ // React #310 fix(對齊 select.tsx):hooks 必在 conditional early-return 前無條件呼叫。
582
+ // resolvedMode 在 edit↔非edit 切換(<Field mode/disabled> cascade / DataTable cell 進出編輯)時
583
+ // hook 數量不可變動,否則 Rules of Hooks violation → React #310 「rendered fewer/more hooks」crash。
584
+ const selectRef = React.useRef<HTMLSelectElement>(null)
585
+ const tagAreaRef = React.useRef<HTMLDivElement>(null)
586
+
578
587
  if (resolvedMode !== 'edit') {
579
588
  return <ReadonlyMultiSelect mode={resolvedMode} variant={variant} size={size} options={options} value={value} wrap={wrap} className={className} showDisplayEndIcon={showDisplayEndIcon} />
580
589
  }
581
590
 
582
591
  const items = value.map(v => ({ value: v, label: options.find(o => o.value === v)?.label ?? v }))
583
592
  const unselected = options.filter(o => !value.includes(o.value))
584
- const selectRef = React.useRef<HTMLSelectElement>(null)
585
- const tagAreaRef = React.useRef<HTMLDivElement>(null)
586
593
  const tagHeight = size === 'sm' ? 20 : 24
587
594
 
588
595
  const selectDropdown = unselected.length > 0 ? (
@@ -597,7 +604,7 @@ function NativeCombobox({
597
604
  return (
598
605
  <div ref={__triggerRef} className={cn(fieldWrapperStyles({ mode: 'edit', variant: variant, size }), value.length > 0 && tagPadding[size], 'relative',
599
606
  wrap && 'items-start py-1', error && ['border-error hover:border-error-hover', 'focus-within:border-error focus-within:hover:border-error'], className)}
600
- style={{ paddingRight: '0.75rem', ...(wrap ? { height: 'auto' } : undefined) }} data-field-mode="edit" data-error={error ? '' : undefined}
607
+ style={{ paddingRight: 'var(--field-px)', ...(wrap ? { height: 'auto' } : undefined) }} data-field-mode="edit" data-error={error ? '' : undefined}
601
608
  onClick={(e) => { if (e.target === e.currentTarget) { selectRef.current?.showPicker?.(); selectRef.current?.focus() } }}>
602
609
  {/* 2026-05-18 F2 sync(per user verbatim「modifying 修好 PeoplePicker stack 後改壞 Combobox tag display」
603
610
  + 「tag 應該要判斷所在空間最多可以呈現幾個tag(包括+n)去自動判斷何時要變成+n」):
@@ -671,10 +678,10 @@ function CustomCombobox({
671
678
 
672
679
  React.useEffect(() => { if (!open) setSearch('') }, [open])
673
680
 
674
- if (resolvedMode !== 'edit') {
675
- return <ReadonlyMultiSelect mode={resolvedMode} variant={variant} size={size} options={options} value={value} wrap={wrap} className={className} showDisplayEndIcon={showDisplayEndIcon} />
676
- }
677
-
681
+ // React #310 fix(對齊 select.tsx):以下 hooks(useMemo/useRef)必在 conditional early-return 前
682
+ // 無條件呼叫。resolvedMode edit↔非edit 切換時 hook 數量不可變動,否則 Rules of Hooks
683
+ // violation → React #310 「rendered fewer/more hooks」crash(ReadonlyMultiSelect 用不到這些值,
684
+ // 多算無害,對齊 select.tsx hoist pattern)。
678
685
  const items = React.useMemo(
679
686
  () => value.map(v => ({ value: v, label: options.find(o => o.value === v)?.label ?? v })),
680
687
  [value, options]
@@ -708,6 +715,10 @@ function CustomCombobox({
708
715
  [filteredOptions]
709
716
  )
710
717
 
718
+ if (resolvedMode !== 'edit') {
719
+ return <ReadonlyMultiSelect mode={resolvedMode} variant={variant} size={size} options={options} value={value} wrap={wrap} className={className} showDisplayEndIcon={showDisplayEndIcon} />
720
+ }
721
+
711
722
  const chevronEl = <ChevronDown size={iconSize} className={cn('shrink-0 text-fg-muted transition-transform', open && 'rotate-180')} aria-hidden />
712
723
 
713
724
  const trigger = (
@@ -725,7 +736,7 @@ function CustomCombobox({
725
736
  // 2026-05-06 v13.3 SSOT retire:per-control `open && 'border-primary'` 移除。Field default
726
737
  // 統一處理 — open=灰深(data-state)/ focus=藍(focus-within !important)。改一處全 control 跟動。
727
738
  error && ['border-error hover:border-error-hover', 'focus-within:border-error focus-within:hover:border-error'], className)}
728
- style={{ paddingRight: '0.75rem', ...(wrap ? { height: 'auto' } : undefined) }}
739
+ style={{ paddingRight: 'var(--field-px)', ...(wrap ? { height: 'auto' } : undefined) }}
729
740
  data-field-mode="edit" data-error={error ? '' : undefined}
730
741
  // WAI-ARIA APG combobox 鍵盤開啟語意 — 對齊 sibling Select(select.tsx:593-598)。
731
742
  // <div role=combobox> 不像 native <button> 自動 synthesize Enter/Space click,故顯式補:
@@ -14,6 +14,7 @@ import {
14
14
  CommandGroup,
15
15
  CommandItem,
16
16
  CommandSeparator,
17
+ CommandShortcut,
17
18
  } from './command'
18
19
  import { H3, Desc, Td, Th } from '@/design-system/stories-helpers/anatomy/anatomy-utils'
19
20
 
@@ -44,6 +45,7 @@ export const Overview: Story = {
44
45
  <CommandItem>
45
46
  <Settings className="mr-2 h-4 w-4" />
46
47
  前往設定
48
+ <CommandShortcut>⌘K</CommandShortcut>
47
49
  </CommandItem>
48
50
  </CommandGroup>
49
51
  <CommandSeparator />
@@ -135,7 +135,7 @@ const CommandItem = React.forwardRef<
135
135
  <CommandPrimitive.Item
136
136
  ref={ref}
137
137
  className={cn(
138
- "relative flex cursor-default gap-2 select-none items-center rounded-md px-2 py-1.5 text-body outline-none data-[disabled=true]:pointer-events-none data-[selected='true']:bg-neutral-hover data-[selected=true]:text-foreground data-[disabled=true]:text-fg-disabled [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
138
+ "relative flex cursor-default gap-2 select-none items-center rounded-md px-2 py-1.5 text-body outline-none data-[disabled=true]:pointer-events-none data-[selected=true]:bg-neutral-hover data-[selected=true]:text-foreground data-[disabled=true]:text-fg-disabled [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
139
139
  className
140
140
  )}
141
141
  {...props}
@@ -164,7 +164,7 @@ CommandShortcut.displayName = "CommandShortcut"
164
164
  // Phase 2 fill needed: purpose descriptions + when rationale + world-class refs
165
165
  export const commandMeta = {
166
166
  component: 'Command',
167
- family: null, // non-family composite / overlay / layout
167
+ family: 'composite', // 對齊 command.spec.md frontmatter family: composite(SSOT)
168
168
  variants: {
169
169
 
170
170
  },
@@ -174,6 +174,11 @@ function NumberCell({ value, meta, mode, size, isDisabled, onCommit, onCancel, o
174
174
  // currency 透過 columnType-aware prefix:type='currency' → 預設 '$'(可 override)
175
175
  const isCurrency = meta?.type === 'currency'
176
176
  const prefix = isCurrency ? (meta?.prefix ?? '$') : meta?.prefix
177
+ // React #310 fix:useState 必在 display early-return 前無條件呼叫。同一 memo'd cell instance
178
+ // 在 display↔edit 切換時被重用(render site 無 key={mode},data-table.tsx:1352),hook 數量不可變,
179
+ // 否則 Rules of Hooks violation → React #310 crash。對齊 combobox/select hoist pattern。
180
+ const initial = typeof value === 'number' ? value : null
181
+ const [localValue, setLocalValue] = React.useState<number | null>(initial)
177
182
  if (mode === 'display') {
178
183
  return (
179
184
  <NumberInput
@@ -193,8 +198,7 @@ function NumberCell({ value, meta, mode, size, isDisabled, onCommit, onCancel, o
193
198
  // (`value={value ?? ''}`)— 若 NumberCell 以 `defaultValue` 傳入,NumberInput value=undefined → ''
194
199
  // empty。對齊 cell-as-input「edit mode 自動帶入 display 值」(對齊 Notion / Airtable 共識),
195
200
  // 改用 local state controlled。User typing → setLocalValue;blur/Enter → onCommit(localValue)。
196
- const initial = typeof value === 'number' ? value : null
197
- const [localValue, setLocalValue] = React.useState<number | null>(initial)
201
+ // (initial + useState hoist display-return 見上方 React #310 fix。)
198
202
  return (
199
203
  <NumberInput
200
204
  autoFocus
@@ -650,13 +650,13 @@ function ConjunctionLabel({
650
650
  // index === 1:**唯一可改**的 AND/OR Select(連動整 group conjunction)
651
651
  // index ≥ 2:被連動的 row,read-only 顯示當前 conjunction 文字(同 Where 視覺,A6 canonical)
652
652
  // 對齊 Airtable / Notion / Linear 共識 @benchmark-unverified(non-OSS)
653
- // px-3 對齊 Field 內部 padding 12px(Q13)
653
+ // px-[var(--field-px)] 對齊 Field 內部 padding 12px(Q13)
654
654
  if (index === 0) {
655
- return <div className="w-20 shrink-0 text-body text-fg-muted px-3 self-center">Where</div>
655
+ return <div className="w-20 shrink-0 text-body text-fg-muted px-[var(--field-px)] self-center">Where</div>
656
656
  }
657
657
  if (index >= 2) {
658
658
  const label = conjunction === 'and' ? 'And' : 'Or'
659
- return <div className="w-20 shrink-0 text-body text-fg-muted px-3 self-center">{label}</div>
659
+ return <div className="w-20 shrink-0 text-body text-fg-muted px-[var(--field-px)] self-center">{label}</div>
660
660
  }
661
661
  // index === 1:可切換的 AND/OR Select
662
662
  // minRows={2} — And/Or 2 選項,顯式縮 menu 高度避免 reserve 3 row 空白(Q5)
@@ -131,7 +131,7 @@ export const Inspector: Story = {
131
131
  bordered: { control: 'boolean', description: '外框(嵌入已帶框容器時可設 false 避免雙重邊框)' },
132
132
  pinnedLeft: { control: 'boolean', description: 'Pin 產品名稱欄到左側,橫向捲動時保持可見' },
133
133
  pinnedRight: { control: 'boolean', description: 'Pin 上架日期欄到右側' },
134
- inlineEdit: { control: 'boolean', description: 'inline edit 視覺:cell 間加垂直分隔線 + select 欄顯示 chevron' },
134
+ inlineEdit: { control: 'boolean', description: 'inline edit 視覺:cell 間加垂直分隔線(dtCellGrid)。Select chevron column meta.editable 控制(showDisplayEndIcon),非本 prop' },
135
135
  height: {
136
136
  control: 'select',
137
137
  options: ['auto', '300px'],
@@ -7,7 +7,7 @@
7
7
  ───────────────────────────────────────────────────────── */
8
8
 
9
9
  :root {
10
- --table-cell-px: 0.75rem;
10
+ --table-cell-px: var(--field-px); /* 預設取自 --field-px(form/cell 同 12px content gutter SSOT);仍是 DataTable-scoped named token、可獨立 override(spec field-controls.spec.md:330 scoped 決策不變) */
11
11
  }
12
12
 
13
13
  [data-table-size="sm"] {
@@ -386,12 +386,12 @@ ValueShape ↔ DS picker 對照(canonical 2026-05-02):
386
386
 
387
387
  ### 四、UI canonical
388
388
 
389
- - 第 1 row conjunction 是靜態 `Where` label(`px-3` 對齊下方 Field value 起點 = 12px)
389
+ - 第 1 row conjunction 是靜態 `Where` label(`px-[var(--field-px)]` 對齊下方 Field value 起點 = 12px)
390
390
  - field 未選 → operator + value picker disabled;同 group 共用 conjunction(toggle 任一 → flip 整 group)
391
391
  - **空狀態**:無 condition → 只顯 inline `+ 加篩選` CTA(對齊 Notion / Airtable / Linear,**禁止** auto-create 空 row) <!-- @benchmark-unverified: see frontmatter benchmark list for canonical DS source URL -->
392
392
  - **CTA 位置**:緊貼最後一條 row(text variant 輕量,**廢 SurfaceFooter**),條件與「加入」屬同一語境
393
393
  - **Trash / 刪除**:row 是 form-control row → text Button(non Inline Action,違 item-anatomy canonical)
394
- - **And/Or Select** `minRows={2}`(2 選項顯式縮 menu 高度);**Where padding** `px-3` align Field
394
+ - **And/Or Select** `minRows={2}`(2 選項顯式縮 menu 高度);**Where padding** `px-[var(--field-px)]` align Field
395
395
  - Header refresh icon:`value !== defaultValue` 顯;ButtonDivider 串接 close X(對齊欄位顯示 chrome canonical)
396
396
  - **Relative date 群組**:`DATE_RELATIVE_GROUPS` Past / Current / Future,走 `<Select groups>`
397
397
  - Trigger button checked(`aria-pressed`):`value` 有 ≥ 1 active condition → on(語意:資料被篩,獨立於 refresh)
@@ -39,7 +39,7 @@ const PARTS: Record<PartKey, PartSpec> = {
39
39
  caption: { label: '月份標題', bg: 'transparent', text: '--foreground', border: 'transparent', extra: 'text-body font-medium' },
40
40
  nav: { label: 'Nav 按鈕', bg: 'transparent', text: '--foreground', border: 'transparent', extra: 'Button variant=text size=xs iconOnly · hover 藍圈' },
41
41
  weekday: { label: '星期標頭', bg: 'transparent', text: '--foreground', border: 'transparent', extra: 'text-body font-medium · h-7' },
42
- day: { label: '日格(default)', bg: 'transparent', text: '--foreground', border: 'transparent', extra: 'h-field-sm w-field-sm rounded-full' },
42
+ day: { label: '日格(default)', bg: 'transparent', text: '--foreground', border: 'transparent', extra: 'h-field-sm w-[var(--field-height-sm)] rounded-full' },
43
43
  daySelected: { label: 'Selected', bg: '--primary', text: 'white', border: 'transparent' },
44
44
  dayToday: { label: 'Today(未選)', bg: 'transparent', text: '--foreground', border: 'transparent', extra: '數字下方藍色底線(underline bar)' },
45
45
  dayHover: { label: 'Hover', bg: 'transparent', text: '--foreground', border: '--primary', extra: 'hover 藍圈 1.5px(無填底)' },
@@ -82,7 +82,7 @@ DateGrid 是 internal primitive(見「定位」),一般 consumer 經 `DatePicker
82
82
 
83
83
  因此用 `[&[data-range-middle=true]]:xxx` 這種 attribute selector **根本不會生效**(舊版做法錯誤)。
84
84
 
85
- **正解**:把 state 樣式放進 `classNames[state]` 物件,v9 的 `getClassNamesForModifiers` 會在對應 modifier 為 true 時把該 key 的 class 附加到 Day CELL。範例:`classNames.range_middle: 'bg-[var(--color-neutral-2)] [&>button]:!bg-transparent'`。
85
+ **正解**:把 state 樣式放進 `classNames[state]` 物件,v9 的 `getClassNamesForModifiers` 會在對應 modifier 為 true 時把該 key 的 class 附加到 Day CELL。範例:`classNames.range_middle: "before:content-[''] before:absolute before:inset-y-0 before:-inset-x-[2px] before:bg-neutral-selected [&>button]:!bg-transparent"`(用 `before:` pseudo + semantic `--neutral-selected` token,對齊下方 Range track canonical 與 tsx)。
86
86
 
87
87
  `[&>button]:xxx` 從 cell 向內選子 button 用於 button-level 樣式(selected / disabled 的藍底白圓等)。
88
88