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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (477) hide show
  1. package/AGENTS.md +210 -0
  2. package/CLAUDE.md +17 -191
  3. package/cli-init.mjs +30 -2
  4. package/dist/components/Accordion/accordion.d.ts +2 -2
  5. package/dist/components/Accordion/accordion.d.ts.map +1 -1
  6. package/dist/components/Accordion/accordion.js.map +1 -1
  7. package/dist/components/Avatar/avatar.js +1 -1
  8. package/dist/components/Avatar/avatar.js.map +1 -1
  9. package/dist/components/BulkActionBar/bulk-action-bar.d.ts +1 -1
  10. package/dist/components/BulkActionBar/bulk-action-bar.d.ts.map +1 -1
  11. package/dist/components/BulkActionBar/bulk-action-bar.js +3 -3
  12. package/dist/components/BulkActionBar/bulk-action-bar.js.map +1 -1
  13. package/dist/components/Button/button-group.d.ts +2 -2
  14. package/dist/components/Button/button-group.d.ts.map +1 -1
  15. package/dist/components/Button/button-group.js +2 -2
  16. package/dist/components/Button/button-group.js.map +1 -1
  17. package/dist/components/Button/button.d.ts +2 -1
  18. package/dist/components/Button/button.d.ts.map +1 -1
  19. package/dist/components/Button/button.js +1 -1
  20. package/dist/components/Button/button.js.map +1 -1
  21. package/dist/components/Carousel/carousel.d.ts +14 -8
  22. package/dist/components/Carousel/carousel.d.ts.map +1 -1
  23. package/dist/components/Carousel/carousel.js +64 -37
  24. package/dist/components/Carousel/carousel.js.map +1 -1
  25. package/dist/components/Checkbox/checkbox-group.d.ts.map +1 -1
  26. package/dist/components/Checkbox/checkbox-group.js +21 -14
  27. package/dist/components/Checkbox/checkbox-group.js.map +1 -1
  28. package/dist/components/Checkbox/checkbox.d.ts +2 -2
  29. package/dist/components/Checkbox/checkbox.d.ts.map +1 -1
  30. package/dist/components/Checkbox/checkbox.js +49 -10
  31. package/dist/components/Checkbox/checkbox.js.map +1 -1
  32. package/dist/components/Combobox/combobox.d.ts +15 -6
  33. package/dist/components/Combobox/combobox.d.ts.map +1 -1
  34. package/dist/components/Combobox/combobox.js +13 -7
  35. package/dist/components/Combobox/combobox.js.map +1 -1
  36. package/dist/components/Command/command.d.ts.map +1 -1
  37. package/dist/components/Command/command.js +11 -2
  38. package/dist/components/Command/command.js.map +1 -1
  39. package/dist/components/DataTable/cell-registry.d.ts +5 -5
  40. package/dist/components/DataTable/cell-registry.d.ts.map +1 -1
  41. package/dist/components/DataTable/cell-registry.js +41 -38
  42. package/dist/components/DataTable/cell-registry.js.map +1 -1
  43. package/dist/components/DataTable/column-types.d.ts +1 -17
  44. package/dist/components/DataTable/column-types.d.ts.map +1 -1
  45. package/dist/components/DataTable/column-types.js.map +1 -1
  46. package/dist/components/DataTable/data-table-column-visibility-panel.d.ts.map +1 -1
  47. package/dist/components/DataTable/data-table-column-visibility-panel.js +71 -64
  48. package/dist/components/DataTable/data-table-column-visibility-panel.js.map +1 -1
  49. package/dist/components/DataTable/data-table-filter-panel.d.ts +0 -2
  50. package/dist/components/DataTable/data-table-filter-panel.d.ts.map +1 -1
  51. package/dist/components/DataTable/data-table-filter-panel.js +3 -4
  52. package/dist/components/DataTable/data-table-filter-panel.js.map +1 -1
  53. package/dist/components/DataTable/data-table-interaction-layer.d.ts +2 -2
  54. package/dist/components/DataTable/data-table-interaction-layer.js.map +1 -1
  55. package/dist/components/DataTable/data-table-sort-manager.js +2 -2
  56. package/dist/components/DataTable/data-table-sort-manager.js.map +1 -1
  57. package/dist/components/DataTable/data-table.d.ts +1 -1
  58. package/dist/components/DataTable/data-table.d.ts.map +1 -1
  59. package/dist/components/DataTable/data-table.js +44 -32
  60. package/dist/components/DataTable/data-table.js.map +1 -1
  61. package/dist/components/DatePicker/date-picker.d.ts +3 -3
  62. package/dist/components/DatePicker/date-picker.js +4 -4
  63. package/dist/components/DatePicker/date-picker.js.map +1 -1
  64. package/dist/components/DescriptionList/description-list.d.ts +3 -3
  65. package/dist/components/DescriptionList/description-list.d.ts.map +1 -1
  66. package/dist/components/DescriptionList/description-list.js +6 -6
  67. package/dist/components/DescriptionList/description-list.js.map +1 -1
  68. package/dist/components/Dialog/dialog.d.ts +2 -2
  69. package/dist/components/Dialog/dialog.d.ts.map +1 -1
  70. package/dist/components/Dialog/dialog.js.map +1 -1
  71. package/dist/components/DropdownMenu/dropdown-menu.d.ts +8 -3
  72. package/dist/components/DropdownMenu/dropdown-menu.d.ts.map +1 -1
  73. package/dist/components/DropdownMenu/dropdown-menu.js +7 -7
  74. package/dist/components/DropdownMenu/dropdown-menu.js.map +1 -1
  75. package/dist/components/Field/field-context.d.ts +16 -10
  76. package/dist/components/Field/field-context.d.ts.map +1 -1
  77. package/dist/components/Field/field-context.js.map +1 -1
  78. package/dist/components/Field/field-types.d.ts +2 -2
  79. package/dist/components/Field/field-types.d.ts.map +1 -1
  80. package/dist/components/Field/field-types.js.map +1 -1
  81. package/dist/components/Field/field-wrapper.d.ts +20 -4
  82. package/dist/components/Field/field-wrapper.d.ts.map +1 -1
  83. package/dist/components/Field/field-wrapper.js +30 -31
  84. package/dist/components/Field/field-wrapper.js.map +1 -1
  85. package/dist/components/Field/field.d.ts +1 -1
  86. package/dist/components/Field/field.d.ts.map +1 -1
  87. package/dist/components/Field/field.js +25 -3
  88. package/dist/components/Field/field.js.map +1 -1
  89. package/dist/components/Field/use-form-validation.d.ts +1 -1
  90. package/dist/components/Field/use-form-validation.js.map +1 -1
  91. package/dist/components/FileItem/file-item.d.ts.map +1 -1
  92. package/dist/components/FileItem/file-item.js +2 -5
  93. package/dist/components/FileItem/file-item.js.map +1 -1
  94. package/dist/components/FileUpload/file-upload.d.ts.map +1 -1
  95. package/dist/components/FileUpload/file-upload.js +5 -1
  96. package/dist/components/FileUpload/file-upload.js.map +1 -1
  97. package/dist/components/FileViewer/file-viewer.d.ts.map +1 -1
  98. package/dist/components/FileViewer/file-viewer.js +2 -1
  99. package/dist/components/FileViewer/file-viewer.js.map +1 -1
  100. package/dist/components/HoverCard/hover-card.d.ts +2 -2
  101. package/dist/components/HoverCard/hover-card.d.ts.map +1 -1
  102. package/dist/components/HoverCard/hover-card.js +4 -2
  103. package/dist/components/HoverCard/hover-card.js.map +1 -1
  104. package/dist/components/InlineEdit/inline-edit.d.ts +23 -16
  105. package/dist/components/InlineEdit/inline-edit.d.ts.map +1 -1
  106. package/dist/components/InlineEdit/inline-edit.js +104 -62
  107. package/dist/components/InlineEdit/inline-edit.js.map +1 -1
  108. package/dist/components/Input/input.d.ts +1 -1
  109. package/dist/components/Input/input.d.ts.map +1 -1
  110. package/dist/components/Input/input.js +6 -6
  111. package/dist/components/Input/input.js.map +1 -1
  112. package/dist/components/LinkInput/link-input.d.ts +5 -5
  113. package/dist/components/LinkInput/link-input.d.ts.map +1 -1
  114. package/dist/components/LinkInput/link-input.js +3 -3
  115. package/dist/components/LinkInput/link-input.js.map +1 -1
  116. package/dist/components/Notice/notice.d.ts +17 -1
  117. package/dist/components/Notice/notice.d.ts.map +1 -1
  118. package/dist/components/Notice/notice.js +7 -1
  119. package/dist/components/Notice/notice.js.map +1 -1
  120. package/dist/components/NumberInput/number-input.d.ts +1 -1
  121. package/dist/components/NumberInput/number-input.d.ts.map +1 -1
  122. package/dist/components/NumberInput/number-input.js +12 -1
  123. package/dist/components/NumberInput/number-input.js.map +1 -1
  124. package/dist/components/PeoplePicker/avatar-stack-overflow.d.ts +1 -1
  125. package/dist/components/PeoplePicker/avatar-stack-overflow.js.map +1 -1
  126. package/dist/components/PeoplePicker/people-picker.d.ts +5 -5
  127. package/dist/components/PeoplePicker/people-picker.d.ts.map +1 -1
  128. package/dist/components/PeoplePicker/people-picker.js +7 -5
  129. package/dist/components/PeoplePicker/people-picker.js.map +1 -1
  130. package/dist/components/PeoplePicker/person-display.d.ts +1 -1
  131. package/dist/components/PeoplePicker/person-display.js.map +1 -1
  132. package/dist/components/ProfileCard/profile-card.d.ts +1 -1
  133. package/dist/components/ProfileCard/profile-card.js.map +1 -1
  134. package/dist/components/RadioGroup/radio-group.d.ts +3 -3
  135. package/dist/components/RadioGroup/radio-group.d.ts.map +1 -1
  136. package/dist/components/RadioGroup/radio-group.js +1 -1
  137. package/dist/components/RadioGroup/radio-group.js.map +1 -1
  138. package/dist/components/Rating/rating.d.ts.map +1 -1
  139. package/dist/components/Rating/rating.js +4 -0
  140. package/dist/components/Rating/rating.js.map +1 -1
  141. package/dist/components/ScrollArea/scroll-area.d.ts +3 -2
  142. package/dist/components/ScrollArea/scroll-area.d.ts.map +1 -1
  143. package/dist/components/ScrollArea/scroll-area.js.map +1 -1
  144. package/dist/components/SegmentedControl/segmented-control.d.ts +4 -3
  145. package/dist/components/SegmentedControl/segmented-control.d.ts.map +1 -1
  146. package/dist/components/SegmentedControl/segmented-control.js.map +1 -1
  147. package/dist/components/Select/select.d.ts +16 -6
  148. package/dist/components/Select/select.d.ts.map +1 -1
  149. package/dist/components/Select/select.js +17 -5
  150. package/dist/components/Select/select.js.map +1 -1
  151. package/dist/components/SelectMenu/select-menu.d.ts +17 -1
  152. package/dist/components/SelectMenu/select-menu.d.ts.map +1 -1
  153. package/dist/components/SelectMenu/select-menu.js +17 -3
  154. package/dist/components/SelectMenu/select-menu.js.map +1 -1
  155. package/dist/components/SelectionControl/boolean-value.d.ts +1 -1
  156. package/dist/components/SelectionControl/boolean-value.js.map +1 -1
  157. package/dist/components/Sheet/sheet.d.ts +2 -2
  158. package/dist/components/Sheet/sheet.d.ts.map +1 -1
  159. package/dist/components/Sheet/sheet.js.map +1 -1
  160. package/dist/components/Sidebar/sidebar.d.ts +1 -1
  161. package/dist/components/Sidebar/sidebar.js +2 -2
  162. package/dist/components/Sidebar/sidebar.js.map +1 -1
  163. package/dist/components/Slider/slider.d.ts.map +1 -1
  164. package/dist/components/Slider/slider.js +6 -3
  165. package/dist/components/Slider/slider.js.map +1 -1
  166. package/dist/components/Steps/steps.d.ts +6 -4
  167. package/dist/components/Steps/steps.d.ts.map +1 -1
  168. package/dist/components/Steps/steps.js +19 -5
  169. package/dist/components/Steps/steps.js.map +1 -1
  170. package/dist/components/Switch/switch.d.ts +4 -4
  171. package/dist/components/Switch/switch.d.ts.map +1 -1
  172. package/dist/components/Switch/switch.js +3 -1
  173. package/dist/components/Switch/switch.js.map +1 -1
  174. package/dist/components/Tabs/tabs.d.ts +1 -1
  175. package/dist/components/Tabs/tabs.d.ts.map +1 -1
  176. package/dist/components/Tabs/tabs.js +73 -23
  177. package/dist/components/Tabs/tabs.js.map +1 -1
  178. package/dist/components/Tag/tag.d.ts +3 -1
  179. package/dist/components/Tag/tag.d.ts.map +1 -1
  180. package/dist/components/Tag/tag.js +2 -2
  181. package/dist/components/Tag/tag.js.map +1 -1
  182. package/dist/components/Textarea/textarea.d.ts +4 -4
  183. package/dist/components/Textarea/textarea.d.ts.map +1 -1
  184. package/dist/components/Textarea/textarea.js +15 -15
  185. package/dist/components/Textarea/textarea.js.map +1 -1
  186. package/dist/components/TimePicker/time-picker.d.ts +4 -4
  187. package/dist/components/TimePicker/time-picker.d.ts.map +1 -1
  188. package/dist/components/TimePicker/time-picker.js +8 -5
  189. package/dist/components/TimePicker/time-picker.js.map +1 -1
  190. package/dist/components/Tooltip/tooltip.d.ts.map +1 -1
  191. package/dist/components/Tooltip/tooltip.js +27 -23
  192. package/dist/components/Tooltip/tooltip.js.map +1 -1
  193. package/dist/components/TreeView/tree-view.d.ts.map +1 -1
  194. package/dist/components/TreeView/tree-view.js +16 -0
  195. package/dist/components/TreeView/tree-view.js.map +1 -1
  196. package/dist/index.d.ts +7 -7
  197. package/dist/index.d.ts.map +1 -1
  198. package/dist/index.js +6 -13
  199. package/dist/index.js.map +1 -1
  200. package/dist/lib/drag-visual.d.ts +2 -2
  201. package/dist/lib/drag-visual.js.map +1 -1
  202. package/dist/lib/utils.d.ts +1 -0
  203. package/dist/lib/utils.d.ts.map +1 -1
  204. package/dist/lib/utils.js +21 -1
  205. package/dist/lib/utils.js.map +1 -1
  206. package/dist/patterns/element-anatomy/item-anatomy.d.ts +2 -0
  207. package/dist/patterns/element-anatomy/item-anatomy.d.ts.map +1 -1
  208. package/dist/patterns/element-anatomy/item-anatomy.js +2 -2
  209. package/dist/patterns/element-anatomy/item-anatomy.js.map +1 -1
  210. package/dist/tokens/categorical-color.d.ts +3 -3
  211. package/dist/tokens/categorical-color.d.ts.map +1 -1
  212. package/dist/tokens/categorical-color.js +2 -2
  213. package/dist/tokens/categorical-color.js.map +1 -1
  214. package/ds-canonical/fork/AGENTS.md +213 -0
  215. package/ds-canonical/fork/codex/agents/skills/independent-review/SKILL.md +33 -0
  216. package/ds-canonical/fork/codex/hooks.json +16 -0
  217. package/ds-canonical/fork/governance.lock +22 -7
  218. package/ds-canonical/fork/hooks/check_ds_anchor_preflight.sh +22 -1
  219. package/ds-canonical/fork/hooks/inject_deploy_url_after_push.sh +14 -2
  220. package/ds-canonical/fork/manifest.json +10 -1
  221. package/ds-canonical/fork/preamble.md +3 -2
  222. package/ds-canonical/fork/skills/product-ui-audit/SKILL.md +7 -7
  223. package/ds-canonical/fork/skills/ux-audit/SKILL.md +1 -1
  224. package/ds-canonical/fork/skills/visual-audit/SKILL.md +0 -1
  225. package/ds-canonical/hooks/check_codex_brief_invariants.sh +9 -3
  226. package/ds-canonical/hooks/check_ds_anchor_preflight.sh +22 -1
  227. package/ds-canonical/hooks/check_overlay_open_focus_escape_probe.sh +11 -2
  228. package/ds-canonical/hooks/check_plugin_fork_health.sh +17 -1
  229. package/ds-canonical/hooks/check_post_main_ssot_propagate.sh +21 -4
  230. package/ds-canonical/hooks/inject_deploy_url_after_push.sh +14 -2
  231. package/ds-canonical/hooks/session_start_governance_check.sh +10 -2
  232. package/ds-canonical/references/certified-surfaces.md +20 -0
  233. package/ds-canonical/references/preflight-gate-baseline.json +5 -0
  234. package/ds-canonical/references/principle-dim-map.json +20 -2
  235. package/ds-canonical/references/props-naming.md +2 -1
  236. package/ds-canonical/references/ssot-index.md +2 -2
  237. package/ds-canonical/references/story-baseline-registry.json +2 -2
  238. package/ds-canonical/rules/story-rules.md +1 -1
  239. package/ds-canonical/rules/ui-development.md +2 -1
  240. package/ds-canonical/skills/codex-collab/SKILL.md +7 -7
  241. package/ds-canonical/skills/codex-collab/references/brief-template.md +5 -1
  242. package/ds-canonical/skills/component-quality-gate/SKILL.md +1 -1
  243. package/ds-canonical/skills/deep-audit-cross-codex/SKILL.md +5 -5
  244. package/ds-canonical/skills/design-system-audit/SKILL.md +5 -5
  245. package/ds-canonical/skills/product-ui-audit/SKILL.md +7 -7
  246. package/ds-canonical/skills/ux-audit/SKILL.md +1 -1
  247. package/ds-canonical/skills/visual-audit/SKILL.md +0 -1
  248. package/ds-story-manifest.json +21 -21
  249. package/llms-full.txt +3 -3
  250. package/llms.txt +1 -1
  251. package/package.json +2 -1
  252. package/src/components/Accordion/accordion.anatomy.stories.tsx +11 -11
  253. package/src/components/Accordion/accordion.principles.stories.tsx +21 -6
  254. package/src/components/Accordion/accordion.spec.md +10 -1
  255. package/src/components/Accordion/accordion.stories.tsx +10 -5
  256. package/src/components/Accordion/accordion.tsx +7 -2
  257. package/src/components/Alert/alert.principles.stories.tsx +2 -8
  258. package/src/components/AppShell/app-shell.anatomy.stories.tsx +19 -13
  259. package/src/components/AppShell/app-shell.principles.stories.tsx +21 -21
  260. package/src/components/AppShell/app-shell.spec.md +4 -0
  261. package/src/components/AppShell/app-shell.stories.tsx +4 -5
  262. package/src/components/Avatar/avatar.tsx +1 -1
  263. package/src/components/Badge/badge.spec.md +2 -2
  264. package/src/components/Breadcrumb/breadcrumb.anatomy.stories.tsx +7 -3
  265. package/src/components/Breadcrumb/breadcrumb.principles.stories.tsx +4 -1
  266. package/src/components/Breadcrumb/breadcrumb.stories.tsx +1 -1
  267. package/src/components/BulkActionBar/bulk-action-bar.anatomy.stories.tsx +6 -6
  268. package/src/components/BulkActionBar/bulk-action-bar.principles.stories.tsx +18 -6
  269. package/src/components/BulkActionBar/bulk-action-bar.spec.md +15 -5
  270. package/src/components/BulkActionBar/bulk-action-bar.stories.tsx +8 -24
  271. package/src/components/BulkActionBar/bulk-action-bar.tsx +4 -4
  272. package/src/components/Button/button-group.tsx +4 -4
  273. package/src/components/Button/button.anatomy.stories.tsx +26 -7
  274. package/src/components/Button/button.principles.stories.tsx +1 -1
  275. package/src/components/Button/button.spec.md +2 -2
  276. package/src/components/Button/button.stories.tsx +22 -28
  277. package/src/components/Button/button.tsx +2 -1
  278. package/src/components/Calendar/calendar.anatomy.stories.tsx +1 -1
  279. package/src/components/Calendar/calendar.spec.md +1 -1
  280. package/src/components/Carousel/carousel.spec.md +17 -1
  281. package/src/components/Carousel/carousel.tsx +93 -42
  282. package/src/components/Checkbox/checkbox-group.tsx +26 -15
  283. package/src/components/Checkbox/checkbox.anatomy.stories.tsx +3 -3
  284. package/src/components/Checkbox/checkbox.spec.md +12 -3
  285. package/src/components/Checkbox/checkbox.stories.tsx +4 -4
  286. package/src/components/Checkbox/checkbox.tsx +79 -19
  287. package/src/components/Coachmark/coachmark.anatomy.stories.tsx +5 -5
  288. package/src/components/Coachmark/coachmark.principles.stories.tsx +1 -1
  289. package/src/components/Combobox/combobox.anatomy.stories.tsx +11 -4
  290. package/src/components/Combobox/combobox.principles.stories.tsx +10 -11
  291. package/src/components/Combobox/combobox.spec.md +6 -2
  292. package/src/components/Combobox/combobox.stories.tsx +9 -36
  293. package/src/components/Combobox/combobox.tsx +42 -27
  294. package/src/components/Command/command.spec.md +3 -3
  295. package/src/components/Command/command.tsx +9 -2
  296. package/src/components/DataTable/cell-registry.tsx +75 -76
  297. package/src/components/DataTable/column-types.ts +4 -17
  298. package/src/components/DataTable/data-table-column-visibility-panel.tsx +20 -7
  299. package/src/components/DataTable/data-table-filter-panel.tsx +6 -6
  300. package/src/components/DataTable/data-table-interaction-layer.tsx +3 -3
  301. package/src/components/DataTable/data-table-sort-manager.tsx +2 -2
  302. package/src/components/DataTable/data-table.anatomy.stories.tsx +25 -24
  303. package/src/components/DataTable/data-table.css +2 -3
  304. package/src/components/DataTable/data-table.principles.stories.tsx +17 -11
  305. package/src/components/DataTable/data-table.spec.md +7 -7
  306. package/src/components/DataTable/data-table.stories.tsx +39 -29
  307. package/src/components/DataTable/data-table.tsx +90 -67
  308. package/src/components/DataTable/filter-operators.spec.md +2 -0
  309. package/src/components/DateGrid/date-grid.spec.md +1 -1
  310. package/src/components/DatePicker/date-picker.anatomy.stories.tsx +9 -9
  311. package/src/components/DatePicker/date-picker.principles.stories.tsx +4 -4
  312. package/src/components/DatePicker/date-picker.spec.md +9 -9
  313. package/src/components/DatePicker/date-picker.stories.tsx +9 -9
  314. package/src/components/DatePicker/date-picker.tsx +13 -13
  315. package/src/components/DescriptionList/description-list.anatomy.stories.tsx +5 -5
  316. package/src/components/DescriptionList/description-list.principles.stories.tsx +2 -2
  317. package/src/components/DescriptionList/description-list.spec.md +12 -12
  318. package/src/components/DescriptionList/description-list.stories.tsx +3 -3
  319. package/src/components/DescriptionList/description-list.tsx +10 -10
  320. package/src/components/Dialog/dialog.tsx +4 -1
  321. package/src/components/DropdownMenu/dropdown-menu.tsx +27 -14
  322. package/src/components/Field/field-context.ts +17 -11
  323. package/src/components/Field/field-controls.spec.md +44 -37
  324. package/src/components/Field/field-types.ts +14 -12
  325. package/src/components/Field/field-wrapper.tsx +59 -43
  326. package/src/components/Field/field.anatomy.stories.tsx +6 -6
  327. package/src/components/Field/field.principles.stories.tsx +5 -28
  328. package/src/components/Field/field.spec.md +4 -4
  329. package/src/components/Field/field.stories.tsx +103 -72
  330. package/src/components/Field/field.tsx +36 -4
  331. package/src/components/Field/form-validation.spec.md +2 -0
  332. package/src/components/Field/use-form-validation.ts +1 -1
  333. package/src/components/FieldControlGroup/field-control-group.principles.stories.tsx +3 -3
  334. package/src/components/FieldControlGroup/field-control-group.spec.md +3 -3
  335. package/src/components/FileItem/file-item.tsx +10 -8
  336. package/src/components/FileUpload/file-upload.principles.stories.tsx +7 -7
  337. package/src/components/FileUpload/file-upload.spec.md +3 -2
  338. package/src/components/FileUpload/file-upload.tsx +5 -1
  339. package/src/components/FileViewer/file-viewer.anatomy.stories.tsx +1 -1
  340. package/src/components/FileViewer/file-viewer.principles.stories.tsx +1 -1
  341. package/src/components/FileViewer/file-viewer.spec.md +1 -1
  342. package/src/components/FileViewer/file-viewer.stories.tsx +1 -1
  343. package/src/components/FileViewer/file-viewer.tsx +3 -2
  344. package/src/components/HoverCard/hover-card.anatomy.stories.tsx +6 -4
  345. package/src/components/HoverCard/hover-card.principles.stories.tsx +6 -1
  346. package/src/components/HoverCard/hover-card.spec.md +2 -2
  347. package/src/components/HoverCard/hover-card.tsx +4 -2
  348. package/src/components/InlineEdit/inline-edit.anatomy.stories.tsx +184 -0
  349. package/src/components/InlineEdit/inline-edit.principles.stories.tsx +142 -0
  350. package/src/components/InlineEdit/inline-edit.spec.md +71 -46
  351. package/src/components/InlineEdit/inline-edit.stories.tsx +20 -19
  352. package/src/components/InlineEdit/inline-edit.tsx +211 -125
  353. package/src/components/Input/input.anatomy.stories.tsx +15 -15
  354. package/src/components/Input/input.principles.stories.tsx +3 -3
  355. package/src/components/Input/input.spec.md +4 -4
  356. package/src/components/Input/input.stories.tsx +3 -3
  357. package/src/components/Input/input.tsx +13 -13
  358. package/src/components/LinkInput/link-input.anatomy.stories.tsx +2 -2
  359. package/src/components/LinkInput/link-input.principles.stories.tsx +1 -1
  360. package/src/components/LinkInput/link-input.spec.md +6 -2
  361. package/src/components/LinkInput/link-input.stories.tsx +8 -8
  362. package/src/components/LinkInput/link-input.tsx +21 -19
  363. package/src/components/Notice/notice.principles.stories.tsx +1 -1
  364. package/src/components/Notice/notice.tsx +8 -2
  365. package/src/components/NumberInput/number-input.anatomy.stories.tsx +6 -6
  366. package/src/components/NumberInput/number-input.principles.stories.tsx +4 -4
  367. package/src/components/NumberInput/number-input.spec.md +6 -6
  368. package/src/components/NumberInput/number-input.stories.tsx +4 -4
  369. package/src/components/NumberInput/number-input.tsx +17 -5
  370. package/src/components/OverflowIndicator/overflow-indicator.anatomy.stories.tsx +8 -4
  371. package/src/components/OverflowIndicator/overflow-indicator.principles.stories.tsx +6 -7
  372. package/src/components/OverflowIndicator/overflow-indicator.spec.md +1 -1
  373. package/src/components/OverflowIndicator/overflow-indicator.stories.tsx +6 -30
  374. package/src/components/PeoplePicker/avatar-stack-overflow.ts +1 -1
  375. package/src/components/PeoplePicker/people-picker.anatomy.stories.tsx +13 -10
  376. package/src/components/PeoplePicker/people-picker.principles.stories.tsx +5 -1
  377. package/src/components/PeoplePicker/people-picker.stories.tsx +5 -5
  378. package/src/components/PeoplePicker/people-picker.tsx +39 -20
  379. package/src/components/PeoplePicker/person-display.tsx +3 -3
  380. package/src/components/Popover/popover.anatomy.stories.tsx +10 -7
  381. package/src/components/Popover/popover.principles.stories.tsx +49 -28
  382. package/src/components/Popover/popover.spec.md +4 -2
  383. package/src/components/ProfileCard/profile-card.anatomy.stories.tsx +1 -1
  384. package/src/components/ProfileCard/profile-card.tsx +1 -1
  385. package/src/components/ProgressBar/progress-bar.anatomy.stories.tsx +14 -13
  386. package/src/components/ProgressBar/progress-bar.principles.stories.tsx +8 -23
  387. package/src/components/ProgressBar/progress-bar.stories.tsx +8 -2
  388. package/src/components/README.md +1 -0
  389. package/src/components/RadioGroup/radio-group.anatomy.stories.tsx +8 -3
  390. package/src/components/RadioGroup/radio-group.stories.tsx +3 -3
  391. package/src/components/RadioGroup/radio-group.tsx +19 -14
  392. package/src/components/Rating/rating.anatomy.stories.tsx +1 -1
  393. package/src/components/Rating/rating.principles.stories.tsx +3 -3
  394. package/src/components/Rating/rating.spec.md +2 -2
  395. package/src/components/Rating/rating.stories.tsx +4 -18
  396. package/src/components/Rating/rating.tsx +5 -1
  397. package/src/components/ScrollArea/scroll-area.principles.stories.tsx +1 -1
  398. package/src/components/ScrollArea/scroll-area.spec.md +1 -1
  399. package/src/components/ScrollArea/scroll-area.tsx +5 -2
  400. package/src/components/SegmentedControl/segmented-control.principles.stories.tsx +4 -1
  401. package/src/components/SegmentedControl/segmented-control.spec.md +5 -10
  402. package/src/components/SegmentedControl/segmented-control.tsx +6 -3
  403. package/src/components/Select/select.anatomy.stories.tsx +13 -7
  404. package/src/components/Select/select.principles.stories.tsx +3 -3
  405. package/src/components/Select/select.spec.md +9 -1
  406. package/src/components/Select/select.stories.tsx +41 -27
  407. package/src/components/Select/select.tsx +44 -26
  408. package/src/components/SelectMenu/select-menu.anatomy.stories.tsx +44 -26
  409. package/src/components/SelectMenu/select-menu.principles.stories.tsx +0 -1
  410. package/src/components/SelectMenu/select-menu.stories.tsx +7 -19
  411. package/src/components/SelectMenu/select-menu.tsx +38 -5
  412. package/src/components/SelectionControl/boolean-value.tsx +2 -2
  413. package/src/components/SelectionControl/selection-item.anatomy.stories.tsx +1 -1
  414. package/src/components/SelectionControl/selection-item.principles.stories.tsx +4 -4
  415. package/src/components/Separator/separator.principles.stories.tsx +1 -1
  416. package/src/components/Sheet/sheet.principles.stories.tsx +5 -3
  417. package/src/components/Sheet/sheet.tsx +4 -1
  418. package/src/components/Sidebar/sidebar.tsx +1 -1
  419. package/src/components/Slider/slider.anatomy.stories.tsx +5 -4
  420. package/src/components/Slider/slider.principles.stories.tsx +6 -6
  421. package/src/components/Slider/slider.spec.md +13 -1
  422. package/src/components/Slider/slider.stories.tsx +44 -32
  423. package/src/components/Slider/slider.tsx +13 -4
  424. package/src/components/Steps/steps.anatomy.stories.tsx +7 -7
  425. package/src/components/Steps/steps.principles.stories.tsx +1 -1
  426. package/src/components/Steps/steps.spec.md +4 -2
  427. package/src/components/Steps/steps.stories.tsx +4 -56
  428. package/src/components/Steps/steps.tsx +31 -7
  429. package/src/components/Switch/switch.anatomy.stories.tsx +3 -3
  430. package/src/components/Switch/switch.principles.stories.tsx +1 -1
  431. package/src/components/Switch/switch.spec.md +4 -4
  432. package/src/components/Switch/switch.stories.tsx +3 -3
  433. package/src/components/Switch/switch.tsx +16 -10
  434. package/src/components/Tabs/tabs.anatomy.stories.tsx +6 -0
  435. package/src/components/Tabs/tabs.principles.stories.tsx +10 -2
  436. package/src/components/Tabs/tabs.spec.md +5 -2
  437. package/src/components/Tabs/tabs.stories.tsx +36 -44
  438. package/src/components/Tabs/tabs.tsx +101 -36
  439. package/src/components/Tag/tag.anatomy.stories.tsx +2 -2
  440. package/src/components/Tag/tag.principles.stories.tsx +4 -4
  441. package/src/components/Tag/tag.spec.md +3 -1
  442. package/src/components/Tag/tag.tsx +5 -3
  443. package/src/components/Textarea/textarea.anatomy.stories.tsx +6 -6
  444. package/src/components/Textarea/textarea.spec.md +5 -5
  445. package/src/components/Textarea/textarea.tsx +24 -24
  446. package/src/components/TimePicker/time-picker.anatomy.stories.tsx +12 -11
  447. package/src/components/TimePicker/time-picker.principles.stories.tsx +37 -44
  448. package/src/components/TimePicker/time-picker.spec.md +6 -5
  449. package/src/components/TimePicker/time-picker.stories.tsx +3 -3
  450. package/src/components/TimePicker/time-picker.tsx +24 -14
  451. package/src/components/Toast/toast.principles.stories.tsx +1 -1
  452. package/src/components/Toast/toast.stories.tsx +1 -1
  453. package/src/components/Tooltip/tooltip.anatomy.stories.tsx +15 -6
  454. package/src/components/Tooltip/tooltip.principles.stories.tsx +4 -4
  455. package/src/components/Tooltip/tooltip.spec.md +9 -3
  456. package/src/components/Tooltip/tooltip.stories.tsx +1 -1
  457. package/src/components/Tooltip/tooltip.tsx +12 -2
  458. package/src/components/TreeView/tree-view.anatomy.stories.tsx +16 -14
  459. package/src/components/TreeView/tree-view.principles.stories.tsx +44 -11
  460. package/src/components/TreeView/tree-view.spec.md +8 -6
  461. package/src/components/TreeView/tree-view.stories.tsx +9 -43
  462. package/src/components/TreeView/tree-view.tsx +36 -0
  463. package/src/index.ts +13 -10
  464. package/src/lib/drag-visual.ts +2 -2
  465. package/src/lib/utils.ts +19 -0
  466. package/src/patterns/element-anatomy/inline-action.spec.md +1 -1
  467. package/src/patterns/element-anatomy/item-anatomy.tsx +4 -2
  468. package/src/patterns/header-canonical/header-canonical.spec.md +9 -0
  469. package/src/patterns/header-canonical/header-canonical.stories.tsx +31 -5
  470. package/src/tokens/categorical-color.ts +5 -5
  471. package/src/tokens/color/color.spec.md +4 -4
  472. package/src/tokens/color/primitives.css +9 -2
  473. package/src/tokens/opacity/opacity.spec.md +1 -1
  474. package/src/tokens/orphan-tokens.spec.md +1 -1
  475. package/src/tokens/typography/typography.spec.md +2 -0
  476. package/src/tokens/uiSize/uiSize.css +5 -0
  477. package/src/tokens/uiSize/uiSize.spec.md +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"tabs.js","sources":["../../../src/components/Tabs/tabs.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.\n// code-quality-allow: file-size — foundational composite(Tabs + overflow scroll mode + dropdown switcher + inline action slot)在單一 wrapper SSOT 內,拆分會破壞 a11y / focus management chain。當前 515 < cap 800。\nimport * as React from 'react'\nimport * as TabsPrimitive from '@radix-ui/react-tabs'\nimport { cva } from 'class-variance-authority'\nimport type { LucideIcon } from 'lucide-react'\nimport { cn } from '@/lib/utils'\nimport {\n DropdownMenu,\n DropdownMenuTrigger,\n DropdownMenuContent,\n DropdownMenuItem,\n} from '@/design-system/components/DropdownMenu/dropdown-menu'\nimport {\n useScrollEdges,\n useScrollByPage,\n buildFadeMask,\n ARROW_BUTTON_WIDTH,\n OverflowScrollArrow,\n OverflowMenuTriggerButton,\n} from '@/design-system/patterns/horizontal-overflow/horizontal-overflow'\nimport { ICON_SIZE } from '@/design-system/tokens/uiSize/icon-size'\n\n/**\n * Tabs — 基於 Radix Tabs,橋接設計系統 token\n *\n * ── 定位 ──\n * 同一上下文底下切換平行的 view。切 view(不是切 value)。\n * 切 value 用 SegmentedControl;切路由用 navigation。\n *\n * ── Size ──\n * sm h-tab-sm(32/40),★ 預設(2026-05-17 從 md 改),overlay / chrome / dense\n * md h-tab-md(40/48),future tier 無 recommended use case\n * lg h-tab-lg(48/56),page-level hero / 獨立 tabs 取代 chrome header\n *\n * ── 寬度行為 ──\n * Trigger 寬度永遠由內容決定(hug content)。\n * Triggers 之間 gap 為 --layout-space-loose(16px / 24px lg density)。\n *\n * ── Trigger 結構 ──\n * [startIcon?] [label] [suffix: badge? + endIcon?]\n * slot 間 gap-2,suffix 內 gap-1\n *\n * ── Selected underline ──\n * 使用 ::after 絕對定位在 bottom: -1px,2px primary(2026-07-06 拍板:持續選中站 base),\n * 蓋住 TabsList 的 1px gray border(單一視覺線條,不雙線)。\n */\n\n// ── Size context ──\ntype TabsSize = 'sm' | 'md' | 'lg'\ntype TabsOverflow = 'none' | 'scroll' | 'menu'\n\ninterface TabsContextValue {\n size: TabsSize\n}\nconst TabsContext = React.createContext<TabsContextValue>({ size: 'md' })\n\n// ── Root ──\n// Wrap Radix Tabs 以支援 value/onValueChange 的 context pass-through\n// (menu 模式的 overflow items 需要能 proxy click 觸發同一個 onValueChange)\ninterface TabsProps extends React.ComponentPropsWithoutRef<typeof TabsPrimitive.Root> {}\n\nconst TabsValueContext = React.createContext<{\n value?: string\n onValueChange?: (value: string) => void\n}>({})\n\n\n// ── getElementRef(Radix Slot idiom;鏡射 chip.tsx 同名 helper — 2026-07-05 M10 同型修)──\n// 讀 child 既有 ref 而不觸發 React 18/19 dev deprecation warning。\nfunction getElementRef(element: React.ReactElement): React.Ref<HTMLElement> | undefined {\n let getter = Object.getOwnPropertyDescriptor(element.props, 'ref')?.get\n let mayWarn = getter && 'isReactWarning' in getter && getter.isReactWarning\n if (mayWarn) return (element as unknown as { ref?: React.Ref<HTMLElement> }).ref\n getter = Object.getOwnPropertyDescriptor(element, 'ref')?.get\n mayWarn = getter && 'isReactWarning' in getter && getter.isReactWarning\n if (mayWarn) return (element.props as { ref?: React.Ref<HTMLElement> }).ref\n return (\n (element.props as { ref?: React.Ref<HTMLElement> }).ref ??\n (element as unknown as { ref?: React.Ref<HTMLElement> }).ref ??\n undefined\n )\n}\n\n// 遞迴攤平 Fragment:React.Children.toArray 不展開 <>…</>(把 Fragment 當單一 element),\n// 導致 menu 模式 dropdown 漏掉被 Fragment 包起來的 trigger(如條件渲染的溢出 tabs)+ cloneElement\n// 對 Fragment 傳 ref 會噴 React warning。攤平後每個 keyless child 補 path-index key,對齊\n// React.Children.toArray 的自動 key 行為,避免 list key warning。\nfunction flattenTabTriggers(children: React.ReactNode, prefix = ''): React.ReactElement[] {\n const out: React.ReactElement[] = []\n React.Children.forEach(children, (child, i) => {\n if (!React.isValidElement(child)) return\n if (child.type === React.Fragment) {\n out.push(\n ...flattenTabTriggers((child.props as { children?: React.ReactNode }).children, `${prefix}${i}.`),\n )\n } else {\n out.push(child.key != null ? child : React.cloneElement(child, { key: `tab-${prefix}${i}` }))\n }\n })\n return out\n}\n\nconst Tabs = React.forwardRef<\n React.ElementRef<typeof TabsPrimitive.Root>,\n TabsProps\n>(({ value, onValueChange, defaultValue, children, ...props }, ref) => {\n // 內部維護一份 uncontrolled state,讓 overflow menu 的 proxy 有 onValueChange 可呼叫\n const [internalValue, setInternalValue] = React.useState<string | undefined>(defaultValue)\n const isControlled = value !== undefined\n const currentValue = isControlled ? value : internalValue\n\n const handleValueChange = React.useCallback(\n (next: string) => {\n if (!isControlled) setInternalValue(next)\n onValueChange?.(next)\n },\n [isControlled, onValueChange]\n )\n\n const valueContext = React.useMemo(\n () => ({ value: currentValue, onValueChange: handleValueChange }),\n [currentValue, handleValueChange]\n )\n\n return (\n <TabsValueContext.Provider value={valueContext}>\n <TabsPrimitive.Root\n ref={ref}\n value={currentValue}\n onValueChange={handleValueChange}\n {...props}\n >\n {children}\n </TabsPrimitive.Root>\n </TabsValueContext.Provider>\n )\n})\nTabs.displayName = 'Tabs'\n\n// ── List ──\n// TabsList 基礎 class — inline-flex 單列 + gap-loose + 底部 border-divider\n// 2026-05-18 改 border-border → border-divider(per user verbatim「我認為應該把 tabs 的\n// 下底線統一改成是 divider 色吧?」+「做」approval):\n// - 跟 Dialog / Sheet / Popover / Sidebar header `border-b border-divider`(neutral-4)同色\n// - withTabs scenario 下 tabs underline = chrome separator,跟 dialog 其他 separator 視覺一致\n// - Selected trigger 2px primary 仍 overlay underlying divider(對比 primary >> divider 不弱)\n// - 對齊 `color.spec.md`「T-junction connectivity 原則」段 outer-vs-divider 判準(Dialog 結構,T-junction 思路適用)\nconst TABS_LIST_BASE = [\n 'inline-flex items-stretch',\n 'gap-[var(--layout-space-loose)]',\n 'border-b border-divider',\n].join(' ')\n\ninterface TabsListProps\n extends React.ComponentPropsWithoutRef<typeof TabsPrimitive.List> {\n size?: TabsSize\n /**\n * Overflow 處理模式。詳見 tabs.spec.md 的 overflow 段。\n * 'none' ★ 預設,不處理,triggers 溢出父容器(適用 tabs 數量可控的情境)\n * 'scroll' 單行橫向滾動 + 邊緣 fade mask(Material / Polaris / iOS 作法)\n * 'menu' show-all navigator——全部 triggers 一直顯示在底層 overflow-x-auto 捲動容器內,\n * (Ant Design / Atlassian 作法)\n */\n overflow?: TabsOverflow\n}\n\nconst TabsList = React.forwardRef<\n React.ElementRef<typeof TabsPrimitive.List>,\n TabsListProps\n>(({ className, size = 'sm', overflow = 'none', children, ...props }, ref) => {\n const tabsSizeContext = React.useMemo(() => ({ size }), [size])\n if (overflow === 'scroll') {\n return (\n <TabsContext.Provider value={tabsSizeContext}>\n <ScrollTabsList ref={ref} className={className} {...props}>\n {children}\n </ScrollTabsList>\n </TabsContext.Provider>\n )\n }\n if (overflow === 'menu') {\n return (\n <TabsContext.Provider value={tabsSizeContext}>\n <MenuTabsList ref={ref} className={className} {...props}>\n {children}\n </MenuTabsList>\n </TabsContext.Provider>\n )\n }\n // none(預設)\n return (\n <TabsContext.Provider value={tabsSizeContext}>\n <TabsPrimitive.List\n ref={ref}\n className={cn(TABS_LIST_BASE, 'w-full', className)}\n {...props}\n >\n {children}\n </TabsPrimitive.List>\n </TabsContext.Provider>\n )\n})\nTabsList.displayName = 'TabsList'\n\n// ── Scroll mode ──\n//\n// 共同策略(對齊 Material 3 / Ant Design / Primer UnderlineNav 世界級作法):\n// - 容器 overflow-x-auto + overflow-y-hidden(真的可滾,鍵盤焦點可 scroll-into-view;\n// 明示 y-hidden 阻 CSS overflow-3 spec「一軸 auto 時另軸 visible compute auto」)\n// - border-b 在 TabsList 內部(TABS_LIST_BASE),不在 outer wrapper,避免\n// active underline `after:bottom:-1px` 跟 outer + overflow-x-auto 觸發 y promote bug\n// - mask / arrow / fade 全部從 horizontal-overflow pattern module 取得——\n// 參見 `patterns/horizontal-overflow/horizontal-overflow.spec.md`\n// - Menu 模式 = scroll 模式 + 額外的 ⌄ quick-jump button,點 menu item 同時\n// 觸發 onValueChange + scrollIntoView,讓使用者在 tab strip 看到選中的 tab\n// 單行水平滾動 + 邊緣 fade mask 延伸到 arrow button 底下 + 左右 always-visible arrows\n// - 鍵盤: Radix 原生方向鍵 + 瀏覽器 scroll-into-view\n// - Trackpad: 兩指橫向滑動\n// - 滑鼠滾輪: 點 arrow buttons (Shift+wheel 太隱晦)\n\nconst ScrollTabsList = React.forwardRef<\n React.ElementRef<typeof TabsPrimitive.List>,\n React.ComponentPropsWithoutRef<typeof TabsPrimitive.List>\n>(({ className, children, ...props }, ref) => {\n const { scrollRef, atStart, atEnd, canScroll } = useScrollEdges<HTMLDivElement>()\n const scrollByPage = useScrollByPage(scrollRef)\n const maskImage = buildFadeMask({\n canScroll,\n atStart,\n atEnd,\n reserveArrowWidth: ARROW_BUTTON_WIDTH,\n })\n\n return (\n // 2026-05-19 fix(scroll-overflow underline clip + y auto-promote):\n // outer 撤 `border-b` → owner 升到 `TabsList` (TABS_LIST_BASE 含 border-b border-divider)\n // 把 trigger `after:bottom-[-1px]` 2px underline 的下半部 1px 收進 list border-box,\n // 再加 `overflow-y-hidden` 明示阻 browser y auto-promote(CSS overflow-3 spec:\n // overflow-x:auto + overflow-y:visible 必 compute auto)。\n // 不加 `pb-px`(outer border 撤後 list border 已接 -1px 部分,加 pb 多 1px 多餘空白)。\n // 對齊 Primer UnderlineNav `overflow-x:auto; overflow-y:hidden` canonical 同步動\n // horizontal-overflow.spec.md(「Hook re-export」+「典型 scroll / menu 模式組裝」段)owner 升 list 內部。\n <div className=\"relative\">\n <div\n ref={scrollRef}\n className=\"overflow-x-auto overflow-y-hidden [scrollbar-width:none] [&::-webkit-scrollbar]:hidden\"\n style={{ maskImage, WebkitMaskImage: maskImage }}\n >\n <TabsPrimitive.List\n ref={ref}\n className={cn(TABS_LIST_BASE, 'min-w-full', className)}\n {...props}\n >\n {children}\n </TabsPrimitive.List>\n </div>\n {!atStart && canScroll && (\n <OverflowScrollArrow direction=\"left\" onClick={() => scrollByPage('left')} />\n )}\n {!atEnd && canScroll && (\n <OverflowScrollArrow direction=\"right\" onClick={() => scrollByPage('right')} />\n )}\n </div>\n )\n})\nScrollTabsList.displayName = 'ScrollTabsList'\n\n// ── Menu mode ──\n// Show-all navigator pattern (Chrome tab dropdown / VS Code editor tabs / Discord channel jumper):\n// - Menu 永遠顯示全部 tabs,active 的用 checked 標記 (單選語意)\n// - 點 menu item = onValueChange + scrollIntoView(center),把該 tab 捲到視圖中央\n// - Menu 內容穩定,跟 scroll 位置無關,使用者對「⌄ = navigator」的直覺一致\n//\n// 為什麼底層仍是 overflow-x-auto 而非 overflow-hidden:\n// - scrollIntoView 需要真實 scroll 容器\n// - 鍵盤 focus 也依賴真實 scroll 讓 browser auto scroll-into-view\n// - scrollbar 用 CSS 隱藏,視覺看不出來\n//\n// Fade mask 純視覺,軟化內容硬邊,跟 menu 機制正交 (兩者可並存)。\n// Menu button 出現條件: canScroll (有溢出空間才需要 navigator)。\n\n// code-quality-allow: long-function — foundational composite main body — 拆 sub-fn 會複雜化 local state / ref / context binding\nconst MenuTabsList = React.forwardRef<\n React.ElementRef<typeof TabsPrimitive.List>,\n React.ComponentPropsWithoutRef<typeof TabsPrimitive.List>\n>(({ className, children, ...props }, ref) => {\n const { scrollRef, atStart, atEnd, canScroll } = useScrollEdges<HTMLDivElement>()\n const { onValueChange, value: activeValue } = React.useContext(TabsValueContext)\n\n // Local ref map — 追蹤每個 trigger 的 DOM 元素,供 scrollIntoView 使用。\n // 不用 useOverflowIndices 因為 menu 永遠顯示全部,不需要動態 overflow 計算。\n // code-quality-allow: long-function — helper fn 結構緊密,拆 sub-fn 會跨 fn 傳 state 反而複雜\n const itemRefs = React.useRef<Map<number, HTMLElement>>(new Map())\n // 2026-05-16 audit codex Round 6:capture rAF + cancel on unmount(defensive hygiene)\n const scrollRafIdRef = React.useRef<number>(0)\n React.useEffect(() => () => { if (scrollRafIdRef.current) cancelAnimationFrame(scrollRafIdRef.current) }, [])\n const registerItem = React.useCallback(\n (index: number) => (el: HTMLElement | null) => {\n if (el) itemRefs.current.set(index, el)\n else itemRefs.current.delete(index)\n },\n []\n )\n\n // Fragment-safe:攤平 <>…</> 讓條件渲染的溢出 tabs 也進 dropdown / ref map(見 flattenTabTriggers)\n const items = React.useMemo(\n () => flattenTabTriggers(children),\n [children]\n )\n\n // 2026-07-05 M10 同型修(Chip MenuChipGroup 同款):原 cloneElement 直接覆寫 child ref,\n // consumer 自有 ref 被靜默取代 → compose(registerItem + child 原 ref)。\n const enhancedChildren = items.map((child, i) => {\n const childRef = getElementRef(child)\n return React.cloneElement(\n child as React.ReactElement<{ ref?: React.Ref<HTMLElement> }>,\n {\n ref: (el: HTMLElement | null) => {\n registerItem(i)(el)\n if (typeof childRef === 'function') childRef(el)\n else if (childRef) (childRef as React.MutableRefObject<HTMLElement | null>).current = el\n },\n }\n )\n })\n\n // Menu 模式沒有 arrows,但仍套 fade mask (reserveArrowWidth: 0) 軟化內容硬邊\n // code-quality-allow: long-function — helper fn 結構緊密,拆 sub-fn 會跨 fn 傳 state 反而複雜\n const maskImage = buildFadeMask({ canScroll, atStart, atEnd, reserveArrowWidth: 0 })\n\n const handleMenuSelect = React.useCallback(\n (triggerValue: string, index: number) => {\n onValueChange?.(triggerValue)\n // 下一個 tick 再 scroll, 讓 Radix 先完成 data-state 更新\n if (scrollRafIdRef.current) cancelAnimationFrame(scrollRafIdRef.current)\n scrollRafIdRef.current = requestAnimationFrame(() => {\n scrollRafIdRef.current = 0\n itemRefs.current.get(index)?.scrollIntoView({ behavior: 'smooth', inline: 'center', block: 'nearest' })\n })\n },\n [onValueChange]\n )\n\n return (\n // 2026-05-19 fix(scroll-overflow underline clip + y auto-promote,parallel to ScrollTabsList):\n // outer 改 items-stretch(menu button 容器跟 TabsList 含 border 共底線)+ 撤 border。\n // list 套 TABS_LIST_BASE,inner scroll 加 overflow-y-hidden。menu button 容器自帶\n // border-b border-divider 跟 TabsList border 同 y 對齊(items-stretch 保證)。\n <div className=\"flex items-stretch\">\n <div\n ref={scrollRef}\n className=\"flex-1 min-w-0 overflow-x-auto overflow-y-hidden [scrollbar-width:none] [&::-webkit-scrollbar]:hidden\"\n style={{ maskImage, WebkitMaskImage: maskImage }}\n >\n <TabsPrimitive.List\n ref={ref}\n className={cn(TABS_LIST_BASE, 'min-w-full', className)}\n {...props}\n >\n {enhancedChildren}\n </TabsPrimitive.List>\n </div>\n {canScroll && (\n <div className=\"flex-shrink-0 pl-[var(--layout-space-loose)] flex items-center border-b border-divider\">\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <OverflowMenuTriggerButton\n aria-label={`頁籤選單(共 ${items.length} 個)`}\n />\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\">\n {items.map((trigger, index) => {\n const triggerProps = trigger.props as {\n value?: string\n children?: React.ReactNode\n disabled?: boolean\n }\n const triggerValue = triggerProps.value\n if (typeof triggerValue !== 'string') return null\n // 單選 active 用 DropdownMenuItem 的 selected prop\n // → 對應 bg-neutral-selected 持續選中背景, 跟 SelectMenu 單選完全\n // 同一套 canonical 視覺, 全 DS 一致。不可用 className 發明樣式。\n return (\n <DropdownMenuItem\n key={triggerValue}\n disabled={triggerProps.disabled}\n selected={activeValue === triggerValue}\n onSelect={() => handleMenuSelect(triggerValue, index)}\n >\n {triggerProps.children}\n </DropdownMenuItem>\n )\n })}\n </DropdownMenuContent>\n </DropdownMenu>\n </div>\n )}\n </div>\n )\n})\nMenuTabsList.displayName = 'MenuTabsList'\n\n// ── Trigger ──\nconst tabsTriggerVariants = cva(\n [\n 'relative inline-flex items-center justify-center',\n 'gap-2',\n 'whitespace-nowrap',\n 'font-medium text-fg-secondary',\n 'transition-colors duration-150',\n 'cursor-pointer select-none',\n 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1',\n // Trigger 無水平 padding — 寬度 = 內容寬度。triggers 間的分隔靠 TabsList 的 gap-[var(--layout-space-loose)]\n // selected underline:::after 絕對定位在 bottom:-1px,2px primary(持續選中 base)\n // left-0/right-0 因為 trigger 已無 padding,底線等於內容寬度\n // 底線蓋住 TabsList 的 1px gray border,視覺單一線條\n 'after:absolute after:left-0 after:right-0 after:bottom-[-1px] after:h-0.5',\n 'after:bg-transparent after:transition-colors after:duration-150',\n // hover(未選):文字轉深\n 'hover:text-foreground',\n // selected\n 'data-[state=active]:text-foreground data-[state=active]:font-medium',\n // 2026-07-06 user 拍板:選中底線 hover 階 → primary base(持續選中站 base;\n // Ant inkBarColor = colorPrimary / Material 3 active indicator = primary 同款)\n 'data-[state=active]:after:bg-primary',\n // disabled:cursor-not-allowed + 不吃 hover 色\n // 不用 pointer-events-none,否則 cursor 不會改變;button[disabled] 本身就擋 click\n 'disabled:cursor-not-allowed disabled:text-fg-disabled',\n 'disabled:hover:text-fg-disabled',\n ],\n {\n variants: {\n size: {\n // leading-compact:trigger 是單行文字容器,使用 1.3 行高避免 text-body/body-lg 預設 1.5 造成垂直偏移\n sm: 'h-[var(--tab-height-sm)] text-body leading-compact',\n md: 'h-[var(--tab-height-md)] text-body leading-compact',\n lg: 'h-[var(--tab-height-lg)] text-body-lg leading-compact',\n },\n },\n defaultVariants: {\n // size = 'sm' per header-canonical.spec.md W6:overlay / chrome / dense toolbar\n // 都用 sm;獨立 page hero 用 lg。md 為 future tier(無 recommended use case)。\n // 2026-05-17 從 'md' 改 'sm'(M31 codex 比稿後)— production consumer = 0,\n // 影響面限 stories baseline(已過 visual diff gate)。\n size: 'sm',\n },\n }\n)\n\ninterface TabsTriggerProps\n extends React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger> {\n /** 左側 icon(LucideIcon) */\n startIcon?: LucideIcon\n /** 右側 badge(通常是計數指示器) */\n badge?: React.ReactNode\n /**\n * 右側純視覺 indicator(LucideIcon)。**僅限方向 / 狀態 indicator**:ChevronDown / Pin / Star。\n * **不要拼 click 行為** — endIcon 是 tab body 的一部分,點到也是切 tab。\n * 需要「點該後綴開 dropdown / menu」場景請用 `inlineAction` slot(2026-05-21 拆分)。\n */\n endIcon?: LucideIcon\n /**\n * Inline action slot(2026-05-21 v3 加,per user「圖一 後綴應該是 inline action」+「點擊\n * 該 tab 的 inline action 跟其他地方應該不同反應」directive):\n * 提供 `<ItemInlineAction>` / `<DropdownMenuTrigger asChild><ItemInlineAction ... /></DropdownMenuTrigger>`\n * 等獨立 click target。**DOM 上是 trigger 的 sibling(絕對定位),非巢狀 button**\n * (2026-07-05 D4 修:Radix TabsTrigger 是原生 `<button>`,巢 button = HTML 不合法 +\n * axe nested-interactive;同 SidebarMenuButton suffixNode canonical),inline action\n * 點擊天然不落在 tab body,達成 split-click 行為(對齊 GitHub「Code ▾」/\n * Linear \"Triage...\" menu / Atlassian split-tab 共識)。\n *\n * 跟 endIcon 區別:endIcon = 純視覺 indicator(無獨立行為,連同 tab 一起 click),\n * inlineAction = 獨立 click target(自己的 handler,不切 tab)。語意分家明確。\n */\n inlineAction?: React.ReactNode\n}\n\nconst TabsTrigger = React.forwardRef<\n React.ElementRef<typeof TabsPrimitive.Trigger>,\n TabsTriggerProps\n>(({ className, startIcon: StartIcon, badge, endIcon: EndIcon, inlineAction, children, ...props }, ref) => {\n const { size } = React.useContext(TabsContext)\n // 2026-05-18 改 import ICON_SIZE SSOT(per user『做完』approval,消除 M17 違反 7+ 重複 ternary)\n const iconSize = ICON_SIZE[size as 'sm' | 'md' | 'lg']\n const hasSuffix = badge != null || EndIcon !== undefined\n // 2026-07-05 D4 修(deep-audit「Tabs:inlineAction slot 造成 button 巢 button」):\n // Radix TabsTrigger render 原生 <button>,inlineAction(ItemInlineActionButton)也是原生\n // <button> — 巢狀 interactive = HTML 不合法 + axe nested-interactive(serious)+ tab 的\n // accessible name 混入 action 內容。改 sibling 佈局(同 SidebarMenuButton suffixNode\n // canonical,sidebar.tsx):wrapper span(relative)內 trigger + 絕對定位 action;trigger 以\n // paddingRight 預留 action 空間 — icon 寬 + 原 gap(有 badge/endIcon 時 4px = suffix 內\n // gap-1;僅 action 時 8px = slot 間 gap-2),幾何與巢狀版一致。click 天然分流(action 不在\n // trigger 子樹,不冒泡),原 stopPropagation 五連發不再需要。\n const hasAction = inlineAction != null\n const actionPaddingRight = hasAction ? iconSize + (hasSuffix ? 4 : 8) : undefined\n\n const trigger = (\n <TabsPrimitive.Trigger\n ref={ref}\n className={cn(tabsTriggerVariants({ size }), className)}\n style={hasAction ? { paddingRight: actionPaddingRight } : undefined}\n {...props}\n >\n {StartIcon && <StartIcon size={iconSize} aria-hidden />}\n {children != null && <span>{children}</span>}\n {hasSuffix && (\n <span className=\"inline-flex items-center gap-1\">\n {badge}\n {EndIcon && <EndIcon size={iconSize} aria-hidden />}\n </span>\n )}\n </TabsPrimitive.Trigger>\n )\n\n if (!hasAction) return trigger\n\n return (\n // sibling 佈局 wrapper:span(relative)內 trigger + 絕對定位 action。\n // Radix roving focus 用 collection context 收 trigger,不要求 trigger 是 List 直接子層。\n <span className=\"relative inline-flex\">\n {trigger}\n <span className=\"absolute top-1/2 -translate-y-1/2 right-0 inline-flex items-center\">\n {inlineAction}\n </span>\n </span>\n )\n})\nTabsTrigger.displayName = 'TabsTrigger'\n\n// ── Content ──\nconst TabsContent = React.forwardRef<\n React.ElementRef<typeof TabsPrimitive.Content>,\n React.ComponentPropsWithoutRef<typeof TabsPrimitive.Content>\n>(({ className, ...props }, ref) => (\n <TabsPrimitive.Content\n ref={ref}\n className={cn(\n // 與 TabsList 的間距 canonical(2026-06-12 user 拍板):--layout-space-tight(md 12px,\n // density 連動)。依 layoutSpace.spec「親疏 3 級」:Tabs↔Content 同 bundle(第一級,\n // 元件 spec own),值取規則 3「直接功能依賴 = tight」精神(heading → labeled content 同類)。\n // 收斂原 DS-wide 四種土法(無間距 / mt-4 / p-4 / pt-4 — M17 假 SSOT)。\n // full-height 佈局(AppShell pane)用 className=\"mt-0\" 覆寫(tailwind-merge)。\n 'mt-[var(--layout-space-tight)]',\n 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1',\n className\n )}\n {...props}\n />\n))\nTabsContent.displayName = 'TabsContent'\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 tabsMeta = {\n component: 'Tabs',\n family: null, // non-family composite / overlay / layout\n variants: {\n\n },\n // 注:`fieldHeight` 為 meta 通用 height key;Tabs **不複用 field-height**,值為 `--tab-height-*`(md density,對齊 spec.md size table + uiSize.css)\n sizes: {\n sm: { fieldHeight: 32, iconSize: 16, typography: 'body' },\n md: { fieldHeight: 40, iconSize: 16, typography: 'body' },\n lg: { fieldHeight: 48, iconSize: 20, typography: 'body-lg' },\n },\n // 'selected' = 選中 tab(Radix DOM attr 為 data-state=\"active\",meta 詞彙統一用 'selected' 避免與「按壓 active」混用;2026-07-07)\n states: ['default', 'hover', 'selected', 'focus-visible', 'disabled'],\n tokens: {\n bg: ['bg-primary', 'bg-transparent'],\n fg: ['text-fg-disabled', 'text-fg-secondary', 'text-foreground'],\n ring: ['ring-ring'],\n },\n defaultSize: 'sm',\n} as const\n\nexport { Tabs, TabsList, TabsTrigger, TabsContent, tabsTriggerVariants }\nexport type { TabsSize, TabsListProps, TabsTriggerProps }\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"tabs.js","sources":["../../../src/components/Tabs/tabs.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.\n// code-quality-allow: file-size — foundational composite(Tabs + overflow scroll mode + dropdown switcher + inline action slot)在單一 wrapper SSOT 內,拆分會破壞 a11y / focus management chain。當前 ~578 < cap 800。\nimport * as React from 'react'\nimport { createPortal } from 'react-dom'\nimport * as TabsPrimitive from '@radix-ui/react-tabs'\nimport { cva } from 'class-variance-authority'\nimport type { LucideIcon } from 'lucide-react'\nimport { cn } from '@/lib/utils'\nimport {\n DropdownMenu,\n DropdownMenuTrigger,\n DropdownMenuContent,\n DropdownMenuItem,\n} from '@/design-system/components/DropdownMenu/dropdown-menu'\nimport {\n useScrollEdges,\n useScrollByPage,\n buildFadeMask,\n ARROW_BUTTON_WIDTH,\n OverflowScrollArrow,\n OverflowMenuTriggerButton,\n} from '@/design-system/patterns/horizontal-overflow/horizontal-overflow'\nimport { ICON_SIZE } from '@/design-system/tokens/uiSize/icon-size'\n\n/**\n * Tabs — 基於 Radix Tabs,橋接設計系統 token\n *\n * ── 定位 ──\n * 同一上下文底下切換平行的 view。切 view(不是切 value)。\n * 切 value 用 SegmentedControl;切路由用 navigation。\n *\n * ── Size ──\n * sm h-tab-sm(32/40),★ 預設(2026-05-17 從 md 改),overlay / chrome / dense\n * md h-tab-md(40/48),future tier 無 recommended use case\n * lg h-tab-lg(48/56),page-level hero / 獨立 tabs 取代 chrome header\n *\n * ── 寬度行為 ──\n * Trigger 寬度永遠由內容決定(hug content)。\n * Triggers 之間 gap 為 --layout-space-loose(16px / 24px lg density)。\n *\n * ── Trigger 結構 ──\n * [startIcon?] [label] [suffix: badge? + endIcon?]\n * slot 間 gap-2,suffix 內 gap-1\n *\n * ── Selected underline ──\n * 使用 ::after 絕對定位在 bottom: -1px,2px primary(2026-07-06 拍板:持續選中站 base),\n * 蓋住 TabsList 的 1px gray border(單一視覺線條,不雙線)。\n */\n\n// ── Size context ──\ntype TabsSize = 'sm' | 'md' | 'lg'\ntype TabsOverflow = 'none' | 'scroll' | 'menu'\n\ninterface TabsContextValue {\n size: TabsSize\n}\nconst TabsContext = React.createContext<TabsContextValue>({ size: 'md' })\n\n// ── Inline-action overlay context(2026-07-18 決策1,axe aria-required-children 正規修)──\n// role=tablist 只能擁有 role=tab;per-tab inlineAction(如「更多 ▾」dropdown)若 DOM-在 tablist 內\n// = critical 違規(axe 實測確認)。Radix 要求 trigger 為 TabsList 的 React 子代(roving focus\n// context),故 trigger 必 DOM-在 tablist 內;唯一正規解 = 把 action **portal 出 tablist** 到一個\n// sibling overlay 層,以 trigger 的量測座標定位(涵蓋 3 種 overflow 模式,viewport-relative)。\n// aria-owns 不可行:axe required-children 計 DOM-owned 子代,不因 aria-owns 排除。\n// 代價(user 2026-07-18 接受):鍵盤 Tab 序 = 全部 tab 後才到 action(action 現為 overlay 內獨立 tab stop)。\nconst TabsActionOverlayContext = React.createContext<HTMLDivElement | null>(null)\n\n// ── Root ──\n// Wrap Radix Tabs 以支援 value/onValueChange 的 context pass-through\n// (menu 模式的 overflow items 需要能 proxy click 觸發同一個 onValueChange)\ninterface TabsProps extends React.ComponentPropsWithoutRef<typeof TabsPrimitive.Root> {}\n\nconst TabsValueContext = React.createContext<{\n value?: string\n onValueChange?: (value: string) => void\n}>({})\n\n\n// ── getElementRef(Radix Slot idiom;鏡射 chip.tsx 同名 helper — 2026-07-05 M10 同型修)──\n// 讀 child 既有 ref 而不觸發 React 18/19 dev deprecation warning。\nfunction getElementRef(element: React.ReactElement): React.Ref<HTMLElement> | undefined {\n let getter = Object.getOwnPropertyDescriptor(element.props, 'ref')?.get\n let mayWarn = getter && 'isReactWarning' in getter && getter.isReactWarning\n if (mayWarn) return (element as unknown as { ref?: React.Ref<HTMLElement> }).ref\n getter = Object.getOwnPropertyDescriptor(element, 'ref')?.get\n mayWarn = getter && 'isReactWarning' in getter && getter.isReactWarning\n if (mayWarn) return (element.props as { ref?: React.Ref<HTMLElement> }).ref\n return (\n (element.props as { ref?: React.Ref<HTMLElement> }).ref ??\n (element as unknown as { ref?: React.Ref<HTMLElement> }).ref ??\n undefined\n )\n}\n\n// 遞迴攤平 Fragment:React.Children.toArray 不展開 <>…</>(把 Fragment 當單一 element),\n// 導致 menu 模式 dropdown 漏掉被 Fragment 包起來的 trigger(如條件渲染的溢出 tabs)+ cloneElement\n// 對 Fragment 傳 ref 會噴 React warning。攤平後每個 keyless child 補 path-index key,對齊\n// React.Children.toArray 的自動 key 行為,避免 list key warning。\nfunction flattenTabTriggers(children: React.ReactNode, prefix = ''): React.ReactElement[] {\n const out: React.ReactElement[] = []\n React.Children.forEach(children, (child, i) => {\n if (!React.isValidElement(child)) return\n if (child.type === React.Fragment) {\n out.push(\n ...flattenTabTriggers((child.props as { children?: React.ReactNode }).children, `${prefix}${i}.`),\n )\n } else {\n out.push(child.key != null ? child : React.cloneElement(child, { key: `tab-${prefix}${i}` }))\n }\n })\n return out\n}\n\nconst Tabs = React.forwardRef<\n React.ElementRef<typeof TabsPrimitive.Root>,\n TabsProps\n>(({ value, onValueChange, defaultValue, children, ...props }, ref) => {\n // 內部維護一份 uncontrolled state,讓 overflow menu 的 proxy 有 onValueChange 可呼叫\n const [internalValue, setInternalValue] = React.useState<string | undefined>(defaultValue)\n const isControlled = value !== undefined\n const currentValue = isControlled ? value : internalValue\n\n const handleValueChange = React.useCallback(\n (next: string) => {\n if (!isControlled) setInternalValue(next)\n onValueChange?.(next)\n },\n [isControlled, onValueChange]\n )\n\n const valueContext = React.useMemo(\n () => ({ value: currentValue, onValueChange: handleValueChange }),\n [currentValue, handleValueChange]\n )\n\n return (\n <TabsValueContext.Provider value={valueContext}>\n <TabsPrimitive.Root\n ref={ref}\n value={currentValue}\n onValueChange={handleValueChange}\n {...props}\n >\n {children}\n </TabsPrimitive.Root>\n </TabsValueContext.Provider>\n )\n})\nTabs.displayName = 'Tabs'\n\n// ── List ──\n// TabsList 基礎 class — inline-flex 單列 + gap-loose + 底部 border-divider\n// 2026-05-18 改 border-border → border-divider(per user verbatim「我認為應該把 tabs 的\n// 下底線統一改成是 divider 色吧?」+「做」approval):\n// - 跟 Dialog / Sheet / Popover / Sidebar header `border-b border-divider`(neutral-4)同色\n// - withTabs scenario 下 tabs underline = chrome separator,跟 dialog 其他 separator 視覺一致\n// - Selected trigger 2px primary 仍 overlay underlying divider(對比 primary >> divider 不弱)\n// - 對齊 `color.spec.md`「T-junction connectivity 原則」段 outer-vs-divider 判準(Dialog 結構,T-junction 思路適用)\nconst TABS_LIST_BASE = [\n 'inline-flex items-stretch',\n 'gap-[var(--layout-space-loose)]',\n 'border-b border-divider',\n].join(' ')\n\ninterface TabsListProps\n extends React.ComponentPropsWithoutRef<typeof TabsPrimitive.List> {\n size?: TabsSize\n /**\n * Overflow 處理模式。詳見 tabs.spec.md 的 overflow 段。\n * 'none' ★ 預設,不處理,triggers 溢出父容器(適用 tabs 數量可控的情境)\n * 'scroll' 單行橫向滾動 + 邊緣 fade mask(Material / Polaris / iOS 作法)\n * 'menu' show-all navigator——全部 triggers 一直顯示在底層 overflow-x-auto 捲動容器內,\n * (Ant Design / Atlassian 作法)\n */\n overflow?: TabsOverflow\n}\n\nconst TabsList = React.forwardRef<\n React.ElementRef<typeof TabsPrimitive.List>,\n TabsListProps\n>(({ className, size = 'sm', overflow = 'none', children, ...props }, ref) => {\n const tabsSizeContext = React.useMemo(() => ({ size }), [size])\n // Overlay 層(決策1):per-tab inlineAction 由 TabsTrigger portal 到此,DOM-在 tablist 外,\n // 以量測座標定位在對應 tab 之上。pointer-events-none(action span 自帶 auto);z 疊在 tab 之上。\n const [overlayEl, setOverlayEl] = React.useState<HTMLDivElement | null>(null)\n\n let listNode: React.ReactNode\n if (overflow === 'scroll') {\n listNode = (\n <ScrollTabsList ref={ref} className={className} {...props}>\n {children}\n </ScrollTabsList>\n )\n } else if (overflow === 'menu') {\n listNode = (\n <MenuTabsList ref={ref} className={className} {...props}>\n {children}\n </MenuTabsList>\n )\n } else {\n // none(預設)\n listNode = (\n <TabsPrimitive.List\n ref={ref}\n className={cn(TABS_LIST_BASE, 'w-full', className)}\n {...props}\n >\n {children}\n </TabsPrimitive.List>\n )\n }\n\n return (\n <TabsContext.Provider value={tabsSizeContext}>\n <TabsActionOverlayContext.Provider value={overlayEl}>\n <div className=\"relative\">\n {listNode}\n {/* inline-action overlay:sibling of tablist(非其 DOM 子代)→ 修 axe aria-required-children */}\n <div ref={setOverlayEl} className=\"pointer-events-none absolute inset-0 z-10\" />\n </div>\n </TabsActionOverlayContext.Provider>\n </TabsContext.Provider>\n )\n})\nTabsList.displayName = 'TabsList'\n\n// ── Scroll mode ──\n//\n// 共同策略(對齊 Material 3 / Ant Design / Primer UnderlineNav 世界級作法):\n// - 容器 overflow-x-auto + overflow-y-hidden(真的可滾,鍵盤焦點可 scroll-into-view;\n// 明示 y-hidden 阻 CSS overflow-3 spec「一軸 auto 時另軸 visible compute auto」)\n// - border-b 在 TabsList 內部(TABS_LIST_BASE),不在 outer wrapper,避免\n// active underline `after:bottom:-1px` 跟 outer + overflow-x-auto 觸發 y promote bug\n// - mask / arrow / fade 全部從 horizontal-overflow pattern module 取得——\n// 參見 `patterns/horizontal-overflow/horizontal-overflow.spec.md`\n// - Menu 模式 = scroll 模式 + 額外的 ⌄ quick-jump button,點 menu item 同時\n// 觸發 onValueChange + scrollIntoView,讓使用者在 tab strip 看到選中的 tab\n// 單行水平滾動 + 邊緣 fade mask 延伸到 arrow button 底下 + 左右 always-visible arrows\n// - 鍵盤: Radix 原生方向鍵 + 瀏覽器 scroll-into-view\n// - Trackpad: 兩指橫向滑動\n// - 滑鼠滾輪: 點 arrow buttons (Shift+wheel 太隱晦)\n\nconst ScrollTabsList = React.forwardRef<\n React.ElementRef<typeof TabsPrimitive.List>,\n React.ComponentPropsWithoutRef<typeof TabsPrimitive.List>\n>(({ className, children, ...props }, ref) => {\n const { scrollRef, atStart, atEnd, canScroll } = useScrollEdges<HTMLDivElement>()\n const scrollByPage = useScrollByPage(scrollRef)\n const maskImage = buildFadeMask({\n canScroll,\n atStart,\n atEnd,\n reserveArrowWidth: ARROW_BUTTON_WIDTH,\n })\n\n return (\n // 2026-05-19 fix(scroll-overflow underline clip + y auto-promote):\n // outer 撤 `border-b` → owner 升到 `TabsList` (TABS_LIST_BASE 含 border-b border-divider)\n // 把 trigger `after:bottom-[-1px]` 2px underline 的下半部 1px 收進 list border-box,\n // 再加 `overflow-y-hidden` 明示阻 browser y auto-promote(CSS overflow-3 spec:\n // overflow-x:auto + overflow-y:visible 必 compute auto)。\n // 不加 `pb-px`(outer border 撤後 list border 已接 -1px 部分,加 pb 多 1px 多餘空白)。\n // 對齊 Primer UnderlineNav `overflow-x:auto; overflow-y:hidden` canonical 同步動\n // horizontal-overflow.spec.md(「Hook re-export」+「典型 scroll / menu 模式組裝」段)owner 升 list 內部。\n <div className=\"relative\">\n <div\n ref={scrollRef}\n className=\"overflow-x-auto overflow-y-hidden [scrollbar-width:none] [&::-webkit-scrollbar]:hidden\"\n style={{ maskImage, WebkitMaskImage: maskImage }}\n >\n <TabsPrimitive.List\n ref={ref}\n className={cn(TABS_LIST_BASE, 'min-w-full', className)}\n {...props}\n >\n {children}\n </TabsPrimitive.List>\n </div>\n {!atStart && canScroll && (\n <OverflowScrollArrow direction=\"left\" onClick={() => scrollByPage('left')} />\n )}\n {!atEnd && canScroll && (\n <OverflowScrollArrow direction=\"right\" onClick={() => scrollByPage('right')} />\n )}\n </div>\n )\n})\nScrollTabsList.displayName = 'ScrollTabsList'\n\n// ── Menu mode ──\n// Show-all navigator pattern (Chrome tab dropdown / VS Code editor tabs / Discord channel jumper):\n// - Menu 永遠顯示全部 tabs,active 的用 checked 標記 (單選語意)\n// - 點 menu item = onValueChange + scrollIntoView(center),把該 tab 捲到視圖中央\n// - Menu 內容穩定,跟 scroll 位置無關,使用者對「⌄ = navigator」的直覺一致\n//\n// 為什麼底層仍是 overflow-x-auto 而非 overflow-hidden:\n// - scrollIntoView 需要真實 scroll 容器\n// - 鍵盤 focus 也依賴真實 scroll 讓 browser auto scroll-into-view\n// - scrollbar 用 CSS 隱藏,視覺看不出來\n//\n// Fade mask 純視覺,軟化內容硬邊,跟 menu 機制正交 (兩者可並存)。\n// Menu button 出現條件: canScroll (有溢出空間才需要 navigator)。\n\n// code-quality-allow: long-function — foundational composite main body — 拆 sub-fn 會複雜化 local state / ref / context binding\nconst MenuTabsList = React.forwardRef<\n React.ElementRef<typeof TabsPrimitive.List>,\n React.ComponentPropsWithoutRef<typeof TabsPrimitive.List>\n>(({ className, children, ...props }, ref) => {\n const { scrollRef, atStart, atEnd, canScroll } = useScrollEdges<HTMLDivElement>()\n const { onValueChange, value: activeValue } = React.useContext(TabsValueContext)\n\n // Local ref map — 追蹤每個 trigger 的 DOM 元素,供 scrollIntoView 使用。\n // 不用 useOverflowIndices 因為 menu 永遠顯示全部,不需要動態 overflow 計算。\n // code-quality-allow: long-function — helper fn 結構緊密,拆 sub-fn 會跨 fn 傳 state 反而複雜\n const itemRefs = React.useRef<Map<number, HTMLElement>>(new Map())\n // 2026-05-16 audit codex Round 6:capture rAF + cancel on unmount(defensive hygiene)\n const scrollRafIdRef = React.useRef<number>(0)\n React.useEffect(() => () => { if (scrollRafIdRef.current) cancelAnimationFrame(scrollRafIdRef.current) }, [])\n const registerItem = React.useCallback(\n (index: number) => (el: HTMLElement | null) => {\n if (el) itemRefs.current.set(index, el)\n else itemRefs.current.delete(index)\n },\n []\n )\n\n // Fragment-safe:攤平 <>…</> 讓條件渲染的溢出 tabs 也進 dropdown / ref map(見 flattenTabTriggers)\n const items = React.useMemo(\n () => flattenTabTriggers(children),\n [children]\n )\n\n // 2026-07-05 M10 同型修(Chip MenuChipGroup 同款):原 cloneElement 直接覆寫 child ref,\n // consumer 自有 ref 被靜默取代 → compose(registerItem + child 原 ref)。\n const enhancedChildren = items.map((child, i) => {\n const childRef = getElementRef(child)\n return React.cloneElement(\n child as React.ReactElement<{ ref?: React.Ref<HTMLElement> }>,\n {\n ref: (el: HTMLElement | null) => {\n registerItem(i)(el)\n if (typeof childRef === 'function') childRef(el)\n else if (childRef) (childRef as React.MutableRefObject<HTMLElement | null>).current = el\n },\n }\n )\n })\n\n // Menu 模式沒有 arrows,但仍套 fade mask (reserveArrowWidth: 0) 軟化內容硬邊\n // code-quality-allow: long-function — helper fn 結構緊密,拆 sub-fn 會跨 fn 傳 state 反而複雜\n const maskImage = buildFadeMask({ canScroll, atStart, atEnd, reserveArrowWidth: 0 })\n\n const handleMenuSelect = React.useCallback(\n (triggerValue: string, index: number) => {\n onValueChange?.(triggerValue)\n // 下一個 tick 再 scroll, 讓 Radix 先完成 data-state 更新\n if (scrollRafIdRef.current) cancelAnimationFrame(scrollRafIdRef.current)\n scrollRafIdRef.current = requestAnimationFrame(() => {\n scrollRafIdRef.current = 0\n itemRefs.current.get(index)?.scrollIntoView({ behavior: 'smooth', inline: 'center', block: 'nearest' })\n })\n },\n [onValueChange]\n )\n\n return (\n // 2026-05-19 fix(scroll-overflow underline clip + y auto-promote,parallel to ScrollTabsList):\n // outer 改 items-stretch(menu button 容器跟 TabsList 含 border 共底線)+ 撤 border。\n // list 套 TABS_LIST_BASE,inner scroll 加 overflow-y-hidden。menu button 容器自帶\n // border-b border-divider 跟 TabsList border 同 y 對齊(items-stretch 保證)。\n <div className=\"flex items-stretch\">\n <div\n ref={scrollRef}\n className=\"flex-1 min-w-0 overflow-x-auto overflow-y-hidden [scrollbar-width:none] [&::-webkit-scrollbar]:hidden\"\n style={{ maskImage, WebkitMaskImage: maskImage }}\n >\n <TabsPrimitive.List\n ref={ref}\n className={cn(TABS_LIST_BASE, 'min-w-full', className)}\n {...props}\n >\n {enhancedChildren}\n </TabsPrimitive.List>\n </div>\n {canScroll && (\n <div className=\"flex-shrink-0 pl-[var(--layout-space-loose)] flex items-center border-b border-divider\">\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <OverflowMenuTriggerButton\n aria-label={`頁籤選單(共 ${items.length} 個)`}\n />\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\">\n {items.map((trigger, index) => {\n const triggerProps = trigger.props as {\n value?: string\n children?: React.ReactNode\n disabled?: boolean\n }\n const triggerValue = triggerProps.value\n if (typeof triggerValue !== 'string') return null\n // 單選 active 用 DropdownMenuItem 的 selected prop\n // → 對應 bg-neutral-selected 持續選中背景, 跟 SelectMenu 單選完全\n // 同一套 canonical 視覺, 全 DS 一致。不可用 className 發明樣式。\n return (\n <DropdownMenuItem\n key={triggerValue}\n disabled={triggerProps.disabled}\n selected={activeValue === triggerValue}\n onSelect={() => handleMenuSelect(triggerValue, index)}\n >\n {triggerProps.children}\n </DropdownMenuItem>\n )\n })}\n </DropdownMenuContent>\n </DropdownMenu>\n </div>\n )}\n </div>\n )\n})\nMenuTabsList.displayName = 'MenuTabsList'\n\n// ── Trigger ──\nconst tabsTriggerVariants = cva(\n [\n 'relative inline-flex items-center justify-center',\n 'gap-2',\n 'whitespace-nowrap',\n 'font-medium text-fg-secondary',\n 'transition-colors duration-150',\n 'cursor-pointer select-none',\n 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1',\n // Trigger 無水平 padding — 寬度 = 內容寬度。triggers 間的分隔靠 TabsList 的 gap-[var(--layout-space-loose)]\n // selected underline:::after 絕對定位在 bottom:-1px,2px primary(持續選中 base)\n // left-0/right-0 因為 trigger 已無 padding,底線等於內容寬度\n // 底線蓋住 TabsList 的 1px gray border,視覺單一線條\n 'after:absolute after:left-0 after:right-0 after:bottom-[-1px] after:h-0.5',\n 'after:bg-transparent after:transition-colors after:duration-150',\n // hover(未選):文字轉深\n 'hover:text-foreground',\n // selected\n 'data-[state=active]:text-foreground data-[state=active]:font-medium',\n // 2026-07-06 user 拍板:選中底線 hover 階 → primary base(持續選中站 base;\n // Ant inkBarColor = colorPrimary / Material 3 active indicator = primary 同款)\n 'data-[state=active]:after:bg-primary',\n // disabled:cursor-not-allowed + 不吃 hover 色\n // 不用 pointer-events-none,否則 cursor 不會改變;button[disabled] 本身就擋 click\n 'disabled:cursor-not-allowed disabled:text-fg-disabled',\n 'disabled:hover:text-fg-disabled',\n ],\n {\n variants: {\n size: {\n // leading-compact:trigger 是單行文字容器,使用 1.3 行高避免 text-body/body-lg 預設 1.5 造成垂直偏移\n sm: 'h-[var(--tab-height-sm)] text-body leading-compact',\n md: 'h-[var(--tab-height-md)] text-body leading-compact',\n lg: 'h-[var(--tab-height-lg)] text-body-lg leading-compact',\n },\n },\n defaultVariants: {\n // size = 'sm' per header-canonical.spec.md W6:overlay / chrome / dense toolbar\n // 都用 sm;獨立 page hero 用 lg。md 為 future tier(無 recommended use case)。\n // 2026-05-17 從 'md' 改 'sm'(M31 codex 比稿後)— production consumer = 0,\n // 影響面限 stories baseline(已過 visual diff gate)。\n size: 'sm',\n },\n }\n)\n\ninterface TabsTriggerProps\n // asChild Omit(2026-07-18 決策2):Tab trigger 固定渲染 [startIcon?, <span>{children}</span>,\n // suffix?] 多節點陣列 → Radix Slot 的 React.Children.only 恆 crash(每個 tab 都會)。\n // children(label)保留合法;TabsRoot / TabsList 為真容器不收窄。\n extends Omit<React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger>, 'asChild'> {\n /** 左側 icon(LucideIcon) */\n startIcon?: LucideIcon\n /** 右側 badge(通常是計數指示器) */\n badge?: React.ReactNode\n /**\n * 右側純視覺 indicator(LucideIcon)。**僅限方向 / 狀態 indicator**:ChevronDown / Pin / Star。\n * **不要拼 click 行為** — endIcon 是 tab body 的一部分,點到也是切 tab。\n * 需要「點該後綴開 dropdown / menu」場景請用 `inlineAction` slot(2026-05-21 拆分)。\n */\n endIcon?: LucideIcon\n /**\n * Inline action slot(2026-05-21 v3 加,per user「圖一 後綴應該是 inline action」+「點擊\n * 該 tab 的 inline action 跟其他地方應該不同反應」directive):\n * 提供 `<ItemInlineAction>` / `<DropdownMenuTrigger asChild><ItemInlineAction ... /></DropdownMenuTrigger>`\n * 等獨立 click target。**DOM 上是 trigger 的 sibling(絕對定位),非巢狀 button**\n * (2026-07-05 D4 修:Radix TabsTrigger 是原生 `<button>`,巢 button = HTML 不合法 +\n * axe nested-interactive;同 SidebarMenuButton suffixNode canonical),inline action\n * 點擊天然不落在 tab body,達成 split-click 行為(對齊 GitHub「Code ▾」/\n * Linear \"Triage...\" menu / Atlassian split-tab 共識)。\n *\n * 跟 endIcon 區別:endIcon = 純視覺 indicator(無獨立行為,連同 tab 一起 click),\n * inlineAction = 獨立 click target(自己的 handler,不切 tab)。語意分家明確。\n */\n inlineAction?: React.ReactNode\n}\n\nconst TabsTrigger = React.forwardRef<\n React.ElementRef<typeof TabsPrimitive.Trigger>,\n TabsTriggerProps\n>(({ className, startIcon: StartIcon, badge, endIcon: EndIcon, inlineAction, children, ...props }, ref) => {\n const { size } = React.useContext(TabsContext)\n const overlayEl = React.useContext(TabsActionOverlayContext)\n // 2026-05-18 改 import ICON_SIZE SSOT(per user『做完』approval,消除 M17 違反 7+ 重複 ternary)\n const iconSize = ICON_SIZE[size as 'sm' | 'md' | 'lg']\n const hasSuffix = badge != null || EndIcon !== undefined\n // inlineAction 幾何:trigger 以 paddingRight 預留 action 空間 — icon 寬 + 原 gap(有 badge/endIcon\n // 時 4px = suffix 內 gap-1;僅 action 時 8px = slot 間 gap-2)。2026-07-05 修 button-巢-button\n // (sibling 佈局);2026-07-18 決策1 再進化:action 不再是 tablist DOM 內的 sibling(axe\n // aria-required-children critical),改 portal 到 TabsList 的 overlay 層(tablist 外),以量測座標\n // 定位在 trigger 右緣。trigger 仍在 tablist 內(Radix roving focus 要求)。\n const hasAction = inlineAction != null\n const actionPaddingRight = hasAction ? iconSize + (hasSuffix ? 4 : 8) : undefined\n\n // 量測 trigger 位置(viewport-relative,涵蓋 none/scroll/menu 三模式),同步 overlay 內 action 座標。\n const triggerElRef = React.useRef<HTMLButtonElement | null>(null)\n const setTriggerRef = React.useCallback(\n (el: HTMLButtonElement | null) => {\n triggerElRef.current = el\n if (typeof ref === 'function') ref(el)\n else if (ref) (ref as React.MutableRefObject<HTMLButtonElement | null>).current = el\n },\n [ref],\n )\n const [actionPos, setActionPos] = React.useState<{ left: number; top: number; height: number } | null>(null)\n\n React.useLayoutEffect(() => {\n if (!hasAction || !overlayEl) return\n const el = triggerElRef.current\n if (!el) return\n const compute = () => {\n const t = triggerElRef.current?.getBoundingClientRect()\n const o = overlayEl.getBoundingClientRect()\n if (!t) return\n setActionPos({ left: t.right - o.left, top: t.top - o.top, height: t.height })\n }\n compute()\n const ro = new ResizeObserver(compute)\n ro.observe(el)\n ro.observe(overlayEl)\n window.addEventListener('resize', compute)\n window.addEventListener('scroll', compute, true) // capture:含 scroll-mode 捲動容器內部捲動\n return () => {\n ro.disconnect()\n window.removeEventListener('resize', compute)\n window.removeEventListener('scroll', compute, true)\n }\n }, [hasAction, overlayEl])\n\n const trigger = (\n <TabsPrimitive.Trigger\n ref={setTriggerRef}\n className={cn(tabsTriggerVariants({ size }), className)}\n style={hasAction ? { paddingRight: actionPaddingRight } : undefined}\n {...props}\n >\n {StartIcon && <StartIcon size={iconSize} aria-hidden />}\n {children != null && <span>{children}</span>}\n {hasSuffix && (\n <span className=\"inline-flex items-center gap-1\">\n {badge}\n {EndIcon && <EndIcon size={iconSize} aria-hidden />}\n </span>\n )}\n </TabsPrimitive.Trigger>\n )\n\n if (!hasAction) return trigger\n\n return (\n <>\n {trigger}\n {overlayEl &&\n actionPos &&\n createPortal(\n // action portal 到 overlay(tablist 外);left=trigger.right + `-translate-x-full` → action\n // 右緣對齊 trigger 右緣;top/height + items-center → 垂直置中(同原 right-0 + top-1/2 幾何)。\n <span\n className=\"pointer-events-auto absolute inline-flex -translate-x-full items-center\"\n style={{ left: actionPos.left, top: actionPos.top, height: actionPos.height }}\n >\n {inlineAction}\n </span>,\n overlayEl,\n )}\n </>\n )\n})\nTabsTrigger.displayName = 'TabsTrigger'\n\n// ── Content ──\nconst TabsContent = React.forwardRef<\n React.ElementRef<typeof TabsPrimitive.Content>,\n React.ComponentPropsWithoutRef<typeof TabsPrimitive.Content>\n>(({ className, ...props }, ref) => (\n <TabsPrimitive.Content\n ref={ref}\n className={cn(\n // 與 TabsList 的間距 canonical(2026-06-12 user 拍板):--layout-space-tight(md 12px,\n // density 連動)。依 layoutSpace.spec「親疏 3 級」:Tabs↔Content 同 bundle(第一級,\n // 元件 spec own),值取規則 3「直接功能依賴 = tight」精神(heading → labeled content 同類)。\n // 收斂原 DS-wide 四種土法(無間距 / mt-4 / p-4 / pt-4 — M17 假 SSOT)。\n // full-height 佈局(AppShell pane)用 className=\"mt-0\" 覆寫(tailwind-merge)。\n 'mt-[var(--layout-space-tight)]',\n 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1',\n className\n )}\n {...props}\n />\n))\nTabsContent.displayName = 'TabsContent'\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 tabsMeta = {\n component: 'Tabs',\n family: null, // non-family composite / overlay / layout\n variants: {\n\n },\n // 注:`fieldHeight` 為 meta 通用 height key;Tabs **不複用 field-height**,值為 `--tab-height-*`(md density,對齊 spec.md size table + uiSize.css)\n sizes: {\n sm: { fieldHeight: 32, iconSize: 16, typography: 'body' },\n md: { fieldHeight: 40, iconSize: 16, typography: 'body' },\n lg: { fieldHeight: 48, iconSize: 20, typography: 'body-lg' },\n },\n // 'selected' = 選中 tab(Radix DOM attr 為 data-state=\"active\",meta 詞彙統一用 'selected' 避免與「按壓 active」混用;2026-07-07)\n states: ['default', 'hover', 'selected', 'focus-visible', 'disabled'],\n tokens: {\n bg: ['bg-primary', 'bg-transparent'],\n fg: ['text-fg-disabled', 'text-fg-secondary', 'text-foreground'],\n ring: ['ring-ring'],\n },\n defaultSize: 'sm',\n} as const\n\nexport { Tabs, TabsList, TabsTrigger, TabsContent, tabsTriggerVariants }\nexport type { TabsSize, TabsListProps, TabsTriggerProps }\n"],"names":[],"mappings":"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"}
@@ -12,7 +12,9 @@ export interface TagProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'pr
12
12
  avatar?: React.ReactNode;
13
13
  /** 可移除——Tag 自動渲染 remove 按鈕並控制尺寸與互動樣式(從集合移除 item) */
14
14
  onRemove?: () => void;
15
- /** remove 按鈕的 aria-label 目標名(a11y)。children 為非字串 ReactNode 時建議傳,否則 SR 讀不出移除哪個 tag。預設取 string children。 */
15
+ /** remove 按鈕的 aria-label 目標名(a11y)。children 為非字串 ReactNode 時建議傳,否則 SR 讀不出移除哪個 tag。預設取 string children。對齊 FileUpload `removeAriaLabel`(同「移除集合項目」場景 + DS `<動作>AriaLabel` 命名慣例)。 */
16
+ removeAriaLabel?: string;
17
+ /** @deprecated 2026-07-17 改名為 `removeAriaLabel`(對齊 FileUpload + onRemove callback 語意;dismiss 保留給 Alert/Notice/Toast「通知被忽略」)。舊名過渡一版後移除。 */
16
18
  dismissLabel?: string;
17
19
  /** 深底模式(step-6 背景 + on-emphasis 配對前景;亮色 hue yellow/amber/orange/lime 用深字 --on-emphasis-dark,green 白字例外) */
18
20
  solid?: boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"tag.d.ts","sourceRoot":"","sources":["../../../src/components/Tag/tag.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAC9B,OAAO,EAAO,KAAK,YAAY,EAAE,MAAM,0BAA0B,CAAA;AAEjE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAwB9C,QAAA,MAAM,WAAW;;;8EAuBhB,CAAA;AAWD,MAAM,WAAW,QACf,SAAQ,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,cAAc,CAAC,EAAE,QAAQ,GAAG,OAAO,CAAC,EACpE,YAAY,CAAC,OAAO,WAAW,CAAC;IAClC,qDAAqD;IACrD,IAAI,CAAC,EAAE,UAAU,CAAA;IACjB,sCAAsC;IACtC,MAAM,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IACxB,oDAAoD;IACpD,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAA;IACrB,0GAA0G;IAC1G,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,2GAA2G;IAC3G,KAAK,CAAC,EAAE,OAAO,CAAA;IACf;;;;;;OAMG;IACH,SAAS,CAAC,EAAE,OAAO,CAAA;CACpB;AAwID,QAAA,MAAM,GAAG,iFAAuD,CAAA;AAKhE,eAAO,MAAM,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA4CV,CAAA;AAEV,OAAO,EAAE,GAAG,EAAE,WAAW,EAAE,CAAA"}
1
+ {"version":3,"file":"tag.d.ts","sourceRoot":"","sources":["../../../src/components/Tag/tag.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAC9B,OAAO,EAAO,KAAK,YAAY,EAAE,MAAM,0BAA0B,CAAA;AAEjE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAwB9C,QAAA,MAAM,WAAW;;;8EAuBhB,CAAA;AAWD,MAAM,WAAW,QACf,SAAQ,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,cAAc,CAAC,EAAE,QAAQ,GAAG,OAAO,CAAC,EACpE,YAAY,CAAC,OAAO,WAAW,CAAC;IAClC,qDAAqD;IACrD,IAAI,CAAC,EAAE,UAAU,CAAA;IACjB,sCAAsC;IACtC,MAAM,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IACxB,oDAAoD;IACpD,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAA;IACrB,iLAAiL;IACjL,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,0IAA0I;IAC1I,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,2GAA2G;IAC3G,KAAK,CAAC,EAAE,OAAO,CAAA;IACf;;;;;;OAMG;IACH,SAAS,CAAC,EAAE,OAAO,CAAA;CACpB;AAwID,QAAA,MAAM,GAAG,iFAAuD,CAAA;AAKhE,eAAO,MAAM,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA4CV,CAAA;AAEV,OAAO,EAAE,GAAG,EAAE,WAAW,EAAE,CAAA"}
@@ -61,7 +61,7 @@ function TagDismiss({ onRemove, label, solid, color }) {
61
61
  }
62
62
  );
63
63
  }
64
- function TagInner({ className, color, size, icon: Icon, avatar, onRemove, dismissLabel, solid, unbounded = false, children, style, ...props }, forwardedRef) {
64
+ function TagInner({ className, color, size, icon: Icon, avatar, onRemove, removeAriaLabel, dismissLabel, solid, unbounded = false, children, style, ...props }, forwardedRef) {
65
65
  const solidClass = solid ? SOLID_CLASSES[color ?? "neutral"] : void 0;
66
66
  const ownRef = React.useRef(null);
67
67
  const [isTruncated, setIsTruncated] = React.useState(false);
@@ -118,7 +118,7 @@ function TagInner({ className, color, size, icon: Icon, avatar, onRemove, dismis
118
118
  Icon && /* @__PURE__ */ jsx(Icon, { size: 16, "aria-hidden": true }),
119
119
  avatar && /* @__PURE__ */ jsx("span", { className: "shrink-0 w-4 h-4 rounded-full overflow-hidden inline-grid place-content-center [&>*]:w-full [&>*]:h-full", children: avatar }),
120
120
  /* @__PURE__ */ jsx("span", { "data-tag-text": "", className: "px-1 truncate min-w-0", children }),
121
- onRemove && /* @__PURE__ */ jsx(TagDismiss, { onRemove, label: dismissLabel || label, solid, color: color ?? "neutral" })
121
+ onRemove && /* @__PURE__ */ jsx(TagDismiss, { onRemove, label: removeAriaLabel ?? dismissLabel ?? label, solid, color: color ?? "neutral" })
122
122
  ]
123
123
  }
124
124
  );
@@ -1 +1 @@
1
- {"version":3,"file":"tag.js","sources":["../../../src/components/Tag/tag.tsx"],"sourcesContent":["import * as React from \"react\"\nimport { cva, type VariantProps } from \"class-variance-authority\"\nimport { X } from \"lucide-react\"\nimport type { LucideIcon } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Tooltip, TooltipTrigger, TooltipContent } from \"@/design-system/components/Tooltip/tooltip\"\nimport { ItemInlineActionButton } from \"@/design-system/patterns/element-anatomy/item-anatomy\"\nimport { CAT_SUBTLE, CAT_SOLID, CAT_INTERACT } from \"@/design-system/tokens/categorical-color\"\n\n// ── Tag(inline label)─────────────────────────────────────────────────────\n// 用於分類標籤、狀態標記、多選已選值。\n//\n// 三種尺寸(子元件補齊原則——消費端直接透傳 size,不做 mapping):\n// sm — 20px 高, 12px 字, 4px tag-px, font-medium(配 field sm)\n// md — 24px 高, 14px 字, 4px tag-px, font-normal(配 field md)— 預設\n// lg — 24px = md alias(配 field lg,子元件補齊原則)\n//\n// 截斷:max-w-40(160px),超出時文字 truncate + 自動 tooltip。\n// 用 Canvas measureText 偵測截斷(scrollWidth 在 flex 內不可靠)。\n\nlet _measureCtx: CanvasRenderingContext2D | null = null\nfunction getMeasureCtx() {\n if (!_measureCtx) _measureCtx = document.createElement('canvas').getContext('2d')\n return _measureCtx\n}\n\nconst tagVariants = cva(\n \"inline-flex items-center rounded-md border border-transparent transition-colors cursor-text\",\n {\n variants: {\n // color:categorical 色相(裝飾性分類,非語意狀態)。**消費 categorical-color SSOT**——\n // key X 一律對 `--color-X-*`(1:1,零 offset)。neutral 非色相(無 hue),用 secondary 底自處理。\n // 2026-06-04 修:原 `red` 誤接 `--color-deep-orange-*`(red=品牌紅 hue-25 ≠ deep-orange hue-38\n // ≠ 語意 --error〔= deep-orange〕);改消費 SSOT 後 red→`--color-red-*`,並補齊全 12 色相。\n color: {\n neutral: \"bg-secondary text-foreground\",\n ...CAT_SUBTLE,\n },\n size: {\n sm: \"h-5 px-1 text-caption font-medium\",\n md: \"h-6 px-1 text-body font-normal\",\n lg: \"h-6 px-1 text-body font-normal\",\n },\n },\n defaultVariants: {\n color: \"neutral\",\n size: \"md\",\n },\n }\n)\n\n// ── Solid variant 色彩(step-6 底 + on-emphasis 配對文字,消費 categorical-color SSOT)──\n// 白字 --on-emphasis(夠深的 hue)/ 深字 --on-emphasis-dark(亮 hue:yellow/amber/orange/lime);green 白字例外。\n// **消費 categorical-color SSOT**(CAT_SOLID,1:1 色相)。neutral 非色相,用 neutral-9\n// + --inverse-fg(light=白字, dark=深字,自動反轉)自處理。\nconst SOLID_CLASSES: Record<string, string> = {\n neutral: 'bg-[var(--color-neutral-9)] text-inverse-fg',\n ...CAT_SOLID,\n}\n\nexport interface TagProps\n extends Omit<React.HTMLAttributes<HTMLDivElement>, 'prefix' | 'color'>,\n VariantProps<typeof tagVariants> {\n /** 左側 icon(LucideIcon),由 Tag 統一 16px。與 avatar 互斥。 */\n icon?: LucideIcon\n /** 左側 avatar(ReactNode),與 icon 互斥。 */\n avatar?: React.ReactNode\n /** 可移除——Tag 自動渲染 remove 按鈕並控制尺寸與互動樣式(從集合移除 item) */\n onRemove?: () => void\n /** remove 按鈕的 aria-label 目標名(a11y)。children 為非字串 ReactNode 時建議傳,否則 SR 讀不出移除哪個 tag。預設取 string children。 */\n dismissLabel?: string\n /** 深底模式(step-6 背景 + on-emphasis 配對前景;亮色 hue yellow/amber/orange/lime 用深字 --on-emphasis-dark,green 白字例外) */\n solid?: boolean\n /**\n * 2026-05-15 Q3 真 SSOT fix(per user verbatim「同空間兩判斷點」+「不要冰山一角」):\n * Tag 寬度由 parent constrain,不套預設 max-w-40(160px)。用於 cell-as-input narrow cell\n * (< 160px)時 Tag fit cell 寬度 + truncate ellipsis,而非 160px 後被 cell `overflow-hidden`\n * 硬切。對齊「同 cell width → 同 overflow 判斷」SSOT。Default false 保 backward compat\n * (wrap layout / pill rail 等仍受 160px 保護)。\n */\n unbounded?: boolean\n}\n\n// ── Solid dismiss hover/active bg ──\n// **消費 categorical-color SSOT**(CAT_INTERACT,semantic --{hue}-hover/active token),\n// 跟 --primary-hover/active 同模式:solid 色彩 shade change(hover 較亮 step、active 較暗 step),\n// 在 semantic 層做 dark mode swap 確保方向跨 mode 一致。\n// neutral 特例:bg 是 neutral-9 隨 mode 反轉,用 --inverse-neutral-* 鏡射,自處理。\nconst SOLID_DISMISS_HOVER: Record<string, { hover: string; active: string }> = {\n neutral: { hover: 'var(--inverse-neutral-hover)', active: 'var(--inverse-neutral-active)' },\n ...CAT_INTERACT,\n}\n\n// ── Dismiss(internal)────────────────────────────────────────────────────\n// 走 `ItemInlineActionButton`(item-anatomy SSOT)+ `hoverBgClassName` override prop\n// (2026-05-01 整合,消除原 Tag 自刻 `<button>` 繞 DS infra 的 tech debt)。\n//\n// 視覺對齊:`size=\"md\"` → icon 16 / hover-bg 18,跟 Tag 既有手刻幾何完全相等。\n// Solid variant(blue/green/red 等)透過 `hoverBgClassName` 套色相 override token;\n// Subtle variant 落用 ItemInlineActionButton 預設 neutral-hover。\n// 圖標色繼承 Tag 文字色 → `text-current` 三態覆寫。\n\nfunction TagDismiss({ onRemove, label, solid, color }: { onRemove: () => void; label: string; solid?: boolean; color?: string }) {\n const solidColors = solid && color ? SOLID_DISMISS_HOVER[color] : undefined\n\n return (\n <ItemInlineActionButton\n icon={X}\n size=\"md\"\n onClick={(e) => { e.stopPropagation(); onRemove() }}\n aria-label={label ? `移除 ${label}` : '移除'}\n style={solidColors ? ({ '--dismiss-hover': solidColors.hover, '--dismiss-active': solidColors.active } as React.CSSProperties) : undefined}\n hoverBgClassName={\n solidColors\n ? 'group-hover/action:bg-[var(--dismiss-hover)] group-active/action:bg-[var(--dismiss-active)]'\n : undefined\n }\n // Override default fg-muted → 繼承 Tag 文字色(label 同色)\n className=\"text-current hover:text-current active:text-current\"\n />\n )\n}\n\nfunction TagInner(\n { className, color, size, icon: Icon, avatar, onRemove, dismissLabel, solid, unbounded = false, children, style, ...props }: TagProps,\n forwardedRef: React.ForwardedRef<HTMLDivElement>,\n) {\n const solidClass = solid ? SOLID_CLASSES[color ?? 'neutral'] : undefined\n const ownRef = React.useRef<HTMLDivElement | null>(null)\n const [isTruncated, setIsTruncated] = React.useState(false)\n\n React.useLayoutEffect(() => {\n const el = ownRef.current\n if (!el) return\n const ctx = getMeasureCtx()\n const check = () => {\n const textSpan = el.querySelector('[data-tag-text]')\n if (!textSpan || !ctx) return\n const text = textSpan.textContent || ''\n const cs = getComputedStyle(textSpan)\n ctx.font = `${cs.fontWeight} ${cs.fontSize} ${cs.fontFamily}`\n const textWidth = ctx.measureText(text).width\n const padL = parseFloat(cs.paddingLeft) || 0\n const padR = parseFloat(cs.paddingRight) || 0\n const needed = textWidth + padL + padR\n setIsTruncated(needed > (textSpan as HTMLElement).clientWidth + 1)\n }\n check()\n const obs = new ResizeObserver(check)\n obs.observe(el)\n return () => obs.disconnect()\n }, [children])\n\n const label = typeof children === 'string' ? children : ''\n\n // ── Dev-mode warning:icon 與 avatar 互斥 ──\n // 兩者同為 prefix slot(tag.spec.md Props 表「與 avatar 互斥」),同時傳會並列渲染破壞 Tag\n // 內部結構;原僅 jsdoc/spec 約束會靜默通過 → 2026-07-05 對齊 Button overlayBadge dev-warn\n // 先例(button.tsx「Dev-mode warning」段)補齊家族一致 runtime 防線。\n if (process.env.NODE_ENV !== 'production' && Icon && avatar) {\n // eslint-disable-next-line no-console\n console.warn(\n '[DS Tag] `icon` 與 `avatar` 互斥(同為 prefix slot),同時傳會並列渲染破壞 Tag 內部結構,請只擇一。SSOT:tag.spec.md Props 表 + tag.principles.stories.tsx IconRule。'\n )\n }\n\n const tag = (\n <div\n ref={(el) => {\n ownRef.current = el\n if (typeof forwardedRef === 'function') forwardedRef(el)\n else if (forwardedRef) (forwardedRef as React.MutableRefObject<HTMLDivElement | null>).current = el\n }}\n data-tag-root=\"\"\n // 截斷時條件性可 focus(2026-07-06 user 拍板「確認世界級有這樣做就做」——APG Tooltip\n // pattern 原文:tooltip 在元素「收到鍵盤焦點」或 hover 時顯示,trigger 可 focus 是\n // W3C 官方 pattern 前提;Radix Tooltip focus-open 自動生效 → 鍵盤 Tab 到即見全文。\n // 未截斷不入 tab 序(維持 tag.spec「靜態 Tag 不取得 focus」預設,不汙染 tab order)。\n tabIndex={isTruncated ? 0 : undefined}\n className={cn(\n tagVariants({ color, size }),\n solidClass,\n 'w-fit min-w-0 overflow-hidden',\n isTruncated && 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1',\n className,\n )}\n // 2026-05-18 Round 5 fix(per Codex M31 Round 5 verdict + user 拍板「那就開始做」):\n // 用 CSS var `--combobox-tag-area-inline-size`(由 Combobox useOverflowCount JS-injected)取代\n // `min(100%, 10rem)` cyclic percentage。CSS Sizing 3 §5.2.1:percentage 在 indefinite containing\n // block 退化為 initial value → Round 4 的 100% 沒 enforce。改 explicit px 值(JS measured)避此 trap。\n // unbounded=true:cap = inject 寬(回 cell-as-input narrow cell 原 behavior)\n // default:cap = min(inject 寬, 160px)— 兩 cap 取小。fallback(無 var,Form context 等)= 100% / 10rem。\n style={{\n maxWidth: unbounded\n ? 'var(--combobox-tag-area-inline-size, 100%)'\n : 'min(var(--combobox-tag-area-inline-size, 10rem), 10rem)',\n ...style,\n }}\n {...props}\n >\n {Icon && <Icon size={16} aria-hidden />}\n {avatar && <span className=\"shrink-0 w-4 h-4 rounded-full overflow-hidden inline-grid place-content-center [&>*]:w-full [&>*]:h-full\">{avatar}</span>}\n <span data-tag-text=\"\" className=\"px-1 truncate min-w-0\">{children}</span>\n {onRemove && <TagDismiss onRemove={onRemove} label={dismissLabel || label} solid={solid} color={color ?? 'neutral'} />}\n </div>\n )\n\n if (!isTruncated) return tag\n\n return (\n <Tooltip>\n <TooltipTrigger asChild>{tag}</TooltipTrigger>\n <TooltipContent>{children}</TooltipContent>\n </Tooltip>\n )\n}\n\nconst Tag = React.forwardRef<HTMLDivElement, TagProps>(TagInner)\nTag.displayName = 'Tag'\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 tagMeta = {\n component: 'Tag',\n family: 3,\n // categorical 色相(裝飾性分類,非語意狀態)。**1:1 對 `--color-{hue}-*` primitive,零 offset**。\n // 不對應語意 token——語意狀態(error/info/success/warning)走 Notice / Alert 等狀態元件,\n // 不是 Tag 色相。2026-06-04 修:移除原「red 對應 --error / blue 對應 --info ...」誤導框架\n // (red = 品牌紅 hue-25,跟語意 --error〔= deep-orange〕無關)。\n variants: {\n neutral: { purpose: '通用分類、草稿、無特定語義(secondary 底)' },\n blue: { purpose: 'categorical 色相(--color-blue-*)' },\n green: { purpose: 'categorical 色相(--color-green-*)' },\n 'deep-orange': { purpose: 'categorical 色相(--color-deep-orange-*,hue 38)' },\n yellow: { purpose: 'categorical 色相(--color-yellow-*,淺底深字)' },\n red: { purpose: 'categorical 色相(--color-red-*,品牌紅家族 hue 25;≠ 語意 --error)' },\n orange: { purpose: 'categorical 色相(--color-orange-*)' },\n amber: { purpose: 'categorical 色相(--color-amber-*,淺底深字)' },\n lime: { purpose: 'categorical 色相(--color-lime-*)' },\n turquoise: { purpose: 'categorical 色相(--color-turquoise-*)' },\n indigo: { purpose: 'categorical 色相(--color-indigo-*)' },\n purple: { purpose: 'categorical 色相(--color-purple-*)' },\n magenta: { purpose: 'categorical 色相(--color-magenta-*)' },\n },\n sizes: {\n // Tag 尺寸不引用 field-height token(spec「尺寸」段——Tag 與 Field 尺寸獨立;段名指法免行號漂移)。\n // height = Tag 自身高度(cva h-5/h-6/h-6 = 20/24/24,lg = md alias)。\n // iconSize 全尺寸統一 16(本檔 CAT icon render 硬寫 size={16})。\n sm: { height: 20, iconSize: 16, typography: 'caption' },\n md: { height: 24, iconSize: 16, typography: 'body' },\n lg: { height: 24, iconSize: 16, typography: 'body' },\n },\n // Tag 為純展示 indicator,無互動 state(spec「為何無 StateBehavior」段;段名指法免行號漂移)。\n // 唯一行為 dismiss 屬 Inline Action pattern,非 Tag 自有 state。\n states: ['default'],\n tokens: {\n // 2026-07-04 audit 註記:另有動態色相家族不逐一列 — CAT_SUBTLE 12 色相\n // bg-[var(--color-{hue}-1)] + step-7 深字、CAT_SOLID step-6 底 + --on-emphasis(-dark) 配字、\n // neutral solid bg-[var(--color-neutral-9)] — 全由 categorical-color.ts SSOT 衍生\n //(anatomy TOKEN_MAP 以 stripVar 機械衍生,零漂移)。\n bg: ['bg-neutral-active', 'bg-neutral-hover', 'bg-secondary', 'bg-transparent'],\n fg: ['text-foreground', 'text-inverse-fg'],\n ring: [],\n },\n defaultVariant: 'neutral',\n defaultSize: 'md',\n} as const\n\nexport { Tag, tagVariants }\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"tag.js","sources":["../../../src/components/Tag/tag.tsx"],"sourcesContent":["import * as React from \"react\"\nimport { cva, type VariantProps } from \"class-variance-authority\"\nimport { X } from \"lucide-react\"\nimport type { LucideIcon } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Tooltip, TooltipTrigger, TooltipContent } from \"@/design-system/components/Tooltip/tooltip\"\nimport { ItemInlineActionButton } from \"@/design-system/patterns/element-anatomy/item-anatomy\"\nimport { CAT_SUBTLE, CAT_SOLID, CAT_INTERACT } from \"@/design-system/tokens/categorical-color\"\n\n// ── Tag(inline label)─────────────────────────────────────────────────────\n// 用於分類標籤、狀態標記、多選已選值。\n//\n// 三種尺寸(子元件補齊原則——消費端直接透傳 size,不做 mapping):\n// sm — 20px 高, 12px 字, 4px tag-px, font-medium(配 field sm)\n// md — 24px 高, 14px 字, 4px tag-px, font-normal(配 field md)— 預設\n// lg — 24px = md alias(配 field lg,子元件補齊原則)\n//\n// 截斷:max-w-40(160px),超出時文字 truncate + 自動 tooltip。\n// 用 Canvas measureText 偵測截斷(scrollWidth 在 flex 內不可靠)。\n\nlet _measureCtx: CanvasRenderingContext2D | null = null\nfunction getMeasureCtx() {\n if (!_measureCtx) _measureCtx = document.createElement('canvas').getContext('2d')\n return _measureCtx\n}\n\nconst tagVariants = cva(\n \"inline-flex items-center rounded-md border border-transparent transition-colors cursor-text\",\n {\n variants: {\n // color:categorical 色相(裝飾性分類,非語意狀態)。**消費 categorical-color SSOT**——\n // key X 一律對 `--color-X-*`(1:1,零 offset)。neutral 非色相(無 hue),用 secondary 底自處理。\n // 2026-06-04 修:原 `red` 誤接 `--color-deep-orange-*`(red=品牌紅 hue-25 ≠ deep-orange hue-38\n // ≠ 語意 --error〔= deep-orange〕);改消費 SSOT 後 red→`--color-red-*`,並補齊全 12 色相。\n color: {\n neutral: \"bg-secondary text-foreground\",\n ...CAT_SUBTLE,\n },\n size: {\n sm: \"h-5 px-1 text-caption font-medium\",\n md: \"h-6 px-1 text-body font-normal\",\n lg: \"h-6 px-1 text-body font-normal\",\n },\n },\n defaultVariants: {\n color: \"neutral\",\n size: \"md\",\n },\n }\n)\n\n// ── Solid variant 色彩(step-6 底 + on-emphasis 配對文字,消費 categorical-color SSOT)──\n// 白字 --on-emphasis(夠深的 hue)/ 深字 --on-emphasis-dark(亮 hue:yellow/amber/orange/lime);green 白字例外。\n// **消費 categorical-color SSOT**(CAT_SOLID,1:1 色相)。neutral 非色相,用 neutral-9\n// + --inverse-fg(light=白字, dark=深字,自動反轉)自處理。\nconst SOLID_CLASSES: Record<string, string> = {\n neutral: 'bg-[var(--color-neutral-9)] text-inverse-fg',\n ...CAT_SOLID,\n}\n\nexport interface TagProps\n extends Omit<React.HTMLAttributes<HTMLDivElement>, 'prefix' | 'color'>,\n VariantProps<typeof tagVariants> {\n /** 左側 icon(LucideIcon),由 Tag 統一 16px。與 avatar 互斥。 */\n icon?: LucideIcon\n /** 左側 avatar(ReactNode),與 icon 互斥。 */\n avatar?: React.ReactNode\n /** 可移除——Tag 自動渲染 remove 按鈕並控制尺寸與互動樣式(從集合移除 item) */\n onRemove?: () => void\n /** remove 按鈕的 aria-label 目標名(a11y)。children 為非字串 ReactNode 時建議傳,否則 SR 讀不出移除哪個 tag。預設取 string children。對齊 FileUpload `removeAriaLabel`(同「移除集合項目」場景 + DS `<動作>AriaLabel` 命名慣例)。 */\n removeAriaLabel?: string\n /** @deprecated 2026-07-17 改名為 `removeAriaLabel`(對齊 FileUpload + onRemove callback 語意;dismiss 保留給 Alert/Notice/Toast「通知被忽略」)。舊名過渡一版後移除。 */\n dismissLabel?: string\n /** 深底模式(step-6 背景 + on-emphasis 配對前景;亮色 hue yellow/amber/orange/lime 用深字 --on-emphasis-dark,green 白字例外) */\n solid?: boolean\n /**\n * 2026-05-15 Q3 真 SSOT fix(per user verbatim「同空間兩判斷點」+「不要冰山一角」):\n * Tag 寬度由 parent constrain,不套預設 max-w-40(160px)。用於 cell-as-input narrow cell\n * (< 160px)時 Tag fit cell 寬度 + truncate ellipsis,而非 160px 後被 cell `overflow-hidden`\n * 硬切。對齊「同 cell width → 同 overflow 判斷」SSOT。Default false 保 backward compat\n * (wrap layout / pill rail 等仍受 160px 保護)。\n */\n unbounded?: boolean\n}\n\n// ── Solid dismiss hover/active bg ──\n// **消費 categorical-color SSOT**(CAT_INTERACT,semantic --{hue}-hover/active token),\n// 跟 --primary-hover/active 同模式:solid 色彩 shade change(hover 較亮 step、active 較暗 step),\n// 在 semantic 層做 dark mode swap 確保方向跨 mode 一致。\n// neutral 特例:bg 是 neutral-9 隨 mode 反轉,用 --inverse-neutral-* 鏡射,自處理。\nconst SOLID_DISMISS_HOVER: Record<string, { hover: string; active: string }> = {\n neutral: { hover: 'var(--inverse-neutral-hover)', active: 'var(--inverse-neutral-active)' },\n ...CAT_INTERACT,\n}\n\n// ── Dismiss(internal)────────────────────────────────────────────────────\n// 走 `ItemInlineActionButton`(item-anatomy SSOT)+ `hoverBgClassName` override prop\n// (2026-05-01 整合,消除原 Tag 自刻 `<button>` 繞 DS infra 的 tech debt)。\n//\n// 視覺對齊:`size=\"md\"` → icon 16 / hover-bg 18,跟 Tag 既有手刻幾何完全相等。\n// Solid variant(blue/green/red 等)透過 `hoverBgClassName` 套色相 override token;\n// Subtle variant 落用 ItemInlineActionButton 預設 neutral-hover。\n// 圖標色繼承 Tag 文字色 → `text-current` 三態覆寫。\n\nfunction TagDismiss({ onRemove, label, solid, color }: { onRemove: () => void; label: string; solid?: boolean; color?: string }) {\n const solidColors = solid && color ? SOLID_DISMISS_HOVER[color] : undefined\n\n return (\n <ItemInlineActionButton\n icon={X}\n size=\"md\"\n onClick={(e) => { e.stopPropagation(); onRemove() }}\n aria-label={label ? `移除 ${label}` : '移除'}\n style={solidColors ? ({ '--dismiss-hover': solidColors.hover, '--dismiss-active': solidColors.active } as React.CSSProperties) : undefined}\n hoverBgClassName={\n solidColors\n ? 'group-hover/action:bg-[var(--dismiss-hover)] group-active/action:bg-[var(--dismiss-active)]'\n : undefined\n }\n // Override default fg-muted → 繼承 Tag 文字色(label 同色)\n className=\"text-current hover:text-current active:text-current\"\n />\n )\n}\n\nfunction TagInner(\n { className, color, size, icon: Icon, avatar, onRemove, removeAriaLabel, dismissLabel, solid, unbounded = false, children, style, ...props }: TagProps,\n forwardedRef: React.ForwardedRef<HTMLDivElement>,\n) {\n const solidClass = solid ? SOLID_CLASSES[color ?? 'neutral'] : undefined\n const ownRef = React.useRef<HTMLDivElement | null>(null)\n const [isTruncated, setIsTruncated] = React.useState(false)\n\n React.useLayoutEffect(() => {\n const el = ownRef.current\n if (!el) return\n const ctx = getMeasureCtx()\n const check = () => {\n const textSpan = el.querySelector('[data-tag-text]')\n if (!textSpan || !ctx) return\n const text = textSpan.textContent || ''\n const cs = getComputedStyle(textSpan)\n ctx.font = `${cs.fontWeight} ${cs.fontSize} ${cs.fontFamily}`\n const textWidth = ctx.measureText(text).width\n const padL = parseFloat(cs.paddingLeft) || 0\n const padR = parseFloat(cs.paddingRight) || 0\n const needed = textWidth + padL + padR\n setIsTruncated(needed > (textSpan as HTMLElement).clientWidth + 1)\n }\n check()\n const obs = new ResizeObserver(check)\n obs.observe(el)\n return () => obs.disconnect()\n }, [children])\n\n const label = typeof children === 'string' ? children : ''\n\n // ── Dev-mode warning:icon 與 avatar 互斥 ──\n // 兩者同為 prefix slot(tag.spec.md Props 表「與 avatar 互斥」),同時傳會並列渲染破壞 Tag\n // 內部結構;原僅 jsdoc/spec 約束會靜默通過 → 2026-07-05 對齊 Button overlayBadge dev-warn\n // 先例(button.tsx「Dev-mode warning」段)補齊家族一致 runtime 防線。\n if (process.env.NODE_ENV !== 'production' && Icon && avatar) {\n // eslint-disable-next-line no-console\n console.warn(\n '[DS Tag] `icon` 與 `avatar` 互斥(同為 prefix slot),同時傳會並列渲染破壞 Tag 內部結構,請只擇一。SSOT:tag.spec.md Props 表 + tag.principles.stories.tsx IconRule。'\n )\n }\n\n const tag = (\n <div\n ref={(el) => {\n ownRef.current = el\n if (typeof forwardedRef === 'function') forwardedRef(el)\n else if (forwardedRef) (forwardedRef as React.MutableRefObject<HTMLDivElement | null>).current = el\n }}\n data-tag-root=\"\"\n // 截斷時條件性可 focus(2026-07-06 user 拍板「確認世界級有這樣做就做」——APG Tooltip\n // pattern 原文:tooltip 在元素「收到鍵盤焦點」或 hover 時顯示,trigger 可 focus 是\n // W3C 官方 pattern 前提;Radix Tooltip focus-open 自動生效 → 鍵盤 Tab 到即見全文。\n // 未截斷不入 tab 序(維持 tag.spec「靜態 Tag 不取得 focus」預設,不汙染 tab order)。\n tabIndex={isTruncated ? 0 : undefined}\n className={cn(\n tagVariants({ color, size }),\n solidClass,\n 'w-fit min-w-0 overflow-hidden',\n isTruncated && 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1',\n className,\n )}\n // 2026-05-18 Round 5 fix(per Codex M31 Round 5 verdict + user 拍板「那就開始做」):\n // 用 CSS var `--combobox-tag-area-inline-size`(由 Combobox useOverflowCount JS-injected)取代\n // `min(100%, 10rem)` cyclic percentage。CSS Sizing 3 §5.2.1:percentage 在 indefinite containing\n // block 退化為 initial value → Round 4 的 100% 沒 enforce。改 explicit px 值(JS measured)避此 trap。\n // unbounded=true:cap = inject 寬(回 cell-as-input narrow cell 原 behavior)\n // default:cap = min(inject 寬, 160px)— 兩 cap 取小。fallback(無 var,Form context 等)= 100% / 10rem。\n style={{\n maxWidth: unbounded\n ? 'var(--combobox-tag-area-inline-size, 100%)'\n : 'min(var(--combobox-tag-area-inline-size, 10rem), 10rem)',\n ...style,\n }}\n {...props}\n >\n {Icon && <Icon size={16} aria-hidden />}\n {avatar && <span className=\"shrink-0 w-4 h-4 rounded-full overflow-hidden inline-grid place-content-center [&>*]:w-full [&>*]:h-full\">{avatar}</span>}\n <span data-tag-text=\"\" className=\"px-1 truncate min-w-0\">{children}</span>\n {onRemove && <TagDismiss onRemove={onRemove} label={removeAriaLabel ?? dismissLabel ?? label} solid={solid} color={color ?? 'neutral'} />}\n </div>\n )\n\n if (!isTruncated) return tag\n\n return (\n <Tooltip>\n <TooltipTrigger asChild>{tag}</TooltipTrigger>\n <TooltipContent>{children}</TooltipContent>\n </Tooltip>\n )\n}\n\nconst Tag = React.forwardRef<HTMLDivElement, TagProps>(TagInner)\nTag.displayName = 'Tag'\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 tagMeta = {\n component: 'Tag',\n family: 3,\n // categorical 色相(裝飾性分類,非語意狀態)。**1:1 對 `--color-{hue}-*` primitive,零 offset**。\n // 不對應語意 token——語意狀態(error/info/success/warning)走 Notice / Alert 等狀態元件,\n // 不是 Tag 色相。2026-06-04 修:移除原「red 對應 --error / blue 對應 --info ...」誤導框架\n // (red = 品牌紅 hue-25,跟語意 --error〔= deep-orange〕無關)。\n variants: {\n neutral: { purpose: '通用分類、草稿、無特定語義(secondary 底)' },\n blue: { purpose: 'categorical 色相(--color-blue-*)' },\n green: { purpose: 'categorical 色相(--color-green-*)' },\n 'deep-orange': { purpose: 'categorical 色相(--color-deep-orange-*,hue 38)' },\n yellow: { purpose: 'categorical 色相(--color-yellow-*,淺底深字)' },\n red: { purpose: 'categorical 色相(--color-red-*,品牌紅家族 hue 25;≠ 語意 --error)' },\n orange: { purpose: 'categorical 色相(--color-orange-*)' },\n amber: { purpose: 'categorical 色相(--color-amber-*,淺底深字)' },\n lime: { purpose: 'categorical 色相(--color-lime-*)' },\n turquoise: { purpose: 'categorical 色相(--color-turquoise-*)' },\n indigo: { purpose: 'categorical 色相(--color-indigo-*)' },\n purple: { purpose: 'categorical 色相(--color-purple-*)' },\n magenta: { purpose: 'categorical 色相(--color-magenta-*)' },\n },\n sizes: {\n // Tag 尺寸不引用 field-height token(spec「尺寸」段——Tag 與 Field 尺寸獨立;段名指法免行號漂移)。\n // height = Tag 自身高度(cva h-5/h-6/h-6 = 20/24/24,lg = md alias)。\n // iconSize 全尺寸統一 16(本檔 CAT icon render 硬寫 size={16})。\n sm: { height: 20, iconSize: 16, typography: 'caption' },\n md: { height: 24, iconSize: 16, typography: 'body' },\n lg: { height: 24, iconSize: 16, typography: 'body' },\n },\n // Tag 為純展示 indicator,無互動 state(spec「為何無 StateBehavior」段;段名指法免行號漂移)。\n // 唯一行為 dismiss 屬 Inline Action pattern,非 Tag 自有 state。\n states: ['default'],\n tokens: {\n // 2026-07-04 audit 註記:另有動態色相家族不逐一列 — CAT_SUBTLE 12 色相\n // bg-[var(--color-{hue}-1)] + step-7 深字、CAT_SOLID step-6 底 + --on-emphasis(-dark) 配字、\n // neutral solid bg-[var(--color-neutral-9)] — 全由 categorical-color.ts SSOT 衍生\n //(anatomy TOKEN_MAP 以 stripVar 機械衍生,零漂移)。\n bg: ['bg-neutral-active', 'bg-neutral-hover', 'bg-secondary', 'bg-transparent'],\n fg: ['text-foreground', 'text-inverse-fg'],\n ring: [],\n },\n defaultVariant: 'neutral',\n defaultSize: 'md',\n} as const\n\nexport { Tag, tagVariants }\n"],"names":[],"mappings":"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"}
@@ -5,12 +5,12 @@ import type { FieldMode, FieldVariant } from '../../components/Field/field-types
5
5
  * Textarea — 多行文字輸入
6
6
  *
7
7
  * ── 定位 ────────────────────────────────────────────────────────────────
8
- * 多行版本的 Input,edit / display / readonly / disabled 四態與 Input 邏輯一致(Phase B1 2026-05-05)。
8
+ * 多行版本的 Input,edit / view / readonly / disabled 四態與 Input 邏輯一致(Phase B1 2026-05-05)。
9
9
  * 不同於 Input:
10
10
  * - 沒有固定 field-height(高度由 rows 或 min-h 決定)
11
11
  * - 沒有 startIcon / endAction(textarea 慣例不放 icon)
12
12
  * - readonly 呈現保留邊框與 padding,只改底色,讓多行文字有合理閱讀區
13
- * - display 渲染 <div> + white-space:pre-wrap 保留多行文本
13
+ * - view 渲染 <div> + white-space:pre-wrap 保留多行文本
14
14
  *
15
15
  * ── Padding 規則 ───────────────────────────────────────────────────────
16
16
  * 多行內容必須有上下內距才能閱讀舒適。不沿用 Input 的 items-center,
@@ -24,13 +24,13 @@ import type { FieldMode, FieldVariant } from '../../components/Field/field-types
24
24
  * 預設 rows={3}。消費者可透過 rows prop 調整,或透過 min-h-* className 覆寫。
25
25
  */
26
26
  declare const textareaVariants: (props?: ({
27
- mode?: "display" | "disabled" | "edit" | "readonly" | null | undefined;
27
+ mode?: "view" | "disabled" | "edit" | "readonly" | null | undefined;
28
28
  variant?: "default" | "naked" | null | undefined;
29
29
  size?: "sm" | "md" | "lg" | null | undefined;
30
30
  error?: boolean | null | undefined;
31
31
  } & import("class-variance-authority/types").ClassProp) | undefined) => string;
32
32
  export interface TextareaProps extends Omit<React.TextareaHTMLAttributes<HTMLTextAreaElement>, 'size'>, Omit<VariantProps<typeof textareaVariants>, 'mode' | 'variant'> {
33
- /** Field display mode */
33
+ /** Field view mode */
34
34
  mode?: FieldMode;
35
35
  /**
36
36
  * Visual chrome(正交於 mode);Phase B1(2026-05-05)新增。透傳自 FieldContext.variant,per-prop override。
@@ -1 +1 @@
1
- {"version":3,"file":"textarea.d.ts","sourceRoot":"","sources":["../../../src/components/Textarea/textarea.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAC9B,OAAO,EAAO,KAAK,YAAY,EAAE,MAAM,0BAA0B,CAAA;AAEjE,OAAO,KAAK,EAAE,SAAS,EAAE,YAAY,EAAwB,MAAM,8CAA8C,CAAA;AAKjH;;;;;;;;;;;;;;;;;;;;;GAqBG;AAIH,QAAA,MAAM,gBAAgB;;;;;8EAgHrB,CAAA;AAED,MAAM,WAAW,aACf,SAAQ,IAAI,CAAC,KAAK,CAAC,sBAAsB,CAAC,mBAAmB,CAAC,EAAE,MAAM,CAAC,EACrE,IAAI,CAAC,YAAY,CAAC,OAAO,gBAAgB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACjE,yBAAyB;IACzB,IAAI,CAAC,EAAE,SAAS,CAAA;IAChB;;;;;OAKG;IACH,OAAO,CAAC,EAAE,YAAY,CAAA;IACtB,sDAAsD;IACtD,KAAK,CAAC,EAAE,OAAO,CAAA;CAChB;AAGD,QAAA,MAAM,QAAQ,2FA0Hb,CAAA;AAMD,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;CAsBf,CAAA;AAEV,OAAO,EAAE,QAAQ,EAAE,gBAAgB,EAAE,CAAA"}
1
+ {"version":3,"file":"textarea.d.ts","sourceRoot":"","sources":["../../../src/components/Textarea/textarea.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAC9B,OAAO,EAAO,KAAK,YAAY,EAAE,MAAM,0BAA0B,CAAA;AAEjE,OAAO,KAAK,EAAE,SAAS,EAAE,YAAY,EAAwB,MAAM,8CAA8C,CAAA;AAKjH;;;;;;;;;;;;;;;;;;;;;GAqBG;AAIH,QAAA,MAAM,gBAAgB;;;;;8EAgHrB,CAAA;AAED,MAAM,WAAW,aACf,SAAQ,IAAI,CAAC,KAAK,CAAC,sBAAsB,CAAC,mBAAmB,CAAC,EAAE,MAAM,CAAC,EACrE,IAAI,CAAC,YAAY,CAAC,OAAO,gBAAgB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACjE,sBAAsB;IACtB,IAAI,CAAC,EAAE,SAAS,CAAA;IAChB;;;;;OAKG;IACH,OAAO,CAAC,EAAE,YAAY,CAAA;IACtB,sDAAsD;IACtD,KAAK,CAAC,EAAE,OAAO,CAAA;CAChB;AAGD,QAAA,MAAM,QAAQ,2FA0Hb,CAAA;AAMD,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;CAsBf,CAAA;AAEV,OAAO,EAAE,QAAQ,EAAE,gBAAgB,EAAE,CAAA"}
@@ -20,7 +20,7 @@ const textareaVariants = cva(
20
20
  variants: {
21
21
  mode: {
22
22
  edit: "",
23
- display: "",
23
+ view: "",
24
24
  readonly: "",
25
25
  disabled: ""
26
26
  },
@@ -52,11 +52,13 @@ const textareaVariants = cva(
52
52
  },
53
53
  { mode: "edit", variant: "default", error: false, className: "focus-within:!border-primary focus-within:hover:!border-primary" },
54
54
  {
55
- mode: "display",
55
+ // 2026-07-16 round16 Model A(user GO,推翻 2026-05-13 Path Ⅰ 的 `!px-0 !py-0`):
56
+ // Textarea view×default = **edit 幾何減 chrome** — 保留 base `px-[var(--field-px)] py-2`(= edit 內距),
57
+ // 只拔 border/bg。多行 read↔edit 零跳(view 與 edit 同一顆 Textarea、py-2 天生一致)。跟 Field wrapper
58
+ // view×default 同 SSOT(field-wrapper.tsx)。
59
+ mode: "view",
56
60
  variant: "default",
57
- // 2026-05-13 Q3 Path Ⅰ:Textarea default display zero chrome,!px-0 !py-0 override base `px-[var(--field-px)] py-2`
58
- // (跟 Input 同 SSOT,per field-controls.spec.md (d))
59
- className: "bg-transparent border border-transparent !px-0 !py-0"
61
+ className: "bg-transparent border border-transparent"
60
62
  },
61
63
  {
62
64
  mode: "readonly",
@@ -90,7 +92,7 @@ const textareaVariants = cva(
90
92
  "!px-[var(--table-cell-px)] !py-[var(--table-cell-py)]",
91
93
  "border border-border",
92
94
  "hover:border-border-hover",
93
- // textarea UA stylesheet 預設 line-height: normal(1.2-1.5 不定),會跟 display
95
+ // textarea UA stylesheet 預設 line-height: normal(1.2-1.5 不定),會跟 view
94
96
  // `<div>` text-body line-height: 1.5(21px @ 14px)不一致 → cell 進 edit 後 height
95
97
  // shift。顯式 leading 對齊 div 行為。
96
98
  "!leading-[1.5]"
@@ -98,16 +100,14 @@ const textareaVariants = cva(
98
100
  },
99
101
  { mode: "edit", variant: "naked", error: false, className: "focus-visible:!border-primary focus-visible:hover:!border-primary" },
100
102
  // 2026-05-13 Q1 R4 verify(per codex Q1 verdict 補 Textarea nuance):
101
- // Textarea naked display/readonly/disabled 用 `!h-full`,**不**對齊 Field wrapper 的 `!h-auto`。
103
+ // Textarea view×naked 用 `!h-full`,**不**對齊 Field wrapper 的 `!h-auto`。
102
104
  // Why divergence:textarea 是 native form element 帶 intrinsic rows-based height,且 cell 內
103
105
  // multi-line text 需要撐滿 cell 而非依 line-height intrinsic。`!h-full` 讓 textarea 填滿 cell
104
106
  // 高度,文字 anchored to cell.top + cell padding(同視覺結果 Field wrapper autoRow !h-auto)。
105
107
  // 此 divergence intentional + documented;非 SSOT violation。
106
- { mode: "display", variant: "naked", className: "bg-transparent !rounded-none !h-full !resize-none !px-0 !py-0 border border-transparent !leading-[1.5]" },
107
- { mode: "readonly", variant: "naked", className: "bg-transparent !rounded-none !h-full !resize-none !px-0 !py-0 border border-transparent !leading-[1.5]" },
108
- // 2026-05-13 codex V2 fix:移除 `opacity-disabled` blanket(對齊 field-wrapper.tsx naked R3 fix +
109
- // color.spec.md:729 逃生艙 rule)。Textarea 已用具體 `text-fg-disabled` token,不需要 wrapper opacity。
110
- { mode: "disabled", variant: "naked", className: "bg-transparent !rounded-none cursor-not-allowed text-fg-disabled !h-full !resize-none !px-0 !py-0 border border-transparent !leading-[1.5]" }
108
+ { mode: "view", variant: "naked", className: "bg-transparent !rounded-none !h-full !resize-none !px-0 !py-0 border border-transparent !leading-[1.5]" }
109
+ // 2026-07-16 round16:`readonly×naked` / `disabled×naked` 死格移除(鏡射 field-wrapper.tsx;
110
+ // DataTable cell disabled + readonly 從不入 naked,全庫 0 消費)。
111
111
  ],
112
112
  defaultVariants: {
113
113
  mode: "edit",
@@ -141,7 +141,7 @@ const Textarea = React.forwardRef(
141
141
  const resolvedMode = useResolvedFieldMode({ mode: modeProp, disabled, readOnly });
142
142
  const emptyDisplay = useFieldEmptyDisplay();
143
143
  const isEditable = resolvedMode === "edit";
144
- const isDisplay = resolvedMode === "display";
144
+ const isView = resolvedMode === "view";
145
145
  const inputId = idProp ?? (fieldCtx == null ? void 0 : fieldCtx.id);
146
146
  const ariaDescribedBy = ariaDescribedByProp ?? (fieldCtx == null ? void 0 : fieldCtx.descriptionId);
147
147
  const ariaErrorMessage = ariaErrorMessageProp ?? (error ? fieldCtx == null ? void 0 : fieldCtx.errorId : void 0);
@@ -151,7 +151,7 @@ const Textarea = React.forwardRef(
151
151
  defaultValue: defaultValue ?? ""
152
152
  });
153
153
  const displayValue = resolved != null && resolved !== "" ? String(resolved) : null;
154
- const showDisplaySpan = isDisplay || resolvedMode === "readonly" && displayValue == null;
154
+ const showDisplaySpan = isView || resolvedMode === "readonly" && displayValue == null;
155
155
  const innerRef = React.useRef(null);
156
156
  React.useEffect(() => {
157
157
  var _a;
@@ -168,7 +168,7 @@ const Textarea = React.forwardRef(
168
168
  else if (ref) ref.current = el;
169
169
  }, [ref]);
170
170
  if (showDisplaySpan) {
171
- const spanMode = isDisplay ? "display" : "readonly";
171
+ const spanMode = isView ? "view" : "readonly";
172
172
  return /* @__PURE__ */ jsx(
173
173
  "div",
174
174
  {
@@ -1 +1 @@
1
- {"version":3,"file":"textarea.js","sources":["../../../src/components/Textarea/textarea.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 { cva, type VariantProps } from 'class-variance-authority'\nimport { cn } from '@/lib/utils'\nimport type { FieldMode, FieldVariant, FieldVariantInternal } from '@/design-system/components/Field/field-types'\nimport { useFieldContext, useResolvedFieldSize, useResolvedFieldMode, useResolvedFieldVariant, useResolvedFieldInvalid } from '@/design-system/components/Field/field-context'\nimport { useFieldEmptyDisplay, fieldEmptyColorClass } from '@/design-system/components/Field/field-context'\nimport { useControllable } from '@/design-system/hooks/use-controllable'\n\n/**\n * Textarea — 多行文字輸入\n *\n * ── 定位 ────────────────────────────────────────────────────────────────\n * 多行版本的 Input,edit / display / readonly / disabled 四態與 Input 邏輯一致(Phase B1 2026-05-05)。\n * 不同於 Input:\n * - 沒有固定 field-height(高度由 rows 或 min-h 決定)\n * - 沒有 startIcon / endAction(textarea 慣例不放 icon)\n * - readonly 呈現保留邊框與 padding,只改底色,讓多行文字有合理閱讀區\n * - display 渲染 <div> + white-space:pre-wrap 保留多行文本\n *\n * ── Padding 規則 ───────────────────────────────────────────────────────\n * 多行內容必須有上下內距才能閱讀舒適。不沿用 Input 的 items-center,\n * 改用 py-2(8px)固定上下內距 + px-[var(--field-px)](12px token,左右內距 SSOT,與 Input/Field family 一致)。\n *\n * ── Size ────────────────────────────────────────────────────────────────\n * sm / md → text-body(14px)\n * lg → text-body-lg(16px)\n *\n * ── rows / min-h ───────────────────────────────────────────────────────\n * 預設 rows={3}。消費者可透過 rows prop 調整,或透過 min-h-* className 覆寫。\n */\n\n// Phase B1(2026-05-05):chrome variant(default / naked;`bare` 2026-07-09 退役),mode×chrome 的\n// chrome 規則由 compoundVariants 決定,鏡射 fieldWrapperStyles 對齊 canonical(Phase D 將整併進 fieldWrapperStyles)。\nconst textareaVariants = cva(\n [\n 'w-full rounded-md',\n 'text-foreground font-normal',\n 'outline-none resize-y',\n 'placeholder:text-fg-muted',\n // K10 fix(2026-05-04):disabled 時 placeholder + text 切 fg-disabled(parallel 到 bareInputStyles)\n // Textarea 自身 `<textarea disabled>` 帶 disabled HTML attribute,用 `disabled:` variant 直接命中\n 'disabled:placeholder:text-fg-disabled disabled:text-fg-disabled',\n 'px-[var(--field-px)] py-2',\n 'transition-colors duration-150',\n ],\n {\n variants: {\n mode: {\n edit: '',\n display: '',\n readonly: '',\n disabled: '',\n },\n // chrome 對齊 fieldWrapperStyles.variant(default / naked;2026-07-09 `bare` 退役)。\n variant: {\n default: '',\n naked: '',\n },\n size: {\n sm: 'text-body',\n md: 'text-body',\n lg: 'text-body-lg',\n },\n // error(2026-07-04 Q1,鏡射 fieldWrapperStyles error variant;Phase D 整併時一起收)\n error: {\n true: '',\n false: '',\n },\n },\n compoundVariants: [\n // default chrome × mode\n {\n mode: 'edit',\n variant: 'default',\n className: [\n 'bg-surface border border-border',\n 'hover:border-border-hover',\n ],\n },\n { mode: 'edit', variant: 'default', error: false, className: 'focus-within:!border-primary focus-within:hover:!border-primary' },\n {\n mode: 'display',\n variant: 'default',\n // 2026-05-13 Q3 Path Ⅰ:Textarea default display zero chrome,!px-0 !py-0 override base `px-[var(--field-px)] py-2`\n // (跟 Input 同 SSOT,per field-controls.spec.md (d))\n className: 'bg-transparent border border-transparent !px-0 !py-0',\n },\n {\n mode: 'readonly',\n variant: 'default',\n // 2026-07-13 a11y(WCAG 2.4.7):readonly 有值渲 native <textarea readOnly> 可鍵盤聚焦,base\n // outline-none + border-transparent 無焦點指示 → 鏡射 field-wrapper.tsx readonly ring idiom\n // (field-controls.spec.md「readonly focus ring」)。textarea 為裸 focusable element,直接用\n // focus-visible:(非 :has()),僅鍵盤聚焦顯示、滑鼠點擊不觸發。\n className: 'bg-readonly border border-transparent focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1',\n },\n {\n mode: 'disabled',\n variant: 'default',\n className: 'bg-disabled border border-transparent cursor-not-allowed text-fg-disabled',\n },\n // (2026-07-09 `bare` chrome × mode compounds 退役已移除;error:true 為 variant-agnostic 保留)\n {\n mode: 'edit',\n error: true,\n className: 'border-error hover:border-error-hover focus-within:!border-error focus-within:hover:!border-error',\n },\n // (2026-07-09 display×bare / readonly×bare / disabled×bare compounds 退役已移除)\n // naked chrome × mode — cell-as-input substrate(2026-05-06 v14 revert v12)。\n // v12 `!absolute -inset-px` autoRowHeight 不相容 → revert v9 baseline + 保留 v13.3\n // focus !important。focus-visible 用 textarea 自身 selector(focusable element)。\n {\n mode: 'edit',\n variant: 'naked',\n className: [\n 'bg-transparent !rounded-none !resize-none !h-full',\n '!px-[var(--table-cell-px)] !py-[var(--table-cell-py)]',\n 'border border-border',\n 'hover:border-border-hover',\n // textarea UA stylesheet 預設 line-height: normal(1.2-1.5 不定),會跟 display\n // `<div>` text-body line-height: 1.5(21px @ 14px)不一致 → cell 進 edit 後 height\n // shift。顯式 leading 對齊 div 行為。\n '!leading-[1.5]',\n ],\n },\n { mode: 'edit', variant: 'naked', error: false, className: 'focus-visible:!border-primary focus-visible:hover:!border-primary' },\n // 2026-05-13 Q1 R4 verify(per codex Q1 verdict 補 Textarea nuance):\n // Textarea naked display/readonly/disabled 用 `!h-full`,**不**對齊 Field wrapper 的 `!h-auto`。\n // Why divergence:textarea 是 native form element 帶 intrinsic rows-based height,且 cell 內\n // multi-line text 需要撐滿 cell 而非依 line-height intrinsic。`!h-full` 讓 textarea 填滿 cell\n // 高度,文字 anchored to cell.top + cell padding(同視覺結果 Field wrapper autoRow !h-auto)。\n // 此 divergence intentional + documented;非 SSOT violation。\n { mode: 'display', variant: 'naked', className: 'bg-transparent !rounded-none !h-full !resize-none !px-0 !py-0 border border-transparent !leading-[1.5]' },\n { mode: 'readonly', variant: 'naked', className: 'bg-transparent !rounded-none !h-full !resize-none !px-0 !py-0 border border-transparent !leading-[1.5]' },\n // 2026-05-13 codex V2 fix:移除 `opacity-disabled` blanket(對齊 field-wrapper.tsx naked R3 fix +\n // color.spec.md:729 逃生艙 rule)。Textarea 已用具體 `text-fg-disabled` token,不需要 wrapper opacity。\n { mode: 'disabled', variant: 'naked', className: 'bg-transparent !rounded-none cursor-not-allowed text-fg-disabled !h-full !resize-none !px-0 !py-0 border border-transparent !leading-[1.5]' },\n ],\n defaultVariants: {\n mode: 'edit',\n variant: 'default',\n size: 'md',\n error: false,\n },\n }\n)\n\nexport interface TextareaProps\n extends Omit<React.TextareaHTMLAttributes<HTMLTextAreaElement>, 'size'>,\n Omit<VariantProps<typeof textareaVariants>, 'mode' | 'variant'> {\n /** Field display mode */\n mode?: FieldMode\n /**\n * Visual chrome(正交於 mode);Phase B1(2026-05-05)新增。透傳自 FieldContext.variant,per-prop override。\n * - `'default'` — 完整 chrome(form 場景)\n * (2026-07-09 `bare` variant 退役;naked = cell-as-input substrate,@internal)\n * - `'naked'` — 完全無 chrome,cell-as-input(host cell 提供 border + focus frame,對齊 Airtable / Notion / Excel cell editing)\n */\n variant?: FieldVariant\n /** Error 狀態(正交於 mode)。border-error + aria-invalid。 */\n error?: boolean\n}\n\n// code-quality-allow: long-function — Textarea forwardRef body 含 mode×size×variant×error 4 軸 prop + autoFocus + aria 完整覆蓋,拆 sub-fn 會把 useFieldContext / fieldWrapperStyles 跨檔 drilling\nconst Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(\n (\n {\n mode: modeProp,\n variant: variantProp,\n error: errorProp = false,\n size: sizeProp,\n className,\n disabled,\n readOnly,\n rows = 3,\n value,\n defaultValue,\n id: idProp,\n 'aria-describedby': ariaDescribedByProp,\n 'aria-errormessage': ariaErrorMessageProp,\n ...props\n },\n ref\n ) => {\n // Field context 整合:disabled / mode / chrome / invalid / size / id 都能從 context 繼承\n const fieldCtx = useFieldContext()\n // chrome 透傳:per-prop override context\n const variant: FieldVariantInternal = useResolvedFieldVariant(variantProp)\n const error = useResolvedFieldInvalid(errorProp)\n const size = useResolvedFieldSize(sizeProp)\n // 2026-06-08 SSOT:mode 經 useResolvedFieldMode 統一解析(prop > 有效 disabled > fieldCtx.mode > readOnly > 'edit')\n const resolvedMode: FieldMode = useResolvedFieldMode({ mode: modeProp, disabled, readOnly })\n const emptyDisplay = useFieldEmptyDisplay()\n const isEditable = resolvedMode === 'edit'\n const isDisplay = resolvedMode === 'display'\n const inputId = idProp ?? fieldCtx?.id\n const ariaDescribedBy = ariaDescribedByProp ?? fieldCtx?.descriptionId\n const ariaErrorMessage = ariaErrorMessageProp ?? (error ? fieldCtx?.errorId : undefined)\n\n // ── display mode(+ readonly 空值)純展示,渲染 <div> 取代 <textarea>(white-space:pre-wrap 保留多行) ──\n // 對齊 Carbon read-only / Cloudscape display-mode\n // 2026-07-08 user 拍板:readonly 空值也走 display-div 顯 '-'(readonly **有值** 仍走下方\n // native <textarea readOnly> 保留選取/複製語意 — field-controls.spec.md「null / undefined 值」)。\n // 2026-07-13 fix:空值判斷需認得 uncontrolled defaultValue — readonly / display 也要顯示內容,\n // 不可誤判為空值渲 '-'(原為靜態 value ?? defaultValue)。\n // 2026-07-14 R2(dual-model consensus,同 Input input.tsx):resolved value 改走 useControllable\n // 內部 SSOT(Radix idiom)。原靜態版有 typing-stale gap:uncontrolled 打字後外部切\n // display / readonly 仍顯示 stale defaultValue。uncontrolled 時 native textarea 由\n // value={resolved} 內部驅動(defaultValue 只作初始值,不落 DOM attribute),同時修\n // remount-stale(切 display 再回 edit 從 defaultValue attribute 重掛)+ form.reset() stale\n // (HTML 標準 reset 不發 input event,下方 reset bridge 修)。controlled(傳 value)時\n // useControllable 純 passthrough,行為與原本完全一致。consumer onChange 不接進 hook\n // (保留 native event signature),於 <textarea> onChange 另行轉發。\n const isControlled = value !== undefined\n const [resolved, setResolved] = useControllable<string | number | readonly string[]>({\n value: value as string | number | readonly string[] | undefined,\n defaultValue: defaultValue ?? '',\n })\n const displayValue = resolved != null && resolved !== '' ? String(resolved) : null\n const showDisplaySpan = isDisplay || (resolvedMode === 'readonly' && displayValue == null)\n\n // form.reset() bridge(uncontrolled only):reset 恢復 defaultValue 但不發 input event →\n // 手動把 resolved 歸位 defaultValue。keyed on showDisplaySpan:display 分支不掛 native\n // textarea,回 edit 重掛時 effect 重跑補掛 listener。\n const innerRef = React.useRef<HTMLTextAreaElement | null>(null)\n React.useEffect(() => {\n if (isControlled || showDisplaySpan) return\n const form = innerRef.current?.form\n if (!form) return\n const handleReset = () => setResolved(defaultValue ?? '')\n form.addEventListener('reset', handleReset)\n return () => form.removeEventListener('reset', handleReset)\n }, [isControlled, showDisplaySpan, defaultValue, setResolved])\n // Merge refs(LinkInput setRef idiom,link-input.tsx:138)\n const setRef = React.useCallback((el: HTMLTextAreaElement | null) => {\n innerRef.current = el\n if (typeof ref === 'function') ref(el)\n else if (ref) (ref as React.MutableRefObject<HTMLTextAreaElement | null>).current = el\n }, [ref])\n\n if (showDisplaySpan) {\n const spanMode = isDisplay ? 'display' : 'readonly'\n return (\n <div\n id={inputId}\n data-field-mode={spanMode}\n aria-describedby={ariaDescribedBy}\n className={cn(\n textareaVariants({ mode: spanMode, variant: variant, size }),\n 'whitespace-pre-wrap break-words',\n displayValue == null && fieldEmptyColorClass(resolvedMode),\n className,\n )}\n >\n {displayValue ?? emptyDisplay}\n </div>\n )\n }\n\n return (\n <textarea\n ref={setRef}\n id={inputId}\n rows={rows}\n value={resolved}\n disabled={resolvedMode === 'disabled'}\n readOnly={resolvedMode === 'readonly'}\n aria-invalid={error || undefined}\n aria-required={fieldCtx?.required || undefined}\n aria-describedby={ariaDescribedBy}\n aria-errormessage={ariaErrorMessage}\n data-field-mode={resolvedMode}\n data-error={isEditable && error ? '' : undefined}\n className={cn(\n textareaVariants({ mode: resolvedMode, variant: variant, size, error }),\n className\n )}\n {...props}\n // 置於 {...props} 後:寫回 resolved SSOT(controlled 時 useControllable 內部為 no-op)再轉發 consumer onChange。\n onChange={(e) => {\n setResolved(e.target.value)\n props.onChange?.(e)\n }}\n />\n )\n }\n)\nTextarea.displayName = 'Textarea'\n\n// Story auto-compile metadata — Phase 1 mechanical migration(2026-04-24)\n// sizes 只反映 Textarea 真實控制的軸:typography(size 只控字體,不綁 field-height、無 icon slot —\n// 高度由 rows + resize-y 決定,per textarea.spec.md L88-92 / L64 / L113)。\nexport const textareaMeta = {\n component: 'Textarea',\n family: 4,\n variants: {\n\n },\n sizes: {\n sm: { typography: 'body' },\n md: { typography: 'body' },\n lg: { typography: 'body-lg' },\n },\n // states 對齊 cva compoundVariants + anatomy ColorMatrix 真實 state 集合;\n // text input 無 'active'(按下)視覺態(Material/Polaris/Carbon TextArea 共識)。\n // 'focus-visible' 泛指 focus 態:default chrome 實為 focus-within(滑鼠+鍵盤皆亮),naked chrome\n // 為 focus-visible(鍵盤限定),readonly 有值態為 focus-visible ring(見上方 compoundVariants)。\n states: ['default', 'hover', 'focus-visible', 'readonly', 'disabled', 'error'],\n tokens: {\n bg: ['bg-disabled', 'bg-readonly', 'bg-surface'], // 2026-07-04 補:readonly×default cva 實際消費(spec「Readonly 特例」主打 token)\n fg: ['text-fg-disabled', 'text-fg-muted', 'text-foreground'],\n ring: [],\n },\n defaultSize: 'md',\n} as const\n\nexport { Textarea, textareaVariants }\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"textarea.js","sources":["../../../src/components/Textarea/textarea.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 { cva, type VariantProps } from 'class-variance-authority'\nimport { cn } from '@/lib/utils'\nimport type { FieldMode, FieldVariant, FieldVariantInternal } from '@/design-system/components/Field/field-types'\nimport { useFieldContext, useResolvedFieldSize, useResolvedFieldMode, useResolvedFieldVariant, useResolvedFieldInvalid } from '@/design-system/components/Field/field-context'\nimport { useFieldEmptyDisplay, fieldEmptyColorClass } from '@/design-system/components/Field/field-context'\nimport { useControllable } from '@/design-system/hooks/use-controllable'\n\n/**\n * Textarea — 多行文字輸入\n *\n * ── 定位 ────────────────────────────────────────────────────────────────\n * 多行版本的 Input,edit / view / readonly / disabled 四態與 Input 邏輯一致(Phase B1 2026-05-05)。\n * 不同於 Input:\n * - 沒有固定 field-height(高度由 rows 或 min-h 決定)\n * - 沒有 startIcon / endAction(textarea 慣例不放 icon)\n * - readonly 呈現保留邊框與 padding,只改底色,讓多行文字有合理閱讀區\n * - view 渲染 <div> + white-space:pre-wrap 保留多行文本\n *\n * ── Padding 規則 ───────────────────────────────────────────────────────\n * 多行內容必須有上下內距才能閱讀舒適。不沿用 Input 的 items-center,\n * 改用 py-2(8px)固定上下內距 + px-[var(--field-px)](12px token,左右內距 SSOT,與 Input/Field family 一致)。\n *\n * ── Size ────────────────────────────────────────────────────────────────\n * sm / md → text-body(14px)\n * lg → text-body-lg(16px)\n *\n * ── rows / min-h ───────────────────────────────────────────────────────\n * 預設 rows={3}。消費者可透過 rows prop 調整,或透過 min-h-* className 覆寫。\n */\n\n// Phase B1(2026-05-05):chrome variant(default / naked;`bare` 2026-07-09 退役),mode×chrome 的\n// chrome 規則由 compoundVariants 決定,鏡射 fieldWrapperStyles 對齊 canonical(Phase D 將整併進 fieldWrapperStyles)。\nconst textareaVariants = cva(\n [\n 'w-full rounded-md',\n 'text-foreground font-normal',\n 'outline-none resize-y',\n 'placeholder:text-fg-muted',\n // K10 fix(2026-05-04):disabled 時 placeholder + text 切 fg-disabled(parallel 到 bareInputStyles)\n // Textarea 自身 `<textarea disabled>` 帶 disabled HTML attribute,用 `disabled:` variant 直接命中\n 'disabled:placeholder:text-fg-disabled disabled:text-fg-disabled',\n 'px-[var(--field-px)] py-2',\n 'transition-colors duration-150',\n ],\n {\n variants: {\n mode: {\n edit: '',\n view: '',\n readonly: '',\n disabled: '',\n },\n // chrome 對齊 fieldWrapperStyles.variant(default / naked;2026-07-09 `bare` 退役)。\n variant: {\n default: '',\n naked: '',\n },\n size: {\n sm: 'text-body',\n md: 'text-body',\n lg: 'text-body-lg',\n },\n // error(2026-07-04 Q1,鏡射 fieldWrapperStyles error variant;Phase D 整併時一起收)\n error: {\n true: '',\n false: '',\n },\n },\n compoundVariants: [\n // default chrome × mode\n {\n mode: 'edit',\n variant: 'default',\n className: [\n 'bg-surface border border-border',\n 'hover:border-border-hover',\n ],\n },\n { mode: 'edit', variant: 'default', error: false, className: 'focus-within:!border-primary focus-within:hover:!border-primary' },\n {\n // 2026-07-16 round16 Model A(user GO,推翻 2026-05-13 Path Ⅰ 的 `!px-0 !py-0`):\n // Textarea view×default = **edit 幾何減 chrome** — 保留 base `px-[var(--field-px)] py-2`(= edit 內距),\n // 只拔 border/bg。多行 read↔edit 零跳(view 與 edit 同一顆 Textarea、py-2 天生一致)。跟 Field wrapper\n // view×default 同 SSOT(field-wrapper.tsx)。\n mode: 'view',\n variant: 'default',\n className: 'bg-transparent border border-transparent',\n },\n {\n mode: 'readonly',\n variant: 'default',\n // 2026-07-13 a11y(WCAG 2.4.7):readonly 有值渲 native <textarea readOnly> 可鍵盤聚焦,base\n // outline-none + border-transparent 無焦點指示 → 鏡射 field-wrapper.tsx readonly ring idiom\n // (field-controls.spec.md「readonly focus ring」)。textarea 為裸 focusable element,直接用\n // focus-visible:(非 :has()),僅鍵盤聚焦顯示、滑鼠點擊不觸發。\n className: 'bg-readonly border border-transparent focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1',\n },\n {\n mode: 'disabled',\n variant: 'default',\n className: 'bg-disabled border border-transparent cursor-not-allowed text-fg-disabled',\n },\n // (2026-07-09 `bare` chrome × mode compounds 退役已移除;error:true 為 variant-agnostic 保留)\n {\n mode: 'edit',\n error: true,\n className: 'border-error hover:border-error-hover focus-within:!border-error focus-within:hover:!border-error',\n },\n // (2026-07-09 display×bare / readonly×bare / disabled×bare compounds 退役已移除)\n // naked chrome × mode — cell-as-input substrate(2026-05-06 v14 revert v12)。\n // v12 `!absolute -inset-px` autoRowHeight 不相容 → revert v9 baseline + 保留 v13.3\n // focus !important。focus-visible 用 textarea 自身 selector(focusable element)。\n {\n mode: 'edit',\n variant: 'naked',\n className: [\n 'bg-transparent !rounded-none !resize-none !h-full',\n '!px-[var(--table-cell-px)] !py-[var(--table-cell-py)]',\n 'border border-border',\n 'hover:border-border-hover',\n // textarea UA stylesheet 預設 line-height: normal(1.2-1.5 不定),會跟 view\n // `<div>` text-body line-height: 1.5(21px @ 14px)不一致 → cell 進 edit 後 height\n // shift。顯式 leading 對齊 div 行為。\n '!leading-[1.5]',\n ],\n },\n { mode: 'edit', variant: 'naked', error: false, className: 'focus-visible:!border-primary focus-visible:hover:!border-primary' },\n // 2026-05-13 Q1 R4 verify(per codex Q1 verdict 補 Textarea nuance):\n // Textarea view×naked 用 `!h-full`,**不**對齊 Field wrapper 的 `!h-auto`。\n // Why divergence:textarea 是 native form element 帶 intrinsic rows-based height,且 cell 內\n // multi-line text 需要撐滿 cell 而非依 line-height intrinsic。`!h-full` 讓 textarea 填滿 cell\n // 高度,文字 anchored to cell.top + cell padding(同視覺結果 Field wrapper autoRow !h-auto)。\n // 此 divergence intentional + documented;非 SSOT violation。\n { mode: 'view', variant: 'naked', className: 'bg-transparent !rounded-none !h-full !resize-none !px-0 !py-0 border border-transparent !leading-[1.5]' },\n // 2026-07-16 round16:`readonly×naked` / `disabled×naked` 死格移除(鏡射 field-wrapper.tsx;\n // DataTable cell 廢 disabled 態 + readonly 從不入 naked,全庫 0 消費)。\n ],\n defaultVariants: {\n mode: 'edit',\n variant: 'default',\n size: 'md',\n error: false,\n },\n }\n)\n\nexport interface TextareaProps\n extends Omit<React.TextareaHTMLAttributes<HTMLTextAreaElement>, 'size'>,\n Omit<VariantProps<typeof textareaVariants>, 'mode' | 'variant'> {\n /** Field view mode */\n mode?: FieldMode\n /**\n * Visual chrome(正交於 mode);Phase B1(2026-05-05)新增。透傳自 FieldContext.variant,per-prop override。\n * - `'default'` — 完整 chrome(form 場景)\n * (2026-07-09 `bare` variant 退役;naked = cell-as-input substrate,@internal)\n * - `'naked'` — 完全無 chrome,cell-as-input(host cell 提供 border + focus frame,對齊 Airtable / Notion / Excel cell editing)\n */\n variant?: FieldVariant\n /** Error 狀態(正交於 mode)。border-error + aria-invalid。 */\n error?: boolean\n}\n\n// code-quality-allow: long-function — Textarea forwardRef body 含 mode×size×variant×error 4 軸 prop + autoFocus + aria 完整覆蓋,拆 sub-fn 會把 useFieldContext / fieldWrapperStyles 跨檔 drilling\nconst Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(\n (\n {\n mode: modeProp,\n variant: variantProp,\n error: errorProp = false,\n size: sizeProp,\n className,\n disabled,\n readOnly,\n rows = 3,\n value,\n defaultValue,\n id: idProp,\n 'aria-describedby': ariaDescribedByProp,\n 'aria-errormessage': ariaErrorMessageProp,\n ...props\n },\n ref\n ) => {\n // Field context 整合:disabled / mode / chrome / invalid / size / id 都能從 context 繼承\n const fieldCtx = useFieldContext()\n // chrome 透傳:per-prop override context\n const variant: FieldVariantInternal = useResolvedFieldVariant(variantProp)\n const error = useResolvedFieldInvalid(errorProp)\n const size = useResolvedFieldSize(sizeProp)\n // 2026-06-08 SSOT:mode 經 useResolvedFieldMode 統一解析(prop > 有效 disabled > fieldCtx.mode > readOnly > 'edit')\n const resolvedMode: FieldMode = useResolvedFieldMode({ mode: modeProp, disabled, readOnly })\n const emptyDisplay = useFieldEmptyDisplay()\n const isEditable = resolvedMode === 'edit'\n const isView = resolvedMode === 'view'\n const inputId = idProp ?? fieldCtx?.id\n const ariaDescribedBy = ariaDescribedByProp ?? fieldCtx?.descriptionId\n const ariaErrorMessage = ariaErrorMessageProp ?? (error ? fieldCtx?.errorId : undefined)\n\n // ── view mode(+ readonly 空值)純展示,渲染 <div> 取代 <textarea>(white-space:pre-wrap 保留多行) ──\n // 對齊 Carbon read-only / Cloudscape display-mode\n // 2026-07-08 user 拍板:readonly 空值也走 view-div 顯 '-'(readonly **有值** 仍走下方\n // native <textarea readOnly> 保留選取/複製語意 — field-controls.spec.md「null / undefined 值」)。\n // 2026-07-13 fix:空值判斷需認得 uncontrolled defaultValue — readonly / view 也要顯示內容,\n // 不可誤判為空值渲 '-'(原為靜態 value ?? defaultValue)。\n // 2026-07-14 R2(dual-model consensus,同 Input input.tsx):resolved value 改走 useControllable\n // 內部 SSOT(Radix idiom)。原靜態版有 typing-stale gap:uncontrolled 打字後外部切\n // view / readonly 仍顯示 stale defaultValue。uncontrolled 時 native textarea 由\n // value={resolved} 內部驅動(defaultValue 只作初始值,不落 DOM attribute),同時修\n // remount-stale(切 view 再回 edit 從 defaultValue attribute 重掛)+ form.reset() stale\n // (HTML 標準 reset 不發 input event,下方 reset bridge 修)。controlled(傳 value)時\n // useControllable 純 passthrough,行為與原本完全一致。consumer onChange 不接進 hook\n // (保留 native event signature),於 <textarea> onChange 另行轉發。\n const isControlled = value !== undefined\n const [resolved, setResolved] = useControllable<string | number | readonly string[]>({\n value: value as string | number | readonly string[] | undefined,\n defaultValue: defaultValue ?? '',\n })\n const displayValue = resolved != null && resolved !== '' ? String(resolved) : null\n const showDisplaySpan = isView || (resolvedMode === 'readonly' && displayValue == null)\n\n // form.reset() bridge(uncontrolled only):reset 恢復 defaultValue 但不發 input event →\n // 手動把 resolved 歸位 defaultValue。keyed on showDisplaySpan:view 分支不掛 native\n // textarea,回 edit 重掛時 effect 重跑補掛 listener。\n const innerRef = React.useRef<HTMLTextAreaElement | null>(null)\n React.useEffect(() => {\n if (isControlled || showDisplaySpan) return\n const form = innerRef.current?.form\n if (!form) return\n const handleReset = () => setResolved(defaultValue ?? '')\n form.addEventListener('reset', handleReset)\n return () => form.removeEventListener('reset', handleReset)\n }, [isControlled, showDisplaySpan, defaultValue, setResolved])\n // Merge refs(LinkInput setRef idiom,link-input.tsx:138)\n const setRef = React.useCallback((el: HTMLTextAreaElement | null) => {\n innerRef.current = el\n if (typeof ref === 'function') ref(el)\n else if (ref) (ref as React.MutableRefObject<HTMLTextAreaElement | null>).current = el\n }, [ref])\n\n if (showDisplaySpan) {\n const spanMode = isView ? 'view' : 'readonly'\n return (\n <div\n id={inputId}\n data-field-mode={spanMode}\n aria-describedby={ariaDescribedBy}\n className={cn(\n textareaVariants({ mode: spanMode, variant: variant, size }),\n 'whitespace-pre-wrap break-words',\n displayValue == null && fieldEmptyColorClass(resolvedMode),\n className,\n )}\n >\n {displayValue ?? emptyDisplay}\n </div>\n )\n }\n\n return (\n <textarea\n ref={setRef}\n id={inputId}\n rows={rows}\n value={resolved}\n disabled={resolvedMode === 'disabled'}\n readOnly={resolvedMode === 'readonly'}\n aria-invalid={error || undefined}\n aria-required={fieldCtx?.required || undefined}\n aria-describedby={ariaDescribedBy}\n aria-errormessage={ariaErrorMessage}\n data-field-mode={resolvedMode}\n data-error={isEditable && error ? '' : undefined}\n className={cn(\n textareaVariants({ mode: resolvedMode, variant: variant, size, error }),\n className\n )}\n {...props}\n // 置於 {...props} 後:寫回 resolved SSOT(controlled 時 useControllable 內部為 no-op)再轉發 consumer onChange。\n onChange={(e) => {\n setResolved(e.target.value)\n props.onChange?.(e)\n }}\n />\n )\n }\n)\nTextarea.displayName = 'Textarea'\n\n// Story auto-compile metadata — Phase 1 mechanical migration(2026-04-24)\n// sizes 只反映 Textarea 真實控制的軸:typography(size 只控字體,不綁 field-height、無 icon slot —\n// 高度由 rows + resize-y 決定,per textarea.spec.md L88-92 / L64 / L113)。\nexport const textareaMeta = {\n component: 'Textarea',\n family: 4,\n variants: {\n\n },\n sizes: {\n sm: { typography: 'body' },\n md: { typography: 'body' },\n lg: { typography: 'body-lg' },\n },\n // states 對齊 cva compoundVariants + anatomy ColorMatrix 真實 state 集合;\n // text input 無 'active'(按下)視覺態(Material/Polaris/Carbon TextArea 共識)。\n // 'focus-visible' 泛指 focus 態:default chrome 實為 focus-within(滑鼠+鍵盤皆亮),naked chrome\n // 為 focus-visible(鍵盤限定),readonly 有值態為 focus-visible ring(見上方 compoundVariants)。\n states: ['default', 'hover', 'focus-visible', 'readonly', 'disabled', 'error'],\n tokens: {\n bg: ['bg-disabled', 'bg-readonly', 'bg-surface'], // 2026-07-04 補:readonly×default cva 實際消費(spec「Readonly 特例」主打 token)\n fg: ['text-fg-disabled', 'text-fg-muted', 'text-foreground'],\n ring: [],\n },\n defaultSize: 'md',\n} as const\n\nexport { Textarea, textareaVariants }\n"],"names":[],"mappings":"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"}
@@ -36,7 +36,7 @@ export interface DisabledTimeResult {
36
36
  disabledMinutes?: number[];
37
37
  disabledSeconds?: number[];
38
38
  }
39
- export interface TimePickerProps extends TimeFormatOptions, Omit<React.HTMLAttributes<HTMLDivElement>, 'onChange' | 'placeholder'> {
39
+ export interface TimePickerProps extends TimeFormatOptions, Omit<React.HTMLAttributes<HTMLDivElement>, 'onChange' | 'placeholder' | 'defaultValue'> {
40
40
  mode?: FieldMode;
41
41
  /** Field chrome variant. Default = context.variant ?? 'default'. Per-prop override. */
42
42
  variant?: FieldVariant;
@@ -67,9 +67,9 @@ export interface TimePickerProps extends TimeFormatOptions, Omit<React.HTMLAttri
67
67
  */
68
68
  endIcon?: LucideIcon | null;
69
69
  /**
70
- * Display 是否渲 endIcon + Field naked wrapper(D-path opt-in,2026-05-08)
71
- * — DataTable cell display↔edit 像素級對齊用。預設 false(裸 span,backward compat)。
72
- * 設 true 時 display 也走 fieldWrapperStyles(naked variant)+ ItemSuffix Clock,
70
+ * View 態是否渲 endIcon + Field naked wrapper(D-path opt-in,2026-05-08)
71
+ * — DataTable cell view↔edit 像素級對齊用。預設 false(裸 span,backward compat)。
72
+ * 設 true 時 view 也走 fieldWrapperStyles(naked variant)+ ItemSuffix Clock,
73
73
  * 與 edit 同 DOM 結構,消除 Layer-B padding mismatch。
74
74
  */
75
75
  showDisplayEndIcon?: boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"time-picker.d.ts","sourceRoot":"","sources":["../../../src/components/TimePicker/time-picker.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAE9B,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAE9C,OAAO,KAAK,EAAE,SAAS,EAAE,YAAY,EAAwB,MAAM,8CAA8C,CAAA;AAWjH,OAAO,EAIL,KAAK,SAAS,EACd,KAAK,QAAQ,EAEd,MAAM,oDAAoD,CAAA;AAG3D;;;;;;;;;;;;;;;;;;;;;;GAsBG;AASH,MAAM,WAAW,iBAAiB;IAChC,4FAA4F;IAC5F,aAAa,CAAC,EAAE,IAAI,CAAC,qBAAqB,CAAA;IAC1C,yBAAyB;IACzB,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB;AAqCD,MAAM,WAAW,kBAAkB;IACjC,aAAa,CAAC,EAAE,MAAM,EAAE,CAAA;IACxB,eAAe,CAAC,EAAE,MAAM,EAAE,CAAA;IAC1B,eAAe,CAAC,EAAE,MAAM,EAAE,CAAA;CAC3B;AAID,MAAM,WAAW,eACf,SAAQ,iBAAiB,EACvB,IAAI,CACF,KAAK,CAAC,cAAc,CAAC,cAAc,CAAC,EACpC,UAAU,GAAG,aAAa,CAC3B;IACH,IAAI,CAAC,EAAE,SAAS,CAAA;IAChB,uFAAuF;IACvF,OAAO,CAAC,EAAE,YAAY,CAAA;IACtB,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,IAAI,CAAC,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,CAAA;IACzB,4CAA4C;IAC5C,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACrB,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;IAClC,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,cAAc;IACd,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB;;;OAGG;IACH,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,yBAAyB;IACzB,UAAU,CAAC,EAAE,QAAQ,CAAA;IACrB,qCAAqC;IACrC,UAAU,CAAC,EAAE,QAAQ,CAAA;IACrB,qCAAqC;IACrC,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,SAAS,KAAK,kBAAkB,CAAA;IACvD;;;OAGG;IACH,OAAO,CAAC,EAAE,UAAU,GAAG,IAAI,CAAA;IAC3B;;;;;OAKG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAA;IAC5B,sFAAsF;IACtF,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,mFAAmF;IACnF,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;CACvC;AAGD,QAAA,MAAM,UAAU,wFAwRf,CAAA;AAKD,eAAO,MAAM,cAAc;;;;;;;;;;;CAkBjB,CAAA;AAEV,OAAO,EAAE,UAAU,EAAE,CAAA"}
1
+ {"version":3,"file":"time-picker.d.ts","sourceRoot":"","sources":["../../../src/components/TimePicker/time-picker.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAE9B,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAE9C,OAAO,KAAK,EAAE,SAAS,EAAE,YAAY,EAAwB,MAAM,8CAA8C,CAAA;AAWjH,OAAO,EAIL,KAAK,SAAS,EACd,KAAK,QAAQ,EAEd,MAAM,oDAAoD,CAAA;AAG3D;;;;;;;;;;;;;;;;;;;;;;GAsBG;AASH,MAAM,WAAW,iBAAiB;IAChC,4FAA4F;IAC5F,aAAa,CAAC,EAAE,IAAI,CAAC,qBAAqB,CAAA;IAC1C,yBAAyB;IACzB,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB;AAqCD,MAAM,WAAW,kBAAkB;IACjC,aAAa,CAAC,EAAE,MAAM,EAAE,CAAA;IACxB,eAAe,CAAC,EAAE,MAAM,EAAE,CAAA;IAC1B,eAAe,CAAC,EAAE,MAAM,EAAE,CAAA;CAC3B;AAID,MAAM,WAAW,eACf,SAAQ,iBAAiB,EACvB,IAAI,CACF,KAAK,CAAC,cAAc,CAAC,cAAc,CAAC,EAIpC,UAAU,GAAG,aAAa,GAAG,cAAc,CAC5C;IACH,IAAI,CAAC,EAAE,SAAS,CAAA;IAChB,uFAAuF;IACvF,OAAO,CAAC,EAAE,YAAY,CAAA;IACtB,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,IAAI,CAAC,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,CAAA;IACzB,4CAA4C;IAC5C,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACrB,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;IAClC,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,cAAc;IACd,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB;;;OAGG;IACH,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,yBAAyB;IACzB,UAAU,CAAC,EAAE,QAAQ,CAAA;IACrB,qCAAqC;IACrC,UAAU,CAAC,EAAE,QAAQ,CAAA;IACrB,qCAAqC;IACrC,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,SAAS,KAAK,kBAAkB,CAAA;IACvD;;;OAGG;IACH,OAAO,CAAC,EAAE,UAAU,GAAG,IAAI,CAAA;IAC3B;;;;;OAKG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAA;IAC5B,sFAAsF;IACtF,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,mFAAmF;IACnF,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;CACvC;AAGD,QAAA,MAAM,UAAU,wFA+Rf,CAAA;AAKD,eAAO,MAAM,cAAc;;;;;;;;;;;CAkBjB,CAAA;AAEV,OAAO,EAAE,UAAU,EAAE,CAAA"}