@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
@@ -228,6 +228,7 @@ const buttonVariants = cva(
228
228
  interface ButtonGroupContextValue {
229
229
  fullWidth?: boolean
230
230
  }
231
+ /** @internal — ButtonGroup↔Button 私有 context(fullWidth 注入);consumer 不直接 import,經 <ButtonGroup> 消費。root barrel 排除(subpath 仍可 wrap 後用)。 */
231
232
  const ButtonGroupContext = React.createContext<ButtonGroupContextValue>({})
232
233
 
233
234
  type InternalVariant = VariantProps<typeof buttonVariants>['variant']
@@ -581,7 +582,7 @@ export const buttonMeta = {
581
582
  // 2026-07-14 deep-audit 二次補齊(掃 cva 全 bg-* class):+ neutral-selected-hover / neutral-selected-active
582
583
  // (neutral pressedTone compound 消費)+ error / error-hover / error-active(primary+danger bg-error 系)
583
584
  bg: ['--primary', '--primary-hover', '--primary-active', '--primary-subtle', '--bg-disabled', '--surface', '--neutral-hover', '--neutral-active', '--neutral-selected', '--neutral-selected-hover', '--neutral-selected-active', '--error', '--error-hover', '--error-active'],
584
- fg: ['--on-emphasis', '--fg-disabled', '--foreground', '--primary', '--error', '--error-hover', '--error-active'],
585
+ fg: ['--on-emphasis', '--fg-disabled', '--foreground', '--primary', '--primary-hover', '--primary-active', '--error', '--error-hover', '--error-active'],
585
586
  ring: ['--ring'],
586
587
  },
587
588
  defaultVariant: 'tertiary', // 2026-06-10 修 stale:對齊 cva defaultVariants(2026-06-06 labeled 預設改 tertiary,meta 漏同步)
@@ -216,7 +216,7 @@ export const Accessibility = {
216
216
  render: () => (
217
217
  <div className="max-w-3xl text-body text-fg-secondary">
218
218
  <h3 className="text-h5 text-foreground mb-2">無障礙設計</h3>
219
- <p className="whitespace-pre-line">{"詳 `calendar.spec.md` 「A11y 預設」段。摘要:\n\n Grid role :月格容器 `role=\"grid\"` + `aria-label`(月份),每列 `role=\"row\"`(`display:contents` 保 CSS grid 佈局),每格 `role=\"gridcell\"`(非互動容器),日期數字按鈕帶 `aria-label`(日期 + 事件數)。事件 tile `role=\"button\"` + `aria-label`(事件標題)。\n\n Keyboard 行為(MVP 實作現況) :\n\n- Tab — 逐一 focus 每格的日期數字按鈕與其中的事件 tile(cell 為非互動 gridcell 容器,滑鼠點空白處等同點日期)\n- Enter / Space — 日期數字按鈕觸發 `onDateClick`;事件 tile 觸發 `onEventClick`\n- Toolbar 的 ◀ / 今天 / ▶ / 檢視切換為標準可聚焦控件,Tab 可達\n\n Keyboard tech debt(尚未實作,見 spec.md「MVP vs 後續增量」) :\n\n- ↑/↓/←/→ 在日期格間 roving 移動、PageUp/Down 切月、Shift+PageUp/Down 切年、Esc 關閉 — 隨週 / 日 view 增量一併補上 roving tabindex\n\n Focus :focus-visible ring 對齊 DS 設計準則( outline: 2px solid var(--ring) );日期數字按鈕與事件 tile 皆有 ring。\n\n 驗證 :Storybook a11y addon panel 應 0 critical violation。WCAG AA contrast ≥ 4.5:1(text)/ 3:1(UI)。"}</p>
219
+ <p className="whitespace-pre-line">{"詳 `calendar.spec.md` 「A11y 預設」段。摘要:\n\n Grid role :月格容器 `role=\"grid\"` + `aria-label`(月份),每列 `role=\"row\"`(`display:contents` 保 CSS grid 佈局),每格 `role=\"gridcell\"`(非互動容器),日期數字按鈕帶 `aria-label`(日期 + 事件數)。事件 tile `role=\"button\"` + `aria-label`(事件標題)。\n\n Keyboard 行為(MVP 實作現況) :\n\n- Tab — 逐一 focus 每格的日期數字按鈕與其中的事件 tile(cell 為非互動 gridcell 容器,滑鼠點空白處等同點日期)\n- Enter / Space — 日期數字按鈕觸發 `onDateClick`;事件 tile 觸發 `onEventClick`\n- Toolbar 的 ◀ / 今天 / ▶ / 檢視切換為標準可聚焦控件,Tab 可達\n\n Keyboard tech debt(尚未實作,見 spec.md「MVP vs 後續增量」) :\n\n- ↑/↓/←/→ 在日期格間 roving 移動、PageUp/Down 切月、Shift+PageUp/Down 切年、Esc 關閉 — 隨週 / 日 view 增量一併補上 roving tabindex\n\n Focus :focus-visible ring 對齊 DS 設計準則(ring-2 ring-ring,box-shadow 實作 + outline-none);日期數字按鈕與事件 tile 皆有 ring。\n\n 驗證 :Storybook a11y addon panel 應 0 critical violation。WCAG AA contrast ≥ 4.5:1(text)/ 3:1(UI)。"}</p>
220
220
  </div>
221
221
  ),
222
222
  }
@@ -267,6 +267,6 @@ MVP 無內建 error 狀態(無 `error` / `onRetry` prop)——載入失敗由 co
267
267
  - ↑/↓/←/→ 在日期格間 roving 移動、PageUp/Down 切月、Shift+PageUp/Down 切年、Esc 關閉 — 隨週 / 日 view 增量一併補上 roving tabindex
268
268
  - 此為**已知 a11y gap(非發佈 blocker)**:方向鍵 roving 是 APG grid pattern 建議;MVP 所有互動元素皆 Tab 可達(鍵盤可操作性已滿足),驗證基準見下方「驗證」
269
269
 
270
- **Focus**:focus-visible ring 對齊 DS canonical(`outline: 2px solid var(--ring)`);日期數字按鈕與事件 tile 皆有 ring。
270
+ **Focus**:focus-visible ring 對齊 DS canonical(`ring-2 ring-ring`,box-shadow 實作 + `outline-none`,同 button.spec A11y );日期數字按鈕與事件 tile 皆有 ring。
271
271
 
272
272
  **驗證**:Storybook a11y addon panel 應 0 critical violation。WCAG AA contrast ≥ 4.5:1(text)/ 3:1(UI)。
@@ -128,7 +128,7 @@ Tabs 和 Carousel 都能「按順序切換下方內容」,但**語意與視覺
128
128
 
129
129
  ### 視覺規格
130
130
 
131
- - **基於 DS Button**:`<Button variant="tertiary" size="md" iconOnly />`——對齊 DS 規則,唯 shape 覆寫 `rounded-full`(media carousel documented 例外,見下一條「形狀 documented 例外」);其餘視覺全繼承 Button tertiary,不另自訂
131
+ - **基於 DS Button**:`<Button variant="tertiary" size="md" iconOnly />`(variant/size 為 **default,可覆寫**——見「Arrow Props」)——對齊 DS 規則,唯 shape 覆寫 `rounded-full`(media carousel documented 例外,見下一條「形狀 documented 例外」);其餘視覺全繼承 Button tertiary,不另自訂
132
132
  - **形狀 documented 例外**:override `rounded-md` → **`rounded-full`**(圓形)。理由:media carousel 視覺取向,圓形箭頭減少方塊感、不壓迫媒體內容(對齊 **Instagram / Airbnb / Notion Gallery / Apple Photos / Google Photos lightbox 世界級慣例** — 媒體 overlay 控制器圓形)。本 DS 內 Button default 是 `rounded-md`,這裡是唯一文明 override,documented 例外記錄即為本段(同步 `carousel.tsx` Button `className="rounded-full"` 旁 inline 註解)。**不傳染**:其他元件的 Button 仍走 rounded-md <!-- @benchmark-unverified: see frontmatter benchmark list for canonical DS source URL -->
133
133
  - 尺寸:`size="md"` 對應 `--field-height-md`(default/md 32px / lg 36px),iconOnly 正方形;形狀 **`rounded-full`**(documented 例外,見上一條)
134
134
  - 為什麼 tertiary:`bg-surface` + `border-border`,hover 僅改 text/border 為 `primary-hover`——無 bg-hover tint,視覺最輕,不搶 media 權重;secondary 有 neutral 邊框+hover 較重,primary 有底色更搶焦點,均不合適
@@ -136,6 +136,22 @@ Tabs 和 Carousel 都能「按順序切換下方內容」,但**語意與視覺
136
136
  - 圖標:`startIcon={ChevronLeft}` / `startIcon={ChevronRight}`,尺寸由 Button 內部程式化決定
137
137
  - **絕對定位 wrapper**:Button 外層 `<div>` 負責 `absolute` 定位 + hover-only opacity(`group-hover/carousel:opacity-100` / `focus-within:opacity-100`);Button 本身不做任何定位或可見性邏輯(職責分離)
138
138
 
139
+ ### Arrow Props(API 契約,2026-07-18 決策14)
140
+
141
+ `CarouselPrevious` / `CarouselNext` 開放**完整 Button props**——對齊 shadcn Carousel 的 `React.ComponentProps<typeof Button>` idiom(source `https://github.com/shadcn-ui/ui` `apps/www/registry/default/ui/carousel.tsx` CarouselPrevious/Next) <!-- @benchmark-unverified: shadcn Carousel arrow 轉發 Button props 為已知 pattern,確切行號未逐一 WebFetch 驗 -->。consumer 可換 `variant` / `size`、綁 analytics `onClick`、傳 `data-*` / `id`:
142
+
143
+ | Prop | 指向 | 說明 |
144
+ |------|------|------|
145
+ | `className` | **箭頭按鈕本身** | 疊在 DS default(`rounded-full` 等)之後,可覆寫按鈕樣式(className 指按鈕,非 wrapper) |
146
+ | `wrapperClassName` | **定位層 wrapper** | 絕對定位 + hover-only 顯示層的 className(換位置 / 顯示邏輯用) |
147
+ | `variant` / `size` | 箭頭按鈕 | default `tertiary` / `md`,可覆寫 |
148
+ | `startIcon` | 箭頭按鈕 | default `ChevronLeft` / `ChevronRight`,可覆寫圖標 |
149
+ | `onClick` | 箭頭按鈕 | 與內部 `scrollPrev/Next` **compose**(先導覽再呼叫 consumer handler;analytics 用) |
150
+ | `disabled` | 箭頭按鈕 | 與邊界態 **compose**(`!canScroll* \|\| disabled`;consumer 可強制 disable,不可強制在邊界 enable) |
151
+ | `iconOnly` | — | 固定 `true`(箭頭恆 icon-only),不可覆寫 |
152
+
153
+ **⚠️ BREAKING(決策14 遷移)**:`className` 語義由「定位 wrapper」改為「箭頭按鈕」。**原本用 `className` 做定位覆寫的 consumer → 改傳 `wrapperClassName`**。DS 內既有 consumer 全數 bare `<CarouselPrevious />`(無 className 傳入)= 0 遷移成本;fork consumer 若有覆寫定位,依本條遷移。
154
+
139
155
  ---
140
156
 
141
157
  ## Dots indicator
@@ -187,8 +187,12 @@ Carousel.displayName = 'Carousel'
187
187
  const CarouselContent = React.forwardRef<
188
188
  HTMLDivElement,
189
189
  React.HTMLAttributes<HTMLDivElement>
190
- >(({ className, ...props }, ref) => {
190
+ >(({ className, children, ...props }, ref) => {
191
191
  const { carouselRef, orientation } = useCarousel()
192
+ // 2026-07-18 決策5:注入 slide index/total 給 CarouselItem 自動產生「第 N 張,共 M 張」a11y 位置名
193
+ // (WAI-ARIA APG carousel「N of M」https://www.w3.org/WAI/ARIA/apg/patterns/carousel/)。count 取
194
+ // React.Children.count(可靠,不依賴 embla init 後的 scrollSnaps)。
195
+ const total = React.Children.count(children)
192
196
  return (
193
197
  <div ref={carouselRef} className="overflow-hidden">
194
198
  <div
@@ -199,31 +203,46 @@ const CarouselContent = React.forwardRef<
199
203
  className,
200
204
  )}
201
205
  {...props}
202
- />
206
+ >
207
+ {React.Children.map(children, (child, index) =>
208
+ React.isValidElement(child)
209
+ ? React.cloneElement(child as React.ReactElement<CarouselItemInternalProps>, { _slideIndex: index, _slideTotal: total })
210
+ : child,
211
+ )}
212
+ </div>
203
213
  </div>
204
214
  )
205
215
  })
206
216
  CarouselContent.displayName = 'CarouselContent'
207
217
 
208
- const CarouselItem = React.forwardRef<
209
- HTMLDivElement,
210
- React.HTMLAttributes<HTMLDivElement>
211
- >(({ className, ...props }, ref) => {
212
- const { orientation } = useCarousel()
213
- return (
214
- <div
215
- ref={ref}
216
- role="group"
217
- aria-roledescription="slide"
218
- className={cn(
219
- 'min-w-0 shrink-0 grow-0 basis-full',
220
- orientation === 'horizontal' ? 'pl-4' : 'pt-4',
221
- className,
222
- )}
223
- {...props}
224
- />
225
- )
226
- })
218
+ interface CarouselItemInternalProps extends React.HTMLAttributes<HTMLDivElement> {
219
+ /** @internal CarouselContent 注入 —— 用於自動 slide 位置名。 */
220
+ _slideIndex?: number
221
+ _slideTotal?: number
222
+ }
223
+
224
+ const CarouselItem = React.forwardRef<HTMLDivElement, CarouselItemInternalProps>(
225
+ ({ className, _slideIndex, _slideTotal, 'aria-label': ariaLabel, ...props }, ref) => {
226
+ const { orientation } = useCarousel()
227
+ // 無 consumer aria-label 時自動位置名(第 N 張,共 M 張);consumer 傳 aria-label 優先(可自訂內容名)。
228
+ const autoLabel =
229
+ ariaLabel ?? (_slideIndex != null && _slideTotal != null ? `第 ${_slideIndex + 1} 張,共 ${_slideTotal} 張` : undefined)
230
+ return (
231
+ <div
232
+ ref={ref}
233
+ role="group"
234
+ aria-roledescription="slide"
235
+ aria-label={autoLabel}
236
+ className={cn(
237
+ 'min-w-0 shrink-0 grow-0 basis-full',
238
+ orientation === 'horizontal' ? 'pl-4' : 'pt-4',
239
+ className,
240
+ )}
241
+ {...props}
242
+ />
243
+ )
244
+ },
245
+ )
227
246
  CarouselItem.displayName = 'CarouselItem'
228
247
 
229
248
  // ── Arrow buttons(hover 才顯示)────────────────────────────────────────────
@@ -231,10 +250,15 @@ CarouselItem.displayName = 'CarouselItem'
231
250
  // opacity transition 控制(Button 本身不負責)。此 wrapper 存在僅為絕對定位 +
232
251
  // hover/focus 可見性,不再覆寫 Button 的視覺 token。
233
252
 
234
- type ArrowProps = {
235
- className?: string
236
- /** ARIA label. Override for i18n. Prev default: 「上一張」;Next default: 「下一張」 */
237
- 'aria-label'?: string
253
+ // 2026-07-18 決策14(user 授權 B 案:開放完整 Button props + wrapperClassName):
254
+ // 對齊 shadcn/Radix Carousel —— `className` 指**箭頭按鈕本身**(非定位 wrapper),
255
+ // consumer 可換 variant/size、綁 analytics onClick、傳 data-* 等完整 Button props。
256
+ // 定位層(絕對定位 + hover-only 顯示)的 className 走新 `wrapperClassName`。
257
+ // **BREAKING**:`className` 語義由「定位 wrapper」改為「按鈕」(遷移:定位覆寫改傳 wrapperClassName)。
258
+ // onClick/disabled 內部與 carousel 導覽 compose(scrollPrev/Next + 邊界 disable 保留,consumer 疊加)。
259
+ type ArrowProps = Omit<React.ComponentPropsWithoutRef<typeof Button>, 'iconOnly'> & {
260
+ /** 定位層 wrapper 的 className(絕對定位 + hover-only 顯示層);箭頭按鈕樣式走 `className`。 */
261
+ wrapperClassName?: string
238
262
  }
239
263
 
240
264
  const arrowWrapperClass = cn(
@@ -247,7 +271,19 @@ const arrowWrapperClass = cn(
247
271
 
248
272
  // code-quality-allow: long-function — foundational composite main body — 拆 sub-fn 會複雜化 local state / ref / context binding
249
273
  const CarouselPrevious = React.forwardRef<HTMLButtonElement, ArrowProps>(
250
- ({ className, 'aria-label': ariaLabel = '上一張' /* i18n-allow: DS default; consumer override via aria-label prop */ }, ref) => {
274
+ (
275
+ {
276
+ className,
277
+ wrapperClassName,
278
+ onClick,
279
+ disabled,
280
+ variant = 'tertiary',
281
+ size = 'md',
282
+ 'aria-label': ariaLabel = '上一張' /* i18n-allow: DS default; consumer override via aria-label prop */,
283
+ ...props
284
+ },
285
+ ref,
286
+ ) => {
251
287
  const { orientation, scrollPrev, canScrollPrev } = useCarousel()
252
288
  return (
253
289
  <div
@@ -256,22 +292,24 @@ const CarouselPrevious = React.forwardRef<HTMLButtonElement, ArrowProps>(
256
292
  orientation === 'horizontal'
257
293
  ? 'left-3 top-1/2 -translate-y-1/2'
258
294
  : 'top-3 left-1/2 -translate-x-1/2 rotate-90',
259
- className,
295
+ wrapperClassName,
260
296
  )}
261
297
  >
262
298
  <Button
263
299
  ref={ref}
264
- variant="tertiary"
265
- size="md"
266
- iconOnly
300
+ variant={variant}
301
+ size={size}
267
302
  startIcon={ChevronLeft}
268
303
  aria-label={ariaLabel}
269
- disabled={!canScrollPrev}
270
- onClick={scrollPrev}
304
+ {...props}
305
+ iconOnly
306
+ disabled={!canScrollPrev || disabled}
307
+ onClick={(e) => { scrollPrev(); onClick?.(e) }}
271
308
  // documented exception:視覺取向的 media carousel 箭頭用 rounded-full 圓形,
272
309
  // 優於 DS default rounded-md。對齊 Instagram / Airbnb / Notion / Apple Photos
273
310
  // 世界級慣例 — media carousel 箭頭圓形減少視覺方塊感壓迫內容。spec「箭頭視覺規格」有明示。
274
- className="rounded-full"
311
+ // consumer className 疊加在後可覆寫(決策14)。
312
+ className={cn('rounded-full', className)}
275
313
  />
276
314
  </div>
277
315
  )
@@ -281,7 +319,19 @@ CarouselPrevious.displayName = 'CarouselPrevious'
281
319
 
282
320
  // code-quality-allow: long-function — 與 CarouselPrevious 結構對稱的 JSX-heavy arrow(方向/位置/邊界三態 className 組合),拆 helper 會破壞兩支對讀性
283
321
  const CarouselNext = React.forwardRef<HTMLButtonElement, ArrowProps>(
284
- ({ className, 'aria-label': ariaLabel = '下一張' /* i18n-allow: DS default; consumer override via aria-label prop */ }, ref) => {
322
+ (
323
+ {
324
+ className,
325
+ wrapperClassName,
326
+ onClick,
327
+ disabled,
328
+ variant = 'tertiary',
329
+ size = 'md',
330
+ 'aria-label': ariaLabel = '下一張' /* i18n-allow: DS default; consumer override via aria-label prop */,
331
+ ...props
332
+ },
333
+ ref,
334
+ ) => {
285
335
  const { orientation, scrollNext, canScrollNext } = useCarousel()
286
336
  return (
287
337
  <div
@@ -290,20 +340,21 @@ const CarouselNext = React.forwardRef<HTMLButtonElement, ArrowProps>(
290
340
  orientation === 'horizontal'
291
341
  ? 'right-3 top-1/2 -translate-y-1/2'
292
342
  : 'bottom-3 left-1/2 -translate-x-1/2 rotate-90',
293
- className,
343
+ wrapperClassName,
294
344
  )}
295
345
  >
296
346
  <Button
297
347
  ref={ref}
298
- variant="tertiary"
299
- size="md"
300
- iconOnly
348
+ variant={variant}
349
+ size={size}
301
350
  startIcon={ChevronRight}
302
351
  aria-label={ariaLabel}
303
- disabled={!canScrollNext}
304
- onClick={scrollNext}
305
- // documented exception:同 Previous,媒體導向 carousel 箭頭圓形
306
- className="rounded-full"
352
+ {...props}
353
+ iconOnly
354
+ disabled={!canScrollNext || disabled}
355
+ onClick={(e) => { scrollNext(); onClick?.(e) }}
356
+ // documented exception:同 Previous,媒體導向 carousel 箭頭圓形;consumer className 疊加可覆寫。
357
+ className={cn('rounded-full', className)}
307
358
  />
308
359
  </div>
309
360
  )
@@ -1,6 +1,7 @@
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
2
  import * as React from 'react'
3
3
  import { cn } from '@/lib/utils'
4
+ import { useFieldContext } from '@/design-system/components/Field/field-context'
4
5
 
5
6
  // ── CheckboxGroupContext ────────────────────────────────────────────────────
6
7
  // 讓內部 `<Checkbox>` 知道「我在 CheckboxGroup 裡」→ 即使 Field context 也存在,
@@ -71,21 +72,31 @@ export interface CheckboxGroupProps extends React.HTMLAttributes<HTMLDivElement>
71
72
  const CHECKBOX_GROUP_CTX_VALUE = { inGroup: true } as const
72
73
 
73
74
  const CheckboxGroup = React.forwardRef<HTMLDivElement, CheckboxGroupProps>(
74
- ({ className, orientation = 'vertical', ...props }, ref) => (
75
- <CheckboxGroupContext.Provider value={CHECKBOX_GROUP_CTX_VALUE}>
76
- <div
77
- ref={ref}
78
- role="group"
79
- className={cn(
80
- // 垂直 CheckboxGroup:zero gap(間距由 SelectionItem py 獨家擁有,見 docblock canonical)
81
- // 水平:短 label 並排需水平 gap-4(label 沒有 py 擴散,需要顯式 gap)
82
- orientation === 'vertical' ? 'grid' : 'flex flex-wrap gap-4',
83
- className
84
- )}
85
- {...props}
86
- />
87
- </CheckboxGroupContext.Provider>
88
- )
75
+ ({ className, orientation = 'vertical', ...props }, ref) => {
76
+ // Field 整合 a11y(2026-07-17 d10i1):role="group" 的 <div> 無法被 <FieldLabel htmlFor> 命名 →
77
+ // 自讀 fieldCtx 接 aria-labelledby / aria-describedby / aria-invalid,讓群組在 accessibility tree
78
+ // 有名稱、描述與錯誤狀態(對齊 RadioGroup Root 的 fieldCtx 接線,radio-group.tsx L164-166)。
79
+ // standalone(無 Field)時皆 undefined,consumer 經 {...props} 傳的 aria-* 照舊生效(置於 spread 前故可被覆蓋)。
80
+ const fieldCtx = useFieldContext()
81
+ return (
82
+ <CheckboxGroupContext.Provider value={CHECKBOX_GROUP_CTX_VALUE}>
83
+ <div
84
+ ref={ref}
85
+ role="group"
86
+ aria-labelledby={fieldCtx?.labelId}
87
+ aria-describedby={fieldCtx?.descriptionId}
88
+ aria-invalid={fieldCtx?.invalid || undefined}
89
+ className={cn(
90
+ // 垂直 CheckboxGroup:zero gap(間距由 SelectionItem py 獨家擁有,見 docblock canonical)
91
+ // 水平:短 label 並排需水平 gap-4(label 沒有 py 擴散,需要顯式 gap)
92
+ orientation === 'vertical' ? 'grid' : 'flex flex-wrap gap-4',
93
+ className
94
+ )}
95
+ {...props}
96
+ />
97
+ </CheckboxGroupContext.Provider>
98
+ )
99
+ }
89
100
  )
90
101
  CheckboxGroup.displayName = 'CheckboxGroup'
91
102
  // Field layout declaration:block primitive(多項堆疊)——進入 <Field> 時
@@ -297,8 +297,8 @@ const InspectorInner = () => {
297
297
  size={size}
298
298
  checked={checkedState === 'checked' ? true : checkedState === 'indeterminate' ? 'indeterminate' : false}
299
299
  disabled={interaction === 'disabled'}
300
- label="Label text"
301
- description={withDescription ? '次要說明文字,給使用者更多 context' : undefined}
300
+ label="接收每週專案摘要"
301
+ description={withDescription ? '每週一自動寄送專案進度與待辦事項摘要到你的信箱' : undefined}
302
302
  />
303
303
  ) : (
304
304
  <Checkbox
@@ -496,7 +496,7 @@ export const SizeMatrix = {
496
496
  <Td><Checkbox size={sz} defaultChecked /></Td>
497
497
  <Td>
498
498
  {/* 公開 API:`<Checkbox label>` 內部自動包 SelectionItem + wire id/htmlFor(selection-item.spec.md 禁裸用) */}
499
- <Checkbox size={sz} defaultChecked label="Label text" />
499
+ <Checkbox size={sz} defaultChecked label="接收通知" />
500
500
  </Td>
501
501
  </tr>
502
502
  ))}
@@ -232,6 +232,10 @@ Indeterminate 是由父層邏輯控制的狀態,Checkbox 本身不會自動進
232
232
  | disabled unchecked | transparent | bg-disabled | 無 |
233
233
  | disabled checked | transparent | bg-disabled | fg-disabled dot |
234
234
 
235
+ ### Loading(N/A)
236
+
237
+ Checkbox / Radio **無 Loading 狀態**——選擇控件是同步 toggle,值隨 form submit 才套用(見「定位」),不承載非同步進度。提交進行中的 loading 狀態由**表單的 submit `Button` loading state** 擁有;提交期間若需鎖定選項,將 Checkbox 設 `disabled` 即可,不在控件本體加 spinner。(對齊 Switch spec「無 loading state」——async 進度不屬選擇控件本體。)
238
+
235
239
  ---
236
240
 
237
241
  ## Controlled / Uncontrolled API(M26)
@@ -244,14 +248,18 @@ Indeterminate 是由父層邏輯控制的狀態,Checkbox 本身不會自動進
244
248
 
245
249
  ### `mode` prop(Field mode,正交於 size)
246
250
 
247
- `mode?: 'edit' | 'display' | 'readonly' | 'disabled'`(默認 inherit Field context 或 `'edit'`),對齊 `field-types.ts` FieldMode(完整 4-mode canonical SSOT → `Field/field-controls.spec.md`;本段鏡像 switch.spec.md 同名段):
251
+ `mode?: 'edit' | 'view' | 'readonly' | 'disabled'`(默認 inherit Field context 或 `'edit'`),對齊 `field-types.ts` FieldMode(完整 4-mode canonical SSOT → `Field/field-controls.spec.md`;本段鏡像 switch.spec.md 同名段):
248
252
  - `edit`(預設)— 可勾選的 Checkbox。
249
- - `display` — **純展示**:渲染 **Check icon(true=勾)/ X icon(false=叉)**,中性 `text-foreground` 色,非互動、無 input chrome,供 DataTable boolean cell 非編輯態共用。boolean 值符號 SSOT = `../SelectionControl/boolean-value.tsx`(勾/叉 icon + 中性色;M22 世界級:MUI X DataGrid Check/Close icon 中性色 / React-admin TrueIcon-FalseIcon;false 中性非紅 = 「否」值非「錯誤」)。
253
+ - `view` — **純展示**:渲染 **Check icon(true=勾)/ X icon(false=叉)**,中性 `text-foreground` 色,非互動、無 input chrome,供 DataTable boolean cell 非編輯態共用。boolean 值符號 SSOT = `../SelectionControl/boolean-value.tsx`(勾/叉 icon + 中性色;M22 世界級:MUI X DataGrid Check/Close icon 中性色 / React-admin TrueIcon-FalseIcon;false 中性非紅 = 「否」值非「錯誤」)。
250
254
  - `readonly` — **Field 內(無 inline label)**= `fieldWrapperStyles` readonly 灰框 + 勾/叉 icon(與 Input readonly 同一視覺語言,2026-06-12 user 拍板;世界級:Salesforce output ✓ glyph / SAP 靜態文字);**standalone / 有 inline label**(SelectionItem row)= 正常色鎖互動(同下方 Read-only)。
251
- - `disabled` — 落到真 disabled chrome(`effectiveDisabled`,2026-06-12 修:mode='disabled' 直傳〔如 DataTable disabled cell〕與 `disabled` prop 等效,降色 + 不可 focus)。
255
+ - `disabled` — 落到真 disabled chrome(`effectiveDisabled`,2026-06-12 修:mode='disabled' 直傳〔如 `<Field mode="disabled">` cascade〕與 `disabled` prop 等效,降色 + 不可 focus)。DataTable cell 無 disabled 態(已廢除);cell 非編輯態 = `view` 純展示。
252
256
 
253
257
  CheckboxGroup 是純 layout primitive — **不**持有 group-level selection state(無 `value` / `defaultValue` / `onValueChange`)。每個 `<Checkbox>` child 各自管自己的 `checked` / `defaultChecked` / `onCheckedChange`;CheckboxGroup 只透過 `CheckboxGroupContext` 告知 child「你在 group 裡」(保留各自 label)。
254
258
 
259
+ ### 表單驗證接線(useFormValidation v1 邊界)
260
+
261
+ Checkbox 值走 `onCheckedChange`(非 value/onChange 型控件),接 `useFormValidation`(`../Field/form-validation.spec.md`)時用 **`setFieldValue` 自接、不經 `getInputProps`**。這是該 spec「v1 邊界」誠實記載的已知限制:**無自動 blur 驗證 / 邊改邊清 error**(該層 timing 只覆蓋 value/onChange 型控件)—— Checkbox 值變更後由 consumer 於 `setFieldValue` 後自行 re-validate,或隨 submit 全驗。**error 視覺不受此限制影響**:`Field invalid` context 是 engine-agnostic 的,紅框 + `FieldError` 照常呈現;`<Field>` 錯誤群組化見 field.spec.md。
262
+
255
263
  ---
256
264
 
257
265
  ## 群組模式(CheckboxGroup)
@@ -336,6 +344,7 @@ Horizontal 需 `gap-4` 因 row 的 py 不擴散到左右。
336
344
  - `../RadioGroup/radio-group.spec.md` — Radio 的 group 容器 + 結構對稱 reciprocal
337
345
  - `../SelectionControl/selection-item.spec.md` — Checkbox / Radio 共用的 SelectionItem 佈局 primitive(本 spec 的 Clamp 政策為其 SSOT)
338
346
  - `../Field/field-controls.spec.md` — Field Control 共用規則
347
+ - `../Field/form-validation.spec.md` — 表單驗證引擎;Checkbox 用 `setFieldValue` 自接的 v1 邊界見「Controlled / Uncontrolled API → 表單驗證接線」
339
348
 
340
349
  ## A11y 預設
341
350
 
@@ -13,7 +13,7 @@ const meta: Meta<typeof Checkbox> = {
13
13
  export default meta
14
14
  type Story = StoryObj<typeof Checkbox>
15
15
 
16
- // @story-trait-rationale: pre-existing trait gaps tracked separately; this PR scope = add Modes story with display card.
16
+ // @story-trait-rationale: pre-existing trait gaps tracked separately; this PR scope = add Modes story with view card.
17
17
  /* ── 四模式 ── */
18
18
  export const Modes: Story = {
19
19
  name: '四模式',
@@ -24,9 +24,9 @@ export const Modes: Story = {
24
24
  <Checkbox defaultChecked aria-label="同意條款(edit mode demo)" />
25
25
  </div>
26
26
  <div>
27
- <h3 className="text-body font-bold text-foreground mb-2">display</h3>
28
- <Checkbox mode="display" checked />
29
- <p className="text-caption text-fg-muted mt-1">純視覺 glyph(勾/叉 icon,Check/X);語意由 context(如 DataTable 表頭 + 行標籤)提供,display 不暴露獨立 aria-label。需螢幕報讀器可讀的勾選請用 edit / readonly 模式。</p>
27
+ <h3 className="text-body font-bold text-foreground mb-2">view</h3>
28
+ <Checkbox mode="view" checked />
29
+ <p className="text-caption text-fg-muted mt-1">純視覺 glyph(勾/叉 icon,Check/X);語意由 context(如 DataTable 表頭 + 行標籤)提供,view 不暴露獨立 aria-label。需螢幕報讀器可讀的勾選請用 edit / readonly 模式。</p>
30
30
  </div>
31
31
  <div>
32
32
  <h3 className="text-body font-bold text-foreground mb-2">readonly</h3>
@@ -7,6 +7,7 @@ import { cva, type VariantProps } from "class-variance-authority"
7
7
  import { cn } from "@/lib/utils"
8
8
  import type { FieldMode, FieldVariant } from "@/design-system/components/Field/field-types"
9
9
  import { useFieldContext, useResolvedFieldDisabled, useResolvedFieldMode, useResolvedFieldSize } from "@/design-system/components/Field/field-context"
10
+ import { useControllable } from "@/design-system/hooks/use-controllable"
10
11
  import { fieldWrapperStyles } from "@/design-system/components/Field/field-wrapper"
11
12
  import { SelectionItem } from "@/design-system/components/SelectionControl/selection-item"
12
13
  import { BooleanValueIcon } from "@/design-system/components/SelectionControl/boolean-value"
@@ -80,8 +81,13 @@ const checkStrokeWidth: Record<string, number> = { sm: 3, md: 3, lg: 2.5 }
80
81
 
81
82
  // ── Types ───────────────────────────────────────────────────────────────────
82
83
 
84
+ // asChild / children 從公開型別 Omit(2026-07-17 d9i6):Checkbox 有**固定 anatomy** —
85
+ // 內部恆渲染 Indicator(Check / Minus 兩子節點),非 Slot host。若保留 Radix `asChild`,
86
+ // consumer 傳入的元素會被 Radix Slot `React.Children.only` 因多子節點在執行期拋錯(失敗記憶索引
87
+ // 已 codify 此 asChild 多子節點 bug class);`children` 同理會被固定 Indicator 覆蓋。需自訂佈局
88
+ // 請用 `label` / `description` / `icon` / `avatar` 一級 props(內部自動包 SelectionItem)。
83
89
  export interface CheckboxProps
84
- extends React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>,
90
+ extends Omit<React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>, 'asChild' | 'children'>,
85
91
  VariantProps<typeof checkboxVariants> {
86
92
  /**
87
93
  * Inline label。提供時 Checkbox 自動透過 SelectionItem 包裝,
@@ -111,7 +117,7 @@ export interface CheckboxProps
111
117
  /**
112
118
  * Field mode(2026-05-05 Phase B3 align):
113
119
  * edit — 一般可互動 checkbox(預設)
114
- * display — **純展示**:渲染 Check / X icon(true=勾 / false=叉,無互動 primitive、無 input chrome);
120
+ * view — **純展示**:渲染 Check / X icon(true=勾 / false=叉,無互動 primitive、無 input chrome);
115
121
  * 對齊 Carbon read-only / DataTable boolean cell 場景。取代既有 BooleanDisplay。
116
122
  * readonly — 同 readOnly prop:checkbox 視覺保留 + 鎖互動 + a11y readonly signal
117
123
  * disabled — 同 disabled prop:降色 + 鎖互動
@@ -143,6 +149,13 @@ const Checkbox = React.forwardRef<
143
149
  mode,
144
150
  // chrome 對 Checkbox 主體無視覺影響(無 input wrapper)— 接收純為 prop 一致性;destructure 防 leak 到 DOM。
145
151
  variant: _chrome,
152
+ // controllable checked triplet(2026-07-17 d26i0):於 wrapper 層取出,交由 useControllable 持有
153
+ // resolved 值,供 edit / view / readonly 三分支共用。原本 view / readonly 分支各自讀 raw prop →
154
+ // uncontrolled(只傳 defaultChecked)在 edit 態 toggle 後切 mode 會丟失目前值(Radix 內部狀態
155
+ // wrapper 讀不到)。移出 props 後 edit 主路徑改由 checked={resolvedChecked} 受控(見下方 rootEl)。
156
+ checked: checkedProp,
157
+ defaultChecked: defaultCheckedProp,
158
+ onCheckedChange,
146
159
  id: idProp,
147
160
  ...props
148
161
  },
@@ -154,7 +167,7 @@ const Checkbox = React.forwardRef<
154
167
 
155
168
  // Field context:Checkbox 單獨塞進 Field(binary toggle)時,忽略自己的 label 讓 FieldLabel 接管
156
169
  // 2026-05-31 #35:hooks(useFieldContext / useContext / useId)必在任何 conditional return 前呼叫(Rules of Hooks)。
157
- // 原 mode='display' early return 寫在 hooks 之上 → runtime 切 mode 會 hook count 不一致 crash;已下移至 hooks 後。
170
+ // 原 mode='view' early return 寫在 hooks 之上 → runtime 切 mode 會 hook count 不一致 crash;已下移至 hooks 後。
158
171
  //
159
172
  // **例外**:Checkbox 是 CheckboxGroup 的 child 時(multi-select 情境),**每個 checkbox
160
173
  // 的 label 是它自己的選項名**,FieldLabel 只是群組名稱 — 此時 label **必須保留**,
@@ -181,7 +194,7 @@ const Checkbox = React.forwardRef<
181
194
  const generatedId = React.useId()
182
195
  const inputId = idProp ?? (insideGroup ? generatedId : (fieldCtx?.id ?? generatedId))
183
196
 
184
- // 2026-06-08 SSOT cascade:disabled + mode 經 resolver hook(原 raw prop → <Field disabled>/<Field mode="display"> 漏 cascade)
197
+ // 2026-06-08 SSOT cascade:disabled + mode 經 resolver hook(原 raw prop → <Field disabled>/<Field mode="view"> 漏 cascade)
185
198
  const resolvedDisabled = useResolvedFieldDisabled(disabled)
186
199
  const resolvedMode = useResolvedFieldMode({ mode, disabled, readOnly })
187
200
  const effectiveReadOnly = readOnly || resolvedMode === 'readonly'
@@ -192,36 +205,74 @@ const Checkbox = React.forwardRef<
192
205
  // readonly 灰框 size:走 SSOT resolver(prop > ctx > 'md',field-context.ts:150-161)
193
206
  const resolvedBoxSize = useResolvedFieldSize(size ?? undefined, 'md') as 'sm' | 'md' | 'lg'
194
207
 
195
- // 2026-07-04 修:display / readonly-in-Field 分支的裸 span/div 原丟棄剩餘 props
208
+ // 2026-07-04 修:view / readonly-in-Field 分支的裸 span/div 原丟棄剩餘 props
196
209
  // (id/data-*/aria-*)且不轉發 forwardRef ref → consumer 的 aria-label 等靜默失效。
197
- // 抽出 Radix 專屬 non-DOM props,其餘 DOM props 於兩分支 spread 轉發(與 Switch/
198
- // RadioGroup 同修);edit 主路徑不受影響(rootEl 仍 spread 完整 props)。
210
+ // 抽出 Radix 專屬 form props(required/name/value 不可落到裸 span/div),其餘 DOM props 於兩分支
211
+ // spread 轉發(與 Switch/RadioGroup 同修);edit 主路徑不受影響(rootEl 仍 spread 完整 props)。
212
+ // (checked/defaultChecked/onCheckedChange 已於上方 top-level destructure 取出交 useControllable;
213
+ // asChild/children 已從 CheckboxProps 型別 Omit — 固定 anatomy 非 Slot host,見型別註解。)
199
214
  const {
200
- checked: checkedProp,
201
- defaultChecked: defaultCheckedProp,
202
- onCheckedChange: _onCheckedChange,
203
215
  required: _required,
204
216
  name: _name,
205
217
  value: _value,
206
- asChild: _asChild,
207
- children: _children,
208
218
  ...restDomProps
209
219
  } = props
210
220
 
211
- // ── mode='display'(下移至所有 hooks 之後,per #35 Rules of Hooks)──────────
221
+ // ── Resolved checked SSOT(controlled / uncontrolled dual-mode,2026-07-17 d26i0)──────────
222
+ // 對齊 Input / Textarea 既有 canonical(field-controls.spec.md「有值判定」:useControllable 內部
223
+ // resolved value + form.reset bridge)。修 view / readonly 分支原各自讀 raw prop 導致「uncontrolled
224
+ // 切 mode 丟失目前值」的 dual-mode 缺口。controlled(傳 checked)時 useControllable 純 passthrough,
225
+ // edit 行為 Δ=0。
226
+ const isControlled = checkedProp !== undefined
227
+ const [resolvedChecked, setResolvedChecked] = useControllable<boolean | 'indeterminate'>({
228
+ value: checkedProp,
229
+ defaultValue: defaultCheckedProp ?? false,
230
+ })
231
+
232
+ // form.reset() bridge(uncontrolled only):wrapper 取得 controllable 主導後,HTML 標準 reset 不發
233
+ // 變更事件 → 手動把 resolvedChecked 歸位 defaultChecked(對齊 input.tsx 同名 bridge)。keyed on
234
+ // resolvedMode:切回 edit 重掛 native Root 時 effect 重跑補掛 listener;view / readonly(Root
235
+ // unmount)時 innerRef 為 null → early return 不掛。
236
+ const innerRef = React.useRef<HTMLButtonElement | null>(null)
237
+ React.useEffect(() => {
238
+ if (isControlled) return
239
+ const form = innerRef.current?.form
240
+ if (!form) return
241
+ const handleReset = () => setResolvedChecked(defaultCheckedProp ?? false)
242
+ form.addEventListener('reset', handleReset)
243
+ return () => form.removeEventListener('reset', handleReset)
244
+ }, [isControlled, resolvedMode, defaultCheckedProp, setResolvedChecked])
245
+ // Merge refs(input.tsx setRef idiom):innerRef 供 reset bridge 取 form,同時轉發 forwardRef ref。
246
+ const setRef = React.useCallback((el: HTMLButtonElement | null) => {
247
+ innerRef.current = el
248
+ if (typeof ref === 'function') ref(el)
249
+ else if (ref) (ref as React.MutableRefObject<HTMLButtonElement | null>).current = el
250
+ }, [ref])
251
+
252
+ // ── mode='view'(下移至所有 hooks 之後,per #35 Rules of Hooks)──────────
212
253
  // 純展示模式:無互動 primitive、渲染 Check / X icon(true=勾 / false=叉,中性 foreground 色)。
213
254
  // boolean 值展示符號 SSOT = SelectionControl/boolean-value.tsx(勾/叉 icon + 中性色 + M22 世界級對照)。取代 BooleanDisplay。
214
- if (resolvedMode === 'display') {
215
- const isChecked = checkedProp === true
255
+ if (resolvedMode === 'view') {
256
+ const isChecked = resolvedChecked === true
257
+ // a11y(2026-07-17 d10i2):BooleanValueIcon 帶 aria-hidden → 裸 span 在 a11y tree 無名,
258
+ // 布林值對螢幕閱讀器完全消失。給 view 容器 role="img" + aria-label 承載值語意(對齊 MUI X
259
+ // DataGrid boolean cell 的 icon role/label 世界級做法);consumer 若自帶 aria-label 則優先。
260
+ const viewAriaLabel = (restDomProps as { 'aria-label'?: string })['aria-label'] ?? (isChecked ? '是' : '否')
216
261
  return (
217
- <span {...restDomProps} ref={ref as React.Ref<HTMLSpanElement>} className="inline-flex">
262
+ <span
263
+ {...restDomProps}
264
+ ref={ref as React.Ref<HTMLSpanElement>}
265
+ role="img"
266
+ aria-label={viewAriaLabel}
267
+ className="inline-flex"
268
+ >
218
269
  <BooleanValueIcon checked={isChecked} size={resolvedBoxSize} />
219
270
  </span>
220
271
  )
221
272
  }
222
273
 
223
274
  // ── mode='readonly' in Field(2026-06-12 拍板「灰框 + 勾/叉」;2026-07-09 ✓/— glyph → Check/X icon)─
224
- // Field 內 readonly boolean = readonly 灰框 chrome + display 同款值語言(勾/叉 icon)。
275
+ // Field 內 readonly boolean = readonly 灰框 chrome + view 同款值語言(勾/叉 icon)。
225
276
  // 灰框消費 fieldWrapperStyles 同一 cva = 與 Input readonly 字面同源(SSOT,改一處全動)。
226
277
  // 理由:同一張 readonly 表單裡文字控件有 bg-readonly 灰框鎖定訊號,boolean 保留全彩
227
278
  // 控件會誤導「仍可操作」(世界級 0/4 用原樣鎖互動:Salesforce=✓ 靜態 glyph /
@@ -229,7 +280,7 @@ const Checkbox = React.forwardRef<
229
280
  // Scope:僅 Field 內且無 inline label(FieldLabel 接管 label 的表單欄位場景);
230
281
  // standalone readOnly(settings list / SelectionItem row)維持原樣鎖互動不變。
231
282
  if (effectiveReadOnly && insideField && effectiveLabel == null) {
232
- const isChecked = (checkedProp ?? defaultCheckedProp) === true
283
+ const isChecked = resolvedChecked === true
233
284
  const boxSize = resolvedBoxSize
234
285
  return (
235
286
  <div
@@ -256,8 +307,17 @@ const Checkbox = React.forwardRef<
256
307
  const rootEl = (
257
308
  <CheckboxPrimitive.Root
258
309
  id={inputId}
259
- ref={ref}
310
+ ref={setRef}
260
311
  disabled={effectiveDisabled}
312
+ // controllable checked SSOT(2026-07-17 d26i0):Root 受 wrapper resolvedChecked 主導,
313
+ // onCheckedChange 先同步 wrapper 內部 state 再 forward consumer → uncontrolled(只傳
314
+ // defaultChecked)toggle 後切 mode(view / readonly)仍保留目前值。controlled(傳 checked)
315
+ // 時 useControllable 純 passthrough,edit 行為 Δ=0。
316
+ checked={resolvedChecked}
317
+ onCheckedChange={(next) => {
318
+ setResolvedChecked(next)
319
+ onCheckedChange?.(next)
320
+ }}
261
321
  aria-readonly={effectiveReadOnly || undefined}
262
322
  data-readonly={effectiveReadOnly || undefined}
263
323
  tabIndex={effectiveReadOnly ? -1 : undefined}