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

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 (481) hide show
  1. package/AGENTS.md +210 -0
  2. package/CLAUDE.md +17 -191
  3. package/cli-init.mjs +30 -2
  4. package/dist/components/Accordion/accordion.d.ts +2 -2
  5. package/dist/components/Accordion/accordion.d.ts.map +1 -1
  6. package/dist/components/Accordion/accordion.js.map +1 -1
  7. package/dist/components/Avatar/avatar.js +1 -1
  8. package/dist/components/Avatar/avatar.js.map +1 -1
  9. package/dist/components/BulkActionBar/bulk-action-bar.d.ts +1 -1
  10. package/dist/components/BulkActionBar/bulk-action-bar.d.ts.map +1 -1
  11. package/dist/components/BulkActionBar/bulk-action-bar.js +3 -3
  12. package/dist/components/BulkActionBar/bulk-action-bar.js.map +1 -1
  13. package/dist/components/Button/button-group.d.ts +2 -2
  14. package/dist/components/Button/button-group.d.ts.map +1 -1
  15. package/dist/components/Button/button-group.js +2 -2
  16. package/dist/components/Button/button-group.js.map +1 -1
  17. package/dist/components/Button/button.d.ts +2 -1
  18. package/dist/components/Button/button.d.ts.map +1 -1
  19. package/dist/components/Button/button.js +1 -1
  20. package/dist/components/Button/button.js.map +1 -1
  21. package/dist/components/Carousel/carousel.d.ts +14 -8
  22. package/dist/components/Carousel/carousel.d.ts.map +1 -1
  23. package/dist/components/Carousel/carousel.js +64 -37
  24. package/dist/components/Carousel/carousel.js.map +1 -1
  25. package/dist/components/Checkbox/checkbox-group.d.ts.map +1 -1
  26. package/dist/components/Checkbox/checkbox-group.js +21 -14
  27. package/dist/components/Checkbox/checkbox-group.js.map +1 -1
  28. package/dist/components/Checkbox/checkbox.d.ts +2 -2
  29. package/dist/components/Checkbox/checkbox.d.ts.map +1 -1
  30. package/dist/components/Checkbox/checkbox.js +49 -10
  31. package/dist/components/Checkbox/checkbox.js.map +1 -1
  32. package/dist/components/Combobox/combobox.d.ts +15 -6
  33. package/dist/components/Combobox/combobox.d.ts.map +1 -1
  34. package/dist/components/Combobox/combobox.js +13 -7
  35. package/dist/components/Combobox/combobox.js.map +1 -1
  36. package/dist/components/Command/command.d.ts.map +1 -1
  37. package/dist/components/Command/command.js +11 -2
  38. package/dist/components/Command/command.js.map +1 -1
  39. package/dist/components/DataTable/cell-registry.d.ts +5 -5
  40. package/dist/components/DataTable/cell-registry.d.ts.map +1 -1
  41. package/dist/components/DataTable/cell-registry.js +41 -38
  42. package/dist/components/DataTable/cell-registry.js.map +1 -1
  43. package/dist/components/DataTable/column-types.d.ts +1 -17
  44. package/dist/components/DataTable/column-types.d.ts.map +1 -1
  45. package/dist/components/DataTable/column-types.js.map +1 -1
  46. package/dist/components/DataTable/data-table-column-visibility-panel.d.ts.map +1 -1
  47. package/dist/components/DataTable/data-table-column-visibility-panel.js +71 -64
  48. package/dist/components/DataTable/data-table-column-visibility-panel.js.map +1 -1
  49. package/dist/components/DataTable/data-table-filter-panel.d.ts +0 -2
  50. package/dist/components/DataTable/data-table-filter-panel.d.ts.map +1 -1
  51. package/dist/components/DataTable/data-table-filter-panel.js +3 -4
  52. package/dist/components/DataTable/data-table-filter-panel.js.map +1 -1
  53. package/dist/components/DataTable/data-table-interaction-layer.d.ts +2 -2
  54. package/dist/components/DataTable/data-table-interaction-layer.js.map +1 -1
  55. package/dist/components/DataTable/data-table-sort-manager.js +2 -2
  56. package/dist/components/DataTable/data-table-sort-manager.js.map +1 -1
  57. package/dist/components/DataTable/data-table.d.ts +1 -1
  58. package/dist/components/DataTable/data-table.d.ts.map +1 -1
  59. package/dist/components/DataTable/data-table.js +44 -32
  60. package/dist/components/DataTable/data-table.js.map +1 -1
  61. package/dist/components/DatePicker/date-picker.d.ts +3 -3
  62. package/dist/components/DatePicker/date-picker.js +4 -4
  63. package/dist/components/DatePicker/date-picker.js.map +1 -1
  64. package/dist/components/DescriptionList/description-list.d.ts +3 -3
  65. package/dist/components/DescriptionList/description-list.d.ts.map +1 -1
  66. package/dist/components/DescriptionList/description-list.js +6 -6
  67. package/dist/components/DescriptionList/description-list.js.map +1 -1
  68. package/dist/components/Dialog/dialog.d.ts +2 -2
  69. package/dist/components/Dialog/dialog.d.ts.map +1 -1
  70. package/dist/components/Dialog/dialog.js.map +1 -1
  71. package/dist/components/DropdownMenu/dropdown-menu.d.ts +8 -3
  72. package/dist/components/DropdownMenu/dropdown-menu.d.ts.map +1 -1
  73. package/dist/components/DropdownMenu/dropdown-menu.js +7 -7
  74. package/dist/components/DropdownMenu/dropdown-menu.js.map +1 -1
  75. package/dist/components/Field/field-context.d.ts +16 -10
  76. package/dist/components/Field/field-context.d.ts.map +1 -1
  77. package/dist/components/Field/field-context.js.map +1 -1
  78. package/dist/components/Field/field-types.d.ts +2 -2
  79. package/dist/components/Field/field-types.d.ts.map +1 -1
  80. package/dist/components/Field/field-types.js.map +1 -1
  81. package/dist/components/Field/field-wrapper.d.ts +20 -4
  82. package/dist/components/Field/field-wrapper.d.ts.map +1 -1
  83. package/dist/components/Field/field-wrapper.js +30 -31
  84. package/dist/components/Field/field-wrapper.js.map +1 -1
  85. package/dist/components/Field/field.d.ts +1 -1
  86. package/dist/components/Field/field.d.ts.map +1 -1
  87. package/dist/components/Field/field.js +25 -3
  88. package/dist/components/Field/field.js.map +1 -1
  89. package/dist/components/Field/use-form-validation.d.ts +1 -1
  90. package/dist/components/Field/use-form-validation.js.map +1 -1
  91. package/dist/components/FileItem/file-item.d.ts.map +1 -1
  92. package/dist/components/FileItem/file-item.js +2 -5
  93. package/dist/components/FileItem/file-item.js.map +1 -1
  94. package/dist/components/FileUpload/file-upload.d.ts.map +1 -1
  95. package/dist/components/FileUpload/file-upload.js +5 -1
  96. package/dist/components/FileUpload/file-upload.js.map +1 -1
  97. package/dist/components/FileViewer/file-viewer.d.ts.map +1 -1
  98. package/dist/components/FileViewer/file-viewer.js +2 -1
  99. package/dist/components/FileViewer/file-viewer.js.map +1 -1
  100. package/dist/components/HoverCard/hover-card.d.ts +2 -2
  101. package/dist/components/HoverCard/hover-card.d.ts.map +1 -1
  102. package/dist/components/HoverCard/hover-card.js +4 -2
  103. package/dist/components/HoverCard/hover-card.js.map +1 -1
  104. package/dist/components/InlineEdit/inline-edit.d.ts +23 -16
  105. package/dist/components/InlineEdit/inline-edit.d.ts.map +1 -1
  106. package/dist/components/InlineEdit/inline-edit.js +104 -62
  107. package/dist/components/InlineEdit/inline-edit.js.map +1 -1
  108. package/dist/components/Input/input.d.ts +1 -1
  109. package/dist/components/Input/input.d.ts.map +1 -1
  110. package/dist/components/Input/input.js +6 -6
  111. package/dist/components/Input/input.js.map +1 -1
  112. package/dist/components/LinkInput/link-input.d.ts +5 -5
  113. package/dist/components/LinkInput/link-input.d.ts.map +1 -1
  114. package/dist/components/LinkInput/link-input.js +3 -3
  115. package/dist/components/LinkInput/link-input.js.map +1 -1
  116. package/dist/components/Notice/notice.d.ts +17 -1
  117. package/dist/components/Notice/notice.d.ts.map +1 -1
  118. package/dist/components/Notice/notice.js +7 -1
  119. package/dist/components/Notice/notice.js.map +1 -1
  120. package/dist/components/NumberInput/number-input.d.ts +1 -1
  121. package/dist/components/NumberInput/number-input.d.ts.map +1 -1
  122. package/dist/components/NumberInput/number-input.js +12 -1
  123. package/dist/components/NumberInput/number-input.js.map +1 -1
  124. package/dist/components/PeoplePicker/avatar-stack-overflow.d.ts +1 -1
  125. package/dist/components/PeoplePicker/avatar-stack-overflow.js.map +1 -1
  126. package/dist/components/PeoplePicker/people-picker.d.ts +5 -5
  127. package/dist/components/PeoplePicker/people-picker.d.ts.map +1 -1
  128. package/dist/components/PeoplePicker/people-picker.js +7 -5
  129. package/dist/components/PeoplePicker/people-picker.js.map +1 -1
  130. package/dist/components/PeoplePicker/person-display.d.ts +1 -1
  131. package/dist/components/PeoplePicker/person-display.js.map +1 -1
  132. package/dist/components/ProfileCard/profile-card.d.ts +1 -1
  133. package/dist/components/ProfileCard/profile-card.js.map +1 -1
  134. package/dist/components/RadioGroup/radio-group.d.ts +3 -3
  135. package/dist/components/RadioGroup/radio-group.d.ts.map +1 -1
  136. package/dist/components/RadioGroup/radio-group.js +1 -1
  137. package/dist/components/RadioGroup/radio-group.js.map +1 -1
  138. package/dist/components/Rating/rating.d.ts.map +1 -1
  139. package/dist/components/Rating/rating.js +4 -0
  140. package/dist/components/Rating/rating.js.map +1 -1
  141. package/dist/components/ScrollArea/scroll-area.d.ts +3 -2
  142. package/dist/components/ScrollArea/scroll-area.d.ts.map +1 -1
  143. package/dist/components/ScrollArea/scroll-area.js.map +1 -1
  144. package/dist/components/SegmentedControl/segmented-control.d.ts +4 -3
  145. package/dist/components/SegmentedControl/segmented-control.d.ts.map +1 -1
  146. package/dist/components/SegmentedControl/segmented-control.js.map +1 -1
  147. package/dist/components/Select/select.d.ts +16 -6
  148. package/dist/components/Select/select.d.ts.map +1 -1
  149. package/dist/components/Select/select.js +17 -5
  150. package/dist/components/Select/select.js.map +1 -1
  151. package/dist/components/SelectMenu/select-menu.d.ts +17 -1
  152. package/dist/components/SelectMenu/select-menu.d.ts.map +1 -1
  153. package/dist/components/SelectMenu/select-menu.js +17 -3
  154. package/dist/components/SelectMenu/select-menu.js.map +1 -1
  155. package/dist/components/SelectionControl/boolean-value.d.ts +1 -1
  156. package/dist/components/SelectionControl/boolean-value.js.map +1 -1
  157. package/dist/components/Sheet/sheet.d.ts +2 -2
  158. package/dist/components/Sheet/sheet.d.ts.map +1 -1
  159. package/dist/components/Sheet/sheet.js.map +1 -1
  160. package/dist/components/Sidebar/sidebar.d.ts +1 -1
  161. package/dist/components/Sidebar/sidebar.js +2 -2
  162. package/dist/components/Sidebar/sidebar.js.map +1 -1
  163. package/dist/components/Slider/slider.d.ts.map +1 -1
  164. package/dist/components/Slider/slider.js +6 -3
  165. package/dist/components/Slider/slider.js.map +1 -1
  166. package/dist/components/Steps/steps.d.ts +6 -4
  167. package/dist/components/Steps/steps.d.ts.map +1 -1
  168. package/dist/components/Steps/steps.js +19 -5
  169. package/dist/components/Steps/steps.js.map +1 -1
  170. package/dist/components/Switch/switch.d.ts +4 -4
  171. package/dist/components/Switch/switch.d.ts.map +1 -1
  172. package/dist/components/Switch/switch.js +3 -1
  173. package/dist/components/Switch/switch.js.map +1 -1
  174. package/dist/components/Tabs/tabs.d.ts +1 -1
  175. package/dist/components/Tabs/tabs.d.ts.map +1 -1
  176. package/dist/components/Tabs/tabs.js +73 -23
  177. package/dist/components/Tabs/tabs.js.map +1 -1
  178. package/dist/components/Tag/tag.d.ts +3 -1
  179. package/dist/components/Tag/tag.d.ts.map +1 -1
  180. package/dist/components/Tag/tag.js +2 -2
  181. package/dist/components/Tag/tag.js.map +1 -1
  182. package/dist/components/Textarea/textarea.d.ts +4 -4
  183. package/dist/components/Textarea/textarea.d.ts.map +1 -1
  184. package/dist/components/Textarea/textarea.js +15 -15
  185. package/dist/components/Textarea/textarea.js.map +1 -1
  186. package/dist/components/TimePicker/time-picker.d.ts +4 -4
  187. package/dist/components/TimePicker/time-picker.d.ts.map +1 -1
  188. package/dist/components/TimePicker/time-picker.js +8 -5
  189. package/dist/components/TimePicker/time-picker.js.map +1 -1
  190. package/dist/components/Tooltip/tooltip.d.ts.map +1 -1
  191. package/dist/components/Tooltip/tooltip.js +27 -23
  192. package/dist/components/Tooltip/tooltip.js.map +1 -1
  193. package/dist/components/TreeView/tree-view.d.ts.map +1 -1
  194. package/dist/components/TreeView/tree-view.js +16 -0
  195. package/dist/components/TreeView/tree-view.js.map +1 -1
  196. package/dist/index.d.ts +7 -7
  197. package/dist/index.d.ts.map +1 -1
  198. package/dist/index.js +6 -13
  199. package/dist/index.js.map +1 -1
  200. package/dist/lib/drag-visual.d.ts +2 -2
  201. package/dist/lib/drag-visual.js.map +1 -1
  202. package/dist/lib/utils.d.ts +1 -0
  203. package/dist/lib/utils.d.ts.map +1 -1
  204. package/dist/lib/utils.js +21 -1
  205. package/dist/lib/utils.js.map +1 -1
  206. package/dist/patterns/element-anatomy/item-anatomy.d.ts +2 -0
  207. package/dist/patterns/element-anatomy/item-anatomy.d.ts.map +1 -1
  208. package/dist/patterns/element-anatomy/item-anatomy.js +2 -2
  209. package/dist/patterns/element-anatomy/item-anatomy.js.map +1 -1
  210. package/dist/tokens/categorical-color.d.ts +3 -3
  211. package/dist/tokens/categorical-color.d.ts.map +1 -1
  212. package/dist/tokens/categorical-color.js +2 -2
  213. package/dist/tokens/categorical-color.js.map +1 -1
  214. package/ds-canonical/fork/AGENTS.md +213 -0
  215. package/ds-canonical/fork/codex/agents/skills/independent-review/SKILL.md +33 -0
  216. package/ds-canonical/fork/codex/hooks.json +16 -0
  217. package/ds-canonical/fork/governance.lock +22 -7
  218. package/ds-canonical/fork/hooks/check_ds_anchor_preflight.sh +22 -1
  219. package/ds-canonical/fork/hooks/inject_deploy_url_after_push.sh +14 -2
  220. package/ds-canonical/fork/manifest.json +10 -1
  221. package/ds-canonical/fork/preamble.md +3 -2
  222. package/ds-canonical/fork/skills/product-ui-audit/SKILL.md +7 -7
  223. package/ds-canonical/fork/skills/ux-audit/SKILL.md +1 -1
  224. package/ds-canonical/fork/skills/visual-audit/SKILL.md +0 -1
  225. package/ds-canonical/hooks/check_codex_brief_invariants.sh +9 -3
  226. package/ds-canonical/hooks/check_ds_anchor_preflight.sh +22 -1
  227. package/ds-canonical/hooks/check_overlay_open_focus_escape_probe.sh +19 -2
  228. package/ds-canonical/hooks/check_plugin_fork_health.sh +17 -1
  229. package/ds-canonical/hooks/check_post_main_ssot_propagate.sh +21 -4
  230. package/ds-canonical/hooks/inject_deploy_url_after_push.sh +14 -2
  231. package/ds-canonical/hooks/session_start_governance_check.sh +10 -2
  232. package/ds-canonical/references/certified-surfaces.md +20 -0
  233. package/ds-canonical/references/preflight-gate-baseline.json +5 -0
  234. package/ds-canonical/references/principle-dim-map.json +20 -2
  235. package/ds-canonical/references/props-naming.md +2 -1
  236. package/ds-canonical/references/ssot-index.md +2 -2
  237. package/ds-canonical/references/story-baseline-registry.json +2 -2
  238. package/ds-canonical/rules/story-rules.md +1 -1
  239. package/ds-canonical/rules/ui-development.md +2 -1
  240. package/ds-canonical/skills/codex-collab/SKILL.md +7 -7
  241. package/ds-canonical/skills/codex-collab/references/brief-template.md +5 -1
  242. package/ds-canonical/skills/component-quality-gate/SKILL.md +1 -1
  243. package/ds-canonical/skills/deep-audit-cross-codex/SKILL.md +5 -5
  244. package/ds-canonical/skills/design-system-audit/SKILL.md +5 -5
  245. package/ds-canonical/skills/product-ui-audit/SKILL.md +7 -7
  246. package/ds-canonical/skills/ux-audit/SKILL.md +1 -1
  247. package/ds-canonical/skills/visual-audit/SKILL.md +0 -1
  248. package/ds-story-manifest.json +21 -21
  249. package/llms-full.txt +3 -3
  250. package/llms.txt +1 -1
  251. package/package.json +2 -1
  252. package/src/components/Accordion/accordion.anatomy.stories.tsx +11 -11
  253. package/src/components/Accordion/accordion.principles.stories.tsx +21 -6
  254. package/src/components/Accordion/accordion.spec.md +10 -1
  255. package/src/components/Accordion/accordion.stories.tsx +10 -5
  256. package/src/components/Accordion/accordion.tsx +7 -2
  257. package/src/components/Alert/alert.principles.stories.tsx +2 -8
  258. package/src/components/AppShell/app-shell.anatomy.stories.tsx +19 -13
  259. package/src/components/AppShell/app-shell.principles.stories.tsx +21 -21
  260. package/src/components/AppShell/app-shell.spec.md +4 -0
  261. package/src/components/AppShell/app-shell.stories.tsx +4 -5
  262. package/src/components/Avatar/avatar.tsx +1 -1
  263. package/src/components/Badge/badge.spec.md +2 -2
  264. package/src/components/Breadcrumb/breadcrumb.anatomy.stories.tsx +7 -3
  265. package/src/components/Breadcrumb/breadcrumb.principles.stories.tsx +4 -1
  266. package/src/components/Breadcrumb/breadcrumb.stories.tsx +1 -1
  267. package/src/components/BulkActionBar/bulk-action-bar.anatomy.stories.tsx +6 -6
  268. package/src/components/BulkActionBar/bulk-action-bar.principles.stories.tsx +18 -6
  269. package/src/components/BulkActionBar/bulk-action-bar.spec.md +15 -5
  270. package/src/components/BulkActionBar/bulk-action-bar.stories.tsx +8 -24
  271. package/src/components/BulkActionBar/bulk-action-bar.tsx +4 -4
  272. package/src/components/Button/button-group.tsx +4 -4
  273. package/src/components/Button/button.anatomy.stories.tsx +26 -7
  274. package/src/components/Button/button.principles.stories.tsx +1 -1
  275. package/src/components/Button/button.spec.md +2 -2
  276. package/src/components/Button/button.stories.tsx +22 -28
  277. package/src/components/Button/button.tsx +2 -1
  278. package/src/components/Calendar/calendar.anatomy.stories.tsx +1 -1
  279. package/src/components/Calendar/calendar.spec.md +1 -1
  280. package/src/components/Carousel/carousel.spec.md +17 -1
  281. package/src/components/Carousel/carousel.tsx +93 -42
  282. package/src/components/Checkbox/checkbox-group.tsx +26 -15
  283. package/src/components/Checkbox/checkbox.anatomy.stories.tsx +3 -3
  284. package/src/components/Checkbox/checkbox.spec.md +12 -3
  285. package/src/components/Checkbox/checkbox.stories.tsx +4 -4
  286. package/src/components/Checkbox/checkbox.tsx +79 -19
  287. package/src/components/Coachmark/coachmark.anatomy.stories.tsx +5 -5
  288. package/src/components/Coachmark/coachmark.principles.stories.tsx +1 -1
  289. package/src/components/Combobox/combobox.anatomy.stories.tsx +11 -4
  290. package/src/components/Combobox/combobox.principles.stories.tsx +10 -11
  291. package/src/components/Combobox/combobox.spec.md +6 -2
  292. package/src/components/Combobox/combobox.stories.tsx +9 -36
  293. package/src/components/Combobox/combobox.tsx +42 -27
  294. package/src/components/Command/command.spec.md +3 -3
  295. package/src/components/Command/command.tsx +9 -2
  296. package/src/components/DataTable/cell-registry.tsx +75 -76
  297. package/src/components/DataTable/column-types.ts +4 -17
  298. package/src/components/DataTable/data-table-column-visibility-panel.tsx +20 -7
  299. package/src/components/DataTable/data-table-filter-panel.tsx +6 -6
  300. package/src/components/DataTable/data-table-interaction-layer.tsx +3 -3
  301. package/src/components/DataTable/data-table-sort-manager.tsx +2 -2
  302. package/src/components/DataTable/data-table.anatomy.stories.tsx +25 -24
  303. package/src/components/DataTable/data-table.css +2 -3
  304. package/src/components/DataTable/data-table.principles.stories.tsx +17 -11
  305. package/src/components/DataTable/data-table.spec.md +8 -7
  306. package/src/components/DataTable/data-table.stories.tsx +39 -29
  307. package/src/components/DataTable/data-table.tsx +90 -67
  308. package/src/components/DataTable/filter-operators.spec.md +2 -0
  309. package/src/components/DateGrid/date-grid.spec.md +1 -1
  310. package/src/components/DatePicker/date-picker.anatomy.stories.tsx +9 -9
  311. package/src/components/DatePicker/date-picker.principles.stories.tsx +4 -4
  312. package/src/components/DatePicker/date-picker.spec.md +10 -9
  313. package/src/components/DatePicker/date-picker.stories.tsx +9 -9
  314. package/src/components/DatePicker/date-picker.tsx +13 -13
  315. package/src/components/DescriptionList/description-list.anatomy.stories.tsx +5 -5
  316. package/src/components/DescriptionList/description-list.principles.stories.tsx +2 -2
  317. package/src/components/DescriptionList/description-list.spec.md +12 -12
  318. package/src/components/DescriptionList/description-list.stories.tsx +3 -3
  319. package/src/components/DescriptionList/description-list.tsx +10 -10
  320. package/src/components/Dialog/dialog.tsx +4 -1
  321. package/src/components/DropdownMenu/dropdown-menu.tsx +27 -14
  322. package/src/components/Empty/empty.spec.md +0 -1
  323. package/src/components/Field/field-context.ts +17 -11
  324. package/src/components/Field/field-controls.spec.md +46 -37
  325. package/src/components/Field/field-types.ts +14 -12
  326. package/src/components/Field/field-wrapper.tsx +59 -43
  327. package/src/components/Field/field.anatomy.stories.tsx +6 -6
  328. package/src/components/Field/field.principles.stories.tsx +5 -28
  329. package/src/components/Field/field.spec.md +5 -4
  330. package/src/components/Field/field.stories.tsx +103 -72
  331. package/src/components/Field/field.tsx +36 -4
  332. package/src/components/Field/form-validation.spec.md +4 -0
  333. package/src/components/Field/use-form-validation.ts +1 -1
  334. package/src/components/FieldControlGroup/field-control-group.principles.stories.tsx +3 -3
  335. package/src/components/FieldControlGroup/field-control-group.spec.md +3 -3
  336. package/src/components/FileItem/file-item.tsx +10 -8
  337. package/src/components/FileUpload/file-upload.principles.stories.tsx +7 -7
  338. package/src/components/FileUpload/file-upload.spec.md +3 -2
  339. package/src/components/FileUpload/file-upload.tsx +5 -1
  340. package/src/components/FileViewer/file-viewer.anatomy.stories.tsx +1 -1
  341. package/src/components/FileViewer/file-viewer.principles.stories.tsx +1 -1
  342. package/src/components/FileViewer/file-viewer.spec.md +1 -1
  343. package/src/components/FileViewer/file-viewer.stories.tsx +1 -1
  344. package/src/components/FileViewer/file-viewer.tsx +3 -2
  345. package/src/components/HoverCard/hover-card.anatomy.stories.tsx +6 -4
  346. package/src/components/HoverCard/hover-card.principles.stories.tsx +6 -1
  347. package/src/components/HoverCard/hover-card.spec.md +2 -2
  348. package/src/components/HoverCard/hover-card.tsx +4 -2
  349. package/src/components/InlineEdit/inline-edit.anatomy.stories.tsx +184 -0
  350. package/src/components/InlineEdit/inline-edit.principles.stories.tsx +142 -0
  351. package/src/components/InlineEdit/inline-edit.spec.md +71 -46
  352. package/src/components/InlineEdit/inline-edit.stories.tsx +20 -19
  353. package/src/components/InlineEdit/inline-edit.tsx +211 -125
  354. package/src/components/Input/input.anatomy.stories.tsx +15 -15
  355. package/src/components/Input/input.principles.stories.tsx +3 -3
  356. package/src/components/Input/input.spec.md +4 -4
  357. package/src/components/Input/input.stories.tsx +3 -3
  358. package/src/components/Input/input.tsx +13 -13
  359. package/src/components/LinkInput/link-input.anatomy.stories.tsx +2 -2
  360. package/src/components/LinkInput/link-input.principles.stories.tsx +1 -1
  361. package/src/components/LinkInput/link-input.spec.md +6 -2
  362. package/src/components/LinkInput/link-input.stories.tsx +8 -8
  363. package/src/components/LinkInput/link-input.tsx +21 -19
  364. package/src/components/Notice/notice.principles.stories.tsx +1 -1
  365. package/src/components/Notice/notice.tsx +8 -2
  366. package/src/components/NumberInput/number-input.anatomy.stories.tsx +6 -6
  367. package/src/components/NumberInput/number-input.principles.stories.tsx +4 -4
  368. package/src/components/NumberInput/number-input.spec.md +6 -6
  369. package/src/components/NumberInput/number-input.stories.tsx +4 -4
  370. package/src/components/NumberInput/number-input.tsx +17 -5
  371. package/src/components/OverflowIndicator/overflow-indicator.anatomy.stories.tsx +8 -4
  372. package/src/components/OverflowIndicator/overflow-indicator.principles.stories.tsx +6 -7
  373. package/src/components/OverflowIndicator/overflow-indicator.spec.md +1 -1
  374. package/src/components/OverflowIndicator/overflow-indicator.stories.tsx +6 -30
  375. package/src/components/PeoplePicker/avatar-stack-overflow.ts +1 -1
  376. package/src/components/PeoplePicker/people-picker.anatomy.stories.tsx +13 -10
  377. package/src/components/PeoplePicker/people-picker.principles.stories.tsx +5 -1
  378. package/src/components/PeoplePicker/people-picker.stories.tsx +5 -5
  379. package/src/components/PeoplePicker/people-picker.tsx +39 -20
  380. package/src/components/PeoplePicker/person-display.tsx +3 -3
  381. package/src/components/Popover/popover.anatomy.stories.tsx +10 -7
  382. package/src/components/Popover/popover.principles.stories.tsx +49 -28
  383. package/src/components/Popover/popover.spec.md +4 -2
  384. package/src/components/ProfileCard/profile-card.anatomy.stories.tsx +1 -1
  385. package/src/components/ProfileCard/profile-card.tsx +1 -1
  386. package/src/components/ProgressBar/progress-bar.anatomy.stories.tsx +14 -13
  387. package/src/components/ProgressBar/progress-bar.principles.stories.tsx +8 -23
  388. package/src/components/ProgressBar/progress-bar.stories.tsx +8 -2
  389. package/src/components/README.md +1 -0
  390. package/src/components/RadioGroup/radio-group.anatomy.stories.tsx +8 -3
  391. package/src/components/RadioGroup/radio-group.stories.tsx +3 -3
  392. package/src/components/RadioGroup/radio-group.tsx +19 -14
  393. package/src/components/Rating/rating.anatomy.stories.tsx +1 -1
  394. package/src/components/Rating/rating.principles.stories.tsx +3 -3
  395. package/src/components/Rating/rating.spec.md +2 -2
  396. package/src/components/Rating/rating.stories.tsx +4 -18
  397. package/src/components/Rating/rating.tsx +5 -1
  398. package/src/components/ScrollArea/scroll-area.principles.stories.tsx +1 -1
  399. package/src/components/ScrollArea/scroll-area.spec.md +1 -1
  400. package/src/components/ScrollArea/scroll-area.tsx +5 -2
  401. package/src/components/SegmentedControl/segmented-control.principles.stories.tsx +4 -1
  402. package/src/components/SegmentedControl/segmented-control.spec.md +5 -10
  403. package/src/components/SegmentedControl/segmented-control.tsx +6 -3
  404. package/src/components/Select/select.anatomy.stories.tsx +13 -7
  405. package/src/components/Select/select.principles.stories.tsx +3 -3
  406. package/src/components/Select/select.spec.md +10 -1
  407. package/src/components/Select/select.stories.tsx +41 -27
  408. package/src/components/Select/select.tsx +44 -26
  409. package/src/components/SelectMenu/select-menu.anatomy.stories.tsx +44 -26
  410. package/src/components/SelectMenu/select-menu.principles.stories.tsx +0 -1
  411. package/src/components/SelectMenu/select-menu.stories.tsx +7 -19
  412. package/src/components/SelectMenu/select-menu.tsx +38 -5
  413. package/src/components/SelectionControl/boolean-value.tsx +2 -2
  414. package/src/components/SelectionControl/selection-item.anatomy.stories.tsx +1 -1
  415. package/src/components/SelectionControl/selection-item.principles.stories.tsx +4 -4
  416. package/src/components/Separator/separator.principles.stories.tsx +1 -1
  417. package/src/components/Sheet/sheet.principles.stories.tsx +5 -3
  418. package/src/components/Sheet/sheet.tsx +4 -1
  419. package/src/components/Sidebar/sidebar.tsx +1 -1
  420. package/src/components/Slider/slider.anatomy.stories.tsx +5 -4
  421. package/src/components/Slider/slider.principles.stories.tsx +6 -6
  422. package/src/components/Slider/slider.spec.md +13 -1
  423. package/src/components/Slider/slider.stories.tsx +44 -32
  424. package/src/components/Slider/slider.tsx +13 -4
  425. package/src/components/Steps/steps.anatomy.stories.tsx +7 -7
  426. package/src/components/Steps/steps.principles.stories.tsx +1 -1
  427. package/src/components/Steps/steps.spec.md +4 -2
  428. package/src/components/Steps/steps.stories.tsx +4 -56
  429. package/src/components/Steps/steps.tsx +31 -7
  430. package/src/components/Switch/switch.anatomy.stories.tsx +3 -3
  431. package/src/components/Switch/switch.principles.stories.tsx +1 -1
  432. package/src/components/Switch/switch.spec.md +4 -4
  433. package/src/components/Switch/switch.stories.tsx +3 -3
  434. package/src/components/Switch/switch.tsx +16 -10
  435. package/src/components/Tabs/tabs.anatomy.stories.tsx +6 -0
  436. package/src/components/Tabs/tabs.principles.stories.tsx +10 -2
  437. package/src/components/Tabs/tabs.spec.md +5 -2
  438. package/src/components/Tabs/tabs.stories.tsx +36 -44
  439. package/src/components/Tabs/tabs.tsx +101 -36
  440. package/src/components/Tag/tag.anatomy.stories.tsx +2 -2
  441. package/src/components/Tag/tag.principles.stories.tsx +4 -4
  442. package/src/components/Tag/tag.spec.md +4 -1
  443. package/src/components/Tag/tag.tsx +5 -3
  444. package/src/components/Textarea/textarea.anatomy.stories.tsx +6 -6
  445. package/src/components/Textarea/textarea.spec.md +5 -5
  446. package/src/components/Textarea/textarea.tsx +24 -24
  447. package/src/components/TimePicker/time-picker.anatomy.stories.tsx +12 -11
  448. package/src/components/TimePicker/time-picker.principles.stories.tsx +37 -44
  449. package/src/components/TimePicker/time-picker.spec.md +6 -5
  450. package/src/components/TimePicker/time-picker.stories.tsx +3 -3
  451. package/src/components/TimePicker/time-picker.tsx +24 -14
  452. package/src/components/Toast/toast.principles.stories.tsx +1 -1
  453. package/src/components/Toast/toast.stories.tsx +1 -1
  454. package/src/components/Tooltip/tooltip.anatomy.stories.tsx +15 -6
  455. package/src/components/Tooltip/tooltip.principles.stories.tsx +4 -4
  456. package/src/components/Tooltip/tooltip.spec.md +9 -3
  457. package/src/components/Tooltip/tooltip.stories.tsx +1 -1
  458. package/src/components/Tooltip/tooltip.tsx +12 -2
  459. package/src/components/TreeView/tree-view.anatomy.stories.tsx +16 -14
  460. package/src/components/TreeView/tree-view.principles.stories.tsx +44 -11
  461. package/src/components/TreeView/tree-view.spec.md +8 -6
  462. package/src/components/TreeView/tree-view.stories.tsx +9 -43
  463. package/src/components/TreeView/tree-view.tsx +36 -0
  464. package/src/index.ts +13 -10
  465. package/src/lib/drag-visual.ts +2 -2
  466. package/src/lib/utils.ts +19 -0
  467. package/src/patterns/action-bar/action-bar.spec.md +1 -0
  468. package/src/patterns/element-anatomy/inline-action.spec.md +1 -1
  469. package/src/patterns/element-anatomy/item-anatomy.spec.md +4 -0
  470. package/src/patterns/element-anatomy/item-anatomy.tsx +4 -2
  471. package/src/patterns/header-canonical/header-canonical.spec.md +9 -0
  472. package/src/patterns/header-canonical/header-canonical.stories.tsx +31 -5
  473. package/src/tokens/categorical-color.ts +5 -5
  474. package/src/tokens/color/color.spec.md +4 -4
  475. package/src/tokens/color/primitives.css +9 -2
  476. package/src/tokens/motion/motion.spec.md +6 -7
  477. package/src/tokens/opacity/opacity.spec.md +1 -1
  478. package/src/tokens/orphan-tokens.spec.md +1 -1
  479. package/src/tokens/typography/typography.spec.md +2 -0
  480. package/src/tokens/uiSize/uiSize.css +5 -0
  481. package/src/tokens/uiSize/uiSize.spec.md +1 -1
@@ -0,0 +1,142 @@
1
+ // @benchmark-unverified-blanket: file-level retraction per M22 (d) — 本檔 brand 引用(Jira/Linear/Notion)為真實業務場景示意(mindset #4),非 URL-citable 的 DS 行為 benchmark claim。
2
+ import * as React from 'react'
3
+ import type { Meta, StoryObj } from '@storybook/react'
4
+ import { InlineEdit } from './inline-edit'
5
+ import { Field, FieldLabel } from '@/design-system/components/Field/field'
6
+ import { Input } from '@/design-system/components/Input/input'
7
+
8
+ /**
9
+ * InlineEdit 設計原則(principles)—— 何時用 / 何時不用 / 近親分界 / 內容準則。
10
+ */
11
+ const meta: Meta<typeof InlineEdit> = {
12
+ title: 'Design System/Components/InlineEdit/設計原則',
13
+ component: InlineEdit,
14
+ parameters: { layout: 'padded' },
15
+ }
16
+ export default meta
17
+ type Story = StoryObj<typeof InlineEdit>
18
+
19
+ function Card({ tone, title, children }: { tone: 'do' | 'dont' | 'neutral'; title: string; children: React.ReactNode }) {
20
+ const ring = tone === 'do' ? 'border-success' : tone === 'dont' ? 'border-error' : 'border-border'
21
+ const tag = tone === 'do' ? '✓ 適用' : tone === 'dont' ? '✕ 不適用' : '—'
22
+ const tagColor = tone === 'do' ? 'text-success' : tone === 'dont' ? 'text-error' : 'text-fg-muted'
23
+ return (
24
+ <div className={`rounded-lg border ${ring} bg-surface p-[var(--layout-space-loose)]`}>
25
+ <p className={`mb-1 text-caption font-semibold ${tagColor}`}>{tag}</p>
26
+ <p className="mb-2 text-body font-medium text-foreground">{title}</p>
27
+ <div className="text-body text-fg-secondary">{children}</div>
28
+ </div>
29
+ )
30
+ }
31
+ function Stateful({ value: initial, ...props }: Omit<React.ComponentProps<typeof InlineEdit>, 'onCommit'>) {
32
+ const [value, setValue] = React.useState(initial)
33
+ return <InlineEdit {...props} value={value} onCommit={setValue} />
34
+ }
35
+
36
+ export const WhenToUse: Story = {
37
+ name: '何時用',
38
+ render: () => (
39
+ <div className="grid max-w-[860px] grid-cols-2 gap-4">
40
+ <Card tone="do" title="詳情面板的可編欄位">
41
+ Jira / Linear issue 詳情、Notion property —— 值平常是純內容,原地點擊即可改,不跳表單。
42
+ <div className="mt-3"><Stateful value="Fix flaky checkout e2e test" label="標題" /></div>
43
+ </Card>
44
+ <Card tone="do" title="標題型就地編輯">
45
+ 頁面 / 文件標題 <code>as="h1"</code>,保留文件大綱,同時可點擊改名。
46
+ <div className="mt-3"><Stateful as="h2" value="Q3 產品路線圖" readClassName="text-h5 font-bold" label="文件標題" /></div>
47
+ </Card>
48
+ <Card tone="do" title="唯讀值同場並列(editable=false)">
49
+ 系統產生值(訂單編號、建立時間)與可編欄位並列 —— 用 <code>editable=false</code>,視覺一致但無編輯入口、不灰化。
50
+ <div className="mt-3"><Stateful value="PROJ-482" label="編號" editable={false} /></div>
51
+ </Card>
52
+ <Card tone="do" title="格式化值就地編輯">
53
+ 狀態 Tag / 日期 / 負責人頭像 —— <code>renderRead</code> 委派控件 view mode,edit 換該控件 edit mode。
54
+ </Card>
55
+ </div>
56
+ ),
57
+ }
58
+
59
+ export const WhenNotToUse: Story = {
60
+ name: '何時不用',
61
+ render: () => (
62
+ <div className="grid max-w-[860px] grid-cols-2 gap-4">
63
+ <Card tone="dont" title="整張表單填寫">
64
+ 新增 / 建立流程要一次填多欄 —— 用 <code>&lt;Field&gt; + 控件</code> 明確表單,不用逐欄就地編輯。
65
+ <div className="mt-3">
66
+ <Field><FieldLabel>Email</FieldLabel><Input placeholder="you@company.com" /></Field>
67
+ </div>
68
+ </Card>
69
+ <Card tone="dont" title="永久唯讀、不同場可編欄位">
70
+ 純展示頁(唯讀報表)—— 直接用 <code>&lt;Control mode="view"&gt;</code> 或純文字,不需就地編輯的 hover 入口成本。
71
+ </Card>
72
+ <Card tone="dont" title="需要即時驗證 / 複雜互動">
73
+ 密碼、需即時錯誤回饋、multi-step 的欄位 —— 用完整 Field + FieldError,不塞進二態就地編輯。
74
+ </Card>
75
+ <Card tone="dont" title="條件停用的欄位">
76
+ 「選國家前城市不可填」這種表單條件停用 —— 用 <code>&lt;Field mode="disabled"&gt;</code>,不是 InlineEdit
77
+ (就地編輯無 disabled 態;鎖定 = editable=false 純 view)。
78
+ </Card>
79
+ </div>
80
+ ),
81
+ }
82
+
83
+ export const VsControlView: Story = {
84
+ name: '近親分界:就地編輯 vs 唯讀展示 vs 表單',
85
+ render: () => (
86
+ <div className="max-w-[820px]">
87
+ <table className="w-full">
88
+ <thead>
89
+ <tr className="border-b-2 border-border text-left text-caption font-semibold text-fg-muted">
90
+ <th className="py-2 pr-4">用它</th>
91
+ <th className="py-2 pr-4">場景</th>
92
+ <th className="py-2">可編 / 入口</th>
93
+ </tr>
94
+ </thead>
95
+ <tbody className="text-body text-fg-secondary">
96
+ <tr className="border-b border-divider align-top">
97
+ <td className="py-2 pr-4 font-medium text-foreground">InlineEdit</td>
98
+ <td className="py-2 pr-4">詳情面板、可原地改的值</td>
99
+ <td className="py-2">view↔edit,hover 入口(editable=true)</td>
100
+ </tr>
101
+ <tr className="border-b border-divider align-top">
102
+ <td className="py-2 pr-4 font-medium text-foreground">InlineEdit editable=false</td>
103
+ <td className="py-2 pr-4">與可編欄位並列的唯讀值(訂單編號)</td>
104
+ <td className="py-2">純 view,無入口、不灰化</td>
105
+ </tr>
106
+ <tr className="border-b border-divider align-top">
107
+ <td className="py-2 pr-4 font-medium text-foreground">{'<Control mode="view">'}</td>
108
+ <td className="py-2 pr-4">永久唯讀展示頁的值</td>
109
+ <td className="py-2">純展示,無互動</td>
110
+ </tr>
111
+ <tr className="align-top">
112
+ <td className="py-2 pr-4 font-medium text-foreground">{'<Field> + 控件'}</td>
113
+ <td className="py-2 pr-4">表單填寫(一次多欄)</td>
114
+ <td className="py-2">恆 edit,label + error</td>
115
+ </tr>
116
+ </tbody>
117
+ </table>
118
+ </div>
119
+ ),
120
+ }
121
+
122
+ export const ContentGuidelines: Story = {
123
+ name: '內容準則',
124
+ render: () => (
125
+ <div className="grid max-w-[860px] grid-cols-2 gap-4">
126
+ <Card tone="do" title="placeholder 用行動語氣">
127
+ 空值提示寫「指派負責人…」「新增說明…」,告訴使用者能做什麼,而非「無」。
128
+ <div className="mt-3"><Stateful value="" label="負責人" placeholder="指派負責人…" /></div>
129
+ </Card>
130
+ <Card tone="do" title="label 傳人類可讀名稱">
131
+ <code>label</code> 組出 aria-label「編輯 負責人」—— 傳欄位真名,別傳代號。
132
+ </Card>
133
+ <Card tone="dont" title="別把格式化 Tag 自刻">
134
+ 狀態值要顯示 Tag,<code>renderRead</code> 必須渲染 <code>&lt;Select mode="view" display="tag"&gt;</code> ——
135
+ 自己拼 <code>&lt;Tag&gt;</code> 會與 edit 態格式漂移。
136
+ </Card>
137
+ <Card tone="neutral" title="多行用 multiline">
138
+ Jira description 類長文字傳 <code>multiline</code> —— view 保留換行、edit 換 Textarea,上下 padding 一致。
139
+ </Card>
140
+ </div>
141
+ ),
142
+ }
@@ -9,79 +9,100 @@ benchmark:
9
9
  - Atlassian inline-edit read-view: https://github.com/pioug/atlassian-frontend-mirror/blob/main/design-system/inline-edit/src/internal/read-view.tsx
10
10
  - PatternFly inline-edit: https://www.patternfly.org/components/inline-edit/design-guidelines
11
11
  - Notion database title cell: https://www.notion.com/help/intro-to-databases
12
+ - MUI X DataGrid editing(isCellEditable): https://mui.com/x/react-data-grid/editing/
13
+ - AG Grid cell editing(editable): https://www.ag-grid.com/react-data-grid/cell-editing/
12
14
  ---
13
15
 
14
16
  # InlineEdit 設計原則
15
17
 
16
- **Layout Family**:composite。**edit 態委派 Family 4(Field Control Layout)**——直接渲染 Field 控件(預設 `<Input>`),幾何/邊框/focus 全繼承 `fieldWrapperStyles`,不新增 layout primitive。**read 態為 self-contained 純文字承載 + 隱形 Pressable**(絕對定位透明 `<button>`),只負責「看起來像純文字、hover 才有 affordance、點擊進 edit」。
18
+ **Layout Family**:composite。**edit 態委派 Family 4(Field Control Layout)**——直接渲染 Field 控件(預設 `<Input>`),幾何/邊框/focus 全繼承 `fieldWrapperStyles`,不新增 layout primitive。**view 態幾何同樣委派 Family 4(Model A,不自帶 geometry cva)**——值-格式化路徑委派 `<Control mode="view">`、純值/標題路徑套 `fieldViewGeometry` helper(見「幾何典範」);本體只疊 hover bg + focus 藍框 + 隱形 Pressable(絕對定位透明 `<button>`),負責「看起來像純內容、hover 才有 affordance、點擊進 edit」。
17
19
 
18
20
  ## 定位
19
21
 
20
22
  **實作基礎:自建 + composes Field 家族**(`renderEdit` 預設 `<Input>`)。
21
23
 
22
- 為何自建而非 Field prop/variant:InlineEdit 的本質是**兩個顯示態之間的 toggle 狀態機**(純文字 read view ↔ 完整 Field 控件 edit view)+ **就地編輯的進入/退出/commit/cancel 生命週期**。這是 Field 控件之上的**互動編排層**,不是某個 Field 控件的視覺 variant——Field 控件本身永遠是「一直可編輯的輸入框」,InlineEdit 才決定「何時顯示成純文字、何時顯示成輸入框」。故它 **compose** Field(消費 `<Input>` 當 edit 態),而非擴充 Field。
24
+ 為何自建而非 Field prop/variant:InlineEdit 的本質是**兩個顯示態之間的 toggle 狀態機**(純值 view ↔ 完整 Field 控件 edit)+ **就地編輯的進入/退出/commit/cancel 生命週期**。這是 Field 控件之上的**互動編排層**,不是某個 Field 控件的視覺 variant——Field 控件本身永遠是「一直可編輯的輸入框」,InlineEdit 才決定「何時顯示成純文字、何時顯示成輸入框」。故它 **compose** Field(消費 `<Input>` 當 edit 態),而非擴充 Field。
23
25
 
24
26
  一句話:**Field 控件回答「這個值長什麼樣、怎麼編輯」;InlineEdit 回答「這個值平常是純文字,想改時才變成 Field 控件」**。
25
27
 
26
- ## 互動模型(read / hover / edit 三態 canonical
28
+ ## Controlled-only rationale(Dim 26
29
+
30
+ 本元件採 **controlled-only**:`value` + `onCommit`,不支援 `defaultValue` uncontrolled fallback。對齊 Field 家族 7 個 controlled-only 元件慣例(Combobox / DatePicker / TimePicker / SelectMenu / LinkInput / NumberInput / PeoplePicker,rationale 見各 spec 同名段)。就地編輯的語意本質 = **值住在 consumer**(detail-pane 資料層),`onCommit` 單向回寫;內部已有 draft 草稿層(進 edit 以 `value` 為初值、結算才 commit),再開 uncontrolled 會產生 draft ↔ internal value 雙層 sync race。未來要改 dual-mode 需 `useControllableState` helper,屬 major API 擴充,目前不在 scope。
31
+
32
+ ## 互動模型(view ↔ edit 二態 canonical;hover 是 view 子態)
27
33
 
28
34
  | 態 | 視覺 | 行為 |
29
35
  |----|------|------|
30
- | **read**(靜態) | 純文字,寬度 fill 容器,**透明邊框(預留、不可見)、無底色**(視覺 = 純文字) | 隱形 Pressable 提供 click / 鍵盤 Tab focus / Enter·Space 進 edit |
31
- | **read + hover** | **灰色底色 `bg-neutral-hover` + `rounded-md`**(**非邊框**)+ cursor | 提示「這裡可點擊編輯」 |
32
- | **read + focus-visible**(鍵盤) | 外框染 **`border-primary` 藍框**(消費 Field edit focus 語言,**非 Button ring**) | Enter / Space → 進 edit |
33
- | **edit** | **真正的 Field 控件**(完整 chrome:border + focus 藍框 + input) | 回歸 field-controls edit canonical;blur/Enter → commit、Esc → cancel |
36
+ | **view**(靜止) | 純值 / 格式化 renderRead,寬度 fill 容器,**透明邊框(預留、不可見)、無底色**(視覺 = 純內容) | editable 時隱形 Pressable 提供 click / 鍵盤 Tab focus / Enter·Space 進 edit |
37
+ | **view + hover**(editable 限定) | **灰色底色 `bg-neutral-hover` + `rounded-md`**(**非邊框**)+ cursor | 提示「這裡可點擊編輯」 |
38
+ | **view + focus-visible**(editable 限定,鍵盤) | 外框染 **`border-primary` 藍框**(消費 Field edit focus 語言,**非 Button ring**) | Enter / Space → 進 edit |
39
+ | **edit** | **真正的 Field 控件**(`mode="edit"` 完整 chrome:border + focus 藍框 + input) | 回歸 field-controls edit canonical;blur/Enter → commit、Esc → cancel |
40
+
41
+ 只有 **view ↔ edit 二態**(hover / focus-visible 是 view 的子態,非第三態);與 DataTable cell 是同一份「就地編輯 host」語義(`../Field/field-controls.spec.md`「軸二 就地編輯 host」段)。
42
+
43
+ **退出 edit 態(focus 分流)**:
44
+ - **滑鼠 blur**(點外面)→ `commit`(草稿有變更才觸發 `onCommit`),**焦點刻意不送回**——回到純 view,不搶焦
45
+ - **Enter**(鍵盤)→ `commit`,**焦點送回 view 按鈕** + focus-visible 顯藍框(鍵盤焦點不遺失)
46
+ - **Esc**(鍵盤)→ `cancel`(放棄草稿還原 view 態,不 commit),焦點同送回 view 按鈕
47
+ - 分流理由:鍵盤路徑需焦點連續(a11y);滑鼠 blur = 使用者意圖已在別處,強拉焦點回來是搶焦。`focus-visible` 保證只有鍵盤路徑顯藍框。
48
+ - **exit 由 InlineEdit 保證、不靠 consumer 自接(2026-07-17 root cause 修:blur 後 chrome 殘留)**:自訂 `renderEdit`(Select/DatePicker 等 overlay 控件)若只接 `onChange→commit`、不接 dismiss,**不選直接點外面**時舊設計卡 `editing=true`(edit chrome 殘留)。修:InlineEdit 用 **document-level pointerdown-outside**(對齊 [Radix DismissableLayer](https://www.radix-ui.com/primitives/docs/utilities/dismissable-layer) + [Atlassian inline-edit](https://github.com/pioug/atlassian-frontend-mirror/blob/main/design-system/inline-edit/src/inline-edit.tsx) click-outside)—— editing 時點在 edit 子樹外且不在本 edit popover 內 → `commit` 回 view;+ `onBlur` focusout 補 tab-out 路徑。**任何控件(含 overlay)blur/點外面都保證回 display**,pixel 驗證 `probe-inline-edit-align.mjs`(點 Status 進 edit→blur→`editing=0`)。
49
+
50
+ ## editable 閘(無 disabled 態)
34
51
 
35
- **退出 edit 態**:
36
- - **blur**(點外面)→ `commit`(草稿有變更才觸發 `onCommit`)
37
- - **Enter** → `commit`
38
- - **Esc** → `cancel`(放棄草稿,還原 read 態,不 commit)
39
- - 任一路徑結算後,focus 送回 read 按鈕(鍵盤焦點不遺失)
52
+ `editable?: boolean`(預設 true)。`editable=false` → view 態**無 hover 入口、不渲染隱形 Pressable、無藍框、不可進 edit,且不灰化**——就地編輯 host **無 disabled 態**,鎖定 = 純 view 無入口(detail-pane 權限鎖定語意)。與 DataTable cell 的 `editable:(row)=>boolean` 判準閘是**同一份語義**(`../Field/field-controls.spec.md`「軸二」);世界級就地編輯 grid 同模型:MUI X isCellEditable(https://mui.com/x/react-data-grid/editing/)/ AG Grid editable(https://www.ag-grid.com/react-data-grid/cell-editing/)。永久唯讀資料 → 用 `<Control mode="view">`;隨權限切換可編/不可編 → `editable`;**不用 disabled**(灰化是表單 disabled 語言,不是就地編輯語言)。
40
53
 
41
- ## 幾何典範(2026-07-09 user 拍板,每值消費既有 SSOT,零自創)
54
+ ## 幾何典範(Model A 委派模型,2026-07-16 round16 user GO;推翻 2026-07-09「自帶外框幾何」版)
42
55
 
43
- | 面向 | 定義 | 消費的 SSOT |
44
- |------|------|------------|
45
- | 外框(read/edit 共用) | `-mx-[var(--field-px)]` + `w-[calc(100% + 2*var(--field-px))]` → 左右各對稱外擴 `--field-px`、填滿容器 | `--field-px` |
46
- | 水平內距 | 文字 `px-[var(--field-px)]`(read=edit 同值)→ 文字左緣與相鄰內容切齊、底色/框外擴 gutter | `--field-px` |
47
- | 垂直 | `min-h-[var(--field-height-{size})]` + 垂直置中(**無 py token**——Field 家族本就以「固定高度 + 置中」定義呼吸,非 padding;故 InlineEdit 同法,隨 size/density 自動縮放不漂移) | `--field-height-{sm/md/lg}` |
48
- | hover(read) | `bg-neutral-hover` + `rounded-md`(底色 tint,**非邊框**) | color token + field radius |
49
- | focus(read/鍵盤) | 透明 `border` 預留、focus 染 `border-primary`(Field focus 藍框語言,**非 ring**) | Field focus canonical |
50
- | edit | 真正 `<Input>`(border + focus + input),width=fill 填滿外框 | field-controls edit canonical |
56
+ InlineEdit **不自帶 geometry cva**(M17 消重)——view 態幾何兩路來源,皆消費既有 SSOT:
51
57
 
52
- **「底色範圍 = 輸入框範圍」不變量**:read edit 兩態共用同一外框幾何(同 `-mx`+calc 寬、同 `min-h`=field-height、同 1px 邊框盒),故 hover 底色與 edit 輸入框逐 pixel 對齊、態切換零位移。(舊版 read 外框 `w-full`+`-mx` 疊加 → 右側短一截、整塊左偏,已修。)
58
+ | 路徑 | 幾何來源 | 說明 |
59
+ |------|---------|------|
60
+ | **值-格式化**(Select→Tag / 日期 / avatar) | 委派 `<Control mode="view">`(view×default = edit 幾何減 chrome) | 幾何 + 內部間距(多 tag gap / avatar↔label)皆由控件 view mode 提供——view 與 edit **同一顆控件、只差 chrome** → 天生一致、零偏移 |
61
+ | **純值 / 標題**(`String(value)` / `as="h1"`) | `fieldViewGeometry(size, multiline)` helper(`../Field/field-wrapper.tsx`,view×default 幾何 SSOT) | = `px-[var(--field-px)]` + `min-h-[var(--field-height-{size})]`;單行 `items-center`、多行 `items-start py-2` |
53
62
 
54
- **尺寸預設 = `sm`**(2026-07-09 user 拍板):read 態無邊框、視覺即純文字,尺寸過大會使版面鬆散;consumer 可傳 `size` 覆寫,標題場景另用 `readClassName` 疊大字級。
63
+ InlineEdit 本體只給:**對齊盒(-mx orientation-aware)+ 委派 view 統一 field-px + exit 保證 + hover bg + focus 藍框 + 隱形 Pressable**。
55
64
 
56
- **核心不變量:read 態 hover = 背景 tint;read 態 focus = Field 藍框(`border-primary`),與 edit 態同源。** hover(滑鼠懸停)用底色、focus(鍵盤選中)用藍框——藍框是 edit focus 的預覽,read→edit 一路都是藍框、不會 ring→框跳一下;靜態(無 hover/focus)則透明邊框不可見 = 純文字。
65
+ **對齊盒(-mx orientation-aware,2026-07-17 移除 fieldCtx gate root cause 修)**:
66
+ - **對齊 root invariant**:固定 `-mx-[--field-px]` 要能把值/tag/avatar 左緣拉到 label x=0,**充要條件 = 委派控件 view 左 px 恆 = field-px**。故 InlineEdit **兩條 view 路徑都用同一個 `fieldViewGeometry`(field-px + min-h + py)**:純值/標題路徑直接套;**委派控件路徑(renderRead)也用 `fieldViewGeometry` 包住**(委派控件 bare view 皆 0px 左內距 → 包住後統一 field-px)。這樣 tag/avatar/date 全對齊 label(不碰任何控件的 tagPadding)。
67
+ - **vertical(預設,含 standalone)** → `-mx-[var(--field-px)]` + `w-[calc(100%+2*var(--field-px))]`:整塊拉到欄左緣 → 值/tag 左緣落 label x=0(= [Atlassian read-view](https://github.com/pioug/atlassian-frontend-mirror/blob/main/design-system/inline-edit/src/internal/read-view.tsx) 固定負邊距對照)。**-mx 只依 orientation、不依 fieldCtx**(spec Model A 原意;舊 `fieldCtx &&` gate 讓 standalone 不對齊 = bug,已移除)。-mx bleed field-px 到容器 padding = 刻意(hover/edit 才顯真實邊界)→ consumer 放 ≥field-px padding 容器
68
+ - **horizontal** → `w-full min-w-0`,**不用 -mx**:值內縮 px = 對齊 sibling 控件
69
+ - **edit 態完全繼承原 field**(Jira 式,user 2026-07-17 拍板):edit **不套** field-px 統一,tag 保留 tagPadding 等原生樣式。純文字 view↔edit 零跳;tag 進 edit 有 ~6px 微移(= Jira 式,可接受)。pixel 驗證 `scripts/probe-inline-edit-align.mjs`(VerticalFieldForm plain/tag/多行 Δ≤1px)
57
70
 
58
- ## 型別化 read view + 多行 + 鍵盤 SSOT(2026-07-09 L2)
71
+ **多行(multiline)**:view = `items-start py-2`,**= Textarea edit `py-2`**(textarea.tsx base)——`scripts/inline-edit-view-geometry-invariant.mjs` 機械鎖此契約(Textarea py 一改即紅)。
72
+
73
+ **「底色範圍 = 輸入框範圍」不變量**:view 與 edit 兩態共用同一對齊盒(同寬、同 1px 邊框盒),幾何同源(view×default = edit 幾何減 chrome),故 hover 底色與 edit 輸入框逐 pixel 對齊、態切換零位移。(舊版 read 外框 `w-full`+`-mx` 疊加 → 右側短一截、整塊左偏,已修;2026-07-16 -mx 由恆定改條件化;2026-07-17 進一步移除 fieldCtx gate → 純 orientation-aware,委派 view 統一 fieldViewGeometry field-px,standalone 也對齊。)
74
+
75
+ **尺寸預設 = `sm`**(2026-07-09 user 拍板):view 態無邊框、視覺即純文字,尺寸過大會使版面鬆散;consumer 可傳 `size` 覆寫,標題場景另用 `readClassName` 疊大字級。
76
+
77
+ **核心不變量:view 態 hover = 背景 tint;view 態 focus = Field 藍框(`border-primary`),與 edit 態同源。** hover(滑鼠懸停)用底色、focus(鍵盤選中)用藍框——藍框是 edit focus 的預覽,view→edit 一路都是藍框、不會 ring→框跳一下;靜止(無 hover/focus)則透明邊框不可見 = 純值。
78
+
79
+ ## 型別化 view + 多行 + 鍵盤 SSOT(2026-07-09 L2)
59
80
 
60
81
  InlineEdit 為**泛型 `<T>`**(非只 string):`value: T` / `onCommit: (next: T) => void`。三個 render 面向:
61
82
 
62
83
  | Prop | 作用 | 預設 | SSOT 鐵律 |
63
84
  |---|---|---|---|
64
- | `renderRead?(value)` | read 態格式化顯示 | `String(value)`(空 → placeholder) | 要顯 **Tag / 日期 / option label**,**渲染對應控件的 `mode="display"`**(如 `<Select mode="display" display="tag">`),**禁自刻**——格式化 SSOT 住在該控件的 display mode |
85
+ | `renderRead?(value)` | view 態格式化顯示 | `String(value)`(空 → placeholder) | 要顯 **Tag / 日期 / option label**,**渲染對應控件的 `mode="view"`**(如 `<Select mode="view" display="tag">`),**禁自刻**——格式化 SSOT 住在該控件的 view mode |
65
86
  | `renderEdit?(props)` | edit 態控件 | `multiline`→`<Textarea>` / 否則 `<Input>` | 非 string 值必傳(預設路徑僅 string) |
66
- | `multiline?` | 多行(Jira description 類) | false | read `items-start` 換行、edit Textarea(Enter=換行、Cmd/Ctrl+Enter 或 blur 提交) |
87
+ | `multiline?` | 多行(Jira description 類) | false | view `items-start py-2` 換行(= Textarea edit py-2,見「幾何典範」)、edit Textarea(Enter=換行、Cmd/Ctrl+Enter 或 blur 提交) |
67
88
 
68
- **Tag 顯示的 SSOT 對齊(user 2026-07-09 問)**:「select 值 → Tag」的格式化住在 **Select 的 `mode="display" display="tag"`**(`select.tsx` ReadonlyDisplay Tag 分支)—— 這正是 **DataTable 格子消費的同一份**(`cell-registry.tsx` SelectCell display 分支)。InlineEdit read 態顯 Tag = `renderRead={(v) => <Select mode="display" display="tag" value={v} options={opts} />}`,**read 與 edit 用同一個 Select、只切 mode → 格式零分歧**。此即「乾淨對齊」:InlineEdit 從不重刻 Tag,格式化永遠委派給控件的 display mode。示範見 `inline-edit.stories.tsx` `SelectTagField`。
89
+ **Tag 顯示的 SSOT 對齊(user 2026-07-09 問)**:「select 值 → Tag」的格式化住在 **Select 的 `mode="view" display="tag"`**(`select.tsx` ReadonlyDisplay Tag 分支)—— 這正是 **DataTable 格子消費的同一份**(`cell-registry.tsx` SelectCell view 分支)。InlineEdit view 態顯 Tag = `renderRead={(v) => <Select mode="view" display="tag" value={v} options={opts} />}`,**view 與 edit 用同一個 Select、只切 mode → 格式零分歧**。此即「乾淨對齊」:InlineEdit 從不重刻 Tag,格式化永遠委派給控件的 view mode。示範見 `inline-edit.stories.tsx` `SelectTagField`。
69
90
 
70
91
  **鍵盤結算 SSOT**:Enter=commit / Esc=cancel + **中文 IME 組字 guard** 抽為 `../Field/field-edit-keys.ts` `makeEditSettleKeyHandler`,**InlineEdit 與 DataTable cell(`cell-registry.tsx`)同源消費**(2026-07-09 抽出;原本 InlineEdit 漏 IME guard = 中文選字 Enter 誤提交半截組字 bug,現已修)。edit-in-place SSOT 研究(2026-07-09,6-agent + 世界級)結論:兩 host 的 draft 擁有權 / state locus(單實例 vs 表格級 keyed)/ focus model 根本不同,故**不抽大共用狀態機**(違 M21 + 破壞表格虛擬捲動效能),只抽此純鍵盤結算片段。世界級共識 = **field 控件 + 型別 registry 可共用,edit-in-place orchestration 各自實作**(見 `../Field/field-controls.spec.md`「Field 框架地圖」段 cite)。
71
92
 
72
93
  ## 世界級對照
73
94
 
74
- | DS | read 態 | hover affordance | 進 edit | 鍵盤 |
95
+ | DS | view 態 | hover affordance | 進 edit | 鍵盤 |
75
96
  |----|---------|-----------------|---------|------|
76
97
  | **Atlassian inline-edit**([read-view.tsx](https://github.com/pioug/atlassian-frontend-mirror/blob/main/design-system/inline-edit/src/internal/read-view.tsx)) | 純值 + 隱形 Pressable | **背景 tint**(`background.neutral.subtle.hovered`,**非邊框**) | click read view | 隱形 button focus + Enter;5px drag threshold 防選字誤觸 |
77
98
  | **PatternFly inline-edit** | value 文字 | 可編輯提示 | value/input 二態 toggle | Enter commit / Esc cancel |
78
99
  | **Notion title cell** | 純文字 | hover 浮 affordance | click 編輯 | — |
79
100
 
80
- 三家共識:read 態 = 純值、hover = 輕背景提示(非重邊框)、click 或鍵盤進 edit、edit = 真正的輸入控件。本 DS 對齊此模型;`bg-neutral-hover` = 本 DS 的「neutral subtle hover」等價 token。
101
+ 三家共識:view 態 = 純值、hover = 輕背景提示(非重邊框)、click 或鍵盤進 edit、edit = 真正的輸入控件。本 DS 對齊此模型;`bg-neutral-hover` = 本 DS 的「neutral subtle hover」等價 token。
81
102
 
82
- **read 態 focus canonical**(2026-07-09):Atlassian read-view 的隱形 Pressable focus 時,外框顯 `color.border.focused` **實線 solid border**(非 ring)——[read-view.tsx](https://github.com/pioug/atlassian-frontend-mirror/blob/main/design-system/inline-edit/src/internal/read-view.tsx) `&:focus + div { borderColor: token('color.border.focused') }`。本 DS 對齊此語言:read focus = `border-primary`(= Field edit focus 藍框),使 read→edit focus 視覺連續,不引入第三種(ring)focus 語言。
103
+ **view 態 focus canonical**(2026-07-09):Atlassian read-view 的隱形 Pressable focus 時,外框顯 `color.border.focused` **實線 solid border**(非 ring)——[read-view.tsx](https://github.com/pioug/atlassian-frontend-mirror/blob/main/design-system/inline-edit/src/internal/read-view.tsx) `&:focus + div { borderColor: token('color.border.focused') }`。本 DS 對齊此語言:view focus = `border-primary`(= Field edit focus 藍框),使 view→edit focus 視覺連續,不引入第三種(ring)focus 語言。
83
104
 
84
- **read 態 typography canonical**(2026-07-09):Atlassian inline-edit / PatternFly 將讀態字級**委派**給 consumer(read view 不強加字體,由外層 context 決定)。本 DS 更 opinionated——read 態預設消費 `fieldDisplayTextClass(size)`(sm/md→`text-body`,lg→`text-body-lg`,`../Field/field-wrapper.tsx` Field display 字體 SSOT),使 **plain 用法**(`<InlineEdit value onCommit />`)read 字級即 = edit `<Input>`(`text-body`)= `<Input mode="display">` 字級,三態零跳字;`readClassName`(如標題 `text-h4 font-bold`)僅作特例 override 疊其後。對齊 [Atlassian inline-edit read-view](https://github.com/pioug/atlassian-frontend-mirror/blob/main/design-system/inline-edit/src/internal/read-view.tsx) 讀態委派模型,並補上「plain 用法即 canonical」的預設。
105
+ **view 態 typography canonical**(2026-07-09):Atlassian inline-edit / PatternFly view 態字級**委派**給 consumer(readView 不強加字體,由外層 context 決定)。本 DS 更 opinionated——view 態預設消費 `fieldDisplayTextClass(size)`(sm/md→`text-body`,lg→`text-body-lg`,`../Field/field-wrapper.tsx` Field view 字體 SSOT),使 **plain 用法**(`<InlineEdit value onCommit />`)view 字級即 = edit `<Input>`(`text-body`)= `<Input mode="view">` 字級,三者一致零跳字;`readClassName`(如標題 `text-h4 font-bold`)僅作特例 override 疊其後。對齊 [Atlassian inline-edit read-view](https://github.com/pioug/atlassian-frontend-mirror/blob/main/design-system/inline-edit/src/internal/read-view.tsx) 委派模型,並補上「plain 用法即 canonical」的預設。
85
106
 
86
107
  **狀態 data 屬性**(可測性):外層容器 `data-editing`(read/edit 態)、read 元素 `data-empty`(空值)——供 audit / Playwright 斷言態切換與空值渲染(Dice UI data-attribute idiom,對齊 M32 pixel/DOM 可驗)。
87
108
 
@@ -93,9 +114,9 @@ InlineEdit 為**泛型 `<T>`**(非只 string):`value: T` / `onCommit: (next:
93
114
 
94
115
  ## 何時不用
95
116
 
96
- - **表單欄位** → 直接用 `<Field>` + Field 控件(表單本來就是編輯情境,不需要 read 態偽裝成純文字)。
117
+ - **表單欄位** → 直接用 `<Field>` + Field 控件(表單本來就是編輯情境,不需要 view 態偽裝成純文字)。
97
118
  - **DataTable 密集可編輯格** → 用 naked cell(host cell 自管 border + focus、hover outline affordance;見 `field-controls.spec.md`「Cell(naked variant)」),不是每格包一個 InlineEdit 狀態機。
98
- - **一律唯讀展示**(不可編) → 用 `<Input mode="display">` / 純文字,不需要 Pressable。
119
+ - **一律唯讀展示**(不可編) → 用 `<Input mode="view">` / 純文字,不需要 Pressable;**隨權限暫時鎖定** → 留 InlineEdit 傳 `editable=false`(不灰化,見「editable 閘」)
99
120
  - **多欄位一次編輯** → 用表單(進入一次編輯全部欄位),不是逐欄 inline edit。
100
121
 
101
122
  ## 近親分界
@@ -103,18 +124,18 @@ InlineEdit 為**泛型 `<T>`**(非只 string):`value: T` / `onCommit: (next:
103
124
  | 元件 | 分界 |
104
125
  |------|------|
105
126
  | **Field 控件**(Input/Select/Textarea…) | 一直可編輯的輸入框;InlineEdit 讓它「平常是純文字,想改才現身」 |
106
- | **`<Input mode="display">`** | 純展示(無互動、不可編);InlineEdit 的 read 態可點擊進 edit |
127
+ | **`<Input mode="view">`** | 純展示(無互動、不可編);InlineEdit 的 view 態可點擊進 edit |
107
128
  | **naked cell**(DataTable) | 表格密集網格的 cell-as-input,host cell 自管 chrome;InlineEdit 是單一 standalone 欄位的閱讀↔編輯 toggle |
108
129
 
109
130
  ## 常見誤解
110
131
 
111
- - **「read 態 hover 該加邊框」** → 錯。hover = 背景 tint(`bg-neutral-hover`);邊框是 edit 態語言。
132
+ - **「view 態 hover 該加邊框」** → 錯。hover = 背景 tint(`bg-neutral-hover`);邊框是 edit 態語言。
112
133
  - **「edit 態要自己刻 input chrome」** → 錯。edit 態 = 真正的 Field 控件(預設 `<Input>`),完全回歸 field-controls edit canonical,不重刻。
113
134
  - **「大標題 inline edit 要自己做 focus/commit/esc」** → 錯。用 InlineEdit,生命週期(commit/cancel/Esc/focus 返回)由 primitive 統一處理。
114
135
 
115
136
  ## 空值
116
137
 
117
- `value` 為空字串 / null 時,read 態顯示 `placeholder`(若有)+ `text-fg-muted`;無 placeholder 則空白。edit 態進入時草稿初值 = 空字串,顯示空 input。(不用唯讀展示語言的半形 `-` dash——InlineEdit 是可編輯欄位,空值 = 「還沒填、點來填」,dash 是唯讀展示語言。)
138
+ `value` 為空字串 / null 時,view 態顯示 `placeholder`(若有)+ `text-fg-muted`;無 placeholder 則空白。edit 態進入時草稿初值 = 空字串,顯示空 input。(不用唯讀展示語言的半形 `-` dash——InlineEdit 是可編輯欄位,空值 = 「還沒填、點來填」,dash 是唯讀展示語言。)
118
139
 
119
140
  ## 驗證
120
141
 
@@ -127,23 +148,27 @@ InlineEdit 不含 async loading state。若 commit 需要 async 儲存,consumer
127
148
  ## A11y 預設
128
149
 
129
150
  **ARIA**:
130
- - read 態隱形 Pressable = `<button type="button">`,帶 `aria-label`(預設「編輯 {label}」)——螢幕閱讀器朗讀「可編輯」意圖。
131
- - read 態語意標籤(`as`,預設 `span`;標題傳 `h1`/`h2`)獨立於 button 之外——`as="h1"` 保留文件大綱結構(HTML 內容模型不允許 heading 巢狀於 button,故隱形 Pressable 與語意標籤分離,對齊 [read-view.tsx](https://github.com/pioug/atlassian-frontend-mirror/blob/main/design-system/inline-edit/src/internal/read-view.tsx) hidden-Pressable pattern)。
151
+ - view 態隱形 Pressable = `<button type="button">`,帶 `aria-label`(預設「編輯 {label}」)——螢幕閱讀器朗讀「可編輯」意圖。**僅 editable 時渲染**;`editable=false` 無 Pressable = 純 view(非 disabled,不進 tab order 也不灰化)。
152
+ - view 態語意標籤(`as`,預設 `span`;標題傳 `h1`/`h2`)獨立於 button 之外——`as="h1"` 保留文件大綱結構(HTML 內容模型不允許 heading 巢狀於 button,故隱形 Pressable 與語意標籤分離,對齊 [read-view.tsx](https://github.com/pioug/atlassian-frontend-mirror/blob/main/design-system/inline-edit/src/internal/read-view.tsx) hidden-Pressable pattern)。
132
153
  - edit 態控件帶 `aria-label`(= `label`)。
133
154
 
134
155
  **Keyboard**:
135
- | 鍵 | read 態 | edit 態 |
156
+ | 鍵 | view 態 | edit 態 |
136
157
  |----|---------|---------|
137
- | `Tab` | focus read 按鈕(外框染 `border-primary` 藍框,**非** ring;對齊本 spec 其餘段) | 依控件內部 focus 行為 |
138
- | `Enter` / `Space` | 進 edit | `Enter` commit |
139
- | `Esc` | — | cancel(還原不 commit) |
158
+ | `Tab` | focus view 按鈕(僅 editable;外框染 `border-primary` 藍框,**非** ring;對齊本 spec 其餘段) | 依控件內部 focus 行為 |
159
+ | `Enter` / `Space` | 進 edit | `Enter` commit(焦點回 view 按鈕) |
160
+ | `Esc` | — | cancel(還原不 commit,焦點回 view 按鈕) |
161
+
162
+ **Focus 管理(分流)**:進 edit 態自動 focus 控件(`autoFocus`);**鍵盤結算**(Enter commit / Esc cancel)後焦點送回 view 按鈕 + focus-visible 藍框(焦點不遺失);**滑鼠 blur 結算刻意不回焦**——回純 view 不搶焦(理由見「退出 edit 態(focus 分流)」段)。
140
163
 
141
- **Focus 管理**:進 edit 態自動 focus 控件(`autoFocus`);commit/cancel 結算後 focus 返回 read 按鈕(焦點不遺失)。
164
+ ## shadcn passthrough 例外說明
165
+ InlineEdit 是 **composite 編排層**(view 對齊盒 + 隱形 Pressable + 動態生成的 edit 控件),遵循 shadcn 結構 idiom(forwardRef + displayName)但**無 `...props` spread**:外層 div 只是對齊盒、非 consumer 可預期的互動 surface(spread 目標不明——view 按鈕?edit 控件?),DOM 級屬性應透過 `renderEdit` / `renderRead` 下傳給真正的控件;`ref` / `className` 附著於外層對齊盒。`asChild` 不支援(composite 非 Slot-compat,Slot 慣例見 https://www.radix-ui.com/primitives/docs/utilities/slot)。
142
166
 
143
167
  ## 禁止事項
144
168
 
145
- - ❌ read 態 hover 用邊框——用 `bg-neutral-hover` 背景 tint(邊框是 edit 態語言)
169
+ - ❌ view 態 hover 用邊框——用 `bg-neutral-hover` 背景 tint(邊框是 edit 態語言)
146
170
  - ❌ edit 態手刻 input chrome——用 Field 控件(預設 `<Input>`)
147
171
  - ❌ 用 InlineEdit 包 DataTable 密集可編輯格——用 naked cell
148
172
  - ❌ 空值顯示唯讀展示的半形 `-` dash——InlineEdit 是可編輯欄位,空值 = placeholder 或空白
149
- - ❌ read 態語意標籤用 heading 時把它塞進 button——用隱形 Pressable 疊加,保留文件大綱
173
+ - ❌ view 態語意標籤用 heading 時把它塞進 button——用隱形 Pressable 疊加,保留文件大綱
174
+ - ❌ 用 disabled / 灰化表達鎖定——就地編輯無 disabled 態,鎖定 = `editable=false` 純 view 無入口、不灰化(見「editable 閘」)
@@ -69,7 +69,7 @@ export const MetadataFields: Story = {
69
69
  ),
70
70
  }
71
71
 
72
- /* ── 三態 matrix:read / hover / editplay 驅動 hover + edit 態顯示)── */
72
+ /* ── 狀態 matrix:view / hover / edit(二態 canonical,hover 是 view 子態;play 驅動 hover + edit 態顯示)── */
73
73
  function StateRow({
74
74
  caption,
75
75
  children,
@@ -77,22 +77,23 @@ function StateRow({
77
77
  }: { caption: string; children: React.ReactNode } & React.HTMLAttributes<HTMLDivElement>) {
78
78
  return (
79
79
  <div {...rest}>
80
- <p className="mb-2 text-caption text-fg-muted">{caption}</p>
81
- {/* px-field-px 抵銷 InlineEdit 的 -mx-field-px,讓文字左緣與說明對齊 */}
82
- <div className="px-[var(--field-px)]">{children}</div>
80
+ {/* standalone(無 Field)的 InlineEdit 不帶 -mx(w-full),view 值自帶 px-field-px 內距
81
+ (fieldViewGeometry);caption 同步 px-field-px 讓說明與值文字左緣對齊 */}
82
+ <p className="mb-2 px-[var(--field-px)] text-caption text-fg-muted">{caption}</p>
83
+ {children}
83
84
  </div>
84
85
  )
85
86
  }
86
87
 
87
88
  export const States: Story = {
88
- name: '三態:閱讀/懸停/編輯',
89
+ name: '狀態:檢視/懸停/編輯',
89
90
  render: () => (
90
91
  <div className="flex w-[440px] flex-col gap-6">
91
- <StateRow caption="read(靜態)—— 純文字,透明邊框(預留、不可見)、無底色">
92
+ <StateRow caption="view(靜止)—— 純值,透明邊框(預留、不可見)、無底色">
92
93
  <StatefulInlineEdit value="Design review notes" label="標題" />
93
94
  </StateRow>
94
95
  <StateRow
95
- caption="read + hover —— 灰底 bg-neutral-hover + rounded-md(非邊框)"
96
+ caption="view + hover —— 灰底 bg-neutral-hover + rounded-md(非邊框)"
96
97
  data-testid="inline-edit-hover-row"
97
98
  >
98
99
  <StatefulInlineEdit value="Design review notes" label="標題" />
@@ -120,10 +121,10 @@ export const States: Story = {
120
121
  },
121
122
  }
122
123
 
123
- /* ── 真實業務:工作項目 Status 就地編輯(型別化 read = Tag)──
124
- SSOT 示範:read 態的 Tag **不是** InlineEdit 自刻,而是渲染 `<Select mode="display" display="tag">`
125
- —— 「select 值 → Tag」的格式化 SSOT 住在 Select 的 display mode(= DataTable 格子消費的同一份)。
126
- read 與 edit 用同一個 Select、只切 mode,格式零分歧。 */
124
+ /* ── 真實業務:工作項目 Status 就地編輯(型別化 view = Tag)──
125
+ SSOT 示範:view 態的 Tag **不是** InlineEdit 自刻,而是渲染 `<Select mode="view" display="tag">`
126
+ —— 「select 值 → Tag」的格式化 SSOT 住在 Select 的 view mode(= DataTable 格子消費的同一份)。
127
+ view 與 edit 用同一個 Select、只切 mode,格式零分歧。 */
127
128
  const STATUS_OPTIONS = [
128
129
  { value: 'todo', label: 'To Do', tagVariant: 'neutral' },
129
130
  { value: 'in-progress', label: 'In Progress', tagVariant: 'blue' },
@@ -131,8 +132,8 @@ const STATUS_OPTIONS = [
131
132
  { value: 'done', label: 'Done', tagVariant: 'green' },
132
133
  ]
133
134
 
134
- // 架構理由(移出 story name,per story-rules.md name 必人話):read 態顯示為 Tag,SSOT = Select
135
- // 的 display mode —— InlineEdit 委派 Select 控件,read view 消費 Select 的 display 呈現(標籤/色點)。
135
+ // 架構理由(移出 story name,per story-rules.md name 必人話):view 態顯示為 Tag,SSOT = Select
136
+ // 的 view mode —— InlineEdit 委派 Select 控件,view 態消費 Select 的 view 呈現(標籤/色點)。
136
137
  export const SelectTagField: Story = {
137
138
  name: '狀態就地編輯(讀取時顯示為標籤)',
138
139
  render: () => {
@@ -145,13 +146,13 @@ export const SelectTagField: Story = {
145
146
  onCommit={setStatus}
146
147
  label="status"
147
148
  size="sm"
148
- // read:Select 的 display mode 輸出 Tag(格式化 SSOT 住在 Select,InlineEdit 不自刻)。
149
+ // view:Select 的 view mode 輸出 Tag(格式化 SSOT 住在 Select,InlineEdit 不自刻)。
149
150
  // Tag **盒子左緣**對齊 label / 其他純文字值,tag 文字被 pill 內距自然縮排 —— 對齊 Meegle 實測
150
151
  // (PIL 量測 2026-07-10:Meegle pill box=label 左緣、text 縮排 ~8px@1x)。**不做 text outdent**。
151
152
  renderRead={(v) => (
152
- <Select mode="display" display="tag" size="sm" value={v} options={STATUS_OPTIONS} />
153
+ <Select mode="view" display="tag" size="sm" value={v} options={STATUS_OPTIONS} />
153
154
  )}
154
- // edit:同一個 Select 切 edit mode + 立即開選單;選完即 commit 回 read
155
+ // edit:同一個 Select 切 edit mode + 立即開選單;選完即 commit 回 view
155
156
  renderEdit={(p) => (
156
157
  <Select
157
158
  autoFocus
@@ -173,7 +174,7 @@ export const SelectTagField: Story = {
173
174
  }
174
175
 
175
176
  /* ── 真實業務:工作項目 Description 多行就地編輯(Jira description 類)──
176
- multiline:read 態換行顯示、edit 態預設 Textarea(Enter=換行、Cmd/Ctrl+Enter 或 blur 提交)。 */
177
+ multiline:view 態換行顯示、edit 態預設 Textarea(Enter=換行、Cmd/Ctrl+Enter 或 blur 提交)。 */
177
178
  export const MultilineDescription: Story = {
178
179
  name: '多行描述就地編輯(Jira 描述欄類)',
179
180
  render: () => {
@@ -198,7 +199,7 @@ export const MultilineDescription: Story = {
198
199
  /* ── 真實業務:工作項目詳情面板 —— vertical Field(FieldLabel + control)+ InlineEdit ──
199
200
  混合 plain / Tag / 多行欄位,驗證 Tag 值與 FieldLabel + 其他 plain 值的左緣視覺對齊。 */
200
201
  export const VerticalFieldForm: Story = {
201
- name: '詳情面板:垂直排列 Field + InlineEdit(Tag 對齊)',
202
+ name: '詳情面板:垂直欄位就地編輯',
202
203
  render: () => {
203
204
  const [status, setStatus] = React.useState('in-progress')
204
205
  const [desc, setDesc] = React.useState('Safari 上點擊結帳時,e2e 測試會間歇性失敗。')
@@ -218,7 +219,7 @@ export const VerticalFieldForm: Story = {
218
219
  onCommit={setStatus}
219
220
  label="status"
220
221
  renderRead={(v) => (
221
- <Select mode="display" display="tag" size="sm" value={v} options={STATUS_OPTIONS} />
222
+ <Select mode="view" display="tag" size="sm" value={v} options={STATUS_OPTIONS} />
222
223
  )}
223
224
  renderEdit={(p) => (
224
225
  <Select