@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
@@ -46,10 +46,10 @@ export const TaskTitle: Story = {
46
46
  /* ── 真實業務:詳情面板多個 metadata 欄位逐欄就地編輯 ── */
47
47
  function MetaRow({ label, value, placeholder }: { label: string; value: string; placeholder?: string }) {
48
48
  return (
49
- <div className="grid grid-cols-[128px_1fr] items-center gap-[var(--layout-space-tight)]">
50
- <span className="text-body text-fg-secondary">{label}</span>
49
+ <Field orientation="horizontal" labelWidth="128px">
50
+ <FieldLabel>{label}</FieldLabel>
51
51
  <StatefulInlineEdit value={value} label={label} placeholder={placeholder} size="sm" />
52
- </div>
52
+ </Field>
53
53
  )
54
54
  }
55
55
 
@@ -108,11 +108,11 @@ export const States: Story = {
108
108
  ),
109
109
  play: async ({ canvasElement }) => {
110
110
  const { userEvent } = await import('@storybook/test')
111
- // hover 態:滑鼠移入第二列 → 顯示 bg-neutral-hover 灰底
111
+ // 只標記第二列;真實 CSS :hover 由 visual-audit 的 Playwright pointer 建立。
112
112
  const hoverBtn = canvasElement
113
113
  .querySelector('[data-testid="inline-edit-hover-row"]')
114
114
  ?.querySelector('button')
115
- if (hoverBtn) await userEvent.hover(hoverBtn)
115
+ if (hoverBtn) hoverBtn.setAttribute('data-visual-hover-target', '')
116
116
  // edit 態:點擊第三列 → 切成真正的 Input 控件
117
117
  const editBtn = canvasElement
118
118
  .querySelector('[data-testid="inline-edit-edit-row"]')
@@ -139,16 +139,21 @@ export const SelectTagField: Story = {
139
139
  render: () => {
140
140
  const [status, setStatus] = React.useState('in-progress')
141
141
  return (
142
- <div className="grid w-[440px] grid-cols-[128px_1fr] items-center gap-[var(--layout-space-tight)] rounded-lg border border-border bg-surface p-[var(--layout-space-loose)]">
143
- <span className="text-body text-fg-secondary">Status</span>
142
+ <Field
143
+ orientation="horizontal"
144
+ labelWidth="128px"
145
+ className="w-[440px] rounded-lg border border-border bg-surface p-[var(--layout-space-loose)]"
146
+ >
147
+ <FieldLabel>Status</FieldLabel>
144
148
  <InlineEdit<string>
145
149
  value={status}
146
150
  onCommit={setStatus}
147
151
  label="status"
148
152
  size="sm"
149
153
  // view:Select 的 view mode 輸出 Tag(格式化 SSOT 住在 Select,InlineEdit 不自刻)。
150
- // Tag **盒子左緣**對齊 label / 其他純文字值,tag 文字被 pill 內距自然縮排 —— 對齊 Meegle 實測
151
- // (PIL 量測 2026-07-10:Meegle pill box=label 左緣、text 縮排 ~8px@1x)。**不做 text outdent**。
154
+ // Tag **盒子左緣**對齊同欄其他純文字值(horizontal Field:值落內容欄左緣 + field-px,
155
+ // 不用 -mx),tag 文字被 pill 內距自然縮排 —— 對齊 Meegle 實測
156
+ // (PIL 量測 2026-07-10:Meegle pill box=值左緣、text 縮排 ~8px@1x)。**不做 text outdent**。
152
157
  renderRead={(v) => (
153
158
  <Select mode="view" display="tag" size="sm" value={v} options={STATUS_OPTIONS} />
154
159
  )}
@@ -168,7 +173,7 @@ export const SelectTagField: Story = {
168
173
  />
169
174
  )}
170
175
  />
171
- </div>
176
+ </Field>
172
177
  )
173
178
  },
174
179
  }
@@ -183,14 +188,16 @@ export const MultilineDescription: Story = {
183
188
  )
184
189
  return (
185
190
  <div className="w-[560px] rounded-lg border border-border bg-surface p-[var(--layout-space-loose)]">
186
- <p className="mb-1 text-caption text-fg-secondary">DESCRIPTION</p>
187
- <InlineEdit
188
- multiline
189
- value={desc}
190
- onCommit={setDesc}
191
- label="description"
192
- placeholder="Add a description…"
193
- />
191
+ <Field orientation="vertical">
192
+ <FieldLabel>DESCRIPTION</FieldLabel>
193
+ <InlineEdit
194
+ multiline
195
+ value={desc}
196
+ onCommit={setDesc}
197
+ label="description"
198
+ placeholder="Add a description…"
199
+ />
200
+ </Field>
194
201
  </div>
195
202
  )
196
203
  },
@@ -201,6 +208,10 @@ export const MultilineDescription: Story = {
201
208
  export const VerticalFieldForm: Story = {
202
209
  name: '詳情面板:垂直欄位就地編輯',
203
210
  render: () => {
211
+ // Assignee 的 state 提到 story 本體(不包 wrapper 元件)—— InlineEdit 必須是 <Field> 的**直接子**,
212
+ // Field 才讀得到靜態 fieldPreferredSize='sm';包一層 wrapper 會遮蔽它 → 控件槽退回 md(32px),
213
+ // 與同表其他 sm(28px)列不齊(對照 inline-edit.anatomy.stories.tsx CascadeDemo)。
214
+ const [assignee, setAssignee] = React.useState('Alex Rivera')
204
215
  const [status, setStatus] = React.useState('in-progress')
205
216
  const [desc, setDesc] = React.useState('Safari 上點擊結帳時,e2e 測試會間歇性失敗。')
206
217
  return (
@@ -210,7 +221,7 @@ export const VerticalFieldForm: Story = {
210
221
  >
211
222
  <Field orientation="vertical">
212
223
  <FieldLabel>Assignee</FieldLabel>
213
- <StatefulInlineEdit value="Alex Rivera" label="assignee" />
224
+ <InlineEdit value={assignee} onCommit={setAssignee} label="assignee" />
214
225
  </Field>
215
226
  <Field orientation="vertical">
216
227
  <FieldLabel>Status</FieldLabel>
@@ -26,9 +26,11 @@ import { cn } from '@/lib/utils'
26
26
  import { Input } from '@/design-system/components/Input/input'
27
27
  import { Textarea } from '@/design-system/components/Textarea/textarea'
28
28
  import type { FieldSize } from '@/design-system/components/Field/field-context'
29
- import { useFieldContext, useResolvedFieldSize, FieldSurfaceProvider } from '@/design-system/components/Field/field-context'
29
+ import { useFieldContext, useResolvedFieldSize, useRegisterFieldPreferredSize, FieldSurfaceProvider } from '@/design-system/components/Field/field-context'
30
30
  import { fieldDisplayTextClass, fieldViewGeometry } from '@/design-system/components/Field/field-wrapper'
31
31
  import { makeEditSettleKeyHandler } from '@/design-system/components/Field/field-edit-keys'
32
+ import { useTruncated } from '@/design-system/hooks/use-truncated'
33
+ import { Tooltip, TooltipContent, TooltipTrigger } from '@/design-system/components/Tooltip/tooltip'
32
34
 
33
35
  // ── Types ───────────────────────────────────────────────────────────────────
34
36
 
@@ -71,6 +73,11 @@ export interface InlineEditProps<T = string> {
71
73
  renderRead?: (value: T) => React.ReactNode
72
74
  /** 多行欄位(Jira description 類):view 態換行 + 預設 edit 態用 `<Textarea>`,Enter=換行、Cmd/Ctrl+Enter 或 blur 提交 */
73
75
  multiline?: boolean
76
+ /**
77
+ * 多行 edit 態的高度上限(行數)。預設無上限 —— 高度隨內容,與 view 態同源(= DataTable cell 語意)。
78
+ * 設定後超過行數改為捲動。僅在 `multiline` 時有意義。
79
+ */
80
+ editMaxRows?: number
74
81
  /**
75
82
  * 是否可編輯(預設 true)。false → view 態無 hover 入口、無藍框、不可點進 edit(對齊世界級就地編輯
76
83
  * **無 disabled 態**,鎖定 = 純 view 無入口、**不灰化**;cite 見檔頭 MUI X / AG Grid URL)。
@@ -113,6 +120,7 @@ function InlineEditImpl<T = string>(
113
120
  renderEdit,
114
121
  renderRead,
115
122
  multiline = false,
123
+ editMaxRows,
116
124
  editable = true,
117
125
  label,
118
126
  placeholder,
@@ -123,9 +131,11 @@ function InlineEditImpl<T = string>(
123
131
  }: InlineEditProps<T>,
124
132
  ref: React.Ref<HTMLDivElement>,
125
133
  ) {
126
- // size:接 fieldCtx.size cascade,standalone fallback sm(InlineEdit 靜態 fieldPreferredSize='sm'
127
- // 讓外層 `<Field>` 自動收 sm — 見檔尾 static)。
134
+ // size:接 fieldCtx.size cascade,fallback sm。Field 只有在 consumer 顯式指定或已解析出偏好時
135
+ // 才壓過此 fallback(sizeExplicit),因此包一層 wrapper 也不會靜默退回 md。
128
136
  const size = useResolvedFieldSize(sizeProp, 'sm')
137
+ // 同時向外層 Field 註冊偏好,讓 Field 的控件槽高度跟上(靜態 fieldPreferredSize 被 wrapper 遮蔽時的補位)。
138
+ useRegisterFieldPreferredSize('sm')
129
139
  // orientation:vertical(值貼 label 左緣 → 用 -mx)/ horizontal(值落內容欄左緣+field-px = 對齊 sibling 控件 → 不用 -mx)。
130
140
  const fieldCtx = useFieldContext()
131
141
  const orientation = fieldCtx?.orientation ?? 'vertical'
@@ -238,6 +248,35 @@ function InlineEditImpl<T = string>(
238
248
  value == null || (value as unknown) === '' || (Array.isArray(value) && value.length === 0)
239
249
  const Tag = as as React.ElementType
240
250
 
251
+ // 單行純值 view 的截斷 tooltip:量測內層值 span,但 **trigger = 整個 view 容器**。
252
+ // 不能用 <TruncatedText>(trigger=值 span 本身):view 上疊著透明 Pressable(click-to-edit
253
+ // 入口,absolute inset-0),所有 hover 都被按鈕吞掉,值 span 永遠收不到 pointerenter →
254
+ // tooltip 永不開(2026-07-28 user 實測抓到)。truncated-text.spec 對「trigger 需自控」場景
255
+ // 的指定解 = 直接消費 public useTruncated 自組;DataTable cell 無疊層所以能用 primitive。
256
+ const singleLinePlainView = !renderRead && !multiline
257
+ const { ref: truncationRef, isTruncated } = useTruncated<HTMLSpanElement>({
258
+ deps: [singleLinePlainView && !isEmpty ? String(value) : ''],
259
+ })
260
+ const plainTruncationActive = singleLinePlainView && !isEmpty && isTruncated
261
+
262
+ // 委派 view(renderRead)同規則(tooltip.spec.md「截斷文字 → tooltip,截斷才顯」):委派控件
263
+ // 自帶的截斷 tooltip(Input <TruncatedText> / Tag)trigger 在內層,editable 時同樣被透明
264
+ // Pressable 吞掉 hover → 永不開,故外層容器 Tooltip 一併服務委派路徑。量測 = use-truncated
265
+ // options.measure 自訂策略(檔頭明載 Tag「observe root、量測內層」即同款開口):走訪 wrapper
266
+ // 子樹,任一 `.truncate` 後代溢出即視為截斷(委派控件截斷元素皆帶 truncate class:Tag 內層
267
+ // span / TruncatedText span / Select view span)。editable=false 不渲染 Pressable → 控件自身
268
+ // tooltip 可達,外層讓位避免雙重顯示。
269
+ const delegatedView = !!renderRead
270
+ const { ref: delegatedViewRef, isTruncated: delegatedTruncated } = useTruncated<HTMLDivElement>({
271
+ measure: (el) =>
272
+ Array.from(el.querySelectorAll<HTMLElement>('.truncate')).some(
273
+ (node) => node.scrollWidth > node.clientWidth,
274
+ ),
275
+ deps: [delegatedView && editable && !isEmpty ? value : null],
276
+ })
277
+ const delegatedTruncationActive = delegatedView && editable && !isEmpty && delegatedTruncated
278
+ const truncationTooltipActive = plainTruncationActive || delegatedTruncationActive
279
+
241
280
  // 對齊盒(orientation-aware,Model A;2026-07-17 移除 fieldCtx gate — root cause 修):
242
281
  // vertical(預設,含 standalone)→ `-mx-field-px` + `w-calc` 把整塊拉到欄左緣(值貼 label 左緣;
243
282
  // view 委派控件在 `surface='inline-edit'` 下 px 統一 field-px → 被 -mx 精準抵消 → 落 x=0 對齊 label)。
@@ -269,6 +308,9 @@ function InlineEditImpl<T = string>(
269
308
  <Textarea
270
309
  autoFocus
271
310
  mode="edit"
311
+ // view 態高度由內容決定,edit 態必須同源,否則 read↔edit 之間跳高(DataTable cell 早就是
312
+ // 內容驅動,standalone InlineEdit 原本卡在 rows=3)。consumer 要上限就傳 editMaxRows。
313
+ autoSize={editMaxRows === undefined ? true : { maxRows: editMaxRows }}
272
314
  value={draft as unknown as string}
273
315
  size={size}
274
316
  aria-label={label}
@@ -310,7 +352,8 @@ function InlineEditImpl<T = string>(
310
352
  // surface='inline-edit' 保留為 per-control 逃生訊號(若某控件 bare view 有內部 inset 破壞對齊,該控件可
311
353
  // 讀 surface 自抑;目前全控件 bare view=0px 不需)。
312
354
  <FieldSurfaceProvider surface="inline-edit">
313
- <div className={fieldViewGeometry(size, multiline)}>{renderRead(value)}</div>
355
+ {/* delegatedViewRef = 委派路徑截斷量測根(walk `.truncate` 後代;見上方 useTruncated 註解) */}
356
+ <div ref={delegatedViewRef} className={fieldViewGeometry(size, multiline)}>{renderRead(value)}</div>
314
357
  </FieldSurfaceProvider>
315
358
  ) : (
316
359
  <Tag
@@ -327,37 +370,57 @@ function InlineEditImpl<T = string>(
327
370
  isEmpty && 'text-fg-muted',
328
371
  )}
329
372
  >
330
- {isEmpty ? placeholder : String(value)}
373
+ {isEmpty || multiline
374
+ ? (isEmpty ? placeholder : String(value))
375
+ // 單行 view 恆一行:溢出改 ellipsis(tooltip.spec.md「截斷文字 → tooltip」);
376
+ // tooltip 掛在外層 view 容器(見 truncationRef 註解——隱形 Pressable 吞 hover)。
377
+ : <span ref={truncationRef} className="truncate min-w-0">{String(value)}</span>}
331
378
  </Tag>
332
379
  )
333
380
 
334
381
  return (
335
- <div
336
- ref={ref}
337
- data-editing={false}
338
- data-editable={editable || undefined}
339
- className={cn(
340
- 'relative flex min-w-0 rounded-md border border-transparent transition-colors duration-150',
341
- alignBleed,
342
- // editable 才有 hover 底色 + 鍵盤 focus 藍框(Field focus 語言,非 Button ring);
343
- // editable=false = 純 view 鎖定,無入口、無藍框、不灰化。
344
- editable && 'hover:bg-neutral-hover [&:has(button:focus-visible)]:border-primary',
345
- className,
346
- )}
347
- >
348
- {viewNode}
349
- {/* 隱形 Pressable(僅 editable):提供 click + 鍵盤 Tab focus + Enter/Space 進 edit;透明疊於內容上。
350
- hover 底色 + focus 藍框由外層 div 承載(Field focus 語言),故本 button 只需 outline-none 消瀏覽器預設外框。 */}
351
- {editable && (
352
- <button
353
- ref={readButtonRef}
354
- type="button"
355
- aria-label={label ? `編輯 ${label}` : '編輯'}
356
- onClick={enterEdit}
357
- className="absolute inset-0 cursor-text rounded-md focus-visible:outline-none"
358
- />
359
- )}
360
- </div>
382
+ // 截斷 tooltip:trigger = 整個 view 容器(隱形 Pressable 吞掉內層 hover,故不能掛在值 span);
383
+ // 純值路徑量內層值 span、委派路徑量 wrapper 子樹(見上方兩段 useTruncated 註解);
384
+ // 恆 wrap 保 DOM 生命週期穩定(truncated-text 檔頭 always-wrap 教訓),未截斷時 open={false} 靜默。
385
+ <Tooltip open={truncationTooltipActive ? undefined : false}>
386
+ <TooltipTrigger asChild>
387
+ <div
388
+ ref={ref}
389
+ data-editing={false}
390
+ data-editable={editable || undefined}
391
+ className={cn(
392
+ 'relative flex min-w-0 rounded-md border border-transparent transition-colors duration-150',
393
+ alignBleed,
394
+ // editable 才有 hover 底色 + 鍵盤 focus 藍框(Field focus 語言,非 Button ring);
395
+ // editable=false = 純 view 鎖定,無入口、無藍框、不灰化。
396
+ editable && 'hover:bg-neutral-hover [&:has(button:focus-visible)]:border-primary',
397
+ className,
398
+ )}
399
+ >
400
+ {viewNode}
401
+ {/* 隱形 Pressable(僅 editable):提供 click + 鍵盤 Tab focus + Enter/Space 進 edit;透明疊於內容上。
402
+ hover 底色 + focus 藍框由外層 div 承載(Field focus 語言),故本 button 只需 outline-none 消瀏覽器預設外框。 */}
403
+ {editable && (
404
+ <button
405
+ ref={readButtonRef}
406
+ type="button"
407
+ aria-label={label ? `編輯 ${label}` : '編輯'}
408
+ onClick={enterEdit}
409
+ className="absolute inset-0 cursor-text rounded-md focus-visible:outline-none"
410
+ />
411
+ )}
412
+ </div>
413
+ </TooltipTrigger>
414
+ <TooltipContent>
415
+ {delegatedTruncationActive
416
+ ? // 委派路徑內容 = wrapper 子樹當下 DOM 的 textContent(完整格式化文字;
417
+ // 非 string T 不可 String(value) —— 如 string[] / PersonValue)
418
+ delegatedViewRef.current?.textContent
419
+ : plainTruncationActive
420
+ ? String(value)
421
+ : null}
422
+ </TooltipContent>
423
+ </Tooltip>
361
424
  )
362
425
  }
363
426
 
@@ -214,9 +214,9 @@ export const Overview = {
214
214
  {/* view + readonly 讀 controlled value:兩者「空值」皆走 <span> 顯 '-'(input.tsx:121),
215
215
  demo 要展示有值狀態故傳 value;edit / disabled 用 defaultValue 保持 uncontrolled */}
216
216
  {(m === 'view' || m === 'readonly') ? (
217
- <Input mode={m} value="Wireless Bluetooth Headphones" size="md" />
217
+ <Input mode={m} value="Wireless Bluetooth Headphones" size="md" aria-label={`商品名稱(${m} 模式)`} />
218
218
  ) : (
219
- <Input mode={m} defaultValue="Wireless Bluetooth Headphones" size="md" />
219
+ <Input mode={m} defaultValue="Wireless Bluetooth Headphones" size="md" aria-label={`商品名稱(${m} 模式)`} />
220
220
  )}
221
221
  </div>
222
222
  <span className="text-caption text-fg-secondary">{MODE_DESC[m]}</span>
@@ -227,7 +227,7 @@ export const Overview = {
227
227
  <span className="text-[11px] text-fg-muted font-medium">+ error prop(僅 edit 模式生效)</span>
228
228
  <div className="flex items-center gap-4">
229
229
  <div className="w-64 shrink-0">
230
- <Input error defaultValue="invalid-email@" size="md" />
230
+ <Input error defaultValue="invalid-email@" size="md" aria-label="Email(錯誤狀態)" />
231
231
  </div>
232
232
  <span className="text-caption text-fg-secondary">border-error — 錯誤訊息由 Form help text 提供</span>
233
233
  </div>
@@ -237,7 +237,7 @@ export const Overview = {
237
237
  {/* Props table */}
238
238
  <div className="flex flex-col gap-3">
239
239
  <H3>Props</H3>
240
- <div className="overflow-x-auto">
240
+ <div className="overflow-x-auto" tabIndex={0} aria-label="Input props 表格">
241
241
  <table className="text-caption border-collapse">
242
242
  <thead><tr><Th>Prop</Th><Th>Type</Th><Th>Default</Th><Th>說明</Th></tr></thead>
243
243
  <tbody>
@@ -332,6 +332,7 @@ const InspectorInner = () => {
332
332
  mode={mode}
333
333
  size={size}
334
334
  error={error}
335
+ aria-label="商品名稱(互動檢閱器)"
335
336
  startIcon={hasStartIcon ? Search : undefined}
336
337
  endAction={hasEndAction && isEdit ? { icon: X, label: '清除', onClick: () => {} } : undefined}
337
338
  // view mode 讀 value 渲染 <span>;其餘 mode 用 defaultValue 保持 uncontrolled
@@ -505,6 +506,7 @@ export const ColorMatrix = {
505
506
  error={st === 'error'}
506
507
  defaultValue="Wireless Bluetooth Headphones"
507
508
  size="md"
509
+ aria-label={`商品名稱(${st} 狀態)`}
508
510
  />
509
511
  </td>
510
512
  <td className="p-3 border-b border-divider align-top">
@@ -529,7 +531,12 @@ export const ColorMatrix = {
529
531
  <tr key={m}>
530
532
  <td className="p-3 border-b border-divider font-mono text-caption font-medium align-top w-24">{m}</td>
531
533
  <td className="p-3 border-b border-divider align-top min-w-[240px]">
532
- <Input mode={m} defaultValue="Wireless Bluetooth Headphones" size="md" />
534
+ <Input
535
+ mode={m}
536
+ defaultValue="Wireless Bluetooth Headphones"
537
+ size="md"
538
+ aria-label={`商品名稱(${m} 模式)`}
539
+ />
533
540
  </td>
534
541
  <td className="p-3 border-b border-divider align-top">
535
542
  <TokenAnnotation colors={COLOR_MAP[m].default!} />
@@ -634,7 +641,13 @@ export const SizeMatrix = {
634
641
  <Td mono>{sz}</Td>
635
642
  {MODES.map((m) => (
636
643
  <td key={m} className="p-2 border-b border-divider">
637
- <Input mode={m} size={sz} defaultValue="Headphones" className="w-48" />
644
+ <Input
645
+ mode={m}
646
+ size={sz}
647
+ defaultValue="Headphones"
648
+ aria-label={`商品名稱(${sz} 尺寸、${m} 模式)`}
649
+ className="w-48"
650
+ />
638
651
  </td>
639
652
  ))}
640
653
  <td className="p-2 border-b border-divider">
@@ -643,6 +656,7 @@ export const SizeMatrix = {
643
656
  startIcon={Search}
644
657
  endAction={{ icon: X, label: '清除', onClick: () => {} }}
645
658
  defaultValue="Headphones"
659
+ aria-label={`商品搜尋(${sz} 尺寸)`}
646
660
  className="w-48"
647
661
  />
648
662
  </td>
@@ -678,8 +692,8 @@ export const StateBehavior = {
678
692
  <span className="text-caption font-medium text-fg-secondary">Focus — border-primary(1px),無 ring(readonly 例外:ring-2 ring-ring focus 指示,WCAG 2.4.7)</span>
679
693
  <div className="flex flex-col gap-2 max-w-sm">
680
694
  <span className="text-[11px] text-fg-muted">文字輸入永遠 focus-visible(瀏覽器規範),click 和 Tab 觸發相同效果。點擊下方 input 或用 Tab 切換觀察。</span>
681
- <Input placeholder="點擊或 Tab 觀察 focus 邊框" />
682
- <Input placeholder="第二個,測試 Tab 切換" />
695
+ <Input placeholder="點擊或 Tab 觀察 focus 邊框" aria-label="第一個焦點測試欄位" />
696
+ <Input placeholder="第二個,測試 Tab 切換" aria-label="第二個焦點測試欄位" />
683
697
  </div>
684
698
  </div>
685
699
 
@@ -689,12 +703,12 @@ export const StateBehavior = {
689
703
  <div className="flex flex-col gap-3 max-w-sm">
690
704
  <div>
691
705
  <span className="text-[11px] text-fg-muted block mb-1">border-error,hover 時 border-error-hover</span>
692
- <Input error defaultValue="invalid-email@" />
706
+ <Input error defaultValue="invalid-email@" aria-label="Email(錯誤狀態)" />
693
707
  <p className="text-caption text-error mt-1">請輸入有效的 email 地址</p>
694
708
  </div>
695
709
  <div>
696
710
  <span className="text-[11px] text-fg-muted block mb-1">focus 時 border-error(不切回 primary)</span>
697
- <Input error defaultValue="bad-url" startIcon={Search} />
711
+ <Input error defaultValue="bad-url" startIcon={Search} aria-label="網址(錯誤焦點狀態)" />
698
712
  </div>
699
713
  </div>
700
714
  </div>
@@ -708,6 +722,7 @@ export const StateBehavior = {
708
722
  startIcon={Search}
709
723
  value={query}
710
724
  onChange={(e) => setQuery(e.target.value)}
725
+ aria-label="搜尋商品"
711
726
  endAction={query ? { icon: X, label: '清除搜尋', onClick: () => setQuery('') } : undefined}
712
727
  />
713
728
  </div>
@@ -722,6 +737,7 @@ export const StateBehavior = {
722
737
  <Input
723
738
  type={showPwd ? 'text' : 'password'}
724
739
  defaultValue="my-secret-123"
740
+ aria-label="密碼"
725
741
  endAction={{
726
742
  icon: showPwd ? EyeOff : Eye,
727
743
  label: showPwd ? '隱藏密碼' : '顯示密碼',
@@ -737,10 +753,10 @@ export const StateBehavior = {
737
753
  <span className="text-caption font-medium text-fg-secondary">Disabled — endAction 不渲染,停用原因由外部承擔</span>
738
754
  <div className="flex flex-col gap-3 max-w-sm">
739
755
  <div className="flex items-center gap-3">
740
- <Input mode="edit" startIcon={Mail} defaultValue="alice@example.com"
756
+ <Input mode="edit" startIcon={Mail} defaultValue="alice@example.com" aria-label="Email(可編輯)"
741
757
  endAction={{ icon: X, label: '清除', onClick: () => {} }} className="w-56" />
742
758
  <span className="text-fg-muted text-caption">→</span>
743
- <Input mode="disabled" startIcon={Mail} defaultValue="alice@example.com"
759
+ <Input mode="disabled" startIcon={Mail} defaultValue="alice@example.com" aria-label="Email(停用)"
744
760
  endAction={{ icon: X, label: '清除', onClick: () => {} }} className="w-56" />
745
761
  </div>
746
762
  <span className="text-[11px] text-fg-muted">左:edit(有 endAction)→ 右:disabled(endAction 消失,icon 變 fg-disabled)</span>
@@ -73,11 +73,11 @@ export const UsageGuidance: Story = {
73
73
  title="❌ 不用 Input 顯示需要格式化的資料 → NumberInput / DatePicker"
74
74
  note="數字、日期、金額都需要格式化(千分位、時區、幣別符號)。每種資料類型有專屬元件,擁有該類型的格式化邏輯(唯一真實來源),同時服務 Form 和 DataTable"
75
75
  >
76
- <Input defaultValue="1234567" placeholder="金額" />
76
+ <Input defaultValue="1234567" placeholder="金額" aria-label="金額(不建議用 Input)" />
77
77
  <Label warn>↑ 金額 → 用 NumberInput + currency mode(自動 $1,234,567)</Label>
78
- <Input defaultValue="2026-04-18" placeholder="日期" />
78
+ <Input defaultValue="2026-04-18" placeholder="日期" aria-label="日期(不建議用 Input)" />
79
79
  <Label warn>↑ 日期 → 用 DatePicker(in-locale 顯示、避免時區誤判)</Label>
80
- <Input defaultValue="1234567" placeholder="數量" />
80
+ <Input defaultValue="1234567" placeholder="數量" aria-label="數量(不建議用 Input)" />
81
81
  <Label warn>↑ 數字 → 用 NumberInput(千分位、min/max、step)</Label>
82
82
  </Rule>
83
83
 
@@ -85,9 +85,9 @@ export const UsageGuidance: Story = {
85
85
  title="✅ Input 只用於純文字——沒有格式化需求的 value"
86
86
  note="姓名、URL、搜尋字串、email。格式化邏輯是 identity(value → value)"
87
87
  >
88
- <Input placeholder="姓名" defaultValue="Ada Chen" />
89
- <Input startIcon={Globe} defaultValue="https://example.com" />
90
- <Input startIcon={Search} placeholder="搜尋..." />
88
+ <Input placeholder="姓名" defaultValue="Ada Chen" aria-label="姓名" />
89
+ <Input startIcon={Globe} defaultValue="https://example.com" aria-label="網址" />
90
+ <Input startIcon={Search} placeholder="搜尋..." aria-label="搜尋" />
91
91
  </Rule>
92
92
  </Section>
93
93
  </div>
@@ -102,14 +102,14 @@ export const ModeRule: Story = {
102
102
  title="edit — 表單中可編輯的欄位(預設)"
103
103
  note="Focus 時邊框變 primary,使用者正在輸入或修改"
104
104
  >
105
- <Input defaultValue="Wireless Bluetooth Headphones" />
105
+ <Input defaultValue="Wireless Bluetooth Headphones" aria-label="商品名稱" />
106
106
  </Rule>
107
107
 
108
108
  <Rule
109
109
  title="readonly — 表單中顯示但當下不可編輯"
110
110
  note="同 edit 高度(neutral-2 底色、無邊框),確保與其他欄位並排對齊。常見場景:viewing mode、送出後鎖定但仍在表單脈絡"
111
111
  >
112
- <Input mode="readonly" value="alice@example.com" />
112
+ <Input mode="readonly" value="alice@example.com" aria-label="Email(唯讀)" />
113
113
  </Rule>
114
114
 
115
115
  <Rule
@@ -119,7 +119,7 @@ export const ModeRule: Story = {
119
119
  <Tooltip>
120
120
  <TooltipTrigger asChild>
121
121
  <div>
122
- <Input mode="disabled" defaultValue="僅付費方案可用" />
122
+ <Input mode="disabled" defaultValue="僅付費方案可用" aria-label="付費方案欄位(停用)" />
123
123
  </div>
124
124
  </TooltipTrigger>
125
125
  <TooltipContent>此欄位在免費方案中不可編輯</TooltipContent>
@@ -130,7 +130,7 @@ export const ModeRule: Story = {
130
130
  title="❌ 不用 readonly 作為 DataTable cell"
131
131
  note="readonly 有底色 chrome(bg-readonly),密集列表會顯得鬆散。Table cell 用 `<Input mode='view'>`(Model A:edit 幾何減 chrome —— 保留 px 與高度,只拔 border/bg;cell 內經 naked variant 由 host cell 供 padding)"
132
132
  >
133
- <Input mode="readonly" value="alice@example.com" />
133
+ <Input mode="readonly" value="alice@example.com" aria-label="Email(資料表誤用示範)" />
134
134
  <Label warn>↑ 在 table cell 中有多餘底色 chrome → 用 &lt;Input mode=&quot;view&quot; /&gt;</Label>
135
135
  </Rule>
136
136
  </div>
@@ -145,18 +145,18 @@ export const StartIconRule: Story = {
145
145
  title="startIcon 描述 input 的「用途」,不是 value 的類型"
146
146
  note="icon 幫助使用者一眼辨識這個欄位要填什麼——搜尋、Email、密碼、網址等。與 Button 的 startIcon 同理(描述動作),這裡描述欄位身份"
147
147
  >
148
- <Input startIcon={Search} placeholder="搜尋商品..." />
149
- <Input startIcon={Mail} defaultValue="alice@example.com" />
150
- <Input startIcon={Lock} type="password" defaultValue="secret-123" />
151
- <Input startIcon={Globe} placeholder="https://example.com" />
148
+ <Input startIcon={Search} placeholder="搜尋商品..." aria-label="搜尋商品" />
149
+ <Input startIcon={Mail} defaultValue="alice@example.com" aria-label="Email" />
150
+ <Input startIcon={Lock} type="password" defaultValue="secret-123" aria-label="密碼" />
151
+ <Input startIcon={Globe} placeholder="https://example.com" aria-label="網址" />
152
152
  </Rule>
153
153
 
154
154
  <Rule
155
155
  title="❌ startIcon 不可隨 value 變化"
156
156
  note="icon 是「身份」不是「狀態」——value 變了但欄位用途沒變,icon 不該跟著跳動。狀態回饋用 error border 或 Form help text"
157
157
  >
158
- <Input startIcon={AlertCircle} defaultValue="invalid-email" />
159
- <Input startIcon={CheckCircle2} defaultValue="alice@example.com" />
158
+ <Input startIcon={AlertCircle} defaultValue="invalid-email" aria-label="Email(錯誤圖示誤用)" />
159
+ <Input startIcon={CheckCircle2} defaultValue="alice@example.com" aria-label="Email(成功圖示誤用)" />
160
160
  <Label warn>↑ 用 icon 切換暗示「有效/無效」——status 應該走邊框和 help text,不佔 startIcon 語意槽</Label>
161
161
  </Rule>
162
162
  </div>
@@ -177,6 +177,7 @@ export const EndActionRule: Story = {
177
177
  <Input
178
178
  type={showPwd ? 'text' : 'password'}
179
179
  defaultValue="my-secret-123"
180
+ aria-label="密碼"
180
181
  endAction={{
181
182
  icon: showPwd ? EyeOff : Eye,
182
183
  label: showPwd ? '隱藏密碼' : '顯示密碼',
@@ -187,6 +188,7 @@ export const EndActionRule: Story = {
187
188
  startIcon={Search}
188
189
  value={query}
189
190
  onChange={(e) => setQuery(e.target.value)}
191
+ aria-label="搜尋商品"
190
192
  endAction={query ? { icon: X, label: '清除搜尋', onClick: () => setQuery('') } : undefined}
191
193
  />
192
194
  </Rule>
@@ -199,11 +201,13 @@ export const EndActionRule: Story = {
199
201
  startIcon={Search}
200
202
  value=""
201
203
  placeholder="輸入後會出現清除按鈕"
204
+ aria-label="搜尋商品(空值)"
202
205
  readOnly
203
206
  />
204
207
  <Input
205
208
  startIcon={Search}
206
209
  defaultValue="Bluetooth"
210
+ aria-label="搜尋商品(有值)"
207
211
  endAction={{ icon: X, label: '清除搜尋', onClick: () => {} }}
208
212
  />
209
213
  </Rule>
@@ -215,6 +219,7 @@ export const EndActionRule: Story = {
215
219
  <Input
216
220
  error
217
221
  defaultValue="invalid-email@"
222
+ aria-label="Email(錯誤狀態圖示誤用)"
218
223
  endAction={{ icon: AlertCircle, label: '錯誤', onClick: () => {} }}
219
224
  />
220
225
  <Label warn>↑ 錯誤已由紅邊框傳達,endAction 放 AlertCircle 會讓使用者誤以為可點擊解決</Label>
@@ -233,7 +238,7 @@ export const ErrorRule: Story = {
233
238
  note="紅色邊框已足夠傳達「這個欄位有問題」,具體訊息由 Form 層的 help text 補充。Input 尾部不放 ⚠️ 狀態 icon"
234
239
  >
235
240
  <div>
236
- <Input error defaultValue="invalid-email@" />
241
+ <Input error defaultValue="invalid-email@" aria-label="Email(錯誤狀態)" />
237
242
  <p className="text-caption text-error mt-1">請輸入有效的 email 地址</p>
238
243
  </div>
239
244
  </Rule>
@@ -245,6 +250,7 @@ export const ErrorRule: Story = {
245
250
  <Input
246
251
  error
247
252
  defaultValue="invalid-email@"
253
+ aria-label="Email(錯誤狀態圖示誤用)"
248
254
  endAction={{ icon: AlertCircle, label: '錯誤', onClick: () => {} }}
249
255
  />
250
256
  <Label warn>↑ 邊框已紅,再加尾部 icon = 雙重傳達 + 佔用 action 槽</Label>
@@ -252,4 +258,3 @@ export const ErrorRule: Story = {
252
258
  </div>
253
259
  ),
254
260
  }
255
-
@@ -58,7 +58,12 @@ export const SizeAlignment: Story = {
58
58
  <div className="flex flex-col gap-4">
59
59
  {(['sm', 'md', 'lg'] as const).map(size => (
60
60
  <div key={size} className="flex items-center gap-3">
61
- <Input size={size} defaultValue="Wireless Bluetooth Headphones" className="max-w-xs" />
61
+ <Input
62
+ size={size}
63
+ defaultValue="Wireless Bluetooth Headphones"
64
+ aria-label={`商品名稱(${size} 尺寸)`}
65
+ className="max-w-xs"
66
+ />
62
67
  <Button variant="primary" size={size}>送出</Button>
63
68
  <span className="text-caption text-fg-muted">size="{size}"</span>
64
69
  </div>
@@ -83,6 +88,7 @@ export const EndAction: Story = {
83
88
  <Input
84
89
  type={showPwd ? 'text' : 'password'}
85
90
  defaultValue="my-secret-123"
91
+ aria-label="密碼"
86
92
  endAction={{
87
93
  icon: showPwd ? EyeOff : Eye,
88
94
  label: showPwd ? '隱藏密碼' : '顯示密碼',
@@ -96,6 +102,7 @@ export const EndAction: Story = {
96
102
  startIcon={Search}
97
103
  value={query}
98
104
  onChange={(e) => setQuery(e.target.value)}
105
+ aria-label="搜尋商品"
99
106
  endAction={query ? { icon: X, label: '清除搜尋', onClick: () => setQuery('') } : undefined}
100
107
  />
101
108
  </div>
@@ -106,6 +113,7 @@ export const EndAction: Story = {
106
113
  startIcon={Search}
107
114
  value={queryLg}
108
115
  onChange={(e) => setQueryLg(e.target.value)}
116
+ aria-label="搜尋商品(大尺寸)"
109
117
  endAction={queryLg ? { icon: X, label: '清除搜尋', onClick: () => setQueryLg('') } : undefined}
110
118
  />
111
119
  </div>
@@ -8,6 +8,7 @@ import { fieldWrapperStyles, bareInputStyles } from '@/design-system/components/
8
8
  import { useFieldContext, useResolvedFieldSize, useResolvedFieldDisabled, useResolvedFieldMode, useResolvedFieldVariant, useResolvedFieldInvalid, useFieldEmptyDisplay, fieldEmptyColorClass } from '@/design-system/components/Field/field-context'
9
9
  import { useControllable } from '@/design-system/hooks/use-controllable'
10
10
  import { ItemInlineAction, ItemPrefix, type InlineActionConfig } from '@/design-system/patterns/element-anatomy/item-anatomy'
11
+ import { TruncatedText } from '@/design-system/patterns/element-anatomy/truncated-text'
11
12
  import { CircularProgress } from '@/design-system/components/CircularProgress/circular-progress'
12
13
  import { ICON_SIZE } from '@/design-system/tokens/uiSize/icon-size'
13
14
 
@@ -184,11 +185,15 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
184
185
  // B1 fix(2026-05-05):view mode 單行 ellipsis 截斷(對齊 Notion / Airtable / Linear
185
186
  // cell display canonical:single-line value 過長 → ellipsis。Textarea view 走 wrap path,
186
187
  // 不在此處;Input view 永遠 single-line。)
187
- 'truncate',
188
+ 'min-w-0',
188
189
  displayValue == null && fieldEmptyColorClass(resolvedMode),
189
190
  )}
190
191
  >
191
- {displayValue ?? emptyDisplay}
192
+ {/* 2026-07-28:截斷必附 tooltip(tooltip.spec.md)—— 原本只有 `truncate` 沒有 tooltip,
193
+ 長值被裁掉就再也讀不到。改消費 truncated-text primitive(截斷才顯,未截斷靜默)。 */}
194
+ {displayValue == null
195
+ ? emptyDisplay
196
+ : <TruncatedText>{displayValue}</TruncatedText>}
192
197
  </span>
193
198
  </div>
194
199
  )