@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
@@ -82,22 +82,11 @@ TabsList ─┬─ TabsTrigger [startIcon?] [label] [suffix?: badge? + endIcon?
82
82
  TabsContent ← 對應被選中的 trigger
83
83
  ```
84
84
 
85
- **單個 Trigger 內部**:
86
-
87
- ```
88
- [startIcon?] [label] [suffix?]
89
- ↑ ↑ ↑
90
- 16/20px plain span(gap-1): [badge?] [endIcon?]
91
- span
92
- │─── gap-2 ────│─── gap-2 ──│
93
- ```
94
-
95
- - **Trigger 三層 slot 間 gap-2**(8px),對標 **item-layout pattern** 的橫向 inline 變體
96
- - **suffix 內 gap-1**(4px),對標 **Button** suffix wrapper(`button.tsx` 的 `<span className="inline-flex items-center gap-1">`)
85
+ **單個 Trigger 內部**:`[startIcon 16/20px] ─gap-2─ [label] ─gap-2─ [suffix span(gap-1): badge? endIcon?]`——slot 間 **gap-2**(8px)對標 **item-layout pattern** 橫向 inline 變體;suffix 內 **gap-1**(4px)對標 **Button** suffix wrapper(`button.tsx` 的 `<span className="inline-flex items-center gap-1">`)
97
86
  - `startIcon` 描述 tab 的內容性質(人像 icon 配「成員」、齒輪 icon 配「設定」)
98
87
  - `badge` 傳達該 tab 底下的待處理計數(「通知 3」「成員 12」)
99
88
  - `endIcon` **純視覺 indicator only**(方向 / 狀態 — ChevronDown 暗示「展開後看到子內容」、Pin / Star 狀態徽記)。**不拼 click 行為** — 點到 endIcon 跟點到 tab body 同效果(切 tab)
100
- - `inlineAction`(2026-05-21 加)拆分 click target:點到 inlineAction 走它自己的 handler 不切 tab;典型如「『更多 ▾』tab 後綴點開 overflow dropdown 不切 tab」(split-click pattern,對齊 GitHub「Code ▾」/ Linear "Triage..." menu / Atlassian split-tab 共識)
89
+ - `inlineAction`(2026-05-21 加)拆分 click target:點到 inlineAction 走它自己的 handler 不切 tab;典型如「『更多 ▾』tab 後綴點開 overflow dropdown 不切 tab」(split-click pattern,對齊 GitHub「Code ▾」/ Linear "Triage..." menu / Atlassian split-tab 共識)。DOM 上 inlineAction 是 trigger 的 **sibling**(絕對定位在 trigger 以 paddingRight 預留的區域,同 SidebarMenuButton suffixNode canonical),**非巢狀 button** — Radix trigger 是原生 `<button>`,巢 button 為 HTML 不合法 + axe nested-interactive(2026-07-05 D4 修)
101
90
 
102
91
  ### 對標對象與故意的偏離
103
92
 
@@ -116,7 +105,7 @@ Tabs trigger = **item-layout 橫向變體 + Button 高度系統**。對標 item-
116
105
 
117
106
  **Tabs 目前只有一種視覺**:底線標示型(underline indicator)。
118
107
 
119
- 選中 trigger 底部有 2px 的 `bg-primary-hover` 底線,未選 trigger 僅文字色變化。未來若需要「填色型」或「pill 型」tabs,應先評估是否真的是 Tabs 的語意(通常是 SegmentedControl 或 Button group 的偽裝)再考慮擴充。
108
+ 選中 trigger 底部有 2px 的 `bg-primary` 底線(2026-07-06 拍板:持續選中站 base),未選 trigger 僅文字色變化。未來若需要「填色型」或「pill 型」tabs,應先評估是否真的是 Tabs 的語意(通常是 SegmentedControl 或 Button group 的偽裝)再考慮擴充。
120
109
 
121
110
  ---
122
111
 
@@ -191,17 +180,11 @@ Tabs 是 **navigation anchor**,不是 compact control:
191
180
 
192
181
  ## Underline 與 TabsList border 的視覺關係
193
182
 
194
- TabsList 底部有 1px gray border(`border-divider`,neutral-4),selected trigger 有 2px primary-hover 底線。**這兩條線視覺上必須是同一條**——selected 底線從 TabsList 的 gray border 位置「長出來」,不得出現雙線。
183
+ TabsList 底部有 1px gray border(`border-divider`,neutral-4),selected trigger 有 2px primary 底線。**這兩條線視覺上必須是同一條**——selected 底線從 TabsList 的 gray border 位置「長出來」,不得出現雙線。
195
184
 
196
185
  **原則**:selected 底線必須覆蓋並延伸 TabsList 的 gray border,避免疊線。實作手法(pseudo-element 定位)見 `.tsx`。
197
186
 
198
- **色 token = `--divider`(neutral-4)而非 `--border`(neutral-5)**(2026-05-18 改 per user verbatim「我認為應該把 tabs 的下底線統一改成是 divider 色吧?」+「做」approval):
199
- - 跟 Dialog / Sheet / Popover / Sidebar header `border-b border-divider` 同色 — chrome separator 一致
200
- - withTabs scenario 下 tabs underline = chrome separator,色不同會視覺斷裂
201
- - Selected trigger 2px primary indicator overlay underlying 1px divider 對比仍清楚(primary 比 neutral-4 強得多)
202
- - 對齊 `color.spec.md`「T-junction connectivity 原則」段 outer-vs-divider 判準的「T-junction seamless」直覺(tabs underline 跟 header separator T 字交接)
203
-
204
- 在 Dialog / Sidebar header 內特別重要——header 的 `border-b` 和 Tabs 的 `border-b` 必須感知為同一條水平線。
187
+ **色 token = `--divider`(neutral-4)而非 `--border`(neutral-5)**(2026-05-18 改 per user verbatim「我認為應該把 tabs 的下底線統一改成是 divider 色吧?」+「做」approval):跟 Dialog / Sheet / Popover / Sidebar header `border-b border-divider` 同色——chrome separator 一致,withTabs scenario 下 underline = chrome separator,色不同會視覺斷裂;selected trigger 2px primary indicator overlay 1px divider 對比仍清楚;對齊 `color.spec.md`「T-junction connectivity 原則」(tabs underline 跟 header separator T 字交接)。在 Dialog / Sidebar header 內特別重要——header 的 `border-b` 和 Tabs 的 `border-b` 必須感知為同一條水平線。
205
188
 
206
189
  ---
207
190
 
@@ -209,13 +192,9 @@ TabsList 底部有 1px gray border(`border-divider`,neutral-4),selected
209
192
 
210
193
  Tabs 常與容器 header 的底邊 border 合併——**視覺上只有一條線**,不是 header border + tabs border 疊兩條。
211
194
 
212
- **做法**:Tabs 的 `TabsList` 底部 border 與 header 的 `border-b` 實際上是**同一條線**(設計上重疊、實作上不重複渲染)。**Header 退讓**(移除自己 `border-b`),**Tabs 接管**(自身 `border-b border-divider` per `TABS_LIST_BASE`,2026-05-18 fd843c25 統一 chrome separator 色)。三種 overflow mode 的 TabsList 一律跨滿父容器(none 模式 `w-full`;scroll / menu 模式 inner list `min-w-full` 保留溢出成長,見 L184;`TABS_LIST_BASE` + `overflow-y-hidden` 阻 y auto-promote,2026-05-19 c359c711 border owner 升 list 內部);chrome header 內的全寬 paint 由 `ChromeHeader` tabsSlot wrapper 以 `[&>[role=tablist]]:w-full` 強制(見 `header-canonical.spec.md`)。
195
+ **做法**:Tabs 的 `TabsList` 底部 border 與 header 的 `border-b` 實際上是**同一條線**(設計上重疊、實作上不重複渲染)。**Header 退讓**(移除自己 `border-b`),**Tabs 接管**(自身 `border-b border-divider` per `TABS_LIST_BASE`,2026-05-18 fd843c25 統一 chrome separator 色)。三種 overflow mode 的 TabsList 一律跨滿父容器(none 模式 `w-full`;scroll / menu 模式 inner list `min-w-full` 保留溢出成長,見「寬度行為」段;`TABS_LIST_BASE` + `overflow-y-hidden` 阻 y auto-promote,2026-05-19 c359c711 border owner 升 list 內部);chrome header 內的全寬 paint 由 `ChromeHeader` tabsSlot wrapper 以 `[&>[role=tablist]]:w-full` 強制(見 `header-canonical.spec.md`)。
213
196
 
214
- **世界級對照(verbatim cite)**:
215
- - **GitHub Primer PageHeader**:「`hasBorder` defaults true,**but border NOT rendered if Navigation child contains UnderlineNav**;UnderlineNav itself provides bottom border」(`primer.style/components/page-header/react`)
216
- - **Ant Design Tabs**:line type 自帶 bottom border(`ant.design/components/tabs`)
217
- - **Mantine Tabs**:default variant 自包 bottom border(`mantine.dev/core/tabs/`)
218
- - **Counter-pattern(reject)**:Material UI 走「container 畫 border + tabs 不畫」(`<Box sx={{ borderBottom: 1 }}><Tabs>...</Tabs></Box>` 從 `mui.com/material-ui/react-tabs/`)— 本 DS reject,理由:tabs underline 需 1px gray base line(`tabs.spec.md:185-187`),tabs 不畫 border 會讓 2px primary indicator 浮空失去 base
197
+ **世界級對照(verbatim cite)**:GitHub Primer PageHeader「`hasBorder` defaults true,**but border NOT rendered if Navigation child contains UnderlineNav**;UnderlineNav itself provides bottom border」(`primer.style/components/page-header/react`);Ant Tabs line type 自帶 bottom border(`ant.design/components/tabs`);Mantine Tabs default variant 自包 bottom border(`mantine.dev/core/tabs/`)。**Counter-pattern(reject)**:Material UI 走「container 畫 border + tabs 不畫」(`<Box sx={{ borderBottom: 1 }}><Tabs>` 從 `mui.com/material-ui/react-tabs/`)——本 DS reject,理由:tabs underline 需 1px gray base line(見「Underline 與 TabsList border 的視覺關係」段),tabs 不畫 border 會讓 2px primary indicator 浮空失去 base
219
198
 
220
199
  **Auto-suppress 機制(Phase 2 production code 提案)**:header primitive 加 `withTabs?: boolean` prop → true 時自動移除自身 `border-b`,免 consumer 手動忘記。對齊 GitHub Primer 的 auto-suppress 模式(免 consumer 手動 prop)。完整 cross-header canonical 詳 `patterns/header-canonical/header-canonical.spec.md` W1。
221
200
 
@@ -227,27 +206,14 @@ Size 建議:overlay / chrome header 內用 `sm`(32/40)— 對應 close X 也是
227
206
 
228
207
  ## 狀態
229
208
 
230
- ### active
231
-
232
- 選中的 trigger:
233
- - 文字色 `text-foreground`、`font-medium`
234
- - 底部 2px 底線 `bg-primary-hover`(對齊 semantic.css:選中狀態的邊框/文字統一用 `--primary-hover`)
235
-
236
- 未選的 trigger:
237
- - 文字色 `text-fg-secondary`、一般 weight
238
- - 無底線
239
- - hover 時文字色轉 `text-foreground`
240
-
241
- ### disabled
242
-
243
- - 文字色 `text-fg-disabled`
244
- - 無 hover 色變化、無底線
245
- - Cursor 變為 `not-allowed`(滑鼠移過明確告知不可點)
246
- - 不接受鍵盤 focus
247
-
248
- ### focus-visible
209
+ DS 詞彙 **selected**(持續選中);Radix DOM attr 為 `data-state="active"`(primitive 命名,不改寫)。
249
210
 
250
- `ring-2 ring-ring ring-offset-1`,與 Button 一致。鍵盤導覽(Radix Tabs 原生支援左右箭頭)由 Radix 處理。
211
+ | State | 視覺 |
212
+ |---|---|
213
+ | selected | `text-foreground` + `font-medium` + 底部 2px `bg-primary` 底線(semantic.css 選中規則;2026-07-06 拍板持續選中站 base,Ant inkBarColor = colorPrimary 同款)|
214
+ | 未選 | `text-fg-secondary`、一般 weight、無底線;hover 時文字色轉 `text-foreground` |
215
+ | disabled | `text-fg-disabled`;無 hover 色變化、無底線;`cursor-not-allowed`;不接受鍵盤 focus |
216
+ | focus-visible | `ring-2 ring-ring ring-offset-1`,與 Button 一致;鍵盤導覽(左右箭頭)由 Radix 原生處理 |
251
217
 
252
218
  ---
253
219
 
@@ -292,6 +258,7 @@ Tabs anatomy 採 DS 標準結構 + 元件特有矩陣:
292
258
  - 標準 `SizeMatrix` / `ColorMatrix` / `StateBehavior`(selected / hover / disabled)—— 結構性對照,呈現 underline / selected border 與 TabsList border 的視覺關係(見「Underline 與 TabsList border 的視覺關係」段),單組合 Inspector 無法並列呈現
293
259
  - 元件特有 `OverflowMatrix`(scroll / menu / fade 三模式)—— 「tabs 放不下時怎麼處理」是設計決策題,需三方案 side-by-side 同時比較,Inspector 單組合無法呈現
294
260
  - `SpacingTokens` —— 間距 token 對照
261
+ - `Accessibility`(`無障礙`)—— Radix tabs a11y 預設摘要 + Keyboard 行為對照(詳「A11y 預設」段)
295
262
 
296
263
  ---
297
264
 
@@ -42,7 +42,7 @@ import { ICON_SIZE } from '@/design-system/tokens/uiSize/icon-size'
42
42
  * slot 間 gap-2,suffix 內 gap-1
43
43
  *
44
44
  * ── Selected underline ──
45
- * 使用 ::after 絕對定位在 bottom: -1px,2px primary-hover,
45
+ * 使用 ::after 絕對定位在 bottom: -1px,2px primary(2026-07-06 拍板:持續選中站 base),
46
46
  * 蓋住 TabsList 的 1px gray border(單一視覺線條,不雙線)。
47
47
  */
48
48
 
@@ -65,6 +65,23 @@ const TabsValueContext = React.createContext<{
65
65
  onValueChange?: (value: string) => void
66
66
  }>({})
67
67
 
68
+
69
+ // ── getElementRef(Radix Slot idiom;鏡射 chip.tsx 同名 helper — 2026-07-05 M10 同型修)──
70
+ // 讀 child 既有 ref 而不觸發 React 18/19 dev deprecation warning。
71
+ function getElementRef(element: React.ReactElement): React.Ref<HTMLElement> | undefined {
72
+ let getter = Object.getOwnPropertyDescriptor(element.props, 'ref')?.get
73
+ let mayWarn = getter && 'isReactWarning' in getter && getter.isReactWarning
74
+ if (mayWarn) return (element as unknown as { ref?: React.Ref<HTMLElement> }).ref
75
+ getter = Object.getOwnPropertyDescriptor(element, 'ref')?.get
76
+ mayWarn = getter && 'isReactWarning' in getter && getter.isReactWarning
77
+ if (mayWarn) return (element.props as { ref?: React.Ref<HTMLElement> }).ref
78
+ return (
79
+ (element.props as { ref?: React.Ref<HTMLElement> }).ref ??
80
+ (element as unknown as { ref?: React.Ref<HTMLElement> }).ref ??
81
+ undefined
82
+ )
83
+ }
84
+
68
85
  const Tabs = React.forwardRef<
69
86
  React.ElementRef<typeof TabsPrimitive.Root>,
70
87
  TabsProps
@@ -272,12 +289,21 @@ const MenuTabsList = React.forwardRef<
272
289
  [children]
273
290
  )
274
291
 
275
- const enhancedChildren = items.map((child, i) =>
276
- React.cloneElement(
292
+ // 2026-07-05 M10 同型修(Chip MenuChipGroup 同款):原 cloneElement 直接覆寫 child ref,
293
+ // consumer 自有 ref 被靜默取代 → compose(registerItem + child 原 ref)。
294
+ const enhancedChildren = items.map((child, i) => {
295
+ const childRef = getElementRef(child)
296
+ return React.cloneElement(
277
297
  child as React.ReactElement<{ ref?: React.Ref<HTMLElement> }>,
278
- { ref: registerItem(i) }
298
+ {
299
+ ref: (el: HTMLElement | null) => {
300
+ registerItem(i)(el)
301
+ if (typeof childRef === 'function') childRef(el)
302
+ else if (childRef) (childRef as React.MutableRefObject<HTMLElement | null>).current = el
303
+ },
304
+ }
279
305
  )
280
- )
306
+ })
281
307
 
282
308
  // Menu 模式沒有 arrows,但仍套 fade mask (reserveArrowWidth: 0) 軟化內容硬邊
283
309
  // code-quality-allow: long-function — helper fn 結構緊密,拆 sub-fn 會跨 fn 傳 state 反而複雜
@@ -365,8 +391,8 @@ const tabsTriggerVariants = cva(
365
391
  'transition-colors duration-150',
366
392
  'cursor-pointer select-none',
367
393
  'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1',
368
- // Trigger 無水平 padding — 寬度 = 內容寬度。triggers 間的分隔靠 TabsList 的 gap-[layout-space-loose]
369
- // selected underline:::after 絕對定位在 bottom:-1px,2px primary-hover
394
+ // Trigger 無水平 padding — 寬度 = 內容寬度。triggers 間的分隔靠 TabsList 的 gap-[var(--layout-space-loose)]
395
+ // selected underline:::after 絕對定位在 bottom:-1px,2px primary(持續選中 base)
370
396
  // left-0/right-0 因為 trigger 已無 padding,底線等於內容寬度
371
397
  // 底線蓋住 TabsList 的 1px gray border,視覺單一線條
372
398
  'after:absolute after:left-0 after:right-0 after:bottom-[-1px] after:h-0.5',
@@ -375,7 +401,9 @@ const tabsTriggerVariants = cva(
375
401
  'hover:text-foreground',
376
402
  // selected
377
403
  'data-[state=active]:text-foreground data-[state=active]:font-medium',
378
- 'data-[state=active]:after:bg-primary-hover',
404
+ // 2026-07-06 user 拍板:選中底線 hover 階 → primary base(持續選中站 base;
405
+ // Ant inkBarColor = colorPrimary / Material 3 active indicator = primary 同款)
406
+ 'data-[state=active]:after:bg-primary',
379
407
  // disabled:cursor-not-allowed + 不吃 hover 色
380
408
  // 不用 pointer-events-none,否則 cursor 不會改變;button[disabled] 本身就擋 click
381
409
  'disabled:cursor-not-allowed disabled:text-fg-disabled',
@@ -416,8 +444,11 @@ interface TabsTriggerProps
416
444
  * Inline action slot(2026-05-21 v3 加,per user「圖一 後綴應該是 inline action」+「點擊
417
445
  * 該 tab 的 inline action 跟其他地方應該不同反應」directive):
418
446
  * 提供 `<ItemInlineAction>` / `<DropdownMenuTrigger asChild><ItemInlineAction ... /></DropdownMenuTrigger>`
419
- * 等獨立 click target。**TabsTrigger 自動 stopPropagation**,inline action 點擊不冒泡到 tab body,
420
- * 達成 split-click 行為(對齊 GitHub「Code ▾」/ Linear "Triage..." menu / Atlassian split-tab 共識)。
447
+ * 等獨立 click target。**DOM 上是 trigger sibling(絕對定位),非巢狀 button**
448
+ * (2026-07-05 D4 修:Radix TabsTrigger 是原生 `<button>`,巢 button = HTML 不合法 +
449
+ * axe nested-interactive;同 SidebarMenuButton suffixNode canonical),inline action
450
+ * 點擊天然不落在 tab body,達成 split-click 行為(對齊 GitHub「Code ▾」/
451
+ * Linear "Triage..." menu / Atlassian split-tab 共識)。
421
452
  *
422
453
  * 跟 endIcon 區別:endIcon = 純視覺 indicator(無獨立行為,連同 tab 一起 click),
423
454
  * inlineAction = 獨立 click target(自己的 handler,不切 tab)。語意分家明確。
@@ -432,12 +463,23 @@ const TabsTrigger = React.forwardRef<
432
463
  const { size } = React.useContext(TabsContext)
433
464
  // 2026-05-18 改 import ICON_SIZE SSOT(per user『做完』approval,消除 M17 違反 7+ 重複 ternary)
434
465
  const iconSize = ICON_SIZE[size as 'sm' | 'md' | 'lg']
435
- const hasSuffix = badge != null || EndIcon !== undefined || inlineAction != null
466
+ const hasSuffix = badge != null || EndIcon !== undefined
467
+ // 2026-07-05 D4 修(deep-audit「Tabs:inlineAction slot 造成 button 巢 button」):
468
+ // Radix TabsTrigger render 原生 <button>,inlineAction(ItemInlineActionButton)也是原生
469
+ // <button> — 巢狀 interactive = HTML 不合法 + axe nested-interactive(serious)+ tab 的
470
+ // accessible name 混入 action 內容。改 sibling 佈局(同 SidebarMenuButton suffixNode
471
+ // canonical,sidebar.tsx):wrapper span(relative)內 trigger + 絕對定位 action;trigger 以
472
+ // paddingRight 預留 action 空間 — icon 寬 + 原 gap(有 badge/endIcon 時 4px = suffix 內
473
+ // gap-1;僅 action 時 8px = slot 間 gap-2),幾何與巢狀版一致。click 天然分流(action 不在
474
+ // trigger 子樹,不冒泡),原 stopPropagation 五連發不再需要。
475
+ const hasAction = inlineAction != null
476
+ const actionPaddingRight = hasAction ? iconSize + (hasSuffix ? 4 : 8) : undefined
436
477
 
437
- return (
478
+ const trigger = (
438
479
  <TabsPrimitive.Trigger
439
480
  ref={ref}
440
481
  className={cn(tabsTriggerVariants({ size }), className)}
482
+ style={hasAction ? { paddingRight: actionPaddingRight } : undefined}
441
483
  {...props}
442
484
  >
443
485
  {StartIcon && <StartIcon size={iconSize} aria-hidden />}
@@ -446,30 +488,23 @@ const TabsTrigger = React.forwardRef<
446
488
  <span className="inline-flex items-center gap-1">
447
489
  {badge}
448
490
  {EndIcon && <EndIcon size={iconSize} aria-hidden />}
449
- {inlineAction != null && (
450
- // 2026-05-21 split-click invariant:inlineAction 點擊不冒泡到 TabsPrimitive.Trigger。
451
- // Radix Tabs 在 3 個 channel 觸發 tab 切換,全部 stopPropagation:
452
- // - onMouseDown(primary, Radix Tabs source code main switch trigger)
453
- // - onFocus(activationMode='automatic' default,focus 落內部按鈕也算「focused」)
454
- // - onKeyDown Enter/Space(鍵盤啟動)
455
- // 加 onPointerDown / onClick 防禦其他 framework 慣例。
456
- // 對齊 GitHub「Code ▾」/ Linear "Triage..." split-tab 共識。
457
- <span
458
- onPointerDown={(e) => e.stopPropagation()}
459
- onMouseDown={(e) => e.stopPropagation()}
460
- onClick={(e) => e.stopPropagation()}
461
- onFocus={(e) => e.stopPropagation()}
462
- onKeyDown={(e) => {
463
- if (e.key === 'Enter' || e.key === ' ') e.stopPropagation()
464
- }}
465
- >
466
- {inlineAction}
467
- </span>
468
- )}
469
491
  </span>
470
492
  )}
471
493
  </TabsPrimitive.Trigger>
472
494
  )
495
+
496
+ if (!hasAction) return trigger
497
+
498
+ return (
499
+ // sibling 佈局 wrapper:span(relative)內 trigger + 絕對定位 action。
500
+ // Radix roving focus 用 collection context 收 trigger,不要求 trigger 是 List 直接子層。
501
+ <span className="relative inline-flex">
502
+ {trigger}
503
+ <span className="absolute top-1/2 -translate-y-1/2 right-0 inline-flex items-center">
504
+ {inlineAction}
505
+ </span>
506
+ </span>
507
+ )
473
508
  })
474
509
  TabsTrigger.displayName = 'TabsTrigger'
475
510
 
@@ -509,9 +544,10 @@ export const tabsMeta = {
509
544
  md: { fieldHeight: 40, iconSize: 16, typography: 'body' },
510
545
  lg: { fieldHeight: 48, iconSize: 20, typography: 'body-lg' },
511
546
  },
512
- states: ['default', 'hover', 'active', 'focus-visible', 'disabled'],
547
+ // 'selected' = 選中 tab(Radix DOM attr 為 data-state="active",meta 詞彙統一用 'selected' 避免與「按壓 active」混用;2026-07-07)
548
+ states: ['default', 'hover', 'selected', 'focus-visible', 'disabled'],
513
549
  tokens: {
514
- bg: ['bg-primary-hover', 'bg-transparent'],
550
+ bg: ['bg-primary', 'bg-transparent'],
515
551
  fg: ['text-fg-disabled', 'text-fg-secondary', 'text-foreground'],
516
552
  ring: ['ring-ring'],
517
553
  },
@@ -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
  )
@@ -261,7 +261,7 @@ export const IconRule: Story = {
261
261
 
262
262
  <Rule
263
263
  title="❌ icon + avatar 同時用(互斥)"
264
- note="icon 和 avatar 都是 prefix slot,設計慣例上互斥( consumer 自律,非型別強制)。若同時傳,icon 與 avatar 會並列渲染,破壞 Tag 內部結構"
264
+ note="icon 和 avatar 都是 prefix slot,設計慣例上互斥(非型別強制;dev mode 同時傳會 console.warn 提醒 — 對齊 Button overlayBadge dev-warn 先例)。若同時傳,icon 與 avatar 仍會並列渲染,破壞 Tag 內部結構"
265
265
  >
266
266
  <Label warn>(範例省略)設計慣例上互斥——同時傳會並列渲染,請只擇一</Label>
267
267
  </Rule>
@@ -125,15 +125,11 @@ Tag 是 inline label,用於分類標籤、狀態標記、多選已選值。不
125
125
  | `purple` | `--color-purple-*` |
126
126
  | `magenta` | `--color-magenta-*` |
127
127
 
128
- 所有有色 variant 直接使用 primitive token(`--color-blue-*` 等),不使用 semantic token(`--primary`、`--error`)。Tag 的「blue」代表藍色本身,不代表「主要操作」語義;「red」代表紅色(品牌紅 hue 25),**不代表 `--error`**。
129
-
130
- **注意**:`red` 與語意 `--error` 完全無關 —— `--error` = `--color-deep-orange-6`(hue 38),而 `red` variant = `--color-red-*`(品牌紅 hue 25)。三者(`red` / `deep-orange` / `--error`)是各自獨立的色相 / 語意 token,**無映射關係**。2026-06-04 修正原「red variant 接 deep-orange」offset。
128
+ 所有有色 variant 直接使用 primitive token(`--color-blue-*` 等),不使用 semantic token(`--primary`、`--error`)。Tag 的「blue」代表藍色本身,不代表「主要操作」語義;`red` = 品牌紅 hue 25,與語意 `--error`(= `--color-deep-orange-6`,hue 38)**完全無關**——`red` / `deep-orange` / `--error` 是各自獨立的色相 / 語意 token,**無映射關係**(2026-06-04 修正原「red variant 接 deep-orange」offset)。
131
129
 
132
130
  ### 文字色
133
131
 
134
- 所有有色 variant 的文字一律使用 primitive step-7(`--color-{hue}-7`),優先辨識度。Primitives 的相對色階公式在 dark mode 自動把 step-7 解析為高對比方向,確保文字在 subtle 底色上始終可讀。Tag 以呈現資訊為主,小面積色塊文字需要更高對比才能舒適閱讀。neutral variant 用 `text-foreground`(不適用此規則)。
135
-
136
- 見 `color.spec.md` 的「文字色 Step 原則」。
132
+ 所有有色 variant 文字一律 primitive step-7(`--color-{hue}-7`)——小面積色塊文字需高對比才可讀,primitives 相對色階公式在 dark mode 自動把 step-7 解析為高對比方向。neutral variant 用 `text-foreground`(不適用此規則)。見 `color.spec.md`「文字色 Step 原則」。
137
133
 
138
134
  ---
139
135
 
@@ -184,24 +180,11 @@ Dismiss 是 Inline Action,但 icon 色繼承 Tag 文字色(非 `fg-muted`)
184
180
 
185
181
  #### 為什麼 Tag 同時用 primitive(靜態色)和 semantic(互動色)
186
182
 
187
- 這是 **有意的職責分離**,非 code smell
188
-
189
- | 概念 | 為什麼住這層 |
190
- |------|------------|
191
- | 靜態色(subtle bg、text、solid bg)→ **primitive** | 不需要 mode 翻轉知識——primitives 公式翻轉已自動處理 step-1 alpha、step-7 對比方向 |
192
- | 互動色(hover、active)→ **semantic `--{hue}-hover/active`** | 需要保證「hover 永遠較亮、active 永遠較暗」,dark mode swap 必須住 semantic 層 |
193
-
194
- 兩個概念本來就不該綁同一層。Tailwind 也是同樣分離:`bg-blue-500`(靜態 scale step)vs `hover:bg-blue-600 dark:hover:bg-blue-400`(互動需 consumer 處理 mode)。差別只在於我們把 mode swap 封裝進 token,consumer 不需自己寫 dark variant。
195
-
196
- 詳細流派討論見 `color.spec.md` 的「架構流派定位」段落。
183
+ **有意的職責分離**,非 code smell:靜態色(subtle bg、text、solid bg)住 **primitive**——primitives 公式已自動處理 dark mode 翻轉(step-1 alpha、step-7 對比方向);互動色(hover、active)住 **semantic `--{hue}-hover/active`**——「hover 永遠較亮、active 永遠較暗」的 mode swap 必須住 semantic 層。Tailwind 同款分離(`bg-blue-500` 靜態 vs `hover:bg-blue-600 dark:hover:bg-blue-400` 互動),差別在我們把 mode swap 封裝進 token,consumer 不需自寫 dark variant。詳細流派討論見 `color.spec.md`「架構流派定位」。
197
184
 
198
185
  #### `--{hue}-hover/active` 的定位
199
186
 
200
- Semantic 互動 token,從 brand color (`--primary-hover`) 延伸到所有作為 bg 的色相。**刻意只有 hover/active 兩個 token**——base/subtle/text 用 primitive。沒有 `--blue` base 或 `--blue-subtle`,避免重新引入完整 categorical 層。
201
-
202
- #### Neutral 例外
203
-
204
- neutral solid 的 bg 是 `--color-neutral-9`,在 dark mode 反轉(近黑 → 近白),bg 本身會跨 mode 變色。所以 overlay 不能用 `--{hue}-hover/active`(這些針對「bg 不變」的飽和色),改用 `--inverse-neutral-hover` / `--inverse-neutral-active`(內部處理 mode 鏡射 swap)。
187
+ Semantic 互動 token,從 brand color (`--primary-hover`) 延伸到所有作為 bg 的色相。**刻意只有 hover/active 兩個 token**——base/subtle/text 用 primitive,沒有 `--blue` base 或 `--blue-subtle`,避免重新引入完整 categorical 層。**Neutral 例外**:neutral solid bg = `--color-neutral-9`,dark mode 反轉(近黑 → 近白)bg 本身跨 mode 變色,不能用針對「bg 不變」飽和色的 `--{hue}-hover/active`,改用 `--inverse-neutral-hover` / `--inverse-neutral-active`(內部處理 mode 鏡射 swap)。
205
188
 
206
189
  Inline Action 的其他規則(尺寸、hover 背景 pattern)不變。
207
190
 
@@ -240,27 +223,17 @@ Inline Action 的其他規則(尺寸、hover 背景 pattern)不變。
240
223
  | `solid` | `boolean` | 深底配對前景模式(step-6 背景;yellow/amber/orange/lime 用深字,其餘白字),預設 false |
241
224
  | `unbounded` | `boolean` | 預設 false = 寬度 cap 160px;true = 寬度交給 parent constrain(cell-as-input narrow cell 場景),詳「邊界案例」 |
242
225
 
243
- ## 圓角
244
-
245
- 統一 `rounded-md`(4px)。
226
+ ## 圓角與間距
246
227
 
247
- ## Tag 間距
248
-
249
- tag tag 之間:`gap-1`(4px)。
250
-
251
- ## 包含 Tag 的 Field
252
-
253
- Field 內包含 Tag 時,Field 的 padding 改為 `(field-height - tag-height) / 2`,確保 tag 四邊等距。
228
+ - 圓角:統一 `rounded-md`(4px)
229
+ - Tag 與 Tag 之間:`gap-1`(4px)
230
+ - Field 內包含 Tag 時,Field 的 padding 改為 `(field-height - tag-height) / 2`,確保 tag 四邊等距
254
231
 
255
232
  ---
256
233
 
257
234
  ## Dismiss(Inline Action)
258
235
 
259
- 傳入 `onRemove` callback 時,Tag 自動渲染 remove 按鈕。消費端不需要自行建構 remove 按鈕或知道 inline action 的尺寸規格。
260
-
261
- 尺寸 / 互動規則共用 SSOT 見 `../../patterns/element-anatomy/inline-action.spec.md`(獨立 SSOT,2026-04-24 自 item-anatomy 抽出)。
262
-
263
- **Icon 色彩特例(colored host)**:Tag dismiss icon **繼承 Tag 文字色**(非 `fg-muted`)— Tag 屬「colored host」分類。完整兩支規則(neutral host vs colored host)見上述 item-anatomy.spec.md SSOT。詳細每 variant 的 icon 色 + hover 背景見上面「Dismiss 行為(Inline Action 客製)」段。
236
+ 傳入 `onRemove` callback 時,Tag 自動渲染 remove 按鈕,消費端不需自行建構或知道 inline action 尺寸規格。尺寸 / 互動規則 SSOT 見 `../../patterns/element-anatomy/inline-action.spec.md`(含 neutral host vs colored host 兩支規則;Tag 屬 colored host——dismiss icon 繼承 Tag 文字色而非 `fg-muted`);每 variant 的 icon 色 + hover 背景見上「Dismiss 行為(Inline Action 客製)」段。
264
237
 
265
238
  | Tag size | Icon | Hover 背景 | 上下呼吸空間 |
266
239
  |---|---|---|---|
@@ -289,12 +262,7 @@ Field 內包含 Tag 時,Field 的 padding 改為 `(field-height - tag-height)
289
262
 
290
263
  ## 為何無 StateBehavior
291
264
 
292
- Tag 是**純顯示 indicator**(非互動 見「與 Button 的差異」段),本身**無互動狀態**:
293
-
294
- - 無 hover / focus / active / selected / disabled——Tag 是 Family 3 indicator variant(非 Pill button)。若要 hover / selected 語意,改用 `Chip`(可互動版本)。
295
- - 僅有的「行為」是 remove(`onRemove` callback 觸發,Tag 本身不管 remove 動畫),close icon 的 hover 狀態屬 Inline Action pattern(`inline-action.spec.md` 獨立 SSOT),非 Tag 自有。
296
-
297
- 對應 anatomy story:保留 `Overview` + `Inspector` + `ColorMatrix`(variant solid/subtle) + `SizeMatrix`。互動狀態 → 改用 Chip(見「與 Button 的差異」段)。
265
+ Tag 是**純顯示 indicator**(Family 3 indicator variant,見「與 Button 的差異」段),無 hover / focus / active / selected / disabled 互動狀態——要互動語意改用 `Chip`。僅有的「行為」是 remove(`onRemove` callback,Tag 不管 remove 動畫),close icon 的 hover 屬 Inline Action pattern(`inline-action.spec.md` SSOT),非 Tag 自有。對應 anatomy story:`Overview` + `Inspector` + `ColorMatrix`(solid/subtle)+ `SizeMatrix`,無 StateBehavior。
298
266
 
299
267
  ---
300
268
 
@@ -304,7 +272,7 @@ Tag 是**純視覺 indicator**(非互動 control,互動版本是 Chip),預設 AR
304
272
 
305
273
  - **靜態 Tag**(無 `onRemove`):render 為 `<div data-tag-root>`(無 role、`inline-flex`,SR 等價於無語意 generic container,視覺上為 inline-level 跟隨 inline flow)。若 Tag 表達狀態語意(「進行中」「已逾期」),consumer 應在父 region 用 `aria-label` 或視覺 prefix(icon)讓 SR 理解 context
306
274
  - **Removable Tag**(`onRemove` 存在):inline X close 走 `<ItemInlineActionButton icon={X} size="md" aria-label="移除 {tagLabel}" />`(item-anatomy SSOT 的原生 `<button>`,非 `Button` 元件)— 對齊 inline-action canonical(`patterns/element-anatomy/inline-action.spec.md`)
307
- - **Keyboard**:靜態 Tag 不取得 focus(無互動);dismissible 的 X 走原生 button 鍵盤(Tab → focus → Enter/Space → dismiss)
275
+ - **Keyboard**:靜態 Tag 不取得 focus(無互動);**例外:截斷時條件性 `tabIndex=0`**(2026-07-06 拍板)——全文 tooltip 需鍵盤可達,APG Tooltip pattern 原文「popup 在元素收到鍵盤焦點或 hover 時顯示」= trigger 可 focus 是 W3C 官方前提;Radix Tooltip focus-open 自動生效,未截斷不入 tab 序。dismissible 的 X 走原生 button 鍵盤(Tab → focus → Enter/Space → dismiss)
308
276
  - **Color-only 語意警告**:Tag 用色相區分狀態時必有文字 label 或 prefix icon(色盲 user 友好);對齊 WCAG 1.4.1「不僅靠顏色傳達」
309
277
  - **驗證**:Storybook a11y addon panel 0 critical violation;WCAG AA contrast ≥ 4.5:1(text)/ 3:1(icon)
310
278
 
@@ -154,6 +154,17 @@ function TagInner(
154
154
 
155
155
  const label = typeof children === 'string' ? children : ''
156
156
 
157
+ // ── Dev-mode warning:icon 與 avatar 互斥 ──
158
+ // 兩者同為 prefix slot(tag.spec.md Props 表「與 avatar 互斥」),同時傳會並列渲染破壞 Tag
159
+ // 內部結構;原僅 jsdoc/spec 約束會靜默通過 → 2026-07-05 對齊 Button overlayBadge dev-warn
160
+ // 先例(button.tsx「Dev-mode warning」段)補齊家族一致 runtime 防線。
161
+ if (process.env.NODE_ENV !== 'production' && Icon && avatar) {
162
+ // eslint-disable-next-line no-console
163
+ console.warn(
164
+ '[DS Tag] `icon` 與 `avatar` 互斥(同為 prefix slot),同時傳會並列渲染破壞 Tag 內部結構,請只擇一。SSOT:tag.spec.md Props 表 + tag.principles.stories.tsx IconRule。'
165
+ )
166
+ }
167
+
157
168
  const tag = (
158
169
  <div
159
170
  ref={(el) => {
@@ -162,7 +173,18 @@ function TagInner(
162
173
  else if (forwardedRef) (forwardedRef as React.MutableRefObject<HTMLDivElement | null>).current = el
163
174
  }}
164
175
  data-tag-root=""
165
- className={cn(tagVariants({ color, size }), solidClass, 'w-fit min-w-0 overflow-hidden', className)}
176
+ // 截斷時條件性可 focus(2026-07-06 user 拍板「確認世界級有這樣做就做」——APG Tooltip
177
+ // pattern 原文:tooltip 在元素「收到鍵盤焦點」或 hover 時顯示,trigger 可 focus 是
178
+ // W3C 官方 pattern 前提;Radix Tooltip focus-open 自動生效 → 鍵盤 Tab 到即見全文。
179
+ // 未截斷不入 tab 序(維持 tag.spec「靜態 Tag 不取得 focus」預設,不汙染 tab order)。
180
+ tabIndex={isTruncated ? 0 : undefined}
181
+ className={cn(
182
+ tagVariants({ color, size }),
183
+ solidClass,
184
+ 'w-fit min-w-0 overflow-hidden',
185
+ isTruncated && 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1',
186
+ className,
187
+ )}
166
188
  // 2026-05-18 Round 5 fix(per Codex M31 Round 5 verdict + user 拍板「那就開始做」):
167
189
  // 用 CSS var `--combobox-tag-area-inline-size`(由 Combobox useOverflowCount JS-injected)取代
168
190
  // `min(100%, 10rem)` cyclic percentage。CSS Sizing 3 §5.2.1:percentage 在 indefinite containing
@@ -222,17 +244,21 @@ export const tagMeta = {
222
244
  magenta: { purpose: 'categorical 色相(--color-magenta-*)' },
223
245
  },
224
246
  sizes: {
225
- // Tag 尺寸不引用 field-height token(spec.md:180/241——Tag 與 Field 尺寸獨立)。
247
+ // Tag 尺寸不引用 field-height token(spec「尺寸」段——Tag 與 Field 尺寸獨立;段名指法免行號漂移)。
226
248
  // height = Tag 自身高度(cva h-5/h-6/h-6 = 20/24/24,lg = md alias)。
227
- // iconSize 全尺寸統一 16(tag.tsx:195 硬寫 size={16})。
249
+ // iconSize 全尺寸統一 16(本檔 CAT icon render 硬寫 size={16})。
228
250
  sm: { height: 20, iconSize: 16, typography: 'caption' },
229
251
  md: { height: 24, iconSize: 16, typography: 'body' },
230
252
  lg: { height: 24, iconSize: 16, typography: 'body' },
231
253
  },
232
- // Tag 為純展示 indicator,無互動 state(spec.md:249-256「為何無 StateBehavior」)。
254
+ // Tag 為純展示 indicator,無互動 state(spec「為何無 StateBehavior」段;段名指法免行號漂移)。
233
255
  // 唯一行為 dismiss 屬 Inline Action pattern,非 Tag 自有 state。
234
256
  states: ['default'],
235
257
  tokens: {
258
+ // 2026-07-04 audit 註記:另有動態色相家族不逐一列 — CAT_SUBTLE 12 色相
259
+ // bg-[var(--color-{hue}-1)] + step-7 深字、CAT_SOLID step-6 底 + --on-emphasis(-dark) 配字、
260
+ // neutral solid bg-[var(--color-neutral-9)] — 全由 categorical-color.ts SSOT 衍生
261
+ //(anatomy TOKEN_MAP 以 stripVar 機械衍生,零漂移)。
236
262
  bg: ['bg-neutral-active', 'bg-neutral-hover', 'bg-secondary', 'bg-transparent'],
237
263
  fg: ['text-foreground', 'text-inverse-fg'],
238
264
  ring: [],
@@ -4,6 +4,7 @@ import LinkTo from '@storybook/addon-links/react'
4
4
  import type { Meta, StoryObj } from '@storybook/react'
5
5
  import { Textarea } from './textarea'
6
6
  import { Input } from '@/design-system/components/Input/input'
7
+ import { Button } from '@/design-system/components/Button/button'
7
8
 
8
9
  const meta: Meta = {
9
10
  title: 'Design System/Components/Textarea/設計原則',
@@ -165,7 +166,7 @@ export const NoIconRule: Story = {
165
166
  <div className="flex flex-col gap-2">
166
167
  <Textarea placeholder="留下你的評論..." rows={3} />
167
168
  <div className="flex justify-end">
168
- <button className="px-4 py-2 text-body bg-primary text-inverse-fg rounded-md">送出</button>
169
+ <Button variant="primary" size="sm">送出</Button>
169
170
  </div>
170
171
  </div>
171
172
  <Label>↑ 按鈕在 Textarea 下方,不塞進框內</Label>
@@ -71,6 +71,7 @@ Textarea 是**多行文字**的輸入與顯示元件——Input 的多行版本
71
71
 
72
72
  - **`rows` prop**:預設 `3`,控制初始可見行數
73
73
  - **`resize-y`**:使用者可手動拖拉下邊緣垂直 resize
74
+ - **`variant="naked"` 例外**:cell-as-input substrate(DataTable cell)四 mode 一律 `!resize-none` 覆蓋 `resize-y`,並以 `!h-full` 撐滿 cell——cell 內手動 resize 會破壞表格佈局(`textarea.tsx` naked compoundVariants,intentional divergence)
74
75
  - **`min-h-*` className**:消費者可透過 Tailwind utility 覆寫最小高度
75
76
  - **禁止 `resize: horizontal` 或 `both`**:水平 resize 破壞 form 佈局
76
77
 
@@ -87,7 +88,7 @@ sm 與 md 視覺相同(純命名 mapping,對齊 Field family)。
87
88
 
88
89
  ### 為什麼不完全對齊 `--field-height-*`
89
90
 
90
- - **現況**:Textarea 高度由 `rows` + `resize-y` 決定,**不綁 `--field-height-*`**;`size` 只控字體(sm/md = text-body 14px / lg = text-body-lg 16px) padding 風格,**不控高度**
91
+ - **現況**:Textarea 高度由 `rows` + `resize-y` 決定,**不綁 `--field-height-*`**;`size` 只控字體(sm/md = text-body 14px / lg = text-body-lg 16px),**不控高度也不控 padding**(padding 為固定 `px-[var(--field-px)] py-2`,三 size 相同)
91
92
  - **Rationale**:Textarea 是**多行輸入**,高度由內容 / rows 決定是本質特徵——若硬綁 field-height 只有單行高度,multi-line 場景無法表達。字體 tier 仍對齊 Field family(sm/md 共 text-body、lg 切 text-body-lg),確保並排單行 Input 的視覺 rhythm 一致
92
93
  - **世界級對照**:Ant Design `<Input.TextArea>` rows 決定高度、autoSize object 配置 min/max / Material MUI `<TextField multiline>` 用 minRows/maxRows / Polaris `<TextField multiline>` 同流派——全部 textarea 的 container 高度獨立於 field-height,只繼承字體 / padding / border token <!-- @benchmark-unverified: see frontmatter benchmark list for canonical DS source URL -->
93
94
 
@@ -62,6 +62,11 @@ const textareaVariants = cva(
62
62
  md: 'text-body',
63
63
  lg: 'text-body-lg',
64
64
  },
65
+ // error(2026-07-04 Q1,鏡射 fieldWrapperStyles error variant;Phase D 整併時一起收)
66
+ error: {
67
+ true: '',
68
+ false: '',
69
+ },
65
70
  },
66
71
  compoundVariants: [
67
72
  // default chrome × mode
@@ -71,9 +76,9 @@ const textareaVariants = cva(
71
76
  className: [
72
77
  'bg-surface border border-border',
73
78
  'hover:border-border-hover',
74
- 'focus-visible:!border-primary focus-visible:hover:!border-primary',
75
79
  ],
76
80
  },
81
+ { mode: 'edit', variant: 'default', error: false, className: 'focus-within:!border-primary focus-within:hover:!border-primary' },
77
82
  {
78
83
  mode: 'display',
79
84
  variant: 'default',
@@ -98,9 +103,14 @@ const textareaVariants = cva(
98
103
  className: [
99
104
  'bg-transparent border border-transparent',
100
105
  'hover:border-border',
101
- 'focus-visible:!border-primary focus-visible:hover:!border-primary',
102
106
  ],
103
107
  },
108
+ { mode: 'edit', variant: 'bare', error: false, className: 'focus-within:!border-primary focus-within:hover:!border-primary' },
109
+ {
110
+ mode: 'edit',
111
+ error: true,
112
+ className: 'border-error hover:border-error-hover focus-within:!border-error focus-within:hover:!border-error',
113
+ },
104
114
  {
105
115
  mode: 'display',
106
116
  variant: 'bare',
@@ -111,10 +121,13 @@ const textareaVariants = cva(
111
121
  variant: 'bare',
112
122
  className: 'bg-transparent border border-transparent',
113
123
  },
124
+ // 2026-07-05 fix(deep-audit A.1b,鏡射下方 naked 2026-05-13 codex V2 fix):移除 bare×disabled
125
+ // 的 `opacity-disabled` blanket — 同 class 已用具體 `text-fg-disabled` token swap,兩策略同時
126
+ // 套用違 opacity.spec.md「不可混用」canonical(0.45 × token 疊加 = 過度褪色)。
114
127
  {
115
128
  mode: 'disabled',
116
129
  variant: 'bare',
117
- className: 'bg-transparent border border-transparent cursor-not-allowed opacity-disabled text-fg-disabled',
130
+ className: 'bg-transparent border border-transparent cursor-not-allowed text-fg-disabled',
118
131
  },
119
132
  // naked chrome × mode — cell-as-input substrate(2026-05-06 v14 revert v12)。
120
133
  // v12 `!absolute -inset-px` autoRowHeight 不相容 → revert v9 baseline + 保留 v13.3
@@ -127,13 +140,13 @@ const textareaVariants = cva(
127
140
  '!px-[var(--table-cell-px)] !py-[var(--table-cell-py)]',
128
141
  'border border-border',
129
142
  'hover:border-border-hover',
130
- 'focus-visible:!border-primary focus-visible:hover:!border-primary',
131
143
  // textarea UA stylesheet 預設 line-height: normal(1.2-1.5 不定),會跟 display
132
144
  // `<div>` text-body line-height: 1.5(21px @ 14px)不一致 → cell 進 edit 後 height
133
145
  // shift。顯式 leading 對齊 div 行為。
134
146
  '!leading-[1.5]',
135
147
  ],
136
148
  },
149
+ { mode: 'edit', variant: 'naked', error: false, className: 'focus-visible:!border-primary focus-visible:hover:!border-primary' },
137
150
  // 2026-05-13 Q1 R4 verify(per codex Q1 verdict 補 Textarea nuance):
138
151
  // Textarea naked display/readonly/disabled 用 `!h-full`,**不**對齊 Field wrapper 的 `!h-auto`。
139
152
  // Why divergence:textarea 是 native form element 帶 intrinsic rows-based height,且 cell 內
@@ -150,6 +163,7 @@ const textareaVariants = cva(
150
163
  mode: 'edit',
151
164
  variant: 'default',
152
165
  size: 'md',
166
+ error: false,
153
167
  },
154
168
  }
155
169
  )
@@ -240,11 +254,7 @@ const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
240
254
  data-field-mode={resolvedMode}
241
255
  data-error={isEditable && error ? '' : undefined}
242
256
  className={cn(
243
- textareaVariants({ mode: resolvedMode, variant: variant, size }),
244
- isEditable && error && [
245
- 'border-error hover:border-error-hover',
246
- 'focus-visible:border-error focus-visible:hover:border-error',
247
- ],
257
+ textareaVariants({ mode: resolvedMode, variant: variant, size, error }),
248
258
  className
249
259
  )}
250
260
  {...props}
@@ -272,7 +282,7 @@ export const textareaMeta = {
272
282
  // text input 無 'active'(按下)視覺態(Material/Polaris/Carbon TextArea 共識)。
273
283
  states: ['default', 'hover', 'focus-visible', 'readonly', 'disabled', 'error'],
274
284
  tokens: {
275
- bg: ['bg-disabled', 'bg-surface'],
285
+ bg: ['bg-disabled', 'bg-readonly', 'bg-surface'], // 2026-07-04 補:readonly×default cva 實際消費(spec「Readonly 特例」主打 token)
276
286
  fg: ['text-fg-disabled', 'text-fg-muted', 'text-foreground'],
277
287
  ring: [],
278
288
  },
@@ -12,7 +12,8 @@ import type { Meta, StoryObj } from '@storybook/react'
12
12
  import { TimePicker } from './time-picker'
13
13
 
14
14
  /**
15
- * TimePicker 設計規格 — 完整技術規格。anatomy 5-story 最小版本。
15
+ * TimePicker 設計規格 — 完整技術規格。anatomy 7 story(6-canonical 去 StateBehavior,
16
+ * 加元件特有 ModeMatrix / PrecisionMatrix;見 spec「為何無 StateBehavior」)。
16
17
  * 深度規格見 `time-picker.spec.md`。
17
18
  */
18
19