@qijenchen/design-system 0.1.0-beta.80 → 0.1.0-beta.82

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 (548) hide show
  1. package/CLAUDE.md +1 -1
  2. package/dist/components/Alert/alert.d.ts +5 -0
  3. package/dist/components/Alert/alert.d.ts.map +1 -1
  4. package/dist/components/Alert/alert.js +4 -1
  5. package/dist/components/Alert/alert.js.map +1 -1
  6. package/dist/components/AppShell/app-shell.d.ts +1 -1
  7. package/dist/components/AppShell/app-shell.js.map +1 -1
  8. package/dist/components/AspectRatio/aspect-ratio.d.ts +2 -1
  9. package/dist/components/AspectRatio/aspect-ratio.d.ts.map +1 -1
  10. package/dist/components/AspectRatio/aspect-ratio.js +1 -1
  11. package/dist/components/AspectRatio/aspect-ratio.js.map +1 -1
  12. package/dist/components/Avatar/avatar.d.ts +3 -2
  13. package/dist/components/Avatar/avatar.d.ts.map +1 -1
  14. package/dist/components/Avatar/avatar.js +8 -3
  15. package/dist/components/Avatar/avatar.js.map +1 -1
  16. package/dist/components/Badge/badge.d.ts.map +1 -1
  17. package/dist/components/Badge/badge.js +2 -1
  18. package/dist/components/Badge/badge.js.map +1 -1
  19. package/dist/components/Breadcrumb/breadcrumb.d.ts +14 -1
  20. package/dist/components/Breadcrumb/breadcrumb.d.ts.map +1 -1
  21. package/dist/components/Breadcrumb/breadcrumb.js +9 -3
  22. package/dist/components/Breadcrumb/breadcrumb.js.map +1 -1
  23. package/dist/components/BulkActionBar/bulk-action-bar.d.ts +4 -1
  24. package/dist/components/BulkActionBar/bulk-action-bar.d.ts.map +1 -1
  25. package/dist/components/BulkActionBar/bulk-action-bar.js +2 -2
  26. package/dist/components/BulkActionBar/bulk-action-bar.js.map +1 -1
  27. package/dist/components/Button/button.d.ts +4 -4
  28. package/dist/components/Button/button.d.ts.map +1 -1
  29. package/dist/components/Button/button.js +73 -44
  30. package/dist/components/Button/button.js.map +1 -1
  31. package/dist/components/Button/index.d.ts +1 -0
  32. package/dist/components/Button/index.d.ts.map +1 -1
  33. package/dist/components/Button/index.js +3 -0
  34. package/dist/components/Button/index.js.map +1 -1
  35. package/dist/components/Calendar/calendar.d.ts +3 -1
  36. package/dist/components/Calendar/calendar.d.ts.map +1 -1
  37. package/dist/components/Calendar/calendar.js +40 -17
  38. package/dist/components/Calendar/calendar.js.map +1 -1
  39. package/dist/components/Carousel/carousel.d.ts +1 -1
  40. package/dist/components/Carousel/carousel.d.ts.map +1 -1
  41. package/dist/components/Carousel/carousel.js +3 -1
  42. package/dist/components/Carousel/carousel.js.map +1 -1
  43. package/dist/components/Chart/chart.d.ts.map +1 -1
  44. package/dist/components/Chart/chart.js +1 -1
  45. package/dist/components/Chart/chart.js.map +1 -1
  46. package/dist/components/Checkbox/checkbox.d.ts +6 -3
  47. package/dist/components/Checkbox/checkbox.d.ts.map +1 -1
  48. package/dist/components/Checkbox/checkbox.js +26 -4
  49. package/dist/components/Checkbox/checkbox.js.map +1 -1
  50. package/dist/components/Checkbox/index.d.ts +1 -0
  51. package/dist/components/Checkbox/index.d.ts.map +1 -1
  52. package/dist/components/Checkbox/index.js +3 -0
  53. package/dist/components/Checkbox/index.js.map +1 -1
  54. package/dist/components/Chip/chip.d.ts +9 -3
  55. package/dist/components/Chip/chip.d.ts.map +1 -1
  56. package/dist/components/Chip/chip.js +46 -13
  57. package/dist/components/Chip/chip.js.map +1 -1
  58. package/dist/components/Coachmark/coachmark.d.ts +5 -3
  59. package/dist/components/Coachmark/coachmark.d.ts.map +1 -1
  60. package/dist/components/Coachmark/coachmark.js +10 -7
  61. package/dist/components/Coachmark/coachmark.js.map +1 -1
  62. package/dist/components/Combobox/combobox.d.ts +3 -1
  63. package/dist/components/Combobox/combobox.d.ts.map +1 -1
  64. package/dist/components/Combobox/combobox.js +11 -7
  65. package/dist/components/Combobox/combobox.js.map +1 -1
  66. package/dist/components/Command/command.d.ts +2 -2
  67. package/dist/components/Command/command.d.ts.map +1 -1
  68. package/dist/components/Command/command.js +4 -2
  69. package/dist/components/Command/command.js.map +1 -1
  70. package/dist/components/DataTable/active-editor-controller.d.ts +4 -3
  71. package/dist/components/DataTable/active-editor-controller.d.ts.map +1 -1
  72. package/dist/components/DataTable/cell-registry.d.ts.map +1 -1
  73. package/dist/components/DataTable/cell-registry.js +3 -0
  74. package/dist/components/DataTable/cell-registry.js.map +1 -1
  75. package/dist/components/DataTable/data-table-filter-panel.d.ts.map +1 -1
  76. package/dist/components/DataTable/data-table-interaction-layer.d.ts +4 -3
  77. package/dist/components/DataTable/data-table-interaction-layer.d.ts.map +1 -1
  78. package/dist/components/DataTable/data-table-interaction-layer.js.map +1 -1
  79. package/dist/components/DataTable/data-table.d.ts +38 -7
  80. package/dist/components/DataTable/data-table.d.ts.map +1 -1
  81. package/dist/components/DataTable/data-table.js +213 -63
  82. package/dist/components/DataTable/data-table.js.map +1 -1
  83. package/dist/components/DataTable/filter-tree.d.ts +5 -0
  84. package/dist/components/DataTable/filter-tree.d.ts.map +1 -1
  85. package/dist/components/DateGrid/date-grid.d.ts +3 -3
  86. package/dist/components/DateGrid/date-grid.d.ts.map +1 -1
  87. package/dist/components/DateGrid/date-grid.js +52 -6
  88. package/dist/components/DateGrid/date-grid.js.map +1 -1
  89. package/dist/components/DatePicker/date-picker.d.ts +2 -8
  90. package/dist/components/DatePicker/date-picker.d.ts.map +1 -1
  91. package/dist/components/DatePicker/date-picker.js +139 -109
  92. package/dist/components/DatePicker/date-picker.js.map +1 -1
  93. package/dist/components/DescriptionList/description-list.d.ts.map +1 -1
  94. package/dist/components/DescriptionList/description-list.js +1 -1
  95. package/dist/components/DescriptionList/description-list.js.map +1 -1
  96. package/dist/components/Dialog/dialog.d.ts +1 -1
  97. package/dist/components/Dialog/dialog.d.ts.map +1 -1
  98. package/dist/components/Dialog/dialog.js +2 -1
  99. package/dist/components/Dialog/dialog.js.map +1 -1
  100. package/dist/components/DropdownMenu/dropdown-menu.d.ts +4 -4
  101. package/dist/components/DropdownMenu/dropdown-menu.d.ts.map +1 -1
  102. package/dist/components/DropdownMenu/dropdown-menu.js +25 -8
  103. package/dist/components/DropdownMenu/dropdown-menu.js.map +1 -1
  104. package/dist/components/Empty/empty.d.ts.map +1 -1
  105. package/dist/components/Empty/empty.js +1 -1
  106. package/dist/components/Empty/empty.js.map +1 -1
  107. package/dist/components/Field/field-wrapper.d.ts +1 -0
  108. package/dist/components/Field/field-wrapper.d.ts.map +1 -1
  109. package/dist/components/Field/field-wrapper.js +27 -7
  110. package/dist/components/Field/field-wrapper.js.map +1 -1
  111. package/dist/components/Field/field.d.ts +1 -1
  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/Field/use-form-validation.d.ts +5 -1
  116. package/dist/components/Field/use-form-validation.d.ts.map +1 -1
  117. package/dist/components/Field/use-form-validation.js +31 -21
  118. package/dist/components/Field/use-form-validation.js.map +1 -1
  119. package/dist/components/FieldControlGroup/field-control-group.d.ts +3 -3
  120. package/dist/components/FieldControlGroup/field-control-group.js.map +1 -1
  121. package/dist/components/FileItem/file-item.d.ts.map +1 -1
  122. package/dist/components/FileItem/file-item.js +3 -2
  123. package/dist/components/FileItem/file-item.js.map +1 -1
  124. package/dist/components/FileUpload/file-upload.d.ts +1 -1
  125. package/dist/components/FileUpload/file-upload.d.ts.map +1 -1
  126. package/dist/components/FileUpload/file-upload.js +22 -10
  127. package/dist/components/FileUpload/file-upload.js.map +1 -1
  128. package/dist/components/FileViewer/can-render-image.d.ts +6 -0
  129. package/dist/components/FileViewer/can-render-image.d.ts.map +1 -0
  130. package/dist/components/FileViewer/can-render-image.js +11 -0
  131. package/dist/components/FileViewer/can-render-image.js.map +1 -0
  132. package/dist/components/FileViewer/file-viewer.d.ts +2 -2
  133. package/dist/components/FileViewer/file-viewer.d.ts.map +1 -1
  134. package/dist/components/FileViewer/file-viewer.js +24 -16
  135. package/dist/components/FileViewer/file-viewer.js.map +1 -1
  136. package/dist/components/FileViewer/image-renderer.d.ts +0 -5
  137. package/dist/components/FileViewer/image-renderer.d.ts.map +1 -1
  138. package/dist/components/FileViewer/image-renderer.js +1 -9
  139. package/dist/components/FileViewer/image-renderer.js.map +1 -1
  140. package/dist/components/HoverCard/hover-card.d.ts.map +1 -1
  141. package/dist/components/HoverCard/hover-card.js +3 -1
  142. package/dist/components/HoverCard/hover-card.js.map +1 -1
  143. package/dist/components/Input/input.d.ts +26 -3
  144. package/dist/components/Input/input.d.ts.map +1 -1
  145. package/dist/components/Input/input.js +12 -8
  146. package/dist/components/Input/input.js.map +1 -1
  147. package/dist/components/LinkInput/link-input.d.ts +3 -3
  148. package/dist/components/LinkInput/link-input.d.ts.map +1 -1
  149. package/dist/components/LinkInput/link-input.js +29 -28
  150. package/dist/components/LinkInput/link-input.js.map +1 -1
  151. package/dist/components/Menu/menu-item.d.ts.map +1 -1
  152. package/dist/components/Menu/menu-item.js +5 -4
  153. package/dist/components/Menu/menu-item.js.map +1 -1
  154. package/dist/components/Notice/notice.d.ts +5 -0
  155. package/dist/components/Notice/notice.d.ts.map +1 -1
  156. package/dist/components/Notice/notice.js +11 -5
  157. package/dist/components/Notice/notice.js.map +1 -1
  158. package/dist/components/NumberInput/number-input.d.ts +1 -1
  159. package/dist/components/NumberInput/number-input.d.ts.map +1 -1
  160. package/dist/components/NumberInput/number-input.js +17 -8
  161. package/dist/components/NumberInput/number-input.js.map +1 -1
  162. package/dist/components/OverflowIndicator/overflow-indicator.d.ts +1 -1
  163. package/dist/components/OverflowIndicator/overflow-indicator.d.ts.map +1 -1
  164. package/dist/components/OverflowIndicator/overflow-indicator.js +2 -1
  165. package/dist/components/OverflowIndicator/overflow-indicator.js.map +1 -1
  166. package/dist/components/Pagination/index.d.ts +2 -0
  167. package/dist/components/Pagination/index.d.ts.map +1 -0
  168. package/dist/components/Pagination/index.js +6 -0
  169. package/dist/components/Pagination/index.js.map +1 -0
  170. package/dist/components/Pagination/pagination.d.ts +49 -0
  171. package/dist/components/Pagination/pagination.d.ts.map +1 -0
  172. package/dist/components/Pagination/pagination.js +182 -0
  173. package/dist/components/Pagination/pagination.js.map +1 -0
  174. package/dist/components/PeoplePicker/people-picker.d.ts +3 -3
  175. package/dist/components/PeoplePicker/people-picker.d.ts.map +1 -1
  176. package/dist/components/PeoplePicker/people-picker.js +38 -36
  177. package/dist/components/PeoplePicker/people-picker.js.map +1 -1
  178. package/dist/components/PeoplePicker/person-display.d.ts +4 -0
  179. package/dist/components/PeoplePicker/person-display.d.ts.map +1 -1
  180. package/dist/components/PeoplePicker/person-display.js +3 -3
  181. package/dist/components/PeoplePicker/person-display.js.map +1 -1
  182. package/dist/components/Popover/popover.d.ts.map +1 -1
  183. package/dist/components/Popover/popover.js +58 -32
  184. package/dist/components/Popover/popover.js.map +1 -1
  185. package/dist/components/ProfileCard/profile-card.d.ts +11 -3
  186. package/dist/components/ProfileCard/profile-card.d.ts.map +1 -1
  187. package/dist/components/ProfileCard/profile-card.js +14 -4
  188. package/dist/components/ProfileCard/profile-card.js.map +1 -1
  189. package/dist/components/RadioGroup/radio-group.d.ts +1 -1
  190. package/dist/components/RadioGroup/radio-group.d.ts.map +1 -1
  191. package/dist/components/RadioGroup/radio-group.js +28 -3
  192. package/dist/components/RadioGroup/radio-group.js.map +1 -1
  193. package/dist/components/Rating/rating.js +3 -3
  194. package/dist/components/Rating/rating.js.map +1 -1
  195. package/dist/components/ScrollArea/scroll-area.d.ts +1 -1
  196. package/dist/components/ScrollArea/scroll-area.js +2 -2
  197. package/dist/components/ScrollArea/scroll-area.js.map +1 -1
  198. package/dist/components/SegmentedControl/segmented-control.d.ts +2 -2
  199. package/dist/components/SegmentedControl/segmented-control.d.ts.map +1 -1
  200. package/dist/components/SegmentedControl/segmented-control.js +23 -10
  201. package/dist/components/SegmentedControl/segmented-control.js.map +1 -1
  202. package/dist/components/Select/select.d.ts +10 -4
  203. package/dist/components/Select/select.d.ts.map +1 -1
  204. package/dist/components/Select/select.js +57 -14
  205. package/dist/components/Select/select.js.map +1 -1
  206. package/dist/components/SelectMenu/index.js +4 -2
  207. package/dist/components/SelectMenu/select-menu.d.ts +31 -6
  208. package/dist/components/SelectMenu/select-menu.d.ts.map +1 -1
  209. package/dist/components/SelectMenu/select-menu.js +169 -85
  210. package/dist/components/SelectMenu/select-menu.js.map +1 -1
  211. package/dist/components/Separator/separator.js +1 -1
  212. package/dist/components/Separator/separator.js.map +1 -1
  213. package/dist/components/Sheet/sheet.d.ts +1 -1
  214. package/dist/components/Sheet/sheet.d.ts.map +1 -1
  215. package/dist/components/Sheet/sheet.js +3 -2
  216. package/dist/components/Sheet/sheet.js.map +1 -1
  217. package/dist/components/Sidebar/sidebar.d.ts +2 -2
  218. package/dist/components/Sidebar/sidebar.d.ts.map +1 -1
  219. package/dist/components/Sidebar/sidebar.js +28 -10
  220. package/dist/components/Sidebar/sidebar.js.map +1 -1
  221. package/dist/components/Skeleton/skeleton.d.ts +1 -1
  222. package/dist/components/Skeleton/skeleton.js +2 -2
  223. package/dist/components/Skeleton/skeleton.js.map +1 -1
  224. package/dist/components/Slider/slider.d.ts +1 -1
  225. package/dist/components/Slider/slider.d.ts.map +1 -1
  226. package/dist/components/Slider/slider.js +7 -3
  227. package/dist/components/Slider/slider.js.map +1 -1
  228. package/dist/components/Steps/steps.d.ts.map +1 -1
  229. package/dist/components/Steps/steps.js +6 -3
  230. package/dist/components/Steps/steps.js.map +1 -1
  231. package/dist/components/Switch/switch.d.ts +2 -2
  232. package/dist/components/Switch/switch.d.ts.map +1 -1
  233. package/dist/components/Switch/switch.js +47 -9
  234. package/dist/components/Switch/switch.js.map +1 -1
  235. package/dist/components/Tabs/tabs.d.ts +8 -5
  236. package/dist/components/Tabs/tabs.d.ts.map +1 -1
  237. package/dist/components/Tabs/tabs.js +45 -33
  238. package/dist/components/Tabs/tabs.js.map +1 -1
  239. package/dist/components/Tag/tag.d.ts.map +1 -1
  240. package/dist/components/Tag/tag.js +20 -4
  241. package/dist/components/Tag/tag.js.map +1 -1
  242. package/dist/components/Textarea/textarea.d.ts +2 -1
  243. package/dist/components/Textarea/textarea.d.ts.map +1 -1
  244. package/dist/components/Textarea/textarea.js +24 -13
  245. package/dist/components/Textarea/textarea.js.map +1 -1
  246. package/dist/components/TimePicker/time-picker.d.ts +2 -2
  247. package/dist/components/TimePicker/time-picker.d.ts.map +1 -1
  248. package/dist/components/TimePicker/time-picker.js +23 -12
  249. package/dist/components/TimePicker/time-picker.js.map +1 -1
  250. package/dist/components/Toast/toast.d.ts +1 -1
  251. package/dist/components/Toast/toast.d.ts.map +1 -1
  252. package/dist/components/Toast/toast.js +7 -3
  253. package/dist/components/Toast/toast.js.map +1 -1
  254. package/dist/components/Tooltip/tooltip.js +1 -1
  255. package/dist/components/Tooltip/tooltip.js.map +1 -1
  256. package/dist/components/TreeView/tree-view.d.ts +1 -1
  257. package/dist/components/TreeView/tree-view.d.ts.map +1 -1
  258. package/dist/components/TreeView/tree-view.js +11 -5
  259. package/dist/components/TreeView/tree-view.js.map +1 -1
  260. package/dist/hooks/use-controllable.d.ts.map +1 -1
  261. package/dist/hooks/use-controllable.js +4 -2
  262. package/dist/hooks/use-controllable.js.map +1 -1
  263. package/dist/hooks/use-is-touch-device.d.ts.map +1 -1
  264. package/dist/hooks/use-is-touch-device.js +3 -1
  265. package/dist/hooks/use-is-touch-device.js.map +1 -1
  266. package/dist/index.d.ts +2 -0
  267. package/dist/index.d.ts.map +1 -1
  268. package/dist/index.js +14 -0
  269. package/dist/index.js.map +1 -1
  270. package/dist/patterns/element-anatomy/item-anatomy.d.ts +2 -2
  271. package/dist/patterns/element-anatomy/item-anatomy.d.ts.map +1 -1
  272. package/dist/patterns/element-anatomy/item-anatomy.js.map +1 -1
  273. package/dist/patterns/overlay-surface/overlay-surface.js.map +1 -1
  274. package/dist/tokens/categorical-color.d.ts +1 -1
  275. package/dist/tokens/categorical-color.js.map +1 -1
  276. package/dist/tokens/uiSize/icon-size.d.ts +2 -2
  277. package/ds-canonical/fork/governance.lock +1 -1
  278. package/ds-canonical/fork/hooks/check_opacity_token_usage.sh +10 -1
  279. package/ds-canonical/hooks/check_audit_post_report_validator.sh +13 -0
  280. package/ds-canonical/hooks/check_full_story_visual_interaction_sweep.sh +4 -3
  281. package/ds-canonical/hooks/check_opacity_token_usage.sh +10 -1
  282. package/ds-canonical/hooks/check_plugin_fork_health.sh +30 -22
  283. package/ds-canonical/hooks/check_story_invariants.sh +17 -5
  284. package/ds-canonical/hooks/check_substantive_edit_approval_preflight.sh +5 -2
  285. package/ds-canonical/references/principle-dim-map.json +100 -0
  286. package/ds-canonical/references/story-baseline-registry.json +4 -3
  287. package/ds-canonical/skills/deep-audit-cross-codex/SKILL.md +1 -1
  288. package/ds-canonical/skills/design-system-audit/SKILL.md +2 -2
  289. package/ds-story-manifest.json +41 -18
  290. package/llms-full.txt +11 -1
  291. package/llms.txt +3 -1
  292. package/package.json +1 -1
  293. package/src/components/Alert/alert.anatomy.stories.tsx +9 -6
  294. package/src/components/Alert/alert.spec.md +3 -3
  295. package/src/components/Alert/alert.tsx +9 -1
  296. package/src/components/AppShell/app-shell.anatomy.stories.tsx +3 -0
  297. package/src/components/AppShell/app-shell.spec.md +3 -2
  298. package/src/components/AppShell/app-shell.tsx +1 -1
  299. package/src/components/AspectRatio/aspect-ratio.spec.md +1 -2
  300. package/src/components/AspectRatio/aspect-ratio.tsx +3 -2
  301. package/src/components/Avatar/avatar.principles.stories.tsx +1 -1
  302. package/src/components/Avatar/avatar.spec.md +10 -30
  303. package/src/components/Avatar/avatar.stories.tsx +1 -1
  304. package/src/components/Avatar/avatar.tsx +30 -16
  305. package/src/components/Badge/badge.anatomy.stories.tsx +2 -2
  306. package/src/components/Badge/badge.spec.md +5 -4
  307. package/src/components/Badge/badge.tsx +4 -1
  308. package/src/components/Breadcrumb/breadcrumb.anatomy.stories.tsx +2 -2
  309. package/src/components/Breadcrumb/breadcrumb.principles.stories.tsx +15 -10
  310. package/src/components/Breadcrumb/breadcrumb.spec.md +5 -3
  311. package/src/components/Breadcrumb/breadcrumb.tsx +10 -4
  312. package/src/components/BulkActionBar/bulk-action-bar.anatomy.stories.tsx +3 -2
  313. package/src/components/BulkActionBar/bulk-action-bar.spec.md +15 -14
  314. package/src/components/BulkActionBar/bulk-action-bar.tsx +10 -4
  315. package/src/components/Button/button.anatomy.stories.tsx +6 -6
  316. package/src/components/Button/button.spec.md +3 -2
  317. package/src/components/Button/button.tsx +67 -27
  318. package/src/components/Button/index.ts +1 -0
  319. package/src/components/Calendar/calendar.anatomy.stories.tsx +5 -3
  320. package/src/components/Calendar/calendar.principles.stories.tsx +3 -1
  321. package/src/components/Calendar/calendar.spec.md +9 -2
  322. package/src/components/Calendar/calendar.stories.tsx +3 -1
  323. package/src/components/Calendar/calendar.tsx +53 -19
  324. package/src/components/Carousel/carousel.principles.stories.tsx +1 -1
  325. package/src/components/Carousel/carousel.tsx +4 -1
  326. package/src/components/Chart/chart.spec.md +1 -1
  327. package/src/components/Chart/chart.tsx +2 -1
  328. package/src/components/Checkbox/checkbox.principles.stories.tsx +1 -1
  329. package/src/components/Checkbox/checkbox.spec.md +1 -1
  330. package/src/components/Checkbox/checkbox.tsx +41 -7
  331. package/src/components/Checkbox/index.ts +1 -0
  332. package/src/components/Chip/chip.anatomy.stories.tsx +9 -9
  333. package/src/components/Chip/chip.principles.stories.tsx +3 -3
  334. package/src/components/Chip/chip.spec.md +7 -4
  335. package/src/components/Chip/chip.tsx +60 -15
  336. package/src/components/CircularProgress/circular-progress.anatomy.stories.tsx +1 -1
  337. package/src/components/CircularProgress/circular-progress.principles.stories.tsx +7 -5
  338. package/src/components/CircularProgress/circular-progress.spec.md +2 -0
  339. package/src/components/CircularProgress/circular-progress.stories.tsx +3 -11
  340. package/src/components/Coachmark/coachmark.anatomy.stories.tsx +2 -2
  341. package/src/components/Coachmark/coachmark.principles.stories.tsx +1 -1
  342. package/src/components/Coachmark/coachmark.spec.md +3 -3
  343. package/src/components/Coachmark/coachmark.tsx +23 -13
  344. package/src/components/Combobox/combobox.anatomy.stories.tsx +4 -4
  345. package/src/components/Combobox/combobox.spec.md +1 -1
  346. package/src/components/Combobox/combobox.tsx +30 -17
  347. package/src/components/Command/command.principles.stories.tsx +10 -12
  348. package/src/components/Command/command.spec.md +5 -5
  349. package/src/components/Command/command.stories.tsx +4 -4
  350. package/src/components/Command/command.tsx +3 -2
  351. package/src/components/DataTable/active-editor-controller.ts +4 -3
  352. package/src/components/DataTable/cell-registry.tsx +8 -0
  353. package/src/components/DataTable/data-table-filter-panel.tsx +8 -2
  354. package/src/components/DataTable/data-table-interaction-layer.tsx +8 -7
  355. package/src/components/DataTable/data-table.anatomy.stories.tsx +4 -4
  356. package/src/components/DataTable/data-table.css +2 -2
  357. package/src/components/DataTable/data-table.spec.md +72 -115
  358. package/src/components/DataTable/data-table.stories.tsx +51 -3
  359. package/src/components/DataTable/data-table.tsx +348 -63
  360. package/src/components/DataTable/filter-operators.spec.md +4 -4
  361. package/src/components/DataTable/filter-tree.ts +192 -42
  362. package/src/components/DateGrid/date-grid.anatomy.stories.tsx +1 -1
  363. package/src/components/DateGrid/date-grid.spec.md +11 -8
  364. package/src/components/DateGrid/date-grid.tsx +63 -8
  365. package/src/components/DatePicker/date-picker.principles.stories.tsx +1 -1
  366. package/src/components/DatePicker/date-picker.spec.md +1 -0
  367. package/src/components/DatePicker/date-picker.stories.tsx +19 -0
  368. package/src/components/DatePicker/date-picker.tsx +52 -15
  369. package/src/components/DescriptionList/description-list.spec.md +1 -1
  370. package/src/components/DescriptionList/description-list.tsx +3 -1
  371. package/src/components/Dialog/dialog.spec.md +1 -1
  372. package/src/components/Dialog/dialog.tsx +2 -1
  373. package/src/components/DropdownMenu/dropdown-menu.anatomy.stories.tsx +3 -1
  374. package/src/components/DropdownMenu/dropdown-menu.spec.md +8 -5
  375. package/src/components/DropdownMenu/dropdown-menu.tsx +33 -7
  376. package/src/components/Empty/empty.anatomy.stories.tsx +1 -1
  377. package/src/components/Empty/empty.spec.md +2 -2
  378. package/src/components/Empty/empty.stories.tsx +4 -15
  379. package/src/components/Empty/empty.tsx +4 -1
  380. package/src/components/Field/field-controls.spec.md +2 -1
  381. package/src/components/Field/field-wrapper.tsx +28 -8
  382. package/src/components/Field/field.anatomy.stories.tsx +7 -7
  383. package/src/components/Field/field.principles.stories.tsx +1 -1
  384. package/src/components/Field/field.spec.md +9 -7
  385. package/src/components/Field/field.tsx +2 -1
  386. package/src/components/Field/form-validation.spec.md +6 -3
  387. package/src/components/Field/use-form-validation.ts +49 -21
  388. package/src/components/FieldControlGroup/field-control-group.anatomy.stories.tsx +11 -10
  389. package/src/components/FieldControlGroup/field-control-group.principles.stories.tsx +3 -3
  390. package/src/components/FieldControlGroup/field-control-group.spec.md +3 -3
  391. package/src/components/FieldControlGroup/field-control-group.tsx +3 -3
  392. package/src/components/FileItem/file-item.anatomy.stories.tsx +6 -4
  393. package/src/components/FileItem/file-item.tsx +3 -2
  394. package/src/components/FileUpload/file-upload.anatomy.stories.tsx +1 -1
  395. package/src/components/FileUpload/file-upload.spec.md +4 -1
  396. package/src/components/FileUpload/file-upload.tsx +36 -10
  397. package/src/components/FileViewer/can-render-image.ts +15 -0
  398. package/src/components/FileViewer/file-viewer.anatomy.stories.tsx +6 -4
  399. package/src/components/FileViewer/file-viewer.spec.md +2 -2
  400. package/src/components/FileViewer/file-viewer.tsx +47 -21
  401. package/src/components/FileViewer/image-renderer.tsx +3 -8
  402. package/src/components/HoverCard/hover-card.spec.md +1 -1
  403. package/src/components/HoverCard/hover-card.tsx +3 -1
  404. package/src/components/Input/input.anatomy.stories.tsx +4 -4
  405. package/src/components/Input/input.principles.stories.tsx +1 -1
  406. package/src/components/Input/input.spec.md +1 -1
  407. package/src/components/Input/input.stories.tsx +11 -5
  408. package/src/components/Input/input.tsx +11 -9
  409. package/src/components/LinkInput/link-input.anatomy.stories.tsx +2 -0
  410. package/src/components/LinkInput/link-input.tsx +49 -34
  411. package/src/components/Menu/menu-item.anatomy.stories.tsx +1 -1
  412. package/src/components/Menu/menu-item.spec.md +4 -3
  413. package/src/components/Menu/menu-item.tsx +15 -4
  414. package/src/components/Notice/notice.anatomy.stories.tsx +10 -5
  415. package/src/components/Notice/notice.spec.md +4 -3
  416. package/src/components/Notice/notice.tsx +22 -5
  417. package/src/components/NumberInput/number-input.anatomy.stories.tsx +5 -4
  418. package/src/components/NumberInput/number-input.principles.stories.tsx +2 -1
  419. package/src/components/NumberInput/number-input.spec.md +2 -1
  420. package/src/components/NumberInput/number-input.tsx +25 -9
  421. package/src/components/OverflowIndicator/overflow-indicator.anatomy.stories.tsx +3 -3
  422. package/src/components/OverflowIndicator/overflow-indicator.spec.md +1 -1
  423. package/src/components/OverflowIndicator/overflow-indicator.tsx +2 -1
  424. package/src/components/Pagination/index.ts +5 -0
  425. package/src/components/Pagination/pagination.anatomy.stories.tsx +143 -0
  426. package/src/components/Pagination/pagination.principles.stories.tsx +81 -0
  427. package/src/components/Pagination/pagination.spec.md +150 -0
  428. package/src/components/Pagination/pagination.stories.tsx +98 -0
  429. package/src/components/Pagination/pagination.tsx +263 -0
  430. package/src/components/PeoplePicker/people-picker.anatomy.stories.tsx +1 -1
  431. package/src/components/PeoplePicker/people-picker.spec.md +4 -4
  432. package/src/components/PeoplePicker/people-picker.stories.tsx +1 -1
  433. package/src/components/PeoplePicker/people-picker.tsx +65 -55
  434. package/src/components/PeoplePicker/person-display.tsx +10 -4
  435. package/src/components/Popover/popover.anatomy.stories.tsx +2 -2
  436. package/src/components/Popover/popover.spec.md +13 -14
  437. package/src/components/Popover/popover.tsx +88 -39
  438. package/src/components/ProfileCard/profile-card.anatomy.stories.tsx +4 -4
  439. package/src/components/ProfileCard/profile-card.principles.stories.tsx +2 -2
  440. package/src/components/ProfileCard/profile-card.spec.md +1 -2
  441. package/src/components/ProfileCard/profile-card.stories.tsx +1 -1
  442. package/src/components/ProfileCard/profile-card.tsx +29 -8
  443. package/src/components/RadioGroup/radio-group.principles.stories.tsx +1 -1
  444. package/src/components/RadioGroup/radio-group.spec.md +2 -2
  445. package/src/components/RadioGroup/radio-group.stories.tsx +3 -1
  446. package/src/components/RadioGroup/radio-group.tsx +37 -3
  447. package/src/components/Rating/rating.anatomy.stories.tsx +11 -8
  448. package/src/components/Rating/rating.principles.stories.tsx +1 -1
  449. package/src/components/Rating/rating.spec.md +2 -2
  450. package/src/components/Rating/rating.stories.tsx +1 -1
  451. package/src/components/Rating/rating.tsx +3 -3
  452. package/src/components/ScrollArea/scroll-area.spec.md +1 -0
  453. package/src/components/ScrollArea/scroll-area.tsx +1 -1
  454. package/src/components/SegmentedControl/segmented-control.anatomy.stories.tsx +3 -3
  455. package/src/components/SegmentedControl/segmented-control.spec.md +7 -3
  456. package/src/components/SegmentedControl/segmented-control.tsx +29 -11
  457. package/src/components/Select/select.anatomy.stories.tsx +2 -2
  458. package/src/components/Select/select.spec.md +3 -2
  459. package/src/components/Select/select.tsx +62 -19
  460. package/src/components/SelectMenu/select-menu.anatomy.stories.tsx +1 -1
  461. package/src/components/SelectMenu/select-menu.principles.stories.tsx +1 -1
  462. package/src/components/SelectMenu/select-menu.spec.md +12 -8
  463. package/src/components/SelectMenu/select-menu.tsx +176 -20
  464. package/src/components/SelectionControl/selection-item.anatomy.stories.tsx +1 -1
  465. package/src/components/SelectionControl/selection-item.principles.stories.tsx +2 -2
  466. package/src/components/SelectionControl/selection-item.spec.md +2 -1
  467. package/src/components/SelectionControl/selection-item.stories.tsx +1 -1
  468. package/src/components/Separator/separator.anatomy.stories.tsx +2 -2
  469. package/src/components/Separator/separator.tsx +1 -1
  470. package/src/components/Sheet/sheet.spec.md +2 -1
  471. package/src/components/Sheet/sheet.tsx +4 -2
  472. package/src/components/Sidebar/sidebar.anatomy.stories.tsx +1 -1
  473. package/src/components/Sidebar/sidebar.spec.md +2 -1
  474. package/src/components/Sidebar/sidebar.stories.tsx +6 -4
  475. package/src/components/Sidebar/sidebar.tsx +41 -13
  476. package/src/components/Skeleton/skeleton.principles.stories.tsx +1 -1
  477. package/src/components/Skeleton/skeleton.spec.md +1 -2
  478. package/src/components/Skeleton/skeleton.tsx +1 -1
  479. package/src/components/Slider/slider.anatomy.stories.tsx +4 -4
  480. package/src/components/Slider/slider.spec.md +19 -85
  481. package/src/components/Slider/slider.tsx +13 -5
  482. package/src/components/Steps/steps.anatomy.stories.tsx +1 -1
  483. package/src/components/Steps/steps.spec.md +6 -4
  484. package/src/components/Steps/steps.tsx +11 -3
  485. package/src/components/Switch/switch.spec.md +4 -3
  486. package/src/components/Switch/switch.tsx +59 -10
  487. package/src/components/Tabs/tabs.anatomy.stories.tsx +7 -6
  488. package/src/components/Tabs/tabs.principles.stories.tsx +5 -3
  489. package/src/components/Tabs/tabs.spec.md +15 -48
  490. package/src/components/Tabs/tabs.tsx +70 -34
  491. package/src/components/Tag/tag.principles.stories.tsx +2 -2
  492. package/src/components/Tag/tag.spec.md +11 -43
  493. package/src/components/Tag/tag.tsx +30 -4
  494. package/src/components/Textarea/textarea.principles.stories.tsx +2 -1
  495. package/src/components/Textarea/textarea.spec.md +2 -1
  496. package/src/components/Textarea/textarea.tsx +20 -10
  497. package/src/components/TimePicker/time-picker.anatomy.stories.tsx +2 -1
  498. package/src/components/TimePicker/time-picker.spec.md +2 -2
  499. package/src/components/TimePicker/time-picker.tsx +35 -16
  500. package/src/components/Toast/toast.anatomy.stories.tsx +5 -6
  501. package/src/components/Toast/toast.principles.stories.tsx +10 -11
  502. package/src/components/Toast/toast.spec.md +5 -5
  503. package/src/components/Toast/toast.stories.tsx +5 -1
  504. package/src/components/Toast/toast.tsx +13 -7
  505. package/src/components/Tooltip/tooltip.spec.md +3 -3
  506. package/src/components/Tooltip/tooltip.tsx +1 -1
  507. package/src/components/TreeView/tree-view.anatomy.stories.tsx +6 -1
  508. package/src/components/TreeView/tree-view.principles.stories.tsx +2 -2
  509. package/src/components/TreeView/tree-view.spec.md +2 -2
  510. package/src/components/TreeView/tree-view.tsx +27 -5
  511. package/src/hooks/use-controllable.ts +7 -2
  512. package/src/hooks/use-is-touch-device.ts +8 -1
  513. package/src/index.ts +2 -1
  514. package/src/patterns/element-anatomy/element-anatomy.spec.md +3 -3
  515. package/src/patterns/element-anatomy/inline-action.spec.md +1 -1
  516. package/src/patterns/element-anatomy/item-anatomy.spec.md +5 -14
  517. package/src/patterns/element-anatomy/item-anatomy.stories.tsx +1 -0
  518. package/src/patterns/element-anatomy/item-anatomy.tsx +5 -6
  519. package/src/patterns/header-canonical/header-canonical.css +1 -2
  520. package/src/patterns/header-canonical/header-canonical.spec.md +5 -5
  521. package/src/patterns/horizontal-overflow/horizontal-overflow.spec.md +5 -4
  522. package/src/patterns/horizontal-overflow/horizontal-overflow.stories.tsx +137 -0
  523. package/src/patterns/overlay-surface/overlay-surface.spec.md +8 -7
  524. package/src/patterns/overlay-surface/overlay-surface.stories.tsx +116 -0
  525. package/src/patterns/overlay-surface/overlay-surface.tsx +5 -5
  526. package/src/patterns/resize-handle/resize-handle.spec.md +1 -1
  527. package/src/tokens/categorical-color.ts +1 -1
  528. package/src/tokens/color/color.spec.md +11 -13
  529. package/src/tokens/color/color.stories.tsx +1 -1
  530. package/src/tokens/color/primitives.css +7 -5
  531. package/src/tokens/color/semantic.css +8 -2
  532. package/src/tokens/density/density.spec.md +2 -0
  533. package/src/tokens/elevation/elevation.spec.md +1 -1
  534. package/src/tokens/layoutSpace/layoutSpace.spec.md +2 -1
  535. package/src/tokens/layoutSpace/layoutSpace.stories.tsx +3 -3
  536. package/src/tokens/motion/motion.spec.md +1 -1
  537. package/src/tokens/motion/motion.stories.tsx +142 -0
  538. package/src/tokens/opacity/opacity.spec.md +1 -1
  539. package/src/tokens/opacity/opacity.stories.tsx +7 -11
  540. package/src/tokens/radius/radius.spec.md +1 -1
  541. package/src/tokens/typography/typography.css +1 -1
  542. package/src/tokens/typography/typography.spec.md +1 -1
  543. package/src/tokens/uiSize/icon-size.ts +2 -2
  544. package/src/tokens/uiSize/uiSize.spec.md +12 -11
  545. package/src/tokens/uiSize/uiSize.stories.tsx +103 -0
  546. package/src/tokens/utility-registry.json +5 -4
  547. package/dist/node_modules/react-hook-form/dist/index.esm.js +0 -1950
  548. package/dist/node_modules/react-hook-form/dist/index.esm.js.map +0 -1
@@ -11,7 +11,7 @@ import { useControllable } from '@/design-system/hooks/use-controllable'
11
11
  import type { AvatarData } from '@/design-system/components/Avatar/avatar'
12
12
  import { Popover, PopoverContent, PopoverTrigger } from '@/design-system/components/Popover/popover'
13
13
  import { Command, CommandList, CommandEmpty, CommandGroup, CommandItem, CommandSeparator } from '@/design-system/components/Command/command'
14
- import { Command as CommandPrimitive } from 'cmdk'
14
+ import { Command as CommandPrimitive, useCommandState } from 'cmdk'
15
15
  import { MenuItem, MenuFooter } from '@/design-system/components/Menu/menu-item'
16
16
  import { Empty } from '@/design-system/components/Empty/empty'
17
17
  import { CircularProgress } from '@/design-system/components/CircularProgress/circular-progress'
@@ -86,10 +86,13 @@ export interface SelectMenuProps {
86
86
  searchPlaceholder?: string
87
87
  /** 空選項提示 */
88
88
  emptyText?: string
89
- /** Loading 狀態(2026-05-15 audit B fix per user verbatim「dropdown 隨時可開,讀取在 panel 中間 CircularProgress」)
90
- * true → render `<Empty icon={<CircularProgress size={48}/>} className="py-6" />` 取代 options(純 spinner,無 description);
91
- * trigger 不變,user 隨時可開 dropdown。對齊 MUI Autocomplete loading dropdown-body + Ant Select
92
- * loading idiom + DS 既有 `empty.spec.md:191` 「全頁 loading = Empty + CircularProgress compose」SSOT。
89
+ /** 多選 footer 全選列文字(2026-07-05 D4:原「全部」字面 hardcode,無法覆寫也無法 i18n) */
90
+ selectAllLabel?: string
91
+ /** Loading 狀態(2026-05-15 audit B fix;2026-07-04 Q3 拍板措辭修訂)
92
+ * true 無可顯示選項時 empty slot(cmdk CommandEmpty)渲 `<Empty icon={<CircularProgress size={48}/>} className="py-6" />`
93
+ * (純 spinner,無 description);已有 options 時保留顯示不清空(MUI Autocomplete「only if there are
94
+ * no suggestions」共識)。trigger 不變,user 隨時可開 dropdown。DS `empty.spec.md`「loading = Empty +
95
+ * CircularProgress compose」SSOT。
93
96
  */
94
97
  loading?: boolean
95
98
 
@@ -124,6 +127,25 @@ export interface SelectMenuProps {
124
127
  className?: string
125
128
  }
126
129
 
130
+ // ── SR live status(2026-07-05 D4:empty / loading 空狀態對 SR 不可感知修)──
131
+ // cmdk CommandEmpty 渲染為 role="presentation" div、cmdk 全鏈無 aria-live,且 DOM focus 停在
132
+ // combobox input(aria-activedescendant 虛擬焦點)→ SR 使用者搜尋到 0 結果或 loading 佔位時
133
+ // 聽不到任何播報(loading spinner 的 CircularProgress 無 label 時更是 aria-hidden)。
134
+ // 補 visually-hidden polite live region,鏡射 CommandEmpty 可見內容(cmdk Empty 只在
135
+ // filtered.count === 0 渲染:loading → 載入文案;否則 → emptyText)。對齊 react-select A11yText /
136
+ // APG combobox no-results 播報 + empty.spec.md「動態 filter no-results 容器需 aria-live="polite"」。
137
+ // SSOT 放 SelectMenu 一處 → Select / Combobox / PeoplePicker 全體受益。
138
+ function SelectMenuLiveStatus({ loading, emptyText }: { loading: boolean; emptyText: string }) {
139
+ const filteredCount = useCommandState((state) => state.filtered.count)
140
+ return (
141
+ <div role="status" aria-live="polite" className="sr-only">
142
+ {filteredCount === 0
143
+ ? (loading ? '載入中…' /* i18n-allow: DS default SR-only 播報文案 */ : emptyText)
144
+ : null}
145
+ </div>
146
+ )
147
+ }
148
+
127
149
  // shadcn canonical:forwardRef + displayName 統一。SelectMenu 是 Popover + Command
128
150
  // composite,自身無 DOM host(trigger 由 consumer 以 asChild children 提供),ref 簽名
129
151
  // 保留但不附著(consumer 想取 trigger DOM 直接在 children 上自己 ref)。className 合併到
@@ -141,6 +163,7 @@ const SelectMenu = React.forwardRef<HTMLElement, SelectMenuProps>(function Selec
141
163
  children,
142
164
  searchPlaceholder = '搜尋…', // i18n-allow: DS default; consumer override via searchPlaceholder prop
143
165
  emptyText = '沒有符合的選項', // i18n-allow: DS default; consumer override via emptyText prop
166
+ selectAllLabel = '全部', // i18n-allow: DS default; consumer override via selectAllLabel prop
144
167
  loading = false,
145
168
  size = 'md',
146
169
  align = 'start',
@@ -177,6 +200,12 @@ const SelectMenu = React.forwardRef<HTMLElement, SelectMenuProps>(function Selec
177
200
  [selectedValues]
178
201
  )
179
202
 
203
+ // 2026-07-05 P2:單選已選 option — 供 cmdk defaultValue 定 cursor 起點(見下方 <Command>)
204
+ const selectedOption = React.useMemo(
205
+ () => (!multiple ? options.find((o) => o.value === selectedValues[0]) : undefined),
206
+ [multiple, options, selectedValues]
207
+ )
208
+
180
209
  const handleSelect = React.useCallback(
181
210
  (optionValue: string) => {
182
211
  if (multiple) {
@@ -316,7 +345,18 @@ const SelectMenu = React.forwardRef<HTMLElement, SelectMenuProps>(function Selec
316
345
  }
317
346
  }}
318
347
  >
319
- <Command shouldFilter={searchable} className="bg-transparent">
348
+ <Command
349
+ shouldFilter={searchable}
350
+ // 2026-07-06 cursor 起點修:單選已有值時 cmdk virtual focus 落在已選項而非第一項。
351
+ // cmdk 1.1.1 初始 state 取 defaultValue、item mount 的 selectFirstItem 有
352
+ // `state.value ||` guard 不覆蓋(dist source 驗證);Popover 關閉即 unmount(無
353
+ // forceMount)→ 每次開啟 remount 重新生效。需搭配下方 CommandItem value={opt.value}。
354
+ defaultValue={selectedOption?.value}
355
+ // 2026-07-06 A11y:combobox accessible name — cmdk 永遠渲 sr-only <label htmlFor={inputId}>,
356
+ // 僅 searchable(真的有 input)時傳,避免 orphan label 指向不存在的 input id。
357
+ label={searchable ? searchPlaceholder : undefined}
358
+ className="bg-transparent"
359
+ >
320
360
  {searchable && (
321
361
  <div className={cn(
322
362
  'flex items-center gap-2 px-3 py-1 border-b border-divider',
@@ -343,7 +383,15 @@ const SelectMenu = React.forwardRef<HTMLElement, SelectMenuProps>(function Selec
343
383
  user 過濾出 < minRows 個 match 時 list 底下空一片(eg. 打 'c' 出 2 個 match
344
384
  卻撐高到 3 row 容量,1 row 留白)。 Fix:只有 empty state 才需要 minHeight 撐
345
385
  起 placeholder 視覺;有 results 時 CommandList 自然 fit content。 */}
346
- <CommandList className="relative">
386
+ {/* aria-busy(2026-07-04):loading 時標注 listbox 忙碌——兌現 select.spec.md「Loading」段
387
+ 「+ aria-busy」承諾(cmdk List 本身即 role="listbox" 容器,wrapper forward props)。 */}
388
+ <CommandList
389
+ className="relative"
390
+ aria-busy={loading || undefined}
391
+ // 2026-07-06 A11y:cmdk List 的 aria-label 由其 `label` prop 渲染(內部 spread 後 override,
392
+ // 直接傳 aria-label 會被 cmdk default "Suggestions" 蓋掉 silent 失效)— 必走 label prop。
393
+ label="選項" // i18n-allow: DS default; listbox accessible name
394
+ >
347
395
  <CommandEmpty
348
396
  className="flex items-center justify-center"
349
397
  style={{ minHeight: getMenuListMinHeight(size, minRows) }}
@@ -356,18 +404,35 @@ const SelectMenu = React.forwardRef<HTMLElement, SelectMenuProps>(function Selec
356
404
  {groupedOptions.map((group, gi) => (
357
405
  <React.Fragment key={group.key}>
358
406
  {gi > 0 && <CommandSeparator />}
359
- <CommandGroup className="p-0 py-2">
360
- {group.label && (
361
- <MenuItem size={size} header>{group.label}</MenuItem>
362
- )}
407
+ <CommandGroup
408
+ className="p-0 py-2 [&_[cmdk-group-heading]]:p-0"
409
+ // 2026-07-06 A11y:群組標題走 cmdk `heading`(自動產 cmdk-group-heading id,選項容器
410
+ // role="group" + aria-labelledby 指向之)取代手刻 child row(原本 AT 不可感知;
411
+ // aria-label fallback 對 role="presentation" group 無效)。[&_[cmdk-group-heading]]:p-0
412
+ // 中和 command.tsx base 的 px-3 py-1.5(tailwind-merge p-0 勝);字級 / 顏色由 MenuItem
413
+ // header 自帶(text-body leading-compact + font-medium + text-fg-muted),視覺不變。
414
+ heading={group.label ? <MenuItem size={size} header>{group.label}</MenuItem> : undefined}
415
+ >
363
416
  {group.options.map((opt) => (
364
417
  <CommandItem
365
418
  key={opt.value}
366
- value={opt.label}
367
- keywords={opt.description ? [opt.description] : undefined}
419
+ // 2026-07-06 識別值修:cmdk 以 value 當 item identity + selection state。原用
420
+ // opt.label 重複 label 時雙亮 + Enter 選錯;改唯一的 opt.value,label 移入
421
+ // keywords 保搜尋照樣命中(cmdk filter 對 value + keywords 計分)。
422
+ value={opt.value}
423
+ keywords={opt.description ? [opt.label, opt.description] : [opt.label]}
368
424
  disabled={opt.disabled}
369
425
  onSelect={() => handleSelect(opt.value)}
370
- className="p-0 rounded-none data-[selected=true]:bg-transparent"
426
+ // 2026-07-05 D4 P0 修:原 data-[selected=true]:bg-transparent 蓋掉 command.tsx base
427
+ // 的 data-[selected=true]:bg-neutral-hover = cmdk virtual focus(aria-activedescendant)
428
+ // 鍵盤 cursor 唯一視覺通道被抹掉 → 方向鍵移動畫面零變化(影響 Select/Combobox/PeoplePicker
429
+ // 全家)。鏡射 DropdownMenu wrapper pattern:互動 bg 單一 owner 在外層 CommandItem
430
+ //(base highlight 恢復;persistent selection 上移;selected×cursor 加深一階 —
431
+ // 對齊本日 DropdownMenu Q2 決策),內層 MenuItem 全透明。
432
+ className={cn(
433
+ 'p-0 rounded-none',
434
+ !multiple && isSelected(opt.value) && 'bg-neutral-selected data-[selected=true]:bg-neutral-selected-active',
435
+ )}
371
436
  >
372
437
  <MenuItem
373
438
  size={size}
@@ -378,6 +443,13 @@ const SelectMenu = React.forwardRef<HTMLElement, SelectMenuProps>(function Selec
378
443
  checked={isSelected(opt.value)}
379
444
  selected={!multiple && isSelected(opt.value)}
380
445
  disabled={opt.disabled}
446
+ // 2026-07-05 D4:巢狀 role 修 — MenuItem 預設 role="option" + aria-selected(persistent
447
+ // selection)巢狀在 cmdk CommandItem(本身 role="option" + aria-selected=cursor)內,
448
+ // AT 讀到 option 包 option 且內外 aria-selected 語意相反 → 朗讀錯亂。鏡射 DropdownMenu
449
+ // canonical(dropdown-menu.tsx「Pure visual — Radix parent handles role/aria」):
450
+ // 內層純視覺 role="presentation",cmdk CommandItem 是唯一 option 節點。
451
+ role="presentation"
452
+ className="!bg-transparent hover:!bg-transparent"
381
453
  >
382
454
  {renderLabel ? renderLabel(opt) : opt.label}
383
455
  </MenuItem>
@@ -393,14 +465,20 @@ const SelectMenu = React.forwardRef<HTMLElement, SelectMenuProps>(function Selec
393
465
  <CommandSeparator />
394
466
  <CommandGroup className="p-0 py-2">
395
467
  <CommandItem
396
- value={search}
468
+ // 2026-07-06:`__create__` 前綴防 identity 撞名 — 選項 row 改用 opt.value 識別後,
469
+ // search 恰等於某 option.value 時裸 search 會與該 row 同 value(cmdk 雙亮 + Enter
470
+ // 選錯)。value 含 search 子字串,cmdk filter 照樣命中;onSelect closure 讀 search
471
+ // 不受影響。
472
+ value={`__create__${search}`}
397
473
  onSelect={() => {
398
474
  onCreate?.(search.trim())
399
475
  setSearch('')
400
476
  }}
401
- className="p-0 rounded-none data-[selected=true]:bg-transparent"
477
+ className="p-0 rounded-none"
402
478
  >
403
- <MenuItem size={size} startIcon={Plus}>
479
+ {/* 2026-07-05 D4:同上方選項 row 的巢狀 role 修 — creatable row 同樣是
480
+ MenuItem 巢狀在 cmdk CommandItem(option)內,內層純視覺 role="presentation"。 */}
481
+ <MenuItem size={size} startIcon={Plus} role="presentation" className="!bg-transparent hover:!bg-transparent">
404
482
  {createLabel(search.trim())}
405
483
  </MenuItem>
406
484
  </CommandItem>
@@ -409,18 +487,38 @@ const SelectMenu = React.forwardRef<HTMLElement, SelectMenuProps>(function Selec
409
487
  )}
410
488
  </CommandList>
411
489
 
490
+ {/* SR 播報 empty / loading 空狀態(見 SelectMenuLiveStatus docblock,2026-07-05 D4) */}
491
+ <SelectMenuLiveStatus loading={loading} emptyText={emptyText} />
492
+
412
493
  {/* Multi-select footer: Select All
413
494
  - 沒有選項時不顯示(selectableOptions.length === 0)
414
495
  - 搜尋有文字時不顯示(search 非空 = 使用者在找特定項目,「全選」沒意義) */}
415
496
  {multiple && selectableOptions.length > 0 && !search && (
416
497
  <MenuFooter>
498
+ {/* 2026-07-05 D4:全選列鍵盤可達修 — 原裸 MenuItem(div 預設 role="option" 無 tabIndex)
499
+ 位於 CommandList 之外:cmdk 方向鍵只導覽 [cmdk-item]、Tab 也到不了 div → 鍵盤使用者
500
+ 完全無法操作全選(WCAG 2.1.1),且該 role="option" 無 listbox 祖先(orphan,axe
501
+ aria-required-parent)。改真實 focusable checkbox 語意:tabIndex=0 + role="checkbox" +
502
+ aria-checked(indeterminate → "mixed")+ Enter / Space 觸發;preventDefault 讓 cmdk root
503
+ onKeyDown(源碼檢查 e.defaultPrevented)不會再對 active option 重複觸發 Enter。
504
+ aria-selected 顯式蓋回 undefined(MenuItem 內建 aria-selected 對 role="checkbox" 無效)。 */}
417
505
  <MenuItem
418
506
  size={size}
419
507
  checkbox
420
508
  checked={allState}
421
509
  onClick={handleSelectAll}
510
+ role="checkbox"
511
+ aria-checked={allState === 'indeterminate' ? 'mixed' : allState}
512
+ aria-selected={undefined}
513
+ tabIndex={0}
514
+ onKeyDown={(e) => {
515
+ if (e.key === 'Enter' || e.key === ' ') {
516
+ e.preventDefault()
517
+ handleSelectAll()
518
+ }
519
+ }}
422
520
  >
423
- 全部
521
+ {selectAllLabel}
424
522
  </MenuItem>
425
523
  </MenuFooter>
426
524
  )}
@@ -444,12 +542,70 @@ export const selectMenuMeta = {
444
542
  sizes: {
445
543
 
446
544
  },
447
- states: ['default', 'hover', 'active', 'focus-visible', 'disabled'],
545
+ // 'selected' = 單選 option 持續選中(bg-neutral-selected);'active' 保留 — cmdk virtual-focus on
546
+ // selected 走 bg-neutral-selected-active(active 階 token;2026-07-07 詞彙統一補修)。
547
+ states: ['default', 'hover', 'active', 'selected', 'focus-visible', 'disabled'],
448
548
  tokens: {
449
- bg: ['bg-surface-raised', 'bg-transparent'],
549
+ bg: ['bg-neutral-selected', 'bg-neutral-selected-active', 'bg-surface-raised', 'bg-transparent'],
450
550
  fg: ['text-fg-muted'],
451
551
  ring: [],
452
552
  },
453
553
  } as const
454
554
 
555
+ /**
556
+ * 2026-07-05 D4 P0 修(searchable 鍵盤死路):trigger 內的裸 <input> 與 portal 內的 cmdk root
557
+ * 在不同 DOM 子樹 — 鍵盤事件永遠 bubble 不到 cmdk 的 ArrowUp/Down/Enter handler([cmdk-root]
558
+ * onKeyDown)→ searchable Select / PeoplePicker single / Combobox searchIn='trigger' 開啟後
559
+ * 只能 Esc。修法 = APG combobox-with-list:trigger input 把三鍵 re-dispatch 給 cmdk root
560
+ * (native KeyboardEvent bubbles 經 React root delegation 觸發 cmdk synthetic handler)。
561
+ * Home/End 刻意不轉送(文字輸入的 caret 語意優先,對齊 MUI/Ant Autocomplete)。
562
+ * aria-activedescendant 綁回 trigger input → 見下方 useActiveDescendant(2026-07-05 D4 補齊)。
563
+ */
564
+ export function forwardKeyToListbox(contentId: string | undefined, e: React.KeyboardEvent): boolean {
565
+ if (!contentId) return false
566
+ if (e.key !== 'ArrowDown' && e.key !== 'ArrowUp' && e.key !== 'Enter') return false
567
+ const root = document.getElementById(contentId)?.querySelector<HTMLElement>('[cmdk-root]')
568
+ if (!root) return false
569
+ e.preventDefault()
570
+ root.dispatchEvent(new KeyboardEvent('keydown', { key: e.key, bubbles: true, cancelable: true }))
571
+ return true
572
+ }
573
+
574
+ /**
575
+ * 2026-07-05 D4 補齊(APG combobox aria-activedescendant):追蹤 cmdk 目前 virtual-focus item
576
+ * 的 DOM id,供 trigger 端搜尋 input 綁 `aria-activedescendant` —— SR 才會在方向鍵導覽 /
577
+ * 打字過濾時播報 active option 名。機制:trigger 與 portal 內 cmdk 分屬不同 DOM 子樹,cmdk
578
+ * 只把 active id 綁在自己的 Command.Input / List(cmdk source:item 自帶 auto-generated id +
579
+ * `data-selected="true"` 標記 virtual focus)→ trigger 端用 MutationObserver 監聽 popover 容器
580
+ * (contentId = PopoverContent id)內 `data-selected` 屬性變化 + childList(打字過濾 re-render
581
+ * 換 item 節點),單一機制涵蓋全部更新路徑:開啟初始 auto-highlight / forwardKeyToListbox
582
+ * 方向鍵轉送 / 搜尋過濾後 cmdk 自動移 cursor / pointer hover。
583
+ * 關閉時清 undefined —— ARIA 要求 id 必指向存在於 DOM 的節點,不可留 stale id。
584
+ */
585
+ export function useActiveDescendant(contentId: string | undefined, open: boolean): string | undefined {
586
+ const [activeId, setActiveId] = React.useState<string | undefined>(undefined)
587
+ React.useEffect(() => {
588
+ if (!open || !contentId) {
589
+ setActiveId(undefined)
590
+ return
591
+ }
592
+ // PopoverContent 與 trigger 同一個 React commit mount(open state 同批 render)→ effect 跑時已在 DOM
593
+ const container = document.getElementById(contentId)
594
+ if (!container) return
595
+ const read = () => {
596
+ setActiveId(container.querySelector<HTMLElement>('[cmdk-item][data-selected="true"]')?.id || undefined)
597
+ }
598
+ // 初始補讀:MutationObserver 只看「觀察開始後」的變化;cmdk 初始 auto-highlight(layout effect
599
+ // 排程)可能已 commit → rAF 讀當下狀態兜底,與 observer 互補、誰先到都不漏。
600
+ const raf = requestAnimationFrame(read)
601
+ const observer = new MutationObserver(read)
602
+ observer.observe(container, { subtree: true, childList: true, attributes: true, attributeFilter: ['data-selected'] })
603
+ return () => {
604
+ cancelAnimationFrame(raf)
605
+ observer.disconnect()
606
+ }
607
+ }, [open, contentId])
608
+ return activeId
609
+ }
610
+
455
611
  export { SelectMenu }
@@ -565,7 +565,7 @@ export const Accessibility = {
565
565
  render: () => (
566
566
  <div className="max-w-3xl text-body text-fg-secondary">
567
567
  <h3 className="text-h5 text-foreground mb-2">無障礙設計</h3>
568
- <p className="whitespace-pre-line">{"SelectionItem 是純佈局元件,本身不接管鍵盤與焦點——它只渲染外層容器加上指向控件的 label(label 的 htmlFor 連到控件 id,點文字會觸發控件)。\n\n鍵盤操作、焦點環、勾選狀態都由傳入的控件(Checkbox / Radio)負責:\n\n- Tab — 焦點落在控件本身\n- Space — 由控件切換勾選狀態\n- 焦點環(outline 2px,顏色取自 var(--ring))由控件自己畫\n\n搭配建議:\n\n- 控件對齊 [W3C ARIA Authoring Practices Guide](https://www.w3.org/WAI/ARIA/apg/patterns/) 對應的 checkbox / radio pattern。\n- 用 label 的 htmlFor 連到控件 id,讓點擊文字也能操作。\n- 驗證:Storybook 無障礙面板應 0 項嚴重違規;不靠滑鼠也能完整操作。文字對比 ≥ 4.5:1,介面元素對比 ≥ 3:1(WCAG AA)。"}</p>
568
+ <p className="whitespace-pre-line">{"SelectionItem 是純佈局元件,本身不接管鍵盤與焦點——它只渲染外層容器加上指向控件的 label(label 的 htmlFor 連到控件 id,點文字會觸發控件)。\n\n鍵盤操作、焦點環、勾選狀態都由傳入的控件(Checkbox / Radio)負責:\n\n- Tab — 焦點落在控件本身\n- Space — 由控件切換勾選狀態\n- 焦點環(ring 2px box-shadow,顏色取自 var(--ring))由控件自己畫\n\n搭配建議:\n\n- 控件對齊 [W3C ARIA Authoring Practices Guide](https://www.w3.org/WAI/ARIA/apg/patterns/) 對應的 checkbox / radio pattern。\n- 用 label 的 htmlFor 連到控件 id,讓點擊文字也能操作。\n- 驗證:Storybook 無障礙面板應 0 項嚴重違規;不靠滑鼠也能完整操作。文字對比 ≥ 4.5:1,介面元素對比 ≥ 3:1(WCAG AA)。"}</p>
569
569
  </div>
570
570
  ),
571
571
  }
@@ -43,10 +43,10 @@ export const UsageGuidance: Story = {
43
43
  <div className="prose prose-sm max-w-prose">
44
44
  <p>兩者表達 selectable state,但結構與場景不同:</p>
45
45
  <ul>
46
- <li><strong>SelectionControl(本元件)</strong>—大塊 row(含 icon / avatar / multi-line description),適合 settings / list-as-page;Notion/Slack idiom</li>
46
+ <li><strong>SelectionControl rich row(本 primitive,經 Checkbox / RadioGroup 消費)</strong>—大塊 row(含 icon / avatar / multi-line description),適合 settings / list-as-page;Notion/Slack idiom</li>
47
47
  <li><strong>Checkbox + Field</strong>—緊湊 form 欄位;適合 form group 多選</li>
48
48
  </ul>
49
- <p className="text-fg-muted">判斷:row 是 page content(讀取/設定)→ SelectionControl;row 是 form input(submit value)→ Checkbox + Field。</p>
49
+ <p className="text-fg-muted">判斷:row 是 page content(讀取/設定)→ Checkbox / RadioGroup 的 rich-row 形態(內部消費 SelectionItem,不裸用 &lt;SelectionItem&gt;);row 是 form input(submit value)→ Checkbox + Field。</p>
50
50
  </div>
51
51
  </div>
52
52
  ),
@@ -61,6 +61,7 @@ Checkbox 和 Radio 視覺幾乎完全一致(差異只在形狀 `rounded-md` vs
61
61
  - `flex items-start gap-2`:控件與 content 對齊第一行
62
62
  - 控件包在 `h-[1lh]` 容器中,跟第一行文字垂直置中
63
63
  - 多行 label 時,控件保持對齊第一行(不跳到中間)
64
+ - **例外——avatar block 模式**:`avatar` + `description`(block avatar 32/32/40px)時,控件與 avatar 一起切 text-block 垂直置中,不對齊第一行——「selection + identity」是同一視覺單元,不歪斜(SSOT:selection-item.tsx `useBlock` jsDoc;anatomy story「Prefix 對齊規則」)
64
65
 
65
66
  ### 字體
66
67
 
@@ -127,7 +128,7 @@ SelectionItem 是純佈局元件,本身**不接管**鍵盤、焦點與勾選狀
127
128
  - Tab — focus 落在 control 本身
128
129
  - Space — 由 control 切換勾選狀態
129
130
 
130
- **Focus**:focus-visible ring 對齊 DS canonical(`outline: 2px solid var(--ring)`),由 control 自己畫;SelectionItem 不獨立 own focus management。
131
+ **Focus**:focus-visible ring control 自己畫——Checkbox / Radio 顯式 `focus-visible:outline-none` + `focus-visible:ring-2 focus-visible:ring-ring`(box-shadow ring,2px、`var(--ring)` 色;checkbox.tsx / radio-group.tsx 控件層 canonical,非全域 `outline` 預設);SelectionItem 不獨立 own focus management。
131
132
 
132
133
  **驗證**:Storybook a11y addon panel 應 0 critical violation;鍵盤完整可操作(無需滑鼠)。WCAG AA contrast ≥ 4.5:1(text)/ 3:1(UI)。
133
134
 
@@ -1,4 +1,4 @@
1
- // @story-trait-rationale: SelectionControl 是 isInternal primitive,被 Checkbox / RadioGroup 消費;hasSizes 由 anatomy.stories.tsx SizeMatrix auto-compile owns size showcase(2026-05-15 F-migration);Default 由 NotificationPreferences / PlanPicker 等真實 consumer 情境覆蓋。
1
+ // @story-trait-rationale: SelectionControl 是 isInternal primitive,被 Checkbox / RadioGroup 消費;本檔為 primitive 自家 Internal 結構文件,裸用 <SelectionItem control={...}> 為 API 展示豁免(consumer 仍走 wrapper 形態 <Checkbox icon>/<RadioGroupItem avatar>,spec「不准裸用」;2026-07-04 audit 判定);hasSizes 由 anatomy.stories.tsx SizeMatrix auto-compile owns size showcase(2026-05-15 F-migration);Default 由 NotificationPreferences / PlanPicker 等真實 consumer 情境覆蓋。
2
2
  import type { Meta, StoryObj } from '@storybook/react'
3
3
  import { Mail, Bell, Folder, Shield } from 'lucide-react'
4
4
  import { SelectionItem } from './selection-item'
@@ -30,7 +30,7 @@ export const Overview: Story = {
30
30
  <div className="flex flex-col gap-10">
31
31
  <div>
32
32
  <H3>Anatomy</H3>
33
- <Desc>Separator 是語意分隔元件——consumer 手動放置的分隔線。基於 Radix Separator(shadcn passthrough),提供正確的 ARIA 語意:預設 `decorative=true` 時 render `role="none"`(SR 跳過),`decorative={false}` 時 render `role="separator"` + orientation 語意。元件固定結構(header/footer 邊框)和裝飾性邊框**不使用 Separator**。</Desc>
33
+ <Desc>Separator 是語意分隔元件——consumer 手動放置的分隔線。基於 Radix Separator(shadcn passthrough),提供正確的 ARIA 語意:預設 `decorative=true` 時 render `role="none"`(SR 跳過),`decorative=&#123;false&#125;` 時 render `role="separator"` + orientation 語意。元件固定結構(header/footer 邊框)和裝飾性邊框**不使用 Separator**。</Desc>
34
34
  <div className="border border-border rounded-lg p-4 max-w-md">
35
35
  <div className="flex flex-col gap-2 py-2">
36
36
  <div className="text-body">個人資訊</div>
@@ -100,7 +100,7 @@ export const TokenMatrix: Story = {
100
100
  <table className="text-caption border-collapse">
101
101
  <thead><tr><Th>決策者</Th><Th>使用</Th><Th>理由</Th></tr></thead>
102
102
  <tbody>
103
- <tr><Td>Consumer 手動放置</Td><Td mono>&lt;Separator /&gt;</Td><Td>需要語意標記(role="separator")讓輔助技術辨識</Td></tr>
103
+ <tr><Td>Consumer 手動放置</Td><Td mono>&lt;Separator /&gt;</Td><Td>分隔語意由放置者宣告——預設 decorative(role="none",SR 跳過),真實切分內容區段時 decorative=&#123;false&#125; 才輸出 role="separator"</Td></tr>
104
104
  <tr><Td>元件自動分隔相鄰群組</Td><Td mono>CSS [&+&]</Td><Td>MenuGroup / SidebarGroup——無處插入 DOM node</Td></tr>
105
105
  <tr><Td>元件固定結構</Td><Td mono>CSS border-t/b</Td><Td>Dialog Header/Footer、Sidebar Header/Footer——結構的一部分</Td></tr>
106
106
  <tr><Td>控件外框 / 容器輪廓</Td><Td mono>CSS border</Td><Td>Input 外框、Card 容器——不分隔內容,是元件邊緣</Td></tr>
@@ -37,7 +37,7 @@ export const separatorMeta = {
37
37
  sizes: {
38
38
 
39
39
  },
40
- states: ['default'], // 純 Radix Separator.Root,非互動,無 hover/active/focus/disabled(對齊 spec L86/L99 + Tag tag.tsx:232 canonical)
40
+ states: ['default'], // 純 Radix Separator.Root,非互動,無 hover/active/focus/disabled(對齊 spec「無 StateBehavior」bullet + A11y 段;段名指法免行號漂移)
41
41
  tokens: {
42
42
  bg: [],
43
43
  fg: [],
@@ -104,7 +104,7 @@ Sheet 為容器,內容由 consumer 決定;focus trap + Escape close + `aria-labe
104
104
  ## 邊界案例
105
105
 
106
106
  - **Disabled**:Sheet 本身無 disabled state(非互動 control,是 surface container);trigger Button 由 consumer 控 disabled。
107
- - **Loading(panel content loading)**:Sheet 為純容器,不獨立 own loading state。Async body 場景由 consumer 在 body slot 渲 `<Empty icon={<CircularProgress size={48}/>}/>`(對齊 `empty.spec.md:168` loading compose SSOT)或 `<Skeleton>` line-stack 取代 content,不開 / 不關 Sheet 即可。
107
+ - **Loading(panel content loading)**:Sheet 為純容器,不獨立 own loading state。Async body 場景由 consumer 在 body slot 渲 `<Empty icon={<CircularProgress size={48}/>}/>`(對齊 `empty.spec.md:167` loading compose SSOT)或 `<Skeleton>` line-stack 取代 content,不開 / 不關 Sheet 即可。
108
108
  - **Empty(no content)**:Sheet 必含 title(`aria-labelledby` 強制要求);body 為空時 consumer 應渲 `<Empty>` placeholder,不渲空白 panel。
109
109
  - **Scroll(body 內容超過視口高度)**:`SheetBody` = `<ScrollArea>`(`flex-1 min-h-0`),超高內容在 body 內部捲動,header / footer 固定不動;consumer 不需自寫 `overflow-y-auto`。
110
110
  - **誤嵌套降級(nested Sheet)**:禁止事項已禁 nested;若 consumer 不慎嵌套,Radix 的 Esc 與 focus trap 只作用於**最上層** layer(DismissableLayer 只 dismiss highest layer;FocusScope stack 最新 scope active),關閉由上而下逐層。
@@ -143,4 +143,5 @@ Sheet 為容器,內容由 consumer 決定;focus trap + Escape close + `aria-labe
143
143
  - `accordion.spec.md`
144
144
  - `app-shell.spec.md`
145
145
  - `dialog.spec.md`
146
+ - `popover.spec.md`
146
147
  - `scroll-area.spec.md`
@@ -50,8 +50,9 @@ const SheetOverlay = React.forwardRef<
50
50
  React.ComponentPropsWithoutRef<typeof SheetPrimitive.Overlay>
51
51
  >(({ className, ...props }, ref) => (
52
52
  <SheetPrimitive.Overlay
53
+ // motion-reduce:animate-none — prefers-reduced-motion 豁免,對齊 DialogOverlay canonical(dialog.tsx DialogOverlay 同 token,2026-07-05 P2 收乾)
53
54
  className={cn(
54
- "fixed inset-0 z-50 bg-overlay data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
55
+ "fixed inset-0 z-50 bg-overlay data-[state=open]:animate-in data-[state=closed]:animate-out motion-reduce:animate-none data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
55
56
  className
56
57
  )}
57
58
  {...props}
@@ -217,7 +218,8 @@ export const sheetMeta = {
217
218
  sizes: {
218
219
 
219
220
  },
220
- states: ['default', 'hover', 'active', 'focus-visible', 'disabled'],
221
+ // 2026-07-04 audit 對齊:Sheet 本身無 disabled state(surface container,spec 邊界案例明文;對齊 popover/dialog meta pattern)
222
+ states: ['default'],
221
223
  tokens: {
222
224
  bg: ['bg-surface-raised'],
223
225
  fg: ['text-fg-secondary', 'text-foreground'],
@@ -766,7 +766,7 @@ export const Accessibility = {
766
766
  render: () => (
767
767
  <div className="max-w-3xl text-body text-fg-secondary">
768
768
  <h3 className="text-h5 text-foreground mb-2">無障礙設計</h3>
769
- <p className="whitespace-pre-line">{"- 收合切換鍵:Cmd+B(Mac)/ Ctrl+B(Windows)是業界慣例(VS Code / Linear / shadcn 都用),Sidebar 內建並會攔下這組鍵,避免穿透到瀏覽器書籤列。任何焦點位置都能觸發;目前未排除文字輸入框——在 input / textarea 內按下仍會 toggle(無 disableShortcut opt-out)。\n\n- 收合按鈕:SidebarTrigger 內建 aria-label「Toggle Sidebar」,讓螢幕報讀使用者知道按鈕用途;consumer 可傳 aria-label 覆寫(i18n)。SidebarGroupLabel 的收合 chevron 另有 toggleAriaLabel prop(預設「展開或收合」)。\n\n- 當前項目:選中的 menu item 帶 data-active,呈現選取底色(bg-neutral-selected)作為視覺指引;即使收合成 icon-only 模式,選取底色仍然保留,使用者一眼知道現在在哪一頁。\n\n- 手機尺寸:768px 以下自動切換成從旁滑出的抽屜(Sheet),內建焦點鎖定、Esc 關閉、關閉後焦點回到觸發按鈕。"}</p>
769
+ <p className="whitespace-pre-line">{"- 收合切換鍵:Cmd+B(Mac)/ Ctrl+B(Windows)是業界慣例(VS Code / Linear / shadcn 都用),Sidebar 內建並會攔下這組鍵,避免穿透到瀏覽器書籤列。任何焦點位置都能觸發;目前未排除文字輸入框——在 input / textarea 內按下仍會 toggle(無 disableShortcut opt-out)。\n\n- 收合按鈕:SidebarTrigger 內建 aria-label「切換側邊欄」,讓螢幕報讀使用者知道按鈕用途;consumer 可傳 aria-label 覆寫(i18n)。SidebarGroupLabel 的收合 chevron 另有 toggleAriaLabel prop(預設「展開或收合」)。\n\n- 當前項目:選中的 menu item 帶 data-active,呈現選取底色(bg-neutral-selected)作為視覺指引;即使收合成 icon-only 模式,選取底色仍然保留,使用者一眼知道現在在哪一頁。\n\n- 手機尺寸:768px 以下自動切換成從旁滑出的抽屜(Sheet),內建焦點鎖定、Esc 關閉、關閉後焦點回到觸發按鈕。"}</p>
770
770
  </div>
771
771
  ),
772
772
  }
@@ -685,7 +685,8 @@ Item-level default / hover / selected / disabled **色彩**完全共用 item-ana
685
685
 
686
686
  - **Landmark**:`<Sidebar>` 目前渲染 `<div>`(三種 collapsible 分支皆然),**不自帶** `<nav>` 元素或 `role="navigation"`、`aria-label`。consumer 若要 page-level navigation landmark,需自行用 `<nav aria-label="Main">` 包住 `<Sidebar>`(或對導覽區塊 `<SidebarGroup>` 加 `role="navigation"`)。
687
687
  - **Active item**:`SidebarMenuButton` 帶 `id` + 命中 `activeId` 時自動加 `data-active="true"`,呈現選取底色(`bg-neutral-selected`)。**目前僅視覺 + `data-active` attribute,不設 `aria-current="page"`**——SR 讀不到「current page」語意。`variant="meta"` 永不參與 selection(`data-active` 永遠 false)。
688
- - **快捷鍵不衝突**:`Cmd+B` / `Ctrl+B` 是 industry-standard(VS Code / Linear / shadcn),DS 內建在 `SidebarProvider` 的 `window` keydown handler,只判斷 `key==="b" && (metaKey||ctrlKey)` `preventDefault` + toggle,避免穿透到 browser bookmark bar(`Cmd+B` 在 Safari 是 favorites)。**目前無 opt-out prop**( `disableShortcut`)、**也未排除文字輸入框內**——在 `<input>` / `<textarea>` 內按 `Cmd/Ctrl+B` 仍會 toggle 並攔掉預設行為。
688
+ - **快捷鍵不衝突**:`Cmd+B` / `Ctrl+B` 是 industry-standard(VS Code / Linear / shadcn),DS 內建在 `SidebarProvider` 的 `window` keydown handler,判斷 `key==="b" && (metaKey||ctrlKey)` `preventDefault` + toggle,避免穿透到 browser bookmark bar(`Cmd+B` 在 Safari 是 favorites)。**三道 guard(2026-07-05 D4 修)**:(1) `defaultPrevented`——app 層 editor 已處理同快捷鍵時不 double-fire;(2) `isComposing`——IME 組字中不攔;(3) 可編輯 target——`<input>` / `<textarea>` / `contentEditable` 內按 `Cmd/Ctrl+B` toggle(bold 等編輯語意優先,對齊 VS Code / Linear 快捷鍵讓位可編輯區慣例)。**目前無 opt-out prop**(無 `disableShortcut`)。
689
+ - **Collapsed accessible name(2026-07-05 D4 修)**:icon 模式 label 以 `sr-only` 視覺隱藏(非 display:none),SidebarMenuButton 對 SR 保留 accessible name(WCAG 4.1.2);Tooltip 僅 hover 提示、不供名;badge / inline actions 仍 display:none(補充資訊,供名責任在 label)。
689
690
  - **Mobile sheet focus trap**:`md` breakpoint 以下切 Sheet 模式時,Radix Dialog 自帶 focus trap + Esc dismiss + restore focus to trigger(詳 sheet.spec.md)。
690
691
  - **Collapsible group**:`<SidebarGroup collapsible>` 的 label trigger 帶 `aria-expanded` + `aria-controls` 指向 GroupContent id,SR 可朗讀展開狀態。
691
692
  - **Sticky header / footer focus order**:Tab 順序按 DOM 順序 — Header → Content → Footer,SidebarTrigger 在 Pattern A/B 都位於 page top-left,Tab 第一站即可達。
@@ -128,13 +128,15 @@ const PageContent = ({ title, description }: { title: string; description: React
128
128
  平等 sibling。「當下在哪頁」的訊號由 sidebar active item 負責,不靠 title 字重 */}
129
129
  <h1 className="text-body-lg font-medium">{title}</h1>
130
130
  </ChromeHeader>
131
- <div className="flex-1 overflow-auto p-8">
131
+ {/* 2026-07-04 dim 90:macro 間距對齊 layout-space token(同 87dbc9cd template DashboardPage
132
+ 已拍板 pattern;本檔是 R8 registry archetype,錯 rhythm 會被 consumer 機械複製)*/}
133
+ <div className="flex-1 overflow-auto px-[var(--layout-space-loose)] py-[var(--layout-space-tight)]">
132
134
  <div className="max-w-2xl">
133
- <p className="text-body text-fg-secondary mb-6">{description}</p>
134
- <div className="grid grid-cols-2 gap-4">
135
+ <p className="text-body text-fg-secondary mb-[var(--layout-space-loose)]">{description}</p>
136
+ <div className="grid grid-cols-2 gap-[var(--layout-space-loose)]">
135
137
  {/* 固定值(非 Math.random)— story 是 visual baseline,必確定性,否則每次 build 像素不同 = composition-fidelity / visual regression 無法比對(2026-06-02 fix)*/}
136
138
  {([['專案數量', 24], ['團隊成員', 8], ['本週提交', 47], ['待處理', 3]] as const).map(([t, v]) => (
137
- <div key={t} className="rounded-lg border border-divider bg-surface p-4">
139
+ <div key={t} className="rounded-lg border border-divider bg-surface p-[var(--layout-space-loose)]">
138
140
  <p className="text-caption text-fg-muted">{t}</p>
139
141
  <p className="text-h5 font-semibold mt-1">{v}</p>
140
142
  </div>
@@ -194,6 +194,21 @@ const SidebarProvider = React.forwardRef<
194
194
  event.key === SIDEBAR_KEYBOARD_SHORTCUT &&
195
195
  (event.metaKey || event.ctrlKey)
196
196
  ) {
197
+ // 2026-07-05 D4 修:全域攔截加三道 guard——
198
+ // (1) defaultPrevented:app 層 editor 已處理同快捷鍵時不 double-fire;
199
+ // (2) isComposing:IME 組字中不攔(組字期間 key 事件不代表使用者意圖);
200
+ // (3) 可編輯 target:input / textarea / contentEditable 內 Cmd/Ctrl+B 是
201
+ // bold 等編輯語意,快捷鍵讓位(對齊 VS Code / Linear 讓位可編輯區慣例)。
202
+ if (event.defaultPrevented || event.isComposing) return
203
+ const target = event.target
204
+ if (
205
+ target instanceof HTMLElement &&
206
+ target.closest(
207
+ 'input, textarea, [contenteditable]:not([contenteditable="false"])'
208
+ )
209
+ ) {
210
+ return
211
+ }
197
212
  event.preventDefault()
198
213
  toggleSidebar()
199
214
  }
@@ -426,7 +441,7 @@ const SidebarTrigger = React.forwardRef<
426
441
  size="sm"
427
442
  iconOnly
428
443
  startIcon={PanelLeft}
429
- aria-label="Toggle Sidebar"
444
+ aria-label="切換側邊欄" /* i18n-allow: DS default;consumer 經 {...props} override */
430
445
  className={className}
431
446
  onClick={(event) => {
432
447
  onClick?.(event)
@@ -705,7 +720,7 @@ const sidebarGroupLabelVariants = cva(
705
720
  "px-[var(--layout-space-loose)]",
706
721
  "font-medium text-fg-muted",
707
722
  "cursor-default select-none pointer-events-none outline-none",
708
- // icon 模式:display:none 硬隱藏( SidebarMenuButton label span 一致的策略)
723
+ // icon 模式:GroupLabel 純視覺分段 → collapsed 連 SR 一併隱藏是刻意(供名責任在各 menu button 的 sr-only label,2026-07-05 D4 修後與 MenuButton 策略分工)
709
724
  "group-data-[collapsible=icon]:hidden",
710
725
  ],
711
726
  {
@@ -858,7 +873,8 @@ SidebarMenuItem.displayName = "SidebarMenuItem"
858
873
  * - selected: bg-neutral-selected(data-active=true)
859
874
  *
860
875
  * Icon 模式:button 保持 w-full 填滿 sidebar icon rail,鎖高為 field-height-{size},
861
- * content 用 justify-center 居中,label span 以 display:none 硬隱藏。
876
+ * content 用 justify-center 居中,label span 以 sr-only 視覺隱藏(a11y tree 保留,
877
+ * 2026-07-05 D4——SR 仍讀得到 accessible name)。
862
878
  */
863
879
  const sidebarMenuButtonVariants = cva(
864
880
  [
@@ -877,8 +893,11 @@ const sidebarMenuButtonVariants = cva(
877
893
  // range 內。Row geometry 永遠 stable(px-loose + items-start + gap-2 + height auto),
878
894
  // 無 discrete property switch → 無 fly / 無 flutter / 無 jitter。
879
895
  //
880
- // Label/badge/action 自然存在 DOM(a11y screen reader 仍讀到),只是 outer clip 視覺消失。
881
- // Tooltip 仍在 collapsed state 顯示(per L1043-1055 既有 hidden 條件)
896
+ // 2026-07-05 D4 更新:v4「outer clip、SR 仍讀到」的宣稱在 v5 label display:none
897
+ // 已失真;現行 = label sr-only(SR 讀到、供 accessible name,見下方 v5/D4 註解),
898
+ // badge / action / inline-actions 仍 display:none(視覺 + SR 一併隱藏——collapsed 供名
899
+ // 責任在 label,badge 計數屬補充資訊)。Tooltip 仍在 collapsed state 顯示
900
+ // (per buttonWithTooltip 既有 hidden 條件),僅 hover 提示、不供名。
882
901
  //
883
902
  // 對齊 MUI MiniDrawer + shadcn canonical(width morph + overflow clip + row geometry 不變)。
884
903
  "transition-[background-color,color] duration-200 ease-linear motion-reduce:duration-0",
@@ -891,11 +910,16 @@ const sidebarMenuButtonVariants = cva(
891
910
  "aria-disabled:pointer-events-none aria-disabled:text-fg-disabled",
892
911
  "data-[active=true]:bg-neutral-selected data-[active=true]:text-foreground",
893
912
  "group-has-[[data-sidebar=menu-action]]/menu-item:pr-8",
894
- // 2026-05-21 v5 restore label display:none(user 抓「label 沒消失」):
913
+ // 2026-05-21 v5 restore label 硬隱藏(user 抓「label 沒消失」):
895
914
  // C* outer overflow-x:hidden 理論 clip,但 label.x=40 在 sidebar-width-icon=48 內 → 首字
896
- // 部分可見。display:none 是 instant 切換非 main animation,跟 C* 「不用 display:none 做主
897
- // 動畫」不衝突(label 不參與 width 動畫,純 final state)。對齊 shadcn canonical
898
- "group-data-[collapsible=icon]:[&_[data-sidebar=menu-label]]:hidden",
915
+ // 部分可見。instant 切換非 main animation,跟 C* 「不用 display:none 做主
916
+ // 動畫」不衝突(label 不參與 width 動畫,純 final state)。
917
+ // 2026-07-05 D4 修:v5 的 display:none 讓 accessible-name 計算排除 label
918
+ // (icon aria-hidden、badge 也 hidden)→ collapsed 時 menu button 對 SR 是無名
919
+ // button(WCAG 4.1.2)。改 sr-only:position:absolute + 1px + clip 視覺隱藏但保留
920
+ // a11y tree,button 仍有 accessible name;absolute 移出 flex flow,layout 效果同
921
+ // display:none(icon 置中公式不變)。
922
+ "group-data-[collapsible=icon]:[&_[data-sidebar=menu-label]]:sr-only",
899
923
  // 2026-05-22 Approach 9 — 收合時所有 prefix center 鎖回 rail center
900
924
  // (--sidebar-width-icon/2 = GlobalHeader toggle geometry SSOT)。
901
925
  // 公式: pl = (rail - prefix-width) / 2,prefix-width 由 SidebarProvider 注入的
@@ -1030,8 +1054,9 @@ const SidebarMenuButton = React.forwardRef<
1030
1054
  // 所以 asChild 的 consumer 自行放 icon + label(記得 prefix 要包在 h-[1lh] 容器,
1031
1055
  // label span 要有 data-sidebar="menu-label" attribute 才能參與 icon 模式自動隱藏)。
1032
1056
  //
1033
- // Label span 在 icon 模式下透過 cva base 的 `[&_[data-sidebar=menu-label]]:hidden` display:none,
1034
- // 這樣 flex 只剩 icon 一個 child,justify-center 可以真正置中。
1057
+ // Label span 在 icon 模式下透過 cva base 的 `[&_[data-sidebar=menu-label]]:sr-only`
1058
+ // 視覺隱藏(2026-07-05 D4:sr-only display:none,SR 保留 accessible name;
1059
+ // absolute 移出 flex flow),這樣 flex 只剩 icon 一個 in-flow child,置中公式不變。
1035
1060
  // Sidebar 寬度 transition 200ms 是唯一持續動畫,使用者視線跟著寬度走,不會察覺 label 的瞬切。
1036
1061
  // 用 `<ItemIcon>` / `<ItemLabel>` helper,**不直接寫 ItemPrefix wrap StartIcon**——
1037
1062
  // ItemIcon 內部會自動加 `data-prefix-type="icon"`,讓 SidebarProvider 的全域
@@ -1259,9 +1284,12 @@ export const sidebarMeta = {
1259
1284
  md: { px: 32, when: '預設 — 應用程式主導覽' },
1260
1285
  lg: { px: 36, when: '重要主導覽 / icon-prominent workspace switcher' },
1261
1286
  },
1262
- states: ['default', 'hover', 'active', 'focus-visible', 'disabled'],
1287
+ // 'selected' = nav 當前項(data-[active=true] → bg-neutral-selected 灰底家族;2026-07-07 meta 詞彙統一補列);
1288
+ // 'active' 移除 — 全檔無 Tailwind 按壓 utility(內嵌 SidebarTrigger 的按壓屬 Button meta,同 Notice/Alert
1289
+ // 排除內嵌 dismiss Button 慣例;2026-07-07 對抗稽核抓 doc-claim ≠ code 補修)。
1290
+ states: ['default', 'hover', 'selected', 'focus-visible', 'disabled'],
1263
1291
  tokens: {
1264
- bg: ['bg-neutral-hover', 'bg-surface', 'bg-transparent'],
1292
+ bg: ['bg-neutral-hover', 'bg-neutral-selected', 'bg-surface', 'bg-transparent'],
1265
1293
  fg: ['text-fg-muted', 'text-fg-secondary', 'text-foreground'],
1266
1294
  ring: ['ring-ring'],
1267
1295
  },
@@ -32,7 +32,7 @@ const Label = ({ children, warn }: { children: React.ReactNode; warn?: boolean }
32
32
 
33
33
  const Section = ({ title, children }: { title: string; children: React.ReactNode }) => (
34
34
  <section className="mb-12">
35
- <h2 className="text-heading-3 font-bold text-foreground mb-4 pb-2 border-b border-border">{title}</h2>
35
+ <h2 className="text-h3 font-bold text-foreground mb-4 pb-2 border-b border-border">{title}</h2>
36
36
  {children}
37
37
  </section>
38
38
  )
@@ -3,8 +3,7 @@ component: Skeleton
3
3
  family: self-contained
4
4
  variants: {}
5
5
  sizes: {}
6
- traits:
7
- - isMatrixHeavy
6
+ traits: [] # 2026-07-04 audit:移除幻影 isMatrixHeavy(spec body「為何無 Matrix」明文 rationale + 0 matrix stories,trait 與自家宣告矛盾)
8
7
  benchmark:
9
8
  - Ant Design Skeleton: github.com/ant-design/ant-design/tree/master/components/skeleton
10
9
  - MUI Skeleton: github.com/mui/material-ui/tree/master/packages/mui-material/src/Skeleton