@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
@@ -51,8 +51,16 @@ LinkInput 是 **URL 的**輸入與顯示元件。外觀基於 Input,但 value
51
51
 
52
52
  有合法 URL 且未在編輯中時:
53
53
  - value 以 `text-primary` 藍色顯示,hover 加底線,點擊開啟連結
54
+ - **連結的可點範圍 = 網址文字本身**:連結只有文字那麼寬,太長時縮到欄寬並截斷;不撐滿整行
54
55
  - 右側 Pencil inline action 觸發編輯模式
55
- - 點擊 value 是開啟連結,不是編輯——這是 LinkInput 與 Input 的核心互動差異
56
+ - **點外框裡其他地方 = 按 Pencil**(user 2026-09-26 原話「網址欄的部分你再好好研究Jira等世界級的設計是怎麼做的,確保沒有分歧才照你建議做。」;研究結論:查到的一手來源沒有一家從空白處開連結):內距、1px 邊框、文字與 Pencil 之間、文字右側的空白,點下去都進入編輯並聚焦輸入框。外框滑過變色是 Field 家族共用外觀(`../Field/field-wrapper.tsx` `hover:border-border-hover`),變色的地方點下去就要有反應(`../../../ds-canonical/references/hit-area-canonical.md`「看到亮起來卻點不到」)。外框用文字游標(`FIELD_TEXT_ENTRY_CURSOR`,同 `../Input/input.spec.md`「點外框 = 點輸入處」);連結維持手形、Pencil 維持 `cursor-pointer`
57
+ - 按在空白處、拖過網址文字再放開(選字)不算點一下,不進入編輯
58
+ - 點擊 value 是開啟連結,不是編輯——這是 LinkInput 與 Input 的核心互動差異。「value」指網址文字本身,不含外框裡的空白
59
+
60
+ **世界級對照**:
61
+ - Atlassian inline-edit 的讀取態:點到連結照連結原本的行為,點讀取區其他地方進入編輯 —— [`read-view.js#L27-L37`](https://cdn.jsdelivr.net/npm/@atlaskit/inline-edit@16.4.5/dist/es2019/internal/read-view.js)「If a link is clicked in the read view, default action should be taken」,非連結的點擊 `onEditRequested()`;點擊入口只到滑過範圍為止([`CHANGELOG.md` 4.5.5](https://cdn.jsdelivr.net/npm/@atlaskit/inline-edit@16.4.5/CHANGELOG.md)「fix inline-edit component edit mode triggering when clicking outside hover width」)
62
+ - Notion URL 屬性:點網址開啟、不進入編輯,編輯另有滑過才出現的按鈕 —— [2021-09-08 release note](https://www.notion.com/releases/2021-09-08)「Clicking the URL opens that page in your web browser, rather than selecting the property for editing. And there's a new Edit URL button that appears when you hover over the property with your cursor.」
63
+ - Salesforce Lightning Design System 記錄詳情:`<a>` 只包住值的文字,編輯是另一顆鉛筆鈕(`Edit: ${label}`)—— [`record-detail/index.jsx#L64-L96`](https://github.com/salesforce-ux/design-system/blob/cb91709d48074a8422a968fecda298a8bc715749/ui/components/form-element/record-detail/index.jsx#L64-L96)
56
64
 
57
65
  ### Input 狀態
58
66
 
@@ -105,6 +113,8 @@ URL 格式要求:必須包含 `http://` 或 `https://` protocol。
105
113
  ## 禁止事項
106
114
 
107
115
  - ❌ 不在 link 狀態下讓點擊 value 進入編輯——點擊連結必須開啟連結
116
+ - ❌ 不讓連結撐滿整行——文字右邊看起來空白的地方點下去卻開網頁
117
+ - ❌ 不讓 link 狀態外框裡的空白處滑過變色、點下去卻沒有反應——空白處 = Pencil
108
118
  - ❌ 不在打字過程中即時驗證格式——等 blur
109
119
  - ❌ 不省略 protocol(http/https)驗證——裸 domain 不是合法 URL
110
120
 
@@ -134,6 +144,7 @@ LinkInput 是 **Field Controls family 成員**——互動狀態(focus / invalid
134
144
  - 字母鍵 — 輸入
135
145
  - Enter — 提交,觸發 blur 驗證
136
146
  - Esc — 取消編輯,回復原值,不觸發驗證
147
+ - Link 狀態:Tab 依序停在連結(Enter 開啟)與 Pencil(Enter / Space 進入編輯並聚焦輸入框)。「點外框空白處 = 按 Pencil」是滑鼠的捷徑,外框本身不是 tab stop,鍵盤走 Pencil
137
148
 
138
149
  **Focus**:原生 input outline 已關閉;focus 視覺提示由 Field wrapper 的 `focus-within:!border-primary` 提供(滑鼠點入也亮藍框,對齊 Field wrapper canonical)。
139
150
 
@@ -3,7 +3,7 @@ import { Pencil } from 'lucide-react'
3
3
  import type { VariantProps } from 'class-variance-authority'
4
4
  import { cn } from '@/lib/utils'
5
5
  import type { FieldMode, FieldVariant, FieldVariantInternal } from '@/design-system/components/Field/field-types'
6
- import { fieldWrapperStyles, bareInputStyles, fieldDisplayTextClass } from '@/design-system/components/Field/field-wrapper'
6
+ import { fieldWrapperStyles, bareInputStyles, fieldDisplayTextClass, FIELD_CHROME_OWN_TARGET, FIELD_TEXT_ENTRY_CURSOR, focusFieldInputFromChrome } from '@/design-system/components/Field/field-wrapper'
7
7
  import { useFieldContext, useResolvedFieldSize, useResolvedFieldDisabled, useResolvedFieldMode, useResolvedFieldVariant, useResolvedFieldInvalid, useFieldEmptyDisplay, fieldEmptyColorClass } from '@/design-system/components/Field/field-context'
8
8
  import { ItemInlineAction } from '@/design-system/patterns/element-anatomy/item-anatomy'
9
9
  import { TruncatedText } from '@/design-system/patterns/element-anatomy/truncated-text'
@@ -32,6 +32,7 @@ function formatHostname(url: string): string {
32
32
  // 取代 LinkInputDisplay sub-component:純展示 a tag,無 input chrome、無 hover affordance。
33
33
  // edit mode 內 link state(showLink branch)也共用此 helper,確保「編輯態的 link 顯示」與
34
34
  // view mode 的視覺完全一致(SSOT)。
35
+ // 連結 hover 字色瞬間切換,不寫 transition-colors(tokens/motion/motion.spec.md「hover 回饋不做過渡」;2026-09-26 由底色延伸到字色,待辦總帳 L9 / N4(3))。
35
36
  function renderLinkAnchor(value: string, label?: string) {
36
37
  const displayText = label || formatHostname(value)
37
38
  return (
@@ -39,7 +40,7 @@ function renderLinkAnchor(value: string, label?: string) {
39
40
  href={value}
40
41
  target="_blank"
41
42
  rel="noopener noreferrer"
42
- className="block truncate min-w-0 text-primary hover:text-primary-hover hover:underline transition-colors"
43
+ className="block truncate min-w-0 text-primary hover:text-primary-hover hover:underline"
43
44
  >
44
45
  {/* 截斷必附 tooltip(tooltip.spec.md:32)— anchor 自身即 hover 目標、無疊層,TruncatedText
45
46
  放 anchor 內(trigger = 其 span child)即可;view/readonly/edit-showLink 共用本 helper 一處修。
@@ -194,6 +195,25 @@ const LinkInput = React.forwardRef<HTMLInputElement, LinkInputProps>(
194
195
  })
195
196
  }
196
197
 
198
+ // 連結狀態:外框裡「不是連結、不是鉛筆」的地方點下去 = 按鉛筆(user 2026-09-26「確保沒有分歧才照你建議做」,
199
+ // 研究後照做;規則與出處 link-input.spec.md「Link 狀態」)。
200
+ // 外框滑過會變色(Field 家族 hover:border-border-hover),變色的地方點下去就要有反應 ——
201
+ // hit-area-canonical.md 要防的「看到亮起來卻點不到」。連結與鉛筆照它們自己的行為走:
202
+ // 判斷用 Field 家族共用的「外框裡自有行為的東西」清單(field-wrapper.tsx FIELD_CHROME_OWN_TARGET),不另寫一份。
203
+ // 用 click 不用 mousedown:與鉛筆同一個觸發時機;按下後拖出外框才放開,click 落在外框之外的共同祖先,不會觸發這裡。
204
+ // 拖曳選字不是點一下:按在空白處、拖過網址文字、在外框裡放開,瀏覽器仍會在外框上發 click;
205
+ // 此時直接看「外框裡有沒有被選起來的文字」,有就不進編輯(量的就是要保護的那件事,不拿移動距離當代理)。
206
+ const handleLinkChromeClick = (event: React.MouseEvent<HTMLDivElement>) => {
207
+ const chrome = event.currentTarget
208
+ const target = event.target
209
+ if (!(target instanceof Element)) return
210
+ const own = target.closest(FIELD_CHROME_OWN_TARGET)
211
+ if (own && own !== chrome && chrome.contains(own)) return
212
+ const selection = window.getSelection()
213
+ if (selection && !selection.isCollapsed && selection.anchorNode && chrome.contains(selection.anchorNode)) return
214
+ handleEdit()
215
+ }
216
+
197
217
  const handleBlur = () => {
198
218
  setEditing(false)
199
219
  const trimmed = localValue.trim()
@@ -234,6 +254,9 @@ const LinkInput = React.forwardRef<HTMLInputElement, LinkInputProps>(
234
254
  <div
235
255
  className={cn(fieldWrapperStyles({ mode: resolvedMode, variant: resolvedVariant, size }), className)}
236
256
  data-field-mode={resolvedMode}
257
+ // 2026-09-29 a11y(待辦總帳 OE21):disabled 的純展示殼明告 inactive(對齊 FieldLabel 2026-06-10 同修),
258
+ // 否則 axe 把灰化連結文字當一般文字誤報 color-contrast(modes / 色彩對照 / 尺寸對照三則 story)
259
+ aria-disabled={resolvedMode === 'disabled' || undefined}
237
260
  >
238
261
  <span className="flex-1 min-w-0 truncate">
239
262
  {resolvedMode === 'disabled'
@@ -255,11 +278,17 @@ const LinkInput = React.forwardRef<HTMLInputElement, LinkInputProps>(
255
278
  <div
256
279
  // 2026-07-05 D4:link 顯示分支同樣接 error(errorProp / fieldCtx.invalid)紅框,
257
280
  // 否則 <Field invalid> 下 link state 完全無 error 呈現
258
- className={cn(fieldWrapperStyles({ mode: 'edit', variant: resolvedVariant, size, error }), className)}
281
+ // 外框用文字游標:點空白處進入的是打字(field-controls.spec.md「游標指引」input → cursor-text;同 Input 外框)。
282
+ // 連結(瀏覽器預設手形)與鉛筆(cursor-pointer)各自覆寫。
283
+ className={cn(fieldWrapperStyles({ mode: 'edit', variant: resolvedVariant, size, error }), FIELD_TEXT_ENTRY_CURSOR, className)}
259
284
  data-field-mode="edit"
260
285
  data-error={error ? '' : undefined}
286
+ onClick={handleLinkChromeClick}
261
287
  >
262
- <span className="flex-1 min-w-0">
288
+ {/* flex:連結是 flex item,寬度 = 文字本身、太長時縮到欄寬截斷(min-w-0 + truncate)。
289
+ 不撐滿整行 —— 文字右邊看起來空白的那一段屬於外框,點下去進入編輯,不是開網頁。
290
+ 只改這個分支:view / readonly 的外框沒有第二個動作,連結維持原樣。 */}
291
+ <span className="flex-1 min-w-0 flex">
263
292
  {value && renderLinkAnchor(value, label)}
264
293
  </span>
265
294
  <ItemInlineAction
@@ -275,8 +304,12 @@ const LinkInput = React.forwardRef<HTMLInputElement, LinkInputProps>(
275
304
  <div
276
305
  className={cn(
277
306
  fieldWrapperStyles({ mode: 'edit', variant: resolvedVariant, size, error }),
307
+ // 打字狀態的整個外框都是輸入處:文字游標 + 點內距 / 邊框就聚焦 input(同 Input / NumberInput;待辦總帳 N53③,
308
+ // input.spec.md 已寫三者同一條;共用實作 field-wrapper.tsx focusFieldInputFromChrome)
309
+ FIELD_TEXT_ENTRY_CURSOR,
278
310
  className,
279
311
  )}
312
+ onMouseDown={focusFieldInputFromChrome}
280
313
  data-field-mode="edit"
281
314
  data-error={error ? '' : undefined}
282
315
  >
@@ -8,6 +8,7 @@ import { useState, useEffect } from 'react'
8
8
  import { Mail, Bell, FileText } from 'lucide-react'
9
9
  import { MenuItem, MenuGroup } from './menu-item'
10
10
  import { Tag } from '@/design-system/components/Tag/tag'
11
+ import { SegmentedControl, SegmentedControlItem } from '@/design-system/components/SegmentedControl/segmented-control'
11
12
  // Avatar is now passed as AvatarData (data object), rendered internally by MenuItem
12
13
 
13
14
  const meta: Meta = {
@@ -132,18 +133,6 @@ const TokenAnnotation = ({ colors }: { colors: ColorSpec }) => (
132
133
  </div>
133
134
  )
134
135
 
135
- const Tab = ({ active, onClick, disabled, children }: { active: boolean; onClick: () => void; disabled?: boolean; children: React.ReactNode }) => {
136
- if (disabled) return <span className="px-2.5 py-1 text-[12px] font-mono rounded-md text-fg-disabled bg-neutral-hover cursor-not-allowed">{children}</span>
137
- return (
138
- <button type="button" onClick={onClick}
139
- className={`px-2.5 py-1 text-[12px] font-mono rounded-md cursor-pointer transition-colors ${
140
- active ? 'bg-primary text-white font-semibold' : 'bg-neutral-hover text-fg-secondary hover:bg-neutral-active'
141
- }`}>
142
- {children}
143
- </button>
144
- )
145
- }
146
-
147
136
  const PropRow = ({ label, dot, children }: { label: string; dot?: string; children: React.ReactNode }) => (
148
137
  <div className="flex items-start gap-3 py-2 border-b border-divider last:border-b-0">
149
138
  <span className="text-[11px] text-fg-muted font-medium w-[80px] shrink-0 pt-0.5 flex items-center gap-1.5">
@@ -186,8 +175,11 @@ const BpZoneV = ({ h, color, label, sub }: { h: number; color: { bg: string; bor
186
175
  </div>
187
176
  )
188
177
 
178
+ // 靜態視覺預覽容器(不是真的 listbox)。2026-09-24 從 `role="listbox"` 改 `role="group"`、
179
+ // 內部 MenuItem 一律傳 `role="presentation"`:預覽沒有選取狀態也沒有方向鍵,
180
+ // 掛 composite 角色 = 空頭承諾(`ds-canonical/references/keyboard-model-canonical.md`「鐵律」)。
189
181
  const MenuContainer = ({ children, width = 320 }: { children: React.ReactNode; width?: number }) => (
190
- <div role="listbox" aria-label="MenuItem 設計預覽" className="rounded-lg bg-surface-raised border border-border overflow-hidden"
182
+ <div role="group" aria-label="MenuItem 設計預覽" className="rounded-lg bg-surface-raised border border-border overflow-hidden"
191
183
  style={{ boxShadow: 'var(--elevation-200)', width }}>
192
184
  {children}
193
185
  </div>
@@ -324,62 +316,64 @@ const InspectorInner = () => {
324
316
 
325
317
  return (
326
318
  <div className="flex flex-col gap-6">
327
- {/* Controls */}
319
+ {/* Controls — 互斥切換消費 SegmentedControl(segmented-control.spec.md「何時用」2–5 個互斥選項;
320
+ 取代手刻 Tab:它靜止借 neutral-hover、hover 借 neutral-active,是 color.spec.md 成對 token 的錯配) */}
328
321
  <div className="flex flex-col gap-2.5">
329
322
  <div className="flex items-center gap-2">
330
323
  <span className="text-[11px] text-fg-muted w-20 shrink-0">Size</span>
331
- <div className="flex gap-1.5">
332
- {SIZES.map((sz) => <Tab key={sz} active={size === sz} onClick={() => setSize(sz)}>{sz}</Tab>)}
333
- </div>
324
+ <SegmentedControl size="sm" aria-label="Size" value={size} onValueChange={(v) => setSize(v as SizeKey)}>
325
+ {SIZES.map((sz) => <SegmentedControlItem key={sz} value={sz}>{sz}</SegmentedControlItem>)}
326
+ </SegmentedControl>
334
327
  </div>
335
328
  <div className="flex items-center gap-2">
336
329
  <span className="text-[11px] text-fg-muted w-20 shrink-0">Mode</span>
337
- <div className="flex gap-1.5">
338
- <Tab active={mode === 'single'} onClick={() => setMode('single')}>single</Tab>
339
- <Tab active={mode === 'multi'} onClick={() => setMode('multi')}>multi</Tab>
340
- </div>
330
+ <SegmentedControl size="sm" aria-label="Mode" value={mode} onValueChange={(v) => setMode(v as ModeKey)}>
331
+ <SegmentedControlItem value="single">single</SegmentedControlItem>
332
+ <SegmentedControlItem value="multi">multi</SegmentedControlItem>
333
+ </SegmentedControl>
341
334
  </div>
342
335
  <div className="flex items-center gap-2">
343
336
  <span className="text-[11px] text-fg-muted w-20 shrink-0">selected</span>
344
- <div className="flex gap-1.5">
345
- <Tab active={!selected} onClick={() => setSelected(false)}>off</Tab>
346
- <Tab active={selected} onClick={() => setSelected(true)}>on</Tab>
347
- </div>
337
+ <SegmentedControl size="sm" aria-label="selected" value={selected ? 'on' : 'off'} onValueChange={(v) => setSelected(v === 'on')}>
338
+ <SegmentedControlItem value="off">off</SegmentedControlItem>
339
+ <SegmentedControlItem value="on">on</SegmentedControlItem>
340
+ </SegmentedControl>
348
341
  </div>
349
342
  <div className="flex items-center gap-2">
350
343
  <span className="text-[11px] text-fg-muted w-20 shrink-0">disabled</span>
351
- <div className="flex gap-1.5">
352
- <Tab active={!disabled} onClick={() => setDisabled(false)}>off</Tab>
353
- <Tab active={disabled} onClick={() => setDisabled(true)}>on</Tab>
354
- </div>
344
+ <SegmentedControl size="sm" aria-label="disabled" value={disabled ? 'on' : 'off'} onValueChange={(v) => setDisabled(v === 'on')}>
345
+ <SegmentedControlItem value="off">off</SegmentedControlItem>
346
+ <SegmentedControlItem value="on">on</SegmentedControlItem>
347
+ </SegmentedControl>
355
348
  </div>
356
349
  <div className="flex items-center gap-2">
357
350
  <span className="text-[11px] text-fg-muted w-20 shrink-0">startIcon</span>
358
- <div className="flex gap-1.5">
359
- <Tab active={!hasIcon} onClick={() => setHasIcon(false)}>off</Tab>
360
- <Tab active={hasIcon} onClick={() => setHasIcon(true)} disabled={hasAvatar}>on</Tab>
361
- </div>
351
+ {/* 停用項用元件自身 disabled(segmented-control.spec.md「disabled」);icon / avatar 互斥,停用的一側恆為 off,不會是當前值 */}
352
+ <SegmentedControl size="sm" aria-label="startIcon" value={hasIcon ? 'on' : 'off'} onValueChange={(v) => setHasIcon(v === 'on')}>
353
+ <SegmentedControlItem value="off">off</SegmentedControlItem>
354
+ <SegmentedControlItem value="on" disabled={hasAvatar}>on</SegmentedControlItem>
355
+ </SegmentedControl>
362
356
  </div>
363
357
  <div className="flex items-center gap-2">
364
358
  <span className="text-[11px] text-fg-muted w-20 shrink-0">avatar</span>
365
- <div className="flex gap-1.5">
366
- <Tab active={!hasAvatar} onClick={() => setHasAvatar(false)}>off</Tab>
367
- <Tab active={hasAvatar} onClick={() => setHasAvatar(true)} disabled={hasIcon}>on</Tab>
368
- </div>
359
+ <SegmentedControl size="sm" aria-label="avatar" value={hasAvatar ? 'on' : 'off'} onValueChange={(v) => setHasAvatar(v === 'on')}>
360
+ <SegmentedControlItem value="off">off</SegmentedControlItem>
361
+ <SegmentedControlItem value="on" disabled={hasIcon}>on</SegmentedControlItem>
362
+ </SegmentedControl>
369
363
  </div>
370
364
  <div className="flex items-center gap-2">
371
365
  <span className="text-[11px] text-fg-muted w-20 shrink-0">description</span>
372
- <div className="flex gap-1.5">
373
- <Tab active={!hasDesc} onClick={() => setHasDesc(false)}>off</Tab>
374
- <Tab active={hasDesc} onClick={() => setHasDesc(true)}>on</Tab>
375
- </div>
366
+ <SegmentedControl size="sm" aria-label="description" value={hasDesc ? 'on' : 'off'} onValueChange={(v) => setHasDesc(v === 'on')}>
367
+ <SegmentedControlItem value="off">off</SegmentedControlItem>
368
+ <SegmentedControlItem value="on">on</SegmentedControlItem>
369
+ </SegmentedControl>
376
370
  </div>
377
371
  <div className="flex items-center gap-2">
378
372
  <span className="text-[11px] text-fg-muted w-20 shrink-0">tag</span>
379
- <div className="flex gap-1.5">
380
- <Tab active={!hasTag} onClick={() => setHasTag(false)}>off</Tab>
381
- <Tab active={hasTag} onClick={() => setHasTag(true)}>on</Tab>
382
- </div>
373
+ <SegmentedControl size="sm" aria-label="tag" value={hasTag ? 'on' : 'off'} onValueChange={(v) => setHasTag(v === 'on')}>
374
+ <SegmentedControlItem value="off">off</SegmentedControlItem>
375
+ <SegmentedControlItem value="on">on</SegmentedControlItem>
376
+ </SegmentedControl>
383
377
  </div>
384
378
  </div>
385
379
 
@@ -390,7 +384,7 @@ const InspectorInner = () => {
390
384
  <div className="px-4 py-6 rounded-lg bg-canvas border border-divider flex items-center justify-center">
391
385
  <MenuContainer width={340}>
392
386
  <MenuGroup>
393
- <MenuItem
387
+ <MenuItem role="presentation"
394
388
  size={size}
395
389
  startIcon={hasIcon ? Mail : undefined}
396
390
  avatar={hasAvatar ? { alt: "Alice", color: "indigo" as const } : undefined}
@@ -567,7 +561,7 @@ export const ColorMatrix = {
567
561
  <td className="p-3 border-b border-divider align-top min-w-[280px]">
568
562
  <MenuContainer width={260}>
569
563
  <MenuGroup>
570
- <MenuItem
564
+ <MenuItem role="presentation"
571
565
  size="md"
572
566
  startIcon={Mail}
573
567
  selected={st === 'selected'}
@@ -602,7 +596,7 @@ export const ColorMatrix = {
602
596
  <td className="p-3 border-b border-divider align-top min-w-[280px]">
603
597
  <MenuContainer width={260}>
604
598
  <MenuGroup>
605
- <MenuItem
599
+ <MenuItem role="presentation"
606
600
  size="md"
607
601
  startIcon={Mail}
608
602
  checkbox
@@ -631,8 +625,8 @@ export const ColorMatrix = {
631
625
  <div className="flex items-start gap-4">
632
626
  <MenuContainer width={260}>
633
627
  <MenuGroup>
634
- <MenuItem size="md" header>群組標題</MenuItem>
635
- <MenuItem size="md" startIcon={FileText}>一般選項</MenuItem>
628
+ <MenuItem role="presentation" size="md" header>群組標題</MenuItem>
629
+ <MenuItem role="presentation" size="md" startIcon={FileText}>一般選項</MenuItem>
636
630
  </MenuGroup>
637
631
  </MenuContainer>
638
632
  <div className="flex flex-col gap-0.5">
@@ -742,19 +736,19 @@ export const SizeMatrix = {
742
736
  <span className="text-caption text-fg-muted font-mono">{sz}{sz === 'md' ? '(預設)' : ''}</span>
743
737
  <MenuContainer width={380}>
744
738
  <MenuGroup>
745
- <MenuItem size={sz} startIcon={Mail} description="每日摘要信件">
739
+ <MenuItem role="presentation" size={sz} startIcon={Mail} description="每日摘要信件">
746
740
  電子郵件通知
747
741
  </MenuItem>
748
- <MenuItem size={sz} avatar={{ src: "https://i.pravatar.cc/48?u=alice-chen", alt: "Alice", color: "indigo" as const }}>
742
+ <MenuItem role="presentation" size={sz} avatar={{ src: "https://i.pravatar.cc/48?u=alice-chen", alt: "Alice", color: "indigo" as const }}>
749
743
  Alice Chen
750
744
  </MenuItem>
751
- <MenuItem size={sz} avatar={{ src: "https://i.pravatar.cc/48?u=bob-wang", alt: "Bob", color: "magenta" as const }} description="工程部門">
745
+ <MenuItem role="presentation" size={sz} avatar={{ src: "https://i.pravatar.cc/48?u=bob-wang", alt: "Bob", color: "magenta" as const }} description="工程部門">
752
746
  Bob Wang
753
747
  </MenuItem>
754
- <MenuItem size={sz} checkbox checked={true} startIcon={Bell}>
748
+ <MenuItem role="presentation" size={sz} checkbox checked={true} startIcon={Bell}>
755
749
  推播通知
756
750
  </MenuItem>
757
- <MenuItem size={sz} tag={<Tag size={sz} color="neutral">Admin</Tag>}>
751
+ <MenuItem role="presentation" size={sz} tag={<Tag size={sz} color="neutral">Admin</Tag>}>
758
752
  權限標記
759
753
  </MenuItem>
760
754
  </MenuGroup>
@@ -774,7 +768,7 @@ export const Accessibility = {
774
768
  render: () => (
775
769
  <div className="max-w-3xl text-body text-fg-secondary">
776
770
  <h3 className="text-h5 text-foreground mb-2">無障礙設計</h3>
777
- <p className="whitespace-pre-line">{"MenuItem 只負責單行的視覺排版,本身不接管鍵盤與焦點。真正的鍵盤導覽(上下鍵切換、Enter 選取、Esc 關閉)與焦點管理由外層的選單元件(SelectMenu / DropdownMenu)負責——MenuItem 不重複實作這些行為。\n\n 語意 role :直接使用時(如本元件 stories 的 listbox demo)MenuItem 預設渲染為 role=\"option\" 並帶 aria-selected / aria-disabled;巢狀在外層互動節點內時(SelectMenu / Combobox 的 cmdk option、DropdownMenu 的 Radix menuitem)外層會傳 role=\"presentation\",MenuItem 同步抑制 aria-selected / aria-disabled——ARIA 語意由外層節點單一持有,內層僅保留 data-* 樣式屬性。哪一項目前被聚焦與選取由外層選單決定。\n\n 焦點外觀 :聚焦時以 bg-neutral-hover 背景高亮標示被聚焦的選項(cva base 為 outline-none + focus-visible:bg-neutral-hover),對齊 menu/listbox option 的 active-highlight 慣例,而非畫 outline ring。\n\n 驗證 :Storybook a11y addon 面板應 0 critical violation;整個選單用鍵盤即可完整操作(無需滑鼠);文字對比 ≥ 4.5:1、介面元素對比 ≥ 3:1(WCAG AA)。"}</p>
771
+ <p className="whitespace-pre-line">{"MenuItem 只負責單行的視覺排版,本身不接管鍵盤與焦點。真正的鍵盤導覽(上下鍵切換、Enter 選取、Esc 關閉)與焦點管理由外層的選單元件(SelectMenu / DropdownMenu)負責——MenuItem 不重複實作這些行為。\n\n 語意 role :直接使用時 MenuItem 預設渲染為 role=\"option\" 並帶 aria-selected / aria-disabled——但那要求外層真的是一個有方向鍵、有焦點管理的 listbox;本元件 stories 的預覽容器並不是(2026-09-24 起容器為 role=\"group\"、每個 MenuItem 傳 role=\"presentation\",不假裝是選單);巢狀在外層互動節點內時(SelectMenu / Combobox 的 cmdk option、DropdownMenu 的 Radix menuitem)外層會傳 role=\"presentation\",MenuItem 同步抑制 aria-selected / aria-disabled——ARIA 語意由外層節點單一持有,內層僅保留 data-* 樣式屬性。哪一項目前被聚焦與選取由外層選單決定。\n\n 焦點外觀 :聚焦時以 bg-neutral-hover 背景高亮標示被聚焦的選項(cva base 為 outline-none + focus-visible:bg-neutral-hover),對齊 menu/listbox option 的 active-highlight 慣例,而非畫 outline ring。\n\n 驗證 :Storybook a11y addon 面板應 0 critical violation;整個選單用鍵盤即可完整操作(無需滑鼠);文字對比 ≥ 4.5:1、介面元素對比 ≥ 3:1(WCAG AA)。"}</p>
778
772
  </div>
779
773
  ),
780
774
  }
@@ -251,7 +251,7 @@ Menu item 的 prefix icon 跟 label 同色(foreground),不是 fg-muted。P
251
251
  | 建立操作選單(複製 / 刪除 / 分享)| 用 `DropdownMenu` + `DropdownMenuItem`(內部消費 MenuItem)|
252
252
  | 建立選值下拉(選項 + 搜尋)| 用 `SelectMenu`(內部消費 MenuItem)|
253
253
  | 建立右鍵選單 | 用未來的 `ContextMenu`(待開發)|
254
- | 直接在 JSX 中用 `<MenuItem>` | ❌ **禁止**——會失去 menu 外層的 Radix 無障礙 / 鍵盤 / 焦點管理。唯一例外:本元件**自身 stories** 的 internal 展示(demo 容器以 `role="listbox"` 滿足 a11y parent 要求,見 `menu-item.stories.tsx`),產品 code 無豁免 |
254
+ | 直接在 JSX 中用 `<MenuItem>` | ❌ **禁止**——會失去 menu 外層的 Radix 無障礙 / 鍵盤 / 焦點管理。唯一例外:本元件與 Avatar / Separator / item-anatomy 的**自身 stories** 靜態視覺預覽;該預覽**不得**假裝成選單——容器用 `role="group"`、每個 MenuItem 傳 `role="presentation"`(2026-09-24 訂正,原本容器掛 `role="listbox"` 只為消 axe `aria-required-parent`,但那個容器沒有選取狀態、沒有方向鍵、沒有焦點管理 = 對輔助科技的空頭承諾,SSOT `ds-canonical/references/keyboard-model-canonical.md`「鐵律」)。產品 code 無豁免 |
255
255
 
256
256
  ### 消費者
257
257
 
@@ -37,9 +37,15 @@ const meta: Meta<typeof MenuItem> = {
37
37
  export default meta
38
38
  type Story = StoryObj<typeof MenuItem>
39
39
 
40
- /** Menu 容器 — 模擬浮層外觀。demo 用 role='listbox' 滿足 MenuItem role='option' 的 parent 要求(axe aria-required-parent);
41
- * 只放訊息列(非 option)的 demo 改 role='group' —— listbox 裡不得只有非 option 子元素(axe aria-required-children,2026-09-08)。 */
42
- const MenuContainer = ({ children, width = 320, role = 'listbox' }: { children: React.ReactNode; width?: number; role?: 'listbox' | 'group' }) => (
40
+ /** Menu 容器 — 模擬浮層外觀的**靜態視覺預覽**,不是真的選單。
41
+ *
42
+ * 2026-09-24:預設 role 從 `'listbox'` 改 `'group'`,裡面的 MenuItem 一律傳 `role="presentation"`。
43
+ * 原本掛 listbox 的理由只是「滿足 MenuItem role='option' 的 parent 要求(axe aria-required-parent)」——
44
+ * 那是拿角色去消 lint,不是描述它真的是什麼:這個容器沒有選取狀態、沒有方向鍵、沒有焦點管理,
45
+ * `listbox` 對螢幕閱讀器是空頭承諾(SSOT:`ds-canonical/references/keyboard-model-canonical.md`「鐵律」)。
46
+ * 兩邊一起拿掉就同時消滅了孤兒 option 與假 composite,axe 仍 0 ARIA violation(2026-09-24 實跑)。
47
+ * `role` prop 保留是為了讓個別 demo 還能顯式標注,但沒有任何 demo 該回去用 listbox。 */
48
+ const MenuContainer = ({ children, width = 320, role = 'group' }: { children: React.ReactNode; width?: number; role?: 'listbox' | 'group' }) => (
43
49
  <div role={role} aria-label="Menu demo items" className="rounded-lg bg-surface-raised border border-border overflow-hidden"
44
50
  style={{ boxShadow: 'var(--elevation-200)', width }}>
45
51
  {children}
@@ -52,9 +58,9 @@ export const Default: Story = {
52
58
  name: '基本',
53
59
  render: () => (
54
60
  <MenuContainer><MenuGroup>
55
- <MenuItem>收件匣</MenuItem>
56
- <MenuItem>草稿</MenuItem>
57
- <MenuItem>已傳送</MenuItem>
61
+ <MenuItem role="presentation">收件匣</MenuItem>
62
+ <MenuItem role="presentation">草稿</MenuItem>
63
+ <MenuItem role="presentation">已傳送</MenuItem>
58
64
  </MenuGroup></MenuContainer>
59
65
  ),
60
66
  }
@@ -70,9 +76,9 @@ export const WithDescription: Story = {
70
76
  name: '前置圖示 + 說明文字',
71
77
  render: () => (
72
78
  <MenuContainer><MenuGroup>
73
- <MenuItem startIcon={Mail} description="每日摘要信件">電子郵件通知</MenuItem>
74
- <MenuItem startIcon={Bell} description="即時推播到裝置">推播通知</MenuItem>
75
- <MenuItem startIcon={Settings} description="自訂通知偏好">進階設定</MenuItem>
79
+ <MenuItem role="presentation" startIcon={Mail} description="每日摘要信件">電子郵件通知</MenuItem>
80
+ <MenuItem role="presentation" startIcon={Bell} description="即時推播到裝置">推播通知</MenuItem>
81
+ <MenuItem role="presentation" startIcon={Settings} description="自訂通知偏好">進階設定</MenuItem>
76
82
  </MenuGroup></MenuContainer>
77
83
  ),
78
84
  }
@@ -83,9 +89,9 @@ export const AvatarInline: Story = {
83
89
  name: '頭像',
84
90
  render: () => (
85
91
  <MenuContainer><MenuGroup>
86
- <MenuItem avatar={{ src: avatarSrc('Alice Chen', 48), alt: "Alice Chen", color: "indigo", hoverCard: personHover('Alice Chen') }}>Alice Chen</MenuItem>
87
- <MenuItem avatar={{ src: avatarSrc('Bob Wang', 48), alt: "Bob Wang", color: "magenta", hoverCard: personHover('Bob Wang') }}>Bob Wang</MenuItem>
88
- <MenuItem avatar={{ src: avatarSrc('Carol Lin', 48), alt: "Carol Lin", color: "green", hoverCard: personHover('Carol Lin') }}>Carol Lin</MenuItem>
92
+ <MenuItem role="presentation" avatar={{ src: avatarSrc('Alice Chen', 48), alt: "Alice Chen", color: "indigo", hoverCard: personHover('Alice Chen') }}>Alice Chen</MenuItem>
93
+ <MenuItem role="presentation" avatar={{ src: avatarSrc('Bob Wang', 48), alt: "Bob Wang", color: "magenta", hoverCard: personHover('Bob Wang') }}>Bob Wang</MenuItem>
94
+ <MenuItem role="presentation" avatar={{ src: avatarSrc('Carol Lin', 48), alt: "Carol Lin", color: "green", hoverCard: personHover('Carol Lin') }}>Carol Lin</MenuItem>
89
95
  </MenuGroup></MenuContainer>
90
96
  ),
91
97
  }
@@ -96,9 +102,9 @@ export const AvatarBlock: Story = {
96
102
  name: '頭像 + 說明文字',
97
103
  render: () => (
98
104
  <MenuContainer><MenuGroup>
99
- <MenuItem avatar={{ src: avatarSrc('Alice Chen', 64), alt: "Alice Chen", color: "indigo", hoverCard: personHover('Alice Chen', '設計部門') }} description="設計部門">Alice Chen</MenuItem>
100
- <MenuItem avatar={{ src: avatarSrc('Bob Wang', 64), alt: "Bob Wang", color: "magenta", hoverCard: personHover('Bob Wang', '工程部門') }} description="工程部門">Bob Wang</MenuItem>
101
- <MenuItem avatar={{ src: avatarSrc('Carol Lin', 64), alt: "Carol Lin", color: "green", hoverCard: personHover('Carol Lin', '行銷部門') }} description="行銷部門">Carol Lin</MenuItem>
105
+ <MenuItem role="presentation" avatar={{ src: avatarSrc('Alice Chen', 64), alt: "Alice Chen", color: "indigo", hoverCard: personHover('Alice Chen', '設計部門') }} description="設計部門">Alice Chen</MenuItem>
106
+ <MenuItem role="presentation" avatar={{ src: avatarSrc('Bob Wang', 64), alt: "Bob Wang", color: "magenta", hoverCard: personHover('Bob Wang', '工程部門') }} description="工程部門">Bob Wang</MenuItem>
107
+ <MenuItem role="presentation" avatar={{ src: avatarSrc('Carol Lin', 64), alt: "Carol Lin", color: "green", hoverCard: personHover('Carol Lin', '行銷部門') }} description="行銷部門">Carol Lin</MenuItem>
102
108
  </MenuGroup></MenuContainer>
103
109
  ),
104
110
  }
@@ -119,10 +125,10 @@ export const WithStartContent: Story = {
119
125
  // LucideIcon 表達不了 → startContent;selected = 目前編輯中的類型
120
126
  <MenuContainer width={224}>
121
127
  <MenuGroup>
122
- <MenuItem size="sm" startContent={<TypeBadge hue="indigo" glyph={FileText} />} selected>Task</MenuItem>
123
- <MenuItem size="sm" startContent={<TypeBadge hue="red" glyph={Bug} />}>Bug</MenuItem>
124
- <MenuItem size="sm" startContent={<TypeBadge hue="turquoise" glyph={Rocket} />}>Release</MenuItem>
125
- <MenuItem size="sm" startContent={<TypeBadge hue="purple" glyph={FileText} />} disabled>Epic(已停用)</MenuItem>
128
+ <MenuItem role="presentation" size="sm" startContent={<TypeBadge hue="indigo" glyph={FileText} />} selected>Task</MenuItem>
129
+ <MenuItem role="presentation" size="sm" startContent={<TypeBadge hue="red" glyph={Bug} />}>Bug</MenuItem>
130
+ <MenuItem role="presentation" size="sm" startContent={<TypeBadge hue="turquoise" glyph={Rocket} />}>Release</MenuItem>
131
+ <MenuItem role="presentation" size="sm" startContent={<TypeBadge hue="purple" glyph={FileText} />} disabled>Epic(已停用)</MenuItem>
126
132
  </MenuGroup>
127
133
  </MenuContainer>
128
134
  ),
@@ -134,9 +140,9 @@ export const States: Story = {
134
140
  name: '狀態',
135
141
  render: () => (
136
142
  <MenuContainer><MenuGroup>
137
- <MenuItem startIcon={Mail}>收件匣</MenuItem>
138
- <MenuItem startIcon={Mail} selected>已星標(單選)</MenuItem>
139
- <MenuItem startIcon={Mail} disabled>已封存</MenuItem>
143
+ <MenuItem role="presentation" startIcon={Mail}>收件匣</MenuItem>
144
+ <MenuItem role="presentation" startIcon={Mail} selected>已星標(單選)</MenuItem>
145
+ <MenuItem role="presentation" startIcon={Mail} disabled>已封存</MenuItem>
140
146
  </MenuGroup></MenuContainer>
141
147
  ),
142
148
  }
@@ -159,13 +165,13 @@ const MultiSelectDemo = () => {
159
165
  return (
160
166
  <>
161
167
  <MenuGroup>
162
- <MenuItem checkbox checked={selected.email} startIcon={Mail} onClick={() => toggle('email')}>電子郵件</MenuItem>
163
- <MenuItem checkbox checked={selected.push} startIcon={Bell} onClick={() => toggle('push')}>推播通知</MenuItem>
164
- <MenuItem checkbox checked={selected.slack} startIcon={Settings} onClick={() => toggle('slack')}>Slack</MenuItem>
165
- <MenuItem checkbox checked={false} disabled startIcon={Star}>SMS(已停用)</MenuItem>
168
+ <MenuItem role="presentation" checkbox checked={selected.email} startIcon={Mail} onClick={() => toggle('email')}>電子郵件</MenuItem>
169
+ <MenuItem role="presentation" checkbox checked={selected.push} startIcon={Bell} onClick={() => toggle('push')}>推播通知</MenuItem>
170
+ <MenuItem role="presentation" checkbox checked={selected.slack} startIcon={Settings} onClick={() => toggle('slack')}>Slack</MenuItem>
171
+ <MenuItem role="presentation" checkbox checked={false} disabled startIcon={Star}>SMS(已停用)</MenuItem>
166
172
  </MenuGroup>
167
173
  <MenuFooter>
168
- <MenuItem checkbox checked={allState} onClick={toggleAll}>全部</MenuItem>
174
+ <MenuItem role="presentation" checkbox checked={allState} onClick={toggleAll}>全部</MenuItem>
169
175
  </MenuFooter>
170
176
  </>
171
177
  )
@@ -183,15 +189,15 @@ export const Groups: Story = {
183
189
  render: () => (
184
190
  <MenuContainer>
185
191
  <MenuGroup>
186
- <MenuItem header>最近使用</MenuItem>
187
- <MenuItem startIcon={FileText}>Q3 產品路線圖.md</MenuItem>
188
- <MenuItem startIcon={FileText}>客戶反饋整理</MenuItem>
192
+ <MenuItem role="presentation" header>最近使用</MenuItem>
193
+ <MenuItem role="presentation" startIcon={FileText}>Q3 產品路線圖.md</MenuItem>
194
+ <MenuItem role="presentation" startIcon={FileText}>客戶反饋整理</MenuItem>
189
195
  </MenuGroup>
190
196
  <MenuGroup>
191
- <MenuItem header>所有專案</MenuItem>
192
- <MenuItem startIcon={Folder}>設計系統升級 v2</MenuItem>
193
- <MenuItem startIcon={Folder}>行銷活動 2026</MenuItem>
194
- <MenuItem startIcon={BarChart3}>業務儀表板</MenuItem>
197
+ <MenuItem role="presentation" header>所有專案</MenuItem>
198
+ <MenuItem role="presentation" startIcon={Folder}>設計系統升級 v2</MenuItem>
199
+ <MenuItem role="presentation" startIcon={Folder}>行銷活動 2026</MenuItem>
200
+ <MenuItem role="presentation" startIcon={BarChart3}>業務儀表板</MenuItem>
195
201
  </MenuGroup>
196
202
  </MenuContainer>
197
203
  ),
@@ -206,10 +212,10 @@ export const Messages: Story = {
206
212
  // 前綴槽放列圖示尺寸(md = ICON_SIZE.md)的轉圈,文字仍可見。
207
213
  <div className="flex gap-4">
208
214
  <MenuContainer width={240} role="group"><MenuGroup>
209
- <MenuItem message>沒有人員</MenuItem>
215
+ <MenuItem role="presentation" message>沒有人員</MenuItem>
210
216
  </MenuGroup></MenuContainer>
211
217
  <MenuContainer width={240} role="group"><MenuGroup>
212
- <MenuItem message startContent={<CircularProgress size={ICON_SIZE.md} />}>載入選項中</MenuItem>
218
+ <MenuItem role="presentation" message startContent={<CircularProgress size={ICON_SIZE.md} />}>載入選項中</MenuItem>
213
219
  </MenuGroup></MenuContainer>
214
220
  </div>
215
221
  ),
@@ -221,7 +227,7 @@ export const Creatable: Story = {
221
227
  name: '可建立新項',
222
228
  render: () => (
223
229
  <MenuContainer><MenuGroup>
224
- <MenuItem startIcon={Plus}>直接使用「新標籤」</MenuItem>
230
+ <MenuItem role="presentation" startIcon={Plus}>直接使用「新標籤」</MenuItem>
225
231
  </MenuGroup></MenuContainer>
226
232
  ),
227
233
  }
@@ -245,13 +251,13 @@ const FullExampleDemo = () => {
245
251
  return (
246
252
  <MenuContainer>
247
253
  <MenuGroup>
248
- <MenuItem header>成員</MenuItem>
249
- <MenuItem checkbox checked={selected.alice} onClick={() => toggle('alice')} avatar={{ src: avatarSrc('Alice Chen', 64), alt: "Alice", color: "indigo" }} description="設計部門">Alice Chen</MenuItem>
250
- <MenuItem checkbox checked={selected.bob} onClick={() => toggle('bob')} avatar={{ src: avatarSrc('Bob Wang', 64), alt: "Bob", color: "magenta" }} description="工程部門">Bob Wang</MenuItem>
251
- <MenuItem checkbox checked={false} avatar={{ src: avatarSrc('Carol Lin', 64), alt: "Carol", color: "green" }} description="行銷部門" disabled>Carol Lin(已離職)</MenuItem>
254
+ <MenuItem role="presentation" header>成員</MenuItem>
255
+ <MenuItem role="presentation" checkbox checked={selected.alice} onClick={() => toggle('alice')} avatar={{ src: avatarSrc('Alice Chen', 64), alt: "Alice", color: "indigo" }} description="設計部門">Alice Chen</MenuItem>
256
+ <MenuItem role="presentation" checkbox checked={selected.bob} onClick={() => toggle('bob')} avatar={{ src: avatarSrc('Bob Wang', 64), alt: "Bob", color: "magenta" }} description="工程部門">Bob Wang</MenuItem>
257
+ <MenuItem role="presentation" checkbox checked={false} avatar={{ src: avatarSrc('Carol Lin', 64), alt: "Carol", color: "green" }} description="行銷部門" disabled>Carol Lin(已離職)</MenuItem>
252
258
  </MenuGroup>
253
259
  <MenuFooter>
254
- <MenuItem checkbox checked={allState} onClick={toggleAll}>全部</MenuItem>
260
+ <MenuItem role="presentation" checkbox checked={allState} onClick={toggleAll}>全部</MenuItem>
255
261
  </MenuFooter>
256
262
  </MenuContainer>
257
263
  )
@@ -20,6 +20,7 @@ import {
20
20
  } from 'lucide-react'
21
21
  import { Notice, SUBTLE_ICON_COLOR, type NoticeVariant } from './notice'
22
22
  import { Button } from '@/design-system/components/Button/button'
23
+ import { Chip, ChipGroup } from '@/design-system/components/Chip/chip'
23
24
  import { H3, Desc, Td, Th, Swatch } from '@/design-system/stories-helpers/anatomy/anatomy-utils'
24
25
 
25
26
  const meta: Meta = {
@@ -277,22 +278,13 @@ function NoticeInspector() {
277
278
  <div className="flex flex-col gap-6">
278
279
  <div>
279
280
  <H3>Variant</H3>
280
- <div className="flex flex-wrap gap-2">
281
- {VARIANTS.map((v) => (
282
- <button
283
- key={v}
284
- type="button"
285
- onClick={() => setVariant(v)}
286
- className={`px-2.5 py-1 text-caption rounded-md font-mono cursor-pointer ${
287
- v === variant
288
- ? 'bg-primary text-white'
289
- : 'bg-neutral-hover text-fg-secondary hover:bg-neutral-active'
290
- }`}
291
- >
292
- {v}
293
- </button>
294
- ))}
295
- </div>
281
+ {/* 5 個互斥選項放在 320px 右欄:SegmentedControl 連體不換行、放不下(segmented-control.spec.md「不支援 overflow…
282
+ 若選項可能超出容器寬度,代表選錯元件了」)→ ChipGroup 單選 wrap(chip.spec.md「與 SegmentedControl 的差異」)。
283
+ 取代手刻 pill:它靜止借 neutral-hover、hover 借 neutral-active(color.spec.md 成對 token 錯配)。
284
+ Radix 單選再點已選項回傳空字串 → 忽略,維持恆有一值 */}
285
+ <ChipGroup type="single" aria-label="Variant" value={variant} onValueChange={(v) => { if (v) setVariant(v as NoticeVariant) }}>
286
+ {VARIANTS.map((v) => <Chip key={v} value={v}>{v}</Chip>)}
287
+ </ChipGroup>
296
288
  </div>
297
289
 
298
290
  <div>
@@ -155,7 +155,7 @@ Notice 是 **Toast / Alert 共用的 layout primitive**,刻意不擁有尺寸與
155
155
 
156
156
  Notice **不**自帶 Esc-to-dismiss 行為(`notice.tsx` 無 keydown handler);dismiss 純粹由 dismiss button 的 `onClick={onDismiss}` 觸發。若 consumer(Alert / Toast)需要 Esc 關閉,於 consumer 層自行掛 keydown。Dismiss 後的焦點處置 Notice 同樣不管理(無 focus restoration 邏輯)——節點移除後焦點落點由 consumer(Alert / Toast host)決定。
157
157
 
158
- **Focus**:Notice 自身渲染的唯一 focusable 元素是 dismiss `<Button>`(consumer 經 `endContent` 注入的互動元素——如上方建議的 tertiary Button——由 consumer 自管 a11y),focus indicator 走 Button canonical(`focus-visible:ring-2 ring-ring`,box-shadow ring 非 CSS `outline`);Notice 本身無 focus management(dismiss 後焦點處置見上段,由 consumer 決定)。
158
+ **Focus**:Notice 自身渲染的唯一 focusable 元素是 dismiss `<Button>`(consumer 經 `endContent` 注入的互動元素——如上方建議的 tertiary Button——由 consumer 自管 a11y),focus indicator 走 Button canonical(全域 `:focus-visible` 外描邊 `outline: 2px solid var(--ring)`,往外 2px;Button 與 Notice 都不寫任何 focus class);Notice 本身無 focus management(dismiss 後焦點處置見上段,由 consumer 決定)。
159
159
 
160
160
  **驗證**:Storybook a11y addon panel 應 0 critical violation;鍵盤完整可操作(無需滑鼠)。WCAG AA contrast ≥ 4.5:1(text)/ 3:1(UI)。
161
161
 
@@ -153,7 +153,7 @@ const Notice = React.forwardRef<HTMLDivElement, NoticeProps>(
153
153
  // 已移除)。item-anatomy.spec.md 把 Notice 的 action/dismiss 對應到 suffix slot;ItemSuffix
154
154
  // base geometry(h-[1lh] shrink-0 ml-auto flex items-center gap-2)正是此處所需,hoverReveal
155
155
  // 預設 false 故無 row inline-action 機制干擾。內裝 dismiss = Button iconOnly dismiss xs(banner
156
- // family canonical,overlay-surface.spec.md「Chrome dismiss size canonical」)。
156
+ // family canonical,patterns/overlay-surface/overlay-chrome-sizing.spec.md「Chrome dismiss size canonical」)。
157
157
  <ItemSuffix>
158
158
  {endContent}
159
159
  {/* 誤觸保護分隔線由 DS 放,consumer 零自刻(同 Dialog 先例 dialog.tsx:184-193)。