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

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 (481) 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 +19 -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 +8 -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 +10 -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/Empty/empty.spec.md +0 -1
  323. package/src/components/Field/field-context.ts +17 -11
  324. package/src/components/Field/field-controls.spec.md +46 -37
  325. package/src/components/Field/field-types.ts +14 -12
  326. package/src/components/Field/field-wrapper.tsx +59 -43
  327. package/src/components/Field/field.anatomy.stories.tsx +6 -6
  328. package/src/components/Field/field.principles.stories.tsx +5 -28
  329. package/src/components/Field/field.spec.md +5 -4
  330. package/src/components/Field/field.stories.tsx +103 -72
  331. package/src/components/Field/field.tsx +36 -4
  332. package/src/components/Field/form-validation.spec.md +4 -0
  333. package/src/components/Field/use-form-validation.ts +1 -1
  334. package/src/components/FieldControlGroup/field-control-group.principles.stories.tsx +3 -3
  335. package/src/components/FieldControlGroup/field-control-group.spec.md +3 -3
  336. package/src/components/FileItem/file-item.tsx +10 -8
  337. package/src/components/FileUpload/file-upload.principles.stories.tsx +7 -7
  338. package/src/components/FileUpload/file-upload.spec.md +3 -2
  339. package/src/components/FileUpload/file-upload.tsx +5 -1
  340. package/src/components/FileViewer/file-viewer.anatomy.stories.tsx +1 -1
  341. package/src/components/FileViewer/file-viewer.principles.stories.tsx +1 -1
  342. package/src/components/FileViewer/file-viewer.spec.md +1 -1
  343. package/src/components/FileViewer/file-viewer.stories.tsx +1 -1
  344. package/src/components/FileViewer/file-viewer.tsx +3 -2
  345. package/src/components/HoverCard/hover-card.anatomy.stories.tsx +6 -4
  346. package/src/components/HoverCard/hover-card.principles.stories.tsx +6 -1
  347. package/src/components/HoverCard/hover-card.spec.md +2 -2
  348. package/src/components/HoverCard/hover-card.tsx +4 -2
  349. package/src/components/InlineEdit/inline-edit.anatomy.stories.tsx +184 -0
  350. package/src/components/InlineEdit/inline-edit.principles.stories.tsx +142 -0
  351. package/src/components/InlineEdit/inline-edit.spec.md +71 -46
  352. package/src/components/InlineEdit/inline-edit.stories.tsx +20 -19
  353. package/src/components/InlineEdit/inline-edit.tsx +211 -125
  354. package/src/components/Input/input.anatomy.stories.tsx +15 -15
  355. package/src/components/Input/input.principles.stories.tsx +3 -3
  356. package/src/components/Input/input.spec.md +4 -4
  357. package/src/components/Input/input.stories.tsx +3 -3
  358. package/src/components/Input/input.tsx +13 -13
  359. package/src/components/LinkInput/link-input.anatomy.stories.tsx +2 -2
  360. package/src/components/LinkInput/link-input.principles.stories.tsx +1 -1
  361. package/src/components/LinkInput/link-input.spec.md +6 -2
  362. package/src/components/LinkInput/link-input.stories.tsx +8 -8
  363. package/src/components/LinkInput/link-input.tsx +21 -19
  364. package/src/components/Notice/notice.principles.stories.tsx +1 -1
  365. package/src/components/Notice/notice.tsx +8 -2
  366. package/src/components/NumberInput/number-input.anatomy.stories.tsx +6 -6
  367. package/src/components/NumberInput/number-input.principles.stories.tsx +4 -4
  368. package/src/components/NumberInput/number-input.spec.md +6 -6
  369. package/src/components/NumberInput/number-input.stories.tsx +4 -4
  370. package/src/components/NumberInput/number-input.tsx +17 -5
  371. package/src/components/OverflowIndicator/overflow-indicator.anatomy.stories.tsx +8 -4
  372. package/src/components/OverflowIndicator/overflow-indicator.principles.stories.tsx +6 -7
  373. package/src/components/OverflowIndicator/overflow-indicator.spec.md +1 -1
  374. package/src/components/OverflowIndicator/overflow-indicator.stories.tsx +6 -30
  375. package/src/components/PeoplePicker/avatar-stack-overflow.ts +1 -1
  376. package/src/components/PeoplePicker/people-picker.anatomy.stories.tsx +13 -10
  377. package/src/components/PeoplePicker/people-picker.principles.stories.tsx +5 -1
  378. package/src/components/PeoplePicker/people-picker.stories.tsx +5 -5
  379. package/src/components/PeoplePicker/people-picker.tsx +39 -20
  380. package/src/components/PeoplePicker/person-display.tsx +3 -3
  381. package/src/components/Popover/popover.anatomy.stories.tsx +10 -7
  382. package/src/components/Popover/popover.principles.stories.tsx +49 -28
  383. package/src/components/Popover/popover.spec.md +4 -2
  384. package/src/components/ProfileCard/profile-card.anatomy.stories.tsx +1 -1
  385. package/src/components/ProfileCard/profile-card.tsx +1 -1
  386. package/src/components/ProgressBar/progress-bar.anatomy.stories.tsx +14 -13
  387. package/src/components/ProgressBar/progress-bar.principles.stories.tsx +8 -23
  388. package/src/components/ProgressBar/progress-bar.stories.tsx +8 -2
  389. package/src/components/README.md +1 -0
  390. package/src/components/RadioGroup/radio-group.anatomy.stories.tsx +8 -3
  391. package/src/components/RadioGroup/radio-group.stories.tsx +3 -3
  392. package/src/components/RadioGroup/radio-group.tsx +19 -14
  393. package/src/components/Rating/rating.anatomy.stories.tsx +1 -1
  394. package/src/components/Rating/rating.principles.stories.tsx +3 -3
  395. package/src/components/Rating/rating.spec.md +2 -2
  396. package/src/components/Rating/rating.stories.tsx +4 -18
  397. package/src/components/Rating/rating.tsx +5 -1
  398. package/src/components/ScrollArea/scroll-area.principles.stories.tsx +1 -1
  399. package/src/components/ScrollArea/scroll-area.spec.md +1 -1
  400. package/src/components/ScrollArea/scroll-area.tsx +5 -2
  401. package/src/components/SegmentedControl/segmented-control.principles.stories.tsx +4 -1
  402. package/src/components/SegmentedControl/segmented-control.spec.md +5 -10
  403. package/src/components/SegmentedControl/segmented-control.tsx +6 -3
  404. package/src/components/Select/select.anatomy.stories.tsx +13 -7
  405. package/src/components/Select/select.principles.stories.tsx +3 -3
  406. package/src/components/Select/select.spec.md +10 -1
  407. package/src/components/Select/select.stories.tsx +41 -27
  408. package/src/components/Select/select.tsx +44 -26
  409. package/src/components/SelectMenu/select-menu.anatomy.stories.tsx +44 -26
  410. package/src/components/SelectMenu/select-menu.principles.stories.tsx +0 -1
  411. package/src/components/SelectMenu/select-menu.stories.tsx +7 -19
  412. package/src/components/SelectMenu/select-menu.tsx +38 -5
  413. package/src/components/SelectionControl/boolean-value.tsx +2 -2
  414. package/src/components/SelectionControl/selection-item.anatomy.stories.tsx +1 -1
  415. package/src/components/SelectionControl/selection-item.principles.stories.tsx +4 -4
  416. package/src/components/Separator/separator.principles.stories.tsx +1 -1
  417. package/src/components/Sheet/sheet.principles.stories.tsx +5 -3
  418. package/src/components/Sheet/sheet.tsx +4 -1
  419. package/src/components/Sidebar/sidebar.tsx +1 -1
  420. package/src/components/Slider/slider.anatomy.stories.tsx +5 -4
  421. package/src/components/Slider/slider.principles.stories.tsx +6 -6
  422. package/src/components/Slider/slider.spec.md +13 -1
  423. package/src/components/Slider/slider.stories.tsx +44 -32
  424. package/src/components/Slider/slider.tsx +13 -4
  425. package/src/components/Steps/steps.anatomy.stories.tsx +7 -7
  426. package/src/components/Steps/steps.principles.stories.tsx +1 -1
  427. package/src/components/Steps/steps.spec.md +4 -2
  428. package/src/components/Steps/steps.stories.tsx +4 -56
  429. package/src/components/Steps/steps.tsx +31 -7
  430. package/src/components/Switch/switch.anatomy.stories.tsx +3 -3
  431. package/src/components/Switch/switch.principles.stories.tsx +1 -1
  432. package/src/components/Switch/switch.spec.md +4 -4
  433. package/src/components/Switch/switch.stories.tsx +3 -3
  434. package/src/components/Switch/switch.tsx +16 -10
  435. package/src/components/Tabs/tabs.anatomy.stories.tsx +6 -0
  436. package/src/components/Tabs/tabs.principles.stories.tsx +10 -2
  437. package/src/components/Tabs/tabs.spec.md +5 -2
  438. package/src/components/Tabs/tabs.stories.tsx +36 -44
  439. package/src/components/Tabs/tabs.tsx +101 -36
  440. package/src/components/Tag/tag.anatomy.stories.tsx +2 -2
  441. package/src/components/Tag/tag.principles.stories.tsx +4 -4
  442. package/src/components/Tag/tag.spec.md +4 -1
  443. package/src/components/Tag/tag.tsx +5 -3
  444. package/src/components/Textarea/textarea.anatomy.stories.tsx +6 -6
  445. package/src/components/Textarea/textarea.spec.md +5 -5
  446. package/src/components/Textarea/textarea.tsx +24 -24
  447. package/src/components/TimePicker/time-picker.anatomy.stories.tsx +12 -11
  448. package/src/components/TimePicker/time-picker.principles.stories.tsx +37 -44
  449. package/src/components/TimePicker/time-picker.spec.md +6 -5
  450. package/src/components/TimePicker/time-picker.stories.tsx +3 -3
  451. package/src/components/TimePicker/time-picker.tsx +24 -14
  452. package/src/components/Toast/toast.principles.stories.tsx +1 -1
  453. package/src/components/Toast/toast.stories.tsx +1 -1
  454. package/src/components/Tooltip/tooltip.anatomy.stories.tsx +15 -6
  455. package/src/components/Tooltip/tooltip.principles.stories.tsx +4 -4
  456. package/src/components/Tooltip/tooltip.spec.md +9 -3
  457. package/src/components/Tooltip/tooltip.stories.tsx +1 -1
  458. package/src/components/Tooltip/tooltip.tsx +12 -2
  459. package/src/components/TreeView/tree-view.anatomy.stories.tsx +16 -14
  460. package/src/components/TreeView/tree-view.principles.stories.tsx +44 -11
  461. package/src/components/TreeView/tree-view.spec.md +8 -6
  462. package/src/components/TreeView/tree-view.stories.tsx +9 -43
  463. package/src/components/TreeView/tree-view.tsx +36 -0
  464. package/src/index.ts +13 -10
  465. package/src/lib/drag-visual.ts +2 -2
  466. package/src/lib/utils.ts +19 -0
  467. package/src/patterns/action-bar/action-bar.spec.md +1 -0
  468. package/src/patterns/element-anatomy/inline-action.spec.md +1 -1
  469. package/src/patterns/element-anatomy/item-anatomy.spec.md +4 -0
  470. package/src/patterns/element-anatomy/item-anatomy.tsx +4 -2
  471. package/src/patterns/header-canonical/header-canonical.spec.md +9 -0
  472. package/src/patterns/header-canonical/header-canonical.stories.tsx +31 -5
  473. package/src/tokens/categorical-color.ts +5 -5
  474. package/src/tokens/color/color.spec.md +4 -4
  475. package/src/tokens/color/primitives.css +9 -2
  476. package/src/tokens/motion/motion.spec.md +6 -7
  477. package/src/tokens/opacity/opacity.spec.md +1 -1
  478. package/src/tokens/orphan-tokens.spec.md +1 -1
  479. package/src/tokens/typography/typography.spec.md +2 -0
  480. package/src/tokens/uiSize/uiSize.css +5 -0
  481. package/src/tokens/uiSize/uiSize.spec.md +1 -1
@@ -112,8 +112,9 @@ const Carousel = React.forwardRef(
112
112
  }
113
113
  );
114
114
  Carousel.displayName = "Carousel";
115
- const CarouselContent = React.forwardRef(({ className, ...props }, ref) => {
115
+ const CarouselContent = React.forwardRef(({ className, children, ...props }, ref) => {
116
116
  const { carouselRef, orientation } = useCarousel();
117
+ const total = React.Children.count(children);
117
118
  return /* @__PURE__ */ jsx("div", { ref: carouselRef, className: "overflow-hidden", children: /* @__PURE__ */ jsx(
118
119
  "div",
119
120
  {
@@ -123,28 +124,36 @@ const CarouselContent = React.forwardRef(({ className, ...props }, ref) => {
123
124
  orientation === "horizontal" ? "-ml-4" : "-mt-4 flex-col",
124
125
  className
125
126
  ),
126
- ...props
127
+ ...props,
128
+ children: React.Children.map(
129
+ children,
130
+ (child, index) => React.isValidElement(child) ? React.cloneElement(child, { _slideIndex: index, _slideTotal: total }) : child
131
+ )
127
132
  }
128
133
  ) });
129
134
  });
130
135
  CarouselContent.displayName = "CarouselContent";
131
- const CarouselItem = React.forwardRef(({ className, ...props }, ref) => {
132
- const { orientation } = useCarousel();
133
- return /* @__PURE__ */ jsx(
134
- "div",
135
- {
136
- ref,
137
- role: "group",
138
- "aria-roledescription": "slide",
139
- className: cn(
140
- "min-w-0 shrink-0 grow-0 basis-full",
141
- orientation === "horizontal" ? "pl-4" : "pt-4",
142
- className
143
- ),
144
- ...props
145
- }
146
- );
147
- });
136
+ const CarouselItem = React.forwardRef(
137
+ ({ className, _slideIndex, _slideTotal, "aria-label": ariaLabel, ...props }, ref) => {
138
+ const { orientation } = useCarousel();
139
+ const autoLabel = ariaLabel ?? (_slideIndex != null && _slideTotal != null ? `第 ${_slideIndex + 1} 張,共 ${_slideTotal} 張` : void 0);
140
+ return /* @__PURE__ */ jsx(
141
+ "div",
142
+ {
143
+ ref,
144
+ role: "group",
145
+ "aria-roledescription": "slide",
146
+ "aria-label": autoLabel,
147
+ className: cn(
148
+ "min-w-0 shrink-0 grow-0 basis-full",
149
+ orientation === "horizontal" ? "pl-4" : "pt-4",
150
+ className
151
+ ),
152
+ ...props
153
+ }
154
+ );
155
+ }
156
+ );
148
157
  CarouselItem.displayName = "CarouselItem";
149
158
  const arrowWrapperClass = cn(
150
159
  "absolute z-10",
@@ -156,8 +165,13 @@ const arrowWrapperClass = cn(
156
165
  const CarouselPrevious = React.forwardRef(
157
166
  ({
158
167
  className,
159
- "aria-label": ariaLabel = "上一張"
160
- /* i18n-allow: DS default; consumer override via aria-label prop */
168
+ wrapperClassName,
169
+ onClick,
170
+ disabled,
171
+ variant = "tertiary",
172
+ size = "md",
173
+ "aria-label": ariaLabel = "上一張",
174
+ ...props
161
175
  }, ref) => {
162
176
  const { orientation, scrollPrev, canScrollPrev } = useCarousel();
163
177
  return /* @__PURE__ */ jsx(
@@ -166,20 +180,24 @@ const CarouselPrevious = React.forwardRef(
166
180
  className: cn(
167
181
  arrowWrapperClass,
168
182
  orientation === "horizontal" ? "left-3 top-1/2 -translate-y-1/2" : "top-3 left-1/2 -translate-x-1/2 rotate-90",
169
- className
183
+ wrapperClassName
170
184
  ),
171
185
  children: /* @__PURE__ */ jsx(
172
186
  Button,
173
187
  {
174
188
  ref,
175
- variant: "tertiary",
176
- size: "md",
177
- iconOnly: true,
189
+ variant,
190
+ size,
178
191
  startIcon: ChevronLeft,
179
192
  "aria-label": ariaLabel,
180
- disabled: !canScrollPrev,
181
- onClick: scrollPrev,
182
- className: "rounded-full"
193
+ ...props,
194
+ iconOnly: true,
195
+ disabled: !canScrollPrev || disabled,
196
+ onClick: (e) => {
197
+ scrollPrev();
198
+ onClick == null ? void 0 : onClick(e);
199
+ },
200
+ className: cn("rounded-full", className)
183
201
  }
184
202
  )
185
203
  }
@@ -190,8 +208,13 @@ CarouselPrevious.displayName = "CarouselPrevious";
190
208
  const CarouselNext = React.forwardRef(
191
209
  ({
192
210
  className,
193
- "aria-label": ariaLabel = "下一張"
194
- /* i18n-allow: DS default; consumer override via aria-label prop */
211
+ wrapperClassName,
212
+ onClick,
213
+ disabled,
214
+ variant = "tertiary",
215
+ size = "md",
216
+ "aria-label": ariaLabel = "下一張",
217
+ ...props
195
218
  }, ref) => {
196
219
  const { orientation, scrollNext, canScrollNext } = useCarousel();
197
220
  return /* @__PURE__ */ jsx(
@@ -200,20 +223,24 @@ const CarouselNext = React.forwardRef(
200
223
  className: cn(
201
224
  arrowWrapperClass,
202
225
  orientation === "horizontal" ? "right-3 top-1/2 -translate-y-1/2" : "bottom-3 left-1/2 -translate-x-1/2 rotate-90",
203
- className
226
+ wrapperClassName
204
227
  ),
205
228
  children: /* @__PURE__ */ jsx(
206
229
  Button,
207
230
  {
208
231
  ref,
209
- variant: "tertiary",
210
- size: "md",
211
- iconOnly: true,
232
+ variant,
233
+ size,
212
234
  startIcon: ChevronRight,
213
235
  "aria-label": ariaLabel,
214
- disabled: !canScrollNext,
215
- onClick: scrollNext,
216
- className: "rounded-full"
236
+ ...props,
237
+ iconOnly: true,
238
+ disabled: !canScrollNext || disabled,
239
+ onClick: (e) => {
240
+ scrollNext();
241
+ onClick == null ? void 0 : onClick(e);
242
+ },
243
+ className: cn("rounded-full", className)
217
244
  }
218
245
  )
219
246
  }
@@ -1 +1 @@
1
- {"version":3,"file":"carousel.js","sources":["../../../src/components/Carousel/carousel.tsx"],"sourcesContent":["// @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.\nimport * as React from 'react'\nimport useEmblaCarousel, { type UseEmblaCarouselType } from 'embla-carousel-react'\nimport { ChevronLeft, ChevronRight } from 'lucide-react'\nimport { cn } from '@/lib/utils'\nimport { Button } from '@/design-system/components/Button/button'\n\n/**\n * Carousel — 圖片 / 內容水平(或垂直)輪播\n *\n * 實作基礎:shadcn `Carousel` 結構 + `embla-carousel-react` v8 engine + 本 DS token。\n *\n * ── 世界級對照 ──\n * shadcn Carousel(本元件主要參考)/ Ant Carousel / Polaris 無 /\n * Swiper(獨立 lib,功能更多但不在 DS 範疇)\n *\n * ── 視覺慣例(user 指示) ──\n * 預設「hover 上去」左右兩邊才出現 prev/next 按鈕,不打擾主視覺;\n * 指示器(dots)在底部中央,clickable。\n *\n * ── API(shadcn parity) ──\n * <Carousel opts plugins orientation>\n * <CarouselContent>\n * <CarouselItem>...</CarouselItem>\n * <CarouselItem>...</CarouselItem>\n * </CarouselContent>\n * <CarouselPrevious /> ← 左箭頭\n * <CarouselNext /> ← 右箭頭\n * <CarouselDots /> ← 本 DS 擴充(shadcn 無,Ant/Swiper 慣例)\n * </Carousel>\n */\n\ntype CarouselApi = UseEmblaCarouselType[1]\ntype UseCarouselParameters = Parameters<typeof useEmblaCarousel>\ntype CarouselOptions = UseCarouselParameters[0]\ntype CarouselPlugin = UseCarouselParameters[1]\n\ninterface CarouselProps {\n opts?: CarouselOptions\n plugins?: CarouselPlugin\n orientation?: 'horizontal' | 'vertical'\n setApi?: (api: CarouselApi) => void\n}\n\ninterface CarouselContextProps extends CarouselProps {\n carouselRef: ReturnType<typeof useEmblaCarousel>[0]\n api: ReturnType<typeof useEmblaCarousel>[1]\n scrollPrev: () => void\n scrollNext: () => void\n scrollTo: (i: number) => void\n canScrollPrev: boolean\n canScrollNext: boolean\n selectedIndex: number\n scrollSnaps: number[]\n orientation: 'horizontal' | 'vertical'\n}\n\nconst CarouselContext = React.createContext<CarouselContextProps | null>(null)\n\nfunction useCarousel() {\n const ctx = React.useContext(CarouselContext)\n if (!ctx) throw new Error('useCarousel 必須在 <Carousel> 內使用')\n return ctx\n}\n\n// ── Root ────────────────────────────────────────────────────────────────────\n\nconst Carousel = React.forwardRef<\n HTMLDivElement,\n React.HTMLAttributes<HTMLDivElement> & CarouselProps\n>(\n (\n {\n orientation = 'horizontal',\n opts,\n setApi,\n plugins,\n className,\n children,\n ...props\n },\n ref,\n ) => {\n const [carouselRef, api] = useEmblaCarousel(\n { ...opts, axis: orientation === 'horizontal' ? 'x' : 'y' },\n plugins,\n )\n const [canScrollPrev, setCanScrollPrev] = React.useState(false)\n const [canScrollNext, setCanScrollNext] = React.useState(false)\n const [selectedIndex, setSelectedIndex] = React.useState(0)\n const [scrollSnaps, setScrollSnaps] = React.useState<number[]>([])\n\n const onSelect = React.useCallback((a: CarouselApi) => {\n if (!a) return\n setCanScrollPrev(a.canScrollPrev())\n setCanScrollNext(a.canScrollNext())\n setSelectedIndex(a.selectedScrollSnap())\n }, [])\n\n // reInit(動態 slide 增減 / options 變更)必刷新 scrollSnaps —— onSelect 只讀 select\n // 狀態不含 scrollSnapList;若不在此補,dots 數量 stale(舊 dots 殘留 / 縮減不更新)。\n const onReInit = React.useCallback((a: CarouselApi) => {\n if (!a) return\n setScrollSnaps(a.scrollSnapList())\n onSelect(a)\n }, [onSelect])\n\n const scrollPrev = React.useCallback(() => api?.scrollPrev(), [api])\n const scrollNext = React.useCallback(() => api?.scrollNext(), [api])\n const scrollTo = React.useCallback((i: number) => api?.scrollTo(i), [api])\n\n React.useEffect(() => {\n if (!api || !setApi) return\n setApi(api)\n }, [api, setApi])\n\n React.useEffect(() => {\n if (!api) return\n setScrollSnaps(api.scrollSnapList())\n onSelect(api)\n api.on('reInit', onReInit)\n api.on('select', onSelect)\n return () => {\n api?.off('select', onSelect)\n api?.off('reInit', onReInit) // D3 fix: previously leaked — stale closure on remount\n }\n }, [api, onSelect, onReInit])\n\n const handleKeyDown = (e: React.KeyboardEvent<HTMLDivElement>) => {\n // 鍵盤方向對齊內容捲動方向(APG 建議):horizontal → ←/→;vertical → ↑/↓\n const prevKey = orientation === 'horizontal' ? 'ArrowLeft' : 'ArrowUp'\n const nextKey = orientation === 'horizontal' ? 'ArrowRight' : 'ArrowDown'\n if (e.key === prevKey) { e.preventDefault(); scrollPrev() }\n else if (e.key === nextKey) { e.preventDefault(); scrollNext() }\n }\n\n const contextValue = React.useMemo(\n () => ({\n carouselRef,\n api,\n opts,\n orientation,\n scrollPrev,\n scrollNext,\n scrollTo,\n canScrollPrev,\n canScrollNext,\n selectedIndex,\n scrollSnaps,\n }),\n [\n carouselRef,\n api,\n opts,\n orientation,\n scrollPrev,\n scrollNext,\n scrollTo,\n canScrollPrev,\n canScrollNext,\n selectedIndex,\n scrollSnaps,\n ]\n )\n\n return (\n <CarouselContext.Provider value={contextValue}>\n <div\n ref={ref}\n onKeyDownCapture={handleKeyDown}\n className={cn('group/carousel relative', className)}\n role=\"region\"\n aria-roledescription=\"carousel\"\n aria-label=\"輪播\"\n {...props}\n >\n {children}\n </div>\n </CarouselContext.Provider>\n )\n },\n)\nCarousel.displayName = 'Carousel'\n\n// ── Content / Item ──────────────────────────────────────────────────────────\n\nconst CarouselContent = React.forwardRef<\n HTMLDivElement,\n React.HTMLAttributes<HTMLDivElement>\n>(({ className, ...props }, ref) => {\n const { carouselRef, orientation } = useCarousel()\n return (\n <div ref={carouselRef} className=\"overflow-hidden\">\n <div\n ref={ref}\n className={cn(\n 'flex',\n orientation === 'horizontal' ? '-ml-4' : '-mt-4 flex-col',\n className,\n )}\n {...props}\n />\n </div>\n )\n})\nCarouselContent.displayName = 'CarouselContent'\n\nconst CarouselItem = React.forwardRef<\n HTMLDivElement,\n React.HTMLAttributes<HTMLDivElement>\n>(({ className, ...props }, ref) => {\n const { orientation } = useCarousel()\n return (\n <div\n ref={ref}\n role=\"group\"\n aria-roledescription=\"slide\"\n className={cn(\n 'min-w-0 shrink-0 grow-0 basis-full',\n orientation === 'horizontal' ? 'pl-4' : 'pt-4',\n className,\n )}\n {...props}\n />\n )\n})\nCarouselItem.displayName = 'CarouselItem'\n\n// ── Arrow buttons(hover 才顯示)────────────────────────────────────────────\n// 使用 DS Button (tertiary + iconOnly size md);hover-only 顯示由 wrapper 的\n// opacity transition 控制(Button 本身不負責)。此 wrapper 存在僅為絕對定位 +\n// hover/focus 可見性,不再覆寫 Button 的視覺 token。\n\ntype ArrowProps = {\n className?: string\n /** ARIA label. Override for i18n. Prev default: 「上一張」;Next default: 「下一張」 */\n 'aria-label'?: string\n}\n\nconst arrowWrapperClass = cn(\n 'absolute z-10',\n 'transition-opacity duration-200',\n 'opacity-0 group-hover/carousel:opacity-100',\n 'focus-within:opacity-100',\n '[&:has(button:disabled)]:opacity-0 [&:has(button:disabled)]:pointer-events-none',\n)\n\n// code-quality-allow: long-function — foundational composite main body — 拆 sub-fn 會複雜化 local state / ref / context binding\nconst CarouselPrevious = React.forwardRef<HTMLButtonElement, ArrowProps>(\n ({ className, 'aria-label': ariaLabel = '上一張' /* i18n-allow: DS default; consumer override via aria-label prop */ }, ref) => {\n const { orientation, scrollPrev, canScrollPrev } = useCarousel()\n return (\n <div\n className={cn(\n arrowWrapperClass,\n orientation === 'horizontal'\n ? 'left-3 top-1/2 -translate-y-1/2'\n : 'top-3 left-1/2 -translate-x-1/2 rotate-90',\n className,\n )}\n >\n <Button\n ref={ref}\n variant=\"tertiary\"\n size=\"md\"\n iconOnly\n startIcon={ChevronLeft}\n aria-label={ariaLabel}\n disabled={!canScrollPrev}\n onClick={scrollPrev}\n // documented exception:視覺取向的 media carousel 箭頭用 rounded-full 圓形,\n // 優於 DS default rounded-md。對齊 Instagram / Airbnb / Notion / Apple Photos\n // 世界級慣例 — media carousel 箭頭圓形減少視覺方塊感壓迫內容。spec「箭頭視覺規格」有明示。\n className=\"rounded-full\"\n />\n </div>\n )\n },\n)\nCarouselPrevious.displayName = 'CarouselPrevious'\n\n// code-quality-allow: long-function — 與 CarouselPrevious 結構對稱的 JSX-heavy arrow(方向/位置/邊界三態 className 組合),拆 helper 會破壞兩支對讀性\nconst CarouselNext = React.forwardRef<HTMLButtonElement, ArrowProps>(\n ({ className, 'aria-label': ariaLabel = '下一張' /* i18n-allow: DS default; consumer override via aria-label prop */ }, ref) => {\n const { orientation, scrollNext, canScrollNext } = useCarousel()\n return (\n <div\n className={cn(\n arrowWrapperClass,\n orientation === 'horizontal'\n ? 'right-3 top-1/2 -translate-y-1/2'\n : 'bottom-3 left-1/2 -translate-x-1/2 rotate-90',\n className,\n )}\n >\n <Button\n ref={ref}\n variant=\"tertiary\"\n size=\"md\"\n iconOnly\n startIcon={ChevronRight}\n aria-label={ariaLabel}\n disabled={!canScrollNext}\n onClick={scrollNext}\n // documented exception:同 Previous,媒體導向 carousel 箭頭圓形\n className=\"rounded-full\"\n />\n </div>\n )\n },\n)\nCarouselNext.displayName = 'CarouselNext'\n\n// ── Dots indicator(底部中央)───────────────────────────────────────────────\n\nconst CarouselDots = React.forwardRef<\n HTMLDivElement,\n React.HTMLAttributes<HTMLDivElement>\n>(({ className, ...props }, ref) => {\n const { scrollSnaps, selectedIndex, scrollTo } = useCarousel()\n if (scrollSnaps.length <= 1) return null\n return (\n <div\n ref={ref}\n className={cn(\n 'absolute bottom-3 left-1/2 -translate-x-1/2 z-10',\n 'flex items-center gap-1.5',\n className,\n )}\n // 2026-06-12 a11y(deep-audit R2 verdict b):APG grouped carousel 模型,非 tabs 模型。\n // APG:「picker buttons 收在 group 容器;現張的 picker button aria-disabled=true」\n // (https://www.w3.org/WAI/ARIA/apg/patterns/carousel/)+ Embla 官方 Accessibility plugin\n // 同採 plain button + label、無 tab roles(https://www.embla-carousel.com/docs/plugins/accessibility)。\n // 為何不用完整 tabs 模型(slide=tabpanel + dot aria-controls):\n // (1) Embla snap ≠ slide — basis-1/3 多張並排時 1 dot 控多張,tab↔tabpanel 1:1 不成立\n // (2) tabs 鍵盤契約(方向鍵移 tablist 內 focus)與根容器方向鍵=切張(本檔 handleKeyDown)衝突\n // (3) compound API 下 CarouselItem 不知 index,aria-controls id 接線需額外 registration\n // 舊實作 role=\"tablist\"/\"tab\" + aria-selected 是半套 tabs(無 aria-controls、slide 非 tabpanel)= 非法混血。\n // 現張標記用 aria-current(非 APG 原文的 aria-disabled)— M23 DS 既有 canonical 優先:\n // FileViewer filmstrip(file-viewer.tsx aria-current={active ? 'true' : undefined})同類\n // 「視覺項導航 + 現張標記」已 codify group + button + aria-current;Bootstrap 5 indicators 同 idiom。\n role=\"group\"\n aria-label=\"輪播指示器\" /* i18n-allow: DS default; 對齊同檔其他 aria-label 語言 */\n {...props}\n >\n {scrollSnaps.map((_, i) => (\n <button\n key={i}\n type=\"button\"\n // 現張 dot aria-current(告知 SR「你在這張」;仍可 focus / 點擊為 no-op)\n aria-current={i === selectedIndex ? 'true' : undefined}\n aria-label={`跳至第 ${i + 1} 張`}\n onClick={() => scrollTo(i)}\n className={cn(\n 'h-1.5 rounded-full transition-all',\n // 2026-06-11 a11y(R2;Phase B codex 修重疊):hit-area 垂直擴至 24px、水平 ±3px(dot 6px+gap 6px → 中心距 12px,±3 恰相切零重疊;再寬必互搶點擊)\n // — 視覺 dot 維持 6×6 不變,僅點擊目標擴大(anatomy story 原宣稱的 hit-area 機制補真)\n 'relative before:absolute before:-inset-y-[9px] before:-inset-x-[3px] before:content-[\"\"]',\n // Dots 疊在 media(image/video)之上,不是 token color 底——用 --on-emphasis 保持語義\n // 跟其他「於飽和色底上的淺色前景」一致\n 'bg-on-emphasis/60 hover:bg-on-emphasis/80',\n 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',\n i === selectedIndex ? 'w-6 bg-on-emphasis' : 'w-1.5',\n )}\n />\n ))}\n </div>\n )\n})\nCarouselDots.displayName = 'CarouselDots'\n\n// Story auto-compile metadata — Phase 1 mechanical migration(2026-04-24)\n// Phase 2 fill needed: purpose descriptions + when rationale + world-class refs\nexport const carouselMeta = {\n component: 'Carousel',\n family: null, // non-family composite / overlay / layout\n variants: {\n\n },\n sizes: {\n\n },\n // 'active' 移除 — 全檔無按壓視覺(prev/next 按壓屬內嵌 Button meta,同 Notice/Alert 排除慣例;\n // 2026-07-07 詞彙統一 DS-wide 交叉掃補修)。dot hover 為自有 hover 態(bg-on-emphasis alpha 變體)。\n states: ['default', 'hover', 'focus-visible', 'disabled'],\n tokens: {\n bg: [],\n fg: [],\n ring: ['ring-ring'],\n },\n} as const\n\nexport {\n Carousel,\n CarouselContent,\n CarouselItem,\n CarouselPrevious,\n CarouselNext,\n CarouselDots,\n type CarouselApi,\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"carousel.js","sources":["../../../src/components/Carousel/carousel.tsx"],"sourcesContent":["// @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.\nimport * as React from 'react'\nimport useEmblaCarousel, { type UseEmblaCarouselType } from 'embla-carousel-react'\nimport { ChevronLeft, ChevronRight } from 'lucide-react'\nimport { cn } from '@/lib/utils'\nimport { Button } from '@/design-system/components/Button/button'\n\n/**\n * Carousel — 圖片 / 內容水平(或垂直)輪播\n *\n * 實作基礎:shadcn `Carousel` 結構 + `embla-carousel-react` v8 engine + 本 DS token。\n *\n * ── 世界級對照 ──\n * shadcn Carousel(本元件主要參考)/ Ant Carousel / Polaris 無 /\n * Swiper(獨立 lib,功能更多但不在 DS 範疇)\n *\n * ── 視覺慣例(user 指示) ──\n * 預設「hover 上去」左右兩邊才出現 prev/next 按鈕,不打擾主視覺;\n * 指示器(dots)在底部中央,clickable。\n *\n * ── API(shadcn parity) ──\n * <Carousel opts plugins orientation>\n * <CarouselContent>\n * <CarouselItem>...</CarouselItem>\n * <CarouselItem>...</CarouselItem>\n * </CarouselContent>\n * <CarouselPrevious /> ← 左箭頭\n * <CarouselNext /> ← 右箭頭\n * <CarouselDots /> ← 本 DS 擴充(shadcn 無,Ant/Swiper 慣例)\n * </Carousel>\n */\n\ntype CarouselApi = UseEmblaCarouselType[1]\ntype UseCarouselParameters = Parameters<typeof useEmblaCarousel>\ntype CarouselOptions = UseCarouselParameters[0]\ntype CarouselPlugin = UseCarouselParameters[1]\n\ninterface CarouselProps {\n opts?: CarouselOptions\n plugins?: CarouselPlugin\n orientation?: 'horizontal' | 'vertical'\n setApi?: (api: CarouselApi) => void\n}\n\ninterface CarouselContextProps extends CarouselProps {\n carouselRef: ReturnType<typeof useEmblaCarousel>[0]\n api: ReturnType<typeof useEmblaCarousel>[1]\n scrollPrev: () => void\n scrollNext: () => void\n scrollTo: (i: number) => void\n canScrollPrev: boolean\n canScrollNext: boolean\n selectedIndex: number\n scrollSnaps: number[]\n orientation: 'horizontal' | 'vertical'\n}\n\nconst CarouselContext = React.createContext<CarouselContextProps | null>(null)\n\nfunction useCarousel() {\n const ctx = React.useContext(CarouselContext)\n if (!ctx) throw new Error('useCarousel 必須在 <Carousel> 內使用')\n return ctx\n}\n\n// ── Root ────────────────────────────────────────────────────────────────────\n\nconst Carousel = React.forwardRef<\n HTMLDivElement,\n React.HTMLAttributes<HTMLDivElement> & CarouselProps\n>(\n (\n {\n orientation = 'horizontal',\n opts,\n setApi,\n plugins,\n className,\n children,\n ...props\n },\n ref,\n ) => {\n const [carouselRef, api] = useEmblaCarousel(\n { ...opts, axis: orientation === 'horizontal' ? 'x' : 'y' },\n plugins,\n )\n const [canScrollPrev, setCanScrollPrev] = React.useState(false)\n const [canScrollNext, setCanScrollNext] = React.useState(false)\n const [selectedIndex, setSelectedIndex] = React.useState(0)\n const [scrollSnaps, setScrollSnaps] = React.useState<number[]>([])\n\n const onSelect = React.useCallback((a: CarouselApi) => {\n if (!a) return\n setCanScrollPrev(a.canScrollPrev())\n setCanScrollNext(a.canScrollNext())\n setSelectedIndex(a.selectedScrollSnap())\n }, [])\n\n // reInit(動態 slide 增減 / options 變更)必刷新 scrollSnaps —— onSelect 只讀 select\n // 狀態不含 scrollSnapList;若不在此補,dots 數量 stale(舊 dots 殘留 / 縮減不更新)。\n const onReInit = React.useCallback((a: CarouselApi) => {\n if (!a) return\n setScrollSnaps(a.scrollSnapList())\n onSelect(a)\n }, [onSelect])\n\n const scrollPrev = React.useCallback(() => api?.scrollPrev(), [api])\n const scrollNext = React.useCallback(() => api?.scrollNext(), [api])\n const scrollTo = React.useCallback((i: number) => api?.scrollTo(i), [api])\n\n React.useEffect(() => {\n if (!api || !setApi) return\n setApi(api)\n }, [api, setApi])\n\n React.useEffect(() => {\n if (!api) return\n setScrollSnaps(api.scrollSnapList())\n onSelect(api)\n api.on('reInit', onReInit)\n api.on('select', onSelect)\n return () => {\n api?.off('select', onSelect)\n api?.off('reInit', onReInit) // D3 fix: previously leaked — stale closure on remount\n }\n }, [api, onSelect, onReInit])\n\n const handleKeyDown = (e: React.KeyboardEvent<HTMLDivElement>) => {\n // 鍵盤方向對齊內容捲動方向(APG 建議):horizontal → ←/→;vertical → ↑/↓\n const prevKey = orientation === 'horizontal' ? 'ArrowLeft' : 'ArrowUp'\n const nextKey = orientation === 'horizontal' ? 'ArrowRight' : 'ArrowDown'\n if (e.key === prevKey) { e.preventDefault(); scrollPrev() }\n else if (e.key === nextKey) { e.preventDefault(); scrollNext() }\n }\n\n const contextValue = React.useMemo(\n () => ({\n carouselRef,\n api,\n opts,\n orientation,\n scrollPrev,\n scrollNext,\n scrollTo,\n canScrollPrev,\n canScrollNext,\n selectedIndex,\n scrollSnaps,\n }),\n [\n carouselRef,\n api,\n opts,\n orientation,\n scrollPrev,\n scrollNext,\n scrollTo,\n canScrollPrev,\n canScrollNext,\n selectedIndex,\n scrollSnaps,\n ]\n )\n\n return (\n <CarouselContext.Provider value={contextValue}>\n <div\n ref={ref}\n onKeyDownCapture={handleKeyDown}\n className={cn('group/carousel relative', className)}\n role=\"region\"\n aria-roledescription=\"carousel\"\n aria-label=\"輪播\"\n {...props}\n >\n {children}\n </div>\n </CarouselContext.Provider>\n )\n },\n)\nCarousel.displayName = 'Carousel'\n\n// ── Content / Item ──────────────────────────────────────────────────────────\n\nconst CarouselContent = React.forwardRef<\n HTMLDivElement,\n React.HTMLAttributes<HTMLDivElement>\n>(({ className, children, ...props }, ref) => {\n const { carouselRef, orientation } = useCarousel()\n // 2026-07-18 決策5:注入 slide index/total 給 CarouselItem 自動產生「第 N 張,共 M 張」a11y 位置名\n // (WAI-ARIA APG carousel「N of M」https://www.w3.org/WAI/ARIA/apg/patterns/carousel/)。count 取\n // React.Children.count(可靠,不依賴 embla init 後的 scrollSnaps)。\n const total = React.Children.count(children)\n return (\n <div ref={carouselRef} className=\"overflow-hidden\">\n <div\n ref={ref}\n className={cn(\n 'flex',\n orientation === 'horizontal' ? '-ml-4' : '-mt-4 flex-col',\n className,\n )}\n {...props}\n >\n {React.Children.map(children, (child, index) =>\n React.isValidElement(child)\n ? React.cloneElement(child as React.ReactElement<CarouselItemInternalProps>, { _slideIndex: index, _slideTotal: total })\n : child,\n )}\n </div>\n </div>\n )\n})\nCarouselContent.displayName = 'CarouselContent'\n\ninterface CarouselItemInternalProps extends React.HTMLAttributes<HTMLDivElement> {\n /** @internal CarouselContent 注入 —— 用於自動 slide 位置名。 */\n _slideIndex?: number\n _slideTotal?: number\n}\n\nconst CarouselItem = React.forwardRef<HTMLDivElement, CarouselItemInternalProps>(\n ({ className, _slideIndex, _slideTotal, 'aria-label': ariaLabel, ...props }, ref) => {\n const { orientation } = useCarousel()\n // 無 consumer aria-label 時自動位置名(第 N 張,共 M 張);consumer 傳 aria-label 優先(可自訂內容名)。\n const autoLabel =\n ariaLabel ?? (_slideIndex != null && _slideTotal != null ? `第 ${_slideIndex + 1} 張,共 ${_slideTotal} 張` : undefined)\n return (\n <div\n ref={ref}\n role=\"group\"\n aria-roledescription=\"slide\"\n aria-label={autoLabel}\n className={cn(\n 'min-w-0 shrink-0 grow-0 basis-full',\n orientation === 'horizontal' ? 'pl-4' : 'pt-4',\n className,\n )}\n {...props}\n />\n )\n },\n)\nCarouselItem.displayName = 'CarouselItem'\n\n// ── Arrow buttons(hover 才顯示)────────────────────────────────────────────\n// 使用 DS Button (tertiary + iconOnly size md);hover-only 顯示由 wrapper 的\n// opacity transition 控制(Button 本身不負責)。此 wrapper 存在僅為絕對定位 +\n// hover/focus 可見性,不再覆寫 Button 的視覺 token。\n\n// 2026-07-18 決策14(user 授權 B 案:開放完整 Button props + wrapperClassName):\n// 對齊 shadcn/Radix Carousel —— `className` 指**箭頭按鈕本身**(非定位 wrapper),\n// consumer 可換 variant/size、綁 analytics onClick、傳 data-* 等完整 Button props。\n// 定位層(絕對定位 + hover-only 顯示)的 className 走新 `wrapperClassName`。\n// **BREAKING**:`className` 語義由「定位 wrapper」改為「按鈕」(遷移:定位覆寫改傳 wrapperClassName)。\n// onClick/disabled 內部與 carousel 導覽 compose(scrollPrev/Next + 邊界 disable 保留,consumer 疊加)。\ntype ArrowProps = Omit<React.ComponentPropsWithoutRef<typeof Button>, 'iconOnly'> & {\n /** 定位層 wrapper 的 className(絕對定位 + hover-only 顯示層);箭頭按鈕樣式走 `className`。 */\n wrapperClassName?: string\n}\n\nconst arrowWrapperClass = cn(\n 'absolute z-10',\n 'transition-opacity duration-200',\n 'opacity-0 group-hover/carousel:opacity-100',\n 'focus-within:opacity-100',\n '[&:has(button:disabled)]:opacity-0 [&:has(button:disabled)]:pointer-events-none',\n)\n\n// code-quality-allow: long-function — foundational composite main body — 拆 sub-fn 會複雜化 local state / ref / context binding\nconst CarouselPrevious = React.forwardRef<HTMLButtonElement, ArrowProps>(\n (\n {\n className,\n wrapperClassName,\n onClick,\n disabled,\n variant = 'tertiary',\n size = 'md',\n 'aria-label': ariaLabel = '上一張' /* i18n-allow: DS default; consumer override via aria-label prop */,\n ...props\n },\n ref,\n ) => {\n const { orientation, scrollPrev, canScrollPrev } = useCarousel()\n return (\n <div\n className={cn(\n arrowWrapperClass,\n orientation === 'horizontal'\n ? 'left-3 top-1/2 -translate-y-1/2'\n : 'top-3 left-1/2 -translate-x-1/2 rotate-90',\n wrapperClassName,\n )}\n >\n <Button\n ref={ref}\n variant={variant}\n size={size}\n startIcon={ChevronLeft}\n aria-label={ariaLabel}\n {...props}\n iconOnly\n disabled={!canScrollPrev || disabled}\n onClick={(e) => { scrollPrev(); onClick?.(e) }}\n // documented exception:視覺取向的 media carousel 箭頭用 rounded-full 圓形,\n // 優於 DS default rounded-md。對齊 Instagram / Airbnb / Notion / Apple Photos\n // 世界級慣例 — media carousel 箭頭圓形減少視覺方塊感壓迫內容。spec「箭頭視覺規格」有明示。\n // consumer className 疊加在後可覆寫(決策14)。\n className={cn('rounded-full', className)}\n />\n </div>\n )\n },\n)\nCarouselPrevious.displayName = 'CarouselPrevious'\n\n// code-quality-allow: long-function — 與 CarouselPrevious 結構對稱的 JSX-heavy arrow(方向/位置/邊界三態 className 組合),拆 helper 會破壞兩支對讀性\nconst CarouselNext = React.forwardRef<HTMLButtonElement, ArrowProps>(\n (\n {\n className,\n wrapperClassName,\n onClick,\n disabled,\n variant = 'tertiary',\n size = 'md',\n 'aria-label': ariaLabel = '下一張' /* i18n-allow: DS default; consumer override via aria-label prop */,\n ...props\n },\n ref,\n ) => {\n const { orientation, scrollNext, canScrollNext } = useCarousel()\n return (\n <div\n className={cn(\n arrowWrapperClass,\n orientation === 'horizontal'\n ? 'right-3 top-1/2 -translate-y-1/2'\n : 'bottom-3 left-1/2 -translate-x-1/2 rotate-90',\n wrapperClassName,\n )}\n >\n <Button\n ref={ref}\n variant={variant}\n size={size}\n startIcon={ChevronRight}\n aria-label={ariaLabel}\n {...props}\n iconOnly\n disabled={!canScrollNext || disabled}\n onClick={(e) => { scrollNext(); onClick?.(e) }}\n // documented exception:同 Previous,媒體導向 carousel 箭頭圓形;consumer className 疊加可覆寫。\n className={cn('rounded-full', className)}\n />\n </div>\n )\n },\n)\nCarouselNext.displayName = 'CarouselNext'\n\n// ── Dots indicator(底部中央)───────────────────────────────────────────────\n\nconst CarouselDots = React.forwardRef<\n HTMLDivElement,\n React.HTMLAttributes<HTMLDivElement>\n>(({ className, ...props }, ref) => {\n const { scrollSnaps, selectedIndex, scrollTo } = useCarousel()\n if (scrollSnaps.length <= 1) return null\n return (\n <div\n ref={ref}\n className={cn(\n 'absolute bottom-3 left-1/2 -translate-x-1/2 z-10',\n 'flex items-center gap-1.5',\n className,\n )}\n // 2026-06-12 a11y(deep-audit R2 verdict b):APG grouped carousel 模型,非 tabs 模型。\n // APG:「picker buttons 收在 group 容器;現張的 picker button aria-disabled=true」\n // (https://www.w3.org/WAI/ARIA/apg/patterns/carousel/)+ Embla 官方 Accessibility plugin\n // 同採 plain button + label、無 tab roles(https://www.embla-carousel.com/docs/plugins/accessibility)。\n // 為何不用完整 tabs 模型(slide=tabpanel + dot aria-controls):\n // (1) Embla snap ≠ slide — basis-1/3 多張並排時 1 dot 控多張,tab↔tabpanel 1:1 不成立\n // (2) tabs 鍵盤契約(方向鍵移 tablist 內 focus)與根容器方向鍵=切張(本檔 handleKeyDown)衝突\n // (3) compound API 下 CarouselItem 不知 index,aria-controls id 接線需額外 registration\n // 舊實作 role=\"tablist\"/\"tab\" + aria-selected 是半套 tabs(無 aria-controls、slide 非 tabpanel)= 非法混血。\n // 現張標記用 aria-current(非 APG 原文的 aria-disabled)— M23 DS 既有 canonical 優先:\n // FileViewer filmstrip(file-viewer.tsx aria-current={active ? 'true' : undefined})同類\n // 「視覺項導航 + 現張標記」已 codify group + button + aria-current;Bootstrap 5 indicators 同 idiom。\n role=\"group\"\n aria-label=\"輪播指示器\" /* i18n-allow: DS default; 對齊同檔其他 aria-label 語言 */\n {...props}\n >\n {scrollSnaps.map((_, i) => (\n <button\n key={i}\n type=\"button\"\n // 現張 dot aria-current(告知 SR「你在這張」;仍可 focus / 點擊為 no-op)\n aria-current={i === selectedIndex ? 'true' : undefined}\n aria-label={`跳至第 ${i + 1} 張`}\n onClick={() => scrollTo(i)}\n className={cn(\n 'h-1.5 rounded-full transition-all',\n // 2026-06-11 a11y(R2;Phase B codex 修重疊):hit-area 垂直擴至 24px、水平 ±3px(dot 6px+gap 6px → 中心距 12px,±3 恰相切零重疊;再寬必互搶點擊)\n // — 視覺 dot 維持 6×6 不變,僅點擊目標擴大(anatomy story 原宣稱的 hit-area 機制補真)\n 'relative before:absolute before:-inset-y-[9px] before:-inset-x-[3px] before:content-[\"\"]',\n // Dots 疊在 media(image/video)之上,不是 token color 底——用 --on-emphasis 保持語義\n // 跟其他「於飽和色底上的淺色前景」一致\n 'bg-on-emphasis/60 hover:bg-on-emphasis/80',\n 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',\n i === selectedIndex ? 'w-6 bg-on-emphasis' : 'w-1.5',\n )}\n />\n ))}\n </div>\n )\n})\nCarouselDots.displayName = 'CarouselDots'\n\n// Story auto-compile metadata — Phase 1 mechanical migration(2026-04-24)\n// Phase 2 fill needed: purpose descriptions + when rationale + world-class refs\nexport const carouselMeta = {\n component: 'Carousel',\n family: null, // non-family composite / overlay / layout\n variants: {\n\n },\n sizes: {\n\n },\n // 'active' 移除 — 全檔無按壓視覺(prev/next 按壓屬內嵌 Button meta,同 Notice/Alert 排除慣例;\n // 2026-07-07 詞彙統一 DS-wide 交叉掃補修)。dot hover 為自有 hover 態(bg-on-emphasis alpha 變體)。\n states: ['default', 'hover', 'focus-visible', 'disabled'],\n tokens: {\n bg: [],\n fg: [],\n ring: ['ring-ring'],\n },\n} as const\n\nexport {\n Carousel,\n CarouselContent,\n CarouselItem,\n CarouselPrevious,\n CarouselNext,\n CarouselDots,\n type CarouselApi,\n}\n"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"checkbox-group.d.ts","sourceRoot":"","sources":["../../../src/components/Checkbox/checkbox-group.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAY9B,eAAO,MAAM,oBAAoB;aAAkC,IAAI;SAAgB,CAAA;AAEvF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AAEH,MAAM,WAAW,kBAAmB,SAAQ,KAAK,CAAC,cAAc,CAAC,cAAc,CAAC;IAC9E;;;;OAIG;IACH,WAAW,CAAC,EAAE,UAAU,GAAG,YAAY,CAAA;CACxC;AAKD,QAAA,MAAM,aAAa,2FAgBlB,CAAA;AAMD,OAAO,EAAE,aAAa,EAAE,CAAA"}
1
+ {"version":3,"file":"checkbox-group.d.ts","sourceRoot":"","sources":["../../../src/components/Checkbox/checkbox-group.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAa9B,eAAO,MAAM,oBAAoB;aAAkC,IAAI;SAAgB,CAAA;AAEvF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AAEH,MAAM,WAAW,kBAAmB,SAAQ,KAAK,CAAC,cAAc,CAAC,cAAc,CAAC;IAC9E;;;;OAIG;IACH,WAAW,CAAC,EAAE,UAAU,GAAG,YAAY,CAAA;CACxC;AAKD,QAAA,MAAM,aAAa,2FA0BlB,CAAA;AAMD,OAAO,EAAE,aAAa,EAAE,CAAA"}
@@ -1,23 +1,30 @@
1
1
  import { jsx } from "react/jsx-runtime";
2
2
  import * as React from "react";
3
3
  import { cn } from "../../lib/utils.js";
4
+ import { useFieldContext } from "../Field/field-context.js";
4
5
  const CheckboxGroupContext = React.createContext(null);
5
6
  const CHECKBOX_GROUP_CTX_VALUE = { inGroup: true };
6
7
  const CheckboxGroup = React.forwardRef(
7
- ({ className, orientation = "vertical", ...props }, ref) => /* @__PURE__ */ jsx(CheckboxGroupContext.Provider, { value: CHECKBOX_GROUP_CTX_VALUE, children: /* @__PURE__ */ jsx(
8
- "div",
9
- {
10
- ref,
11
- role: "group",
12
- className: cn(
13
- // 垂直 CheckboxGroup:zero gap(間距由 SelectionItem py 獨家擁有,見 docblock canonical)
14
- // 水平:短 label 並排需水平 gap-4(label 沒有 py 擴散,需要顯式 gap)
15
- orientation === "vertical" ? "grid" : "flex flex-wrap gap-4",
16
- className
17
- ),
18
- ...props
19
- }
20
- ) })
8
+ ({ className, orientation = "vertical", ...props }, ref) => {
9
+ const fieldCtx = useFieldContext();
10
+ return /* @__PURE__ */ jsx(CheckboxGroupContext.Provider, { value: CHECKBOX_GROUP_CTX_VALUE, children: /* @__PURE__ */ jsx(
11
+ "div",
12
+ {
13
+ ref,
14
+ role: "group",
15
+ "aria-labelledby": fieldCtx == null ? void 0 : fieldCtx.labelId,
16
+ "aria-describedby": fieldCtx == null ? void 0 : fieldCtx.descriptionId,
17
+ "aria-invalid": (fieldCtx == null ? void 0 : fieldCtx.invalid) || void 0,
18
+ className: cn(
19
+ // 垂直 CheckboxGroup:zero gap(間距由 SelectionItem py 獨家擁有,見 docblock canonical)
20
+ // 水平:短 label 並排需水平 gap-4(label 沒有 py 擴散,需要顯式 gap)
21
+ orientation === "vertical" ? "grid" : "flex flex-wrap gap-4",
22
+ className
23
+ ),
24
+ ...props
25
+ }
26
+ ) });
27
+ }
21
28
  );
22
29
  CheckboxGroup.displayName = "CheckboxGroup";
23
30
  CheckboxGroup.fieldLayout = "block";
@@ -1 +1 @@
1
- {"version":3,"file":"checkbox-group.js","sources":["../../../src/components/Checkbox/checkbox-group.tsx"],"sourcesContent":["// @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.\nimport * as React from 'react'\nimport { cn } from '@/lib/utils'\n\n// ── CheckboxGroupContext ────────────────────────────────────────────────────\n// 讓內部 `<Checkbox>` 知道「我在 CheckboxGroup 裡」→ 即使 Field context 也存在,\n// checkbox 仍然保留自己的 label(每個 checkbox 是 group 的一個選項,FieldLabel 只是群組名稱)。\n//\n// 反之:Checkbox **單獨**塞進 Field(無 CheckboxGroup 包)時,Checkbox 才忽略自己的 label\n// 讓 FieldLabel 接管 —— 那是「binary toggle in Field」的場景。\n//\n// 這是 AR50 的根因:沒這個 context 前,CheckboxGroup 內部的 Checkbox 會誤以為「被 Field 包了」\n// 就把 label 全丟掉,導致 Sheet 範例的 Checkboxes 全部沒 label。\nexport const CheckboxGroupContext = React.createContext<{ inGroup: true } | null>(null)\n\n/**\n * CheckboxGroup — 多選 Checkbox 的 layout primitive\n *\n * ── Canonical 鐵律(2026-04-21 user 明示 + codified)──\n *\n * **垂直 CheckboxGroup 的 item 之間沒有外部 gap**。間距完全靠每個 Checkbox 內部的\n * `SelectionItem py = (field-height - 1lh) / 2` 公式生成 —— 單行高度對齊 field-height,\n * 多 row stacked 時 row-to-row 自然有 py × 2 的呼吸空間。\n *\n * **禁止**外層加 `gap-y-*` / `space-y-*` / margin —— 會 double padding,違反 canonical。\n *\n * ── 對齊 RadioGroup canonical ──\n * 本 DS `RadioGroup` wrapper 用 `grid`(無 gap;radio-group.tsx 自加——Radix primitive\n * 本身 unstyled,無預設 layout);本元件垂直也用 `grid`(無 gap),\n * horizontal 用 `flex flex-wrap gap-4`(短 label 並排才需水平 gap)。\n *\n * ── 為什麼 vertical 不給 gap 也能好看 ──\n * SelectionItem py 的公式讓每個 Checkbox row 的「單行高度 = field-height」。row 堆疊時\n * 相鄰 row 的 py 相加 = 2×py ≈ 10-12px 真實 row-to-row 視覺呼吸空間。Atlassian / Ant /\n * Chakra / GitHub CheckboxGroup 皆同流派 —— row 高度定義 gap,不加外部 gap。\n *\n * ── 本 session 曾經的錯誤 + 釐清 ──\n * 早先曾以為「gap 要加」是因為「row 黏在一起」——但實際根因是 `Checkbox` 在 Field\n * context 內誤吞自己的 label(見 checkbox.tsx 的 CheckboxGroupContext 修正)。\n * label 回來後 row 自然撐開,不需要 gap。修正後的 canonical 鐵律:**zero gap,\n * 間距由 SelectionItem py 獨家擁有**。\n *\n * ── fieldLayout:block ──\n * 在 `<Field orientation=\"horizontal\">` 內,control area auto `items-start` +\n * padding-top 公式對齊第一個 item 的 label 第一行(見 field.spec.md)。\n *\n * ── 用法範例 ──\n * <CheckboxGroup>\n * <Checkbox label=\"待處理\" defaultChecked />\n * <Checkbox label=\"進行中\" defaultChecked />\n * <Checkbox label=\"已完成\" />\n * </CheckboxGroup>\n *\n * <CheckboxGroup orientation=\"horizontal\">\n * <Checkbox label=\"Email\" />\n * <Checkbox label=\"Push\" />\n * <Checkbox label=\"SMS\" />\n * </CheckboxGroup>\n */\n\nexport interface CheckboxGroupProps extends React.HTMLAttributes<HTMLDivElement> {\n /**\n * 排列方向。\n * - `vertical`(預設):多選項目堆疊,row 間距由 SelectionItem 擁有(外層 0 gap)\n * - `horizontal`:選項並排,gap-4 分隔(短 label 場景,如「Email / Push / SMS」)\n */\n orientation?: 'vertical' | 'horizontal'\n}\n\n// Module-level 常數(2026-04-22 D3 perf audit):provider value 無狀態,hoist 避免 render 重建\nconst CHECKBOX_GROUP_CTX_VALUE = { inGroup: true } as const\n\nconst CheckboxGroup = React.forwardRef<HTMLDivElement, CheckboxGroupProps>(\n ({ className, orientation = 'vertical', ...props }, ref) => (\n <CheckboxGroupContext.Provider value={CHECKBOX_GROUP_CTX_VALUE}>\n <div\n ref={ref}\n role=\"group\"\n className={cn(\n // 垂直 CheckboxGroup:zero gap(間距由 SelectionItem py 獨家擁有,見 docblock canonical)\n // 水平:短 label 並排需水平 gap-4(label 沒有 py 擴散,需要顯式 gap)\n orientation === 'vertical' ? 'grid' : 'flex flex-wrap gap-4',\n className\n )}\n {...props}\n />\n </CheckboxGroupContext.Provider>\n )\n)\nCheckboxGroup.displayName = 'CheckboxGroup'\n// Field layout declaration:block primitive(多項堆疊)——進入 <Field> 時\n// control area 自動切 items-start + padding-top 公式。對齊 RadioGroup 做法。\n;(CheckboxGroup as unknown as { fieldLayout: 'block' }).fieldLayout = 'block'\n\nexport { CheckboxGroup }\n"],"names":[],"mappings":";;;AAaO,MAAM,uBAAuB,MAAM,cAAwC,IAAI;AAyDtF,MAAM,2BAA2B,EAAE,SAAS,KAAA;AAE5C,MAAM,gBAAgB,MAAM;AAAA,EAC1B,CAAC,EAAE,WAAW,cAAc,YAAY,GAAG,MAAA,GAAS,QAClD,oBAAC,qBAAqB,UAArB,EAA8B,OAAO,0BACpC,UAAA;AAAA,IAAC;AAAA,IAAA;AAAA,MACC;AAAA,MACA,MAAK;AAAA,MACL,WAAW;AAAA;AAAA;AAAA,QAGT,gBAAgB,aAAa,SAAS;AAAA,QACtC;AAAA,MAAA;AAAA,MAED,GAAG;AAAA,IAAA;AAAA,EAAA,EACN,CACF;AAEJ;AACA,cAAc,cAAc;AAG1B,cAAsD,cAAc;"}
1
+ {"version":3,"file":"checkbox-group.js","sources":["../../../src/components/Checkbox/checkbox-group.tsx"],"sourcesContent":["// @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.\nimport * as React from 'react'\nimport { cn } from '@/lib/utils'\nimport { useFieldContext } from '@/design-system/components/Field/field-context'\n\n// ── CheckboxGroupContext ────────────────────────────────────────────────────\n// 讓內部 `<Checkbox>` 知道「我在 CheckboxGroup 裡」→ 即使 Field context 也存在,\n// checkbox 仍然保留自己的 label(每個 checkbox 是 group 的一個選項,FieldLabel 只是群組名稱)。\n//\n// 反之:Checkbox **單獨**塞進 Field(無 CheckboxGroup 包)時,Checkbox 才忽略自己的 label\n// 讓 FieldLabel 接管 —— 那是「binary toggle in Field」的場景。\n//\n// 這是 AR50 的根因:沒這個 context 前,CheckboxGroup 內部的 Checkbox 會誤以為「被 Field 包了」\n// 就把 label 全丟掉,導致 Sheet 範例的 Checkboxes 全部沒 label。\nexport const CheckboxGroupContext = React.createContext<{ inGroup: true } | null>(null)\n\n/**\n * CheckboxGroup — 多選 Checkbox 的 layout primitive\n *\n * ── Canonical 鐵律(2026-04-21 user 明示 + codified)──\n *\n * **垂直 CheckboxGroup 的 item 之間沒有外部 gap**。間距完全靠每個 Checkbox 內部的\n * `SelectionItem py = (field-height - 1lh) / 2` 公式生成 —— 單行高度對齊 field-height,\n * 多 row stacked 時 row-to-row 自然有 py × 2 的呼吸空間。\n *\n * **禁止**外層加 `gap-y-*` / `space-y-*` / margin —— 會 double padding,違反 canonical。\n *\n * ── 對齊 RadioGroup canonical ──\n * 本 DS `RadioGroup` wrapper 用 `grid`(無 gap;radio-group.tsx 自加——Radix primitive\n * 本身 unstyled,無預設 layout);本元件垂直也用 `grid`(無 gap),\n * horizontal 用 `flex flex-wrap gap-4`(短 label 並排才需水平 gap)。\n *\n * ── 為什麼 vertical 不給 gap 也能好看 ──\n * SelectionItem py 的公式讓每個 Checkbox row 的「單行高度 = field-height」。row 堆疊時\n * 相鄰 row 的 py 相加 = 2×py ≈ 10-12px 真實 row-to-row 視覺呼吸空間。Atlassian / Ant /\n * Chakra / GitHub CheckboxGroup 皆同流派 —— row 高度定義 gap,不加外部 gap。\n *\n * ── 本 session 曾經的錯誤 + 釐清 ──\n * 早先曾以為「gap 要加」是因為「row 黏在一起」——但實際根因是 `Checkbox` 在 Field\n * context 內誤吞自己的 label(見 checkbox.tsx 的 CheckboxGroupContext 修正)。\n * label 回來後 row 自然撐開,不需要 gap。修正後的 canonical 鐵律:**zero gap,\n * 間距由 SelectionItem py 獨家擁有**。\n *\n * ── fieldLayout:block ──\n * 在 `<Field orientation=\"horizontal\">` 內,control area auto `items-start` +\n * padding-top 公式對齊第一個 item 的 label 第一行(見 field.spec.md)。\n *\n * ── 用法範例 ──\n * <CheckboxGroup>\n * <Checkbox label=\"待處理\" defaultChecked />\n * <Checkbox label=\"進行中\" defaultChecked />\n * <Checkbox label=\"已完成\" />\n * </CheckboxGroup>\n *\n * <CheckboxGroup orientation=\"horizontal\">\n * <Checkbox label=\"Email\" />\n * <Checkbox label=\"Push\" />\n * <Checkbox label=\"SMS\" />\n * </CheckboxGroup>\n */\n\nexport interface CheckboxGroupProps extends React.HTMLAttributes<HTMLDivElement> {\n /**\n * 排列方向。\n * - `vertical`(預設):多選項目堆疊,row 間距由 SelectionItem 擁有(外層 0 gap)\n * - `horizontal`:選項並排,gap-4 分隔(短 label 場景,如「Email / Push / SMS」)\n */\n orientation?: 'vertical' | 'horizontal'\n}\n\n// Module-level 常數(2026-04-22 D3 perf audit):provider value 無狀態,hoist 避免 render 重建\nconst CHECKBOX_GROUP_CTX_VALUE = { inGroup: true } as const\n\nconst CheckboxGroup = React.forwardRef<HTMLDivElement, CheckboxGroupProps>(\n ({ className, orientation = 'vertical', ...props }, ref) => {\n // Field 整合 a11y(2026-07-17 d10i1):role=\"group\" 的 <div> 無法被 <FieldLabel htmlFor> 命名 →\n // 自讀 fieldCtx 接 aria-labelledby / aria-describedby / aria-invalid,讓群組在 accessibility tree\n // 有名稱、描述與錯誤狀態(對齊 RadioGroup Root 的 fieldCtx 接線,radio-group.tsx L164-166)。\n // standalone(無 Field)時皆 undefined,consumer 經 {...props} 傳的 aria-* 照舊生效(置於 spread 前故可被覆蓋)。\n const fieldCtx = useFieldContext()\n return (\n <CheckboxGroupContext.Provider value={CHECKBOX_GROUP_CTX_VALUE}>\n <div\n ref={ref}\n role=\"group\"\n aria-labelledby={fieldCtx?.labelId}\n aria-describedby={fieldCtx?.descriptionId}\n aria-invalid={fieldCtx?.invalid || undefined}\n className={cn(\n // 垂直 CheckboxGroup:zero gap(間距由 SelectionItem py 獨家擁有,見 docblock canonical)\n // 水平:短 label 並排需水平 gap-4(label 沒有 py 擴散,需要顯式 gap)\n orientation === 'vertical' ? 'grid' : 'flex flex-wrap gap-4',\n className\n )}\n {...props}\n />\n </CheckboxGroupContext.Provider>\n )\n }\n)\nCheckboxGroup.displayName = 'CheckboxGroup'\n// Field layout declaration:block primitive(多項堆疊)——進入 <Field> 時\n// control area 自動切 items-start + padding-top 公式。對齊 RadioGroup 做法。\n;(CheckboxGroup as unknown as { fieldLayout: 'block' }).fieldLayout = 'block'\n\nexport { CheckboxGroup }\n"],"names":[],"mappings":";;;;AAcO,MAAM,uBAAuB,MAAM,cAAwC,IAAI;AAyDtF,MAAM,2BAA2B,EAAE,SAAS,KAAA;AAE5C,MAAM,gBAAgB,MAAM;AAAA,EAC1B,CAAC,EAAE,WAAW,cAAc,YAAY,GAAG,MAAA,GAAS,QAAQ;AAK1D,UAAM,WAAW,gBAAA;AACjB,WACE,oBAAC,qBAAqB,UAArB,EAA8B,OAAO,0BACpC,UAAA;AAAA,MAAC;AAAA,MAAA;AAAA,QACC;AAAA,QACA,MAAK;AAAA,QACL,mBAAiB,qCAAU;AAAA,QAC3B,oBAAkB,qCAAU;AAAA,QAC5B,iBAAc,qCAAU,YAAW;AAAA,QACnC,WAAW;AAAA;AAAA;AAAA,UAGT,gBAAgB,aAAa,SAAS;AAAA,UACtC;AAAA,QAAA;AAAA,QAED,GAAG;AAAA,MAAA;AAAA,IAAA,GAER;AAAA,EAEJ;AACF;AACA,cAAc,cAAc;AAG1B,cAAsD,cAAc;"}
@@ -7,7 +7,7 @@ import type { AvatarData } from "../../components/Avatar/avatar";
7
7
  declare const checkboxVariants: (props?: ({
8
8
  size?: "sm" | "md" | "lg" | null | undefined;
9
9
  } & import("class-variance-authority/types").ClassProp) | undefined) => string;
10
- export interface CheckboxProps extends React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>, VariantProps<typeof checkboxVariants> {
10
+ export interface CheckboxProps extends Omit<React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>, 'asChild' | 'children'>, VariantProps<typeof checkboxVariants> {
11
11
  /**
12
12
  * Inline label。提供時 Checkbox 自動透過 SelectionItem 包裝,
13
13
  * 套用 text-body / text-foreground / disabled 色 的 codified 樣式。
@@ -36,7 +36,7 @@ export interface CheckboxProps extends React.ComponentPropsWithoutRef<typeof Che
36
36
  /**
37
37
  * Field mode(2026-05-05 Phase B3 align):
38
38
  * edit — 一般可互動 checkbox(預設)
39
- * display — **純展示**:渲染 Check / X icon(true=勾 / false=叉,無互動 primitive、無 input chrome);
39
+ * view — **純展示**:渲染 Check / X icon(true=勾 / false=叉,無互動 primitive、無 input chrome);
40
40
  * 對齊 Carbon read-only / DataTable boolean cell 場景。取代既有 BooleanDisplay。
41
41
  * readonly — 同 readOnly prop:checkbox 視覺保留 + 鎖互動 + a11y readonly signal
42
42
  * disabled — 同 disabled prop:降色 + 鎖互動
@@ -1 +1 @@
1
- {"version":3,"file":"checkbox.d.ts","sourceRoot":"","sources":["../../../src/components/Checkbox/checkbox.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAC9B,OAAO,KAAK,iBAAiB,MAAM,0BAA0B,CAAA;AAE7D,OAAO,EAAO,KAAK,YAAY,EAAE,MAAM,0BAA0B,CAAA;AAGjE,OAAO,KAAK,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,8CAA8C,CAAA;AAK3F,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAC9C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,0CAA0C,CAAA;AAM1E,QAAA,MAAM,gBAAgB;;8EA8BrB,CAAA;AAiCD,MAAM,WAAW,aACf,SAAQ,KAAK,CAAC,wBAAwB,CAAC,OAAO,iBAAiB,CAAC,IAAI,CAAC,EACnE,YAAY,CAAC,OAAO,gBAAgB,CAAC;IACvC;;;;;;OAMG;IACH,KAAK,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IACvB;;;;;OAKG;IACH,WAAW,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IAC7B,uHAAuH;IACvH,IAAI,CAAC,EAAE,UAAU,CAAA;IACjB,iDAAiD;IACjD,MAAM,CAAC,EAAE,UAAU,CAAA;IACnB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB;;;;;;;OAOG;IACH,IAAI,CAAC,EAAE,SAAS,CAAA;IAChB;;;OAGG;IACH,OAAO,CAAC,EAAE,YAAY,CAAA;CACvB;AAID,QAAA,MAAM,QAAQ,yFAkLb,CAAA;AAKD,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAoBf,CAAA;AAEV,OAAO,EAAE,QAAQ,EAAE,gBAAgB,EAAE,CAAA"}
1
+ {"version":3,"file":"checkbox.d.ts","sourceRoot":"","sources":["../../../src/components/Checkbox/checkbox.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAC9B,OAAO,KAAK,iBAAiB,MAAM,0BAA0B,CAAA;AAE7D,OAAO,EAAO,KAAK,YAAY,EAAE,MAAM,0BAA0B,CAAA;AAGjE,OAAO,KAAK,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,8CAA8C,CAAA;AAM3F,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAC9C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,0CAA0C,CAAA;AAM1E,QAAA,MAAM,gBAAgB;;8EA8BrB,CAAA;AAsCD,MAAM,WAAW,aACf,SAAQ,IAAI,CAAC,KAAK,CAAC,wBAAwB,CAAC,OAAO,iBAAiB,CAAC,IAAI,CAAC,EAAE,SAAS,GAAG,UAAU,CAAC,EACjG,YAAY,CAAC,OAAO,gBAAgB,CAAC;IACvC;;;;;;OAMG;IACH,KAAK,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IACvB;;;;;OAKG;IACH,WAAW,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IAC7B,uHAAuH;IACvH,IAAI,CAAC,EAAE,UAAU,CAAA;IACjB,iDAAiD;IACjD,MAAM,CAAC,EAAE,UAAU,CAAA;IACnB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB;;;;;;;OAOG;IACH,IAAI,CAAC,EAAE,SAAS,CAAA;IAChB;;;OAGG;IACH,OAAO,CAAC,EAAE,YAAY,CAAA;CACvB;AAID,QAAA,MAAM,QAAQ,yFAwOb,CAAA;AAKD,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAoBf,CAAA;AAEV,OAAO,EAAE,QAAQ,EAAE,gBAAgB,EAAE,CAAA"}
@@ -5,6 +5,7 @@ import { Minus, Check } from "lucide-react";
5
5
  import { cva } from "class-variance-authority";
6
6
  import { cn } from "../../lib/utils.js";
7
7
  import { useFieldContext, useResolvedFieldDisabled, useResolvedFieldMode, useResolvedFieldSize } from "../Field/field-context.js";
8
+ import { useControllable } from "../../hooks/use-controllable.js";
8
9
  import { fieldWrapperStyles } from "../Field/field-wrapper.js";
9
10
  import { SelectionItem } from "../SelectionControl/selection-item.js";
10
11
  import { BooleanValueIcon } from "../SelectionControl/boolean-value.js";
@@ -55,6 +56,13 @@ const Checkbox = React.forwardRef(
55
56
  mode,
56
57
  // chrome 對 Checkbox 主體無視覺影響(無 input wrapper)— 接收純為 prop 一致性;destructure 防 leak 到 DOM。
57
58
  variant: _chrome,
59
+ // controllable checked triplet(2026-07-17 d26i0):於 wrapper 層取出,交由 useControllable 持有
60
+ // resolved 值,供 edit / view / readonly 三分支共用。原本 view / readonly 分支各自讀 raw prop →
61
+ // uncontrolled(只傳 defaultChecked)在 edit 態 toggle 後切 mode 會丟失目前值(Radix 內部狀態
62
+ // wrapper 讀不到)。移出 props 後 edit 主路徑改由 checked={resolvedChecked} 受控(見下方 rootEl)。
63
+ checked: checkedProp,
64
+ defaultChecked: defaultCheckedProp,
65
+ onCheckedChange,
58
66
  id: idProp,
59
67
  ...props
60
68
  }, ref) => {
@@ -76,22 +84,48 @@ const Checkbox = React.forwardRef(
76
84
  const effectiveDisabled = resolvedDisabled || resolvedMode === "disabled";
77
85
  const resolvedBoxSize = useResolvedFieldSize(size ?? void 0, "md");
78
86
  const {
79
- checked: checkedProp,
80
- defaultChecked: defaultCheckedProp,
81
- onCheckedChange: _onCheckedChange,
82
87
  required: _required,
83
88
  name: _name,
84
89
  value: _value,
85
- asChild: _asChild,
86
- children: _children,
87
90
  ...restDomProps
88
91
  } = props;
89
- if (resolvedMode === "display") {
90
- const isChecked = checkedProp === true;
91
- return /* @__PURE__ */ jsx("span", { ...restDomProps, ref, className: "inline-flex", children: /* @__PURE__ */ jsx(BooleanValueIcon, { checked: isChecked, size: resolvedBoxSize }) });
92
+ const isControlled = checkedProp !== void 0;
93
+ const [resolvedChecked, setResolvedChecked] = useControllable({
94
+ value: checkedProp,
95
+ defaultValue: defaultCheckedProp ?? false
96
+ });
97
+ const innerRef = React.useRef(null);
98
+ React.useEffect(() => {
99
+ var _a;
100
+ if (isControlled) return;
101
+ const form = (_a = innerRef.current) == null ? void 0 : _a.form;
102
+ if (!form) return;
103
+ const handleReset = () => setResolvedChecked(defaultCheckedProp ?? false);
104
+ form.addEventListener("reset", handleReset);
105
+ return () => form.removeEventListener("reset", handleReset);
106
+ }, [isControlled, resolvedMode, defaultCheckedProp, setResolvedChecked]);
107
+ const setRef = React.useCallback((el) => {
108
+ innerRef.current = el;
109
+ if (typeof ref === "function") ref(el);
110
+ else if (ref) ref.current = el;
111
+ }, [ref]);
112
+ if (resolvedMode === "view") {
113
+ const isChecked = resolvedChecked === true;
114
+ const viewAriaLabel = restDomProps["aria-label"] ?? (isChecked ? "是" : "否");
115
+ return /* @__PURE__ */ jsx(
116
+ "span",
117
+ {
118
+ ...restDomProps,
119
+ ref,
120
+ role: "img",
121
+ "aria-label": viewAriaLabel,
122
+ className: "inline-flex",
123
+ children: /* @__PURE__ */ jsx(BooleanValueIcon, { checked: isChecked, size: resolvedBoxSize })
124
+ }
125
+ );
92
126
  }
93
127
  if (effectiveReadOnly && insideField && effectiveLabel == null) {
94
- const isChecked = (checkedProp ?? defaultCheckedProp) === true;
128
+ const isChecked = resolvedChecked === true;
95
129
  const boxSize = resolvedBoxSize;
96
130
  return /* @__PURE__ */ jsx(
97
131
  "div",
@@ -118,8 +152,13 @@ const Checkbox = React.forwardRef(
118
152
  CheckboxPrimitive.Root,
119
153
  {
120
154
  id: inputId,
121
- ref,
155
+ ref: setRef,
122
156
  disabled: effectiveDisabled,
157
+ checked: resolvedChecked,
158
+ onCheckedChange: (next) => {
159
+ setResolvedChecked(next);
160
+ onCheckedChange == null ? void 0 : onCheckedChange(next);
161
+ },
123
162
  "aria-readonly": effectiveReadOnly || void 0,
124
163
  "data-readonly": effectiveReadOnly || void 0,
125
164
  tabIndex: effectiveReadOnly ? -1 : void 0,
@@ -1 +1 @@
1
- {"version":3,"file":"checkbox.js","sources":["../../../src/components/Checkbox/checkbox.tsx"],"sourcesContent":["// @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.\nimport * as React from \"react\"\nimport * as CheckboxPrimitive from \"@radix-ui/react-checkbox\"\nimport { Check, Minus } from \"lucide-react\"\nimport { cva, type VariantProps } from \"class-variance-authority\"\n\nimport { cn } from \"@/lib/utils\"\nimport type { FieldMode, FieldVariant } from \"@/design-system/components/Field/field-types\"\nimport { useFieldContext, useResolvedFieldDisabled, useResolvedFieldMode, useResolvedFieldSize } from \"@/design-system/components/Field/field-context\"\nimport { fieldWrapperStyles } from \"@/design-system/components/Field/field-wrapper\"\nimport { SelectionItem } from \"@/design-system/components/SelectionControl/selection-item\"\nimport { BooleanValueIcon } from \"@/design-system/components/SelectionControl/boolean-value\"\nimport type { LucideIcon } from \"lucide-react\"\nimport type { AvatarData } from \"@/design-system/components/Avatar/avatar\"\nimport { CheckboxGroupContext } from \"./checkbox-group\"\n\n// ── Variants ────────────────────────────────────────────────────────────────\n// 三種尺寸(sm/md=16px, lg=20px),對齊 icon 系統與 SelectionItem。\n\nconst checkboxVariants = cva(\n [\n 'grid place-content-center shrink-0 rounded-md',\n 'border border-border bg-surface',\n 'transition-colors duration-150',\n 'hover:border-border-hover',\n 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1',\n 'data-[state=checked]:bg-primary data-[state=checked]:text-on-emphasis data-[state=checked]:border-primary',\n 'data-[state=checked]:hover:bg-primary-hover data-[state=checked]:hover:border-primary-hover',\n 'data-[state=indeterminate]:bg-primary data-[state=indeterminate]:text-on-emphasis data-[state=indeterminate]:border-primary',\n 'data-[state=indeterminate]:hover:bg-primary-hover data-[state=indeterminate]:hover:border-primary-hover',\n 'disabled:cursor-not-allowed disabled:bg-disabled disabled:border-transparent disabled:hover:border-transparent',\n 'disabled:data-[state=checked]:bg-disabled disabled:data-[state=checked]:text-fg-disabled disabled:data-[state=checked]:border-transparent',\n 'disabled:data-[state=indeterminate]:bg-disabled disabled:data-[state=indeterminate]:text-fg-disabled disabled:data-[state=indeterminate]:border-transparent',\n // readOnly:鎖定互動但維持 checked/unchecked 視覺\n 'data-[readonly=true]:pointer-events-none data-[readonly=true]:cursor-default',\n 'data-[readonly=true]:hover:border-border',\n ],\n {\n variants: {\n size: {\n sm: 'h-4 w-4',\n md: 'h-4 w-4',\n lg: 'h-5 w-5',\n },\n },\n defaultVariants: {\n size: 'md',\n },\n }\n)\n\n// ── Check Icon Size ─────────────────────────────────────────────────────────\nconst checkIconSize: Record<string, number> = { sm: 12, md: 12, lg: 16 }\n\n// ── Check Icon Stroke Weight ────────────────────────────────────────────────\n// 16px 以下 icon 視覺不夠顯眼 → 用較粗 stroke 補償。Lucide 預設 strokeWidth=2 在\n// 12px 下 render 約 1px 線寬,視覺偏細;加粗到 3.5(render ≈ 1.75px)才有足夠視覺權重。\n// 16px 用 2.5(render ≈ 1.67px)讓 checked 態的 check icon 夠顯眼。\n//\n// 為什麼不是 3 / 2:本 session 實測 3 / 2 在 storybook 上兩個 size 的 render 線寬差僅\n// 0.17px(1.5 vs 1.33),使用者肉眼看不出差異(image #64 回報)。改為 3.5 / 2.5:\n// - md 12px × 3.5 → 1.75px 線寬\n// - lg 16px × 2.5 → 1.67px 線寬\n// 兩者仍接近但 md 的線寬 **絕對值** 跟 16px 預設(1.33)有更明顯差異,視覺上「小 check 更粗」。\n//\n// 世界級對照:iOS HIG / Material 3 / Polaris 的 checkmark 在 <16px 下皆加粗 compensate。\n// 為什麼不用 Lucide absoluteStrokeWidth:那保持「絕對 px 粗細」,我們反而要「小尺寸比例更粗」。\n//\n// Check 與 Minus(indeterminate)共用此規則;Switch 的 SPECS.checkStroke 採同樣值。\n// 2026-05-18 簡化 per user 視覺證「sm/md 3.5 vs lg 2.5 看不出差別」(image #64 + 2nd round\n// 圖一 video proof)+「做完」approval:\n// - 原 {3.5, 3.5, 2.5} → effective render thickness 1.75 / 1.75 / 1.67 = 跨 size 差 0.08px(視覺看不出)\n// - 改 {3, 3, 2.5} 保留 sm/md 小尺寸 legibility insurance(per iOS HIG / Material 3 cite)\n// + lg 仍稍粗於 Lucide default 2(保留 compensation 主旨,但不過度差異化)\n// ⚠️ 2026-06-12:發現 base.css `.lucide{stroke-width:1.75}` 全域規則自 2026-04-08 起\n// 無條件蓋掉本 prop(CSS class > SVG attribute)→ 本表從未真渲染過,上述 05-18 視覺\n// 測試兩者實為 0.875px(證據污染)。base.css 已改 `[stroke-width='2']` 限定,本表自此\n// 真實生效(pixel 驗證 1.50px)。SSOT → .claude/references/ui-dev-rules.md「小尺寸 icon stroke 補償」\nconst checkStrokeWidth: Record<string, number> = { sm: 3, md: 3, lg: 2.5 }\n\n// ── Types ───────────────────────────────────────────────────────────────────\n\nexport interface CheckboxProps\n extends React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>,\n VariantProps<typeof checkboxVariants> {\n /**\n * Inline label。提供時 Checkbox 自動透過 SelectionItem 包裝,\n * 套用 text-body / text-foreground / disabled 色 的 codified 樣式。\n * 在 <Field> context 內且不在 CheckboxGroup 內時,此 prop 會被忽略(由 FieldLabel 接管);\n * CheckboxGroup 內每個 checkbox 的 label 是它自己的選項名,必保留\n * (同檔 shouldSuppressLabel = insideField && !insideGroup,AR50 anchor)。\n */\n label?: React.ReactNode\n /**\n * Inline description(secondary 文字)。須與 label 搭配使用。\n * 套用 text-body / text-fg-secondary 樣式。\n * 在 <Field> context 內且不在 CheckboxGroup 內時,此 prop 會被忽略(由 FieldDescription 接管);\n * CheckboxGroup 內的 description 屬各選項自有,必保留(同 label 的 group 例外)。\n */\n description?: React.ReactNode\n /** 可選左側 icon(label 前)— 2026-06-12 M30 修:轉發 SelectionItem 既有 canonical 槽(selection-item.tsx jsDoc 為 SSOT;與 avatar 互斥)*/\n icon?: LucideIcon\n /** 可選左側 avatar(label 前)— 同上,SelectionItem 槽轉發 */\n avatar?: AvatarData\n /**\n * readonly 模式:鎖定互動但維持 checked/unchecked 視覺正確。\n * 與 disabled 的差異:readonly 不降色(可讀),disabled 降色(弱化)。\n * 用於表單 readonly 呈現、DataTable cell 非編輯態。\n */\n readOnly?: boolean\n /**\n * Field mode(2026-05-05 Phase B3 align):\n * edit — 一般可互動 checkbox(預設)\n * display — **純展示**:渲染 Check / X icon(true=勾 / false=叉,無互動 primitive、無 input chrome);\n * 對齊 Carbon read-only / DataTable boolean cell 場景。取代既有 BooleanDisplay。\n * readonly — 同 readOnly prop:checkbox 視覺保留 + 鎖互動 + a11y readonly signal\n * disabled — 同 disabled prop:降色 + 鎖互動\n */\n mode?: FieldMode\n /**\n * Visual chrome — checkbox 本體無 input wrapper variant,本 prop 對 checkbox 主體無視覺影響;\n * 為對齊 Field 4-mode + chrome 透傳契約而保留(M19 一致性)。\n */\n variant?: FieldVariant\n}\n\n// ── Component ───────────────────────────────────────────────────────────────\n\nconst Checkbox = React.forwardRef<\n React.ElementRef<typeof CheckboxPrimitive.Root>,\n CheckboxProps\n>(\n (\n {\n className,\n size,\n label,\n icon,\n avatar,\n description,\n readOnly = false,\n disabled,\n mode,\n // chrome 對 Checkbox 主體無視覺影響(無 input wrapper)— 接收純為 prop 一致性;destructure 防 leak 到 DOM。\n variant: _chrome,\n id: idProp,\n ...props\n },\n ref\n ) => {\n const sizeKey = size ?? 'md'\n const iconPx = checkIconSize[sizeKey]\n const iconStrokeWidth = checkStrokeWidth[sizeKey]\n\n // Field context:Checkbox 單獨塞進 Field(binary toggle)時,忽略自己的 label 讓 FieldLabel 接管\n // 2026-05-31 #35:hooks(useFieldContext / useContext / useId)必在任何 conditional return 前呼叫(Rules of Hooks)。\n // 原 mode='display' early return 寫在 hooks 之上 → runtime 切 mode 會 hook count 不一致 crash;已下移至 hooks 後。\n //\n // **例外**:Checkbox 是 CheckboxGroup 的 child 時(multi-select 情境),**每個 checkbox\n // 的 label 是它自己的選項名**,FieldLabel 只是群組名稱 — 此時 label **必須保留**,\n // 不能被 Field context 吞掉。AR50 的根因就是這個 branch 之前誤把 group 內的 checkbox\n // label 全清空,導致 sheet 內 3 個 checkbox 沒 label。\n const fieldCtx = useFieldContext()\n const checkboxGroupCtx = React.useContext(CheckboxGroupContext)\n const insideField = fieldCtx?.hasFieldWrapper === true\n const insideGroup = checkboxGroupCtx?.inGroup === true\n const shouldSuppressLabel = insideField && !insideGroup\n const effectiveLabel = shouldSuppressLabel ? undefined : label\n const effectiveDescription = shouldSuppressLabel ? undefined : description\n\n // Id 連結\n //\n // ── 2026-04-21 bug fix ──\n // 原本:`idProp ?? fieldCtx?.id ?? generatedId`。\n // 在 Field 內 fieldCtx.id 存在,CheckboxGroup 所有 children 共用同一個 id →\n // 每個 checkbox 的 `<label htmlFor={sameId}>` 全指向第一個 checkbox →\n // **點任何 label 都只開關第一個 checkbox(real bug)**。\n //\n // 修法:group 內的 checkbox 強制用 generatedId(唯一),不沿用 Field id;\n // solo in Field(binary toggle)才沿用 fieldCtx.id 讓 FieldLabel htmlFor 生效。\n const generatedId = React.useId()\n const inputId = idProp ?? (insideGroup ? generatedId : (fieldCtx?.id ?? generatedId))\n\n // 2026-06-08 SSOT cascade:disabled + mode 經 resolver hook(原 raw prop → <Field disabled>/<Field mode=\"display\"> 漏 cascade)\n const resolvedDisabled = useResolvedFieldDisabled(disabled)\n const resolvedMode = useResolvedFieldMode({ mode, disabled, readOnly })\n const effectiveReadOnly = readOnly || resolvedMode === 'readonly'\n // mode='disabled'(如 DataTable disabled cell 直傳)必須落到真 disabled chrome —\n // 2026-06-12 修:原本只看 resolvedDisabled(prop/fieldCtx),mode='disabled' 無人消費\n // → disabled boolean cell 渲出可 focus 的正常外觀 checkbox(違 field-controls.spec L286)\n const effectiveDisabled = resolvedDisabled || resolvedMode === 'disabled'\n // readonly 灰框 size:走 SSOT resolver(prop > ctx > 'md',field-context.ts:150-161)\n const resolvedBoxSize = useResolvedFieldSize(size ?? undefined, 'md') as 'sm' | 'md' | 'lg'\n\n // 2026-07-04 修:display / readonly-in-Field 分支的裸 span/div 原丟棄剩餘 props\n // (id/data-*/aria-*)且不轉發 forwardRef ref → consumer 的 aria-label 等靜默失效。\n // 抽出 Radix 專屬 non-DOM props,其餘 DOM props 於兩分支 spread 轉發(與 Switch/\n // RadioGroup 同修);edit 主路徑不受影響(rootEl 仍 spread 完整 props)。\n const {\n checked: checkedProp,\n defaultChecked: defaultCheckedProp,\n onCheckedChange: _onCheckedChange,\n required: _required,\n name: _name,\n value: _value,\n asChild: _asChild,\n children: _children,\n ...restDomProps\n } = props\n\n // ── mode='display'(下移至所有 hooks 之後,per #35 Rules of Hooks)──────────\n // 純展示模式:無互動 primitive、渲染 Check / X icon(true=勾 / false=叉,中性 foreground 色)。\n // boolean 值展示符號 SSOT = SelectionControl/boolean-value.tsx(勾/叉 icon + 中性色 + M22 世界級對照)。取代 BooleanDisplay。\n if (resolvedMode === 'display') {\n const isChecked = checkedProp === true\n return (\n <span {...restDomProps} ref={ref as React.Ref<HTMLSpanElement>} className=\"inline-flex\">\n <BooleanValueIcon checked={isChecked} size={resolvedBoxSize} />\n </span>\n )\n }\n\n // ── mode='readonly' in Field(2026-06-12 拍板「灰框 + 勾/叉」;2026-07-09 ✓/— glyph → Check/X icon)─\n // Field 內 readonly boolean = readonly 灰框 chrome + display 同款值語言(勾/叉 icon)。\n // 灰框消費 fieldWrapperStyles 同一 cva = 與 Input readonly 字面同源(SSOT,改一處全動)。\n // 理由:同一張 readonly 表單裡文字控件有 bg-readonly 灰框鎖定訊號,boolean 保留全彩\n // 控件會誤導「仍可操作」(世界級 0/4 用原樣鎖互動:Salesforce=✓ 靜態 glyph /\n // SAP=靜態文字 / Atlassian=readView / Ant Pro=文字)。\n // Scope:僅 Field 內且無 inline label(FieldLabel 接管 label 的表單欄位場景);\n // standalone readOnly(settings list / SelectionItem row)維持原樣鎖互動不變。\n if (effectiveReadOnly && insideField && effectiveLabel == null) {\n const isChecked = (checkedProp ?? defaultCheckedProp) === true\n const boxSize = resolvedBoxSize\n return (\n <div\n {...(restDomProps as React.HTMLAttributes<HTMLDivElement>)}\n ref={ref as React.Ref<HTMLDivElement>}\n role=\"checkbox\"\n aria-checked={isChecked}\n aria-readonly=\"true\"\n aria-labelledby={fieldCtx?.labelId}\n aria-invalid={fieldCtx?.invalid || undefined}\n data-readonly=\"true\"\n tabIndex={0}\n className={cn(\n fieldWrapperStyles({ size: boxSize, mode: 'readonly', variant: 'default' }),\n 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring',\n className,\n )}\n >\n <BooleanValueIcon checked={isChecked} size={boxSize} />\n </div>\n )\n }\n\n const rootEl = (\n <CheckboxPrimitive.Root\n id={inputId}\n ref={ref}\n disabled={effectiveDisabled}\n aria-readonly={effectiveReadOnly || undefined}\n data-readonly={effectiveReadOnly || undefined}\n tabIndex={effectiveReadOnly ? -1 : undefined}\n aria-describedby={fieldCtx?.descriptionId}\n className={cn(checkboxVariants({ size }), className)}\n {...props}\n // 2026-07-05 D4 修:readOnly 鎖互動補 label 繞道 — <label htmlFor> 的 synthetic click\n // 不是 pointer event,cva pointer-events-none 攔不到,點 label 文字仍 toggle。\n // preventDefault 讓 Radix composeEventHandlers(checkForDefaultPrevented)跳過內部 toggle;\n // 必列在 {...props} 之後才不被 consumer onClick 覆蓋(readOnly 時 consumer onClick 一併鎖,\n // 與 pointer-events-none 擋直接點擊的行為一致)。\n onClick={(event) => {\n if (effectiveReadOnly) {\n event.preventDefault()\n return\n }\n props.onClick?.(event)\n }}\n >\n {/* 圖示切換走 Radix data-state(CSS),非 props.checked(JS)—— uncontrolled\n defaultChecked=\"indeterminate\" 時 props.checked=undefined,JS 判斷會錯渲 Check;\n data-state 涵蓋 controlled/uncontrolled 全部轉換(Radix 為 SSOT)。 */}\n <CheckboxPrimitive.Indicator className=\"group/indicator grid place-content-center text-current\">\n <Minus className=\"hidden group-data-[state=indeterminate]/indicator:block\" style={{ width: iconPx, height: iconPx }} strokeWidth={iconStrokeWidth} />\n <Check className=\"hidden group-data-[state=checked]/indicator:block\" style={{ width: iconPx, height: iconPx }} strokeWidth={iconStrokeWidth} />\n </CheckboxPrimitive.Indicator>\n </CheckboxPrimitive.Root>\n )\n\n // 無 label → 只渲染 checkbox 本體\n if (effectiveLabel == null) return rootEl\n\n // 有 label → 透過 SelectionItem 包裝(control 在左、label+description 在右)\n return (\n <SelectionItem\n control={rootEl}\n label={effectiveLabel}\n description={effectiveDescription}\n icon={icon}\n avatar={avatar}\n htmlFor={inputId}\n disabled={effectiveDisabled}\n size={sizeKey}\n />\n )\n }\n)\nCheckbox.displayName = CheckboxPrimitive.Root.displayName\n\n// Story auto-compile metadata — Phase 1 mechanical migration(2026-04-24)\n// Phase 2 fill needed: purpose descriptions + when rationale + world-class refs\nexport const checkboxMeta = {\n component: 'Checkbox',\n family: null, // self-contained primitive(對齊 spec frontmatter self-contained + body L31;非 Family 4)\n variants: {\n\n },\n sizes: {\n // 2026-06-10 修 stale meta:iconSize 對齊 checkIconSize 真值(L49 sm/md=12, lg=16;deep-audit A.1b 抓 metadata drift)\n sm: { fieldHeight: 28, iconSize: 12, typography: 'body' },\n md: { fieldHeight: 32, iconSize: 12, typography: 'body' },\n lg: { fieldHeight: 36, iconSize: 16, typography: 'body-lg' },\n },\n // 'checked' = 勾選持續態(bg-primary;2026-07-07 meta 詞彙統一補列,含 indeterminate 同族)\n states: ['default', 'hover', 'checked', 'focus-visible', 'disabled'],\n tokens: {\n bg: ['bg-disabled', 'bg-primary', 'bg-primary-hover', 'bg-surface'],\n fg: ['text-fg-disabled', 'text-fg-secondary', 'text-foreground'],\n ring: ['ring-ring'],\n },\n defaultSize: 'md',\n} as const\n\nexport { Checkbox, checkboxVariants }\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"checkbox.js","sources":["../../../src/components/Checkbox/checkbox.tsx"],"sourcesContent":["// @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.\nimport * as React from \"react\"\nimport * as CheckboxPrimitive from \"@radix-ui/react-checkbox\"\nimport { Check, Minus } from \"lucide-react\"\nimport { cva, type VariantProps } from \"class-variance-authority\"\n\nimport { cn } from \"@/lib/utils\"\nimport type { FieldMode, FieldVariant } from \"@/design-system/components/Field/field-types\"\nimport { useFieldContext, useResolvedFieldDisabled, useResolvedFieldMode, useResolvedFieldSize } from \"@/design-system/components/Field/field-context\"\nimport { useControllable } from \"@/design-system/hooks/use-controllable\"\nimport { fieldWrapperStyles } from \"@/design-system/components/Field/field-wrapper\"\nimport { SelectionItem } from \"@/design-system/components/SelectionControl/selection-item\"\nimport { BooleanValueIcon } from \"@/design-system/components/SelectionControl/boolean-value\"\nimport type { LucideIcon } from \"lucide-react\"\nimport type { AvatarData } from \"@/design-system/components/Avatar/avatar\"\nimport { CheckboxGroupContext } from \"./checkbox-group\"\n\n// ── Variants ────────────────────────────────────────────────────────────────\n// 三種尺寸(sm/md=16px, lg=20px),對齊 icon 系統與 SelectionItem。\n\nconst checkboxVariants = cva(\n [\n 'grid place-content-center shrink-0 rounded-md',\n 'border border-border bg-surface',\n 'transition-colors duration-150',\n 'hover:border-border-hover',\n 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1',\n 'data-[state=checked]:bg-primary data-[state=checked]:text-on-emphasis data-[state=checked]:border-primary',\n 'data-[state=checked]:hover:bg-primary-hover data-[state=checked]:hover:border-primary-hover',\n 'data-[state=indeterminate]:bg-primary data-[state=indeterminate]:text-on-emphasis data-[state=indeterminate]:border-primary',\n 'data-[state=indeterminate]:hover:bg-primary-hover data-[state=indeterminate]:hover:border-primary-hover',\n 'disabled:cursor-not-allowed disabled:bg-disabled disabled:border-transparent disabled:hover:border-transparent',\n 'disabled:data-[state=checked]:bg-disabled disabled:data-[state=checked]:text-fg-disabled disabled:data-[state=checked]:border-transparent',\n 'disabled:data-[state=indeterminate]:bg-disabled disabled:data-[state=indeterminate]:text-fg-disabled disabled:data-[state=indeterminate]:border-transparent',\n // readOnly:鎖定互動但維持 checked/unchecked 視覺\n 'data-[readonly=true]:pointer-events-none data-[readonly=true]:cursor-default',\n 'data-[readonly=true]:hover:border-border',\n ],\n {\n variants: {\n size: {\n sm: 'h-4 w-4',\n md: 'h-4 w-4',\n lg: 'h-5 w-5',\n },\n },\n defaultVariants: {\n size: 'md',\n },\n }\n)\n\n// ── Check Icon Size ─────────────────────────────────────────────────────────\nconst checkIconSize: Record<string, number> = { sm: 12, md: 12, lg: 16 }\n\n// ── Check Icon Stroke Weight ────────────────────────────────────────────────\n// 16px 以下 icon 視覺不夠顯眼 → 用較粗 stroke 補償。Lucide 預設 strokeWidth=2 在\n// 12px 下 render 約 1px 線寬,視覺偏細;加粗到 3.5(render ≈ 1.75px)才有足夠視覺權重。\n// 16px 用 2.5(render ≈ 1.67px)讓 checked 態的 check icon 夠顯眼。\n//\n// 為什麼不是 3 / 2:本 session 實測 3 / 2 在 storybook 上兩個 size 的 render 線寬差僅\n// 0.17px(1.5 vs 1.33),使用者肉眼看不出差異(image #64 回報)。改為 3.5 / 2.5:\n// - md 12px × 3.5 → 1.75px 線寬\n// - lg 16px × 2.5 → 1.67px 線寬\n// 兩者仍接近但 md 的線寬 **絕對值** 跟 16px 預設(1.33)有更明顯差異,視覺上「小 check 更粗」。\n//\n// 世界級對照:iOS HIG / Material 3 / Polaris 的 checkmark 在 <16px 下皆加粗 compensate。\n// 為什麼不用 Lucide absoluteStrokeWidth:那保持「絕對 px 粗細」,我們反而要「小尺寸比例更粗」。\n//\n// Check 與 Minus(indeterminate)共用此規則;Switch 的 SPECS.checkStroke 採同樣值。\n// 2026-05-18 簡化 per user 視覺證「sm/md 3.5 vs lg 2.5 看不出差別」(image #64 + 2nd round\n// 圖一 video proof)+「做完」approval:\n// - 原 {3.5, 3.5, 2.5} → effective render thickness 1.75 / 1.75 / 1.67 = 跨 size 差 0.08px(視覺看不出)\n// - 改 {3, 3, 2.5} 保留 sm/md 小尺寸 legibility insurance(per iOS HIG / Material 3 cite)\n// + lg 仍稍粗於 Lucide default 2(保留 compensation 主旨,但不過度差異化)\n// ⚠️ 2026-06-12:發現 base.css `.lucide{stroke-width:1.75}` 全域規則自 2026-04-08 起\n// 無條件蓋掉本 prop(CSS class > SVG attribute)→ 本表從未真渲染過,上述 05-18 視覺\n// 測試兩者實為 0.875px(證據污染)。base.css 已改 `[stroke-width='2']` 限定,本表自此\n// 真實生效(pixel 驗證 1.50px)。SSOT → .claude/references/ui-dev-rules.md「小尺寸 icon stroke 補償」\nconst checkStrokeWidth: Record<string, number> = { sm: 3, md: 3, lg: 2.5 }\n\n// ── Types ───────────────────────────────────────────────────────────────────\n\n// asChild / children 從公開型別 Omit(2026-07-17 d9i6):Checkbox 有**固定 anatomy** —\n// 內部恆渲染 Indicator(Check / Minus 兩子節點),非 Slot host。若保留 Radix `asChild`,\n// consumer 傳入的元素會被 Radix Slot `React.Children.only` 因多子節點在執行期拋錯(失敗記憶索引\n// 已 codify 此 asChild 多子節點 bug class);`children` 同理會被固定 Indicator 覆蓋。需自訂佈局\n// 請用 `label` / `description` / `icon` / `avatar` 一級 props(內部自動包 SelectionItem)。\nexport interface CheckboxProps\n extends Omit<React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>, 'asChild' | 'children'>,\n VariantProps<typeof checkboxVariants> {\n /**\n * Inline label。提供時 Checkbox 自動透過 SelectionItem 包裝,\n * 套用 text-body / text-foreground / disabled 色 的 codified 樣式。\n * 在 <Field> context 內且不在 CheckboxGroup 內時,此 prop 會被忽略(由 FieldLabel 接管);\n * CheckboxGroup 內每個 checkbox 的 label 是它自己的選項名,必保留\n * (同檔 shouldSuppressLabel = insideField && !insideGroup,AR50 anchor)。\n */\n label?: React.ReactNode\n /**\n * Inline description(secondary 文字)。須與 label 搭配使用。\n * 套用 text-body / text-fg-secondary 樣式。\n * 在 <Field> context 內且不在 CheckboxGroup 內時,此 prop 會被忽略(由 FieldDescription 接管);\n * CheckboxGroup 內的 description 屬各選項自有,必保留(同 label 的 group 例外)。\n */\n description?: React.ReactNode\n /** 可選左側 icon(label 前)— 2026-06-12 M30 修:轉發 SelectionItem 既有 canonical 槽(selection-item.tsx jsDoc 為 SSOT;與 avatar 互斥)*/\n icon?: LucideIcon\n /** 可選左側 avatar(label 前)— 同上,SelectionItem 槽轉發 */\n avatar?: AvatarData\n /**\n * readonly 模式:鎖定互動但維持 checked/unchecked 視覺正確。\n * 與 disabled 的差異:readonly 不降色(可讀),disabled 降色(弱化)。\n * 用於表單 readonly 呈現、DataTable cell 非編輯態。\n */\n readOnly?: boolean\n /**\n * Field mode(2026-05-05 Phase B3 align):\n * edit — 一般可互動 checkbox(預設)\n * view — **純展示**:渲染 Check / X icon(true=勾 / false=叉,無互動 primitive、無 input chrome);\n * 對齊 Carbon read-only / DataTable boolean cell 場景。取代既有 BooleanDisplay。\n * readonly — 同 readOnly prop:checkbox 視覺保留 + 鎖互動 + a11y readonly signal\n * disabled — 同 disabled prop:降色 + 鎖互動\n */\n mode?: FieldMode\n /**\n * Visual chrome — checkbox 本體無 input wrapper variant,本 prop 對 checkbox 主體無視覺影響;\n * 為對齊 Field 4-mode + chrome 透傳契約而保留(M19 一致性)。\n */\n variant?: FieldVariant\n}\n\n// ── Component ───────────────────────────────────────────────────────────────\n\nconst Checkbox = React.forwardRef<\n React.ElementRef<typeof CheckboxPrimitive.Root>,\n CheckboxProps\n>(\n (\n {\n className,\n size,\n label,\n icon,\n avatar,\n description,\n readOnly = false,\n disabled,\n mode,\n // chrome 對 Checkbox 主體無視覺影響(無 input wrapper)— 接收純為 prop 一致性;destructure 防 leak 到 DOM。\n variant: _chrome,\n // controllable checked triplet(2026-07-17 d26i0):於 wrapper 層取出,交由 useControllable 持有\n // resolved 值,供 edit / view / readonly 三分支共用。原本 view / readonly 分支各自讀 raw prop →\n // uncontrolled(只傳 defaultChecked)在 edit 態 toggle 後切 mode 會丟失目前值(Radix 內部狀態\n // wrapper 讀不到)。移出 props 後 edit 主路徑改由 checked={resolvedChecked} 受控(見下方 rootEl)。\n checked: checkedProp,\n defaultChecked: defaultCheckedProp,\n onCheckedChange,\n id: idProp,\n ...props\n },\n ref\n ) => {\n const sizeKey = size ?? 'md'\n const iconPx = checkIconSize[sizeKey]\n const iconStrokeWidth = checkStrokeWidth[sizeKey]\n\n // Field context:Checkbox 單獨塞進 Field(binary toggle)時,忽略自己的 label 讓 FieldLabel 接管\n // 2026-05-31 #35:hooks(useFieldContext / useContext / useId)必在任何 conditional return 前呼叫(Rules of Hooks)。\n // 原 mode='view' early return 寫在 hooks 之上 → runtime 切 mode 會 hook count 不一致 crash;已下移至 hooks 後。\n //\n // **例外**:Checkbox 是 CheckboxGroup 的 child 時(multi-select 情境),**每個 checkbox\n // 的 label 是它自己的選項名**,FieldLabel 只是群組名稱 — 此時 label **必須保留**,\n // 不能被 Field context 吞掉。AR50 的根因就是這個 branch 之前誤把 group 內的 checkbox\n // label 全清空,導致 sheet 內 3 個 checkbox 沒 label。\n const fieldCtx = useFieldContext()\n const checkboxGroupCtx = React.useContext(CheckboxGroupContext)\n const insideField = fieldCtx?.hasFieldWrapper === true\n const insideGroup = checkboxGroupCtx?.inGroup === true\n const shouldSuppressLabel = insideField && !insideGroup\n const effectiveLabel = shouldSuppressLabel ? undefined : label\n const effectiveDescription = shouldSuppressLabel ? undefined : description\n\n // Id 連結\n //\n // ── 2026-04-21 bug fix ──\n // 原本:`idProp ?? fieldCtx?.id ?? generatedId`。\n // 在 Field 內 fieldCtx.id 存在,CheckboxGroup 所有 children 共用同一個 id →\n // 每個 checkbox 的 `<label htmlFor={sameId}>` 全指向第一個 checkbox →\n // **點任何 label 都只開關第一個 checkbox(real bug)**。\n //\n // 修法:group 內的 checkbox 強制用 generatedId(唯一),不沿用 Field id;\n // solo in Field(binary toggle)才沿用 fieldCtx.id 讓 FieldLabel htmlFor 生效。\n const generatedId = React.useId()\n const inputId = idProp ?? (insideGroup ? generatedId : (fieldCtx?.id ?? generatedId))\n\n // 2026-06-08 SSOT cascade:disabled + mode 經 resolver hook(原 raw prop → <Field disabled>/<Field mode=\"view\"> 漏 cascade)\n const resolvedDisabled = useResolvedFieldDisabled(disabled)\n const resolvedMode = useResolvedFieldMode({ mode, disabled, readOnly })\n const effectiveReadOnly = readOnly || resolvedMode === 'readonly'\n // mode='disabled'(如 DataTable disabled cell 直傳)必須落到真 disabled chrome —\n // 2026-06-12 修:原本只看 resolvedDisabled(prop/fieldCtx),mode='disabled' 無人消費\n // → disabled boolean cell 渲出可 focus 的正常外觀 checkbox(違 field-controls.spec L286)\n const effectiveDisabled = resolvedDisabled || resolvedMode === 'disabled'\n // readonly 灰框 size:走 SSOT resolver(prop > ctx > 'md',field-context.ts:150-161)\n const resolvedBoxSize = useResolvedFieldSize(size ?? undefined, 'md') as 'sm' | 'md' | 'lg'\n\n // 2026-07-04 修:view / readonly-in-Field 分支的裸 span/div 原丟棄剩餘 props\n // (id/data-*/aria-*)且不轉發 forwardRef ref → consumer 的 aria-label 等靜默失效。\n // 抽出 Radix 專屬 form props(required/name/value 不可落到裸 span/div),其餘 DOM props 於兩分支\n // spread 轉發(與 Switch/RadioGroup 同修);edit 主路徑不受影響(rootEl 仍 spread 完整 props)。\n // (checked/defaultChecked/onCheckedChange 已於上方 top-level destructure 取出交 useControllable;\n // asChild/children 已從 CheckboxProps 型別 Omit — 固定 anatomy 非 Slot host,見型別註解。)\n const {\n required: _required,\n name: _name,\n value: _value,\n ...restDomProps\n } = props\n\n // ── Resolved checked SSOT(controlled / uncontrolled dual-mode,2026-07-17 d26i0)──────────\n // 對齊 Input / Textarea 既有 canonical(field-controls.spec.md「有值判定」:useControllable 內部\n // resolved value + form.reset bridge)。修 view / readonly 分支原各自讀 raw prop 導致「uncontrolled\n // 切 mode 丟失目前值」的 dual-mode 缺口。controlled(傳 checked)時 useControllable 純 passthrough,\n // edit 行為 Δ=0。\n const isControlled = checkedProp !== undefined\n const [resolvedChecked, setResolvedChecked] = useControllable<boolean | 'indeterminate'>({\n value: checkedProp,\n defaultValue: defaultCheckedProp ?? false,\n })\n\n // form.reset() bridge(uncontrolled only):wrapper 取得 controllable 主導後,HTML 標準 reset 不發\n // 變更事件 → 手動把 resolvedChecked 歸位 defaultChecked(對齊 input.tsx 同名 bridge)。keyed on\n // resolvedMode:切回 edit 重掛 native Root 時 effect 重跑補掛 listener;view / readonly(Root\n // unmount)時 innerRef 為 null → early return 不掛。\n const innerRef = React.useRef<HTMLButtonElement | null>(null)\n React.useEffect(() => {\n if (isControlled) return\n const form = innerRef.current?.form\n if (!form) return\n const handleReset = () => setResolvedChecked(defaultCheckedProp ?? false)\n form.addEventListener('reset', handleReset)\n return () => form.removeEventListener('reset', handleReset)\n }, [isControlled, resolvedMode, defaultCheckedProp, setResolvedChecked])\n // Merge refs(input.tsx setRef idiom):innerRef 供 reset bridge 取 form,同時轉發 forwardRef ref。\n const setRef = React.useCallback((el: HTMLButtonElement | null) => {\n innerRef.current = el\n if (typeof ref === 'function') ref(el)\n else if (ref) (ref as React.MutableRefObject<HTMLButtonElement | null>).current = el\n }, [ref])\n\n // ── mode='view'(下移至所有 hooks 之後,per #35 Rules of Hooks)──────────\n // 純展示模式:無互動 primitive、渲染 Check / X icon(true=勾 / false=叉,中性 foreground 色)。\n // boolean 值展示符號 SSOT = SelectionControl/boolean-value.tsx(勾/叉 icon + 中性色 + M22 世界級對照)。取代 BooleanDisplay。\n if (resolvedMode === 'view') {\n const isChecked = resolvedChecked === true\n // a11y(2026-07-17 d10i2):BooleanValueIcon 帶 aria-hidden → 裸 span 在 a11y tree 無名,\n // 布林值對螢幕閱讀器完全消失。給 view 容器 role=\"img\" + aria-label 承載值語意(對齊 MUI X\n // DataGrid boolean cell 的 icon role/label 世界級做法);consumer 若自帶 aria-label 則優先。\n const viewAriaLabel = (restDomProps as { 'aria-label'?: string })['aria-label'] ?? (isChecked ? '是' : '否')\n return (\n <span\n {...restDomProps}\n ref={ref as React.Ref<HTMLSpanElement>}\n role=\"img\"\n aria-label={viewAriaLabel}\n className=\"inline-flex\"\n >\n <BooleanValueIcon checked={isChecked} size={resolvedBoxSize} />\n </span>\n )\n }\n\n // ── mode='readonly' in Field(2026-06-12 拍板「灰框 + 勾/叉」;2026-07-09 ✓/— glyph → Check/X icon)─\n // Field 內 readonly boolean = readonly 灰框 chrome + view 同款值語言(勾/叉 icon)。\n // 灰框消費 fieldWrapperStyles 同一 cva = 與 Input readonly 字面同源(SSOT,改一處全動)。\n // 理由:同一張 readonly 表單裡文字控件有 bg-readonly 灰框鎖定訊號,boolean 保留全彩\n // 控件會誤導「仍可操作」(世界級 0/4 用原樣鎖互動:Salesforce=✓ 靜態 glyph /\n // SAP=靜態文字 / Atlassian=readView / Ant Pro=文字)。\n // Scope:僅 Field 內且無 inline label(FieldLabel 接管 label 的表單欄位場景);\n // standalone readOnly(settings list / SelectionItem row)維持原樣鎖互動不變。\n if (effectiveReadOnly && insideField && effectiveLabel == null) {\n const isChecked = resolvedChecked === true\n const boxSize = resolvedBoxSize\n return (\n <div\n {...(restDomProps as React.HTMLAttributes<HTMLDivElement>)}\n ref={ref as React.Ref<HTMLDivElement>}\n role=\"checkbox\"\n aria-checked={isChecked}\n aria-readonly=\"true\"\n aria-labelledby={fieldCtx?.labelId}\n aria-invalid={fieldCtx?.invalid || undefined}\n data-readonly=\"true\"\n tabIndex={0}\n className={cn(\n fieldWrapperStyles({ size: boxSize, mode: 'readonly', variant: 'default' }),\n 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring',\n className,\n )}\n >\n <BooleanValueIcon checked={isChecked} size={boxSize} />\n </div>\n )\n }\n\n const rootEl = (\n <CheckboxPrimitive.Root\n id={inputId}\n ref={setRef}\n disabled={effectiveDisabled}\n // controllable checked SSOT(2026-07-17 d26i0):Root 受 wrapper resolvedChecked 主導,\n // onCheckedChange 先同步 wrapper 內部 state 再 forward consumer → uncontrolled(只傳\n // defaultChecked)toggle 後切 mode(view / readonly)仍保留目前值。controlled(傳 checked)\n // 時 useControllable 純 passthrough,edit 行為 Δ=0。\n checked={resolvedChecked}\n onCheckedChange={(next) => {\n setResolvedChecked(next)\n onCheckedChange?.(next)\n }}\n aria-readonly={effectiveReadOnly || undefined}\n data-readonly={effectiveReadOnly || undefined}\n tabIndex={effectiveReadOnly ? -1 : undefined}\n aria-describedby={fieldCtx?.descriptionId}\n className={cn(checkboxVariants({ size }), className)}\n {...props}\n // 2026-07-05 D4 修:readOnly 鎖互動補 label 繞道 — <label htmlFor> 的 synthetic click\n // 不是 pointer event,cva pointer-events-none 攔不到,點 label 文字仍 toggle。\n // preventDefault 讓 Radix composeEventHandlers(checkForDefaultPrevented)跳過內部 toggle;\n // 必列在 {...props} 之後才不被 consumer onClick 覆蓋(readOnly 時 consumer onClick 一併鎖,\n // 與 pointer-events-none 擋直接點擊的行為一致)。\n onClick={(event) => {\n if (effectiveReadOnly) {\n event.preventDefault()\n return\n }\n props.onClick?.(event)\n }}\n >\n {/* 圖示切換走 Radix data-state(CSS),非 props.checked(JS)—— uncontrolled\n defaultChecked=\"indeterminate\" 時 props.checked=undefined,JS 判斷會錯渲 Check;\n data-state 涵蓋 controlled/uncontrolled 全部轉換(Radix 為 SSOT)。 */}\n <CheckboxPrimitive.Indicator className=\"group/indicator grid place-content-center text-current\">\n <Minus className=\"hidden group-data-[state=indeterminate]/indicator:block\" style={{ width: iconPx, height: iconPx }} strokeWidth={iconStrokeWidth} />\n <Check className=\"hidden group-data-[state=checked]/indicator:block\" style={{ width: iconPx, height: iconPx }} strokeWidth={iconStrokeWidth} />\n </CheckboxPrimitive.Indicator>\n </CheckboxPrimitive.Root>\n )\n\n // 無 label → 只渲染 checkbox 本體\n if (effectiveLabel == null) return rootEl\n\n // 有 label → 透過 SelectionItem 包裝(control 在左、label+description 在右)\n return (\n <SelectionItem\n control={rootEl}\n label={effectiveLabel}\n description={effectiveDescription}\n icon={icon}\n avatar={avatar}\n htmlFor={inputId}\n disabled={effectiveDisabled}\n size={sizeKey}\n />\n )\n }\n)\nCheckbox.displayName = CheckboxPrimitive.Root.displayName\n\n// Story auto-compile metadata — Phase 1 mechanical migration(2026-04-24)\n// Phase 2 fill needed: purpose descriptions + when rationale + world-class refs\nexport const checkboxMeta = {\n component: 'Checkbox',\n family: null, // self-contained primitive(對齊 spec frontmatter self-contained + body L31;非 Family 4)\n variants: {\n\n },\n sizes: {\n // 2026-06-10 修 stale meta:iconSize 對齊 checkIconSize 真值(L49 sm/md=12, lg=16;deep-audit A.1b 抓 metadata drift)\n sm: { fieldHeight: 28, iconSize: 12, typography: 'body' },\n md: { fieldHeight: 32, iconSize: 12, typography: 'body' },\n lg: { fieldHeight: 36, iconSize: 16, typography: 'body-lg' },\n },\n // 'checked' = 勾選持續態(bg-primary;2026-07-07 meta 詞彙統一補列,含 indeterminate 同族)\n states: ['default', 'hover', 'checked', 'focus-visible', 'disabled'],\n tokens: {\n bg: ['bg-disabled', 'bg-primary', 'bg-primary-hover', 'bg-surface'],\n fg: ['text-fg-disabled', 'text-fg-secondary', 'text-foreground'],\n ring: ['ring-ring'],\n },\n defaultSize: 'md',\n} as const\n\nexport { Checkbox, checkboxVariants }\n"],"names":[],"mappings":"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"}