@qijenchen/design-system 0.1.0-beta.85 → 0.1.0-beta.86

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 (477) hide show
  1. package/AGENTS.md +210 -0
  2. package/CLAUDE.md +17 -191
  3. package/cli-init.mjs +30 -2
  4. package/dist/components/Accordion/accordion.d.ts +2 -2
  5. package/dist/components/Accordion/accordion.d.ts.map +1 -1
  6. package/dist/components/Accordion/accordion.js.map +1 -1
  7. package/dist/components/Avatar/avatar.js +1 -1
  8. package/dist/components/Avatar/avatar.js.map +1 -1
  9. package/dist/components/BulkActionBar/bulk-action-bar.d.ts +1 -1
  10. package/dist/components/BulkActionBar/bulk-action-bar.d.ts.map +1 -1
  11. package/dist/components/BulkActionBar/bulk-action-bar.js +3 -3
  12. package/dist/components/BulkActionBar/bulk-action-bar.js.map +1 -1
  13. package/dist/components/Button/button-group.d.ts +2 -2
  14. package/dist/components/Button/button-group.d.ts.map +1 -1
  15. package/dist/components/Button/button-group.js +2 -2
  16. package/dist/components/Button/button-group.js.map +1 -1
  17. package/dist/components/Button/button.d.ts +2 -1
  18. package/dist/components/Button/button.d.ts.map +1 -1
  19. package/dist/components/Button/button.js +1 -1
  20. package/dist/components/Button/button.js.map +1 -1
  21. package/dist/components/Carousel/carousel.d.ts +14 -8
  22. package/dist/components/Carousel/carousel.d.ts.map +1 -1
  23. package/dist/components/Carousel/carousel.js +64 -37
  24. package/dist/components/Carousel/carousel.js.map +1 -1
  25. package/dist/components/Checkbox/checkbox-group.d.ts.map +1 -1
  26. package/dist/components/Checkbox/checkbox-group.js +21 -14
  27. package/dist/components/Checkbox/checkbox-group.js.map +1 -1
  28. package/dist/components/Checkbox/checkbox.d.ts +2 -2
  29. package/dist/components/Checkbox/checkbox.d.ts.map +1 -1
  30. package/dist/components/Checkbox/checkbox.js +49 -10
  31. package/dist/components/Checkbox/checkbox.js.map +1 -1
  32. package/dist/components/Combobox/combobox.d.ts +15 -6
  33. package/dist/components/Combobox/combobox.d.ts.map +1 -1
  34. package/dist/components/Combobox/combobox.js +13 -7
  35. package/dist/components/Combobox/combobox.js.map +1 -1
  36. package/dist/components/Command/command.d.ts.map +1 -1
  37. package/dist/components/Command/command.js +11 -2
  38. package/dist/components/Command/command.js.map +1 -1
  39. package/dist/components/DataTable/cell-registry.d.ts +5 -5
  40. package/dist/components/DataTable/cell-registry.d.ts.map +1 -1
  41. package/dist/components/DataTable/cell-registry.js +41 -38
  42. package/dist/components/DataTable/cell-registry.js.map +1 -1
  43. package/dist/components/DataTable/column-types.d.ts +1 -17
  44. package/dist/components/DataTable/column-types.d.ts.map +1 -1
  45. package/dist/components/DataTable/column-types.js.map +1 -1
  46. package/dist/components/DataTable/data-table-column-visibility-panel.d.ts.map +1 -1
  47. package/dist/components/DataTable/data-table-column-visibility-panel.js +71 -64
  48. package/dist/components/DataTable/data-table-column-visibility-panel.js.map +1 -1
  49. package/dist/components/DataTable/data-table-filter-panel.d.ts +0 -2
  50. package/dist/components/DataTable/data-table-filter-panel.d.ts.map +1 -1
  51. package/dist/components/DataTable/data-table-filter-panel.js +3 -4
  52. package/dist/components/DataTable/data-table-filter-panel.js.map +1 -1
  53. package/dist/components/DataTable/data-table-interaction-layer.d.ts +2 -2
  54. package/dist/components/DataTable/data-table-interaction-layer.js.map +1 -1
  55. package/dist/components/DataTable/data-table-sort-manager.js +2 -2
  56. package/dist/components/DataTable/data-table-sort-manager.js.map +1 -1
  57. package/dist/components/DataTable/data-table.d.ts +1 -1
  58. package/dist/components/DataTable/data-table.d.ts.map +1 -1
  59. package/dist/components/DataTable/data-table.js +44 -32
  60. package/dist/components/DataTable/data-table.js.map +1 -1
  61. package/dist/components/DatePicker/date-picker.d.ts +3 -3
  62. package/dist/components/DatePicker/date-picker.js +4 -4
  63. package/dist/components/DatePicker/date-picker.js.map +1 -1
  64. package/dist/components/DescriptionList/description-list.d.ts +3 -3
  65. package/dist/components/DescriptionList/description-list.d.ts.map +1 -1
  66. package/dist/components/DescriptionList/description-list.js +6 -6
  67. package/dist/components/DescriptionList/description-list.js.map +1 -1
  68. package/dist/components/Dialog/dialog.d.ts +2 -2
  69. package/dist/components/Dialog/dialog.d.ts.map +1 -1
  70. package/dist/components/Dialog/dialog.js.map +1 -1
  71. package/dist/components/DropdownMenu/dropdown-menu.d.ts +8 -3
  72. package/dist/components/DropdownMenu/dropdown-menu.d.ts.map +1 -1
  73. package/dist/components/DropdownMenu/dropdown-menu.js +7 -7
  74. package/dist/components/DropdownMenu/dropdown-menu.js.map +1 -1
  75. package/dist/components/Field/field-context.d.ts +16 -10
  76. package/dist/components/Field/field-context.d.ts.map +1 -1
  77. package/dist/components/Field/field-context.js.map +1 -1
  78. package/dist/components/Field/field-types.d.ts +2 -2
  79. package/dist/components/Field/field-types.d.ts.map +1 -1
  80. package/dist/components/Field/field-types.js.map +1 -1
  81. package/dist/components/Field/field-wrapper.d.ts +20 -4
  82. package/dist/components/Field/field-wrapper.d.ts.map +1 -1
  83. package/dist/components/Field/field-wrapper.js +30 -31
  84. package/dist/components/Field/field-wrapper.js.map +1 -1
  85. package/dist/components/Field/field.d.ts +1 -1
  86. package/dist/components/Field/field.d.ts.map +1 -1
  87. package/dist/components/Field/field.js +25 -3
  88. package/dist/components/Field/field.js.map +1 -1
  89. package/dist/components/Field/use-form-validation.d.ts +1 -1
  90. package/dist/components/Field/use-form-validation.js.map +1 -1
  91. package/dist/components/FileItem/file-item.d.ts.map +1 -1
  92. package/dist/components/FileItem/file-item.js +2 -5
  93. package/dist/components/FileItem/file-item.js.map +1 -1
  94. package/dist/components/FileUpload/file-upload.d.ts.map +1 -1
  95. package/dist/components/FileUpload/file-upload.js +5 -1
  96. package/dist/components/FileUpload/file-upload.js.map +1 -1
  97. package/dist/components/FileViewer/file-viewer.d.ts.map +1 -1
  98. package/dist/components/FileViewer/file-viewer.js +2 -1
  99. package/dist/components/FileViewer/file-viewer.js.map +1 -1
  100. package/dist/components/HoverCard/hover-card.d.ts +2 -2
  101. package/dist/components/HoverCard/hover-card.d.ts.map +1 -1
  102. package/dist/components/HoverCard/hover-card.js +4 -2
  103. package/dist/components/HoverCard/hover-card.js.map +1 -1
  104. package/dist/components/InlineEdit/inline-edit.d.ts +23 -16
  105. package/dist/components/InlineEdit/inline-edit.d.ts.map +1 -1
  106. package/dist/components/InlineEdit/inline-edit.js +104 -62
  107. package/dist/components/InlineEdit/inline-edit.js.map +1 -1
  108. package/dist/components/Input/input.d.ts +1 -1
  109. package/dist/components/Input/input.d.ts.map +1 -1
  110. package/dist/components/Input/input.js +6 -6
  111. package/dist/components/Input/input.js.map +1 -1
  112. package/dist/components/LinkInput/link-input.d.ts +5 -5
  113. package/dist/components/LinkInput/link-input.d.ts.map +1 -1
  114. package/dist/components/LinkInput/link-input.js +3 -3
  115. package/dist/components/LinkInput/link-input.js.map +1 -1
  116. package/dist/components/Notice/notice.d.ts +17 -1
  117. package/dist/components/Notice/notice.d.ts.map +1 -1
  118. package/dist/components/Notice/notice.js +7 -1
  119. package/dist/components/Notice/notice.js.map +1 -1
  120. package/dist/components/NumberInput/number-input.d.ts +1 -1
  121. package/dist/components/NumberInput/number-input.d.ts.map +1 -1
  122. package/dist/components/NumberInput/number-input.js +12 -1
  123. package/dist/components/NumberInput/number-input.js.map +1 -1
  124. package/dist/components/PeoplePicker/avatar-stack-overflow.d.ts +1 -1
  125. package/dist/components/PeoplePicker/avatar-stack-overflow.js.map +1 -1
  126. package/dist/components/PeoplePicker/people-picker.d.ts +5 -5
  127. package/dist/components/PeoplePicker/people-picker.d.ts.map +1 -1
  128. package/dist/components/PeoplePicker/people-picker.js +7 -5
  129. package/dist/components/PeoplePicker/people-picker.js.map +1 -1
  130. package/dist/components/PeoplePicker/person-display.d.ts +1 -1
  131. package/dist/components/PeoplePicker/person-display.js.map +1 -1
  132. package/dist/components/ProfileCard/profile-card.d.ts +1 -1
  133. package/dist/components/ProfileCard/profile-card.js.map +1 -1
  134. package/dist/components/RadioGroup/radio-group.d.ts +3 -3
  135. package/dist/components/RadioGroup/radio-group.d.ts.map +1 -1
  136. package/dist/components/RadioGroup/radio-group.js +1 -1
  137. package/dist/components/RadioGroup/radio-group.js.map +1 -1
  138. package/dist/components/Rating/rating.d.ts.map +1 -1
  139. package/dist/components/Rating/rating.js +4 -0
  140. package/dist/components/Rating/rating.js.map +1 -1
  141. package/dist/components/ScrollArea/scroll-area.d.ts +3 -2
  142. package/dist/components/ScrollArea/scroll-area.d.ts.map +1 -1
  143. package/dist/components/ScrollArea/scroll-area.js.map +1 -1
  144. package/dist/components/SegmentedControl/segmented-control.d.ts +4 -3
  145. package/dist/components/SegmentedControl/segmented-control.d.ts.map +1 -1
  146. package/dist/components/SegmentedControl/segmented-control.js.map +1 -1
  147. package/dist/components/Select/select.d.ts +16 -6
  148. package/dist/components/Select/select.d.ts.map +1 -1
  149. package/dist/components/Select/select.js +17 -5
  150. package/dist/components/Select/select.js.map +1 -1
  151. package/dist/components/SelectMenu/select-menu.d.ts +17 -1
  152. package/dist/components/SelectMenu/select-menu.d.ts.map +1 -1
  153. package/dist/components/SelectMenu/select-menu.js +17 -3
  154. package/dist/components/SelectMenu/select-menu.js.map +1 -1
  155. package/dist/components/SelectionControl/boolean-value.d.ts +1 -1
  156. package/dist/components/SelectionControl/boolean-value.js.map +1 -1
  157. package/dist/components/Sheet/sheet.d.ts +2 -2
  158. package/dist/components/Sheet/sheet.d.ts.map +1 -1
  159. package/dist/components/Sheet/sheet.js.map +1 -1
  160. package/dist/components/Sidebar/sidebar.d.ts +1 -1
  161. package/dist/components/Sidebar/sidebar.js +2 -2
  162. package/dist/components/Sidebar/sidebar.js.map +1 -1
  163. package/dist/components/Slider/slider.d.ts.map +1 -1
  164. package/dist/components/Slider/slider.js +6 -3
  165. package/dist/components/Slider/slider.js.map +1 -1
  166. package/dist/components/Steps/steps.d.ts +6 -4
  167. package/dist/components/Steps/steps.d.ts.map +1 -1
  168. package/dist/components/Steps/steps.js +19 -5
  169. package/dist/components/Steps/steps.js.map +1 -1
  170. package/dist/components/Switch/switch.d.ts +4 -4
  171. package/dist/components/Switch/switch.d.ts.map +1 -1
  172. package/dist/components/Switch/switch.js +3 -1
  173. package/dist/components/Switch/switch.js.map +1 -1
  174. package/dist/components/Tabs/tabs.d.ts +1 -1
  175. package/dist/components/Tabs/tabs.d.ts.map +1 -1
  176. package/dist/components/Tabs/tabs.js +73 -23
  177. package/dist/components/Tabs/tabs.js.map +1 -1
  178. package/dist/components/Tag/tag.d.ts +3 -1
  179. package/dist/components/Tag/tag.d.ts.map +1 -1
  180. package/dist/components/Tag/tag.js +2 -2
  181. package/dist/components/Tag/tag.js.map +1 -1
  182. package/dist/components/Textarea/textarea.d.ts +4 -4
  183. package/dist/components/Textarea/textarea.d.ts.map +1 -1
  184. package/dist/components/Textarea/textarea.js +15 -15
  185. package/dist/components/Textarea/textarea.js.map +1 -1
  186. package/dist/components/TimePicker/time-picker.d.ts +4 -4
  187. package/dist/components/TimePicker/time-picker.d.ts.map +1 -1
  188. package/dist/components/TimePicker/time-picker.js +8 -5
  189. package/dist/components/TimePicker/time-picker.js.map +1 -1
  190. package/dist/components/Tooltip/tooltip.d.ts.map +1 -1
  191. package/dist/components/Tooltip/tooltip.js +27 -23
  192. package/dist/components/Tooltip/tooltip.js.map +1 -1
  193. package/dist/components/TreeView/tree-view.d.ts.map +1 -1
  194. package/dist/components/TreeView/tree-view.js +16 -0
  195. package/dist/components/TreeView/tree-view.js.map +1 -1
  196. package/dist/index.d.ts +7 -7
  197. package/dist/index.d.ts.map +1 -1
  198. package/dist/index.js +6 -13
  199. package/dist/index.js.map +1 -1
  200. package/dist/lib/drag-visual.d.ts +2 -2
  201. package/dist/lib/drag-visual.js.map +1 -1
  202. package/dist/lib/utils.d.ts +1 -0
  203. package/dist/lib/utils.d.ts.map +1 -1
  204. package/dist/lib/utils.js +21 -1
  205. package/dist/lib/utils.js.map +1 -1
  206. package/dist/patterns/element-anatomy/item-anatomy.d.ts +2 -0
  207. package/dist/patterns/element-anatomy/item-anatomy.d.ts.map +1 -1
  208. package/dist/patterns/element-anatomy/item-anatomy.js +2 -2
  209. package/dist/patterns/element-anatomy/item-anatomy.js.map +1 -1
  210. package/dist/tokens/categorical-color.d.ts +3 -3
  211. package/dist/tokens/categorical-color.d.ts.map +1 -1
  212. package/dist/tokens/categorical-color.js +2 -2
  213. package/dist/tokens/categorical-color.js.map +1 -1
  214. package/ds-canonical/fork/AGENTS.md +213 -0
  215. package/ds-canonical/fork/codex/agents/skills/independent-review/SKILL.md +33 -0
  216. package/ds-canonical/fork/codex/hooks.json +16 -0
  217. package/ds-canonical/fork/governance.lock +22 -7
  218. package/ds-canonical/fork/hooks/check_ds_anchor_preflight.sh +22 -1
  219. package/ds-canonical/fork/hooks/inject_deploy_url_after_push.sh +14 -2
  220. package/ds-canonical/fork/manifest.json +10 -1
  221. package/ds-canonical/fork/preamble.md +3 -2
  222. package/ds-canonical/fork/skills/product-ui-audit/SKILL.md +7 -7
  223. package/ds-canonical/fork/skills/ux-audit/SKILL.md +1 -1
  224. package/ds-canonical/fork/skills/visual-audit/SKILL.md +0 -1
  225. package/ds-canonical/hooks/check_codex_brief_invariants.sh +9 -3
  226. package/ds-canonical/hooks/check_ds_anchor_preflight.sh +22 -1
  227. package/ds-canonical/hooks/check_overlay_open_focus_escape_probe.sh +11 -2
  228. package/ds-canonical/hooks/check_plugin_fork_health.sh +17 -1
  229. package/ds-canonical/hooks/check_post_main_ssot_propagate.sh +21 -4
  230. package/ds-canonical/hooks/inject_deploy_url_after_push.sh +14 -2
  231. package/ds-canonical/hooks/session_start_governance_check.sh +10 -2
  232. package/ds-canonical/references/certified-surfaces.md +20 -0
  233. package/ds-canonical/references/preflight-gate-baseline.json +5 -0
  234. package/ds-canonical/references/principle-dim-map.json +20 -2
  235. package/ds-canonical/references/props-naming.md +2 -1
  236. package/ds-canonical/references/ssot-index.md +2 -2
  237. package/ds-canonical/references/story-baseline-registry.json +2 -2
  238. package/ds-canonical/rules/story-rules.md +1 -1
  239. package/ds-canonical/rules/ui-development.md +2 -1
  240. package/ds-canonical/skills/codex-collab/SKILL.md +7 -7
  241. package/ds-canonical/skills/codex-collab/references/brief-template.md +5 -1
  242. package/ds-canonical/skills/component-quality-gate/SKILL.md +1 -1
  243. package/ds-canonical/skills/deep-audit-cross-codex/SKILL.md +5 -5
  244. package/ds-canonical/skills/design-system-audit/SKILL.md +5 -5
  245. package/ds-canonical/skills/product-ui-audit/SKILL.md +7 -7
  246. package/ds-canonical/skills/ux-audit/SKILL.md +1 -1
  247. package/ds-canonical/skills/visual-audit/SKILL.md +0 -1
  248. package/ds-story-manifest.json +21 -21
  249. package/llms-full.txt +3 -3
  250. package/llms.txt +1 -1
  251. package/package.json +2 -1
  252. package/src/components/Accordion/accordion.anatomy.stories.tsx +11 -11
  253. package/src/components/Accordion/accordion.principles.stories.tsx +21 -6
  254. package/src/components/Accordion/accordion.spec.md +10 -1
  255. package/src/components/Accordion/accordion.stories.tsx +10 -5
  256. package/src/components/Accordion/accordion.tsx +7 -2
  257. package/src/components/Alert/alert.principles.stories.tsx +2 -8
  258. package/src/components/AppShell/app-shell.anatomy.stories.tsx +19 -13
  259. package/src/components/AppShell/app-shell.principles.stories.tsx +21 -21
  260. package/src/components/AppShell/app-shell.spec.md +4 -0
  261. package/src/components/AppShell/app-shell.stories.tsx +4 -5
  262. package/src/components/Avatar/avatar.tsx +1 -1
  263. package/src/components/Badge/badge.spec.md +2 -2
  264. package/src/components/Breadcrumb/breadcrumb.anatomy.stories.tsx +7 -3
  265. package/src/components/Breadcrumb/breadcrumb.principles.stories.tsx +4 -1
  266. package/src/components/Breadcrumb/breadcrumb.stories.tsx +1 -1
  267. package/src/components/BulkActionBar/bulk-action-bar.anatomy.stories.tsx +6 -6
  268. package/src/components/BulkActionBar/bulk-action-bar.principles.stories.tsx +18 -6
  269. package/src/components/BulkActionBar/bulk-action-bar.spec.md +15 -5
  270. package/src/components/BulkActionBar/bulk-action-bar.stories.tsx +8 -24
  271. package/src/components/BulkActionBar/bulk-action-bar.tsx +4 -4
  272. package/src/components/Button/button-group.tsx +4 -4
  273. package/src/components/Button/button.anatomy.stories.tsx +26 -7
  274. package/src/components/Button/button.principles.stories.tsx +1 -1
  275. package/src/components/Button/button.spec.md +2 -2
  276. package/src/components/Button/button.stories.tsx +22 -28
  277. package/src/components/Button/button.tsx +2 -1
  278. package/src/components/Calendar/calendar.anatomy.stories.tsx +1 -1
  279. package/src/components/Calendar/calendar.spec.md +1 -1
  280. package/src/components/Carousel/carousel.spec.md +17 -1
  281. package/src/components/Carousel/carousel.tsx +93 -42
  282. package/src/components/Checkbox/checkbox-group.tsx +26 -15
  283. package/src/components/Checkbox/checkbox.anatomy.stories.tsx +3 -3
  284. package/src/components/Checkbox/checkbox.spec.md +12 -3
  285. package/src/components/Checkbox/checkbox.stories.tsx +4 -4
  286. package/src/components/Checkbox/checkbox.tsx +79 -19
  287. package/src/components/Coachmark/coachmark.anatomy.stories.tsx +5 -5
  288. package/src/components/Coachmark/coachmark.principles.stories.tsx +1 -1
  289. package/src/components/Combobox/combobox.anatomy.stories.tsx +11 -4
  290. package/src/components/Combobox/combobox.principles.stories.tsx +10 -11
  291. package/src/components/Combobox/combobox.spec.md +6 -2
  292. package/src/components/Combobox/combobox.stories.tsx +9 -36
  293. package/src/components/Combobox/combobox.tsx +42 -27
  294. package/src/components/Command/command.spec.md +3 -3
  295. package/src/components/Command/command.tsx +9 -2
  296. package/src/components/DataTable/cell-registry.tsx +75 -76
  297. package/src/components/DataTable/column-types.ts +4 -17
  298. package/src/components/DataTable/data-table-column-visibility-panel.tsx +20 -7
  299. package/src/components/DataTable/data-table-filter-panel.tsx +6 -6
  300. package/src/components/DataTable/data-table-interaction-layer.tsx +3 -3
  301. package/src/components/DataTable/data-table-sort-manager.tsx +2 -2
  302. package/src/components/DataTable/data-table.anatomy.stories.tsx +25 -24
  303. package/src/components/DataTable/data-table.css +2 -3
  304. package/src/components/DataTable/data-table.principles.stories.tsx +17 -11
  305. package/src/components/DataTable/data-table.spec.md +7 -7
  306. package/src/components/DataTable/data-table.stories.tsx +39 -29
  307. package/src/components/DataTable/data-table.tsx +90 -67
  308. package/src/components/DataTable/filter-operators.spec.md +2 -0
  309. package/src/components/DateGrid/date-grid.spec.md +1 -1
  310. package/src/components/DatePicker/date-picker.anatomy.stories.tsx +9 -9
  311. package/src/components/DatePicker/date-picker.principles.stories.tsx +4 -4
  312. package/src/components/DatePicker/date-picker.spec.md +9 -9
  313. package/src/components/DatePicker/date-picker.stories.tsx +9 -9
  314. package/src/components/DatePicker/date-picker.tsx +13 -13
  315. package/src/components/DescriptionList/description-list.anatomy.stories.tsx +5 -5
  316. package/src/components/DescriptionList/description-list.principles.stories.tsx +2 -2
  317. package/src/components/DescriptionList/description-list.spec.md +12 -12
  318. package/src/components/DescriptionList/description-list.stories.tsx +3 -3
  319. package/src/components/DescriptionList/description-list.tsx +10 -10
  320. package/src/components/Dialog/dialog.tsx +4 -1
  321. package/src/components/DropdownMenu/dropdown-menu.tsx +27 -14
  322. package/src/components/Field/field-context.ts +17 -11
  323. package/src/components/Field/field-controls.spec.md +44 -37
  324. package/src/components/Field/field-types.ts +14 -12
  325. package/src/components/Field/field-wrapper.tsx +59 -43
  326. package/src/components/Field/field.anatomy.stories.tsx +6 -6
  327. package/src/components/Field/field.principles.stories.tsx +5 -28
  328. package/src/components/Field/field.spec.md +4 -4
  329. package/src/components/Field/field.stories.tsx +103 -72
  330. package/src/components/Field/field.tsx +36 -4
  331. package/src/components/Field/form-validation.spec.md +2 -0
  332. package/src/components/Field/use-form-validation.ts +1 -1
  333. package/src/components/FieldControlGroup/field-control-group.principles.stories.tsx +3 -3
  334. package/src/components/FieldControlGroup/field-control-group.spec.md +3 -3
  335. package/src/components/FileItem/file-item.tsx +10 -8
  336. package/src/components/FileUpload/file-upload.principles.stories.tsx +7 -7
  337. package/src/components/FileUpload/file-upload.spec.md +3 -2
  338. package/src/components/FileUpload/file-upload.tsx +5 -1
  339. package/src/components/FileViewer/file-viewer.anatomy.stories.tsx +1 -1
  340. package/src/components/FileViewer/file-viewer.principles.stories.tsx +1 -1
  341. package/src/components/FileViewer/file-viewer.spec.md +1 -1
  342. package/src/components/FileViewer/file-viewer.stories.tsx +1 -1
  343. package/src/components/FileViewer/file-viewer.tsx +3 -2
  344. package/src/components/HoverCard/hover-card.anatomy.stories.tsx +6 -4
  345. package/src/components/HoverCard/hover-card.principles.stories.tsx +6 -1
  346. package/src/components/HoverCard/hover-card.spec.md +2 -2
  347. package/src/components/HoverCard/hover-card.tsx +4 -2
  348. package/src/components/InlineEdit/inline-edit.anatomy.stories.tsx +184 -0
  349. package/src/components/InlineEdit/inline-edit.principles.stories.tsx +142 -0
  350. package/src/components/InlineEdit/inline-edit.spec.md +71 -46
  351. package/src/components/InlineEdit/inline-edit.stories.tsx +20 -19
  352. package/src/components/InlineEdit/inline-edit.tsx +211 -125
  353. package/src/components/Input/input.anatomy.stories.tsx +15 -15
  354. package/src/components/Input/input.principles.stories.tsx +3 -3
  355. package/src/components/Input/input.spec.md +4 -4
  356. package/src/components/Input/input.stories.tsx +3 -3
  357. package/src/components/Input/input.tsx +13 -13
  358. package/src/components/LinkInput/link-input.anatomy.stories.tsx +2 -2
  359. package/src/components/LinkInput/link-input.principles.stories.tsx +1 -1
  360. package/src/components/LinkInput/link-input.spec.md +6 -2
  361. package/src/components/LinkInput/link-input.stories.tsx +8 -8
  362. package/src/components/LinkInput/link-input.tsx +21 -19
  363. package/src/components/Notice/notice.principles.stories.tsx +1 -1
  364. package/src/components/Notice/notice.tsx +8 -2
  365. package/src/components/NumberInput/number-input.anatomy.stories.tsx +6 -6
  366. package/src/components/NumberInput/number-input.principles.stories.tsx +4 -4
  367. package/src/components/NumberInput/number-input.spec.md +6 -6
  368. package/src/components/NumberInput/number-input.stories.tsx +4 -4
  369. package/src/components/NumberInput/number-input.tsx +17 -5
  370. package/src/components/OverflowIndicator/overflow-indicator.anatomy.stories.tsx +8 -4
  371. package/src/components/OverflowIndicator/overflow-indicator.principles.stories.tsx +6 -7
  372. package/src/components/OverflowIndicator/overflow-indicator.spec.md +1 -1
  373. package/src/components/OverflowIndicator/overflow-indicator.stories.tsx +6 -30
  374. package/src/components/PeoplePicker/avatar-stack-overflow.ts +1 -1
  375. package/src/components/PeoplePicker/people-picker.anatomy.stories.tsx +13 -10
  376. package/src/components/PeoplePicker/people-picker.principles.stories.tsx +5 -1
  377. package/src/components/PeoplePicker/people-picker.stories.tsx +5 -5
  378. package/src/components/PeoplePicker/people-picker.tsx +39 -20
  379. package/src/components/PeoplePicker/person-display.tsx +3 -3
  380. package/src/components/Popover/popover.anatomy.stories.tsx +10 -7
  381. package/src/components/Popover/popover.principles.stories.tsx +49 -28
  382. package/src/components/Popover/popover.spec.md +4 -2
  383. package/src/components/ProfileCard/profile-card.anatomy.stories.tsx +1 -1
  384. package/src/components/ProfileCard/profile-card.tsx +1 -1
  385. package/src/components/ProgressBar/progress-bar.anatomy.stories.tsx +14 -13
  386. package/src/components/ProgressBar/progress-bar.principles.stories.tsx +8 -23
  387. package/src/components/ProgressBar/progress-bar.stories.tsx +8 -2
  388. package/src/components/README.md +1 -0
  389. package/src/components/RadioGroup/radio-group.anatomy.stories.tsx +8 -3
  390. package/src/components/RadioGroup/radio-group.stories.tsx +3 -3
  391. package/src/components/RadioGroup/radio-group.tsx +19 -14
  392. package/src/components/Rating/rating.anatomy.stories.tsx +1 -1
  393. package/src/components/Rating/rating.principles.stories.tsx +3 -3
  394. package/src/components/Rating/rating.spec.md +2 -2
  395. package/src/components/Rating/rating.stories.tsx +4 -18
  396. package/src/components/Rating/rating.tsx +5 -1
  397. package/src/components/ScrollArea/scroll-area.principles.stories.tsx +1 -1
  398. package/src/components/ScrollArea/scroll-area.spec.md +1 -1
  399. package/src/components/ScrollArea/scroll-area.tsx +5 -2
  400. package/src/components/SegmentedControl/segmented-control.principles.stories.tsx +4 -1
  401. package/src/components/SegmentedControl/segmented-control.spec.md +5 -10
  402. package/src/components/SegmentedControl/segmented-control.tsx +6 -3
  403. package/src/components/Select/select.anatomy.stories.tsx +13 -7
  404. package/src/components/Select/select.principles.stories.tsx +3 -3
  405. package/src/components/Select/select.spec.md +9 -1
  406. package/src/components/Select/select.stories.tsx +41 -27
  407. package/src/components/Select/select.tsx +44 -26
  408. package/src/components/SelectMenu/select-menu.anatomy.stories.tsx +44 -26
  409. package/src/components/SelectMenu/select-menu.principles.stories.tsx +0 -1
  410. package/src/components/SelectMenu/select-menu.stories.tsx +7 -19
  411. package/src/components/SelectMenu/select-menu.tsx +38 -5
  412. package/src/components/SelectionControl/boolean-value.tsx +2 -2
  413. package/src/components/SelectionControl/selection-item.anatomy.stories.tsx +1 -1
  414. package/src/components/SelectionControl/selection-item.principles.stories.tsx +4 -4
  415. package/src/components/Separator/separator.principles.stories.tsx +1 -1
  416. package/src/components/Sheet/sheet.principles.stories.tsx +5 -3
  417. package/src/components/Sheet/sheet.tsx +4 -1
  418. package/src/components/Sidebar/sidebar.tsx +1 -1
  419. package/src/components/Slider/slider.anatomy.stories.tsx +5 -4
  420. package/src/components/Slider/slider.principles.stories.tsx +6 -6
  421. package/src/components/Slider/slider.spec.md +13 -1
  422. package/src/components/Slider/slider.stories.tsx +44 -32
  423. package/src/components/Slider/slider.tsx +13 -4
  424. package/src/components/Steps/steps.anatomy.stories.tsx +7 -7
  425. package/src/components/Steps/steps.principles.stories.tsx +1 -1
  426. package/src/components/Steps/steps.spec.md +4 -2
  427. package/src/components/Steps/steps.stories.tsx +4 -56
  428. package/src/components/Steps/steps.tsx +31 -7
  429. package/src/components/Switch/switch.anatomy.stories.tsx +3 -3
  430. package/src/components/Switch/switch.principles.stories.tsx +1 -1
  431. package/src/components/Switch/switch.spec.md +4 -4
  432. package/src/components/Switch/switch.stories.tsx +3 -3
  433. package/src/components/Switch/switch.tsx +16 -10
  434. package/src/components/Tabs/tabs.anatomy.stories.tsx +6 -0
  435. package/src/components/Tabs/tabs.principles.stories.tsx +10 -2
  436. package/src/components/Tabs/tabs.spec.md +5 -2
  437. package/src/components/Tabs/tabs.stories.tsx +36 -44
  438. package/src/components/Tabs/tabs.tsx +101 -36
  439. package/src/components/Tag/tag.anatomy.stories.tsx +2 -2
  440. package/src/components/Tag/tag.principles.stories.tsx +4 -4
  441. package/src/components/Tag/tag.spec.md +3 -1
  442. package/src/components/Tag/tag.tsx +5 -3
  443. package/src/components/Textarea/textarea.anatomy.stories.tsx +6 -6
  444. package/src/components/Textarea/textarea.spec.md +5 -5
  445. package/src/components/Textarea/textarea.tsx +24 -24
  446. package/src/components/TimePicker/time-picker.anatomy.stories.tsx +12 -11
  447. package/src/components/TimePicker/time-picker.principles.stories.tsx +37 -44
  448. package/src/components/TimePicker/time-picker.spec.md +6 -5
  449. package/src/components/TimePicker/time-picker.stories.tsx +3 -3
  450. package/src/components/TimePicker/time-picker.tsx +24 -14
  451. package/src/components/Toast/toast.principles.stories.tsx +1 -1
  452. package/src/components/Toast/toast.stories.tsx +1 -1
  453. package/src/components/Tooltip/tooltip.anatomy.stories.tsx +15 -6
  454. package/src/components/Tooltip/tooltip.principles.stories.tsx +4 -4
  455. package/src/components/Tooltip/tooltip.spec.md +9 -3
  456. package/src/components/Tooltip/tooltip.stories.tsx +1 -1
  457. package/src/components/Tooltip/tooltip.tsx +12 -2
  458. package/src/components/TreeView/tree-view.anatomy.stories.tsx +16 -14
  459. package/src/components/TreeView/tree-view.principles.stories.tsx +44 -11
  460. package/src/components/TreeView/tree-view.spec.md +8 -6
  461. package/src/components/TreeView/tree-view.stories.tsx +9 -43
  462. package/src/components/TreeView/tree-view.tsx +36 -0
  463. package/src/index.ts +13 -10
  464. package/src/lib/drag-visual.ts +2 -2
  465. package/src/lib/utils.ts +19 -0
  466. package/src/patterns/element-anatomy/inline-action.spec.md +1 -1
  467. package/src/patterns/element-anatomy/item-anatomy.tsx +4 -2
  468. package/src/patterns/header-canonical/header-canonical.spec.md +9 -0
  469. package/src/patterns/header-canonical/header-canonical.stories.tsx +31 -5
  470. package/src/tokens/categorical-color.ts +5 -5
  471. package/src/tokens/color/color.spec.md +4 -4
  472. package/src/tokens/color/primitives.css +9 -2
  473. package/src/tokens/opacity/opacity.spec.md +1 -1
  474. package/src/tokens/orphan-tokens.spec.md +1 -1
  475. package/src/tokens/typography/typography.spec.md +2 -0
  476. package/src/tokens/uiSize/uiSize.css +5 -0
  477. package/src/tokens/uiSize/uiSize.spec.md +1 -1
@@ -73,7 +73,7 @@ const MultiSelectDemo = () => {
73
73
  return (
74
74
  <div className="flex flex-col gap-4 max-w-sm">
75
75
  <p className="text-caption text-fg-muted">Combobox — checkbox 多選,浮層不關閉</p>
76
- <Combobox options={categoryOptions} value={value} onChange={setValue} />
76
+ <Combobox options={categoryOptions} value={value} onChange={setValue} aria-label="商品分類(SelectMenu multi-select demo)" />
77
77
  </div>
78
78
  )
79
79
  }
@@ -90,7 +90,7 @@ const MultiSearchDemo = () => {
90
90
  return (
91
91
  <div className="flex flex-col gap-4 max-w-sm">
92
92
  <p className="text-caption text-fg-muted">searchable — 浮層內搜尋框,關鍵字保留可連續勾選</p>
93
- <Combobox options={countries} value={value} onChange={setValue} searchable />
93
+ <Combobox options={countries} value={value} onChange={setValue} searchable aria-label="國家(SelectMenu multi-select + search demo)" />
94
94
  </div>
95
95
  )
96
96
  }
@@ -100,23 +100,11 @@ export const MultiSearchable: StoryObj = {
100
100
  render: () => <MultiSearchDemo />,
101
101
  }
102
102
 
103
- // ── 可清除 ──
104
-
105
- const ClearableDemo = () => {
106
- const [value, setValue] = useState<string>('in_stock')
107
- return (
108
- <div className="flex flex-col gap-4 max-w-xs">
109
- <p className="text-caption text-fg-muted">clearable — 有值時右側出現清除按鈕</p>
110
- <Select options={statusOptions} value={value} onChange={setValue} clearable />
111
- </div>
112
- )
113
- }
114
-
115
- export const Clearable: StoryObj = {
116
- name: '可清除',
117
- render: () => <ClearableDemo />,
118
- }
119
-
103
+ // @story-trait-rationale: 原 Clearable(可清除)retired 2026-07-17(Dim 25 earn-existence)—
104
+ // 該 story 實際 render `<Select clearable>`,clearable 是 Select trigger/field state 的 API(spec
105
+ // L45/L53/L160 明定 trigger state 由外層 Select/Combobox own,SelectMenu 本身無此 prop),
106
+ // 且 Select.stories.tsx「可清除」已 own canonical。SelectMenu 展示層只保留浮層自身能力
107
+ // (搜尋 / 多選 / creatable / 選項狀態),不重演 wrapper 的 field feature。principles 頁對應 LinkTo 同步移除。
120
108
  // @story-trait-rationale: AllSizes retired per F migration 2026-05-15 — anatomy.stories.tsx SizeMatrix auto-compile owns size showcase。
121
109
  // @story-trait-rationale: 原 States(edit/readonly/disabled/error 觸發器狀態)retired 2026-06-11 —
122
110
  // trigger field state 由 Select.stories.tsx「四模式」owns(SelectMenu 是 internal popover surface,
@@ -31,7 +31,7 @@ import { ICON_SIZE } from '@/design-system/tokens/uiSize/icon-size'
31
31
  * ── 架構 ──
32
32
  * Popover(浮動容器)
33
33
  * └── Command(cmdk,搜尋 + 鍵盤導覽)
34
- * ├── CommandInput(搜尋框)
34
+ * ├── CommandPrimitive.Input(搜尋框,raw cmdk + 自建 icon/min-h wrapper,非 DS CommandInput
35
35
  * ├── CommandList(選項列表)
36
36
  * │ └── CommandGroup → MenuItem
37
37
  * └── Footer(多選全選)
@@ -81,6 +81,15 @@ export interface SelectMenuProps {
81
81
  onCreate?: (value: string) => void
82
82
  /** creatable 的 label 格式,預設 '直接使用「{query}」' */
83
83
  createLabel?: (query: string) => string
84
+ /**
85
+ * 受控搜尋字串(2026-07-18 決策11:讓 consumer 用**外部搜尋**(如 Select 的 trigger 內嵌 input)
86
+ * 驅動 creatable create-row 顯隱)。傳入 = 受控(SelectMenu 不自管 search、close 不 reset,由
87
+ * parent 負責);不傳 = 內部 uncontrolled(既有行為,零影響)。搭配 `searchable=false` +
88
+ * `creatable` 時,SelectMenu 不畫自己的 input,但 create-row 仍依此 search 顯示。
89
+ */
90
+ search?: string
91
+ /** 受控搜尋變更通知(選配;SelectMenu 內部無 input 時通常由 parent 單向驅動,不需此) */
92
+ onSearchChange?: (value: string) => void
84
93
 
85
94
  /** 觸發元件(asChild) */
86
95
  children: React.ReactNode
@@ -126,6 +135,14 @@ export interface SelectMenuProps {
126
135
  */
127
136
  contentId?: string
128
137
 
138
+ /**
139
+ * 浮層 accessible name(2026-07-17 Dim 10 a11y 修)。Radix Popover 的 content 為
140
+ * `role="dialog"` 但無自動命名機制 → 無 aria-label 時 dialog 無 accessible name。
141
+ * 預設「選項清單」讓 SR 使用者知道浮層用途;consumer(Combobox / Select / PeoplePicker)
142
+ * 可傳更 contextual 的名(如欄位名)。SSOT 放此一處 → 全 SelectMenu consumer 受益。
143
+ */
144
+ 'aria-label'?: string
145
+
129
146
  className?: string
130
147
  }
131
148
 
@@ -162,6 +179,8 @@ const SelectMenu = React.forwardRef<HTMLElement, SelectMenuProps>(function Selec
162
179
  creatable = false,
163
180
  onCreate,
164
181
  createLabel = (q) => `直接使用「${q}」`,
182
+ search: controlledSearch,
183
+ onSearchChange,
165
184
  children,
166
185
  searchPlaceholder = '搜尋…', // i18n-allow: DS default; consumer override via searchPlaceholder prop
167
186
  emptyText = '沒有符合的選項', // i18n-allow: DS default; consumer override via emptyText prop
@@ -177,6 +196,7 @@ const SelectMenu = React.forwardRef<HTMLElement, SelectMenuProps>(function Selec
177
196
  renderLabel,
178
197
  onOpenAutoFocus,
179
198
  contentId,
199
+ 'aria-label': ariaLabel = '選項清單', // i18n-allow: DS default; consumer override via aria-label prop
180
200
  className,
181
201
  }, _ref) {
182
202
  // ── State ──
@@ -189,7 +209,18 @@ const SelectMenu = React.forwardRef<HTMLElement, SelectMenuProps>(function Selec
189
209
  defaultValue: defaultOpen ?? false,
190
210
  onChange: controlledOnOpenChange,
191
211
  })
192
- const [search, setSearch] = React.useState('')
212
+ // search:選配受控(決策11)。傳 `search` prop = 受控(parent 驅動,如 Select trigger input);
213
+ // 不傳 = 內部 uncontrolled(既有行為)。setSearch 同步內部 + 通知 onSearchChange。
214
+ const isSearchControlled = controlledSearch !== undefined
215
+ const [internalSearch, setInternalSearch] = React.useState('')
216
+ const search = isSearchControlled ? controlledSearch : internalSearch
217
+ const setSearch = React.useCallback(
218
+ (next: string) => {
219
+ if (!isSearchControlled) setInternalSearch(next)
220
+ onSearchChange?.(next)
221
+ },
222
+ [isSearchControlled, onSearchChange],
223
+ )
193
224
 
194
225
  // ── Value helpers ──
195
226
  const selectedValues = React.useMemo<string[]>(() => {
@@ -279,10 +310,10 @@ const SelectMenu = React.forwardRef<HTMLElement, SelectMenuProps>(function Selec
279
310
  return grouped
280
311
  }, [groups, options])
281
312
 
282
- // ── Reset search on close ──
313
+ // ── Reset search on close(僅 uncontrolled;受控時由 parent 負責 reset)──
283
314
  React.useEffect(() => {
284
- if (!open) setSearch('')
285
- }, [open])
315
+ if (!open && !isSearchControlled) setInternalSearch('')
316
+ }, [open, isSearchControlled])
286
317
 
287
318
  // 2026-06-01 Select/Combobox #15(user 拍板 A):非搜尋時開選單把 focus 移到 cmdk-root,
288
319
  // 讓 cmdk 內建方向鍵 / Enter / Home / End 導覽生效。原 PopoverContent default autofocus 找 body
@@ -307,6 +338,8 @@ const SelectMenu = React.forwardRef<HTMLElement, SelectMenuProps>(function Selec
307
338
  <RowSizeProvider value={size}>
308
339
  <PopoverContent
309
340
  id={contentId}
341
+ // 2026-07-17 Dim 10 a11y 修:role="dialog" 浮層 accessible name(Radix Popover 無自動命名)
342
+ aria-label={ariaLabel}
310
343
  // w-auto override PopoverContent default w-72(rich-popover canonical)— SelectMenu 走「跟 trigger 同寬」
311
344
  // canonical(spec L72)。minWidth = max(trigger-width, 240px sensible-min)— 對齊 shadcn / Material / Ant
312
345
  // select dropdown 共識(2026-05-04 D1 verify SelectMenu spec implementation)。
@@ -1,4 +1,4 @@
1
- // @internal — boolean 值展示符號 SSOT(display / readonly 態);consumer 用 Checkbox / Switch 不直用。
1
+ // @internal — boolean 值展示符號 SSOT(view / readonly 態);consumer 用 Checkbox / Switch 不直用。
2
2
  // ── 消費的 SSOT ──
3
3
  // - lucide-react Check / X(DS icon canonical:成功/勾 = Check、關閉/否 = X,見 ui-development.md「Icon canonical」)
4
4
  // - components/Field/field-context.ts(FieldSize 型別 → icon 尺寸對齊 value 文字)
@@ -23,7 +23,7 @@ const booleanIconSize: Record<FieldSize, string> = {
23
23
 
24
24
  /**
25
25
  * BooleanValueIcon — boolean 值的展示符號(true = Check 勾 / false = X 叉),中性 `text-foreground` 色。
26
- * Checkbox / Switch 的 display + readonly 態、DataTable boolean cell(經 Checkbox)全消費此 SSOT。
26
+ * Checkbox / Switch 的 view + readonly 態、DataTable boolean cell(經 Checkbox)全消費此 SSOT。
27
27
  */
28
28
  export function BooleanValueIcon({
29
29
  checked,
@@ -192,7 +192,7 @@ export const Overview: Story = {
192
192
  <Td mono>disabled</Td>
193
193
  <Td mono>boolean</Td>
194
194
  <Td mono>false</Td>
195
- <Td>label / description / icon text-fg-disabled(M24);label 另加 cursor-not-allowed</Td>
195
+ <Td>label / description / icon 全轉 text-fg-disabled(disabled 狀態優先於 muted 裝飾色);label 另加 cursor-not-allowed</Td>
196
196
  </tr>
197
197
  <tr>
198
198
  <Td mono>htmlFor</Td>
@@ -9,7 +9,7 @@ const meta: Meta = {
9
9
  export default meta
10
10
  type Story = StoryObj
11
11
 
12
- // ── WhenToUse — 何時使用 SelectionControl ──────────────────────
12
+ // ── WhenToUse — 何時使用 SelectionItem ──────────────────────
13
13
 
14
14
 
15
15
  // ── UsageGuidance — 整合何時用 / 何時不用 / vs 近親(Polaris/Material/Ant 共識)
@@ -21,7 +21,7 @@ export const UsageGuidance: Story = {
21
21
  <div className="flex flex-col gap-12">
22
22
  {/* 何時用 — 原 WhenToUse */}
23
23
  <div className="prose prose-sm max-w-prose">
24
- <p>適合 SelectionControl 的真實業務場景(點擊跳轉「展示」頁範例):</p>
24
+ <p>適合 SelectionItem 的真實業務場景(點擊跳轉「展示」頁範例):</p>
25
25
  <ul className="space-y-1">
26
26
  <li>
27
27
  <LinkTo kind="Design System/Internal/SelectionControl/展示" name="通知偏好"><span className="text-primary hover:text-primary-hover font-medium cursor-pointer">通知偏好</span></LinkTo>
@@ -36,14 +36,14 @@ export const UsageGuidance: Story = {
36
36
  <LinkTo kind="Design System/Internal/SelectionControl/展示" name="前綴頭像"><span className="text-primary hover:text-primary-hover font-medium cursor-pointer">前綴頭像</span></LinkTo>
37
37
  </li>
38
38
  </ul>
39
- <p className="text-fg-muted mt-3">判斷不確定時,參考下方「SelectionControl 與 Checkbox + Field 的差異」說明;若場景不符,改用對應的近親元件。</p>
39
+ <p className="text-fg-muted mt-3">判斷不確定時,參考下方「SelectionItem 與 Checkbox + Field 的差異」說明;若場景不符,改用對應的近親元件。</p>
40
40
  </div>
41
41
 
42
42
  {/* vs 近親 — VsCheckboxRule — 原 VsCheckboxRule */}
43
43
  <div className="prose prose-sm max-w-prose">
44
44
  <p>兩者表達 selectable state,但結構與場景不同:</p>
45
45
  <ul>
46
- <li><strong>SelectionControl rich row(本 primitive,經 Checkbox / RadioGroup 消費)</strong>—大塊 row(含 icon / avatar / multi-line description),適合 settings / list-as-page;Notion/Slack idiom</li>
46
+ <li><strong>SelectionItem rich row(本 primitive,經 Checkbox / RadioGroup 消費)</strong>—大塊 row(含 icon / avatar / multi-line description),適合 settings / list-as-page;Notion/Slack idiom</li>
47
47
  <li><strong>Checkbox + Field</strong>—緊湊 form 欄位;適合 form group 多選</li>
48
48
  </ul>
49
49
  <p className="text-fg-muted">判斷:row 是 page content(讀取/設定)→ Checkbox / RadioGroup 的 rich-row 形態(內部消費 SelectionItem,不裸用 &lt;SelectionItem&gt;);row 是 form input(submit value)→ Checkbox + Field。</p>
@@ -60,7 +60,7 @@ export const UsageGuidance: Story = {
60
60
  <LinkTo kind="Design System/Components/Separator/展示" name="垂直"><span className="text-primary hover:text-primary-hover font-medium cursor-pointer">垂直分隔</span></LinkTo>
61
61
  </li>
62
62
  <li>
63
- <LinkTo kind="Design System/Components/Separator/展示" name="在 DropdownMenu 內"><span className="text-primary hover:text-primary-hover font-medium cursor-pointer">在 DropdownMenu 內</span></LinkTo>
63
+ <LinkTo kind="Design System/Components/DropdownMenu/展示" name="基本"><span className="text-primary hover:text-primary-hover font-medium cursor-pointer">在 DropdownMenu 內(DropdownMenuSeparator,見 DropdownMenu 展示「基本」)</span></LinkTo>
64
64
  </li>
65
65
  <li>
66
66
  <LinkTo kind="Design System/Components/Separator/展示" name="在 DescriptionList 區塊之間"><span className="text-primary hover:text-primary-hover font-medium cursor-pointer">在 DescriptionList 區塊之間</span></LinkTo>
@@ -1,4 +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
+ // @overlay-open-skip: 本檔為 usage-guidance(設計原則)story——每個 Rule 卡片以「關閉的 trigger 按鈕」並排比較 Sheet / Dialog / Popover 何時用;全部 defaultOpen 會讓多個 portal 疊在一起、視覺全毀。overlay 開啟狀態的視覺覆蓋由 sheet.stories.tsx(展示)+ sheet.anatomy.stories.tsx(Inspector defaultOpen)負責。
2
3
  import React from 'react'
3
4
  import LinkTo from '@storybook/addon-links/react'
4
5
  import type { Meta, StoryObj } from '@storybook/react'
@@ -179,7 +180,8 @@ export const UsageGuidance: Story = {
179
180
  <PopoverTrigger asChild>
180
181
  <Button variant="tertiary" startIcon={Filter}>篩選</Button>
181
182
  </PopoverTrigger>
182
- <PopoverContent align="start">
183
+ {/* naked popover(無 PopoverTitle)→ 自傳 aria-label 讓 role="dialog" 有 accessible name(popover.spec.md A11y) */}
184
+ <PopoverContent align="start" aria-label="依狀態篩選">
183
185
  <PopoverBody>
184
186
  <CheckboxGroup>
185
187
  <Checkbox defaultChecked label="進行中" />
@@ -264,7 +266,7 @@ export const SidePropRule: Story = {
264
266
  >
265
267
  <Sheet>
266
268
  <SheetTrigger asChild>
267
- <Button variant="tertiary">分享選項(mobile 常用 bottom)</Button>
269
+ <Button variant="tertiary">分享</Button>
268
270
  </SheetTrigger>
269
271
  <SheetContent side="bottom" className="max-w-full">
270
272
  <SheetHeader>
@@ -309,7 +311,7 @@ export const HeaderFooterStructureRule: Story = {
309
311
  >
310
312
  <Sheet>
311
313
  <SheetTrigger asChild>
312
- <Button variant="tertiary">標準結構範例</Button>
314
+ <Button variant="tertiary">建立新客戶</Button>
313
315
  </SheetTrigger>
314
316
  <SheetContent side="right" className="flex flex-col sm:max-w-lg">
315
317
  <SheetHeader>
@@ -88,8 +88,11 @@ const sheetVariants = cva(
88
88
  }
89
89
  )
90
90
 
91
+ // asChild Omit(2026-07-18 決策2):SheetContent 是固定 edge-anchored surface(overlay + sheetVariants
92
+ // 定位/slide 動畫 + onOpenAutoFocus),恆渲染 {children}(consumer body 多節點)→ <Content asChild>
93
+ // Radix Slot React.Children.only crash。children 保留(consumer body)。同 Dialog 收窄。
91
94
  interface SheetContentProps
92
- extends React.ComponentPropsWithoutRef<typeof SheetPrimitive.Content>,
95
+ extends Omit<React.ComponentPropsWithoutRef<typeof SheetPrimitive.Content>, 'asChild'>,
93
96
  VariantProps<typeof sheetVariants> {}
94
97
 
95
98
  // AutoFocus canonical(對齊 Dialog / Material / Polaris)— 見 dialog.tsx handleOpenAutoFocus 註解
@@ -1286,7 +1286,7 @@ SidebarMenuSkeleton.displayName = "SidebarMenuSkeleton"
1286
1286
  // Phase 2 fill needed: purpose descriptions + when rationale + world-class refs
1287
1287
  export const sidebarMeta = {
1288
1288
  component: 'Sidebar',
1289
- family: null, // non-family composite / overlay / layout
1289
+ family: 1, // Family 1(Menu item layout)消費者 — 對齊 sidebar.spec.md frontmatter family: 1(SidebarMenuButton rows 消費 item-anatomy;同 tree-view.tsx 慣例)
1290
1290
  variants: {
1291
1291
  default: { when: '標準導覽 row,參與 single-selection' },
1292
1292
  meta: { when: 'Section 底部命令 row(Show more / 新增),不參與 selection' },
@@ -62,6 +62,7 @@ export const Inspector: Story = {
62
62
  docs: { description: { story: '右側 Controls 切 props 即時 render,取代 Figma inspect。調整 `min` / `max` / `step` / `defaultValue` 看值域行為,切 `size` 看容器高度 tier(track / thumb 視覺固定)。' } },
63
63
  },
64
64
  args: {
65
+ 'aria-label': '音量',
65
66
  defaultValue: [60],
66
67
  min: 0,
67
68
  max: 100,
@@ -130,7 +131,7 @@ export const SizeMatrix: Story = {
130
131
  {(['sm', 'md', 'lg'] as const).map(size => (
131
132
  <div key={size} className="flex items-center gap-3 border border-dashed border-divider rounded-md p-3">
132
133
  <span className="text-caption text-fg-muted font-mono w-8">{size}</span>
133
- <Slider size={size} defaultValue={[50]} className="flex-1" />
134
+ <Slider size={size} defaultValue={[50]} aria-label="音量" className="flex-1" />
134
135
  </div>
135
136
  ))}
136
137
  </div>
@@ -149,15 +150,15 @@ export const StateBehavior: Story = {
149
150
  <div className="flex flex-col gap-3">
150
151
  <div className="border border-dashed border-divider rounded-md p-3">
151
152
  <div className="text-caption text-fg-muted mb-2">Default</div>
152
- <Slider defaultValue={[50]} />
153
+ <Slider defaultValue={[50]} aria-label="音量" />
153
154
  </div>
154
155
  <div className="border border-dashed border-divider rounded-md p-3">
155
156
  <div className="text-caption text-fg-muted mb-2">Disabled</div>
156
- <Slider defaultValue={[50]} disabled />
157
+ <Slider defaultValue={[50]} disabled aria-label="音量" />
157
158
  </div>
158
159
  <div className="border border-dashed border-divider rounded-md p-3">
159
160
  <div className="text-caption text-fg-muted mb-2">Range + Disabled</div>
160
- <Slider defaultValue={[20, 80]} disabled />
161
+ <Slider defaultValue={[20, 80]} disabled aria-label="價格區間" />
161
162
  </div>
162
163
  </div>
163
164
  </div>
@@ -65,7 +65,7 @@ export const UsageGuidance: Story = {
65
65
  >
66
66
  <div className="flex items-center gap-3">
67
67
  <span className="text-body w-12">音量</span>
68
- <Slider value={volume} onValueChange={setVolume} className="flex-1" />
68
+ <Slider value={volume} onValueChange={setVolume} aria-label="音量" className="flex-1" />
69
69
  <span className="text-caption text-fg-muted font-mono w-10">{volume[0]}%</span>
70
70
  </div>
71
71
  </Rule>
@@ -76,7 +76,7 @@ export const UsageGuidance: Story = {
76
76
  >
77
77
  <div className="flex items-center gap-3">
78
78
  <span className="text-body w-12">價格</span>
79
- <Slider value={price} onValueChange={setPrice} min={0} max={10000} step={100} className="flex-1" />
79
+ <Slider value={price} onValueChange={setPrice} min={0} max={10000} step={100} aria-label="價格範圍" className="flex-1" />
80
80
  <span className="text-caption text-fg-muted font-mono w-24">${price[0]} - ${price[1]}</span>
81
81
  </div>
82
82
  </Rule>
@@ -87,7 +87,7 @@ export const UsageGuidance: Story = {
87
87
  >
88
88
  <div className="flex items-center gap-3">
89
89
  <span className="text-body w-12">大小</span>
90
- <Slider defaultValue={[50]} step={50} min={0} max={100} className="flex-1" />
90
+ <Slider defaultValue={[50]} step={50} min={0} max={100} aria-label="大小" className="flex-1" />
91
91
  <span className="text-caption text-fg-muted font-mono w-10">中</span>
92
92
  </div>
93
93
  <Label warn>↑ 強制拖到 0 / 50 / 100 的 Slider = 反模式。改 SegmentedControl:</Label>
@@ -135,13 +135,13 @@ export const PositionAsStateRule: Story = {
135
135
  <div className="flex items-center gap-4">
136
136
  <div className="flex-1">
137
137
  <div className="text-caption text-fg-muted mb-1">Rest(primary)</div>
138
- <Slider defaultValue={[60]} />
138
+ <Slider defaultValue={[60]} aria-label="亮度" />
139
139
  </div>
140
140
  </div>
141
141
  <div className="flex items-center gap-4">
142
142
  <div className="flex-1">
143
143
  <div className="text-caption text-fg-muted mb-1">Disabled(灰階——位置/長度完全可辨)</div>
144
- <Slider defaultValue={[60]} disabled />
144
+ <Slider defaultValue={[60]} disabled aria-label="亮度" />
145
145
  </div>
146
146
  </div>
147
147
  <Label>↑ 灰階後 thumb 位置(60%)+ range 填滿長度(0-60%)仍然清楚。不需要保留藍色</Label>
@@ -194,7 +194,7 @@ export const HoverUsesShadowRule: Story = {
194
194
  >
195
195
  <div>
196
196
  <div className="text-caption text-fg-muted mb-1">Hover thumb → 陰影 elevation-100</div>
197
- <Slider defaultValue={[50]} />
197
+ <Slider defaultValue={[50]} aria-label="音量" />
198
198
  </div>
199
199
  <Label>↑ hover 時 thumb 加 elevation-100,不改底色。對齊 Material 3 / iOS / Linear 共識</Label>
200
200
  </Rule>
@@ -129,11 +129,13 @@ Slider 不是 button——它是「當前位置指示器」,底色不該動(動
129
129
  step={number} // 預設 1
130
130
  size="sm" | "md" | "lg" // 預設 md(只影響容器外高)
131
131
  disabled={boolean}
132
- orientation="horizontal" // 不支援 vertical(DS 尚無垂直變體;未來真有需求再擴充)
133
132
  minStepsBetweenThumbs={number} // range mode 時兩個 thumb 的最小距離
134
133
  />
135
134
  ```
136
135
 
136
+ - **不支援 vertical**:`orientation` 已自型別排除(`slider.tsx` `Omit<..., 'orientation'>`);DS 尚無垂直變體,未來真有需求再擴充。
137
+ - **`value` / `defaultValue` 必為非空陣列**:至少一個元素(`[single]` 或 `[min, max]` range)。傳空陣列 `[]` 時 Radix 無對應值會隱藏 thumb,留下沒有可操作 thumb 的空軌道 → `slider.tsx` 正規化為 `[min]`(`min` 未傳時為 `0`),保證至少一個坐落在 `min` 的可操作 thumb。省略值(不傳 `value` / `defaultValue`)時走 Radix 預設單 thumb。
138
+
137
139
  ### Range mode(雙 thumb)
138
140
 
139
141
  Radix Slider 原生支援多 thumb——只要 `value` / `defaultValue` 傳長度 > 1 的 array,就自動渲染對應數量的 thumb,range(填滿段)落在最小和最大 thumb 之間。 <!-- @benchmark-unverified: see frontmatter benchmark list for canonical DS source URL -->
@@ -214,6 +216,10 @@ Disabled slider 要傳達的兩件事——thumb 位置、range 長度——**
214
216
 
215
217
  Slider 沒有獨立的 error 視覺——拖曳選值本身不太會「無效」。如果業務邏輯需要限制範圍,用 `min` / `max` 直接限制使用者能拖到的範圍,不要讓他拖到再報錯。
216
218
 
219
+ ### 無 Loading state(N/A)
220
+
221
+ Slider 是**同步輸入 primitive**,thumb 上不加 spinner。值本身即為當前狀態,沒有「載入中的值」這種中間態。若資料尚未就緒(例如初始範圍要等 API 回),loading 由父層或 `Field` 擁有(在 Field 層顯示 skeleton / spinner),Slider 本身只在值到位後渲染——不在 thumb / track 疊加載入視覺。
222
+
217
223
  ### Readonly(僅 Field cascade,無獨立 prop)
218
224
 
219
225
  Slider 無獨立 `readOnly` prop;但在 `<Field mode="readonly">` 內(2026-06-12 拍板)= **鎖互動、保留正常視覺**(pointer-events-none + thumb tabIndex=-1 + aria-readonly on thumb;值仍可讀、不降色——readonly ≠ disabled)。理由:readonly 表單中 Slider 的值(thumb 位置 + range 長度)本身就是 value 呈現。若要在非表單情境顯示「歷史值」,用純文字或另一個 display 元件。
@@ -278,6 +284,12 @@ Inspector 提供 `min` / `max` / `step` / `defaultValue` × `size` 即時調整(
278
284
 
279
285
  **ARIA / Pattern**:繼承 Radix `slider` primitive a11y 預設(role / aria-* / 鍵盤導覽)。詳 [Radix Accessibility docs](https://www.radix-ui.com/primitives/docs/components/slider#accessibility)。
280
286
 
287
+ **Accessible name(命名,強制)**:`role="slider"` 的 thumb **必須有 accessible name**,否則螢幕閱讀器只讀出數值、不知這是「什麼」的值。Radix 對**單 thumb 不會自動產生名稱**(只有 2+ thumb 才給 Minimum / Maximum 泛稱)。命名三層(`slider.tsx` 已實作,由高到低優先):
288
+
289
+ 1. **consumer `aria-label`**:standalone(不在 Field 內)的 Slider **必傳** `aria-label`(例:`<Slider aria-label="音量" />`);多 thumb 時自動展開為 `{label} (1)` / `{label} (2)` 區分各 thumb。
290
+ 2. **Field 自動接線**:包在 `<Field>` 內且未傳 `aria-label` 時,thumb 自動 `aria-labelledby` 接 `FieldLabel`(元件讀 `fieldCtx.labelId`)——此情境不需另傳 `aria-label`。
291
+ 3. **Radix fallback**:兩者皆無時,2+ thumb 由 Radix 提供 Minimum / Maximum 泛稱;單 thumb 則**無名稱**(= 需靠上面兩層補)。
292
+
281
293
  **Keyboard 行為**:完整鍵盤對照見上方「鍵盤操作(Radix 原生,免手工)」節,不重複列。
282
294
 
283
295
  **Focus**:Radix primitive 自管 focus / restoration;thumb 鍵盤聚焦時 `outline-none focus-visible:border-primary-hover`(border 升 hover 階,跟 hover 同視覺,不加 ring / halo)。
@@ -2,6 +2,7 @@
2
2
  import * as React from 'react'
3
3
  import type { Meta, StoryObj } from '@storybook/react'
4
4
  import { Slider } from './slider'
5
+ import { NumberInput } from '@/design-system/components/NumberInput/number-input'
5
6
 
6
7
  const meta: Meta<typeof Slider> = {
7
8
  title: 'Design System/Components/Slider/展示',
@@ -21,19 +22,19 @@ type Story = StoryObj<typeof Slider>
21
22
  export const SizeAlignment: Story = {
22
23
  name: '容器尺寸對齊',
23
24
  render: () => (
24
- <div className="w-[360px] flex flex-col gap-6">
25
+ <div className="w-[420px] flex flex-col gap-6">
25
26
  <p className="text-caption text-fg-secondary max-w-[480px]">
26
27
  三個 size 下 track 厚度與 thumb 直徑一致——只有容器外高跟著
27
- `--field-height-*` 變。這讓 Slider 能在 Field 內跟 Input / Select /
28
- NumberInput 並排對齊,同時保持自己的視覺身分不變。
28
+ `--field-height-*` 變。這讓 Slider 能在同一列跟 NumberInput field
29
+ 控件並排、field-height 完美對齊,同時保持自己的視覺身分不變。
29
30
  </p>
30
31
  {(['sm', 'md', 'lg'] as const).map(size => (
31
32
  <div key={size} className="flex flex-col gap-2">
32
- <div className="text-caption text-fg-muted">
33
- size = {size}(h-field-{size})
34
- </div>
35
- <div className="border border-dashed border-border rounded-md p-0">
36
- <Slider size={size} defaultValue={[40]} />
33
+ <div className="text-caption text-fg-muted">size = {size}</div>
34
+ <div className="flex items-center gap-3">
35
+ <span className="text-body w-10 shrink-0">音量</span>
36
+ <Slider size={size} defaultValue={[40]} aria-label="音量" className="flex-1" />
37
+ <NumberInput size={size} value={40} onChange={() => {}} className="w-20 shrink-0" />
37
38
  </div>
38
39
  </div>
39
40
  ))}
@@ -46,20 +47,25 @@ export const SizeAlignment: Story = {
46
47
  export const MinMaxStep: Story = {
47
48
  name: '最小 / 最大 / 步階',
48
49
  render: () => {
49
- const [value, setValue] = React.useState([32])
50
+ const [quality, setQuality] = React.useState([80])
50
51
  return (
51
52
  <div className="w-[360px] flex flex-col gap-4">
52
- <div className="text-caption text-fg-muted">
53
- min=0, max=100, step=4
53
+ <p className="text-caption text-fg-secondary">
54
+ 匯出圖片品質——限制在 10–100% 之間,每次以 5% 為一階(min / max / step)
55
+ </p>
56
+ <div className="flex items-center gap-3">
57
+ <span className="text-body w-16 shrink-0">圖片品質</span>
58
+ <Slider
59
+ value={quality}
60
+ onValueChange={setQuality}
61
+ min={10}
62
+ max={100}
63
+ step={5}
64
+ aria-label="匯出圖片品質"
65
+ className="flex-1"
66
+ />
67
+ <span className="text-caption text-fg-muted font-mono w-10 shrink-0">{quality[0]}%</span>
54
68
  </div>
55
- <Slider
56
- value={value}
57
- onValueChange={setValue}
58
- min={0}
59
- max={100}
60
- step={4}
61
- />
62
- <p className="text-caption text-fg-secondary">Value: {value[0]}</p>
63
69
  </div>
64
70
  )
65
71
  },
@@ -74,24 +80,30 @@ export const MinMaxStep: Story = {
74
80
  export const OnCommit: Story = {
75
81
  name: '提交數值回呼',
76
82
  render: () => {
77
- const [live, setLive] = React.useState([50])
78
- const [committed, setCommitted] = React.useState([50])
83
+ const [preview, setPreview] = React.useState([3000])
84
+ const [applied, setApplied] = React.useState([3000])
79
85
  return (
80
86
  <div className="w-[360px] flex flex-col gap-4">
81
87
  <p className="text-caption text-fg-secondary">
82
- 拖曳時 live 跟著變,放開才更新 committed(適合昂貴操作如 API
83
- query、圖片重繪)
88
+ 價格上限篩選——拖曳時即時預覽,放開才送出查詢(適合昂貴操作如 API
89
+ query、重新載入結果)
84
90
  </p>
85
- <Slider
86
- value={live}
87
- onValueChange={setLive}
88
- onValueCommit={setCommitted}
89
- />
91
+ <div className="flex items-center gap-3">
92
+ <span className="text-body w-12 shrink-0">價格</span>
93
+ <Slider
94
+ value={preview}
95
+ onValueChange={setPreview}
96
+ onValueCommit={setApplied}
97
+ min={0}
98
+ max={10000}
99
+ step={100}
100
+ aria-label="價格上限"
101
+ className="flex-1"
102
+ />
103
+ </div>
90
104
  <div className="flex flex-col gap-1 text-caption">
91
- <span className="text-fg-secondary">Live: {live[0]}</span>
92
- <span className="text-foreground font-medium">
93
- Committed: {committed[0]}
94
- </span>
105
+ <span className="text-fg-secondary">即時預覽:${preview[0]}</span>
106
+ <span className="text-foreground font-medium">已套用查詢:${applied[0]}</span>
95
107
  </div>
96
108
  </div>
97
109
  )
@@ -70,18 +70,27 @@ const Slider = React.forwardRef<
70
70
  // 2026-06-10 a11y:Field 內 Slider thumb(role=slider)無 accessible name(deep-audit axe 抓 aria-input-field-name)
71
71
  // → 預設接 FieldLabel(aria-labelledby),consumer ariaLabel 優先。對齊 rating/time-picker labelId 接線。
72
72
  const fieldLabelId = useFieldContext()?.labelId
73
+ // Degenerate-input 硬化(2026-07 deep-audit d7i20 / d8i7):value / defaultValue 傳空陣列
74
+ // 時,Radix 無對應值 → thumb display:none,留下沒有可操作 thumb 的空軌道。正規化為 `[min]`
75
+ // (min 未傳時 Radix 預設 0),保證至少一個坐落在 min 的可操作 thumb —— 對齊 spec API
76
+ //「value / defaultValue 必為非空陣列」契約,且不影響任何有效輸入。
77
+ const minValue = (props as { min?: number }).min ?? 0
78
+ const safeValue =
79
+ Array.isArray(value) && value.length === 0 ? [minValue] : value
80
+ const safeDefaultValue =
81
+ Array.isArray(defaultValue) && defaultValue.length === 0 ? [minValue] : defaultValue
73
82
  // 推導要渲染幾個 thumb:controlled 用 value,uncontrolled 用 defaultValue,
74
83
  // 都沒有時 fallback 單 thumb(Radix 預設行為)
75
84
  const thumbCount =
76
- (Array.isArray(value) && value.length) ||
77
- (Array.isArray(defaultValue) && defaultValue.length) ||
85
+ (Array.isArray(safeValue) && safeValue.length) ||
86
+ (Array.isArray(safeDefaultValue) && safeDefaultValue.length) ||
78
87
  1
79
88
 
80
89
  return (
81
90
  <SliderPrimitive.Root
82
91
  ref={ref}
83
- value={value}
84
- defaultValue={defaultValue}
92
+ value={safeValue}
93
+ defaultValue={safeDefaultValue}
85
94
  className={cn(sliderRootVariants({ size }), fieldReadonly && 'pointer-events-none', className)}
86
95
  {...props}
87
96
  disabled={(props as { disabled?: boolean }).disabled || fieldDisabled}
@@ -251,9 +251,9 @@ export const SizeMatrix: Story = {
251
251
  <div key={size} className="border border-dashed border-divider rounded-md p-4">
252
252
  <div className="text-caption text-fg-muted mb-2 font-mono">size="{size}"</div>
253
253
  <Steps size={size} value="b" completedValues={['a']}>
254
- <StepItem value="a"><StepLabel>第一步</StepLabel></StepItem>
255
- <StepItem value="b"><StepLabel>第二步</StepLabel></StepItem>
256
- <StepItem value="c"><StepLabel>第三步</StepLabel></StepItem>
254
+ <StepItem value="a"><StepLabel>填寫資料</StepLabel></StepItem>
255
+ <StepItem value="b"><StepLabel>確認訂單</StepLabel></StepItem>
256
+ <StepItem value="c"><StepLabel>完成付款</StepLabel></StepItem>
257
257
  </Steps>
258
258
  </div>
259
259
  ))}
@@ -421,12 +421,12 @@ export const IndentAlignment: Story = {
421
421
  <Desc>**刻意打破 item-layout 的 24px 閾值規則**——不管 indicator 尺寸、不管有無 description,一律 inline 對齊。Column rhythm 優先於「大 prefix 視覺重量平衡文字塊」。這是 Steps 跟其他 row primitive 的本質差異——Steps 是「一條有連接關係的進度路徑」,column rhythm 是元件本身。</Desc>
422
422
  <div className="border border-border rounded-lg p-4 max-w-md">
423
423
  <Steps orientation="vertical" value="b" completedValues={['a']}>
424
- <StepItem value="a"><StepLabel>簡短 label</StepLabel></StepItem>
424
+ <StepItem value="a"><StepLabel>驗證電子信箱</StepLabel></StepItem>
425
425
  <StepItem value="b">
426
- <StepLabel>帶有描述的 label</StepLabel>
427
- <StepDescription>即使有多行 description,indicator 仍然對齊 label 第一行,保持 column rhythm</StepDescription>
426
+ <StepLabel>完善公司資料</StepLabel>
427
+ <StepDescription>填寫營業登記號碼、負責人身分與收款銀行帳戶,審核通過後才會開通金流收款功能</StepDescription>
428
428
  </StepItem>
429
- <StepItem value="c"><StepLabel>另一個 label</StepLabel></StepItem>
429
+ <StepItem value="c"><StepLabel>啟用收款</StepLabel></StepItem>
430
430
  </Steps>
431
431
  </div>
432
432
  <p className="text-footnote text-fg-muted mt-3">業界共識:Apple HIG、Material 3、Linear、GitHub Actions 的 steps 都是 indicator 對齊 label 第一行</p>
@@ -128,7 +128,7 @@ export const ParentControlled: Story = {
128
128
  </StepItem>
129
129
  <StepItem value="payment">
130
130
  <StepLabel>付款</StepLabel>
131
- <StepDescription>此頁為 current + error</StepDescription>
131
+ <StepDescription>信用卡授權失敗,請確認卡號與有效期限後重新付款</StepDescription>
132
132
  </StepItem>
133
133
  <StepItem value="done">
134
134
  <StepLabel>完成</StepLabel>
@@ -221,8 +221,8 @@ Per-item `state="error"` prop 存在但是 **escape hatch**,僅用在 inline JSX
221
221
 
222
222
  ### Outer ring 的關鍵設計
223
223
 
224
- - **Bounding box 固定**:box-shadow 不佔 layout(zero layout impact),focused / non-focused 的 indicator 佔用完全相同的寬高(md=24px,lg=32px,sm=24px hit area)
225
- - **Surface gap + ring 兩層 box-shadow 實作**:`0 0 0 2px var(--surface), 0 0 0 4px <ringColor>`——內圈先用 surface 色拉開 2px gap,外圈再疊 2px ring 色,形成「indicator 外有一圈帶間隙的環」(對齊 Polaris / shadcn focus-ring surface-gap idiom)
224
+ - **Bounding box 固定**:focus 外環以 box-shadow 表達,不改變 indicator bounding box——focused / non-focused 佔用完全相同的寬高(md=24px,lg=32px,sm=24px hit area)
225
+ - **Surface gap + ring 兩層外環**:內圈先用 surface 色拉開一段 gap,外圈再疊 ring 色,形成「indicator 外有一圈帶間隙的環」——精確 gap / ring 寬度與 box-shadow 值見 steps.tsx `getOuterRingShadow`(對齊 Polaris / shadcn focus-ring surface-gap idiom)<!-- @benchmark-unverified: see frontmatter benchmark list for canonical DS source URL -->
226
226
  - **Ring 色由 state 決定**:`error` → `--error-hover`;non-linear `current` → `--border-hover`;其餘(含 linear current / completed / upcoming / reachable)→ `--info-hover`
227
227
 
228
228
  ### State × Focus 視覺矩陣(md/lg)
@@ -346,6 +346,8 @@ Description 在 error state 下維持 `text-fg-secondary`(跟其他 state 一樣
346
346
 
347
347
  `orientation="horizontal"` 時 `<StepContent>` 一律不渲染,`expansion` prop 被忽略。水平空間不夠塞 content 區,強塞會破壞 stepper 的掃視節奏。Consumer 可以共用同一份 JSX 在兩種 orientation 間切換,不會報錯。
348
348
 
349
+ **展開 controlled/uncontrolled(2026-07-18 user 拍板補完整雙向)**:`multiple` 模式支援 controlled `expanded: string[]` + `onExpandedChange`(與 `defaultExpanded` uncontrolled 二選一)。理由:Steps 整體 API 哲學是 **parent 掌控狀態**(`value` / `completedValues` / `errorValues` 全 parent-controlled),唯獨展開狀態原只 uncontrolled 自相矛盾;且最近親 `TreeView` 就是完整雙向(`expandedIds` + `onExpandedChange` + `defaultExpandedIds`)。對齊 Radix/MUI/Ant Accordion 全數雙向。傳 `expanded` 時展開狀態由 consumer 掌控,toggle step 經 `onExpandedChange` 回寫新陣列。
350
+
349
351
  ---
350
352
 
351
353
  ## Orientation