@qijenchen/design-system 0.1.0-beta.80 → 0.1.0-beta.81

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 (458) hide show
  1. package/CLAUDE.md +1 -0
  2. package/dist/components/Alert/alert.d.ts +5 -0
  3. package/dist/components/Alert/alert.d.ts.map +1 -1
  4. package/dist/components/Alert/alert.js +4 -1
  5. package/dist/components/Alert/alert.js.map +1 -1
  6. package/dist/components/AppShell/app-shell.d.ts +1 -1
  7. package/dist/components/AppShell/app-shell.js.map +1 -1
  8. package/dist/components/AspectRatio/aspect-ratio.d.ts +2 -1
  9. package/dist/components/AspectRatio/aspect-ratio.d.ts.map +1 -1
  10. package/dist/components/AspectRatio/aspect-ratio.js +1 -1
  11. package/dist/components/AspectRatio/aspect-ratio.js.map +1 -1
  12. package/dist/components/Avatar/avatar.d.ts +3 -2
  13. package/dist/components/Avatar/avatar.d.ts.map +1 -1
  14. package/dist/components/Avatar/avatar.js +7 -1
  15. package/dist/components/Avatar/avatar.js.map +1 -1
  16. package/dist/components/Badge/badge.d.ts.map +1 -1
  17. package/dist/components/Badge/badge.js +2 -1
  18. package/dist/components/Badge/badge.js.map +1 -1
  19. package/dist/components/Breadcrumb/breadcrumb.d.ts +14 -1
  20. package/dist/components/Breadcrumb/breadcrumb.d.ts.map +1 -1
  21. package/dist/components/Breadcrumb/breadcrumb.js +9 -3
  22. package/dist/components/Breadcrumb/breadcrumb.js.map +1 -1
  23. package/dist/components/BulkActionBar/bulk-action-bar.d.ts +1 -1
  24. package/dist/components/BulkActionBar/bulk-action-bar.d.ts.map +1 -1
  25. package/dist/components/BulkActionBar/bulk-action-bar.js.map +1 -1
  26. package/dist/components/Button/button.d.ts +3 -3
  27. package/dist/components/Button/button.d.ts.map +1 -1
  28. package/dist/components/Button/button.js +68 -42
  29. package/dist/components/Button/button.js.map +1 -1
  30. package/dist/components/Button/index.d.ts +1 -0
  31. package/dist/components/Button/index.d.ts.map +1 -1
  32. package/dist/components/Button/index.js +3 -0
  33. package/dist/components/Button/index.js.map +1 -1
  34. package/dist/components/Carousel/carousel.d.ts.map +1 -1
  35. package/dist/components/Carousel/carousel.js.map +1 -1
  36. package/dist/components/Chart/chart.d.ts.map +1 -1
  37. package/dist/components/Chart/chart.js +1 -1
  38. package/dist/components/Chart/chart.js.map +1 -1
  39. package/dist/components/Checkbox/checkbox.d.ts +5 -2
  40. package/dist/components/Checkbox/checkbox.d.ts.map +1 -1
  41. package/dist/components/Checkbox/checkbox.js +24 -3
  42. package/dist/components/Checkbox/checkbox.js.map +1 -1
  43. package/dist/components/Checkbox/index.d.ts +1 -0
  44. package/dist/components/Checkbox/index.d.ts.map +1 -1
  45. package/dist/components/Checkbox/index.js +3 -0
  46. package/dist/components/Checkbox/index.js.map +1 -1
  47. package/dist/components/Chip/chip.d.ts +7 -1
  48. package/dist/components/Chip/chip.d.ts.map +1 -1
  49. package/dist/components/Chip/chip.js +5 -1
  50. package/dist/components/Chip/chip.js.map +1 -1
  51. package/dist/components/Coachmark/coachmark.d.ts +5 -3
  52. package/dist/components/Coachmark/coachmark.d.ts.map +1 -1
  53. package/dist/components/Coachmark/coachmark.js +4 -1
  54. package/dist/components/Coachmark/coachmark.js.map +1 -1
  55. package/dist/components/Combobox/combobox.d.ts +2 -0
  56. package/dist/components/Combobox/combobox.d.ts.map +1 -1
  57. package/dist/components/Combobox/combobox.js +7 -6
  58. package/dist/components/Combobox/combobox.js.map +1 -1
  59. package/dist/components/Command/command.d.ts +1 -1
  60. package/dist/components/Command/command.js +2 -1
  61. package/dist/components/Command/command.js.map +1 -1
  62. package/dist/components/DataTable/active-editor-controller.d.ts +4 -3
  63. package/dist/components/DataTable/active-editor-controller.d.ts.map +1 -1
  64. package/dist/components/DataTable/cell-registry.d.ts.map +1 -1
  65. package/dist/components/DataTable/cell-registry.js +3 -0
  66. package/dist/components/DataTable/cell-registry.js.map +1 -1
  67. package/dist/components/DataTable/data-table-filter-panel.d.ts.map +1 -1
  68. package/dist/components/DataTable/data-table-interaction-layer.d.ts +4 -3
  69. package/dist/components/DataTable/data-table-interaction-layer.d.ts.map +1 -1
  70. package/dist/components/DataTable/data-table-interaction-layer.js.map +1 -1
  71. package/dist/components/DataTable/data-table.d.ts +9 -6
  72. package/dist/components/DataTable/data-table.d.ts.map +1 -1
  73. package/dist/components/DataTable/data-table.js +142 -50
  74. package/dist/components/DataTable/data-table.js.map +1 -1
  75. package/dist/components/DataTable/filter-tree.d.ts +5 -0
  76. package/dist/components/DataTable/filter-tree.d.ts.map +1 -1
  77. package/dist/components/DateGrid/date-grid.d.ts +1 -1
  78. package/dist/components/DateGrid/date-grid.d.ts.map +1 -1
  79. package/dist/components/DateGrid/date-grid.js +6 -4
  80. package/dist/components/DateGrid/date-grid.js.map +1 -1
  81. package/dist/components/DatePicker/date-picker.d.ts.map +1 -1
  82. package/dist/components/DatePicker/date-picker.js +124 -106
  83. package/dist/components/DatePicker/date-picker.js.map +1 -1
  84. package/dist/components/Dialog/dialog.d.ts +1 -1
  85. package/dist/components/Dialog/dialog.d.ts.map +1 -1
  86. package/dist/components/Dialog/dialog.js +2 -1
  87. package/dist/components/Dialog/dialog.js.map +1 -1
  88. package/dist/components/DropdownMenu/dropdown-menu.d.ts +1 -1
  89. package/dist/components/DropdownMenu/dropdown-menu.d.ts.map +1 -1
  90. package/dist/components/DropdownMenu/dropdown-menu.js +11 -4
  91. package/dist/components/DropdownMenu/dropdown-menu.js.map +1 -1
  92. package/dist/components/Field/field-wrapper.d.ts +1 -0
  93. package/dist/components/Field/field-wrapper.d.ts.map +1 -1
  94. package/dist/components/Field/field-wrapper.js +27 -7
  95. package/dist/components/Field/field-wrapper.js.map +1 -1
  96. package/dist/components/Field/use-form-validation.d.ts +5 -1
  97. package/dist/components/Field/use-form-validation.d.ts.map +1 -1
  98. package/dist/components/Field/use-form-validation.js +26 -14
  99. package/dist/components/Field/use-form-validation.js.map +1 -1
  100. package/dist/components/FieldControlGroup/field-control-group.d.ts +3 -3
  101. package/dist/components/FieldControlGroup/field-control-group.js.map +1 -1
  102. package/dist/components/FileItem/file-item.d.ts.map +1 -1
  103. package/dist/components/FileItem/file-item.js +3 -2
  104. package/dist/components/FileItem/file-item.js.map +1 -1
  105. package/dist/components/FileUpload/file-upload.d.ts.map +1 -1
  106. package/dist/components/FileUpload/file-upload.js +12 -6
  107. package/dist/components/FileUpload/file-upload.js.map +1 -1
  108. package/dist/components/FileViewer/file-viewer.d.ts.map +1 -1
  109. package/dist/components/FileViewer/file-viewer.js +4 -3
  110. package/dist/components/FileViewer/file-viewer.js.map +1 -1
  111. package/dist/components/Input/input.d.ts.map +1 -1
  112. package/dist/components/Input/input.js +1 -5
  113. package/dist/components/Input/input.js.map +1 -1
  114. package/dist/components/LinkInput/link-input.d.ts +2 -2
  115. package/dist/components/LinkInput/link-input.d.ts.map +1 -1
  116. package/dist/components/LinkInput/link-input.js +29 -28
  117. package/dist/components/LinkInput/link-input.js.map +1 -1
  118. package/dist/components/Menu/menu-item.d.ts.map +1 -1
  119. package/dist/components/Menu/menu-item.js +1 -1
  120. package/dist/components/Menu/menu-item.js.map +1 -1
  121. package/dist/components/Notice/notice.d.ts +5 -0
  122. package/dist/components/Notice/notice.d.ts.map +1 -1
  123. package/dist/components/Notice/notice.js +11 -5
  124. package/dist/components/Notice/notice.js.map +1 -1
  125. package/dist/components/NumberInput/number-input.d.ts.map +1 -1
  126. package/dist/components/NumberInput/number-input.js +15 -7
  127. package/dist/components/NumberInput/number-input.js.map +1 -1
  128. package/dist/components/OverflowIndicator/overflow-indicator.d.ts +1 -1
  129. package/dist/components/OverflowIndicator/overflow-indicator.d.ts.map +1 -1
  130. package/dist/components/OverflowIndicator/overflow-indicator.js +2 -1
  131. package/dist/components/OverflowIndicator/overflow-indicator.js.map +1 -1
  132. package/dist/components/PeoplePicker/people-picker.d.ts +2 -2
  133. package/dist/components/PeoplePicker/people-picker.d.ts.map +1 -1
  134. package/dist/components/PeoplePicker/people-picker.js +36 -35
  135. package/dist/components/PeoplePicker/people-picker.js.map +1 -1
  136. package/dist/components/PeoplePicker/person-display.d.ts.map +1 -1
  137. package/dist/components/PeoplePicker/person-display.js +2 -2
  138. package/dist/components/PeoplePicker/person-display.js.map +1 -1
  139. package/dist/components/Popover/popover.js.map +1 -1
  140. package/dist/components/ProfileCard/profile-card.d.ts +5 -2
  141. package/dist/components/ProfileCard/profile-card.d.ts.map +1 -1
  142. package/dist/components/ProfileCard/profile-card.js.map +1 -1
  143. package/dist/components/RadioGroup/radio-group.d.ts +1 -1
  144. package/dist/components/RadioGroup/radio-group.d.ts.map +1 -1
  145. package/dist/components/RadioGroup/radio-group.js +27 -3
  146. package/dist/components/RadioGroup/radio-group.js.map +1 -1
  147. package/dist/components/Rating/rating.js +3 -3
  148. package/dist/components/Rating/rating.js.map +1 -1
  149. package/dist/components/ScrollArea/scroll-area.d.ts +1 -1
  150. package/dist/components/ScrollArea/scroll-area.js +2 -2
  151. package/dist/components/ScrollArea/scroll-area.js.map +1 -1
  152. package/dist/components/SegmentedControl/segmented-control.d.ts +2 -2
  153. package/dist/components/SegmentedControl/segmented-control.d.ts.map +1 -1
  154. package/dist/components/SegmentedControl/segmented-control.js +10 -5
  155. package/dist/components/SegmentedControl/segmented-control.js.map +1 -1
  156. package/dist/components/Select/select.d.ts +9 -3
  157. package/dist/components/Select/select.d.ts.map +1 -1
  158. package/dist/components/Select/select.js +45 -13
  159. package/dist/components/Select/select.js.map +1 -1
  160. package/dist/components/SelectMenu/index.js +2 -1
  161. package/dist/components/SelectMenu/select-menu.d.ts +17 -4
  162. package/dist/components/SelectMenu/select-menu.d.ts.map +1 -1
  163. package/dist/components/SelectMenu/select-menu.js +39 -6
  164. package/dist/components/SelectMenu/select-menu.js.map +1 -1
  165. package/dist/components/Separator/separator.js +1 -1
  166. package/dist/components/Separator/separator.js.map +1 -1
  167. package/dist/components/Sheet/sheet.d.ts +1 -1
  168. package/dist/components/Sheet/sheet.d.ts.map +1 -1
  169. package/dist/components/Sheet/sheet.js +2 -1
  170. package/dist/components/Sheet/sheet.js.map +1 -1
  171. package/dist/components/Sidebar/sidebar.d.ts.map +1 -1
  172. package/dist/components/Sidebar/sidebar.js +23 -8
  173. package/dist/components/Sidebar/sidebar.js.map +1 -1
  174. package/dist/components/Skeleton/skeleton.d.ts +1 -1
  175. package/dist/components/Skeleton/skeleton.js +2 -2
  176. package/dist/components/Skeleton/skeleton.js.map +1 -1
  177. package/dist/components/Steps/steps.d.ts.map +1 -1
  178. package/dist/components/Steps/steps.js +6 -3
  179. package/dist/components/Steps/steps.js.map +1 -1
  180. package/dist/components/Switch/switch.d.ts +2 -2
  181. package/dist/components/Switch/switch.d.ts.map +1 -1
  182. package/dist/components/Switch/switch.js +38 -8
  183. package/dist/components/Switch/switch.js.map +1 -1
  184. package/dist/components/Tabs/tabs.d.ts +5 -2
  185. package/dist/components/Tabs/tabs.d.ts.map +1 -1
  186. package/dist/components/Tabs/tabs.js +16 -24
  187. package/dist/components/Tabs/tabs.js.map +1 -1
  188. package/dist/components/Tag/tag.d.ts.map +1 -1
  189. package/dist/components/Tag/tag.js +7 -3
  190. package/dist/components/Tag/tag.js.map +1 -1
  191. package/dist/components/Textarea/textarea.d.ts +2 -1
  192. package/dist/components/Textarea/textarea.d.ts.map +1 -1
  193. package/dist/components/Textarea/textarea.js +20 -12
  194. package/dist/components/Textarea/textarea.js.map +1 -1
  195. package/dist/components/TimePicker/time-picker.d.ts +2 -2
  196. package/dist/components/TimePicker/time-picker.d.ts.map +1 -1
  197. package/dist/components/TimePicker/time-picker.js +5 -7
  198. package/dist/components/TimePicker/time-picker.js.map +1 -1
  199. package/dist/components/Toast/toast.d.ts +1 -1
  200. package/dist/components/Toast/toast.d.ts.map +1 -1
  201. package/dist/components/Toast/toast.js +3 -2
  202. package/dist/components/Toast/toast.js.map +1 -1
  203. package/dist/components/Tooltip/tooltip.js +1 -1
  204. package/dist/components/Tooltip/tooltip.js.map +1 -1
  205. package/dist/components/TreeView/tree-view.d.ts.map +1 -1
  206. package/dist/components/TreeView/tree-view.js +3 -2
  207. package/dist/components/TreeView/tree-view.js.map +1 -1
  208. package/dist/index.d.ts +1 -0
  209. package/dist/index.d.ts.map +1 -1
  210. package/dist/index.js +11 -0
  211. package/dist/index.js.map +1 -1
  212. package/dist/patterns/element-anatomy/item-anatomy.d.ts +1 -1
  213. package/dist/patterns/element-anatomy/item-anatomy.js.map +1 -1
  214. package/dist/patterns/overlay-surface/overlay-surface.js.map +1 -1
  215. package/dist/tokens/categorical-color.d.ts +1 -1
  216. package/dist/tokens/categorical-color.js.map +1 -1
  217. package/dist/tokens/uiSize/icon-size.d.ts +2 -2
  218. package/ds-canonical/fork/governance.lock +1 -1
  219. package/ds-canonical/fork/hooks/check_opacity_token_usage.sh +10 -1
  220. package/ds-canonical/hooks/check_audit_post_report_validator.sh +13 -0
  221. package/ds-canonical/hooks/check_full_story_visual_interaction_sweep.sh +4 -3
  222. package/ds-canonical/hooks/check_opacity_token_usage.sh +10 -1
  223. package/ds-canonical/hooks/check_plugin_fork_health.sh +30 -22
  224. package/ds-canonical/hooks/check_story_invariants.sh +17 -5
  225. package/ds-canonical/hooks/check_substantive_edit_approval_preflight.sh +5 -2
  226. package/ds-canonical/references/principle-dim-map.json +100 -0
  227. package/ds-canonical/references/story-baseline-registry.json +4 -3
  228. package/ds-canonical/skills/deep-audit-cross-codex/SKILL.md +1 -1
  229. package/ds-canonical/skills/design-system-audit/SKILL.md +2 -2
  230. package/ds-story-manifest.json +16 -16
  231. package/llms-full.txt +6 -1
  232. package/llms.txt +2 -1
  233. package/package.json +1 -1
  234. package/src/components/Alert/alert.anatomy.stories.tsx +9 -6
  235. package/src/components/Alert/alert.spec.md +3 -3
  236. package/src/components/Alert/alert.tsx +9 -1
  237. package/src/components/AppShell/app-shell.anatomy.stories.tsx +3 -0
  238. package/src/components/AppShell/app-shell.spec.md +3 -2
  239. package/src/components/AppShell/app-shell.tsx +1 -1
  240. package/src/components/AspectRatio/aspect-ratio.spec.md +1 -2
  241. package/src/components/AspectRatio/aspect-ratio.tsx +3 -2
  242. package/src/components/Avatar/avatar.principles.stories.tsx +1 -1
  243. package/src/components/Avatar/avatar.spec.md +1 -1
  244. package/src/components/Avatar/avatar.stories.tsx +1 -1
  245. package/src/components/Avatar/avatar.tsx +17 -9
  246. package/src/components/Badge/badge.anatomy.stories.tsx +2 -2
  247. package/src/components/Badge/badge.spec.md +4 -4
  248. package/src/components/Badge/badge.tsx +4 -1
  249. package/src/components/Breadcrumb/breadcrumb.anatomy.stories.tsx +2 -2
  250. package/src/components/Breadcrumb/breadcrumb.principles.stories.tsx +15 -10
  251. package/src/components/Breadcrumb/breadcrumb.spec.md +4 -2
  252. package/src/components/Breadcrumb/breadcrumb.tsx +10 -4
  253. package/src/components/BulkActionBar/bulk-action-bar.anatomy.stories.tsx +2 -1
  254. package/src/components/BulkActionBar/bulk-action-bar.spec.md +15 -14
  255. package/src/components/BulkActionBar/bulk-action-bar.tsx +2 -2
  256. package/src/components/Button/button.anatomy.stories.tsx +6 -6
  257. package/src/components/Button/button.spec.md +2 -2
  258. package/src/components/Button/button.tsx +63 -26
  259. package/src/components/Button/index.ts +1 -0
  260. package/src/components/Calendar/calendar.anatomy.stories.tsx +2 -2
  261. package/src/components/Calendar/calendar.spec.md +7 -1
  262. package/src/components/Carousel/carousel.principles.stories.tsx +1 -1
  263. package/src/components/Carousel/carousel.tsx +1 -0
  264. package/src/components/Chart/chart.spec.md +1 -1
  265. package/src/components/Chart/chart.tsx +2 -1
  266. package/src/components/Checkbox/checkbox.principles.stories.tsx +1 -1
  267. package/src/components/Checkbox/checkbox.spec.md +1 -1
  268. package/src/components/Checkbox/checkbox.tsx +39 -6
  269. package/src/components/Checkbox/index.ts +1 -0
  270. package/src/components/Chip/chip.anatomy.stories.tsx +1 -1
  271. package/src/components/Chip/chip.spec.md +1 -1
  272. package/src/components/Chip/chip.tsx +4 -4
  273. package/src/components/CircularProgress/circular-progress.anatomy.stories.tsx +1 -1
  274. package/src/components/CircularProgress/circular-progress.principles.stories.tsx +7 -5
  275. package/src/components/CircularProgress/circular-progress.spec.md +2 -0
  276. package/src/components/CircularProgress/circular-progress.stories.tsx +3 -11
  277. package/src/components/Coachmark/coachmark.anatomy.stories.tsx +2 -2
  278. package/src/components/Coachmark/coachmark.principles.stories.tsx +1 -1
  279. package/src/components/Coachmark/coachmark.spec.md +3 -3
  280. package/src/components/Coachmark/coachmark.tsx +13 -5
  281. package/src/components/Combobox/combobox.anatomy.stories.tsx +4 -4
  282. package/src/components/Combobox/combobox.spec.md +1 -1
  283. package/src/components/Combobox/combobox.tsx +23 -16
  284. package/src/components/Command/command.principles.stories.tsx +10 -12
  285. package/src/components/Command/command.spec.md +5 -5
  286. package/src/components/Command/command.stories.tsx +4 -4
  287. package/src/components/Command/command.tsx +1 -1
  288. package/src/components/DataTable/active-editor-controller.ts +4 -3
  289. package/src/components/DataTable/cell-registry.tsx +8 -0
  290. package/src/components/DataTable/data-table-filter-panel.tsx +8 -2
  291. package/src/components/DataTable/data-table-interaction-layer.tsx +8 -7
  292. package/src/components/DataTable/data-table.anatomy.stories.tsx +4 -4
  293. package/src/components/DataTable/data-table.css +2 -2
  294. package/src/components/DataTable/data-table.spec.md +25 -25
  295. package/src/components/DataTable/data-table.stories.tsx +4 -3
  296. package/src/components/DataTable/data-table.tsx +203 -52
  297. package/src/components/DataTable/filter-operators.spec.md +4 -4
  298. package/src/components/DataTable/filter-tree.ts +32 -9
  299. package/src/components/DateGrid/date-grid.anatomy.stories.tsx +1 -1
  300. package/src/components/DateGrid/date-grid.spec.md +6 -4
  301. package/src/components/DateGrid/date-grid.tsx +7 -5
  302. package/src/components/DatePicker/date-picker.principles.stories.tsx +1 -1
  303. package/src/components/DatePicker/date-picker.spec.md +1 -0
  304. package/src/components/DatePicker/date-picker.stories.tsx +19 -0
  305. package/src/components/DatePicker/date-picker.tsx +33 -12
  306. package/src/components/DescriptionList/description-list.spec.md +1 -1
  307. package/src/components/Dialog/dialog.spec.md +1 -1
  308. package/src/components/Dialog/dialog.tsx +2 -1
  309. package/src/components/DropdownMenu/dropdown-menu.anatomy.stories.tsx +3 -1
  310. package/src/components/DropdownMenu/dropdown-menu.spec.md +4 -2
  311. package/src/components/DropdownMenu/dropdown-menu.tsx +11 -3
  312. package/src/components/Empty/empty.anatomy.stories.tsx +1 -1
  313. package/src/components/Empty/empty.spec.md +2 -1
  314. package/src/components/Empty/empty.stories.tsx +4 -15
  315. package/src/components/Field/field-controls.spec.md +1 -0
  316. package/src/components/Field/field-wrapper.tsx +28 -8
  317. package/src/components/Field/field.anatomy.stories.tsx +7 -7
  318. package/src/components/Field/field.principles.stories.tsx +1 -1
  319. package/src/components/Field/field.spec.md +9 -7
  320. package/src/components/Field/form-validation.spec.md +5 -2
  321. package/src/components/Field/use-form-validation.ts +38 -14
  322. package/src/components/FieldControlGroup/field-control-group.anatomy.stories.tsx +11 -10
  323. package/src/components/FieldControlGroup/field-control-group.principles.stories.tsx +3 -3
  324. package/src/components/FieldControlGroup/field-control-group.spec.md +3 -3
  325. package/src/components/FieldControlGroup/field-control-group.tsx +3 -3
  326. package/src/components/FileItem/file-item.anatomy.stories.tsx +6 -4
  327. package/src/components/FileItem/file-item.tsx +3 -2
  328. package/src/components/FileUpload/file-upload.anatomy.stories.tsx +1 -1
  329. package/src/components/FileUpload/file-upload.spec.md +3 -0
  330. package/src/components/FileUpload/file-upload.tsx +21 -6
  331. package/src/components/FileViewer/file-viewer.anatomy.stories.tsx +6 -4
  332. package/src/components/FileViewer/file-viewer.spec.md +2 -2
  333. package/src/components/FileViewer/file-viewer.tsx +4 -3
  334. package/src/components/Input/input.anatomy.stories.tsx +4 -4
  335. package/src/components/Input/input.principles.stories.tsx +1 -1
  336. package/src/components/Input/input.spec.md +1 -1
  337. package/src/components/Input/input.stories.tsx +11 -5
  338. package/src/components/Input/input.tsx +1 -5
  339. package/src/components/LinkInput/link-input.anatomy.stories.tsx +2 -0
  340. package/src/components/LinkInput/link-input.tsx +45 -33
  341. package/src/components/Menu/menu-item.spec.md +2 -1
  342. package/src/components/Menu/menu-item.tsx +3 -1
  343. package/src/components/Notice/notice.anatomy.stories.tsx +10 -5
  344. package/src/components/Notice/notice.spec.md +4 -3
  345. package/src/components/Notice/notice.tsx +22 -5
  346. package/src/components/NumberInput/number-input.anatomy.stories.tsx +5 -4
  347. package/src/components/NumberInput/number-input.principles.stories.tsx +2 -1
  348. package/src/components/NumberInput/number-input.spec.md +2 -1
  349. package/src/components/NumberInput/number-input.tsx +23 -8
  350. package/src/components/OverflowIndicator/overflow-indicator.anatomy.stories.tsx +3 -3
  351. package/src/components/OverflowIndicator/overflow-indicator.spec.md +1 -1
  352. package/src/components/OverflowIndicator/overflow-indicator.tsx +2 -1
  353. package/src/components/PeoplePicker/people-picker.anatomy.stories.tsx +1 -1
  354. package/src/components/PeoplePicker/people-picker.spec.md +4 -4
  355. package/src/components/PeoplePicker/people-picker.stories.tsx +1 -1
  356. package/src/components/PeoplePicker/people-picker.tsx +63 -54
  357. package/src/components/PeoplePicker/person-display.tsx +5 -3
  358. package/src/components/Popover/popover.anatomy.stories.tsx +1 -1
  359. package/src/components/Popover/popover.spec.md +11 -13
  360. package/src/components/Popover/popover.tsx +2 -2
  361. package/src/components/ProfileCard/profile-card.anatomy.stories.tsx +4 -4
  362. package/src/components/ProfileCard/profile-card.principles.stories.tsx +2 -2
  363. package/src/components/ProfileCard/profile-card.spec.md +1 -2
  364. package/src/components/ProfileCard/profile-card.stories.tsx +1 -1
  365. package/src/components/ProfileCard/profile-card.tsx +5 -2
  366. package/src/components/RadioGroup/radio-group.principles.stories.tsx +1 -1
  367. package/src/components/RadioGroup/radio-group.spec.md +2 -2
  368. package/src/components/RadioGroup/radio-group.stories.tsx +3 -1
  369. package/src/components/RadioGroup/radio-group.tsx +36 -3
  370. package/src/components/Rating/rating.anatomy.stories.tsx +11 -8
  371. package/src/components/Rating/rating.principles.stories.tsx +1 -1
  372. package/src/components/Rating/rating.spec.md +2 -2
  373. package/src/components/Rating/rating.stories.tsx +1 -1
  374. package/src/components/Rating/rating.tsx +3 -3
  375. package/src/components/ScrollArea/scroll-area.spec.md +1 -0
  376. package/src/components/ScrollArea/scroll-area.tsx +1 -1
  377. package/src/components/SegmentedControl/segmented-control.anatomy.stories.tsx +2 -2
  378. package/src/components/SegmentedControl/segmented-control.spec.md +1 -1
  379. package/src/components/SegmentedControl/segmented-control.tsx +16 -6
  380. package/src/components/Select/select.anatomy.stories.tsx +2 -2
  381. package/src/components/Select/select.spec.md +2 -1
  382. package/src/components/Select/select.tsx +37 -16
  383. package/src/components/SelectMenu/select-menu.anatomy.stories.tsx +1 -1
  384. package/src/components/SelectMenu/select-menu.principles.stories.tsx +1 -1
  385. package/src/components/SelectMenu/select-menu.spec.md +11 -8
  386. package/src/components/SelectMenu/select-menu.tsx +92 -10
  387. package/src/components/SelectionControl/selection-item.anatomy.stories.tsx +1 -1
  388. package/src/components/SelectionControl/selection-item.principles.stories.tsx +2 -2
  389. package/src/components/SelectionControl/selection-item.spec.md +2 -1
  390. package/src/components/SelectionControl/selection-item.stories.tsx +1 -1
  391. package/src/components/Separator/separator.anatomy.stories.tsx +2 -2
  392. package/src/components/Separator/separator.tsx +1 -1
  393. package/src/components/Sheet/sheet.spec.md +1 -1
  394. package/src/components/Sheet/sheet.tsx +2 -1
  395. package/src/components/Sidebar/sidebar.anatomy.stories.tsx +1 -1
  396. package/src/components/Sidebar/sidebar.spec.md +2 -1
  397. package/src/components/Sidebar/sidebar.stories.tsx +6 -4
  398. package/src/components/Sidebar/sidebar.tsx +36 -11
  399. package/src/components/Skeleton/skeleton.principles.stories.tsx +1 -1
  400. package/src/components/Skeleton/skeleton.spec.md +1 -2
  401. package/src/components/Skeleton/skeleton.tsx +1 -1
  402. package/src/components/Slider/slider.spec.md +4 -4
  403. package/src/components/Steps/steps.anatomy.stories.tsx +1 -1
  404. package/src/components/Steps/steps.spec.md +5 -4
  405. package/src/components/Steps/steps.tsx +11 -3
  406. package/src/components/Switch/switch.spec.md +3 -3
  407. package/src/components/Switch/switch.tsx +51 -10
  408. package/src/components/Tabs/tabs.anatomy.stories.tsx +3 -2
  409. package/src/components/Tabs/tabs.principles.stories.tsx +5 -3
  410. package/src/components/Tabs/tabs.spec.md +2 -1
  411. package/src/components/Tabs/tabs.tsx +32 -25
  412. package/src/components/Tag/tag.principles.stories.tsx +1 -1
  413. package/src/components/Tag/tag.tsx +7 -3
  414. package/src/components/Textarea/textarea.spec.md +2 -1
  415. package/src/components/Textarea/textarea.tsx +16 -9
  416. package/src/components/TimePicker/time-picker.anatomy.stories.tsx +2 -1
  417. package/src/components/TimePicker/time-picker.spec.md +2 -2
  418. package/src/components/TimePicker/time-picker.tsx +6 -8
  419. package/src/components/Toast/toast.anatomy.stories.tsx +5 -6
  420. package/src/components/Toast/toast.principles.stories.tsx +10 -11
  421. package/src/components/Toast/toast.spec.md +5 -5
  422. package/src/components/Toast/toast.stories.tsx +5 -1
  423. package/src/components/Toast/toast.tsx +9 -6
  424. package/src/components/Tooltip/tooltip.spec.md +3 -3
  425. package/src/components/Tooltip/tooltip.tsx +1 -1
  426. package/src/components/TreeView/tree-view.anatomy.stories.tsx +6 -1
  427. package/src/components/TreeView/tree-view.principles.stories.tsx +2 -2
  428. package/src/components/TreeView/tree-view.spec.md +1 -1
  429. package/src/components/TreeView/tree-view.tsx +17 -2
  430. package/src/index.ts +1 -1
  431. package/src/patterns/element-anatomy/element-anatomy.spec.md +3 -3
  432. package/src/patterns/element-anatomy/item-anatomy.spec.md +4 -12
  433. package/src/patterns/element-anatomy/item-anatomy.stories.tsx +1 -0
  434. package/src/patterns/element-anatomy/item-anatomy.tsx +1 -1
  435. package/src/patterns/header-canonical/header-canonical.css +1 -2
  436. package/src/patterns/header-canonical/header-canonical.spec.md +5 -5
  437. package/src/patterns/horizontal-overflow/horizontal-overflow.spec.md +5 -4
  438. package/src/patterns/overlay-surface/overlay-surface.spec.md +8 -7
  439. package/src/patterns/overlay-surface/overlay-surface.tsx +5 -5
  440. package/src/patterns/resize-handle/resize-handle.spec.md +1 -1
  441. package/src/tokens/categorical-color.ts +1 -1
  442. package/src/tokens/color/color.spec.md +6 -6
  443. package/src/tokens/color/color.stories.tsx +1 -1
  444. package/src/tokens/color/primitives.css +7 -5
  445. package/src/tokens/density/density.spec.md +2 -0
  446. package/src/tokens/elevation/elevation.spec.md +1 -1
  447. package/src/tokens/layoutSpace/layoutSpace.spec.md +1 -1
  448. package/src/tokens/layoutSpace/layoutSpace.stories.tsx +3 -3
  449. package/src/tokens/motion/motion.spec.md +1 -1
  450. package/src/tokens/opacity/opacity.stories.tsx +7 -11
  451. package/src/tokens/radius/radius.spec.md +1 -1
  452. package/src/tokens/typography/typography.css +1 -1
  453. package/src/tokens/typography/typography.spec.md +1 -1
  454. package/src/tokens/uiSize/icon-size.ts +2 -2
  455. package/src/tokens/uiSize/uiSize.spec.md +12 -11
  456. package/src/tokens/utility-registry.json +5 -4
  457. package/dist/node_modules/react-hook-form/dist/index.esm.js +0 -1950
  458. package/dist/node_modules/react-hook-form/dist/index.esm.js.map +0 -1
@@ -63,7 +63,7 @@ sm / md 跟 Tag 同階(20/24px),lg 對齊 md(尺寸需求一致,不需
63
63
 
64
64
  **字級依 shape 分流**:`circle` shape 走元件內建的 `triggerText` map(sm 10px / md·lg 12px);`tag` shape 直接套 `tagVariants({ size })`(sm 12px / md·lg 14px,見 `tag.spec.md`),刻意對齊 Tag 視覺一致(見上方「trigger 不用 Tag 元件」段)。下方「sm 用 `text-[10px]`」的理由僅適用於 circle shape。
65
65
 
66
- **sm 用 `text-[10px]` 的理由**:sub-footnote 特殊例外,與 Badge 共享「micro-indicator typography」tier——trigger 數字是次要輔助 indicator(非主訊息),在 20px 小容器內 12px 會擠。完整理由與 pattern-family 說明詳見 `badge.spec.md`「字體例外:`text-[10px]`」。若未來 OverflowIndicator / Badge 以外的元件也需要 sub-footnote 尺寸,應在 typography system `--font-micro` token,再改用 token 而非 arbitrary value。
66
+ **sm 用 `text-[10px]` 的理由**:micro-indicator 特殊例外(10px,與 `--font-footnote` 同值但不消費其 utility——lh 1.3 vs 需要的緊湊行高),與 Badge 共享「micro-indicator typography」tier——trigger 數字是次要輔助 indicator(非主訊息),在 20px 小容器內 caption 12px 會擠。完整理由與 pattern-family 說明詳見 `badge.spec.md`「字體例外:`text-[10px]`」。若未來 OverflowIndicator / Badge 以外的元件也需要這個 10px micro-indicator 尺寸,先依 `badge.spec.md`「其他元件不得比照」歸同一 family 討論,確立第三個消費者才升 typography token,而非直接抄 arbitrary value。
67
67
 
68
68
  ---
69
69
 
@@ -158,7 +158,8 @@ export const overflowIndicatorMeta = {
158
158
  sizes: {
159
159
 
160
160
  },
161
- states: ['default', 'hover', 'active', 'focus-visible', 'disabled'],
161
+ // 2026-07-04 audit 對齊(spec:95「本身無 hover / active / disabled / selected 變化」;code 有 focus-visible ring tsx:106/121)
162
+ states: ['default', 'focus-visible'],
162
163
  tokens: {
163
164
  bg: ['bg-muted'],
164
165
  fg: ['text-foreground'],
@@ -252,7 +252,7 @@ export const ColorMatrix: Story = {
252
252
  <tr>
253
253
  <Td mono>disabled</Td>
254
254
  <Td><TokenCell token="--bg-disabled" display="bg-disabled" /></Td>
255
- <Td><TokenCell token="--border" /></Td>
255
+ <Td>—(border transparent)</Td>
256
256
  <Td><TokenCell token="--fg-disabled" display="fg-disabled" /></Td>
257
257
  </tr>
258
258
  <tr>
@@ -88,7 +88,7 @@ PeoplePicker 永遠支援搜尋(內部使用 `Command` / cmdk)——因為
88
88
  | 資料類型 | **人員**(含 Avatar) | 任意(純文字)| 任意(純文字)|
89
89
  | 單選 / 多選 | 兩者皆支援(value 類型決定)| 單選 | 多選 |
90
90
  | 視覺 prefix | Avatar(必備) | 可選 startIcon | 無 |
91
- | 底層實作 | Popover + Command + SelectMenu | 桌機自建 listbox(`role="combobox"` div + cmdk SelectMenu,`select.tsx:570`)/ 觸控 native `<select>`(`select.tsx` NativeSelect) | 桌機自建 listbox(`role="combobox"` div + cmdk SelectMenu + Tag overlay,`combobox.tsx:699`)/ 觸控 native `<select>`(`combobox.tsx` NativeCombobox) |
91
+ | 底層實作 | Popover + Command + SelectMenu | 桌機自建 listbox(`role="combobox"` div + cmdk SelectMenu,`select.tsx:582`)/ 觸控 native `<select>`(`select.tsx` NativeSelect) | 桌機自建 listbox(`role="combobox"` div + cmdk SelectMenu + Tag overlay,`combobox.tsx:728`)/ 觸控 native `<select>`(`combobox.tsx` NativeCombobox) |
92
92
  | 搜尋 | 永遠啟用(人名本質);single = inline-trigger(wrap `<Select searchable>`),multi 預設 panel-top(`searchIn='menu'`,`searchIn='trigger'` opt-in)— 對齊本 spec L91-92 SSOT 表 | 可選 `searchable`(單選短列表 → inline-trigger only)| 可選 `searchable` + `searchIn='menu' \| 'trigger'`(多選 → 兩種模式)|
93
93
 
94
94
  **搜尋型態 SSOT canonical**(A1-A5 spec,2026-05-11;2026-05-12 補 PeoplePicker multi `searchIn` opt-in):
@@ -147,7 +147,7 @@ PeoplePicker 永遠支援搜尋(內部使用 `Command` / cmdk)——因為
147
147
 
148
148
  | 共享項 | SSOT owner |
149
149
  |---|---|
150
- | Avatar 左 inset 距 trigger.left = 12px 固定(不隨 size/density 漂移)| 本 spec L94 + `people-picker.tsx:362` `!px-[var(--field-px)]` inject(form context)/ `!px-[var(--table-cell-px)]`(naked variant table cell)|
150
+ | Avatar 左 inset 距 trigger.left = 12px 固定(不隨 size/density 漂移)| 本 spec L175「Avatar 左 inset SSOT」段 + `people-picker.tsx:371` `!px-[var(--field-px)]` inject(form context)/ `!px-[var(--table-cell-px)]`(naked variant table cell)|
151
151
  | Avatar+人名視覺 | `person-display.tsx:137-152` `PersonDisplay`(共享 renderer)|
152
152
  | 人名按空間 ellipsis | `person-display.tsx:150` `truncate flex-1 min-w-0` |
153
153
  | Placeholder ellipsis(inline-search active 時)| `field-wrapper.tsx:244` `bareInputStyles` 含 `truncate` |
@@ -237,7 +237,7 @@ PeoplePicker 是 **composite Field Control**(Field shell + Avatar + SelectMenu),
237
237
 
238
238
  ## shadcn passthrough 例外說明
239
239
 
240
- PeoplePicker 是 **composite 元件**(內部 wrap `<Select>`(single)/ `<Combobox>`(multi)),其主 API 為 declarative(value / onChange / size / mode / options...)。**有套 `forwardRef` + HTML root props spread**(codex P2 fix 2026-05-07 v15.10,`people-picker.tsx:110` / L52 / L130 `...rest`):
240
+ PeoplePicker 是 **composite 元件**(內部 wrap `<Select>`(single)/ `<Combobox>`(multi)),其主 API 為 declarative(value / onChange / size / mode / options...)。**有套 `forwardRef` + HTML root props spread**(codex P2 fix 2026-05-07 v15.10,`people-picker.tsx:111` / L53 / L131 `...rest`):
241
241
 
242
242
  - **forwardRef + 單一 root**:`PeoplePickerProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'onChange'>`,ref 與 `...rest`(`id` / `data-testid` / `onBlur` / `onFocus` / `aria-*` 等 HTML root props)forward 到內部 trigger 容器(readonly/disabled root div、或 wrapped Select / Combobox 的 root),對齊 DS 既有 Combobox / Select 慣例
243
243
  - **`onChange` 走 Omit**:本元件 onChange 用 `(value: PersonValue[]) => void` custom signature,跟 HTMLAttributes onChange 衝突,故 Omit
@@ -251,7 +251,7 @@ PeoplePicker 是 **composite 元件**(內部 wrap `<Select>`(single)/ `<Combobox
251
251
 
252
252
  - **Disabled**:`disabled` → `resolvedMode='disabled'`(`useResolvedFieldMode`),走獨立 static-display 分支(`people-picker.tsx`「readonly / disabled」段)——渲染靜態 `<div>` 包 `MultiPersonDisplay` / `PersonDisplay` + `ItemSuffix` ChevronDown 類型身份 indicator(2026-06-26:disabled 保留 chevron、readonly 不顯示;naked variant 依 `showDisplayEndIcon`),**不** wrap Select / Combobox、無 dismiss X、無 inline-search input。token 走 M24 state precedence(`text-fg-disabled`,含 chevron)。
253
253
  - **Loading(async people fetch)**:目前 PeoplePicker **未暴露 `loading` prop**(`PeoplePickerProps` 無此欄位,內部也未 forward 給 wrapped Select / Combobox)。consumer 若需 loading 態,在 fetch 完成前自行控制 `people=[]`(走 emptyText 空態)。底層 Select / Combobox / SelectMenu 各有自己的 loading 機制,但尚未經 PeoplePicker API 層轉發。
254
- - **Empty(no search results)**:`emptyText` 預設「沒有符合的人員」(本 spec L72 已 codify);無 creatable mode(人員不可建立)。
254
+ - **Empty(no search results)**:`emptyText` 預設「沒有符合的人員」(本 spec L80 已 codify);無 creatable mode(人員不可建立)。
255
255
  - **Empty(no value selected)**:single mode → trigger 顯 placeholder「請選擇人員」;multi mode `value=[]` → trigger 同 placeholder(無 avatar stack 渲);詳「Trigger display SSOT canonical table」B-D 段。
256
256
  - **`people` 清單變動(async fetch 後更新)**:已選 value 顯示不依賴 `people` 查找 — display 路徑直接渲染 value 自帶資料;edit 路徑以人名回查 `people`,查不到降級為純名字 string(initials fallback),不報錯(`people-picker-helpers.ts:74-75`)。選單選項即時跟隨 props 重渲。
257
257
  - **RTL**:未實作方向鏡像(avatar stack overlap `-ml-0.5` / 12px inset 皆以 LTR physical 方向設計);RTL 屬 DS-wide 決策,未定(與 Chip / Breadcrumb 同口徑)。
@@ -150,7 +150,7 @@ const AsyncDirectoryPicker = () => {
150
150
  <div className="flex flex-col gap-2 max-w-xs">
151
151
  <PeoplePicker value={val} people={people} onChange={setVal} aria-label="任務協作者" />
152
152
  <p className="text-caption text-fg-secondary">
153
- Jira 任務「協作者」欄位場景:已選成員隨任務資料先抵達,組織人員名錄約 1.5 秒後才從 API 回來——已選的 avatar 與姓名立即顯示,不等名錄載入。
153
+ Jira 任務「協作者」欄位場景:已選成員隨任務資料先抵達,組織人員名錄約 1.5 秒後才從 API 回來——已選成員立即顯示、不報錯;名錄未到前 avatar 先以姓名縮寫 fallback 呈現,名錄載入後自動補上頭像。
154
154
  </p>
155
155
  </div>
156
156
  )
@@ -1,5 +1,5 @@
1
1
  // @benchmark-unverified-blanket: file-level retraction per M22 (d) — claims herein not individually URL-cited; treat as unverified visual/usage rumor unless retrofit per-claim. Hook escape preserved.
2
- // @placeholder-vocabulary-allow: 1-cycle backward-compat `placeholder` 已加(trigger empty SSOT),`emptyPlaceholder={emptyText}` forward 仍保留讓既有 consumer 不被 silent break;Combobox line 760 `placeholder ?? emptyPlaceholder` fallback → placeholder 永遠 takes precedence。Future cycle 移除 emptyPlaceholder forward(per field-controls.spec.md 共享 contract b)
2
+ // @placeholder-vocabulary-allow: 2026-07-04 Q4 完成 field-controls.spec.md 共享 contract b emptyPlaceholder forward 已移除,emptyText 直達 Combobox SelectMenu emptyText(search-empty 真住所);placeholder trigger empty SSOT
3
3
  // @cell-metric-escape-allow: comment describes RETIRED `tagAreaPaddingLeftPx={8}` magic — current code is surface-guarded (`surface === 'form'` only injects `!px-[var(--field-px)]`; table-cell context untouched, lets naked `!px-[var(--table-cell-px)]` SSOT take over). Hook regex grep'd the comment word, not the live code path. Per (a) fix 2026-05-13 user-approved Path a.
4
4
  import * as React from 'react'
5
5
  import { ChevronDown } from 'lucide-react'
@@ -67,8 +67,8 @@ export interface PeoplePickerProps extends Omit<React.HTMLAttributes<HTMLDivElem
67
67
  placeholder?: string
68
68
  /** 搜尋框 placeholder */
69
69
  searchPlaceholder?: string
70
- /** 搜尋無結果訊息(filtered menu empty)。**僅**用於 SelectMenu noResultsText,
71
- * 不再 silent trigger placeholder(2026-05-12 Issue 4 semantic fix)。 */
70
+ /** 搜尋無結果訊息(filtered menu empty)。**僅**用於 SelectMenu `emptyText`(菜單空狀態,
71
+ * 2026-07-04 Q4 接線完成),不轉 trigger placeholder(2026-05-12 Issue 4 semantic fix)。 */
72
72
  emptyText?: string
73
73
  className?: string
74
74
  disabled?: boolean
@@ -139,6 +139,56 @@ const PeoplePicker = React.forwardRef<HTMLDivElement, PeoplePickerProps>(functio
139
139
  const isMulti = Array.isArray(value)
140
140
  const isEmpty = !value || (isMulti && value.length === 0)
141
141
 
142
+ // 2026-07-05 D3 P0 修:以下派生 + 4 hooks 原宣告在 display/readonly/single/pill 四個 early return
143
+ // 之後 — 同一 mounted instance 的 resolvedMode 於 edit↔display/disabled 切換(<Field mode>/<Field
144
+ // disabled> cascade 正是 runtime 翻轉機制)時 hook 數 7→11 變動 → React #310 crash(與 Combobox
145
+ // beta.76 / LinkInput 2026-07-04 同家族)。全部 hoist 到任何 early return 之前(對齊 select.tsx
146
+ // 「所有 hooks 必在 early return 前 call」canonical);effect 加 mode/stack guard,邏輯不變。
147
+ const selectedNames: string[] = !value
148
+ ? []
149
+ : Array.isArray(value)
150
+ ? value.map(v => resolvePerson(v).name)
151
+ : [resolvePerson(value).name]
152
+
153
+ // SSOT visible count compute via formula primitive + ResizeObserver(multi stack mode 專用)
154
+ const stackContainerRef = React.useRef<HTMLDivElement | null>(null)
155
+ const [stackVisibleCount, setStackVisibleCount] = React.useState<number | undefined>(undefined)
156
+ React.useLayoutEffect(() => {
157
+ if (resolvedMode !== 'edit' || !isMulti || multiDisplay !== 'stack' || selectedNames.length <= 1) {
158
+ setStackVisibleCount(undefined); return
159
+ }
160
+ const root = stackContainerRef.current
161
+ if (!root) return
162
+ // 2026-05-15 ROOT CAUSE FIX(user 抓「之前說的問題都還是存在」):
163
+ // stackContainerRef 透過 mergedStackRef 接 Combobox forwarded ref → root **就是** [role=combobox]
164
+ // div 自己。原 `root.querySelector('[role=combobox]')` 不找 self 永遠 null → trigger=null →
165
+ // tagArea=null → available=0 → setStackVisibleCount(0) → 整 stack 全 overflow → fallback 到
166
+ // Combobox DOM-based useOverflowCount(非 deterministic 那個算法)。修:用 root 自己當 trigger,
167
+ // 從 root 內找 tagArea(flex-1 min-w-0 div)。
168
+ const calc = () => {
169
+ const trigger = root.matches('[role="combobox"]') ? root : root.querySelector<HTMLElement>('[role="combobox"]')
170
+ const tagArea = trigger?.querySelector<HTMLElement>('div[class*="flex-1"][class*="min-w-0"]')
171
+ const available = tagArea?.clientWidth ?? trigger?.clientWidth ?? 0
172
+ const visible = getAvatarStackVisibleCount({
173
+ availablePx: available,
174
+ total: selectedNames.length,
175
+ avatarPx: AVATAR_STACK_AVATAR_PX[size],
176
+ overflowChipPx: AVATAR_STACK_OVERFLOW_CHIP_PX[size],
177
+ })
178
+ setStackVisibleCount(visible)
179
+ }
180
+ calc()
181
+ const ro = new ResizeObserver(calc)
182
+ ro.observe(root)
183
+ return () => ro.disconnect()
184
+ }, [resolvedMode, isMulti, multiDisplay, selectedNames.length, size])
185
+ // Merge ref:forward to parent + capture for ResizeObserver
186
+ const mergedStackRef = React.useCallback((el: HTMLDivElement | null) => {
187
+ stackContainerRef.current = el
188
+ if (typeof ref === 'function') ref(el)
189
+ else if (ref) (ref as React.MutableRefObject<HTMLDivElement | null>).current = el
190
+ }, [ref])
191
+
142
192
  // ── mode='display' ────────────────────────────────────────────────────────
143
193
  // Default(showDisplayEndIcon=false):裸 PersonDisplay / MultiPersonDisplay — backward compat。
144
194
  // Opt-in(showDisplayEndIcon=true,2026-05-08 D-path):Field naked wrapper + ItemSuffix ChevronDown,
@@ -200,11 +250,7 @@ const PeoplePicker = React.forwardRef<HTMLDivElement, PeoplePickerProps>(functio
200
250
  }
201
251
 
202
252
  // ── edit mode ─────────────────────────────────────────────────────────────
203
- const selectedNames: string[] = !value
204
- ? []
205
- : Array.isArray(value)
206
- ? value.map(v => resolvePerson(v).name)
207
- : [resolvePerson(value).name]
253
+ // (selectedNames 派生已 hoist 至檔上方 hooks 區,見 D3 P0 修註解)
208
254
 
209
255
  // ── single mode → wraps Select ────────────────────────────────────────────
210
256
  if (!isMulti) {
@@ -219,8 +265,9 @@ const PeoplePicker = React.forwardRef<HTMLDivElement, PeoplePickerProps>(functio
219
265
  onChange={handleSingleChange}
220
266
  searchable
221
267
  placeholder={placeholder}
222
- // 2026-05-12 Stream C Issue 4 fix(codex Q3):傳 `placeholder` Select trigger empty。
223
- // 不再傳 `emptyText`(search-empty semantic trigger-empty 分離,canonical SSOT)。
268
+ // 2026-05-12 Issue 4:placeholder = trigger empty。2026-07-04 Q4:emptyText Select
269
+ // SelectMenu 接線(search-empty 語意,與 trigger-empty 分離)。
270
+ emptyText={emptyText}
224
271
  defaultOpen={defaultOpen}
225
272
  onOpenChange={onOpenChange}
226
273
  className={className}
@@ -252,11 +299,10 @@ const PeoplePicker = React.forwardRef<HTMLDivElement, PeoplePickerProps>(functio
252
299
  searchable
253
300
  searchPlaceholder={searchPlaceholder}
254
301
  // 2026-05-12 Stream C Issue 4(codex Q3):placeholder = trigger empty hint('請選擇人員')
255
- // — semantic clean separation;emptyText 不再 silent trigger placeholder。
256
- // emptyPlaceholder backward-compat forward(Combobox line 760 `placeholder ?? emptyPlaceholder` fallback)
257
- // 1 cycle:future 移除 emptyPlaceholder forward,emptyText 改傳 SelectMenu noResultsText。
302
+ // — semantic clean separation。2026-07-04 Q4 拍板完成 1-cycle 承諾:emptyPlaceholder forward
303
+ // 移除,emptyText 改傳 Combobox SelectMenu emptyText(search-empty 真住所,菜單空狀態)
258
304
  placeholder={placeholder}
259
- emptyPlaceholder={emptyText}
305
+ emptyText={emptyText}
260
306
  wrap={pillWrap}
261
307
  defaultOpen={defaultOpen}
262
308
  onOpenChange={onOpenChange}
@@ -302,45 +348,8 @@ const PeoplePicker = React.forwardRef<HTMLDivElement, PeoplePickerProps>(functio
302
348
  const handleMultiChange = (next: string[]) => {
303
349
  onChange?.(next.map(name => findPerson(people, name)))
304
350
  }
305
- // SSOT visible count compute via formula primitive + ResizeObserver
306
- const stackContainerRef = React.useRef<HTMLDivElement | null>(null)
307
- const [stackVisibleCount, setStackVisibleCount] = React.useState<number | undefined>(undefined)
308
- React.useLayoutEffect(() => {
309
- // 注:此 effect 只在 multi stack mode 跑(early return if not stack);length<=1 不需 override
310
- if (!isMulti || selectedNames.length <= 1) {
311
- setStackVisibleCount(undefined); return
312
- }
313
- const root = stackContainerRef.current
314
- if (!root) return
315
- // 2026-05-15 ROOT CAUSE FIX(user 抓「之前說的問題都還是存在」):
316
- // stackContainerRef 透過 mergedStackRef 接 Combobox forwarded ref → root **就是** [role=combobox]
317
- // div 自己。原 `root.querySelector('[role=combobox]')` 不找 self 永遠 null → trigger=null →
318
- // tagArea=null → available=0 → setStackVisibleCount(0) → 整 stack 全 overflow → fallback 到
319
- // Combobox DOM-based useOverflowCount(非 deterministic 那個算法)。修:用 root 自己當 trigger,
320
- // 從 root 內找 tagArea(flex-1 min-w-0 div)。
321
- const calc = () => {
322
- const trigger = root.matches('[role="combobox"]') ? root : root.querySelector<HTMLElement>('[role="combobox"]')
323
- const tagArea = trigger?.querySelector<HTMLElement>('div[class*="flex-1"][class*="min-w-0"]')
324
- const available = tagArea?.clientWidth ?? trigger?.clientWidth ?? 0
325
- const visible = getAvatarStackVisibleCount({
326
- availablePx: available,
327
- total: selectedNames.length,
328
- avatarPx: AVATAR_STACK_AVATAR_PX[size],
329
- overflowChipPx: AVATAR_STACK_OVERFLOW_CHIP_PX[size],
330
- })
331
- setStackVisibleCount(visible)
332
- }
333
- calc()
334
- const ro = new ResizeObserver(calc)
335
- ro.observe(root)
336
- return () => ro.disconnect()
337
- }, [isMulti, multiDisplay, selectedNames.length, size])
338
- // Merge ref:forward to parent + capture for ResizeObserver
339
- const mergedStackRef = React.useCallback((el: HTMLDivElement | null) => {
340
- stackContainerRef.current = el
341
- if (typeof ref === 'function') ref(el)
342
- else if (ref) (ref as React.MutableRefObject<HTMLDivElement | null>).current = el
343
- }, [ref])
351
+ // ── edit mode ─────────────────────────────────────────────────────────────
352
+ // (selectedNames 派生已 hoist 至檔上方 hooks 區,見 D3 P0 修註解)
344
353
 
345
354
  return (
346
355
  <Combobox
@@ -355,7 +364,7 @@ const PeoplePicker = React.forwardRef<HTMLDivElement, PeoplePickerProps>(functio
355
364
  searchPlaceholder={searchPlaceholder}
356
365
  // 2026-05-12 Stream C Issue 4(codex Q3):placeholder = trigger empty('請選擇人員');emptyText = search-empty(僅 backward-compat forward 1 cycle)
357
366
  placeholder={placeholder}
358
- emptyPlaceholder={emptyText}
367
+ emptyText={emptyText}
359
368
  wrap={false}
360
369
  defaultOpen={defaultOpen}
361
370
  onOpenChange={onOpenChange}
@@ -179,8 +179,8 @@ function MultiPersonDisplay({
179
179
  /** 傳入時啟用 dismiss(edit mode),callback 接收被移除的 person */
180
180
  onRemove?: (person: PersonValue) => void
181
181
  }) {
182
- if (!value || value.length === 0) return <span className="text-fg-muted">{EMPTY_DISPLAY}</span>
183
-
182
+ // 2026-07-05 D3 P1 修(React #310 家族):3 hooks 原在 empty early return 之後 — value 非空↔空
183
+ // 切換時 hook 數 0↔3 變動必 crash(現靠 consumer 三元切換 element type 偶然避開)。hoist 到 return 前。
184
184
  // 2026-05-15 Bug 3 fix(Claude+Codex Step 5 比稿 consensus):消費 shared `avatar-stack-overflow`
185
185
  // primitive。原 inline canvas-based formula 是 dual-implementation 違反 user SSOT「同 cell width 同
186
186
  // overflow 判斷」(edit path 用 Combobox useOverflowCount DOM offsetWidth / display path 用 inline
@@ -189,7 +189,7 @@ function MultiPersonDisplay({
189
189
  const containerRef = React.useRef<HTMLSpanElement>(null)
190
190
  const [measuredCount, setMeasuredCount] = React.useState<number | null>(null)
191
191
  React.useLayoutEffect(() => {
192
- if (!measured) return
192
+ if (!measured || !value || value.length === 0) return
193
193
  const el = containerRef.current
194
194
  if (!el) return
195
195
  const calc = () => {
@@ -207,6 +207,8 @@ function MultiPersonDisplay({
207
207
  return () => ro.disconnect()
208
208
  }, [measured, size, value])
209
209
 
210
+ if (!value || value.length === 0) return <span className="text-fg-muted">{EMPTY_DISPLAY}</span>
211
+
210
212
  const resolvedMax = measured && measuredCount !== null ? measuredCount : (max ?? 3)
211
213
  const people = value.map(resolvePerson)
212
214
  const visible = people.slice(0, resolvedMax)
@@ -244,7 +244,7 @@ export const StateBehavior: Story = {
244
244
  <div className="flex flex-col gap-8">
245
245
  <div>
246
246
  <H3>開啟 / 關閉</H3>
247
- <Desc>Radix 原生動畫:進場 `fade-in + zoom-in-95 + slide-from-side`,離場鏡像。所有方向都使用 side-aware slide 動畫,浮層看起來從 trigger 「長出來」。</Desc>
247
+ <Desc>Radix 提供 data-state 切換 + DS Tailwind animate 驅動(popover.tsx L70-71):進場 `fade-in + zoom-in-95 + slide-from-side`,離場鏡像。所有方向都使用 side-aware slide 動畫,浮層看起來從 trigger 「長出來」。</Desc>
248
248
  <ul className="text-caption text-fg-muted list-disc pl-5 space-y-1">
249
249
  <li>點擊 trigger → 開啟</li>
250
250
  <li>點 Popover 外部 → 關閉(click outside)</li>
@@ -24,14 +24,14 @@ Popover 是**點擊觸發的浮層容器**——提供定位、動畫、焦點
24
24
 
25
25
  **Layout Family**:非上述 family — composite / multi-section(多區塊組合,自 own layout)。
26
26
 
27
- **視覺對齊 Dialog**:外殼 token 與 Dialog 完全一致(`bg-surface-raised` / `border-border` / `rounded-lg` / `elevation-200`)。差別僅兩點:(1) Popover 是 non-modal 輕量浮層不阻斷背景,(2) **density 永遠鎖 `md`** — Popover 不隨頁面 density 放大(lightweight 浮層保持緊湊)。
27
+ **視覺對齊 Dialog**:外殼 token 與 Dialog 完全一致(`bg-surface-raised` / `border-border` / `rounded-lg` / `elevation-200`)。差別僅兩點:(1) Popover 是 non-modal 輕量浮層不阻斷背景,(2) **layout-space `md`** — header / footer padding 不隨頁面放大(輕量 chrome 保持精簡);**ui-size 不鎖、繼承 page** — 內部控件隨 page 放大、對齊觸發點(2026-06-15 改,原 `data-density` master switch,見 `popover.tsx` docblock)。
28
28
 
29
- **Content size canonical(2026-04-29)**:density lock md → **fields(Input / Select / Checkbox)預設 `size="md"`**(field-height-md 32px,對齊 density)。其餘細節走 SSOT pointer:
29
+ **Content size canonical(2026-04-29,2026-06-15 更新)**:ui-size 繼承 page → **fields(Input / Select / Checkbox)預設隨 page ui-size**(md page = `size="md"` field-height-md 32px;lg page 放大,與觸發點一致)。其餘細節走 SSOT pointer:
30
30
  - Chrome corner buttons(close X / refresh / dismiss)→ `patterns/overlay-surface/overlay-surface.spec.md`「Chrome dismiss size canonical」(sm native + v5 trick)
31
31
  - Row inline action(panel list row 內 Eye / drag / suffix toggle)→ `patterns/element-anatomy/inline-action.spec.md`「尺寸對照」table(md = 16+18 hover bg)
32
32
  - Footer action button → `action-bar.spec.md`(輕量 chrome,sm canonical)
33
33
 
34
- **觸發距離 canonical**:`sideOffset = 8`(px)—— trigger 到 Popover 邊緣的垂直/水平間距。8px 是世界級浮層 idiom(Notion / Linear / Figma / Stripe 皆約 6-8px);< 4px 會讓浮層貼死 trigger 失去「另一層」感,> 12px 會拉斷 trigger ↔ content 的視覺關聯。Radix/shadcn 預設 4px 太緊,本 DS 改為 8 對齊 overlay primitive 的呼吸感。Dialog 不適用(居中或 full-screen)。 <!-- @benchmark-unverified: see frontmatter benchmark list for canonical DS source URL -->
34
+ **觸發距離 canonical**:`sideOffset = 8`(px)—— trigger 到 Popover 邊緣的垂直/水平間距。8px 是世界級浮層 idiom(Notion / Linear / Figma / Stripe 皆約 6-8px);< 4px 會讓浮層貼死 trigger 失去「另一層」感,> 12px 會拉斷 trigger ↔ content 的視覺關聯。shadcn 預設 4px 太緊(Radix 原生預設 0 更貼),本 DS 改為 8 對齊 overlay primitive 的呼吸感。Dialog 不適用(居中或 full-screen)。 <!-- @benchmark-unverified: see frontmatter benchmark list for canonical DS source URL -->
35
35
 
36
36
  **Viewport 邊距 canonical**:`collisionPadding = 8`(px)——浮層與 viewport 邊緣的最小間距。當 popover 原本要貼近 viewport 邊(trigger 在頁面最右側、或視窗很窄),Radix 會自動推開 8px 讓浮層不貼死邊界。世界級對照:macOS / iOS / Notion / Figma 的 overflow 選單永遠留視覺呼吸距。`sideOffset` 管 trigger↔popover,`collisionPadding` 管 popover↔viewport,兩者互補。Consumer 不需傳 — 預設值是 canonical。 <!-- @benchmark-unverified: see frontmatter benchmark list for canonical DS source URL -->
37
37
 
@@ -54,7 +54,7 @@ Popover 是**點擊觸發的浮層容器**——提供定位、動畫、焦點
54
54
  其他結構化浮層 spec 若討論 align 應 pointer 指回本節。
55
55
 
56
56
  **結構化組合(可選)**:PopoverContent 外殼無內距,consumer 可選擇:
57
- - **單一 body**:包 `<PopoverBody>` 取得 Dialog body padding (`px-loose py-tight`)
57
+ - **單一 body**:包 `<PopoverBody>` 取得 SurfaceBody canonical padding (`px-loose py-tight`,`overlay-surface.spec.md` SSOT;Dialog body 另有 `pb-[--layout-space-bottom]` 特化)
58
58
  - **含標題**:組合 `<PopoverHeader>` + `<PopoverBody>`(下分隔線由 Header 提供)
59
59
  - **含操作按鈕列**:再加 `<PopoverFooter>`(上分隔線由 Footer 提供)
60
60
 
@@ -64,13 +64,13 @@ Popover 是**點擊觸發的浮層容器**——提供定位、動畫、焦點
64
64
 
65
65
  **Close X 按鈕(2026-04-22 v5 unbounded canonical)**:所有 `PopoverHeader` 內建右上 X 按鈕,`<Button data-dismiss iconOnly dismiss size="sm" />` native sm(28 md / 32 lg)。透過 SurfaceHeader 的 v5 CSS rule 自動套負 margin 讓 layout 佔位;PopoverHeader 走 `COMPACT_HEADER_SLOT`(= calc(--font-body-size×1.5) = 21 = title line-box,衍生)→ 佔位 21(輕量 chrome,見 `popover.tsx` docblock),非 Dialog 的 24。`hideClose` prop 可讓 composition 元件(如 Coachmark)選擇隱藏。詳 `patterns/overlay-surface/overlay-surface.spec.md`「Chrome dismiss size canonical」。
66
66
 
67
- **Header / Footer 高度 canonical**:padding-based(繼承 SurfaceHeader / SurfaceFooter),高度 = max(child layout) + 2×tight。header:slot 佔位 21(`COMPACT_HEADER_SLOT` = title line-box,衍生)→ max(21 title, 21 slot) + 2×12 = **45**,自然比 Dialog / Sheet 48 輕一級(`popover.tsx` PopoverHeader docblock + overlay-surface.tsx 對照);footer:無 override,unbounded 佔位 2448(若只有 unbounded)或自然長(若有 bounded)。詳 `tokens/uiSize/uiSize.spec.md`「Chrome header 選型 canonical」。
67
+ **Header / Footer 高度 canonical**:padding-based(繼承 SurfaceHeader / SurfaceFooter),高度 = max(child layout) + 2×tight。header:slot 佔位 21(`COMPACT_HEADER_SLOT` = title line-box,衍生)→ max(21 title, 21 slot) + 2×12 = **45**,自然比 Dialog / Sheet 48 輕一級(`popover.tsx` PopoverHeader docblock + overlay-surface.tsx 對照);footer:SurfaceFooter **不**套 unbounded slot trick(僅 padding + `border-t`,見 `overlay-surface.spec.md`「為什麼 SurfaceHeader 是 padding-based」)高度 = max(child layout) + 2×tight(sm action button 28 → **52** md)。詳 `tokens/uiSize/uiSize.spec.md`「Chrome header 選型 canonical」。
68
68
 
69
69
  ---
70
70
 
71
71
  ## Title typography canonical(non-modal 特化,2026-04-22 v4)
72
72
 
73
- **PopoverTitle 用 `text-body font-medium`(14px),不是 Dialog / Sheet 的 `text-body-lg`(16px)**。這是針對 **non-modal 輕量浮層** 的視覺考量,跟 density 鎖 md 同源的輕量 rationale。
73
+ **PopoverTitle 用 `text-body font-medium`(14px),不是 Dialog / Sheet 的 `text-body-lg`(16px)**。這是針對 **non-modal 輕量浮層** 的視覺考量,跟 layout-space 鎖 md 同源的輕量 rationale。
74
74
 
75
75
  **重量分級(world-class 共通 pattern)**:
76
76
 
@@ -152,11 +152,11 @@ canonical 判斷:「使用者 click 單項是否立即改變系統狀態?」是
152
152
 
153
153
  ## 邊界案例
154
154
 
155
- - **極長內容**:PopoverContent 設 `max-h-[var(--radix-popover-content-available-height)]`(popover.tsx:65),body 包 `<PopoverBody>`(SurfaceBody `flex-1 min-h-0 overflow-y-auto`)→ viewport 太小時 header / footer 留在視窗內、body 內捲。中間 wrapper 必 forward flex chain,詳 `../../patterns/overlay-surface/overlay-surface.spec.md`「Viewport-aware scroll chain invariant」。
155
+ - **極長內容**:PopoverContent 設 `max-h-[var(--radix-popover-content-available-height)]`(popover.tsx:69),body 包 `<PopoverBody>`(SurfaceBody `flex-1 min-h-0 overflow-y-auto`)→ viewport 太小時 header / footer 留在視窗內、body 內捲。中間 wrapper 必 forward flex chain,詳 `../../patterns/overlay-surface/overlay-surface.spec.md`「Viewport-aware scroll chain invariant」。
156
156
  - **Viewport 邊緣**:`collisionPadding = 8` 自動推開(見「Viewport 邊距 canonical」)。
157
157
  - **嵌套 Popover**:禁止(見「禁止事項」)。
158
158
  - **Dark mode**:殼走 semantic token 自動 adapt(見 `color.spec.md`)。
159
- - **Density**:永遠鎖 `md`(見「定位」段),不隨頁面 density 放大。
159
+ - **Density**:layout-space `md`(header / footer padding 精簡);ui-size 繼承 page(見「定位」段)。
160
160
 
161
161
  ---
162
162
 
@@ -175,13 +175,11 @@ Consumer 無需額外處理 a11y,保留 Radix `data-state` 屬性即可(開啟
175
175
 
176
176
  ---
177
177
 
178
- ## 為何無 Inspector
178
+ ## Inspector
179
179
 
180
- Popover 是**浮層容器 primitive**——關鍵決策是 `side` × `align` × `sideOffset`,以及內部 Header / Body / Footer 結構。`PlacementMatrix` 是完整的 side × align 12-cell 矩陣,比互動 Inspector(切單一 side/align)更能呈現所有定位組合。Header / Body / Footer padding 屬 `overlay-surface` SSOT,已由 `ColorMatrix` 的 token 表 + 該 pattern spec 覆蓋。
180
+ `Inspector` 提供互動 Controls 即時切換 PopoverContent 六個關鍵維度:`side` / `align`(12 種定位組合)、`sideOffset`(與 trigger 距離)、`width`(w-72 / w-80 / w-96)、`showHeader` / `showFooter`(chrome 結構開關)。內容區由 consumer 決定,Inspector 聚焦容器層級的可試玩 prop(對齊 `sheet.spec.md`「Inspector」canonical)。`PlacementMatrix` 另以靜態 12-cell side × align 矩陣一眼呈現所有定位組合;Header / Body / Footer padding 屬 `overlay-surface` SSOT,由 `ColorMatrix` 的 token 表 + 該 pattern spec 覆蓋。
181
181
 
182
- Popover 內容完全由 consumer 決定,無自己的 variant/size/disabled 等可試玩 prop——Inspector container 類元件價值低。
183
-
184
- 對應 anatomy story:保留 `Overview` + 元件特有 `PlacementMatrix`(12-cell side × align 矩陣) + `ColorMatrix` + `SizeMatrix` + `StateBehavior`(open / close / animation)。
182
+ 對應 anatomy story:保留 `Overview` + `Inspector` + 元件特有 `PlacementMatrix`(12-cell side × align 矩陣) + `ColorMatrix` + `SizeMatrix` + `StateBehavior`(open / close / animation) + `Accessibility`。
185
183
 
186
184
  ---
187
185
 
@@ -134,8 +134,8 @@ PopoverFooter.displayName = "PopoverFooter"
134
134
  // PopoverTitle(2026-04-22 v4 non-modal canonical):`text-body font-medium`(14px)
135
135
  // Rationale:Popover / Coachmark 屬 **non-modal 輕量浮層**,跟 density 鎖 md 同源的視覺語言 —
136
136
  // chrome 全體輕量:
137
- // - density 鎖 md(不隨 page 放大)
138
- // - dismiss X 透過 v5 unbounded trick layout 佔位 24
137
+ // - layout-space 鎖 md(ui-size 繼承 page,2026-06-15)
138
+ // - dismiss X COMPACT_HEADER_SLOT 佔位 21(= title text-body line-box;見 PopoverHeader docblock)
139
139
  // - **title `text-body`(14px)跟 Dialog / Sheet modal 的 body-lg(16px)形成重量級 vs 輕量級視覺區分**
140
140
  // 世界級對照:Figma / Notion / Linear / Material 的 popover / filter panel / inline settings
141
141
  // 的 header title 多半比 modal dialog title 小一級(16→14 或同比),視覺宣告「此浮層可忽略」
@@ -24,7 +24,7 @@ type InspectorArgs = {
24
24
  }
25
25
 
26
26
  const meta: Meta = {
27
- title: 'Design System/Internal/ProfileCard/設計規格',
27
+ title: 'Design System/Components/ProfileCard/設計規格',
28
28
  parameters: { layout: 'padded' },
29
29
  }
30
30
  export default meta
@@ -97,7 +97,7 @@ export const Overview: Story = {
97
97
  {[
98
98
  ['name', 'string', '必填', '姓名(profile header)'],
99
99
  ['subtitle', 'string', '—', '職稱 / 位置 / 描述'],
100
- ['avatar', 'AvatarData', '—', '{ src?, alt?, color? };size/status 由 ProfileCard 覆寫'],
100
+ ['avatar', 'AvatarData', '—', '{ src?, alt, color?, hoverCard? }(alt 必填;未傳 avatar 時以 name 作 fallback alt);size/status 由 ProfileCard 覆寫,hoverCard 不 forward'],
101
101
  ['status', "'online' | 'away' | 'busy' | 'offline'", '—', '觸發 Status section + Avatar 狀態點'],
102
102
  ['statusMessage', 'ReactNode', '—', '狀態說明文字(完整顯示,內容多時 Body 區捲動)'],
103
103
  ['actions', 'ReactNode', '—', 'CTA buttons(通常 2 個 sm Button)'],
@@ -122,7 +122,7 @@ export const Overview: Story = {
122
122
  <tr><Td>Card 寬度</Td><Td mono>w-[320px](元件級常數,不抽 token)</Td></tr>
123
123
  <tr><Td>Profile header padding</Td><Td mono>px-4 py-3</Td></tr>
124
124
  <tr><Td>Profile avatar / text gap</Td><Td mono>gap-3</Td></tr>
125
- <tr><Td>Avatar 大小</Td><Td mono>64px(對齊 Avatar.block tier)</Td></tr>
125
+ <tr><Td>Avatar 大小</Td><Td mono>64px(profile-card.tsx 元件級常數 AVATAR_SIZE;非 item-anatomy AVATAR_SIZE.block tier — block 為 32/32/40)</Td></tr>
126
126
  <tr><Td>Actions padding</Td><Td mono>px-4 pb-3 + gap-2</Td></tr>
127
127
  <tr><Td>Status / Info padding</Td><Td mono>px-4 py-3</Td></tr>
128
128
  <tr><Td>View more padding</Td><Td mono>px-4 py-3</Td></tr>
@@ -376,7 +376,7 @@ export const HoverCardIntegration: Story = {
376
376
  <Avatar alt={p.name} size={40} status={p.status} />
377
377
  </span>
378
378
  </HoverCardTrigger>
379
- <HoverCardContent align="start" className="p-0" sideOffset={8}>
379
+ <HoverCardContent align="start" className="p-0">{/* sideOffset 不 override — spec 禁止事項(default 即 OVERLAY_SIDE_OFFSET) */}
380
380
  <ProfileCard
381
381
  name={p.name}
382
382
  subtitle={p.subtitle}
@@ -8,7 +8,7 @@ import { Avatar } from '@/design-system/components/Avatar/avatar'
8
8
  import { Button } from '@/design-system/components/Button/button'
9
9
 
10
10
  const meta: Meta = {
11
- title: 'Design System/Internal/ProfileCard/設計原則',
11
+ title: 'Design System/Components/ProfileCard/設計原則',
12
12
  parameters: { layout: 'padded' },
13
13
  }
14
14
  export default meta
@@ -87,7 +87,7 @@ export const UsageGuidance: Story = {
87
87
  <p>適合 ProfileCard 的真實業務場景(點擊跳轉「展示」頁範例):</p>
88
88
  <ul className="space-y-1">
89
89
  <li>
90
- <LinkTo kind="Design System/Internal/ProfileCard/展示" name="懸停展開 ProfileCard"><span className="text-primary hover:text-primary-hover font-medium cursor-pointer">懸停留言者 / PR reviewer / 成員列表的頭像,展開人員詳情卡</span></LinkTo>
90
+ <LinkTo kind="Design System/Components/ProfileCard/展示" name="懸停展開 ProfileCard"><span className="text-primary hover:text-primary-hover font-medium cursor-pointer">懸停留言者 / PR reviewer / 成員列表的頭像,展開人員詳情卡</span></LinkTo>
91
91
  </li>
92
92
  </ul>
93
93
  <p>判斷時對照 spec.md「何時用 / 何時不用」段落。</p>
@@ -1,8 +1,7 @@
1
1
  ---
2
2
  component: ProfileCard
3
3
  family: composite
4
- traits:
5
- - isInternal
4
+ traits: []
6
5
  variants: {}
7
6
  sizes: {}
8
7
  benchmark:
@@ -3,7 +3,7 @@ import { ProfileCard, ProfileCardDefaultActions } from './profile-card'
3
3
  import { Avatar } from '@/design-system/components/Avatar/avatar'
4
4
 
5
5
  const meta: Meta = {
6
- title: 'Design System/Internal/ProfileCard/展示',
6
+ title: 'Design System/Components/ProfileCard/展示',
7
7
  parameters: { layout: 'padded' },
8
8
  }
9
9
  export default meta
@@ -1,6 +1,9 @@
1
1
  /**
2
- * @internal — DS-internal 單元(per `.claude/rules/ui-development.md` Public vs Internal canonical;spec frontmatter `isInternal`)。
3
- * 不進 root barrel front-door;由 PeoplePicker(person-display)wrap 消費,end-user app 請用 wrapper 元件。
2
+ * Public 元件(2026-07-04 Q8 拍板升 public,原 @internal 標記移除)。
3
+ * 理由:avatar.spec.md「hover 必出 ProfileCard」DS-wide canonical 要求 consumer 直接
4
+ * import ProfileCard + ProfileCardDefaultActions 組 HoverCard — 按 ui-development.md
5
+ * 唯一判準(DS 想讓 consumer 直接用 = public)internal 標記是 governance bug;
6
+ * 世界級對照:Atlassian ProfileCard 同為 public standalone。
4
7
  */
5
8
  // @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.
6
9
  import * as React from 'react'
@@ -29,7 +29,7 @@ const Label = ({ children, warn }: { children: React.ReactNode; warn?: boolean }
29
29
 
30
30
  const Section = ({ title, children }: { title: string; children: React.ReactNode }) => (
31
31
  <section className="mb-12">
32
- <h2 className="text-heading-3 font-bold text-foreground mb-4 pb-2 border-b border-border">{title}</h2>
32
+ <h2 className="text-h3 font-bold text-foreground mb-4 pb-2 border-b border-border">{title}</h2>
33
33
  {children}
34
34
  </section>
35
35
  )
@@ -90,7 +90,7 @@ Item-level default / hover / checked / disabled **色彩**與 Checkbox 共用同
90
90
 
91
91
  繼承 Field family,詳見 `../Field/field-controls.spec.md` + `../Field/form-validation.spec.md`。
92
92
 
93
- **Field 內 readonly(2026-06-12 user 拍板)**:不渲染 radio 群組,改渲染 `fieldWrapperStyles` readonly 灰框 + 選中項 label(= Select readonly 同款,同為單選資料的鎖定呈現);standalone readonly 維持原樣鎖互動(ReadonlyContext)。
93
+ **Field 內 readonly(2026-06-12 user 拍板)**:不渲染 radio 群組,改渲染 `fieldWrapperStyles` readonly 灰框 + 選中項 label(= Select readonly 同款,同為單選資料的鎖定呈現);standalone readonly 維持原樣鎖互動(ReadonlyContext + item onClick preventDefault guard — 2026-07-05 D4 補:`<label htmlFor>` synthetic click 非 pointer event,`pointer-events-none` 攔不到,需 guard 才真鎖)。
94
94
 
95
95
  ---
96
96
 
@@ -112,7 +112,7 @@ Item-level default / hover / checked / disabled **色彩**與 Checkbox 共用同
112
112
  - **極長 label / description**:預設不截斷、自然換行(`labelMaxLines` / `descMaxLines` 預設 `'none'`,Clamp 政策 SSOT 見 `checkbox.spec.md`「Clamp 政策」)。
113
113
  - **icon / avatar prefix**:`RadioGroupItem` 一級 `icon` / `avatar` props 轉發 `SelectionItem` prefix 槽(2026-06-12 M30;規則 SSOT 見 `checkbox.spec.md` 同段 + `selection-item.spec.md`)。
114
114
  - **RTL**:未實作方向鏡像;RTL 屬 DS-wide 決策,未定(與 Chip / Breadcrumb 同口徑)。
115
- - **Dark mode / density**:走 SelectionItem semantic token 自動 adapt;垂直 gap layoutSpace token density-aware
115
+ - **Dark mode / density**:走 SelectionItem semantic token 自動 adapt;垂直行距由 SelectionItem `py = (field-height − 1lh) / 2` 公式消費 uiSize token(`--field-height-*`)生成,隨 density 自動調整(零外部 gap 機制 SSOT 見 `checkbox.spec.md`「群組模式」)
116
116
 
117
117
  ---
118
118
 
@@ -44,7 +44,9 @@ export const Modes: Story = {
44
44
  </div>
45
45
  <div>
46
46
  <h3 className="text-body font-bold text-foreground mb-2">disabled</h3>
47
- <RadioGroup value="yearly" aria-label="付款方案(disabled mode demo)">
47
+ {/* mode="disabled" → Radix Root disabled 原生 propagate 全 item(radio 控件真停用);
48
+ SelectionItem disabled 另管 label 降色 — 雙傳對齊「停用」story 寫法 */}
49
+ <RadioGroup mode="disabled" value="yearly" aria-label="付款方案(disabled mode demo)">
48
50
  <SelectionItem control={<RadioGroupItem value="monthly" id="m-dis" />} label="月付方案" htmlFor="m-dis" disabled />
49
51
  <SelectionItem control={<RadioGroupItem value="yearly" id="y-dis" />} label="年付方案" htmlFor="y-dis" disabled />
50
52
  <SelectionItem control={<RadioGroupItem value="lifetime" id="l-dis" />} label="終身方案" htmlFor="l-dis" disabled />
@@ -80,14 +80,31 @@ const RadioGroup = React.forwardRef<
80
80
  // 對齊 Carbon read-only single-select(只顯示 selected 內容)+ Airtable / Notion read-only。
81
81
  // 實作:walk children 找 control.value === selectedValue 的 SelectionItem,render label plain text。
82
82
  // (不用 context dispatch 給 RadioGroupItem — SelectionItem layout wrapper 仍會渲染所有 item label)
83
+ // 2026-07-04 修:display / readonly-in-Field 分支的 <div> 原丟棄剩餘 props(id/data-*/aria-*)
84
+ // 且不轉發 forwardRef ref → 自家 radio-group.stories.tsx display demo 傳的 aria-label 都靜默失效。
85
+ // 抽出 Radix 專屬 non-DOM props,其餘 DOM props 於兩分支 spread 轉發(與 Checkbox/Switch 同修);
86
+ // edit 主路徑不受影響(Root 仍 spread 完整 props)。
87
+ const {
88
+ onValueChange: _onValueChange,
89
+ disabled: _disabled,
90
+ required: _required,
91
+ name: _name,
92
+ orientation: _orientation,
93
+ dir: _dir,
94
+ loop: _loop,
95
+ asChild: _asChild,
96
+ children: _children,
97
+ ...restDomProps
98
+ } = props as RadioGroupProps & { asChild?: boolean }
99
+
83
100
  if (resolvedMode === 'display') {
84
101
  const selectedValue = (value ?? defaultValue) as string | undefined
85
102
  if (!selectedValue) {
86
- return <div role="group" className={cn('grid', className)}><span className="text-fg-muted">{EMPTY_DISPLAY}</span></div>
103
+ return <div {...restDomProps} ref={ref as React.Ref<HTMLDivElement>} role="group" className={cn('grid', className)}><span className="text-fg-muted">{EMPTY_DISPLAY}</span></div>
87
104
  }
88
105
  const selectedLabel = findSelectedRadioLabel(props.children, selectedValue)
89
106
  return (
90
- <div role="group" className={cn('grid', className)}>
107
+ <div {...restDomProps} ref={ref as React.Ref<HTMLDivElement>} role="group" className={cn('grid', className)}>
91
108
  <span className="text-foreground">{selectedLabel ?? selectedValue}</span>
92
109
  </div>
93
110
  )
@@ -103,6 +120,8 @@ const RadioGroup = React.forwardRef<
103
120
  const boxSize = resolvedBoxSize
104
121
  return (
105
122
  <div
123
+ {...restDomProps}
124
+ ref={ref as React.Ref<HTMLDivElement>}
106
125
  role="radiogroup"
107
126
  aria-readonly="true"
108
127
  aria-labelledby={fieldCtx?.labelId}
@@ -260,6 +279,18 @@ const RadioGroupItem = React.forwardRef<
260
279
  tabIndex={effectiveReadonly ? -1 : undefined}
261
280
  className={cn(radioItemVariants({ size }), className)}
262
281
  {...props}
282
+ // 2026-07-05 D4 修:readOnly 鎖互動補 label 繞道 — <label htmlFor> 的 synthetic click
283
+ // 不是 pointer event,cva pointer-events-none 攔不到,點 label 文字仍選中。
284
+ // preventDefault 讓 Radix composeEventHandlers(checkForDefaultPrevented)跳過內部 onCheck;
285
+ // 必列在 {...props} 之後才不被 consumer onClick 覆蓋(與 Checkbox/Switch 同修)。
286
+ // 同時攔 Radix roving-focus 的 arrow-key onFocus → click() 路徑(同為 click event)。
287
+ onClick={(event) => {
288
+ if (effectiveReadonly) {
289
+ event.preventDefault()
290
+ return
291
+ }
292
+ props.onClick?.(event)
293
+ }}
263
294
  >
264
295
  <RadioGroupPrimitive.Indicator className="grid place-content-center">
265
296
  <Circle
@@ -306,7 +337,9 @@ export const radioGroupMeta = {
306
337
  md: { fieldHeight: 32, iconSize: 8, typography: 'body' },
307
338
  lg: { fieldHeight: 36, iconSize: 10, typography: 'body-lg' },
308
339
  },
309
- states: ['default', 'hover', 'active', 'focus-visible', 'disabled'],
340
+ // 2026-07-04 stale meta:移除 'active' — radioItemVariants cva 無 active-state 樣式,
341
+ // Radix radio 也無 data-state=active(對照 checkboxMeta 正確不含 active)
342
+ states: ['default', 'hover', 'focus-visible', 'disabled'],
310
343
  tokens: {
311
344
  bg: ['bg-disabled', 'bg-surface'],
312
345
  fg: ['text-fg-disabled', 'text-fg-secondary', 'text-foreground', 'text-primary'],