@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
@@ -67,23 +67,65 @@ const radixItemClass = [
67
67
  ].join(' ')
68
68
 
69
69
  // ── Root ──
70
- const DropdownMenu = DropdownMenuPrimitive.Root
70
+ // Radix modal menu 會在 open 時把 trigger 所在的 app subtree 設成 aria-hidden。瀏覽器仍可
71
+ // 讓 subtree 內原本的 trigger 留在 sequential focus order,axe 因而正確報
72
+ // aria-hidden-focus。由 Root SSOT 對所有 DropdownMenu 統一同步 trigger tab stop;Portal
73
+ // 仍保留 React context,所以不需要 consumer/story 各自 patch。
74
+ const DropdownMenuModalOpenContext = React.createContext(false)
75
+
76
+ const DropdownMenu = ({
77
+ open,
78
+ defaultOpen,
79
+ onOpenChange,
80
+ modal = true,
81
+ ...props
82
+ }: React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Root>) => {
83
+ const [uncontrolledOpen, setUncontrolledOpen] = React.useState(defaultOpen ?? false)
84
+ const resolvedOpen = open ?? uncontrolledOpen
85
+ const handleOpenChange = React.useCallback(
86
+ (nextOpen: boolean) => {
87
+ if (open === undefined) setUncontrolledOpen(nextOpen)
88
+ onOpenChange?.(nextOpen)
89
+ },
90
+ [onOpenChange, open],
91
+ )
92
+
93
+ return (
94
+ <DropdownMenuModalOpenContext.Provider value={modal && resolvedOpen}>
95
+ <DropdownMenuPrimitive.Root
96
+ open={open}
97
+ defaultOpen={defaultOpen}
98
+ onOpenChange={handleOpenChange}
99
+ modal={modal}
100
+ {...props}
101
+ />
102
+ </DropdownMenuModalOpenContext.Provider>
103
+ )
104
+ }
105
+ DropdownMenu.displayName = DropdownMenuPrimitive.Root.displayName
106
+
71
107
  const DropdownMenuTrigger = React.forwardRef<
72
108
  React.ElementRef<typeof DropdownMenuPrimitive.Trigger>,
73
109
  React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Trigger>
74
- >(({ className, ...props }, ref) => (
75
- <DropdownMenuPrimitive.Trigger
76
- ref={ref}
77
- // 2026-07-05:非 asChild 裸用需可見 focus 指示(WCAG 2.4.7)——原 `outline-none` 讓裸 Trigger
78
- // 完全無 focus ring。消費 Button focus canonical(button.tsx buttonVariants base)。
79
- // asChild+Button 場景同名 class 重複,無影響。
80
- className={cn(
81
- 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1',
82
- className,
83
- )}
84
- {...props}
85
- />
86
- ))
110
+ >(({ className, tabIndex, ...props }, ref) => {
111
+ const modalOpen = React.useContext(DropdownMenuModalOpenContext)
112
+ return (
113
+ <DropdownMenuPrimitive.Trigger
114
+ ref={ref}
115
+ // modal open 時 trigger 位於 Radix aria-hidden subtree:移出 sequential focus order;
116
+ // 關閉時仍由 Radix programmatically focus,且恢復 consumer 原 tabIndex。
117
+ tabIndex={modalOpen ? -1 : tabIndex}
118
+ // 2026-07-05:非 asChild 裸用需可見 focus 指示(WCAG 2.4.7)——原 `outline-none` 讓裸 Trigger
119
+ // 完全無 focus ring。消費 Button focus canonical(button.tsx buttonVariants base)。
120
+ // asChild+Button 場景同名 class 重複,無影響。
121
+ className={cn(
122
+ 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1',
123
+ className,
124
+ )}
125
+ {...props}
126
+ />
127
+ )
128
+ })
87
129
  DropdownMenuTrigger.displayName = DropdownMenuPrimitive.Trigger.displayName
88
130
  // DropdownMenuGroup — 對齊 MenuGroup 的 group separation 設計語言
89
131
  //
@@ -178,7 +220,7 @@ const DropdownMenuContent = React.forwardRef<
178
220
  <RowSizeProvider value={size}>
179
221
  {/* body 恆在 flex-1 ScrollArea 內:超過 clamped 高度即跨-OS 一致捲動(不吃寬度);
180
222
  短選單 flex-1 貼內容不塌(同 HoverCard/Popover/DataTable panel 已驗)。py-2 在內層,整 padded 區可捲。 */}
181
- <ScrollArea className="flex-1 min-h-0">
223
+ <ScrollArea className="flex-1 min-h-0" viewportTabIndex={null}>
182
224
  <div className="py-2">{children}</div>
183
225
  </ScrollArea>
184
226
  </RowSizeProvider>
@@ -413,7 +455,7 @@ const DropdownMenuLabel = React.forwardRef<
413
455
  size={size}
414
456
  header
415
457
  role="presentation"
416
- className="pointer-events-none"
458
+ className="pointer-events-none text-fg-secondary"
417
459
  >
418
460
  {children}
419
461
  </MenuItem>
@@ -44,6 +44,11 @@ export interface FieldContextValue {
44
44
  required: boolean
45
45
  invalid: boolean
46
46
  size: FieldSize
47
+ /** `size` 是被決定過的(consumer 顯式傳 / control 宣告偏好)還是 Field 的預設 md。
48
+ * 預設 md 不得壓過 control 自己宣告的 fallback —— 詳 useResolvedFieldSize 與 field.spec.md。 */
49
+ sizeExplicit: boolean
50
+ /** Control 於掛載時宣告偏好尺寸(wrapper/HOC 遮蔽靜態 `fieldPreferredSize` 時的補位通道)。 */
51
+ registerPreferredSize?: (preferred: FieldSize) => void
47
52
  orientation: FieldOrientation
48
53
  controlLayout: FieldControlLayout
49
54
  hasFieldWrapper: true
@@ -166,7 +171,26 @@ export function useResolvedFieldSize<T extends string = FieldSize>(sizeProp?: T
166
171
  // generic T(預設 FieldSize)讓非-input 控件(SegmentedControl/Rating 的 'xs'|'sm'|'md'|'lg' 超集、各自 fallback)
167
172
  // 也走同一 SSOT resolution。fieldCtx.size/surfaceSize 為 FieldSize(T 的子集)→ widen cast 安全。
168
173
  // fallback 未傳預設 'md'(input-class 控件向後相容);Rating 傳 'xs'、SegmentedControl 傳 'md'。
169
- return (sizeProp ?? (fieldCtx?.size as T | undefined) ?? (surfaceSize as T | undefined) ?? fallback ?? ('md' as T))
174
+ //
175
+ // 2026-07-28:Field 的 size 只有在 consumer 顯式指定、或 Field 已解析出 control 偏好時才算「決定過」
176
+ // (`sizeExplicit`)。否則那只是 Field 的預設 md,不該壓過控件自己宣告的 fallback ——
177
+ // 舊行為讓「InlineEdit 被包一層 wrapper」時靜默退回 md(詳 field.spec.md 偏好尺寸 SSOT)。
178
+ const contextSize = fieldCtx && fieldCtx.sizeExplicit ? (fieldCtx.size as T | undefined) : undefined
179
+ return (sizeProp ?? contextSize ?? fallback ?? (fieldCtx?.size as T | undefined) ?? (surfaceSize as T | undefined) ?? ('md' as T))
180
+ }
181
+
182
+ /**
183
+ * Control 向外層 Field 宣告偏好尺寸(render-time)。靜態 `fieldPreferredSize` 只有在 control 是
184
+ * Field 的**直接子元件**時才偵測得到;包 wrapper / `React.memo` / HOC 會遮蔽它。本 hook 讓 control
185
+ * 在掛載時主動註冊,Field 的**控件槽高度**因此也能跟上(控件本身的尺寸則由 `useResolvedFieldSize`
186
+ * 同步解析,不等這次註冊)。
187
+ */
188
+ export function useRegisterFieldPreferredSize(preferred?: FieldSize | null): void {
189
+ const fieldCtx = React.useContext(FieldContext)
190
+ const register = fieldCtx?.registerPreferredSize
191
+ React.useEffect(() => {
192
+ if (preferred && register) register(preferred)
193
+ }, [preferred, register])
170
194
  }
171
195
 
172
196
  /**
@@ -388,5 +388,11 @@ export const fieldViewGeometry = (size: 'sm' | 'md' | 'lg', multiline?: boolean)
388
388
  size === 'sm' && 'min-h-[var(--field-height-sm)]',
389
389
  size === 'md' && 'min-h-[var(--field-height-md)]',
390
390
  size === 'lg' && 'min-h-[var(--field-height-lg)]',
391
- multiline ? 'items-start py-2' : 'items-center',
391
+ // 多行 view py 必等於 Textarea edit py(同一 --field-control-py-* token),否則
392
+ // InlineEdit 多行 read↔edit 垂直跳位;scripts/inline-edit-view-geometry-invariant.mjs 機械鎖。
393
+ multiline && 'items-start',
394
+ multiline && size === 'sm' && 'py-[var(--field-control-py-sm)]',
395
+ multiline && size === 'md' && 'py-[var(--field-control-py-md)]',
396
+ multiline && size === 'lg' && 'py-[var(--field-control-py-lg)]',
397
+ !multiline && 'items-center',
392
398
  )
@@ -214,6 +214,12 @@ Field 透過 Context 暴露以下狀態給子元件(Primitive 可以透過 `us
214
214
  | `orientation` | FieldLabel 的垂直對齊策略(horizontal 模式套用 padding-top 公式) |
215
215
  | `hasFieldWrapper` | Primitive 讀到此旗標時應忽略自己的 label / description prop,由 FieldLabel / FieldDescription 接管 |
216
216
 
217
+ ### Control 偏好尺寸(`fieldPreferredSize`)SSOT
218
+
219
+ Field 未收到顯式 `size` 時,依序取 **顯式 prop → control 宣告的 `fieldPreferredSize` → `md`**(`field.tsx` `detectPreferredSize`)。Control 以自身檔案的 static 宣告偏好(唯一現行宣告者 = InlineEdit `'sm'`,owner 句 `InlineEdit/inline-edit.spec.md`)。
220
+
221
+ **失效語意(硬規則)**:偏好只在 control 是 Field 的**直接子元件**時才偵測得到。包一層 wrapper 元件、`React.memo`、HOC 都會遮蔽 static,Field 退回 `md`,且 `fieldCtx.size` 在 `useResolvedFieldSize` 的優先序中**高於** control 自己的 fallback,因此 control 再也搶不回偏好尺寸。**Consumer 對策**:把 state 提升到 Field 外層讓 control 保持直接子元件,或顯式傳 `size`。**禁**:為了包 wrapper 而預期偏好仍然生效。
222
+
217
223
  ### Primitive 的 Field-aware 行為
218
224
 
219
225
  **Checkbox / Switch / RadioItem** 等 primitive 自己有 `label` / `description` props 可用於**獨立使用場景**(不在 Field 內),但在 Field context 內時:
@@ -335,7 +341,7 @@ Button 的 height 與 `field-height` 共用同一組 token,放進 inline control
335
341
  ## 邊界案例
336
342
 
337
343
  - **空 Field(無 control children)**:code 不擋——control area 渲染成空容器。Field 是佈局容器,無 control 即無意義,屬 consumer 誤用
338
- - **RTL**:未實作方向鏡像(horizontal label 欄固定在左、Switch `ml-auto` 推右皆以 LTR 設計);RTL 屬 DS-wide 決策,未定(與 Chip / Breadcrumb 同口徑)
344
+ - **RTL**:不支援;全域 LTR-only compatibility contract 見 `packages/design-system/README.md#compatibility-matrix`。horizontal label / suffix alignment 不在本檔另立支援決策。
339
345
 
340
346
  ---
341
347
 
@@ -458,6 +464,7 @@ Field 內的資料輸入控件(Input / NumberInput / DatePicker / Select / Com
458
464
  - `field-control-group.spec.md`
459
465
  - `field-controls.spec.md`
460
466
  - `form-validation.spec.md`
467
+ - `inline-edit.spec.md`
461
468
  - `input.spec.md`
462
469
  - `radio-group.spec.md`
463
470
  - `switch.spec.md`
@@ -216,7 +216,16 @@ const Field = React.forwardRef<HTMLDivElement, FieldProps>(
216
216
 
217
217
  // 解析 size:consumer 顯式 > control primitive 偏好(如 InlineEdit='sm')> 預設 md
218
218
  // (對齊 controlLayout 三層優先序;InlineEdit 進 Field 不指定 size 時自動收 sm)
219
- const size: FieldSize = sizeProp ?? detectPreferredSize(controlNodes) ?? 'md'
219
+ // 靜態偵測只看得到直接子元件;包 wrapper / memo / HOC 時由 control 於掛載時註冊補位,
220
+ // 讓 Field 的控件槽高度也跟上(control 自身尺寸走 useResolvedFieldSize 同步解析)。
221
+ const [registeredSize, setRegisteredSize] = React.useState<FieldSize | null>(null)
222
+ const registerPreferredSize = React.useCallback((preferred: FieldSize) => {
223
+ setRegisteredSize((current) => (current === preferred ? current : preferred))
224
+ }, [])
225
+ const detectedSize = detectPreferredSize(controlNodes)
226
+ const resolvedPreferredSize = sizeProp ?? detectedSize ?? registeredSize
227
+ const size: FieldSize = resolvedPreferredSize ?? 'md'
228
+ const sizeExplicit = resolvedPreferredSize != null
220
229
 
221
230
  const contextValue = React.useMemo<FieldContextValue>(
222
231
  () => ({
@@ -230,11 +239,13 @@ const Field = React.forwardRef<HTMLDivElement, FieldProps>(
230
239
  required,
231
240
  invalid,
232
241
  size,
242
+ sizeExplicit,
243
+ registerPreferredSize,
233
244
  orientation,
234
245
  controlLayout,
235
246
  hasFieldWrapper: true,
236
247
  }),
237
- [id, labelId, descriptionId, errorId, mode, variant, disabled, required, invalid, size, orientation, controlLayout]
248
+ [id, labelId, descriptionId, errorId, mode, variant, disabled, required, invalid, size, sizeExplicit, registerPreferredSize, orientation, controlLayout]
238
249
  )
239
250
 
240
251
  // Control area:兩種佈局模型,「第一行內容中線」都錨在 field-height/2,
@@ -426,6 +437,7 @@ const FieldLabel = React.forwardRef<HTMLLabelElement, FieldLabelProps>(
426
437
  <button
427
438
  type="button"
428
439
  aria-label={info}
440
+ // 弱化 icon hover 一階(inline-action.spec.md「Icon 色彩」;2026-07-30 前全 DS 唯一合規處)
429
441
  className="inline-flex items-center text-fg-muted hover:text-fg-secondary bg-transparent border-0 p-0 cursor-pointer"
430
442
  >
431
443
  <InfoIcon size={16} aria-hidden />
@@ -53,14 +53,14 @@ export const Overview: Story = {
53
53
  ),
54
54
  }
55
55
 
56
- /* SizeMatrix */
56
+ /* Export id 保留 corpus 穩定;此矩陣只改 child size,FieldControlGroup 本身沒有 size API。 */
57
57
  export const SizeMatrix: Story = {
58
- name: '尺寸矩陣',
58
+ name: '子元件尺寸對齊',
59
59
  render: () => (
60
60
  <div className="flex flex-col gap-6 w-[420px]">
61
61
  {(['sm', 'md', 'lg'] as const).map(size => (
62
62
  <div key={size}>
63
- <p className="text-caption text-fg-muted mb-2">size="{size}"</p>
63
+ <p className="text-caption text-fg-muted mb-2">children size="{size}"(group 無 size prop)</p>
64
64
  <FieldControlGroup block>
65
65
  <Select size={size} className="!w-[120px]" options={FIELDS} value="name" onChange={() => {}} aria-label="篩選欄位" />
66
66
  <Input size={size} className="flex-1" defaultValue="無線滑鼠" aria-label="篩選值" />
@@ -52,7 +52,6 @@ benchmark:
52
52
 
53
53
  ```ts
54
54
  interface FieldControlGroupProps extends HTMLAttributes<HTMLDivElement> {
55
- size?: 'sm' | 'md' | 'lg' // default 'md';目前為 no-op(FCG 不傳遞),size 由各 child 自管 — 見「Size cascade」
56
55
  block?: boolean // 對齊 Ant Space.Compact `block`,默 false=inline-flex
57
56
  }
58
57
  ```
@@ -87,7 +86,7 @@ interface FieldControlGroupProps extends HTMLAttributes<HTMLDivElement> {
87
86
 
88
87
  ## Size
89
88
 
90
- **FCG `size` prop 目前為 no-op**:本元件只負責 border-collapse 接合(border / radius / z-index),不傳遞 size 給 children(無 Context Provider / 無 cloneElement)。Children 的尺寸完全由 child 自己決定 — 以下三種來源:
89
+ **FCG 不暴露 `size` prop**:本元件只負責 border-collapse 接合(border / radius / z-index),不傳遞 size 給 children(無 Context Provider / 無 cloneElement)。未實作的 group-level size 不預佔 API。Children 的尺寸完全由 child 自己決定 — 以下三種來源:
91
90
 
92
91
  **繼承 Field 尺寸**(包進 Field 當 control slot):children 各自透過 `useFieldContext().size` 讀外層 `<Field>` 的 size(是 Field 的 context,不是 FCG 的)。
93
92
  ```tsx
@@ -100,7 +99,7 @@ interface FieldControlGroupProps extends HTMLAttributes<HTMLDivElement> {
100
99
  </Field>
101
100
  ```
102
101
 
103
- **Standalone 逐一設尺寸**:**逐一給每個 child 設 `size`**(設 `<FieldControlGroup size>` 無效)。
102
+ **Standalone 逐一設尺寸**:**逐一給每個 child 設 `size`**。
104
103
  ```tsx
105
104
  <FieldControlGroup>
106
105
  <Select size="md" className="!w-[120px]" options={fields} />
@@ -158,4 +158,5 @@ export const FilterRow: Story = {
158
158
  },
159
159
  }
160
160
 
161
- /* @story-trait-rationale: AllSizes retired per F migration 2026-05-15 — anatomy.stories.tsx SizeMatrix auto-compile owns size showcase。 */
161
+ /* @story-trait-rationale: AllSizes retired per F migration 2026-05-15 — anatomy.stories.tsx 的
162
+ ChildSizeAlignment 只展示 children 尺寸接合;FieldControlGroup 本身不擁有 size API。 */
@@ -9,7 +9,6 @@
9
9
  //
10
10
  import * as React from 'react'
11
11
  import { cn } from '@/lib/utils'
12
- import type { FieldSize } from '@/design-system/components/Field/field-context'
13
12
 
14
13
  /**
15
14
  * FieldControlGroup — 多個 Field controls 視覺接合成一個 input frame
@@ -26,10 +25,10 @@ import type { FieldSize } from '@/design-system/components/Field/field-context'
26
25
  * - z-index:default 2 / hover|focus|focus-within 3 / disabled 0
27
26
  * - Container `display: inline-flex`(Ant default)/ `block` prop → `display: flex; width: 100%`
28
27
  *
29
- * **Size**(FCG `size` 為 no-op — 不傳遞 size 給 children,無 Context Provider / 無 cloneElement;見 spec「Size」段):
28
+ * **Size**(FCG 不擁有 size API;不傳遞 size 給 children,無 Context Provider / 無 cloneElement;見 spec「Size」段):
30
29
  * - children 尺寸完全由 child 自己決定;FCG 只負責 border-collapse 接合(border / radius / z-index)
31
30
  * - Mode A:整個 FieldControlGroup 包進 Field 當 control slot,children 各自讀外層 `<Field>` 的 context size(是 Field 的 context,不是 FCG 的)
32
- * - Mode B:standalone 用,逐一給每個 child 設 `size`(設 `<FieldControlGroup size>` 無效)
31
+ * - Mode B:standalone 用,逐一給每個 child 設 `size`
33
32
  *
34
33
  * **Width 配置**(Ant Space.Compact W-A canonical):
35
34
  * - 子 controls 自管 width(`className="w-[140px]"` / `style={{width:120}}` / `flex-1` etc.)
@@ -62,14 +61,12 @@ import type { FieldSize } from '@/design-system/components/Field/field-context'
62
61
  */
63
62
 
64
63
  export interface FieldControlGroupProps extends React.HTMLAttributes<HTMLDivElement> {
65
- /** no-op — FCG 不傳遞 size 給 children;Mode A 由 children 讀外層 Field context、Mode B 逐一給每個 child 設 size(見 spec「Size」段) */
66
- size?: FieldSize
67
64
  /** Block 模式:width 100% 撐滿 parent(對齊 Ant Space.Compact `block` prop) */
68
65
  block?: boolean
69
66
  }
70
67
 
71
68
  const FieldControlGroup = React.forwardRef<HTMLDivElement, FieldControlGroupProps>(
72
- ({ size: _size = 'md', block = false, className, children, ...props }, ref) => {
69
+ ({ block = false, className, children, ...props }, ref) => {
73
70
  return (
74
71
  <div
75
72
  ref={ref}
@@ -116,14 +113,14 @@ FieldControlGroup.displayName = 'FieldControlGroup'
116
113
  // Story auto-compile metadata — Phase 4 migration(2026-05-10 #12 task complete)
117
114
  // Per scripts/compile-stories.mjs --check。FieldControlGroup is self-contained
118
115
  // structural wrapper(border-collapse pattern for Field controls)。
119
- // **No own sizes** — size prop 目前為 no-op(不傳遞給 children,無 Context Provider /
120
- // 無 cloneElement;children 尺寸自管,見上方 docblock「Size」段 + spec「FCG `size` prop 目前為 no-op」),
116
+ // **No own sizes** — 不暴露 size prop(不傳遞給 children,無 Context Provider /
117
+ // 無 cloneElement;children 尺寸自管,見上方 docblock「Size」段),
121
118
  // so sizes:{} matches spec frontmatter (no sizes declared)。
122
119
  export const fieldControlGroupMeta = {
123
120
  component: 'FieldControlGroup',
124
121
  family: 'self-contained',
125
122
  variants: {},
126
- sizes: {}, // self-contained wrapper;size prop no-op(children 尺寸自管)
123
+ sizes: {}, // self-contained wrapper;children 尺寸自管
127
124
  states: ['default', 'children-hover', 'children-focus', 'children-disabled'],
128
125
  tokens: {
129
126
  bg: [], // structural wrapper has no own bg
@@ -459,7 +459,7 @@ Passive status icon 置中於 action-sized 容器,hover 時 active action 填滿
459
459
  - **超長檔名**:label 經 `ItemContent` 預設 `truncate` 單行截斷(ellipsis),不換行。
460
460
  - **過長 description**:自由換行不截斷(FileItem 未傳 `descriptionClamp`);rich 多行 desc 時 progress bar 隨內容下移並保 8px min gap(見 Rich layout invariant)。
461
461
  - **progress 精度**:傳入值直接顯示(rich 的 `%` 文字與 bar 同源),元件不四捨五入;`completed` 強制 100。
462
- - **RTL**:未特化——compact progress bar offset 用 physical `left/right`(見 `file-item.tsx`),RTL 支援需另案。
462
+ - **RTL**:不支援;全域 LTR-only compatibility contract 見 `packages/design-system/README.md#compatibility-matrix`。compact progress offset 維持 physical direction。
463
463
  - **Dark mode**:走 semantic token 自動 adapt。無 `disabled` prop(展示型 row,非 form control)。
464
464
 
465
465
  ---
@@ -82,7 +82,7 @@ export const HoverSwap = {
82
82
  </div>
83
83
  {/* Rich border card list:無外框 + `gap-2`(item-anatomy「連續 item 貼邊合法性」) */}
84
84
  <div className="flex flex-col gap-2">
85
- <FileItem mode="rich" name="Q1 營收報表.xlsx" status="completed"
85
+ <FileItem mode="rich" name="Q1 營收報表.xlsx" status="completed" data-visual-hover-target
86
86
  description="Uploaded to URL" thumbnailSrc="https://i.pravatar.cc/80?u=xls"
87
87
  onDownload={noop} actions={deleteBtn} />
88
88
  <FileItem mode="rich" name="合約草案 v3.pdf" status="error"
@@ -1,5 +1,6 @@
1
1
  // @story-trait-rationale: hasInteractiveStates 的 Disabled / States 由 anatomy.stories.tsx StateBehavior auto-compile owns(2026-05-15 F-migration);showcase 層展示真實上傳 / 自訂內容情境。
2
2
  import type { Meta } from '@storybook/react'
3
+ import { within } from '@storybook/test'
3
4
  import { useState } from 'react'
4
5
  import { Image as ImageIcon, X } from 'lucide-react'
5
6
  import { FileUpload } from './file-upload'
@@ -116,6 +117,37 @@ export const WithFileList = {
116
117
  },
117
118
  }
118
119
 
120
+ // beta.97 弱化 icon hover 一階的真實互動證據:
121
+ // FileUpload 內建 files 清單自己渲染 remove Button,story 不以 className 模擬 hover。
122
+ export const RemoveHoverState = {
123
+ name: '移除檔案懸停狀態',
124
+ tags: ['!autodocs'],
125
+ render: () => (
126
+ <div className="max-w-lg">
127
+ <FileUpload
128
+ title="上傳季度報告"
129
+ description="PDF,單檔最大 20 MB"
130
+ files={[
131
+ {
132
+ id: 'quarterly-report',
133
+ name: '2026-Q2-季度報告.pdf',
134
+ size: 2_500_000,
135
+ status: 'completed',
136
+ },
137
+ ]}
138
+ fileListMode="compact"
139
+ onRemove={noop}
140
+ onUpload={noop}
141
+ />
142
+ </div>
143
+ ),
144
+ play: async ({ canvasElement }: { canvasElement: HTMLElement }) => {
145
+ const canvas = within(canvasElement)
146
+ const remove = await canvas.findByRole('button', { name: '移除 2026-Q2-季度報告.pdf' })
147
+ remove.setAttribute('data-visual-hover-target', '')
148
+ },
149
+ }
150
+
119
151
  // children 覆寫:Figma 匯入資產客製文案 / 品牌 logo
120
152
  export const CustomChildren = {
121
153
  name: '自訂內容',
@@ -208,7 +208,7 @@ const FileUpload = React.forwardRef<HTMLDivElement, FileUploadProps>(
208
208
  e.stopPropagation()
209
209
  onRemove(f.id)
210
210
  }}
211
- className="text-fg-muted hover:text-foreground"
211
+ className="text-fg-muted hover:text-fg-secondary"
212
212
  />
213
213
  ) : undefined
214
214
  }
@@ -1124,7 +1124,7 @@ export const Accessibility = {
1124
1124
  render: () => (
1125
1125
  <div className="max-w-3xl text-body text-fg-secondary">
1126
1126
  <h3 className="text-h5 text-foreground mb-2">無障礙設計</h3>
1127
- <p className="whitespace-pre-line">{"詳 `file-viewer.spec.md` 「A11y 預設」段。摘要:\n\nRadix DialogPrimitive 自動處理:\n- role=\"dialog\" (Radix 1.1.x 不輸出 aria-modal 屬性;modal 隔離靠 focus trap + hideOthers 對背景兄弟樹加 aria-hidden,非 aria-modal)\n- <DialogPrimitive.Title> (sr-only)自動成 aria-labelledby 目標——screen reader 開啟時讀「檔案檢視器:{file.name}」\n- Focus trap:焦點一旦進入 Content 即鎖在 viewer 內\n- Esc 關閉(Radix DismissableLayer,document capture-phase 監聽 → 即使焦點在 InfoPanel textarea 內按 Esc 仍會關閉整個 viewer);Backdrop click 關閉為自寫 geometric onClick handler(非 Radix outside-click — Content 為 fixed inset-0 全螢幕覆蓋,判斷 click 落在 img rect 外才關)\n\n自加的 a11y:\n- 所有 iconOnly button 皆有 aria-label (中文,跟 DS 其他元件風格一致)\n- Filmstrip role=\"group\" + thumb <button> + aria-current (非 tablist:選圖導航非切 tabpanel)\n- InfoPanel 用 <aside aria-label=\"檔案詳細資訊\"> 語意標記\n- onOpenAutoFocus preventDefault:僅取消 Radix 自動聚焦(焦點留在觸發按鈕、不主動移入 viewport);焦點實際進入 Content 後 FocusScope 才啟動 trap"}</p>
1127
+ <p className="whitespace-pre-line">{"詳 `file-viewer.spec.md` 「A11y 預設」段。摘要:\n\nRadix DialogPrimitive 自動處理:\n- role=\"dialog\" (Radix 1.1.x 不輸出 aria-modal 屬性;modal 隔離靠 focus trap + hideOthers 對背景兄弟樹加 aria-hidden,非 aria-modal)\n- <DialogPrimitive.Title> (sr-only)自動成 aria-labelledby 目標——screen reader 開啟時讀「檔案檢視器:{file.name}」\n- Focus trap:開啟時 Content 本身取得焦點並立即鎖在 viewer 內\n- Esc 關閉(Radix DismissableLayer,document capture-phase 監聽 → 即使焦點在 InfoPanel textarea 內按 Esc 仍會關閉整個 viewer);Backdrop click 關閉為自寫 geometric onClick handler(非 Radix outside-click — Content 為 fixed inset-0 全螢幕覆蓋,判斷 click 落在 img rect 外才關)\n\n自加的 a11y:\n- 所有 iconOnly button 皆有 aria-label (中文,跟 DS 其他元件風格一致)\n- Filmstrip role=\"group\" + thumb <button> + aria-current (非 tablist:選圖導航非切 tabpanel)\n- InfoPanel 用 <aside aria-label=\"檔案詳細資訊\"> 語意標記\n- Content 固定 tabIndex=-1;onOpenAutoFocus 在 consumer 未取消時聚焦 fullscreen shell,不把焦點留在 aria-hidden 背景或誤觸第一個 toolbar tooltip"}</p>
1128
1128
  </div>
1129
1129
  ),
1130
1130
  }
@@ -370,7 +370,7 @@ Radix DialogPrimitive 自動處理:
370
370
  - 所有 iconOnly button 皆有 `aria-label`(中文,跟 DS 其他元件風格一致)
371
371
  - Filmstrip `role="group"` + `aria-label` + thumb `<button>` + `aria-current`(非 tablist;見「Filmstrip 規則」段理由)
372
372
  - InfoPanel 用 `<aside aria-label="檔案詳細資訊">` 語意標記
373
- - `onOpenAutoFocus={e => e.preventDefault()}` **僅取消** Radix 開啟時的自動聚焦(避免焦點跑進第一個 tabbable 觸發 tooltip);此 handler **不主動把焦點移入 viewport** —— 焦點停在觸發按鈕,直到 user 操作使焦點進入 Content,FocusScope 才啟動 trap。(known a11y gap:若要開啟即把焦點移入 viewer 需補 `tabIndex` + `ref.focus()`,屬 SSOT-affecting 待拍板)
373
+ - `DialogContent` 固定 `tabIndex={-1}`;`onOpenAutoFocus` 先執行 consumer handler,若未被取消則阻止 Radix 把焦點送到第一個 toolbar control,並主動 `focus({ preventScroll: true })` fullscreen shell。開啟後焦點立即位於 viewer/FocusScope 內,不留在被 `aria-hidden` 的背景 trigger,也不因首個 icon button focus 而誤觸 tooltip。consumer 若有專屬初始焦點可在 handler 中 `preventDefault()` 後自行聚焦。
374
374
 
375
375
  ---
376
376
 
@@ -1,5 +1,6 @@
1
1
  import React from 'react'
2
2
  import type { Meta, StoryObj } from '@storybook/react'
3
+ import { expect, waitFor } from '@storybook/test'
3
4
  import { FileViewer, type FileInfo } from './file-viewer'
4
5
  import { Button } from '@/design-system/components/Button/button'
5
6
  import { Image as ImageIcon, Paperclip, Camera, Figma } from 'lucide-react'
@@ -403,4 +404,10 @@ export const OpenSnapshot: Story = {
403
404
  />
404
405
  )
405
406
  },
407
+ play: async () => {
408
+ await waitFor(() => {
409
+ expect(document.activeElement).toHaveAttribute('role', 'dialog')
410
+ expect(document.activeElement).toHaveAttribute('tabindex', '-1')
411
+ })
412
+ },
406
413
  }
@@ -735,7 +735,7 @@ const DEFAULT_LABELS: Required<FileViewerLabels> = {
735
735
  export interface FileViewerProps
736
736
  extends Omit<
737
737
  React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>,
738
- 'onOpenChange'
738
+ 'onOpenChange' | 'tabIndex'
739
739
  > {
740
740
  files: FileInfo[]
741
741
  initialIndex?: number
@@ -776,6 +776,7 @@ const FileViewer = React.forwardRef<HTMLDivElement, FileViewerProps>(function Fi
776
776
  onDownload,
777
777
  labels: labelsOverride,
778
778
  className,
779
+ onOpenAutoFocus,
779
780
  ...props
780
781
  }, ref) {
781
782
  const labels = React.useMemo(
@@ -793,6 +794,15 @@ const FileViewer = React.forwardRef<HTMLDivElement, FileViewerProps>(function Fi
793
794
  defaultValue: defaultOpen ?? false,
794
795
  onChange: onOpenChange,
795
796
  })
797
+ const contentRef = React.useRef<HTMLDivElement | null>(null)
798
+ const setContentRef = React.useCallback(
799
+ (node: HTMLDivElement | null) => {
800
+ contentRef.current = node
801
+ if (typeof ref === 'function') ref(node)
802
+ else if (ref) ref.current = node
803
+ },
804
+ [ref],
805
+ )
796
806
 
797
807
  // Index:uncontrolled fallback
798
808
  const [internalIndex, setInternalIndex] = React.useState(initialIndex)
@@ -949,7 +959,9 @@ const FileViewer = React.forwardRef<HTMLDivElement, FileViewerProps>(function Fi
949
959
  )}
950
960
  />
951
961
  <DialogPrimitive.Content
952
- ref={ref}
962
+ ref={setContentRef}
963
+ {...props}
964
+ tabIndex={-1}
953
965
  className={cn(
954
966
  // Edge-to-edge fullscreen,無 inset / 無 radius(與一般 Dialog 差別的所在)
955
967
  'fixed inset-0 z-50 outline-none',
@@ -958,9 +970,15 @@ const FileViewer = React.forwardRef<HTMLDivElement, FileViewerProps>(function Fi
958
970
  'data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0',
959
971
  'data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95',
960
972
  )}
961
- // 避免 Radix 自動把焦點送進 Content 的第一個 tabbable —— 我們要留給 viewport
962
- onOpenAutoFocus={(e) => e.preventDefault()}
963
- {...props}
973
+ // FileViewer 是 fullscreen content-consumption dialog:開啟即把焦點放到 shell,
974
+ // 讓 Radix FocusScope 立即接管、但不誤觸第一個 toolbar control/Tooltip。
975
+ // Consumer handler 可先取消;未取消時由本元件阻止 Radix default 並聚焦 Content。
976
+ onOpenAutoFocus={(event) => {
977
+ onOpenAutoFocus?.(event)
978
+ if (event.defaultPrevented) return
979
+ event.preventDefault()
980
+ contentRef.current?.focus({ preventScroll: true })
981
+ }}
964
982
  >
965
983
  {/* 鎖 dark subtree。Density 繼承 page(不另設 data-density)。
966
984
  header 高度透過 `--chrome-header-height` 自動 density-aware(md=48 / lg=56)。
@@ -74,6 +74,12 @@ InlineEdit 本體只給:**對齊盒(-mx orientation-aware)+ 委派 view 統一 f
74
74
 
75
75
  **尺寸預設 = `sm`**(2026-07-09 user 拍板):view 態無邊框、視覺即純文字,尺寸過大會使版面鬆散;consumer 可傳 `size` 覆寫,標題場景另用 `readClassName` 疊大字級。
76
76
 
77
+ **在 `<Field>` 內也是 `sm`**(2026-07-28 user 拍板):Field 的預設 `md` 是「沒人決定過」的預設值,不得壓過本元件宣告的 `sm`。實作雙通道 —— 靜態 `InlineEdit.fieldPreferredSize = 'sm'` 供 Field 直接子元件同步偵測,加上掛載時的 `useRegisterFieldPreferredSize('sm')` 補位(包 wrapper / `React.memo` / HOC 會遮蔽靜態);控件自身尺寸則由 `useResolvedFieldSize` 依 `sizeExplicit` 同步解析,不等註冊。**唯有 consumer 顯式 `size`(在 Field 或 InlineEdit 上)才覆寫**。機制與失效語意 owner → `Field/field.spec.md`「Control 偏好尺寸 SSOT」。
78
+
79
+ **單行 view 恆一行**(2026-07-28 user 拍板):`multiline=false` 的 view 態溢出**不換行**,一律 ellipsis,且**截斷才顯 tooltip**顯示完整值(規則 owner:`Tooltip/tooltip.spec.md`「截斷文字 → tooltip」+ `truncated-text.spec.md`)。**實作 = `useTruncated` 自組而非 `<TruncatedText>`**:view 上疊著透明 Pressable(click-to-edit 入口)會吞掉內層所有 hover,tooltip trigger 必須掛在**整個 view 容器**、量測留在內層值 span —— 這正是 truncated-text.spec 對「trigger 需自控」場景指定的 escape(2026-07-28 user 實測抓到 primitive 直用時 tooltip 永不開)。委派 view(`renderRead`)同一條規則、同一個外層 Tooltip:委派控件自帶的截斷 tooltip(Input `<TruncatedText>` / Tag)trigger 在內層,editable 時同樣被 Pressable 攔截 —— 故外層容器 Tooltip 一併服務委派路徑:`useTruncated` 傳自訂 `measure`(`hooks/use-truncated.ts` options.measure 即為此類自訂策略開的口)走訪 wrapper 子樹,任一 `.truncate` 後代溢出即視為截斷;tooltip 內容 = wrapper 當下 DOM 的 `textContent`(完整格式化文字;非 string `T` 不可 `String(value)`)。`editable=false` 不渲染 Pressable → 委派控件自身 tooltip 可達,外層讓位不重複顯示。多行(`multiline`)維持 `pre-wrap` 忠實呈現。
80
+
81
+ **多行 edit 高度隨內容**(2026-07-28 user 拍板):view 態高度由內容決定,edit 態必須同源,否則 read↔edit 跳高。預設 `Textarea autoSize`(`field-sizing: content`),consumer 以 `editMaxRows` 設上限、超過改捲動。與 DataTable cell 共用同一份 `autoSize` 實作(原本只有 cell 有呼叫端膠水 → 兩者漂移)。
82
+
77
83
  **核心不變量:view 態 hover = 背景 tint;view 態 focus = Field 藍框(`border-primary`),與 edit 態同源。** hover(滑鼠懸停)用底色、focus(鍵盤選中)用藍框——藍框是 edit focus 的預覽,view→edit 一路都是藍框、不會 ring→框跳一下;靜止(無 hover/focus)則透明邊框不可見 = 純值。
78
84
 
79
85
  ## 型別化 view + 多行 + 鍵盤 SSOT(2026-07-09 L2)
@@ -172,3 +178,9 @@ InlineEdit 是 **composite 編排層**(view 對齊盒 + 隱形 Pressable + 動
172
178
  - ❌ 空值顯示唯讀展示的半形 `-` dash——InlineEdit 是可編輯欄位,空值 = placeholder 或空白
173
179
  - ❌ view 態語意標籤用 heading 時把它塞進 button——用隱形 Pressable 疊加,保留文件大綱
174
180
  - ❌ 用 disabled / 灰化表達鎖定——就地編輯無 disabled 態,鎖定 = `editable=false` 純 view 無入口、不灰化(見「editable 閘」)
181
+
182
+ ## 被引用(auto-maintained,Dim 3 reciprocal audit)
183
+
184
+ > 本節由 `scripts/add-reciprocal-pointers.mjs` 自動維護,列出在 SSOT 語境下指向本 spec 的其他 spec。若要手動補充,寫在本節之前。
185
+
186
+ - `field.spec.md`