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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (458) hide show
  1. package/CLAUDE.md +1 -0
  2. package/dist/components/Alert/alert.d.ts +5 -0
  3. package/dist/components/Alert/alert.d.ts.map +1 -1
  4. package/dist/components/Alert/alert.js +4 -1
  5. package/dist/components/Alert/alert.js.map +1 -1
  6. package/dist/components/AppShell/app-shell.d.ts +1 -1
  7. package/dist/components/AppShell/app-shell.js.map +1 -1
  8. package/dist/components/AspectRatio/aspect-ratio.d.ts +2 -1
  9. package/dist/components/AspectRatio/aspect-ratio.d.ts.map +1 -1
  10. package/dist/components/AspectRatio/aspect-ratio.js +1 -1
  11. package/dist/components/AspectRatio/aspect-ratio.js.map +1 -1
  12. package/dist/components/Avatar/avatar.d.ts +3 -2
  13. package/dist/components/Avatar/avatar.d.ts.map +1 -1
  14. package/dist/components/Avatar/avatar.js +7 -1
  15. package/dist/components/Avatar/avatar.js.map +1 -1
  16. package/dist/components/Badge/badge.d.ts.map +1 -1
  17. package/dist/components/Badge/badge.js +2 -1
  18. package/dist/components/Badge/badge.js.map +1 -1
  19. package/dist/components/Breadcrumb/breadcrumb.d.ts +14 -1
  20. package/dist/components/Breadcrumb/breadcrumb.d.ts.map +1 -1
  21. package/dist/components/Breadcrumb/breadcrumb.js +9 -3
  22. package/dist/components/Breadcrumb/breadcrumb.js.map +1 -1
  23. package/dist/components/BulkActionBar/bulk-action-bar.d.ts +1 -1
  24. package/dist/components/BulkActionBar/bulk-action-bar.d.ts.map +1 -1
  25. package/dist/components/BulkActionBar/bulk-action-bar.js.map +1 -1
  26. package/dist/components/Button/button.d.ts +3 -3
  27. package/dist/components/Button/button.d.ts.map +1 -1
  28. package/dist/components/Button/button.js +68 -42
  29. package/dist/components/Button/button.js.map +1 -1
  30. package/dist/components/Button/index.d.ts +1 -0
  31. package/dist/components/Button/index.d.ts.map +1 -1
  32. package/dist/components/Button/index.js +3 -0
  33. package/dist/components/Button/index.js.map +1 -1
  34. package/dist/components/Carousel/carousel.d.ts.map +1 -1
  35. package/dist/components/Carousel/carousel.js.map +1 -1
  36. package/dist/components/Chart/chart.d.ts.map +1 -1
  37. package/dist/components/Chart/chart.js +1 -1
  38. package/dist/components/Chart/chart.js.map +1 -1
  39. package/dist/components/Checkbox/checkbox.d.ts +5 -2
  40. package/dist/components/Checkbox/checkbox.d.ts.map +1 -1
  41. package/dist/components/Checkbox/checkbox.js +24 -3
  42. package/dist/components/Checkbox/checkbox.js.map +1 -1
  43. package/dist/components/Checkbox/index.d.ts +1 -0
  44. package/dist/components/Checkbox/index.d.ts.map +1 -1
  45. package/dist/components/Checkbox/index.js +3 -0
  46. package/dist/components/Checkbox/index.js.map +1 -1
  47. package/dist/components/Chip/chip.d.ts +7 -1
  48. package/dist/components/Chip/chip.d.ts.map +1 -1
  49. package/dist/components/Chip/chip.js +5 -1
  50. package/dist/components/Chip/chip.js.map +1 -1
  51. package/dist/components/Coachmark/coachmark.d.ts +5 -3
  52. package/dist/components/Coachmark/coachmark.d.ts.map +1 -1
  53. package/dist/components/Coachmark/coachmark.js +4 -1
  54. package/dist/components/Coachmark/coachmark.js.map +1 -1
  55. package/dist/components/Combobox/combobox.d.ts +2 -0
  56. package/dist/components/Combobox/combobox.d.ts.map +1 -1
  57. package/dist/components/Combobox/combobox.js +7 -6
  58. package/dist/components/Combobox/combobox.js.map +1 -1
  59. package/dist/components/Command/command.d.ts +1 -1
  60. package/dist/components/Command/command.js +2 -1
  61. package/dist/components/Command/command.js.map +1 -1
  62. package/dist/components/DataTable/active-editor-controller.d.ts +4 -3
  63. package/dist/components/DataTable/active-editor-controller.d.ts.map +1 -1
  64. package/dist/components/DataTable/cell-registry.d.ts.map +1 -1
  65. package/dist/components/DataTable/cell-registry.js +3 -0
  66. package/dist/components/DataTable/cell-registry.js.map +1 -1
  67. package/dist/components/DataTable/data-table-filter-panel.d.ts.map +1 -1
  68. package/dist/components/DataTable/data-table-interaction-layer.d.ts +4 -3
  69. package/dist/components/DataTable/data-table-interaction-layer.d.ts.map +1 -1
  70. package/dist/components/DataTable/data-table-interaction-layer.js.map +1 -1
  71. package/dist/components/DataTable/data-table.d.ts +9 -6
  72. package/dist/components/DataTable/data-table.d.ts.map +1 -1
  73. package/dist/components/DataTable/data-table.js +142 -50
  74. package/dist/components/DataTable/data-table.js.map +1 -1
  75. package/dist/components/DataTable/filter-tree.d.ts +5 -0
  76. package/dist/components/DataTable/filter-tree.d.ts.map +1 -1
  77. package/dist/components/DateGrid/date-grid.d.ts +1 -1
  78. package/dist/components/DateGrid/date-grid.d.ts.map +1 -1
  79. package/dist/components/DateGrid/date-grid.js +6 -4
  80. package/dist/components/DateGrid/date-grid.js.map +1 -1
  81. package/dist/components/DatePicker/date-picker.d.ts.map +1 -1
  82. package/dist/components/DatePicker/date-picker.js +124 -106
  83. package/dist/components/DatePicker/date-picker.js.map +1 -1
  84. package/dist/components/Dialog/dialog.d.ts +1 -1
  85. package/dist/components/Dialog/dialog.d.ts.map +1 -1
  86. package/dist/components/Dialog/dialog.js +2 -1
  87. package/dist/components/Dialog/dialog.js.map +1 -1
  88. package/dist/components/DropdownMenu/dropdown-menu.d.ts +1 -1
  89. package/dist/components/DropdownMenu/dropdown-menu.d.ts.map +1 -1
  90. package/dist/components/DropdownMenu/dropdown-menu.js +11 -4
  91. package/dist/components/DropdownMenu/dropdown-menu.js.map +1 -1
  92. package/dist/components/Field/field-wrapper.d.ts +1 -0
  93. package/dist/components/Field/field-wrapper.d.ts.map +1 -1
  94. package/dist/components/Field/field-wrapper.js +27 -7
  95. package/dist/components/Field/field-wrapper.js.map +1 -1
  96. package/dist/components/Field/use-form-validation.d.ts +5 -1
  97. package/dist/components/Field/use-form-validation.d.ts.map +1 -1
  98. package/dist/components/Field/use-form-validation.js +26 -14
  99. package/dist/components/Field/use-form-validation.js.map +1 -1
  100. package/dist/components/FieldControlGroup/field-control-group.d.ts +3 -3
  101. package/dist/components/FieldControlGroup/field-control-group.js.map +1 -1
  102. package/dist/components/FileItem/file-item.d.ts.map +1 -1
  103. package/dist/components/FileItem/file-item.js +3 -2
  104. package/dist/components/FileItem/file-item.js.map +1 -1
  105. package/dist/components/FileUpload/file-upload.d.ts.map +1 -1
  106. package/dist/components/FileUpload/file-upload.js +12 -6
  107. package/dist/components/FileUpload/file-upload.js.map +1 -1
  108. package/dist/components/FileViewer/file-viewer.d.ts.map +1 -1
  109. package/dist/components/FileViewer/file-viewer.js +4 -3
  110. package/dist/components/FileViewer/file-viewer.js.map +1 -1
  111. package/dist/components/Input/input.d.ts.map +1 -1
  112. package/dist/components/Input/input.js +1 -5
  113. package/dist/components/Input/input.js.map +1 -1
  114. package/dist/components/LinkInput/link-input.d.ts +2 -2
  115. package/dist/components/LinkInput/link-input.d.ts.map +1 -1
  116. package/dist/components/LinkInput/link-input.js +29 -28
  117. package/dist/components/LinkInput/link-input.js.map +1 -1
  118. package/dist/components/Menu/menu-item.d.ts.map +1 -1
  119. package/dist/components/Menu/menu-item.js +1 -1
  120. package/dist/components/Menu/menu-item.js.map +1 -1
  121. package/dist/components/Notice/notice.d.ts +5 -0
  122. package/dist/components/Notice/notice.d.ts.map +1 -1
  123. package/dist/components/Notice/notice.js +11 -5
  124. package/dist/components/Notice/notice.js.map +1 -1
  125. package/dist/components/NumberInput/number-input.d.ts.map +1 -1
  126. package/dist/components/NumberInput/number-input.js +15 -7
  127. package/dist/components/NumberInput/number-input.js.map +1 -1
  128. package/dist/components/OverflowIndicator/overflow-indicator.d.ts +1 -1
  129. package/dist/components/OverflowIndicator/overflow-indicator.d.ts.map +1 -1
  130. package/dist/components/OverflowIndicator/overflow-indicator.js +2 -1
  131. package/dist/components/OverflowIndicator/overflow-indicator.js.map +1 -1
  132. package/dist/components/PeoplePicker/people-picker.d.ts +2 -2
  133. package/dist/components/PeoplePicker/people-picker.d.ts.map +1 -1
  134. package/dist/components/PeoplePicker/people-picker.js +36 -35
  135. package/dist/components/PeoplePicker/people-picker.js.map +1 -1
  136. package/dist/components/PeoplePicker/person-display.d.ts.map +1 -1
  137. package/dist/components/PeoplePicker/person-display.js +2 -2
  138. package/dist/components/PeoplePicker/person-display.js.map +1 -1
  139. package/dist/components/Popover/popover.js.map +1 -1
  140. package/dist/components/ProfileCard/profile-card.d.ts +5 -2
  141. package/dist/components/ProfileCard/profile-card.d.ts.map +1 -1
  142. package/dist/components/ProfileCard/profile-card.js.map +1 -1
  143. package/dist/components/RadioGroup/radio-group.d.ts +1 -1
  144. package/dist/components/RadioGroup/radio-group.d.ts.map +1 -1
  145. package/dist/components/RadioGroup/radio-group.js +27 -3
  146. package/dist/components/RadioGroup/radio-group.js.map +1 -1
  147. package/dist/components/Rating/rating.js +3 -3
  148. package/dist/components/Rating/rating.js.map +1 -1
  149. package/dist/components/ScrollArea/scroll-area.d.ts +1 -1
  150. package/dist/components/ScrollArea/scroll-area.js +2 -2
  151. package/dist/components/ScrollArea/scroll-area.js.map +1 -1
  152. package/dist/components/SegmentedControl/segmented-control.d.ts +2 -2
  153. package/dist/components/SegmentedControl/segmented-control.d.ts.map +1 -1
  154. package/dist/components/SegmentedControl/segmented-control.js +10 -5
  155. package/dist/components/SegmentedControl/segmented-control.js.map +1 -1
  156. package/dist/components/Select/select.d.ts +9 -3
  157. package/dist/components/Select/select.d.ts.map +1 -1
  158. package/dist/components/Select/select.js +45 -13
  159. package/dist/components/Select/select.js.map +1 -1
  160. package/dist/components/SelectMenu/index.js +2 -1
  161. package/dist/components/SelectMenu/select-menu.d.ts +17 -4
  162. package/dist/components/SelectMenu/select-menu.d.ts.map +1 -1
  163. package/dist/components/SelectMenu/select-menu.js +39 -6
  164. package/dist/components/SelectMenu/select-menu.js.map +1 -1
  165. package/dist/components/Separator/separator.js +1 -1
  166. package/dist/components/Separator/separator.js.map +1 -1
  167. package/dist/components/Sheet/sheet.d.ts +1 -1
  168. package/dist/components/Sheet/sheet.d.ts.map +1 -1
  169. package/dist/components/Sheet/sheet.js +2 -1
  170. package/dist/components/Sheet/sheet.js.map +1 -1
  171. package/dist/components/Sidebar/sidebar.d.ts.map +1 -1
  172. package/dist/components/Sidebar/sidebar.js +23 -8
  173. package/dist/components/Sidebar/sidebar.js.map +1 -1
  174. package/dist/components/Skeleton/skeleton.d.ts +1 -1
  175. package/dist/components/Skeleton/skeleton.js +2 -2
  176. package/dist/components/Skeleton/skeleton.js.map +1 -1
  177. package/dist/components/Steps/steps.d.ts.map +1 -1
  178. package/dist/components/Steps/steps.js +6 -3
  179. package/dist/components/Steps/steps.js.map +1 -1
  180. package/dist/components/Switch/switch.d.ts +2 -2
  181. package/dist/components/Switch/switch.d.ts.map +1 -1
  182. package/dist/components/Switch/switch.js +38 -8
  183. package/dist/components/Switch/switch.js.map +1 -1
  184. package/dist/components/Tabs/tabs.d.ts +5 -2
  185. package/dist/components/Tabs/tabs.d.ts.map +1 -1
  186. package/dist/components/Tabs/tabs.js +16 -24
  187. package/dist/components/Tabs/tabs.js.map +1 -1
  188. package/dist/components/Tag/tag.d.ts.map +1 -1
  189. package/dist/components/Tag/tag.js +7 -3
  190. package/dist/components/Tag/tag.js.map +1 -1
  191. package/dist/components/Textarea/textarea.d.ts +2 -1
  192. package/dist/components/Textarea/textarea.d.ts.map +1 -1
  193. package/dist/components/Textarea/textarea.js +20 -12
  194. package/dist/components/Textarea/textarea.js.map +1 -1
  195. package/dist/components/TimePicker/time-picker.d.ts +2 -2
  196. package/dist/components/TimePicker/time-picker.d.ts.map +1 -1
  197. package/dist/components/TimePicker/time-picker.js +5 -7
  198. package/dist/components/TimePicker/time-picker.js.map +1 -1
  199. package/dist/components/Toast/toast.d.ts +1 -1
  200. package/dist/components/Toast/toast.d.ts.map +1 -1
  201. package/dist/components/Toast/toast.js +3 -2
  202. package/dist/components/Toast/toast.js.map +1 -1
  203. package/dist/components/Tooltip/tooltip.js +1 -1
  204. package/dist/components/Tooltip/tooltip.js.map +1 -1
  205. package/dist/components/TreeView/tree-view.d.ts.map +1 -1
  206. package/dist/components/TreeView/tree-view.js +3 -2
  207. package/dist/components/TreeView/tree-view.js.map +1 -1
  208. package/dist/index.d.ts +1 -0
  209. package/dist/index.d.ts.map +1 -1
  210. package/dist/index.js +11 -0
  211. package/dist/index.js.map +1 -1
  212. package/dist/patterns/element-anatomy/item-anatomy.d.ts +1 -1
  213. package/dist/patterns/element-anatomy/item-anatomy.js.map +1 -1
  214. package/dist/patterns/overlay-surface/overlay-surface.js.map +1 -1
  215. package/dist/tokens/categorical-color.d.ts +1 -1
  216. package/dist/tokens/categorical-color.js.map +1 -1
  217. package/dist/tokens/uiSize/icon-size.d.ts +2 -2
  218. package/ds-canonical/fork/governance.lock +1 -1
  219. package/ds-canonical/fork/hooks/check_opacity_token_usage.sh +10 -1
  220. package/ds-canonical/hooks/check_audit_post_report_validator.sh +13 -0
  221. package/ds-canonical/hooks/check_full_story_visual_interaction_sweep.sh +4 -3
  222. package/ds-canonical/hooks/check_opacity_token_usage.sh +10 -1
  223. package/ds-canonical/hooks/check_plugin_fork_health.sh +30 -22
  224. package/ds-canonical/hooks/check_story_invariants.sh +17 -5
  225. package/ds-canonical/hooks/check_substantive_edit_approval_preflight.sh +5 -2
  226. package/ds-canonical/references/principle-dim-map.json +100 -0
  227. package/ds-canonical/references/story-baseline-registry.json +4 -3
  228. package/ds-canonical/skills/deep-audit-cross-codex/SKILL.md +1 -1
  229. package/ds-canonical/skills/design-system-audit/SKILL.md +2 -2
  230. package/ds-story-manifest.json +16 -16
  231. package/llms-full.txt +6 -1
  232. package/llms.txt +2 -1
  233. package/package.json +1 -1
  234. package/src/components/Alert/alert.anatomy.stories.tsx +9 -6
  235. package/src/components/Alert/alert.spec.md +3 -3
  236. package/src/components/Alert/alert.tsx +9 -1
  237. package/src/components/AppShell/app-shell.anatomy.stories.tsx +3 -0
  238. package/src/components/AppShell/app-shell.spec.md +3 -2
  239. package/src/components/AppShell/app-shell.tsx +1 -1
  240. package/src/components/AspectRatio/aspect-ratio.spec.md +1 -2
  241. package/src/components/AspectRatio/aspect-ratio.tsx +3 -2
  242. package/src/components/Avatar/avatar.principles.stories.tsx +1 -1
  243. package/src/components/Avatar/avatar.spec.md +1 -1
  244. package/src/components/Avatar/avatar.stories.tsx +1 -1
  245. package/src/components/Avatar/avatar.tsx +17 -9
  246. package/src/components/Badge/badge.anatomy.stories.tsx +2 -2
  247. package/src/components/Badge/badge.spec.md +4 -4
  248. package/src/components/Badge/badge.tsx +4 -1
  249. package/src/components/Breadcrumb/breadcrumb.anatomy.stories.tsx +2 -2
  250. package/src/components/Breadcrumb/breadcrumb.principles.stories.tsx +15 -10
  251. package/src/components/Breadcrumb/breadcrumb.spec.md +4 -2
  252. package/src/components/Breadcrumb/breadcrumb.tsx +10 -4
  253. package/src/components/BulkActionBar/bulk-action-bar.anatomy.stories.tsx +2 -1
  254. package/src/components/BulkActionBar/bulk-action-bar.spec.md +15 -14
  255. package/src/components/BulkActionBar/bulk-action-bar.tsx +2 -2
  256. package/src/components/Button/button.anatomy.stories.tsx +6 -6
  257. package/src/components/Button/button.spec.md +2 -2
  258. package/src/components/Button/button.tsx +63 -26
  259. package/src/components/Button/index.ts +1 -0
  260. package/src/components/Calendar/calendar.anatomy.stories.tsx +2 -2
  261. package/src/components/Calendar/calendar.spec.md +7 -1
  262. package/src/components/Carousel/carousel.principles.stories.tsx +1 -1
  263. package/src/components/Carousel/carousel.tsx +1 -0
  264. package/src/components/Chart/chart.spec.md +1 -1
  265. package/src/components/Chart/chart.tsx +2 -1
  266. package/src/components/Checkbox/checkbox.principles.stories.tsx +1 -1
  267. package/src/components/Checkbox/checkbox.spec.md +1 -1
  268. package/src/components/Checkbox/checkbox.tsx +39 -6
  269. package/src/components/Checkbox/index.ts +1 -0
  270. package/src/components/Chip/chip.anatomy.stories.tsx +1 -1
  271. package/src/components/Chip/chip.spec.md +1 -1
  272. package/src/components/Chip/chip.tsx +4 -4
  273. package/src/components/CircularProgress/circular-progress.anatomy.stories.tsx +1 -1
  274. package/src/components/CircularProgress/circular-progress.principles.stories.tsx +7 -5
  275. package/src/components/CircularProgress/circular-progress.spec.md +2 -0
  276. package/src/components/CircularProgress/circular-progress.stories.tsx +3 -11
  277. package/src/components/Coachmark/coachmark.anatomy.stories.tsx +2 -2
  278. package/src/components/Coachmark/coachmark.principles.stories.tsx +1 -1
  279. package/src/components/Coachmark/coachmark.spec.md +3 -3
  280. package/src/components/Coachmark/coachmark.tsx +13 -5
  281. package/src/components/Combobox/combobox.anatomy.stories.tsx +4 -4
  282. package/src/components/Combobox/combobox.spec.md +1 -1
  283. package/src/components/Combobox/combobox.tsx +23 -16
  284. package/src/components/Command/command.principles.stories.tsx +10 -12
  285. package/src/components/Command/command.spec.md +5 -5
  286. package/src/components/Command/command.stories.tsx +4 -4
  287. package/src/components/Command/command.tsx +1 -1
  288. package/src/components/DataTable/active-editor-controller.ts +4 -3
  289. package/src/components/DataTable/cell-registry.tsx +8 -0
  290. package/src/components/DataTable/data-table-filter-panel.tsx +8 -2
  291. package/src/components/DataTable/data-table-interaction-layer.tsx +8 -7
  292. package/src/components/DataTable/data-table.anatomy.stories.tsx +4 -4
  293. package/src/components/DataTable/data-table.css +2 -2
  294. package/src/components/DataTable/data-table.spec.md +25 -25
  295. package/src/components/DataTable/data-table.stories.tsx +4 -3
  296. package/src/components/DataTable/data-table.tsx +203 -52
  297. package/src/components/DataTable/filter-operators.spec.md +4 -4
  298. package/src/components/DataTable/filter-tree.ts +32 -9
  299. package/src/components/DateGrid/date-grid.anatomy.stories.tsx +1 -1
  300. package/src/components/DateGrid/date-grid.spec.md +6 -4
  301. package/src/components/DateGrid/date-grid.tsx +7 -5
  302. package/src/components/DatePicker/date-picker.principles.stories.tsx +1 -1
  303. package/src/components/DatePicker/date-picker.spec.md +1 -0
  304. package/src/components/DatePicker/date-picker.stories.tsx +19 -0
  305. package/src/components/DatePicker/date-picker.tsx +33 -12
  306. package/src/components/DescriptionList/description-list.spec.md +1 -1
  307. package/src/components/Dialog/dialog.spec.md +1 -1
  308. package/src/components/Dialog/dialog.tsx +2 -1
  309. package/src/components/DropdownMenu/dropdown-menu.anatomy.stories.tsx +3 -1
  310. package/src/components/DropdownMenu/dropdown-menu.spec.md +4 -2
  311. package/src/components/DropdownMenu/dropdown-menu.tsx +11 -3
  312. package/src/components/Empty/empty.anatomy.stories.tsx +1 -1
  313. package/src/components/Empty/empty.spec.md +2 -1
  314. package/src/components/Empty/empty.stories.tsx +4 -15
  315. package/src/components/Field/field-controls.spec.md +1 -0
  316. package/src/components/Field/field-wrapper.tsx +28 -8
  317. package/src/components/Field/field.anatomy.stories.tsx +7 -7
  318. package/src/components/Field/field.principles.stories.tsx +1 -1
  319. package/src/components/Field/field.spec.md +9 -7
  320. package/src/components/Field/form-validation.spec.md +5 -2
  321. package/src/components/Field/use-form-validation.ts +38 -14
  322. package/src/components/FieldControlGroup/field-control-group.anatomy.stories.tsx +11 -10
  323. package/src/components/FieldControlGroup/field-control-group.principles.stories.tsx +3 -3
  324. package/src/components/FieldControlGroup/field-control-group.spec.md +3 -3
  325. package/src/components/FieldControlGroup/field-control-group.tsx +3 -3
  326. package/src/components/FileItem/file-item.anatomy.stories.tsx +6 -4
  327. package/src/components/FileItem/file-item.tsx +3 -2
  328. package/src/components/FileUpload/file-upload.anatomy.stories.tsx +1 -1
  329. package/src/components/FileUpload/file-upload.spec.md +3 -0
  330. package/src/components/FileUpload/file-upload.tsx +21 -6
  331. package/src/components/FileViewer/file-viewer.anatomy.stories.tsx +6 -4
  332. package/src/components/FileViewer/file-viewer.spec.md +2 -2
  333. package/src/components/FileViewer/file-viewer.tsx +4 -3
  334. package/src/components/Input/input.anatomy.stories.tsx +4 -4
  335. package/src/components/Input/input.principles.stories.tsx +1 -1
  336. package/src/components/Input/input.spec.md +1 -1
  337. package/src/components/Input/input.stories.tsx +11 -5
  338. package/src/components/Input/input.tsx +1 -5
  339. package/src/components/LinkInput/link-input.anatomy.stories.tsx +2 -0
  340. package/src/components/LinkInput/link-input.tsx +45 -33
  341. package/src/components/Menu/menu-item.spec.md +2 -1
  342. package/src/components/Menu/menu-item.tsx +3 -1
  343. package/src/components/Notice/notice.anatomy.stories.tsx +10 -5
  344. package/src/components/Notice/notice.spec.md +4 -3
  345. package/src/components/Notice/notice.tsx +22 -5
  346. package/src/components/NumberInput/number-input.anatomy.stories.tsx +5 -4
  347. package/src/components/NumberInput/number-input.principles.stories.tsx +2 -1
  348. package/src/components/NumberInput/number-input.spec.md +2 -1
  349. package/src/components/NumberInput/number-input.tsx +23 -8
  350. package/src/components/OverflowIndicator/overflow-indicator.anatomy.stories.tsx +3 -3
  351. package/src/components/OverflowIndicator/overflow-indicator.spec.md +1 -1
  352. package/src/components/OverflowIndicator/overflow-indicator.tsx +2 -1
  353. package/src/components/PeoplePicker/people-picker.anatomy.stories.tsx +1 -1
  354. package/src/components/PeoplePicker/people-picker.spec.md +4 -4
  355. package/src/components/PeoplePicker/people-picker.stories.tsx +1 -1
  356. package/src/components/PeoplePicker/people-picker.tsx +63 -54
  357. package/src/components/PeoplePicker/person-display.tsx +5 -3
  358. package/src/components/Popover/popover.anatomy.stories.tsx +1 -1
  359. package/src/components/Popover/popover.spec.md +11 -13
  360. package/src/components/Popover/popover.tsx +2 -2
  361. package/src/components/ProfileCard/profile-card.anatomy.stories.tsx +4 -4
  362. package/src/components/ProfileCard/profile-card.principles.stories.tsx +2 -2
  363. package/src/components/ProfileCard/profile-card.spec.md +1 -2
  364. package/src/components/ProfileCard/profile-card.stories.tsx +1 -1
  365. package/src/components/ProfileCard/profile-card.tsx +5 -2
  366. package/src/components/RadioGroup/radio-group.principles.stories.tsx +1 -1
  367. package/src/components/RadioGroup/radio-group.spec.md +2 -2
  368. package/src/components/RadioGroup/radio-group.stories.tsx +3 -1
  369. package/src/components/RadioGroup/radio-group.tsx +36 -3
  370. package/src/components/Rating/rating.anatomy.stories.tsx +11 -8
  371. package/src/components/Rating/rating.principles.stories.tsx +1 -1
  372. package/src/components/Rating/rating.spec.md +2 -2
  373. package/src/components/Rating/rating.stories.tsx +1 -1
  374. package/src/components/Rating/rating.tsx +3 -3
  375. package/src/components/ScrollArea/scroll-area.spec.md +1 -0
  376. package/src/components/ScrollArea/scroll-area.tsx +1 -1
  377. package/src/components/SegmentedControl/segmented-control.anatomy.stories.tsx +2 -2
  378. package/src/components/SegmentedControl/segmented-control.spec.md +1 -1
  379. package/src/components/SegmentedControl/segmented-control.tsx +16 -6
  380. package/src/components/Select/select.anatomy.stories.tsx +2 -2
  381. package/src/components/Select/select.spec.md +2 -1
  382. package/src/components/Select/select.tsx +37 -16
  383. package/src/components/SelectMenu/select-menu.anatomy.stories.tsx +1 -1
  384. package/src/components/SelectMenu/select-menu.principles.stories.tsx +1 -1
  385. package/src/components/SelectMenu/select-menu.spec.md +11 -8
  386. package/src/components/SelectMenu/select-menu.tsx +92 -10
  387. package/src/components/SelectionControl/selection-item.anatomy.stories.tsx +1 -1
  388. package/src/components/SelectionControl/selection-item.principles.stories.tsx +2 -2
  389. package/src/components/SelectionControl/selection-item.spec.md +2 -1
  390. package/src/components/SelectionControl/selection-item.stories.tsx +1 -1
  391. package/src/components/Separator/separator.anatomy.stories.tsx +2 -2
  392. package/src/components/Separator/separator.tsx +1 -1
  393. package/src/components/Sheet/sheet.spec.md +1 -1
  394. package/src/components/Sheet/sheet.tsx +2 -1
  395. package/src/components/Sidebar/sidebar.anatomy.stories.tsx +1 -1
  396. package/src/components/Sidebar/sidebar.spec.md +2 -1
  397. package/src/components/Sidebar/sidebar.stories.tsx +6 -4
  398. package/src/components/Sidebar/sidebar.tsx +36 -11
  399. package/src/components/Skeleton/skeleton.principles.stories.tsx +1 -1
  400. package/src/components/Skeleton/skeleton.spec.md +1 -2
  401. package/src/components/Skeleton/skeleton.tsx +1 -1
  402. package/src/components/Slider/slider.spec.md +4 -4
  403. package/src/components/Steps/steps.anatomy.stories.tsx +1 -1
  404. package/src/components/Steps/steps.spec.md +5 -4
  405. package/src/components/Steps/steps.tsx +11 -3
  406. package/src/components/Switch/switch.spec.md +3 -3
  407. package/src/components/Switch/switch.tsx +51 -10
  408. package/src/components/Tabs/tabs.anatomy.stories.tsx +3 -2
  409. package/src/components/Tabs/tabs.principles.stories.tsx +5 -3
  410. package/src/components/Tabs/tabs.spec.md +2 -1
  411. package/src/components/Tabs/tabs.tsx +32 -25
  412. package/src/components/Tag/tag.principles.stories.tsx +1 -1
  413. package/src/components/Tag/tag.tsx +7 -3
  414. package/src/components/Textarea/textarea.spec.md +2 -1
  415. package/src/components/Textarea/textarea.tsx +16 -9
  416. package/src/components/TimePicker/time-picker.anatomy.stories.tsx +2 -1
  417. package/src/components/TimePicker/time-picker.spec.md +2 -2
  418. package/src/components/TimePicker/time-picker.tsx +6 -8
  419. package/src/components/Toast/toast.anatomy.stories.tsx +5 -6
  420. package/src/components/Toast/toast.principles.stories.tsx +10 -11
  421. package/src/components/Toast/toast.spec.md +5 -5
  422. package/src/components/Toast/toast.stories.tsx +5 -1
  423. package/src/components/Toast/toast.tsx +9 -6
  424. package/src/components/Tooltip/tooltip.spec.md +3 -3
  425. package/src/components/Tooltip/tooltip.tsx +1 -1
  426. package/src/components/TreeView/tree-view.anatomy.stories.tsx +6 -1
  427. package/src/components/TreeView/tree-view.principles.stories.tsx +2 -2
  428. package/src/components/TreeView/tree-view.spec.md +1 -1
  429. package/src/components/TreeView/tree-view.tsx +17 -2
  430. package/src/index.ts +1 -1
  431. package/src/patterns/element-anatomy/element-anatomy.spec.md +3 -3
  432. package/src/patterns/element-anatomy/item-anatomy.spec.md +4 -12
  433. package/src/patterns/element-anatomy/item-anatomy.stories.tsx +1 -0
  434. package/src/patterns/element-anatomy/item-anatomy.tsx +1 -1
  435. package/src/patterns/header-canonical/header-canonical.css +1 -2
  436. package/src/patterns/header-canonical/header-canonical.spec.md +5 -5
  437. package/src/patterns/horizontal-overflow/horizontal-overflow.spec.md +5 -4
  438. package/src/patterns/overlay-surface/overlay-surface.spec.md +8 -7
  439. package/src/patterns/overlay-surface/overlay-surface.tsx +5 -5
  440. package/src/patterns/resize-handle/resize-handle.spec.md +1 -1
  441. package/src/tokens/categorical-color.ts +1 -1
  442. package/src/tokens/color/color.spec.md +6 -6
  443. package/src/tokens/color/color.stories.tsx +1 -1
  444. package/src/tokens/color/primitives.css +7 -5
  445. package/src/tokens/density/density.spec.md +2 -0
  446. package/src/tokens/elevation/elevation.spec.md +1 -1
  447. package/src/tokens/layoutSpace/layoutSpace.spec.md +1 -1
  448. package/src/tokens/layoutSpace/layoutSpace.stories.tsx +3 -3
  449. package/src/tokens/motion/motion.spec.md +1 -1
  450. package/src/tokens/opacity/opacity.stories.tsx +7 -11
  451. package/src/tokens/radius/radius.spec.md +1 -1
  452. package/src/tokens/typography/typography.css +1 -1
  453. package/src/tokens/typography/typography.spec.md +1 -1
  454. package/src/tokens/uiSize/icon-size.ts +2 -2
  455. package/src/tokens/uiSize/uiSize.spec.md +12 -11
  456. package/src/tokens/utility-registry.json +5 -4
  457. package/dist/node_modules/react-hook-form/dist/index.esm.js +0 -1950
  458. package/dist/node_modules/react-hook-form/dist/index.esm.js.map +0 -1
@@ -58,7 +58,7 @@ const SIZE_SPECS: Record<SizeKey, SizeSpec> = {
58
58
 
59
59
  const MODE_DESC: Record<ModeKey, string> = {
60
60
  edit: '表單可編輯欄位 — bg-surface + border + hover/focus 回饋',
61
- display: '純展示資料 — 無 chrome(transparent)+ 文字正常色,空值顯示 —(em dash)',
61
+ display: '純展示資料 — 無 chrome(transparent + !px-0 !py-0 零內距,field-wrapper.tsx Path Ⅰ)+ 文字正常色,空值顯示 —(em dash)',
62
62
  readonly: '不可編輯但可見 — bg-readonly(neutral-2) + 無邊框 + 文字正常色',
63
63
  disabled: '被停用的欄位 — bg-disabled(neutral-2) + 無邊框 + 文字灰化',
64
64
  }
@@ -360,7 +360,7 @@ const InspectorInner = () => {
360
360
  </div>
361
361
  <div className="flex items-center">
362
362
  <div className="flex items-center rounded-md overflow-hidden" style={{ height: 52, outline: `2px solid ${Z.dim.text}22` }}>
363
- <BpZone w={44} color={Z.pad} label={s.pxToken} sub={`${s.px}px`} />
363
+ <BpZone w={44} color={Z.pad} label={mode === 'display' ? '!px-0' : s.pxToken} sub={mode === 'display' ? '0px' : `${s.px}px`} />
364
364
  {hasStartIcon && (
365
365
  <>
366
366
  <BpZone w={44} color={Z.icon} label={`${s.icon}px`} sub="icon" />
@@ -374,7 +374,7 @@ const InspectorInner = () => {
374
374
  <BpZone w={44} color={Z.action} label={`${s.icon}px`} sub="action" />
375
375
  </>
376
376
  )}
377
- <BpZone w={44} color={Z.pad} label={s.pxToken} sub={`${s.px}px`} />
377
+ <BpZone w={44} color={Z.pad} label={mode === 'display' ? '!px-0' : s.pxToken} sub={mode === 'display' ? '0px' : `${s.px}px`} />
378
378
  </div>
379
379
  <div className="ml-3 flex items-center" style={{ height: 52 }}>
380
380
  <svg width="10" height="52" className="shrink-0">
@@ -425,7 +425,7 @@ const InspectorInner = () => {
425
425
  <div className="px-4 py-1">
426
426
  <div className="py-2 border-b border-divider"><span className="text-[10px] font-semibold text-fg-muted uppercase tracking-wider">Layout</span></div>
427
427
  <PropRow label="高度" dot={Z.dim.text}><TkVal token={s.heightToken} value={s.height} /></PropRow>
428
- <PropRow label="左右內距" dot={Z.pad.text}><TkVal token={s.pxToken} value={`${s.px}px`} /></PropRow>
428
+ <PropRow label="左右內距" dot={Z.pad.text}><TkVal token={mode === 'display' ? '!px-0 !py-0' : s.pxToken} value={mode === 'display' ? '0px(零 chrome)' : `${s.px}px`} /></PropRow>
429
429
  <PropRow label="元素間距" dot={Z.gap.text}><TkVal token={s.gapToken} value={`${s.gap}px`} /></PropRow>
430
430
  {hasStartIcon && (
431
431
  <PropRow label="Icon 尺寸" dot={Z.icon.text}>{s.icon}px</PropRow>
@@ -35,7 +35,7 @@ const Label = ({ children, warn }: { children: React.ReactNode; warn?: boolean }
35
35
 
36
36
  const Section = ({ title, children }: { title: string; children: React.ReactNode }) => (
37
37
  <section className="mb-12">
38
- <h2 className="text-heading-3 font-bold text-foreground mb-4 pb-2 border-b border-border">{title}</h2>
38
+ <h2 className="text-h3 font-bold text-foreground mb-4 pb-2 border-b border-border">{title}</h2>
39
39
  {children}
40
40
  </section>
41
41
  )
@@ -109,7 +109,7 @@ endAction(host 內嵌 inline action)vs 獨立 `<Button iconOnly>` 的分界詳 `
109
109
 
110
110
  ## 邊界案例
111
111
 
112
- - **Disabled**:`disabled` prop 由 Field SSOT own(`Field/field.spec.md`「Field state machine SSOT」段)。視覺 token:wrapper bg → `bg-disabled`(neutral-2)、text → `text-fg-disabled`(neutral-6,M24 state>emphasis canonical)、startIcon → `text-fg-disabled`、endAction button 自動 disabled、cursor → `cursor-not-allowed`、border 弱化、無 hover/focus ring。`readOnly` 與 `disabled` 視覺分離:readOnly 維持 default text color(可選取複製)、disabled 全面弱化(不可選)。
112
+ - **Disabled**:`disabled` prop 由 Field SSOT own(`Field/field.spec.md`「Field state machine SSOT」段)。視覺 token:wrapper bg → `bg-disabled`(neutral-2)、text → `text-fg-disabled`(neutral-6,M24 state>emphasis canonical)、startIcon → `text-fg-disabled`、endAction 不渲染(僅 edit mode 渲染)、cursor → `cursor-not-allowed`、border 弱化、無 hover/focus ring。`readOnly` 與 `disabled` 視覺分離:readOnly 維持 default text color(可選取複製)、disabled 全面弱化(不可選)。
113
113
  - **Loading**:已 codify(見「Loading」段)。`loading=true` 時 endAction slot 自動切 `<CircularProgress/>`,input 仍可編輯(Ant Search 派 idiom)。
114
114
  - **Empty(no value)**:placeholder 走 `text-fg-muted`(neutral-7);`disabled + empty` 時 placeholder 切 `text-fg-disabled`(M24 state precedence)。
115
115
  - **Dark mode**:走 semantic token 自動 adapt,無 per-component override。
@@ -2,6 +2,7 @@ import React from 'react'
2
2
  import type { Meta, StoryObj } from '@storybook/react'
3
3
  import { Search, Eye, EyeOff, X } from 'lucide-react'
4
4
  import { Input } from './input'
5
+ import { Field, FieldLabel, FieldError } from '@/design-system/components/Field/field'
5
6
  import { Button } from '@/design-system/components/Button/button'
6
7
  import { Tooltip, TooltipContent, TooltipTrigger } from '@/design-system/components/Tooltip/tooltip'
7
8
 
@@ -114,15 +115,20 @@ export const EndAction: Story = {
114
115
  }
115
116
 
116
117
  /* ── Error ── */
118
+ // 2026-07-04 audit Dim 24/M23(d):錯誤訊息改消費 Field + FieldError primitive(對齊 Textarea / TimePicker /
119
+ // Rating showcase idiom),不手刻 <p text-error>(重複 FieldError 內部樣式 = drift 來源)。
117
120
  export const ErrorState: Story = {
118
121
  name: '錯誤狀態',
119
122
  render: () => (
120
123
  <div className="flex flex-col gap-4 max-w-sm">
121
- <p className="text-caption text-fg-muted">Error 以紅色邊框表示。錯誤訊息由 Form help text 提供,不在 input 內放狀態 icon</p>
122
- <div>
123
- <Input error defaultValue="invalid-email@" />
124
- <p className="text-caption text-error mt-1">請輸入有效的 email 地址</p>
125
- </div>
124
+ <p className="text-caption text-fg-muted">Error 以紅色邊框表示。錯誤訊息由 FieldError 提供,不在 input 內放狀態 icon</p>
125
+ {/* Field invalid 自動 cascade 到 Input(useResolvedFieldInvalid),不需重複傳 error;
126
+ standalone <Input error> 用法由 anatomy「+ error prop」section 展示 */}
127
+ <Field invalid>
128
+ <FieldLabel>Email</FieldLabel>
129
+ <Input defaultValue="invalid-email@" />
130
+ <FieldError>請輸入有效的 email 地址</FieldError>
131
+ </Field>
126
132
  </div>
127
133
  ),
128
134
  }
@@ -155,11 +155,7 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
155
155
  return (
156
156
  <div
157
157
  className={cn(
158
- fieldWrapperStyles({ mode: resolvedMode, variant: variant, size }),
159
- isEditable && resolvedError && [
160
- 'border-error hover:border-error-hover',
161
- 'focus-within:border-error focus-within:hover:border-error',
162
- ],
158
+ fieldWrapperStyles({ mode: resolvedMode, variant: variant, size, error: resolvedError }),
163
159
  // autoWidth:wrapper 縮到 inline-flex + w-auto,讓寬度由 startIcon + input(field-sizing: content)+ endAction 自然累加
164
160
  autoWidth && 'inline-flex w-auto',
165
161
  className,
@@ -257,6 +257,7 @@ export const Overview = {
257
257
  <tbody>
258
258
  {[
259
259
  ['mode', "'edit' | 'display' | 'readonly' | 'disabled'", "'edit'", 'FieldMode 四模式'],
260
+ ['variant', "'default' | 'bare' | 'naked'", "'default'", 'Field chrome variant(bare:toolbar / cell-as-input 透明 chrome)'],
260
261
  ['size', "'sm' | 'md' | 'lg'", "'md'", '尺寸,與 Button 共用 token'],
261
262
  ['error', 'boolean', 'false', '錯誤狀態,紅色邊框 + aria-invalid'],
262
263
  ['value', 'string | null', '—', 'URL 值'],
@@ -264,6 +265,7 @@ export const Overview = {
264
265
  ['placeholder', 'string', "'https://'", '空值時的佔位文字'],
265
266
  ['disabled', 'boolean', 'false', '等同 mode="disabled"'],
266
267
  ['label', 'string', '—', '自訂顯示文字(覆蓋自動 hostname 提取)'],
268
+ ['showDisplayEndIcon', 'boolean', 'false', 'display 態改包 Field naked wrapper(D-path opt-in,DataTable cell display↔edit 像素對齊;本元件 edit 無 endIcon,故僅 wrapper 無 ItemSuffix)'],
267
269
  ].map(([p, t, d, desc]) => (
268
270
  <tr key={p}><Td mono>{p}</Td><Td mono>{t}</Td><Td mono>{d}</Td><Td>{desc}</Td></tr>
269
271
  ))}
@@ -4,7 +4,7 @@ import type { VariantProps } from 'class-variance-authority'
4
4
  import { cn } from '@/lib/utils'
5
5
  import type { FieldMode, FieldVariant } from '@/design-system/components/Field/field-types'
6
6
  import { fieldWrapperStyles, bareInputStyles, EMPTY_DISPLAY, fieldDisplayTextClass } from '@/design-system/components/Field/field-wrapper'
7
- import { useFieldContext, useResolvedFieldSize, useResolvedFieldDisabled, useResolvedFieldMode, useResolvedFieldVariant } from '@/design-system/components/Field/field-context'
7
+ import { useFieldContext, useResolvedFieldSize, useResolvedFieldDisabled, useResolvedFieldMode, useResolvedFieldVariant, useResolvedFieldInvalid } from '@/design-system/components/Field/field-context'
8
8
  import { ItemInlineAction } from '@/design-system/patterns/element-anatomy/item-anatomy'
9
9
 
10
10
  // ── URL Validation ──────────────────────────────────────────────────────────
@@ -110,6 +110,37 @@ const LinkInput = React.forwardRef<HTMLInputElement, LinkInputProps>(
110
110
  // chrome resolution:per-prop > context > 'default'
111
111
  const resolvedVariant: FieldVariant = useResolvedFieldVariant(variantProp)
112
112
 
113
+ // 2026-07-05 D4:invalid 經 useResolvedFieldInvalid 統一接 fieldCtx.invalid(field-controls.spec.md
114
+ // resolver 契約「error/invalid = prop OR fieldCtx.invalid」)— 原本紅框只吃 errorProp||localError、
115
+ // aria-invalid 卻 inline 加 fieldCtx?.invalid → <Field invalid> 下 AT 聽到 invalid 視覺卻正常框
116
+ // (聚焦還變藍)。紅框與 aria 自此同源。宣告於 display early return 之前(Rules-of-Hooks)。
117
+ const fieldInvalid = useResolvedFieldInvalid(errorProp)
118
+
119
+ // ── Local state / refs ──────────────────────────────────────────────────
120
+ // 2026-07-04 Rules-of-Hooks 修:hooks 必在 display early return 之前宣告,
121
+ // 否則 mode 於 render 間切換 display↔edit 會改變 hook 呼叫數 → React #310 crash
122
+ // (同 beta.76 Combobox 同款修法;僅搬位置,hook 邏輯不變)。
123
+ const [editing, setEditing] = React.useState(false)
124
+ const [localValue, setLocalValue] = React.useState(value ?? '')
125
+ const [localError, setLocalError] = React.useState(false)
126
+ const inputRef = React.useRef<HTMLInputElement | null>(null)
127
+
128
+ // Sync external value → local
129
+ React.useEffect(() => {
130
+ if (!editing) setLocalValue(value ?? '')
131
+ }, [value, editing])
132
+
133
+ // Merge refs
134
+ const setRef = React.useCallback((el: HTMLInputElement | null) => {
135
+ inputRef.current = el
136
+ if (typeof ref === 'function') ref(el)
137
+ else if (ref) (ref as React.MutableRefObject<HTMLInputElement | null>).current = el
138
+ }, [ref])
139
+
140
+ // 2026-05-16 audit codex Round 6:capture rAF + cancel on unmount(defensive hygiene)
141
+ const focusRafIdRef = React.useRef<number>(0)
142
+ React.useEffect(() => () => { if (focusRafIdRef.current) cancelAnimationFrame(focusRafIdRef.current) }, [])
143
+
113
144
  // ── mode='display' ─────────────────────────────────────────────────────
114
145
  // 純展示:無 input chrome / 無 hover affordance / 無 Pencil edit 入口。
115
146
  // 取代既有 LinkInputDisplay sub-component(2026-05-05 Phase B3 retire)。
@@ -140,30 +171,9 @@ const LinkInput = React.forwardRef<HTMLInputElement, LinkInputProps>(
140
171
  )
141
172
  }
142
173
 
143
- const [editing, setEditing] = React.useState(false)
144
- const [localValue, setLocalValue] = React.useState(value ?? '')
145
- const [localError, setLocalError] = React.useState(false)
146
- const inputRef = React.useRef<HTMLInputElement | null>(null)
147
-
148
- // Sync external value → local
149
- React.useEffect(() => {
150
- if (!editing) setLocalValue(value ?? '')
151
- }, [value, editing])
152
-
153
- // Merge refs
154
- const setRef = React.useCallback((el: HTMLInputElement | null) => {
155
- inputRef.current = el
156
- if (typeof ref === 'function') ref(el)
157
- else if (ref) (ref as React.MutableRefObject<HTMLInputElement | null>).current = el
158
- }, [ref])
159
-
160
174
  const hasValidValue = !!value && isValidUrl(value)
161
175
  const showLink = isEditable && hasValidValue && !editing && !localError
162
- const error = errorProp || localError
163
-
164
- // 2026-05-16 audit codex Round 6:capture rAF + cancel on unmount(defensive hygiene)
165
- const focusRafIdRef = React.useRef<number>(0)
166
- React.useEffect(() => () => { if (focusRafIdRef.current) cancelAnimationFrame(focusRafIdRef.current) }, [])
176
+ const error = fieldInvalid || localError
167
177
 
168
178
  const handleEdit = () => {
169
179
  setEditing(true)
@@ -233,8 +243,11 @@ const LinkInput = React.forwardRef<HTMLInputElement, LinkInputProps>(
233
243
  if (showLink) {
234
244
  return (
235
245
  <div
236
- className={cn(fieldWrapperStyles({ mode: 'edit', variant: resolvedVariant, size }), className)}
246
+ // 2026-07-05 D4:link 顯示分支同樣接 error(errorProp / fieldCtx.invalid)紅框,
247
+ // 否則 <Field invalid> 下 link state 完全無 error 呈現
248
+ className={cn(fieldWrapperStyles({ mode: 'edit', variant: resolvedVariant, size, error }), className)}
237
249
  data-field-mode="edit"
250
+ data-error={error ? '' : undefined}
238
251
  >
239
252
  <span className="flex-1 min-w-0">
240
253
  {value && renderLinkAnchor(value, label)}
@@ -251,11 +264,7 @@ const LinkInput = React.forwardRef<HTMLInputElement, LinkInputProps>(
251
264
  return (
252
265
  <div
253
266
  className={cn(
254
- fieldWrapperStyles({ mode: 'edit', variant: resolvedVariant, size }),
255
- error && [
256
- 'border-error hover:border-error-hover',
257
- 'focus-within:border-error focus-within:hover:border-error',
258
- ],
267
+ fieldWrapperStyles({ mode: 'edit', variant: resolvedVariant, size, error }),
259
268
  className,
260
269
  )}
261
270
  data-field-mode="edit"
@@ -270,10 +279,10 @@ const LinkInput = React.forwardRef<HTMLInputElement, LinkInputProps>(
270
279
  onBlur={handleBlur}
271
280
  onKeyDown={handleKeyDown}
272
281
  placeholder={placeholder}
273
- aria-invalid={(error || fieldCtx?.invalid) || undefined}
282
+ aria-invalid={error || undefined}
274
283
  aria-required={fieldCtx?.required || undefined}
275
284
  aria-describedby={ariaDescribedByProp ?? fieldCtx?.descriptionId}
276
- aria-errormessage={ariaErrorMessageProp ?? ((error || fieldCtx?.invalid) ? fieldCtx?.errorId : undefined)}
285
+ aria-errormessage={ariaErrorMessageProp ?? (error ? fieldCtx?.errorId : undefined)}
277
286
  className={bareInputStyles}
278
287
  {...props}
279
288
  />
@@ -294,9 +303,12 @@ export const linkInputMeta = {
294
303
  sizes: {
295
304
 
296
305
  },
297
- states: ['default', 'hover', 'active', 'focus-visible', 'disabled'],
306
+ // states 對齊真實 state 集:text-input 家族無 'active'(按下)專屬視覺態(同 textareaMeta 修法);
307
+ // readonly / error 為 fieldWrapperStyles mode + errorProp/localError 實有狀態(2026-07-04 audit 對齊)。
308
+ states: ['default', 'hover', 'focus-visible', 'readonly', 'disabled', 'error'],
298
309
  tokens: {
299
- bg: [],
310
+ // bg 經 fieldWrapperStyles 實渲:edit bg-surface / readonly bg-readonly / disabled bg-disabled(field-wrapper.tsx)。
311
+ bg: ['bg-surface', 'bg-readonly', 'bg-disabled'],
300
312
  fg: ['text-fg-disabled', 'text-fg-muted', 'text-primary'],
301
313
  ring: [],
302
314
  },
@@ -150,6 +150,7 @@ Menu 的設計目的是「**快速掃視多個選項挑一個**」。垂直空
150
150
  | default | transparent | foreground | — |
151
151
  | hover | `--neutral-hover` | foreground | 滑鼠 hover |
152
152
  | selected(單選) | `--neutral-selected` | foreground | bg 高亮(不用 ✓ 勾號,避免影響 prefix 對齊) |
153
+ | **selected × hover(疊加)** | `--neutral-selected`(**selected 勝**) | foreground | 2026-07-04 Q2 拍板 M5 明文:bg 是唯一選中指示器,hover 不得洗掉。code = selected 時關 hover bg(menu-item.tsx)。對齊 Ant Menu `:not(-item-selected)` / VS Code list `:hover:not(.selected)`;消費者(DropdownMenu highlighted)同規則 |
153
154
  | selected(多選) | transparent | foreground | checkbox 勾選 |
154
155
  | disabled | transparent | `--fg-disabled` | disabled prop |
155
156
 
@@ -223,7 +224,7 @@ Menu item 的 prefix icon 跟 label 同色(foreground),不是 fg-muted。P
223
224
  | 建立操作選單(複製 / 刪除 / 分享)| 用 `DropdownMenu` + `DropdownMenuItem`(內部消費 MenuItem)|
224
225
  | 建立選值下拉(選項 + 搜尋)| 用 `SelectMenu`(內部消費 MenuItem)|
225
226
  | 建立右鍵選單 | 用未來的 `ContextMenu`(待開發)|
226
- | 直接在 JSX 中用 `<MenuItem>` | ❌ **禁止**——會失去 menu 外層的 Radix 無障礙 / 鍵盤 / 焦點管理 |
227
+ | 直接在 JSX 中用 `<MenuItem>` | ❌ **禁止**——會失去 menu 外層的 Radix 無障礙 / 鍵盤 / 焦點管理。唯一例外:本元件**自身 stories** 的 internal 展示(demo 容器以 `role="listbox"` 滿足 a11y parent 要求,見 `menu-item.stories.tsx`),產品 code 無豁免 |
227
228
 
228
229
  ### 消費者
229
230
 
@@ -202,9 +202,11 @@ const MenuItem = React.forwardRef<HTMLDivElement, MenuItemProps>(
202
202
  disabled && 'text-fg-disabled cursor-not-allowed',
203
203
  className,
204
204
  )}
205
+ // 2026-07-04 robustness:先 spread 其餘 props、後放 guarded handlers — 消除
206
+ // 「handler 置前 + 後方 runtime filter 同名 key」對屬性順序的隱性依賴,行為不變
207
+ {...Object.fromEntries(Object.entries(props).filter(([k]) => k !== 'onClick' && k !== 'onKeyDown'))}
205
208
  onClick={disabled ? undefined : props.onClick}
206
209
  onKeyDown={disabled ? undefined : props.onKeyDown}
207
- {...Object.fromEntries(Object.entries(props).filter(([k]) => k !== 'onClick' && k !== 'onKeyDown'))}
208
210
  >
209
211
  {/* Prefix 對齊容器 */}
210
212
  {hasPrefix && (
@@ -96,6 +96,7 @@ export const Overview: Story = {
96
96
  升級方案
97
97
  </Button>
98
98
  }
99
+ onDismiss={() => {}} // anatomy 4-slot 展示含 dismiss X(2026-07-05 D4:X 僅在傳 onDismiss 時渲染)
99
100
  />
100
101
  </SubtleShell>
101
102
  </div>
@@ -189,13 +190,13 @@ export const Overview: Story = {
189
190
  <Td mono>dismissible</Td>
190
191
  <Td mono>boolean</Td>
191
192
  <Td mono>true</Td>
192
- <Td>顯示關閉按鈕</Td>
193
+ <Td>搭配 onDismiss 顯示關閉按鈕(兩者同時成立才渲染 X)</Td>
193
194
  </tr>
194
195
  <tr>
195
196
  <Td mono>onDismiss</Td>
196
197
  <Td mono>() =&gt; void</Td>
197
198
  <Td mono>undefined</Td>
198
- <Td>關閉按鈕 callback</Td>
199
+ <Td>關閉按鈕 callback;未傳則不渲染 X(2026-07-05 D4)</Td>
199
200
  </tr>
200
201
  <tr>
201
202
  <Td mono>iconClassName</Td>
@@ -248,6 +249,7 @@ function NoticeInspector() {
248
249
  ) : undefined
249
250
  }
250
251
  dismissible={dismissible}
252
+ onDismiss={() => {}} // 2026-07-05 D4:X 僅在傳 onDismiss 時渲染,Inspector 切 dismissible 才看得到 X
251
253
  />
252
254
  </SubtleShell>
253
255
  </div>
@@ -481,19 +483,22 @@ export const StateBehavior: Story = {
481
483
  <div>
482
484
  <H3>Dismissible 行為</H3>
483
485
  <Desc>
484
- Alert 預設可 dismiss(持久通知,使用者需主動關);Toast 通常不 dismiss(計時器自動關)。兩者共用 Notice,僅透過
486
+ Alert 預設可 dismiss(持久通知,使用者需主動關);Toast 通常不 dismiss(計時器自動關)。兩者共用 Notice,透過
485
487
  <code className="font-mono text-footnote mx-1">dismissible</code>
486
- prop 控制。
488
+ +
489
+ <code className="font-mono text-footnote mx-1">onDismiss</code>
490
+ 控制(X 僅在兩者同時成立時渲染——未接 handler 的 X 是死按鈕,2026-07-05 D4)。
487
491
  </Desc>
488
492
  <div className="grid grid-cols-2 gap-3 max-w-3xl">
489
493
  <div>
490
- <p className="text-caption text-fg-muted mb-2">dismissible (default)</p>
494
+ <p className="text-caption text-fg-muted mb-2">dismissible + onDismiss</p>
491
495
  <SubtleShell variant="info">
492
496
  <Notice
493
497
  variant="info"
494
498
  iconClassName={SUBTLE_ICON_COLOR.info}
495
499
  title="Audit log 僅限 Business 方案"
496
500
  dismissible
501
+ onDismiss={() => {}}
497
502
  />
498
503
  </SubtleShell>
499
504
  </div>
@@ -57,7 +57,7 @@ item-layout 4-slot:
57
57
  ```
58
58
 
59
59
  - Icon: 16px,`h-[1lh]` inline 對齊 first line
60
- - Dismiss X: `<Button iconOnly dismiss size="xs" />` — chrome corner action group region(Cat 3)。xs 是 **Notification banner family canonical**(Notice / Alert / Toast inherit):ephemeral banner `px-4 py-3` 固定不隨 density,dismiss 邊角小 affordance 輕量不搶眼。詳見 `patterns/overlay-surface/overlay-surface.spec.md`「Chrome dismiss size canonical」(overlay header 走 sm native + 負 margin trick;只有 notification banner family 用 xs)+ `patterns/element-anatomy/item-anatomy.spec.md`「Dismiss canonical — X close only」
60
+ - Dismiss X: `<Button iconOnly dismiss size="xs" />` — chrome corner action group region(Cat 3)。xs 是 **Notification banner family canonical**(Notice / Alert / Toast inherit):ephemeral banner `px-4 py-3` 固定不隨 density,dismiss 邊角小 affordance 輕量不搶眼。詳見 `patterns/overlay-surface/overlay-surface.spec.md`「Chrome dismiss size canonical」(overlay header 走 sm native + 負 margin trick;只有 notification banner family 用 xs)+ `patterns/element-anatomy/inline-action.spec.md`「Dismiss canonical — X close only」
61
61
  - endContent: 通常放 `<Button variant="tertiary" size="xs">`
62
62
 
63
63
  ## Variant
@@ -121,6 +121,7 @@ Notice 是 **Toast / Alert 共用的 layout primitive**,刻意不擁有尺寸與
121
121
  - **Loading**:Notice 非 async surface,無 loading state。Body 內若 consumer 注入 CTA Button,該 Button 自行處理 loading。
122
122
  - **Empty**:Notice 的 `title` 為必填(TypeScript required prop,消費合約),`description` 選填;layout 至少 1 行 title 內容。誤傳空字串時照常 render(無 runtime warn / throw),空 title 的內容責任在 consumer(Alert / Toast)。`neutral` variant 不渲 status icon 時即為 title-only icon-less 形態(見 `NeutralTitleOnly` story),仍合法 render。
123
123
  - **Icon-only / variant=neutral**:`neutral` variant 不渲 status icon,layout 自動收斂為 `[title + description?] [endContent?] [dismiss X?]` 三 slot。
124
+ - **`dismissible` 無 `onDismiss`**(2026-07-05 D4):X 渲染條件 = `dismissible`(預設 true)**且**有傳 `onDismiss`——Notice / Alert 為 controlled-only(不自我移除),無 handler 的 X 是可 focus、SR 播報「關閉通知」但按下零反應的死按鈕(假 affordance)。對齊 Polaris Banner(`onDismiss` 存在才顯示 X)/ Ant Alert(`closable` 預設 false)。顯式傳 `dismissible={true}` 卻缺 `onDismiss` 時 dev-only `console.warn` 提示(預設未傳不吵)。
124
125
 
125
126
  ---
126
127
 
@@ -138,12 +139,12 @@ Notice 是 **Toast / Alert 共用的 layout primitive**,刻意不擁有尺寸與
138
139
 
139
140
  **Keyboard 行為**:
140
141
 
141
- - Tab — focus dismiss button(若 dismissible)
142
+ - Tab — focus dismiss button(若 dismissible 且有傳 `onDismiss`——X 僅在兩者同時成立時渲染,見「邊界案例」,2026-07-05 D4;consumer 經 `endContent` 注入互動元素時,其 DOM 順序在 dismiss 之前,Tab 先落 endContent)
142
143
  - Enter / Space — 觸發 focus 中的 dismiss button(原生 button 行為,呼叫 `onDismiss`)
143
144
 
144
145
  Notice **不**自帶 Esc-to-dismiss 行為(`notice.tsx` 無 keydown handler);dismiss 純粹由 dismiss button 的 `onClick={onDismiss}` 觸發。若 consumer(Alert / Toast)需要 Esc 關閉,於 consumer 層自行掛 keydown。Dismiss 後的焦點處置 Notice 同樣不管理(無 focus restoration 邏輯)——節點移除後焦點落點由 consumer(Alert / Toast host)決定。
145
146
 
146
- **Focus**:唯一 focusable 元素是 dismiss `<Button>`,focus indicator 走 Button canonical(`focus-visible:ring-2 ring-ring`,box-shadow ring 非 CSS `outline`);Notice 本身無 focus management(dismiss 後焦點處置見上段,由 consumer 決定)。
147
+ **Focus**:Notice 自身渲染的唯一 focusable 元素是 dismiss `<Button>`(consumer 經 `endContent` 注入的互動元素——如上方建議的 tertiary Button——由 consumer 自管 a11y),focus indicator 走 Button canonical(`focus-visible:ring-2 ring-ring`,box-shadow ring 非 CSS `outline`);Notice 本身無 focus management(dismiss 後焦點處置見上段,由 consumer 決定)。
147
148
 
148
149
  **驗證**:Storybook a11y addon panel 應 0 critical violation;鍵盤完整可操作(無需滑鼠)。WCAG AA contrast ≥ 4.5:1(text)/ 3:1(UI)。
149
150
 
@@ -6,7 +6,7 @@ import * as React from 'react'
6
6
  import { X as XIcon, Info, CircleCheck, TriangleAlert, XCircle, type LucideIcon } from 'lucide-react'
7
7
  import { cn } from '@/lib/utils'
8
8
  import { Button } from '@/design-system/components/Button/button'
9
- import { ItemContent, ItemPrefix, ItemSuffix } from '@/design-system/patterns/element-anatomy/item-anatomy'
9
+ import { ICON_SIZE, ItemContent, ItemPrefix, ItemSuffix } from '@/design-system/patterns/element-anatomy/item-anatomy'
10
10
 
11
11
  /**
12
12
  * Notice — Toast / Alert 共用的視覺佈局層
@@ -75,6 +75,11 @@ export interface NoticeProps
75
75
  title: React.ReactNode
76
76
  description?: React.ReactNode
77
77
  endContent?: React.ReactNode
78
+ /**
79
+ * 顯示關閉鈕(預設 true)。**需同時傳 `onDismiss` 才會渲染 X** — Notice 是 controlled-only
80
+ * (不自我移除),無 handler 的 X 是可 focus、SR 播報「關閉通知」但按下零反應的死按鈕
81
+ * (2026-07-05 D4;對齊 Polaris Banner `onDismiss` 存在才顯示 X / Ant Alert `closable` 預設 false)。
82
+ */
78
83
  dismissible?: boolean
79
84
  onDismiss?: () => void
80
85
  /** ARIA label for the dismiss button. Override for i18n. Default: "關閉通知" */
@@ -99,7 +104,9 @@ const Notice = React.forwardRef<HTMLDivElement, NoticeProps>(
99
104
  title,
100
105
  description,
101
106
  endContent,
102
- dismissible = true,
107
+ // destructure-default(保留「顯式傳 true」的可辨識性給下方 dev-warn);等效預設 true 由
108
+ // showDismiss 的 `!== false` 承擔。
109
+ dismissible,
103
110
  onDismiss,
104
111
  dismissAriaLabel = '關閉通知', // i18n-allow: DS default; consumer override via dismissAriaLabel prop
105
112
  iconClassName,
@@ -110,6 +117,16 @@ const Notice = React.forwardRef<HTMLDivElement, NoticeProps>(
110
117
  ) => {
111
118
  const StatusIcon = VARIANT_ICON[variant]
112
119
 
120
+ // 2026-07-05 D4:X 渲染條件 = dismissible(預設 true)**且**有傳 onDismiss — 原 `dismissible &&`
121
+ // 單獨判斷讓預設用法 <Alert title="…"/> 渲染一顆死 X(controlled-only 不自我移除,onClick=undefined
122
+ // 零反應;keyboard / SR 使用者按 Enter 零回饋 = 假 affordance)。對齊 Polaris Banner(onDismiss
123
+ // 存在才顯示 X)/ Ant Alert(closable 預設 false)。
124
+ const showDismiss = dismissible !== false && onDismiss != null
125
+ if (process.env.NODE_ENV !== 'production' && dismissible === true && onDismiss == null) {
126
+ // 只在「顯式傳 dismissible={true} 卻沒接 onDismiss」的消費端矛盾時提示(預設 undefined 不吵)
127
+ console.warn('[Notice] dismissible=true 但未傳 onDismiss,關閉鈕不會渲染(需同時提供 onDismiss)。')
128
+ }
129
+
113
130
  return (
114
131
  <div
115
132
  ref={ref}
@@ -118,7 +135,7 @@ const Notice = React.forwardRef<HTMLDivElement, NoticeProps>(
118
135
  >
119
136
  {StatusIcon && (
120
137
  <ItemPrefix>
121
- <StatusIcon size={16} className={cn('shrink-0', iconClassName)} aria-hidden />
138
+ <StatusIcon size={ICON_SIZE.md} className={cn('shrink-0', iconClassName)} aria-hidden />
122
139
  </ItemPrefix>
123
140
  )}
124
141
 
@@ -130,7 +147,7 @@ const Notice = React.forwardRef<HTMLDivElement, NoticeProps>(
130
147
  labelClassName={description ? 'font-medium' : undefined}
131
148
  />
132
149
 
133
- {(endContent || dismissible) && (
150
+ {(endContent || showDismiss) && (
134
151
  // 2026-06-15 user 拍板:消費 ItemSuffix primitive(原手刻 div + @row-slot-handcraft-allow
135
152
  // 已移除)。item-anatomy.spec.md 把 Notice 的 action/dismiss 對應到 suffix slot;ItemSuffix
136
153
  // base geometry(h-[1lh] shrink-0 ml-auto flex items-center gap-2)正是此處所需,hoverReveal
@@ -138,7 +155,7 @@ const Notice = React.forwardRef<HTMLDivElement, NoticeProps>(
138
155
  // family canonical,overlay-surface.spec.md「Chrome dismiss size canonical」)。
139
156
  <ItemSuffix>
140
157
  {endContent}
141
- {dismissible && (
158
+ {showDismiss && (
142
159
  <Button
143
160
  data-dismiss
144
161
  iconOnly
@@ -178,7 +178,7 @@ export const Overview = {
178
178
  <div className="flex flex-col gap-2 items-start">
179
179
  <span className="text-[11px] text-fg-muted font-medium">edit</span>
180
180
  <div className="inline-flex items-center border-2 border-dashed border-primary/30 rounded-md px-3 py-2.5 gap-2">
181
- {[{ name: 'wrapper', color: 'info' }, { name: 'wrapper', color: 'success' }, { name: 'wrapper', color: 'warning' }].map((s) => (
181
+ {[{ name: 'wrapper', color: 'info' }, { name: 'input', color: 'success' }, { name: 'endAction', color: 'warning' }].map((s) => (
182
182
  <span key={s.name} className="rounded px-2 py-1 text-[11px] font-mono border border-dashed"
183
183
  style={{ borderColor: `var(--${s.color})`, backgroundColor: `var(--${s.color}-subtle)`, color: `var(--${s.color})` }}>{s.name}</span>
184
184
  ))}
@@ -189,7 +189,7 @@ export const Overview = {
189
189
  <div className="flex flex-col gap-2 items-start">
190
190
  <span className="text-[11px] text-fg-muted font-medium">readonly / disabled</span>
191
191
  <div className="inline-flex items-center border-2 border-dashed border-primary/30 rounded-md px-3 py-2.5 gap-2">
192
- {[{ name: 'wrapper', color: 'info' }, { name: 'wrapper', color: 'success' }].map((s) => (
192
+ {[{ name: 'wrapper', color: 'info' }, { name: 'formatted span', color: 'success' }].map((s) => (
193
193
  <span key={s.name} className="rounded px-2 py-1 text-[11px] font-mono border border-dashed"
194
194
  style={{ borderColor: `var(--${s.color})`, backgroundColor: `var(--${s.color}-subtle)`, color: `var(--${s.color})` }}>{s.name}</span>
195
195
  ))}
@@ -255,6 +255,7 @@ export const Overview = {
255
255
  ['precision', 'number', '—', '小數位數'],
256
256
  ['locale', 'string', "'en-US'", '數字格式 locale'],
257
257
  ['endAction', 'InlineActionConfig', '—', '右側 inline action(宣告式 API)'],
258
+ ['endSlot', 'ReactNode', '—', '右側自訂 slot(escape hatch,僅 edit 渲染);與 endAction 互斥,同時傳時 endSlot 優先'],
258
259
  ['disabled', 'boolean', 'false', '原生屬性;mode 未顯式指定時解析為 disabled(顯式 mode prop 永遠最優先,useResolvedFieldMode SSOT)'],
259
260
  ['readOnly', 'boolean', 'false', '原生屬性;mode 未顯式指定且無 Field context mode 時解析為 readonly(優先序 mode > 有效 disabled > fieldCtx.mode > readOnly)'],
260
261
  ].map(([p, t, d, desc]) => (
@@ -744,7 +745,7 @@ export const StateBehavior = {
744
745
  <div key={m} className="flex flex-col gap-1 items-start">
745
746
  <span className="text-[11px] text-fg-muted font-mono">{m}</span>
746
747
  <div className="w-[160px]"><NumberInput mode={m} value={null} prefix="$" /></div>
747
- <span className="text-[10px] text-fg-muted">{m === 'edit' ? '空白 input' : 'em dash — + fg-muted'}</span>
748
+ <span className="text-[10px] text-fg-muted">{m === 'edit' ? '空白 input' : m === 'disabled' ? 'em dash — + fg-disabled(M24 state > emphasis)' : 'em dash — + fg-muted'}</span>
748
749
  </div>
749
750
  ))}
750
751
  </div>
@@ -781,7 +782,7 @@ export const Accessibility = {
781
782
  render: () => (
782
783
  <div className="max-w-3xl text-body text-fg-secondary">
783
784
  <h3 className="text-h5 text-foreground mb-2">無障礙設計</h3>
784
- <p className="whitespace-pre-line">{"ARIA :使用原生 <input type=\"text\" inputMode=\"decimal\"> ;外層 Field 自動補上標籤、錯誤狀態與描述的關聯。 inputMode=\"decimal\" 讓行動裝置彈出數字鍵盤。\n\n 鍵盤行為 :\n\n- Tab — 移入欄位\n- 數字鍵 — 輸入數值\n\n 焦點 :原生 input 焦點外框,聚焦時外框轉為主色,由外層 Field 提供。\n\n 驗證 :Storybook 無障礙檢查面板應為 0 項嚴重問題;不靠滑鼠也能完整操作。文字對比度至少 4.5:1、介面元件至少 3:1(WCAG AA)。"}</p>
785
+ <p className="whitespace-pre-line">{"ARIA :使用原生 <input type=\"text\" inputMode=\"decimal\"> ;外層 Field 自動補上標籤、錯誤狀態與描述的關聯。 inputMode=\"decimal\" 讓行動裝置彈出數字鍵盤。\n\n 鍵盤行為 :\n\n- Tab — 移入欄位\n- 數字鍵 — 輸入數值\n\n 焦點 :原生 input 以 outline-none 移除預設焦點外框;聚焦時由元件自身 wrapper 的 focus-within 將邊框轉為主色(border-primary)。\n\n 驗證 :Storybook 無障礙檢查面板應為 0 項嚴重問題;不靠滑鼠也能完整操作。文字對比度至少 4.5:1、介面元件至少 3:1(WCAG AA)。"}</p>
785
786
  </div>
786
787
  ),
787
788
  }
@@ -1,4 +1,5 @@
1
1
  // @principles-rationale: UsageGuidance merges WhenToUse + WhenNotToUse into single 使用指引 story per refactor task (2026-04-26)
2
+ // @story-baseline: packages/design-system/src/components/DataTable/data-table.stories.tsx#NumberAlignment
2
3
  import React from 'react'
3
4
  import LinkTo from '@storybook/addon-links/react'
4
5
  import type { Meta, StoryObj } from '@storybook/react'
@@ -35,7 +36,7 @@ const Label = ({ children, warn }: { children: React.ReactNode; warn?: boolean }
35
36
 
36
37
  const Section = ({ title, children }: { title: string; children: React.ReactNode }) => (
37
38
  <section className="mb-12">
38
- <h2 className="text-heading-3 font-bold text-foreground mb-4 pb-2 border-b border-border">{title}</h2>
39
+ <h2 className="text-h3 font-bold text-foreground mb-4 pb-2 border-b border-border">{title}</h2>
39
40
  {children}
40
41
  </section>
41
42
  )
@@ -106,7 +106,8 @@ col.accessor('price', {
106
106
  - **Disabled**:由 Field SSOT own(`Field/field-controls.spec.md` State machine 段)。視覺:wrapper bg → `bg-disabled`(neutral-2,`semantic.css` `--bg-disabled`)、formatted text → `text-fg-disabled`(M24 state>emphasis)。Display mode + disabled 維持格式化輸出但 token 切 disabled。
107
107
  - **Loading**:NumberInput 不提供 loading state(`NumberInputProps` 無 `loading` prop、無 `CircularProgress` / `aria-busy` 邏輯)。數值輸入為同步操作,無 async fetch 語意;若需 async 場景(如遠端校驗)請走外層 Field validation + `endSlot` 自訂 spinner。
108
108
  - **Empty(null / undefined / 空字串)**:Display mode 渲 `—`(em dash + `text-fg-muted`);Edit mode placeholder 走 default placeholder color。
109
- - **Invalid input**(non-numeric):input 為 `type="text" inputMode="decimal"`,onChange 以 `Number(raw)` parse,NaN 時忽略不觸發 `onChange`(value 維持原值);搭配 Field validation 渲 error variant(`aria-invalid="true"` + `text-fg-error` border + 下方 error message)。
109
+ - **Invalid input**(non-numeric):input 為 `type="text" inputMode="decimal"`,onChange 以 `Number(raw)` parse,NaN 時忽略不觸發 `onChange`(value 維持原值);搭配 Field validation 渲 error variant(`aria-invalid="true"` + wrapper `border-error`(hover 時 `border-error-hover`)+ 下方 error message)。
110
+ - **輸入中間態**(`-`、`1.`、`0.0` 等 parse 後 lossy 的 raw 字串,2026-07-05 D4 codify):edit mode 以 internal draft string 承載——輸入期間 DOM 顯示 draft 原字串(負號、尾點不被 controlled 回寫吃掉),parse 成功即時同步 `onChange(parsed)`;blur / Enter commit(清 draft、顯示回 committed value,不合法殘字自然還原)、Escape 棄 draft(對齊 form-validation 規則 4)。value 面語意不變:`''` / `'-'` 視為 `null`。對齊 Ant InputNumber / Adobe Spectrum NumberField internal draft canonical。 <!-- @benchmark-unverified -->
110
111
  - **負數 / 超大數字**:`toLocaleString(locale)` 自動處理負號與千分位分組,無特殊 prop。
111
112
  - **科學記號輸入**(`1e5`):`Number(raw)` parse 為 `100000`,非 NaN 即觸發 `onChange`(`number-input.tsx:136-138`)。
112
113
  - **精度上限**:`precision` > 6 為禁止事項(浮點精度雜訊),極端精度走 `Input` + 自驗證。
@@ -89,6 +89,8 @@ const NumberInput = React.forwardRef<HTMLInputElement, NumberInputProps>(
89
89
  disabled: disabledProp,
90
90
  readOnly,
91
91
  id: idProp,
92
+ onBlur: onBlurProp,
93
+ onKeyDown: onKeyDownProp,
92
94
  'aria-describedby': ariaDescribedByProp,
93
95
  'aria-errormessage': ariaErrorMessageProp,
94
96
  ...props
@@ -104,6 +106,15 @@ const NumberInput = React.forwardRef<HTMLInputElement, NumberInputProps>(
104
106
  // 2026-06-08 SSOT:mode 經 useResolvedFieldMode 統一解析(prop > 有效 disabled > fieldCtx.mode > readOnly > 'edit')
105
107
  const resolvedMode: FieldMode = useResolvedFieldMode({ mode: modeProp, disabled, readOnly })
106
108
 
109
+ // 2026-07-05 D4 draft state:controlled value 回寫會吃掉輸入中間態('-'、'1.'、'0.0' 等
110
+ // parse 後 lossy 的 raw 字串)— 例:已有值 5 全選打「-」→ onChange(null) 回寫把 DOM 重設
111
+ // 為空,負號被吃、無法打出 -5。修法對齊 Ant InputNumber / Adobe Spectrum NumberField 的
112
+ // internal draft string canonical:輸入期間 DOM 顯示以 local draft 優先,parse 成功即時同步
113
+ // onChange(parsed);blur / Enter commit 清 draft 回 value 顯示;Escape 一併棄 draft(對齊
114
+ // form-validation 規則 4 回復原值)。宣告於 non-edit early return 之前(Rules-of-Hooks,
115
+ // 同 LinkInput 2026-07-04 修法)。
116
+ const [draft, setDraft] = React.useState<string | null>(null)
117
+
107
118
  // display / readonly / disabled 都顯示格式化值(span 取代 input)
108
119
  if (resolvedMode !== 'edit') {
109
120
  return (
@@ -125,10 +136,11 @@ const NumberInput = React.forwardRef<HTMLInputElement, NumberInputProps>(
125
136
  )
126
137
  }
127
138
 
128
- // edit 模式:raw 數值輸入
139
+ // edit 模式:raw 數值輸入(draft 承載中間態;value 面語意不變 — ''/'-' 視為空、NaN 忽略)
129
140
  const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
130
- if (!onChange) return
131
141
  const raw = e.target.value
142
+ setDraft(raw)
143
+ if (!onChange) return
132
144
  if (raw === '' || raw === '-') {
133
145
  onChange(null)
134
146
  return
@@ -142,11 +154,7 @@ const NumberInput = React.forwardRef<HTMLInputElement, NumberInputProps>(
142
154
  return (
143
155
  <div
144
156
  className={cn(
145
- fieldWrapperStyles({ mode: 'edit', variant: variant, size }),
146
- error && [
147
- 'border-error hover:border-error-hover',
148
- 'focus-within:border-error focus-within:hover:border-error',
149
- ],
157
+ fieldWrapperStyles({ mode: 'edit', variant: variant, size, error }),
150
158
  className,
151
159
  )}
152
160
  data-field-mode="edit"
@@ -157,8 +165,15 @@ const NumberInput = React.forwardRef<HTMLInputElement, NumberInputProps>(
157
165
  type="text"
158
166
  inputMode="decimal"
159
167
  id={idProp ?? fieldCtx?.id}
160
- value={value ?? ''}
168
+ // 輸入期間 draft(raw 字串)優先;非輸入期間才從 value prop 同步顯示(2026-07-05 D4)
169
+ value={draft ?? (value != null ? String(value) : '')}
161
170
  onChange={handleChange}
171
+ // blur / Enter = commit(清 draft,顯示回 committed value);Escape 棄 draft
172
+ onBlur={(e) => { setDraft(null); onBlurProp?.(e) }}
173
+ onKeyDown={(e) => {
174
+ if (e.key === 'Enter' || e.key === 'Escape') setDraft(null)
175
+ onKeyDownProp?.(e)
176
+ }}
162
177
  aria-invalid={error || undefined}
163
178
  aria-required={fieldCtx?.required || undefined}
164
179
  aria-describedby={ariaDescribedByProp ?? fieldCtx?.descriptionId}
@@ -46,8 +46,8 @@ type ShapeKey = 'circle' | 'tag'
46
46
  const SIZES: SizeKey[] = ['sm', 'md', 'lg']
47
47
  const SHAPES: ShapeKey[] = ['circle', 'tag']
48
48
 
49
- // font-size 依 shape 分流(re-verified 2026-06-01):circle shape 走元件內建 triggerText
50
- // map(overflow-indicator.tsx:24-28),tag shape 走 tagVariants({ size })(tag.tsx:45-47)。
49
+ // font-size 依 shape 分流(re-verified 2026-07-04):circle shape 走元件內建 triggerText
50
+ // map(overflow-indicator.tsx:28-32),tag shape 走 tagVariants({ size })(tag.tsx:40-44)。
51
51
  // circleText / tagText 兩欄分別對應,避免把 circle-only 字級當成兩 shape 通用。
52
52
  const SIZE_SPECS: Record<
53
53
  SizeKey,
@@ -467,7 +467,7 @@ export const SizeMatrix: Story = {
467
467
  <code className="font-mono">tag</code> 走 <code className="font-mono">tagVariants</code>(對齊 Tag 字級)。
468
468
  circle shape 的 <code className="font-mono">sm</code> 用
469
469
  <code className="font-mono mx-1">text-[10px]</code>
470
- 的理由見 spec「尺寸」段——sub-footnote 特殊例外,與 Badge 共享 micro-indicator typography tier。
470
+ 的理由見 spec「尺寸」段——與 Badge 共享 micro-indicator typography tier 的特殊例外(10px)
471
471
  </p>
472
472
  </div>
473
473