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

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 (548) hide show
  1. package/CLAUDE.md +1 -1
  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 +8 -3
  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 +4 -1
  24. package/dist/components/BulkActionBar/bulk-action-bar.d.ts.map +1 -1
  25. package/dist/components/BulkActionBar/bulk-action-bar.js +2 -2
  26. package/dist/components/BulkActionBar/bulk-action-bar.js.map +1 -1
  27. package/dist/components/Button/button.d.ts +4 -4
  28. package/dist/components/Button/button.d.ts.map +1 -1
  29. package/dist/components/Button/button.js +73 -44
  30. package/dist/components/Button/button.js.map +1 -1
  31. package/dist/components/Button/index.d.ts +1 -0
  32. package/dist/components/Button/index.d.ts.map +1 -1
  33. package/dist/components/Button/index.js +3 -0
  34. package/dist/components/Button/index.js.map +1 -1
  35. package/dist/components/Calendar/calendar.d.ts +3 -1
  36. package/dist/components/Calendar/calendar.d.ts.map +1 -1
  37. package/dist/components/Calendar/calendar.js +40 -17
  38. package/dist/components/Calendar/calendar.js.map +1 -1
  39. package/dist/components/Carousel/carousel.d.ts +1 -1
  40. package/dist/components/Carousel/carousel.d.ts.map +1 -1
  41. package/dist/components/Carousel/carousel.js +3 -1
  42. package/dist/components/Carousel/carousel.js.map +1 -1
  43. package/dist/components/Chart/chart.d.ts.map +1 -1
  44. package/dist/components/Chart/chart.js +1 -1
  45. package/dist/components/Chart/chart.js.map +1 -1
  46. package/dist/components/Checkbox/checkbox.d.ts +6 -3
  47. package/dist/components/Checkbox/checkbox.d.ts.map +1 -1
  48. package/dist/components/Checkbox/checkbox.js +26 -4
  49. package/dist/components/Checkbox/checkbox.js.map +1 -1
  50. package/dist/components/Checkbox/index.d.ts +1 -0
  51. package/dist/components/Checkbox/index.d.ts.map +1 -1
  52. package/dist/components/Checkbox/index.js +3 -0
  53. package/dist/components/Checkbox/index.js.map +1 -1
  54. package/dist/components/Chip/chip.d.ts +9 -3
  55. package/dist/components/Chip/chip.d.ts.map +1 -1
  56. package/dist/components/Chip/chip.js +46 -13
  57. package/dist/components/Chip/chip.js.map +1 -1
  58. package/dist/components/Coachmark/coachmark.d.ts +5 -3
  59. package/dist/components/Coachmark/coachmark.d.ts.map +1 -1
  60. package/dist/components/Coachmark/coachmark.js +10 -7
  61. package/dist/components/Coachmark/coachmark.js.map +1 -1
  62. package/dist/components/Combobox/combobox.d.ts +3 -1
  63. package/dist/components/Combobox/combobox.d.ts.map +1 -1
  64. package/dist/components/Combobox/combobox.js +11 -7
  65. package/dist/components/Combobox/combobox.js.map +1 -1
  66. package/dist/components/Command/command.d.ts +2 -2
  67. package/dist/components/Command/command.d.ts.map +1 -1
  68. package/dist/components/Command/command.js +4 -2
  69. package/dist/components/Command/command.js.map +1 -1
  70. package/dist/components/DataTable/active-editor-controller.d.ts +4 -3
  71. package/dist/components/DataTable/active-editor-controller.d.ts.map +1 -1
  72. package/dist/components/DataTable/cell-registry.d.ts.map +1 -1
  73. package/dist/components/DataTable/cell-registry.js +3 -0
  74. package/dist/components/DataTable/cell-registry.js.map +1 -1
  75. package/dist/components/DataTable/data-table-filter-panel.d.ts.map +1 -1
  76. package/dist/components/DataTable/data-table-interaction-layer.d.ts +4 -3
  77. package/dist/components/DataTable/data-table-interaction-layer.d.ts.map +1 -1
  78. package/dist/components/DataTable/data-table-interaction-layer.js.map +1 -1
  79. package/dist/components/DataTable/data-table.d.ts +38 -7
  80. package/dist/components/DataTable/data-table.d.ts.map +1 -1
  81. package/dist/components/DataTable/data-table.js +213 -63
  82. package/dist/components/DataTable/data-table.js.map +1 -1
  83. package/dist/components/DataTable/filter-tree.d.ts +5 -0
  84. package/dist/components/DataTable/filter-tree.d.ts.map +1 -1
  85. package/dist/components/DateGrid/date-grid.d.ts +3 -3
  86. package/dist/components/DateGrid/date-grid.d.ts.map +1 -1
  87. package/dist/components/DateGrid/date-grid.js +52 -6
  88. package/dist/components/DateGrid/date-grid.js.map +1 -1
  89. package/dist/components/DatePicker/date-picker.d.ts +2 -8
  90. package/dist/components/DatePicker/date-picker.d.ts.map +1 -1
  91. package/dist/components/DatePicker/date-picker.js +139 -109
  92. package/dist/components/DatePicker/date-picker.js.map +1 -1
  93. package/dist/components/DescriptionList/description-list.d.ts.map +1 -1
  94. package/dist/components/DescriptionList/description-list.js +1 -1
  95. package/dist/components/DescriptionList/description-list.js.map +1 -1
  96. package/dist/components/Dialog/dialog.d.ts +1 -1
  97. package/dist/components/Dialog/dialog.d.ts.map +1 -1
  98. package/dist/components/Dialog/dialog.js +2 -1
  99. package/dist/components/Dialog/dialog.js.map +1 -1
  100. package/dist/components/DropdownMenu/dropdown-menu.d.ts +4 -4
  101. package/dist/components/DropdownMenu/dropdown-menu.d.ts.map +1 -1
  102. package/dist/components/DropdownMenu/dropdown-menu.js +25 -8
  103. package/dist/components/DropdownMenu/dropdown-menu.js.map +1 -1
  104. package/dist/components/Empty/empty.d.ts.map +1 -1
  105. package/dist/components/Empty/empty.js +1 -1
  106. package/dist/components/Empty/empty.js.map +1 -1
  107. package/dist/components/Field/field-wrapper.d.ts +1 -0
  108. package/dist/components/Field/field-wrapper.d.ts.map +1 -1
  109. package/dist/components/Field/field-wrapper.js +27 -7
  110. package/dist/components/Field/field-wrapper.js.map +1 -1
  111. package/dist/components/Field/field.d.ts +1 -1
  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/Field/use-form-validation.d.ts +5 -1
  116. package/dist/components/Field/use-form-validation.d.ts.map +1 -1
  117. package/dist/components/Field/use-form-validation.js +31 -21
  118. package/dist/components/Field/use-form-validation.js.map +1 -1
  119. package/dist/components/FieldControlGroup/field-control-group.d.ts +3 -3
  120. package/dist/components/FieldControlGroup/field-control-group.js.map +1 -1
  121. package/dist/components/FileItem/file-item.d.ts.map +1 -1
  122. package/dist/components/FileItem/file-item.js +3 -2
  123. package/dist/components/FileItem/file-item.js.map +1 -1
  124. package/dist/components/FileUpload/file-upload.d.ts +1 -1
  125. package/dist/components/FileUpload/file-upload.d.ts.map +1 -1
  126. package/dist/components/FileUpload/file-upload.js +22 -10
  127. package/dist/components/FileUpload/file-upload.js.map +1 -1
  128. package/dist/components/FileViewer/can-render-image.d.ts +6 -0
  129. package/dist/components/FileViewer/can-render-image.d.ts.map +1 -0
  130. package/dist/components/FileViewer/can-render-image.js +11 -0
  131. package/dist/components/FileViewer/can-render-image.js.map +1 -0
  132. package/dist/components/FileViewer/file-viewer.d.ts +2 -2
  133. package/dist/components/FileViewer/file-viewer.d.ts.map +1 -1
  134. package/dist/components/FileViewer/file-viewer.js +24 -16
  135. package/dist/components/FileViewer/file-viewer.js.map +1 -1
  136. package/dist/components/FileViewer/image-renderer.d.ts +0 -5
  137. package/dist/components/FileViewer/image-renderer.d.ts.map +1 -1
  138. package/dist/components/FileViewer/image-renderer.js +1 -9
  139. package/dist/components/FileViewer/image-renderer.js.map +1 -1
  140. package/dist/components/HoverCard/hover-card.d.ts.map +1 -1
  141. package/dist/components/HoverCard/hover-card.js +3 -1
  142. package/dist/components/HoverCard/hover-card.js.map +1 -1
  143. package/dist/components/Input/input.d.ts +26 -3
  144. package/dist/components/Input/input.d.ts.map +1 -1
  145. package/dist/components/Input/input.js +12 -8
  146. package/dist/components/Input/input.js.map +1 -1
  147. package/dist/components/LinkInput/link-input.d.ts +3 -3
  148. package/dist/components/LinkInput/link-input.d.ts.map +1 -1
  149. package/dist/components/LinkInput/link-input.js +29 -28
  150. package/dist/components/LinkInput/link-input.js.map +1 -1
  151. package/dist/components/Menu/menu-item.d.ts.map +1 -1
  152. package/dist/components/Menu/menu-item.js +5 -4
  153. package/dist/components/Menu/menu-item.js.map +1 -1
  154. package/dist/components/Notice/notice.d.ts +5 -0
  155. package/dist/components/Notice/notice.d.ts.map +1 -1
  156. package/dist/components/Notice/notice.js +11 -5
  157. package/dist/components/Notice/notice.js.map +1 -1
  158. package/dist/components/NumberInput/number-input.d.ts +1 -1
  159. package/dist/components/NumberInput/number-input.d.ts.map +1 -1
  160. package/dist/components/NumberInput/number-input.js +17 -8
  161. package/dist/components/NumberInput/number-input.js.map +1 -1
  162. package/dist/components/OverflowIndicator/overflow-indicator.d.ts +1 -1
  163. package/dist/components/OverflowIndicator/overflow-indicator.d.ts.map +1 -1
  164. package/dist/components/OverflowIndicator/overflow-indicator.js +2 -1
  165. package/dist/components/OverflowIndicator/overflow-indicator.js.map +1 -1
  166. package/dist/components/Pagination/index.d.ts +2 -0
  167. package/dist/components/Pagination/index.d.ts.map +1 -0
  168. package/dist/components/Pagination/index.js +6 -0
  169. package/dist/components/Pagination/index.js.map +1 -0
  170. package/dist/components/Pagination/pagination.d.ts +49 -0
  171. package/dist/components/Pagination/pagination.d.ts.map +1 -0
  172. package/dist/components/Pagination/pagination.js +182 -0
  173. package/dist/components/Pagination/pagination.js.map +1 -0
  174. package/dist/components/PeoplePicker/people-picker.d.ts +3 -3
  175. package/dist/components/PeoplePicker/people-picker.d.ts.map +1 -1
  176. package/dist/components/PeoplePicker/people-picker.js +38 -36
  177. package/dist/components/PeoplePicker/people-picker.js.map +1 -1
  178. package/dist/components/PeoplePicker/person-display.d.ts +4 -0
  179. package/dist/components/PeoplePicker/person-display.d.ts.map +1 -1
  180. package/dist/components/PeoplePicker/person-display.js +3 -3
  181. package/dist/components/PeoplePicker/person-display.js.map +1 -1
  182. package/dist/components/Popover/popover.d.ts.map +1 -1
  183. package/dist/components/Popover/popover.js +58 -32
  184. package/dist/components/Popover/popover.js.map +1 -1
  185. package/dist/components/ProfileCard/profile-card.d.ts +11 -3
  186. package/dist/components/ProfileCard/profile-card.d.ts.map +1 -1
  187. package/dist/components/ProfileCard/profile-card.js +14 -4
  188. package/dist/components/ProfileCard/profile-card.js.map +1 -1
  189. package/dist/components/RadioGroup/radio-group.d.ts +1 -1
  190. package/dist/components/RadioGroup/radio-group.d.ts.map +1 -1
  191. package/dist/components/RadioGroup/radio-group.js +28 -3
  192. package/dist/components/RadioGroup/radio-group.js.map +1 -1
  193. package/dist/components/Rating/rating.js +3 -3
  194. package/dist/components/Rating/rating.js.map +1 -1
  195. package/dist/components/ScrollArea/scroll-area.d.ts +1 -1
  196. package/dist/components/ScrollArea/scroll-area.js +2 -2
  197. package/dist/components/ScrollArea/scroll-area.js.map +1 -1
  198. package/dist/components/SegmentedControl/segmented-control.d.ts +2 -2
  199. package/dist/components/SegmentedControl/segmented-control.d.ts.map +1 -1
  200. package/dist/components/SegmentedControl/segmented-control.js +23 -10
  201. package/dist/components/SegmentedControl/segmented-control.js.map +1 -1
  202. package/dist/components/Select/select.d.ts +10 -4
  203. package/dist/components/Select/select.d.ts.map +1 -1
  204. package/dist/components/Select/select.js +57 -14
  205. package/dist/components/Select/select.js.map +1 -1
  206. package/dist/components/SelectMenu/index.js +4 -2
  207. package/dist/components/SelectMenu/select-menu.d.ts +31 -6
  208. package/dist/components/SelectMenu/select-menu.d.ts.map +1 -1
  209. package/dist/components/SelectMenu/select-menu.js +169 -85
  210. package/dist/components/SelectMenu/select-menu.js.map +1 -1
  211. package/dist/components/Separator/separator.js +1 -1
  212. package/dist/components/Separator/separator.js.map +1 -1
  213. package/dist/components/Sheet/sheet.d.ts +1 -1
  214. package/dist/components/Sheet/sheet.d.ts.map +1 -1
  215. package/dist/components/Sheet/sheet.js +3 -2
  216. package/dist/components/Sheet/sheet.js.map +1 -1
  217. package/dist/components/Sidebar/sidebar.d.ts +2 -2
  218. package/dist/components/Sidebar/sidebar.d.ts.map +1 -1
  219. package/dist/components/Sidebar/sidebar.js +28 -10
  220. package/dist/components/Sidebar/sidebar.js.map +1 -1
  221. package/dist/components/Skeleton/skeleton.d.ts +1 -1
  222. package/dist/components/Skeleton/skeleton.js +2 -2
  223. package/dist/components/Skeleton/skeleton.js.map +1 -1
  224. package/dist/components/Slider/slider.d.ts +1 -1
  225. package/dist/components/Slider/slider.d.ts.map +1 -1
  226. package/dist/components/Slider/slider.js +7 -3
  227. package/dist/components/Slider/slider.js.map +1 -1
  228. package/dist/components/Steps/steps.d.ts.map +1 -1
  229. package/dist/components/Steps/steps.js +6 -3
  230. package/dist/components/Steps/steps.js.map +1 -1
  231. package/dist/components/Switch/switch.d.ts +2 -2
  232. package/dist/components/Switch/switch.d.ts.map +1 -1
  233. package/dist/components/Switch/switch.js +47 -9
  234. package/dist/components/Switch/switch.js.map +1 -1
  235. package/dist/components/Tabs/tabs.d.ts +8 -5
  236. package/dist/components/Tabs/tabs.d.ts.map +1 -1
  237. package/dist/components/Tabs/tabs.js +45 -33
  238. package/dist/components/Tabs/tabs.js.map +1 -1
  239. package/dist/components/Tag/tag.d.ts.map +1 -1
  240. package/dist/components/Tag/tag.js +20 -4
  241. package/dist/components/Tag/tag.js.map +1 -1
  242. package/dist/components/Textarea/textarea.d.ts +2 -1
  243. package/dist/components/Textarea/textarea.d.ts.map +1 -1
  244. package/dist/components/Textarea/textarea.js +24 -13
  245. package/dist/components/Textarea/textarea.js.map +1 -1
  246. package/dist/components/TimePicker/time-picker.d.ts +2 -2
  247. package/dist/components/TimePicker/time-picker.d.ts.map +1 -1
  248. package/dist/components/TimePicker/time-picker.js +23 -12
  249. package/dist/components/TimePicker/time-picker.js.map +1 -1
  250. package/dist/components/Toast/toast.d.ts +1 -1
  251. package/dist/components/Toast/toast.d.ts.map +1 -1
  252. package/dist/components/Toast/toast.js +7 -3
  253. package/dist/components/Toast/toast.js.map +1 -1
  254. package/dist/components/Tooltip/tooltip.js +1 -1
  255. package/dist/components/Tooltip/tooltip.js.map +1 -1
  256. package/dist/components/TreeView/tree-view.d.ts +1 -1
  257. package/dist/components/TreeView/tree-view.d.ts.map +1 -1
  258. package/dist/components/TreeView/tree-view.js +11 -5
  259. package/dist/components/TreeView/tree-view.js.map +1 -1
  260. package/dist/hooks/use-controllable.d.ts.map +1 -1
  261. package/dist/hooks/use-controllable.js +4 -2
  262. package/dist/hooks/use-controllable.js.map +1 -1
  263. package/dist/hooks/use-is-touch-device.d.ts.map +1 -1
  264. package/dist/hooks/use-is-touch-device.js +3 -1
  265. package/dist/hooks/use-is-touch-device.js.map +1 -1
  266. package/dist/index.d.ts +2 -0
  267. package/dist/index.d.ts.map +1 -1
  268. package/dist/index.js +14 -0
  269. package/dist/index.js.map +1 -1
  270. package/dist/patterns/element-anatomy/item-anatomy.d.ts +2 -2
  271. package/dist/patterns/element-anatomy/item-anatomy.d.ts.map +1 -1
  272. package/dist/patterns/element-anatomy/item-anatomy.js.map +1 -1
  273. package/dist/patterns/overlay-surface/overlay-surface.js.map +1 -1
  274. package/dist/tokens/categorical-color.d.ts +1 -1
  275. package/dist/tokens/categorical-color.js.map +1 -1
  276. package/dist/tokens/uiSize/icon-size.d.ts +2 -2
  277. package/ds-canonical/fork/governance.lock +1 -1
  278. package/ds-canonical/fork/hooks/check_opacity_token_usage.sh +10 -1
  279. package/ds-canonical/hooks/check_audit_post_report_validator.sh +13 -0
  280. package/ds-canonical/hooks/check_full_story_visual_interaction_sweep.sh +4 -3
  281. package/ds-canonical/hooks/check_opacity_token_usage.sh +10 -1
  282. package/ds-canonical/hooks/check_plugin_fork_health.sh +30 -22
  283. package/ds-canonical/hooks/check_story_invariants.sh +17 -5
  284. package/ds-canonical/hooks/check_substantive_edit_approval_preflight.sh +5 -2
  285. package/ds-canonical/references/principle-dim-map.json +100 -0
  286. package/ds-canonical/references/story-baseline-registry.json +4 -3
  287. package/ds-canonical/skills/deep-audit-cross-codex/SKILL.md +1 -1
  288. package/ds-canonical/skills/design-system-audit/SKILL.md +2 -2
  289. package/ds-story-manifest.json +41 -18
  290. package/llms-full.txt +11 -1
  291. package/llms.txt +3 -1
  292. package/package.json +1 -1
  293. package/src/components/Alert/alert.anatomy.stories.tsx +9 -6
  294. package/src/components/Alert/alert.spec.md +3 -3
  295. package/src/components/Alert/alert.tsx +9 -1
  296. package/src/components/AppShell/app-shell.anatomy.stories.tsx +3 -0
  297. package/src/components/AppShell/app-shell.spec.md +3 -2
  298. package/src/components/AppShell/app-shell.tsx +1 -1
  299. package/src/components/AspectRatio/aspect-ratio.spec.md +1 -2
  300. package/src/components/AspectRatio/aspect-ratio.tsx +3 -2
  301. package/src/components/Avatar/avatar.principles.stories.tsx +1 -1
  302. package/src/components/Avatar/avatar.spec.md +10 -30
  303. package/src/components/Avatar/avatar.stories.tsx +1 -1
  304. package/src/components/Avatar/avatar.tsx +30 -16
  305. package/src/components/Badge/badge.anatomy.stories.tsx +2 -2
  306. package/src/components/Badge/badge.spec.md +5 -4
  307. package/src/components/Badge/badge.tsx +4 -1
  308. package/src/components/Breadcrumb/breadcrumb.anatomy.stories.tsx +2 -2
  309. package/src/components/Breadcrumb/breadcrumb.principles.stories.tsx +15 -10
  310. package/src/components/Breadcrumb/breadcrumb.spec.md +5 -3
  311. package/src/components/Breadcrumb/breadcrumb.tsx +10 -4
  312. package/src/components/BulkActionBar/bulk-action-bar.anatomy.stories.tsx +3 -2
  313. package/src/components/BulkActionBar/bulk-action-bar.spec.md +15 -14
  314. package/src/components/BulkActionBar/bulk-action-bar.tsx +10 -4
  315. package/src/components/Button/button.anatomy.stories.tsx +6 -6
  316. package/src/components/Button/button.spec.md +3 -2
  317. package/src/components/Button/button.tsx +67 -27
  318. package/src/components/Button/index.ts +1 -0
  319. package/src/components/Calendar/calendar.anatomy.stories.tsx +5 -3
  320. package/src/components/Calendar/calendar.principles.stories.tsx +3 -1
  321. package/src/components/Calendar/calendar.spec.md +9 -2
  322. package/src/components/Calendar/calendar.stories.tsx +3 -1
  323. package/src/components/Calendar/calendar.tsx +53 -19
  324. package/src/components/Carousel/carousel.principles.stories.tsx +1 -1
  325. package/src/components/Carousel/carousel.tsx +4 -1
  326. package/src/components/Chart/chart.spec.md +1 -1
  327. package/src/components/Chart/chart.tsx +2 -1
  328. package/src/components/Checkbox/checkbox.principles.stories.tsx +1 -1
  329. package/src/components/Checkbox/checkbox.spec.md +1 -1
  330. package/src/components/Checkbox/checkbox.tsx +41 -7
  331. package/src/components/Checkbox/index.ts +1 -0
  332. package/src/components/Chip/chip.anatomy.stories.tsx +9 -9
  333. package/src/components/Chip/chip.principles.stories.tsx +3 -3
  334. package/src/components/Chip/chip.spec.md +7 -4
  335. package/src/components/Chip/chip.tsx +60 -15
  336. package/src/components/CircularProgress/circular-progress.anatomy.stories.tsx +1 -1
  337. package/src/components/CircularProgress/circular-progress.principles.stories.tsx +7 -5
  338. package/src/components/CircularProgress/circular-progress.spec.md +2 -0
  339. package/src/components/CircularProgress/circular-progress.stories.tsx +3 -11
  340. package/src/components/Coachmark/coachmark.anatomy.stories.tsx +2 -2
  341. package/src/components/Coachmark/coachmark.principles.stories.tsx +1 -1
  342. package/src/components/Coachmark/coachmark.spec.md +3 -3
  343. package/src/components/Coachmark/coachmark.tsx +23 -13
  344. package/src/components/Combobox/combobox.anatomy.stories.tsx +4 -4
  345. package/src/components/Combobox/combobox.spec.md +1 -1
  346. package/src/components/Combobox/combobox.tsx +30 -17
  347. package/src/components/Command/command.principles.stories.tsx +10 -12
  348. package/src/components/Command/command.spec.md +5 -5
  349. package/src/components/Command/command.stories.tsx +4 -4
  350. package/src/components/Command/command.tsx +3 -2
  351. package/src/components/DataTable/active-editor-controller.ts +4 -3
  352. package/src/components/DataTable/cell-registry.tsx +8 -0
  353. package/src/components/DataTable/data-table-filter-panel.tsx +8 -2
  354. package/src/components/DataTable/data-table-interaction-layer.tsx +8 -7
  355. package/src/components/DataTable/data-table.anatomy.stories.tsx +4 -4
  356. package/src/components/DataTable/data-table.css +2 -2
  357. package/src/components/DataTable/data-table.spec.md +72 -115
  358. package/src/components/DataTable/data-table.stories.tsx +51 -3
  359. package/src/components/DataTable/data-table.tsx +348 -63
  360. package/src/components/DataTable/filter-operators.spec.md +4 -4
  361. package/src/components/DataTable/filter-tree.ts +192 -42
  362. package/src/components/DateGrid/date-grid.anatomy.stories.tsx +1 -1
  363. package/src/components/DateGrid/date-grid.spec.md +11 -8
  364. package/src/components/DateGrid/date-grid.tsx +63 -8
  365. package/src/components/DatePicker/date-picker.principles.stories.tsx +1 -1
  366. package/src/components/DatePicker/date-picker.spec.md +1 -0
  367. package/src/components/DatePicker/date-picker.stories.tsx +19 -0
  368. package/src/components/DatePicker/date-picker.tsx +52 -15
  369. package/src/components/DescriptionList/description-list.spec.md +1 -1
  370. package/src/components/DescriptionList/description-list.tsx +3 -1
  371. package/src/components/Dialog/dialog.spec.md +1 -1
  372. package/src/components/Dialog/dialog.tsx +2 -1
  373. package/src/components/DropdownMenu/dropdown-menu.anatomy.stories.tsx +3 -1
  374. package/src/components/DropdownMenu/dropdown-menu.spec.md +8 -5
  375. package/src/components/DropdownMenu/dropdown-menu.tsx +33 -7
  376. package/src/components/Empty/empty.anatomy.stories.tsx +1 -1
  377. package/src/components/Empty/empty.spec.md +2 -2
  378. package/src/components/Empty/empty.stories.tsx +4 -15
  379. package/src/components/Empty/empty.tsx +4 -1
  380. package/src/components/Field/field-controls.spec.md +2 -1
  381. package/src/components/Field/field-wrapper.tsx +28 -8
  382. package/src/components/Field/field.anatomy.stories.tsx +7 -7
  383. package/src/components/Field/field.principles.stories.tsx +1 -1
  384. package/src/components/Field/field.spec.md +9 -7
  385. package/src/components/Field/field.tsx +2 -1
  386. package/src/components/Field/form-validation.spec.md +6 -3
  387. package/src/components/Field/use-form-validation.ts +49 -21
  388. package/src/components/FieldControlGroup/field-control-group.anatomy.stories.tsx +11 -10
  389. package/src/components/FieldControlGroup/field-control-group.principles.stories.tsx +3 -3
  390. package/src/components/FieldControlGroup/field-control-group.spec.md +3 -3
  391. package/src/components/FieldControlGroup/field-control-group.tsx +3 -3
  392. package/src/components/FileItem/file-item.anatomy.stories.tsx +6 -4
  393. package/src/components/FileItem/file-item.tsx +3 -2
  394. package/src/components/FileUpload/file-upload.anatomy.stories.tsx +1 -1
  395. package/src/components/FileUpload/file-upload.spec.md +4 -1
  396. package/src/components/FileUpload/file-upload.tsx +36 -10
  397. package/src/components/FileViewer/can-render-image.ts +15 -0
  398. package/src/components/FileViewer/file-viewer.anatomy.stories.tsx +6 -4
  399. package/src/components/FileViewer/file-viewer.spec.md +2 -2
  400. package/src/components/FileViewer/file-viewer.tsx +47 -21
  401. package/src/components/FileViewer/image-renderer.tsx +3 -8
  402. package/src/components/HoverCard/hover-card.spec.md +1 -1
  403. package/src/components/HoverCard/hover-card.tsx +3 -1
  404. package/src/components/Input/input.anatomy.stories.tsx +4 -4
  405. package/src/components/Input/input.principles.stories.tsx +1 -1
  406. package/src/components/Input/input.spec.md +1 -1
  407. package/src/components/Input/input.stories.tsx +11 -5
  408. package/src/components/Input/input.tsx +11 -9
  409. package/src/components/LinkInput/link-input.anatomy.stories.tsx +2 -0
  410. package/src/components/LinkInput/link-input.tsx +49 -34
  411. package/src/components/Menu/menu-item.anatomy.stories.tsx +1 -1
  412. package/src/components/Menu/menu-item.spec.md +4 -3
  413. package/src/components/Menu/menu-item.tsx +15 -4
  414. package/src/components/Notice/notice.anatomy.stories.tsx +10 -5
  415. package/src/components/Notice/notice.spec.md +4 -3
  416. package/src/components/Notice/notice.tsx +22 -5
  417. package/src/components/NumberInput/number-input.anatomy.stories.tsx +5 -4
  418. package/src/components/NumberInput/number-input.principles.stories.tsx +2 -1
  419. package/src/components/NumberInput/number-input.spec.md +2 -1
  420. package/src/components/NumberInput/number-input.tsx +25 -9
  421. package/src/components/OverflowIndicator/overflow-indicator.anatomy.stories.tsx +3 -3
  422. package/src/components/OverflowIndicator/overflow-indicator.spec.md +1 -1
  423. package/src/components/OverflowIndicator/overflow-indicator.tsx +2 -1
  424. package/src/components/Pagination/index.ts +5 -0
  425. package/src/components/Pagination/pagination.anatomy.stories.tsx +143 -0
  426. package/src/components/Pagination/pagination.principles.stories.tsx +81 -0
  427. package/src/components/Pagination/pagination.spec.md +150 -0
  428. package/src/components/Pagination/pagination.stories.tsx +98 -0
  429. package/src/components/Pagination/pagination.tsx +263 -0
  430. package/src/components/PeoplePicker/people-picker.anatomy.stories.tsx +1 -1
  431. package/src/components/PeoplePicker/people-picker.spec.md +4 -4
  432. package/src/components/PeoplePicker/people-picker.stories.tsx +1 -1
  433. package/src/components/PeoplePicker/people-picker.tsx +65 -55
  434. package/src/components/PeoplePicker/person-display.tsx +10 -4
  435. package/src/components/Popover/popover.anatomy.stories.tsx +2 -2
  436. package/src/components/Popover/popover.spec.md +13 -14
  437. package/src/components/Popover/popover.tsx +88 -39
  438. package/src/components/ProfileCard/profile-card.anatomy.stories.tsx +4 -4
  439. package/src/components/ProfileCard/profile-card.principles.stories.tsx +2 -2
  440. package/src/components/ProfileCard/profile-card.spec.md +1 -2
  441. package/src/components/ProfileCard/profile-card.stories.tsx +1 -1
  442. package/src/components/ProfileCard/profile-card.tsx +29 -8
  443. package/src/components/RadioGroup/radio-group.principles.stories.tsx +1 -1
  444. package/src/components/RadioGroup/radio-group.spec.md +2 -2
  445. package/src/components/RadioGroup/radio-group.stories.tsx +3 -1
  446. package/src/components/RadioGroup/radio-group.tsx +37 -3
  447. package/src/components/Rating/rating.anatomy.stories.tsx +11 -8
  448. package/src/components/Rating/rating.principles.stories.tsx +1 -1
  449. package/src/components/Rating/rating.spec.md +2 -2
  450. package/src/components/Rating/rating.stories.tsx +1 -1
  451. package/src/components/Rating/rating.tsx +3 -3
  452. package/src/components/ScrollArea/scroll-area.spec.md +1 -0
  453. package/src/components/ScrollArea/scroll-area.tsx +1 -1
  454. package/src/components/SegmentedControl/segmented-control.anatomy.stories.tsx +3 -3
  455. package/src/components/SegmentedControl/segmented-control.spec.md +7 -3
  456. package/src/components/SegmentedControl/segmented-control.tsx +29 -11
  457. package/src/components/Select/select.anatomy.stories.tsx +2 -2
  458. package/src/components/Select/select.spec.md +3 -2
  459. package/src/components/Select/select.tsx +62 -19
  460. package/src/components/SelectMenu/select-menu.anatomy.stories.tsx +1 -1
  461. package/src/components/SelectMenu/select-menu.principles.stories.tsx +1 -1
  462. package/src/components/SelectMenu/select-menu.spec.md +12 -8
  463. package/src/components/SelectMenu/select-menu.tsx +176 -20
  464. package/src/components/SelectionControl/selection-item.anatomy.stories.tsx +1 -1
  465. package/src/components/SelectionControl/selection-item.principles.stories.tsx +2 -2
  466. package/src/components/SelectionControl/selection-item.spec.md +2 -1
  467. package/src/components/SelectionControl/selection-item.stories.tsx +1 -1
  468. package/src/components/Separator/separator.anatomy.stories.tsx +2 -2
  469. package/src/components/Separator/separator.tsx +1 -1
  470. package/src/components/Sheet/sheet.spec.md +2 -1
  471. package/src/components/Sheet/sheet.tsx +4 -2
  472. package/src/components/Sidebar/sidebar.anatomy.stories.tsx +1 -1
  473. package/src/components/Sidebar/sidebar.spec.md +2 -1
  474. package/src/components/Sidebar/sidebar.stories.tsx +6 -4
  475. package/src/components/Sidebar/sidebar.tsx +41 -13
  476. package/src/components/Skeleton/skeleton.principles.stories.tsx +1 -1
  477. package/src/components/Skeleton/skeleton.spec.md +1 -2
  478. package/src/components/Skeleton/skeleton.tsx +1 -1
  479. package/src/components/Slider/slider.anatomy.stories.tsx +4 -4
  480. package/src/components/Slider/slider.spec.md +19 -85
  481. package/src/components/Slider/slider.tsx +13 -5
  482. package/src/components/Steps/steps.anatomy.stories.tsx +1 -1
  483. package/src/components/Steps/steps.spec.md +6 -4
  484. package/src/components/Steps/steps.tsx +11 -3
  485. package/src/components/Switch/switch.spec.md +4 -3
  486. package/src/components/Switch/switch.tsx +59 -10
  487. package/src/components/Tabs/tabs.anatomy.stories.tsx +7 -6
  488. package/src/components/Tabs/tabs.principles.stories.tsx +5 -3
  489. package/src/components/Tabs/tabs.spec.md +15 -48
  490. package/src/components/Tabs/tabs.tsx +70 -34
  491. package/src/components/Tag/tag.principles.stories.tsx +2 -2
  492. package/src/components/Tag/tag.spec.md +11 -43
  493. package/src/components/Tag/tag.tsx +30 -4
  494. package/src/components/Textarea/textarea.principles.stories.tsx +2 -1
  495. package/src/components/Textarea/textarea.spec.md +2 -1
  496. package/src/components/Textarea/textarea.tsx +20 -10
  497. package/src/components/TimePicker/time-picker.anatomy.stories.tsx +2 -1
  498. package/src/components/TimePicker/time-picker.spec.md +2 -2
  499. package/src/components/TimePicker/time-picker.tsx +35 -16
  500. package/src/components/Toast/toast.anatomy.stories.tsx +5 -6
  501. package/src/components/Toast/toast.principles.stories.tsx +10 -11
  502. package/src/components/Toast/toast.spec.md +5 -5
  503. package/src/components/Toast/toast.stories.tsx +5 -1
  504. package/src/components/Toast/toast.tsx +13 -7
  505. package/src/components/Tooltip/tooltip.spec.md +3 -3
  506. package/src/components/Tooltip/tooltip.tsx +1 -1
  507. package/src/components/TreeView/tree-view.anatomy.stories.tsx +6 -1
  508. package/src/components/TreeView/tree-view.principles.stories.tsx +2 -2
  509. package/src/components/TreeView/tree-view.spec.md +2 -2
  510. package/src/components/TreeView/tree-view.tsx +27 -5
  511. package/src/hooks/use-controllable.ts +7 -2
  512. package/src/hooks/use-is-touch-device.ts +8 -1
  513. package/src/index.ts +2 -1
  514. package/src/patterns/element-anatomy/element-anatomy.spec.md +3 -3
  515. package/src/patterns/element-anatomy/inline-action.spec.md +1 -1
  516. package/src/patterns/element-anatomy/item-anatomy.spec.md +5 -14
  517. package/src/patterns/element-anatomy/item-anatomy.stories.tsx +1 -0
  518. package/src/patterns/element-anatomy/item-anatomy.tsx +5 -6
  519. package/src/patterns/header-canonical/header-canonical.css +1 -2
  520. package/src/patterns/header-canonical/header-canonical.spec.md +5 -5
  521. package/src/patterns/horizontal-overflow/horizontal-overflow.spec.md +5 -4
  522. package/src/patterns/horizontal-overflow/horizontal-overflow.stories.tsx +137 -0
  523. package/src/patterns/overlay-surface/overlay-surface.spec.md +8 -7
  524. package/src/patterns/overlay-surface/overlay-surface.stories.tsx +116 -0
  525. package/src/patterns/overlay-surface/overlay-surface.tsx +5 -5
  526. package/src/patterns/resize-handle/resize-handle.spec.md +1 -1
  527. package/src/tokens/categorical-color.ts +1 -1
  528. package/src/tokens/color/color.spec.md +11 -13
  529. package/src/tokens/color/color.stories.tsx +1 -1
  530. package/src/tokens/color/primitives.css +7 -5
  531. package/src/tokens/color/semantic.css +8 -2
  532. package/src/tokens/density/density.spec.md +2 -0
  533. package/src/tokens/elevation/elevation.spec.md +1 -1
  534. package/src/tokens/layoutSpace/layoutSpace.spec.md +2 -1
  535. package/src/tokens/layoutSpace/layoutSpace.stories.tsx +3 -3
  536. package/src/tokens/motion/motion.spec.md +1 -1
  537. package/src/tokens/motion/motion.stories.tsx +142 -0
  538. package/src/tokens/opacity/opacity.spec.md +1 -1
  539. package/src/tokens/opacity/opacity.stories.tsx +7 -11
  540. package/src/tokens/radius/radius.spec.md +1 -1
  541. package/src/tokens/typography/typography.css +1 -1
  542. package/src/tokens/typography/typography.spec.md +1 -1
  543. package/src/tokens/uiSize/icon-size.ts +2 -2
  544. package/src/tokens/uiSize/uiSize.spec.md +12 -11
  545. package/src/tokens/uiSize/uiSize.stories.tsx +103 -0
  546. package/src/tokens/utility-registry.json +5 -4
  547. package/dist/node_modules/react-hook-form/dist/index.esm.js +0 -1950
  548. package/dist/node_modules/react-hook-form/dist/index.esm.js.map +0 -1
@@ -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}
@@ -453,7 +462,8 @@ export const peoplePickerMeta = {
453
462
  family: 4,
454
463
  variants: {},
455
464
  sizes: {},
456
- states: ['default', 'hover', 'active', 'focus-visible', 'disabled'],
465
+ // 'active' 移除 trigger 走 Field chrome、menu 走 MenuItem,皆無自有按壓視覺(2026-07-07 詞彙統一 DS-wide 按壓訊號盤點:檔內 0 active: utility / 0 *-active token)。
466
+ states: ['default', 'hover', 'focus-visible', 'disabled'],
457
467
  tokens: {
458
468
  bg: [],
459
469
  fg: ['text-fg-disabled', 'text-fg-muted'],
@@ -43,6 +43,10 @@ export interface PersonData {
43
43
  fields?: { label: string; value: string }[]
44
44
  /** 跳至完整 profile 頁的 handler(hover ProfileCard 必含,不傳時 fallback noop placeholder) */
45
45
  onViewProfile?: () => void
46
+ /** ProfileCard 預設 Chat 按鈕 handler(2026-07-06 user 拍板加接線;未傳 dev-warn,見 ProfileCardDefaultActions) */
47
+ onChat?: () => void
48
+ /** ProfileCard 預設 Audio call 按鈕 handler(ChevronDown 下拉行為留 v2) */
49
+ onCall?: () => void
46
50
  }
47
51
 
48
52
  export type PersonValue = string | PersonData
@@ -71,7 +75,7 @@ function buildPersonProfileCard(person: PersonData): React.ReactNode {
71
75
  employeeNumber: person.employeeNumber,
72
76
  }}
73
77
  fields={person.fields}
74
- actions={<ProfileCardDefaultActions />}
78
+ actions={<ProfileCardDefaultActions onChat={person.onChat} onCall={person.onCall} />}
75
79
  // onViewMore hover context 必含(avatar.spec.md canonical)。consumer 傳
76
80
  // `onViewProfile` 則用真 handler,否則 noop placeholder(UI 仍渲染 View more
77
81
  // footer,避免 preview 變死路)。
@@ -179,8 +183,8 @@ function MultiPersonDisplay({
179
183
  /** 傳入時啟用 dismiss(edit mode),callback 接收被移除的 person */
180
184
  onRemove?: (person: PersonValue) => void
181
185
  }) {
182
- if (!value || value.length === 0) return <span className="text-fg-muted">{EMPTY_DISPLAY}</span>
183
-
186
+ // 2026-07-05 D3 P1 修(React #310 家族):3 hooks 原在 empty early return 之後 — value 非空↔空
187
+ // 切換時 hook 數 0↔3 變動必 crash(現靠 consumer 三元切換 element type 偶然避開)。hoist 到 return 前。
184
188
  // 2026-05-15 Bug 3 fix(Claude+Codex Step 5 比稿 consensus):消費 shared `avatar-stack-overflow`
185
189
  // primitive。原 inline canvas-based formula 是 dual-implementation 違反 user SSOT「同 cell width 同
186
190
  // overflow 判斷」(edit path 用 Combobox useOverflowCount DOM offsetWidth / display path 用 inline
@@ -189,7 +193,7 @@ function MultiPersonDisplay({
189
193
  const containerRef = React.useRef<HTMLSpanElement>(null)
190
194
  const [measuredCount, setMeasuredCount] = React.useState<number | null>(null)
191
195
  React.useLayoutEffect(() => {
192
- if (!measured) return
196
+ if (!measured || !value || value.length === 0) return
193
197
  const el = containerRef.current
194
198
  if (!el) return
195
199
  const calc = () => {
@@ -207,6 +211,8 @@ function MultiPersonDisplay({
207
211
  return () => ro.disconnect()
208
212
  }, [measured, size, value])
209
213
 
214
+ if (!value || value.length === 0) return <span className="text-fg-muted">{EMPTY_DISPLAY}</span>
215
+
210
216
  const resolvedMax = measured && measuredCount !== null ? measuredCount : (max ?? 3)
211
217
  const people = value.map(resolvePerson)
212
218
  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>
@@ -273,7 +273,7 @@ export const Accessibility = {
273
273
  render: () => (
274
274
  <div className="max-w-3xl text-body text-fg-secondary">
275
275
  <h3 className="text-h5 text-foreground mb-2">無障礙設計</h3>
276
- <p className="whitespace-pre-line">{"焦點 / 鍵盤 / ARIA 行為(部分為 DS 覆寫,部分為 Radix 內建):\n\n- 開啟焦點(DS 覆寫) :DS 以 onOpenAutoFocus 覆寫 Radix 預設,開啟時把焦點落在 body 第一個可互動元素(避免 Radix 預設先 focus 右上 close X,觸發 tooltip leak)\n- 關閉返回(Radix 內建) :關閉時 focus return to trigger\n- Esc 關閉(Radix 內建) :按 Esc 自動關閉並返回焦點\n- Focus trap(Radix 內建,僅 modal) : modal={true} 時焦點鎖在 content 內\n- 點外 / 焦點離開即關(Radix 內建,non-modal) :預設 non-modal 不鎖焦點,焦點或指標離開 content 樹即自動關閉(dismiss,非 focus trap)\n- ARIA(Radix 內建) :trigger 自動 aria-expanded / aria-controls ,content role=\"dialog\" \n\nConsumer 無需額外處理 a11y,保留 Radix data-state 屬性即可。"}</p>
276
+ <p className="whitespace-pre-line">{"焦點 / 鍵盤 / ARIA 行為(部分為 DS 覆寫,部分為 Radix 內建):\n\n- 開啟焦點(DS 覆寫) :DS 以 onOpenAutoFocus 覆寫 Radix 預設,開啟時把焦點落在 body 第一個可互動元素(避免 Radix 預設先 focus 右上 close X,觸發 tooltip leak)\n- 關閉返回(Radix 內建) :關閉時 focus return to trigger\n- Esc 關閉(Radix 內建) :按 Esc 自動關閉並返回焦點\n- Focus trap(Radix 內建,僅 modal) : modal={true} 時焦點鎖在 content 內\n- 點外 / 焦點離開即關(Radix 內建,non-modal) :預設 non-modal 不鎖焦點,焦點或指標離開 content 樹即自動關閉(dismiss,非 focus trap)\n- ARIA(Radix 內建) :trigger 自動 aria-expanded / aria-controls ,content role=\"dialog\" \n- Accessible name(DS 覆寫) :content role=\"dialog\" 預設無 accessible name, PopoverTitle 掛載時自動接上 content 的 aria-labelledby (consumer 自傳 aria-label / aria-labelledby 優先)\n\n保留 Radix data-state 屬性即可;無 PopoverTitle 的 naked popover 需 consumer 自傳 aria-label ,否則 role=\"dialog\" 無 accessible name。"}</p>
277
277
  </div>
278
278
  ),
279
279
  }
@@ -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
 
@@ -170,18 +170,17 @@ canonical 判斷:「使用者 click 單項是否立即改變系統狀態?」是
170
170
  - **Focus trap(Radix 內建,僅 modal)**:`modal={true}` 時焦點鎖在 content 內
171
171
  - **點外 / 焦點離開即關(Radix 內建,non-modal)**:預設 non-modal **不鎖焦點**(無 focus trap),焦點或指標離開 content 樹時由 DismissableLayer 觸發 dismiss 自動關閉——這是 dismiss-on-focus-out 機制,**不是** focus trap
172
172
  - **ARIA(Radix 內建)**:trigger 自動 `aria-expanded` / `aria-controls`,content `role="dialog"`
173
+ - **Accessible name(DS 覆寫)**:Radix Popover 沒有 Radix Dialog Title 式的自動接線,content `role="dialog"` 預設**無 accessible name**。DS 以 context 補同機制(見 `popover.tsx` `PopoverTitleContext`):`<PopoverTitle>` 掛載時自動把 id 接上 content 的 `aria-labelledby`;consumer 自傳 `aria-label` / `aria-labelledby` 一律優先
173
174
 
174
- Consumer 無需額外處理 a11y,保留 Radix `data-state` 屬性即可(開啟焦點的 DS 覆寫 default 已 wire 在 `PopoverContent`,consumer 可傳自己的 `onOpenAutoFocus` override)。
175
+ 焦點與 accessible name DS 覆寫 default 已 wire 在 `PopoverContent`(consumer 可傳自己的 `onOpenAutoFocus` override),保留 Radix `data-state` 屬性即可。**無 `<PopoverTitle>` 的 naked popover 需 consumer 自傳 `aria-label`**,否則 `role="dialog"` 無 accessible name
175
176
 
176
177
  ---
177
178
 
178
- ## 為何無 Inspector
179
+ ## Inspector
179
180
 
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 覆蓋。
181
+ `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
182
 
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)。
183
+ 對應 anatomy story:保留 `Overview` + `Inspector` + 元件特有 `PlacementMatrix`(12-cell side × align 矩陣) + `ColorMatrix` + `SizeMatrix` + `StateBehavior`(open / close / animation) + `Accessibility`。
185
184
 
186
185
  ---
187
186
 
@@ -33,49 +33,84 @@ const PopoverAnchor = PopoverPrimitive.Anchor
33
33
  const PopoverClose = PopoverPrimitive.Close
34
34
 
35
35
  // AutoFocus canonical(對齊 Dialog / Sheet / Material / Polaris)—
36
- // 開啟時 focus 落在 body 第一個有意義互動元素,避免 focus 到 close X 觸發 tooltip leak
36
+ // 開啟時 focus 落在 body 第一個有意義互動元素,避免 focus 到 close X 觸發 tooltip leak
37
+ // 兩段查詢對齊 dialog.tsx handleOpenAutoFocus(2026-07-05 修):舊版單一混合清單按 DOM 序取
38
+ // 第一 match,header 內互動元素(非 data-dismiss)會搶走 body 首元素焦點,且 bare 段漏 select。
39
+ // 改為:先 body-scoped 查詢,查無才退 bare 全域清單(保留 naked popover — consumer 不用
40
+ // PopoverBody 自管結構 — 的 fallback 意圖),最後 footer → content 容器。
37
41
  const handlePopoverOpenAutoFocus = (e: Event) => {
38
42
  e.preventDefault()
39
43
  const content = e.currentTarget as HTMLElement
40
44
  const firstBodyTarget = content.querySelector<HTMLElement>(
41
- '[data-popover-body] input:not([disabled]),[data-popover-body] textarea:not([disabled]),[data-popover-body] select:not([disabled]),[data-popover-body] button:not([disabled]):not([data-dismiss]),input:not([disabled]),textarea:not([disabled]),button:not([disabled]):not([data-dismiss])'
45
+ '[data-popover-body] input:not([disabled]),[data-popover-body] textarea:not([disabled]),[data-popover-body] select:not([disabled]),[data-popover-body] button:not([disabled]):not([data-dismiss])'
46
+ )
47
+ const firstBareTarget = content.querySelector<HTMLElement>(
48
+ 'input:not([disabled]),textarea:not([disabled]),select:not([disabled]),button:not([disabled]):not([data-dismiss])'
42
49
  )
43
50
  const firstFooterButton = content.querySelector<HTMLElement>(
44
51
  '[data-popover-footer] button:not([disabled]):not([data-dismiss])'
45
52
  )
46
- ;(firstBodyTarget ?? firstFooterButton ?? content).focus({ preventScroll: true })
53
+ ;(firstBodyTarget ?? firstBareTarget ?? firstFooterButton ?? content).focus({ preventScroll: true })
54
+ }
55
+
56
+ // ── PopoverTitle → aria-labelledby 自動接線(2026-07-05,照 Radix Dialog Title 機制補 DS 層)──
57
+ // Radix Popover 的 content 有 role="dialog",但沒有 Radix Dialog Title 式的自動 accessible name
58
+ // 接線 → 一般 Popover 預設無 accessible name。DS 層以 context 補同機制:PopoverContent 以 useId
59
+ // 建 titleId 供給 children;PopoverTitle 掛載時註冊,PopoverContent 偵測到有 title 註冊才設
60
+ // aria-labelledby。Consumer 自傳 aria-label / aria-labelledby 一律優先。PopoverTitle 亦被
61
+ // DataTable sort / filter panel 等非 PopoverContent 場景裸用(raw SurfaceHeader chrome)—
62
+ // context 為 null 時全部 no-op,行為與舊版一致。
63
+ interface PopoverTitleContextValue {
64
+ titleId: string
65
+ onTitleMount: () => void
66
+ onTitleUnmount: () => void
47
67
  }
48
68
 
69
+ const PopoverTitleContext = React.createContext<PopoverTitleContextValue | null>(null)
70
+
49
71
  const PopoverContent = React.forwardRef<
50
72
  React.ElementRef<typeof PopoverPrimitive.Content>,
51
73
  React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content>
52
- >(({ className, align = "center", sideOffset = OVERLAY_SIDE_OFFSET, collisionPadding = OVERLAY_COLLISION_PADDING, onOpenAutoFocus, ...props }, ref) => (
53
- <PopoverPrimitive.Portal>
54
- <PopoverPrimitive.Content
55
- ref={ref}
56
- align={align}
57
- sideOffset={sideOffset}
58
- collisionPadding={collisionPadding}
59
- // Layout-space lock(2026-06-15 canonical,原 data-density="md" 改為只鎖 layout-space):Popover 是
60
- // 輕量浮層,header/footer 用 py-tight(layout-space)→ 鎖 layout-space=md 保持精簡 padding;但
61
- // **ui-size 不鎖、繼承 page** → 內部 field 控件 / dismiss 按鈕隨 page 放大,跟觸發點一致(decouple)。
62
- // [data-layout-space="md"] 有 reset selector(layoutSpace.css L31)→ lg page 上正確 reset 回 md。
63
- data-layout-space="md"
64
- onOpenAutoFocus={onOpenAutoFocus ?? handlePopoverOpenAutoFocus}
65
- className={cn(
66
- "z-50 w-72 rounded-lg border border-border bg-surface-raised text-foreground shadow-[var(--elevation-200)] outline-none",
67
- // 2026-05-04 viewport-aware max-h SSOT(從 ProfileCard 升 DS-wide):header/footer 永遠 in-viewport,body 壓縮 scroll
68
- // 2026-05-05 audit dim 35 補:加 `min-h-0` 完成 M25 chain invariant(flex item default min-h: auto 阻 shrink)
69
- "max-h-[var(--radix-popover-content-available-height,100vh)] flex flex-col overflow-hidden min-h-0",
70
- "data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 motion-reduce:animate-none",
71
- "data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
72
- "origin-[var(--radix-popover-content-transform-origin)]",
73
- className
74
- )}
75
- {...props}
76
- />
77
- </PopoverPrimitive.Portal>
78
- ))
74
+ >(({ className, align = "center", sideOffset = OVERLAY_SIDE_OFFSET, collisionPadding = OVERLAY_COLLISION_PADDING, onOpenAutoFocus, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, ...props }, ref) => {
75
+ const titleId = React.useId()
76
+ const [hasTitle, setHasTitle] = React.useState(false)
77
+ const titleContext = React.useMemo<PopoverTitleContextValue>(
78
+ () => ({ titleId, onTitleMount: () => setHasTitle(true), onTitleUnmount: () => setHasTitle(false) }),
79
+ [titleId],
80
+ )
81
+ return (
82
+ <PopoverPrimitive.Portal>
83
+ <PopoverTitleContext.Provider value={titleContext}>
84
+ <PopoverPrimitive.Content
85
+ ref={ref}
86
+ align={align}
87
+ sideOffset={sideOffset}
88
+ collisionPadding={collisionPadding}
89
+ // Layout-space lock(2026-06-15 canonical,原 data-density="md" 改為只鎖 layout-space):Popover
90
+ // 輕量浮層,header/footer py-tight(layout-space)→ layout-space=md 保持精簡 padding;但
91
+ // **ui-size 不鎖、繼承 page** 內部 field 控件 / dismiss 按鈕隨 page 放大,跟觸發點一致(decouple)。
92
+ // [data-layout-space="md"] reset selector(layoutSpace.css L31)→ lg page 上正確 reset 回 md。
93
+ data-layout-space="md"
94
+ onOpenAutoFocus={onOpenAutoFocus ?? handlePopoverOpenAutoFocus}
95
+ aria-label={ariaLabel}
96
+ // accessible name 自動接線:consumer 自傳優先;無 aria-label 且有 PopoverTitle 註冊才掛 titleId
97
+ aria-labelledby={ariaLabelledBy ?? (ariaLabel == null && hasTitle ? titleId : undefined)}
98
+ className={cn(
99
+ "z-50 w-72 rounded-lg border border-border bg-surface-raised text-foreground shadow-[var(--elevation-200)] outline-none",
100
+ // 2026-05-04 viewport-aware max-h SSOT(從 ProfileCard 升 DS-wide):header/footer 永遠 in-viewport,body 壓縮 scroll
101
+ // 2026-05-05 audit dim 35 補:加 `min-h-0` 完成 M25 chain invariant(flex item default min-h: auto 阻 shrink)
102
+ "max-h-[var(--radix-popover-content-available-height,100vh)] flex flex-col overflow-hidden min-h-0",
103
+ "data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 motion-reduce:animate-none",
104
+ "data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
105
+ "origin-[var(--radix-popover-content-transform-origin)]",
106
+ className
107
+ )}
108
+ {...props}
109
+ />
110
+ </PopoverTitleContext.Provider>
111
+ </PopoverPrimitive.Portal>
112
+ )
113
+ })
79
114
  PopoverContent.displayName = PopoverPrimitive.Content.displayName
80
115
 
81
116
  // PopoverHeader: SurfaceHeader + Close X(對齊 Dialog 的 canonical,見 docblock)
@@ -134,8 +169,8 @@ PopoverFooter.displayName = "PopoverFooter"
134
169
  // PopoverTitle(2026-04-22 v4 non-modal canonical):`text-body font-medium`(14px)
135
170
  // Rationale:Popover / Coachmark 屬 **non-modal 輕量浮層**,跟 density 鎖 md 同源的視覺語言 —
136
171
  // chrome 全體輕量:
137
- // - density 鎖 md(不隨 page 放大)
138
- // - dismiss X 透過 v5 unbounded trick layout 佔位 24
172
+ // - layout-space 鎖 md(ui-size 繼承 page,2026-06-15)
173
+ // - dismiss X COMPACT_HEADER_SLOT 佔位 21(= title text-body line-box;見 PopoverHeader docblock)
139
174
  // - **title `text-body`(14px)跟 Dialog / Sheet modal 的 body-lg(16px)形成重量級 vs 輕量級視覺區分**
140
175
  // 世界級對照:Figma / Notion / Linear / Material 的 popover / filter panel / inline settings
141
176
  // 的 header title 多半比 modal dialog title 小一級(16→14 或同比),視覺宣告「此浮層可忽略」
@@ -145,13 +180,27 @@ PopoverFooter.displayName = "PopoverFooter"
145
180
  const PopoverTitle = React.forwardRef<
146
181
  HTMLHeadingElement,
147
182
  React.HTMLAttributes<HTMLHeadingElement>
148
- >(({ className, ...props }, ref) => (
149
- <h2
150
- ref={ref}
151
- className={cn("text-body font-medium truncate", className)}
152
- {...props}
153
- />
154
- ))
183
+ >(({ className, id, ...props }, ref) => {
184
+ const titleCtx = React.useContext(PopoverTitleContext)
185
+ // 掛載時向 PopoverContent 註冊 → content 自動設 aria-labelledby(見 PopoverTitleContext docblock)。
186
+ // consumer 自傳 id 時不註冊:auto 接線指向 context titleId,自訂 id 下引用會斷 → aria 由 consumer 自管。
187
+ const shouldRegister = id == null && titleCtx != null
188
+ const onTitleMount = titleCtx?.onTitleMount
189
+ const onTitleUnmount = titleCtx?.onTitleUnmount
190
+ React.useLayoutEffect(() => {
191
+ if (!shouldRegister) return
192
+ onTitleMount?.()
193
+ return onTitleUnmount
194
+ }, [shouldRegister, onTitleMount, onTitleUnmount])
195
+ return (
196
+ <h2
197
+ ref={ref}
198
+ id={id ?? titleCtx?.titleId}
199
+ className={cn("text-body font-medium truncate", className)}
200
+ {...props}
201
+ />
202
+ )
203
+ })
155
204
  PopoverTitle.displayName = "PopoverTitle"
156
205
 
157
206
  // Story auto-compile metadata — Phase 1 mechanical migration(2026-04-24)
@@ -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