@qijenchen/design-system 0.1.0-beta.145 → 0.1.0-beta.147

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 (576) hide show
  1. package/dist/components/Accordion/accordion.d.ts +1 -1
  2. package/dist/components/Accordion/accordion.d.ts.map +1 -1
  3. package/dist/components/Accordion/accordion.js +8 -3
  4. package/dist/components/Accordion/accordion.js.map +1 -1
  5. package/dist/components/AccountMenu/account-menu.d.ts.map +1 -1
  6. package/dist/components/AccountMenu/account-menu.js.map +1 -1
  7. package/dist/components/AgentPanel/agent-panel-fab.d.ts +7 -1
  8. package/dist/components/AgentPanel/agent-panel-fab.d.ts.map +1 -1
  9. package/dist/components/AgentPanel/agent-panel-fab.js +36 -14
  10. package/dist/components/AgentPanel/agent-panel-fab.js.map +1 -1
  11. package/dist/components/AgentPanel/agent-panel-logo.d.ts +3 -3
  12. package/dist/components/AgentPanel/agent-panel-logo.js.map +1 -1
  13. package/dist/components/AgentPanel/agent-panel.d.ts +1 -1
  14. package/dist/components/AgentPanel/agent-panel.d.ts.map +1 -1
  15. package/dist/components/AgentPanel/agent-panel.js +28 -22
  16. package/dist/components/AgentPanel/agent-panel.js.map +1 -1
  17. package/dist/components/AppShell/app-shell.d.ts +1 -1
  18. package/dist/components/AppShell/app-shell.js +1 -1
  19. package/dist/components/AppShell/app-shell.js.map +1 -1
  20. package/dist/components/Avatar/avatar.d.ts +95 -1
  21. package/dist/components/Avatar/avatar.d.ts.map +1 -1
  22. package/dist/components/Avatar/avatar.js +106 -15
  23. package/dist/components/Avatar/avatar.js.map +1 -1
  24. package/dist/components/Avatar/index.js +14 -2
  25. package/dist/components/Breadcrumb/breadcrumb.d.ts +4 -2
  26. package/dist/components/Breadcrumb/breadcrumb.d.ts.map +1 -1
  27. package/dist/components/Breadcrumb/breadcrumb.js +2 -2
  28. package/dist/components/Breadcrumb/breadcrumb.js.map +1 -1
  29. package/dist/components/BulkActionBar/bulk-action-bar.d.ts.map +1 -1
  30. package/dist/components/BulkActionBar/bulk-action-bar.js +27 -2
  31. package/dist/components/BulkActionBar/bulk-action-bar.js.map +1 -1
  32. package/dist/components/Button/button.d.ts +2 -2
  33. package/dist/components/Button/button.d.ts.map +1 -1
  34. package/dist/components/Button/button.js +118 -22
  35. package/dist/components/Button/button.js.map +1 -1
  36. package/dist/components/Calendar/calendar.d.ts +34 -10
  37. package/dist/components/Calendar/calendar.d.ts.map +1 -1
  38. package/dist/components/Calendar/calendar.js +210 -36
  39. package/dist/components/Calendar/calendar.js.map +1 -1
  40. package/dist/components/Carousel/carousel.d.ts +1 -1
  41. package/dist/components/Carousel/carousel.d.ts.map +1 -1
  42. package/dist/components/Carousel/carousel.js +3 -2
  43. package/dist/components/Carousel/carousel.js.map +1 -1
  44. package/dist/components/Chart/chart.d.ts +1 -0
  45. package/dist/components/Chart/chart.d.ts.map +1 -1
  46. package/dist/components/Chart/chart.js +8 -0
  47. package/dist/components/Chart/chart.js.map +1 -1
  48. package/dist/components/Checkbox/checkbox.d.ts +1 -1
  49. package/dist/components/Checkbox/checkbox.d.ts.map +1 -1
  50. package/dist/components/Checkbox/checkbox.js +10 -2
  51. package/dist/components/Checkbox/checkbox.js.map +1 -1
  52. package/dist/components/Chip/chip.d.ts +1 -1
  53. package/dist/components/Chip/chip.d.ts.map +1 -1
  54. package/dist/components/Chip/chip.js +4 -2
  55. package/dist/components/Chip/chip.js.map +1 -1
  56. package/dist/components/Combobox/combobox.d.ts +1 -1
  57. package/dist/components/Combobox/combobox.d.ts.map +1 -1
  58. package/dist/components/Combobox/combobox.js +37 -11
  59. package/dist/components/Combobox/combobox.js.map +1 -1
  60. package/dist/components/Command/command.d.ts +1 -1
  61. package/dist/components/Command/command.d.ts.map +1 -1
  62. package/dist/components/Command/command.js +106 -8
  63. package/dist/components/Command/command.js.map +1 -1
  64. package/dist/components/DataTable/cell-registry.d.ts.map +1 -1
  65. package/dist/components/DataTable/cell-registry.js +3 -2
  66. package/dist/components/DataTable/cell-registry.js.map +1 -1
  67. package/dist/components/DataTable/data-table-column-visibility-panel.d.ts.map +1 -1
  68. package/dist/components/DataTable/data-table-column-visibility-panel.js +24 -24
  69. package/dist/components/DataTable/data-table-column-visibility-panel.js.map +1 -1
  70. package/dist/components/DataTable/data-table-interaction-layer.d.ts +2 -1
  71. package/dist/components/DataTable/data-table-interaction-layer.d.ts.map +1 -1
  72. package/dist/components/DataTable/data-table-interaction-layer.js +3 -4
  73. package/dist/components/DataTable/data-table-interaction-layer.js.map +1 -1
  74. package/dist/components/DataTable/data-table-sort-manager.d.ts.map +1 -1
  75. package/dist/components/DataTable/data-table-sort-manager.js +20 -20
  76. package/dist/components/DataTable/data-table-sort-manager.js.map +1 -1
  77. package/dist/components/DataTable/data-table.d.ts +8 -5
  78. package/dist/components/DataTable/data-table.d.ts.map +1 -1
  79. package/dist/components/DataTable/data-table.js +197 -71
  80. package/dist/components/DataTable/data-table.js.map +1 -1
  81. package/dist/components/DateGrid/date-grid.d.ts +1 -1
  82. package/dist/components/DateGrid/date-grid.d.ts.map +1 -1
  83. package/dist/components/DateGrid/date-grid.js +358 -225
  84. package/dist/components/DateGrid/date-grid.js.map +1 -1
  85. package/dist/components/DatePicker/date-picker.d.ts.map +1 -1
  86. package/dist/components/DatePicker/date-picker.js +16 -1
  87. package/dist/components/DatePicker/date-picker.js.map +1 -1
  88. package/dist/components/Dialog/dialog.d.ts.map +1 -1
  89. package/dist/components/Dialog/dialog.js +55 -43
  90. package/dist/components/Dialog/dialog.js.map +1 -1
  91. package/dist/components/DropdownMenu/dropdown-menu-keyboard.d.ts +86 -0
  92. package/dist/components/DropdownMenu/dropdown-menu-keyboard.d.ts.map +1 -0
  93. package/dist/components/DropdownMenu/dropdown-menu-keyboard.js +172 -0
  94. package/dist/components/DropdownMenu/dropdown-menu-keyboard.js.map +1 -0
  95. package/dist/components/DropdownMenu/dropdown-menu.d.ts +6 -1
  96. package/dist/components/DropdownMenu/dropdown-menu.d.ts.map +1 -1
  97. package/dist/components/DropdownMenu/dropdown-menu.js +50 -39
  98. package/dist/components/DropdownMenu/dropdown-menu.js.map +1 -1
  99. package/dist/components/Field/field-context.d.ts +4 -0
  100. package/dist/components/Field/field-context.d.ts.map +1 -1
  101. package/dist/components/Field/field-context.js +1 -2
  102. package/dist/components/Field/field-context.js.map +1 -1
  103. package/dist/components/Field/field-edit-keys.d.ts +8 -0
  104. package/dist/components/Field/field-edit-keys.d.ts.map +1 -1
  105. package/dist/components/Field/field-edit-keys.js +3 -0
  106. package/dist/components/Field/field-edit-keys.js.map +1 -1
  107. package/dist/components/Field/field-wrapper.d.ts +18 -1
  108. package/dist/components/Field/field-wrapper.d.ts.map +1 -1
  109. package/dist/components/Field/field-wrapper.js +39 -10
  110. package/dist/components/Field/field-wrapper.js.map +1 -1
  111. package/dist/components/Field/field.d.ts +3 -2
  112. package/dist/components/Field/field.d.ts.map +1 -1
  113. package/dist/components/Field/field.js +2 -1
  114. package/dist/components/Field/field.js.map +1 -1
  115. package/dist/components/FileItem/file-item.d.ts +26 -2
  116. package/dist/components/FileItem/file-item.d.ts.map +1 -1
  117. package/dist/components/FileItem/file-item.js +82 -25
  118. package/dist/components/FileItem/file-item.js.map +1 -1
  119. package/dist/components/FileItem/index.js +4 -2
  120. package/dist/components/FileUpload/file-upload.d.ts +1 -1
  121. package/dist/components/FileUpload/file-upload.d.ts.map +1 -1
  122. package/dist/components/FileUpload/file-upload.js +132 -55
  123. package/dist/components/FileUpload/file-upload.js.map +1 -1
  124. package/dist/components/FileViewer/file-viewer.d.ts +1 -1
  125. package/dist/components/FileViewer/file-viewer.d.ts.map +1 -1
  126. package/dist/components/FileViewer/file-viewer.js +9 -4
  127. package/dist/components/FileViewer/file-viewer.js.map +1 -1
  128. package/dist/components/InlineEdit/inline-edit.d.ts.map +1 -1
  129. package/dist/components/InlineEdit/inline-edit.js +1 -1
  130. package/dist/components/InlineEdit/inline-edit.js.map +1 -1
  131. package/dist/components/Input/input.d.ts.map +1 -1
  132. package/dist/components/Input/input.js +6 -1
  133. package/dist/components/Input/input.js.map +1 -1
  134. package/dist/components/LinkInput/link-input.d.ts.map +1 -1
  135. package/dist/components/LinkInput/link-input.js +20 -4
  136. package/dist/components/LinkInput/link-input.js.map +1 -1
  137. package/dist/components/Notice/notice.js +1 -1
  138. package/dist/components/Notice/notice.js.map +1 -1
  139. package/dist/components/NumberInput/number-input.d.ts.map +1 -1
  140. package/dist/components/NumberInput/number-input.js +6 -1
  141. package/dist/components/NumberInput/number-input.js.map +1 -1
  142. package/dist/components/OverflowIndicator/index.js +2 -1
  143. package/dist/components/OverflowIndicator/overflow-indicator.d.ts +19 -1
  144. package/dist/components/OverflowIndicator/overflow-indicator.d.ts.map +1 -1
  145. package/dist/components/OverflowIndicator/overflow-indicator.js +47 -27
  146. package/dist/components/OverflowIndicator/overflow-indicator.js.map +1 -1
  147. package/dist/components/Pagination/pagination.d.ts +1 -1
  148. package/dist/components/Pagination/pagination.js +1 -1
  149. package/dist/components/Pagination/pagination.js.map +1 -1
  150. package/dist/components/PeoplePicker/avatar-stack-overflow.d.ts +7 -6
  151. package/dist/components/PeoplePicker/avatar-stack-overflow.d.ts.map +1 -1
  152. package/dist/components/PeoplePicker/avatar-stack-overflow.js +6 -7
  153. package/dist/components/PeoplePicker/avatar-stack-overflow.js.map +1 -1
  154. package/dist/components/PeoplePicker/people-picker-helpers.d.ts.map +1 -1
  155. package/dist/components/PeoplePicker/people-picker-helpers.js +1 -1
  156. package/dist/components/PeoplePicker/people-picker-helpers.js.map +1 -1
  157. package/dist/components/PeoplePicker/people-picker.d.ts.map +1 -1
  158. package/dist/components/PeoplePicker/people-picker.js +2 -2
  159. package/dist/components/PeoplePicker/people-picker.js.map +1 -1
  160. package/dist/components/PeoplePicker/person-display.d.ts.map +1 -1
  161. package/dist/components/PeoplePicker/person-display.js +26 -28
  162. package/dist/components/PeoplePicker/person-display.js.map +1 -1
  163. package/dist/components/ProfileCard/profile-card.d.ts.map +1 -1
  164. package/dist/components/ProfileCard/profile-card.js.map +1 -1
  165. package/dist/components/RadioGroup/radio-group.d.ts +1 -1
  166. package/dist/components/RadioGroup/radio-group.d.ts.map +1 -1
  167. package/dist/components/RadioGroup/radio-group.js +10 -4
  168. package/dist/components/RadioGroup/radio-group.js.map +1 -1
  169. package/dist/components/Rating/rating.d.ts +28 -6
  170. package/dist/components/Rating/rating.d.ts.map +1 -1
  171. package/dist/components/Rating/rating.js +97 -80
  172. package/dist/components/Rating/rating.js.map +1 -1
  173. package/dist/components/ScrollArea/scroll-area.d.ts +1 -1
  174. package/dist/components/ScrollArea/scroll-area.js +1 -1
  175. package/dist/components/ScrollArea/scroll-area.js.map +1 -1
  176. package/dist/components/SegmentedControl/segmented-control.d.ts +1 -1
  177. package/dist/components/SegmentedControl/segmented-control.d.ts.map +1 -1
  178. package/dist/components/SegmentedControl/segmented-control.js +3 -2
  179. package/dist/components/SegmentedControl/segmented-control.js.map +1 -1
  180. package/dist/components/Select/select.d.ts.map +1 -1
  181. package/dist/components/Select/select.js +15 -8
  182. package/dist/components/Select/select.js.map +1 -1
  183. package/dist/components/SelectMenu/index.js +2 -1
  184. package/dist/components/SelectMenu/index.js.map +1 -1
  185. package/dist/components/SelectMenu/select-menu-keyboard.d.ts +59 -0
  186. package/dist/components/SelectMenu/select-menu-keyboard.d.ts.map +1 -0
  187. package/dist/components/SelectMenu/select-menu-keyboard.js +107 -0
  188. package/dist/components/SelectMenu/select-menu-keyboard.js.map +1 -0
  189. package/dist/components/SelectMenu/select-menu.d.ts +1 -22
  190. package/dist/components/SelectMenu/select-menu.d.ts.map +1 -1
  191. package/dist/components/SelectMenu/select-menu.js +18 -34
  192. package/dist/components/SelectMenu/select-menu.js.map +1 -1
  193. package/dist/components/SelectionControl/selection-item.d.ts.map +1 -1
  194. package/dist/components/SelectionControl/selection-item.js +1 -0
  195. package/dist/components/SelectionControl/selection-item.js.map +1 -1
  196. package/dist/components/Sidebar/sidebar.d.ts +7 -9
  197. package/dist/components/Sidebar/sidebar.d.ts.map +1 -1
  198. package/dist/components/Sidebar/sidebar.js +171 -50
  199. package/dist/components/Sidebar/sidebar.js.map +1 -1
  200. package/dist/components/Slider/slider.d.ts +1 -1
  201. package/dist/components/Slider/slider.d.ts.map +1 -1
  202. package/dist/components/Slider/slider.js +9 -4
  203. package/dist/components/Slider/slider.js.map +1 -1
  204. package/dist/components/Steps/steps.d.ts.map +1 -1
  205. package/dist/components/Steps/steps.js +54 -26
  206. package/dist/components/Steps/steps.js.map +1 -1
  207. package/dist/components/Switch/switch.d.ts +1 -1
  208. package/dist/components/Switch/switch.d.ts.map +1 -1
  209. package/dist/components/Switch/switch.js +7 -2
  210. package/dist/components/Switch/switch.js.map +1 -1
  211. package/dist/components/Tabs/tabs.d.ts +1 -1
  212. package/dist/components/Tabs/tabs.d.ts.map +1 -1
  213. package/dist/components/Tabs/tabs.js +39 -4
  214. package/dist/components/Tabs/tabs.js.map +1 -1
  215. package/dist/components/Tag/tag.d.ts +1 -1
  216. package/dist/components/Tag/tag.d.ts.map +1 -1
  217. package/dist/components/Tag/tag.js +3 -2
  218. package/dist/components/Tag/tag.js.map +1 -1
  219. package/dist/components/Textarea/textarea.d.ts.map +1 -1
  220. package/dist/components/Textarea/textarea.js +8 -2
  221. package/dist/components/Textarea/textarea.js.map +1 -1
  222. package/dist/components/TimePicker/time-columns.d.ts.map +1 -1
  223. package/dist/components/TimePicker/time-columns.js +8 -4
  224. package/dist/components/TimePicker/time-columns.js.map +1 -1
  225. package/dist/components/TimePicker/time-picker.d.ts +1 -1
  226. package/dist/components/TimePicker/time-picker.js +1 -1
  227. package/dist/components/TimePicker/time-picker.js.map +1 -1
  228. package/dist/components/TreeView/tree-view.d.ts +9 -5
  229. package/dist/components/TreeView/tree-view.d.ts.map +1 -1
  230. package/dist/components/TreeView/tree-view.js +266 -214
  231. package/dist/components/TreeView/tree-view.js.map +1 -1
  232. package/dist/hooks/use-input-modality.d.ts +12 -17
  233. package/dist/hooks/use-input-modality.d.ts.map +1 -1
  234. package/dist/hooks/use-input-modality.js +4 -34
  235. package/dist/hooks/use-input-modality.js.map +1 -1
  236. package/dist/hooks/use-overflow-items.d.ts.map +1 -1
  237. package/dist/hooks/use-overflow-items.js +2 -0
  238. package/dist/hooks/use-overflow-items.js.map +1 -1
  239. package/dist/hooks/use-truncated.d.ts.map +1 -1
  240. package/dist/hooks/use-truncated.js.map +1 -1
  241. package/dist/index.d.ts +3 -0
  242. package/dist/index.d.ts.map +1 -1
  243. package/dist/index.js +23 -3
  244. package/dist/index.js.map +1 -1
  245. package/dist/lib/drag-announcements.d.ts +32 -0
  246. package/dist/lib/drag-announcements.d.ts.map +1 -1
  247. package/dist/lib/drag-announcements.js +39 -1
  248. package/dist/lib/drag-announcements.js.map +1 -1
  249. package/dist/lib/focus-after-trigger.d.ts +45 -0
  250. package/dist/lib/focus-after-trigger.d.ts.map +1 -0
  251. package/dist/lib/focus-after-trigger.js +66 -0
  252. package/dist/lib/focus-after-trigger.js.map +1 -0
  253. package/dist/lib/overlay-coexistence.js +1 -1
  254. package/dist/lib/roving-list-keyboard.d.ts +208 -0
  255. package/dist/lib/roving-list-keyboard.d.ts.map +1 -0
  256. package/dist/lib/roving-list-keyboard.js +174 -0
  257. package/dist/lib/roving-list-keyboard.js.map +1 -0
  258. package/dist/lib/utils.d.ts.map +1 -1
  259. package/dist/lib/utils.js +6 -1
  260. package/dist/lib/utils.js.map +1 -1
  261. package/dist/patterns/element-anatomy/item-anatomy.d.ts +4 -4
  262. package/dist/patterns/element-anatomy/item-anatomy.d.ts.map +1 -1
  263. package/dist/patterns/element-anatomy/item-anatomy.js +14 -4
  264. package/dist/patterns/element-anatomy/item-anatomy.js.map +1 -1
  265. package/dist/patterns/horizontal-overflow/horizontal-overflow.d.ts.map +1 -1
  266. package/dist/patterns/horizontal-overflow/horizontal-overflow.js +25 -0
  267. package/dist/patterns/horizontal-overflow/horizontal-overflow.js.map +1 -1
  268. package/dist/patterns/overlay-surface/overlay-surface.d.ts.map +1 -1
  269. package/dist/patterns/overlay-surface/overlay-surface.js.map +1 -1
  270. package/dist/tokens/categorical-color.d.ts +6 -1
  271. package/dist/tokens/categorical-color.d.ts.map +1 -1
  272. package/dist/tokens/categorical-color.js.map +1 -1
  273. package/dist/tokens/uiSize/icon-size.d.ts +1 -1
  274. package/dist/tokens/uiSize/stack-gap.d.ts +18 -0
  275. package/dist/tokens/uiSize/stack-gap.d.ts.map +1 -0
  276. package/dist/tokens/uiSize/stack-gap.js +5 -0
  277. package/dist/tokens/uiSize/stack-gap.js.map +1 -0
  278. package/ds-canonical/fork/codex/agents/skills/performance-audit/SKILL.md +1 -1
  279. package/ds-canonical/fork/codex/hooks.json +1 -1
  280. package/ds-canonical/fork/consumer/lock.json +17 -14
  281. package/ds-canonical/fork/consumer/managed-files/423729840acdd43c812f17c245b871716c6b60f76e4d3e94864d0e34a815bf8d.blob +9 -0
  282. package/ds-canonical/fork/consumer/managed-files/46f10bc55cdfdf834d0749eebff42ea0bb8576f361b36d2b6ba1851354c50150.blob +47 -9
  283. package/ds-canonical/fork/consumer/managed-files/5a0d134a2645114c8caac20458070b00a7f0fd55b3e8805b7325cb4cf52fa15e.blob +110 -0
  284. package/ds-canonical/fork/consumer/managed-files/5af0a46760af338d51fd0f7950675f8e97f7d2f09386b8818fcd7a58e6654e75.blob +790 -0
  285. package/ds-canonical/fork/consumer/managed-files/68bfcddff9c4a16388c248cd34fb66163e9e64b3d809a48ef3984f6ae4ff6d12.blob +211 -0
  286. package/ds-canonical/fork/consumer/managed-files/7eb6cc60abfdabaf6617c336058fad1454ccde08889295767bfd9b7fd153cc6e.blob +45 -6
  287. package/ds-canonical/fork/consumer/managed-files/92e4828cdcb96ff0d01daa16f6885f24045a8e72820665650f2a1890dc3a446c.blob +42 -2
  288. package/ds-canonical/fork/consumer/managed-files/ab072e7f2eca51f0e18a5e18c4b333260ee5d219a12cc6df5bb2d683f0b2734d.blob +27 -1
  289. package/ds-canonical/fork/consumer/managed-files/e71c65f45c6af7ce12b6b3c0d8028f185da5b719e2050c9b2a6b243d41ae4878.blob +62 -14
  290. package/ds-canonical/fork/governance.lock +41 -17
  291. package/ds-canonical/fork/hooks/check_consumer_app_invariants.sh +16 -4
  292. package/ds-canonical/fork/hooks/check_consumer_code_quality.sh +3 -1
  293. package/ds-canonical/fork/hooks/check_pattern_invariants.sh +13 -9
  294. package/ds-canonical/fork/launchers/settings-hooks.json +1 -1
  295. package/ds-canonical/fork/manifest.json +17 -11
  296. package/ds-canonical/fork/providers/claude/skills/performance-audit/SKILL.md +1 -1
  297. package/ds-canonical/fork/providers/codex/hook-config.json +1 -1
  298. package/ds-canonical/fork/providers/codex/skills/performance-audit/SKILL.md +1 -1
  299. package/ds-canonical/fork/skills/performance-audit/SKILL.md +1 -1
  300. package/ds-canonical/hooks/check_consumer_app_invariants.sh +16 -4
  301. package/ds-canonical/hooks/check_consumer_code_quality.sh +3 -1
  302. package/ds-canonical/hooks/check_datatable_invariants.sh +4 -1
  303. package/ds-canonical/hooks/check_pattern_invariants.sh +13 -9
  304. package/ds-canonical/hooks/check_plugin_fork_health.sh +8 -1
  305. package/ds-canonical/hooks/check_propose_discipline.sh +8 -1
  306. package/ds-canonical/hooks/check_storybook_addon_packaging.sh +8 -1
  307. package/ds-canonical/hooks/check_substantive_edit_approval_preflight.sh +64 -2
  308. package/ds-canonical/hooks/lib/_overlay_handcraft.sh +5 -2
  309. package/ds-canonical/hooks/registrations.json +12 -7
  310. package/ds-canonical/hooks/session_start_governance_check.sh +16 -0
  311. package/ds-canonical/hooks/tests/test_check_consumer_app_invariants.sh +25 -0
  312. package/ds-canonical/hooks/tests/test_check_consumer_code_quality.sh +4 -0
  313. package/ds-canonical/hooks/tests/test_check_consumer_ds_primitive_misuse.sh +14 -0
  314. package/ds-canonical/hooks/tests/test_check_datatable_invariants.sh +24 -0
  315. package/ds-canonical/hooks/tests/test_check_overlay_handcraft.sh +32 -0
  316. package/ds-canonical/hooks/tests/test_check_pattern_invariants.sh +59 -0
  317. package/ds-canonical/hooks/tests/test_check_plugin_fork_health.sh +32 -1
  318. package/ds-canonical/hooks/tests/test_check_propose_discipline.sh +21 -0
  319. package/ds-canonical/hooks/tests/test_check_storybook_addon_packaging.sh +21 -0
  320. package/ds-canonical/hooks/tests/test_check_substantive_edit_approval_preflight.sh +97 -0
  321. package/ds-canonical/hooks/tests/test_session_start_governance_check.sh +49 -0
  322. package/ds-canonical/references/README.md +12 -6
  323. package/ds-canonical/references/drag-canonical.md +3 -1
  324. package/ds-canonical/references/focus-canonical.md +26 -13
  325. package/ds-canonical/references/governance-audit-coverage.md +1 -1
  326. package/ds-canonical/references/hit-area-canonical.md +361 -0
  327. package/ds-canonical/references/item-anatomy-recipe.md +4 -2
  328. package/ds-canonical/references/keyboard-model-canonical.md +559 -0
  329. package/ds-canonical/rules/meta-patterns.md +4 -4
  330. package/ds-canonical/rules/self-verify.md +4 -4
  331. package/ds-canonical/skills/design-system-audit/references/audit-prompts.md +1 -1
  332. package/ds-canonical/skills/design-system-audit/references/historical-bugs.md +96 -0
  333. package/ds-canonical/skills/knowledge-prune/SKILL.md +4 -1
  334. package/ds-canonical/skills/performance-audit/SKILL.md +1 -1
  335. package/ds-story-manifest.json +19 -7
  336. package/llms-full.txt +2 -2
  337. package/llms.txt +2 -2
  338. package/package.json +3 -1
  339. package/src/components/Accordion/accordion.anatomy.stories.tsx +3 -3
  340. package/src/components/Accordion/accordion.spec.md +1 -1
  341. package/src/components/Accordion/accordion.tsx +8 -3
  342. package/src/components/AccountMenu/account-menu.spec.md +2 -3
  343. package/src/components/AccountMenu/account-menu.tsx +0 -1
  344. package/src/components/AgentPanel/agent-decision-card.spec.md +88 -0
  345. package/src/components/AgentPanel/agent-panel-fab.spec.md +151 -0
  346. package/src/components/AgentPanel/agent-panel-fab.tsx +67 -24
  347. package/src/components/AgentPanel/agent-panel-logo.spec.md +87 -0
  348. package/src/components/AgentPanel/agent-panel-logo.tsx +3 -3
  349. package/src/components/AgentPanel/agent-panel.anatomy.stories.tsx +1 -1
  350. package/src/components/AgentPanel/agent-panel.principles.stories.tsx +1 -1
  351. package/src/components/AgentPanel/agent-panel.spec.md +30 -275
  352. package/src/components/AgentPanel/agent-panel.stories.tsx +113 -8
  353. package/src/components/AgentPanel/agent-panel.tsx +59 -22
  354. package/src/components/Alert/alert.anatomy.stories.tsx +1 -1
  355. package/src/components/AppShell/app-shell.tsx +1 -1
  356. package/src/components/AspectRatio/aspect-ratio.anatomy.stories.tsx +10 -15
  357. package/src/components/Avatar/avatar.anatomy.stories.tsx +23 -26
  358. package/src/components/Avatar/avatar.principles.stories.tsx +10 -10
  359. package/src/components/Avatar/avatar.spec.md +45 -6
  360. package/src/components/Avatar/avatar.stories.tsx +8 -8
  361. package/src/components/Avatar/avatar.tsx +227 -21
  362. package/src/components/Badge/badge.anatomy.stories.tsx +16 -21
  363. package/src/components/Breadcrumb/breadcrumb.anatomy.stories.tsx +3 -3
  364. package/src/components/Breadcrumb/breadcrumb.spec.md +3 -3
  365. package/src/components/Breadcrumb/breadcrumb.tsx +5 -3
  366. package/src/components/BulkActionBar/bulk-action-bar.anatomy.stories.tsx +1 -1
  367. package/src/components/BulkActionBar/bulk-action-bar.principles.stories.tsx +1 -1
  368. package/src/components/BulkActionBar/bulk-action-bar.spec.md +1 -0
  369. package/src/components/BulkActionBar/bulk-action-bar.stories.tsx +57 -2
  370. package/src/components/BulkActionBar/bulk-action-bar.tsx +32 -2
  371. package/src/components/Button/button.anatomy.stories.tsx +174 -33
  372. package/src/components/Button/button.spec.md +23 -8
  373. package/src/components/Button/button.tsx +116 -21
  374. package/src/components/Calendar/calendar.anatomy.stories.tsx +34 -11
  375. package/src/components/Calendar/calendar.principles.stories.tsx +44 -1
  376. package/src/components/Calendar/calendar.spec.md +97 -20
  377. package/src/components/Calendar/calendar.stories.tsx +101 -4
  378. package/src/components/Calendar/calendar.tsx +379 -74
  379. package/src/components/Carousel/carousel.anatomy.stories.tsx +16 -17
  380. package/src/components/Carousel/carousel.spec.md +48 -2
  381. package/src/components/Carousel/carousel.tsx +9 -3
  382. package/src/components/Chart/chart.anatomy.stories.tsx +7 -5
  383. package/src/components/Chart/chart.spec.md +32 -7
  384. package/src/components/Chart/chart.tsx +9 -0
  385. package/src/components/Checkbox/checkbox.anatomy.stories.tsx +21 -28
  386. package/src/components/Checkbox/checkbox.spec.md +4 -2
  387. package/src/components/Checkbox/checkbox.tsx +10 -2
  388. package/src/components/Chip/chip.anatomy.stories.tsx +1 -1
  389. package/src/components/Chip/chip.spec.md +2 -1
  390. package/src/components/Chip/chip.tsx +4 -2
  391. package/src/components/Combobox/combobox.anatomy.stories.tsx +30 -37
  392. package/src/components/Combobox/combobox.spec.md +11 -3
  393. package/src/components/Combobox/combobox.tsx +43 -12
  394. package/src/components/Command/command.spec.md +11 -0
  395. package/src/components/Command/command.tsx +143 -10
  396. package/src/components/DataTable/cell-registry.tsx +5 -1
  397. package/src/components/DataTable/data-table-column-visibility-panel.tsx +5 -6
  398. package/src/components/DataTable/data-table-interaction-layer.tsx +27 -14
  399. package/src/components/DataTable/data-table-known-defects.spec.md +72 -0
  400. package/src/components/DataTable/data-table-scroll-performance.spec.md +295 -0
  401. package/src/components/DataTable/data-table-sort-manager.tsx +5 -6
  402. package/src/components/DataTable/data-table.anatomy.stories.tsx +1 -1
  403. package/src/components/DataTable/data-table.css +64 -0
  404. package/src/components/DataTable/data-table.spec.md +70 -346
  405. package/src/components/DataTable/data-table.stories.tsx +56 -9
  406. package/src/components/DataTable/data-table.tsx +392 -103
  407. package/src/components/DateGrid/date-grid.anatomy.stories.tsx +2 -2
  408. package/src/components/DateGrid/date-grid.spec.md +163 -1
  409. package/src/components/DateGrid/date-grid.tsx +270 -7
  410. package/src/components/DatePicker/date-picker.anatomy.stories.tsx +18 -27
  411. package/src/components/DatePicker/date-picker.spec.md +6 -2
  412. package/src/components/DatePicker/date-picker.tsx +20 -1
  413. package/src/components/Dialog/dialog.spec.md +6 -2
  414. package/src/components/Dialog/dialog.stories.tsx +18 -9
  415. package/src/components/Dialog/dialog.tsx +83 -48
  416. package/src/components/DropdownMenu/dropdown-menu-keyboard.ts +255 -0
  417. package/src/components/DropdownMenu/dropdown-menu.anatomy.stories.tsx +32 -33
  418. package/src/components/DropdownMenu/dropdown-menu.spec.md +18 -5
  419. package/src/components/DropdownMenu/dropdown-menu.stories.tsx +58 -1
  420. package/src/components/DropdownMenu/dropdown-menu.tsx +70 -33
  421. package/src/components/Empty/empty.spec.md +1 -2
  422. package/src/components/Field/field-context.ts +9 -4
  423. package/src/components/Field/field-controls.spec.md +5 -2
  424. package/src/components/Field/field-edit-keys.ts +12 -0
  425. package/src/components/Field/field-wrapper.tsx +76 -10
  426. package/src/components/Field/field.anatomy.stories.tsx +1 -1
  427. package/src/components/Field/field.spec.md +11 -6
  428. package/src/components/Field/field.stories.tsx +24 -1
  429. package/src/components/Field/field.tsx +10 -4
  430. package/src/components/FileItem/file-item.anatomy.stories.tsx +20 -10
  431. package/src/components/FileItem/file-item.principles.stories.tsx +14 -7
  432. package/src/components/FileItem/file-item.spec.md +50 -48
  433. package/src/components/FileItem/file-item.stories.tsx +41 -28
  434. package/src/components/FileItem/file-item.tsx +184 -50
  435. package/src/components/FileUpload/file-upload.anatomy.stories.tsx +11 -15
  436. package/src/components/FileUpload/file-upload.spec.md +27 -1
  437. package/src/components/FileUpload/file-upload.stories.tsx +33 -0
  438. package/src/components/FileUpload/file-upload.tsx +173 -43
  439. package/src/components/FileViewer/file-viewer.tsx +9 -4
  440. package/src/components/HoverCard/hover-card.stories.tsx +2 -2
  441. package/src/components/InlineEdit/inline-edit.spec.md +2 -0
  442. package/src/components/InlineEdit/inline-edit.tsx +8 -3
  443. package/src/components/Input/input.anatomy.stories.tsx +23 -31
  444. package/src/components/Input/input.spec.md +11 -0
  445. package/src/components/Input/input.tsx +6 -1
  446. package/src/components/LinkInput/link-input.anatomy.stories.tsx +18 -21
  447. package/src/components/LinkInput/link-input.principles.stories.tsx +4 -4
  448. package/src/components/LinkInput/link-input.spec.md +12 -1
  449. package/src/components/LinkInput/link-input.tsx +37 -4
  450. package/src/components/Menu/menu-item.anatomy.stories.tsx +50 -56
  451. package/src/components/Menu/menu-item.spec.md +1 -1
  452. package/src/components/Menu/menu-item.stories.tsx +48 -42
  453. package/src/components/Notice/notice.anatomy.stories.tsx +8 -16
  454. package/src/components/Notice/notice.spec.md +1 -1
  455. package/src/components/Notice/notice.tsx +1 -1
  456. package/src/components/NumberInput/number-input.anatomy.stories.tsx +21 -28
  457. package/src/components/NumberInput/number-input.spec.md +2 -0
  458. package/src/components/NumberInput/number-input.tsx +8 -1
  459. package/src/components/OverflowIndicator/overflow-indicator.anatomy.stories.tsx +28 -48
  460. package/src/components/OverflowIndicator/overflow-indicator.spec.md +13 -1
  461. package/src/components/OverflowIndicator/overflow-indicator.stories.tsx +12 -9
  462. package/src/components/OverflowIndicator/overflow-indicator.tsx +32 -12
  463. package/src/components/Pagination/pagination.spec.md +1 -0
  464. package/src/components/Pagination/pagination.tsx +1 -1
  465. package/src/components/PeoplePicker/avatar-stack-overflow.ts +14 -13
  466. package/src/components/PeoplePicker/people-picker-helpers.ts +5 -2
  467. package/src/components/PeoplePicker/people-picker.anatomy.stories.tsx +1 -1
  468. package/src/components/PeoplePicker/people-picker.spec.md +4 -2
  469. package/src/components/PeoplePicker/people-picker.tsx +1 -4
  470. package/src/components/PeoplePicker/person-display.tsx +45 -28
  471. package/src/components/Popover/popover.anatomy.stories.tsx +1 -1
  472. package/src/components/Popover/popover.principles.stories.tsx +3 -12
  473. package/src/components/Popover/popover.spec.md +7 -3
  474. package/src/components/ProfileCard/profile-card.anatomy.stories.tsx +1 -1
  475. package/src/components/ProfileCard/profile-card.spec.md +1 -1
  476. package/src/components/ProfileCard/profile-card.tsx +0 -3
  477. package/src/components/ProgressBar/progress-bar.anatomy.stories.tsx +11 -21
  478. package/src/components/ProgressBar/progress-bar.spec.md +1 -1
  479. package/src/components/RadioGroup/radio-group.anatomy.stories.tsx +17 -24
  480. package/src/components/RadioGroup/radio-group.spec.md +1 -1
  481. package/src/components/RadioGroup/radio-group.tsx +22 -4
  482. package/src/components/Rating/rating.anatomy.stories.tsx +131 -124
  483. package/src/components/Rating/rating.principles.stories.tsx +69 -43
  484. package/src/components/Rating/rating.spec.md +142 -63
  485. package/src/components/Rating/rating.stories.tsx +11 -15
  486. package/src/components/Rating/rating.tsx +131 -117
  487. package/src/components/ScrollArea/scroll-area.anatomy.stories.tsx +1 -1
  488. package/src/components/ScrollArea/scroll-area.principles.stories.tsx +6 -3
  489. package/src/components/ScrollArea/scroll-area.spec.md +3 -1
  490. package/src/components/ScrollArea/scroll-area.stories.tsx +5 -2
  491. package/src/components/ScrollArea/scroll-area.tsx +1 -1
  492. package/src/components/SegmentedControl/segmented-control.anatomy.stories.tsx +1 -1
  493. package/src/components/SegmentedControl/segmented-control.spec.md +2 -2
  494. package/src/components/SegmentedControl/segmented-control.tsx +3 -2
  495. package/src/components/Select/select.anatomy.stories.tsx +26 -35
  496. package/src/components/Select/select.spec.md +4 -3
  497. package/src/components/Select/select.tsx +11 -4
  498. package/src/components/SelectMenu/select-menu-keyboard.ts +218 -0
  499. package/src/components/SelectMenu/select-menu.anatomy.stories.tsx +6 -17
  500. package/src/components/SelectMenu/select-menu.spec.md +12 -4
  501. package/src/components/SelectMenu/select-menu.tsx +21 -56
  502. package/src/components/SelectionControl/selection-item.anatomy.stories.tsx +9 -33
  503. package/src/components/SelectionControl/selection-item.spec.md +2 -2
  504. package/src/components/SelectionControl/selection-item.tsx +3 -1
  505. package/src/components/Separator/separator.stories.tsx +4 -4
  506. package/src/components/Sheet/sheet.principles.stories.tsx +31 -13
  507. package/src/components/Sidebar/sidebar.anatomy.stories.tsx +1 -1
  508. package/src/components/Sidebar/sidebar.spec.md +73 -6
  509. package/src/components/Sidebar/sidebar.stories.tsx +82 -4
  510. package/src/components/Sidebar/sidebar.tsx +281 -64
  511. package/src/components/Skeleton/skeleton.spec.md +1 -1
  512. package/src/components/Slider/slider.anatomy.stories.tsx +1 -1
  513. package/src/components/Slider/slider.spec.md +6 -5
  514. package/src/components/Slider/slider.tsx +13 -8
  515. package/src/components/Steps/steps-state-visuals.spec.md +157 -0
  516. package/src/components/Steps/steps.anatomy.stories.tsx +1 -0
  517. package/src/components/Steps/steps.spec.md +59 -142
  518. package/src/components/Steps/steps.stories.tsx +1 -1
  519. package/src/components/Steps/steps.tsx +125 -33
  520. package/src/components/Switch/switch.spec.md +3 -3
  521. package/src/components/Switch/switch.tsx +6 -1
  522. package/src/components/Tabs/tabs.spec.md +5 -1
  523. package/src/components/Tabs/tabs.tsx +42 -3
  524. package/src/components/Tag/tag.anatomy.stories.tsx +24 -28
  525. package/src/components/Tag/tag.spec.md +10 -11
  526. package/src/components/Tag/tag.tsx +3 -3
  527. package/src/components/Textarea/textarea.tsx +7 -1
  528. package/src/components/TimePicker/time-columns.tsx +9 -4
  529. package/src/components/TimePicker/time-picker.spec.md +4 -1
  530. package/src/components/TimePicker/time-picker.tsx +1 -1
  531. package/src/components/Tooltip/tooltip.anatomy.stories.tsx +6 -15
  532. package/src/components/Tooltip/tooltip.principles.stories.tsx +4 -16
  533. package/src/components/Tooltip/tooltip.spec.md +2 -1
  534. package/src/components/TreeView/tree-view.anatomy.stories.tsx +35 -13
  535. package/src/components/TreeView/tree-view.spec.md +67 -38
  536. package/src/components/TreeView/tree-view.stories.tsx +221 -12
  537. package/src/components/TreeView/tree-view.tsx +463 -356
  538. package/src/hooks/use-input-modality.ts +17 -46
  539. package/src/hooks/use-overflow-items.ts +4 -0
  540. package/src/hooks/use-truncated.ts +3 -2
  541. package/src/index.ts +17 -0
  542. package/src/lib/README.md +2 -0
  543. package/src/lib/drag-announcements.ts +78 -0
  544. package/src/lib/focus-after-trigger.ts +102 -0
  545. package/src/lib/roving-list-keyboard.ts +400 -0
  546. package/src/lib/utils.ts +5 -0
  547. package/src/patterns/action-bar/action-bar.spec.md +2 -0
  548. package/src/patterns/element-anatomy/inline-action.spec.md +16 -12
  549. package/src/patterns/element-anatomy/item-anatomy.spec.md +98 -6
  550. package/src/patterns/element-anatomy/item-anatomy.stories.tsx +75 -70
  551. package/src/patterns/element-anatomy/item-anatomy.tsx +17 -6
  552. package/src/patterns/element-anatomy/truncated-text.spec.md +1 -0
  553. package/src/patterns/header-canonical/header-canonical.spec.md +3 -2
  554. package/src/patterns/horizontal-overflow/horizontal-overflow.spec.md +2 -0
  555. package/src/patterns/horizontal-overflow/horizontal-overflow.stories.tsx +36 -0
  556. package/src/patterns/horizontal-overflow/horizontal-overflow.tsx +28 -0
  557. package/src/patterns/overlay-surface/overlay-chrome-sizing.spec.md +180 -0
  558. package/src/patterns/overlay-surface/overlay-surface.spec.md +8 -162
  559. package/src/patterns/overlay-surface/overlay-surface.tsx +5 -3
  560. package/src/patterns/resize-handle/resize-handle.spec.md +3 -3
  561. package/src/styles/base.css +1 -1
  562. package/src/tokens/categorical-color.ts +6 -1
  563. package/src/tokens/color/color.spec.md +91 -24
  564. package/src/tokens/color/color.stories.tsx +50 -2
  565. package/src/tokens/color/primitives.css +41 -38
  566. package/src/tokens/color/semantic.css +105 -36
  567. package/src/tokens/layoutSpace/layoutSpace.spec.md +2 -0
  568. package/src/tokens/motion/motion.spec.md +31 -8
  569. package/src/tokens/orphan-tokens.spec.md +1 -1
  570. package/src/tokens/uiSize/icon-size.ts +1 -1
  571. package/src/tokens/uiSize/stack-gap.ts +17 -0
  572. package/src/tokens/uiSize/uiSize.css +12 -0
  573. package/src/tokens/uiSize/uiSize.spec.md +17 -16
  574. package/src/tokens/uiSize/uiSize.stories.tsx +1 -1
  575. package/dist/node_modules/aria-hidden/dist/es2015/index.js +0 -147
  576. package/dist/node_modules/aria-hidden/dist/es2015/index.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"switch.js","sources":["../../../src/components/Switch/switch.tsx"],"sourcesContent":["// @benchmark-unverified-blanket: file-level retraction per M22 (d) — claims herein not individually URL-cited; treat as unverified visual/usage rumor unless retrofit per-claim. Hook escape preserved.\nimport * as React from 'react'\nimport * as SwitchPrimitives from '@radix-ui/react-switch'\nimport { Check } from 'lucide-react'\nimport { cva, type VariantProps } from 'class-variance-authority'\nimport { cn } from '@/lib/utils'\nimport type { FieldMode, FieldVariant } from '@/design-system/components/Field/field-types'\nimport { useFieldContext, useResolvedFieldDisabled, useResolvedFieldMode, useResolvedFieldSize } from '@/design-system/components/Field/field-context'\nimport { useControllable } from '@/design-system/hooks/use-controllable'\nimport { fieldWrapperStyles } from '@/design-system/components/Field/field-wrapper'\nimport { BooleanValueIcon } from '@/design-system/components/SelectionControl/boolean-value'\n\n/**\n * Switch — 開關控件\n *\n * ── 結構 ──\n * Track(pill 形容器)→ Thumb(白色圓 + 2px border + check icon)\n * Track 寬 = 2 × 高,thumb 直徑 = track 高度\n *\n * ── 尺寸(sm = md)──\n * sm/md: track 20×40, thumb 20, 白色圓 16, check 12(= checkbox sm/md)\n * lg: track 24×48, thumb 24, 白色圓 20, check 16(= checkbox lg)\n *\n * ── 視覺狀態 ──\n * OFF: track border (neutral-5), thumb 白色 + 2px border-border(neutral-5,與 OFF track 同色)無 check\n * ON: track primary, thumb 白色 + 2px primary border + primary check icon\n * disabled: opacity-disabled(整體透明度)\n * readOnly(standalone): 視覺同一般態,但 pointer-events-none + aria-readonly\n * readOnly(Field 內): 渲染 readonly 灰框 + 勾/叉 icon(2026-06-12 拍板,= Input readonly 同視覺語言)\n *\n * ── label / description / readOnly ──\n * Switch 可以透過 `label` 和 `description` props 在元件內直接渲染緊鄰的文字,\n * 樣式全部 codify 在元件內(text-body、foreground/fg-secondary、disabled 色)。\n *\n * 單獨使用時:\n * <Switch label=\"啟用通知\" description=\"收到新訊息時提醒\" />\n *\n * Form 內使用(在 <Field> context 內):\n * <Field>\n * <FieldLabel>啟用通知</FieldLabel>\n * <Switch /> ← label/description prop 會被自動忽略\n * <FieldDescription>收到新訊息時提醒</FieldDescription>\n * </Field>\n *\n * Field context 透過 useFieldContext() 偵測,避免雙層 label。\n *\n * readOnly 模式:\n * <Switch readOnly checked={true} label=\"...\" />\n * standalone:視覺維持 ON/OFF 正確狀態,但無法互動、不在 tab order 內、寫入 aria-readonly。\n * Field 內:渲染 readonly 灰框 + 勾/叉 icon(不渲染 toggle;見 forwardRef 內 readonly 分支)。\n * 與 disabled 的差異:readonly 不降色(可讀),disabled 降色(弱化)。\n */\n\nconst switchVariants = cva(\n [\n 'group peer inline-flex shrink-0 cursor-pointer items-center rounded-full',\n // @hover-transition-allow: 這條過渡的主人是 checked ↔ unchecked 的狀態切換(Ant / Material 的核取框與切換鈕同樣會動),\n // 不是 hover;而且它是控件大小的點目標,不是指標掃過去的列面。hover 一律瞬間的規則見 tokens/motion/motion.spec.md。\n 'transition-colors duration-150',\n 'disabled:cursor-not-allowed disabled:opacity-disabled',\n // readOnly:鎖定互動但視覺正常\n 'data-[readonly=true]:pointer-events-none data-[readonly=true]:cursor-default',\n // OFF → ON 背景色\n 'data-[state=unchecked]:bg-border',\n 'data-[state=checked]:bg-primary',\n // 選中之上的 hover = 同色相升 hover 階(2026-07-06 user 掃出 Switch 是「藍色選中家族」\n // 唯一漏網:Checkbox checked hover 早有同款、Ant Switch checked hover = colorPrimaryHover\n // 源碼實錘;順修 meta states 宣稱 'hover' 但 code 原本零 hover 樣式的 claim-vs-code 落差)。\n // unchecked hover:border(n-5)→ border-hover(n-6)深一階,對齊 Checkbox 未選 hover\n // 加深一階慣例 + Ant unchecked hover(colorTextTertiary 加深)同方向。\n // `disabled:` 前綴把 hover 升階擋掉:Radix 的 disabled Switch 是 `<button disabled>`,\n // 而 `:disabled` 不會關掉 `:hover` —— 沒擋的話停在一個「按不動」的 Switch 上 track 仍會變深,\n // 等於用視覺回饋承諾了一個不存在的互動。Checkbox 早有同款守衛(`checkbox.tsx:33`\n // `disabled:hover:border-transparent`),Ant Switch 也是 `&:hover:not(&-disabled)`。\n 'data-[state=unchecked]:hover:bg-border-hover disabled:data-[state=unchecked]:hover:bg-border',\n 'data-[state=checked]:hover:bg-primary-hover disabled:data-[state=checked]:hover:bg-primary',\n ],\n {\n variants: {\n size: {\n sm: 'h-5 w-10', // 20×40\n md: 'h-5 w-10', // 20×40\n lg: 'h-6 w-12', // 24×48\n },\n },\n defaultVariants: { size: 'md' },\n }\n)\n\nconst SPECS: Record<string, { thumb: number; check: number; checkStroke: number }> = {\n // checkStroke:16px 以下 icon 視覺不夠顯眼 → 加粗 stroke 補償(跟 Checkbox 共用原則,\n // 見 checkbox.tsx 的 checkStrokeWidth 註解)。12px 用 3.5(render ≈ 1.75px 線寬,比 Lucide\n // 預設的 1px render 明顯更粗,視覺跟 16px 預設 stroke 的 1.33px 有足夠區別),16px 用 2.5\n // (render ≈ 1.67px,比預設 1.33px 稍粗讓 toggle check 夠顯眼)。跨 size 配對值由 checkbox.tsx 共用。\n // 2026-05-18 簡化 per user 視覺證 + Checkbox 同步(3.5 → 3 sm/md):effective render 差\n // 0.08px 視覺看不出,保留 compensation 主旨但不過度差異化。\n // 2026-07-04 刪 dead field translate:整檔零消費,實際位移 SSOT = Thumb className\n // translate-x-5(20px)/ translate-x-6(24px)(見下方 Thumb),消除雙住所漂移風險。\n sm: { thumb: 20, check: 12, checkStroke: 3 },\n md: { thumb: 20, check: 12, checkStroke: 3 },\n lg: { thumb: 24, check: 16, checkStroke: 2.5 },\n}\n\nexport interface SwitchProps\n // asChild / children 從公開型別 Omit(2026-07-18 決策2):Switch 固定 anatomy —\n // 恆渲染 Thumb + BooleanValueIcon,非 Slot host;children 被固定 Thumb 靜默覆蓋,\n // asChild(Radix Slot 單子節點)破壞 track/thumb 結構(CLAUDE.md 失敗記憶「asChild ? Slot」)。\n // 對齊 Checkbox 同款收窄(sibling selection control)。\n extends Omit<React.ComponentPropsWithoutRef<typeof SwitchPrimitives.Root>, 'asChild' | 'children'>,\n VariantProps<typeof switchVariants> {\n /**\n * Inline label。提供時 Switch 自動包一個 <label> 並連結 htmlFor,\n * 套用 text-body / text-foreground / disabled 色 的 codified 樣式。\n * 在 <Field> context 內時此 prop 會被忽略(由 FieldLabel 接管)。\n */\n label?: React.ReactNode\n /**\n * Inline description(secondary 文字)。須與 label 搭配使用,\n * 單獨設定 description 無效果。套用 text-body / text-fg-secondary 樣式。\n * 在 <Field> context 內時此 prop 會被忽略(由 FieldDescription 接管)。\n */\n description?: React.ReactNode\n /**\n * readonly 模式:standalone = 鎖定互動但維持 ON/OFF 視覺;Field 內 = 灰框 + 勾/叉 icon。\n * 與 disabled 的差異:readonly 不降色(可讀),disabled 降色(弱化)。\n * DataTable cell 非編輯態用 mode=\"view\"(勾/叉 icon),非 readOnly。\n */\n readOnly?: boolean\n /**\n * Field mode(2026-05-05 Phase B3 align):\n * edit — 一般可互動 Switch(預設)\n * view — **純展示**:渲染 勾/叉 icon(無互動 primitive、無 input chrome);\n * 對齊 Carbon read-only / DataTable boolean cell。\n * `view` 完全無 toggle 形體;`readonly` 視場景(field-types.ts)。\n * readonly — standalone 同 readOnly prop(保留視覺鎖互動);Field 內 = 灰框 + 勾/叉 icon\n * disabled — 同 disabled prop\n */\n mode?: FieldMode\n /**\n * Visual chrome — Switch 本體無 input wrapper variant,本 prop 對 Switch 主體無視覺影響;\n * 為對齊 Field 4-mode + chrome 透傳契約而保留(M19 一致性)。\n */\n variant?: FieldVariant\n}\n\nconst Switch = React.forwardRef<\n React.ElementRef<typeof SwitchPrimitives.Root>,\n SwitchProps\n>(\n (\n {\n className,\n size,\n label,\n description,\n readOnly = false,\n disabled: disabledProp,\n mode,\n // chrome 對 Switch 主體無視覺影響(無 input wrapper)— 接收純為 prop 一致性;destructure 防 leak 到 DOM。\n variant: _chrome,\n checked: checkedProp,\n defaultChecked: defaultCheckedProp,\n onCheckedChange,\n id: idProp,\n ...props\n },\n ref\n ) => {\n const sizeKey = size ?? 'md'\n const spec = SPECS[sizeKey]\n\n // Field context 偵測:在 Field 內時忽略自己的 label/description,避免雙層\n // 2026-05-31 #35:hooks(useFieldContext / useId)必在任何 conditional return 前呼叫(Rules of Hooks)。\n // 原 mode='view' early return 寫在 hooks 之上 → runtime 切 mode 會 hook count 不一致 crash;已下移至 hooks 後。\n const fieldCtx = useFieldContext()\n // 2026-06-08 SSOT:<Field disabled>/<Field mode> cascade(原 disabled/mode 直傳 prop,漏 fieldCtx)\n const disabled = useResolvedFieldDisabled(disabledProp)\n const resolvedMode = useResolvedFieldMode({ mode, disabled, readOnly })\n const effectiveReadOnly = readOnly || resolvedMode === 'readonly'\n // mode='disabled' 直傳(無 Field ctx 的 cell 場景)必須落到真 disabled chrome(同 Checkbox 2026-06-12 修)\n const effectiveDisabled = disabled || resolvedMode === 'disabled'\n // readonly 灰框 size:走 SSOT resolver(prop > ctx > 'md',field-context.ts:150-161)\n const resolvedBoxSize = useResolvedFieldSize(size ?? undefined, 'md') as 'sm' | 'md' | 'lg'\n const insideField = fieldCtx?.hasFieldWrapper === true\n const effectiveLabel = insideField ? undefined : label\n const effectiveDescription = insideField ? undefined : description\n\n // 在 horizontal Field 內自動齊右(iOS / macOS Settings canonical):\n // horizontal Field 的 layout 是 label(固定寬)| control area(fill),Switch 作為\n // trailing control 應靠右(對齊 horizontal DescriptionItem 的「label 左 / value 右」模式)。\n // 世界級對照:iOS Settings / macOS System Settings / GitHub Settings / Figma prefs\n // 一律 switch 齊右 — 視覺掃描快、對齊一致。\n const alignRightInField =\n insideField && fieldCtx?.orientation === 'horizontal' ? 'ml-auto' : ''\n\n // Id 連結:優先使用 prop,再退到 Field context 的 id,最後用 useId 生成\n const generatedId = React.useId()\n const inputId = idProp ?? fieldCtx?.id ?? generatedId\n\n // 2026-07-04 修:view / readonly-in-Field 分支的裸 span/div 原丟棄剩餘 props\n // (id/data-*/aria-*)且不轉發 forwardRef ref → consumer 的 aria-label 等靜默失效。\n // 抽出 Radix 專屬 non-DOM props,其餘 DOM props 於兩分支 spread 轉發(與 Checkbox/\n // RadioGroup 同修);edit 主路徑不受影響(rootEl 仍 spread 完整 props)。\n const {\n required: _required,\n name: _name,\n value: _value,\n asChild: _asChild,\n children: _children,\n ...restDomProps\n // asChild/children 已從 SwitchProps 型別 Omit(見型別註解);此處 cast 保留 runtime strip\n // (防 any-cast consumer 洩漏到 primitive),與 RadioGroup 同款防禦。\n } = props as SwitchProps & { asChild?: boolean; children?: React.ReactNode }\n\n // Wrapper 持有 dual-mode 狀態,讓 edit toggle 後改成 view / Field readonly\n // 仍顯示當前值;否則 Radix 內部 uncontrolled state 無法被非 Root 分支讀取。\n const isControlled = checkedProp !== undefined\n const [resolvedChecked, setResolvedChecked] = useControllable<boolean>({\n value: checkedProp,\n defaultValue: defaultCheckedProp ?? false,\n })\n\n // form.reset() 不發 checked-change event;wrapper 持有 uncontrolled SSOT 後需回復初始值。\n const innerRef = React.useRef<HTMLButtonElement | null>(null)\n React.useEffect(() => {\n if (isControlled) return\n const form = innerRef.current?.form\n if (!form) return\n const handleReset = () => setResolvedChecked(defaultCheckedProp ?? false)\n form.addEventListener('reset', handleReset)\n return () => form.removeEventListener('reset', handleReset)\n }, [defaultCheckedProp, isControlled, resolvedMode, setResolvedChecked])\n const setRef = React.useCallback((element: HTMLButtonElement | null) => {\n innerRef.current = element\n if (typeof ref === 'function') ref(element)\n else if (ref) (ref as React.MutableRefObject<HTMLButtonElement | null>).current = element\n }, [ref])\n\n // ── mode='view'(下移至所有 hooks 之後,per #35 Rules of Hooks)──────────\n // 純展示模式:無互動 toggle、渲染 Check / X icon。與 Checkbox view 對齊(同 boolean primitive,\n // 共用 SelectionControl/boolean-value.tsx SSOT — 勾/叉 icon + 中性 foreground 色)。\n if (resolvedMode === 'view') {\n const isChecked = resolvedChecked\n const viewAriaLabel = (restDomProps as { 'aria-label'?: string })['aria-label'] ?? (isChecked ? '是' : '否')\n return (\n <span\n {...restDomProps}\n ref={ref as React.Ref<HTMLSpanElement>}\n role=\"img\"\n aria-label={viewAriaLabel}\n className={cn('inline-flex', className)}\n >\n <BooleanValueIcon checked={isChecked} size={resolvedBoxSize} />\n </span>\n )\n }\n\n // ── mode='readonly' in Field(2026-06-12 拍板「灰框 + 勾/叉」,與 Checkbox 同款;2026-07-09 glyph → Check/X icon)──\n // Field 內 readonly boolean = fieldWrapperStyles readonly 灰框(= Input readonly 同源)\n // + 勾/叉 icon 值語言;standalone readOnly(settings list)維持原樣鎖互動。詳 checkbox.tsx 同段註解。\n if (effectiveReadOnly && insideField) {\n const isChecked = resolvedChecked\n const boxSize = resolvedBoxSize\n return (\n <div\n {...(restDomProps as React.HTMLAttributes<HTMLDivElement>)}\n ref={ref as React.Ref<HTMLDivElement>}\n role=\"switch\"\n aria-checked={isChecked}\n aria-readonly=\"true\"\n aria-labelledby={fieldCtx?.labelId}\n aria-invalid={fieldCtx?.invalid || undefined}\n data-readonly=\"true\"\n tabIndex={0}\n className={cn(\n fieldWrapperStyles({ size: boxSize, mode: 'readonly', variant: 'default' }),\n className,\n )}\n >\n <BooleanValueIcon checked={isChecked} size={boxSize} />\n </div>\n )\n }\n\n const rootEl = (\n <SwitchPrimitives.Root\n id={inputId}\n className={cn(switchVariants({ size }), alignRightInField, className)}\n ref={setRef}\n disabled={effectiveDisabled}\n checked={resolvedChecked}\n onCheckedChange={(next) => {\n setResolvedChecked(next)\n onCheckedChange?.(next)\n }}\n aria-readonly={effectiveReadOnly || undefined}\n data-readonly={effectiveReadOnly || undefined}\n tabIndex={effectiveReadOnly ? -1 : undefined}\n aria-describedby={fieldCtx?.descriptionId}\n {...props}\n // 2026-07-05 D4 修:readOnly 鎖互動補 label 繞道 — <label htmlFor> 的 synthetic click\n // 不是 pointer event,cva pointer-events-none 攔不到,點 label 文字仍 toggle。\n // preventDefault 讓 Radix composeEventHandlers(checkForDefaultPrevented)跳過內部 toggle;\n // 必列在 {...props} 之後才不被 consumer onClick 覆蓋(與 Checkbox/RadioGroupItem 同修)。\n onClick={(event) => {\n if (effectiveReadOnly) {\n event.preventDefault()\n return\n }\n props.onClick?.(event)\n }}\n >\n <SwitchPrimitives.Thumb\n className={cn(\n // **那圈 2px 邊直接透出 track,不再用「跟 track 同色」去模擬(2026-09-11 root-layer 修法)**。\n // 舊做法:邊框色 = track 色。它在三種情況會破:\n // (1) hover — track 升階、邊框沒跟(user 截圖的那圈灰邊)\n // (2) **dark mode** — 邊框色是白 25%,而 `background-clip` 預設 border-box 會把 thumb 自己的白底\n // 鋪到邊框底下 → 白疊白、邊框消失。像素實測:白圓視覺寬度 light 15.5px vs **dark 19.5px**,\n // 而 spec.md 的尺寸表要求白色圓 16px —— dark mode 一直是壞的,跟 hover 無關。\n // (3) 非白底的容器上,rest 態就會露環(邊框疊在白 thumb 上、track 疊在容器底色上,兩者只有白底時才相等)\n // 新做法:`bg-clip-padding` 讓白底停在 padding box、`border-transparent` 讓那 2px 直接顯示 track 本身。\n // 於是「邊框與 track 同色」不再是需要維護的巧合,而是**幾何上必然成立** —— 任何 theme、任何 hover 階、任何容器底色。\n 'pointer-events-none flex items-center justify-center rounded-full bg-on-emphasis bg-clip-padding border-2 border-transparent',\n 'transition-all duration-150 motion-reduce:duration-0',\n 'data-[state=unchecked]:translate-x-0',\n sizeKey === 'lg' ? 'data-[state=checked]:translate-x-6' : 'data-[state=checked]:translate-x-5',\n )}\n style={{ width: spec.thumb, height: spec.thumb }}\n >\n {/* Check icon — Radix Thumb inherits data-state from Root */}\n <Check\n size={spec.check}\n strokeWidth={spec.checkStroke}\n className=\"text-primary opacity-0 transition-opacity duration-150 motion-reduce:duration-0 group-data-[state=checked]:opacity-100\"\n aria-hidden\n />\n </SwitchPrimitives.Thumb>\n </SwitchPrimitives.Root>\n )\n\n // 無 label → 只渲染 switch 本體\n if (effectiveLabel == null) return rootEl\n\n // 有 label → 包 <label> + codified 樣式\n // Switch 慣例:label 在左、switch 在右(對齊 iOS / Polaris / Material 標準)\n // label 行第一行對齊 switch 中線:容器用 items-start + switch 包 h-[1lh] flex-center\n return (\n <label\n htmlFor={inputId}\n // 2026-07-05 D4 修:readOnly 時在 label 端取消 synthetic activation(第一道防線;\n // Root onClick guard 為第二道 — 兩道對齊 finding suggestion 防 consumer 自寫 label 繞過)。\n onClick={(event) => {\n if (effectiveReadOnly) event.preventDefault()\n }}\n className={cn(\n 'inline-flex items-start gap-3 select-none',\n effectiveDisabled ? 'cursor-not-allowed' : effectiveReadOnly ? 'cursor-default' : 'cursor-pointer'\n )}\n >\n {/* Label↔desc gap typography-mode-aware:\n sm/md = reading(body+body 14/1.5),lg = reading-lg(body-lg+body 14/1.5) */}\n <span\n className={cn(\n 'flex-1 min-w-0 flex flex-col',\n sizeKey === 'lg'\n ? 'gap-[var(--item-gap-label-desc-reading-lg)]'\n : 'gap-[var(--item-gap-label-desc-reading)]',\n )}\n >\n <span\n className={cn(\n // Reading mode 字級:lg → text-body-lg (16px),sm/md → text-body (14px)\n sizeKey === 'lg' ? 'text-body-lg' : 'text-body',\n effectiveDisabled ? 'text-fg-disabled' : 'text-foreground'\n )}\n >\n {effectiveLabel}\n </span>\n {effectiveDescription != null && (\n <span\n className={cn(\n effectiveDisabled ? 'text-fg-disabled' : 'text-fg-secondary'\n )}\n // Reading mode description:**最小 14px**(spec 14→14px, 16→14px),lh 預設 1.5。\n // 用 inline style 直接繞過 tailwind-merge 對 text-body / text-fg-* 的潛在衝突。\n style={{ fontSize: 'var(--font-body-size)' }}\n >\n {effectiveDescription}\n </span>\n )}\n </span>\n {/* @row-slot-handcraft-allow: Field 控件的 1lh 首行對齊 wrapper(selection-control 家族 Checkbox/RadioGroup 同構),非 row item slot,不消費 MenuItem */}\n <span className=\"h-[1lh] flex items-center shrink-0\">\n {rootEl}\n </span>\n </label>\n )\n }\n)\nSwitch.displayName = SwitchPrimitives.Root.displayName\n\n// Story auto-compile metadata — Phase 1 mechanical migration(2026-04-24)\n// Phase 2 fill needed: purpose descriptions + when rationale + world-class refs\nexport const switchMeta = {\n component: 'Switch',\n family: null, // self-contained primitive(對齊 spec frontmatter self-contained + body L31;非 Family 4)\n variants: {\n\n },\n sizes: {\n sm: { fieldHeight: 28, iconSize: 12, typography: 'body' },\n md: { fieldHeight: 32, iconSize: 12, typography: 'body' },\n lg: { fieldHeight: 36, iconSize: 16, typography: 'body-lg' },\n },\n // 2026-07-04 修 stale meta:移除 'active' — switchVariants / Thumb 無 active-state 樣式\n // (cva 只有 unchecked/checked/disabled/readonly/focus-visible;對照 checkboxMeta 正確不含 active)\n // 'checked' = ON 持續態(bg-primary;2026-07-07 meta 詞彙統一補列,toggle 家族用 'checked')\n states: ['default', 'hover', 'checked', 'focus-visible', 'disabled'],\n tokens: {\n // 2026-07-04 補齊漏列:bg-border = OFF track(cva data-[state=unchecked]:bg-border)、\n // bg-on-emphasis = Thumb 白圓(Thumb className)— 兩者 code 實際消費,meta 原漏列\n bg: ['bg-border', 'bg-on-emphasis', 'bg-primary'],\n fg: ['text-fg-disabled', 'text-fg-secondary', 'text-foreground', 'text-primary'],\n ring: ['ring-ring'],\n },\n defaultSize: 'md',\n} as const\n\nexport { Switch, switchVariants }\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"switch.js","sources":["../../../src/components/Switch/switch.tsx"],"sourcesContent":["// @benchmark-unverified-blanket: file-level retraction per M22 (d) — claims herein not individually URL-cited; treat as unverified visual/usage rumor unless retrofit per-claim. Hook escape preserved.\nimport * as React from 'react'\nimport * as SwitchPrimitives from '@radix-ui/react-switch'\nimport { Check } from 'lucide-react'\nimport { cva, type VariantProps } from 'class-variance-authority'\nimport { cn } from '@/lib/utils'\nimport type { FieldMode, FieldVariant } from '@/design-system/components/Field/field-types'\nimport { useFieldContext, useResolvedFieldDisabled, useResolvedFieldMode, useResolvedFieldSize } from '@/design-system/components/Field/field-context'\nimport { useControllable } from '@/design-system/hooks/use-controllable'\nimport { fieldWrapperStyles } from '@/design-system/components/Field/field-wrapper'\nimport { BooleanValueIcon } from '@/design-system/components/SelectionControl/boolean-value'\n\n/**\n * Switch — 開關控件\n *\n * ── 結構 ──\n * Track(pill 形容器)→ Thumb(白色圓 + 2px border + check icon)\n * Track 寬 = 2 × 高,thumb 直徑 = track 高度\n *\n * ── 尺寸(sm = md)──\n * sm/md: track 20×40, thumb 20, 白色圓 16, check 12(= checkbox sm/md)\n * lg: track 24×48, thumb 24, 白色圓 20, check 16(= checkbox lg)\n *\n * ── 視覺狀態 ──\n * OFF: track border (neutral-5), thumb 白色 + 2px border-border(neutral-5,與 OFF track 同色)無 check\n * ON: track primary, thumb 白色 + 2px primary border + primary check icon\n * disabled: opacity-disabled(整體透明度)\n * readOnly(standalone): 視覺同一般態,但 pointer-events-none + aria-readonly\n * readOnly(Field 內): 渲染 readonly 灰框 + 勾/叉 icon(2026-06-12 拍板,= Input readonly 同視覺語言)\n *\n * ── label / description / readOnly ──\n * Switch 可以透過 `label` 和 `description` props 在元件內直接渲染緊鄰的文字,\n * 樣式全部 codify 在元件內(text-body、foreground/fg-secondary、disabled 色)。\n *\n * 單獨使用時:\n * <Switch label=\"啟用通知\" description=\"收到新訊息時提醒\" />\n *\n * Form 內使用(在 <Field> context 內):\n * <Field>\n * <FieldLabel>啟用通知</FieldLabel>\n * <Switch /> ← label/description prop 會被自動忽略\n * <FieldDescription>收到新訊息時提醒</FieldDescription>\n * </Field>\n *\n * Field context 透過 useFieldContext() 偵測,避免雙層 label。\n *\n * readOnly 模式:\n * <Switch readOnly checked={true} label=\"...\" />\n * standalone:視覺維持 ON/OFF 正確狀態,但無法互動、不在 tab order 內、寫入 aria-readonly。\n * Field 內:渲染 readonly 灰框 + 勾/叉 icon(不渲染 toggle;見 forwardRef 內 readonly 分支)。\n * 與 disabled 的差異:readonly 不降色(可讀),disabled 降色(弱化)。\n */\n\nconst switchVariants = cva(\n [\n 'group peer inline-flex shrink-0 cursor-pointer items-center rounded-full',\n // @hover-transition-allow: 這條過渡的主人是 checked ↔ unchecked 的狀態切換(Ant / Material 的核取框與切換鈕同樣會動),\n // 不是 hover;而且它是控件大小的點目標,不是指標掃過去的列面。hover 一律瞬間的規則見 tokens/motion/motion.spec.md。\n 'transition-colors duration-150',\n 'disabled:cursor-not-allowed disabled:opacity-disabled',\n // readOnly:鎖定互動但視覺正常\n 'data-[readonly=true]:pointer-events-none data-[readonly=true]:cursor-default',\n // OFF → ON 背景色\n 'data-[state=unchecked]:bg-border',\n 'data-[state=checked]:bg-primary',\n // 選中之上的 hover = 同色相升 hover 階(2026-07-06 user 掃出 Switch 是「藍色選中家族」\n // 唯一漏網:Checkbox checked hover 早有同款、Ant Switch checked hover = colorPrimaryHover\n // 源碼實錘;順修 meta states 宣稱 'hover' 但 code 原本零 hover 樣式的 claim-vs-code 落差)。\n // unchecked hover:border(n-5)→ border-hover(n-6)深一階,對齊 Checkbox 未選 hover\n // 加深一階慣例 + Ant unchecked hover(colorTextTertiary 加深)同方向。\n // `disabled:` 前綴把 hover 升階擋掉:Radix 的 disabled Switch 是 `<button disabled>`,\n // 而 `:disabled` 不會關掉 `:hover` —— 沒擋的話停在一個「按不動」的 Switch 上 track 仍會變深,\n // 等於用視覺回饋承諾了一個不存在的互動。Checkbox 早有同款守衛(`checkbox.tsx:33`\n // `disabled:hover:border-transparent`),Ant Switch 也是 `&:hover:not(&-disabled)`。\n 'data-[state=unchecked]:hover:bg-border-hover disabled:data-[state=unchecked]:hover:bg-border',\n 'data-[state=checked]:hover:bg-primary-hover disabled:data-[state=checked]:hover:bg-primary',\n // readOnly 同一道守衛(switch.spec.md 狀態表 Readonly 列「hover 不升階」):上面 `data-[readonly=true]:pointer-events-none`\n // 只擋直接指到 track;指標停在外層 <label htmlFor> 上時,HTML 讓被標記的控件一起進入 :hover,track 照樣升階。\n // 外圈白邊是透出的 track(見 Thumb 註解),釘住 track 就一起釘住。寫法與 checkbox.tsx / radio-group.tsx 同一組。\n 'data-[readonly=true]:enabled:data-[state=unchecked]:hover:bg-border',\n 'data-[readonly=true]:enabled:data-[state=checked]:hover:bg-primary',\n ],\n {\n variants: {\n size: {\n sm: 'h-5 w-10', // 20×40\n md: 'h-5 w-10', // 20×40\n lg: 'h-6 w-12', // 24×48\n },\n },\n defaultVariants: { size: 'md' },\n }\n)\n\nconst SPECS: Record<string, { thumb: number; check: number; checkStroke: number }> = {\n // checkStroke:16px 以下 icon 視覺不夠顯眼 → 加粗 stroke 補償(跟 Checkbox 共用原則,\n // 見 checkbox.tsx 的 checkStrokeWidth 註解)。12px 用 3.5(render ≈ 1.75px 線寬,比 Lucide\n // 預設的 1px render 明顯更粗,視覺跟 16px 預設 stroke 的 1.33px 有足夠區別),16px 用 2.5\n // (render ≈ 1.67px,比預設 1.33px 稍粗讓 toggle check 夠顯眼)。跨 size 配對值由 checkbox.tsx 共用。\n // 2026-05-18 簡化 per user 視覺證 + Checkbox 同步(3.5 → 3 sm/md):effective render 差\n // 0.08px 視覺看不出,保留 compensation 主旨但不過度差異化。\n // 2026-07-04 刪 dead field translate:整檔零消費,實際位移 SSOT = Thumb className\n // translate-x-5(20px)/ translate-x-6(24px)(見下方 Thumb),消除雙住所漂移風險。\n sm: { thumb: 20, check: 12, checkStroke: 3 },\n md: { thumb: 20, check: 12, checkStroke: 3 },\n lg: { thumb: 24, check: 16, checkStroke: 2.5 },\n}\n\nexport interface SwitchProps\n // asChild / children 從公開型別 Omit(2026-07-18 決策2):Switch 固定 anatomy —\n // 恆渲染 Thumb + BooleanValueIcon,非 Slot host;children 被固定 Thumb 靜默覆蓋,\n // asChild(Radix Slot 單子節點)破壞 track/thumb 結構(CLAUDE.md 失敗記憶「asChild ? Slot」)。\n // 對齊 Checkbox 同款收窄(sibling selection control)。\n extends Omit<React.ComponentPropsWithoutRef<typeof SwitchPrimitives.Root>, 'asChild' | 'children'>,\n VariantProps<typeof switchVariants> {\n /**\n * Inline label。提供時 Switch 自動包一個 <label> 並連結 htmlFor,\n * 套用 text-body / text-foreground / disabled 色 的 codified 樣式。\n * 在 <Field> context 內時此 prop 會被忽略(由 FieldLabel 接管)。\n */\n label?: React.ReactNode\n /**\n * Inline description(secondary 文字)。須與 label 搭配使用,\n * 單獨設定 description 無效果。套用 text-body / text-fg-secondary 樣式。\n * 在 <Field> context 內時此 prop 會被忽略(由 FieldDescription 接管)。\n */\n description?: React.ReactNode\n /**\n * readonly 模式:standalone = 鎖定互動但維持 ON/OFF 視覺;Field 內 = 灰框 + 勾/叉 icon。\n * 與 disabled 的差異:readonly 不降色(可讀),disabled 降色(弱化)。\n * DataTable cell 非編輯態用 mode=\"view\"(勾/叉 icon),非 readOnly。\n */\n readOnly?: boolean\n /**\n * Field mode(2026-05-05 Phase B3 align):\n * edit — 一般可互動 Switch(預設)\n * view — **純展示**:渲染 勾/叉 icon(無互動 primitive、無 input chrome);\n * 對齊 Carbon read-only / DataTable boolean cell。\n * `view` 完全無 toggle 形體;`readonly` 視場景(field-types.ts)。\n * readonly — standalone 同 readOnly prop(保留視覺鎖互動);Field 內 = 灰框 + 勾/叉 icon\n * disabled — 同 disabled prop\n */\n mode?: FieldMode\n /**\n * Visual chrome — Switch 本體無 input wrapper variant,本 prop 對 Switch 主體無視覺影響;\n * 為對齊 Field 4-mode + chrome 透傳契約而保留(M19 一致性)。\n */\n variant?: FieldVariant\n}\n\nconst Switch = React.forwardRef<\n React.ElementRef<typeof SwitchPrimitives.Root>,\n SwitchProps\n>(\n (\n {\n className,\n size,\n label,\n description,\n readOnly = false,\n disabled: disabledProp,\n mode,\n // chrome 對 Switch 主體無視覺影響(無 input wrapper)— 接收純為 prop 一致性;destructure 防 leak 到 DOM。\n variant: _chrome,\n checked: checkedProp,\n defaultChecked: defaultCheckedProp,\n onCheckedChange,\n id: idProp,\n ...props\n },\n ref\n ) => {\n const sizeKey = size ?? 'md'\n const spec = SPECS[sizeKey]\n\n // Field context 偵測:在 Field 內時忽略自己的 label/description,避免雙層\n // 2026-05-31 #35:hooks(useFieldContext / useId)必在任何 conditional return 前呼叫(Rules of Hooks)。\n // 原 mode='view' early return 寫在 hooks 之上 → runtime 切 mode 會 hook count 不一致 crash;已下移至 hooks 後。\n const fieldCtx = useFieldContext()\n // 2026-06-08 SSOT:<Field disabled>/<Field mode> cascade(原 disabled/mode 直傳 prop,漏 fieldCtx)\n const disabled = useResolvedFieldDisabled(disabledProp)\n const resolvedMode = useResolvedFieldMode({ mode, disabled, readOnly })\n const effectiveReadOnly = readOnly || resolvedMode === 'readonly'\n // mode='disabled' 直傳(無 Field ctx 的 cell 場景)必須落到真 disabled chrome(同 Checkbox 2026-06-12 修)\n const effectiveDisabled = disabled || resolvedMode === 'disabled'\n // readonly 灰框 size:走 SSOT resolver(prop > ctx > 'md',field-context.ts:150-161)\n const resolvedBoxSize = useResolvedFieldSize(size ?? undefined, 'md') as 'sm' | 'md' | 'lg'\n const insideField = fieldCtx?.hasFieldWrapper === true\n const effectiveLabel = insideField ? undefined : label\n const effectiveDescription = insideField ? undefined : description\n\n // 在 horizontal Field 內自動齊右(iOS / macOS Settings canonical):\n // horizontal Field 的 layout 是 label(固定寬)| control area(fill),Switch 作為\n // trailing control 應靠右(對齊 horizontal DescriptionItem 的「label 左 / value 右」模式)。\n // 世界級對照:iOS Settings / macOS System Settings / GitHub Settings / Figma prefs\n // 一律 switch 齊右 — 視覺掃描快、對齊一致。\n const alignRightInField =\n insideField && fieldCtx?.orientation === 'horizontal' ? 'ml-auto' : ''\n\n // Id 連結:優先使用 prop,再退到 Field context 的 id,最後用 useId 生成\n const generatedId = React.useId()\n const inputId = idProp ?? fieldCtx?.id ?? generatedId\n\n // 2026-07-04 修:view / readonly-in-Field 分支的裸 span/div 原丟棄剩餘 props\n // (id/data-*/aria-*)且不轉發 forwardRef ref → consumer 的 aria-label 等靜默失效。\n // 抽出 Radix 專屬 non-DOM props,其餘 DOM props 於兩分支 spread 轉發(與 Checkbox/\n // RadioGroup 同修);edit 主路徑不受影響(rootEl 仍 spread 完整 props)。\n const {\n required: _required,\n name: _name,\n value: _value,\n asChild: _asChild,\n children: _children,\n ...restDomProps\n // asChild/children 已從 SwitchProps 型別 Omit(見型別註解);此處 cast 保留 runtime strip\n // (防 any-cast consumer 洩漏到 primitive),與 RadioGroup 同款防禦。\n } = props as SwitchProps & { asChild?: boolean; children?: React.ReactNode }\n\n // Wrapper 持有 dual-mode 狀態,讓 edit toggle 後改成 view / Field readonly\n // 仍顯示當前值;否則 Radix 內部 uncontrolled state 無法被非 Root 分支讀取。\n const isControlled = checkedProp !== undefined\n const [resolvedChecked, setResolvedChecked] = useControllable<boolean>({\n value: checkedProp,\n defaultValue: defaultCheckedProp ?? false,\n })\n\n // form.reset() 不發 checked-change event;wrapper 持有 uncontrolled SSOT 後需回復初始值。\n const innerRef = React.useRef<HTMLButtonElement | null>(null)\n React.useEffect(() => {\n if (isControlled) return\n const form = innerRef.current?.form\n if (!form) return\n const handleReset = () => setResolvedChecked(defaultCheckedProp ?? false)\n form.addEventListener('reset', handleReset)\n return () => form.removeEventListener('reset', handleReset)\n }, [defaultCheckedProp, isControlled, resolvedMode, setResolvedChecked])\n const setRef = React.useCallback((element: HTMLButtonElement | null) => {\n innerRef.current = element\n if (typeof ref === 'function') ref(element)\n else if (ref) (ref as React.MutableRefObject<HTMLButtonElement | null>).current = element\n }, [ref])\n\n // ── mode='view'(下移至所有 hooks 之後,per #35 Rules of Hooks)──────────\n // 純展示模式:無互動 toggle、渲染 Check / X icon。與 Checkbox view 對齊(同 boolean primitive,\n // 共用 SelectionControl/boolean-value.tsx SSOT — 勾/叉 icon + 中性 foreground 色)。\n if (resolvedMode === 'view') {\n const isChecked = resolvedChecked\n const viewAriaLabel = (restDomProps as { 'aria-label'?: string })['aria-label'] ?? (isChecked ? '是' : '否')\n return (\n <span\n {...restDomProps}\n ref={ref as React.Ref<HTMLSpanElement>}\n role=\"img\"\n aria-label={viewAriaLabel}\n className={cn('inline-flex', className)}\n >\n <BooleanValueIcon checked={isChecked} size={resolvedBoxSize} />\n </span>\n )\n }\n\n // ── mode='readonly' in Field(2026-06-12 拍板「灰框 + 勾/叉」,與 Checkbox 同款;2026-07-09 glyph → Check/X icon)──\n // Field 內 readonly boolean = fieldWrapperStyles readonly 灰框(= Input readonly 同源)\n // + 勾/叉 icon 值語言;standalone readOnly(settings list)維持原樣鎖互動。詳 checkbox.tsx 同段註解。\n if (effectiveReadOnly && insideField) {\n const isChecked = resolvedChecked\n const boxSize = resolvedBoxSize\n return (\n <div\n {...(restDomProps as React.HTMLAttributes<HTMLDivElement>)}\n ref={ref as React.Ref<HTMLDivElement>}\n role=\"switch\"\n aria-checked={isChecked}\n aria-readonly=\"true\"\n aria-labelledby={fieldCtx?.labelId}\n aria-invalid={fieldCtx?.invalid || undefined}\n data-readonly=\"true\"\n tabIndex={0}\n className={cn(\n fieldWrapperStyles({ size: boxSize, mode: 'readonly', variant: 'default' }),\n className,\n )}\n >\n <BooleanValueIcon checked={isChecked} size={boxSize} />\n </div>\n )\n }\n\n const rootEl = (\n <SwitchPrimitives.Root\n id={inputId}\n className={cn(switchVariants({ size }), alignRightInField, className)}\n ref={setRef}\n disabled={effectiveDisabled}\n checked={resolvedChecked}\n onCheckedChange={(next) => {\n setResolvedChecked(next)\n onCheckedChange?.(next)\n }}\n aria-readonly={effectiveReadOnly || undefined}\n data-readonly={effectiveReadOnly || undefined}\n tabIndex={effectiveReadOnly ? -1 : undefined}\n aria-describedby={fieldCtx?.descriptionId}\n {...props}\n // 2026-07-05 D4 修:readOnly 鎖互動補 label 繞道 — <label htmlFor> 的 synthetic click\n // 不是 pointer event,cva pointer-events-none 攔不到,點 label 文字仍 toggle。\n // preventDefault 讓 Radix composeEventHandlers(checkForDefaultPrevented)跳過內部 toggle;\n // 必列在 {...props} 之後才不被 consumer onClick 覆蓋(與 Checkbox/RadioGroupItem 同修)。\n onClick={(event) => {\n if (effectiveReadOnly) {\n event.preventDefault()\n return\n }\n props.onClick?.(event)\n }}\n >\n <SwitchPrimitives.Thumb\n className={cn(\n // **那圈 2px 邊直接透出 track,不再用「跟 track 同色」去模擬(2026-09-11 root-layer 修法)**。\n // 舊做法:邊框色 = track 色。它在三種情況會破:\n // (1) hover — track 升階、邊框沒跟(user 截圖的那圈灰邊)\n // (2) **dark mode** — 邊框色是白 25%,而 `background-clip` 預設 border-box 會把 thumb 自己的白底\n // 鋪到邊框底下 → 白疊白、邊框消失。像素實測:白圓視覺寬度 light 15.5px vs **dark 19.5px**,\n // 而 spec.md 的尺寸表要求白色圓 16px —— dark mode 一直是壞的,跟 hover 無關。\n // (3) 非白底的容器上,rest 態就會露環(邊框疊在白 thumb 上、track 疊在容器底色上,兩者只有白底時才相等)\n // 新做法:`bg-clip-padding` 讓白底停在 padding box、`border-transparent` 讓那 2px 直接顯示 track 本身。\n // 於是「邊框與 track 同色」不再是需要維護的巧合,而是**幾何上必然成立** —— 任何 theme、任何 hover 階、任何容器底色。\n 'pointer-events-none flex items-center justify-center rounded-full bg-on-emphasis bg-clip-padding border-2 border-transparent',\n 'transition-all duration-150 motion-reduce:duration-0',\n 'data-[state=unchecked]:translate-x-0',\n sizeKey === 'lg' ? 'data-[state=checked]:translate-x-6' : 'data-[state=checked]:translate-x-5',\n )}\n style={{ width: spec.thumb, height: spec.thumb }}\n >\n {/* Check icon — Radix Thumb inherits data-state from Root */}\n <Check\n size={spec.check}\n strokeWidth={spec.checkStroke}\n className=\"text-primary opacity-0 transition-opacity duration-150 motion-reduce:duration-0 group-data-[state=checked]:opacity-100\"\n aria-hidden\n />\n </SwitchPrimitives.Thumb>\n </SwitchPrimitives.Root>\n )\n\n // 無 label → 只渲染 switch 本體\n if (effectiveLabel == null) return rootEl\n\n // 有 label → 包 <label> + codified 樣式\n // Switch 慣例:label 在左、switch 在右(對齊 iOS / Polaris / Material 標準)\n // label 行第一行對齊 switch 中線:容器用 items-start + switch 包 h-[1lh] flex-center\n return (\n <label\n htmlFor={inputId}\n // 2026-07-05 D4 修:readOnly 時在 label 端取消 synthetic activation(第一道防線;\n // Root onClick guard 為第二道 — 兩道對齊 finding suggestion 防 consumer 自寫 label 繞過)。\n onClick={(event) => {\n if (effectiveReadOnly) event.preventDefault()\n }}\n className={cn(\n 'inline-flex items-start gap-3 select-none',\n effectiveDisabled ? 'cursor-not-allowed' : effectiveReadOnly ? 'cursor-default' : 'cursor-pointer'\n )}\n >\n {/* Label↔desc gap typography-mode-aware:\n sm/md = reading(body+body 14/1.5),lg = reading-lg(body-lg+body 14/1.5) */}\n <span\n className={cn(\n 'flex-1 min-w-0 flex flex-col',\n sizeKey === 'lg'\n ? 'gap-[var(--item-gap-label-desc-reading-lg)]'\n : 'gap-[var(--item-gap-label-desc-reading)]',\n )}\n >\n <span\n className={cn(\n // Reading mode 字級:lg → text-body-lg (16px),sm/md → text-body (14px)\n sizeKey === 'lg' ? 'text-body-lg' : 'text-body',\n effectiveDisabled ? 'text-fg-disabled' : 'text-foreground'\n )}\n >\n {effectiveLabel}\n </span>\n {effectiveDescription != null && (\n <span\n className={cn(\n effectiveDisabled ? 'text-fg-disabled' : 'text-fg-secondary'\n )}\n // Reading mode description:**最小 14px**(spec 14→14px, 16→14px),lh 預設 1.5。\n // 用 inline style 直接繞過 tailwind-merge 對 text-body / text-fg-* 的潛在衝突。\n style={{ fontSize: 'var(--font-body-size)' }}\n >\n {effectiveDescription}\n </span>\n )}\n </span>\n {/* @row-slot-handcraft-allow: Field 控件的 1lh 首行對齊 wrapper(selection-control 家族 Checkbox/RadioGroup 同構),非 row item slot,不消費 MenuItem */}\n <span className=\"h-[1lh] flex items-center shrink-0\">\n {rootEl}\n </span>\n </label>\n )\n }\n)\nSwitch.displayName = SwitchPrimitives.Root.displayName\n\n// Story auto-compile metadata — Phase 1 mechanical migration(2026-04-24)\n// Phase 2 fill needed: purpose descriptions + when rationale + world-class refs\nexport const switchMeta = {\n component: 'Switch',\n family: null, // self-contained primitive(對齊 spec frontmatter self-contained + body L31;非 Family 4)\n variants: {\n\n },\n sizes: {\n sm: { fieldHeight: 28, iconSize: 12, typography: 'body' },\n md: { fieldHeight: 32, iconSize: 12, typography: 'body' },\n lg: { fieldHeight: 36, iconSize: 16, typography: 'body-lg' },\n },\n // 2026-07-04 修 stale meta:移除 'active' — switchVariants / Thumb 無 active-state 樣式\n // (cva 只有 unchecked/checked/disabled/readonly/focus-visible;對照 checkboxMeta 正確不含 active)\n // 'checked' = ON 持續態(bg-primary;2026-07-07 meta 詞彙統一補列,toggle 家族用 'checked')\n states: ['default', 'hover', 'checked', 'focus-visible', 'disabled'],\n tokens: {\n // 2026-07-04 補齊漏列:bg-border = OFF track(cva data-[state=unchecked]:bg-border)、\n // bg-on-emphasis = Thumb 白圓(Thumb className)— 兩者 code 實際消費,meta 原漏列\n bg: ['bg-border', 'bg-on-emphasis', 'bg-primary'],\n fg: ['text-fg-disabled', 'text-fg-secondary', 'text-foreground', 'text-primary'],\n ring: ['--ring'],\n },\n defaultSize: 'md',\n} as const\n\nexport { Switch, switchVariants }\n"],"names":[],"mappings":"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"}
@@ -98,7 +98,7 @@ export declare const tabsMeta: {
98
98
  readonly tokens: {
99
99
  readonly bg: readonly ["bg-primary", "bg-transparent"];
100
100
  readonly fg: readonly ["text-fg-disabled", "text-fg-secondary", "text-foreground"];
101
- readonly ring: readonly ["ring-ring"];
101
+ readonly ring: readonly ["focus-ring-inset", "--ring"];
102
102
  };
103
103
  readonly defaultSize: "sm";
104
104
  };
@@ -1 +1 @@
1
- {"version":3,"file":"tabs.d.ts","sourceRoot":"","sources":["../../../src/components/Tabs/tabs.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAE9B,OAAO,KAAK,aAAa,MAAM,sBAAsB,CAAA;AAErD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAyB9C;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAGH,KAAK,QAAQ,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAA;AAClC,KAAK,YAAY,GAAG,MAAM,GAAG,QAAQ,GAAG,MAAM,CAAA;AAkD9C,UAAU,SAAU,SAAQ,KAAK,CAAC,wBAAwB,CAAC,OAAO,aAAa,CAAC,IAAI,CAAC;CAAG;AA2CxF,QAAA,MAAM,IAAI,kFAyCR,CAAA;AAyBF,UAAU,aACR,SAAQ,KAAK,CAAC,wBAAwB,CAAC,OAAO,aAAa,CAAC,IAAI,CAAC;IACjE,IAAI,CAAC,EAAE,QAAQ,CAAA;IACf;;;;;;OAMG;IACH,QAAQ,CAAC,EAAE,YAAY,CAAA;CACxB;AAED,QAAA,MAAM,QAAQ,sFA+CZ,CAAA;AA4MF,QAAA,MAAM,mBAAmB;;8EA8CxB,CAAA;AAED,UAAU,gBAIR,SAAQ,IAAI,CAAC,KAAK,CAAC,wBAAwB,CAAC,OAAO,aAAa,CAAC,OAAO,CAAC,EAAE,SAAS,CAAC;IACrF,0BAA0B;IAC1B,SAAS,CAAC,EAAE,UAAU,CAAA;IACtB,yBAAyB;IACzB,KAAK,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IACvB;;;;OAIG;IACH,OAAO,CAAC,EAAE,UAAU,CAAA;IACpB;;;;;;;;;;;;OAYG;IACH,YAAY,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;CAC/B;AAED,QAAA,MAAM,WAAW,4FAiGf,CAAA;AAIF,QAAA,MAAM,WAAW,0JAiBf,CAAA;AAKF,eAAO,MAAM,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAoBX,CAAA;AAEV,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,WAAW,EAAE,mBAAmB,EAAE,CAAA;AACxE,YAAY,EAAE,QAAQ,EAAE,aAAa,EAAE,gBAAgB,EAAE,CAAA"}
1
+ {"version":3,"file":"tabs.d.ts","sourceRoot":"","sources":["../../../src/components/Tabs/tabs.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAE9B,OAAO,KAAK,aAAa,MAAM,sBAAsB,CAAA;AAErD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAyB9C;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAGH,KAAK,QAAQ,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAA;AAClC,KAAK,YAAY,GAAG,MAAM,GAAG,QAAQ,GAAG,MAAM,CAAA;AAkD9C,UAAU,SAAU,SAAQ,KAAK,CAAC,wBAAwB,CAAC,OAAO,aAAa,CAAC,IAAI,CAAC;CAAG;AA2CxF,QAAA,MAAM,IAAI,kFAyCR,CAAA;AAyBF,UAAU,aACR,SAAQ,KAAK,CAAC,wBAAwB,CAAC,OAAO,aAAa,CAAC,IAAI,CAAC;IACjE,IAAI,CAAC,EAAE,QAAQ,CAAA;IACf;;;;;;OAMG;IACH,QAAQ,CAAC,EAAE,YAAY,CAAA;CACxB;AAED,QAAA,MAAM,QAAQ,sFA+CZ,CAAA;AA4MF,QAAA,MAAM,mBAAmB;;8EAuDxB,CAAA;AAED,UAAU,gBAIR,SAAQ,IAAI,CAAC,KAAK,CAAC,wBAAwB,CAAC,OAAO,aAAa,CAAC,OAAO,CAAC,EAAE,SAAS,CAAC;IACrF,0BAA0B;IAC1B,SAAS,CAAC,EAAE,UAAU,CAAA;IACtB,yBAAyB;IACzB,KAAK,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IACvB;;;;OAIG;IACH,OAAO,CAAC,EAAE,UAAU,CAAA;IACpB;;;;;;;;;;;;OAYG;IACH,YAAY,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;CAC/B;AAED,QAAA,MAAM,WAAW,4FA+Hf,CAAA;AAIF,QAAA,MAAM,WAAW,0JAiBf,CAAA;AAKF,eAAO,MAAM,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAoBX,CAAA;AAEV,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,WAAW,EAAE,mBAAmB,EAAE,CAAA;AACxE,YAAY,EAAE,QAAQ,EAAE,aAAa,EAAE,gBAAgB,EAAE,CAAA"}
@@ -264,7 +264,8 @@ const tabsTriggerVariants = cva(
264
264
  "gap-2",
265
265
  "whitespace-nowrap",
266
266
  "font-medium text-fg-secondary",
267
- "transition-colors duration-150",
267
+ // 不寫 transition-colors:滑過的字色一律瞬間(tokens/motion/motion.spec.md「hover 回饋不做過渡」;2026-09-26 由底色延伸到字色,
268
+ // 待辦總帳 L9 / N4(3))。選中切換的**底線**過渡在下方 `after:transition-colors`,那是選中切換(每個分頁自己的底線淡出 / 淡入,不會滑過去)不是滑過,保留(L9 範圍明文排除)。
268
269
  "cursor-pointer select-none",
269
270
  // 焦點框往內:tab 高 = 分頁列高,上下淨空 0(focus-canonical「問題二」驗算表;2026-09-09 Codex R13 抓到規格寫內框、
270
271
  // 實作卻是全域外框,在 overflow-scroll 的 TabsList 裡上下各被裁 3–4px)
@@ -277,6 +278,12 @@ const tabsTriggerVariants = cva(
277
278
  "after:bg-transparent after:transition-colors after:duration-150",
278
279
  // hover(未選):文字轉深
279
280
  "hover:text-foreground",
281
+ // 巢狀 hover(2026-09-25,user 選「卡片保留、按鈕再亮一層」;SSOT = tabs.spec.md `inlineAction` 條「指標在 inlineAction 上時」):
282
+ // 指標在這個 tab 的 inlineAction 上時,tab 保留上一行的 hover 字色,action 再亮自己那層。
283
+ // action 依 ARIA required-children 被 portal 到 tablist 外(見 TabsTrigger 的 portal 註解),指標在它上面時 trigger 不算 :hover,
284
+ // CSS 也寫不出「哪顆 action 對應哪個 trigger」→ 由 portal 外層 span 的 pointerenter/leave 在 trigger 上標 `data-action-hover`。
285
+ // 值與上一行 `hover:` 同一個;改那一行必須同步改這一行。
286
+ "data-[action-hover]:text-foreground",
280
287
  // selected
281
288
  "data-[state=active]:text-foreground data-[state=active]:font-medium",
282
289
  // 2026-07-06 user 拍板:選中底線 hover 階 → primary base(持續選中站 base;
@@ -285,7 +292,9 @@ const tabsTriggerVariants = cva(
285
292
  // disabled:cursor-not-allowed + 不吃 hover 色
286
293
  // 不用 pointer-events-none,否則 cursor 不會改變;button[disabled] 本身就擋 click
287
294
  "disabled:cursor-not-allowed disabled:text-fg-disabled",
288
- "disabled:hover:text-fg-disabled"
295
+ "disabled:hover:text-fg-disabled",
296
+ // 停用的 tab 同樣不吃「指標在 inlineAction 上」的字色(與上一行同理;特異性 (0,3,0) 不靠輸出順序)
297
+ "disabled:data-[action-hover]:text-fg-disabled"
289
298
  ],
290
299
  {
291
300
  variants: {
@@ -322,6 +331,25 @@ const TabsTrigger = React.forwardRef(({ className, startIcon: StartIcon, badge,
322
331
  [ref]
323
332
  );
324
333
  const [actionPos, setActionPos] = React.useState(null);
334
+ const [actionWrapEl, setActionWrapEl] = React.useState(null);
335
+ React.useEffect(() => {
336
+ if (!actionWrapEl) return;
337
+ const enter = (e) => {
338
+ var _a;
339
+ if (e.pointerType !== "touch") (_a = triggerElRef.current) == null ? void 0 : _a.setAttribute("data-action-hover", "");
340
+ };
341
+ const leave = () => {
342
+ var _a;
343
+ return (_a = triggerElRef.current) == null ? void 0 : _a.removeAttribute("data-action-hover");
344
+ };
345
+ actionWrapEl.addEventListener("pointerenter", enter);
346
+ actionWrapEl.addEventListener("pointerleave", leave);
347
+ return () => {
348
+ actionWrapEl.removeEventListener("pointerenter", enter);
349
+ actionWrapEl.removeEventListener("pointerleave", leave);
350
+ leave();
351
+ };
352
+ }, [actionWrapEl]);
325
353
  React.useLayoutEffect(() => {
326
354
  if (!hasAction || !overlayEl) return;
327
355
  const el = triggerElRef.current;
@@ -375,10 +403,17 @@ const TabsTrigger = React.forwardRef(({ className, startIcon: StartIcon, badge,
375
403
  overlayEl && actionPos && createPortal(
376
404
  // action portal 到 overlay(tablist 外);left=trigger.right + `-translate-x-full` → action
377
405
  // 右緣對齊 trigger 右緣;top/height + items-center → 垂直置中(同原 right-0 + top-1/2 幾何)。
406
+ // 外層 span 與 trigger 同高,但 action 本身只有 icon 高 —— span 上下各多出一塊(預設 sm:32px 高的 tab、16px 的 action,實測上下各 8px)。
407
+ // 那兩塊若接指標:指到時分頁字變深(巢狀滑過的 data-action-hover),點下去卻不切分頁 =「亮著卻點不到」
408
+ //(待辦總帳 N53②;hit-area-canonical.md「它要防的失敗是單向的:看到亮起來卻點不到」)。
409
+ // 所以 span 自己不接指標、只有 action 接([&>*]:pointer-events-auto):上下兩塊落回底下的 trigger(那是 trigger
410
+ // 右緣預留的 paddingRight 區,tabs.spec.md `inlineAction` 條),點了切分頁、滑過由 trigger 自己的 :hover 變色。
411
+ // 原生 pointerenter/leave 仍在指標進出 action 時送到 span(事件依 DOM 樹傳給祖先,與 pointer-events 無關),巢狀滑過照常。
378
412
  /* @__PURE__ */ jsx(
379
413
  "span",
380
414
  {
381
- className: "pointer-events-auto absolute inline-flex -translate-x-full items-center",
415
+ ref: setActionWrapEl,
416
+ className: "pointer-events-none [&>*]:pointer-events-auto absolute inline-flex -translate-x-full items-center",
382
417
  style: { left: actionPos.left, top: actionPos.top, height: actionPos.height },
383
418
  children: inlineAction
384
419
  }
@@ -421,7 +456,7 @@ const tabsMeta = {
421
456
  tokens: {
422
457
  bg: ["bg-primary", "bg-transparent"],
423
458
  fg: ["text-fg-disabled", "text-fg-secondary", "text-foreground"],
424
- ring: ["ring-ring"]
459
+ ring: ["focus-ring-inset", "--ring"]
425
460
  },
426
461
  defaultSize: "sm"
427
462
  };
@@ -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。當前 ~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'\nimport { useControllable } from '@/design-system/hooks/use-controllable'\nimport {\n getTabsInlineActionPaddingRight,\n mergeTabsInlineActionStyle,\n resolveTabsInlineActionPosition,\n type TabsInlineActionClipRect,\n} from './tabs-inline-action-geometry'\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\nconst TABS_ACTION_CLIPPING_OVERFLOW = new Set(['auto', 'scroll', 'hidden', 'clip', 'overlay'])\n\nfunction getTabsActionClippingAncestors(triggerEl: HTMLElement): HTMLElement[] {\n const ancestors: HTMLElement[] = []\n let current = triggerEl.parentElement\n while (current) {\n const style = window.getComputedStyle(current)\n if (\n TABS_ACTION_CLIPPING_OVERFLOW.has(style.overflowX) ||\n TABS_ACTION_CLIPPING_OVERFLOW.has(style.overflowY)\n ) {\n ancestors.push(current)\n }\n current = current.parentElement\n }\n return ancestors\n}\n\nfunction toTabsActionClipRect(element: HTMLElement): TabsInlineActionClipRect {\n const rect = element.getBoundingClientRect()\n const style = window.getComputedStyle(element)\n return {\n left: rect.left,\n right: rect.right,\n top: rect.top,\n bottom: rect.bottom,\n clipX: TABS_ACTION_CLIPPING_OVERFLOW.has(style.overflowX),\n clipY: TABS_ACTION_CLIPPING_OVERFLOW.has(style.overflowY),\n }\n}\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 // Wrapper mirror 是 Tabs 與 overflow menu proxy 的共同 SSOT。空字串代表尚未選取,\n // 因此 Radix 從 mount 起恆為 controlled;未提供 defaultValue 的第一次選取也不會\n // 從 value=undefined 翻成字串而觸發 uncontrolled → controlled 警告。\n const [currentValue, setCurrentValue] = useControllable<string>({\n value,\n defaultValue: defaultValue ?? '',\n onChange: onValueChange,\n })\n\n const valueContext = React.useMemo(\n () => ({ value: currentValue, onValueChange: setCurrentValue }),\n [currentValue, setCurrentValue]\n )\n\n return (\n <TabsValueContext.Provider value={valueContext}>\n <TabsPrimitive.Root\n ref={ref}\n value={currentValue}\n onValueChange={setCurrentValue}\n {...props}\n // Root 本來是裸 Radix(`display: block`),在「高度受限的 flex column 容器」裡會**斷鏈**:\n // Dialog / Sheet 的契約是 header/footer `shrink-0` + Body `flex-1 min-h-0` → 空間不夠時 body 內捲\n // (`dialog.spec.md:128`「內容溢出走 body 捲動」)。但只要中間夾一層 `display: block` 且\n // `min-height: auto` 的 wrapper,那層就收縮不了,整包內容原樣頂出容器 —— 2026-09-12 user 截圖的\n // 「dialog body 內容超出容器」正是這個:Tabs Root 在 204px 的 dialog 裡撐到 271px。\n // Root 自己成為可收縮的 flex column 之後,這條鏈在任何容器裡都自然接上,consumer 不需要背咒語。\n // 不受限的容器裡 `flex: 0 1 auto` 的 basis 仍是內容高,所以一般用法幾何完全不變(已逐 story 比對 Δ=0)。\n className={cn(\n 'flex min-h-0 data-[orientation=horizontal]:flex-col data-[orientation=vertical]:flex-row',\n props.className\n )}\n >\n {children}\n </TabsPrimitive.Root>\n </TabsValueContext.Provider>\n )\n})\nTabs.displayName = 'Tabs'\n\n// ── List ──\n// data-slot=\"tabs-list\" 契約(2026-07-30):三種 overflow 模式的 role=tablist 元素\n// (TabsPrimitive.List)一律自標 `data-slot=\"tabs-list\"`,供容器以 attribute contract 注入樣式\n// (header-canonical HEADER_TABS_SLOT_WRAPPER_CLASS 消費,詳 chrome-header.tsx 註解)。\n// 對齊 shadcn v4 tabs.tsx `<TabsPrimitive.List data-slot=\"tabs-list\">` idiom\n// (https://github.com/shadcn-ui/ui/blob/main/apps/v4/registry/new-york-v4/ui/tabs.tsx)\n// + DS 既有 data-unbounded 標記式契約(overlay-surface.tsx CHROME_UNBOUNDED_SLOT)。\n// 背景:beta.86 inlineAction overlay(fc2a3a5c)包 `<div class=\"relative\">` 後,tablist DOM 深度\n// 不再穩定,結構性 direct-child 選擇器不可靠 → 顯式契約免疫未來 DOM 重構。\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 data-slot=\"tabs-list\"\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 data-slot=\"tabs-list\"\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 data-slot=\"tabs-list\"\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 // 焦點框往內:tab 高 = 分頁列高,上下淨空 0(focus-canonical「問題二」驗算表;2026-09-09 Codex R13 抓到規格寫內框、\n // 實作卻是全域外框,在 overflow-scroll 的 TabsList 裡上下各被裁 3–4px)\n 'focus-visible:focus-ring-inset',\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, style, ...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 寬 + sibling\n // canonical gap-2(8px)。badge/endIcon 是 trigger 內 suffix,不得壓縮與獨立 action 的間距。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 = getTabsInlineActionPaddingRight(iconSize, hasAction)\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 clippingAncestors = getTabsActionClippingAncestors(el)\n const compute = () => {\n const t = triggerElRef.current?.getBoundingClientRect()\n const o = overlayEl.getBoundingClientRect()\n if (!t) return\n setActionPos(resolveTabsInlineActionPosition({\n trigger: t,\n overlay: o,\n clips: clippingAncestors.map(toTabsActionClipRect),\n actionWidth: iconSize,\n }))\n }\n compute()\n const ro = new ResizeObserver(compute)\n ro.observe(el)\n ro.observe(overlayEl)\n for (const clippingAncestor of clippingAncestors) ro.observe(clippingAncestor)\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, iconSize, overlayEl])\n\n const trigger = (\n <TabsPrimitive.Trigger\n ref={setTriggerRef}\n className={cn(tabsTriggerVariants({ size }), className)}\n {...props}\n style={mergeTabsInlineActionStyle(style, actionPaddingRight)}\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 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'\nimport { useControllable } from '@/design-system/hooks/use-controllable'\nimport {\n getTabsInlineActionPaddingRight,\n mergeTabsInlineActionStyle,\n resolveTabsInlineActionPosition,\n type TabsInlineActionClipRect,\n} from './tabs-inline-action-geometry'\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\nconst TABS_ACTION_CLIPPING_OVERFLOW = new Set(['auto', 'scroll', 'hidden', 'clip', 'overlay'])\n\nfunction getTabsActionClippingAncestors(triggerEl: HTMLElement): HTMLElement[] {\n const ancestors: HTMLElement[] = []\n let current = triggerEl.parentElement\n while (current) {\n const style = window.getComputedStyle(current)\n if (\n TABS_ACTION_CLIPPING_OVERFLOW.has(style.overflowX) ||\n TABS_ACTION_CLIPPING_OVERFLOW.has(style.overflowY)\n ) {\n ancestors.push(current)\n }\n current = current.parentElement\n }\n return ancestors\n}\n\nfunction toTabsActionClipRect(element: HTMLElement): TabsInlineActionClipRect {\n const rect = element.getBoundingClientRect()\n const style = window.getComputedStyle(element)\n return {\n left: rect.left,\n right: rect.right,\n top: rect.top,\n bottom: rect.bottom,\n clipX: TABS_ACTION_CLIPPING_OVERFLOW.has(style.overflowX),\n clipY: TABS_ACTION_CLIPPING_OVERFLOW.has(style.overflowY),\n }\n}\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 // Wrapper mirror 是 Tabs 與 overflow menu proxy 的共同 SSOT。空字串代表尚未選取,\n // 因此 Radix 從 mount 起恆為 controlled;未提供 defaultValue 的第一次選取也不會\n // 從 value=undefined 翻成字串而觸發 uncontrolled → controlled 警告。\n const [currentValue, setCurrentValue] = useControllable<string>({\n value,\n defaultValue: defaultValue ?? '',\n onChange: onValueChange,\n })\n\n const valueContext = React.useMemo(\n () => ({ value: currentValue, onValueChange: setCurrentValue }),\n [currentValue, setCurrentValue]\n )\n\n return (\n <TabsValueContext.Provider value={valueContext}>\n <TabsPrimitive.Root\n ref={ref}\n value={currentValue}\n onValueChange={setCurrentValue}\n {...props}\n // Root 本來是裸 Radix(`display: block`),在「高度受限的 flex column 容器」裡會**斷鏈**:\n // Dialog / Sheet 的契約是 header/footer `shrink-0` + Body `flex-1 min-h-0` → 空間不夠時 body 內捲\n // (`dialog.spec.md:128`「內容溢出走 body 捲動」)。但只要中間夾一層 `display: block` 且\n // `min-height: auto` 的 wrapper,那層就收縮不了,整包內容原樣頂出容器 —— 2026-09-12 user 截圖的\n // 「dialog body 內容超出容器」正是這個:Tabs Root 在 204px 的 dialog 裡撐到 271px。\n // Root 自己成為可收縮的 flex column 之後,這條鏈在任何容器裡都自然接上,consumer 不需要背咒語。\n // 不受限的容器裡 `flex: 0 1 auto` 的 basis 仍是內容高,所以一般用法幾何完全不變(已逐 story 比對 Δ=0)。\n className={cn(\n 'flex min-h-0 data-[orientation=horizontal]:flex-col data-[orientation=vertical]:flex-row',\n props.className\n )}\n >\n {children}\n </TabsPrimitive.Root>\n </TabsValueContext.Provider>\n )\n})\nTabs.displayName = 'Tabs'\n\n// ── List ──\n// data-slot=\"tabs-list\" 契約(2026-07-30):三種 overflow 模式的 role=tablist 元素\n// (TabsPrimitive.List)一律自標 `data-slot=\"tabs-list\"`,供容器以 attribute contract 注入樣式\n// (header-canonical HEADER_TABS_SLOT_WRAPPER_CLASS 消費,詳 chrome-header.tsx 註解)。\n// 對齊 shadcn v4 tabs.tsx `<TabsPrimitive.List data-slot=\"tabs-list\">` idiom\n// (https://github.com/shadcn-ui/ui/blob/main/apps/v4/registry/new-york-v4/ui/tabs.tsx)\n// + DS 既有 data-unbounded 標記式契約(overlay-surface.tsx CHROME_UNBOUNDED_SLOT)。\n// 背景:beta.86 inlineAction overlay(fc2a3a5c)包 `<div class=\"relative\">` 後,tablist DOM 深度\n// 不再穩定,結構性 direct-child 選擇器不可靠 → 顯式契約免疫未來 DOM 重構。\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 data-slot=\"tabs-list\"\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 data-slot=\"tabs-list\"\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 data-slot=\"tabs-list\"\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:滑過的字色一律瞬間(tokens/motion/motion.spec.md「hover 回饋不做過渡」;2026-09-26 由底色延伸到字色,\n // 待辦總帳 L9 / N4(3))。選中切換的**底線**過渡在下方 `after:transition-colors`,那是選中切換(每個分頁自己的底線淡出 / 淡入,不會滑過去)不是滑過,保留(L9 範圍明文排除)。\n 'cursor-pointer select-none',\n // 焦點框往內:tab 高 = 分頁列高,上下淨空 0(focus-canonical「問題二」驗算表;2026-09-09 Codex R13 抓到規格寫內框、\n // 實作卻是全域外框,在 overflow-scroll 的 TabsList 裡上下各被裁 3–4px)\n 'focus-visible:focus-ring-inset',\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 // 巢狀 hover(2026-09-25,user 選「卡片保留、按鈕再亮一層」;SSOT = tabs.spec.md `inlineAction` 條「指標在 inlineAction 上時」):\n // 指標在這個 tab 的 inlineAction 上時,tab 保留上一行的 hover 字色,action 再亮自己那層。\n // action 依 ARIA required-children 被 portal 到 tablist 外(見 TabsTrigger 的 portal 註解),指標在它上面時 trigger 不算 :hover,\n // CSS 也寫不出「哪顆 action 對應哪個 trigger」→ 由 portal 外層 span 的 pointerenter/leave 在 trigger 上標 `data-action-hover`。\n // 值與上一行 `hover:` 同一個;改那一行必須同步改這一行。\n 'data-[action-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 // 停用的 tab 同樣不吃「指標在 inlineAction 上」的字色(與上一行同理;特異性 (0,3,0) 不靠輸出順序)\n 'disabled:data-[action-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, style, ...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 寬 + sibling\n // canonical gap-2(8px)。badge/endIcon 是 trigger 內 suffix,不得壓縮與獨立 action 的間距。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 = getTabsInlineActionPaddingRight(iconSize, hasAction)\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 // 巢狀 hover(2026-09-25,user 選「卡片保留、按鈕再亮一層」;SSOT = tabs.spec.md `inlineAction` 條「指標在 inlineAction 上時」):\n // 指標在 portal 出去的 action 上時,在 trigger 標 `data-action-hover`,cva 的 `data-[action-hover]:text-foreground` 讀它。\n // - 用**原生** pointerenter/leave,不用 React 的 onPointerEnter/Leave:React 依元件樹算進出,而 inlineAction 裡的\n // DropdownMenuContent 是這個 span 的元件樹後代(DOM 卻在 body)—— 指標移進選單時 React 不發 leave,選單關掉時節點卸載\n // 也不發,標記就殘留在 tab 上。原生事件依 DOM 樹,與 CSS :hover 同一套判定,跟其他宿主(Sidebar 列、表頭)一致。\n // - 觸控不標:對齊 Tailwind 4 `hover:` 的 `@media (hover:hover)`(觸控點一下會 enter 再 leave,不該閃一下)。\n // - span 卸載(量測後不相交 / 拿掉 inlineAction)時指標可能還停在上面、收不到 leave → cleanup 一併撤標,免得 tab 殘亮。\n const [actionWrapEl, setActionWrapEl] = React.useState<HTMLSpanElement | null>(null)\n React.useEffect(() => {\n if (!actionWrapEl) return\n const enter = (e: PointerEvent) => {\n if (e.pointerType !== 'touch') triggerElRef.current?.setAttribute('data-action-hover', '')\n }\n const leave = () => triggerElRef.current?.removeAttribute('data-action-hover')\n actionWrapEl.addEventListener('pointerenter', enter)\n actionWrapEl.addEventListener('pointerleave', leave)\n return () => {\n actionWrapEl.removeEventListener('pointerenter', enter)\n actionWrapEl.removeEventListener('pointerleave', leave)\n leave()\n }\n }, [actionWrapEl])\n\n React.useLayoutEffect(() => {\n if (!hasAction || !overlayEl) return\n const el = triggerElRef.current\n if (!el) return\n const clippingAncestors = getTabsActionClippingAncestors(el)\n const compute = () => {\n const t = triggerElRef.current?.getBoundingClientRect()\n const o = overlayEl.getBoundingClientRect()\n if (!t) return\n setActionPos(resolveTabsInlineActionPosition({\n trigger: t,\n overlay: o,\n clips: clippingAncestors.map(toTabsActionClipRect),\n actionWidth: iconSize,\n }))\n }\n compute()\n const ro = new ResizeObserver(compute)\n ro.observe(el)\n ro.observe(overlayEl)\n for (const clippingAncestor of clippingAncestors) ro.observe(clippingAncestor)\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, iconSize, overlayEl])\n\n const trigger = (\n <TabsPrimitive.Trigger\n ref={setTriggerRef}\n className={cn(tabsTriggerVariants({ size }), className)}\n {...props}\n style={mergeTabsInlineActionStyle(style, actionPaddingRight)}\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 與 trigger 同高,但 action 本身只有 icon 高 —— span 上下各多出一塊(預設 sm:32px 高的 tab、16px 的 action,實測上下各 8px)。\n // 那兩塊若接指標:指到時分頁字變深(巢狀滑過的 data-action-hover),點下去卻不切分頁 =「亮著卻點不到」\n //(待辦總帳 N53②;hit-area-canonical.md「它要防的失敗是單向的:看到亮起來卻點不到」)。\n // 所以 span 自己不接指標、只有 action 接([&>*]:pointer-events-auto):上下兩塊落回底下的 trigger(那是 trigger\n // 右緣預留的 paddingRight 區,tabs.spec.md `inlineAction` 條),點了切分頁、滑過由 trigger 自己的 :hover 變色。\n // 原生 pointerenter/leave 仍在指標進出 action 時送到 span(事件依 DOM 樹傳給祖先,與 pointer-events 無關),巢狀滑過照常。\n <span\n ref={setActionWrapEl}\n className=\"pointer-events-none [&>*]: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 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: ['focus-ring-inset', '--ring'],\n },\n defaultSize: 'sm',\n} as const\n\nexport { Tabs, TabsList, TabsTrigger, TabsContent, tabsTriggerVariants }\nexport type { TabsSize, TabsListProps, TabsTriggerProps }\n"],"names":[],"mappings":";;;;;;;;;;;;AA+DA,MAAM,cAAc,MAAM,cAAgC,EAAE,MAAM,MAAM;AASxE,MAAM,2BAA2B,MAAM,cAAqC,IAAI;AAEhF,MAAM,oDAAoC,IAAI,CAAC,QAAQ,UAAU,UAAU,QAAQ,SAAS,CAAC;AAE7F,SAAS,+BAA+B,WAAuC;AAC7E,QAAM,YAA2B,CAAA;AACjC,MAAI,UAAU,UAAU;AACxB,SAAO,SAAS;AACd,UAAM,QAAQ,OAAO,iBAAiB,OAAO;AAC7C,QACE,8BAA8B,IAAI,MAAM,SAAS,KACjD,8BAA8B,IAAI,MAAM,SAAS,GACjD;AACA,gBAAU,KAAK,OAAO;AAAA,IACxB;AACA,cAAU,QAAQ;AAAA,EACpB;AACA,SAAO;AACT;AAEA,SAAS,qBAAqB,SAAgD;AAC5E,QAAM,OAAO,QAAQ,sBAAA;AACrB,QAAM,QAAQ,OAAO,iBAAiB,OAAO;AAC7C,SAAO;AAAA,IACL,MAAM,KAAK;AAAA,IACX,OAAO,KAAK;AAAA,IACZ,KAAK,KAAK;AAAA,IACV,QAAQ,KAAK;AAAA,IACb,OAAO,8BAA8B,IAAI,MAAM,SAAS;AAAA,IACxD,OAAO,8BAA8B,IAAI,MAAM,SAAS;AAAA,EAAA;AAE5D;AAOA,MAAM,mBAAmB,MAAM,cAG5B,EAAE;AAKL,SAAS,cAAc,SAAiE;;AACtF,MAAI,UAAS,YAAO,yBAAyB,QAAQ,OAAO,KAAK,MAApD,mBAAuD;AACpE,MAAI,UAAU,UAAU,oBAAoB,UAAU,OAAO;AAC7D,MAAI,gBAAiB,QAAwD;AAC7E,YAAS,YAAO,yBAAyB,SAAS,KAAK,MAA9C,mBAAiD;AAC1D,YAAU,UAAU,oBAAoB,UAAU,OAAO;AACzD,MAAI,QAAS,QAAQ,QAAQ,MAA2C;AACxE,SACG,QAAQ,MAA2C,OACnD,QAAwD,OACzD;AAEJ;AAMA,SAAS,mBAAmB,UAA2B,SAAS,IAA0B;AACxF,QAAM,MAA4B,CAAA;AAClC,QAAM,SAAS,QAAQ,UAAU,CAAC,OAAO,MAAM;AAC7C,QAAI,CAAC,MAAM,eAAe,KAAK,EAAG;AAClC,QAAI,MAAM,SAAS,MAAM,UAAU;AACjC,UAAI;AAAA,QACF,GAAG,mBAAoB,MAAM,MAAyC,UAAU,GAAG,MAAM,GAAG,CAAC,GAAG;AAAA,MAAA;AAAA,IAEpG,OAAO;AACL,UAAI,KAAK,MAAM,OAAO,OAAO,QAAQ,MAAM,aAAa,OAAO,EAAE,KAAK,OAAO,MAAM,GAAG,CAAC,GAAA,CAAI,CAAC;AAAA,IAC9F;AAAA,EACF,CAAC;AACD,SAAO;AACT;AAEA,MAAM,OAAO,MAAM,WAGjB,CAAC,EAAE,OAAO,eAAe,cAAc,UAAU,GAAG,MAAA,GAAS,QAAQ;AAIrE,QAAM,CAAC,cAAc,eAAe,IAAI,gBAAwB;AAAA,IAC9D;AAAA,IACA,cAAc,gBAAgB;AAAA,IAC9B,UAAU;AAAA,EAAA,CACX;AAED,QAAM,eAAe,MAAM;AAAA,IACzB,OAAO,EAAE,OAAO,cAAc,eAAe,gBAAA;AAAA,IAC7C,CAAC,cAAc,eAAe;AAAA,EAAA;AAGhC,SACE,oBAAC,iBAAiB,UAAjB,EAA0B,OAAO,cAChC,UAAA;AAAA,IAAC,cAAc;AAAA,IAAd;AAAA,MACC;AAAA,MACA,OAAO;AAAA,MACP,eAAe;AAAA,MACd,GAAG;AAAA,MAQJ,WAAW;AAAA,QACT;AAAA,QACA,MAAM;AAAA,MAAA;AAAA,MAGP;AAAA,IAAA;AAAA,EAAA,GAEL;AAEJ,CAAC;AACD,KAAK,cAAc;AAkBnB,MAAM,iBAAiB;AAAA,EACrB;AAAA,EACA;AAAA,EACA;AACF,EAAE,KAAK,GAAG;AAeV,MAAM,WAAW,MAAM,WAGrB,CAAC,EAAE,WAAW,OAAO,MAAM,WAAW,QAAQ,UAAU,GAAG,MAAA,GAAS,QAAQ;AAC5E,QAAM,kBAAkB,MAAM,QAAQ,OAAO,EAAE,SAAS,CAAC,IAAI,CAAC;AAG9D,QAAM,CAAC,WAAW,YAAY,IAAI,MAAM,SAAgC,IAAI;AAE5E,MAAI;AACJ,MAAI,aAAa,UAAU;AACzB,mCACG,gBAAA,EAAe,KAAU,WAAuB,GAAG,OACjD,UACH;AAAA,EAEJ,WAAW,aAAa,QAAQ;AAC9B,mCACG,cAAA,EAAa,KAAU,WAAuB,GAAG,OAC/C,UACH;AAAA,EAEJ,OAAO;AAEL,eACE;AAAA,MAAC,cAAc;AAAA,MAAd;AAAA,QACC;AAAA,QACA,aAAU;AAAA,QACV,WAAW,GAAG,gBAAgB,UAAU,SAAS;AAAA,QAChD,GAAG;AAAA,QAEH;AAAA,MAAA;AAAA,IAAA;AAAA,EAGP;AAEA,SACE,oBAAC,YAAY,UAAZ,EAAqB,OAAO,iBAC3B,UAAA,oBAAC,yBAAyB,UAAzB,EAAkC,OAAO,WACxC,UAAA,qBAAC,OAAA,EAAI,WAAU,YACZ,UAAA;AAAA,IAAA;AAAA,IAED,oBAAC,OAAA,EAAI,KAAK,cAAc,WAAU,4CAAA,CAA4C;AAAA,EAAA,EAAA,CAChF,GACF,GACF;AAEJ,CAAC;AACD,SAAS,cAAc;AAkBvB,MAAM,iBAAiB,MAAM,WAG3B,CAAC,EAAE,WAAW,UAAU,GAAG,MAAA,GAAS,QAAQ;AAC5C,QAAM,EAAE,WAAW,SAAS,OAAO,UAAA,IAAc,eAAA;AACjD,QAAM,eAAe,gBAAgB,SAAS;AAC9C,QAAM,YAAY,cAAc;AAAA,IAC9B;AAAA,IACA;AAAA,IACA;AAAA,IACA,mBAAmB;AAAA,EAAA,CACpB;AAED;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IASE,qBAAC,OAAA,EAAI,WAAU,YACb,UAAA;AAAA,MAAA;AAAA,QAAC;AAAA,QAAA;AAAA,UACC,KAAK;AAAA,UACL,WAAU;AAAA,UACV,OAAO,EAAE,WAAW,iBAAiB,UAAA;AAAA,UAErC,UAAA;AAAA,YAAC,cAAc;AAAA,YAAd;AAAA,cACC;AAAA,cACA,aAAU;AAAA,cACV,WAAW,GAAG,gBAAgB,cAAc,SAAS;AAAA,cACpD,GAAG;AAAA,cAEH;AAAA,YAAA;AAAA,UAAA;AAAA,QACH;AAAA,MAAA;AAAA,MAED,CAAC,WAAW,aACX,oBAAC,qBAAA,EAAoB,WAAU,QAAO,SAAS,MAAM,aAAa,MAAM,EAAA,CAAG;AAAA,MAE5E,CAAC,SAAS,aACT,oBAAC,qBAAA,EAAoB,WAAU,SAAQ,SAAS,MAAM,aAAa,OAAO,EAAA,CAAG;AAAA,IAAA,EAAA,CAEjF;AAAA;AAEJ,CAAC;AACD,eAAe,cAAc;AAiB7B,MAAM,eAAe,MAAM,WAGzB,CAAC,EAAE,WAAW,UAAU,GAAG,MAAA,GAAS,QAAQ;AAC5C,QAAM,EAAE,WAAW,SAAS,OAAO,UAAA,IAAc,eAAA;AACjD,QAAM,EAAE,eAAe,OAAO,gBAAgB,MAAM,WAAW,gBAAgB;AAK/E,QAAM,WAAW,MAAM,OAAiC,oBAAI,KAAK;AAEjE,QAAM,iBAAiB,MAAM,OAAe,CAAC;AAC7C,QAAM,UAAU,MAAM,MAAM;AAAE,QAAI,eAAe,QAAS,sBAAqB,eAAe,OAAO;AAAA,EAAE,GAAG,CAAA,CAAE;AAC5G,QAAM,eAAe,MAAM;AAAA,IACzB,CAAC,UAAkB,CAAC,OAA2B;AAC7C,UAAI,GAAI,UAAS,QAAQ,IAAI,OAAO,EAAE;AAAA,UACjC,UAAS,QAAQ,OAAO,KAAK;AAAA,IACpC;AAAA,IACA,CAAA;AAAA,EAAC;AAIH,QAAM,QAAQ,MAAM;AAAA,IAClB,MAAM,mBAAmB,QAAQ;AAAA,IACjC,CAAC,QAAQ;AAAA,EAAA;AAKX,QAAM,mBAAmB,MAAM,IAAI,CAAC,OAAO,MAAM;AAC/C,UAAM,WAAW,cAAc,KAAK;AACpC,WAAO,MAAM;AAAA,MACX;AAAA,MACA;AAAA,QACE,KAAK,CAAC,OAA2B;AAC/B,uBAAa,CAAC,EAAE,EAAE;AAClB,cAAI,OAAO,aAAa,WAAY,UAAS,EAAE;AAAA,mBACtC,SAAW,UAAwD,UAAU;AAAA,QACxF;AAAA,MAAA;AAAA,IACF;AAAA,EAEJ,CAAC;AAID,QAAM,YAAY,cAAc,EAAE,WAAW,SAAS,OAAO,mBAAmB,GAAG;AAEnF,QAAM,mBAAmB,MAAM;AAAA,IAC7B,CAAC,cAAsB,UAAkB;AACvC,qDAAgB;AAEhB,UAAI,eAAe,QAAS,sBAAqB,eAAe,OAAO;AACvE,qBAAe,UAAU,sBAAsB,MAAM;;AACnD,uBAAe,UAAU;AACzB,uBAAS,QAAQ,IAAI,KAAK,MAA1B,mBAA6B,eAAe,EAAE,UAAU,UAAU,QAAQ,UAAU,OAAO,UAAA;AAAA,MAC7F,CAAC;AAAA,IACH;AAAA,IACA,CAAC,aAAa;AAAA,EAAA;AAGhB;AAAA;AAAA;AAAA;AAAA;AAAA,IAKE,qBAAC,OAAA,EAAI,WAAU,sBACb,UAAA;AAAA,MAAA;AAAA,QAAC;AAAA,QAAA;AAAA,UACC,KAAK;AAAA,UACL,WAAU;AAAA,UACV,OAAO,EAAE,WAAW,iBAAiB,UAAA;AAAA,UAErC,UAAA;AAAA,YAAC,cAAc;AAAA,YAAd;AAAA,cACC;AAAA,cACA,aAAU;AAAA,cACV,WAAW,GAAG,gBAAgB,cAAc,SAAS;AAAA,cACpD,GAAG;AAAA,cAEH,UAAA;AAAA,YAAA;AAAA,UAAA;AAAA,QACH;AAAA,MAAA;AAAA,MAED,aACC,oBAAC,OAAA,EAAI,WAAU,0FACb,+BAAC,cAAA,EACC,UAAA;AAAA,QAAA,oBAAC,qBAAA,EAAoB,SAAO,MAC1B,UAAA;AAAA,UAAC;AAAA,UAAA;AAAA,YACC,cAAY,UAAU,MAAM,MAAM;AAAA,UAAA;AAAA,QAAA,GAEtC;AAAA,QACA,oBAAC,uBAAoB,OAAM,OACxB,gBAAM,IAAI,CAAC,SAAS,UAAU;AAC7B,gBAAM,eAAe,QAAQ;AAK7B,gBAAM,eAAe,aAAa;AAClC,cAAI,OAAO,iBAAiB,SAAU,QAAO;AAI7C,iBACE;AAAA,YAAC;AAAA,YAAA;AAAA,cAEC,UAAU,aAAa;AAAA,cACvB,UAAU,gBAAgB;AAAA,cAC1B,UAAU,MAAM,iBAAiB,cAAc,KAAK;AAAA,cAEnD,UAAA,aAAa;AAAA,YAAA;AAAA,YALT;AAAA,UAAA;AAAA,QAQX,CAAC,EAAA,CACH;AAAA,MAAA,EAAA,CACF,EAAA,CACF;AAAA,IAAA,EAAA,CAEJ;AAAA;AAEJ,CAAC;AACD,aAAa,cAAc;AAG3B,MAAM,sBAAsB;AAAA,EAC1B;AAAA,IACE;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA;AAAA;AAAA,IAGA;AAAA;AAAA;AAAA,IAGA;AAAA;AAAA;AAAA;AAAA;AAAA,IAKA;AAAA,IACA;AAAA;AAAA,IAEA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAMA;AAAA;AAAA,IAEA;AAAA;AAAA;AAAA,IAGA;AAAA;AAAA;AAAA,IAGA;AAAA,IACA;AAAA;AAAA,IAEA;AAAA,EAAA;AAAA,EAEF;AAAA,IACE,UAAU;AAAA,MACR,MAAM;AAAA;AAAA,QAEJ,IAAI;AAAA,QACJ,IAAI;AAAA,QACJ,IAAI;AAAA,MAAA;AAAA,IACN;AAAA,IAEF,iBAAiB;AAAA;AAAA;AAAA;AAAA;AAAA,MAKf,MAAM;AAAA,IAAA;AAAA,EACR;AAEJ;AAiCA,MAAM,cAAc,MAAM,WAGxB,CAAC,EAAE,WAAW,WAAW,WAAW,OAAO,SAAS,SAAS,cAAc,UAAU,OAAO,GAAG,MAAA,GAAS,QAAQ;AAChH,QAAM,EAAE,KAAA,IAAS,MAAM,WAAW,WAAW;AAC7C,QAAM,YAAY,MAAM,WAAW,wBAAwB;AAE3D,QAAM,WAAW,UAAU,IAA0B;AACrD,QAAM,YAAY,SAAS,QAAQ,YAAY;AAM/C,QAAM,YAAY,gBAAgB;AAClC,QAAM,qBAAqB,gCAAgC,UAAU,SAAS;AAG9E,QAAM,eAAe,MAAM,OAAiC,IAAI;AAChE,QAAM,gBAAgB,MAAM;AAAA,IAC1B,CAAC,OAAiC;AAChC,mBAAa,UAAU;AACvB,UAAI,OAAO,QAAQ,WAAY,KAAI,EAAE;AAAA,eAC5B,IAAM,KAAyD,UAAU;AAAA,IACpF;AAAA,IACA,CAAC,GAAG;AAAA,EAAA;AAEN,QAAM,CAAC,WAAW,YAAY,IAAI,MAAM,SAA+D,IAAI;AAS3G,QAAM,CAAC,cAAc,eAAe,IAAI,MAAM,SAAiC,IAAI;AACnF,QAAM,UAAU,MAAM;AACpB,QAAI,CAAC,aAAc;AACnB,UAAM,QAAQ,CAAC,MAAoB;;AACjC,UAAI,EAAE,gBAAgB,4BAAsB,+BAAS,aAAa,qBAAqB;AAAA,IACzF;AACA,UAAM,QAAQ,MAAA;;AAAM,gCAAa,YAAb,mBAAsB,gBAAgB;AAAA;AAC1D,iBAAa,iBAAiB,gBAAgB,KAAK;AACnD,iBAAa,iBAAiB,gBAAgB,KAAK;AACnD,WAAO,MAAM;AACX,mBAAa,oBAAoB,gBAAgB,KAAK;AACtD,mBAAa,oBAAoB,gBAAgB,KAAK;AACtD,YAAA;AAAA,IACF;AAAA,EACF,GAAG,CAAC,YAAY,CAAC;AAEjB,QAAM,gBAAgB,MAAM;AAC1B,QAAI,CAAC,aAAa,CAAC,UAAW;AAC9B,UAAM,KAAK,aAAa;AACxB,QAAI,CAAC,GAAI;AACT,UAAM,oBAAoB,+BAA+B,EAAE;AAC3D,UAAM,UAAU,MAAM;;AACpB,YAAM,KAAI,kBAAa,YAAb,mBAAsB;AAChC,YAAM,IAAI,UAAU,sBAAA;AACpB,UAAI,CAAC,EAAG;AACR,mBAAa,gCAAgC;AAAA,QAC3C,SAAS;AAAA,QACT,SAAS;AAAA,QACT,OAAO,kBAAkB,IAAI,oBAAoB;AAAA,QACjD,aAAa;AAAA,MAAA,CACd,CAAC;AAAA,IACJ;AACA,YAAA;AACA,UAAM,KAAK,IAAI,eAAe,OAAO;AACrC,OAAG,QAAQ,EAAE;AACb,OAAG,QAAQ,SAAS;AACpB,eAAW,oBAAoB,kBAAmB,IAAG,QAAQ,gBAAgB;AAC7E,WAAO,iBAAiB,UAAU,OAAO;AACzC,WAAO,iBAAiB,UAAU,SAAS,IAAI;AAC/C,WAAO,MAAM;AACX,SAAG,WAAA;AACH,aAAO,oBAAoB,UAAU,OAAO;AAC5C,aAAO,oBAAoB,UAAU,SAAS,IAAI;AAAA,IACpD;AAAA,EACF,GAAG,CAAC,WAAW,UAAU,SAAS,CAAC;AAEnC,QAAM,UACJ;AAAA,IAAC,cAAc;AAAA,IAAd;AAAA,MACC,KAAK;AAAA,MACL,WAAW,GAAG,oBAAoB,EAAE,KAAA,CAAM,GAAG,SAAS;AAAA,MACrD,GAAG;AAAA,MACJ,OAAO,2BAA2B,OAAO,kBAAkB;AAAA,MAE1D,UAAA;AAAA,QAAA,aAAa,oBAAC,WAAA,EAAU,MAAM,UAAU,eAAW,MAAC;AAAA,QACpD,YAAY,QAAQ,oBAAC,QAAA,EAAM,SAAA,CAAS;AAAA,QACpC,aACC,qBAAC,QAAA,EAAK,WAAU,kCACb,UAAA;AAAA,UAAA;AAAA,UACA,WAAW,oBAAC,SAAA,EAAQ,MAAM,UAAU,eAAW,KAAA,CAAC;AAAA,QAAA,EAAA,CACnD;AAAA,MAAA;AAAA,IAAA;AAAA,EAAA;AAKN,MAAI,CAAC,UAAW,QAAO;AAEvB,SACE,qBAAA,UAAA,EACG,UAAA;AAAA,IAAA;AAAA,IACA,aACC,aACA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MASE;AAAA,QAAC;AAAA,QAAA;AAAA,UACC,KAAK;AAAA,UACL,WAAU;AAAA,UACV,OAAO,EAAE,MAAM,UAAU,MAAM,KAAK,UAAU,KAAK,QAAQ,UAAU,OAAA;AAAA,UAEpE,UAAA;AAAA,QAAA;AAAA,MAAA;AAAA,MAEH;AAAA,IAAA;AAAA,EACF,GACJ;AAEJ,CAAC;AACD,YAAY,cAAc;AAG1B,MAAM,cAAc,MAAM,WAGxB,CAAC,EAAE,WAAW,GAAG,MAAA,GAAS,QAC1B;AAAA,EAAC,cAAc;AAAA,EAAd;AAAA,IACC;AAAA,IACA,WAAW;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAMT;AAAA,MACA;AAAA,IAAA;AAAA,IAED,GAAG;AAAA,EAAA;AACN,CACD;AACD,YAAY,cAAc;AAInB,MAAM,WAAW;AAAA,EACtB,WAAW;AAAA,EACX,QAAQ;AAAA;AAAA,EACR,UAAU,CAAA;AAAA;AAAA,EAIV,OAAO;AAAA,IACL,IAAI,EAAE,aAAa,IAAI,UAAU,IAAI,YAAY,OAAA;AAAA,IACjD,IAAI,EAAE,aAAa,IAAI,UAAU,IAAI,YAAY,OAAA;AAAA,IACjD,IAAI,EAAE,aAAa,IAAI,UAAU,IAAI,YAAY,UAAA;AAAA,EAAU;AAAA;AAAA,EAG7D,QAAQ,CAAC,WAAW,SAAS,YAAY,iBAAiB,UAAU;AAAA,EACpE,QAAQ;AAAA,IACN,IAAI,CAAC,cAAc,gBAAgB;AAAA,IACnC,IAAI,CAAC,oBAAoB,qBAAqB,iBAAiB;AAAA,IAC/D,MAAM,CAAC,oBAAoB,QAAQ;AAAA,EAAA;AAAA,EAErC,aAAa;AACf;"}
@@ -102,7 +102,7 @@ export declare const tagMeta: {
102
102
  readonly tokens: {
103
103
  readonly bg: readonly ["bg-neutral-active", "bg-neutral-hover", "bg-secondary", "bg-transparent"];
104
104
  readonly fg: readonly ["text-foreground", "text-inverse-fg"];
105
- readonly ring: readonly [];
105
+ readonly ring: readonly ["focus-ring-inset", "--ring"];
106
106
  };
107
107
  readonly defaultVariant: "neutral";
108
108
  readonly defaultSize: "md";
@@ -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;AAyB9C;;;GAGG;AACH,eAAO,MAAM,aAAa;;;;CAAsC,CAAA;AAEhE,QAAA,MAAM,WAAW;;;8EAyBhB,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;AA+JD,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;AAyB9C;;;GAGG;AACH,eAAO,MAAM,aAAa;;;;CAAsC,CAAA;AAEhE,QAAA,MAAM,WAAW;;;8EA0BhB,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;AA8JD,QAAA,MAAM,GAAG,iFAAuD,CAAA;AAKhE,eAAO,MAAM,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA4CV,CAAA;AAEV,OAAO,EAAE,GAAG,EAAE,WAAW,EAAE,CAAA"}
@@ -14,7 +14,8 @@ function getMeasureCtx() {
14
14
  }
15
15
  const TAG_HEIGHT_PX = { sm: 20, md: 24, lg: 24 };
16
16
  const tagVariants = cva(
17
- "inline-flex items-center rounded-md border border-transparent transition-colors cursor-text",
17
+ // 停用(consumer 掛 aria-disabled;Combobox 的停用示範就是)→ 禁止符號蓋掉 cursor-text(hit-area-canonical.md 滑過原則一-3「停用的用禁止符號」,2026-09-29 全站掃出)
18
+ "inline-flex items-center rounded-md border border-transparent transition-colors cursor-text aria-disabled:cursor-not-allowed",
18
19
  {
19
20
  variants: {
20
21
  // color:categorical 色相(裝飾性分類,非語意狀態)。**消費 categorical-color SSOT**——
@@ -170,7 +171,7 @@ const tagMeta = {
170
171
  //(anatomy TOKEN_MAP 以 stripVar 機械衍生,零漂移)。
171
172
  bg: ["bg-neutral-active", "bg-neutral-hover", "bg-secondary", "bg-transparent"],
172
173
  fg: ["text-foreground", "text-inverse-fg"],
173
- ring: []
174
+ ring: ["focus-ring-inset", "--ring"]
174
175
  },
175
176
  defaultVariant: "neutral",
176
177
  defaultSize: "md"