@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
@@ -12,7 +12,7 @@ const meta: Meta<typeof Rating> = {
12
12
  docs: {
13
13
  description: {
14
14
  component:
15
- '用於讓使用者給出離散星等,或唯讀顯示已提交的個人/平均評分。連續數值改用 Slider,二元喜好改用 Switch 或 icon button,完成進度改用 ProgressBar / CircularProgress。',
15
+ '讓使用者點一顆整星給分;已提交的個人或平均評分一律用唯讀精簡版「★ 4.7 (12,843)」顯示。連續數值改用 Slider,二元喜好改用 Switch 或 icon button,完成進度改用 ProgressBar / CircularProgress。',
16
16
  },
17
17
  },
18
18
  },
@@ -21,7 +21,7 @@ const meta: Meta<typeof Rating> = {
21
21
  export default meta
22
22
  type Story = StoryObj<typeof Rating>
23
23
 
24
- /* ── 商品列表的平均分(Amazon / Shopify 風格)── */
24
+ /* ── 商品列表的平均分(Amazon / Shopify 風格)——唯讀精簡版,評論數走 count ── */
25
25
  export const ReadOnlyProductRating: Story = {
26
26
  name: '商品列表平均分',
27
27
  render: () => (
@@ -35,17 +35,14 @@ export const ReadOnlyProductRating: Story = {
35
35
  <div key={p.name} className="flex items-center gap-3 p-3 border border-border rounded-md bg-surface">
36
36
  <div className="flex-1 min-w-0">
37
37
  <div className="text-body font-medium truncate">{p.name}</div>
38
- <div className="flex items-center gap-2 mt-1">
39
- <Rating
40
- value={p.rating}
41
- readOnly
42
- precision="half"
43
- size="xs"
44
- aria-label={`平均評分 ${p.rating} 星,共 5 星,${p.count} 則評論`}
45
- />
46
- <span className="text-caption text-fg-secondary">{p.rating}</span>
47
- <span className="text-caption text-fg-muted">({p.count.toLocaleString()})</span>
48
- </div>
38
+ <Rating
39
+ value={p.rating}
40
+ count={p.count}
41
+ readOnly
42
+ size="sm"
43
+ className="mt-1"
44
+ aria-label={`平均評分 ${p.rating} 星,共 5 星,${p.count.toLocaleString()} 則評論`}
45
+ />
49
46
  </div>
50
47
  </div>
51
48
  ))}
@@ -53,7 +50,7 @@ export const ReadOnlyProductRating: Story = {
53
50
  ),
54
51
  }
55
52
 
56
- /* ── 送出評分 flow(Yelp / Google Reviews 風格)── */
53
+ /* ── 送出評分 flow(Yelp / Google Reviews 風格)——只有整顆 ── */
57
54
  export const InteractiveReview: Story = {
58
55
  name: '送出評分流程',
59
56
  render: () => {
@@ -71,7 +68,6 @@ export const InteractiveReview: Story = {
71
68
  value={rating}
72
69
  onChange={setRating}
73
70
  size="lg"
74
- precision="full"
75
71
  aria-label="為這次服務給 1 到 5 星"
76
72
  />
77
73
  <span className="text-body text-fg-secondary min-w-[48px]">
@@ -3,6 +3,8 @@ import * as React from 'react'
3
3
  import { Star, type LucideIcon } from 'lucide-react'
4
4
  import { cn } from '@/lib/utils'
5
5
  import { useFieldContext, useResolvedFieldSize, useResolvedFieldDisabled, useResolvedFieldMode } from '@/design-system/components/Field/field-context'
6
+ import { fieldDisplayTextClass } from '@/design-system/components/Field/field-wrapper'
7
+ import { ICON_SIZE } from '@/design-system/tokens/uiSize/icon-size'
6
8
 
7
9
  /**
8
10
  * Rating — 星星評分元件
@@ -20,12 +22,10 @@ import { useFieldContext, useResolvedFieldSize, useResolvedFieldDisabled, useRes
20
22
  * 空色用 `var(--divider)`(neutral-4 借 divider semantic alias;灰色;與 disabled/empty 同級)。
21
23
  *
22
24
  * ── 互動 ──
23
- * interactive(預設):hover 預覽、click 設值、keyboard Left/Right 改值
24
- * readOnly:純顯示,不響應 hover / click
25
- *
26
- * ── 精度 ──
27
- * precision="full"(預設) — 整星(1, 2, 3, 4, 5)
28
- * precision="half" — 半星(0.5, 1, 1.5, 2, 2.5, ..., 5)
25
+ * interactive(預設):只有整顆星 —— hover 預覽、click 設值、keyboard ±1
26
+ * readOnly:一律精簡版「★ 4.7 (12,843)」—— 一顆實心星 + 數值 + 選填評論數,不畫五顆星
27
+ * (user 2026-09-26:「唯讀直接一律給精簡版就好吧?搞得這麼麻煩幹嘛?其他照你建議」;
28
+ * rating.spec.md「Interactive vs ReadOnly」)
29
29
  */
30
30
 
31
31
  // ── Icon size canonical(2026-04-21 AR48 修正)──
@@ -34,25 +34,18 @@ import { useFieldContext, useResolvedFieldSize, useResolvedFieldDisabled, useRes
34
34
  // 理由:
35
35
  // - 星星是 filled shape(解析整個 icon 是重量感的一部分),不像純 outline icon 靠 stroke
36
36
  // - Field 內 Rating 跟 Avatar / Tag 並排時視覺份量要對齊,否則 row height 一致但 icon 看起來比重量不對
37
- // - 世界級對照:Ant Rate in Form = 20px、Material MUI Rating fontSize=inherit 預設約 24、Airbnb 評分星 24px
38
37
  //
39
- // 因此 Field 內 Rating icon size 對齊 **item-anatomy inline Avatar sizes**:sm=20 / md=24 / lg=24。
38
+ // 因此可以點的星對齊 **item-anatomy inline Avatar sizes**:sm=20 / md=24 / lg=24。
40
39
  // 非 icon tier(16/16/20)——star 不是次要 affordance icon,它是主要資料視覺。
41
40
  //
42
- // Container 高度仍對齊 `--field-height-*`(sm=28 / md=32 / lg=36),讓 Rating 可與其他
43
- // field-height family 元件(Input / Select)並排時 row height 對齊。
44
- //
45
- // ── 使用情境 ──
46
- // - **Standalone**(獨立展示評分,如商品卡 / 評論)→ 預設 `xs`(container 24,icon 20,
47
- // 對齊 Avatar sm 20px;iOS HIG / Airbnb 商品卡星星 20-24px)
48
- // - **Field 內**(表單評分欄位)→ 跟 Field 尺寸對齊(sm=20 / md=24 / lg=24,default md)
49
- const SIZE_PX = { xs: 20, sm: 20, md: 24, lg: 24 } as const
50
- const CONTAINER_HEIGHT: Record<'xs' | 'sm' | 'md' | 'lg', string> = {
51
- xs: 'h-field-xs',
52
- sm: 'h-field-sm',
53
- md: 'h-field-md',
54
- lg: 'h-field-lg',
55
- }
41
+ // ── 高度模型(2026-09-27;user:「我反而覺得其跟 switch 和 checkbox 十分接近的邏輯」)──
42
+ // 元件只有內容高、不吃 `--field-height-*` 容器:可以點的評分根高 = 星高;唯讀精簡版 = 星與文字行高的較大者。
43
+ // 列高由所在的列供給 —— Field 控件槽 `min-h-field-* + items-center`(field.tsx)、表格格子、列的 items-center。
44
+ // 世界級六家(MUI / Ant / Fluent / Mantine / Chakra / PrimeReact)評分根容器都 hug 星、表單對齊交給表單列;
45
+ // 之前自己吃容器會把 Field 外的列撐高(商品卡 24 容器實佔 26px),對命中零幫助。出處與實量:rating.spec.md「Size」。
46
+ // xs 於同日退役(可以點的 xs 與 sm 只差容器;唯讀 xs 是 AI 自訂、無 user 同意、產品零處)。
47
+ type RatingSize = 'sm' | 'md' | 'lg'
48
+ const SIZE_PX: Record<RatingSize, number> = { sm: 20, md: 24, lg: 24 }
56
49
 
57
50
  export interface RatingProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'onChange'> {
58
51
  /** 當前評分(0 ~ max) */
@@ -63,12 +56,12 @@ export interface RatingProps extends Omit<React.HTMLAttributes<HTMLDivElement>,
63
56
  onChange?: (value: number) => void
64
57
  /** 滿分(預設 5) */
65
58
  max?: number
66
- /** 尺寸。standalone 建議 xs(24px);Field 內跟隨 Field size 傳 sm/md/lg */
67
- size?: 'xs' | 'sm' | 'md' | 'lg'
68
- /** 精度:full = 整星,half = 半星 */
69
- precision?: 'full' | 'half'
70
- /** 唯讀(無 hover / click 響應) */
59
+ /** 尺寸。預設 md、元件只有內容高(同 Switch / Checkbox);解析序同 Input 類:明傳 > Field 明確指定 > Field 預設 md > 表格 surface size > md。沒有 xs(2026-09-27 退役) */
60
+ size?: RatingSize
61
+ /** 唯讀:顯示精簡版「★ 數值 (評論數)」,不響應 hover / click / 鍵盤 */
71
62
  readOnly?: boolean
63
+ /** 唯讀時顯示在數值後的評論數(千分位);interactive 時不顯示 */
64
+ count?: number
72
65
  /** 完全停用 */
73
66
  disabled?: boolean
74
67
  /**
@@ -97,8 +90,8 @@ const Rating = React.forwardRef<HTMLDivElement, RatingProps>(
97
90
  onChange,
98
91
  max = 5,
99
92
  size: sizeProp,
100
- precision = 'full',
101
93
  readOnly: readOnlyProp = false,
94
+ count,
102
95
  disabled: disabledProp,
103
96
  loading = false,
104
97
  icon: Icon = Star,
@@ -107,11 +100,6 @@ const Rating = React.forwardRef<HTMLDivElement, RatingProps>(
107
100
  },
108
101
  ref,
109
102
  ) => {
110
- // Context-aware default size(AR31 canonical):
111
- // - Field 內(有 FieldContext.size) → 跟 Field size 對齊(sm / md / lg)
112
- // - Standalone(無 Field context) → default `xs`(24px,對齊 Avatar / Tag sm / iOS HIG standalone)
113
- // consumer 可傳 size 顯式 override。世界級對照:Material Rating standalone 24dp、
114
- // Ant Rate in Form 跟 Form.itemSize,standalone 24px。
115
103
  // 2026-07-18 決策4:max dev-warn(spec canonical ≤7;維持 number 型別不收窄,對齊 MUI Rating.max)。
116
104
  if (typeof process !== 'undefined' && process.env?.NODE_ENV !== 'production' && (max > 7 || max < 1 || Math.round(max) !== max)) {
117
105
  console.warn(`[DS] Rating max ${max} 超出 canonical 範圍 1-7(或非整數)。建議 ≤7 顆星(對齊世界級 review-stars)。`)
@@ -119,19 +107,71 @@ const Rating = React.forwardRef<HTMLDivElement, RatingProps>(
119
107
  const fieldCtx = useFieldContext() // 保留:aria-labelledby 用 fieldCtx.labelId
120
108
  // 2026-06-08 SSOT:<Field disabled> cascade(原 isInteractive 只看 local disabled prop)
121
109
  const disabled = useResolvedFieldDisabled(disabledProp)
122
- // <Field mode="readonly"> cascade(2026-06-12 補):Rating 的 readonly 呈現 = 星星本身
123
- // (星星即值語言,role=img,全業界 review-stars canonical)——不包灰框,只鎖互動。
110
+ // <Field mode="readonly"> cascade(2026-06-12 補):Rating 的 readonly 呈現 = 唯讀精簡版「★ 數值」
111
+ // (2026-09-26 起;role=img)——不包灰框,只鎖互動。
124
112
  const resolvedMode = useResolvedFieldMode({ mode: undefined, disabled, readOnly: readOnlyProp })
125
113
  const readOnly = readOnlyProp || resolvedMode === 'readonly'
126
- const size = useResolvedFieldSize<'xs' | 'sm' | 'md' | 'lg'>(sizeProp, 'xs') // SSOT:統一 size resolution(Rating default 'xs')
114
+ // 尺寸解析(field-context useResolvedFieldSize):prop > Field 明確指定的 size > Field 預設 md > 表格 surface size > md。
115
+ // 不傳 fallback:傳了會排在 Field 預設與 surface size 前面(那是 Switch / Checkbox 的 'md' 寫法,表格格子就接不到 surface size);
116
+ // Rating 在表格格子裡要跟表格(rating.spec.md「Size」:使用時機 = 跟所在容器)。
117
+ // 執行期傳 'xs'(型別已拒,JS consumer / 舊 d.ts 仍可能傳)→ 映到 sm 並 dev-warn(靜默映射 = 零訊號);閘 scripts/rating-field-size-invariant.mjs(meta test scripts/test-rating-field-size-invariant.mjs)。
118
+ const legacyXs = (sizeProp as string | undefined) === 'xs'
119
+ if (legacyXs && typeof process !== 'undefined' && process.env?.NODE_ENV !== 'production') {
120
+ console.warn('[DS] Rating size="xs" 已於 2026-09-27 退役(改為 sm;元件只有內容高,列高由所在的列供給)。')
121
+ }
122
+ const size = useResolvedFieldSize<RatingSize>(legacyXs ? 'sm' : sizeProp)
127
123
  const [internalValue, setInternalValue] = React.useState(defaultValue)
128
124
  const [hoverValue, setHoverValue] = React.useState<number | null>(null)
125
+ const valueTextId = React.useId()
129
126
  const isControlled = value !== undefined
130
127
  const currentValue = isControlled ? value : internalValue
131
- const displayValue = hoverValue ?? currentValue
128
+ // 可以點的評分只有整顆:外部給小數時四捨五入成整顆,畫面、鍵盤起點、讀屏念的值三者同一個數
129
+ // (否則 4.7 按 ← 會變 3.7、讀屏念 3.7 卻畫 4 顆)
130
+ const wholeValue = Math.max(0, Math.min(max, Math.round(currentValue)))
131
+ const displayValue = hoverValue ?? wholeValue
132
132
  const iconPx = SIZE_PX[size]
133
133
  const isInteractive = !readOnly && !disabled && !loading
134
134
 
135
+ // ── 唯讀 = 精簡版「★ 4.7 (12,843)」────────────────────────────────────────
136
+ // 世界級:Fluent 2 把唯讀拆成 RatingDisplay,`compact`「Renders a single filled star, with the value
137
+ // written next to it」、`count`「formatted with a thousands separator … displayed next to the value」
138
+ // (github.com/microsoft/fluentui react-rating `RatingDisplay.types.ts`,2026-09-26 讀 master)。
139
+ // 本 DS 不另拆元件:唯讀一律走這條,互動才畫五顆星(user 2026-09-26 選的是「唯讀一律精簡版」)。
140
+ // 星走 icon tier(16 / 16 / 20);星 ↔ 數值的縫三個尺寸都是 gap-1 4px —— Rating 內只有一種縫(可以點的星與星之間也是 4px),
141
+ // user 2026-09-27 拍板(原話「我覺得星號與文字gap應該跟這個一樣都是4px吧?」→「照你建議」);Fluent 2 RatingDisplay 16px 星 + 12px 字
142
+ // 亦是 4px、Tag 資料指示器圖示 ↔ 文字 4px。出處、實量與世界級對照見 rating.spec.md「唯讀精簡版」。
143
+ if (readOnly) {
144
+ const shown = Math.max(0, Math.min(max, currentValue))
145
+ return (
146
+ <div
147
+ ref={ref}
148
+ role="img"
149
+ data-size={size}
150
+ // Field 內:名稱 = 欄位標籤 + 數值(role=img 的子內容讀屏不念,只指標籤會只聽到「滿意度」聽不到分數);
151
+ // standalone 沒有 labelId → 走 consumer 的 aria-label(規格必填)
152
+ aria-labelledby={fieldCtx?.labelId ? `${fieldCtx.labelId} ${valueTextId}` : undefined}
153
+ aria-disabled={disabled || undefined}
154
+ aria-busy={loading || undefined}
155
+ className={cn(
156
+ // 不換行:星、數值、評論數是同一個值,窄容器裡折行會把整列撐高一倍(lg 實測 48px)。
157
+ // 高度 = 星與文字行高的較大者(sm/md 21、lg 24),沒有容器;星 ↔ 數值 gap-1(Rating 內只有一種縫)
158
+ 'inline-flex items-center whitespace-nowrap gap-1',
159
+ fieldDisplayTextClass(size),
160
+ (disabled || loading) && 'opacity-disabled pointer-events-none',
161
+ className,
162
+ )}
163
+ {...props}
164
+ >
165
+ <Icon size={size === 'lg' ? ICON_SIZE.lg : ICON_SIZE.sm} fill={FILL_FILLED} stroke="none" aria-hidden
166
+ className="shrink-0" style={{ color: FILL_FILLED }} />
167
+ <span id={valueTextId} aria-hidden className="text-foreground tabular-nums">
168
+ {formatRatingValue(shown)}
169
+ {count !== undefined && <span className="text-fg-secondary">{` (${count.toLocaleString()})`}</span>}
170
+ </span>
171
+ </div>
172
+ )
173
+ }
174
+
135
175
  const setValue = (v: number) => {
136
176
  if (!isControlled) setInternalValue(v)
137
177
  onChange?.(v)
@@ -139,14 +179,14 @@ const Rating = React.forwardRef<HTMLDivElement, RatingProps>(
139
179
 
140
180
  const handleKeyDown = (e: React.KeyboardEvent<HTMLDivElement>) => {
141
181
  if (!isInteractive) return
142
- const step = precision === 'half' ? 0.5 : 1
182
+ const step = 1 // 只有整顆(rating.spec.md「Interactive vs ReadOnly」)
143
183
  // Full ARIA slider pattern(WAI-ARIA):Arrow / Home / End 支援 — D4 UX audit 2026-04-22
144
184
  if (e.key === 'ArrowRight' || e.key === 'ArrowUp') {
145
185
  e.preventDefault()
146
- setValue(Math.min(max, currentValue + step))
186
+ setValue(Math.min(max, wholeValue + step))
147
187
  } else if (e.key === 'ArrowLeft' || e.key === 'ArrowDown') {
148
188
  e.preventDefault()
149
- setValue(Math.max(0, currentValue - step))
189
+ setValue(Math.max(0, wholeValue - step))
150
190
  } else if (e.key === 'Home') {
151
191
  e.preventDefault()
152
192
  setValue(0)
@@ -160,16 +200,18 @@ const Rating = React.forwardRef<HTMLDivElement, RatingProps>(
160
200
  <div
161
201
  ref={ref}
162
202
  role={isInteractive ? 'slider' : 'img'}
203
+ data-size={size}
163
204
  // a11y(#30):role=slider 必有 accessible name(WAI-ARIA APG slider pattern)。
164
205
  // Field 內 → 自動 aria-labelledby 指向 FieldLabel 的 id(fieldCtx.labelId,免填);
165
206
  // Standalone → 仍需 consumer 傳 aria-label。對齊 TimePicker / DatePicker 同 canonical
166
207
  // (time-picker.tsx:313 / date-picker.tsx:514:aria-labelledby={fieldCtx?.labelId})。
167
208
  // 置於 {...props} 前,consumer 顯式傳的 aria-labelledby 仍可覆寫。
168
209
  aria-labelledby={fieldCtx?.labelId} // 2026-06-12 修:readonly/disabled(role=img)也需 accessible name,labelledby 對 img 合法
169
- aria-valuenow={isInteractive ? currentValue : undefined}
210
+ aria-valuenow={isInteractive ? wholeValue : undefined}
170
211
  aria-valuemin={isInteractive ? 0 : undefined}
171
212
  aria-valuemax={isInteractive ? max : undefined}
172
- aria-valuetext={isInteractive ? `${currentValue} of ${max} stars` : undefined}
213
+ // 讀屏文字中文(全庫 SR label 中文,2026-07-04;同 spec 唯讀範例「平均評分 4.7 星,共 5 星」的說法)
214
+ aria-valuetext={isInteractive ? `${wholeValue} 星,共 ${max} 星` : undefined}
173
215
  aria-disabled={disabled || undefined}
174
216
  // a11y: 刻意不設 aria-readonly — readOnly 時 role=img(axe aria-allowed-attr 禁 img 用 aria-readonly,2026-04-25);
175
217
  // interactive 時 role=slider 但必非 readOnly(isInteractive = !readOnly)。兩 state 皆不該有此屬性,故省略。
@@ -177,40 +219,39 @@ const Rating = React.forwardRef<HTMLDivElement, RatingProps>(
177
219
  tabIndex={isInteractive ? 0 : undefined}
178
220
  onKeyDown={handleKeyDown}
179
221
  onMouseLeave={() => setHoverValue(null)}
222
+ // 星與星之間的縫(`gap-1`;元件只有內容高,沒有上下留白):指標從某顆星移進來時,那顆星的預覽照舊亮著
223
+ // (只有離開整個元件才收);亮著時在這裡點下去 = 確認正在預覽的值(AI 建議,2026-09-26 列在
224
+ // 「其餘建議」裡、user 未另提 → 照建議做,AI 判讀;hit-area-canonical.md 滑過原則一-6)。
225
+ // 點在星上由星自己處理(e.target 是星,不是容器),不會重複送出。
226
+ onClick={isInteractive ? (e) => {
227
+ if (e.target === e.currentTarget && hoverValue !== null) setValue(hoverValue)
228
+ } : undefined}
180
229
  className={cn(
181
- 'inline-flex items-center gap-1',
182
- // Container 對齊 field-height family,讓 Rating 可與 Input/Select/Button 並排 row-align
183
- CONTAINER_HEIGHT[size],
184
- 'rounded-md',
185
- // disabled 跟 loading 視覺相同(composite uniform dim),semantic 由 aria-disabled / aria-busy 區分
186
- (disabled || loading) && 'opacity-disabled pointer-events-none',
230
+ // 元件只有內容高(= 星高);列高由所在的列供給(見檔頭「高度模型」)。焦點框跟著這個內容盒(rounded-md)
231
+ 'inline-flex items-center gap-1 rounded-md',
232
+ // 預覽亮著時縫裡點得到 → 縫裡也是手形(與星同一個游標)
233
+ isInteractive && hoverValue !== null && 'cursor-pointer',
234
+ // disabled 跟 loading 視覺相同(composite uniform dim),semantic 由 aria-disabled / aria-busy 區分。
235
+ // 事件仍全部擋在星上(子元素 pointer-events-none;容器此時沒有任何 handler),容器自己留著接指標
236
+ // 只為了掛禁止符號 —— 容器也 pointer-events-none 的話游標由外層決定,永遠是箭頭
237
+ //(hit-area-canonical.md 滑過原則一-3「停用的用禁止符號」;field-controls.spec.md「游標指引」)。
238
+ (disabled || loading) && 'opacity-disabled cursor-not-allowed [&>*]:pointer-events-none',
187
239
  className,
188
240
  )}
189
241
  {...props}
190
242
  >
191
243
  {Array.from({ length: max }, (_, i) => {
192
244
  const starValue = i + 1
193
- const fillRatio = Math.max(0, Math.min(1, displayValue - i)) // 0..1
194
- const isHalf = precision === 'half' && fillRatio > 0 && fillRatio < 1
195
-
196
245
  return (
197
246
  <StarIcon
198
247
  key={i}
199
248
  Icon={Icon}
200
249
  sizePx={iconPx}
201
- fillRatio={fillRatio}
202
- isHalf={isHalf}
250
+ // 只有整顆:displayValue 已是整數(wholeValue 四捨五入,同 Zag rating-group 沒開半顆時 `Math.round`)
251
+ filled={displayValue >= starValue}
203
252
  interactive={isInteractive}
204
- onHover={(halfFirst) => {
205
- if (!isInteractive) return
206
- const v = precision === 'half' && halfFirst ? starValue - 0.5 : starValue
207
- setHoverValue(v)
208
- }}
209
- onClick={(halfFirst) => {
210
- if (!isInteractive) return
211
- const v = precision === 'half' && halfFirst ? starValue - 0.5 : starValue
212
- setValue(v)
213
- }}
253
+ onHover={() => { if (isInteractive) setHoverValue(starValue) }}
254
+ onClick={() => { if (isInteractive) setValue(starValue) }}
214
255
  />
215
256
  )
216
257
  })}
@@ -220,74 +261,47 @@ const Rating = React.forwardRef<HTMLDivElement, RatingProps>(
220
261
  )
221
262
  Rating.displayName = 'Rating'
222
263
 
223
- // ── StarIcon: 單顆星 + half-precision overlay ─────────────────────────────
264
+ // ── StarIcon: 單顆整星(可以點的評分用;唯讀精簡版直接畫一顆實心星)─────────────
224
265
 
225
266
  interface StarIconProps {
226
267
  Icon: LucideIcon
227
268
  sizePx: number
228
- fillRatio: number // 0..1
229
- isHalf: boolean
269
+ filled: boolean
230
270
  interactive: boolean
231
- onHover: (halfFirst: boolean) => void
232
- onClick: (halfFirst: boolean) => void
271
+ onHover: () => void
272
+ onClick: () => void
233
273
  }
234
274
 
235
275
  const FILL_FILLED = 'var(--warning)' // yellow-6 — 黃星 convention
236
276
  const FILL_EMPTY = 'var(--divider)' // 灰色空星(neutral-4 借 divider semantic alias,user 2026-05-09 拍板;對齊 Material rgba(0,0,0,0.26) muted-fill canonical)
237
277
 
238
- function StarIcon({ Icon, sizePx, fillRatio, isHalf, interactive, onHover, onClick }: StarIconProps) {
278
+ /** 唯讀數值:整數照原樣,小數取一位(4 → "4"、4.7 → "4.7"、4.25 → "4.3");不走 locale,避免小數點變逗號 */
279
+ function formatRatingValue(value: number): string {
280
+ return Number.isInteger(value) ? String(value) : (Math.round(value * 10) / 10).toString()
281
+ }
282
+
283
+ function StarIcon({ Icon, sizePx, filled, interactive, onHover, onClick }: StarIconProps) {
239
284
  // a11y(2026-04-25 axe nested-interactive fix):inner 點擊目標改 <span>(非 interactive
240
285
  // element),不會跟外層 role='slider' 形成 nested-interactive 違規。鍵盤控制統一在外層
241
286
  // slider 的 arrow keys,inner 只處理 mouse click 定位。Ant Rate / Material MUI 同模式。
242
- if (!isHalf) {
243
- // Full: 一整顆 fill(filled 或 empty)
244
- const fill = fillRatio >= 1 ? FILL_FILLED : FILL_EMPTY
245
- return (
246
- <span
247
- role="presentation"
248
- onMouseEnter={interactive ? () => onHover(false) : undefined}
249
- onClick={interactive ? () => onClick(false) : undefined}
250
- className={cn(
251
- 'inline-flex',
252
- interactive ? 'cursor-pointer' : 'cursor-default',
253
- )}
254
- style={{ color: fill }}
255
- aria-hidden
256
- >
257
- {/* stroke="none" 移除 Lucide Star 預設的 outline stroke(lucide defaultAttributes
258
- strokeWidth=2 + stroke=currentColor 會畫輪廓),讓星星是純 fill-only 的 shape——
259
- fill 與 outline 同色視覺上仍有亮度差。
260
- 世界級對照:Ant Rate / Material MUI Rating 皆純 fill,無 outline stroke。*/}
261
- <Icon size={sizePx} fill={fill} stroke="none" className="shrink-0" />
262
- </span>
263
- )
264
- }
265
-
266
- // Half: 兩個重疊 icon,左半 filled / 右半 empty + 兩個 hover zone 切半星
287
+ const fill = filled ? FILL_FILLED : FILL_EMPTY
267
288
  return (
268
- <span aria-hidden className="relative inline-flex" style={{ width: sizePx, height: sizePx }}>
269
- <Icon size={sizePx} fill={FILL_EMPTY} stroke="none" aria-hidden className="absolute inset-0" style={{ color: FILL_EMPTY }} />
270
- <span className="absolute inset-0 overflow-hidden" style={{ width: sizePx * fillRatio }}>
271
- <Icon size={sizePx} fill={FILL_FILLED} stroke="none" aria-hidden style={{ color: FILL_FILLED }} />
272
- </span>
273
- {interactive && (
274
- <>
275
- <span
276
- role="presentation"
277
- onMouseEnter={() => onHover(true)}
278
- onClick={() => onClick(true)}
279
- className="absolute inset-y-0 left-0 w-1/2 cursor-pointer"
280
- aria-hidden
281
- />
282
- <span
283
- role="presentation"
284
- onMouseEnter={() => onHover(false)}
285
- onClick={() => onClick(false)}
286
- className="absolute inset-y-0 right-0 w-1/2 cursor-pointer"
287
- aria-hidden
288
- />
289
- </>
289
+ <span
290
+ role="presentation"
291
+ onMouseEnter={interactive ? onHover : undefined}
292
+ onClick={interactive ? onClick : undefined}
293
+ className={cn(
294
+ 'inline-flex',
295
+ interactive ? 'cursor-pointer' : 'cursor-default',
290
296
  )}
297
+ style={{ color: fill }}
298
+ aria-hidden
299
+ >
300
+ {/* stroke="none" 移除 Lucide Star 預設的 outline stroke(lucide defaultAttributes
301
+ strokeWidth=2 + stroke=currentColor 會畫輪廓),讓星星是純 fill-only 的 shape——
302
+ fill 與 outline 同色視覺上仍有亮度差。
303
+ 世界級對照:Ant Rate / Material MUI Rating 皆純 fill,無 outline stroke。*/}
304
+ <Icon size={sizePx} fill={fill} stroke="none" className="shrink-0" />
291
305
  </span>
292
306
  )
293
307
  }
@@ -307,7 +321,7 @@ export const ratingMeta = {
307
321
  tokens: {
308
322
  bg: ['bg-transparent'],
309
323
  fg: [],
310
- ring: ['ring-ring'],
324
+ ring: ['--ring'],
311
325
  },
312
326
  } as const
313
327
 
@@ -445,7 +445,7 @@ export const Accessibility = {
445
445
  render: () => (
446
446
  <div className="max-w-3xl text-body text-fg-secondary">
447
447
  <h3 className="text-h5 text-foreground mb-2">無障礙設計</h3>
448
- <p className="whitespace-pre-line">{"詳 `scroll-area.spec.md` 「A11y 預設」段。摘要(Radix primitive + 本 DS 橋接):\n\n- 鍵盤捲動 :本 DS 在 Viewport 加 tabIndex={0} 使其可被鍵盤聚焦(Radix 不自動標 focusable,Safari 尤其需要),聚焦後支援 ArrowUp/Down/Left/Right / PageUp/Down / Home/End \n- Focus 可見 :聚焦的 Viewport 顯示 DS focus ring(focus-visible:outline-ring,inset 2px)\n- Scrollbar 非 tab stop :scrollbar thumb 不搶焦點,使用鍵盤的使用者透過 viewport 捲動(Radix 內建)\n- Pointer 支援 :thumb 可拖曳,track 可 click-to-jump(Radix 內建)\n\n本元件已內建 tabIndex 與 focus ring(滿足 axe scrollable-region-focusable);但 Viewport 預設無 role / accessible name——scroll 區域有具體語意(如「留言列表」「程式碼區塊」)時,consumer 應提供 aria-label,否則 SR 只報「可捲動區域」無內容描述;純視覺裝飾容器可省略。"}</p>
448
+ <p className="whitespace-pre-line">{"詳 `scroll-area.spec.md` 「A11y 預設」段。摘要(Radix primitive + 本 DS 橋接):\n\n- 鍵盤捲動 :本 DS 在 Viewport 加 tabIndex={0} 使其可被鍵盤聚焦(Radix 不自動標 focusable,Safari 尤其需要),聚焦後支援 ArrowUp/Down/Left/Right / PageUp/Down / Home/End \n- Focus 可見 :聚焦的 Viewport 顯示 DS 焦點框(focus-visible:focus-ring-inset,往內 2px)\n- Scrollbar 非 tab stop :scrollbar thumb 不搶焦點,使用鍵盤的使用者透過 viewport 捲動(Radix 內建)\n- Pointer 支援 :thumb 可拖曳,track 可 click-to-jump(Radix 內建)\n\n本元件已內建 tabIndex 與 focus ring(滿足 axe scrollable-region-focusable);但 Viewport 預設無 role / accessible name——scroll 區域有具體語意(如「留言列表」「程式碼區塊」)時,consumer 應提供 aria-label,否則 SR 只報「可捲動區域」無內容描述;純視覺裝飾容器可省略。"}</p>
449
449
  </div>
450
450
  ),
451
451
  }
@@ -130,8 +130,9 @@ export const UsageGuidance: Story = {
130
130
  <div className="w-[320px]">
131
131
  <ScrollArea className="h-[180px] border border-border rounded-md bg-surface">
132
132
  <div className="p-2">
133
+ {/* 靜態示意列(無 onClick、不能聚焦)不給 hover 底色(item-anatomy.spec.md「選中 × 互動疊加」:hover 要誠實回答「再點會發生什麼」) */}
133
134
  {NOTION_PAGES.map((p) => (
134
- <div key={p} className="px-3 py-1.5 text-body hover:bg-neutral-hover rounded">{p}</div>
135
+ <div key={p} className="px-3 py-1.5 text-body">{p}</div>
135
136
  ))}
136
137
  </div>
137
138
  </ScrollArea>
@@ -169,8 +170,9 @@ export const UsageGuidance: Story = {
169
170
  <ScrollArea className="h-[220px] border border-success/40 rounded-md bg-surface">
170
171
  <div className="p-3">
171
172
  <div className="text-body font-medium mb-2">單層 ScrollArea</div>
173
+ {/* 靜態示意列不給 hover 底色(同上) */}
172
174
  {NOTION_PAGES.map((p) => (
173
- <div key={p} className="px-2 py-1.5 text-body hover:bg-neutral-hover rounded">{p}</div>
175
+ <div key={p} className="px-2 py-1.5 text-body">{p}</div>
174
176
  ))}
175
177
  </div>
176
178
  </ScrollArea>
@@ -193,8 +195,9 @@ export const OrientationChoice: Story = {
193
195
  <div className="w-[280px]">
194
196
  <ScrollArea className="h-[180px] border border-border rounded-md bg-surface">
195
197
  <div className="p-2">
198
+ {/* 靜態示意列(無 onClick、不能聚焦)不給 hover 底色(item-anatomy.spec.md「選中 × 互動疊加」:hover 要誠實回答「再點會發生什麼」) */}
196
199
  {NOTION_PAGES.map((p) => (
197
- <div key={p} className="px-3 py-1.5 text-body hover:bg-neutral-hover rounded">{p}</div>
200
+ <div key={p} className="px-3 py-1.5 text-body">{p}</div>
198
201
  ))}
199
202
  </div>
200
203
  </ScrollArea>
@@ -101,7 +101,7 @@ ScrollArea 用 Radix 自訂 overlay 捲軸 → **跨 OS 一致、不吃寬度、
101
101
  Radix primitive + 本 DS a11y 橋接:
102
102
 
103
103
  - **鍵盤捲動**:本 DS 在 Viewport 加 `tabIndex={0}`(預設)使其可被鍵盤聚焦(Radix 不自動把 scroll container 標 focusable,Safari 尤其需要;此即 axe `scrollable-region-focusable` fix)。聚焦後 `ArrowUp/Down/Left/Right` / `PageUp/Down` / `Home/End` 捲動為瀏覽器原生行為,非 Radix 提供。**`viewportTabIndex={-1}` opt-out(2026-07-14)**:內容已有自帶 focusable + 鍵盤處理的元素(如 TimePicker `role="listbox"` `tabIndex={0}`)時傳 `-1`,消除「Viewport+內容雙 tab stop、焦點落 Viewport 時方向鍵觸發原生捲動非元件行為」;axe 由內部 focusable 內容滿足。**`viewportTabIndex={null}` composite opt-out(2026-07-31)**:ScrollArea 被 `role="menu"` 等 ARIA composite 當純結構 wrapper、鍵盤與 focus 由其 item family own 時,完全省略 viewport `tabindex`;`-1` 仍會留下 `div[tabindex]`,不能滿足 composite 的 required-children 結構
104
- - **Focus 可見**:聚焦的 Viewport 顯示 DS focus ring(`focus-visible:outline-ring`,inset 2px),非瀏覽器原生 focus ring
104
+ - **Focus 可見**:聚焦的 Viewport 顯示 DS 焦點框(`focus-visible:focus-ring-inset`,往內 2px),非瀏覽器原生 focus ring(2026-09-24 訂正,原 `focus-visible:outline-ring` 是已退役的手寫 outline 寫法,`scroll-area.tsx:69` 實為 `focus-ring-inset`)
105
105
  - **Scrollbar 非 tab stop**:scrollbar thumb 不搶焦點,使用鍵盤的使用者透過 viewport 捲動(Radix 內建)
106
106
  - **Pointer 支援**:thumb 可拖曳,track 可 click-to-jump(Radix 內建)
107
107
 
@@ -151,6 +151,8 @@ Radix Viewport 內建 wrapper `min-width:100%; display:table`——這是**水
151
151
 
152
152
  > 本節由 `scripts/add-reciprocal-pointers.mjs` 自動維護,列出在 SSOT 語境下指向本 spec 的其他 spec。若要手動補充,寫在本節之前。
153
153
 
154
+ - `data-table-known-defects.spec.md`
154
155
  - `dialog.spec.md`
155
156
  - `file-viewer.spec.md`
156
157
  - `overlay-surface.spec.md`
158
+ - `sidebar.spec.md`
@@ -165,9 +165,11 @@ export const VerticalIssueList: Story = {
165
165
  長 issue 清單(12 筆),容器固定 320px 高。macOS / Windows 呈現一致,不吃寬度。
166
166
  </p>
167
167
  <ScrollArea className="h-[320px] border border-border rounded-lg">
168
+ {/* 列是靜態 div(無 onClick、不能聚焦)→ 不給 hover 底色與 cursor-pointer:hover 回饋要誠實回答「再點會發生什麼」
169
+ (item-anatomy.spec.md「選中 × 互動疊加」表)。本則示範的是 ScrollArea 捲動,不是可點的議題列 */}
168
170
  <div className="p-2">
169
171
  {LINEAR_ISSUES.map((issue) => (
170
- <div key={issue.id} className="flex items-center gap-3 px-3 py-2 rounded-md hover:bg-neutral-hover cursor-pointer">
172
+ <div key={issue.id} className="flex items-center gap-3 px-3 py-2">
171
173
  <span className="text-caption font-mono text-fg-muted shrink-0 w-20">{issue.id}</span>
172
174
  <span className="text-body flex-1 truncate">{issue.title}</span>
173
175
  <span className="inline-flex items-center gap-1.5 text-footnote shrink-0">
@@ -194,8 +196,9 @@ export const SidebarNav: Story = {
194
196
  {NOTION_NAV.map((group) => (
195
197
  <div key={group.section} className="mb-4">
196
198
  <div className="px-2 py-1 text-footnote text-fg-muted font-medium">{group.section}</div>
199
+ {/* 同上:靜態示意列不給 hover / cursor-pointer。真的側欄導覽請用 Sidebar(SidebarMenuButton;SidebarContent 自帶 ScrollArea) */}
197
200
  {group.items.map((item) => (
198
- <div key={item} className="px-2 py-1.5 rounded-md text-body hover:bg-neutral-hover cursor-pointer truncate">
201
+ <div key={item} className="px-2 py-1.5 text-body truncate">
199
202
  {item}
200
203
  </div>
201
204
  ))}
@@ -120,7 +120,7 @@ export const scrollAreaMeta = {
120
120
  tokens: {
121
121
  bg: [],
122
122
  fg: [],
123
- ring: [],
123
+ ring: ['focus-ring-inset'],
124
124
  },
125
125
  } as const
126
126
 
@@ -289,7 +289,7 @@ export const Accessibility = {
289
289
  render: () => (
290
290
  <div className="max-w-3xl text-body text-fg-secondary">
291
291
  <h3 className="text-h5 text-foreground mb-2">無障礙設計</h3>
292
- <p className="whitespace-pre-line">{" ARIA / Pattern :繼承 Radix toggle-group primitive 的無障礙預設——整組 role=\"group\",每個 item role=\"radio\"(type=\"single\" 互斥選一),鍵盤導覽由 primitive 處理。詳 [Radix Accessibility docs](https://www.radix-ui.com/primitives/docs/components/toggle-group#accessibility)。\n\n Keyboard 行為 :\n\n- Tab — 進入整組(只有一個 tab 停留點,落在選中項;沒選就落在上次 focus 過的 item,初次進入才是第一項)\n- ←/→ — 在 item 之間移動 roving focus(只移焦點,不切換選取)。WAI-ARIA APG 對水平 segmented/radio group 的主要導覽鍵\n- ↑/↓ — 同 ←/→,也會在 item 間移動 roving focus。Radix toggle-group 未鎖 orientation(沿用 default 雙軸 roving),APG 接受 group 同時支援兩軸方向鍵\n- Enter / Space — 選取目前 focus 的 item\n\n Focus :採 roving tabindex——整組共用一個 tab 停留點,方向鍵在 item 間移動焦點(選取仍需 Enter/Space/click);不是 Dialog 那種把焦點關在裡面的 focus trap。Focus ring 對齊 Button:focus-visible:ring-2 ring-ring ring-offset-1(box-shadow ring,非 CSS outline)。\n\n 驗證 :Storybook a11y addon panel 應 0 critical violation;鍵盤完整可操作(無需滑鼠)。WCAG AA contrast ≥ 4.5:1(text)/ 3:1(UI)。"}</p>
292
+ <p className="whitespace-pre-line">{" ARIA / Pattern :繼承 Radix toggle-group primitive 的無障礙預設——整組 role=\"group\",每個 item role=\"radio\"(type=\"single\" 互斥選一),鍵盤導覽由 primitive 處理。詳 [Radix Accessibility docs](https://www.radix-ui.com/primitives/docs/components/toggle-group#accessibility)。\n\n Keyboard 行為 :\n\n- Tab — 進入整組(只有一個 tab 停留點,落在選中項;沒選就落在上次 focus 過的 item,初次進入才是第一項)\n- ←/→ — 在 item 之間移動 roving focus(只移焦點,不切換選取)。WAI-ARIA APG 對水平 segmented/radio group 的主要導覽鍵\n- ↑/↓ — 同 ←/→,也會在 item 間移動 roving focus。Radix toggle-group 未鎖 orientation(沿用 default 雙軸 roving),APG 接受 group 同時支援兩軸方向鍵\n- Enter / Space — 選取目前 focus 的 item\n\n Focus :採 roving tabindex——整組共用一個 tab 停留點,方向鍵在 item 間移動焦點(選取仍需 Enter/Space/click);不是 Dialog 那種把焦點關在裡面的 focus trap。Focus ring 對齊 Button:全域 :focus-visible 外描邊(outline: 2px solid var(--ring),往外 2px;元件不寫任何焦點框 class,只加 focus-visible:z-20 讓往外長的框壓在相鄰 item 之上)。\n\n 驗證 :Storybook a11y addon panel 應 0 critical violation;鍵盤完整可操作(無需滑鼠)。WCAG AA contrast ≥ 4.5:1(text)/ 3:1(UI)。"}</p>
293
293
  </div>
294
294
  ),
295
295
  }
@@ -218,7 +218,7 @@ Items 之間 `-ml-px`(除了第一個)讓相鄰 border 重疊、視覺上只
218
218
 
219
219
  ### focus-visible
220
220
 
221
- 由底層 ToggleGroup 處理——左右箭頭在 items 間移動 focus,空白/Enter 選取。Focus ring 對齊 Button:`ring-2 ring-ring ring-offset-1`。
221
+ 由底層 ToggleGroup 處理——左右箭頭在 items 間移動 focus,空白/Enter 選取。焦點框對齊 Button:全域 `:focus-visible` 外描邊(`styles/base.css` 的 `outline: 2px solid var(--ring)`,往外 `outline-offset: 2px`;item 不寫任何 ring / outline class,只加 `focus-visible:z-20` 把聚焦 item 提到相鄰 item 之上,讓外描邊浮在 `-ml-px` 重疊的鄰居 border 之上——與選中態 `z-10` 同一種手法;幾何 SSOT `ds-canonical/references/focus-canonical.md`「框怎麼畫」)。
222
222
 
223
223
  ---
224
224
 
@@ -276,7 +276,7 @@ Items 之間 `-ml-px`(除了第一個)讓相鄰 border 重疊、視覺上只
276
276
  - ↑/↓ — 同 ←/→,也會在 item 間移動 roving focus。Radix `toggle-group` 未鎖 `orientation`(沿用 default 雙軸 roving),APG 接受 group 同時支援兩軸方向鍵
277
277
  - Enter / Space — 選取目前 focus 的 item
278
278
 
279
- **Focus**:Radix primitive 採 roving tabindex(整組共用單一 tab 停留點,方向鍵在 item 間移動焦點,不切換選取),非 Dialog 式 focus trap / restoration。Focus ring 對齊 Button focus-visible canonical(`focus-visible:ring-2 ring-ring ring-offset-1`——box-shadow ring,非 CSS outline)。
279
+ **Focus**:Radix primitive 採 roving tabindex(整組共用單一 tab 停留點,方向鍵在 item 間移動焦點,不切換選取),非 Dialog 式 focus trap / restoration。焦點框對齊 Button focus-visible canonical(全域 `:focus-visible` 外描邊 `outline: 2px solid var(--ring)`,往外 2px——CSS outline,非 box-shadow ring;item 不寫任何 ring class)。
280
280
 
281
281
  **驗證**:Storybook a11y addon panel 應 0 critical violation;鍵盤完整可操作(無需滑鼠)。WCAG AA contrast ≥ 4.5:1(text)/ 3:1(UI)。
282
282
 
@@ -167,7 +167,8 @@ const itemVariants = cva(
167
167
  'relative inline-flex items-center justify-center',
168
168
  'whitespace-nowrap font-medium',
169
169
  'border border-border bg-surface text-fg-secondary',
170
- 'transition-colors duration-150',
170
+ // 不寫 transition-colors:hover 的外框與字色一律瞬間(tokens/motion/motion.spec.md「hover 回饋不做過渡」;2026-09-26 由底色延伸到字色與外框,待辦總帳 L9 / N4(3))。
171
+ // 選中的外框與字色跟 hover 是同一組 CSS 屬性,滑鼠點選時指標必在項目上 → 選中變色一併瞬間(與 Chip 同一處理)。
171
172
  'cursor-pointer select-none',
172
173
  'focus-visible:z-20',
173
174
  // disabled:cursor-not-allowed + 鎖住 hover 色(不用 pointer-events-none,否則 cursor 無法變)
@@ -315,7 +316,7 @@ export const segmentedControlMeta = {
315
316
  tokens: {
316
317
  bg: ['bg-surface'],
317
318
  fg: ['text-fg-disabled', 'text-fg-secondary', 'text-foreground', 'text-primary'], // selected 態 cva data-[state=on]:text-primary 實際消費(2026-07-06 選中改 base)
318
- ring: ['ring-ring'],
319
+ ring: ['--ring'],
319
320
  },
320
321
  defaultSize: 'md',
321
322
  } as const