@qijenchen/design-system 0.1.0-beta.145 → 0.1.0-beta.147

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 (576) hide show
  1. package/dist/components/Accordion/accordion.d.ts +1 -1
  2. package/dist/components/Accordion/accordion.d.ts.map +1 -1
  3. package/dist/components/Accordion/accordion.js +8 -3
  4. package/dist/components/Accordion/accordion.js.map +1 -1
  5. package/dist/components/AccountMenu/account-menu.d.ts.map +1 -1
  6. package/dist/components/AccountMenu/account-menu.js.map +1 -1
  7. package/dist/components/AgentPanel/agent-panel-fab.d.ts +7 -1
  8. package/dist/components/AgentPanel/agent-panel-fab.d.ts.map +1 -1
  9. package/dist/components/AgentPanel/agent-panel-fab.js +36 -14
  10. package/dist/components/AgentPanel/agent-panel-fab.js.map +1 -1
  11. package/dist/components/AgentPanel/agent-panel-logo.d.ts +3 -3
  12. package/dist/components/AgentPanel/agent-panel-logo.js.map +1 -1
  13. package/dist/components/AgentPanel/agent-panel.d.ts +1 -1
  14. package/dist/components/AgentPanel/agent-panel.d.ts.map +1 -1
  15. package/dist/components/AgentPanel/agent-panel.js +28 -22
  16. package/dist/components/AgentPanel/agent-panel.js.map +1 -1
  17. package/dist/components/AppShell/app-shell.d.ts +1 -1
  18. package/dist/components/AppShell/app-shell.js +1 -1
  19. package/dist/components/AppShell/app-shell.js.map +1 -1
  20. package/dist/components/Avatar/avatar.d.ts +95 -1
  21. package/dist/components/Avatar/avatar.d.ts.map +1 -1
  22. package/dist/components/Avatar/avatar.js +106 -15
  23. package/dist/components/Avatar/avatar.js.map +1 -1
  24. package/dist/components/Avatar/index.js +14 -2
  25. package/dist/components/Breadcrumb/breadcrumb.d.ts +4 -2
  26. package/dist/components/Breadcrumb/breadcrumb.d.ts.map +1 -1
  27. package/dist/components/Breadcrumb/breadcrumb.js +2 -2
  28. package/dist/components/Breadcrumb/breadcrumb.js.map +1 -1
  29. package/dist/components/BulkActionBar/bulk-action-bar.d.ts.map +1 -1
  30. package/dist/components/BulkActionBar/bulk-action-bar.js +27 -2
  31. package/dist/components/BulkActionBar/bulk-action-bar.js.map +1 -1
  32. package/dist/components/Button/button.d.ts +2 -2
  33. package/dist/components/Button/button.d.ts.map +1 -1
  34. package/dist/components/Button/button.js +118 -22
  35. package/dist/components/Button/button.js.map +1 -1
  36. package/dist/components/Calendar/calendar.d.ts +34 -10
  37. package/dist/components/Calendar/calendar.d.ts.map +1 -1
  38. package/dist/components/Calendar/calendar.js +210 -36
  39. package/dist/components/Calendar/calendar.js.map +1 -1
  40. package/dist/components/Carousel/carousel.d.ts +1 -1
  41. package/dist/components/Carousel/carousel.d.ts.map +1 -1
  42. package/dist/components/Carousel/carousel.js +3 -2
  43. package/dist/components/Carousel/carousel.js.map +1 -1
  44. package/dist/components/Chart/chart.d.ts +1 -0
  45. package/dist/components/Chart/chart.d.ts.map +1 -1
  46. package/dist/components/Chart/chart.js +8 -0
  47. package/dist/components/Chart/chart.js.map +1 -1
  48. package/dist/components/Checkbox/checkbox.d.ts +1 -1
  49. package/dist/components/Checkbox/checkbox.d.ts.map +1 -1
  50. package/dist/components/Checkbox/checkbox.js +10 -2
  51. package/dist/components/Checkbox/checkbox.js.map +1 -1
  52. package/dist/components/Chip/chip.d.ts +1 -1
  53. package/dist/components/Chip/chip.d.ts.map +1 -1
  54. package/dist/components/Chip/chip.js +4 -2
  55. package/dist/components/Chip/chip.js.map +1 -1
  56. package/dist/components/Combobox/combobox.d.ts +1 -1
  57. package/dist/components/Combobox/combobox.d.ts.map +1 -1
  58. package/dist/components/Combobox/combobox.js +37 -11
  59. package/dist/components/Combobox/combobox.js.map +1 -1
  60. package/dist/components/Command/command.d.ts +1 -1
  61. package/dist/components/Command/command.d.ts.map +1 -1
  62. package/dist/components/Command/command.js +106 -8
  63. package/dist/components/Command/command.js.map +1 -1
  64. package/dist/components/DataTable/cell-registry.d.ts.map +1 -1
  65. package/dist/components/DataTable/cell-registry.js +3 -2
  66. package/dist/components/DataTable/cell-registry.js.map +1 -1
  67. package/dist/components/DataTable/data-table-column-visibility-panel.d.ts.map +1 -1
  68. package/dist/components/DataTable/data-table-column-visibility-panel.js +24 -24
  69. package/dist/components/DataTable/data-table-column-visibility-panel.js.map +1 -1
  70. package/dist/components/DataTable/data-table-interaction-layer.d.ts +2 -1
  71. package/dist/components/DataTable/data-table-interaction-layer.d.ts.map +1 -1
  72. package/dist/components/DataTable/data-table-interaction-layer.js +3 -4
  73. package/dist/components/DataTable/data-table-interaction-layer.js.map +1 -1
  74. package/dist/components/DataTable/data-table-sort-manager.d.ts.map +1 -1
  75. package/dist/components/DataTable/data-table-sort-manager.js +20 -20
  76. package/dist/components/DataTable/data-table-sort-manager.js.map +1 -1
  77. package/dist/components/DataTable/data-table.d.ts +8 -5
  78. package/dist/components/DataTable/data-table.d.ts.map +1 -1
  79. package/dist/components/DataTable/data-table.js +197 -71
  80. package/dist/components/DataTable/data-table.js.map +1 -1
  81. package/dist/components/DateGrid/date-grid.d.ts +1 -1
  82. package/dist/components/DateGrid/date-grid.d.ts.map +1 -1
  83. package/dist/components/DateGrid/date-grid.js +358 -225
  84. package/dist/components/DateGrid/date-grid.js.map +1 -1
  85. package/dist/components/DatePicker/date-picker.d.ts.map +1 -1
  86. package/dist/components/DatePicker/date-picker.js +16 -1
  87. package/dist/components/DatePicker/date-picker.js.map +1 -1
  88. package/dist/components/Dialog/dialog.d.ts.map +1 -1
  89. package/dist/components/Dialog/dialog.js +55 -43
  90. package/dist/components/Dialog/dialog.js.map +1 -1
  91. package/dist/components/DropdownMenu/dropdown-menu-keyboard.d.ts +86 -0
  92. package/dist/components/DropdownMenu/dropdown-menu-keyboard.d.ts.map +1 -0
  93. package/dist/components/DropdownMenu/dropdown-menu-keyboard.js +172 -0
  94. package/dist/components/DropdownMenu/dropdown-menu-keyboard.js.map +1 -0
  95. package/dist/components/DropdownMenu/dropdown-menu.d.ts +6 -1
  96. package/dist/components/DropdownMenu/dropdown-menu.d.ts.map +1 -1
  97. package/dist/components/DropdownMenu/dropdown-menu.js +50 -39
  98. package/dist/components/DropdownMenu/dropdown-menu.js.map +1 -1
  99. package/dist/components/Field/field-context.d.ts +4 -0
  100. package/dist/components/Field/field-context.d.ts.map +1 -1
  101. package/dist/components/Field/field-context.js +1 -2
  102. package/dist/components/Field/field-context.js.map +1 -1
  103. package/dist/components/Field/field-edit-keys.d.ts +8 -0
  104. package/dist/components/Field/field-edit-keys.d.ts.map +1 -1
  105. package/dist/components/Field/field-edit-keys.js +3 -0
  106. package/dist/components/Field/field-edit-keys.js.map +1 -1
  107. package/dist/components/Field/field-wrapper.d.ts +18 -1
  108. package/dist/components/Field/field-wrapper.d.ts.map +1 -1
  109. package/dist/components/Field/field-wrapper.js +39 -10
  110. package/dist/components/Field/field-wrapper.js.map +1 -1
  111. package/dist/components/Field/field.d.ts +3 -2
  112. package/dist/components/Field/field.d.ts.map +1 -1
  113. package/dist/components/Field/field.js +2 -1
  114. package/dist/components/Field/field.js.map +1 -1
  115. package/dist/components/FileItem/file-item.d.ts +26 -2
  116. package/dist/components/FileItem/file-item.d.ts.map +1 -1
  117. package/dist/components/FileItem/file-item.js +82 -25
  118. package/dist/components/FileItem/file-item.js.map +1 -1
  119. package/dist/components/FileItem/index.js +4 -2
  120. package/dist/components/FileUpload/file-upload.d.ts +1 -1
  121. package/dist/components/FileUpload/file-upload.d.ts.map +1 -1
  122. package/dist/components/FileUpload/file-upload.js +132 -55
  123. package/dist/components/FileUpload/file-upload.js.map +1 -1
  124. package/dist/components/FileViewer/file-viewer.d.ts +1 -1
  125. package/dist/components/FileViewer/file-viewer.d.ts.map +1 -1
  126. package/dist/components/FileViewer/file-viewer.js +9 -4
  127. package/dist/components/FileViewer/file-viewer.js.map +1 -1
  128. package/dist/components/InlineEdit/inline-edit.d.ts.map +1 -1
  129. package/dist/components/InlineEdit/inline-edit.js +1 -1
  130. package/dist/components/InlineEdit/inline-edit.js.map +1 -1
  131. package/dist/components/Input/input.d.ts.map +1 -1
  132. package/dist/components/Input/input.js +6 -1
  133. package/dist/components/Input/input.js.map +1 -1
  134. package/dist/components/LinkInput/link-input.d.ts.map +1 -1
  135. package/dist/components/LinkInput/link-input.js +20 -4
  136. package/dist/components/LinkInput/link-input.js.map +1 -1
  137. package/dist/components/Notice/notice.js +1 -1
  138. package/dist/components/Notice/notice.js.map +1 -1
  139. package/dist/components/NumberInput/number-input.d.ts.map +1 -1
  140. package/dist/components/NumberInput/number-input.js +6 -1
  141. package/dist/components/NumberInput/number-input.js.map +1 -1
  142. package/dist/components/OverflowIndicator/index.js +2 -1
  143. package/dist/components/OverflowIndicator/overflow-indicator.d.ts +19 -1
  144. package/dist/components/OverflowIndicator/overflow-indicator.d.ts.map +1 -1
  145. package/dist/components/OverflowIndicator/overflow-indicator.js +47 -27
  146. package/dist/components/OverflowIndicator/overflow-indicator.js.map +1 -1
  147. package/dist/components/Pagination/pagination.d.ts +1 -1
  148. package/dist/components/Pagination/pagination.js +1 -1
  149. package/dist/components/Pagination/pagination.js.map +1 -1
  150. package/dist/components/PeoplePicker/avatar-stack-overflow.d.ts +7 -6
  151. package/dist/components/PeoplePicker/avatar-stack-overflow.d.ts.map +1 -1
  152. package/dist/components/PeoplePicker/avatar-stack-overflow.js +6 -7
  153. package/dist/components/PeoplePicker/avatar-stack-overflow.js.map +1 -1
  154. package/dist/components/PeoplePicker/people-picker-helpers.d.ts.map +1 -1
  155. package/dist/components/PeoplePicker/people-picker-helpers.js +1 -1
  156. package/dist/components/PeoplePicker/people-picker-helpers.js.map +1 -1
  157. package/dist/components/PeoplePicker/people-picker.d.ts.map +1 -1
  158. package/dist/components/PeoplePicker/people-picker.js +2 -2
  159. package/dist/components/PeoplePicker/people-picker.js.map +1 -1
  160. package/dist/components/PeoplePicker/person-display.d.ts.map +1 -1
  161. package/dist/components/PeoplePicker/person-display.js +26 -28
  162. package/dist/components/PeoplePicker/person-display.js.map +1 -1
  163. package/dist/components/ProfileCard/profile-card.d.ts.map +1 -1
  164. package/dist/components/ProfileCard/profile-card.js.map +1 -1
  165. package/dist/components/RadioGroup/radio-group.d.ts +1 -1
  166. package/dist/components/RadioGroup/radio-group.d.ts.map +1 -1
  167. package/dist/components/RadioGroup/radio-group.js +10 -4
  168. package/dist/components/RadioGroup/radio-group.js.map +1 -1
  169. package/dist/components/Rating/rating.d.ts +28 -6
  170. package/dist/components/Rating/rating.d.ts.map +1 -1
  171. package/dist/components/Rating/rating.js +97 -80
  172. package/dist/components/Rating/rating.js.map +1 -1
  173. package/dist/components/ScrollArea/scroll-area.d.ts +1 -1
  174. package/dist/components/ScrollArea/scroll-area.js +1 -1
  175. package/dist/components/ScrollArea/scroll-area.js.map +1 -1
  176. package/dist/components/SegmentedControl/segmented-control.d.ts +1 -1
  177. package/dist/components/SegmentedControl/segmented-control.d.ts.map +1 -1
  178. package/dist/components/SegmentedControl/segmented-control.js +3 -2
  179. package/dist/components/SegmentedControl/segmented-control.js.map +1 -1
  180. package/dist/components/Select/select.d.ts.map +1 -1
  181. package/dist/components/Select/select.js +15 -8
  182. package/dist/components/Select/select.js.map +1 -1
  183. package/dist/components/SelectMenu/index.js +2 -1
  184. package/dist/components/SelectMenu/index.js.map +1 -1
  185. package/dist/components/SelectMenu/select-menu-keyboard.d.ts +59 -0
  186. package/dist/components/SelectMenu/select-menu-keyboard.d.ts.map +1 -0
  187. package/dist/components/SelectMenu/select-menu-keyboard.js +107 -0
  188. package/dist/components/SelectMenu/select-menu-keyboard.js.map +1 -0
  189. package/dist/components/SelectMenu/select-menu.d.ts +1 -22
  190. package/dist/components/SelectMenu/select-menu.d.ts.map +1 -1
  191. package/dist/components/SelectMenu/select-menu.js +18 -34
  192. package/dist/components/SelectMenu/select-menu.js.map +1 -1
  193. package/dist/components/SelectionControl/selection-item.d.ts.map +1 -1
  194. package/dist/components/SelectionControl/selection-item.js +1 -0
  195. package/dist/components/SelectionControl/selection-item.js.map +1 -1
  196. package/dist/components/Sidebar/sidebar.d.ts +7 -9
  197. package/dist/components/Sidebar/sidebar.d.ts.map +1 -1
  198. package/dist/components/Sidebar/sidebar.js +171 -50
  199. package/dist/components/Sidebar/sidebar.js.map +1 -1
  200. package/dist/components/Slider/slider.d.ts +1 -1
  201. package/dist/components/Slider/slider.d.ts.map +1 -1
  202. package/dist/components/Slider/slider.js +9 -4
  203. package/dist/components/Slider/slider.js.map +1 -1
  204. package/dist/components/Steps/steps.d.ts.map +1 -1
  205. package/dist/components/Steps/steps.js +54 -26
  206. package/dist/components/Steps/steps.js.map +1 -1
  207. package/dist/components/Switch/switch.d.ts +1 -1
  208. package/dist/components/Switch/switch.d.ts.map +1 -1
  209. package/dist/components/Switch/switch.js +7 -2
  210. package/dist/components/Switch/switch.js.map +1 -1
  211. package/dist/components/Tabs/tabs.d.ts +1 -1
  212. package/dist/components/Tabs/tabs.d.ts.map +1 -1
  213. package/dist/components/Tabs/tabs.js +39 -4
  214. package/dist/components/Tabs/tabs.js.map +1 -1
  215. package/dist/components/Tag/tag.d.ts +1 -1
  216. package/dist/components/Tag/tag.d.ts.map +1 -1
  217. package/dist/components/Tag/tag.js +3 -2
  218. package/dist/components/Tag/tag.js.map +1 -1
  219. package/dist/components/Textarea/textarea.d.ts.map +1 -1
  220. package/dist/components/Textarea/textarea.js +8 -2
  221. package/dist/components/Textarea/textarea.js.map +1 -1
  222. package/dist/components/TimePicker/time-columns.d.ts.map +1 -1
  223. package/dist/components/TimePicker/time-columns.js +8 -4
  224. package/dist/components/TimePicker/time-columns.js.map +1 -1
  225. package/dist/components/TimePicker/time-picker.d.ts +1 -1
  226. package/dist/components/TimePicker/time-picker.js +1 -1
  227. package/dist/components/TimePicker/time-picker.js.map +1 -1
  228. package/dist/components/TreeView/tree-view.d.ts +9 -5
  229. package/dist/components/TreeView/tree-view.d.ts.map +1 -1
  230. package/dist/components/TreeView/tree-view.js +266 -214
  231. package/dist/components/TreeView/tree-view.js.map +1 -1
  232. package/dist/hooks/use-input-modality.d.ts +12 -17
  233. package/dist/hooks/use-input-modality.d.ts.map +1 -1
  234. package/dist/hooks/use-input-modality.js +4 -34
  235. package/dist/hooks/use-input-modality.js.map +1 -1
  236. package/dist/hooks/use-overflow-items.d.ts.map +1 -1
  237. package/dist/hooks/use-overflow-items.js +2 -0
  238. package/dist/hooks/use-overflow-items.js.map +1 -1
  239. package/dist/hooks/use-truncated.d.ts.map +1 -1
  240. package/dist/hooks/use-truncated.js.map +1 -1
  241. package/dist/index.d.ts +3 -0
  242. package/dist/index.d.ts.map +1 -1
  243. package/dist/index.js +23 -3
  244. package/dist/index.js.map +1 -1
  245. package/dist/lib/drag-announcements.d.ts +32 -0
  246. package/dist/lib/drag-announcements.d.ts.map +1 -1
  247. package/dist/lib/drag-announcements.js +39 -1
  248. package/dist/lib/drag-announcements.js.map +1 -1
  249. package/dist/lib/focus-after-trigger.d.ts +45 -0
  250. package/dist/lib/focus-after-trigger.d.ts.map +1 -0
  251. package/dist/lib/focus-after-trigger.js +66 -0
  252. package/dist/lib/focus-after-trigger.js.map +1 -0
  253. package/dist/lib/overlay-coexistence.js +1 -1
  254. package/dist/lib/roving-list-keyboard.d.ts +208 -0
  255. package/dist/lib/roving-list-keyboard.d.ts.map +1 -0
  256. package/dist/lib/roving-list-keyboard.js +174 -0
  257. package/dist/lib/roving-list-keyboard.js.map +1 -0
  258. package/dist/lib/utils.d.ts.map +1 -1
  259. package/dist/lib/utils.js +6 -1
  260. package/dist/lib/utils.js.map +1 -1
  261. package/dist/patterns/element-anatomy/item-anatomy.d.ts +4 -4
  262. package/dist/patterns/element-anatomy/item-anatomy.d.ts.map +1 -1
  263. package/dist/patterns/element-anatomy/item-anatomy.js +14 -4
  264. package/dist/patterns/element-anatomy/item-anatomy.js.map +1 -1
  265. package/dist/patterns/horizontal-overflow/horizontal-overflow.d.ts.map +1 -1
  266. package/dist/patterns/horizontal-overflow/horizontal-overflow.js +25 -0
  267. package/dist/patterns/horizontal-overflow/horizontal-overflow.js.map +1 -1
  268. package/dist/patterns/overlay-surface/overlay-surface.d.ts.map +1 -1
  269. package/dist/patterns/overlay-surface/overlay-surface.js.map +1 -1
  270. package/dist/tokens/categorical-color.d.ts +6 -1
  271. package/dist/tokens/categorical-color.d.ts.map +1 -1
  272. package/dist/tokens/categorical-color.js.map +1 -1
  273. package/dist/tokens/uiSize/icon-size.d.ts +1 -1
  274. package/dist/tokens/uiSize/stack-gap.d.ts +18 -0
  275. package/dist/tokens/uiSize/stack-gap.d.ts.map +1 -0
  276. package/dist/tokens/uiSize/stack-gap.js +5 -0
  277. package/dist/tokens/uiSize/stack-gap.js.map +1 -0
  278. package/ds-canonical/fork/codex/agents/skills/performance-audit/SKILL.md +1 -1
  279. package/ds-canonical/fork/codex/hooks.json +1 -1
  280. package/ds-canonical/fork/consumer/lock.json +17 -14
  281. package/ds-canonical/fork/consumer/managed-files/423729840acdd43c812f17c245b871716c6b60f76e4d3e94864d0e34a815bf8d.blob +9 -0
  282. package/ds-canonical/fork/consumer/managed-files/46f10bc55cdfdf834d0749eebff42ea0bb8576f361b36d2b6ba1851354c50150.blob +47 -9
  283. package/ds-canonical/fork/consumer/managed-files/5a0d134a2645114c8caac20458070b00a7f0fd55b3e8805b7325cb4cf52fa15e.blob +110 -0
  284. package/ds-canonical/fork/consumer/managed-files/5af0a46760af338d51fd0f7950675f8e97f7d2f09386b8818fcd7a58e6654e75.blob +790 -0
  285. package/ds-canonical/fork/consumer/managed-files/68bfcddff9c4a16388c248cd34fb66163e9e64b3d809a48ef3984f6ae4ff6d12.blob +211 -0
  286. package/ds-canonical/fork/consumer/managed-files/7eb6cc60abfdabaf6617c336058fad1454ccde08889295767bfd9b7fd153cc6e.blob +45 -6
  287. package/ds-canonical/fork/consumer/managed-files/92e4828cdcb96ff0d01daa16f6885f24045a8e72820665650f2a1890dc3a446c.blob +42 -2
  288. package/ds-canonical/fork/consumer/managed-files/ab072e7f2eca51f0e18a5e18c4b333260ee5d219a12cc6df5bb2d683f0b2734d.blob +27 -1
  289. package/ds-canonical/fork/consumer/managed-files/e71c65f45c6af7ce12b6b3c0d8028f185da5b719e2050c9b2a6b243d41ae4878.blob +62 -14
  290. package/ds-canonical/fork/governance.lock +41 -17
  291. package/ds-canonical/fork/hooks/check_consumer_app_invariants.sh +16 -4
  292. package/ds-canonical/fork/hooks/check_consumer_code_quality.sh +3 -1
  293. package/ds-canonical/fork/hooks/check_pattern_invariants.sh +13 -9
  294. package/ds-canonical/fork/launchers/settings-hooks.json +1 -1
  295. package/ds-canonical/fork/manifest.json +17 -11
  296. package/ds-canonical/fork/providers/claude/skills/performance-audit/SKILL.md +1 -1
  297. package/ds-canonical/fork/providers/codex/hook-config.json +1 -1
  298. package/ds-canonical/fork/providers/codex/skills/performance-audit/SKILL.md +1 -1
  299. package/ds-canonical/fork/skills/performance-audit/SKILL.md +1 -1
  300. package/ds-canonical/hooks/check_consumer_app_invariants.sh +16 -4
  301. package/ds-canonical/hooks/check_consumer_code_quality.sh +3 -1
  302. package/ds-canonical/hooks/check_datatable_invariants.sh +4 -1
  303. package/ds-canonical/hooks/check_pattern_invariants.sh +13 -9
  304. package/ds-canonical/hooks/check_plugin_fork_health.sh +8 -1
  305. package/ds-canonical/hooks/check_propose_discipline.sh +8 -1
  306. package/ds-canonical/hooks/check_storybook_addon_packaging.sh +8 -1
  307. package/ds-canonical/hooks/check_substantive_edit_approval_preflight.sh +64 -2
  308. package/ds-canonical/hooks/lib/_overlay_handcraft.sh +5 -2
  309. package/ds-canonical/hooks/registrations.json +12 -7
  310. package/ds-canonical/hooks/session_start_governance_check.sh +16 -0
  311. package/ds-canonical/hooks/tests/test_check_consumer_app_invariants.sh +25 -0
  312. package/ds-canonical/hooks/tests/test_check_consumer_code_quality.sh +4 -0
  313. package/ds-canonical/hooks/tests/test_check_consumer_ds_primitive_misuse.sh +14 -0
  314. package/ds-canonical/hooks/tests/test_check_datatable_invariants.sh +24 -0
  315. package/ds-canonical/hooks/tests/test_check_overlay_handcraft.sh +32 -0
  316. package/ds-canonical/hooks/tests/test_check_pattern_invariants.sh +59 -0
  317. package/ds-canonical/hooks/tests/test_check_plugin_fork_health.sh +32 -1
  318. package/ds-canonical/hooks/tests/test_check_propose_discipline.sh +21 -0
  319. package/ds-canonical/hooks/tests/test_check_storybook_addon_packaging.sh +21 -0
  320. package/ds-canonical/hooks/tests/test_check_substantive_edit_approval_preflight.sh +97 -0
  321. package/ds-canonical/hooks/tests/test_session_start_governance_check.sh +49 -0
  322. package/ds-canonical/references/README.md +12 -6
  323. package/ds-canonical/references/drag-canonical.md +3 -1
  324. package/ds-canonical/references/focus-canonical.md +26 -13
  325. package/ds-canonical/references/governance-audit-coverage.md +1 -1
  326. package/ds-canonical/references/hit-area-canonical.md +361 -0
  327. package/ds-canonical/references/item-anatomy-recipe.md +4 -2
  328. package/ds-canonical/references/keyboard-model-canonical.md +559 -0
  329. package/ds-canonical/rules/meta-patterns.md +4 -4
  330. package/ds-canonical/rules/self-verify.md +4 -4
  331. package/ds-canonical/skills/design-system-audit/references/audit-prompts.md +1 -1
  332. package/ds-canonical/skills/design-system-audit/references/historical-bugs.md +96 -0
  333. package/ds-canonical/skills/knowledge-prune/SKILL.md +4 -1
  334. package/ds-canonical/skills/performance-audit/SKILL.md +1 -1
  335. package/ds-story-manifest.json +19 -7
  336. package/llms-full.txt +2 -2
  337. package/llms.txt +2 -2
  338. package/package.json +3 -1
  339. package/src/components/Accordion/accordion.anatomy.stories.tsx +3 -3
  340. package/src/components/Accordion/accordion.spec.md +1 -1
  341. package/src/components/Accordion/accordion.tsx +8 -3
  342. package/src/components/AccountMenu/account-menu.spec.md +2 -3
  343. package/src/components/AccountMenu/account-menu.tsx +0 -1
  344. package/src/components/AgentPanel/agent-decision-card.spec.md +88 -0
  345. package/src/components/AgentPanel/agent-panel-fab.spec.md +151 -0
  346. package/src/components/AgentPanel/agent-panel-fab.tsx +67 -24
  347. package/src/components/AgentPanel/agent-panel-logo.spec.md +87 -0
  348. package/src/components/AgentPanel/agent-panel-logo.tsx +3 -3
  349. package/src/components/AgentPanel/agent-panel.anatomy.stories.tsx +1 -1
  350. package/src/components/AgentPanel/agent-panel.principles.stories.tsx +1 -1
  351. package/src/components/AgentPanel/agent-panel.spec.md +30 -275
  352. package/src/components/AgentPanel/agent-panel.stories.tsx +113 -8
  353. package/src/components/AgentPanel/agent-panel.tsx +59 -22
  354. package/src/components/Alert/alert.anatomy.stories.tsx +1 -1
  355. package/src/components/AppShell/app-shell.tsx +1 -1
  356. package/src/components/AspectRatio/aspect-ratio.anatomy.stories.tsx +10 -15
  357. package/src/components/Avatar/avatar.anatomy.stories.tsx +23 -26
  358. package/src/components/Avatar/avatar.principles.stories.tsx +10 -10
  359. package/src/components/Avatar/avatar.spec.md +45 -6
  360. package/src/components/Avatar/avatar.stories.tsx +8 -8
  361. package/src/components/Avatar/avatar.tsx +227 -21
  362. package/src/components/Badge/badge.anatomy.stories.tsx +16 -21
  363. package/src/components/Breadcrumb/breadcrumb.anatomy.stories.tsx +3 -3
  364. package/src/components/Breadcrumb/breadcrumb.spec.md +3 -3
  365. package/src/components/Breadcrumb/breadcrumb.tsx +5 -3
  366. package/src/components/BulkActionBar/bulk-action-bar.anatomy.stories.tsx +1 -1
  367. package/src/components/BulkActionBar/bulk-action-bar.principles.stories.tsx +1 -1
  368. package/src/components/BulkActionBar/bulk-action-bar.spec.md +1 -0
  369. package/src/components/BulkActionBar/bulk-action-bar.stories.tsx +57 -2
  370. package/src/components/BulkActionBar/bulk-action-bar.tsx +32 -2
  371. package/src/components/Button/button.anatomy.stories.tsx +174 -33
  372. package/src/components/Button/button.spec.md +23 -8
  373. package/src/components/Button/button.tsx +116 -21
  374. package/src/components/Calendar/calendar.anatomy.stories.tsx +34 -11
  375. package/src/components/Calendar/calendar.principles.stories.tsx +44 -1
  376. package/src/components/Calendar/calendar.spec.md +97 -20
  377. package/src/components/Calendar/calendar.stories.tsx +101 -4
  378. package/src/components/Calendar/calendar.tsx +379 -74
  379. package/src/components/Carousel/carousel.anatomy.stories.tsx +16 -17
  380. package/src/components/Carousel/carousel.spec.md +48 -2
  381. package/src/components/Carousel/carousel.tsx +9 -3
  382. package/src/components/Chart/chart.anatomy.stories.tsx +7 -5
  383. package/src/components/Chart/chart.spec.md +32 -7
  384. package/src/components/Chart/chart.tsx +9 -0
  385. package/src/components/Checkbox/checkbox.anatomy.stories.tsx +21 -28
  386. package/src/components/Checkbox/checkbox.spec.md +4 -2
  387. package/src/components/Checkbox/checkbox.tsx +10 -2
  388. package/src/components/Chip/chip.anatomy.stories.tsx +1 -1
  389. package/src/components/Chip/chip.spec.md +2 -1
  390. package/src/components/Chip/chip.tsx +4 -2
  391. package/src/components/Combobox/combobox.anatomy.stories.tsx +30 -37
  392. package/src/components/Combobox/combobox.spec.md +11 -3
  393. package/src/components/Combobox/combobox.tsx +43 -12
  394. package/src/components/Command/command.spec.md +11 -0
  395. package/src/components/Command/command.tsx +143 -10
  396. package/src/components/DataTable/cell-registry.tsx +5 -1
  397. package/src/components/DataTable/data-table-column-visibility-panel.tsx +5 -6
  398. package/src/components/DataTable/data-table-interaction-layer.tsx +27 -14
  399. package/src/components/DataTable/data-table-known-defects.spec.md +72 -0
  400. package/src/components/DataTable/data-table-scroll-performance.spec.md +295 -0
  401. package/src/components/DataTable/data-table-sort-manager.tsx +5 -6
  402. package/src/components/DataTable/data-table.anatomy.stories.tsx +1 -1
  403. package/src/components/DataTable/data-table.css +64 -0
  404. package/src/components/DataTable/data-table.spec.md +70 -346
  405. package/src/components/DataTable/data-table.stories.tsx +56 -9
  406. package/src/components/DataTable/data-table.tsx +392 -103
  407. package/src/components/DateGrid/date-grid.anatomy.stories.tsx +2 -2
  408. package/src/components/DateGrid/date-grid.spec.md +163 -1
  409. package/src/components/DateGrid/date-grid.tsx +270 -7
  410. package/src/components/DatePicker/date-picker.anatomy.stories.tsx +18 -27
  411. package/src/components/DatePicker/date-picker.spec.md +6 -2
  412. package/src/components/DatePicker/date-picker.tsx +20 -1
  413. package/src/components/Dialog/dialog.spec.md +6 -2
  414. package/src/components/Dialog/dialog.stories.tsx +18 -9
  415. package/src/components/Dialog/dialog.tsx +83 -48
  416. package/src/components/DropdownMenu/dropdown-menu-keyboard.ts +255 -0
  417. package/src/components/DropdownMenu/dropdown-menu.anatomy.stories.tsx +32 -33
  418. package/src/components/DropdownMenu/dropdown-menu.spec.md +18 -5
  419. package/src/components/DropdownMenu/dropdown-menu.stories.tsx +58 -1
  420. package/src/components/DropdownMenu/dropdown-menu.tsx +70 -33
  421. package/src/components/Empty/empty.spec.md +1 -2
  422. package/src/components/Field/field-context.ts +9 -4
  423. package/src/components/Field/field-controls.spec.md +5 -2
  424. package/src/components/Field/field-edit-keys.ts +12 -0
  425. package/src/components/Field/field-wrapper.tsx +76 -10
  426. package/src/components/Field/field.anatomy.stories.tsx +1 -1
  427. package/src/components/Field/field.spec.md +11 -6
  428. package/src/components/Field/field.stories.tsx +24 -1
  429. package/src/components/Field/field.tsx +10 -4
  430. package/src/components/FileItem/file-item.anatomy.stories.tsx +20 -10
  431. package/src/components/FileItem/file-item.principles.stories.tsx +14 -7
  432. package/src/components/FileItem/file-item.spec.md +50 -48
  433. package/src/components/FileItem/file-item.stories.tsx +41 -28
  434. package/src/components/FileItem/file-item.tsx +184 -50
  435. package/src/components/FileUpload/file-upload.anatomy.stories.tsx +11 -15
  436. package/src/components/FileUpload/file-upload.spec.md +27 -1
  437. package/src/components/FileUpload/file-upload.stories.tsx +33 -0
  438. package/src/components/FileUpload/file-upload.tsx +173 -43
  439. package/src/components/FileViewer/file-viewer.tsx +9 -4
  440. package/src/components/HoverCard/hover-card.stories.tsx +2 -2
  441. package/src/components/InlineEdit/inline-edit.spec.md +2 -0
  442. package/src/components/InlineEdit/inline-edit.tsx +8 -3
  443. package/src/components/Input/input.anatomy.stories.tsx +23 -31
  444. package/src/components/Input/input.spec.md +11 -0
  445. package/src/components/Input/input.tsx +6 -1
  446. package/src/components/LinkInput/link-input.anatomy.stories.tsx +18 -21
  447. package/src/components/LinkInput/link-input.principles.stories.tsx +4 -4
  448. package/src/components/LinkInput/link-input.spec.md +12 -1
  449. package/src/components/LinkInput/link-input.tsx +37 -4
  450. package/src/components/Menu/menu-item.anatomy.stories.tsx +50 -56
  451. package/src/components/Menu/menu-item.spec.md +1 -1
  452. package/src/components/Menu/menu-item.stories.tsx +48 -42
  453. package/src/components/Notice/notice.anatomy.stories.tsx +8 -16
  454. package/src/components/Notice/notice.spec.md +1 -1
  455. package/src/components/Notice/notice.tsx +1 -1
  456. package/src/components/NumberInput/number-input.anatomy.stories.tsx +21 -28
  457. package/src/components/NumberInput/number-input.spec.md +2 -0
  458. package/src/components/NumberInput/number-input.tsx +8 -1
  459. package/src/components/OverflowIndicator/overflow-indicator.anatomy.stories.tsx +28 -48
  460. package/src/components/OverflowIndicator/overflow-indicator.spec.md +13 -1
  461. package/src/components/OverflowIndicator/overflow-indicator.stories.tsx +12 -9
  462. package/src/components/OverflowIndicator/overflow-indicator.tsx +32 -12
  463. package/src/components/Pagination/pagination.spec.md +1 -0
  464. package/src/components/Pagination/pagination.tsx +1 -1
  465. package/src/components/PeoplePicker/avatar-stack-overflow.ts +14 -13
  466. package/src/components/PeoplePicker/people-picker-helpers.ts +5 -2
  467. package/src/components/PeoplePicker/people-picker.anatomy.stories.tsx +1 -1
  468. package/src/components/PeoplePicker/people-picker.spec.md +4 -2
  469. package/src/components/PeoplePicker/people-picker.tsx +1 -4
  470. package/src/components/PeoplePicker/person-display.tsx +45 -28
  471. package/src/components/Popover/popover.anatomy.stories.tsx +1 -1
  472. package/src/components/Popover/popover.principles.stories.tsx +3 -12
  473. package/src/components/Popover/popover.spec.md +7 -3
  474. package/src/components/ProfileCard/profile-card.anatomy.stories.tsx +1 -1
  475. package/src/components/ProfileCard/profile-card.spec.md +1 -1
  476. package/src/components/ProfileCard/profile-card.tsx +0 -3
  477. package/src/components/ProgressBar/progress-bar.anatomy.stories.tsx +11 -21
  478. package/src/components/ProgressBar/progress-bar.spec.md +1 -1
  479. package/src/components/RadioGroup/radio-group.anatomy.stories.tsx +17 -24
  480. package/src/components/RadioGroup/radio-group.spec.md +1 -1
  481. package/src/components/RadioGroup/radio-group.tsx +22 -4
  482. package/src/components/Rating/rating.anatomy.stories.tsx +131 -124
  483. package/src/components/Rating/rating.principles.stories.tsx +69 -43
  484. package/src/components/Rating/rating.spec.md +142 -63
  485. package/src/components/Rating/rating.stories.tsx +11 -15
  486. package/src/components/Rating/rating.tsx +131 -117
  487. package/src/components/ScrollArea/scroll-area.anatomy.stories.tsx +1 -1
  488. package/src/components/ScrollArea/scroll-area.principles.stories.tsx +6 -3
  489. package/src/components/ScrollArea/scroll-area.spec.md +3 -1
  490. package/src/components/ScrollArea/scroll-area.stories.tsx +5 -2
  491. package/src/components/ScrollArea/scroll-area.tsx +1 -1
  492. package/src/components/SegmentedControl/segmented-control.anatomy.stories.tsx +1 -1
  493. package/src/components/SegmentedControl/segmented-control.spec.md +2 -2
  494. package/src/components/SegmentedControl/segmented-control.tsx +3 -2
  495. package/src/components/Select/select.anatomy.stories.tsx +26 -35
  496. package/src/components/Select/select.spec.md +4 -3
  497. package/src/components/Select/select.tsx +11 -4
  498. package/src/components/SelectMenu/select-menu-keyboard.ts +218 -0
  499. package/src/components/SelectMenu/select-menu.anatomy.stories.tsx +6 -17
  500. package/src/components/SelectMenu/select-menu.spec.md +12 -4
  501. package/src/components/SelectMenu/select-menu.tsx +21 -56
  502. package/src/components/SelectionControl/selection-item.anatomy.stories.tsx +9 -33
  503. package/src/components/SelectionControl/selection-item.spec.md +2 -2
  504. package/src/components/SelectionControl/selection-item.tsx +3 -1
  505. package/src/components/Separator/separator.stories.tsx +4 -4
  506. package/src/components/Sheet/sheet.principles.stories.tsx +31 -13
  507. package/src/components/Sidebar/sidebar.anatomy.stories.tsx +1 -1
  508. package/src/components/Sidebar/sidebar.spec.md +73 -6
  509. package/src/components/Sidebar/sidebar.stories.tsx +82 -4
  510. package/src/components/Sidebar/sidebar.tsx +281 -64
  511. package/src/components/Skeleton/skeleton.spec.md +1 -1
  512. package/src/components/Slider/slider.anatomy.stories.tsx +1 -1
  513. package/src/components/Slider/slider.spec.md +6 -5
  514. package/src/components/Slider/slider.tsx +13 -8
  515. package/src/components/Steps/steps-state-visuals.spec.md +157 -0
  516. package/src/components/Steps/steps.anatomy.stories.tsx +1 -0
  517. package/src/components/Steps/steps.spec.md +59 -142
  518. package/src/components/Steps/steps.stories.tsx +1 -1
  519. package/src/components/Steps/steps.tsx +125 -33
  520. package/src/components/Switch/switch.spec.md +3 -3
  521. package/src/components/Switch/switch.tsx +6 -1
  522. package/src/components/Tabs/tabs.spec.md +5 -1
  523. package/src/components/Tabs/tabs.tsx +42 -3
  524. package/src/components/Tag/tag.anatomy.stories.tsx +24 -28
  525. package/src/components/Tag/tag.spec.md +10 -11
  526. package/src/components/Tag/tag.tsx +3 -3
  527. package/src/components/Textarea/textarea.tsx +7 -1
  528. package/src/components/TimePicker/time-columns.tsx +9 -4
  529. package/src/components/TimePicker/time-picker.spec.md +4 -1
  530. package/src/components/TimePicker/time-picker.tsx +1 -1
  531. package/src/components/Tooltip/tooltip.anatomy.stories.tsx +6 -15
  532. package/src/components/Tooltip/tooltip.principles.stories.tsx +4 -16
  533. package/src/components/Tooltip/tooltip.spec.md +2 -1
  534. package/src/components/TreeView/tree-view.anatomy.stories.tsx +35 -13
  535. package/src/components/TreeView/tree-view.spec.md +67 -38
  536. package/src/components/TreeView/tree-view.stories.tsx +221 -12
  537. package/src/components/TreeView/tree-view.tsx +463 -356
  538. package/src/hooks/use-input-modality.ts +17 -46
  539. package/src/hooks/use-overflow-items.ts +4 -0
  540. package/src/hooks/use-truncated.ts +3 -2
  541. package/src/index.ts +17 -0
  542. package/src/lib/README.md +2 -0
  543. package/src/lib/drag-announcements.ts +78 -0
  544. package/src/lib/focus-after-trigger.ts +102 -0
  545. package/src/lib/roving-list-keyboard.ts +400 -0
  546. package/src/lib/utils.ts +5 -0
  547. package/src/patterns/action-bar/action-bar.spec.md +2 -0
  548. package/src/patterns/element-anatomy/inline-action.spec.md +16 -12
  549. package/src/patterns/element-anatomy/item-anatomy.spec.md +98 -6
  550. package/src/patterns/element-anatomy/item-anatomy.stories.tsx +75 -70
  551. package/src/patterns/element-anatomy/item-anatomy.tsx +17 -6
  552. package/src/patterns/element-anatomy/truncated-text.spec.md +1 -0
  553. package/src/patterns/header-canonical/header-canonical.spec.md +3 -2
  554. package/src/patterns/horizontal-overflow/horizontal-overflow.spec.md +2 -0
  555. package/src/patterns/horizontal-overflow/horizontal-overflow.stories.tsx +36 -0
  556. package/src/patterns/horizontal-overflow/horizontal-overflow.tsx +28 -0
  557. package/src/patterns/overlay-surface/overlay-chrome-sizing.spec.md +180 -0
  558. package/src/patterns/overlay-surface/overlay-surface.spec.md +8 -162
  559. package/src/patterns/overlay-surface/overlay-surface.tsx +5 -3
  560. package/src/patterns/resize-handle/resize-handle.spec.md +3 -3
  561. package/src/styles/base.css +1 -1
  562. package/src/tokens/categorical-color.ts +6 -1
  563. package/src/tokens/color/color.spec.md +91 -24
  564. package/src/tokens/color/color.stories.tsx +50 -2
  565. package/src/tokens/color/primitives.css +41 -38
  566. package/src/tokens/color/semantic.css +105 -36
  567. package/src/tokens/layoutSpace/layoutSpace.spec.md +2 -0
  568. package/src/tokens/motion/motion.spec.md +31 -8
  569. package/src/tokens/orphan-tokens.spec.md +1 -1
  570. package/src/tokens/uiSize/icon-size.ts +1 -1
  571. package/src/tokens/uiSize/stack-gap.ts +17 -0
  572. package/src/tokens/uiSize/uiSize.css +12 -0
  573. package/src/tokens/uiSize/uiSize.spec.md +17 -16
  574. package/src/tokens/uiSize/uiSize.stories.tsx +1 -1
  575. package/dist/node_modules/aria-hidden/dist/es2015/index.js +0 -147
  576. package/dist/node_modules/aria-hidden/dist/es2015/index.js.map +0 -1
@@ -6,6 +6,7 @@ import { Avatar } from '@/design-system/components/Avatar/avatar'
6
6
  import { Button } from '@/design-system/components/Button/button'
7
7
  import { ProgressBar } from '@/design-system/components/ProgressBar/progress-bar'
8
8
  import { ItemContent, ItemPrefix, ItemSuffix } from '@/design-system/patterns/element-anatomy/item-anatomy'
9
+ import { STACK_GAP_PX } from '@/design-system/tokens/uiSize/stack-gap'
9
10
 
10
11
  /**
11
12
  * FileItem — 檔案顯示 / 上傳進度
@@ -15,7 +16,8 @@ import { ItemContent, ItemPrefix, ItemSuffix } from '@/design-system/patterns/el
15
16
  * 兩種 mode(精簡 vs 完整內容呈現):
16
17
  *
17
18
  * compact(★ default): Paperclip 16px 在左。右側 content + bar。
18
- * padding 詳 spec「Padding」表(form: px-3 py-2;surface=upload-manager: px-0 保留 py-2)。
19
+ * padding 詳 spec「Padding」表(form: px-3 py-2;surface=upload-manager: 列自帶左右 loose、上下 tight/2,
20
+ * 面板 body 左右 0 —— 2026-09-25 待辦總帳 B12,可點的列滑過底色鋪到面板邊)。
19
21
  * description 只有 error 才顯示。
20
22
  * bar 跟文字左邊對齊(在 icon 右邊的 column 內)。
21
23
  *
@@ -25,7 +27,7 @@ import { ItemContent, ItemPrefix, ItemSuffix } from '@/design-system/patterns/el
25
27
  * 無 bar → justify-center(文字垂直置中對齊 avatar)
26
28
  *
27
29
  * status 可選。不傳 = 已上傳檔案(無 bar,可點擊下載)。
28
- * onClick → 只加 cursor-pointer(**永不顯示 hover-bg**;FileItem 三型態皆 permanent-anchored,詳 spec「Hover 行為 canonical」)。
30
+ * onClick → cursor-pointer + 滑過底色(只有點了會有反應的才加,依平常底色配對;詳 spec「滑過」段,待辦總帳 B12)。
29
31
  */
30
32
 
31
33
  const STATUS_ICON = {
@@ -59,7 +61,8 @@ export interface FileItemProps extends Omit<React.HTMLAttributes<HTMLDivElement>
59
61
  * card border 多餘 = 雙層容器);avatar 作每筆 item 視覺邊界。compact 的進度條/灰底不受 surface 影響
60
62
  * (由 status 決定)。
61
63
  * surface-driven(非 status-driven):避免 form 內 rich 上傳中變無邊框、存好變 card 的邊框閃爍。
62
- * 列間 gap 由 List wrapper canonical 決定(form rich 8px / upload-manager rich 12px tight / compact 4px,見 spec)。
64
+ * 列間 gap 由 List wrapper canonical 決定(form rich 8px / form compact 4px / upload-manager 0 —— 列自帶上下 tight/2,
65
+ * 列間 ink 距仍 12,2026-09-25 待辦總帳 B12;見 spec)。
63
66
  */
64
67
  surface?: 'form' | 'upload-manager'
65
68
  status?: 'uploading' | 'completed' | 'error'
@@ -85,6 +88,62 @@ export interface FileItemProps extends Omit<React.HTMLAttributes<HTMLDivElement>
85
88
  onRetry?: () => void
86
89
  }
87
90
 
91
+ // ── compact 進度條的左右位置(單一來源)──
92
+ // 進度條本身(下方 compact 分支)與焦點框的挖空(fileItemRingCutoutStyle)都從這裡取,內距一改兩者一起動
93
+ // (2026-06-03 圖五 bug:bar 的 left / right 寫死、內距一改就對不齊)。
94
+ // form:左右 12px(= px-3)。upload-manager(2026-09-25 待辦總帳 B12):左右 loose(= 原本面板給的值,文字 x 不變)。
95
+ // left = 內距 + icon + gap-2(0.5rem)→ 對齊 label 首字;right = 內距 → 收在列內緣。
96
+ function compactBarInset(surface: FileItemProps['surface']) {
97
+ const padX = surface === 'upload-manager' ? 'var(--layout-space-loose)' : '12px'
98
+ return { padX, left: `calc(${padX} + ${ICON_PX}px + 0.5rem)`, right: padX }
99
+ }
100
+
101
+ /**
102
+ * 焦點框在進度條兩端留的縫(px)= DS 通用的「疊在一起的兩樣東西」之間的縫(`tokens/uiSize/stack-gap.ts`,
103
+ * 全域外描邊的 outline-offset、頭像堆疊的縫、步驟條外圈的縫都是同一個 token)。
104
+ */
105
+ const BAR_RING_GAP_PX = STACK_GAP_PX
106
+
107
+ /**
108
+ * @internal
109
+ * 焦點框的「框圖層」:與列同形同大的 `::before`(file-item.spec.md「焦點框 × 貼著列底的進度條」)。
110
+ * 只在**列底貼著進度條**時用 —— form 的 compact 列有 status 時。upload-manager 的進度條離列底 tight/2、
111
+ * rich 的進度條在內容區,都碰不到框,照舊把框畫在列上。
112
+ * 為什麼要另一層:進度條住在列裡。框若畫在列上,列是進度條的祖先,遮罩會連進度條一起挖掉;
113
+ * 畫在 `::before` 上,遮罩只挖框。層疊不變:`::before` 是第一個定位子層,進度條(absolute)仍畫在它上面;
114
+ * 而且洞裡根本沒有框,進度條與框誰畫在上面都不影響結果(CSS UI 4 把 outline 的疊放順序留給瀏覽器自己決定,
115
+ * 「The stacking of the rendering of these outlines is explicitly left up to implementations」)。
116
+ * 這裡只有位置與遮罩,**不含觸發條件**:FileItem 自己的列由整列隱形鈕觸發、FileUpload 的清單列由列自己的
117
+ * `:focus-visible` 觸發,各自在使用端寫。線寬、顏色、往內 2px、圓角全部照舊來自 `focus-ring-inset`
118
+ * (`ds-canonical/references/focus-canonical.md`「框怎麼畫」),不是第四種幾何。
119
+ */
120
+ export const FILE_ITEM_RING_LAYER_CLASS =
121
+ 'before:absolute before:inset-0 before:rounded-[inherit] before:pointer-events-none before:[mask:var(--file-item-ring-cutout)]'
122
+
123
+ /**
124
+ * @internal
125
+ * 框圖層的遮罩(`--file-item-ring-cutout`)。列底沒有貼著進度條時回 `undefined` —— 呼叫端據此照舊把框畫在列上。
126
+ * 遮罩 = 三塊保留區的聯集:上半部整條 + 左段 + 右段。挖掉的是「進度條那一段(兩端各多 BAR_RING_GAP_PX)的下半部」,
127
+ * 框在那個範圍裡只有底邊,所以挖掉的只有底邊那一段。露出來的是**真正在後面的東西**(列自己的滑過色、卡片、面板、頁面),
128
+ * 不需要知道底色是什麼(與 `avatar.tsx` 頭像堆疊「挖空,不是外圈」同一個理由:深色的 `--surface` 本身半透明,
129
+ * 墊任何固定底色都會在某種容器上錯色)。
130
+ */
131
+ export function fileItemRingCutoutStyle({
132
+ mode = 'compact',
133
+ surface = 'form',
134
+ status,
135
+ }: Pick<FileItemProps, 'mode' | 'surface' | 'status'>): React.CSSProperties | undefined {
136
+ if (mode !== 'compact' || surface === 'upload-manager' || !status) return undefined
137
+ const { left, right } = compactBarInset(surface)
138
+ const keep = 'linear-gradient(#000 0 0)' // 遮罩只看不透明度;顏色無意義
139
+ return {
140
+ ['--file-item-ring-cutout' as string]:
141
+ `${keep} top / 100% 50% no-repeat, ` +
142
+ `${keep} left / calc(${left} - ${BAR_RING_GAP_PX}px) 100% no-repeat, ` +
143
+ `${keep} right / calc(${right} - ${BAR_RING_GAP_PX}px) 100% no-repeat`,
144
+ }
145
+ }
146
+
88
147
  // code-quality-allow: long-function — foundational composite main body — 拆 sub-fn 會複雜化 local state / ref / context binding
89
148
  const FileItem = React.forwardRef<HTMLDivElement, FileItemProps>(
90
149
  (
@@ -114,14 +173,18 @@ const FileItem = React.forwardRef<HTMLDivElement, FileItemProps>(
114
173
  // compact 只有 error 才顯示 description
115
174
  const showDesc = isRich ? !!description : (status === 'error' && !!description)
116
175
 
117
- // Hover 行為 canonical(2026-04-23 user 校準):**FileItem 永不顯示 hover-bg**。
118
- // 各型態都有永久 visual anchor:rich surface=form = border card / rich surface=upload-manager
119
- // = avatar 48 thumbnail 作 item 邊界(無邊框)/ compact 無 status = bg-secondary /
120
- // compact 有 status = 底部 progress bar(分隔線型 affordance)——再加 hover-bg 是
121
- // double-emphasis,視覺雜。世界級共識(Slack / Notion / Figma / Gmail 皆無 hover-bg):
122
- // permanent-anchored 元件 hover 只靠 cursor + action icon fade / border highlight,
123
- // 不靠 row bg。onClick 存在時只給 `cursor-pointer`,affordance 靠 cursor + 點擊行為本身。
124
- const hoverClass = onClick ? 'cursor-pointer' : ''
176
+ // 滑過底色(2026-09-25 待辦總帳 B12;取代 2026-04-23「FileItem 永不顯示 hover-bg」)。
177
+ // user 原話:「要點了會有反應的才加,並確保加上去之後不會有任何視覺奇怪的地方,且按鈕的互動樣式也是自然疊加上去吧?用再亮一層這樣的措辭是否不夠精準?」
178
+ // → 只有傳了 onClick 的列 / 卡才有滑過底色;沒傳就沒有(✓→下載鈕的換位照舊,那顆鈕有自己的滑過色)。
179
+ // 換上什麼色依「平常底色」配對(tokens/color/color.spec.md「Hover 換色配對總則」),直接寫在下方各 mode 的 cn() 裡、
180
+ // 跟靜止底同一次呼叫 —— scripts/hover-own-pair-invariant.mjs 才看得到這一對:
181
+ // rich + form 卡片 bg-surface(「底」)→ 底不換,疊一層 bg-interaction-hover
182
+ // compact 小膠囊 bg-secondary → 換成自己的下一階 bg-secondary-hover
183
+ // 透明的列(compact 有進度條、upload-manager 的列)→ 換成 bg-neutral-hover
184
+ // 不加按住(active:)那一階:FileItem「列按下深一階」是待辦總帳 N4 / F 模型 D3(2),尚未提問(AI 推導:不先替 user 決定)。
185
+ // 列內按鈕(刪除、✓→下載)滑過時換上自己的 neutral-hover(半透明),自然疊在列的滑過色上,不寫特例
186
+ // (color.spec.md「巢狀滑過」段)。
187
+ const isUploadManager = surface === 'upload-manager'
125
188
 
126
189
  // 消費 ProgressBar 元件(SSOT);不再自 roll bar。
127
190
  // compact mode 用 2px(極密集 row layout),rich mode 用預設 4px。這是 FileItem
@@ -143,7 +206,8 @@ const FileItem = React.forwardRef<HTMLDivElement, FileItemProps>(
143
206
  // Status slot 幾何(2026-04-23 user 統一):rich + compact 都用 `var(--field-height-xs)`(24)
144
207
  // 容器,裡面 Button xs iconOnly variant="text"(auto data-unbounded)。
145
208
  // Compact 不影響 row 高度 = suffix wrapper 的 data-unbounded CSS 讓 Button layout
146
- // 收斂到 1lh(同 compact row 內容高),視覺/touch target 仍 24。
209
+ // 收斂到 1lh(同 compact row 內容高),視覺與命中區仍 24(命中 ≡ 可視,見
210
+ // ds-canonical/references/hit-area-canonical.md;2026-09-24 把原文的「touch target」正名為命中區)。
147
211
  const slotHw = 'var(--field-height-xs)'
148
212
 
149
213
  const hoverAction =
@@ -157,18 +221,19 @@ const FileItem = React.forwardRef<HTMLDivElement, FileItemProps>(
157
221
  className="relative inline-flex items-center justify-center shrink-0"
158
222
  style={{ width: slotHw, height: slotHw }}
159
223
  >
160
- {/* Passive 狀態 icon:預設可見;若有 hover-swap,row-hover 時淡出 */}
224
+ {/* Passive 狀態 icon:預設可見;若有 hover-swap,row-hover 時瞬間隱藏、換上操作鈕(不淡出;
225
+ tokens/motion/motion.spec.md「hover 回饋不做過渡」,2026-09-26 延伸到滑過才出現的按鈕,待辦總帳 L9 / N4(3)) */}
161
226
  <statusConfig.icon
162
227
  size={ICON_PX}
163
228
  className={cn(
164
- 'shrink-0 transition-opacity motion-reduce:duration-0',
229
+ 'shrink-0',
165
230
  statusConfig.color,
166
231
  // 2026-07-05 D4:鍵盤 focus 同步觸發 swap(:has(:focus-visible),對齊 item-anatomy SUFFIX_HOVER_REVEAL_BY_GROUP SSOT)
167
232
  hoverAction && 'group-hover/row:opacity-0 group-has-[:focus-visible]/row:opacity-0',
168
233
  )}
169
234
  aria-hidden
170
235
  />
171
- {/* Active action:row-hover 時淡入(rich + compact 同 Button xs 統一) */}
236
+ {/* Active action:row-hover 時瞬間出現(不淡入,同上;rich + compact 同 Button xs 統一) */}
172
237
  {hoverAction && (
173
238
  <Button
174
239
  variant="text"
@@ -177,7 +242,7 @@ const FileItem = React.forwardRef<HTMLDivElement, FileItemProps>(
177
242
  startIcon={hoverAction.icon}
178
243
  aria-label={hoverAction.label}
179
244
  onClick={(e) => { e.stopPropagation(); hoverAction.onClick() }}
180
- className="absolute inset-0 opacity-0 group-hover/row:opacity-100 group-has-[:focus-visible]/row:opacity-100 transition-opacity motion-reduce:duration-0"
245
+ className="absolute inset-0 opacity-0 group-hover/row:opacity-100 group-has-[:focus-visible]/row:opacity-100"
181
246
  />
182
247
  )}
183
248
  </span>
@@ -205,7 +270,30 @@ const FileItem = React.forwardRef<HTMLDivElement, FileItemProps>(
205
270
  <span className="text-fg-secondary tabular-nums">{progress}%</span>
206
271
  )}
207
272
  {statusSlot}
208
- {actions}
273
+ {/* **動作鈕不搶整列 click,由元件自己吃掉,不要求 consumer 每次記得**(2026-09-24)。
274
+ Canonical 早就寫了:`patterns/element-anatomy/item-anatomy.spec.md`「suffix 可以塞什麼」表的 Inline Action 列
275
+ 「明確點擊靶子 ≤ 24px,`stopPropagation` 避免搶 row click」。
276
+ 但先前只有 FileItem **自家**的 hover-swap 鈕做到(本檔 statusSlot 裡 `hoverAction` 那顆 Button 的 `stopPropagation`),consumer 從 `actions`
277
+ 傳進來的鈕沒有任何人做 —— 於是「點刪除 → 整列 onClick 也跟著跑 → 檔案被開起來」
278
+ 在我們自己的示範裡活到今天(實測:`已上傳` 這則點刪除鈕,FileViewer 就開)。
279
+ 同一列同時有整列 onClick 和 trailing action 時,這是**必然**會犯的錯,所以答案不是
280
+ 在文件裡要 consumer 記得,而是元件內部把邊界劃好(同 `agent-panel.tsx` 選項卡裡「其他(自由輸入)」輸入框的
281
+ `stopPropagation` / `data-table.tsx` 選取欄 `cellEl` 裡 Checkbox 與 RadioGroupItem 的 `stopPropagation`)。
282
+ **只包 `actions`、不包整個 suffix**:suffix 裡的 `{progress}%` 與被動狀態圖示是唯讀
283
+ metadata,item-anatomy「suffix 可以塞什麼」表明寫唯讀 metadata「不搶 row click target」
284
+ = 點它仍該開整列,所以那兩個不進這層。
285
+ `data-unbounded` 掛在**這一層**:上面那條 `[&>[data-unbounded]]` 只認 ItemSuffix 的
286
+ **直接**子元素;包一層之後負 margin 的承接者換成本層,否則 24 高的鈕會把 compact 列撐高。
287
+ 實測 compact / rich / upload-manager 三則的列高、suffix 高、鈕的座標與尺寸改前改後逐項相同。 */}
288
+ {actions && (
289
+ <span
290
+ data-unbounded="true"
291
+ className="inline-flex items-center gap-2"
292
+ onClick={(e) => e.stopPropagation()}
293
+ >
294
+ {actions}
295
+ </span>
296
+ )}
209
297
  </ItemSuffix>
210
298
  )
211
299
 
@@ -230,14 +318,13 @@ const FileItem = React.forwardRef<HTMLDivElement, FileItemProps>(
230
318
  // 承接;mouse 仍由 row onClick 承接,兩者皆呼叫同一 callback。
231
319
  const rowA11y = {}
232
320
 
233
- // Compact 靜態背景(AR20):無進度條 → 顯示 `bg-secondary`(= neutral-3)作「檔案已上傳 /
234
- // 靜態列表」視覺區隔,跟「上傳中(有 progress bar)」對照。hover 不改 bg(見上方
235
- // hoverClass canonical:FileItem 永不顯示 hover-bg)。
236
- // **為什麼 bg-secondary 不 bg-neutral-3**:`bg-neutral-3` 不是合法 Tailwind utility
237
- // (primitive token `--color-neutral-3` 沒經 `@theme inline` 橋接);`bg-secondary`
238
- // 是 semantic token 橋接的 utility(見 `tokens/color/semantic.css`@theme inline),
239
- // 底色同樣指向 `--color-neutral-3`。對齊 Badge low / ProgressBar track SSOT。
240
- const compactStaticBg = !progressBar ? 'bg-secondary' : ''
321
+ // Compact 靜態背景(AR20):無 status(= 無進度條)→ `bg-secondary`(= neutral-3)作「檔案已上傳 /
322
+ // 靜態列表」視覺區隔,跟「上傳中(有 progress bar)」對照;寫在下方 compact 的 cn() 裡(`!hasStatus &&`),
323
+ // 跟它的滑過配對同一次呼叫(見上方「滑過底色」段)。
324
+ // **為什麼用 semantic 的 bg-secondary、不直接寫 neutral 第 3 階的 primitive 名**:primitive token
325
+ // `--color-neutral-3` 沒經 `@theme inline` 橋接,拿它當 utility 會 silent 失效;`bg-secondary` 是 semantic
326
+ // token 橋接的 utility(見 `tokens/color/semantic.css`@theme inline),底色同樣指向 `--color-neutral-3`。
327
+ // 對齊 Badge low / ProgressBar track SSOT。
241
328
  // Primary row action 與 trailing actions 是 sibling controls,不把整列設成 role=button,
242
329
  // 因此不會形成 nested-interactive。透明 full-row button 只接 keyboard/focus;pointer
243
330
  // 仍由既有 row onClick 接手,保持 hit area 與 consumer callback contract 不變。
@@ -268,16 +355,37 @@ const FileItem = React.forwardRef<HTMLDivElement, FileItemProps>(
268
355
  // hover 底色瞬間切換,不做過渡(user 2026-09-10 拍板「第三題改成全部瞬間」;SSOT = tokens/motion/motion.spec.md「hover 回饋不做過渡」)
269
356
  'group/row relative flex items-start gap-2 w-full text-body leading-compact',
270
357
  // 2026-09-07 H1g:限定只接下面那顆隱形整列鈕(keyboardPrimaryAction)。
271
- // 原本裸寫 `has-[:focus-visible]` 不分對象,trailing action 的 <Button> 自帶
272
- // ring(button.tsx:66)被聚焦時整列也跟著畫 → 一次互動兩個焦點框,
273
- // 違反 focus-canonical「一個項目只有一個指示器」。
274
- 'has-[[data-row-focus-target]:focus-visible]:ring-2 has-[[data-row-focus-target]:focus-visible]:ring-ring',
358
+ // 原本裸寫 `has-[:focus-visible]` 不分對象,trailing action 的 <Button> 被聚焦時整列也跟著畫 →
359
+ // 一次互動兩個焦點框,違反 focus-canonical「一個項目只有一個指示器」。當時 <Button> 自帶
360
+ // ring;2026-09-16 a7b2be94 起它不再自帶焦點 class、改吃全域外描邊,但限定的理由不變。
361
+ // 幾何(2026-09-24 user 拍板「可以照你建議內描邊」):**內描邊**。原本寫的 `ring-2 ring-ring`
362
+ // 是 2026-09-07 就退役的第二套機制,只因閘的舊 regex 只認裸寫的 `focus-visible:ring-2`、
363
+ // 不認這裡的 `has-[…:focus-visible]:` 括號變體才活到今天(該洞同日已補,見
364
+ // scripts/focus-geometry-invariant.mjs R2)。
365
+ // **理由是結構性的**:拿到焦點的是下面那顆 `opacity-0` 的隱形整列鈕,它自己畫不出框,
366
+ // 指示器改畫在列上 —— 這是 focus-canonical「刻意不畫的唯一合法理由是指示器畫在別的元素上,
367
+ // 而且必須指得出承擔者」認可的形狀,承擔者就是本行所在的這個列 div。全域外描邊只作用在
368
+ // 「被聚焦的那個元素」身上,套不到非焦點的列;能掛在列上的只有 focus-ring-inset 與填色專用的
369
+ // focus-ring-inset-emphasis,列不是主色填色 → focus-ring-inset。
370
+ // **不是**靠四周淨空不足,也**不是**引用 item-anatomy「選中 × 互動疊加」表那句「列撐滿容器寬度,
371
+ // 左右沒有 2px 可以往外長」(那格的消費者清單沒有 FileItem):表單的列有圓角、列間 4–12px;
372
+ // 上傳管理器的列 2026-09-25 起鋪滿面板寬、列間 0(B12)—— 兩種幾何都用內描邊,理由只有上面那條結構性的
373
+ // (2026-09-24 審查抓到舊句把「淨空」當理由,勿寫回)。
374
+ // 也**不是**因為捲動容器會裁切 —— 捲動與否明文不進判準。
375
+ 'has-[[data-row-focus-target]:focus-visible]:focus-ring-inset',
275
376
  // surface=form → border card(自立輪廓);surface=upload-manager → 無邊框(box 自身是容器,
276
- // avatar 作 item 邊界)。2026-06-03 codify rich-borderless(原僅 spec 旁註,consumer 自己移除)。
277
- // 2026-06-03 圖五:upload-manager rich 拿掉 px+py(卡片移除後 py 多餘,列高靠 avatar 48 的 content minHeight;
278
- // 容器 + gap 控制間距)。form 保留 px-3 py-3 卡片內距。
279
- surface === 'upload-manager' ? 'rounded-md' : 'px-3 py-3 border border-divider rounded-md bg-surface',
280
- hoverClass,
377
+ // avatar 作 item 邊界)。2026-06-03 codify rich-borderless(原僅 spec 旁註,consumer 自己移除)。form 保留 px-3 py-3 卡片內距。
378
+ surface === 'upload-manager'
379
+ // 上傳管理器的列(2026-09-25 待辦總帳 B12,推翻 2026-06-03「左右拿掉交給容器」):可點的列滑過底色要鋪到
380
+ // 面板左右邊,所以左右 gutter 改由列自己帶、面板 body 給 0(判準 = overlay-surface.spec.md「誰負責左右 gutter」);
381
+ // 值 = 原本面板給的 loose → 縮圖與文字的 x 不變。上下各 tight/2、面板 body 上下也給 tight/2、列間 0 ——
382
+ // 邊緣→縮圖、縮圖→縮圖仍是 tight,滑過底色上下各留 tight/2、不貼縮圖。
383
+ // 不加圓角:列鋪滿面板寬,圓角碰到面板的直邊會缺角(同 MenuItem 滿版列;AI 推導)。
384
+ ? 'px-[var(--layout-space-loose)] py-[calc(var(--layout-space-tight)/2)]'
385
+ : 'px-3 py-3 border border-divider rounded-md bg-surface',
386
+ onClick && 'cursor-pointer',
387
+ // 滑過(B12,見上方「滑過底色」段):上傳列平常透明 → 換成 neutral-hover;表單卡片平常是「底」bg-surface → 疊一層
388
+ onClick && (surface === 'upload-manager' ? 'hover:bg-neutral-hover' : 'hover:bg-interaction-hover'),
281
389
  className,
282
390
  )}
283
391
  onClick={onClick}
@@ -307,24 +415,49 @@ const FileItem = React.forwardRef<HTMLDivElement, FileItemProps>(
307
415
  )
308
416
  }
309
417
 
310
- // ── compact: py-2, bar absolute 底部 ──
311
- // 左右 padding 單一來源(SSOT):form=12px(= px-3);upload-manager=0(由面板提供 L/R)。
312
- // progress bar 是 absolute 定位,其 left/right 必須跟此值「同源」—— 否則 surface 一拿掉 padding,
313
- // bar 的 offset 沒同步就會對齊跑掉(2026-06-03 圖五 bug:原本 left/right 寫死 0.75rem 假設 px-3)。
314
- const compactPadX = surface === 'upload-manager' ? 0 : 12
418
+ // ── compact: bar absolute 底部 ──
419
+ // 內距單一來源(SSOT):progress bar 是 absolute 定位,其 left / right / bottom 必須跟列的內距「同源」——
420
+ // 左右取自 compactBarInset(與焦點框的挖空共用,見檔案上方),上下見下:
421
+ // form:上下 py-2,bar 貼列底。
422
+ // upload-manager(2026-09-25 待辦總帳 B12,推翻 2026-06-03 的「左右 0、交給面板」):可點的列滑過底色要鋪到面板
423
+ // 左右邊,所以左右改由列自己帶 loose(= 原本面板給的值,文字 x 不變)、面板 body 給 0。
424
+ // 上 tight/2、下 tight/2 + 0.5rem(0.5rem = 原 py-2 下緣那 8:文字↔bar 6 + bar 2),bar 離列底 tight/2;
425
+ // 面板 body 上下也給 tight/2、列間 0 → 邊緣→文字、bar→下一列文字、bar→邊緣仍全是 tight(與 06-03 同值),
426
+ // 滑過底色則上下各留 tight/2,bar 不貼底色邊。
427
+ const { padX: compactPadX, left: compactBarLeft, right: compactBarRight } = compactBarInset(surface)
428
+ const compactBarBottom = isUploadManager ? 'calc(var(--layout-space-tight) / 2)' : '0px'
429
+ const compactPadBlock = isUploadManager
430
+ ? { paddingTop: 'calc(var(--layout-space-tight) / 2)', paddingBottom: 'calc(var(--layout-space-tight) / 2 + 0.5rem)' }
431
+ : undefined
432
+ // 列底貼著進度條(form + 有 status)→ 框改畫在框圖層、進度條那段挖空;否則 undefined,框照舊畫在列上(見下方 className)
433
+ const ringCutout = fileItemRingCutoutStyle({ mode, surface, status })
315
434
  return (
316
435
  <div
317
436
  ref={ref}
318
437
  className={cn(
319
438
  // hover 底色瞬間切換,不做過渡(user 2026-09-10 拍板「第三題改成全部瞬間」;SSOT = tokens/motion/motion.spec.md「hover 回饋不做過渡」)
320
- 'group/row relative flex items-start gap-2 py-2 w-full text-body leading-compact rounded-md',
321
- compactStaticBg,
322
- // 2026-09-07 H1g:同 rich —— 只接隱形整列鈕,不接自帶 ring 的 trailing <Button>
323
- 'has-[[data-row-focus-target]:focus-visible]:ring-2 has-[[data-row-focus-target]:focus-visible]:ring-ring',
324
- hoverClass,
439
+ 'group/row relative flex items-start gap-2 w-full text-body leading-compact',
440
+ // form 的列是圓角膠囊 / 圓角列;upload-manager 的列鋪滿面板寬 → 不加圓角(同 rich,AI 推導),上下內距見上方 compactPadBlock
441
+ !isUploadManager && 'py-2 rounded-md',
442
+ !hasStatus && 'bg-secondary',
443
+ // 2026-09-07 H1g:同 rich —— 只接隱形整列鈕,不接同樣會畫框的 trailing <Button>;
444
+ // 幾何同 rich 的內描邊(結構性理由見上方那段)。
445
+ // **列底貼著進度條時,框畫在框圖層 ::before**(2026-09-26,file-item.spec.md「焦點框 × 貼著列底的進度條」)。
446
+ // 進度條是 absolute,本來就畫在框的**上面**,正好落在內描邊底邊那 2px 上 —— 但它沒有「蓋住」框:
447
+ // 軌道 --secondary 是半透明,框的藍從軌道透出來;填色 --info 又與框 --ring 同為 blue-6,與框黏成一條
448
+ // (實測聚焦時填色對軌道 1.10:1,平常 4.55:1;深色 1.17 / 4.04)。框圖層在進度條那一段(兩端各多 2px 縫)
449
+ // 挖空,露出真正的底:聚焦時進度條與平常逐像素相同,進度條的頭尾與框之間各隔一道底色的縫。
450
+ // upload-manager 的 bar 離列底 tight/2、碰不到框 → ringCutout 為 undefined,框照舊畫在列上。
451
+ ringCutout
452
+ ? [FILE_ITEM_RING_LAYER_CLASS, 'has-[[data-row-focus-target]:focus-visible]:before:focus-ring-inset']
453
+ : 'has-[[data-row-focus-target]:focus-visible]:focus-ring-inset',
454
+ onClick && 'cursor-pointer',
455
+ // 滑過(B12,見上方「滑過底色」段):有進度條的列平常透明 → 換成 neutral-hover;
456
+ // 靜態小膠囊平常是 bg-secondary → 換成它自己的下一階 secondary-hover(不借透明底的配對)
457
+ onClick && (hasStatus ? 'hover:bg-neutral-hover' : 'hover:bg-secondary-hover'),
325
458
  className,
326
459
  )}
327
- style={{ paddingInline: compactPadX }}
460
+ style={{ paddingInline: compactPadX, ...compactPadBlock, ...ringCutout }}
328
461
  onClick={onClick}
329
462
  {...rowA11y}
330
463
  {...props}
@@ -340,12 +473,13 @@ const FileItem = React.forwardRef<HTMLDivElement, FileItemProps>(
340
473
  {contentRow}
341
474
  </div>
342
475
 
343
- {/* ProgressBar: absolute 底部。left/right 與 compactPadX 同源:
344
- left = padX + icon + gap-2(0.5rem)對齊 label 首字;right = padX 收在 row 內緣。 */}
476
+ {/* ProgressBar: absolute 底部。left / right 取自 compactBarInset(與焦點框的挖空同源),bottom 與列內距同源:
477
+ left = padX + icon + gap-2(0.5rem)對齊 label 首字;right = padX 收在 row 內緣;
478
+ bottom = form 0(貼列底,聚焦時框在這一段挖空,見上方 className)/ upload-manager tight/2(B12,見上方內距段)。 */}
345
479
  {progressBar && (
346
480
  <div
347
- className="absolute bottom-0"
348
- style={{ left: `calc(${compactPadX}px + ${ICON_PX}px + 0.5rem)`, right: compactPadX }}
481
+ className="absolute"
482
+ style={{ left: compactBarLeft, right: compactBarRight, bottom: compactBarBottom }}
349
483
  >
350
484
  {progressBar}
351
485
  </div>
@@ -371,7 +505,7 @@ export const fileItemMeta = {
371
505
  tokens: {
372
506
  bg: ['bg-secondary', 'bg-surface'],
373
507
  fg: ['text-fg-muted', 'text-fg-secondary'],
374
- ring: [],
508
+ ring: ['focus-ring-inset'],
375
509
  },
376
510
  } as const
377
511
 
@@ -2,6 +2,7 @@
2
2
  import React from 'react'
3
3
  import type { Meta, StoryObj } from '@storybook/react'
4
4
  import { FileUpload } from './file-upload'
5
+ import { SegmentedControl, SegmentedControlItem } from '@/design-system/components/SegmentedControl/segmented-control'
5
6
  import { H3, Desc, Td, Th, TokenCell } from '@/design-system/stories-helpers/anatomy/anatomy-utils'
6
7
 
7
8
  const meta: Meta = {
@@ -126,20 +127,15 @@ export const Inspector: Story = {
126
127
  hover 與 drag-over 視覺統一(純 border-driven)。預覽切換狀態後可對照右側 token 面板。
127
128
  (loading 已 deferred — 見上方 type 註解)
128
129
  </Desc>
129
- <div className="flex gap-2 mb-4">
130
- {(['idle', 'drag-over', 'disabled'] as StateKey[]).map((s) => (
131
- <button
132
- key={s}
133
- onClick={() => setState(s)}
134
- className={`px-3 py-1.5 text-caption rounded-md border ${
135
- state === s
136
- ? 'border-primary bg-primary-subtle text-primary'
137
- : 'border-border bg-surface text-foreground hover:bg-neutral-hover'
138
- }`}
139
- >
140
- {s}
141
- </button>
142
- ))}
130
+ {/* 互斥切換消費 SegmentedControl(segmented-control.spec.md「何時用」2–5 個互斥選項)。取代手刻 pill:
131
+ 它在不透明的 bg-surface 上換成透明底專用的 neutral-hover,深色主題滑過反而變暗(#1D1D1D→#141414,
132
+ color.spec.md 成對 token 錯配);SegmentedControl 底色恆為 surface、滑過只換邊框與字色 */}
133
+ <div className="mb-4">
134
+ <SegmentedControl size="sm" aria-label="State" value={state} onValueChange={(v) => setState(v as StateKey)}>
135
+ {(['idle', 'drag-over', 'disabled'] as StateKey[]).map((s) => (
136
+ <SegmentedControlItem key={s} value={s}>{s}</SegmentedControlItem>
137
+ ))}
138
+ </SegmentedControl>
143
139
  </div>
144
140
  </div>
145
141
 
@@ -440,7 +436,7 @@ export const Accessibility = {
440
436
  render: () => (
441
437
  <div className="max-w-3xl text-body text-fg-secondary">
442
438
  <h3 className="text-h5 text-foreground mb-2">無障礙設計</h3>
443
- <p className="whitespace-pre-line">{"詳 `file-upload.spec.md` 「A11y 預設」段。摘要:\n\n- dropzone 使用 role=\"button\" + tabIndex=0(disabled/loading 時 -1),Enter / Space 開啟選檔;accessible name 來自預設 Empty 文字或自訂 children / aria-label\n- button variant 使用原生 DS Button,accessible name 來自 buttonLabel\n- <input type=\"file\"> 以 hidden(display:none) 隱藏,移出無障礙樹;互動由當前 variant 的可見觸發元件承載"}</p>
439
+ <p className="whitespace-pre-line">{"詳 `file-upload.spec.md` 「A11y 預設」段。摘要:\n\n- dropzone 使用 role=\"button\" + tabIndex=0(disabled/loading 時 -1),Enter / Space 開啟選檔;accessible name 來自預設 Empty 文字或自訂 children / aria-label\n- button variant 使用原生 DS Button,accessible name 來自 buttonLabel\n- <input type=\"file\"> 以 hidden(display:none) 隱藏,移出無障礙樹;互動由當前 variant 的可見觸發元件承載\n- 檔案清單(有 onRemove)= 一個 Tab 停靠點:↑↓ 換列、→ 進該列的移除鈕、← 回列、Tab 一下離開(待辦總帳 B9;按鍵表住 file-upload.spec.md「A11y 預設 › 檔案清單鍵盤」)"}</p>
444
440
  </div>
445
441
  ),
446
442
  }
@@ -62,6 +62,8 @@ FileUpload 是**拖放 / 點擊上傳區塊**——可拖曳檔案進入或點
62
62
  | `loading`(**deferred**)| `loading` prop 為 true | **Deferred:其唯一用途(無清單單檔/頭像替換)場景未定義,showcase 僅呈現 3-state(idle/drag-over/disabled);prop 在 tsx 保留供未來。** 行為:CircularProgress 取代內容;`cursor-progress`(不加 `pointer-events-none`,互動由 isBlocked guard 擋);aria-busy=true。有清單的 flow 進度一律走 FileItem 自身的 progress bar(uploading status)—— FileUpload 內建 list 仍 `surface=form`(dropzone 非獨立浮層 upload manager,見 file-item.spec.md「upload-manager 浮層面板 composition」)|
63
63
  | `disabled` | `disabled` prop 為 true | **語意 token(非 opacity — dashed outline surface 走 DS outline-disabled 慣例,3/4 世界級 Ant/Polaris/Carbon 用 token)**:`bg-disabled` 底 + 邊框不變色 + 文字/icon → `fg-disabled`(由 `<Empty disabled>` 控,icon-circle 維持 muted)+ `cursor-not-allowed`(移除 `pointer-events-none` 後生效)|
64
64
 
65
+ **邊框變色瞬間切換、不過渡**(2026-09-26 待辦總帳 L9「全部瞬間」延伸到外框,user:「確定這樣才是一致設計語言就做」;同一次滑過底色瞬間、外框卻 0.15 秒 = 兩套手感;SSOT = `../../tokens/motion/motion.spec.md`「hover 回饋不做過渡」;2026-09-26 前是 `transition-colors` 0.15 秒)。
66
+
65
67
  **State 優先序**:`disabled > loading > drag-over > idle`。disabled 最硬(完全不可用),loading 次之(處理中,user 要等),drag-over 最柔(互動中),idle 預設。
66
68
 
67
69
  **loading vs disabled 的差異**:
@@ -112,7 +114,7 @@ FileUpload 是**拖放 / 點擊上傳區塊**——可拖曳檔案進入或點
112
114
  - `files`: uploaded / uploading 檔案清單(`FileUploadStatus[]`:id / name / size? / progress? / status? / description? / thumbnailSrc?)。傳入 → drop zone 下方渲染列表,每項經 `FileItem`(status 對應:uploading = progress bar / completed = ✓ / error = ✗);不傳 → 不顯示。consumer 持 state(progress / status),FileUpload 只負責渲染
113
115
  - `fileListMode`: 清單每項顯示模式;預設 `'compact'`(單行),`'rich'` 含 thumbnail / size / progress bar
114
116
  - `onRemove(id)`: 清單移除 callback;有值 → 每項右側顯示 X 移除鈕(ARIA label 由 `removeAriaLabel` 模板客製,預設「移除 {name}」),無 → view-only
115
- - 移除焦點:在 callback 前把 focus 交給下一項 remove button;沒有下一項則前一項;清單清空則回 FileUpload owner trigger(dropzone 或 button)。禁止 item unmount 後讓 focus 掉到 `body`。
117
+ - 移除焦點:在 callback 前把 focus 交給下一項 remove button;沒有下一項則前一項;清單清空則回 FileUpload owner trigger(dropzone 或 button)。禁止 item unmount 後讓 focus 掉到 `body`。移除鈕不在 Tab 路上(見「A11y 預設」檔案清單鍵盤),但仍可由程式聚焦;焦點接力到哪一列,清單的 Tab 停靠點就跟到哪一列(2026-09-25 待辦總帳 B9)。
116
118
  - `variant` / `buttonLabel`: 見「兩種觸發外觀」段
117
119
 
118
120
  ---
@@ -150,6 +152,30 @@ FileUpload 是**拖放 / 點擊上傳區塊**——可拖曳檔案進入或點
150
152
  - `<input type="file">` 以 `className="hidden"`(`display:none`)隱藏,移出無障礙樹且不可聚焦;互動由當前 variant 的可見觸發元件承載。
151
153
  - **dropzone accessible name(name-from-content + custom children 例外)**:預設(未傳 children)時 name 來自 wrapper 內 `<Empty>` 的 title + description 文字,非 input 本身。若 consumer 傳入**只有 icon / branding 圖像、無可見文字**的 children,`role="button"` wrapper 會失去 accessible name;此時必須在 `<FileUpload>` root 傳 `aria-label`,或確保 children 含可見文字。
152
154
 
155
+ ### 檔案清單鍵盤(`files` + `onRemove`)= 一個 Tab 停靠點
156
+
157
+ 2026-09-25 待辦總帳 B9「路線乙」(`governance/planning/2026-09-25-interaction-and-hover-remediation.md`),user 逐字(附條件同意,條件查證成立記在該列):「確定建議符合我們一致的設計語言且不違背世界級的設計就照建議」。
158
+ 規則與一手依據住 `../../../ds-canonical/references/keyboard-model-canonical.md`「列上有小按鈕的一串」;本段只列本元件的按鍵表。2026-09-25 前:每一列的移除鈕各佔一站、沒有方向鍵。
159
+ 判定與執行 = `../../lib/roving-list-keyboard.ts`(2026-09-26 與 Sidebar / TreeView / Command 四份合一,待辦總帳〇節「按鍵規則合併」;判定表 `scripts/test-roving-list-keyboard.mjs`),本元件只提供「誰是列、列裡有哪些東西」;鍵盤處理掛在捕獲階段(同 Sidebar),`description` 裡放進來的選單鈕按 `↓` 也是換列(批次細節 X6)。列裡若有輸入框,它的方向鍵與空白鍵屬於它自己,`Tab` 仍一下離開。
160
+
161
+ | 鍵 | 焦點在列上 | 焦點在這一列的按鈕上(移除鈕,或 `description` 裡的連結) |
162
+ |---|---|---|
163
+ | `↑` `↓` | 上 / 下一列,不繞回 | 回到上 / 下一列(焦點落在列上) |
164
+ | `Home` `End` | 第一 / 最後一列 | 同左 |
165
+ | `→` | 進這一列的第一顆按鈕(沒有就不動) | 下一顆;最後一顆停住 |
166
+ | `←` | 不動 | 上一顆;第一顆 → 回到列 |
167
+ | `Tab` / `Shift+Tab` | 一下離開清單 | 同左(先回到本列,再往下 / 往上走一站) |
168
+ | `Enter` / `Space` | —(列本身沒有動作) | 啟動那顆按鈕 |
169
+
170
+ - 別列的按鈕、這一列的按鈕都不在 Tab 路上(`tabIndex=-1`,`→` 才進得去);從外面 Tab 回來,落在上次停的那一列,沒停過(或那一列已被移除)→ 第一列。
171
+ - 身分:容器 `role="grid"`(`aria-label="已上傳的檔案"`)、每列 `role="row"`(可聚焦,焦點框 = 內描邊 `focus-visible:focus-ring-inset`,同 FileItem 整列焦點框的幾何,`focus-canonical.md`「框怎麼畫」;**compact 清單裡有 `status` 的列**(進度條貼列底)框改畫在列自己的 `::before` 框圖層、在進度條那一段連同兩端各 2px 的縫挖空,列本身不畫全域外描邊 —— 框圖層與遮罩都由 FileItem 提供(`FILE_ITEM_RING_LAYER_CLASS` / `fileItemRingCutoutStyle`,挖的位置與進度條同源),規則與理由見 `../FileItem/file-item.spec.md`「焦點框 × 貼著列底的進度條」)、列的唯一子元素 FileItem `role="gridcell"`。
172
+ - 為什麼是 grid:W3C listbox 模式明說選項裡不能互動,「To present a list of interactive elements, see the Grid Pattern」(<https://github.com/w3c/aria-practices/blob/3f094fde1c81b25dfa69162563bf28d093f854d4/content/patterns/listbox/listbox-pattern.html#L31-L33>)。
173
+ - 列上多動作時用 grid / row、列的直接子元素是 gridcell:Fluent List「the list item roles should be `grid`, and `row` … each direct child of the `ListItem` component has a role `gridcell`」(<https://github.com/microsoft/fluentui/blob/d27922755bebae866d9ffe86b7da44c27ec801ee/packages/react-components/react-list/stories/src/List/ListDescription.md#L58>);React Aria GridList 同構(`role: 'grid'` <https://github.com/adobe/react-spectrum/blob/956ecbcb8803f0d0d5d5d973bb169d70c52aea02/packages/react-aria/src/gridlist/useGridList.ts#L192>、`'row'` / `'gridcell'` <https://github.com/adobe/react-spectrum/blob/956ecbcb8803f0d0d5d5d973bb169d70c52aea02/packages/react-aria/src/gridlist/useGridListItem.ts#L417-L462>)。
174
+ - 容器與列用 `div`,不用 `ul` / `li`:W3C ARIA in HTML 的 `ul` 允許角色不含 grid(<https://www.w3.org/TR/html-aria/#el-ul>),`li` 在清單裡除了 listitem 不准換角色(<https://www.w3.org/TR/html-aria/#el-li>)。
175
+ - **沒有 `onRemove` 的唯讀清單維持純 `ul` / `li`,不是焦點站**:列上沒有可操作的東西(`focus-canonical.md`「問題一」:不可操作 → 不可聚焦)。
176
+ - 按鈕上的 `↑` `↓`、「先回到本列再離開」、唯讀清單不收成一站:AI 推導(`keyboard-model-canonical.md` 來源總帳);`Esc` 不規定(同該檔)。
177
+ - 驗證:`file-upload.stories.tsx`「檔案清單鍵盤走法驗證」(test-only play)。
178
+
153
179
  ---
154
180
 
155
181
  ## 相關
@@ -139,6 +139,39 @@ export const FileListRemoveFocusContract = {
139
139
  },
140
140
  }
141
141
 
142
+ // 鍵盤走法契約 probe(2026-09-25 待辦總帳 B9 路線乙;按鍵表 SSOT = file-upload.spec.md「A11y 預設」):
143
+ // 有移除鈕的清單只佔一個 Tab 停靠點,↑↓ 換列、→ 進這一列的移除鈕、← 回列。只屬測試,不寄生在展示 story。
144
+ export const FileListKeyboardContract = {
145
+ name: '檔案清單鍵盤走法驗證',
146
+ // 示範焦點是本則的主題(story-rules「示範 = 滑鼠使用者」):不放掉 play 造出的鍵盤焦點
147
+ parameters: { demoFocus: 'keep' },
148
+ tags: ['test-only'],
149
+ render: () => <FileListDemo />,
150
+ play: async ({ canvasElement }: { canvasElement: HTMLElement }) => {
151
+ const canvas = within(canvasElement)
152
+ const grid = await canvas.findByRole('grid', { name: '已上傳的檔案' })
153
+ const rows = within(grid).getAllByRole('row')
154
+ // 整串只有一列在 Tab 路上;列裡的移除鈕都不在(-1,→ 才進得去)
155
+ await expect(rows.filter((row) => row.tabIndex === 0)).toHaveLength(1)
156
+ for (const button of within(grid).getAllByRole('button', { name: /^移除 / })) {
157
+ await expect(button).toHaveAttribute('tabindex', '-1')
158
+ }
159
+ // 從拖放區 Tab 一下進清單,落在第一列
160
+ canvas.getByRole('button', { name: /上傳附件/ }).focus()
161
+ await userEvent.tab()
162
+ await expect(rows[0]).toHaveFocus()
163
+ await userEvent.keyboard('{ArrowDown}')
164
+ await expect(rows[1]).toHaveFocus()
165
+ await userEvent.keyboard('{ArrowRight}')
166
+ await expect(within(rows[1]).getByRole('button', { name: '移除 cover-image.png' })).toHaveFocus()
167
+ await userEvent.keyboard('{ArrowLeft}')
168
+ await expect(rows[1]).toHaveFocus()
169
+ // 停靠點跟著焦點走:從外面 Tab 回來會落在這一列
170
+ await expect(rows[1]).toHaveAttribute('tabindex', '0')
171
+ await expect(rows[0]).toHaveAttribute('tabindex', '-1')
172
+ },
173
+ }
174
+
142
175
  // beta.97 弱化 icon hover 一階的真實互動證據:
143
176
  // FileUpload 內建 files 清單自己渲染 remove Button,story 不以 className 模擬 hover。
144
177
  export const RemoveHoverState = {