@qijenchen/design-system 0.1.0-beta.95 → 0.1.0-beta.99

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 (355) hide show
  1. package/README.md +1 -0
  2. package/dist/components/AccountMenu/account-menu.d.ts +86 -0
  3. package/dist/components/AccountMenu/account-menu.d.ts.map +1 -0
  4. package/dist/components/AccountMenu/account-menu.js +64 -0
  5. package/dist/components/AccountMenu/account-menu.js.map +1 -0
  6. package/dist/components/AccountMenu/index.d.ts +2 -0
  7. package/dist/components/AccountMenu/index.d.ts.map +1 -0
  8. package/dist/components/AccountMenu/index.js +6 -0
  9. package/dist/components/AccountMenu/index.js.map +1 -0
  10. package/dist/components/AppShell/_demo-helpers.d.ts.map +1 -1
  11. package/dist/components/AppShell/app-shell.d.ts.map +1 -1
  12. package/dist/components/AppShell/app-shell.js +2 -2
  13. package/dist/components/AppShell/app-shell.js.map +1 -1
  14. package/dist/components/Badge/badge.d.ts +1 -1
  15. package/dist/components/Button/button.d.ts +3 -3
  16. package/dist/components/Button/button.d.ts.map +1 -1
  17. package/dist/components/Button/button.js +3 -2
  18. package/dist/components/Button/button.js.map +1 -1
  19. package/dist/components/Calendar/calendar.d.ts +10 -21
  20. package/dist/components/Calendar/calendar.d.ts.map +1 -1
  21. package/dist/components/Calendar/calendar.js +52 -59
  22. package/dist/components/Calendar/calendar.js.map +1 -1
  23. package/dist/components/Combobox/combobox.d.ts.map +1 -1
  24. package/dist/components/Combobox/combobox.js +1 -0
  25. package/dist/components/Combobox/combobox.js.map +1 -1
  26. package/dist/components/Command/command.d.ts.map +1 -1
  27. package/dist/components/Command/command.js +3 -1
  28. package/dist/components/Command/command.js.map +1 -1
  29. package/dist/components/DataTable/cell-registry.d.ts.map +1 -1
  30. package/dist/components/DataTable/cell-registry.js +1 -5
  31. package/dist/components/DataTable/cell-registry.js.map +1 -1
  32. package/dist/components/DataTable/data-table-filter-group.d.ts +6 -2
  33. package/dist/components/DataTable/data-table-filter-group.d.ts.map +1 -1
  34. package/dist/components/DataTable/data-table-filter-group.js +32 -27
  35. package/dist/components/DataTable/data-table-filter-group.js.map +1 -1
  36. package/dist/components/DataTable/data-table-filter-labels.d.ts +44 -0
  37. package/dist/components/DataTable/data-table-filter-labels.d.ts.map +1 -0
  38. package/dist/components/DataTable/data-table-filter-labels.js +55 -0
  39. package/dist/components/DataTable/data-table-filter-labels.js.map +1 -0
  40. package/dist/components/DataTable/data-table-filter-panel.d.ts +11 -0
  41. package/dist/components/DataTable/data-table-filter-panel.d.ts.map +1 -1
  42. package/dist/components/DataTable/data-table-filter-panel.js +33 -10
  43. package/dist/components/DataTable/data-table-filter-panel.js.map +1 -1
  44. package/dist/components/DataTable/data-table-filter-value-picker.d.ts +4 -1
  45. package/dist/components/DataTable/data-table-filter-value-picker.d.ts.map +1 -1
  46. package/dist/components/DataTable/data-table-filter-value-picker.js +22 -8
  47. package/dist/components/DataTable/data-table-filter-value-picker.js.map +1 -1
  48. package/dist/components/DataTable/data-table.d.ts.map +1 -1
  49. package/dist/components/DataTable/data-table.js +5 -4
  50. package/dist/components/DataTable/data-table.js.map +1 -1
  51. package/dist/components/DataTable/index.js +2 -0
  52. package/dist/components/DataTable/index.js.map +1 -1
  53. package/dist/components/DatePicker/date-picker.d.ts.map +1 -1
  54. package/dist/components/DatePicker/date-picker.js +187 -156
  55. package/dist/components/DatePicker/date-picker.js.map +1 -1
  56. package/dist/components/Dialog/dialog.d.ts +1 -1
  57. package/dist/components/Dialog/dialog.d.ts.map +1 -1
  58. package/dist/components/Dialog/dialog.js +4 -2
  59. package/dist/components/Dialog/dialog.js.map +1 -1
  60. package/dist/components/DropdownMenu/dropdown-menu.d.ts +4 -1
  61. package/dist/components/DropdownMenu/dropdown-menu.d.ts.map +1 -1
  62. package/dist/components/DropdownMenu/dropdown-menu.js +46 -14
  63. package/dist/components/DropdownMenu/dropdown-menu.js.map +1 -1
  64. package/dist/components/Field/field-context.d.ts +12 -0
  65. package/dist/components/Field/field-context.d.ts.map +1 -1
  66. package/dist/components/Field/field-context.js +10 -1
  67. package/dist/components/Field/field-context.js.map +1 -1
  68. package/dist/components/Field/field-wrapper.d.ts.map +1 -1
  69. package/dist/components/Field/field-wrapper.js +7 -1
  70. package/dist/components/Field/field-wrapper.js.map +1 -1
  71. package/dist/components/Field/field.d.ts.map +1 -1
  72. package/dist/components/Field/field.js +11 -2
  73. package/dist/components/Field/field.js.map +1 -1
  74. package/dist/components/FieldControlGroup/field-control-group.d.ts +2 -5
  75. package/dist/components/FieldControlGroup/field-control-group.d.ts.map +1 -1
  76. package/dist/components/FieldControlGroup/field-control-group.js +2 -2
  77. package/dist/components/FieldControlGroup/field-control-group.js.map +1 -1
  78. package/dist/components/FileUpload/file-upload.js +1 -1
  79. package/dist/components/FileUpload/file-upload.js.map +1 -1
  80. package/dist/components/FileViewer/file-viewer.d.ts +1 -1
  81. package/dist/components/FileViewer/file-viewer.d.ts.map +1 -1
  82. package/dist/components/FileViewer/file-viewer.js +20 -3
  83. package/dist/components/FileViewer/file-viewer.js.map +1 -1
  84. package/dist/components/InlineEdit/inline-edit.d.ts +5 -0
  85. package/dist/components/InlineEdit/inline-edit.d.ts.map +1 -1
  86. package/dist/components/InlineEdit/inline-edit.js +65 -33
  87. package/dist/components/InlineEdit/inline-edit.js.map +1 -1
  88. package/dist/components/Input/input.d.ts.map +1 -1
  89. package/dist/components/Input/input.js +3 -2
  90. package/dist/components/Input/input.js.map +1 -1
  91. package/dist/components/LinkInput/link-input.d.ts.map +1 -1
  92. package/dist/components/LinkInput/link-input.js +2 -1
  93. package/dist/components/LinkInput/link-input.js.map +1 -1
  94. package/dist/components/Menu/menu-item.d.ts.map +1 -1
  95. package/dist/components/Menu/menu-item.js +4 -2
  96. package/dist/components/Menu/menu-item.js.map +1 -1
  97. package/dist/components/PeoplePicker/people-picker.d.ts +2 -0
  98. package/dist/components/PeoplePicker/people-picker.d.ts.map +1 -1
  99. package/dist/components/PeoplePicker/people-picker.js +4 -0
  100. package/dist/components/PeoplePicker/people-picker.js.map +1 -1
  101. package/dist/components/Popover/popover.d.ts.map +1 -1
  102. package/dist/components/Popover/popover.js +4 -2
  103. package/dist/components/Popover/popover.js.map +1 -1
  104. package/dist/components/ScrollArea/scroll-area.d.ts +4 -3
  105. package/dist/components/ScrollArea/scroll-area.d.ts.map +1 -1
  106. package/dist/components/ScrollArea/scroll-area.js +1 -1
  107. package/dist/components/ScrollArea/scroll-area.js.map +1 -1
  108. package/dist/components/Select/select.d.ts.map +1 -1
  109. package/dist/components/Select/select.js +59 -37
  110. package/dist/components/Select/select.js.map +1 -1
  111. package/dist/components/SelectMenu/select-menu.d.ts +2 -0
  112. package/dist/components/SelectMenu/select-menu.d.ts.map +1 -1
  113. package/dist/components/SelectMenu/select-menu.js +3 -1
  114. package/dist/components/SelectMenu/select-menu.js.map +1 -1
  115. package/dist/components/Sheet/sheet.d.ts +1 -1
  116. package/dist/components/Sidebar/sidebar.js +2 -2
  117. package/dist/components/Sidebar/sidebar.js.map +1 -1
  118. package/dist/components/Tabs/tabs.d.ts.map +1 -1
  119. package/dist/components/Tabs/tabs.js +3 -0
  120. package/dist/components/Tabs/tabs.js.map +1 -1
  121. package/dist/components/Tag/tag.d.ts +1 -1
  122. package/dist/components/Tag/tag.d.ts.map +1 -1
  123. package/dist/components/Tag/tag.js +4 -3
  124. package/dist/components/Tag/tag.js.map +1 -1
  125. package/dist/components/Textarea/textarea.d.ts +11 -0
  126. package/dist/components/Textarea/textarea.d.ts.map +1 -1
  127. package/dist/components/Textarea/textarea.js +22 -5
  128. package/dist/components/Textarea/textarea.js.map +1 -1
  129. package/dist/components/TimePicker/time-picker.d.ts.map +1 -1
  130. package/dist/components/TimePicker/time-picker.js +4 -3
  131. package/dist/components/TimePicker/time-picker.js.map +1 -1
  132. package/dist/components/Tooltip/tooltip.d.ts +1 -1
  133. package/dist/components/Tooltip/tooltip.d.ts.map +1 -1
  134. package/dist/components/Tooltip/tooltip.js +26 -1
  135. package/dist/components/Tooltip/tooltip.js.map +1 -1
  136. package/dist/components/TreeView/tree-view.d.ts +2 -0
  137. package/dist/components/TreeView/tree-view.d.ts.map +1 -1
  138. package/dist/components/TreeView/tree-view.js +3 -2
  139. package/dist/components/TreeView/tree-view.js.map +1 -1
  140. package/dist/index.d.ts +5 -3
  141. package/dist/index.d.ts.map +1 -1
  142. package/dist/index.js +4 -0
  143. package/dist/index.js.map +1 -1
  144. package/dist/patterns/element-anatomy/item-anatomy.d.ts +10 -1
  145. package/dist/patterns/element-anatomy/item-anatomy.d.ts.map +1 -1
  146. package/dist/patterns/element-anatomy/item-anatomy.js +77 -29
  147. package/dist/patterns/element-anatomy/item-anatomy.js.map +1 -1
  148. package/dist/patterns/header-canonical/chrome-header.d.ts +11 -4
  149. package/dist/patterns/header-canonical/chrome-header.d.ts.map +1 -1
  150. package/dist/patterns/header-canonical/chrome-header.js +1 -1
  151. package/dist/patterns/header-canonical/chrome-header.js.map +1 -1
  152. package/dist/patterns/overlay-surface/overlay-surface.d.ts +4 -3
  153. package/dist/patterns/overlay-surface/overlay-surface.d.ts.map +1 -1
  154. package/dist/patterns/overlay-surface/overlay-surface.js +1 -0
  155. package/dist/patterns/overlay-surface/overlay-surface.js.map +1 -1
  156. package/dist/patterns/resize-handle/resize-handle.d.ts +9 -11
  157. package/dist/patterns/resize-handle/resize-handle.d.ts.map +1 -1
  158. package/dist/patterns/resize-handle/resize-handle.js +3 -6
  159. package/dist/patterns/resize-handle/resize-handle.js.map +1 -1
  160. package/ds-canonical/adapters/claude/agents/canonical-reviewer.md +1 -1
  161. package/ds-canonical/adapters/claude-root-instructions.md +1 -1
  162. package/ds-canonical/adapters/claude-settings-base.json +2 -5
  163. package/ds-canonical/fork/AGENTS.md +21 -10
  164. package/ds-canonical/fork/codex/hooks.json +1 -1
  165. package/ds-canonical/fork/common/instruction.md +21 -10
  166. package/ds-canonical/fork/consumer/governance-check.mjs +101 -13
  167. package/ds-canonical/fork/consumer/lock.json +14 -15
  168. package/ds-canonical/fork/consumer/managed-files/46f10bc55cdfdf834d0749eebff42ea0bb8576f361b36d2b6ba1851354c50150.blob +26 -3
  169. package/ds-canonical/fork/consumer/managed-files/63061e05969667005020166ea2ae7df3c96663f7a928e7dd3eebc1471022df56.blob +61 -5
  170. package/ds-canonical/fork/consumer/managed-files/93a89f16f6717d1e4e4e27ec20ef916e3355c7aa890ca74cd9b12db30d8a899f.blob +3 -10
  171. package/ds-canonical/fork/consumer/managed-files/bc44cabad181f766611dab6a8f52d81100afadd50ac59799015e8ba7bf46d9eb.blob +3 -2
  172. package/ds-canonical/fork/governance.lock +17 -33
  173. package/ds-canonical/fork/hooks/check_consumer_code_quality.sh +33 -0
  174. package/ds-canonical/fork/hooks/check_pattern_invariants.sh +39 -0
  175. package/ds-canonical/fork/hooks/check_sidebar_menu_button_implicit_wrap.sh +6 -4
  176. package/ds-canonical/fork/launchers/provider-hook-normalization.mjs +5 -11
  177. package/ds-canonical/fork/launchers/settings-hooks.json +2 -2
  178. package/ds-canonical/fork/manifest.json +11 -13
  179. package/ds-canonical/fork/providers/claude/hook-config.json +1 -7
  180. package/ds-canonical/fork/providers/codex/hook-config.json +1 -1
  181. package/ds-canonical/fork/references/story-baseline-registry.json +7 -1
  182. package/ds-canonical/hooks/check_consumer_code_quality.sh +33 -0
  183. package/ds-canonical/hooks/check_pattern_invariants.sh +39 -0
  184. package/ds-canonical/hooks/check_sidebar_menu_button_implicit_wrap.sh +6 -4
  185. package/ds-canonical/hooks/session_start_governance_check.sh +5 -6
  186. package/ds-canonical/hooks/tests/test_check_consumer_code_quality.sh +23 -1
  187. package/ds-canonical/hooks/tests/test_check_pattern_invariants.sh +36 -2
  188. package/ds-canonical/references/audit-coverage-vs-24-checklist.md +1 -1
  189. package/ds-canonical/references/failure-class-registry.json +151 -144
  190. package/ds-canonical/references/preflight-gate-baseline.json +16 -12
  191. package/ds-canonical/references/story-baseline-registry.json +7 -1
  192. package/ds-canonical/rules/meta-patterns.md +3 -3
  193. package/ds-canonical/skills/design-system-audit/SKILL.md +1 -1
  194. package/ds-canonical/skills/design-system-audit/references/audit-prompts.md +1 -1
  195. package/ds-canonical/templates/dashboard-app.tsx +31 -8
  196. package/llms-full.txt +6 -1
  197. package/llms.txt +2 -1
  198. package/package.json +1 -1
  199. package/src/components/Accordion/accordion.principles.stories.tsx +16 -26
  200. package/src/components/Accordion/accordion.stories.tsx +22 -40
  201. package/src/components/AccountMenu/account-menu.anatomy.stories.tsx +115 -0
  202. package/src/components/AccountMenu/account-menu.principles.stories.tsx +99 -0
  203. package/src/components/AccountMenu/account-menu.spec.md +101 -0
  204. package/src/components/AccountMenu/account-menu.stories.tsx +61 -0
  205. package/src/components/AccountMenu/account-menu.tsx +170 -0
  206. package/src/components/AccountMenu/index.ts +5 -0
  207. package/src/components/AppShell/_demo-helpers.tsx +53 -53
  208. package/src/components/AppShell/app-shell.spec.md +5 -1
  209. package/src/components/AppShell/app-shell.tsx +14 -12
  210. package/src/components/Avatar/avatar.principles.stories.tsx +4 -4
  211. package/src/components/Avatar/avatar.stories.tsx +20 -5
  212. package/src/components/Breadcrumb/breadcrumb.spec.md +1 -1
  213. package/src/components/Button/button.spec.md +1 -1
  214. package/src/components/Button/button.stories.tsx +21 -9
  215. package/src/components/Button/button.tsx +5 -4
  216. package/src/components/Calendar/calendar.anatomy.stories.tsx +19 -17
  217. package/src/components/Calendar/calendar.principles.stories.tsx +5 -6
  218. package/src/components/Calendar/calendar.spec.md +25 -29
  219. package/src/components/Calendar/calendar.stories.tsx +5 -7
  220. package/src/components/Calendar/calendar.tsx +75 -87
  221. package/src/components/Carousel/carousel.principles.stories.tsx +5 -1
  222. package/src/components/Chart/chart.spec.md +1 -1
  223. package/src/components/Checkbox/checkbox.anatomy.stories.tsx +14 -5
  224. package/src/components/Chip/chip.spec.md +1 -1
  225. package/src/components/CircularProgress/circular-progress.anatomy.stories.tsx +7 -6
  226. package/src/components/CircularProgress/circular-progress.principles.stories.tsx +8 -6
  227. package/src/components/CircularProgress/circular-progress.stories.tsx +3 -3
  228. package/src/components/Combobox/combobox.tsx +1 -0
  229. package/src/components/Command/command.spec.md +1 -0
  230. package/src/components/Command/command.tsx +4 -1
  231. package/src/components/DataTable/cell-registry.tsx +3 -7
  232. package/src/components/DataTable/data-table-filter-group.tsx +44 -27
  233. package/src/components/DataTable/data-table-filter-labels.ts +100 -0
  234. package/src/components/DataTable/data-table-filter-panel.tsx +48 -8
  235. package/src/components/DataTable/data-table-filter-value-picker.tsx +25 -8
  236. package/src/components/DataTable/data-table.spec.md +18 -1
  237. package/src/components/DataTable/data-table.stories.tsx +346 -33
  238. package/src/components/DataTable/data-table.tsx +11 -4
  239. package/src/components/DataTable/filter-operators.spec.md +2 -2
  240. package/src/components/DateGrid/date-grid.anatomy.stories.tsx +24 -20
  241. package/src/components/DateGrid/date-grid.principles.stories.tsx +27 -21
  242. package/src/components/DateGrid/date-grid.stories.tsx +22 -13
  243. package/src/components/DatePicker/date-picker.anatomy.stories.tsx +3 -3
  244. package/src/components/DatePicker/date-picker.spec.md +4 -4
  245. package/src/components/DatePicker/date-picker.stories.tsx +33 -9
  246. package/src/components/DatePicker/date-picker.tsx +210 -147
  247. package/src/components/Dialog/dialog.spec.md +1 -1
  248. package/src/components/Dialog/dialog.tsx +8 -2
  249. package/src/components/DropdownMenu/dropdown-menu.anatomy.stories.tsx +1 -1
  250. package/src/components/DropdownMenu/dropdown-menu.spec.md +5 -0
  251. package/src/components/DropdownMenu/dropdown-menu.stories.tsx +3 -17
  252. package/src/components/DropdownMenu/dropdown-menu.tsx +58 -16
  253. package/src/components/Field/field-context.ts +25 -1
  254. package/src/components/Field/field-wrapper.tsx +7 -1
  255. package/src/components/Field/field.spec.md +8 -1
  256. package/src/components/Field/field.tsx +14 -2
  257. package/src/components/FieldControlGroup/field-control-group.anatomy.stories.tsx +3 -3
  258. package/src/components/FieldControlGroup/field-control-group.spec.md +2 -3
  259. package/src/components/FieldControlGroup/field-control-group.stories.tsx +2 -1
  260. package/src/components/FieldControlGroup/field-control-group.tsx +6 -9
  261. package/src/components/FileItem/file-item.spec.md +1 -1
  262. package/src/components/FileItem/file-item.stories.tsx +1 -1
  263. package/src/components/FileUpload/file-upload.stories.tsx +32 -0
  264. package/src/components/FileUpload/file-upload.tsx +1 -1
  265. package/src/components/FileViewer/file-viewer.anatomy.stories.tsx +1 -1
  266. package/src/components/FileViewer/file-viewer.spec.md +1 -1
  267. package/src/components/FileViewer/file-viewer.stories.tsx +7 -0
  268. package/src/components/FileViewer/file-viewer.tsx +23 -5
  269. package/src/components/InlineEdit/inline-edit.spec.md +12 -0
  270. package/src/components/InlineEdit/inline-edit.stories.tsx +30 -19
  271. package/src/components/InlineEdit/inline-edit.tsx +94 -31
  272. package/src/components/Input/input.anatomy.stories.tsx +28 -12
  273. package/src/components/Input/input.principles.stories.tsx +23 -18
  274. package/src/components/Input/input.stories.tsx +9 -1
  275. package/src/components/Input/input.tsx +7 -2
  276. package/src/components/LinkInput/link-input.principles.stories.tsx +7 -7
  277. package/src/components/LinkInput/link-input.spec.md +1 -1
  278. package/src/components/LinkInput/link-input.tsx +5 -1
  279. package/src/components/Menu/menu-item.anatomy.stories.tsx +1 -1
  280. package/src/components/Menu/menu-item.tsx +9 -2
  281. package/src/components/NumberInput/number-input.anatomy.stories.tsx +44 -10
  282. package/src/components/NumberInput/number-input.principles.stories.tsx +20 -14
  283. package/src/components/NumberInput/number-input.spec.md +1 -1
  284. package/src/components/NumberInput/number-input.stories.tsx +12 -5
  285. package/src/components/PeoplePicker/people-picker.anatomy.stories.tsx +7 -0
  286. package/src/components/PeoplePicker/people-picker.spec.md +3 -1
  287. package/src/components/PeoplePicker/people-picker.tsx +5 -0
  288. package/src/components/Popover/popover.tsx +7 -2
  289. package/src/components/ProgressBar/progress-bar.principles.stories.tsx +14 -12
  290. package/src/components/RadioGroup/radio-group.anatomy.stories.tsx +5 -1
  291. package/src/components/RadioGroup/radio-group.spec.md +1 -1
  292. package/src/components/Rating/rating.principles.stories.tsx +2 -2
  293. package/src/components/ScrollArea/scroll-area.anatomy.stories.tsx +1 -1
  294. package/src/components/ScrollArea/scroll-area.spec.md +3 -3
  295. package/src/components/ScrollArea/scroll-area.tsx +5 -4
  296. package/src/components/Select/select.principles.stories.tsx +4 -3
  297. package/src/components/Select/select.stories.tsx +7 -6
  298. package/src/components/Select/select.tsx +61 -29
  299. package/src/components/SelectMenu/select-menu.spec.md +1 -1
  300. package/src/components/SelectMenu/select-menu.tsx +4 -1
  301. package/src/components/SelectionControl/selection-item.anatomy.stories.tsx +19 -8
  302. package/src/components/SelectionControl/selection-item.stories.tsx +7 -7
  303. package/src/components/Sheet/sheet.spec.md +1 -1
  304. package/src/components/Sidebar/sidebar.spec.md +3 -2
  305. package/src/components/Sidebar/sidebar.stories.tsx +67 -37
  306. package/src/components/Sidebar/sidebar.tsx +3 -3
  307. package/src/components/Slider/slider.principles.stories.tsx +9 -9
  308. package/src/components/Steps/steps.spec.md +1 -0
  309. package/src/components/Steps/steps.stories.tsx +18 -1
  310. package/src/components/Switch/switch.anatomy.stories.tsx +11 -11
  311. package/src/components/Switch/switch.principles.stories.tsx +11 -8
  312. package/src/components/Tabs/tabs.principles.stories.tsx +24 -0
  313. package/src/components/Tabs/tabs.spec.md +6 -4
  314. package/src/components/Tabs/tabs.tsx +11 -0
  315. package/src/components/Tag/tag.anatomy.stories.tsx +5 -2
  316. package/src/components/Tag/tag.spec.md +4 -3
  317. package/src/components/Tag/tag.stories.tsx +19 -0
  318. package/src/components/Tag/tag.tsx +14 -5
  319. package/src/components/Textarea/textarea.anatomy.stories.tsx +15 -9
  320. package/src/components/Textarea/textarea.principles.stories.tsx +21 -5
  321. package/src/components/Textarea/textarea.spec.md +10 -1
  322. package/src/components/Textarea/textarea.tsx +38 -5
  323. package/src/components/TimePicker/time-picker.tsx +11 -5
  324. package/src/components/Tooltip/tooltip.spec.md +10 -0
  325. package/src/components/Tooltip/tooltip.tsx +34 -3
  326. package/src/components/TreeView/tree-view.spec.md +3 -3
  327. package/src/components/TreeView/tree-view.stories.tsx +68 -9
  328. package/src/components/TreeView/tree-view.tsx +7 -2
  329. package/src/index.ts +9 -1
  330. package/src/patterns/element-anatomy/inline-action.spec.md +12 -8
  331. package/src/patterns/element-anatomy/item-anatomy.spec.md +8 -1
  332. package/src/patterns/element-anatomy/item-anatomy.stories.tsx +24 -5
  333. package/src/patterns/element-anatomy/item-anatomy.tsx +120 -35
  334. package/src/patterns/element-anatomy/truncated-text.spec.md +13 -2
  335. package/src/patterns/header-canonical/chrome-header.tsx +14 -5
  336. package/src/patterns/header-canonical/header-canonical.spec.md +10 -7
  337. package/src/patterns/header-canonical/header-canonical.stories.tsx +77 -0
  338. package/src/patterns/horizontal-overflow/horizontal-overflow.stories.tsx +2 -0
  339. package/src/patterns/overlay-surface/overlay-surface.spec.md +1 -0
  340. package/src/patterns/overlay-surface/overlay-surface.stories.tsx +17 -1
  341. package/src/patterns/overlay-surface/overlay-surface.tsx +8 -4
  342. package/src/patterns/resize-handle/resize-handle.spec.md +11 -13
  343. package/src/patterns/resize-handle/resize-handle.stories.tsx +1 -3
  344. package/src/patterns/resize-handle/resize-handle.tsx +13 -18
  345. package/src/tokens/color/semantic.css +3 -1
  346. package/src/tokens/layoutSpace/layoutSpace.stories.tsx +1 -1
  347. package/src/tokens/opacity/opacity.stories.tsx +1 -1
  348. package/src/tokens/radius/radius.stories.tsx +3 -3
  349. package/src/tokens/uiSize/uiSize.css +11 -0
  350. package/src/tokens/uiSize/uiSize.spec.md +1 -0
  351. package/ds-canonical/commands/gov-status.md +0 -9
  352. package/ds-canonical/fork/consumer/managed-files/77e06f62670dfee01e9b5e8b0f8dad085ce711d75538c1fb0679d9818acaca82.blob +0 -424
  353. package/ds-canonical/fork/preamble.md +0 -500
  354. package/ds-canonical/hooks/check_post_main_ssot_propagate.sh +0 -8
  355. package/ds-canonical/hooks/tests/test_check_post_main_ssot_propagate.sh +0 -37
@@ -122,7 +122,7 @@ export const UsageGuidance: Story = {
122
122
  note="status 三選一是系統決定的語意。consumer 要紅、綠、藍以外的色 → 提到系統層討論是否新增 status,不要每個消費者自己用 className override fill 色。"
123
123
  >
124
124
  <Frame>
125
- <ProgressBar value={55} status="inProgress" />
125
+ <ProgressBar value={55} status="inProgress" aria-label="標準進度色示範" />
126
126
  </Frame>
127
127
  <Label>✅ 走 status token(info / success / error),不 override 色值</Label>
128
128
  </Rule>
@@ -139,7 +139,7 @@ export const UsageGuidance: Story = {
139
139
  <span className="text-body flex-1">匯出客戶名單 CSV</span>
140
140
  <span className="text-caption text-fg-muted tabular-nums">812 / 1,250 筆</span>
141
141
  </div>
142
- <ProgressBar value={65} status="inProgress" />
142
+ <ProgressBar value={65} status="inProgress" aria-label="匯出客戶名單進度" />
143
143
  </Frame>
144
144
  <Label>✅ CSV 匯出 / 批次處理:總量已知 → ProgressBar(檔案上傳改用 FileItem,見下方)</Label>
145
145
 
@@ -159,7 +159,7 @@ export const UsageGuidance: Story = {
159
159
  <FileText size={16} className="text-fg-muted" />
160
160
  <span className="text-body flex-1">產生季度報表中...</span>
161
161
  </div>
162
- <ProgressBar value={0} status="inProgress" />
162
+ <ProgressBar value={0} status="inProgress" aria-label="無法量化的報表產生進度(誤用)" />
163
163
  </Frame>
164
164
  <Label warn>❌ 若無法量化卻硬用 ProgressBar(永遠卡 0% 或亂跳):使用者會以為壞掉 → 改用 CircularProgress(indeterminate)</Label>
165
165
  </Rule>
@@ -174,7 +174,7 @@ export const UsageGuidance: Story = {
174
174
  <span className="text-body flex-1">附件_會議記錄.docx</span>
175
175
  <span className="text-caption text-fg-muted">58%</span>
176
176
  </div>
177
- <ProgressBar value={58} status="inProgress" />
177
+ <ProgressBar value={58} status="inProgress" aria-label="附件上傳進度(自組誤用)" />
178
178
  </Frame>
179
179
  <Label warn>❌ Consumer 自組檔名 + Paperclip + ProgressBar 做上傳列表 → 應該用 FileItem</Label>
180
180
 
@@ -184,7 +184,7 @@ export const UsageGuidance: Story = {
184
184
  <span className="text-body-lg font-medium flex-1">匯入客戶名單</span>
185
185
  <span className="text-caption text-fg-muted tabular-nums">812 / 1,250 筆</span>
186
186
  </div>
187
- <ProgressBar value={65} status="inProgress" affix="value" />
187
+ <ProgressBar value={65} status="inProgress" affix="value" aria-label="匯入客戶名單進度" />
188
188
  <p className="text-footnote text-fg-muted mt-2">處理中,預計剩餘 28 秒</p>
189
189
  </Frame>
190
190
  <Label>✅ CSV 批次匯入 / 報表生成 → 直接用 ProgressBar(非檔案情境,FileItem 不適用)</Label>
@@ -207,7 +207,7 @@ export const StatusRule: Story = {
207
207
  <Paperclip size={16} className="text-fg-muted" />
208
208
  <span className="text-body flex-1">Q1_財報.xlsx</span>
209
209
  </div>
210
- <ProgressBar value={42} status="inProgress" affix="value" />
210
+ <ProgressBar value={42} status="inProgress" affix="value" aria-label="Q1 財報上傳進度" />
211
211
  </Frame>
212
212
  <Label>✅ 上傳中用 inProgress,affix 顯示進度百分比</Label>
213
213
 
@@ -216,7 +216,7 @@ export const StatusRule: Story = {
216
216
  <Paperclip size={16} className="text-fg-muted" />
217
217
  <span className="text-body flex-1">簡報_final.pptx</span>
218
218
  </div>
219
- <ProgressBar value={100} status="success" affix="status-icon" />
219
+ <ProgressBar value={100} status="success" affix="status-icon" aria-label="簡報上傳完成" />
220
220
  </Frame>
221
221
  <Label>✅ 完成用 success + 勾 icon(終態指示)</Label>
222
222
 
@@ -226,7 +226,7 @@ export const StatusRule: Story = {
226
226
  <span className="text-body flex-1">影片素材.mp4</span>
227
227
  <span className="text-caption text-error">檔案過大</span>
228
228
  </div>
229
- <ProgressBar value={68} status="error" affix="status-icon" />
229
+ <ProgressBar value={68} status="error" affix="status-icon" aria-label="影片素材上傳失敗" />
230
230
  </Frame>
231
231
  <Label>✅ 失敗用 error + 叉 icon,旁邊可補上具體錯誤說明</Label>
232
232
  </Rule>
@@ -240,7 +240,7 @@ export const StatusRule: Story = {
240
240
  <span className="text-body flex-1">儲存空間</span>
241
241
  <span className="text-caption text-error tabular-nums">95% 使用</span>
242
242
  </div>
243
- <ProgressBar value={95} status="error" />
243
+ <ProgressBar value={95} status="error" aria-label="儲存空間使用率" />
244
244
  </Frame>
245
245
  <Label>✅ 配額快滿:consumer 判斷「95% 以上顯示警示」後自行把 status 切成 error</Label>
246
246
  </Rule>
@@ -260,7 +260,7 @@ export const AffixRule: Story = {
260
260
  <div className="flex items-center gap-2 mb-2">
261
261
  <span className="text-body flex-1">儲存空間</span>
262
262
  </div>
263
- <ProgressBar value={78} status="inProgress" affix="value" />
263
+ <ProgressBar value={78} status="inProgress" affix="value" aria-label="儲存空間使用率" />
264
264
  </Frame>
265
265
  <Label>✅ 配額顯示:affix=value,使用者要知道確切百分比</Label>
266
266
 
@@ -269,7 +269,7 @@ export const AffixRule: Story = {
269
269
  <Paperclip size={16} className="text-fg-muted" />
270
270
  <span className="text-body flex-1">提案書_終版.pdf</span>
271
271
  </div>
272
- <ProgressBar value={100} status="success" affix="status-icon" />
272
+ <ProgressBar value={100} status="success" affix="status-icon" aria-label="提案書上傳完成" />
273
273
  </Frame>
274
274
  <Label>✅ 上傳完成:affix=status-icon,終態不需要再看百分比</Label>
275
275
  </Rule>
@@ -286,6 +286,7 @@ export const AffixRule: Story = {
286
286
  <ProgressBar
287
287
  value={42}
288
288
  status="inProgress"
289
+ aria-label="設計規範上傳進度"
289
290
  affix={<Button variant="text" size="xs" iconOnly startIcon={X} aria-label="取消上傳" />}
290
291
  />
291
292
  </Frame>
@@ -299,6 +300,7 @@ export const AffixRule: Story = {
299
300
  <ProgressBar
300
301
  value={66}
301
302
  status="inProgress"
303
+ aria-label="影片素材上傳進度"
302
304
  affix={<span className="text-caption text-fg-muted tabular-nums shrink-0">3.3 / 5.0 MB</span>}
303
305
  />
304
306
  </Frame>
@@ -315,7 +317,7 @@ export const AffixRule: Story = {
315
317
  <span className="text-body flex-1">附件.zip</span>
316
318
  <span className="text-caption text-fg-muted tabular-nums">55%</span>
317
319
  </div>
318
- <ProgressBar value={55} status="inProgress" />
320
+ <ProgressBar value={55} status="inProgress" aria-label="附件上傳進度" />
319
321
  </Frame>
320
322
  <Label>✅ 上方已有 55% 文字 → bar 不加 affix,避免重複</Label>
321
323
  </Rule>
@@ -429,7 +429,11 @@ export const ColorMatrix = {
429
429
  return (
430
430
  <td key={st} className="p-3 border-b border-divider align-top min-w-[160px]">
431
431
  <RadioGroup defaultValue={isChecked ? 'demo' : undefined}>
432
- <RadioGroupItem value="demo" disabled={isDisabled} />
432
+ <RadioGroupItem
433
+ value="demo"
434
+ disabled={isDisabled}
435
+ aria-label={`${c} ${st} Radio`}
436
+ />
433
437
  </RadioGroup>
434
438
  <TokenAnnotation colors={TOKEN_MAP[c][st]} />
435
439
  </td>
@@ -111,7 +111,7 @@ Item-level default / hover / checked / disabled **色彩**與 Checkbox 共用同
111
111
  - **No value selected**:`value=null` 為合法 initial state(所有 radio 都 unchecked);Field validation 在 required + null 時觸發 error。
112
112
  - **極長 label / description**:預設不截斷、自然換行(`labelMaxLines` / `descMaxLines` 預設 `'none'`,Clamp 政策 SSOT 見 `checkbox.spec.md`「Clamp 政策」)。
113
113
  - **icon / avatar prefix**:`RadioGroupItem` 一級 `icon` / `avatar` props 轉發 `SelectionItem` prefix 槽(2026-06-12 M30;規則 SSOT 見 `checkbox.spec.md` 同段 + `selection-item.spec.md`)。
114
- - **RTL**:未實作方向鏡像;RTL 屬 DS-wide 決策,未定(與 Chip / Breadcrumb 同口徑)。
114
+ - **RTL**:不支援;全域 LTR-only compatibility contract 見 `packages/design-system/README.md#compatibility-matrix`。
115
115
  - **Dark mode / density**:走 SelectionItem semantic token 自動 adapt;垂直行距由 SelectionItem `py = (field-height − 1lh) / 2` 公式消費 uiSize token(`--field-height-*`)生成,隨 density 自動調整(零外部 gap 機制 SSOT 見 `checkbox.spec.md`「群組模式」)。
116
116
 
117
117
  ---
@@ -84,8 +84,8 @@ export const UsageGuidance: Story = {
84
84
  <Label>✅ `aria-label=&quot;平均評分 4.5 星,共 5 星&quot;`</Label>
85
85
  </div>
86
86
  <div className="flex flex-col gap-2 w-[320px] p-4 border border-border rounded-md">
87
- <Rating value={4.5} readOnly precision="half" size="md" />
88
- <Label warn>❌ 無 aria-label,螢幕閱讀器讀不出分數</Label>
87
+ <Rating value={4.5} readOnly precision="half" size="md" aria-label="平均評分 4.5 星,共 5 星" />
88
+ <Label warn>❌ 若省略 aria-label,螢幕閱讀器會讀不出分數;此可執行示範仍保留名稱,避免 demo 本身成為違規來源</Label>
89
89
  </div>
90
90
  </Rule>
91
91
 
@@ -140,7 +140,7 @@ export const Overview: Story = {
140
140
  ['className', 'string', '—', '傳入容器 className(常見:設定 height / border / rounded)'],
141
141
  ['children', 'ReactNode', '—', '要被捲動的內容'],
142
142
  ['type', "'auto'|'always'|'scroll'|'hover'", "'hover'", 'Radix:scrollbar 顯示時機(hover = 游標進入才顯示)'],
143
- ['dir', "'ltr'|'rtl'", "'ltr'", '文字方向(影響 horizontal scrollbar 起點)'],
143
+ ['dir', "'ltr' only", "'ltr'", '全域 compatibility contract 為 LTR-only;不宣稱 RTL 覆蓋'],
144
144
  ['scrollHideDelay', 'number', '600', 'type=scroll / hover 時 scrollbar 隱藏前的延遲(ms)'],
145
145
  ].map(([p, t, d, desc]) => (
146
146
  <tr key={p}><Td mono>{p}</Td><Td mono>{t}</Td><Td mono>{d}</Td><Td>{desc}</Td></tr>
@@ -16,7 +16,7 @@ ScrollArea 是**跨 OS 視覺一致的自訂捲動容器**——用 overlay 捲
16
16
 
17
17
  **Layout Family**:非上述 family — self-contained primitive(純捲動容器,無 slot 結構)。
18
18
 
19
- **實作基礎**:基於 Radix ScrollArea primitive(shadcn passthrough 結構)——由 Radix 處理 viewport / scrollbar / thumb 的 pointer 拖曳與 overflow-detection,本 DS 橋接 token(scrollbar 寬度、thumb bg、hover 反饋)與 a11y 行為(Viewport `tabIndex={0}` 預設 + `viewportTabIndex={-1}` opt-out + DS focus ring;Radix 不處理 keyboard,亦不自動標 Viewport focusable)。
19
+ **實作基礎**:基於 Radix ScrollArea primitive(shadcn passthrough 結構)——由 Radix 處理 viewport / scrollbar / thumb 的 pointer 拖曳與 overflow-detection,本 DS 橋接 token(scrollbar 寬度、thumb bg、hover 反饋)與 a11y 行為(Viewport `tabIndex={0}` 預設 + `viewportTabIndex={-1}` 雙 tab-stop opt-out + `viewportTabIndex={null}` ARIA composite wrapper opt-out + DS focus ring;Radix 不處理 keyboard,亦不自動標 Viewport focusable)。
20
20
 
21
21
  **世界級對照**:shadcn `ScrollArea` / Radix primitive 本身。Polaris / Carbon 未提供等效元件(預設相信 native scrollbar);我們採用 shadcn / Radix 派:為了跨 OS 視覺一致、避免 DataTable / Sheet / Dialog 遇 Windows always-visible scrollbar 吃寬度跑版。 <!-- @benchmark-unverified: see frontmatter benchmark list for canonical DS source URL -->
22
22
 
@@ -100,12 +100,12 @@ ScrollArea 用 Radix 自訂 overlay 捲軸 → **跨 OS 一致、不吃寬度、
100
100
 
101
101
  Radix primitive + 本 DS a11y 橋接:
102
102
 
103
- - **鍵盤捲動**:本 DS 在 Viewport 加 `tabIndex={0}`(預設)使其可被鍵盤聚焦(Radix 不自動把 scroll container 標 focusable,Safari 尤其需要;此即 axe `scrollable-region-focusable` fix)。聚焦後 `ArrowUp/Down/Left/Right` / `PageUp/Down` / `Home/End` 捲動為瀏覽器原生行為,非 Radix 提供。**`viewportTabIndex={-1}` opt-out(2026-07-14)**:內容已有自帶 focusable + 鍵盤處理的元素(如 TimePicker `role="listbox"` `tabIndex={0}`)時傳 `-1`,消除「Viewport+內容雙 tab stop、焦點落 Viewport 時方向鍵觸發原生捲動非元件行為」;axe 由內部 focusable 內容滿足
103
+ - **鍵盤捲動**:本 DS 在 Viewport 加 `tabIndex={0}`(預設)使其可被鍵盤聚焦(Radix 不自動把 scroll container 標 focusable,Safari 尤其需要;此即 axe `scrollable-region-focusable` fix)。聚焦後 `ArrowUp/Down/Left/Right` / `PageUp/Down` / `Home/End` 捲動為瀏覽器原生行為,非 Radix 提供。**`viewportTabIndex={-1}` opt-out(2026-07-14)**:內容已有自帶 focusable + 鍵盤處理的元素(如 TimePicker `role="listbox"` `tabIndex={0}`)時傳 `-1`,消除「Viewport+內容雙 tab stop、焦點落 Viewport 時方向鍵觸發原生捲動非元件行為」;axe 由內部 focusable 內容滿足。**`viewportTabIndex={null}` composite opt-out(2026-07-31)**:ScrollArea 被 `role="menu"` 等 ARIA composite 當純結構 wrapper、鍵盤與 focus 由其 item family own 時,完全省略 viewport `tabindex`;`-1` 仍會留下 `div[tabindex]`,不能滿足 composite 的 required-children 結構
104
104
  - **Focus 可見**:聚焦的 Viewport 顯示 DS focus ring(`focus-visible:outline-ring`,inset 2px),非瀏覽器原生 focus ring
105
105
  - **Scrollbar 非 tab stop**:scrollbar thumb 不搶焦點,使用鍵盤的使用者透過 viewport 捲動(Radix 內建)
106
106
  - **Pointer 支援**:thumb 可拖曳,track 可 click-to-jump(Radix 內建)
107
107
 
108
- 本元件已內建 `tabIndex`(預設 0;`viewportTabIndex={-1}` 可 opt-out,見「鍵盤捲動」)+ focus ring(滿足 axe `scrollable-region-focusable`)。但 Viewport 預設**無 `role` / accessible name**——**若 scroll 區域有具體語意(如「留言列表」「程式碼區塊」),consumer 應提供 `aria-label`**,否則 SR 只報「可捲動區域」無內容描述;純視覺裝飾容器可省略。(2026-05-31 #31:修正原「consumer 無需額外處理 a11y」過度宣稱)
108
+ 本元件已內建 `tabIndex`(預設 0;`viewportTabIndex={-1}` / `{null}` 依上述兩種 owner 情境 opt-out)+ focus ring(滿足 axe `scrollable-region-focusable`)。但 Viewport 預設**無 `role` / accessible name**——**若 scroll 區域有具體語意(如「留言列表」「程式碼區塊」),consumer 應提供 `aria-label`**,否則 SR 只報「可捲動區域」無內容描述;純視覺裝飾容器可省略。(2026-05-31 #31:修正原「consumer 無需額外處理 a11y」過度宣稱)
109
109
 
110
110
  ---
111
111
 
@@ -43,9 +43,10 @@ const ScrollArea = React.forwardRef<
43
43
  * 場景勿開 — table wrapper 是其 min-content 溢出機制)。 */
44
44
  fillX?: boolean
45
45
  /** Viewport tabIndex(預設 0 = axe scrollable-region-focusable fix)。內容已有自帶
46
- * focusable 元素(如 TimePicker role=listbox tabIndex=0)時傳 -1 消除雙 tab stop —
47
- * axe 由內部 focusable 內容滿足,焦點直接落在有 keydown 的 listbox 上。 */
48
- viewportTabIndex?: 0 | -1
46
+ * focusable 元素(如 TimePicker role=listbox tabIndex=0)時傳 -1 消除雙 tab stop。
47
+ * 當 ScrollArea 是 ARIA composite(role=menu 等)的純結構 wrapper 時傳 null,完全省略
48
+ * tabindex,避免在 composite 直屬樹插入不合法的可聚焦 generic div。 */
49
+ viewportTabIndex?: 0 | -1 | null
49
50
  }
50
51
  >(({ className, children, fillX, viewportTabIndex = 0, ...props }, ref) => (
51
52
  <ScrollAreaPrimitive.Root
@@ -63,7 +64,7 @@ const ScrollArea = React.forwardRef<
63
64
  才能 keyboard focus(Safari 不自動把 scroll container 標 focusable)。focus-
64
65
  visible:outline 用 DS focus ring,不破壞視覺。 */}
65
66
  <ScrollAreaPrimitive.Viewport
66
- tabIndex={viewportTabIndex}
67
+ tabIndex={viewportTabIndex ?? undefined}
67
68
  className={cn(
68
69
  'flex-1 min-h-0 w-full rounded-[inherit] focus-visible:outline-2 focus-visible:outline-offset-[-2px] focus-visible:outline-ring',
69
70
  fillX && '[&>div]:!block [&>div]:!w-full [&>div]:!min-w-0',
@@ -7,6 +7,7 @@ import LinkTo from '@storybook/addon-links/react'
7
7
  import type { Meta, StoryObj } from '@storybook/react'
8
8
  import { Flag } from 'lucide-react'
9
9
  import { Select } from './select'
10
+ import { Field, FieldLabel } from '@/design-system/components/Field/field'
10
11
 
11
12
  const meta: Meta = {
12
13
  title: 'Design System/Components/Select/設計原則',
@@ -216,10 +217,10 @@ export const ImmediateVsSubmitRule: Story = {
216
217
  note="建立/編輯表單、對話框設定。onChange 寫進 React state,直到 submit button 被按才送出。有「取消」可回復"
217
218
  >
218
219
  <div className="border border-border rounded-lg p-4 space-y-3">
219
- <div>
220
- <label className="text-caption text-fg-muted mb-1 block">Category</label>
220
+ <Field>
221
+ <FieldLabel>Category</FieldLabel>
221
222
  <Select options={categoryOptions} value={draft} onChange={setDraft} />
222
- </div>
223
+ </Field>
223
224
  <div className="flex gap-2 pt-2">
224
225
  <button className="h-field-md px-3 text-body rounded-md bg-primary text-inverse-fg">儲存</button>
225
226
  <button className="h-field-md px-3 text-body rounded-md border border-border">取消</button>
@@ -6,6 +6,7 @@ import type { Meta, StoryObj } from '@storybook/react'
6
6
  import { createColumnHelper } from '@tanstack/react-table'
7
7
  import { Select } from './select'
8
8
  import { Button } from '@/design-system/components/Button/button'
9
+ import { Field, FieldLabel } from '@/design-system/components/Field/field'
9
10
  import { DataTable } from '@/design-system/components/DataTable/data-table'
10
11
  import '@/design-system/components/DataTable/column-types'
11
12
 
@@ -73,12 +74,12 @@ export const HugWidth: Story = {
73
74
  return (
74
75
  // 任務詳情右欄場景:label 上、hug 寬 select 下 — 框線互動與一般 field 完全相同,只有寬度依 value 收縮
75
76
  <div className="flex w-72 flex-col gap-[var(--layout-space-loose)]">
76
- <div className="flex flex-col gap-1">{/* @layout-space-magic-ok: gap-1 = label↔control 4px 內距 demo */}
77
- <span className="text-caption text-fg-secondary">Status</span>
77
+ <Field>
78
+ <FieldLabel>Status</FieldLabel>
78
79
  <Select width="hug" size="sm" options={statusOptions} value={status} onChange={setStatus} aria-label="Status(hug width demo)" />
79
- </div>
80
- <div className="flex flex-col gap-1">{/* @layout-space-magic-ok: gap-1 = label↔control 4px 內距 demo */}
81
- <span className="text-caption text-fg-secondary">Priority</span>
80
+ </Field>
81
+ <Field>
82
+ <FieldLabel>Priority</FieldLabel>
82
83
  <Select
83
84
  width="hug"
84
85
  size="sm"
@@ -91,7 +92,7 @@ export const HugWidth: Story = {
91
92
  onChange={setPriority}
92
93
  aria-label="Priority(hug width demo)"
93
94
  />
94
- </div>
95
+ </Field>
95
96
  </div>
96
97
  )
97
98
  },
@@ -9,6 +9,9 @@ import type { FieldMode, FieldVariant, FieldVariantInternal, FieldWidth } from '
9
9
  import { fieldWrapperStyles, bareInputStyles, nakedCellRowModeAlign, fieldDisplayTextClass } from '@/design-system/components/Field/field-wrapper'
10
10
  import { Tag } from '@/design-system/components/Tag/tag'
11
11
  import { ItemInlineAction, ItemPrefix, ItemSuffix } from '@/design-system/patterns/element-anatomy/item-anatomy'
12
+ import { TruncatedText } from '@/design-system/patterns/element-anatomy/truncated-text'
13
+ import { Tooltip, TooltipContent, TooltipTrigger } from '@/design-system/components/Tooltip/tooltip'
14
+ import { useTruncated } from '@/design-system/hooks/use-truncated'
12
15
  import { useFieldContext, useResolvedFieldSize, useResolvedFieldDisabled, useResolvedFieldMode, useResolvedFieldVariant, useResolvedFieldInvalid, useFieldEmptyDisplay, fieldEmptyColorClass } from '@/design-system/components/Field/field-context'
13
16
  import { SelectMenu, forwardKeyToListbox, useActiveDescendant, type SelectMenuOption } from '@/design-system/components/SelectMenu/select-menu'
14
17
  import { useIsTouchDevice } from '@/design-system/hooks/use-is-touch-device'
@@ -242,6 +245,7 @@ function CustomSelectTriggerContent({
242
245
  ariaLabel,
243
246
  labelId,
244
247
  selectedItemRenderer,
248
+ truncationRef,
245
249
  }: {
246
250
  searchable: boolean
247
251
  open: boolean
@@ -261,6 +265,8 @@ function CustomSelectTriggerContent({
261
265
  ariaLabel?: string
262
266
  labelId?: string
263
267
  selectedItemRenderer?: (selectedOpt: SelectOption) => React.ReactNode
268
+ /** 截斷 tooltip 量測點(text display 的值 span;tooltip trigger 在外層 combobox host,見 CustomSelect triggerTruncationRef 註解)*/
269
+ truncationRef?: React.Ref<HTMLSpanElement>
264
270
  }): React.ReactNode {
265
271
  // Searchable + open: 顯示搜尋 input
266
272
  // 2026-05-15 Bug 2 fix(Claude+Codex Step 5 比稿 consensus,user verbatim「就 A」):
@@ -332,7 +338,7 @@ function CustomSelectTriggerContent({
332
338
  <>
333
339
  {StartIcon && <ItemPrefix><StartIcon size={iconSize} className="text-fg-muted pointer-events-none" aria-hidden /></ItemPrefix>}
334
340
  {!StartIcon && SelectedIcon && value && <ItemPrefix><SelectedIcon size={iconSize} className="pointer-events-none" aria-hidden /></ItemPrefix>}
335
- <span className={cn('flex-1 min-w-0 truncate', !value && 'text-fg-muted')}>
341
+ <span ref={truncationRef} className={cn('flex-1 min-w-0 truncate', !value && 'text-fg-muted')}>
336
342
  {value ? selectedLabel : (placeholder ?? '選擇…')}
337
343
  </span>
338
344
  </>
@@ -399,7 +405,9 @@ function ReadonlyDisplay({
399
405
  </span>
400
406
  )
401
407
  }
402
- if (isTextDisplay) return <span className={cn(fieldDisplayTextClass(sz), 'truncate', className)}>{label}</span>
408
+ // 截斷必附 tooltip(tooltip.spec.md:32)— 改消費 truncated-text primitive(同 input.tsx:196 同型修;
409
+ // display='tag' 路徑由 Tag 內建 useTruncated 自管,text 路徑原本裸 truncate = gap)
410
+ if (isTextDisplay) return <TruncatedText className={cn(fieldDisplayTextClass(sz), className)}>{label}</TruncatedText>
403
411
  const tVariant = selectedOpt?.tagVariant as 'blue' | 'green' | 'red' | 'yellow' | 'neutral' | undefined
404
412
  return <Tag size={sz} color={tVariant} className={className}>{label}</Tag>
405
413
  }
@@ -416,9 +424,10 @@ function ReadonlyDisplay({
416
424
  {selectedItemRenderer(selectedOpt)}
417
425
  </span>
418
426
  ) : isTextDisplay ? (
419
- <span className={cn(bareInputStyles, 'flex-1 min-w-0 truncate', !value && emptyColorCls)}>
427
+ // 截斷必附 tooltip(tooltip.spec.md:32;data-table.spec.md:183 view 態元件自管 truncation)
428
+ <TruncatedText className={cn(bareInputStyles, 'flex-1 min-w-0', !value && emptyColorCls)}>
420
429
  {value ? label : emptyText}
421
- </span>
430
+ </TruncatedText>
422
431
  ) : value ? (
423
432
  <Tag size={sz} color={tVariant}>{label}</Tag>
424
433
  ) : (
@@ -457,9 +466,10 @@ function ReadonlyDisplay({
457
466
  return (
458
467
  <div className={cn(fieldWrapperStyles({ mode: resolvedMode, variant, width, size: sz }), className)} data-field-mode={resolvedMode} aria-disabled={ariaDisabled}>
459
468
  {StartIcon && <ItemPrefix><StartIcon size={iconSize} className={cn('pointer-events-none', iconColor)} aria-hidden /></ItemPrefix>}
460
- <span className={cn('flex-1 min-w-0 truncate', resolvedMode === 'disabled' && 'text-fg-disabled')}>
469
+ {/* 截斷必附 tooltip(tooltip.spec.md:32)— readonly/disabled 無下拉入口,tooltip 是唯一補救路徑 */}
470
+ <TruncatedText className={cn('flex-1', resolvedMode === 'disabled' && 'text-fg-disabled')}>
461
471
  {value ? label : <span className={emptyColorCls}>{emptyText}</span>}
462
- </span>
472
+ </TruncatedText>
463
473
  {showIndicator && <ItemSuffix className="pointer-events-none"><ChevronDown size={iconSize} className={cn('shrink-0', iconColor)} aria-hidden /></ItemSuffix>}
464
474
  </div>
465
475
  )
@@ -631,6 +641,17 @@ const CustomSelect = React.forwardRef<HTMLDivElement, SelectProps>(
631
641
  // 級 fallback `?? value`。
632
642
  const selectedLabel = selectedOpt?.label ?? value ?? ''
633
643
  const SelectedIcon = selectedOpt?.icon
644
+ // 截斷 tooltip(tooltip.spec.md:32 截斷 → 顯完整內容,僅實際截斷時):closed trigger text
645
+ // display 的值 span 是截斷元素;trigger = 整個 combobox host(interactive host,hover 直達)、
646
+ // 量測內層值 span — useTruncated 自組 pattern(truncated-text.spec.md「trigger 需自控」指定解;
647
+ // 範本 inline-edit.tsx:251。tag display 由 Tag 內建 useTruncated 自管,不經此路)。
648
+ // deps 含 open:searchable && open 切搜尋 input branch 時值 span 會 unmount,關閉重掛後
649
+ // effect 需重跑重新 observe(Tag always-wrap 教訓:RO 卡在 detached node → isTruncated 凍結)。
650
+ // hook 必在下方 resolvedMode early return 之前(React #310 hook-count 鐵律,見上方註解)。
651
+ const { ref: triggerTruncationRef, isTruncated: triggerTruncated } = useTruncated<HTMLSpanElement>({
652
+ deps: [open, isTextDisplay && value ? selectedLabel : ''],
653
+ })
654
+ const triggerTooltipActive = isTextDisplay && !!value && triggerTruncated && !open
634
655
  // ── 過濾選項 ──
635
656
  // 2026-07-18:filter 用 trim 過的 search,對齊 SelectMenu creatable 的 `search.trim()` create-row 判定 —
636
657
  // 否則尾隨空白(如 "Bug ")會讓 filter 漏掉完全同名選項、SelectMenu 卻誤判「無同名」提議重複建立。
@@ -748,6 +769,7 @@ const CustomSelect = React.forwardRef<HTMLDivElement, SelectProps>(
748
769
  ariaLabel={ariaLabel}
749
770
  labelId={fieldCtx?.labelId}
750
771
  selectedItemRenderer={selectedItemRenderer}
772
+ truncationRef={triggerTruncationRef}
751
773
  />
752
774
  )
753
775
 
@@ -828,29 +850,39 @@ const CustomSelect = React.forwardRef<HTMLDivElement, SelectProps>(
828
850
  )
829
851
 
830
852
  return (
831
- <SelectMenu
832
- options={menuOptions}
833
- groups={groups}
834
- value={value ?? null}
835
- onValueChange={handleValueChange}
836
- searchable={false}
837
- creatable={creatable}
838
- onCreate={onCreate}
839
- createLabel={createLabel}
840
- search={searchable ? search : undefined}
841
- onSearchChange={searchable ? setSearch : undefined}
842
- loading={loading}
843
- emptyText={emptyText}
844
- size={size}
845
- minRows={minRows}
846
- open={open}
847
- onOpenChange={(o) => { setOpen(o); onOpenChange?.(o) }}
848
- contentId={listboxId}
849
- renderLabel={renderLabel}
850
- onOpenAutoFocus={searchable ? (e) => { e.preventDefault(); inputRef.current?.focus() } : undefined}
851
- >
852
- {trigger}
853
- </SelectMenu>
853
+ // 截斷 tooltip:恆 wrap(truncated-text.spec.md always-wrap),未截斷或選單開啟時 open={false}
854
+ // 靜默。TooltipTrigger 放 SelectMenu 的 PopoverTrigger asChild 內側(緊貼 trigger div):
855
+ // PopoverTrigger 注入的 data-state(Field open 邊框樣式依賴)在 TooltipTrigger 內以
856
+ // triggerProps 後 spread 勝出;反序會被 tooltip 的 data-state 蓋掉。Tooltip root / Content
857
+ // 都不產 DOM(Content portal),SelectMenu children 結構與佈局不變。
858
+ <Tooltip open={triggerTooltipActive ? undefined : false}>
859
+ <SelectMenu
860
+ options={menuOptions}
861
+ groups={groups}
862
+ value={value ?? null}
863
+ onValueChange={handleValueChange}
864
+ searchable={false}
865
+ creatable={creatable}
866
+ onCreate={onCreate}
867
+ createLabel={createLabel}
868
+ search={searchable ? search : undefined}
869
+ onSearchChange={searchable ? setSearch : undefined}
870
+ loading={loading}
871
+ emptyText={emptyText}
872
+ size={size}
873
+ minRows={minRows}
874
+ open={open}
875
+ onOpenChange={(o) => { setOpen(o); onOpenChange?.(o) }}
876
+ contentId={listboxId}
877
+ renderLabel={renderLabel}
878
+ onOpenAutoFocus={searchable ? (e) => { e.preventDefault(); inputRef.current?.focus() } : undefined}
879
+ >
880
+ <TooltipTrigger asChild>
881
+ {trigger}
882
+ </TooltipTrigger>
883
+ </SelectMenu>
884
+ <TooltipContent>{triggerTooltipActive ? selectedLabel : null}</TooltipContent>
885
+ </Tooltip>
854
886
  )
855
887
  }
856
888
  )
@@ -213,7 +213,7 @@ SelectMenu 是 **composite**(Popover trigger + Command search + 滾動 MenuItem
213
213
 
214
214
  ## A11y 預設
215
215
 
216
- **ARIA / Pattern**:基於 `cmdk` library a11y(combobox / listbox / option role + aria-activedescendant)。詳 [cmdk a11y](https://cmdk.paco.me/#accessibility)。選項 row 的內層 `MenuItem` 傳 `role="presentation"`(cmdk CommandItem 是唯一 option 節點,避免 option 巢狀 option + 內外 `aria-selected` 語意相反;鏡射 DropdownMenu canonical,2026-07-05 D4)。分組標題走 cmdk `CommandGroup heading`(自動產 `cmdk-group-heading` id,選項容器 `role="group"` + `aria-labelledby` 指向之,AT 可感知);combobox accessible name 來自 `Command label`(= `searchPlaceholder`,僅 searchable 時傳),listbox 容器經 cmdk `List label` 預設「選項」取代 cmdk 內建英文 "Suggestions"(2026-07-06)。多選 footer 全選列為 `role="checkbox"` + `aria-checked`(indeterminate → `"mixed"`)。空狀態 / loading 經 visually-hidden `role="status"` + `aria-live="polite"` live region 對 SR 播報(cmdk CommandEmpty 是 `role="presentation"`,SR 原本聽不到;2026-07-05 D4)。
216
+ **ARIA / Pattern**:基於 `cmdk` library a11y(combobox / listbox / option role + aria-activedescendant)。詳 [cmdk a11y](https://cmdk.paco.me/#accessibility)。選項 row 的內層 `MenuItem` 傳 `role="presentation"`(cmdk CommandItem 是唯一 option 節點,避免 option 巢狀 option + 內外 `aria-selected` 語意相反;鏡射 DropdownMenu canonical,2026-07-05 D4)。分組標題走 cmdk `CommandGroup heading`(自動產 `cmdk-group-heading` id,選項容器 `role="group"` + `aria-labelledby` 指向之,AT 可感知);combobox accessible name 來自 `Command label`(= `searchAriaLabel`,default「搜尋選項」,僅 searchable 時傳),與可見 `searchPlaceholder` 分離;listbox 容器經 cmdk `List label` 預設「選項」取代 cmdk 內建英文 "Suggestions"(2026-07-06)。多選 footer 全選列為 `role="checkbox"` + `aria-checked`(indeterminate → `"mixed"`)。空狀態 / loading 經 visually-hidden `role="status"` + `aria-live="polite"` live region 對 SR 播報(cmdk CommandEmpty 是 `role="presentation"`,SR 原本聽不到;2026-07-05 D4)。
217
217
 
218
218
  **Keyboard 行為**:
219
219
 
@@ -95,6 +95,8 @@ export interface SelectMenuProps {
95
95
  children: React.ReactNode
96
96
  /** 搜尋框 placeholder */
97
97
  searchPlaceholder?: string
98
+ /** 搜尋框 accessible name;與可見 placeholder 分離。 */
99
+ searchAriaLabel?: string
98
100
  /** 空選項提示 */
99
101
  emptyText?: string
100
102
  /** 多選 footer 全選列文字(2026-07-05 D4:原「全部」字面 hardcode,無法覆寫也無法 i18n) */
@@ -183,6 +185,7 @@ const SelectMenu = React.forwardRef<HTMLElement, SelectMenuProps>(function Selec
183
185
  onSearchChange,
184
186
  children,
185
187
  searchPlaceholder = '搜尋…', // i18n-allow: DS default; consumer override via searchPlaceholder prop
188
+ searchAriaLabel = '搜尋選項', // i18n-allow: DS default; consumer override via searchAriaLabel prop
186
189
  emptyText = '沒有符合的選項', // i18n-allow: DS default; consumer override via emptyText prop
187
190
  selectAllLabel = '全部', // i18n-allow: DS default; consumer override via selectAllLabel prop
188
191
  loading = false,
@@ -394,7 +397,7 @@ const SelectMenu = React.forwardRef<HTMLElement, SelectMenuProps>(function Selec
394
397
  // 不存在的 input」仍存在(upstream cmdk 限制;空 label 無 accessible name,不產生朗讀
395
398
  // 內容)。僅 searchable(真的有 input)時傳內容 = 避免 non-searchable 帶 search
396
399
  // placeholder 的 accessible name 卻無對應 input;真要消除 orphan label 需 upstream 修。
397
- label={searchable ? searchPlaceholder : undefined}
400
+ label={searchable ? searchAriaLabel : undefined}
398
401
  className="bg-transparent"
399
402
  >
400
403
  {searchable && (
@@ -83,7 +83,7 @@ export const Overview: Story = {
83
83
  </Desc>
84
84
  <div className="border border-divider rounded-md p-4 bg-muted max-w-xl">
85
85
  <SelectionItem
86
- control={<Checkbox defaultChecked />}
86
+ control={<Checkbox defaultChecked aria-label="讀取 email 信箱" />}
87
87
  icon={Mail}
88
88
  label="讀取 email 信箱"
89
89
  description="僅用於通知,永遠不會外流"
@@ -246,7 +246,14 @@ function SelectionInspector() {
246
246
  <div className="border border-divider rounded-md p-4 bg-muted">
247
247
  <SelectionItem
248
248
  size={size}
249
- control={<Checkbox size={size} defaultChecked disabled={disabled} />}
249
+ control={
250
+ <Checkbox
251
+ size={size}
252
+ defaultChecked
253
+ disabled={disabled}
254
+ aria-label="Admin 層級操作"
255
+ />
256
+ }
250
257
  icon={prefix === 'icon' ? Shield : undefined}
251
258
  avatar={prefix === 'avatar' ? { alt: 'Ada Chen', color: 'indigo' } : undefined}
252
259
  label="Admin 層級操作"
@@ -438,7 +445,7 @@ export const PrefixAlignment: Story = {
438
445
  </span>
439
446
  <div className="border border-divider rounded-md p-4 bg-muted">
440
447
  <SelectionItem
441
- control={<Checkbox defaultChecked />}
448
+ control={<Checkbox defaultChecked aria-label="選取 Ada Chen(單行)" />}
442
449
  avatar={{ src: 'https://i.pravatar.cc/48?u=ada-chen', alt: 'Ada Chen', color: 'indigo' }}
443
450
  label="Ada Chen"
444
451
  />
@@ -450,7 +457,7 @@ export const PrefixAlignment: Story = {
450
457
  </span>
451
458
  <div className="border border-divider rounded-md p-4 bg-muted">
452
459
  <SelectionItem
453
- control={<Checkbox defaultChecked />}
460
+ control={<Checkbox defaultChecked aria-label="選取 Ada Chen(含職稱)" />}
454
461
  avatar={{ src: 'https://i.pravatar.cc/64?u=ada-chen', alt: 'Ada Chen', color: 'indigo' }}
455
462
  label="Ada Chen"
456
463
  description="Design Engineer · Frontend team"
@@ -467,11 +474,15 @@ export const PrefixAlignment: Story = {
467
474
  </Desc>
468
475
  <div className="grid grid-cols-2 gap-6 max-w-4xl">
469
476
  <div className="border border-divider rounded-md p-4 bg-muted">
470
- <SelectionItem control={<Checkbox defaultChecked />} icon={Folder} label="存取 Drive 檔案" />
477
+ <SelectionItem
478
+ control={<Checkbox defaultChecked aria-label="存取 Drive 檔案(單行)" />}
479
+ icon={Folder}
480
+ label="存取 Drive 檔案"
481
+ />
471
482
  </div>
472
483
  <div className="border border-divider rounded-md p-4 bg-muted">
473
484
  <SelectionItem
474
- control={<Checkbox defaultChecked />}
485
+ control={<Checkbox defaultChecked aria-label="存取 Drive 檔案(含說明)" />}
475
486
  icon={Folder}
476
487
  label="存取 Drive 檔案"
477
488
  description="讀取並修改你授權的資料夾"
@@ -538,13 +549,13 @@ export const SizeMatrix: Story = {
538
549
  <div className="border border-divider rounded-md bg-muted flex flex-col">
539
550
  <SelectionItem
540
551
  size={s}
541
- control={<Checkbox size={s} defaultChecked />}
552
+ control={<Checkbox size={s} defaultChecked aria-label={`${s} 尺寸產品更新電子報`} />}
542
553
  icon={Mail}
543
554
  label="產品更新電子報"
544
555
  />
545
556
  <SelectionItem
546
557
  size={s}
547
- control={<Checkbox size={s} />}
558
+ control={<Checkbox size={s} aria-label={`${s} 尺寸安全性警告`} />}
548
559
  icon={Shield}
549
560
  label="安全性警告"
550
561
  description="登入裝置變動、密碼變更等重要事件"
@@ -73,24 +73,24 @@ export const WithPrefixIcon: Story = {
73
73
  Figma-like permission scope picker — control + icon(24px 閾值內 inline)+ label + description。
74
74
  </p>
75
75
  <SelectionItem
76
- control={<Checkbox defaultChecked />}
76
+ control={<Checkbox defaultChecked aria-label="讀取 email 信箱" />}
77
77
  icon={Mail}
78
78
  label="讀取 email 信箱"
79
79
  description="僅用於通知,永遠不會外流"
80
80
  />
81
81
  <SelectionItem
82
- control={<Checkbox />}
82
+ control={<Checkbox aria-label="存取 Drive 檔案" />}
83
83
  icon={Folder}
84
84
  label="存取 Drive 檔案"
85
85
  description="讀取並修改你授權的資料夾"
86
86
  />
87
87
  <SelectionItem
88
- control={<Checkbox defaultChecked />}
88
+ control={<Checkbox defaultChecked aria-label="推播通知權限" />}
89
89
  icon={Bell}
90
90
  label="推播通知權限"
91
91
  />
92
92
  <SelectionItem
93
- control={<Checkbox />}
93
+ control={<Checkbox aria-label="Admin 層級操作" />}
94
94
  icon={Shield}
95
95
  label="Admin 層級操作"
96
96
  description="僅限 workspace owner 開啟"
@@ -113,19 +113,19 @@ export const WithPrefixAvatarBlock: Story = {
113
113
  role。
114
114
  </p>
115
115
  <SelectionItem
116
- control={<Checkbox defaultChecked />}
116
+ control={<Checkbox defaultChecked aria-label="選取 Ada Chen" />}
117
117
  avatar={{ src: 'https://i.pravatar.cc/64?u=ada-chen', alt: 'Ada Chen', color: 'indigo' }}
118
118
  label="Ada Chen"
119
119
  description="Design Engineer · Frontend team"
120
120
  />
121
121
  <SelectionItem
122
- control={<Checkbox />}
122
+ control={<Checkbox aria-label="選取張美真" />}
123
123
  avatar={{ src: 'https://i.pravatar.cc/64?u=zhang-meizhen', alt: '張美真', color: 'magenta' }}
124
124
  label="張美真"
125
125
  description="Product Designer · Platform team"
126
126
  />
127
127
  <SelectionItem
128
- control={<Checkbox defaultChecked />}
128
+ control={<Checkbox defaultChecked aria-label="選取林伯彥" />}
129
129
  avatar={{ src: 'https://i.pravatar.cc/64?u=lin-boyan', alt: '林伯彥', color: 'green' }}
130
130
  label="林伯彥"
131
131
  description="Staff Engineer · Infra team"
@@ -108,7 +108,7 @@ Sheet 為容器,內容由 consumer 決定;focus trap + Escape close + `aria-labe
108
108
  - **Empty(no content)**:Sheet 必含 title(`aria-labelledby` 強制要求);body 為空時 consumer 應渲 `<Empty>` placeholder,不渲空白 panel。
109
109
  - **Scroll(body 內容超過視口高度)**:`SheetBody` = `<ScrollArea>`(`flex-1 min-h-0`),超高內容在 body 內部捲動,header / footer 固定不動;consumer 不需自寫 `overflow-y-auto`。
110
110
  - **誤嵌套降級(nested Sheet)**:禁止事項已禁 nested;若 consumer 不慎嵌套,Radix 的 Esc 與 focus trap 只作用於**最上層** layer(DismissableLayer 只 dismiss highest layer;FocusScope stack 最新 scope active),關閉由上而下逐層。
111
- - **RTL**:`side` 為物理方向(`right-0` 物理定位),不隨 `dir="rtl"` 自動鏡像。
111
+ - **RTL**:不支援;全域 LTR-only compatibility contract 見 `packages/design-system/README.md#compatibility-matrix`。`side` 維持 physical direction。
112
112
  - **Dark mode**:overlay-surface SSOT 自動 adapt(`overlay-surface.spec.md`);Sheet 不獨立 own dark token。
113
113
  - **Density**:Sheet body density 由 consumer 控(若內含 form / DataTable / Menu 各自有 density 規則);Sheet container 自身不持 density。
114
114