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

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 (622) hide show
  1. package/CLAUDE.md +8 -8
  2. package/README.md +3 -2
  3. package/dist/components/Alert/alert.js.map +1 -1
  4. package/dist/components/AppShell/_demo-helpers.d.ts.map +1 -1
  5. package/dist/components/AppShell/app-shell.d.ts +1 -1
  6. package/dist/components/AppShell/app-shell.d.ts.map +1 -1
  7. package/dist/components/AppShell/app-shell.js +22 -2
  8. package/dist/components/AppShell/app-shell.js.map +1 -1
  9. package/dist/components/Avatar/avatar.d.ts.map +1 -1
  10. package/dist/components/Avatar/avatar.js +4 -4
  11. package/dist/components/Avatar/avatar.js.map +1 -1
  12. package/dist/components/Breadcrumb/breadcrumb.d.ts.map +1 -1
  13. package/dist/components/Breadcrumb/breadcrumb.js +1 -1
  14. package/dist/components/Breadcrumb/breadcrumb.js.map +1 -1
  15. package/dist/components/Button/button.d.ts +2 -2
  16. package/dist/components/Button/button.d.ts.map +1 -1
  17. package/dist/components/Button/button.js +12 -3
  18. package/dist/components/Button/button.js.map +1 -1
  19. package/dist/components/Calendar/calendar.d.ts +13 -5
  20. package/dist/components/Calendar/calendar.d.ts.map +1 -1
  21. package/dist/components/Calendar/calendar.js +7 -1
  22. package/dist/components/Calendar/calendar.js.map +1 -1
  23. package/dist/components/Carousel/carousel.d.ts.map +1 -1
  24. package/dist/components/Carousel/carousel.js +8 -3
  25. package/dist/components/Carousel/carousel.js.map +1 -1
  26. package/dist/components/Chart/chart.d.ts.map +1 -1
  27. package/dist/components/Chart/chart.js +1 -0
  28. package/dist/components/Chart/chart.js.map +1 -1
  29. package/dist/components/Checkbox/checkbox.d.ts +1 -1
  30. package/dist/components/Checkbox/checkbox.d.ts.map +1 -1
  31. package/dist/components/Checkbox/checkbox.js +8 -4
  32. package/dist/components/Checkbox/checkbox.js.map +1 -1
  33. package/dist/components/Chip/chip.d.ts +1 -1
  34. package/dist/components/Chip/chip.d.ts.map +1 -1
  35. package/dist/components/Chip/chip.js.map +1 -1
  36. package/dist/components/Coachmark/coachmark.d.ts +10 -3
  37. package/dist/components/Coachmark/coachmark.d.ts.map +1 -1
  38. package/dist/components/Coachmark/coachmark.js.map +1 -1
  39. package/dist/components/Combobox/combobox.d.ts +5 -1
  40. package/dist/components/Combobox/combobox.d.ts.map +1 -1
  41. package/dist/components/Combobox/combobox.js +30 -13
  42. package/dist/components/Combobox/combobox.js.map +1 -1
  43. package/dist/components/DataTable/cell-registry.d.ts.map +1 -1
  44. package/dist/components/DataTable/cell-registry.js +53 -62
  45. package/dist/components/DataTable/cell-registry.js.map +1 -1
  46. package/dist/components/DataTable/column-types.d.ts +10 -4
  47. package/dist/components/DataTable/column-types.d.ts.map +1 -1
  48. package/dist/components/DataTable/column-types.js.map +1 -1
  49. package/dist/components/DataTable/data-table-column-visibility-panel.d.ts +1 -1
  50. package/dist/components/DataTable/data-table-column-visibility-panel.js +183 -0
  51. package/dist/components/DataTable/data-table-column-visibility-panel.js.map +1 -0
  52. package/dist/components/DataTable/data-table-filter-group.d.ts +47 -0
  53. package/dist/components/DataTable/data-table-filter-group.d.ts.map +1 -0
  54. package/dist/components/DataTable/data-table-filter-group.js +154 -0
  55. package/dist/components/DataTable/data-table-filter-group.js.map +1 -0
  56. package/dist/components/DataTable/data-table-filter-panel.d.ts.map +1 -1
  57. package/dist/components/DataTable/data-table-filter-panel.js +285 -0
  58. package/dist/components/DataTable/data-table-filter-panel.js.map +1 -0
  59. package/dist/components/DataTable/data-table-filter-value-picker.d.ts +29 -0
  60. package/dist/components/DataTable/data-table-filter-value-picker.d.ts.map +1 -0
  61. package/dist/components/DataTable/data-table-filter-value-picker.js +187 -0
  62. package/dist/components/DataTable/data-table-filter-value-picker.js.map +1 -0
  63. package/dist/components/DataTable/data-table-interaction-layer.d.ts +11 -15
  64. package/dist/components/DataTable/data-table-interaction-layer.d.ts.map +1 -1
  65. package/dist/components/DataTable/data-table-interaction-layer.js +1 -3
  66. package/dist/components/DataTable/data-table-interaction-layer.js.map +1 -1
  67. package/dist/components/DataTable/data-table-sort-manager.js +140 -0
  68. package/dist/components/DataTable/data-table-sort-manager.js.map +1 -0
  69. package/dist/components/DataTable/data-table.d.ts +5 -6
  70. package/dist/components/DataTable/data-table.d.ts.map +1 -1
  71. package/dist/components/DataTable/data-table.js +36 -15
  72. package/dist/components/DataTable/data-table.js.map +1 -1
  73. package/dist/components/DataTable/filter-operators.js +140 -0
  74. package/dist/components/DataTable/filter-operators.js.map +1 -0
  75. package/dist/components/DataTable/filter-tree.js +312 -0
  76. package/dist/components/DataTable/filter-tree.js.map +1 -0
  77. package/dist/components/DataTable/index.d.ts +3 -0
  78. package/dist/components/DataTable/index.d.ts.map +1 -1
  79. package/dist/components/DataTable/index.js +12 -1
  80. package/dist/components/DataTable/index.js.map +1 -1
  81. package/dist/components/DataTable/lib/column-meta.js +25 -0
  82. package/dist/components/DataTable/lib/column-meta.js.map +1 -0
  83. package/dist/components/DateGrid/date-grid.d.ts.map +1 -1
  84. package/dist/components/DateGrid/date-grid.js +3 -1
  85. package/dist/components/DateGrid/date-grid.js.map +1 -1
  86. package/dist/components/DatePicker/date-picker.d.ts +7 -1
  87. package/dist/components/DatePicker/date-picker.d.ts.map +1 -1
  88. package/dist/components/DatePicker/date-picker.js +105 -74
  89. package/dist/components/DatePicker/date-picker.js.map +1 -1
  90. package/dist/components/Dialog/dialog.d.ts +20 -3
  91. package/dist/components/Dialog/dialog.d.ts.map +1 -1
  92. package/dist/components/Dialog/dialog.js +38 -20
  93. package/dist/components/Dialog/dialog.js.map +1 -1
  94. package/dist/components/DropdownMenu/dropdown-menu.d.ts.map +1 -1
  95. package/dist/components/DropdownMenu/dropdown-menu.js +3 -1
  96. package/dist/components/DropdownMenu/dropdown-menu.js.map +1 -1
  97. package/dist/components/Field/field-context.d.ts +45 -5
  98. package/dist/components/Field/field-context.d.ts.map +1 -1
  99. package/dist/components/Field/field-context.js +19 -0
  100. package/dist/components/Field/field-context.js.map +1 -1
  101. package/dist/components/Field/field-edit-keys.d.ts +21 -0
  102. package/dist/components/Field/field-edit-keys.d.ts.map +1 -0
  103. package/dist/components/Field/field-edit-keys.js +19 -0
  104. package/dist/components/Field/field-edit-keys.js.map +1 -0
  105. package/dist/components/Field/field-types.d.ts +20 -1
  106. package/dist/components/Field/field-types.d.ts.map +1 -1
  107. package/dist/components/Field/field-types.js.map +1 -1
  108. package/dist/components/Field/field-wrapper.d.ts +3 -2
  109. package/dist/components/Field/field-wrapper.d.ts.map +1 -1
  110. package/dist/components/Field/field-wrapper.js +38 -39
  111. package/dist/components/Field/field-wrapper.js.map +1 -1
  112. package/dist/components/Field/field.d.ts +2 -1
  113. package/dist/components/Field/field.d.ts.map +1 -1
  114. package/dist/components/Field/field.js.map +1 -1
  115. package/dist/components/Field/index.d.ts +1 -0
  116. package/dist/components/Field/index.d.ts.map +1 -1
  117. package/dist/components/Field/index.js +2 -0
  118. package/dist/components/Field/index.js.map +1 -1
  119. package/dist/components/FieldControlGroup/field-control-group.d.ts +1 -1
  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 +1 -1
  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.js.map +1 -1
  126. package/dist/components/FileViewer/file-viewer.d.ts.map +1 -1
  127. package/dist/components/FileViewer/file-viewer.js +7 -2
  128. package/dist/components/FileViewer/file-viewer.js.map +1 -1
  129. package/dist/components/FileViewer/image-renderer.d.ts.map +1 -1
  130. package/dist/components/FileViewer/image-renderer.js +22 -1
  131. package/dist/components/FileViewer/image-renderer.js.map +1 -1
  132. package/dist/components/HoverCard/hover-card.d.ts.map +1 -1
  133. package/dist/components/HoverCard/hover-card.js +5 -3
  134. package/dist/components/HoverCard/hover-card.js.map +1 -1
  135. package/dist/components/InlineEdit/index.d.ts +2 -0
  136. package/dist/components/InlineEdit/index.d.ts.map +1 -0
  137. package/dist/components/InlineEdit/index.js +5 -0
  138. package/dist/components/InlineEdit/index.js.map +1 -0
  139. package/dist/components/InlineEdit/inline-edit.d.ts +59 -0
  140. package/dist/components/InlineEdit/inline-edit.d.ts.map +1 -0
  141. package/dist/components/InlineEdit/inline-edit.js +164 -0
  142. package/dist/components/InlineEdit/inline-edit.js.map +1 -0
  143. package/dist/components/Input/input.d.ts +6 -11
  144. package/dist/components/Input/input.d.ts.map +1 -1
  145. package/dist/components/Input/input.js +43 -13
  146. package/dist/components/Input/input.js.map +1 -1
  147. package/dist/components/LinkInput/link-input.d.ts +2 -2
  148. package/dist/components/LinkInput/link-input.d.ts.map +1 -1
  149. package/dist/components/LinkInput/link-input.js +6 -5
  150. package/dist/components/LinkInput/link-input.js.map +1 -1
  151. package/dist/components/Menu/menu-item.d.ts +14 -2
  152. package/dist/components/Menu/menu-item.d.ts.map +1 -1
  153. package/dist/components/Menu/menu-item.js +25 -3
  154. package/dist/components/Menu/menu-item.js.map +1 -1
  155. package/dist/components/NumberInput/number-input.d.ts +3 -3
  156. package/dist/components/NumberInput/number-input.d.ts.map +1 -1
  157. package/dist/components/NumberInput/number-input.js +6 -5
  158. package/dist/components/NumberInput/number-input.js.map +1 -1
  159. package/dist/components/OverflowIndicator/overflow-indicator.d.ts.map +1 -1
  160. package/dist/components/OverflowIndicator/overflow-indicator.js +2 -6
  161. package/dist/components/OverflowIndicator/overflow-indicator.js.map +1 -1
  162. package/dist/components/Pagination/pagination.js +1 -1
  163. package/dist/components/Pagination/pagination.js.map +1 -1
  164. package/dist/components/PeoplePicker/index.js +7 -1
  165. package/dist/components/PeoplePicker/index.js.map +1 -1
  166. package/dist/components/PeoplePicker/people-picker.d.ts +9 -2
  167. package/dist/components/PeoplePicker/people-picker.d.ts.map +1 -1
  168. package/dist/components/PeoplePicker/people-picker.js +23 -10
  169. package/dist/components/PeoplePicker/people-picker.js.map +1 -1
  170. package/dist/components/PeoplePicker/person-display.d.ts +6 -3
  171. package/dist/components/PeoplePicker/person-display.d.ts.map +1 -1
  172. package/dist/components/PeoplePicker/person-display.js +19 -14
  173. package/dist/components/PeoplePicker/person-display.js.map +1 -1
  174. package/dist/components/Popover/popover.d.ts.map +1 -1
  175. package/dist/components/Popover/popover.js +3 -1
  176. package/dist/components/Popover/popover.js.map +1 -1
  177. package/dist/components/ProfileCard/profile-card.d.ts +2 -2
  178. package/dist/components/ProfileCard/profile-card.js +3 -3
  179. package/dist/components/ProfileCard/profile-card.js.map +1 -1
  180. package/dist/components/RadioGroup/radio-group.d.ts.map +1 -1
  181. package/dist/components/RadioGroup/radio-group.js +15 -9
  182. package/dist/components/RadioGroup/radio-group.js.map +1 -1
  183. package/dist/components/ScrollArea/scroll-area.d.ts +11 -1
  184. package/dist/components/ScrollArea/scroll-area.d.ts.map +1 -1
  185. package/dist/components/ScrollArea/scroll-area.js +6 -3
  186. package/dist/components/ScrollArea/scroll-area.js.map +1 -1
  187. package/dist/components/Select/select.d.ts +46 -5
  188. package/dist/components/Select/select.d.ts.map +1 -1
  189. package/dist/components/Select/select.js +109 -28
  190. package/dist/components/Select/select.js.map +1 -1
  191. package/dist/components/SelectMenu/select-menu.d.ts +3 -1
  192. package/dist/components/SelectMenu/select-menu.d.ts.map +1 -1
  193. package/dist/components/SelectMenu/select-menu.js +8 -1
  194. package/dist/components/SelectMenu/select-menu.js.map +1 -1
  195. package/dist/components/SelectionControl/boolean-value.d.ts +11 -0
  196. package/dist/components/SelectionControl/boolean-value.d.ts.map +1 -0
  197. package/dist/components/SelectionControl/boolean-value.js +23 -0
  198. package/dist/components/SelectionControl/boolean-value.js.map +1 -0
  199. package/dist/components/SelectionControl/index.d.ts +1 -0
  200. package/dist/components/SelectionControl/index.d.ts.map +1 -1
  201. package/dist/components/SelectionControl/index.js +2 -0
  202. package/dist/components/SelectionControl/index.js.map +1 -1
  203. package/dist/components/Sheet/index.js +2 -1
  204. package/dist/components/Sheet/sheet.d.ts +7 -1
  205. package/dist/components/Sheet/sheet.d.ts.map +1 -1
  206. package/dist/components/Sheet/sheet.js +5 -2
  207. package/dist/components/Sheet/sheet.js.map +1 -1
  208. package/dist/components/Sidebar/sidebar.d.ts +9 -4
  209. package/dist/components/Sidebar/sidebar.d.ts.map +1 -1
  210. package/dist/components/Sidebar/sidebar.js +8 -10
  211. package/dist/components/Sidebar/sidebar.js.map +1 -1
  212. package/dist/components/Slider/slider.d.ts.map +1 -1
  213. package/dist/components/Slider/slider.js.map +1 -1
  214. package/dist/components/Switch/switch.d.ts +6 -6
  215. package/dist/components/Switch/switch.d.ts.map +1 -1
  216. package/dist/components/Switch/switch.js +3 -2
  217. package/dist/components/Switch/switch.js.map +1 -1
  218. package/dist/components/Tabs/tabs.d.ts.map +1 -1
  219. package/dist/components/Tabs/tabs.js +15 -1
  220. package/dist/components/Tabs/tabs.js.map +1 -1
  221. package/dist/components/Textarea/textarea.d.ts +2 -2
  222. package/dist/components/Textarea/textarea.d.ts.map +1 -1
  223. package/dist/components/Textarea/textarea.js +50 -42
  224. package/dist/components/Textarea/textarea.js.map +1 -1
  225. package/dist/components/TimePicker/time-columns.d.ts.map +1 -1
  226. package/dist/components/TimePicker/time-columns.js +42 -38
  227. package/dist/components/TimePicker/time-columns.js.map +1 -1
  228. package/dist/components/TimePicker/time-picker.d.ts.map +1 -1
  229. package/dist/components/TimePicker/time-picker.js +8 -6
  230. package/dist/components/TimePicker/time-picker.js.map +1 -1
  231. package/dist/components/Tooltip/tooltip.d.ts.map +1 -1
  232. package/dist/components/Tooltip/tooltip.js +7 -5
  233. package/dist/components/Tooltip/tooltip.js.map +1 -1
  234. package/dist/components/TreeView/tree-view.d.ts +56 -1
  235. package/dist/components/TreeView/tree-view.d.ts.map +1 -1
  236. package/dist/components/TreeView/tree-view.js +211 -12
  237. package/dist/components/TreeView/tree-view.js.map +1 -1
  238. package/dist/index.d.ts +115 -58
  239. package/dist/index.d.ts.map +1 -1
  240. package/dist/index.js +95 -108
  241. package/dist/index.js.map +1 -1
  242. package/dist/patterns/element-anatomy/item-anatomy.d.ts +7 -6
  243. package/dist/patterns/element-anatomy/item-anatomy.d.ts.map +1 -1
  244. package/dist/patterns/element-anatomy/item-anatomy.js.map +1 -1
  245. package/dist/tokens/categorical-color.js +15 -0
  246. package/dist/tokens/categorical-color.js.map +1 -1
  247. package/dist/tokens/motion/motion.d.ts +7 -6
  248. package/dist/tokens/motion/motion.d.ts.map +1 -1
  249. package/dist/tokens/motion/motion.js +6 -6
  250. package/dist/tokens/motion/motion.js.map +1 -1
  251. package/dist/tokens/motion/overlay-motion.d.ts +19 -0
  252. package/dist/tokens/motion/overlay-motion.d.ts.map +1 -0
  253. package/dist/tokens/motion/overlay-motion.js +9 -0
  254. package/dist/tokens/motion/overlay-motion.js.map +1 -0
  255. package/dist/tokens/uiSize/icon-size.js +5 -0
  256. package/dist/tokens/uiSize/icon-size.js.map +1 -0
  257. package/ds-canonical/fork/governance.lock +31 -19
  258. package/ds-canonical/fork/hooks/_log-fire.sh +46 -0
  259. package/ds-canonical/fork/hooks/block_prototype_imports.py +15 -4
  260. package/ds-canonical/fork/hooks/check_consumer_app_invariants.sh +145 -13
  261. package/ds-canonical/fork/hooks/check_ds_anchor_preflight.sh +2 -1
  262. package/ds-canonical/fork/hooks/check_dynamic_tailwind_class.sh +47 -0
  263. package/ds-canonical/fork/hooks/check_escape_marker_abuse.sh +27 -0
  264. package/ds-canonical/fork/hooks/check_field_family_invariants.sh +61 -1
  265. package/ds-canonical/fork/hooks/check_layout_space_magic_numbers.sh +28 -4
  266. package/ds-canonical/fork/hooks/check_opacity_token_usage.sh +28 -0
  267. package/ds-canonical/fork/hooks/check_story_determinism.sh +2 -0
  268. package/ds-canonical/fork/hooks/check_story_invariants.sh +119 -0
  269. package/ds-canonical/fork/hooks/check_tabs_content_chrome_body_double_gap.sh +29 -0
  270. package/ds-canonical/fork/manifest.json +15 -0
  271. package/ds-canonical/fork/preamble.md +20 -18
  272. package/ds-canonical/fork/skills/bug-fix-rhythm/SKILL.md +3 -3
  273. package/ds-canonical/fork/skills/code-quality-audit/SKILL.md +3 -3
  274. package/ds-canonical/fork/skills/delivery-handoff/SKILL.md +1 -1
  275. package/ds-canonical/fork/skills/product-ui-audit/SKILL.md +3 -3
  276. package/ds-canonical/fork/skills/propose-options/SKILL.md +3 -3
  277. package/ds-canonical/fork/skills/prototype/SKILL.md +1 -1
  278. package/ds-canonical/fork/skills/scan-similar-bugs/SKILL.md +2 -2
  279. package/ds-canonical/fork/skills/visual-audit/SKILL.md +2 -2
  280. package/ds-canonical/hooks/README.md +1 -1
  281. package/ds-canonical/hooks/block_prototype_imports.py +15 -4
  282. package/ds-canonical/hooks/check_audit_post_report_validator.sh +65 -7
  283. package/ds-canonical/hooks/check_codex_brief_invariants.sh +28 -1
  284. package/ds-canonical/hooks/check_consumer_app_invariants.sh +145 -13
  285. package/ds-canonical/hooks/check_datatable_invariants.sh +6 -3
  286. package/ds-canonical/hooks/check_ds_anchor_preflight.sh +2 -1
  287. package/ds-canonical/hooks/check_dynamic_tailwind_class.sh +47 -0
  288. package/ds-canonical/hooks/check_escape_marker_abuse.sh +27 -0
  289. package/ds-canonical/hooks/check_field_family_invariants.sh +61 -1
  290. package/ds-canonical/hooks/check_layout_space_magic_numbers.sh +28 -4
  291. package/ds-canonical/hooks/check_naming_and_abstraction.sh +1 -1
  292. package/ds-canonical/hooks/check_opacity_token_usage.sh +28 -0
  293. package/ds-canonical/hooks/check_pixel_quantified_audit.sh +3 -2
  294. package/ds-canonical/hooks/check_post_main_ssot_propagate.sh +6 -1
  295. package/ds-canonical/hooks/check_ssot_header_declaration.sh +3 -1
  296. package/ds-canonical/hooks/check_story_determinism.sh +2 -0
  297. package/ds-canonical/hooks/check_substantive_edit_approval_preflight.sh +8 -7
  298. package/ds-canonical/hooks/check_tabs_content_chrome_body_double_gap.sh +29 -0
  299. package/ds-canonical/hooks/lib/_story_compile_drift.sh +1 -1
  300. package/ds-canonical/hooks/lib/_token_hygiene.sh +10 -1
  301. package/ds-canonical/hooks/stop_self_audit.sh +31 -0
  302. package/ds-canonical/hooks/tests/test_block_prototype_imports.sh +24 -0
  303. package/ds-canonical/hooks/tests/test_check_audit_post_report_validator.sh +44 -0
  304. package/ds-canonical/hooks/tests/test_check_codex_brief_invariants.sh +43 -2
  305. package/ds-canonical/hooks/tests/test_check_consumer_ds_primitive_misuse.sh +112 -0
  306. package/ds-canonical/hooks/tests/test_check_consumer_story_baseline.sh +9 -2
  307. package/ds-canonical/hooks/tests/test_check_dynamic_tailwind_class.sh +76 -0
  308. package/ds-canonical/hooks/tests/test_check_escape_marker_abuse.sh +24 -0
  309. package/ds-canonical/hooks/tests/test_check_field_family_invariants.sh +27 -2
  310. package/ds-canonical/hooks/tests/test_check_opacity_token_usage.sh +39 -0
  311. package/ds-canonical/hooks/tests/test_check_tabs_content_chrome_body_double_gap.sh +76 -0
  312. package/ds-canonical/references/audit-coverage-vs-24-checklist.md +1 -1
  313. package/ds-canonical/references/build-ui-canonicals.md +2 -1
  314. package/ds-canonical/references/failure-class-registry.json +145 -0
  315. package/ds-canonical/references/governance-audit-coverage.md +33 -0
  316. package/ds-canonical/references/item-anatomy-recipe.md +2 -2
  317. package/ds-canonical/references/scenario-definition.md +6 -9
  318. package/ds-canonical/references/ssot-consultation.md +2 -2
  319. package/ds-canonical/references/ssot-index.md +5 -4
  320. package/ds-canonical/references/story-baseline-registry.json +8 -2
  321. package/ds-canonical/references/structural-token-retention.md +6 -9
  322. package/ds-canonical/rules/meta-patterns.md +8 -8
  323. package/ds-canonical/rules/self-verify.md +4 -3
  324. package/ds-canonical/rules/story-rules.md +2 -2
  325. package/ds-canonical/rules/ui-development.md +2 -2
  326. package/ds-canonical/skills/bug-fix-rhythm/SKILL.md +3 -3
  327. package/ds-canonical/skills/code-quality-audit/SKILL.md +3 -3
  328. package/ds-canonical/skills/codex-collab/SKILL.md +9 -7
  329. package/ds-canonical/skills/codex-collab/references/brief-template.md +1 -1
  330. package/ds-canonical/skills/codify-principle/SKILL.md +2 -2
  331. package/ds-canonical/skills/component-quality-gate/SKILL.md +1 -1
  332. package/ds-canonical/skills/component-quality-gate/references/checklist.md +2 -2
  333. package/ds-canonical/skills/deep-audit-cross-codex/SKILL.md +51 -58
  334. package/ds-canonical/skills/deep-audit-cross-codex/references/phase-a-workflow.md +29 -0
  335. package/ds-canonical/skills/deep-audit-cross-codex/references/phase-b-codex-brief.md +29 -10
  336. package/ds-canonical/skills/deep-audit-cross-codex/references/triage-rubric.md +21 -0
  337. package/ds-canonical/skills/delivery-handoff/SKILL.md +1 -1
  338. package/ds-canonical/skills/design-system-audit/SKILL.md +26 -25
  339. package/ds-canonical/skills/design-system-audit/references/audit-prompts.md +13 -1
  340. package/ds-canonical/skills/design-system-audit/references/rule-placement.md +1 -1
  341. package/ds-canonical/skills/governance-health/SKILL.md +1 -1
  342. package/ds-canonical/skills/knowledge-prune/SKILL.md +3 -0
  343. package/ds-canonical/skills/knowledge-prune/references/prune-dimensions-d5-d10.md +1 -0
  344. package/ds-canonical/skills/new-component/SKILL.md +5 -5
  345. package/ds-canonical/skills/new-component/references/new-component-checklist.md +1 -1
  346. package/ds-canonical/skills/product-ui-audit/SKILL.md +3 -3
  347. package/ds-canonical/skills/propose-options/SKILL.md +3 -3
  348. package/ds-canonical/skills/prototype/SKILL.md +1 -1
  349. package/ds-canonical/skills/scan-similar-bugs/SKILL.md +2 -2
  350. package/ds-canonical/skills/story-auto-compile-migrate/SKILL.md +3 -3
  351. package/ds-canonical/skills/story-writing/SKILL.md +3 -3
  352. package/ds-canonical/skills/visual-audit/SKILL.md +2 -2
  353. package/ds-story-manifest.json +29 -20
  354. package/llms-full.txt +22 -1
  355. package/llms.txt +5 -4
  356. package/package.json +7 -3
  357. package/src/components/Accordion/accordion.principles.stories.tsx +1 -1
  358. package/src/components/Accordion/accordion.stories.tsx +16 -1
  359. package/src/components/Alert/alert.principles.stories.tsx +1 -1
  360. package/src/components/Alert/alert.spec.md +8 -10
  361. package/src/components/Alert/alert.stories.tsx +28 -28
  362. package/src/components/Alert/alert.tsx +1 -1
  363. package/src/components/AppShell/_demo-helpers.tsx +6 -3
  364. package/src/components/AppShell/app-shell.spec.md +4 -4
  365. package/src/components/AppShell/app-shell.tsx +28 -2
  366. package/src/components/AspectRatio/aspect-ratio.spec.md +1 -1
  367. package/src/components/AspectRatio/aspect-ratio.stories.tsx +7 -89
  368. package/src/components/Avatar/avatar.anatomy.stories.tsx +7 -3
  369. package/src/components/Avatar/avatar.principles.stories.tsx +19 -15
  370. package/src/components/Avatar/avatar.spec.md +2 -2
  371. package/src/components/Avatar/avatar.stories.tsx +8 -6
  372. package/src/components/Avatar/avatar.tsx +13 -5
  373. package/src/components/Badge/badge.principles.stories.tsx +1 -1
  374. package/src/components/Badge/badge.spec.md +2 -0
  375. package/src/components/Badge/badge.stories.tsx +4 -2
  376. package/src/components/Breadcrumb/breadcrumb.principles.stories.tsx +6 -4
  377. package/src/components/Breadcrumb/breadcrumb.stories.tsx +6 -5
  378. package/src/components/Breadcrumb/breadcrumb.tsx +3 -2
  379. package/src/components/BulkActionBar/bulk-action-bar.spec.md +4 -2
  380. package/src/components/Button/button.principles.stories.tsx +1 -1
  381. package/src/components/Button/button.spec.md +2 -2
  382. package/src/components/Button/button.tsx +18 -3
  383. package/src/components/Calendar/calendar.anatomy.stories.tsx +12 -10
  384. package/src/components/Calendar/calendar.principles.stories.tsx +14 -6
  385. package/src/components/Calendar/calendar.spec.md +5 -4
  386. package/src/components/Calendar/calendar.stories.tsx +7 -4
  387. package/src/components/Calendar/calendar.tsx +26 -7
  388. package/src/components/Carousel/carousel.anatomy.stories.tsx +5 -4
  389. package/src/components/Carousel/carousel.spec.md +2 -2
  390. package/src/components/Carousel/carousel.tsx +11 -3
  391. package/src/components/Chart/chart.anatomy.stories.tsx +7 -4
  392. package/src/components/Chart/chart.principles.stories.tsx +1 -1
  393. package/src/components/Chart/chart.stories.tsx +2 -8
  394. package/src/components/Chart/chart.tsx +3 -0
  395. package/src/components/Checkbox/checkbox.anatomy.stories.tsx +28 -49
  396. package/src/components/Checkbox/checkbox.principles.stories.tsx +1 -1
  397. package/src/components/Checkbox/checkbox.spec.md +13 -26
  398. package/src/components/Checkbox/checkbox.stories.tsx +11 -8
  399. package/src/components/Checkbox/checkbox.tsx +18 -13
  400. package/src/components/Chip/chip.anatomy.stories.tsx +44 -24
  401. package/src/components/Chip/chip.principles.stories.tsx +1 -1
  402. package/src/components/Chip/chip.spec.md +5 -4
  403. package/src/components/Chip/chip.tsx +5 -1
  404. package/src/components/CircularProgress/circular-progress.principles.stories.tsx +7 -4
  405. package/src/components/CircularProgress/circular-progress.spec.md +3 -3
  406. package/src/components/CircularProgress/circular-progress.stories.tsx +2 -2
  407. package/src/components/Coachmark/coachmark.anatomy.stories.tsx +2 -2
  408. package/src/components/Coachmark/coachmark.principles.stories.tsx +2 -1
  409. package/src/components/Coachmark/coachmark.spec.md +5 -5
  410. package/src/components/Coachmark/coachmark.stories.tsx +3 -1
  411. package/src/components/Coachmark/coachmark.tsx +10 -3
  412. package/src/components/Combobox/combobox.anatomy.stories.tsx +2 -2
  413. package/src/components/Combobox/combobox.tsx +53 -19
  414. package/src/components/Command/command.anatomy.stories.tsx +2 -2
  415. package/src/components/Command/command.principles.stories.tsx +1 -1
  416. package/src/components/Command/command.stories.tsx +42 -10
  417. package/src/components/DataTable/cell-registry.tsx +86 -63
  418. package/src/components/DataTable/column-types.ts +10 -1
  419. package/src/components/DataTable/data-table-column-visibility-panel.tsx +1 -1
  420. package/src/components/DataTable/data-table-filter-group.tsx +231 -0
  421. package/src/components/DataTable/data-table-filter-panel.tsx +16 -430
  422. package/src/components/DataTable/data-table-filter-value-picker.tsx +237 -0
  423. package/src/components/DataTable/data-table-interaction-layer.tsx +15 -19
  424. package/src/components/DataTable/data-table.anatomy.stories.tsx +15 -13
  425. package/src/components/DataTable/data-table.principles.stories.tsx +9 -9
  426. package/src/components/DataTable/data-table.spec.md +33 -13
  427. package/src/components/DataTable/data-table.stories.tsx +16 -17
  428. package/src/components/DataTable/data-table.tsx +107 -61
  429. package/src/components/DataTable/filter-operators.spec.md +3 -3
  430. package/src/components/DataTable/index.ts +3 -0
  431. package/src/components/DateGrid/date-grid.anatomy.stories.tsx +4 -4
  432. package/src/components/DateGrid/date-grid.principles.stories.tsx +1 -1
  433. package/src/components/DateGrid/date-grid.spec.md +1 -1
  434. package/src/components/DateGrid/date-grid.tsx +3 -1
  435. package/src/components/DatePicker/date-picker.anatomy.stories.tsx +8 -8
  436. package/src/components/DatePicker/date-picker.principles.stories.tsx +3 -3
  437. package/src/components/DatePicker/date-picker.spec.md +3 -3
  438. package/src/components/DatePicker/date-picker.stories.tsx +4 -5
  439. package/src/components/DatePicker/date-picker.tsx +99 -45
  440. package/src/components/DescriptionList/description-list.anatomy.stories.tsx +12 -10
  441. package/src/components/DescriptionList/description-list.principles.stories.tsx +2 -2
  442. package/src/components/DescriptionList/description-list.spec.md +3 -3
  443. package/src/components/DescriptionList/description-list.stories.tsx +7 -6
  444. package/src/components/Dialog/dialog.anatomy.stories.tsx +1 -0
  445. package/src/components/Dialog/dialog.spec.md +9 -0
  446. package/src/components/Dialog/dialog.stories.tsx +66 -5
  447. package/src/components/Dialog/dialog.tsx +60 -25
  448. package/src/components/DropdownMenu/dropdown-menu.anatomy.stories.tsx +1 -1
  449. package/src/components/DropdownMenu/dropdown-menu.principles.stories.tsx +8 -6
  450. package/src/components/DropdownMenu/dropdown-menu.spec.md +5 -5
  451. package/src/components/DropdownMenu/dropdown-menu.tsx +18 -9
  452. package/src/components/Empty/empty.anatomy.stories.tsx +1 -1
  453. package/src/components/Empty/empty.spec.md +2 -2
  454. package/src/components/Field/field-context.ts +69 -5
  455. package/src/components/Field/field-controls.spec.md +62 -8
  456. package/src/components/Field/field-edit-keys.ts +66 -0
  457. package/src/components/Field/field-types.ts +58 -9
  458. package/src/components/Field/field-wrapper.tsx +50 -42
  459. package/src/components/Field/field.anatomy.stories.tsx +9 -6
  460. package/src/components/Field/field.spec.md +1 -1
  461. package/src/components/Field/field.stories.tsx +1 -1
  462. package/src/components/Field/field.tsx +2 -1
  463. package/src/components/Field/form-validation.spec.md +3 -3
  464. package/src/components/Field/index.ts +1 -0
  465. package/src/components/FieldControlGroup/field-control-group.principles.stories.tsx +6 -6
  466. package/src/components/FieldControlGroup/field-control-group.spec.md +9 -4
  467. package/src/components/FieldControlGroup/field-control-group.tsx +1 -1
  468. package/src/components/FileItem/file-item.anatomy.stories.tsx +1 -1
  469. package/src/components/FileItem/file-item.spec.md +5 -8
  470. package/src/components/FileItem/file-item.stories.tsx +36 -25
  471. package/src/components/FileItem/file-item.tsx +8 -6
  472. package/src/components/FileUpload/file-upload.anatomy.stories.tsx +6 -6
  473. package/src/components/FileUpload/file-upload.principles.stories.tsx +5 -5
  474. package/src/components/FileUpload/file-upload.spec.md +1 -1
  475. package/src/components/FileUpload/file-upload.tsx +1 -1
  476. package/src/components/FileViewer/file-viewer.anatomy.stories.tsx +4 -3
  477. package/src/components/FileViewer/file-viewer.principles.stories.tsx +3 -3
  478. package/src/components/FileViewer/file-viewer.spec.md +30 -12
  479. package/src/components/FileViewer/file-viewer.stories.tsx +1 -2
  480. package/src/components/FileViewer/file-viewer.tsx +12 -4
  481. package/src/components/FileViewer/image-renderer.tsx +35 -7
  482. package/src/components/HoverCard/hover-card.anatomy.stories.tsx +6 -2
  483. package/src/components/HoverCard/hover-card.principles.stories.tsx +6 -3
  484. package/src/components/HoverCard/hover-card.spec.md +5 -5
  485. package/src/components/HoverCard/hover-card.stories.tsx +16 -16
  486. package/src/components/HoverCard/hover-card.tsx +5 -3
  487. package/src/components/InlineEdit/index.ts +5 -0
  488. package/src/components/InlineEdit/inline-edit.spec.md +149 -0
  489. package/src/components/InlineEdit/inline-edit.stories.tsx +246 -0
  490. package/src/components/InlineEdit/inline-edit.tsx +287 -0
  491. package/src/components/Input/input.anatomy.stories.tsx +8 -7
  492. package/src/components/Input/input.principles.stories.tsx +2 -12
  493. package/src/components/Input/input.spec.md +13 -18
  494. package/src/components/Input/input.tsx +69 -22
  495. package/src/components/LinkInput/link-input.anatomy.stories.tsx +1 -1
  496. package/src/components/LinkInput/link-input.principles.stories.tsx +2 -2
  497. package/src/components/LinkInput/link-input.tsx +11 -10
  498. package/src/components/Menu/menu-item.anatomy.stories.tsx +3 -3
  499. package/src/components/Menu/menu-item.principles.stories.tsx +1 -1
  500. package/src/components/Menu/menu-item.spec.md +22 -15
  501. package/src/components/Menu/menu-item.stories.tsx +39 -11
  502. package/src/components/Menu/menu-item.tsx +38 -4
  503. package/src/components/Notice/notice.principles.stories.tsx +1 -1
  504. package/src/components/Notice/notice.spec.md +16 -3
  505. package/src/components/NumberInput/number-input.anatomy.stories.tsx +3 -3
  506. package/src/components/NumberInput/number-input.principles.stories.tsx +1 -1
  507. package/src/components/NumberInput/number-input.spec.md +4 -4
  508. package/src/components/NumberInput/number-input.tsx +19 -12
  509. package/src/components/OverflowIndicator/overflow-indicator.anatomy.stories.tsx +10 -8
  510. package/src/components/OverflowIndicator/overflow-indicator.principles.stories.tsx +3 -3
  511. package/src/components/OverflowIndicator/overflow-indicator.spec.md +4 -2
  512. package/src/components/OverflowIndicator/overflow-indicator.stories.tsx +71 -63
  513. package/src/components/OverflowIndicator/overflow-indicator.tsx +9 -8
  514. package/src/components/Pagination/pagination.spec.md +1 -1
  515. package/src/components/Pagination/pagination.stories.tsx +2 -6
  516. package/src/components/Pagination/pagination.tsx +1 -1
  517. package/src/components/PeoplePicker/people-picker.anatomy.stories.tsx +4 -4
  518. package/src/components/PeoplePicker/people-picker.principles.stories.tsx +2 -2
  519. package/src/components/PeoplePicker/people-picker.spec.md +6 -6
  520. package/src/components/PeoplePicker/people-picker.tsx +56 -16
  521. package/src/components/PeoplePicker/person-display.tsx +32 -16
  522. package/src/components/Popover/popover.anatomy.stories.tsx +2 -2
  523. package/src/components/Popover/popover.tsx +3 -1
  524. package/src/components/ProfileCard/profile-card.anatomy.stories.tsx +12 -23
  525. package/src/components/ProfileCard/profile-card.spec.md +3 -3
  526. package/src/components/ProfileCard/profile-card.tsx +6 -6
  527. package/src/components/ProgressBar/progress-bar.anatomy.stories.tsx +3 -1
  528. package/src/components/ProgressBar/progress-bar.principles.stories.tsx +1 -1
  529. package/src/components/ProgressBar/progress-bar.stories.tsx +50 -42
  530. package/src/components/RadioGroup/radio-group.anatomy.stories.tsx +10 -28
  531. package/src/components/RadioGroup/radio-group.stories.tsx +29 -32
  532. package/src/components/RadioGroup/radio-group.tsx +41 -15
  533. package/src/components/Rating/rating.spec.md +11 -3
  534. package/src/components/ScrollArea/scroll-area.anatomy.stories.tsx +1 -1
  535. package/src/components/ScrollArea/scroll-area.principles.stories.tsx +1 -1
  536. package/src/components/ScrollArea/scroll-area.spec.md +7 -3
  537. package/src/components/ScrollArea/scroll-area.stories.tsx +1 -1
  538. package/src/components/ScrollArea/scroll-area.tsx +17 -4
  539. package/src/components/Select/select.anatomy.stories.tsx +3 -3
  540. package/src/components/Select/select.spec.md +29 -17
  541. package/src/components/Select/select.stories.tsx +33 -0
  542. package/src/components/Select/select.tsx +190 -36
  543. package/src/components/SelectMenu/select-menu.anatomy.stories.tsx +4 -4
  544. package/src/components/SelectMenu/select-menu.spec.md +15 -7
  545. package/src/components/SelectMenu/select-menu.tsx +16 -3
  546. package/src/components/SelectionControl/boolean-value.tsx +39 -0
  547. package/src/components/SelectionControl/index.ts +1 -0
  548. package/src/components/SelectionControl/selection-item.anatomy.stories.tsx +2 -2
  549. package/src/components/SelectionControl/selection-item.principles.stories.tsx +2 -2
  550. package/src/components/SelectionControl/selection-item.spec.md +3 -3
  551. package/src/components/SelectionControl/selection-item.stories.tsx +3 -3
  552. package/src/components/Separator/separator.spec.md +2 -2
  553. package/src/components/Separator/separator.stories.tsx +6 -27
  554. package/src/components/Sheet/sheet.anatomy.stories.tsx +1 -1
  555. package/src/components/Sheet/sheet.principles.stories.tsx +1 -1
  556. package/src/components/Sheet/sheet.spec.md +2 -2
  557. package/src/components/Sheet/sheet.tsx +9 -5
  558. package/src/components/Sidebar/sidebar.anatomy.stories.tsx +16 -12
  559. package/src/components/Sidebar/sidebar.principles.stories.tsx +10 -8
  560. package/src/components/Sidebar/sidebar.spec.md +13 -15
  561. package/src/components/Sidebar/sidebar.stories.tsx +8 -7
  562. package/src/components/Sidebar/sidebar.tsx +37 -25
  563. package/src/components/Skeleton/skeleton.stories.tsx +4 -11
  564. package/src/components/Slider/slider.anatomy.stories.tsx +3 -3
  565. package/src/components/Slider/slider.principles.stories.tsx +2 -2
  566. package/src/components/Slider/slider.spec.md +5 -5
  567. package/src/components/Slider/slider.tsx +3 -1
  568. package/src/components/Steps/steps.anatomy.stories.tsx +4 -4
  569. package/src/components/Steps/steps.principles.stories.tsx +1 -1
  570. package/src/components/Steps/steps.spec.md +1 -1
  571. package/src/components/Switch/switch.anatomy.stories.tsx +7 -7
  572. package/src/components/Switch/switch.principles.stories.tsx +4 -4
  573. package/src/components/Switch/switch.spec.md +8 -8
  574. package/src/components/Switch/switch.stories.tsx +9 -24
  575. package/src/components/Switch/switch.tsx +17 -13
  576. package/src/components/Tabs/tabs.anatomy.stories.tsx +10 -7
  577. package/src/components/Tabs/tabs.spec.md +1 -1
  578. package/src/components/Tabs/tabs.tsx +21 -1
  579. package/src/components/Textarea/textarea.anatomy.stories.tsx +1 -1
  580. package/src/components/Textarea/textarea.principles.stories.tsx +1 -1
  581. package/src/components/Textarea/textarea.spec.md +4 -4
  582. package/src/components/Textarea/textarea.tsx +72 -46
  583. package/src/components/TimePicker/time-columns.tsx +6 -2
  584. package/src/components/TimePicker/time-picker.principles.stories.tsx +1 -1
  585. package/src/components/TimePicker/time-picker.spec.md +3 -3
  586. package/src/components/TimePicker/time-picker.tsx +16 -10
  587. package/src/components/Toast/toast.stories.tsx +7 -4
  588. package/src/components/Tooltip/tooltip.anatomy.stories.tsx +4 -4
  589. package/src/components/Tooltip/tooltip.principles.stories.tsx +3 -3
  590. package/src/components/Tooltip/tooltip.spec.md +1 -1
  591. package/src/components/Tooltip/tooltip.stories.tsx +4 -39
  592. package/src/components/Tooltip/tooltip.tsx +8 -6
  593. package/src/components/TreeView/tree-view.anatomy.stories.tsx +6 -3
  594. package/src/components/TreeView/tree-view.spec.md +42 -11
  595. package/src/components/TreeView/tree-view.stories.tsx +1 -0
  596. package/src/components/TreeView/tree-view.tsx +322 -17
  597. package/src/index.ts +588 -60
  598. package/src/patterns/action-bar/action-bar.spec.md +9 -0
  599. package/src/patterns/action-bar/action-bar.stories.tsx +6 -3
  600. package/src/patterns/element-anatomy/inline-action.spec.md +2 -0
  601. package/src/patterns/element-anatomy/item-anatomy.spec.md +14 -5
  602. package/src/patterns/element-anatomy/item-anatomy.stories.tsx +6 -4
  603. package/src/patterns/element-anatomy/item-anatomy.tsx +7 -6
  604. package/src/patterns/header-canonical/header-canonical.spec.md +5 -4
  605. package/src/patterns/overlay-surface/overlay-surface.spec.md +22 -1
  606. package/src/styles/tokens.css +3 -0
  607. package/src/tokens/README.md +1 -1
  608. package/src/tokens/color/color.spec.md +4 -4
  609. package/src/tokens/color/color.stories.tsx +3 -3
  610. package/src/tokens/density/density.stories.tsx +16 -3
  611. package/src/tokens/layoutSpace/layoutSpace.spec.md +3 -1
  612. package/src/tokens/motion/motion.css +33 -21
  613. package/src/tokens/motion/motion.spec.md +32 -15
  614. package/src/tokens/motion/motion.stories.tsx +58 -5
  615. package/src/tokens/motion/motion.ts +7 -6
  616. package/src/tokens/motion/overlay-motion.ts +26 -0
  617. package/src/tokens/orphan-tokens.spec.md +4 -4
  618. package/src/tokens/token-system.spec.md +1 -0
  619. package/src/tokens/uiSize/uiSize.css +1 -1
  620. package/src/tokens/uiSize/uiSize.spec.md +1 -1
  621. package/src/tokens/uiSize/uiSize.stories.tsx +4 -2
  622. package/src/tokens/utility-registry.json +160 -23
@@ -1,15 +1,15 @@
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
2
  // code-quality-allow: file-size — Select 含 3 子元件(NativeSelect/CustomSelect/ReadonlyDisplay)+ helpers + 4-mode renderer + Field SSOT consumption,split-into-files 會破壞 file-local helper closure
3
- // @renderer-symmetry-allow: pre-existing Select architecture(2026-05-08 D-path)— selectedItemRenderer CustomSelectTriggerContent 消費(edit + trigger 模式),ReadonlyDisplay separate bare-span path(no D-path)。display→edit unify deferred cycle per spec contract (a) note。本 turn 只加 `nakedCellRowModeAlign` import,no behavior change to renderer symmetry contract
3
+ // @renderer-symmetry-allow: 2026-07-08 WM 戰役 A 案回歸修正 ReadonlyDisplay 現已消費 selectedItemRenderer(display bare-span / D-path / readonly / disabled 四分支),對齊 field-controls.spec.md 共享 contract (a)「display/readonly/disabled/edit 4 mode 共享同一 renderer」。前 note「display→edit unify deferred」已兌現(值內容層);chrome 結構 unify(D-path)仍為 opt-in showDisplayEndIcon
4
4
  import * as React from 'react'
5
5
  import { X, ChevronDown } from 'lucide-react'
6
6
  import type { LucideIcon } from 'lucide-react'
7
7
  import { cn } from '@/lib/utils'
8
- import type { FieldMode, FieldVariant } from '@/design-system/components/Field/field-types'
9
- import { fieldWrapperStyles, bareInputStyles, EMPTY_DISPLAY, nakedCellRowModeAlign, fieldDisplayTextClass } from '@/design-system/components/Field/field-wrapper'
8
+ import type { FieldMode, FieldVariant, FieldVariantInternal, FieldWidth } from '@/design-system/components/Field/field-types'
9
+ import { fieldWrapperStyles, bareInputStyles, nakedCellRowModeAlign, fieldDisplayTextClass } from '@/design-system/components/Field/field-wrapper'
10
10
  import { Tag } from '@/design-system/components/Tag/tag'
11
11
  import { ItemInlineAction, ItemPrefix, ItemSuffix } from '@/design-system/patterns/element-anatomy/item-anatomy'
12
- import { useFieldContext, useResolvedFieldSize, useResolvedFieldDisabled, useResolvedFieldMode, useResolvedFieldVariant, useResolvedFieldInvalid } from '@/design-system/components/Field/field-context'
12
+ import { useFieldContext, useResolvedFieldSize, useResolvedFieldDisabled, useResolvedFieldMode, useResolvedFieldVariant, useResolvedFieldInvalid, useFieldEmptyDisplay, fieldEmptyColorClass } from '@/design-system/components/Field/field-context'
13
13
  import { SelectMenu, forwardKeyToListbox, useActiveDescendant, type SelectMenuOption } from '@/design-system/components/SelectMenu/select-menu'
14
14
  import { useIsTouchDevice } from '@/design-system/hooks/use-is-touch-device'
15
15
  import { useControllable } from '@/design-system/hooks/use-controllable'
@@ -53,11 +53,58 @@ export interface SelectGroupConfig {
53
53
 
54
54
  // ── Types ───────────────────────────────────────────────────────────────────
55
55
 
56
+ /**
57
+ * 原生 `<select>` 屬性 allowlist(2026-07-14 API 策展 D,user 拍板「全部收窄」)。
58
+ *
59
+ * 舊契約 `extends Omit<SelectHTMLAttributes, ...>` 型別承諾**全部**原生 select 屬性,但桌機
60
+ * 自訂 combobox(div trigger + hidden input mirror)只有部分真生效 — 其餘 silent no-op /
61
+ * 被元件內部覆寫,「型別承諾 ≠ 實作出口」= footgun。改 `Pick<>` 顯式 allowlist,**只承諾
62
+ * 兩路徑(desktop CustomSelect / mobile NativeSelect)皆有實作出口的屬性**,盤點以 code 為準:
63
+ *
64
+ * - `id` — 桌機 trigger div / mobile `<select>`,皆 `idProp ?? fieldCtx?.id`
65
+ * - `name` / `form` / `required` — 桌機經 hidden input mirror(D2 2026-07-13,見 hiddenInputEl
66
+ * docblock)/ mobile 原生 `<select>` spread
67
+ * - `disabled` — 兩路徑 `useResolvedFieldDisabled` cascade → mode 分流
68
+ * - `className` / `style` — 兩路徑 field wrapper
69
+ * - `onFocus` / `onBlur` — 桌機 rest spread 到 focusable trigger(tabIndex=0)/ mobile spread
70
+ * - `onKeyDown` — 桌機 consumer handler 先跑再走元件導覽(dim-9)/ mobile 原生
71
+ * - `aria-label` / `aria-describedby` / `aria-errormessage` — 兩路徑顯式接線(fieldCtx fallback)
72
+ * - `autoFocus` / `autoComplete` — **mobile-only**:NativeSelect 原生 `<select>` 生效;桌機
73
+ * custom combobox 無原生對應 = documented no-op(桌機 autofill 由 hidden input mirror 的
74
+ * option-match onChange guard 承擔)
75
+ * - `data-*` — 兩路徑 spread passthrough(dim-9 契約;index signature 顯式承諾,見下)
76
+ *
77
+ * 被砍者(原 extends 承諾但從未生效 / 被元件覆寫,不再進型別):`multiple` / `children` /
78
+ * `tabIndex`(桌機恆 0)/ `aria-labelledby`(桌機由 fieldCtx.labelId / aria-label 管理,
79
+ * consumer 值會被覆寫)/ 其餘 generic HTMLAttributes(title / dir / onClick 等)。
80
+ * 需要時先接線(兩路徑等效)再入表,**不回 extends 全開**。
81
+ *
82
+ * 世界級對照:自訂 combobox 皆走 curated 原生屬性子集,無人 extends 全部 SelectHTMLAttributes —
83
+ * Radix Select.Root 顯式列舉 name/disabled/required/form/autoComplete
84
+ * (radix-ui.com/primitives/docs/components/select#root)/ Headless UI Listbox name/form/disabled
85
+ * (headlessui.com/react/listbox#using-with-html-forms)/ React Aria Select name/autoComplete/
86
+ * isDisabled/isRequired(react-spectrum.adobe.com/react-aria/Select.html)。
87
+ */
56
88
  export interface SelectProps
57
- extends Omit<React.SelectHTMLAttributes<HTMLSelectElement>, 'size' | 'value' | 'defaultValue' | 'onChange'> {
89
+ extends Pick<
90
+ React.SelectHTMLAttributes<HTMLSelectElement>,
91
+ // ── 兩路徑皆真生效 ──
92
+ | 'id' | 'className' | 'style'
93
+ | 'name' | 'form' | 'required' | 'disabled'
94
+ | 'onFocus' | 'onBlur' | 'onKeyDown'
95
+ | 'aria-label' | 'aria-describedby' | 'aria-errormessage'
96
+ // ── mobile-only(NativeSelect 原生 <select> 生效;桌機 custom combobox documented no-op)──
97
+ | 'autoFocus' | 'autoComplete'
98
+ > {
99
+ /** `data-*` passthrough(桌機 trigger div rest spread / mobile `<select>` spread;dim-9 契約)。 */
100
+ [dataAttribute: `data-${string}`]: unknown
58
101
  mode?: FieldMode
59
102
  /** Field chrome variant. Default = context.variant ?? 'default'. Per-prop override. */
60
103
  variant?: FieldVariant
104
+ /** 寬度軸 — `fill` 填滿容器(default)/ `hug` 依內容收縮:value 寬 + slot 元素寬 + gap +
105
+ * field 內 padding;chrome 與互動不變。場景 = detail pane inline metadata。
106
+ * SSOT → field-controls.spec.md「寬度軸(width: fill / hug)」。 */
107
+ width?: FieldWidth
61
108
  error?: boolean
62
109
  size?: 'sm' | 'md' | 'lg'
63
110
  options: SelectOption[]
@@ -104,13 +151,16 @@ export interface SelectProps
104
151
  */
105
152
  showDisplayEndIcon?: boolean
106
153
  /**
107
- * Trigger 內「已選項目」客製 render(2026-05-07 v15.5)。
154
+ * 「已選項目」客製 render(2026-05-07 v15.5;2026-07-08 A 案回歸修正擴及 4 mode)。
108
155
  *
109
- * 設了 → trigger 不走純文字 / Tag 預設 path,改用 consumer 提供的 ReactNode(收 selectedOpt)。
156
+ * 設了 → 已選值不走純文字 / Tag 預設 path,改用 consumer 提供的 ReactNode(收 selectedOpt)。
157
+ * **display / readonly / disabled / edit 4 mode 共享同一 renderer**(field-controls.spec.md
158
+ * 共享 contract (a),禁 edit-only)— renderer 輸出屬「值內容」(status icon+text 等語意呈現),
159
+ * display 態照常渲染;A 案撤的是 affordance(chevron/outline),非值內容(field.spec.md L6 分層)。
110
160
  * Searchable+open 仍走 input(搜尋優先)。Empty value(no selection)仍走 placeholder。
111
161
  *
112
162
  * 用例:PeoplePicker 用此 slot 把 single 選中的 person render 成 PersonDisplay
113
- * (avatar + name)而非純文字 label。對齊 PeoplePicker = Select wrapper SSOT。
163
+ * (avatar + name)而非純文字 label;DataTable status 欄 icon+text。對齊 PeoplePicker = Select wrapper SSOT。
114
164
  */
115
165
  selectedItemRenderer?: (selectedOpt: SelectOption) => React.ReactNode
116
166
  /** 搜尋無結果提示(2026-07-04 Q4 拍板接線 — 原 spec 宣稱可覆寫但 prop 從未轉發)。
@@ -295,19 +345,26 @@ CustomSelectTriggerContent.displayName = 'CustomSelectTriggerContent'
295
345
 
296
346
  // ── Shared readonly/disabled/display render ─────────────────────────────────
297
347
  function ReadonlyDisplay({
298
- mode, variant: variantProp, size, options, value, display, startIcon: StartIcon, className, placeholder, showDisplayEndIcon,
299
- }: Pick<SelectProps, 'mode' | 'variant' | 'size' | 'options' | 'value' | 'display' | 'startIcon' | 'className' | 'placeholder' | 'showDisplayEndIcon'>) {
348
+ mode, variant: variantProp, width, size, options, value, display, startIcon: StartIcon, className, placeholder, showDisplayEndIcon, selectedItemRenderer,
349
+ }: Pick<SelectProps, 'mode' | 'width' | 'size' | 'options' | 'value' | 'display' | 'startIcon' | 'className' | 'placeholder' | 'showDisplayEndIcon' | 'selectedItemRenderer'> & {
350
+ /** @internal 2026-07-14 API 策展 E:內部 render helper 吃 FieldVariantInternal(naked 由 cell-registry 通道傳入)*/
351
+ variant?: FieldVariantInternal
352
+ }) {
300
353
  const resolvedMode = mode ?? 'readonly'
354
+ const emptyDisplay = useFieldEmptyDisplay()
301
355
  const variant = variantProp ?? 'default'
302
356
  const sz = size ?? 'md'
303
357
  const iconSize = getIconSize(sz)
304
- const label = options?.find(o => o.value === value)?.label ?? value
358
+ // selectedOpt 提前 hoist(2026-07-08 A 案回歸修正):display / D-path / readonly / disabled
359
+ // 四分支都需要 — renderer 值內容(field-controls.spec.md contract (a) 4-mode 共享)+ tagVariant 查找。
360
+ const selectedOpt = options?.find(o => o.value === value)
361
+ const label = selectedOpt?.label ?? value
305
362
  const iconColor = resolvedMode === 'disabled' ? 'text-fg-disabled' : 'text-fg-muted'
306
363
  const isTextDisplay = display !== 'tag'
307
364
  // K10+K14 fix(2026-05-04):disabled mode placeholder/empty 顯示色 → fg-disabled(neutral-6),非 fg-muted(neutral-7)
308
365
  // user canonical:disabled 顯著性優於 muted。同時 plain mode 必須 respect placeholder prop(之前忽略 = bug)
309
- const emptyColorCls = resolvedMode === 'disabled' ? 'text-fg-disabled' : 'text-fg-muted'
310
- const emptyText = placeholder ?? EMPTY_DISPLAY
366
+ const emptyColorCls = fieldEmptyColorClass(resolvedMode)
367
+ const emptyText = placeholder ?? emptyDisplay
311
368
 
312
369
  // mode='display':2 path(2026-05-08 D path Phase 1 Select canary)
313
370
  // ❌ 預設(無 showDisplayEndIcon):純內容輸出 bare span/Tag(原行為,backward compat)
@@ -320,21 +377,35 @@ function ReadonlyDisplay({
320
377
  // 2026-05-14 I2 fix(spec contract (e) display typography canonical):bare span 必套
321
378
  // `fieldDisplayTextClass(sz)`(sm/md→text-body,lg→text-body-lg)— 對齊跨 Field
322
379
  // family display 視覺尺寸統一。
323
- if (!value) return <span className={cn(fieldDisplayTextClass(sz), 'text-fg-muted', className)}>{emptyText}</span>
380
+ if (!value) return <span className={cn(fieldDisplayTextClass(sz), emptyColorCls, className)}>{emptyText}</span>
381
+ // 2026-07-08 A 案回歸修正:selectedItemRenderer(值內容:status icon+text 等語意呈現)
382
+ // display 態照常渲染 — 無 chrome 無 chevron,只有值內容本身。分層原則(field.spec.md L6):
383
+ // 本 renderer 歸「值內容」,恆顯;A 案撤的是 affordance(chevron/outline),非值內容。
384
+ // 對齊 field-controls.spec.md 共享 contract (a)「4 mode 共享同一 renderer(禁 edit-only)」。
385
+ if (selectedItemRenderer && selectedOpt) {
386
+ return (
387
+ <span className={cn(fieldDisplayTextClass(sz), 'inline-flex items-center min-w-0', nakedCellRowModeAlign, className)}>
388
+ {selectedItemRenderer(selectedOpt)}
389
+ </span>
390
+ )
391
+ }
324
392
  if (isTextDisplay) return <span className={cn(fieldDisplayTextClass(sz), 'truncate', className)}>{label}</span>
325
- const selOpt = options?.find(o => o.value === value)
326
- const tVariant = selOpt?.tagVariant as 'blue' | 'green' | 'red' | 'yellow' | 'neutral' | undefined
393
+ const tVariant = selectedOpt?.tagVariant as 'blue' | 'green' | 'red' | 'yellow' | 'neutral' | undefined
327
394
  return <Tag size={sz} color={tVariant} className={className}>{label}</Tag>
328
395
  }
329
396
  // D path opt-in: Field naked-display wrapper + ItemSuffix ChevronDown
330
- const selOpt = options?.find(o => o.value === value)
331
- const tVariant = selOpt?.tagVariant as 'blue' | 'green' | 'red' | 'yellow' | 'neutral' | undefined
397
+ const tVariant = selectedOpt?.tagVariant as 'blue' | 'green' | 'red' | 'yellow' | 'neutral' | undefined
332
398
  return (
333
399
  <div
334
- className={cn(fieldWrapperStyles({ mode: 'display', variant, size: sz }), value && !isTextDisplay && tagPadding[sz], className)}
400
+ className={cn(fieldWrapperStyles({ mode: 'display', variant, width, size: sz }), value && !isTextDisplay && tagPadding[sz], className)}
335
401
  data-field-mode="display"
336
402
  >
337
- {isTextDisplay ? (
403
+ {selectedItemRenderer && value && selectedOpt ? (
404
+ // renderer 優先(同 CustomSelectTriggerContent 優先序):值內容歸 renderer,chevron 歸 D-path chrome
405
+ <span className={cn('flex-1 min-w-0 inline-flex items-center', nakedCellRowModeAlign)}>
406
+ {selectedItemRenderer(selectedOpt)}
407
+ </span>
408
+ ) : isTextDisplay ? (
338
409
  <span className={cn(bareInputStyles, 'flex-1 min-w-0 truncate', !value && emptyColorCls)}>
339
410
  {value ? label : emptyText}
340
411
  </span>
@@ -356,9 +427,25 @@ function ReadonlyDisplay({
356
427
  const showIndicator = variant === 'naked' ? !!showDisplayEndIcon : resolvedMode === 'disabled'
357
428
  const ariaDisabled = resolvedMode === 'disabled' ? true : undefined
358
429
 
430
+ // 2026-07-08 A 案回歸修正:readonly / disabled 同樣消費 selectedItemRenderer(值內容 4-mode
431
+ // 共享,field-controls.spec.md contract (a);對照 PeoplePicker readonly/disabled 渲 PersonDisplay
432
+ // 先例)。renderer 優先於 isTextDisplay / Tag 預設 path(同 CustomSelectTriggerContent 優先序);
433
+ // disabled 時 wrapper span 帶 text-fg-disabled 供未自帶色的文字繼承(M24 disabled > muted)。
434
+ if (selectedItemRenderer && value && selectedOpt) {
435
+ return (
436
+ <div className={cn(fieldWrapperStyles({ mode: resolvedMode, variant, width, size: sz }), className)} data-field-mode={resolvedMode} aria-disabled={ariaDisabled}>
437
+ {StartIcon && <ItemPrefix><StartIcon size={iconSize} className={cn('pointer-events-none', iconColor)} aria-hidden /></ItemPrefix>}
438
+ <span className={cn('flex-1 min-w-0 inline-flex items-center', nakedCellRowModeAlign, resolvedMode === 'disabled' && 'text-fg-disabled')}>
439
+ {selectedItemRenderer(selectedOpt)}
440
+ </span>
441
+ {showIndicator && <ItemSuffix className="pointer-events-none"><ChevronDown size={iconSize} className={cn('shrink-0', iconColor)} aria-hidden /></ItemSuffix>}
442
+ </div>
443
+ )
444
+ }
445
+
359
446
  if (isTextDisplay) {
360
447
  return (
361
- <div className={cn(fieldWrapperStyles({ mode: resolvedMode, variant, size: sz }), className)} data-field-mode={resolvedMode} aria-disabled={ariaDisabled}>
448
+ <div className={cn(fieldWrapperStyles({ mode: resolvedMode, variant, width, size: sz }), className)} data-field-mode={resolvedMode} aria-disabled={ariaDisabled}>
362
449
  {StartIcon && <ItemPrefix><StartIcon size={iconSize} className={cn('pointer-events-none', iconColor)} aria-hidden /></ItemPrefix>}
363
450
  <span className={cn('flex-1 min-w-0 truncate', resolvedMode === 'disabled' && 'text-fg-disabled')}>
364
451
  {value ? label : <span className={emptyColorCls}>{emptyText}</span>}
@@ -368,11 +455,10 @@ function ReadonlyDisplay({
368
455
  )
369
456
  }
370
457
 
371
- const selectedOpt = options?.find(o => o.value === value)
372
458
  const tagVariant = selectedOpt?.tagVariant as 'blue' | 'green' | 'red' | 'yellow' | 'neutral' | undefined
373
459
 
374
460
  return (
375
- <div className={cn(fieldWrapperStyles({ mode: resolvedMode, variant, size: sz }), value && tagPadding[sz], className)} style={{ paddingRight: 'var(--field-px)' }} data-field-mode={resolvedMode} aria-disabled={ariaDisabled}>
461
+ <div className={cn(fieldWrapperStyles({ mode: resolvedMode, variant, width, size: sz }), value && tagPadding[sz], className)} style={{ paddingRight: 'var(--field-px)' }} data-field-mode={resolvedMode} aria-disabled={ariaDisabled}>
376
462
  {value ? <Tag size={sz} color={tagVariant}>{label}</Tag> : <span className={emptyColorCls}>{emptyText}</span>}
377
463
  {showIndicator && <ItemSuffix className="pointer-events-none"><ChevronDown size={iconSize} className={cn('shrink-0', iconColor)} aria-hidden /></ItemSuffix>}
378
464
  </div>
@@ -383,10 +469,13 @@ function ReadonlyDisplay({
383
469
 
384
470
  // code-quality-allow: long-function — foundational composite main body — 拆 sub-fn 會複雜化 local state / ref / context binding
385
471
  const NativeSelect = React.forwardRef<HTMLSelectElement, SelectProps>(
386
- ({ mode, variant: variantProp, error: errorProp = false, size: sizeProp, options, value: valueProp, defaultValue, onChange, placeholder, className, disabled: disabledProp, clearable = false, display = 'plain', startIcon: StartIcon, showDisplayEndIcon, id: idProp, 'aria-describedby': ariaDescribedByProp, 'aria-errormessage': ariaErrorMessageProp,
472
+ ({ mode, variant: variantProp, width, error: errorProp = false, size: sizeProp, options, value: valueProp, defaultValue, onChange, placeholder, className, disabled: disabledProp, clearable = false, display = 'plain', startIcon: StartIcon, showDisplayEndIcon, id: idProp, 'aria-describedby': ariaDescribedByProp, 'aria-errormessage': ariaErrorMessageProp,
387
473
  // 2026-07-04:Custom-path-only props 顯式 destructure 丟棄(不 spread 到原生 <select> 消 React
388
474
  // unknown-prop warning);各 prop docblock 已註「僅 Custom path 生效」語意
389
- searchable: _searchable, groups: _groups, loading: _loading, minRows: _minRows, emptyText: _emptyText, defaultOpen: _defaultOpen, onOpenChange: _onOpenChange, selectedItemRenderer: _selectedItemRenderer,
475
+ // 2026-07-08 A 案回歸修正:selectedItemRenderer 從丟棄名單移出 Native path
476
+ // ReadonlyDisplay(display/readonly/disabled)同樣消費值內容 renderer(contract (a) 4-mode
477
+ // 共享,值內容不因 pointer type 而異);native <select> edit 路徑仍不消費(原生 option 無法客製 render)。
478
+ searchable: _searchable, groups: _groups, loading: _loading, minRows: _minRows, emptyText: _emptyText, defaultOpen: _defaultOpen, onOpenChange: _onOpenChange, selectedItemRenderer,
390
479
  ...props }, ref) => {
391
480
  const fieldCtx = useFieldContext()
392
481
  const error = useResolvedFieldInvalid(errorProp)
@@ -395,7 +484,7 @@ const NativeSelect = React.forwardRef<HTMLSelectElement, SelectProps>(
395
484
  const size = useResolvedFieldSize(sizeProp)
396
485
  // 2026-06-08 SSOT:mode 經 useResolvedFieldMode(prop > 有效 disabled > fieldCtx.mode > 'edit');修 <Field mode="display"> 漏 cascade
397
486
  const resolvedMode = useResolvedFieldMode({ mode, disabled })
398
- const variant: FieldVariant = useResolvedFieldVariant(variantProp)
487
+ const variant: FieldVariantInternal = useResolvedFieldVariant(variantProp)
399
488
  const iconSize = getIconSize(size)
400
489
  // 2026-05-21 D3 audit:Controlled / Uncontrolled dual-mode via 既有 SSOT hook(同 CustomSelect)
401
490
  const [value, setValue] = useControllable<string | null>({
@@ -414,7 +503,7 @@ const NativeSelect = React.forwardRef<HTMLSelectElement, SelectProps>(
414
503
  }, [ref])
415
504
 
416
505
  if (resolvedMode !== 'edit') {
417
- return <ReadonlyDisplay mode={resolvedMode} variant={variant} size={size} options={options} value={value} display={display} startIcon={StartIcon} className={className} placeholder={placeholder} showDisplayEndIcon={showDisplayEndIcon} />
506
+ return <ReadonlyDisplay mode={resolvedMode} variant={variant} width={width} size={size} options={options} value={value} display={display} startIcon={StartIcon} className={className} placeholder={placeholder} showDisplayEndIcon={showDisplayEndIcon} selectedItemRenderer={selectedItemRenderer} />
418
507
  }
419
508
 
420
509
  const selectEl = (
@@ -453,7 +542,7 @@ const NativeSelect = React.forwardRef<HTMLSelectElement, SelectProps>(
453
542
 
454
543
  if (!isTextDisplay) {
455
544
  return (
456
- <div className={cn(fieldWrapperStyles({ mode: 'edit', variant: variant, size, error }), value && tagPadding[size], 'relative',
545
+ <div className={cn(fieldWrapperStyles({ mode: 'edit', variant: variant, width, size, error }), value && tagPadding[size], 'relative',
457
546
  className)}
458
547
  style={{ paddingRight: 'var(--field-px)' }} data-field-mode="edit" data-error={error ? '' : undefined}>
459
548
  {value ? <Tag size={size} color={nativeTagVariant} className="shrink-0 relative z-10 pointer-events-none">{label}</Tag> : <span className="text-fg-muted">{placeholder ?? '選擇...'}</span>}
@@ -466,7 +555,7 @@ const NativeSelect = React.forwardRef<HTMLSelectElement, SelectProps>(
466
555
  }
467
556
 
468
557
  return (
469
- <div className={cn(fieldWrapperStyles({ mode: 'edit', variant: variant, size, error }),
558
+ <div className={cn(fieldWrapperStyles({ mode: 'edit', variant: variant, width, size, error }),
470
559
  className)}
471
560
  data-field-mode="edit" data-error={error ? '' : undefined}>
472
561
  {StartIcon && <ItemPrefix><StartIcon size={iconSize} className="text-fg-muted pointer-events-none" aria-hidden /></ItemPrefix>}
@@ -484,7 +573,10 @@ NativeSelect.displayName = 'NativeSelect'
484
573
 
485
574
  // code-quality-allow: long-function — foundational composite main body — 拆 sub-fn 會複雜化 local state / ref / context binding
486
575
  const CustomSelect = React.forwardRef<HTMLDivElement, SelectProps>(
487
- ({ mode, variant: variantProp, error: errorProp = false, size: sizeProp, options, groups, value: valueProp, defaultValue, onChange, placeholder, className, disabled: disabledProp, clearable = false, display = 'plain', startIcon: StartIcon, searchable = false, loading, minRows, emptyText, defaultOpen = false, onOpenChange, selectedItemRenderer, showDisplayEndIcon, id: idProp, 'aria-describedby': ariaDescribedByProp, 'aria-errormessage': ariaErrorMessageProp, 'aria-label': ariaLabel }, ref) => {
576
+ ({ mode, variant: variantProp, width, error: errorProp = false, size: sizeProp, options, groups, value: valueProp, defaultValue, onChange, placeholder, className, disabled: disabledProp, name, required, form, clearable = false, display = 'plain', startIcon: StartIcon, searchable = false, loading, minRows, emptyText, defaultOpen = false, onOpenChange, selectedItemRenderer, showDisplayEndIcon, id: idProp, 'aria-describedby': ariaDescribedByProp, 'aria-errormessage': ariaErrorMessageProp, 'aria-label': ariaLabel, onKeyDown: onKeyDownProp, style: styleProp,
577
+ // 2026-07-14 API 策展 D:mobile-only props(allowlist 註記)desktop 顯式丟棄 — div trigger 無原生
578
+ // 對應,不 spread 進 DOM(對稱 NativeSelect 丟棄 custom-path-only props 的既有 pattern)
579
+ autoFocus: _autoFocus, autoComplete: _autoComplete, ...rest }, ref) => {
488
580
  const fieldCtx = useFieldContext()
489
581
  const error = useResolvedFieldInvalid(errorProp)
490
582
  const disabled = useResolvedFieldDisabled(disabledProp)
@@ -492,7 +584,7 @@ const CustomSelect = React.forwardRef<HTMLDivElement, SelectProps>(
492
584
  const size = useResolvedFieldSize(sizeProp)
493
585
  // 2026-06-08 SSOT:mode 經 useResolvedFieldMode(prop > 有效 disabled > fieldCtx.mode > 'edit');修 <Field mode="display"> 漏 cascade
494
586
  const resolvedMode = useResolvedFieldMode({ mode, disabled })
495
- const variant: FieldVariant = useResolvedFieldVariant(variantProp)
587
+ const variant: FieldVariantInternal = useResolvedFieldVariant(variantProp)
496
588
  const iconSize = getIconSize(size)
497
589
  // 2026-05-21 D3 audit:Controlled / Uncontrolled dual-mode via 既有 SSOT hook(M17 對齊,取代自刻 isControlled pattern)。
498
590
  // Phase B codex 抓:之前 Custom clear 走 `onChange?.('')` 沒 setInternalValue → uncontrolled clear 失效。useControllable 統一 setter 修。
@@ -571,7 +663,7 @@ const CustomSelect = React.forwardRef<HTMLDivElement, SelectProps>(
571
663
 
572
664
  // Early return AFTER all hooks(disabled / readonly / display mode 走 ReadonlyDisplay)
573
665
  if (resolvedMode !== 'edit') {
574
- return <ReadonlyDisplay mode={resolvedMode} variant={variant} size={size} options={options} value={value} display={display} startIcon={StartIcon} className={className} placeholder={placeholder} showDisplayEndIcon={showDisplayEndIcon} />
666
+ return <ReadonlyDisplay mode={resolvedMode} variant={variant} width={width} size={size} options={options} value={value} display={display} startIcon={StartIcon} className={className} placeholder={placeholder} showDisplayEndIcon={showDisplayEndIcon} selectedItemRenderer={selectedItemRenderer} />
575
667
  }
576
668
 
577
669
  // 2026-05-21 D3 Phase B codex 抓:Custom clear 用 setValue 不直接 onChange,uncontrolled clear 才能真清 internal state。
@@ -585,6 +677,44 @@ const CustomSelect = React.forwardRef<HTMLDivElement, SelectProps>(
585
677
  </ItemSuffix>
586
678
  )
587
679
 
680
+ // ── Hidden input form mirror(2026-07-13 D2 拍板 Option A;MUI SelectInput idiom)──
681
+ // 桌機自訂 combobox 無原生 form control,`name` / `required` / `form` 歷史上被靜默丟棄
682
+ // (mobile NativeSelect 卻經 {...props} 生效 = 雙路徑提交語義不對稱 footgun)。
683
+ // 補 visually-hidden `<input>` 鏡像,讓桌機也參與原生表單(裸 <form> submit / FormData):
684
+ // - value **單向鏡射**受控 state(useControllable)= projection,非第二 SSOT(M17);
685
+ // 空值時 value=''(不採 Radix hidden <select> 完整 option tree — 其無空 option 時
686
+ // default 第一項,radix-ui/primitives issue #3521 documented footgun)
687
+ // - 原生 required 僅收顯式 `required` prop(裸表單 opt-in);fieldCtx.required 續走
688
+ // aria-required + useFormValidation(RHF)方法論(form-validation.spec.md 規則 1-9),
689
+ // 不對既有 Field+RHF consumer 注入瀏覽器原生 bubble
690
+ // - onChange 唯一用途 = 瀏覽器 autofill:match 既有非 disabled option 才經 canonical
691
+ // setter 寫回(對齊 MUI SelectInput handleChange 同款 guard),其餘輸入忽略
692
+ // - aria-hidden + tabIndex={-1}:AT 與 Tab 序皆不見(semantics 由 role=combobox trigger
693
+ // own);form-validation 規則 8 focus-first-error 以 DOM name 定位 → 可聚焦 mirror →
694
+ // trigger focus-within 顯示 focus 邊框(桌機 Select 脫離「非 native 控件略過」fallback)
695
+ // 世界級對照:MUI Select 非原生模式 opacity-0 hidden input 攜 name/required
696
+ // (github.com/mui/material-ui .../Select/SelectInput.js;Select.test.js 官方測試涵蓋
697
+ // required 阻止提交 + FormData 值)+ React Spectrum HiddenSelect(github.com/adobe/
698
+ // react-spectrum .../@react-aria/select/src/HiddenSelect.tsx)+ Headless UI Listbox
699
+ // name → hidden input kept in sync(headlessui.com/react/listbox#using-with-html-forms)。
700
+ const hiddenInputEl = (
701
+ <input
702
+ type="text"
703
+ name={name}
704
+ form={form}
705
+ value={value ?? ''}
706
+ onChange={(e) => {
707
+ const matched = options.find((o) => o.value === e.target.value && !o.disabled)
708
+ if (matched) setValue(matched.value)
709
+ }}
710
+ required={required || undefined}
711
+ disabled={disabled}
712
+ aria-hidden="true"
713
+ tabIndex={-1}
714
+ className="absolute bottom-0 left-0 w-full opacity-0 pointer-events-none"
715
+ />
716
+ )
717
+
588
718
  const triggerContent = (
589
719
  <CustomSelectTriggerContent
590
720
  searchable={searchable}
@@ -612,6 +742,16 @@ const CustomSelect = React.forwardRef<HTMLDivElement, SelectProps>(
612
742
 
613
743
  const trigger = (
614
744
  <div
745
+ // passthrough(2026-07-14 dim-9 修;同日 API 策展 D 收窄):SelectProps allowlist
746
+ // (Pick<SelectHTMLAttributes>,見 SelectProps docblock)承諾的 data-* / onFocus /
747
+ // onBlur 等 attrs,mobile NativeSelect 經 {...props} spread 到 <select> 生效,desktop
748
+ // 原本完整列舉全丟 → 同一 <Select> API 隨 pointer type 靜默失效(consumer data-testid /
749
+ // onBlur mobile 有、desktop 無)。rest 先 spread —— component 契約 attrs(role / aria-* /
750
+ // id / tabIndex / onKeyDown / className)在後,不可被覆寫;consumer onKeyDown 經
751
+ // onKeyDownProp 在 handler 內先跑(見下)。mobile-only attrs(autoFocus / autoComplete)
752
+ // 已在 destructure 層丟棄不進 rest(div trigger 無原生語意;desktop 表單語意由 hidden
753
+ // input mirror 承擔,見 D2 docblock)。
754
+ {...(rest as unknown as React.HTMLAttributes<HTMLDivElement>)}
615
755
  ref={ref}
616
756
  id={idProp ?? fieldCtx?.id}
617
757
  role="combobox"
@@ -623,23 +763,36 @@ const CustomSelect = React.forwardRef<HTMLDivElement, SelectProps>(
623
763
  // (field-context.ts labelId jsDoc 明文);consumer aria-label 優先(對齊 slider.tsx:166 guard canonical)。
624
764
  aria-labelledby={ariaLabel ? undefined : fieldCtx?.labelId}
625
765
  aria-invalid={error || undefined}
626
- aria-required={fieldCtx?.required || undefined}
766
+ // D2(2026-07-13):顯式 required prop 同步進 aria-required(mirror 本身 aria-hidden,
767
+ // AT 看不到其 required;語意由 trigger 播報 — 對齊 mobile 原生 <select required> 行為)
768
+ aria-required={(required || fieldCtx?.required) || undefined}
627
769
  aria-describedby={ariaDescribedByProp ?? fieldCtx?.descriptionId}
628
770
  aria-errormessage={ariaErrorMessageProp ?? (error ? fieldCtx?.errorId : undefined)}
629
771
  tabIndex={0}
630
772
  className={cn(
631
- fieldWrapperStyles({ mode: 'edit', variant: variant, size, error }),
773
+ fieldWrapperStyles({ mode: 'edit', variant: variant, width, size, error }),
632
774
  !isTextDisplay && value && !searchable && tagPadding[size],
633
775
  // 2026-05-06 v13.3 SSOT retire:per-control `open && 'border-primary'` 移除。Field default
634
776
  // state machine `data-[state=open]:border-border-hover`(灰深)處理 open。
635
777
  // 2026-07-04 Q1:error border 同樣收進 fieldWrapperStyles error variant(error 勝 focus 色)。
636
- 'cursor-pointer',
778
+ // D2(2026-07-13):`relative` = hidden input mirror 的 absolute 定位容器
779
+ // (fieldWrapperStyles base 無 relative;對齊 NativeSelect tag 分支同款顯式加法)。
780
+ 'relative cursor-pointer',
637
781
  className,
638
782
  )}
639
- style={!isTextDisplay ? { paddingRight: 'var(--field-px)' } : undefined}
783
+ style={styleProp || !isTextDisplay ? { ...styleProp, ...(!isTextDisplay ? { paddingRight: 'var(--field-px)' } : undefined) } : undefined}
640
784
  data-field-mode="edit"
641
785
  data-error={error ? '' : undefined}
642
786
  onKeyDown={(e) => {
787
+ // passthrough(dim-9):consumer onKeyDown 先跑 —— 對齊 native path(<select> 上
788
+ // consumer handler 同樣最先收到);component 導覽邏輯照舊在後。
789
+ onKeyDownProp?.(e as unknown as React.KeyboardEvent<HTMLSelectElement>)
790
+ // 2026-07-14 dim-10 修:內層清除按鈕(SelectClearButton → ItemInlineAction <button>)
791
+ // 的 Enter/Space 會 bubble 到本 handler 被 preventDefault 吞掉 → 鍵盤無法清除。
792
+ // 事件源自 descendant button 時不執行 trigger 鍵盤邏輯(保留原生 activation)。
793
+ // 注意:不能一刀切 e.target !== e.currentTarget — searchable inline <input> 的
794
+ // 鍵盤事件仍需下方 forwardKeyToListbox 轉送選單導覽。
795
+ if (e.target !== e.currentTarget && (e.target as HTMLElement).closest?.('button')) return
643
796
  // 2026-07-05 D4 P0:open 後 ArrowUp/Down/Enter 轉送 cmdk root(見 select-menu.tsx
644
797
  // forwardKeyToListbox docblock — 原「open 後不攔讓方向鍵導覽」在跨 DOM 子樹機制上不成立)
645
798
  if (open && forwardKeyToListbox(listboxId, e)) return
@@ -657,6 +810,7 @@ const CustomSelect = React.forwardRef<HTMLDivElement, SelectProps>(
657
810
  {triggerContent}
658
811
  {clearEl}
659
812
  {chevronEl}
813
+ {hiddenInputEl}
660
814
  </div>
661
815
  )
662
816
 
@@ -34,10 +34,10 @@ const statusOptions: SelectMenuOption[] = [
34
34
  ]
35
35
 
36
36
  const reviewerOptions: SelectMenuOption[] = [
37
- { value: 'alice', label: 'Ada Chen', avatar: { alt: 'Ada Chen', color: 'indigo' }, description: 'Frontend' },
38
- { value: 'bob', label: '張美真', avatar: { alt: '張美真', color: 'magenta' }, description: 'Platform' },
39
- { value: 'carol', label: '林伯彥', avatar: { alt: '林伯彥', color: 'green' }, description: 'Infra' },
40
- { value: 'dave', label: '黃怡君', avatar: { alt: '黃怡君', color: 'turquoise' }, description: 'QA' },
37
+ { value: 'alice', label: 'Ada Chen', avatar: { src: 'https://i.pravatar.cc/48?u=ada-chen', alt: 'Ada Chen', color: 'indigo' }, description: 'Frontend' },
38
+ { value: 'bob', label: '張美真', avatar: { src: 'https://i.pravatar.cc/48?u=zhang-meizhen', alt: '張美真', color: 'magenta' }, description: 'Platform' },
39
+ { value: 'carol', label: '林伯彥', avatar: { src: 'https://i.pravatar.cc/48?u=lin-boyan', alt: '林伯彥', color: 'green' }, description: 'Infra' },
40
+ { value: 'dave', label: '黃怡君', avatar: { src: 'https://i.pravatar.cc/48?u=huang-yijun', alt: '黃怡君', color: 'turquoise' }, description: 'QA' },
41
41
  ]
42
42
 
43
43
  const labelOptions: SelectMenuOption[] = [
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  component: SelectMenu
3
- family: 4
3
+ family: composite # 2026-07-14 修自我矛盾:原 Phase-1 mechanical 填 4,與 body「Layout Family:非上述 family — composite / multi-section」矛盾;Family 4 是 trigger 側 field control(Select / Combobox)的事
4
4
  variants: {}
5
5
  sizes: {}
6
6
  traits:
@@ -29,7 +29,7 @@ SelectMenu 是 **Popover + Command 組成的完整下拉選單浮層**——提
29
29
 
30
30
  ## Controlled-only rationale(Dim 26)
31
31
 
32
- 本元件刻意採 **controlled-only** 模式——**scope 限 value 軸**:value 軸走 `value` + `onValueChange`(型別宣告 optional、無 runtime 強制,但 internal 消費者 Select / Combobox / PeoplePicker 一律傳入),不支援 `defaultValue` uncontrolled fallback。open 軸自 2026-06-11 R2 起走 `useControllable` dual-mode(`open` + `defaultOpen` + `onOpenChange`,`select-menu.tsx:162-166`),不在 controlled-only 範圍。
32
+ 本元件刻意採 **controlled-only** 模式——**scope 限 value 軸**:value 軸走 `value` + `onValueChange`(型別宣告 optional、無 runtime 強制,但 internal 消費者 Select / Combobox / PeoplePicker 一律傳入),不支援 `defaultValue` uncontrolled fallback。open 軸自 2026-06-11 R2 起走 `useControllable` dual-mode(`open` + `defaultOpen` + `onOpenChange`,`select-menu.tsx:187-191`),不在 controlled-only 範圍。
33
33
 
34
34
  **為什麼**:
35
35
  - 內部狀態複雜(search filter / range / menu open state)跟 `value` 雙向 sync 會產生 race condition
@@ -93,7 +93,7 @@ Popover(浮動容器,handle 展開 / 定位)
93
93
 
94
94
  ## Creatable(建立新選項)
95
95
 
96
- 透過 `creatable` + `onCreate` 啟用。search 非空且**無 label 完全相同(忽略大小寫)的既有選項**時,顯示 create row(`Plus` icon + `createLabel(query)`,預設「直接使用「xxx」」;`select-menu.tsx:230-235`)——完全同名時自動隱藏,防重複建立。
96
+ 透過 `creatable` + `onCreate` 啟用。search 非空且**無 label 完全相同(忽略大小寫)的既有選項**時,顯示 create row(`Plus` icon + `createLabel(query)`,預設「直接使用「xxx」」,`select-menu.tsx:164`;顯示/隱藏判斷 `:261-266`,render `:471-495`)——完全同名時自動隱藏,防重複建立。
97
97
 
98
98
  **何時啟用**:
99
99
  - Tag input 允許使用者建立新 tag
@@ -160,7 +160,7 @@ Popover(浮動容器,handle 展開 / 定位)
160
160
  - **Disabled trigger**:trigger 由 consumer(Select / Combobox / PeoplePicker)的 `disabled` prop own,本元件不獨立 disable trigger。
161
161
  - **Loading**:已 codify(見「Loading」段),`loading=true` 且無可顯示選項時 empty slot 渲 panel-center Empty + CircularProgress 48px(stale options 保留)。
162
162
  - **Empty**:已 codify(見「Empty state」段),搜尋無結果 + 非 creatable 時渲 emptyText;creatable 時保留 create row(可鍵盤選取)。
163
- - **Creatable + search 與既有選項完全同名**(忽略大小寫):create row 隱藏(防重複建立,`select-menu.tsx:230-235`);選取既有選項為唯一路徑。
163
+ - **Creatable + search 與既有選項完全同名**(忽略大小寫):create row 隱藏(防重複建立,`select-menu.tsx:261-266`);選取既有選項為唯一路徑。
164
164
  - **Dark mode**:走 Popover / MenuItem semantic token 自動 adapt。
165
165
  - **Density**:row height 由 `MenuItem` SSOT 控(sm/md/lg);SelectMenu 不獨立 own density。
166
166
  - **大量選項(> 100)**:建議上游分頁 / 搜尋收斂或考慮 windowing(對齊 Ant Select / MUI Autocomplete 慣例);本元件無虛擬化。
@@ -169,9 +169,13 @@ Popover(浮動容器,handle 展開 / 定位)
169
169
 
170
170
  ## 為何無 ColorMatrix
171
171
 
172
- SelectMenu 是**多區塊 composite primitive**,不擁有獨立色彩決策:
172
+ SelectMenu 是**多區塊 composite primitive**,色彩全數消費 DS semantic token、不新增 token;但 2026-07-05 D4 後互動色的 **owner 分層**如下(修正舊述「視覺完全繼承內層 primitive」):
173
173
 
174
- - **無 ColorMatrix**:視覺完全繼承既有 DS primitive 的 token(`MenuItem` row / `Command` search / `Empty` layout / `Popover` surface),無自己的 bg / border / hover 決策。色彩漂移由 primitive layer 控制——SelectMenu 層級若加 ColorMatrix 會重複 MenuItem / Popover 的矩陣。
174
+ - **surface / border / elevation**:`PopoverContent` 自設 `bg-surface-raised` + `border-border` + `--elevation-200`(`select-menu.tsx:308-320`)
175
+ - **hover / selected 互動 bg**:owner 是**外層 cmdk `CommandItem`** —— 鍵盤 cursor base `bg-neutral-hover`(command.tsx `data-[selected=true]`)+ 單選 persistent selected `bg-neutral-selected` + selected × cursor 加深一階 `bg-neutral-selected-active`(`select-menu.tsx:439-442`,鏡射 DropdownMenu Q2/D4 canonical)。
176
+ - **內層 `MenuItem`**:強制 `!bg-transparent` 純視覺排版(`select-menu.tsx:460` 選項列 / `:489` create 列),不 own 互動 bg。
177
+
178
+ **無 ColorMatrix 的理由不變**:上述全是既有 token family(`neutral-hover / neutral-selected / neutral-selected-active`,與 DropdownMenu / MenuItem 同組),SelectMenu 不擁有獨立色彩決策;加 ColorMatrix 只會重複 MenuItem / DropdownMenu / Popover 的矩陣。
175
179
 
176
180
  對應 anatomy story:保留 `Overview` / `Inspector` / `SizeMatrix` / `StateBehavior`,額外追加元件特有的 `ModeMatrix`(single / multi / searchable / creatable / grouped 等功能組合矩陣,這是 SelectMenu 真正的決策面向——取代 ColorMatrix)。
177
181
 
@@ -179,7 +183,7 @@ SelectMenu 是**多區塊 composite primitive**,不擁有獨立色彩決策:
179
183
 
180
184
  ## shadcn passthrough 例外說明
181
185
 
182
- SelectMenu 是 **composite**(Popover trigger + Command search + 滾動 MenuItem list + 浮動 surface),純 declarative API。**套 `forwardRef` 簽名但 ref 不附著、無 `...props` spread**(shadcn forwardRef + displayName 統一;`select-menu.tsx:127-130` rationale):
186
+ SelectMenu 是 **composite**(Popover trigger + Command search + 滾動 MenuItem list + 浮動 surface),純 declarative API。**套 `forwardRef` 簽名但 ref 不附著、無 `...props` spread**(shadcn forwardRef + displayName 統一;`select-menu.tsx:151-154` rationale):
183
187
 
184
188
  - **沒有單一 DOM root 可 ref**:trigger / search input / list / content portal 各自 DOM tree 離散
185
189
  - **`...props` spread 目標不明**:composite 的 root wrapper 只是 control 容器,spread 到那裡 consumer 無從預期作用
@@ -192,6 +196,10 @@ SelectMenu 是 **composite**(Popover trigger + Command search + 滾動 MenuItem
192
196
 
193
197
  ---
194
198
 
199
+ ## Option schema 增項紀錄
200
+
201
+ - `iconClassName?`(2026-07-08):option icon 染色通道(status 類彩色選項;WM 戰役——原窄 schema 逼 consumer 手刻 bare trigger)。disabled 色仍勝出(menu-item.tsx 順序保證)。
202
+
195
203
  ## 相關
196
204
 
197
205
  - `../Menu/menu-item.spec.md` — 選項 row 的 item-layout 共用規則(SelectMenu 消費 MenuItem)
@@ -44,6 +44,8 @@ export interface SelectMenuOption {
44
44
  label: string
45
45
  description?: string
46
46
  icon?: LucideIcon
47
+ /** icon 染色 className(2026-07-08:status 類彩色 option;M30 primitive schema 擴充) */
48
+ iconClassName?: string
47
49
  avatar?: AvatarData
48
50
  disabled?: boolean
49
51
  group?: string
@@ -352,8 +354,13 @@ const SelectMenu = React.forwardRef<HTMLElement, SelectMenuProps>(function Selec
352
354
  // `state.value ||` guard 不覆蓋(dist source 驗證);Popover 關閉即 unmount(無
353
355
  // forceMount)→ 每次開啟 remount 重新生效。需搭配下方 CommandItem value={opt.value}。
354
356
  defaultValue={selectedOption?.value}
355
- // 2026-07-06 A11y:combobox accessible name cmdk 永遠渲 sr-only <label htmlFor={inputId}>,
356
- // searchable(真的有 input)時傳,避免 orphan label 指向不存在的 input id。
357
+ // 2026-07-06 A11y:combobox accessible name。2026-07-14 修正註解(cmdk 1.1.1 dist 實證):
358
+ // cmdk **無條件**渲 sr-only <label htmlFor={inputId}>(dist/index.mjs
359
+ // `createElement("label",{htmlFor:U.inputId,id:U.labelId,...},b)` — label prop 只決定
360
+ // 文字內容,不決定 label 元素是否渲染)。故 non-searchable 時「空 label + htmlFor 指向
361
+ // 不存在的 input」仍存在(upstream cmdk 限制;空 label 無 accessible name,不產生朗讀
362
+ // 內容)。僅 searchable(真的有 input)時傳內容 = 避免 non-searchable 帶 search
363
+ // placeholder 的 accessible name 卻無對應 input;真要消除 orphan label 需 upstream 修。
357
364
  label={searchable ? searchPlaceholder : undefined}
358
365
  className="bg-transparent"
359
366
  >
@@ -437,6 +444,7 @@ const SelectMenu = React.forwardRef<HTMLElement, SelectMenuProps>(function Selec
437
444
  <MenuItem
438
445
  size={size}
439
446
  startIcon={opt.icon}
447
+ startIconClassName={opt.iconClassName}
440
448
  avatar={opt.avatar}
441
449
  description={opt.description}
442
450
  checkbox={multiple}
@@ -535,7 +543,12 @@ SelectMenu.displayName = 'SelectMenu'
535
543
  // Phase 2 fill needed: purpose descriptions + when rationale + world-class refs
536
544
  export const selectMenuMeta = {
537
545
  component: 'SelectMenu',
538
- family: 4,
546
+ // 2026-07-14 修 family 自我矛盾:原 Phase-1 mechanical 填 4(Field control),與 spec body
547
+ // 「Layout Family:非上述 family — composite / multi-section」矛盾。SelectMenu 是 Popover +
548
+ // Command composite overlay(trigger 由 consumer Select / Combobox own,Family 4 是 trigger
549
+ // 側 field control 的事);對齊 DS composite 慣例(Accordion / Command / Popover meta 皆
550
+ // family: null + spec frontmatter composite)。
551
+ family: null, // non-family composite / overlay(對齊 spec frontmatter composite)
539
552
  variants: {
540
553
 
541
554
  },
@@ -0,0 +1,39 @@
1
+ // @internal — boolean 值展示符號 SSOT(display / readonly 態);consumer 用 Checkbox / Switch 不直用。
2
+ // ── 消費的 SSOT ──
3
+ // - lucide-react Check / X(DS icon canonical:成功/勾 = Check、關閉/否 = X,見 ui-development.md「Icon canonical」)
4
+ // - components/Field/field-context.ts(FieldSize 型別 → icon 尺寸對齊 value 文字)
5
+ // - tokens/color:text-foreground(展示值中性色,同 readonly value / 空值 canonical)
6
+ //
7
+ // 世界級對照(M22,2026-07-09 本 session 查證):read-only boolean 顯示 = 勾/叉 icon,中性色(非紅綠):
8
+ // MUI X DataGrid boolean cell = Check / Close(X)icon,default 中性色(非 red/green)
9
+ // Refine(Ant)BooleanField https://refine.dev/docs/ui-integrations/ant-design/components/fields/boolean-field/ = 勾 / 叉
10
+ // React-admin BooleanField https://marmelab.com/react-admin/BooleanField.html = TrueIcon / FalseIcon(預設 勾 / 叉)
11
+ // 為何中性色不用紅叉綠勾:false = 「否」值本身,非「錯誤 / 被拒」;error / warning 是另一條 error token 通道,
12
+ // 不靠此 glyph 表達。true/false 對稱同色 = MUI/React-admin 共識。
13
+ import { Check, X } from 'lucide-react'
14
+ import { cn } from '@/lib/utils'
15
+ import type { FieldSize } from '@/design-system/components/Field/field-context'
16
+
17
+ // icon 尺寸對齊 value 文字(fieldDisplayTextClass:sm/md → text-body 14px、lg → text-body-lg 16px)。
18
+ const booleanIconSize: Record<FieldSize, string> = {
19
+ sm: 'size-3.5', // 14px
20
+ md: 'size-3.5', // 14px
21
+ lg: 'size-4', // 16px
22
+ }
23
+
24
+ /**
25
+ * BooleanValueIcon — boolean 值的展示符號(true = Check 勾 / false = X 叉),中性 `text-foreground` 色。
26
+ * Checkbox / Switch 的 display + readonly 態、DataTable boolean cell(經 Checkbox)全消費此 SSOT。
27
+ */
28
+ export function BooleanValueIcon({
29
+ checked,
30
+ size = 'md',
31
+ className,
32
+ }: {
33
+ checked: boolean
34
+ size?: FieldSize
35
+ className?: string
36
+ }) {
37
+ const Icon = checked ? Check : X
38
+ return <Icon aria-hidden className={cn(booleanIconSize[size], 'text-foreground', className)} />
39
+ }
@@ -2,4 +2,5 @@
2
2
  // Re-export pattern enables package.json exports map subpath:
3
3
  // import { ... } from '@qijenchen/design-system/{components,patterns}/<Dir>'
4
4
  // Regenerate: node scripts/gen-component-indexes.mjs
5
+ export * from './boolean-value'
5
6
  export * from './selection-item'
@@ -439,7 +439,7 @@ export const PrefixAlignment: Story = {
439
439
  <div className="border border-divider rounded-md p-4 bg-muted">
440
440
  <SelectionItem
441
441
  control={<Checkbox defaultChecked />}
442
- avatar={{ alt: 'Ada Chen', color: 'indigo' }}
442
+ avatar={{ src: 'https://i.pravatar.cc/48?u=ada-chen', alt: 'Ada Chen', color: 'indigo' }}
443
443
  label="Ada Chen"
444
444
  />
445
445
  </div>
@@ -451,7 +451,7 @@ export const PrefixAlignment: Story = {
451
451
  <div className="border border-divider rounded-md p-4 bg-muted">
452
452
  <SelectionItem
453
453
  control={<Checkbox defaultChecked />}
454
- avatar={{ alt: 'Ada Chen', color: 'indigo' }}
454
+ avatar={{ src: 'https://i.pravatar.cc/64?u=ada-chen', alt: 'Ada Chen', color: 'indigo' }}
455
455
  label="Ada Chen"
456
456
  description="Design Engineer · Frontend team"
457
457
  />