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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (622) hide show
  1. package/CLAUDE.md +8 -8
  2. package/README.md +3 -2
  3. package/dist/components/Alert/alert.js.map +1 -1
  4. package/dist/components/AppShell/_demo-helpers.d.ts.map +1 -1
  5. package/dist/components/AppShell/app-shell.d.ts +1 -1
  6. package/dist/components/AppShell/app-shell.d.ts.map +1 -1
  7. package/dist/components/AppShell/app-shell.js +22 -2
  8. package/dist/components/AppShell/app-shell.js.map +1 -1
  9. package/dist/components/Avatar/avatar.d.ts.map +1 -1
  10. package/dist/components/Avatar/avatar.js +4 -4
  11. package/dist/components/Avatar/avatar.js.map +1 -1
  12. package/dist/components/Breadcrumb/breadcrumb.d.ts.map +1 -1
  13. package/dist/components/Breadcrumb/breadcrumb.js +1 -1
  14. package/dist/components/Breadcrumb/breadcrumb.js.map +1 -1
  15. package/dist/components/Button/button.d.ts +2 -2
  16. package/dist/components/Button/button.d.ts.map +1 -1
  17. package/dist/components/Button/button.js +12 -3
  18. package/dist/components/Button/button.js.map +1 -1
  19. package/dist/components/Calendar/calendar.d.ts +13 -5
  20. package/dist/components/Calendar/calendar.d.ts.map +1 -1
  21. package/dist/components/Calendar/calendar.js +7 -1
  22. package/dist/components/Calendar/calendar.js.map +1 -1
  23. package/dist/components/Carousel/carousel.d.ts.map +1 -1
  24. package/dist/components/Carousel/carousel.js +8 -3
  25. package/dist/components/Carousel/carousel.js.map +1 -1
  26. package/dist/components/Chart/chart.d.ts.map +1 -1
  27. package/dist/components/Chart/chart.js +1 -0
  28. package/dist/components/Chart/chart.js.map +1 -1
  29. package/dist/components/Checkbox/checkbox.d.ts +1 -1
  30. package/dist/components/Checkbox/checkbox.d.ts.map +1 -1
  31. package/dist/components/Checkbox/checkbox.js +8 -4
  32. package/dist/components/Checkbox/checkbox.js.map +1 -1
  33. package/dist/components/Chip/chip.d.ts +1 -1
  34. package/dist/components/Chip/chip.d.ts.map +1 -1
  35. package/dist/components/Chip/chip.js.map +1 -1
  36. package/dist/components/Coachmark/coachmark.d.ts +10 -3
  37. package/dist/components/Coachmark/coachmark.d.ts.map +1 -1
  38. package/dist/components/Coachmark/coachmark.js.map +1 -1
  39. package/dist/components/Combobox/combobox.d.ts +5 -1
  40. package/dist/components/Combobox/combobox.d.ts.map +1 -1
  41. package/dist/components/Combobox/combobox.js +30 -13
  42. package/dist/components/Combobox/combobox.js.map +1 -1
  43. package/dist/components/DataTable/cell-registry.d.ts.map +1 -1
  44. package/dist/components/DataTable/cell-registry.js +53 -62
  45. package/dist/components/DataTable/cell-registry.js.map +1 -1
  46. package/dist/components/DataTable/column-types.d.ts +10 -4
  47. package/dist/components/DataTable/column-types.d.ts.map +1 -1
  48. package/dist/components/DataTable/column-types.js.map +1 -1
  49. package/dist/components/DataTable/data-table-column-visibility-panel.d.ts +1 -1
  50. package/dist/components/DataTable/data-table-column-visibility-panel.js +183 -0
  51. package/dist/components/DataTable/data-table-column-visibility-panel.js.map +1 -0
  52. package/dist/components/DataTable/data-table-filter-group.d.ts +47 -0
  53. package/dist/components/DataTable/data-table-filter-group.d.ts.map +1 -0
  54. package/dist/components/DataTable/data-table-filter-group.js +154 -0
  55. package/dist/components/DataTable/data-table-filter-group.js.map +1 -0
  56. package/dist/components/DataTable/data-table-filter-panel.d.ts.map +1 -1
  57. package/dist/components/DataTable/data-table-filter-panel.js +285 -0
  58. package/dist/components/DataTable/data-table-filter-panel.js.map +1 -0
  59. package/dist/components/DataTable/data-table-filter-value-picker.d.ts +29 -0
  60. package/dist/components/DataTable/data-table-filter-value-picker.d.ts.map +1 -0
  61. package/dist/components/DataTable/data-table-filter-value-picker.js +187 -0
  62. package/dist/components/DataTable/data-table-filter-value-picker.js.map +1 -0
  63. package/dist/components/DataTable/data-table-interaction-layer.d.ts +11 -15
  64. package/dist/components/DataTable/data-table-interaction-layer.d.ts.map +1 -1
  65. package/dist/components/DataTable/data-table-interaction-layer.js +1 -3
  66. package/dist/components/DataTable/data-table-interaction-layer.js.map +1 -1
  67. package/dist/components/DataTable/data-table-sort-manager.js +140 -0
  68. package/dist/components/DataTable/data-table-sort-manager.js.map +1 -0
  69. package/dist/components/DataTable/data-table.d.ts +5 -6
  70. package/dist/components/DataTable/data-table.d.ts.map +1 -1
  71. package/dist/components/DataTable/data-table.js +36 -15
  72. package/dist/components/DataTable/data-table.js.map +1 -1
  73. package/dist/components/DataTable/filter-operators.js +140 -0
  74. package/dist/components/DataTable/filter-operators.js.map +1 -0
  75. package/dist/components/DataTable/filter-tree.js +312 -0
  76. package/dist/components/DataTable/filter-tree.js.map +1 -0
  77. package/dist/components/DataTable/index.d.ts +3 -0
  78. package/dist/components/DataTable/index.d.ts.map +1 -1
  79. package/dist/components/DataTable/index.js +12 -1
  80. package/dist/components/DataTable/index.js.map +1 -1
  81. package/dist/components/DataTable/lib/column-meta.js +25 -0
  82. package/dist/components/DataTable/lib/column-meta.js.map +1 -0
  83. package/dist/components/DateGrid/date-grid.d.ts.map +1 -1
  84. package/dist/components/DateGrid/date-grid.js +3 -1
  85. package/dist/components/DateGrid/date-grid.js.map +1 -1
  86. package/dist/components/DatePicker/date-picker.d.ts +7 -1
  87. package/dist/components/DatePicker/date-picker.d.ts.map +1 -1
  88. package/dist/components/DatePicker/date-picker.js +105 -74
  89. package/dist/components/DatePicker/date-picker.js.map +1 -1
  90. package/dist/components/Dialog/dialog.d.ts +20 -3
  91. package/dist/components/Dialog/dialog.d.ts.map +1 -1
  92. package/dist/components/Dialog/dialog.js +38 -20
  93. package/dist/components/Dialog/dialog.js.map +1 -1
  94. package/dist/components/DropdownMenu/dropdown-menu.d.ts.map +1 -1
  95. package/dist/components/DropdownMenu/dropdown-menu.js +3 -1
  96. package/dist/components/DropdownMenu/dropdown-menu.js.map +1 -1
  97. package/dist/components/Field/field-context.d.ts +45 -5
  98. package/dist/components/Field/field-context.d.ts.map +1 -1
  99. package/dist/components/Field/field-context.js +19 -0
  100. package/dist/components/Field/field-context.js.map +1 -1
  101. package/dist/components/Field/field-edit-keys.d.ts +21 -0
  102. package/dist/components/Field/field-edit-keys.d.ts.map +1 -0
  103. package/dist/components/Field/field-edit-keys.js +19 -0
  104. package/dist/components/Field/field-edit-keys.js.map +1 -0
  105. package/dist/components/Field/field-types.d.ts +20 -1
  106. package/dist/components/Field/field-types.d.ts.map +1 -1
  107. package/dist/components/Field/field-types.js.map +1 -1
  108. package/dist/components/Field/field-wrapper.d.ts +3 -2
  109. package/dist/components/Field/field-wrapper.d.ts.map +1 -1
  110. package/dist/components/Field/field-wrapper.js +38 -39
  111. package/dist/components/Field/field-wrapper.js.map +1 -1
  112. package/dist/components/Field/field.d.ts +2 -1
  113. package/dist/components/Field/field.d.ts.map +1 -1
  114. package/dist/components/Field/field.js.map +1 -1
  115. package/dist/components/Field/index.d.ts +1 -0
  116. package/dist/components/Field/index.d.ts.map +1 -1
  117. package/dist/components/Field/index.js +2 -0
  118. package/dist/components/Field/index.js.map +1 -1
  119. package/dist/components/FieldControlGroup/field-control-group.d.ts +1 -1
  120. package/dist/components/FieldControlGroup/field-control-group.js.map +1 -1
  121. package/dist/components/FileItem/file-item.d.ts.map +1 -1
  122. package/dist/components/FileItem/file-item.js +1 -1
  123. package/dist/components/FileItem/file-item.js.map +1 -1
  124. package/dist/components/FileUpload/file-upload.d.ts +1 -1
  125. package/dist/components/FileUpload/file-upload.js.map +1 -1
  126. package/dist/components/FileViewer/file-viewer.d.ts.map +1 -1
  127. package/dist/components/FileViewer/file-viewer.js +7 -2
  128. package/dist/components/FileViewer/file-viewer.js.map +1 -1
  129. package/dist/components/FileViewer/image-renderer.d.ts.map +1 -1
  130. package/dist/components/FileViewer/image-renderer.js +22 -1
  131. package/dist/components/FileViewer/image-renderer.js.map +1 -1
  132. package/dist/components/HoverCard/hover-card.d.ts.map +1 -1
  133. package/dist/components/HoverCard/hover-card.js +5 -3
  134. package/dist/components/HoverCard/hover-card.js.map +1 -1
  135. package/dist/components/InlineEdit/index.d.ts +2 -0
  136. package/dist/components/InlineEdit/index.d.ts.map +1 -0
  137. package/dist/components/InlineEdit/index.js +5 -0
  138. package/dist/components/InlineEdit/index.js.map +1 -0
  139. package/dist/components/InlineEdit/inline-edit.d.ts +59 -0
  140. package/dist/components/InlineEdit/inline-edit.d.ts.map +1 -0
  141. package/dist/components/InlineEdit/inline-edit.js +164 -0
  142. package/dist/components/InlineEdit/inline-edit.js.map +1 -0
  143. package/dist/components/Input/input.d.ts +6 -11
  144. package/dist/components/Input/input.d.ts.map +1 -1
  145. package/dist/components/Input/input.js +43 -13
  146. package/dist/components/Input/input.js.map +1 -1
  147. package/dist/components/LinkInput/link-input.d.ts +2 -2
  148. package/dist/components/LinkInput/link-input.d.ts.map +1 -1
  149. package/dist/components/LinkInput/link-input.js +6 -5
  150. package/dist/components/LinkInput/link-input.js.map +1 -1
  151. package/dist/components/Menu/menu-item.d.ts +14 -2
  152. package/dist/components/Menu/menu-item.d.ts.map +1 -1
  153. package/dist/components/Menu/menu-item.js +25 -3
  154. package/dist/components/Menu/menu-item.js.map +1 -1
  155. package/dist/components/NumberInput/number-input.d.ts +3 -3
  156. package/dist/components/NumberInput/number-input.d.ts.map +1 -1
  157. package/dist/components/NumberInput/number-input.js +6 -5
  158. package/dist/components/NumberInput/number-input.js.map +1 -1
  159. package/dist/components/OverflowIndicator/overflow-indicator.d.ts.map +1 -1
  160. package/dist/components/OverflowIndicator/overflow-indicator.js +2 -6
  161. package/dist/components/OverflowIndicator/overflow-indicator.js.map +1 -1
  162. package/dist/components/Pagination/pagination.js +1 -1
  163. package/dist/components/Pagination/pagination.js.map +1 -1
  164. package/dist/components/PeoplePicker/index.js +7 -1
  165. package/dist/components/PeoplePicker/index.js.map +1 -1
  166. package/dist/components/PeoplePicker/people-picker.d.ts +9 -2
  167. package/dist/components/PeoplePicker/people-picker.d.ts.map +1 -1
  168. package/dist/components/PeoplePicker/people-picker.js +23 -10
  169. package/dist/components/PeoplePicker/people-picker.js.map +1 -1
  170. package/dist/components/PeoplePicker/person-display.d.ts +6 -3
  171. package/dist/components/PeoplePicker/person-display.d.ts.map +1 -1
  172. package/dist/components/PeoplePicker/person-display.js +19 -14
  173. package/dist/components/PeoplePicker/person-display.js.map +1 -1
  174. package/dist/components/Popover/popover.d.ts.map +1 -1
  175. package/dist/components/Popover/popover.js +3 -1
  176. package/dist/components/Popover/popover.js.map +1 -1
  177. package/dist/components/ProfileCard/profile-card.d.ts +2 -2
  178. package/dist/components/ProfileCard/profile-card.js +3 -3
  179. package/dist/components/ProfileCard/profile-card.js.map +1 -1
  180. package/dist/components/RadioGroup/radio-group.d.ts.map +1 -1
  181. package/dist/components/RadioGroup/radio-group.js +15 -9
  182. package/dist/components/RadioGroup/radio-group.js.map +1 -1
  183. package/dist/components/ScrollArea/scroll-area.d.ts +11 -1
  184. package/dist/components/ScrollArea/scroll-area.d.ts.map +1 -1
  185. package/dist/components/ScrollArea/scroll-area.js +6 -3
  186. package/dist/components/ScrollArea/scroll-area.js.map +1 -1
  187. package/dist/components/Select/select.d.ts +46 -5
  188. package/dist/components/Select/select.d.ts.map +1 -1
  189. package/dist/components/Select/select.js +109 -28
  190. package/dist/components/Select/select.js.map +1 -1
  191. package/dist/components/SelectMenu/select-menu.d.ts +3 -1
  192. package/dist/components/SelectMenu/select-menu.d.ts.map +1 -1
  193. package/dist/components/SelectMenu/select-menu.js +8 -1
  194. package/dist/components/SelectMenu/select-menu.js.map +1 -1
  195. package/dist/components/SelectionControl/boolean-value.d.ts +11 -0
  196. package/dist/components/SelectionControl/boolean-value.d.ts.map +1 -0
  197. package/dist/components/SelectionControl/boolean-value.js +23 -0
  198. package/dist/components/SelectionControl/boolean-value.js.map +1 -0
  199. package/dist/components/SelectionControl/index.d.ts +1 -0
  200. package/dist/components/SelectionControl/index.d.ts.map +1 -1
  201. package/dist/components/SelectionControl/index.js +2 -0
  202. package/dist/components/SelectionControl/index.js.map +1 -1
  203. package/dist/components/Sheet/index.js +2 -1
  204. package/dist/components/Sheet/sheet.d.ts +7 -1
  205. package/dist/components/Sheet/sheet.d.ts.map +1 -1
  206. package/dist/components/Sheet/sheet.js +5 -2
  207. package/dist/components/Sheet/sheet.js.map +1 -1
  208. package/dist/components/Sidebar/sidebar.d.ts +9 -4
  209. package/dist/components/Sidebar/sidebar.d.ts.map +1 -1
  210. package/dist/components/Sidebar/sidebar.js +8 -10
  211. package/dist/components/Sidebar/sidebar.js.map +1 -1
  212. package/dist/components/Slider/slider.d.ts.map +1 -1
  213. package/dist/components/Slider/slider.js.map +1 -1
  214. package/dist/components/Switch/switch.d.ts +6 -6
  215. package/dist/components/Switch/switch.d.ts.map +1 -1
  216. package/dist/components/Switch/switch.js +3 -2
  217. package/dist/components/Switch/switch.js.map +1 -1
  218. package/dist/components/Tabs/tabs.d.ts.map +1 -1
  219. package/dist/components/Tabs/tabs.js +15 -1
  220. package/dist/components/Tabs/tabs.js.map +1 -1
  221. package/dist/components/Textarea/textarea.d.ts +2 -2
  222. package/dist/components/Textarea/textarea.d.ts.map +1 -1
  223. package/dist/components/Textarea/textarea.js +50 -42
  224. package/dist/components/Textarea/textarea.js.map +1 -1
  225. package/dist/components/TimePicker/time-columns.d.ts.map +1 -1
  226. package/dist/components/TimePicker/time-columns.js +42 -38
  227. package/dist/components/TimePicker/time-columns.js.map +1 -1
  228. package/dist/components/TimePicker/time-picker.d.ts.map +1 -1
  229. package/dist/components/TimePicker/time-picker.js +8 -6
  230. package/dist/components/TimePicker/time-picker.js.map +1 -1
  231. package/dist/components/Tooltip/tooltip.d.ts.map +1 -1
  232. package/dist/components/Tooltip/tooltip.js +7 -5
  233. package/dist/components/Tooltip/tooltip.js.map +1 -1
  234. package/dist/components/TreeView/tree-view.d.ts +56 -1
  235. package/dist/components/TreeView/tree-view.d.ts.map +1 -1
  236. package/dist/components/TreeView/tree-view.js +211 -12
  237. package/dist/components/TreeView/tree-view.js.map +1 -1
  238. package/dist/index.d.ts +115 -58
  239. package/dist/index.d.ts.map +1 -1
  240. package/dist/index.js +95 -108
  241. package/dist/index.js.map +1 -1
  242. package/dist/patterns/element-anatomy/item-anatomy.d.ts +7 -6
  243. package/dist/patterns/element-anatomy/item-anatomy.d.ts.map +1 -1
  244. package/dist/patterns/element-anatomy/item-anatomy.js.map +1 -1
  245. package/dist/tokens/categorical-color.js +15 -0
  246. package/dist/tokens/categorical-color.js.map +1 -1
  247. package/dist/tokens/motion/motion.d.ts +7 -6
  248. package/dist/tokens/motion/motion.d.ts.map +1 -1
  249. package/dist/tokens/motion/motion.js +6 -6
  250. package/dist/tokens/motion/motion.js.map +1 -1
  251. package/dist/tokens/motion/overlay-motion.d.ts +19 -0
  252. package/dist/tokens/motion/overlay-motion.d.ts.map +1 -0
  253. package/dist/tokens/motion/overlay-motion.js +9 -0
  254. package/dist/tokens/motion/overlay-motion.js.map +1 -0
  255. package/dist/tokens/uiSize/icon-size.js +5 -0
  256. package/dist/tokens/uiSize/icon-size.js.map +1 -0
  257. package/ds-canonical/fork/governance.lock +31 -19
  258. package/ds-canonical/fork/hooks/_log-fire.sh +46 -0
  259. package/ds-canonical/fork/hooks/block_prototype_imports.py +15 -4
  260. package/ds-canonical/fork/hooks/check_consumer_app_invariants.sh +145 -13
  261. package/ds-canonical/fork/hooks/check_ds_anchor_preflight.sh +2 -1
  262. package/ds-canonical/fork/hooks/check_dynamic_tailwind_class.sh +47 -0
  263. package/ds-canonical/fork/hooks/check_escape_marker_abuse.sh +27 -0
  264. package/ds-canonical/fork/hooks/check_field_family_invariants.sh +61 -1
  265. package/ds-canonical/fork/hooks/check_layout_space_magic_numbers.sh +28 -4
  266. package/ds-canonical/fork/hooks/check_opacity_token_usage.sh +28 -0
  267. package/ds-canonical/fork/hooks/check_story_determinism.sh +2 -0
  268. package/ds-canonical/fork/hooks/check_story_invariants.sh +119 -0
  269. package/ds-canonical/fork/hooks/check_tabs_content_chrome_body_double_gap.sh +29 -0
  270. package/ds-canonical/fork/manifest.json +15 -0
  271. package/ds-canonical/fork/preamble.md +20 -18
  272. package/ds-canonical/fork/skills/bug-fix-rhythm/SKILL.md +3 -3
  273. package/ds-canonical/fork/skills/code-quality-audit/SKILL.md +3 -3
  274. package/ds-canonical/fork/skills/delivery-handoff/SKILL.md +1 -1
  275. package/ds-canonical/fork/skills/product-ui-audit/SKILL.md +3 -3
  276. package/ds-canonical/fork/skills/propose-options/SKILL.md +3 -3
  277. package/ds-canonical/fork/skills/prototype/SKILL.md +1 -1
  278. package/ds-canonical/fork/skills/scan-similar-bugs/SKILL.md +2 -2
  279. package/ds-canonical/fork/skills/visual-audit/SKILL.md +2 -2
  280. package/ds-canonical/hooks/README.md +1 -1
  281. package/ds-canonical/hooks/block_prototype_imports.py +15 -4
  282. package/ds-canonical/hooks/check_audit_post_report_validator.sh +65 -7
  283. package/ds-canonical/hooks/check_codex_brief_invariants.sh +28 -1
  284. package/ds-canonical/hooks/check_consumer_app_invariants.sh +145 -13
  285. package/ds-canonical/hooks/check_datatable_invariants.sh +6 -3
  286. package/ds-canonical/hooks/check_ds_anchor_preflight.sh +2 -1
  287. package/ds-canonical/hooks/check_dynamic_tailwind_class.sh +47 -0
  288. package/ds-canonical/hooks/check_escape_marker_abuse.sh +27 -0
  289. package/ds-canonical/hooks/check_field_family_invariants.sh +61 -1
  290. package/ds-canonical/hooks/check_layout_space_magic_numbers.sh +28 -4
  291. package/ds-canonical/hooks/check_naming_and_abstraction.sh +1 -1
  292. package/ds-canonical/hooks/check_opacity_token_usage.sh +28 -0
  293. package/ds-canonical/hooks/check_pixel_quantified_audit.sh +3 -2
  294. package/ds-canonical/hooks/check_post_main_ssot_propagate.sh +6 -1
  295. package/ds-canonical/hooks/check_ssot_header_declaration.sh +3 -1
  296. package/ds-canonical/hooks/check_story_determinism.sh +2 -0
  297. package/ds-canonical/hooks/check_substantive_edit_approval_preflight.sh +8 -7
  298. package/ds-canonical/hooks/check_tabs_content_chrome_body_double_gap.sh +29 -0
  299. package/ds-canonical/hooks/lib/_story_compile_drift.sh +1 -1
  300. package/ds-canonical/hooks/lib/_token_hygiene.sh +10 -1
  301. package/ds-canonical/hooks/stop_self_audit.sh +31 -0
  302. package/ds-canonical/hooks/tests/test_block_prototype_imports.sh +24 -0
  303. package/ds-canonical/hooks/tests/test_check_audit_post_report_validator.sh +44 -0
  304. package/ds-canonical/hooks/tests/test_check_codex_brief_invariants.sh +43 -2
  305. package/ds-canonical/hooks/tests/test_check_consumer_ds_primitive_misuse.sh +112 -0
  306. package/ds-canonical/hooks/tests/test_check_consumer_story_baseline.sh +9 -2
  307. package/ds-canonical/hooks/tests/test_check_dynamic_tailwind_class.sh +76 -0
  308. package/ds-canonical/hooks/tests/test_check_escape_marker_abuse.sh +24 -0
  309. package/ds-canonical/hooks/tests/test_check_field_family_invariants.sh +27 -2
  310. package/ds-canonical/hooks/tests/test_check_opacity_token_usage.sh +39 -0
  311. package/ds-canonical/hooks/tests/test_check_tabs_content_chrome_body_double_gap.sh +76 -0
  312. package/ds-canonical/references/audit-coverage-vs-24-checklist.md +1 -1
  313. package/ds-canonical/references/build-ui-canonicals.md +2 -1
  314. package/ds-canonical/references/failure-class-registry.json +145 -0
  315. package/ds-canonical/references/governance-audit-coverage.md +33 -0
  316. package/ds-canonical/references/item-anatomy-recipe.md +2 -2
  317. package/ds-canonical/references/scenario-definition.md +6 -9
  318. package/ds-canonical/references/ssot-consultation.md +2 -2
  319. package/ds-canonical/references/ssot-index.md +5 -4
  320. package/ds-canonical/references/story-baseline-registry.json +8 -2
  321. package/ds-canonical/references/structural-token-retention.md +6 -9
  322. package/ds-canonical/rules/meta-patterns.md +8 -8
  323. package/ds-canonical/rules/self-verify.md +4 -3
  324. package/ds-canonical/rules/story-rules.md +2 -2
  325. package/ds-canonical/rules/ui-development.md +2 -2
  326. package/ds-canonical/skills/bug-fix-rhythm/SKILL.md +3 -3
  327. package/ds-canonical/skills/code-quality-audit/SKILL.md +3 -3
  328. package/ds-canonical/skills/codex-collab/SKILL.md +9 -7
  329. package/ds-canonical/skills/codex-collab/references/brief-template.md +1 -1
  330. package/ds-canonical/skills/codify-principle/SKILL.md +2 -2
  331. package/ds-canonical/skills/component-quality-gate/SKILL.md +1 -1
  332. package/ds-canonical/skills/component-quality-gate/references/checklist.md +2 -2
  333. package/ds-canonical/skills/deep-audit-cross-codex/SKILL.md +51 -58
  334. package/ds-canonical/skills/deep-audit-cross-codex/references/phase-a-workflow.md +29 -0
  335. package/ds-canonical/skills/deep-audit-cross-codex/references/phase-b-codex-brief.md +29 -10
  336. package/ds-canonical/skills/deep-audit-cross-codex/references/triage-rubric.md +21 -0
  337. package/ds-canonical/skills/delivery-handoff/SKILL.md +1 -1
  338. package/ds-canonical/skills/design-system-audit/SKILL.md +26 -25
  339. package/ds-canonical/skills/design-system-audit/references/audit-prompts.md +13 -1
  340. package/ds-canonical/skills/design-system-audit/references/rule-placement.md +1 -1
  341. package/ds-canonical/skills/governance-health/SKILL.md +1 -1
  342. package/ds-canonical/skills/knowledge-prune/SKILL.md +3 -0
  343. package/ds-canonical/skills/knowledge-prune/references/prune-dimensions-d5-d10.md +1 -0
  344. package/ds-canonical/skills/new-component/SKILL.md +5 -5
  345. package/ds-canonical/skills/new-component/references/new-component-checklist.md +1 -1
  346. package/ds-canonical/skills/product-ui-audit/SKILL.md +3 -3
  347. package/ds-canonical/skills/propose-options/SKILL.md +3 -3
  348. package/ds-canonical/skills/prototype/SKILL.md +1 -1
  349. package/ds-canonical/skills/scan-similar-bugs/SKILL.md +2 -2
  350. package/ds-canonical/skills/story-auto-compile-migrate/SKILL.md +3 -3
  351. package/ds-canonical/skills/story-writing/SKILL.md +3 -3
  352. package/ds-canonical/skills/visual-audit/SKILL.md +2 -2
  353. package/ds-story-manifest.json +29 -20
  354. package/llms-full.txt +22 -1
  355. package/llms.txt +5 -4
  356. package/package.json +7 -3
  357. package/src/components/Accordion/accordion.principles.stories.tsx +1 -1
  358. package/src/components/Accordion/accordion.stories.tsx +16 -1
  359. package/src/components/Alert/alert.principles.stories.tsx +1 -1
  360. package/src/components/Alert/alert.spec.md +8 -10
  361. package/src/components/Alert/alert.stories.tsx +28 -28
  362. package/src/components/Alert/alert.tsx +1 -1
  363. package/src/components/AppShell/_demo-helpers.tsx +6 -3
  364. package/src/components/AppShell/app-shell.spec.md +4 -4
  365. package/src/components/AppShell/app-shell.tsx +28 -2
  366. package/src/components/AspectRatio/aspect-ratio.spec.md +1 -1
  367. package/src/components/AspectRatio/aspect-ratio.stories.tsx +7 -89
  368. package/src/components/Avatar/avatar.anatomy.stories.tsx +7 -3
  369. package/src/components/Avatar/avatar.principles.stories.tsx +19 -15
  370. package/src/components/Avatar/avatar.spec.md +2 -2
  371. package/src/components/Avatar/avatar.stories.tsx +8 -6
  372. package/src/components/Avatar/avatar.tsx +13 -5
  373. package/src/components/Badge/badge.principles.stories.tsx +1 -1
  374. package/src/components/Badge/badge.spec.md +2 -0
  375. package/src/components/Badge/badge.stories.tsx +4 -2
  376. package/src/components/Breadcrumb/breadcrumb.principles.stories.tsx +6 -4
  377. package/src/components/Breadcrumb/breadcrumb.stories.tsx +6 -5
  378. package/src/components/Breadcrumb/breadcrumb.tsx +3 -2
  379. package/src/components/BulkActionBar/bulk-action-bar.spec.md +4 -2
  380. package/src/components/Button/button.principles.stories.tsx +1 -1
  381. package/src/components/Button/button.spec.md +2 -2
  382. package/src/components/Button/button.tsx +18 -3
  383. package/src/components/Calendar/calendar.anatomy.stories.tsx +12 -10
  384. package/src/components/Calendar/calendar.principles.stories.tsx +14 -6
  385. package/src/components/Calendar/calendar.spec.md +5 -4
  386. package/src/components/Calendar/calendar.stories.tsx +7 -4
  387. package/src/components/Calendar/calendar.tsx +26 -7
  388. package/src/components/Carousel/carousel.anatomy.stories.tsx +5 -4
  389. package/src/components/Carousel/carousel.spec.md +2 -2
  390. package/src/components/Carousel/carousel.tsx +11 -3
  391. package/src/components/Chart/chart.anatomy.stories.tsx +7 -4
  392. package/src/components/Chart/chart.principles.stories.tsx +1 -1
  393. package/src/components/Chart/chart.stories.tsx +2 -8
  394. package/src/components/Chart/chart.tsx +3 -0
  395. package/src/components/Checkbox/checkbox.anatomy.stories.tsx +28 -49
  396. package/src/components/Checkbox/checkbox.principles.stories.tsx +1 -1
  397. package/src/components/Checkbox/checkbox.spec.md +13 -26
  398. package/src/components/Checkbox/checkbox.stories.tsx +11 -8
  399. package/src/components/Checkbox/checkbox.tsx +18 -13
  400. package/src/components/Chip/chip.anatomy.stories.tsx +44 -24
  401. package/src/components/Chip/chip.principles.stories.tsx +1 -1
  402. package/src/components/Chip/chip.spec.md +5 -4
  403. package/src/components/Chip/chip.tsx +5 -1
  404. package/src/components/CircularProgress/circular-progress.principles.stories.tsx +7 -4
  405. package/src/components/CircularProgress/circular-progress.spec.md +3 -3
  406. package/src/components/CircularProgress/circular-progress.stories.tsx +2 -2
  407. package/src/components/Coachmark/coachmark.anatomy.stories.tsx +2 -2
  408. package/src/components/Coachmark/coachmark.principles.stories.tsx +2 -1
  409. package/src/components/Coachmark/coachmark.spec.md +5 -5
  410. package/src/components/Coachmark/coachmark.stories.tsx +3 -1
  411. package/src/components/Coachmark/coachmark.tsx +10 -3
  412. package/src/components/Combobox/combobox.anatomy.stories.tsx +2 -2
  413. package/src/components/Combobox/combobox.tsx +53 -19
  414. package/src/components/Command/command.anatomy.stories.tsx +2 -2
  415. package/src/components/Command/command.principles.stories.tsx +1 -1
  416. package/src/components/Command/command.stories.tsx +42 -10
  417. package/src/components/DataTable/cell-registry.tsx +86 -63
  418. package/src/components/DataTable/column-types.ts +10 -1
  419. package/src/components/DataTable/data-table-column-visibility-panel.tsx +1 -1
  420. package/src/components/DataTable/data-table-filter-group.tsx +231 -0
  421. package/src/components/DataTable/data-table-filter-panel.tsx +16 -430
  422. package/src/components/DataTable/data-table-filter-value-picker.tsx +237 -0
  423. package/src/components/DataTable/data-table-interaction-layer.tsx +15 -19
  424. package/src/components/DataTable/data-table.anatomy.stories.tsx +15 -13
  425. package/src/components/DataTable/data-table.principles.stories.tsx +9 -9
  426. package/src/components/DataTable/data-table.spec.md +33 -13
  427. package/src/components/DataTable/data-table.stories.tsx +16 -17
  428. package/src/components/DataTable/data-table.tsx +107 -61
  429. package/src/components/DataTable/filter-operators.spec.md +3 -3
  430. package/src/components/DataTable/index.ts +3 -0
  431. package/src/components/DateGrid/date-grid.anatomy.stories.tsx +4 -4
  432. package/src/components/DateGrid/date-grid.principles.stories.tsx +1 -1
  433. package/src/components/DateGrid/date-grid.spec.md +1 -1
  434. package/src/components/DateGrid/date-grid.tsx +3 -1
  435. package/src/components/DatePicker/date-picker.anatomy.stories.tsx +8 -8
  436. package/src/components/DatePicker/date-picker.principles.stories.tsx +3 -3
  437. package/src/components/DatePicker/date-picker.spec.md +3 -3
  438. package/src/components/DatePicker/date-picker.stories.tsx +4 -5
  439. package/src/components/DatePicker/date-picker.tsx +99 -45
  440. package/src/components/DescriptionList/description-list.anatomy.stories.tsx +12 -10
  441. package/src/components/DescriptionList/description-list.principles.stories.tsx +2 -2
  442. package/src/components/DescriptionList/description-list.spec.md +3 -3
  443. package/src/components/DescriptionList/description-list.stories.tsx +7 -6
  444. package/src/components/Dialog/dialog.anatomy.stories.tsx +1 -0
  445. package/src/components/Dialog/dialog.spec.md +9 -0
  446. package/src/components/Dialog/dialog.stories.tsx +66 -5
  447. package/src/components/Dialog/dialog.tsx +60 -25
  448. package/src/components/DropdownMenu/dropdown-menu.anatomy.stories.tsx +1 -1
  449. package/src/components/DropdownMenu/dropdown-menu.principles.stories.tsx +8 -6
  450. package/src/components/DropdownMenu/dropdown-menu.spec.md +5 -5
  451. package/src/components/DropdownMenu/dropdown-menu.tsx +18 -9
  452. package/src/components/Empty/empty.anatomy.stories.tsx +1 -1
  453. package/src/components/Empty/empty.spec.md +2 -2
  454. package/src/components/Field/field-context.ts +69 -5
  455. package/src/components/Field/field-controls.spec.md +62 -8
  456. package/src/components/Field/field-edit-keys.ts +66 -0
  457. package/src/components/Field/field-types.ts +58 -9
  458. package/src/components/Field/field-wrapper.tsx +50 -42
  459. package/src/components/Field/field.anatomy.stories.tsx +9 -6
  460. package/src/components/Field/field.spec.md +1 -1
  461. package/src/components/Field/field.stories.tsx +1 -1
  462. package/src/components/Field/field.tsx +2 -1
  463. package/src/components/Field/form-validation.spec.md +3 -3
  464. package/src/components/Field/index.ts +1 -0
  465. package/src/components/FieldControlGroup/field-control-group.principles.stories.tsx +6 -6
  466. package/src/components/FieldControlGroup/field-control-group.spec.md +9 -4
  467. package/src/components/FieldControlGroup/field-control-group.tsx +1 -1
  468. package/src/components/FileItem/file-item.anatomy.stories.tsx +1 -1
  469. package/src/components/FileItem/file-item.spec.md +5 -8
  470. package/src/components/FileItem/file-item.stories.tsx +36 -25
  471. package/src/components/FileItem/file-item.tsx +8 -6
  472. package/src/components/FileUpload/file-upload.anatomy.stories.tsx +6 -6
  473. package/src/components/FileUpload/file-upload.principles.stories.tsx +5 -5
  474. package/src/components/FileUpload/file-upload.spec.md +1 -1
  475. package/src/components/FileUpload/file-upload.tsx +1 -1
  476. package/src/components/FileViewer/file-viewer.anatomy.stories.tsx +4 -3
  477. package/src/components/FileViewer/file-viewer.principles.stories.tsx +3 -3
  478. package/src/components/FileViewer/file-viewer.spec.md +30 -12
  479. package/src/components/FileViewer/file-viewer.stories.tsx +1 -2
  480. package/src/components/FileViewer/file-viewer.tsx +12 -4
  481. package/src/components/FileViewer/image-renderer.tsx +35 -7
  482. package/src/components/HoverCard/hover-card.anatomy.stories.tsx +6 -2
  483. package/src/components/HoverCard/hover-card.principles.stories.tsx +6 -3
  484. package/src/components/HoverCard/hover-card.spec.md +5 -5
  485. package/src/components/HoverCard/hover-card.stories.tsx +16 -16
  486. package/src/components/HoverCard/hover-card.tsx +5 -3
  487. package/src/components/InlineEdit/index.ts +5 -0
  488. package/src/components/InlineEdit/inline-edit.spec.md +149 -0
  489. package/src/components/InlineEdit/inline-edit.stories.tsx +246 -0
  490. package/src/components/InlineEdit/inline-edit.tsx +287 -0
  491. package/src/components/Input/input.anatomy.stories.tsx +8 -7
  492. package/src/components/Input/input.principles.stories.tsx +2 -12
  493. package/src/components/Input/input.spec.md +13 -18
  494. package/src/components/Input/input.tsx +69 -22
  495. package/src/components/LinkInput/link-input.anatomy.stories.tsx +1 -1
  496. package/src/components/LinkInput/link-input.principles.stories.tsx +2 -2
  497. package/src/components/LinkInput/link-input.tsx +11 -10
  498. package/src/components/Menu/menu-item.anatomy.stories.tsx +3 -3
  499. package/src/components/Menu/menu-item.principles.stories.tsx +1 -1
  500. package/src/components/Menu/menu-item.spec.md +22 -15
  501. package/src/components/Menu/menu-item.stories.tsx +39 -11
  502. package/src/components/Menu/menu-item.tsx +38 -4
  503. package/src/components/Notice/notice.principles.stories.tsx +1 -1
  504. package/src/components/Notice/notice.spec.md +16 -3
  505. package/src/components/NumberInput/number-input.anatomy.stories.tsx +3 -3
  506. package/src/components/NumberInput/number-input.principles.stories.tsx +1 -1
  507. package/src/components/NumberInput/number-input.spec.md +4 -4
  508. package/src/components/NumberInput/number-input.tsx +19 -12
  509. package/src/components/OverflowIndicator/overflow-indicator.anatomy.stories.tsx +10 -8
  510. package/src/components/OverflowIndicator/overflow-indicator.principles.stories.tsx +3 -3
  511. package/src/components/OverflowIndicator/overflow-indicator.spec.md +4 -2
  512. package/src/components/OverflowIndicator/overflow-indicator.stories.tsx +71 -63
  513. package/src/components/OverflowIndicator/overflow-indicator.tsx +9 -8
  514. package/src/components/Pagination/pagination.spec.md +1 -1
  515. package/src/components/Pagination/pagination.stories.tsx +2 -6
  516. package/src/components/Pagination/pagination.tsx +1 -1
  517. package/src/components/PeoplePicker/people-picker.anatomy.stories.tsx +4 -4
  518. package/src/components/PeoplePicker/people-picker.principles.stories.tsx +2 -2
  519. package/src/components/PeoplePicker/people-picker.spec.md +6 -6
  520. package/src/components/PeoplePicker/people-picker.tsx +56 -16
  521. package/src/components/PeoplePicker/person-display.tsx +32 -16
  522. package/src/components/Popover/popover.anatomy.stories.tsx +2 -2
  523. package/src/components/Popover/popover.tsx +3 -1
  524. package/src/components/ProfileCard/profile-card.anatomy.stories.tsx +12 -23
  525. package/src/components/ProfileCard/profile-card.spec.md +3 -3
  526. package/src/components/ProfileCard/profile-card.tsx +6 -6
  527. package/src/components/ProgressBar/progress-bar.anatomy.stories.tsx +3 -1
  528. package/src/components/ProgressBar/progress-bar.principles.stories.tsx +1 -1
  529. package/src/components/ProgressBar/progress-bar.stories.tsx +50 -42
  530. package/src/components/RadioGroup/radio-group.anatomy.stories.tsx +10 -28
  531. package/src/components/RadioGroup/radio-group.stories.tsx +29 -32
  532. package/src/components/RadioGroup/radio-group.tsx +41 -15
  533. package/src/components/Rating/rating.spec.md +11 -3
  534. package/src/components/ScrollArea/scroll-area.anatomy.stories.tsx +1 -1
  535. package/src/components/ScrollArea/scroll-area.principles.stories.tsx +1 -1
  536. package/src/components/ScrollArea/scroll-area.spec.md +7 -3
  537. package/src/components/ScrollArea/scroll-area.stories.tsx +1 -1
  538. package/src/components/ScrollArea/scroll-area.tsx +17 -4
  539. package/src/components/Select/select.anatomy.stories.tsx +3 -3
  540. package/src/components/Select/select.spec.md +29 -17
  541. package/src/components/Select/select.stories.tsx +33 -0
  542. package/src/components/Select/select.tsx +190 -36
  543. package/src/components/SelectMenu/select-menu.anatomy.stories.tsx +4 -4
  544. package/src/components/SelectMenu/select-menu.spec.md +15 -7
  545. package/src/components/SelectMenu/select-menu.tsx +16 -3
  546. package/src/components/SelectionControl/boolean-value.tsx +39 -0
  547. package/src/components/SelectionControl/index.ts +1 -0
  548. package/src/components/SelectionControl/selection-item.anatomy.stories.tsx +2 -2
  549. package/src/components/SelectionControl/selection-item.principles.stories.tsx +2 -2
  550. package/src/components/SelectionControl/selection-item.spec.md +3 -3
  551. package/src/components/SelectionControl/selection-item.stories.tsx +3 -3
  552. package/src/components/Separator/separator.spec.md +2 -2
  553. package/src/components/Separator/separator.stories.tsx +6 -27
  554. package/src/components/Sheet/sheet.anatomy.stories.tsx +1 -1
  555. package/src/components/Sheet/sheet.principles.stories.tsx +1 -1
  556. package/src/components/Sheet/sheet.spec.md +2 -2
  557. package/src/components/Sheet/sheet.tsx +9 -5
  558. package/src/components/Sidebar/sidebar.anatomy.stories.tsx +16 -12
  559. package/src/components/Sidebar/sidebar.principles.stories.tsx +10 -8
  560. package/src/components/Sidebar/sidebar.spec.md +13 -15
  561. package/src/components/Sidebar/sidebar.stories.tsx +8 -7
  562. package/src/components/Sidebar/sidebar.tsx +37 -25
  563. package/src/components/Skeleton/skeleton.stories.tsx +4 -11
  564. package/src/components/Slider/slider.anatomy.stories.tsx +3 -3
  565. package/src/components/Slider/slider.principles.stories.tsx +2 -2
  566. package/src/components/Slider/slider.spec.md +5 -5
  567. package/src/components/Slider/slider.tsx +3 -1
  568. package/src/components/Steps/steps.anatomy.stories.tsx +4 -4
  569. package/src/components/Steps/steps.principles.stories.tsx +1 -1
  570. package/src/components/Steps/steps.spec.md +1 -1
  571. package/src/components/Switch/switch.anatomy.stories.tsx +7 -7
  572. package/src/components/Switch/switch.principles.stories.tsx +4 -4
  573. package/src/components/Switch/switch.spec.md +8 -8
  574. package/src/components/Switch/switch.stories.tsx +9 -24
  575. package/src/components/Switch/switch.tsx +17 -13
  576. package/src/components/Tabs/tabs.anatomy.stories.tsx +10 -7
  577. package/src/components/Tabs/tabs.spec.md +1 -1
  578. package/src/components/Tabs/tabs.tsx +21 -1
  579. package/src/components/Textarea/textarea.anatomy.stories.tsx +1 -1
  580. package/src/components/Textarea/textarea.principles.stories.tsx +1 -1
  581. package/src/components/Textarea/textarea.spec.md +4 -4
  582. package/src/components/Textarea/textarea.tsx +72 -46
  583. package/src/components/TimePicker/time-columns.tsx +6 -2
  584. package/src/components/TimePicker/time-picker.principles.stories.tsx +1 -1
  585. package/src/components/TimePicker/time-picker.spec.md +3 -3
  586. package/src/components/TimePicker/time-picker.tsx +16 -10
  587. package/src/components/Toast/toast.stories.tsx +7 -4
  588. package/src/components/Tooltip/tooltip.anatomy.stories.tsx +4 -4
  589. package/src/components/Tooltip/tooltip.principles.stories.tsx +3 -3
  590. package/src/components/Tooltip/tooltip.spec.md +1 -1
  591. package/src/components/Tooltip/tooltip.stories.tsx +4 -39
  592. package/src/components/Tooltip/tooltip.tsx +8 -6
  593. package/src/components/TreeView/tree-view.anatomy.stories.tsx +6 -3
  594. package/src/components/TreeView/tree-view.spec.md +42 -11
  595. package/src/components/TreeView/tree-view.stories.tsx +1 -0
  596. package/src/components/TreeView/tree-view.tsx +322 -17
  597. package/src/index.ts +588 -60
  598. package/src/patterns/action-bar/action-bar.spec.md +9 -0
  599. package/src/patterns/action-bar/action-bar.stories.tsx +6 -3
  600. package/src/patterns/element-anatomy/inline-action.spec.md +2 -0
  601. package/src/patterns/element-anatomy/item-anatomy.spec.md +14 -5
  602. package/src/patterns/element-anatomy/item-anatomy.stories.tsx +6 -4
  603. package/src/patterns/element-anatomy/item-anatomy.tsx +7 -6
  604. package/src/patterns/header-canonical/header-canonical.spec.md +5 -4
  605. package/src/patterns/overlay-surface/overlay-surface.spec.md +22 -1
  606. package/src/styles/tokens.css +3 -0
  607. package/src/tokens/README.md +1 -1
  608. package/src/tokens/color/color.spec.md +4 -4
  609. package/src/tokens/color/color.stories.tsx +3 -3
  610. package/src/tokens/density/density.stories.tsx +16 -3
  611. package/src/tokens/layoutSpace/layoutSpace.spec.md +3 -1
  612. package/src/tokens/motion/motion.css +33 -21
  613. package/src/tokens/motion/motion.spec.md +32 -15
  614. package/src/tokens/motion/motion.stories.tsx +58 -5
  615. package/src/tokens/motion/motion.ts +7 -6
  616. package/src/tokens/motion/overlay-motion.ts +26 -0
  617. package/src/tokens/orphan-tokens.spec.md +4 -4
  618. package/src/tokens/token-system.spec.md +1 -0
  619. package/src/tokens/uiSize/uiSize.css +1 -1
  620. package/src/tokens/uiSize/uiSize.spec.md +1 -1
  621. package/src/tokens/uiSize/uiSize.stories.tsx +4 -2
  622. package/src/tokens/utility-registry.json +160 -23
@@ -20,7 +20,7 @@ benchmark:
20
20
 
21
21
  **Layout Family**:self-contained(form composition layout-primitive,非 4-Family element layout — 對齊 frontmatter `family: self-contained`)。
22
22
 
23
- **多個 Field controls 視覺接合成一個 input frame**(border-collapse pattern)。對齊 ButtonGroup 同類接合 idiom — `<X>Group` 命名一致(X = button → ButtonGroup;X = field control → FieldControlGroup)。
23
+ **多個 Field controls 視覺接合成一個 input frame**(border-collapse pattern)。命名對齊 ButtonGroup `<X>Group` idiom(X = button → ButtonGroup;X = field control → FieldControlGroup),**但兩者機制不同**:ButtonGroup 是 gap 分離群組(`flex gap-2`,子按鈕之間留 8px),本元件才是 border-collapse 接合(負 margin 重疊 border,子無 gap)
24
24
 
25
25
  **自建 + 理由**:無單一 Radix / shadcn 對應 primitive(shadcn 沒提供;Radix 沒對應)。對齊 [Ant `Space.Compact`](https://ant.design/components/space#spacecompact)(verified source)mechanism + [Bootstrap `.input-group`](https://getbootstrap.com/docs/5.3/forms/input-group/)(verified SCSS)idiom 自建。
26
26
 
@@ -46,7 +46,7 @@ benchmark:
46
46
  | `FieldGroup` | 多 Field 垂直堆疊 | gap 分離 | layout primitive |
47
47
  | **`FieldControlGroup`** | 多 control 橫向接合 | border collapse | layout primitive(本元件)|
48
48
  | `RadioGroup` / `CheckboxGroup` | 1 question 多 options | 各自獨立 | semantic group(ARIA role)|
49
- | `ButtonGroup` | 多 Button 接合 | border collapse | layout primitive( mechanism) |
49
+ | `ButtonGroup` | 多 Button 分組 | gap 分離(`flex gap-2`) | layout primitive(**不同 mechanism**,非 border-collapse 接合) |
50
50
 
51
51
  ## API
52
52
 
@@ -149,6 +149,11 @@ interface FieldControlGroupProps extends HTMLAttributes<HTMLDivElement> {
149
149
  ```
150
150
  保留 global `bg-disabled`(neutral-2 灰底)— disabled state 視覺主要由 bg 承載。
151
151
 
152
+ **⚠️ 選擇器覆蓋範圍(2026-07-13 誠實標註 — code 現況,非 uniform)**:disabled state 的兩道視覺處理走**不同選擇器、覆蓋不同 child 類型**,並非對所有 disabled 子一致生效:
153
+ - **z-index 降 0**:`[&>*[disabled]]:z-0` + `[&>*:has([disabled])]:z-0` — 認**原生 `[disabled]` 屬性**。覆蓋 Button(原生 `<button disabled>`)、Input / NumberInput(wrapper 內含原生 `<input disabled>`,`:has()` 命中)。
154
+ - **border-opaque override**:`[&>*[data-field-mode="disabled"]]` — 認 **Field 家族 wrapper 的 `data-field-mode="disabled"`**。覆蓋所有 Field controls(Select / Input / Combobox / DatePicker…)。
155
+ - **覆蓋落差(已知)**:disabled **Select**(桌機自訂 trigger = `<div>`,無原生 `[disabled]`)→ 得 border-opaque 但**不降 z-0**;disabled **Button**(無 `data-field-mode`)→ 降 z-0 但**不得 border-opaque**。要讓兩道處理對所有 disabled 子一致覆蓋需統一兩選擇器(視覺行為改動 → 走設計拍板,非本文件範圍)。
156
+
152
157
  **為什麼用 `--border-opaque` 而非 `--border`**:`--border`(neutral-5 = 15% alpha)會跟 cell bg compositing — 灰底上 composite 略深(物理對比結果)。**`--border-opaque`** semantic token(其 primitive 後盾為 `--color-neutral-5-opaque`,solid #D9D9D9)不分 bg 永遠同色,divider 在 white edit cell 跟 grey disabled cell 上視覺完全一致。
153
158
 
154
159
  **Token 系統設計**:`--border-opaque` 在 `semantic.css` 新增(grep `--border-opaque:` 查定義行,不寫死行號避免漂移),語意「視覺等同 `--border` 但 alpha-immune」。對齊 [Ant Design `colorBorderSecondary`](https://ant.design/docs/react/customize-theme#seedtoken) solid idiom — Ant 用此 token 在 table 外框 + row divider(non-white bg 場景),跟 input alpha border 視覺層級分。
@@ -168,7 +173,7 @@ interface FieldControlGroupProps extends HTMLAttributes<HTMLDivElement> {
168
173
  ## 邊界案例
169
174
 
170
175
  - **子高度不一**:容器 `items-stretch`,但 field controls 自帶固定 `h-field-*`,不會被拉齊——混 size 即視覺高低差(故禁止,見禁止事項)
171
- - **某子 disabled、某子 edit**:機制上可行(disabled 子降 z-0 + K12 border 維持 divider),但語意一體應一致(見禁止事項);**全組 disabled** 走 K12 canonical(見 States)
176
+ - **某子 disabled、某子 edit**:機制上可行(disabled 子降 z-0 + K12 border 維持 divider,實際覆蓋依 child 類型而異 — 見 K12「選擇器覆蓋範圍」note),但語意一體應一致(見禁止事項);**全組 disabled** 走 K12 canonical(見 States)
172
177
  - **極窄容器**:無特化處理——固定寬 children(`w-[Xpx]`)溢出容器、`flex-1` children 壓縮;子自管 width 的對價,consumer 自行配置
173
178
  - **單一 child**:radius 選擇器 `:first-child:not(:last-child)` 不命中,圓角完整保留(等同未包 group,但此時應直接用 Field)
174
179
 
@@ -186,7 +191,7 @@ interface FieldControlGroupProps extends HTMLAttributes<HTMLDivElement> {
186
191
  ## 相關
187
192
 
188
193
  - `../Field/field.spec.md` — FieldGroup(多 Field 垂直堆疊近親,gap 分離)的 home
189
- - `../Button/button-group.tsx` — border-collapse mechanism Button (無獨立 spec,機制同源)
194
+ - `../Button/button-group.tsx` — 命名 idiom 近親(`<X>Group`);但 ButtonGroup **gap 分離**(`flex gap-2`),非 border-collapse 接合,兩者機制不同(無獨立 spec)
190
195
  - `../Field/field-controls.spec.md` — children(Input / Select / DatePicker 等)共用 Field control 規則
191
196
  - `../Checkbox/checkbox.spec.md` / `../RadioGroup/radio-group.spec.md` — semantic group 近親(1 question 多 options,非本元件 scope)
192
197
 
@@ -16,7 +16,7 @@ import type { FieldSize } from '@/design-system/components/Field/field-context'
16
16
  *
17
17
  * **Naming rationale**(2026-05-04):
18
18
  * - Taxonomic 一致:FieldGroup(多 Field 堆疊)/ FieldControlGroup(多 control 接合)— scope 區分
19
- * - Idiom 一致:ButtonGroup(多 Button 接合)同 pattern,只是 X = field control
19
+ * - 命名 idiom 一致:與 ButtonGroup 共享 <X>Group 命名(X = field control);但**機制不同** — ButtonGroup 是 gap 分離群組(flex gap-2),本元件才是 border-collapse 接合(負 margin 重疊 border)
20
20
  * - 不撞 RadioGroup/CheckboxGroup(那是 1-question 多 options semantic group)
21
21
  *
22
22
  * **Behavior canonical**(verified Ant compact-item.ts L21-58):
@@ -72,7 +72,7 @@ export const Inspector: Story = {
72
72
  docs: { description: { story: '右側 Controls 切 props 即時 render,取代 Figma inspect。調整 `mode` / `status` / `progress` 看 compact vs rich 佈局與狀態 icon/進度條變化。' } },
73
73
  },
74
74
  args: {
75
- name: 'Q1 行銷報告 PDF(範例檔)',
75
+ name: 'Q1 行銷報告.pdf',
76
76
  mode: 'rich',
77
77
  status: 'uploading',
78
78
  progress: 60,
@@ -19,7 +19,7 @@ benchmark:
19
19
 
20
20
  **實作基礎**:組合元件——Icon + Text + ProgressBar + Button,無 external primitive base。
21
21
 
22
- **Layout Family**:CLAUDE.md 4-Family Model **Family 2(List item layout)** 消費者。結構繼承 `patterns/element-anatomy/item-anatomy.spec.md`「List item layout」章節的 reading-mode 規格。FileItem 在 rich mode 用 avatar 作 item boundary。
22
+ **Layout Family**:CLAUDE.md 4-Family Model **Family 2(List item layout)** 消費者。結構繼承 `patterns/element-anatomy/item-anatomy.spec.md`「List item layout」章節,兩 mode 皆採 scanning-mode 規格(見下「Typography」段:FileItem 固定傳 ItemContent `mode="scanning"`)。FileItem 在 rich mode 用 avatar 作 item boundary。
23
23
 
24
24
  **命名 rationale**:`compact / rich` 表達精簡 vs 完整內容呈現(對齊 Discord embed type='rich' / Slack rich preview / Notion rich text 世界級 idiom)。不叫 `lg/sm`——兩者是資訊量不同的展示策略,不是同一結構的尺寸縮放。 <!-- @benchmark-unverified: see frontmatter benchmark list for canonical DS source URL -->
25
25
 
@@ -114,7 +114,7 @@ compact 為預設——多數 upload 清單是「快速掃視多檔」場景,
114
114
  item-anatomy「> 24 prefix + 有 desc」規則要求 prefix 對齊 content 塊**中心**(items-center)。
115
115
  FileItem rich **刻意不遵守**這條,使用 `items-start`(top-align),原因:
116
116
 
117
- > **當 FileItem rich 被放在「檔案上傳管理 box」等 tight-stack 情境**(consumer 移除 border 0 gap 相連時),avatar 作為**每個 item 的視覺邊界引導**——若 avatar 中心對齊,連續 item 的 avatar 會擠在一起失去「每筆檔案一個 thumbnail」的視覺節奏。top-align 讓 avatar 明確標示每個 item 的起點。
117
+ > **當 FileItem rich 被放在「檔案上傳管理 box」等 tight-stack 情境**(`surface=upload-manager` 無邊框、列間 `--layout-space-tight` 12px 緊貼時,見「List wrapper canonical」),avatar 作為**每個 item 的視覺邊界引導**——若 avatar 中心對齊,連續 item 的 avatar 會失去「每筆檔案一個 thumbnail」的視覺節奏。top-align 讓 avatar 明確標示每個 item 的起點。
118
118
 
119
119
  ### Avatar 尺寸(固定 48px)
120
120
 
@@ -150,7 +150,8 @@ Avatar **固定 48px square**,不隨 content 高度變化。content(label + desc
150
150
 
151
151
  | Mode | 永久 visual anchor | 加 hover-bg 後果 |
152
152
  |------|-------------------|-----------------|
153
- | rich | `border + rounded-md + bg-surface` 永遠 card | card + hover-bg = 雙層強調,視覺 heavy |
153
+ | rich(`surface=form`) | `border + rounded-md + bg-surface` card | card + hover-bg = 雙層強調,視覺 heavy |
154
+ | rich(`surface=upload-manager`) | Avatar 48 thumbnail 作 item 邊界(無邊框/無 bg,面板自身是容器) | thumbnail 已提供「每筆檔案」節奏,hover-bg 多餘 |
154
155
  | compact 無 status(靜態) | `bg-secondary rounded-md` 永遠 pill | pill bg + hover-bg neutral-hover 兩層相近灰,視覺雜 |
155
156
  | compact 有 status(uploading / error / completed with bar) | 底部 2px progress bar(分隔線型 permanent affordance) | bar + hover-bg 同時並存,affordance 重複 |
156
157
 
@@ -168,12 +169,8 @@ Avatar **固定 48px square**,不隨 content 高度變化。content(label + desc
168
169
 
169
170
  **跟 MenuItem / DataTable row 對比**(它們用 hover-bg):兩者是 **flush transparent row**(無 permanent bg / border),hover-bg 是唯一 affordance。FileItem 三種型態皆已 anchored → 反向 canonical。
170
171
 
171
- **反例**(本 session 修正):
172
- - 原 code `hoverClass = onClick ? 'cursor-pointer hover:bg-neutral-hover' : ''` → rich card 上 hover 多一層灰 bg 雙層強調;無 status pill 上 hover 加 `bg-neutral-hover` 跟 pill 底色近似造成視覺雜
173
- - 修為 `hoverClass = onClick ? 'cursor-pointer' : ''`
174
-
175
172
  **❌ 反例**:
176
- - Rich mode 無邊框 → 與一般 list item 無法區分,跟 MenuItem 混淆
173
+ - Rich `surface=form` 無邊框 → 失去 card 自立輪廓,與一般 list item 無法區分,跟 MenuItem 混淆(`surface=upload-manager` 例外:面板自身是容器 + avatar 作邊界,rich 刻意無邊框,見上表)
177
174
  - Compact mode 靜態 item 無 bg → 純文字列,使用者不知這是「可點下載的檔案」
178
175
  - 外層 list wrapper 加邊框 / `overflow-hidden` → 雙重邊框(list 邊框 + item card 邊框)視覺干擾;並強制邊框相黏破壞 card 自立性(2026-04-22 user 糾正)
179
176
 
@@ -93,7 +93,7 @@ export const HoverSwap = {
93
93
  </div>
94
94
  <div>
95
95
  <div className="text-caption text-fg-muted mb-2">
96
- 同樣 pattern 在 compact mode:status slot 幾何與 delete action 一致(兩 mode 統一 Button xs 24),center 自動對齊
96
+ 緊湊樣式也是同一套規則:狀態圖示與刪除鈕的大小、位置一致,垂直置中對齊
97
97
  </div>
98
98
  {/* Compact list 統一 gap-1(canonical 簡化) */}
99
99
  <div className="flex flex-col gap-1">
@@ -156,30 +156,41 @@ export const Clickable = {
156
156
  }
157
157
  return (
158
158
  <>
159
- <div className="flex flex-col gap-2 max-w-md">
160
- {attachmentFiles.map((f, i) => {
161
- const isImage = i < 2 // 前兩個 rich card(有 thumbnail)
162
- const sizeLabel = f.description ?? (f.size != null ? `${(f.size / 1024 / 1024).toFixed(1)} MB` : '—')
163
- return isImage ? (
164
- <FileItem
165
- key={f.id}
166
- mode="rich"
167
- name={f.name}
168
- description={sizeLabel}
169
- thumbnailSrc={f.url}
170
- onClick={() => openAt(i)}
171
- actions={deleteBtn}
172
- />
173
- ) : (
174
- <FileItem
175
- key={f.id}
176
- mode="compact"
177
- name={f.name}
178
- onClick={() => openAt(i)}
179
- actions={deleteBtn}
180
- />
181
- )
182
- })}
159
+ {/* 2026-07-14 Dim 68 修:原本同一 list 依檔案類型混 rich(圖片)+ compact(文件),
160
+ 違反 file-item.spec.md「❌ 不混用 rich + compact 在同一 list」(Invariant 1
161
+ 高度差破壞 row rhythm)。改拆兩個區段:圖片整組 rich、文件整組 compact,
162
+ 同區段 mode 統一(spec 建議的分區段做法);FileViewer index 跨兩區段連續。 */}
163
+ <div className="flex flex-col gap-4 max-w-md">
164
+ <div className="flex flex-col gap-1.5">
165
+ <h3 className="text-caption font-medium text-fg-muted">圖片</h3>
166
+ <div className="flex flex-col gap-2">
167
+ {attachmentFiles.slice(0, 2).map((f, i) => (
168
+ <FileItem
169
+ key={f.id}
170
+ mode="rich"
171
+ name={f.name}
172
+ description={f.description ?? (f.size != null ? `${(f.size / 1024 / 1024).toFixed(1)} MB` : '—')}
173
+ thumbnailSrc={f.url}
174
+ onClick={() => openAt(i)}
175
+ actions={deleteBtn}
176
+ />
177
+ ))}
178
+ </div>
179
+ </div>
180
+ <div className="flex flex-col gap-1.5">
181
+ <h3 className="text-caption font-medium text-fg-muted">文件</h3>
182
+ <div className="flex flex-col gap-1">
183
+ {attachmentFiles.slice(2).map((f, i) => (
184
+ <FileItem
185
+ key={f.id}
186
+ mode="compact"
187
+ name={f.name}
188
+ onClick={() => openAt(i + 2)}
189
+ actions={deleteBtn}
190
+ />
191
+ ))}
192
+ </div>
193
+ </div>
183
194
  </div>
184
195
  <FileViewer files={attachmentFiles} open={open} onOpenChange={setOpen} index={index} onIndexChange={setIndex} />
185
196
  </>
@@ -112,7 +112,8 @@ const FileItem = React.forwardRef<HTMLDivElement, FileItemProps>(
112
112
  const showDesc = isRich ? !!description : (status === 'error' && !!description)
113
113
 
114
114
  // Hover 行為 canonical(2026-04-23 user 校準):**FileItem 永不顯示 hover-bg**。
115
- // 三種型態都有永久 visual anchor:rich = border card / compact 無 status = bg-secondary /
115
+ // 各型態都有永久 visual anchor:rich surface=form = border card / rich surface=upload-manager
116
+ // = avatar 48 thumbnail 作 item 邊界(無邊框)/ compact 無 status = bg-secondary /
116
117
  // compact 有 status = 底部 progress bar(分隔線型 affordance)——再加 hover-bg 是
117
118
  // double-emphasis,視覺雜。世界級共識(Slack / Notion / Figma / Gmail 皆無 hover-bg):
118
119
  // permanent-anchored 元件 hover 只靠 cursor + action icon fade / border highlight,
@@ -144,8 +145,8 @@ const FileItem = React.forwardRef<HTMLDivElement, FileItemProps>(
144
145
  const slotHw = 'var(--field-height-xs)'
145
146
 
146
147
  const hoverAction =
147
- status === 'completed' && onDownload ? { icon: Download, onClick: onDownload, label: '下載' } :
148
- status === 'error' && onRetry ? { icon: RotateCw, onClick: onRetry, label: '重試' } :
148
+ status === 'completed' && onDownload ? { icon: Download, onClick: onDownload, label: `下載 ${name}` } :
149
+ status === 'error' && onRetry ? { icon: RotateCw, onClick: onRetry, label: `重試 ${name}` } :
149
150
  null
150
151
 
151
152
  const statusSlot = statusConfig ? (
@@ -237,9 +238,10 @@ const FileItem = React.forwardRef<HTMLDivElement, FileItemProps>(
237
238
  // 底色同樣指向 `--color-neutral-3`。對齊 Badge low / ProgressBar track SSOT。
238
239
  const compactStaticBg = !progressBar ? 'bg-secondary' : ''
239
240
 
240
- // ── rich(含縮圖完整呈現)——AR17 canonical:加邊框 + gap-2 ──
241
- // Rich mode 是「檔案 card」風格,外框讓每個 row 視覺上是獨立 card
242
- // (Slack / Notion / Linear attachment 慣例)
241
+ // ── rich(含縮圖完整呈現)——AR17 canonical:surface=form 加邊框 card / surface=upload-manager 無邊框 ──
242
+ // Rich surface=form 是「檔案 card」風格,外框讓每個 row 視覺上是獨立 card(Slack / Notion /
243
+ // Linear attachment 慣例);surface=upload-manager 無邊框,avatar item 邊界(面板自身是容器,
244
+ // 見 file-item.spec.md「邊框 / 背景」表)
243
245
  if (isRich) {
244
246
  return (
245
247
  <div
@@ -164,8 +164,8 @@ export const Inspector: Story = {
164
164
  </div>
165
165
  <div className="flex items-center gap-1.5">
166
166
  border:{' '}
167
- <TokenCell token={state === 'drag-over' ? '--primary' : '--border'} />
168
- <span className="text-fg-muted">(idle `--border` 元件邊框;hover=drag-over 統一 `--primary`,純 border-driven)</span>
167
+ <TokenCell token={state === 'drag-over' ? '--primary-hover' : '--border'} />
168
+ <span className="text-fg-muted">(idle `--border` 元件邊框;hover=drag-over 統一 `--primary-hover`(blue-5),純 border-driven)</span>
169
169
  </div>
170
170
  <div className="flex items-center gap-1.5">
171
171
  icon glyph: <TokenCell token={state === 'disabled' ? '--fg-disabled' : '--foreground'} />
@@ -225,12 +225,12 @@ export const ColorMatrix: Story = {
225
225
  <Td>
226
226
  <TokenCell token="--foreground" />
227
227
  </Td>
228
- <Td>`--border` 元件邊框(非 `--divider`);hover → border 切 `--primary`(同 drag-over)</Td>
228
+ <Td>`--border` 元件邊框(非 `--divider`);hover → border 切 `--primary-hover`(同 drag-over)</Td>
229
229
  </tr>
230
230
  <tr>
231
231
  <Td mono>drag-over = hover</Td>
232
232
  <Td>
233
- <TokenCell token="--primary" />
233
+ <TokenCell token="--primary-hover" />
234
234
  </Td>
235
235
  <Td>
236
236
  <TokenCell token="--surface" />
@@ -238,7 +238,7 @@ export const ColorMatrix: Story = {
238
238
  <Td>
239
239
  <TokenCell token="--foreground" />
240
240
  </Td>
241
- <Td>hover 與 drag-over 統一:純 border-driven(只切 `--primary` 邊框,底色維持 surface,對齊 Ant Dragger)</Td>
241
+ <Td>hover 與 drag-over 統一:純 border-driven(只切 `--primary-hover` 邊框,底色維持 surface,對齊 Ant Dragger)</Td>
242
242
  </tr>
243
243
  <tr>
244
244
  <Td mono>disabled</Td>
@@ -353,7 +353,7 @@ export const StateBehavior: Story = {
353
353
  <tr>
354
354
  <Td mono>dragenter</Td>
355
355
  <Td>檔案拖入區塊</Td>
356
- <Td>`data-state="drag-over"`(border-primary;底色維持 surface,純 border-driven = hover)</Td>
356
+ <Td>`data-state="drag-over"`(border-primary-hover;底色維持 surface,純 border-driven = hover)</Td>
357
357
  </tr>
358
358
  <tr>
359
359
  <Td mono>dragleave</Td>
@@ -76,7 +76,7 @@ export const UsageGuidance: Story = {
76
76
  <LinkTo kind="Design System/Components/FileUpload/展示" name="自訂內容"><span className="text-primary hover:text-primary-hover font-medium cursor-pointer">品牌化上傳區 — 自訂 dropzone 內容</span></LinkTo>
77
77
  </li>
78
78
  </ul>
79
- <p className="text-fg-muted mt-3">判斷不確定時:對照 spec.md「何時用 / 何時不用」段;若仍不符,改用近親元件( <code>Vs*Rule</code> stories)。</p>
79
+ <p className="text-fg-muted mt-3">判斷不確定時:對照 spec.md「何時用 / 何時不用」段;若仍不符,改用近親元件(見本頁 <code>UsageGuidance</code>「vs 近親」段)。</p>
80
80
  </div>
81
81
 
82
82
  {/* 何時不用 / 替代元件 — 原 WhenNotToUse */}
@@ -90,13 +90,13 @@ export const UsageGuidance: Story = {
90
90
 
91
91
  <Rule
92
92
  title="❌ 不加 scale / shadow 等裝飾性 drag-over 信號"
93
- note="drag-over 只用邊框顏色(border-primary,底維持 surface 不變 bg)傳達狀態。加 scale 會讓區塊在拖放瞬間晃動、使用者滑鼠與 drop target 錯位;加 shadow 和元件 elevation 體系衝突。"
93
+ note="drag-over 只用邊框顏色(border-primary-hover,底維持 surface 不變 bg)傳達狀態。加 scale 會讓區塊在拖放瞬間晃動、使用者滑鼠與 drop target 錯位;加 shadow 和元件 elevation 體系衝突。"
94
94
  >
95
95
  <div className="flex flex-col gap-2">
96
- <div className="border-2 border-dashed border-primary bg-surface rounded-md p-[var(--layout-space-loose)] text-center text-caption text-fg-muted">
97
- ✓ 僅改邊框顏色(dashed primary,底維持 surface)
96
+ <div className="border-2 border-dashed border-primary-hover bg-surface rounded-md p-[var(--layout-space-loose)] text-center text-caption text-fg-muted">
97
+ ✓ 僅改邊框顏色(dashed primary-hover,底維持 surface)
98
98
  </div>
99
- <div className="border-2 border-dashed border-primary bg-surface rounded-md p-[var(--layout-space-loose)] text-center text-caption text-fg-muted scale-105 shadow-[var(--elevation-200)]">
99
+ <div className="border-2 border-dashed border-primary-hover bg-surface rounded-md p-[var(--layout-space-loose)] text-center text-caption text-fg-muted scale-105 shadow-[var(--elevation-200)]">
100
100
  ✗ scale + shadow — 視覺噪音,與 elevation 系統衝突
101
101
  </div>
102
102
  </div>
@@ -135,7 +135,7 @@ FileUpload 是**拖放 / 點擊上傳區塊**——可拖曳檔案進入或點
135
135
  - **空選取(0 檔)**:不觸發任何 callback;`files={[]}` 不渲染清單(等同不傳)
136
136
  - **maxSize / accept 全擋下**:`onUpload` 不觸發,只觸發 `onReject`
137
137
  - **同檔重選**(2026-07-05 D4):input value 於 dispatch 後清空 — 「上傳失敗修正後重選同檔」「刪除後重傳同檔」仍觸發 `onUpload`(瀏覽器對同路徑檔案不重發 change event,不清空則靜默無反應)
138
- - **拖曳滑過內部子元素**(2026-07-05 D4):pointer 移過 Empty 的 icon/title 時 drag-over 高亮不熄滅(dragleave relatedTarget guard,避免經典 drag-flicker)
138
+ - **拖曳滑過內部子元素**(2026-07-06 深度計數):pointer 移過 Empty 的 icon/title 時 drag-over 高亮不熄滅 — dragenter/dragleave 成對 +1/−1 深度計數,計數 > 0 即維持高亮;不依賴 `e.relatedTarget`(Safari/WebKit 的 dragleave relatedTarget 恆 null,guard 法必失效,故已於 2026-07-06 廢棄換深度計數),避免經典 drag-flicker
139
139
  - **Disabled / loading 時拖檔 drop**(2026-07-05 D4):操作被吞掉 — 仍 `preventDefault` 擋瀏覽器預設「在當前 tab 開啟被丟檔案」的整頁導航,`onUpload` 不觸發
140
140
  - Disabled / loading 見「四狀態視覺」;dark mode / density 由 semantic token 自動處理
141
141
 
@@ -16,7 +16,7 @@ import { Button } from '@/design-system/components/Button/button'
16
16
  *
17
17
  * ── 4 狀態(2026-06-03 修正)──
18
18
  * idle (default) — border-dashed border-border bg-surface
19
- * hover = drag-over — border-primary(統一,純 border-driven,底維持 surface)
19
+ * hover = drag-over — border-primary-hover(統一,純 border-driven,底維持 surface;2026-07-06 對齊 Ant colorPrimaryHover)
20
20
  * loading — CircularProgress;cursor-progress(無 pointer-events-none)
21
21
  * disabled — bg-disabled + 文字 fg-disabled(語意 token,非 opacity)+ cursor-not-allowed
22
22
  *
@@ -826,7 +826,7 @@ export const StateBehavior: Story = {
826
826
  <tr>
827
827
  <Td>連續 / 任意值</Td>
828
828
  <Td mono>滾輪(step 0.03)/ ZoomInput 打字</Td>
829
- <Td>multiplicative 連續縮放,任意 %(非 preset;spec「Wheel step canonical」);ZoomInput preset menu 才跳 preset</Td>
829
+ <Td>連續縮放,任意 %(非 preset;react-zoom-pan-pinch wheel = `scale + delta×step` 加法線性,非 multiplicative;spec「Wheel step canonical」);ZoomInput preset menu 才跳 preset</Td>
830
830
  </tr>
831
831
  <tr>
832
832
  <Td>Reset 100%</Td>
@@ -903,7 +903,8 @@ export const StateBehavior: Story = {
903
903
  <div>
904
904
  <H3>Keyboard shortcut 對照</H3>
905
905
  <Desc>
906
- Focus 在 input / textarea / contentEditable 時**全部不觸發**(避免打字被快捷鍵劫持)。
906
+ Focus 在 input / textarea / contentEditable 時,FileViewer 自管的快捷鍵(← → / +・−・0 / F / I)**全部不觸發**(避免打字被快捷鍵劫持)。
907
+ **例外:Esc** 由 Radix DismissableLayer 以 document capture-phase 監聽,不受此排除影響 —— 在 InfoPanel textarea 內按 Esc 仍會關閉整個 viewer。
907
908
  所有 keyboard 動作都有 toolbar button 對應(a11y + tablet / touch 可用性)。
908
909
  </Desc>
909
910
  <div className="overflow-x-auto">
@@ -1123,7 +1124,7 @@ export const Accessibility = {
1123
1124
  render: () => (
1124
1125
  <div className="max-w-3xl text-body text-fg-secondary">
1125
1126
  <h3 className="text-h5 text-foreground mb-2">無障礙設計</h3>
1126
- <p className="whitespace-pre-line">{"詳 `file-viewer.spec.md` 「A11y 預設」段。摘要:\n\nRadix DialogPrimitive 自動處理:\n- role=\"dialog\" + aria-modal=\"true\" \n- <DialogPrimitive.Title> (sr-only)自動成 aria-labelledby 目標——screen reader 開啟時讀「檔案檢視器:{file.name}」\n- Focus trap:焦點鎖在 viewer 內\n- Esc 關閉(Radix DismissableLayer);Backdrop click 關閉為自寫 geometric onClick handler(非 Radix outside-click — Content 為 fixed inset-0 全螢幕覆蓋,判斷 click 落在 img rect 外才關)\n\n自加的 a11y:\n- 所有 iconOnly button 皆有 aria-label (中文,跟 DS 其他元件風格一致)\n- Filmstrip role=\"group\" + thumb <button> + aria-current (非 tablist:選圖導航非切 tabpanel)\n- InfoPanel 用 <aside aria-label=\"檔案詳細資訊\"> 語意標記\n- onOpenAutoFocus preventDefault:避免焦點自動跑進第一個 tabbable(讓鍵盤從 viewport 開始)"}</p>
1127
+ <p className="whitespace-pre-line">{"詳 `file-viewer.spec.md` 「A11y 預設」段。摘要:\n\nRadix DialogPrimitive 自動處理:\n- role=\"dialog\" (Radix 1.1.x 不輸出 aria-modal 屬性;modal 隔離靠 focus trap + hideOthers 對背景兄弟樹加 aria-hidden,非 aria-modal)\n- <DialogPrimitive.Title> (sr-only)自動成 aria-labelledby 目標——screen reader 開啟時讀「檔案檢視器:{file.name}」\n- Focus trap:焦點一旦進入 Content 即鎖在 viewer 內\n- Esc 關閉(Radix DismissableLayer,document capture-phase 監聽 → 即使焦點在 InfoPanel textarea 內按 Esc 仍會關閉整個 viewer);Backdrop click 關閉為自寫 geometric onClick handler(非 Radix outside-click — Content 為 fixed inset-0 全螢幕覆蓋,判斷 click 落在 img rect 外才關)\n\n自加的 a11y:\n- 所有 iconOnly button 皆有 aria-label (中文,跟 DS 其他元件風格一致)\n- Filmstrip role=\"group\" + thumb <button> + aria-current (非 tablist:選圖導航非切 tabpanel)\n- InfoPanel 用 <aside aria-label=\"檔案詳細資訊\"> 語意標記\n- onOpenAutoFocus preventDefault:僅取消 Radix 自動聚焦(焦點留在觸發按鈕、不主動移入 viewport);焦點實際進入 Content 後 FocusScope 才啟動 trap"}</p>
1127
1128
  </div>
1128
1129
  ),
1129
1130
  }
@@ -258,7 +258,7 @@ export const CloseButtonPositionRule: Story = {
258
258
  <div>
259
259
  <Rule
260
260
  title="✅ toolbar 順序 — zoom → info → download → close(影響力遞增)"
261
- note="DS 設計準則三方交集:(1) action-bar「全局 primary 在最右」影響力遞增;(2) Notice dismiss X always rightmost;(3) Dialog close 靠右的跨平台慣例(macOS / Windows / Web 一致)。zoom 影響單檔 transform / info 影響 layout / download 搬內容出 viewer / close unmount 所有 state — 影響越大越右"
261
+ note="DS 設計準則三方交集:(1) action-bar「全局 primary 在最右」影響力遞增;(2) Notice dismiss X always rightmost;(3) Dialog close 靠右的跨平台慣例(macOS / Windows / Web 一致)。zoom 影響單檔 transform / info 影響 layout / download 搬內容出 viewer / close 卸載 DialogContent 子樹(shell state 保留於恆掛載外層)— 影響越大越右"
262
262
  >
263
263
  <Button variant="tertiary" startIcon={FolderOpen} onClick={() => setOpen(true)}>
264
264
  開啟 Dashboard 設計稿
@@ -423,13 +423,13 @@ export const ReadOnlyRule: Story = {
423
423
 
424
424
  <Rule
425
425
  title="✅ readOnly — 其他人上傳的 / 公開公告 / 只讀 link 分享"
426
- note="readOnly=true textarea readOnly,onDescriptionChange 不觸發。placeholder 改為「尚無說明」讓無 description 時也不顯示「為這個檔案加上說明...」誘導編輯的文字"
426
+ note="readOnly=true 時說明區依 DS Textarea readonly canonical:有值 → 可聚焦選取但不可改的 native readonly textarea;空值 display div 顯 emptyDisplay(`-`,非 placeholder);onDescriptionChange 不觸發。故 readonly 無 description 時顯示 `-`,不會出現「為這個檔案加上說明...」的編輯誘導 placeholder"
427
427
  >
428
428
  <Button variant="tertiary" startIcon={FolderOpen} onClick={() => setRoOpen(true)}>
429
429
  開啟公司公告(readOnly)
430
430
  </Button>
431
431
  <FileViewer files={publicFiles} open={roOpen} onOpenChange={setRoOpen} readOnly />
432
- <Label>↑ 按 I 打開 info panel,textarea 不可編輯、focus 不進游標</Label>
432
+ <Label>↑ 按 I 打開 info panel;readonly 有值 = 可聚焦選取但不可改的 textarea,無值 = 顯 `-`</Label>
433
433
  </Rule>
434
434
 
435
435
  <Rule
@@ -20,7 +20,7 @@ benchmark:
20
20
 
21
21
  FileViewer 是**可延伸的網頁檔案預覽殼層(modal fullscreen)**——負責 overlay / toolbar / 鍵盤 / filmstrip / 詳細資訊面板一切 chrome,檔案本體由 **renderer registry** 按 file type 決定誰渲染。MVP 內建 `ImageRenderer` + `FallbackRenderer`,未來可透過 `registerFileRenderer()` 擴充 PDF / Video / Code 等 renderer 而不動 shell。
22
22
 
23
- **實作基礎**:自建 composite——直接消費 Radix `DialogPrimitive` 而非 DS 的 `<Dialog>` wrapper。原因:FileViewer 需要 edge-to-edge fullscreen(無 viewport inset / 無 rounded-lg / 無 maxWidth),這些全要覆寫 `<Dialog>` 預設。直接消費 Radix primitive 讓 shell 擁有完整 layout 控制權,同時保有 Radix focus trap / Esc / aria-modal 的結構優勢。內部消費 DS 元件:`<Button>` / `<Input>` / `<Empty>` / `<Separator>` / `<AspectRatio>` / `<Textarea>` / `<Field>`+`<FieldLabel>` / `<DescriptionList>` / `<ScrollArea>` / `<DropdownMenu>` / `<ChromeHeader>` / `ItemInlineActionButton` + `patterns/horizontal-overflow`。
23
+ **實作基礎**:自建 composite——直接消費 Radix `DialogPrimitive` 而非 DS 的 `<Dialog>` wrapper。原因:FileViewer 需要 edge-to-edge fullscreen(無 viewport inset / 無 rounded-lg / 無 maxWidth),這些全要覆寫 `<Dialog>` 預設。直接消費 Radix primitive 讓 shell 擁有完整 layout 控制權,同時保有 Radix focus trap(FocusScope)/ Esc / modal 隔離(`hideOthers` 對背景兄弟樹加 `aria-hidden`;Radix `react-dialog@1.1.x` 不輸出 `aria-modal` 屬性)的結構優勢。內部消費 DS 元件:`<Button>` / `<Input>` / `<Empty>` / `<Separator>` / `<AspectRatio>` / `<Textarea>` / `<Field>`+`<FieldLabel>` / `<DescriptionList>` / `<ScrollArea>` / `<DropdownMenu>` / `<ChromeHeader>` / `ItemInlineActionButton` + `patterns/horizontal-overflow`。
24
24
 
25
25
  **Layout Family**:**非上述 family — composite / multi-region**(Toolbar + Viewport + 可選 InfoPanel + 可選 Filmstrip)。見下「Layout Family Rationale」段。
26
26
 
@@ -140,7 +140,7 @@ FileViewer Toolbar 消費 `patterns/action-bar/action-bar.spec.md` 的**左中
140
140
  - **zoom** — 同一張檔案的微調(影響範圍:當前 renderer 的 transform state),影響最小
141
141
  - **info** — 切換附加資訊面板(影響範圍:layout,不破壞現有內容)
142
142
  - **download** — 把內容搬出 viewer(影響範圍:外部檔案系統,不可逆副作用)
143
- - **close** — 結束整個 viewing session(影響範圍:最大,unmount 所有 state)
143
+ - **close** — 結束整個 viewing session(影響範圍:最大)。註:close = `setOpen(false)`,**僅卸載 `DialogContent` 子樹**;外層 FileViewer 元件恆掛載,shell state(zoom / infoOpen / index 等 `useState`)**保留** —— 重開同一檔沿用上次 zoom / infoOpen,非 fresh state
144
144
 
145
145
  ### Dismiss canonical(Close X)
146
146
 
@@ -199,20 +199,38 @@ Shell 看到 `pageNumber` capability 時自動在 toolbar 顯示 page navigator(
199
199
 
200
200
  - 消費 `react-zoom-pan-pinch`(zoom + pan 行為 primitive,無 UI);世界級產品 Figma / Miro / PhotoSwipe 同流派 <!-- @benchmark-unverified: see frontmatter benchmark list for canonical DS source URL -->
201
201
  - **圖 render 走 natural size**(不 object-contain):transform scale 管實際顯示大小;避免 CSS pre-scale 跟 transform scale 雙重縮放導致 fit-to-* 算法混淆
202
- - **onLoad 自動 fit-to-page**(世界級 canonical:Figma / Preview.app / Acrobat 開圖預設 fit-page,非 100%)——renderer 計算 `min(cw/iw, ch/ih)` 對應的 zoom %(`image-renderer.tsx` computeFitScale),透過 `onZoomChange` 回報 shell 更新 UI 顯示真實倍率(例:40%) <!-- @benchmark-unverified: see frontmatter benchmark list for canonical DS source URL -->
202
+ - **onLoad 自動 fit-to-page**(世界級 canonical:Figma / Preview.app / Acrobat 開圖預設 fit-page,非 100%)——renderer 計算 `min(cw/iw, ch/ih)` 對應的 zoom %(`image-renderer.tsx` computeFitScale),透過 `onZoomChange` 回報 shell 更新 UI 顯示真實倍率(例:40%)。**邊界**:fit scale 受 `MIN_SCALE = 10%` 下限與 `Math.floor` 夾制 —— 所需倍率 < 10% 的超大圖仍溢出(clamp 到 10%),floor 亦刻意留 ~1% under-fill 保對稱置中,故「完整可見」在極端尺寸為近似而非絕對 <!-- @benchmark-unverified: see frontmatter benchmark list for canonical DS source URL -->
203
203
  - **zoom = 100% 語義 = natural pixel size**(跟 Figma / Preview 一致),不是 CSS-contained 的 fit scale
204
204
  - Scroll wheel 縮放;`zoom > 100%` 時可拖曳 pan
205
- - **Wheel step canonical 為 `0.03`(每 tick ~3% scale delta)**——對齊 Figma / Preview.app / Photoshop 細緻度。原 0.1(10%)太粗,接近 Google Slides 離散慣例非世界級連續 zoom。library 內部 multiplicative 乘算 log 視覺等距(符合「等距」需求)。註:原想加 `smoothStep: 0.005` trackpad 細緻度,但當前 react-zoom-pan-pinch 版本型別不含該 key,**未啟用**;若需要升級到有此 prop 的版本或切 `smoothScroll` API <!-- @benchmark-unverified: see frontmatter benchmark list for canonical DS source URL -->
205
+ - **Wheel step canonical 為 `0.03`(每 tick ~3% scale delta)**——對齊 Figma / Preview.app / Photoshop 細緻度。原 0.1(10%)太粗,接近 Google Slides 離散慣例非世界級連續 zoom。**react-zoom-pan-pinch@4 wheel zoom = `scale + delta × step` 加法線性**(`handleCalculateWheelZoom`,`dist/index.esm.js:1265`;**非** multiplicative/log —— 乘算 `scale × exp(delta×step)` 只用於 setup `smooth` 開啟時的按鈕 zoom path)。註:lib 型別**無** `smoothStep` / `smoothScroll` key(僅 setup-level `smooth?: boolean` 走按鈕 exp path、不影響 wheel);trackpad 平滑度目前無對應 wheel prop 可調 <!-- @benchmark-unverified: see frontmatter benchmark list for canonical DS source URL -->
206
206
  - **Zoom anchor(中心點)canonical**:
207
207
  - **Wheel zoom** — anchor 固定在 **cursor pointer 位置**(react-zoom-pan-pinch default,對齊 Figma / Photoshop / 瀏覽器 cmd-scroll canonical)。user 滑鼠指哪,zoom 就以那點為中心,內容不會「跑掉」 <!-- @benchmark-unverified: see frontmatter benchmark list for canonical DS source URL -->
208
208
  - **± button zoom / preset / fit / input 打字**(沒 cursor 位置時)— 走 library canonical `api.centerView(targetScale, 200)`,自動處理 scale + 置中 + animation + bounds。**不自算 `setTransform`**(動畫期間讀 stale positionX 會漂)
209
209
  - **Fit to width / page** — 算 scale emit 回 shell,經由 `centerView` 重新置中
210
210
  - **100% 重設**(user 按 `0` 鍵;雙擊是 fit↔100% 條件式 toggle,見下「雙擊」bullet)— `centerZoomedOut: true` 讓 ≤100% 時自動 center,避免內容停在容器角落
211
- - 結果:user 看哪,zoom **看哪**,視覺焦點不漂移 世界級 media viewer 共識
211
+ - 結果:**wheel / pinch** 以指標為 anchor,zoom 完視覺焦點不漂移(世界級 media viewer 共識);**按鈕 / preset / fit / 輸入打字**走 `centerView`,焦點**重置為內容幾何中心**(非保持指標焦點)—— 兩條 zoom 路徑 anchor 行為不同
212
212
  - Min scale 10%,max scale 400%
213
213
  - **雙擊 = toggle fit-page ↔ 100%**(對齊 Apple Photos / Preview.app / Imgur / PhotoSwipe canonical):當前 zoom 在 fit-page ±5pt 內 → 跳 100% natural;否則 → 回 fit-page。`0` 鍵才是固定重設 100%(`file-viewer.tsx:863`),兩者不同(`image-renderer.tsx:144`)
214
214
  - 切換檔案時 shell **不重設 zoom**——把「下一張該怎麼初始化 zoom」的決定權交給 renderer:image-renderer 自己 watch `file.url` 變化 → reset loaded → onLoad → 重 fit-page(含 cache 命中時 `<img complete>` 主動觸發,避免 onLoad 不 fire 卡在上一張的 zoom)。原本 shell `setZoom(100)` 在 cache 命中時會卡 100% 不 fit,已移除
215
215
 
216
+ ### 畫布自適應(container resize reflow,2026-07-08 codify — 原只活在 code 註解)
217
+
218
+ **單一咽喉點 invariant**:自適應監聽對象 = **畫布容器自身的 `ResizeObserver`**(`image-renderer.tsx`),**禁**改聽 `window resize` — 容器級觀察讓所有尺寸變化來源走同一條路,天然涵蓋、免逐源接線:
219
+
220
+ | 尺寸變化來源 | 涵蓋方式 |
221
+ |---|---|
222
+ | 視窗 resize / RWD breakpoint | 容器跟著變 → observer fire |
223
+ | InfoPanel 開合(`I` 鍵 / close X,右側 320px)| 同上 |
224
+ | Filmstrip 顯示/隱藏(底部高度)| 同上 |
225
+ | 裝置旋轉 / 瀏覽器縮放 | 同上 |
226
+ | 檔案切換(prev/next)| 不走 observer — `file.url` watch → 重 fit-page(上一 bullet)|
227
+
228
+ **Resize 時的行為分流**(rAF debounce 合併連續 fire):
229
+ - **fit mode(fit-page / fit-width)** → 重算 fit scale 並套用(對齊 Apple Photos / Google Drive:留在 fit 意圖就跟著容器重排)
230
+ - **manual zoom** → **scale 不變 + 視覺中心點保持**(對齊 Figma / Photoshop):以「舊容器中心對到的內容點」反推新 position(`setTransform` 同步無動畫),內容不因 InfoPanel 開合而偏移;禁 early-return 什麼都不做(2026-07-08 前的 gap:transform 座標是絕對值,容器變窄內容偏左)
231
+
232
+ **Mode 追蹤**:`lastFitModeRef`(fit-page / fit-width / manual)— fit 請求與雙擊設定之,user wheel/pinch 打破為 manual;programmatic `centerView`/`setTransform` 期間以 flag 防誤標。
233
+
216
234
  ---
217
235
 
218
236
  ## ZoomInput 規則
@@ -257,11 +275,11 @@ Shell 看到 `pageNumber` capability 時自動在 toolbar 顯示 page navigator(
257
275
  - **寬度固定 w-80(320px)**——對齊 Figma right panel(320)的業界慣例;Google Photos 用 360 偏寬,FileViewer 走 Figma 偏窄以讓 viewport 多一些空間 <!-- @benchmark-unverified: see frontmatter benchmark list for canonical DS source URL -->
258
276
  - **Header 高度 = `--chrome-header-height`**(隨 page density:48 md / 56 lg)——與 Toolbar 等高,視覺對齊;消費 `<ChromeHeader>`(繼承 page,2026-06-15 移除 lockDensity)
259
277
  - **內容分兩區**:
260
- - 「說明」Textarea(可編輯 / readOnly 依 `readOnly` prop)
278
+ - 「說明」Textarea:edit mode = 可編輯 native textarea;**readOnly mode DS Textarea canonical 分兩態** —— **有值** → native `<textarea readOnly>`(仍可聚焦 / 選取 / 複製),**空值** → display `<div>` 顯 emptyDisplay(`-`;field-controls readonly canonical,非 placeholder)
261
279
  - 「檔案資訊」`<dl>`:檔名 / 類型 / 大小 / 自訂 metadata 條目
262
280
  - **預設關閉**,user 主動 toggle(I 鍵 or Info 按鈕);切檔案時 InfoPanel 保持同步顯示下一個 file 的資訊
263
281
  - **Description 變化持久化**:onBlur 時若 draft 與原值不同,呼叫 `onDescriptionChange(fileId, description)`;consumer 負責寫後端
264
- - **Readonly 時不觸發 onDescriptionChange**;placeholder 改為「尚無說明」
282
+ - **Readonly 時不觸發 onDescriptionChange**;readonly 空值走 display `<div>` 顯 `-`(emptyDisplay),**placeholder(含 `descriptionPlaceholderReadOnly`「尚無說明」)在 readonly 不會顯示** —— placeholder 僅 edit mode 空值時出現(`descriptionPlaceholderEdit`)
265
283
 
266
284
  ---
267
285
 
@@ -291,7 +309,7 @@ Shell 看到 `pageNumber` capability 時自動在 toolbar 顯示 page navigator(
291
309
  | `F` | Fit to page | `capabilities.zoom === true` |
292
310
  | `I` | 切換 InfoPanel | 永遠 |
293
311
 
294
- **焦點在 input / textarea / contentEditable 時不觸發**——避免 user 打字時被快捷鍵劫持(寫入 description 打到 `-` 不應 zoom out)。
312
+ **焦點在 input / textarea / contentEditable 時不觸發**——避免 user 打字時被快捷鍵劫持(寫入 description 打到 `-` 不應 zoom out)。此排除**僅適用 FileViewer 自管的快捷鍵**(← → / +・−・0 / F / I)。**例外 `Esc`**:由 Radix DismissableLayer 以 document capture-phase 監聽,不受此排除影響 —— 在 InfoPanel description textarea 內按 Esc 仍會關閉整個 viewer(未 blur 的 draft 不觸發持久化)
295
313
 
296
314
  ---
297
315
 
@@ -342,17 +360,17 @@ Shell 看到 `pageNumber` capability 時自動在 toolbar 顯示 page navigator(
342
360
  ## A11y 預設
343
361
 
344
362
  Radix DialogPrimitive 自動處理:
345
- - `role="dialog"` + `aria-modal="true"`
363
+ - `role="dialog"`(**不輸出 `aria-modal`**;`react-dialog@1.1.x` 只設 role / `aria-labelledby` / `aria-describedby`,modal 隔離改由下方 focus trap + `hideOthers` 的 `aria-hidden` 達成)
346
364
  - `<DialogPrimitive.Title>`(sr-only)自動成 `aria-labelledby` 目標——screen reader 開啟時讀「檔案檢視器:{file.name}」
347
- - Focus trap:焦點鎖在 viewer 內
348
- - Esc 關閉(Radix DismissableLayer 預設)
365
+ - Focus trap:焦點**進入 Content 後**鎖在 viewer 內(FocusScope)
366
+ - Esc 關閉(Radix DismissableLayer,document capture-phase 監聽)
349
367
  - Backdrop click 關閉**非** Radix outside-click:Content 為 `fixed inset-0` 全螢幕覆蓋整個 viewport、Overlay 是其後層 sibling,Radix outside-click 不會 fire。實際靠自寫 geometric onClick handler(判斷 click 座標是否落在 `<img>` rect 外 → 才關)
350
368
 
351
369
  自加的 a11y:
352
370
  - 所有 iconOnly button 皆有 `aria-label`(中文,跟 DS 其他元件風格一致)
353
371
  - Filmstrip `role="group"` + `aria-label` + thumb `<button>` + `aria-current`(非 tablist;見「Filmstrip 規則」段理由)
354
372
  - InfoPanel 用 `<aside aria-label="檔案詳細資訊">` 語意標記
355
- - `onOpenAutoFocus={e => e.preventDefault()}` 避免焦點自動跑進第一個 tabbable(讓鍵盤從 viewport 開始)
373
+ - `onOpenAutoFocus={e => e.preventDefault()}` **僅取消** Radix 開啟時的自動聚焦(避免焦點跑進第一個 tabbable 觸發 tooltip);此 handler **不主動把焦點移入 viewport** —— 焦點停在觸發按鈕,直到 user 操作使焦點進入 Content,FocusScope 才啟動 trap。(known a11y gap:若要開啟即把焦點移入 viewer 需補 `tabIndex` + `ref.focus()`,屬 SSOT-affecting 待拍板)
356
374
 
357
375
  ---
358
376
 
@@ -374,8 +374,7 @@ export const FallbackUnknownType: Story = {
374
374
  開啟無 renderer 支援的檔案
375
375
  </Button>
376
376
  <p className="text-footnote text-fg-muted text-center max-w-[520px] leading-relaxed">
377
- 情境:專案備份 zip 檔案無對應 renderer,FallbackRenderer 兜底顯示檔名 +
378
- 「請下載檢視」訊息。Toolbar 隱藏 zoom controls(capabilities.zoom = false)。
377
+ 情境:zip 等無法預覽的檔案,改顯示檔名和「請下載檢視」提示,工具列會自動隱藏縮放按鈕。
379
378
  </p>
380
379
  <FileViewer files={unknownFile} open={open} onOpenChange={setOpen} />
381
380
  </div>
@@ -48,6 +48,7 @@ import type {
48
48
  FileRendererCapabilities,
49
49
  FileRendererProps,
50
50
  } from './file-viewer-types'
51
+ import { surfaceMotion } from "@/design-system/tokens/motion/overlay-motion"
51
52
 
52
53
  /**
53
54
  * FileViewer — 可延伸的網頁檔案 preview shell(modal fullscreen)
@@ -59,8 +60,8 @@ import type {
59
60
  *
60
61
  * ── 實作基礎 ──
61
62
  * 自建 composite,消費 DS primitives:
62
- * - Radix DialogPrimitive(焦點 trap / Esc / aria-modal,保有 shadcn 結構優勢)
63
- * - `<Empty>` / `<Button>` / `<Input variant="bare">` / `<AspectRatio>` / `<Textarea>` / `<DropdownMenu>`
63
+ * - Radix DialogPrimitive(焦點 trap / Esc / modal 隔離靠 hideOthers aria-hidden — react-dialog@1.1.x 不輸出 aria-modal,保有 shadcn 結構優勢)
64
+ * - `<Empty>` / `<Button>` / `<Input size="sm" autoWidth>`(ZoomInput,default variant)/ `<AspectRatio>` / `<Textarea>` / `<DropdownMenu>`
64
65
  * - `patterns/horizontal-overflow`(filmstrip 溢出捲動)
65
66
  * 不用 DS 的 `<Dialog>` wrapper:因為 FileViewer 需要 edge-to-edge fullscreen
66
67
  * (無 viewport inset / 無 rounded-lg / 無 maxWidth),Dialog 的這些預設都要覆寫。
@@ -179,7 +180,7 @@ interface ZoomInputProps {
179
180
  }
180
181
 
181
182
  /**
182
- * ZoomInput — [−] [% input(bare)with ⌄ menu trigger] [+]
183
+ * ZoomInput — [−] [% input(default variant + autoWidth)with ⌄ menu trigger] [+]
183
184
  *
184
185
  * 世界級對照:Figma zoom control / Adobe Acrobat / Google Slides zoom。
185
186
  *
@@ -233,9 +234,13 @@ const ZoomInput: React.FC<ZoomInputProps> = ({ value, onChange, onFit, labels })
233
234
  — 靠 Radix asChild + ItemInlineActionButton:視覺是 Input + endAction,行為是 chevron-as-trigger
234
235
  — 完全對齊 user AR:「只有 inline action 能觸發選單,menu 對齊 inline action」 */}
235
236
  <DropdownMenu open={menuOpen} onOpenChange={setMenuOpen}>
237
+ {/* maxLength 限縮(2026-07-09 user 要求「找合理最大值避免無限輸入」):autoWidth 下輸入框寬度隨字數長,
238
+ 無上限則可無限打字撐爆 toolbar。max 合法值「400%」= 4 字(對齊 ZOOM_PRESETS / ImageRenderer MAX_SCALE 400;
239
+ commitDraft 另 clamp 10–400),留 1 字編輯緩衝 → maxLength={5}。 */}
236
240
  <Input
237
241
  size="sm"
238
242
  autoWidth
243
+ maxLength={5}
239
244
  aria-label={labels.zoomInput}
240
245
  value={draft}
241
246
  onChange={(e) => setDraft(e.target.value)}
@@ -533,7 +538,7 @@ const InfoPanel = React.memo(function InfoPanel({
533
538
  <span className="text-body font-normal text-foreground">{labels.fileInfoHeading}</span>
534
539
  <DescriptionList direction="horizontal" divided>
535
540
  <DescriptionItem label="檔名">{file.name}</DescriptionItem>
536
- <DescriptionItem label="類型">{file.mimeType || ''}</DescriptionItem>
541
+ <DescriptionItem label="類型">{file.mimeType || '-'}</DescriptionItem>
537
542
  {sizeText && (
538
543
  <DescriptionItem label="大小">
539
544
  <span className="tabular-nums">{sizeText}</span>
@@ -937,6 +942,7 @@ const FileViewer = React.forwardRef<HTMLDivElement, FileViewerProps>(function Fi
937
942
  data-theme="dark"
938
943
  className={cn(
939
944
  'fixed inset-0 z-50 bg-overlay',
945
+ surfaceMotion,
940
946
  'data-[state=open]:animate-in data-[state=closed]:animate-out',
941
947
  'data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0',
942
948
  )}
@@ -946,8 +952,10 @@ const FileViewer = React.forwardRef<HTMLDivElement, FileViewerProps>(function Fi
946
952
  className={cn(
947
953
  // Edge-to-edge fullscreen,無 inset / 無 radius(與一般 Dialog 差別的所在)
948
954
  'fixed inset-0 z-50 outline-none',
955
+ surfaceMotion,
949
956
  'data-[state=open]:animate-in data-[state=closed]:animate-out',
950
957
  'data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0',
958
+ 'data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95',
951
959
  )}
952
960
  // 避免 Radix 自動把焦點送進 Content 的第一個 tabbable —— 我們要留給 viewport
953
961
  onOpenAutoFocus={(e) => e.preventDefault()}