@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
@@ -86,7 +86,7 @@ Filter chip 的「移除這個 filter」動作已由「再點一次 deselect」
86
86
 
87
87
  ### 為什麼不需要 checkmark-on-selected
88
88
 
89
- Material 3 filter chip 在 selected 時會把 startIcon 換成 `Check`。我們不做,因為視覺層已經用 **primary-hover border + text(底色維持 surface 不變)**明確表達 selected 狀態,再加 icon 是冗餘信號。
89
+ Material 3 filter chip 在 selected 時會把 startIcon 換成 `Check`。我們不做,因為視覺層已經用 **primary border + text(底色維持 surface 不變)**明確表達 selected 狀態,再加 icon 是冗餘信號。
90
90
 
91
91
  ---
92
92
 
@@ -108,10 +108,12 @@ Chip **只有一個 size**,對應 `h-field-sm`(md density 28px / lg density
108
108
  |---|---|
109
109
  | **Default** | `bg-surface border-border text-fg-secondary`(未選文字用 neutral-8,對齊 SegmentedControl / Tabs 未選狀態;hover 才轉 `text-foreground`)|
110
110
  | **Hover**(未選)| `hover:border-border-hover`(對齊 Input / SegmentedControl hover 的 border 加深一階)|
111
- | **Selected** | `border-primary-hover text-primary-hover`,底色維持 `bg-surface` 不變(primary-hover 同時染 border + text,不染底色,跟 SegmentedControl 一致)|
111
+ | **Selected** | `border-primary text-primary`,底色維持 `bg-surface` 不變(primary base 同時染 border + text,不染底色,跟 SegmentedControl 一致)|
112
+ | **Hover(選中之上)** | `border-primary-hover text-primary-hover`——選中元素的 hover = 同色相升 hover 階(2026-07-06 拍板;Button pressed hover 家族 + Ant active hover 同派),**不用** neutral 灰底 |
112
113
  | **Disabled** | `cursor-not-allowed text-fg-disabled`,border 維持 `border-border` 不變色 |
114
+ | **Disabled × Selected** | disabled 全滅(primary 描邊/染字一併退場 → `border-border text-fg-disabled`)——M24 state > emphasis,Checkbox checked+disabled 全滅同族(2026-07-07 補明文,原 code 只滅字不滅框 = 半滅已修) |
113
115
 
114
- Hover selected 的色彩都對齊系統 canonical 規則,跟 SegmentedControl / Input / Tabs underline 使用同一套 primary-hover token,保持選中語意的視覺統一。
116
+ 選中色對齊系統選中 canonical(semantic.css「選中狀態」段 SSOT),跟 SegmentedControl / Tabs underline 使用同一套 **primary base** token——2026-07-06 user 拍板自 primary-hover 改 base(Ant / Atlassian / Material 三家實錘「瞬時弱、持續強」:hover 階專屬瞬時態,持續選中站 base)。**❌ 選中態不得用 primary-hover**(那是瞬時 hover 專用階)。
115
117
 
116
118
  ---
117
119
 
@@ -225,7 +227,7 @@ ColorMatrix 已建:展示 default / hover / selected / disabled 四狀態的 bg
225
227
  - Tab(再按一次)— 離開整組
226
228
  - Enter / Space — 切換選取
227
229
 
228
- **Focus**:整組 chip 採 roving tabindex——Tab 進入整組、方向鍵在組內移動焦點、再按 Tab 離開整組(不是 Dialog 那種把焦點鎖在裡面、Tab 無法離開的 focus trap)。focus-visible 時顯示 `2px solid var(--ring)` 焦點環。
230
+ **Focus**:整組 chip 採 roving tabindex——Tab 進入整組、方向鍵在組內移動焦點、再按 Tab 離開整組(不是 Dialog 那種把焦點鎖在裡面、Tab 無法離開的 focus trap)。focus-visible 時顯示 focus ring(`focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1`,以 box-shadow 實作並 `outline-none`)。
229
231
 
230
232
  **驗證**:Storybook a11y addon panel 應 0 critical violation;鍵盤完整可操作(無需滑鼠)。WCAG AA contrast ≥ 4.5:1(text)/ 3:1(UI)。
231
233
 
@@ -234,4 +236,5 @@ ColorMatrix 已建:展示 default / hover / selected / disabled 四狀態的 bg
234
236
  > 本節由 `scripts/add-reciprocal-pointers.mjs` 自動維護,列出在 SSOT 語境下指向本 spec 的其他 spec。若要手動補充,寫在本節之前。
235
237
 
236
238
  - `badge.spec.md`
239
+ - `pagination.spec.md`
237
240
  - `tag.spec.md`
@@ -35,7 +35,7 @@ import {
35
35
  * ── State ──
36
36
  * default bg-surface border-border text-fg-secondary
37
37
  * hover border-border-hover text-foreground(對齊 Input / SegmentedControl)
38
- * selected bg-surface(不變) border-primary-hover text-primary-hover
38
+ * selected bg-surface(不變) border-primary text-primary
39
39
  * disabled cursor-not-allowed text-fg-disabled
40
40
  *
41
41
  * ── 詳見 chip.spec.md ──
@@ -56,14 +56,26 @@ const chipVariants = cva(
56
56
  'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1',
57
57
  // hover(未選):border 加深一階 + 文字轉深,對齊 Input / SegmentedControl hover
58
58
  'hover:border-border-hover hover:text-foreground',
59
- // selected: 文字 + 邊框都用 primary-hover,底色維持 bg-surface 不變
60
- // ── pill 風格 canonical 選中規則,跟 SegmentedControl 完全一致:
61
- // primary-hover 同時染文字和線條;底色不改 (不用 primary-subtle)。
62
- 'data-[state=on]:border-primary-hover data-[state=on]:text-primary-hover',
59
+ // selected: 文字 + 邊框都用 primary base,底色維持 bg-surface 不變
60
+ // ── pill 風格 canonical 選中規則,跟 SegmentedControl 完全一致(SSOT = semantic.css「選中狀態」段):
61
+ // primary base 同時染文字和線條;底色不改 (不用 primary-subtle)。
62
+ // 2026-07-06 user 拍板:持續選中 hover 階 → base — Ant(pagination/tabs/input focus 全 base)/
63
+ // Atlassian(selected = brand 同顆)/ Material 3(active tab = primary)三家實錘「瞬時弱、持續強」;
64
+ // hover 階專屬瞬時態(hover/預覽),持續選中站 base。
65
+ 'data-[state=on]:border-primary data-[state=on]:text-primary',
66
+ // 選中之上的 hover = 同色相升 hover 階(2026-07-06 user 拍板;Button pressed hover
67
+ // data-[state=on]:hover:text-primary-hover 家族 + Ant active 頁 hover = colorPrimaryHover
68
+ // 同派)。原本靠 CSS 平手 source-order 讓選中「碰巧」贏過未選 hover(靜止無回饋)= 脆弱,
69
+ // 顯式宣告一併解掉。
70
+ 'data-[state=on]:hover:border-primary-hover data-[state=on]:hover:text-primary-hover',
63
71
  // disabled:cursor-not-allowed + 鎖 hover 不變色
64
72
  // 不用 pointer-events-none(否則 cursor-not-allowed 不會顯示)
65
73
  'disabled:cursor-not-allowed disabled:text-fg-disabled',
66
74
  'disabled:hover:border-border disabled:hover:text-fg-disabled',
75
+ // disabled × selected 疊加(2026-07-07 user 拍板統一;M24 state > emphasis:disabled 全滅,
76
+ // 選中的 primary 描邊/染字一併退場——原本只滅字不滅框 = 半滅,Checkbox checked+disabled 全滅同族)
77
+ 'data-[state=on]:disabled:border-border data-[state=on]:disabled:text-fg-disabled',
78
+ 'data-[state=on]:disabled:hover:border-border data-[state=on]:disabled:hover:text-fg-disabled',
67
79
  ]
68
80
  )
69
81
 
@@ -208,6 +220,29 @@ ScrollChipGroup.displayName = 'ScrollChipGroup'
208
220
  //
209
221
  // Fade mask 仍保留(reserveArrowWidth: 0),軟化內容硬邊。
210
222
 
223
+ // ── getElementRef(Radix Slot idiom)──────────────────────────────────────────
224
+ // 讀取 child element 既有 ref 而不觸發 React dev deprecation warning:
225
+ // React ≤18 ref 在 element.ref;React 19 起移到 element.props.ref,錯誤側讀取會 fire warning
226
+ // (以 getter 上的 isReactWarning 標記)。抄 Radix UI Slot 的偵測法:
227
+ // https://github.com/radix-ui/primitives/blob/main/packages/react/slot/src/slot.tsx(getElementRef)
228
+ // react-compose-refs 僅為 transitive dep(非 package.json 直接依賴),不直接 import — 最小 local 實作。
229
+ function getElementRef(element: React.ReactElement): React.Ref<HTMLElement> | undefined {
230
+ // React <=18 dev:props.ref 掛 isReactWarning getter → 真 ref 在 element.ref
231
+ let getter = Object.getOwnPropertyDescriptor(element.props, 'ref')?.get
232
+ let mayWarn = getter && 'isReactWarning' in getter && getter.isReactWarning
233
+ if (mayWarn) return (element as unknown as { ref?: React.Ref<HTMLElement> }).ref
234
+ // React 19 dev:element.ref 掛 isReactWarning getter → 真 ref 在 props.ref
235
+ getter = Object.getOwnPropertyDescriptor(element, 'ref')?.get
236
+ mayWarn = getter && 'isReactWarning' in getter && getter.isReactWarning
237
+ if (mayWarn) return (element.props as { ref?: React.Ref<HTMLElement> }).ref
238
+ // production:兩處擇一
239
+ return (
240
+ (element.props as { ref?: React.Ref<HTMLElement> }).ref ??
241
+ (element as unknown as { ref?: React.Ref<HTMLElement> }).ref ??
242
+ undefined
243
+ )
244
+ }
245
+
211
246
  // code-quality-allow: long-function — foundational composite main body — 拆 sub-fn 會複雜化 local state / ref / context binding
212
247
  const MenuChipGroup = React.forwardRef<
213
248
  HTMLDivElement,
@@ -284,12 +319,22 @@ const MenuChipGroup = React.forwardRef<
284
319
  [groupType, groupValue, groupOnValueChange]
285
320
  )
286
321
 
287
- const enhancedItems = items.map((child, i) =>
288
- React.cloneElement(
322
+ // 2026-07-05 fix(deep-audit A.1b):原 cloneElement 直接以 registerItem 覆寫 child ref,
323
+ // consumer 傳給 Chip 的自有 ref 在 menu layout 會被靜默取代。改 compose — 同時餵
324
+ // registerItem(scrollIntoView 用)與 child 原 ref(對齊 Radix compose-refs idiom)。
325
+ const enhancedItems = items.map((child, i) => {
326
+ const childRef = getElementRef(child)
327
+ return React.cloneElement(
289
328
  child as React.ReactElement<{ ref?: React.Ref<HTMLElement> }>,
290
- { ref: registerItem(i) }
329
+ {
330
+ ref: (el: HTMLElement | null) => {
331
+ registerItem(i)(el)
332
+ if (typeof childRef === 'function') childRef(el)
333
+ else if (childRef) (childRef as React.MutableRefObject<HTMLElement | null>).current = el
334
+ },
335
+ }
291
336
  )
292
- )
337
+ })
293
338
 
294
339
  return (
295
340
  <div className="flex items-center gap-2">
@@ -342,16 +387,16 @@ MenuChipGroup.displayName = 'MenuChipGroup'
342
387
  export const chipMeta = {
343
388
  component: 'Chip',
344
389
  family: 3,
345
- variants: {
346
-
347
- },
390
+ // 2026-07-04 audit 填 Phase 2:Chip 單一視覺無 variant 軸(variants 留空為 intentional,非漏填)
391
+ variants: {},
348
392
  sizes: {
349
-
393
+ // 單一尺寸:h-field-sm(28/32 依 density)、icon 16、text-body、px-3(cva 真值)
394
+ sm: { fieldHeight: 28, iconSize: 16, typography: 'body' },
350
395
  },
351
- states: ['default', 'hover', 'selected', 'focus-visible', 'disabled'], // selected = data-[state=on](primary-hover);cva 無 active 樣式
396
+ states: ['default', 'hover', 'selected', 'focus-visible', 'disabled'], // selected = data-[state=on](primary base,2026-07-06 拍板);cva 無 active 樣式
352
397
  tokens: {
353
398
  bg: ['bg-surface'],
354
- fg: ['text-fg-disabled', 'text-fg-secondary', 'text-foreground', 'text-primary-hover'],
399
+ fg: ['text-fg-disabled', 'text-fg-secondary', 'text-foreground', 'text-primary'],
355
400
  ring: ['ring-ring'],
356
401
  },
357
402
  } as const
@@ -170,7 +170,7 @@ export const Accessibility = {
170
170
  render: () => (
171
171
  <div className="max-w-3xl text-body text-fg-secondary">
172
172
  <h3 className="text-h5 text-foreground mb-2">無障礙設計</h3>
173
- <p className="whitespace-pre-line">{"本元件不可聚焦、無鍵盤互動(載入指示不接收鍵盤),但會依模式自動輸出對的 ARIA 給輔助技術:\n\n• 有 value(determinate)→ role=\"progressbar\",並帶 aria-valuenow / aria-valuemin=0 / aria-valuemax=100,螢幕報讀器會念出目前進度。\n• 無 value(indeterminate)+ 有傳 label 或 aria-label → role=\"status\",把 label 當作 aria-label 念出(例如「載入訂單資料中」)。\n• 無 value 且沒給任何 label → 標記 aria-hidden,交由外層容器(如 Button 的 aria-busy)負責語義。\n\n當把 CircularProgress 包進互動容器(Button / Card / Link)時,聚焦與鍵盤行為由容器決定。"}</p>
173
+ <p className="whitespace-pre-line">{"本元件不可聚焦、無鍵盤互動(載入指示不接收鍵盤),但會依模式自動輸出對的 ARIA 給輔助技術:\n\n• 有 value(determinate)→ role=\"progressbar\",並帶 aria-valuenow / aria-valuemin=0 / aria-valuemax=100,螢幕報讀器會念出目前進度。\n• 無 value(indeterminate)+ 有傳 label 或 aria-label → role=\"status\",把 label 當作 aria-label 念出(例如「載入訂單資料中」)。\n• 無 value 且沒給任何 label → 標記 aria-hidden,交由外層容器(如 Button 的 aria-busy)負責語義。\n\n動畫尊重 prefers-reduced-motion:偏好減少動態的使用者不會看到停止的圈(那會像卡住),indeterminate 旋轉改放慢至 3s/圈(預設 1s)——保留「進行中」訊號、降低前庭刺激(Material 流派)。\n\n當把 CircularProgress 包進互動容器(Button / Card / Link)時,聚焦與鍵盤行為由容器決定。"}</p>
174
174
  </div>
175
175
  ),
176
176
  }
@@ -8,6 +8,7 @@ import { Search, Check } from 'lucide-react'
8
8
  import { CircularProgress } from './circular-progress'
9
9
  import { Button } from '@/design-system/components/Button/button'
10
10
  import { Skeleton } from '@/design-system/components/Skeleton/skeleton'
11
+ import { Empty } from '@/design-system/components/Empty/empty'
11
12
  import { Input } from '@/design-system/components/Input/input'
12
13
 
13
14
  const meta: Meta = {
@@ -214,12 +215,13 @@ export const UsageScenarioRule: Story = {
214
215
  title="✅ 延遲加載浮層 / 全頁 overlay — 用 Empty compose"
215
216
  note="切換 workspace / 載入 dashboard 等需要阻擋互動的場景,走 Empty compose:<Empty icon={<CircularProgress size={48} />} title=... description=... />。不要自己手刻 absolute overlay"
216
217
  >
218
+ {/* 2026-07-04 audit:改消費 <Empty> compose(原手刻垂直堆疊與本則自教規則+spec Do 矛盾) */}
217
219
  <div className="border border-border rounded-lg w-96 h-40 flex items-center justify-center bg-muted/40">
218
- <div className="flex flex-col items-center gap-3">
219
- <CircularProgress size={48} aria-label="載入 dashboard" />
220
- <p className="text-body font-medium">載入 dashboard 中</p>
221
- <p className="text-caption text-fg-muted">Notion workspace 切換、Stripe dashboard 初載</p>
222
- </div>
220
+ <Empty
221
+ icon={<CircularProgress size={48} aria-label="載入 dashboard" />}
222
+ title="載入 dashboard 中"
223
+ description="Notion workspace 切換、Stripe dashboard 初載"
224
+ />
223
225
  </div>
224
226
  </Rule>
225
227
  </div>
@@ -185,6 +185,8 @@ Track 色鎖 `var(--secondary)`(= neutral-3,與 ProgressBar track 一致)。
185
185
  | Indeterminate + 有 aria-label / label | `status` | `aria-label={aria-label ?? label}`(顯式 aria-label 優先,label 為 fallback) |
186
186
  | Indeterminate + 無 label | `aria-hidden=true` | 由父層 `aria-busy` 管理(Button 模式) |
187
187
 
188
+ **動畫 × `prefers-reduced-motion`**:indeterminate 旋轉對 reduce 使用者**不停止**(loading 回饋仍需可見,停轉會像卡住),改放慢至 3s/圈(預設 1s;`circular-progress.tsx` 的 `motion-reduce:[animation-duration:3s]`,Material 流派)——保留「進行中」訊號、降低前庭刺激。對照 Skeleton 的 `motion-reduce:animate-none`(完全停止):占位塊靜止後仍是有效的靜態內容,語意不失,故兩元件策略不同。
189
+
188
190
  ---
189
191
 
190
192
  ## 視覺與幾何鐵律
@@ -5,7 +5,6 @@ import type { Meta, StoryObj } from '@storybook/react'
5
5
  import { Search, Upload } from 'lucide-react'
6
6
  import type { ColumnDef } from '@tanstack/react-table'
7
7
  import { CircularProgress } from './circular-progress'
8
- import { Button } from '@/design-system/components/Button/button'
9
8
  import { Input } from '@/design-system/components/Input/input'
10
9
  import { Empty } from '@/design-system/components/Empty/empty'
11
10
  import { DataTable } from '@/design-system/components/DataTable/data-table'
@@ -43,16 +42,9 @@ export const Default: Story = {
43
42
  ),
44
43
  }
45
44
 
46
- export const ButtonLoading: Story = {
47
- name: '按鈕載入中',
48
- render: () => (
49
- <div className="flex items-center gap-3">
50
- <Button variant="primary" loading>處理付款中</Button>
51
- <Button variant="secondary" loading>儲存草稿</Button>
52
- <Button variant="tertiary" loading>匯出 CSV</Button>
53
- </div>
54
- ),
55
- }
45
+ // Retired 2026-07-04 audit Dim 24:`ButtonLoading` 全程只 demo `<Button loading>`(0 個直接 `<CircularProgress>`),
46
+ // 跟 button.stories.tsx「載入中」重複且較弱(那邊有 原始→loading 對照 + iconOnly + startIcon 替換)。
47
+ // Button loading 消費 CircularProgress 的關係由 spec「何時用」+ Size canonical 段記載,story home 在 Button。
56
48
 
57
49
  export const InlineAction: Story = {
58
50
  name: '行內操作',
@@ -335,7 +335,7 @@ export const StateBehavior: Story = {
335
335
  <div>
336
336
  <H3>Focus / Esc / Click-outside(繼承 Popover)</H3>
337
337
  <Desc>
338
- Coachmark 大部分行為繼承 Popover(Radix),但刻意抑制開啟時的自動 focus(onOpenAutoFocus preventDefault)——焦點停在 trigger,避免使用者還在讀 body 時 CTA 被 auto-focus 偷觸發;關閉時 return to trigger;Esc 關閉浮層(透過 Radix onOpenChange,效果等同退出,但不觸發 onSkip callback);預設 non-modal,使用者可忽略繼續主流程。
338
+ Coachmark 大部分行為繼承 Popover(Radix),但刻意抑制 CTA 自動 focus(onOpenAutoFocus preventDefault 後 focus content 容器本身)——避免使用者還在讀 body 時 CTA 被 auto-focus 偷觸發,Tab 第一下即達第一個 CTA(焦點不停 trigger,否則 non-modal focus-outside dismiss 會誤關閉浮層;2026-07-05 D4 修);關閉時 return to trigger;Esc 關閉浮層(透過 Radix onOpenChange,效果等同退出,但不觸發 onSkip callback);預設 non-modal,使用者可忽略繼續主流程。
339
339
  </Desc>
340
340
  </div>
341
341
  </div>
@@ -350,7 +350,7 @@ export const Accessibility = {
350
350
  render: () => (
351
351
  <div className="max-w-3xl text-body text-fg-secondary">
352
352
  <h3 className="text-h5 text-foreground mb-2">無障礙設計</h3>
353
- <p className="whitespace-pre-line">{"詳 `coachmark.spec.md` 「A11y 預設」段。摘要:\n\n- 焦點管理 :刻意抑制開啟時的自動 focus(onOpenAutoFocus preventDefault),焦點停在 trigger——避免 user 還在讀 body 時 CTA 被偷觸發;關閉時 return to trigger\n- Esc 關閉 :預設啟用——user 按 Esc 透過 Radix onOpenChange 關閉浮層(效果等同退出),但不觸發 onSkip callback(Esc 是「關閉浮層」非「明確 skip」)\n- ARIA :trigger 自動 aria-expanded / aria-controls ,content role=\"dialog\" (Radix 預設)\n- Step 計數 tabular-nums :螢幕閱讀器讀「2 of 3」語意清楚"}</p>
353
+ <p className="whitespace-pre-line">{"詳 `coachmark.spec.md` 「A11y 預設」段。摘要:\n\n- 焦點管理 :刻意抑制 CTA 自動 focus(onOpenAutoFocus preventDefault 後 focus content 容器本身)——避免 user 還在讀 body 時 CTA 被偷觸發;Tab 第一下即達第一個 CTA(焦點不停 trigger,否則 non-modal focus-outside dismiss 會誤關閉;2026-07-05 D4 修);關閉時 return to trigger\n- Esc 關閉 :預設啟用——user 按 Esc 透過 Radix onOpenChange 關閉浮層(效果等同退出),但不觸發 onSkip callback(Esc 是「關閉浮層」非「明確 skip」)\n- ARIA :trigger 自動 aria-expanded / aria-controls ,content role=\"dialog\" (Radix 預設)\n- Step 計數 tabular-nums :螢幕閱讀器讀「2 of 3」語意清楚"}</p>
354
354
  </div>
355
355
  ),
356
356
  }
@@ -55,7 +55,7 @@ const Label = ({ children, warn }: { children: React.ReactNode; warn?: boolean }
55
55
 
56
56
  const Section = ({ title, children }: { title: string; children: React.ReactNode }) => (
57
57
  <section className="mb-12">
58
- <h2 className="text-heading-3 font-bold text-foreground mb-4 pb-2 border-b border-border">{title}</h2>
58
+ <h2 className="text-h3 font-bold text-foreground mb-4 pb-2 border-b border-border">{title}</h2>
59
59
  {children}
60
60
  </section>
61
61
  )
@@ -166,7 +166,7 @@ Previous(可選)→ Skip(可選)→ Next / Done。對齊 Ant Tour / Intercom con
166
166
 
167
167
  - **Previous 最左**——回到上一步(負方向,視覺權重低)
168
168
  - **Skip 中間**——exit escape hatch,tertiary 不搶焦點;**僅第一步顯示**(見 CTA 語義表)
169
- - **Next / Done 最右**——推進主動線(正方向,primary 視覺權重高);**Coachmark 抑制開啟時的自動 focus**(`onOpenAutoFocus` preventDefault,連 Radix 預設會 focus 的第一個 focusable 也一併抑制),避免使用者還在讀 body 時按 Enter 誤推進;想推進者自行 tab CTA 再 Enter
169
+ - **Next / Done 最右**——推進主動線(正方向,primary 視覺權重高);**Coachmark 抑制 CTA 自動 focus**(`onOpenAutoFocus` preventDefault focus content 容器本身,非第一個 CTA),避免使用者還在讀 body 時按 Enter 誤推進;想推進者按一下 Tab 即達 CTA 再 Enter(焦點已在 content 內,不觸發 non-modal focus-outside dismiss;2026-07-05 D4 修)
170
170
 
171
171
  第 1 步通常無 Previous;最後步 Next 改 Done(由 `isLastStep` 控制)。單步驟 CTA 改 `'知道了'`。
172
172
 
@@ -178,7 +178,7 @@ Previous(可選)→ Skip(可選)→ Next / Done。對齊 Ant Tour / Intercom con
178
178
  |----|-------|------|
179
179
  | 浮層外殼 | 繼承 Popover(`bg-surface-raised` / `border-border` / `rounded-lg` / `--elevation-200`) | 改 Popover 自動跟進 |
180
180
  | Media 背景 | `bg-muted` | illustration / 透明 PNG 底色 |
181
- | Title | `text-body-lg font-medium text-foreground` | Popover header 輕(Coachmark header 分隔線) |
181
+ | Title | `text-body-lg font-medium text-foreground` | body title(16px),比 kind header 的 `PopoverTitle` 小標籤(`text-body` 14px)重一階——主教學訊息是視覺主角 |
182
182
  | Description | `text-body text-fg-secondary` | 主說明文字 |
183
183
  | Step 計數 | `text-body text-fg-secondary tabular-nums` | 跟 body 內文同字體;數字等寬,切換步驟不跳動 |
184
184
  | Body padding | `px-[var(--layout-space-loose)] py-[var(--layout-space-tight)]` | overlay-surface SSOT(PopoverBody) |
@@ -203,7 +203,7 @@ Previous(可選)→ Skip(可選)→ Next / Done。對齊 Ant Tour / Intercom con
203
203
 
204
204
  ## A11y 預設
205
205
 
206
- - **焦點管理**:Coachmark **刻意抑制開啟時的自動 focus**(`onOpenAutoFocus` preventDefault),焦點停在 trigger——避免使用者還在讀 body 時 CTA 被 auto-focus 偷觸發( Enter 誤推進);想推進者自行 tab CTA。關閉時焦點 return to trigger(Radix 預設 `onCloseAutoFocus`,未 override)
206
+ - **焦點管理**:Coachmark **刻意抑制 CTA 自動 focus**(`onOpenAutoFocus` preventDefault 後 focus content 容器本身,對齊 `dialog.tsx` 的 `?? content` fallback canonical)——避免使用者還在讀 body 時 CTA 被 auto-focus 偷觸發(焦點在容器上按 Enter 無動作);Tab 第一下即達 content 內第一個 CTA。焦點**不**停在 trigger:non-modal Popover 焦點在 content 樹外時按 Tab 會觸發 focus-outside dismiss 誤關閉浮層,CTA 鍵盤永遠到不了(2026-07-05 D4 修;dismiss 機制詳 `popover.spec.md` A11y 段)。關閉時焦點 return to trigger(Radix 預設 `onCloseAutoFocus`,未 override)
207
207
  - **Esc 關閉**:預設啟用——user 按 Esc 透過 Radix `onOpenChange` 關閉浮層(效果等同退出),**不觸發 `onSkip` callback**(Esc 是「關閉浮層」非「明確 skip」;若需區分 Esc-退出 vs Skip-按鈕 的追蹤事件,consumer 在 Skip 按鈕的 `onSkip` 處理)
208
208
  - **ARIA**:trigger 自動 `aria-expanded` / `aria-controls`,content `role="dialog"`(Radix 預設)
209
209
  - **Step 計數 tabular-nums**:螢幕閱讀器讀「2 of 3」語意清楚
@@ -34,9 +34,11 @@ import { OVERLAY_SIDE_OFFSET } from '@/design-system/tokens/elevation/overlay-ge
34
34
  * 3. **Multi step 中間 / 最後步**(有 `onPrev`):**Skip 不顯示**(使用者已投入進度,
35
35
  * 再給 Skip 會讓「放棄」入口與「回上一步」衝突 — Linear / Pendo / Shepherd.js
36
36
  * 同樣規則)。CTA = `isLastStep ? 'Done' : 'Next'`
37
- * 4. **抑制開啟時的自動 focus** — `onOpenAutoFocus` preventDefault,連 Radix 預設
38
- * focus 第一個 focusable 也一併抑制(焦點停在 trigger),避免使用者還在讀 body
39
- * Enter 誤推進。想推進者自行 tab CTA Enter(對齊 spec「A11y 預設」)。
37
+ * 4. **抑制 CTA 自動 focus,焦點落在 content 容器** — `onOpenAutoFocus` preventDefault
38
+ * focus content 容器本身(非第一個 CTA),避免使用者還在讀 body 時按 Enter 誤推進;
39
+ * Tab 第一下即達 content 內第一個 CTA。焦點不可停在 trigger:non-modal Popover
40
+ * 焦點在 DismissableLayer 外按 Tab 會觸發 focus-outside dismiss 誤關閉
41
+ * (2026-07-05 D4;對齊 dialog.tsx handleOpenAutoFocus `?? content` fallback,見 spec「A11y 預設」)。
40
42
  *
41
43
  * ── 為什麼 Body+Footer 消費 overlay-surface ──
42
44
  * 避免 padding token 漂移:Dialog / Popover / Coachmark 三者共用同一套 Header/Body/Footer
@@ -150,10 +152,12 @@ const Coachmark = React.forwardRef<HTMLDivElement, CoachmarkProps>(
150
152
  ? KIND_TITLE[kind as 'tips' | 'new-features']
151
153
  : kind
152
154
 
153
- // 2026-05-31 #6:PopoverContent(role=dialog)accessible name。dialog aria-labelledby 接可見標題
154
- // (優先 headerTitle PopoverTitle,否則 body title h3);皆無則 aria-label fallback。
155
- const titleId = React.useId()
156
- const dialogLabelledBy = (headerTitle || title) ? titleId : undefined
155
+ // 2026-05-31 #6 / 2026-07-05 改消費 Popover 自動接線:PopoverContent(role=dialog)accessible name 三層
156
+ // 1. headerTitle <PopoverTitle> 掛載時由 popover.tsx PopoverTitleContext 自動接 aria-labelledby(免手動)
157
+ // 2. header 但有 body 主 title(<h3> 非 PopoverTitle,自動接線接不到)→ 仍手動接 aria-labelledby
158
+ // 3. 皆無 aria-label fallback
159
+ const bodyTitleId = React.useId()
160
+ const bodyTitleLabelledBy = !headerTitle && title ? bodyTitleId : undefined
157
161
 
158
162
  return (
159
163
  <Popover open={open} defaultOpen={defaultOpen} onOpenChange={onOpenChange}>
@@ -166,10 +170,16 @@ const Coachmark = React.forwardRef<HTMLDivElement, CoachmarkProps>(
166
170
  className={cn('w-80 p-0 overflow-hidden', className)}
167
171
  // 禁止 Radix 開啟時自動 focus 第一個 focusable(預設會 focus Prev / Skip / Next),
168
172
  // Coachmark 的 CTA 不該被 auto-focus 偷觸發(user 可能還在讀 body,按 Enter 就推進)。
169
- // 想推進的 user 自己 tab CTA 即可。
170
- onOpenAutoFocus={(e) => e.preventDefault()}
171
- aria-labelledby={dialogLabelledBy}
172
- aria-label={dialogLabelledBy ? undefined : '提示'}
173
+ // 2026-07-05 D4:preventDefault 後必 focus content 容器本身(FocusScope 容器自帶 tabIndex=-1)——
174
+ // 焦點若停在 trigger(DismissableLayer 外),non-modal Popover 按 Tab 即觸發 focus-outside
175
+ // dismiss 直接關閉,CTA 鍵盤永遠到不了;focus 容器後 Tab 第一下即達第一個 CTA,Enter 按在
176
+ // 容器上無動作(原「不誤推進」意圖保留;對齊 dialog.tsx handleOpenAutoFocus `?? content` fallback)。
177
+ onOpenAutoFocus={(e) => {
178
+ e.preventDefault()
179
+ ;(e.currentTarget as HTMLElement).focus({ preventScroll: true })
180
+ }}
181
+ aria-labelledby={bodyTitleLabelledBy}
182
+ aria-label={headerTitle || title ? undefined : '提示'}
173
183
  {...props}
174
184
  >
175
185
  {headerTitle && (
@@ -177,7 +187,7 @@ const Coachmark = React.forwardRef<HTMLDivElement, CoachmarkProps>(
177
187
  // **不 hideClose** — 對齊 Popover / Dialog / 所有 overlay 家族 canonical:header 必有 dismiss X
178
188
  // (user 可隨時關閉,跟 Skip / Done 是不同入口,canonical 重複不冗)
179
189
  <PopoverHeader>
180
- <PopoverTitle id={titleId}>{headerTitle}</PopoverTitle>
190
+ <PopoverTitle>{headerTitle}</PopoverTitle>
181
191
  </PopoverHeader>
182
192
  )}
183
193
 
@@ -194,7 +204,7 @@ const Coachmark = React.forwardRef<HTMLDivElement, CoachmarkProps>(
194
204
  // 世界級參考:Notion / Linear / Figma onboarding tour 皆左對齊;Intercom Messenger 亦如是。
195
205
  <PopoverBody className="flex flex-col">
196
206
  {title && (
197
- <h3 id={!headerTitle ? titleId : undefined} className="text-body-lg font-medium text-foreground">{title}</h3>
207
+ <h3 id={!headerTitle ? bodyTitleId : undefined} className="text-body-lg font-medium text-foreground">{title}</h3>
198
208
  )}
199
209
  {description && (
200
210
  // title(body-lg 16)+ desc(body 14)→ reading-lg token(label tier 決定)
@@ -306,7 +306,7 @@ export const Overview = {
306
306
  <thead><tr><Th>Prop</Th><Th>Type</Th><Th>Default</Th><Th>說明</Th></tr></thead>
307
307
  <tbody>
308
308
  {[
309
- ['mode', "'edit'|'display'|'readonly'|'disabled'", "'edit'", 'FieldMode 四模式;disabled 原生屬性會自動覆蓋'],
309
+ ['mode', "'edit'|'display'|'readonly'|'disabled'", "'edit'", 'FieldMode 四模式;顯式傳入時永遠最優先,不被 disabled 覆蓋'],
310
310
  ['size', "'sm'|'md'|'lg'", "'md'", '尺寸,與 Button 共用 field-height token'],
311
311
  ['options', 'ComboboxOption[]', '—', '選項列表(extends SelectMenuOption:value / label / icon / avatar / description / disabled / group)'],
312
312
  ['value', 'string[]', '[]', '已選中的值陣列'],
@@ -315,7 +315,7 @@ export const Overview = {
315
315
  ['wrap', 'boolean', 'false', '換行模式——高度隨內容展開,Tags 自然換行'],
316
316
  ['clearable', 'boolean', 'false', '有值時顯示 X clear all 按鈕'],
317
317
  ['placeholder', 'string', '—', '無值時的提示文字;未傳時桌機 fallback 到 emptyPlaceholder(預設「選擇…」全形省略號),手機原生 select fallback「選擇...」'],
318
- ['disabled', 'boolean', 'false', '原生 disabled,自動覆蓋 mode'],
318
+ ['disabled', 'boolean', 'false', '原生屬性;未傳 mode 時 resolve 為 disabled 樣式(顯式 mode prop 恆優先,見 useResolvedFieldMode)'],
319
319
  ].map(([p, t, d, desc]) => (
320
320
  <tr key={p}><Td mono>{p}</Td><Td mono>{t}</Td><Td mono>{d}</Td><Td>{desc}</Td></tr>
321
321
  ))}
@@ -783,8 +783,8 @@ export const StateBehavior = {
783
783
  <div className="flex flex-col gap-1 text-[11px] text-fg-secondary">
784
784
  <span>1. useOverflowCount hook 量測 container 寬度與每個 tag 的自然寬度</span>
785
785
  <span>2. ResizeObserver 持續監聽容器變化</span>
786
- <span>3. 量測在 useLayoutEffect paint 前同步完成,超出的 tag 在繪製前就已隱藏,避免閃爍</span>
787
- <span>4. 超出的 tag DOM hidden 隱藏(非 display:none)</span>
786
+ <span>3. 量測在 useEffect 內首次 paint 後執行(確保 nested 場景所有 ref 已 attach),double-rAF 保證 layout 完成才量測;代價是可能 1-2 frame 閃爍,由 value-equal setState guard 收斂</span>
787
+ <span>4. 超出的 tag DOM hidden attribute 隱藏(UA 樣式即 display:none;節點保留在 DOM,量測時先全部解除隱藏再計算)</span>
788
788
  </div>
789
789
  </div>
790
790
 
@@ -160,7 +160,7 @@ value 軸 controlled-only;open 軸方向相反 — **uncontrolled-only**:`defaul
160
160
 
161
161
  ## Internal API(PeoplePicker stack wrapper 私用,end-user 勿用)
162
162
 
163
- `tagWrapperClassName` / `overflowWrapperClassName` / `tagAreaGapPx` / `tagAreaPaddingLeftPx` / `visibleCountOverride` 是 Combobox overflow 量測層的內部 hook,只供 DS-internal wrapper(PeoplePicker avatar stack)用,已標 `@internal`。`tagAreaPaddingLeftPx` 目前無 active consumer(PeoplePicker 走 `!px-3` 路徑),保留供未來精準 padding 但新 consumer 請先評估。`overflowShape` 是 public typed enum(矩形/圓形 +N),不在此列。
163
+ `tagWrapperClassName` / `overflowWrapperClassName` / `tagAreaGapPx` / `tagAreaPaddingLeftPx` / `visibleCountOverride` 是 Combobox overflow 量測層的內部 hook,只供 DS-internal wrapper(PeoplePicker avatar stack)用,已標 `@internal`。`tagAreaPaddingLeftPx` 目前無 active consumer(PeoplePicker 走 `!px-[var(--field-px)]` 路徑,`people-picker.tsx:371`),保留供未來精準 padding 但新 consumer 請先評估。`overflowShape` 是 public typed enum(矩形/圓形 +N),不在此列。
164
164
 
165
165
  ---
166
166
 
@@ -1,5 +1,5 @@
1
1
  // @benchmark-unverified-blanket: file-level retraction per M22 (d) — claims herein not individually URL-cited; treat as unverified visual/usage rumor unless retrofit per-claim. Hook escape preserved.
2
- // @renderer-symmetry-allow: ComboboxTagStack(display path)接 consumer tagRenderer 是 Stream C 下 cycle 工作 — 2026-05-12 先 ship Issues 2/3/4 surgical fixes(placeholder vocabulary + cell surface metrics + placeholder truncate),tagRenderer display-path unify deferred per field-controls.spec.md 共享 contract a。當前 multi=1 顯示已透過 PeoplePicker tagRenderer 線 314 PersonDisplay SSOT 對齊;其他 Combobox consumer 走 default `<Tag>` 純文字 backward-compat。
2
+ // @renderer-symmetry-allow: ComboboxTagStack(display path)接 consumer tagRenderer 是 Stream C 下 cycle 工作 — 2026-05-12 先 ship Issues 2/3/4 surgical fixes(placeholder vocabulary + cell surface metrics + placeholder truncate),tagRenderer display-path unify deferred per field-controls.spec.md 共享 contract a。當前 multi=1 顯示已透過 PeoplePicker tagRenderer(people-picker.tsx:426-444)PersonDisplay SSOT 對齊;其他 Combobox consumer 走 default `<Tag>` 純文字 backward-compat。
3
3
  // code-quality-allow: file-size — Combobox 含 NativeCombobox/CustomCombobox/useOverflowCount/OverflowTagList/ComboboxTagStack 5 子元件 + 共用 helpers,split-into-files 會破壞 measurement closures + 重複 type definitions。
4
4
  import * as React from 'react'
5
5
  import { X, ChevronDown } from 'lucide-react'
@@ -10,7 +10,7 @@ import { useFieldContext, useResolvedFieldSize, useResolvedFieldDisabled, useRes
10
10
  import { Tag } from '@/design-system/components/Tag/tag'
11
11
  import { ItemInlineAction, ItemSuffix } from '@/design-system/patterns/element-anatomy/item-anatomy'
12
12
  import { OverflowIndicator } from '@/design-system/components/OverflowIndicator/overflow-indicator'
13
- import { SelectMenu, type SelectMenuOption } from '@/design-system/components/SelectMenu/select-menu'
13
+ import { SelectMenu, forwardKeyToListbox, useActiveDescendant, type SelectMenuOption } from '@/design-system/components/SelectMenu/select-menu'
14
14
  import { useIsTouchDevice } from '@/design-system/hooks/use-is-touch-device'
15
15
  import { ICON_SIZE } from '@/design-system/tokens/uiSize/icon-size'
16
16
 
@@ -60,7 +60,7 @@ function useOverflowCount(
60
60
  const [state, setState] = React.useState({ visibleCount: totalCount, ready: !enabled })
61
61
  // 2026-05-18 Round 6 fix(per Codex M31 Round 6 H7 verdict + Step 5 共識):
62
62
  // `ofEl.offsetWidth` 在 expanded state(visibleCount === totalCount → overflow=0 →
63
- // `OverflowIndicator` line 92 `return null` → ofEl wrapper empty)= 0,fallback 60;
63
+ // `OverflowIndicator` count≤0 早退 `return null` → ofEl wrapper empty)= 0,fallback 60;
64
64
  // 在 collapsed state(+N rendered)= 真實寬(~28-32px)。同 `available` 在兩 state 給
65
65
  // 不同 verdict → 臨界值區 `max(B+g+Q, B+g+O) ≤ available < B+g+F` 振盪。Cache last
66
66
  // non-zero measurement → measurement state-independent → oscillation 收斂。
@@ -168,7 +168,7 @@ function useOverflowCount(
168
168
  }
169
169
  if (ofEl) ofEl.hidden = count >= totalCount
170
170
  // 2026-05-18 A' fix functional setState value-equal guard(per Codex Round 3 verdict):
171
- // sync calc useLayoutEffect 內 + ResizeObserver re-fire 同時跑 → 若每次都 new object setState
171
+ // effect calc 直跑 + ResizeObserver re-fire 同時跑 → 若每次都 new object setState
172
172
  // 觸發 re-render 即使值沒變,可能 cascade。回 prev 不更新 = avoid 抖動。
173
173
  setState(prev => (prev.visibleCount === count && prev.ready) ? prev : { visibleCount: count, ready: true })
174
174
  }
@@ -180,13 +180,13 @@ function useOverflowCount(
180
180
  //
181
181
  // 2026-05-16 Race close:capture rAF IDs + cancel on cleanup(原版 race I3 沒 close
182
182
  // 完;user 抓 path A 逐個 click vs path B 取消全選再全選 same length 不同 visible)。
183
- // 2026-05-18 A' fix(per Codex Round 3 共識,user 拍板「執行」)— sync calc in useLayoutEffect:
184
- // React 18 `useLayoutEffect` 在 DOM commit 後、瀏覽器繪製前同步跑,sync calc 在 paint 前
183
+ // 2026-05-18 A' fix(per Codex Round 3 共識,user 拍板「執行」)— effect fire 時 calc() 直跑
185
184
  // 完成 `el.hidden` 設定 + functional setState guard(value-equal 不更新 = 避免 ResizeObserver
186
- // 抖動 cascade rerender)。double-rAF fallback only(ResizeObserver / async update path)。
185
+ // 抖動 cascade rerender)。double-rAF fallback path(ResizeObserver / async update path)。
187
186
  // 解 user verbatim「tag 過長 / 過多會先全顯再變 +N 閃動」root cause(per codex Round 3 cite
188
- // `combobox.tsx:248 render 沒設 hidden + L129 calc imperative 寫 DOM`)。
189
- // 對齊 React docs https://react.dev/reference/react/useLayoutEffect pre-paint guarantee。
187
+ // render 沒設 hidden + calc imperative 寫 DOM)。
188
+ // 註:Round 3 原以 useLayoutEffect sync calc(paint 前設 hidden)實作;2026-05-18 Round 5
189
+ // 已改 React.useEffect after-paint(nested 場景 parent ref attach 時序,詳上方 Round 5 註解)。
190
190
  calc()
191
191
  let rafId1 = 0, rafId2 = 0
192
192
  const scheduleCalc = () => {
@@ -287,8 +287,9 @@ function OverflowTagList({ containerRef, items, size, wrap, renderTag, renderHid
287
287
 
288
288
  // 2026-05-18 A' fix(per Codex Round 3 共識,user 拍板「執行」)— 撤掉舊的 `style={{ opacity: ready ? 1 : 0 }}`
289
289
  // gate(掛在 `<span className="contents">` 上,但 CSS Display 3 spec 規定 `display:contents` 元素不產生 box,
290
- // parent opacity 對 children 無效 → gate 從沒生效是 dead code)。Flicker 已改 sync calc in useLayoutEffect 解
291
- // (L153 `calc()` 直跑,paint hidden 設好)。`ready` state 保留供未來 instrumentation / debug,不再當 visual gate。
290
+ // parent opacity 對 children 無效 → gate 從沒生效是 dead code)。Flicker `useOverflowCount` effect
291
+ // `calc()` 直跑(現 L190;2026-05-18 Round 5 useEffect after-paint + double-rAF guard,詳該 hook 註解)解。
292
+ // `ready` state 保留供未來 instrumentation / debug,不再當 visual gate。
292
293
  // 保留 `<span className="contents">` 維持 fragment-like rendering(parent JSX 期望 single child)。
293
294
  void ready // intentional: ready 留供 future debug,目前無 consumer
294
295
  return (
@@ -389,6 +390,8 @@ export interface ComboboxProps {
389
390
  searchAriaLabel?: string
390
391
  /** Empty-selection placeholder text。Default: 「選擇…」 */
391
392
  emptyPlaceholder?: string
393
+ /** 搜尋無結果提示(2026-07-04 Q4 拍板接線)— forward SelectMenu primitive SSOT(default「沒有符合的選項」) */
394
+ emptyText?: string
392
395
  /** a11y:無 Field wrapper 時提供 role='combobox' 的 accessible name(axe aria-input-field-name) */
393
396
  'aria-label'?: string
394
397
  /** Initial open state(uncontrolled)— 對齊 Select.defaultOpen / Radix Popover canonical。
@@ -602,8 +605,8 @@ function NativeCombobox({
602
605
  ) : null
603
606
 
604
607
  return (
605
- <div ref={__triggerRef} className={cn(fieldWrapperStyles({ mode: 'edit', variant: variant, size }), value.length > 0 && tagPadding[size], 'relative',
606
- wrap && 'items-start py-1', error && ['border-error hover:border-error-hover', 'focus-within:border-error focus-within:hover:border-error'], className)}
608
+ <div ref={__triggerRef} className={cn(fieldWrapperStyles({ mode: 'edit', variant: variant, size, error }), value.length > 0 && tagPadding[size], 'relative',
609
+ wrap && 'items-start py-1', className)}
607
610
  style={{ paddingRight: 'var(--field-px)', ...(wrap ? { height: 'auto' } : undefined) }} data-field-mode="edit" data-error={error ? '' : undefined}
608
611
  onClick={(e) => { if (e.target === e.currentTarget) { selectRef.current?.showPicker?.(); selectRef.current?.focus() } }}>
609
612
  {/* 2026-05-18 F2 sync(per user verbatim「modifying 修好 PeoplePicker stack 後改壞 Combobox tag display」
@@ -644,6 +647,7 @@ function CustomCombobox({
644
647
  searchPlaceholder = '搜尋…', // i18n-allow: DS default
645
648
  searchAriaLabel = '搜尋選項', // i18n-allow: DS default
646
649
  emptyPlaceholder = '選擇…', // i18n-allow: DS default
650
+ emptyText,
647
651
  defaultOpen = false,
648
652
  onOpenChange,
649
653
  __triggerRef,
@@ -675,6 +679,9 @@ function CustomCombobox({
675
679
  // a11y: 為 listbox 容器(SelectMenu 內 PopoverContent)建立穩定 id,讓 trigger 的
676
680
  // aria-controls 能指向它(WAI-ARIA combobox pattern 要求)。React.useId 在 SSR/CSR 都穩定。
677
681
  const listboxId = React.useId()
682
+ // a11y(2026-07-05 D4):追蹤 cmdk active item id → searchIn='trigger' 搜尋 input 綁 aria-activedescendant
683
+ // (機制詳 select-menu.tsx useActiveDescendant docblock;必在 early return 前呼叫 — React #310 hook 順序)。
684
+ const activeOptionId = useActiveDescendant(listboxId, open)
678
685
 
679
686
  React.useEffect(() => { if (!open) setSearch('') }, [open])
680
687
 
@@ -731,11 +738,11 @@ function CustomCombobox({
731
738
  aria-required={fieldCtx?.required || undefined}
732
739
  aria-describedby={fieldCtx?.descriptionId}
733
740
  aria-errormessage={error ? fieldCtx?.errorId : undefined}
734
- className={cn(fieldWrapperStyles({ mode: 'edit', variant: variant, size }), value.length > 0 && tagPadding[size], 'relative cursor-pointer',
741
+ className={cn(fieldWrapperStyles({ mode: 'edit', variant: variant, size, error }), value.length > 0 && tagPadding[size], 'relative cursor-pointer',
735
742
  wrap && 'items-start py-1',
736
743
  // 2026-05-06 v13.3 SSOT retire:per-control `open && 'border-primary'` 移除。Field default
737
- // 統一處理 — open=灰深(data-state)/ focus=藍(focus-within !important)。改一處全 control 跟動。
738
- error && ['border-error hover:border-error-hover', 'focus-within:border-error focus-within:hover:border-error'], className)}
744
+ // 統一處理 — open=灰深(data-state)/ focus=藍;2026-07-04 Q1:error 亦收進 error variant。
745
+ className)}
739
746
  style={{ paddingRight: 'var(--field-px)', ...(wrap ? { height: 'auto' } : undefined) }}
740
747
  data-field-mode="edit" data-error={error ? '' : undefined}
741
748
  // WAI-ARIA APG combobox 鍵盤開啟語意 — 對齊 sibling Select(select.tsx:593-598)。
@@ -745,6 +752,8 @@ function CustomCombobox({
745
752
  // open 後不 preventDefault 讓方向鍵自由流向選單導覽)。
746
753
  // 純 additive:此 trigger 原無 onKeyDown,不覆寫既有 handler;open 邏輯仍走 setOpen SSOT。
747
754
  onKeyDown={(e) => {
755
+ // 2026-07-05 D4 P0:open 後 ArrowUp/Down/Enter 轉送 cmdk root(select-menu.tsx forwardKeyToListbox)
756
+ if (open && forwardKeyToListbox(listboxId, e)) return
748
757
  if (e.key === 'Enter' || e.key === ' ') {
749
758
  // 2026-06-11 P0 a11y(R2,同 select.tsx 修):Combobox 恆 searchable → 原 `!searchable`
750
759
  // guard 使 Enter/Space 永遠開不了(僅 ArrowDown 可)。正確 guard = !open。
@@ -782,6 +791,8 @@ function CustomCombobox({
782
791
  // empty-state fallback to trigger placeholder canonical。
783
792
  placeholder={items.length === 0 ? placeholder : ''} onClick={(e) => { e.stopPropagation(); setOpen(true) }}
784
793
  aria-label={searchAriaLabel}
794
+ // a11y(2026-07-05 D4):cmdk active item id(useActiveDescendant)→ SR 播報方向鍵導覽中的 option
795
+ aria-activedescendant={activeOptionId}
785
796
  className="flex-1 min-w-[60px] bg-transparent outline-none text-body leading-compact relative z-10" />
786
797
  ) : undefined} />
787
798
  ) : (
@@ -813,6 +824,7 @@ function CustomCombobox({
813
824
  return (
814
825
  <SelectMenu
815
826
  loading={loading}
827
+ emptyText={emptyText}
816
828
  options={menuOptions}
817
829
  value={value}
818
830
  onValueChange={onChange as (value: string | string[]) => void}
@@ -862,7 +874,8 @@ export const comboboxMeta = {
862
874
  sizes: {
863
875
 
864
876
  },
865
- states: ['default', 'hover', 'active', 'focus-visible', 'disabled'],
877
+ // 'active' 移除 — trigger 走 Field chrome(field-wrapper 無按壓態)、menu row 走 MenuItem(其 meta 無 active)(2026-07-07 詞彙統一 DS-wide 按壓訊號盤點:檔內 0 active: utility / 0 *-active token)。
878
+ states: ['default', 'hover', 'focus-visible', 'disabled'],
866
879
  tokens: {
867
880
  bg: ['bg-disabled', 'bg-transparent'],
868
881
  fg: ['text-fg-disabled', 'text-fg-muted'],
@@ -69,18 +69,16 @@ export const CompositionRules: Story = {
69
69
  <li><LinkTo kind="Design System/Components/PeoplePicker/展示" name="單人"><span className="text-primary hover:text-primary-hover font-medium cursor-pointer">PeoplePicker</span></LinkTo>——人員搜尋(如指派任務負責人)</li>
70
70
  </ul>
71
71
 
72
- <h4>Pattern 2 — Popover + Command 組成 Command Palette(Cmd+K)</h4>
73
- <p>需要「全域 keyboard 觸發 + 跨頁搜尋 / 動作」→ 自行組合 <code>Popover + Command</code>(對齊 Linear ⌘K / Raycast / VS Code Quick Pick):</p>
74
- <pre className="text-xs"><code>{`<Popover open={cmdkOpen}>
75
- <Command>
76
- <CommandInput placeholder="輸入指令..." />
77
- <CommandList>
78
- <CommandGroup heading="動作">
79
- <CommandItem>新建文件</CommandItem>
80
- </CommandGroup>
81
- </CommandList>
82
- </Command>
83
- </Popover>`}</code></pre>
72
+ <h4>Pattern 2 — CommandDialog 組成 Command Palette(Cmd+K)</h4>
73
+ <p>需要「全域 keyboard 觸發 + 跨頁搜尋 / 動作」→ 用本 DS export 的 <code>CommandDialog</code>(cmdk + Radix Dialog 包裝,內部已包 Command),不自行組合浮層(對齊 Linear ⌘K / Raycast / VS Code Quick Pick):</p>
74
+ <pre className="text-xs"><code>{`<CommandDialog open={cmdkOpen} onOpenChange={setCmdkOpen}>
75
+ <CommandInput placeholder="輸入指令..." />
76
+ <CommandList>
77
+ <CommandGroup heading="動作">
78
+ <CommandItem>新建文件</CommandItem>
79
+ </CommandGroup>
80
+ </CommandList>
81
+ </CommandDialog>`}</code></pre>
84
82
  <p className="text-fg-muted">禁止:在產品端自己刻搜尋框 + 過濾清單 — 一律重用 Command,不要重造一份。</p>
85
83
  </div>
86
84
  </div>