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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (548) hide show
  1. package/CLAUDE.md +1 -1
  2. package/dist/components/Alert/alert.d.ts +5 -0
  3. package/dist/components/Alert/alert.d.ts.map +1 -1
  4. package/dist/components/Alert/alert.js +4 -1
  5. package/dist/components/Alert/alert.js.map +1 -1
  6. package/dist/components/AppShell/app-shell.d.ts +1 -1
  7. package/dist/components/AppShell/app-shell.js.map +1 -1
  8. package/dist/components/AspectRatio/aspect-ratio.d.ts +2 -1
  9. package/dist/components/AspectRatio/aspect-ratio.d.ts.map +1 -1
  10. package/dist/components/AspectRatio/aspect-ratio.js +1 -1
  11. package/dist/components/AspectRatio/aspect-ratio.js.map +1 -1
  12. package/dist/components/Avatar/avatar.d.ts +3 -2
  13. package/dist/components/Avatar/avatar.d.ts.map +1 -1
  14. package/dist/components/Avatar/avatar.js +8 -3
  15. package/dist/components/Avatar/avatar.js.map +1 -1
  16. package/dist/components/Badge/badge.d.ts.map +1 -1
  17. package/dist/components/Badge/badge.js +2 -1
  18. package/dist/components/Badge/badge.js.map +1 -1
  19. package/dist/components/Breadcrumb/breadcrumb.d.ts +14 -1
  20. package/dist/components/Breadcrumb/breadcrumb.d.ts.map +1 -1
  21. package/dist/components/Breadcrumb/breadcrumb.js +9 -3
  22. package/dist/components/Breadcrumb/breadcrumb.js.map +1 -1
  23. package/dist/components/BulkActionBar/bulk-action-bar.d.ts +4 -1
  24. package/dist/components/BulkActionBar/bulk-action-bar.d.ts.map +1 -1
  25. package/dist/components/BulkActionBar/bulk-action-bar.js +2 -2
  26. package/dist/components/BulkActionBar/bulk-action-bar.js.map +1 -1
  27. package/dist/components/Button/button.d.ts +4 -4
  28. package/dist/components/Button/button.d.ts.map +1 -1
  29. package/dist/components/Button/button.js +73 -44
  30. package/dist/components/Button/button.js.map +1 -1
  31. package/dist/components/Button/index.d.ts +1 -0
  32. package/dist/components/Button/index.d.ts.map +1 -1
  33. package/dist/components/Button/index.js +3 -0
  34. package/dist/components/Button/index.js.map +1 -1
  35. package/dist/components/Calendar/calendar.d.ts +3 -1
  36. package/dist/components/Calendar/calendar.d.ts.map +1 -1
  37. package/dist/components/Calendar/calendar.js +40 -17
  38. package/dist/components/Calendar/calendar.js.map +1 -1
  39. package/dist/components/Carousel/carousel.d.ts +1 -1
  40. package/dist/components/Carousel/carousel.d.ts.map +1 -1
  41. package/dist/components/Carousel/carousel.js +3 -1
  42. package/dist/components/Carousel/carousel.js.map +1 -1
  43. package/dist/components/Chart/chart.d.ts.map +1 -1
  44. package/dist/components/Chart/chart.js +1 -1
  45. package/dist/components/Chart/chart.js.map +1 -1
  46. package/dist/components/Checkbox/checkbox.d.ts +6 -3
  47. package/dist/components/Checkbox/checkbox.d.ts.map +1 -1
  48. package/dist/components/Checkbox/checkbox.js +26 -4
  49. package/dist/components/Checkbox/checkbox.js.map +1 -1
  50. package/dist/components/Checkbox/index.d.ts +1 -0
  51. package/dist/components/Checkbox/index.d.ts.map +1 -1
  52. package/dist/components/Checkbox/index.js +3 -0
  53. package/dist/components/Checkbox/index.js.map +1 -1
  54. package/dist/components/Chip/chip.d.ts +9 -3
  55. package/dist/components/Chip/chip.d.ts.map +1 -1
  56. package/dist/components/Chip/chip.js +46 -13
  57. package/dist/components/Chip/chip.js.map +1 -1
  58. package/dist/components/Coachmark/coachmark.d.ts +5 -3
  59. package/dist/components/Coachmark/coachmark.d.ts.map +1 -1
  60. package/dist/components/Coachmark/coachmark.js +10 -7
  61. package/dist/components/Coachmark/coachmark.js.map +1 -1
  62. package/dist/components/Combobox/combobox.d.ts +3 -1
  63. package/dist/components/Combobox/combobox.d.ts.map +1 -1
  64. package/dist/components/Combobox/combobox.js +11 -7
  65. package/dist/components/Combobox/combobox.js.map +1 -1
  66. package/dist/components/Command/command.d.ts +2 -2
  67. package/dist/components/Command/command.d.ts.map +1 -1
  68. package/dist/components/Command/command.js +4 -2
  69. package/dist/components/Command/command.js.map +1 -1
  70. package/dist/components/DataTable/active-editor-controller.d.ts +4 -3
  71. package/dist/components/DataTable/active-editor-controller.d.ts.map +1 -1
  72. package/dist/components/DataTable/cell-registry.d.ts.map +1 -1
  73. package/dist/components/DataTable/cell-registry.js +3 -0
  74. package/dist/components/DataTable/cell-registry.js.map +1 -1
  75. package/dist/components/DataTable/data-table-filter-panel.d.ts.map +1 -1
  76. package/dist/components/DataTable/data-table-interaction-layer.d.ts +4 -3
  77. package/dist/components/DataTable/data-table-interaction-layer.d.ts.map +1 -1
  78. package/dist/components/DataTable/data-table-interaction-layer.js.map +1 -1
  79. package/dist/components/DataTable/data-table.d.ts +38 -7
  80. package/dist/components/DataTable/data-table.d.ts.map +1 -1
  81. package/dist/components/DataTable/data-table.js +213 -63
  82. package/dist/components/DataTable/data-table.js.map +1 -1
  83. package/dist/components/DataTable/filter-tree.d.ts +5 -0
  84. package/dist/components/DataTable/filter-tree.d.ts.map +1 -1
  85. package/dist/components/DateGrid/date-grid.d.ts +3 -3
  86. package/dist/components/DateGrid/date-grid.d.ts.map +1 -1
  87. package/dist/components/DateGrid/date-grid.js +52 -6
  88. package/dist/components/DateGrid/date-grid.js.map +1 -1
  89. package/dist/components/DatePicker/date-picker.d.ts +2 -8
  90. package/dist/components/DatePicker/date-picker.d.ts.map +1 -1
  91. package/dist/components/DatePicker/date-picker.js +139 -109
  92. package/dist/components/DatePicker/date-picker.js.map +1 -1
  93. package/dist/components/DescriptionList/description-list.d.ts.map +1 -1
  94. package/dist/components/DescriptionList/description-list.js +1 -1
  95. package/dist/components/DescriptionList/description-list.js.map +1 -1
  96. package/dist/components/Dialog/dialog.d.ts +1 -1
  97. package/dist/components/Dialog/dialog.d.ts.map +1 -1
  98. package/dist/components/Dialog/dialog.js +2 -1
  99. package/dist/components/Dialog/dialog.js.map +1 -1
  100. package/dist/components/DropdownMenu/dropdown-menu.d.ts +4 -4
  101. package/dist/components/DropdownMenu/dropdown-menu.d.ts.map +1 -1
  102. package/dist/components/DropdownMenu/dropdown-menu.js +25 -8
  103. package/dist/components/DropdownMenu/dropdown-menu.js.map +1 -1
  104. package/dist/components/Empty/empty.d.ts.map +1 -1
  105. package/dist/components/Empty/empty.js +1 -1
  106. package/dist/components/Empty/empty.js.map +1 -1
  107. package/dist/components/Field/field-wrapper.d.ts +1 -0
  108. package/dist/components/Field/field-wrapper.d.ts.map +1 -1
  109. package/dist/components/Field/field-wrapper.js +27 -7
  110. package/dist/components/Field/field-wrapper.js.map +1 -1
  111. package/dist/components/Field/field.d.ts +1 -1
  112. package/dist/components/Field/field.d.ts.map +1 -1
  113. package/dist/components/Field/field.js +2 -1
  114. package/dist/components/Field/field.js.map +1 -1
  115. package/dist/components/Field/use-form-validation.d.ts +5 -1
  116. package/dist/components/Field/use-form-validation.d.ts.map +1 -1
  117. package/dist/components/Field/use-form-validation.js +31 -21
  118. package/dist/components/Field/use-form-validation.js.map +1 -1
  119. package/dist/components/FieldControlGroup/field-control-group.d.ts +3 -3
  120. package/dist/components/FieldControlGroup/field-control-group.js.map +1 -1
  121. package/dist/components/FileItem/file-item.d.ts.map +1 -1
  122. package/dist/components/FileItem/file-item.js +3 -2
  123. package/dist/components/FileItem/file-item.js.map +1 -1
  124. package/dist/components/FileUpload/file-upload.d.ts +1 -1
  125. package/dist/components/FileUpload/file-upload.d.ts.map +1 -1
  126. package/dist/components/FileUpload/file-upload.js +22 -10
  127. package/dist/components/FileUpload/file-upload.js.map +1 -1
  128. package/dist/components/FileViewer/can-render-image.d.ts +6 -0
  129. package/dist/components/FileViewer/can-render-image.d.ts.map +1 -0
  130. package/dist/components/FileViewer/can-render-image.js +11 -0
  131. package/dist/components/FileViewer/can-render-image.js.map +1 -0
  132. package/dist/components/FileViewer/file-viewer.d.ts +2 -2
  133. package/dist/components/FileViewer/file-viewer.d.ts.map +1 -1
  134. package/dist/components/FileViewer/file-viewer.js +24 -16
  135. package/dist/components/FileViewer/file-viewer.js.map +1 -1
  136. package/dist/components/FileViewer/image-renderer.d.ts +0 -5
  137. package/dist/components/FileViewer/image-renderer.d.ts.map +1 -1
  138. package/dist/components/FileViewer/image-renderer.js +1 -9
  139. package/dist/components/FileViewer/image-renderer.js.map +1 -1
  140. package/dist/components/HoverCard/hover-card.d.ts.map +1 -1
  141. package/dist/components/HoverCard/hover-card.js +3 -1
  142. package/dist/components/HoverCard/hover-card.js.map +1 -1
  143. package/dist/components/Input/input.d.ts +26 -3
  144. package/dist/components/Input/input.d.ts.map +1 -1
  145. package/dist/components/Input/input.js +12 -8
  146. package/dist/components/Input/input.js.map +1 -1
  147. package/dist/components/LinkInput/link-input.d.ts +3 -3
  148. package/dist/components/LinkInput/link-input.d.ts.map +1 -1
  149. package/dist/components/LinkInput/link-input.js +29 -28
  150. package/dist/components/LinkInput/link-input.js.map +1 -1
  151. package/dist/components/Menu/menu-item.d.ts.map +1 -1
  152. package/dist/components/Menu/menu-item.js +5 -4
  153. package/dist/components/Menu/menu-item.js.map +1 -1
  154. package/dist/components/Notice/notice.d.ts +5 -0
  155. package/dist/components/Notice/notice.d.ts.map +1 -1
  156. package/dist/components/Notice/notice.js +11 -5
  157. package/dist/components/Notice/notice.js.map +1 -1
  158. package/dist/components/NumberInput/number-input.d.ts +1 -1
  159. package/dist/components/NumberInput/number-input.d.ts.map +1 -1
  160. package/dist/components/NumberInput/number-input.js +17 -8
  161. package/dist/components/NumberInput/number-input.js.map +1 -1
  162. package/dist/components/OverflowIndicator/overflow-indicator.d.ts +1 -1
  163. package/dist/components/OverflowIndicator/overflow-indicator.d.ts.map +1 -1
  164. package/dist/components/OverflowIndicator/overflow-indicator.js +2 -1
  165. package/dist/components/OverflowIndicator/overflow-indicator.js.map +1 -1
  166. package/dist/components/Pagination/index.d.ts +2 -0
  167. package/dist/components/Pagination/index.d.ts.map +1 -0
  168. package/dist/components/Pagination/index.js +6 -0
  169. package/dist/components/Pagination/index.js.map +1 -0
  170. package/dist/components/Pagination/pagination.d.ts +49 -0
  171. package/dist/components/Pagination/pagination.d.ts.map +1 -0
  172. package/dist/components/Pagination/pagination.js +182 -0
  173. package/dist/components/Pagination/pagination.js.map +1 -0
  174. package/dist/components/PeoplePicker/people-picker.d.ts +3 -3
  175. package/dist/components/PeoplePicker/people-picker.d.ts.map +1 -1
  176. package/dist/components/PeoplePicker/people-picker.js +38 -36
  177. package/dist/components/PeoplePicker/people-picker.js.map +1 -1
  178. package/dist/components/PeoplePicker/person-display.d.ts +4 -0
  179. package/dist/components/PeoplePicker/person-display.d.ts.map +1 -1
  180. package/dist/components/PeoplePicker/person-display.js +3 -3
  181. package/dist/components/PeoplePicker/person-display.js.map +1 -1
  182. package/dist/components/Popover/popover.d.ts.map +1 -1
  183. package/dist/components/Popover/popover.js +58 -32
  184. package/dist/components/Popover/popover.js.map +1 -1
  185. package/dist/components/ProfileCard/profile-card.d.ts +11 -3
  186. package/dist/components/ProfileCard/profile-card.d.ts.map +1 -1
  187. package/dist/components/ProfileCard/profile-card.js +14 -4
  188. package/dist/components/ProfileCard/profile-card.js.map +1 -1
  189. package/dist/components/RadioGroup/radio-group.d.ts +1 -1
  190. package/dist/components/RadioGroup/radio-group.d.ts.map +1 -1
  191. package/dist/components/RadioGroup/radio-group.js +28 -3
  192. package/dist/components/RadioGroup/radio-group.js.map +1 -1
  193. package/dist/components/Rating/rating.js +3 -3
  194. package/dist/components/Rating/rating.js.map +1 -1
  195. package/dist/components/ScrollArea/scroll-area.d.ts +1 -1
  196. package/dist/components/ScrollArea/scroll-area.js +2 -2
  197. package/dist/components/ScrollArea/scroll-area.js.map +1 -1
  198. package/dist/components/SegmentedControl/segmented-control.d.ts +2 -2
  199. package/dist/components/SegmentedControl/segmented-control.d.ts.map +1 -1
  200. package/dist/components/SegmentedControl/segmented-control.js +23 -10
  201. package/dist/components/SegmentedControl/segmented-control.js.map +1 -1
  202. package/dist/components/Select/select.d.ts +10 -4
  203. package/dist/components/Select/select.d.ts.map +1 -1
  204. package/dist/components/Select/select.js +57 -14
  205. package/dist/components/Select/select.js.map +1 -1
  206. package/dist/components/SelectMenu/index.js +4 -2
  207. package/dist/components/SelectMenu/select-menu.d.ts +31 -6
  208. package/dist/components/SelectMenu/select-menu.d.ts.map +1 -1
  209. package/dist/components/SelectMenu/select-menu.js +169 -85
  210. package/dist/components/SelectMenu/select-menu.js.map +1 -1
  211. package/dist/components/Separator/separator.js +1 -1
  212. package/dist/components/Separator/separator.js.map +1 -1
  213. package/dist/components/Sheet/sheet.d.ts +1 -1
  214. package/dist/components/Sheet/sheet.d.ts.map +1 -1
  215. package/dist/components/Sheet/sheet.js +3 -2
  216. package/dist/components/Sheet/sheet.js.map +1 -1
  217. package/dist/components/Sidebar/sidebar.d.ts +2 -2
  218. package/dist/components/Sidebar/sidebar.d.ts.map +1 -1
  219. package/dist/components/Sidebar/sidebar.js +28 -10
  220. package/dist/components/Sidebar/sidebar.js.map +1 -1
  221. package/dist/components/Skeleton/skeleton.d.ts +1 -1
  222. package/dist/components/Skeleton/skeleton.js +2 -2
  223. package/dist/components/Skeleton/skeleton.js.map +1 -1
  224. package/dist/components/Slider/slider.d.ts +1 -1
  225. package/dist/components/Slider/slider.d.ts.map +1 -1
  226. package/dist/components/Slider/slider.js +7 -3
  227. package/dist/components/Slider/slider.js.map +1 -1
  228. package/dist/components/Steps/steps.d.ts.map +1 -1
  229. package/dist/components/Steps/steps.js +6 -3
  230. package/dist/components/Steps/steps.js.map +1 -1
  231. package/dist/components/Switch/switch.d.ts +2 -2
  232. package/dist/components/Switch/switch.d.ts.map +1 -1
  233. package/dist/components/Switch/switch.js +47 -9
  234. package/dist/components/Switch/switch.js.map +1 -1
  235. package/dist/components/Tabs/tabs.d.ts +8 -5
  236. package/dist/components/Tabs/tabs.d.ts.map +1 -1
  237. package/dist/components/Tabs/tabs.js +45 -33
  238. package/dist/components/Tabs/tabs.js.map +1 -1
  239. package/dist/components/Tag/tag.d.ts.map +1 -1
  240. package/dist/components/Tag/tag.js +20 -4
  241. package/dist/components/Tag/tag.js.map +1 -1
  242. package/dist/components/Textarea/textarea.d.ts +2 -1
  243. package/dist/components/Textarea/textarea.d.ts.map +1 -1
  244. package/dist/components/Textarea/textarea.js +24 -13
  245. package/dist/components/Textarea/textarea.js.map +1 -1
  246. package/dist/components/TimePicker/time-picker.d.ts +2 -2
  247. package/dist/components/TimePicker/time-picker.d.ts.map +1 -1
  248. package/dist/components/TimePicker/time-picker.js +23 -12
  249. package/dist/components/TimePicker/time-picker.js.map +1 -1
  250. package/dist/components/Toast/toast.d.ts +1 -1
  251. package/dist/components/Toast/toast.d.ts.map +1 -1
  252. package/dist/components/Toast/toast.js +7 -3
  253. package/dist/components/Toast/toast.js.map +1 -1
  254. package/dist/components/Tooltip/tooltip.js +1 -1
  255. package/dist/components/Tooltip/tooltip.js.map +1 -1
  256. package/dist/components/TreeView/tree-view.d.ts +1 -1
  257. package/dist/components/TreeView/tree-view.d.ts.map +1 -1
  258. package/dist/components/TreeView/tree-view.js +11 -5
  259. package/dist/components/TreeView/tree-view.js.map +1 -1
  260. package/dist/hooks/use-controllable.d.ts.map +1 -1
  261. package/dist/hooks/use-controllable.js +4 -2
  262. package/dist/hooks/use-controllable.js.map +1 -1
  263. package/dist/hooks/use-is-touch-device.d.ts.map +1 -1
  264. package/dist/hooks/use-is-touch-device.js +3 -1
  265. package/dist/hooks/use-is-touch-device.js.map +1 -1
  266. package/dist/index.d.ts +2 -0
  267. package/dist/index.d.ts.map +1 -1
  268. package/dist/index.js +14 -0
  269. package/dist/index.js.map +1 -1
  270. package/dist/patterns/element-anatomy/item-anatomy.d.ts +2 -2
  271. package/dist/patterns/element-anatomy/item-anatomy.d.ts.map +1 -1
  272. package/dist/patterns/element-anatomy/item-anatomy.js.map +1 -1
  273. package/dist/patterns/overlay-surface/overlay-surface.js.map +1 -1
  274. package/dist/tokens/categorical-color.d.ts +1 -1
  275. package/dist/tokens/categorical-color.js.map +1 -1
  276. package/dist/tokens/uiSize/icon-size.d.ts +2 -2
  277. package/ds-canonical/fork/governance.lock +1 -1
  278. package/ds-canonical/fork/hooks/check_opacity_token_usage.sh +10 -1
  279. package/ds-canonical/hooks/check_audit_post_report_validator.sh +13 -0
  280. package/ds-canonical/hooks/check_full_story_visual_interaction_sweep.sh +4 -3
  281. package/ds-canonical/hooks/check_opacity_token_usage.sh +10 -1
  282. package/ds-canonical/hooks/check_plugin_fork_health.sh +30 -22
  283. package/ds-canonical/hooks/check_story_invariants.sh +17 -5
  284. package/ds-canonical/hooks/check_substantive_edit_approval_preflight.sh +5 -2
  285. package/ds-canonical/references/principle-dim-map.json +100 -0
  286. package/ds-canonical/references/story-baseline-registry.json +4 -3
  287. package/ds-canonical/skills/deep-audit-cross-codex/SKILL.md +1 -1
  288. package/ds-canonical/skills/design-system-audit/SKILL.md +2 -2
  289. package/ds-story-manifest.json +41 -18
  290. package/llms-full.txt +11 -1
  291. package/llms.txt +3 -1
  292. package/package.json +1 -1
  293. package/src/components/Alert/alert.anatomy.stories.tsx +9 -6
  294. package/src/components/Alert/alert.spec.md +3 -3
  295. package/src/components/Alert/alert.tsx +9 -1
  296. package/src/components/AppShell/app-shell.anatomy.stories.tsx +3 -0
  297. package/src/components/AppShell/app-shell.spec.md +3 -2
  298. package/src/components/AppShell/app-shell.tsx +1 -1
  299. package/src/components/AspectRatio/aspect-ratio.spec.md +1 -2
  300. package/src/components/AspectRatio/aspect-ratio.tsx +3 -2
  301. package/src/components/Avatar/avatar.principles.stories.tsx +1 -1
  302. package/src/components/Avatar/avatar.spec.md +10 -30
  303. package/src/components/Avatar/avatar.stories.tsx +1 -1
  304. package/src/components/Avatar/avatar.tsx +30 -16
  305. package/src/components/Badge/badge.anatomy.stories.tsx +2 -2
  306. package/src/components/Badge/badge.spec.md +5 -4
  307. package/src/components/Badge/badge.tsx +4 -1
  308. package/src/components/Breadcrumb/breadcrumb.anatomy.stories.tsx +2 -2
  309. package/src/components/Breadcrumb/breadcrumb.principles.stories.tsx +15 -10
  310. package/src/components/Breadcrumb/breadcrumb.spec.md +5 -3
  311. package/src/components/Breadcrumb/breadcrumb.tsx +10 -4
  312. package/src/components/BulkActionBar/bulk-action-bar.anatomy.stories.tsx +3 -2
  313. package/src/components/BulkActionBar/bulk-action-bar.spec.md +15 -14
  314. package/src/components/BulkActionBar/bulk-action-bar.tsx +10 -4
  315. package/src/components/Button/button.anatomy.stories.tsx +6 -6
  316. package/src/components/Button/button.spec.md +3 -2
  317. package/src/components/Button/button.tsx +67 -27
  318. package/src/components/Button/index.ts +1 -0
  319. package/src/components/Calendar/calendar.anatomy.stories.tsx +5 -3
  320. package/src/components/Calendar/calendar.principles.stories.tsx +3 -1
  321. package/src/components/Calendar/calendar.spec.md +9 -2
  322. package/src/components/Calendar/calendar.stories.tsx +3 -1
  323. package/src/components/Calendar/calendar.tsx +53 -19
  324. package/src/components/Carousel/carousel.principles.stories.tsx +1 -1
  325. package/src/components/Carousel/carousel.tsx +4 -1
  326. package/src/components/Chart/chart.spec.md +1 -1
  327. package/src/components/Chart/chart.tsx +2 -1
  328. package/src/components/Checkbox/checkbox.principles.stories.tsx +1 -1
  329. package/src/components/Checkbox/checkbox.spec.md +1 -1
  330. package/src/components/Checkbox/checkbox.tsx +41 -7
  331. package/src/components/Checkbox/index.ts +1 -0
  332. package/src/components/Chip/chip.anatomy.stories.tsx +9 -9
  333. package/src/components/Chip/chip.principles.stories.tsx +3 -3
  334. package/src/components/Chip/chip.spec.md +7 -4
  335. package/src/components/Chip/chip.tsx +60 -15
  336. package/src/components/CircularProgress/circular-progress.anatomy.stories.tsx +1 -1
  337. package/src/components/CircularProgress/circular-progress.principles.stories.tsx +7 -5
  338. package/src/components/CircularProgress/circular-progress.spec.md +2 -0
  339. package/src/components/CircularProgress/circular-progress.stories.tsx +3 -11
  340. package/src/components/Coachmark/coachmark.anatomy.stories.tsx +2 -2
  341. package/src/components/Coachmark/coachmark.principles.stories.tsx +1 -1
  342. package/src/components/Coachmark/coachmark.spec.md +3 -3
  343. package/src/components/Coachmark/coachmark.tsx +23 -13
  344. package/src/components/Combobox/combobox.anatomy.stories.tsx +4 -4
  345. package/src/components/Combobox/combobox.spec.md +1 -1
  346. package/src/components/Combobox/combobox.tsx +30 -17
  347. package/src/components/Command/command.principles.stories.tsx +10 -12
  348. package/src/components/Command/command.spec.md +5 -5
  349. package/src/components/Command/command.stories.tsx +4 -4
  350. package/src/components/Command/command.tsx +3 -2
  351. package/src/components/DataTable/active-editor-controller.ts +4 -3
  352. package/src/components/DataTable/cell-registry.tsx +8 -0
  353. package/src/components/DataTable/data-table-filter-panel.tsx +8 -2
  354. package/src/components/DataTable/data-table-interaction-layer.tsx +8 -7
  355. package/src/components/DataTable/data-table.anatomy.stories.tsx +4 -4
  356. package/src/components/DataTable/data-table.css +2 -2
  357. package/src/components/DataTable/data-table.spec.md +72 -115
  358. package/src/components/DataTable/data-table.stories.tsx +51 -3
  359. package/src/components/DataTable/data-table.tsx +348 -63
  360. package/src/components/DataTable/filter-operators.spec.md +4 -4
  361. package/src/components/DataTable/filter-tree.ts +192 -42
  362. package/src/components/DateGrid/date-grid.anatomy.stories.tsx +1 -1
  363. package/src/components/DateGrid/date-grid.spec.md +11 -8
  364. package/src/components/DateGrid/date-grid.tsx +63 -8
  365. package/src/components/DatePicker/date-picker.principles.stories.tsx +1 -1
  366. package/src/components/DatePicker/date-picker.spec.md +1 -0
  367. package/src/components/DatePicker/date-picker.stories.tsx +19 -0
  368. package/src/components/DatePicker/date-picker.tsx +52 -15
  369. package/src/components/DescriptionList/description-list.spec.md +1 -1
  370. package/src/components/DescriptionList/description-list.tsx +3 -1
  371. package/src/components/Dialog/dialog.spec.md +1 -1
  372. package/src/components/Dialog/dialog.tsx +2 -1
  373. package/src/components/DropdownMenu/dropdown-menu.anatomy.stories.tsx +3 -1
  374. package/src/components/DropdownMenu/dropdown-menu.spec.md +8 -5
  375. package/src/components/DropdownMenu/dropdown-menu.tsx +33 -7
  376. package/src/components/Empty/empty.anatomy.stories.tsx +1 -1
  377. package/src/components/Empty/empty.spec.md +2 -2
  378. package/src/components/Empty/empty.stories.tsx +4 -15
  379. package/src/components/Empty/empty.tsx +4 -1
  380. package/src/components/Field/field-controls.spec.md +2 -1
  381. package/src/components/Field/field-wrapper.tsx +28 -8
  382. package/src/components/Field/field.anatomy.stories.tsx +7 -7
  383. package/src/components/Field/field.principles.stories.tsx +1 -1
  384. package/src/components/Field/field.spec.md +9 -7
  385. package/src/components/Field/field.tsx +2 -1
  386. package/src/components/Field/form-validation.spec.md +6 -3
  387. package/src/components/Field/use-form-validation.ts +49 -21
  388. package/src/components/FieldControlGroup/field-control-group.anatomy.stories.tsx +11 -10
  389. package/src/components/FieldControlGroup/field-control-group.principles.stories.tsx +3 -3
  390. package/src/components/FieldControlGroup/field-control-group.spec.md +3 -3
  391. package/src/components/FieldControlGroup/field-control-group.tsx +3 -3
  392. package/src/components/FileItem/file-item.anatomy.stories.tsx +6 -4
  393. package/src/components/FileItem/file-item.tsx +3 -2
  394. package/src/components/FileUpload/file-upload.anatomy.stories.tsx +1 -1
  395. package/src/components/FileUpload/file-upload.spec.md +4 -1
  396. package/src/components/FileUpload/file-upload.tsx +36 -10
  397. package/src/components/FileViewer/can-render-image.ts +15 -0
  398. package/src/components/FileViewer/file-viewer.anatomy.stories.tsx +6 -4
  399. package/src/components/FileViewer/file-viewer.spec.md +2 -2
  400. package/src/components/FileViewer/file-viewer.tsx +47 -21
  401. package/src/components/FileViewer/image-renderer.tsx +3 -8
  402. package/src/components/HoverCard/hover-card.spec.md +1 -1
  403. package/src/components/HoverCard/hover-card.tsx +3 -1
  404. package/src/components/Input/input.anatomy.stories.tsx +4 -4
  405. package/src/components/Input/input.principles.stories.tsx +1 -1
  406. package/src/components/Input/input.spec.md +1 -1
  407. package/src/components/Input/input.stories.tsx +11 -5
  408. package/src/components/Input/input.tsx +11 -9
  409. package/src/components/LinkInput/link-input.anatomy.stories.tsx +2 -0
  410. package/src/components/LinkInput/link-input.tsx +49 -34
  411. package/src/components/Menu/menu-item.anatomy.stories.tsx +1 -1
  412. package/src/components/Menu/menu-item.spec.md +4 -3
  413. package/src/components/Menu/menu-item.tsx +15 -4
  414. package/src/components/Notice/notice.anatomy.stories.tsx +10 -5
  415. package/src/components/Notice/notice.spec.md +4 -3
  416. package/src/components/Notice/notice.tsx +22 -5
  417. package/src/components/NumberInput/number-input.anatomy.stories.tsx +5 -4
  418. package/src/components/NumberInput/number-input.principles.stories.tsx +2 -1
  419. package/src/components/NumberInput/number-input.spec.md +2 -1
  420. package/src/components/NumberInput/number-input.tsx +25 -9
  421. package/src/components/OverflowIndicator/overflow-indicator.anatomy.stories.tsx +3 -3
  422. package/src/components/OverflowIndicator/overflow-indicator.spec.md +1 -1
  423. package/src/components/OverflowIndicator/overflow-indicator.tsx +2 -1
  424. package/src/components/Pagination/index.ts +5 -0
  425. package/src/components/Pagination/pagination.anatomy.stories.tsx +143 -0
  426. package/src/components/Pagination/pagination.principles.stories.tsx +81 -0
  427. package/src/components/Pagination/pagination.spec.md +150 -0
  428. package/src/components/Pagination/pagination.stories.tsx +98 -0
  429. package/src/components/Pagination/pagination.tsx +263 -0
  430. package/src/components/PeoplePicker/people-picker.anatomy.stories.tsx +1 -1
  431. package/src/components/PeoplePicker/people-picker.spec.md +4 -4
  432. package/src/components/PeoplePicker/people-picker.stories.tsx +1 -1
  433. package/src/components/PeoplePicker/people-picker.tsx +65 -55
  434. package/src/components/PeoplePicker/person-display.tsx +10 -4
  435. package/src/components/Popover/popover.anatomy.stories.tsx +2 -2
  436. package/src/components/Popover/popover.spec.md +13 -14
  437. package/src/components/Popover/popover.tsx +88 -39
  438. package/src/components/ProfileCard/profile-card.anatomy.stories.tsx +4 -4
  439. package/src/components/ProfileCard/profile-card.principles.stories.tsx +2 -2
  440. package/src/components/ProfileCard/profile-card.spec.md +1 -2
  441. package/src/components/ProfileCard/profile-card.stories.tsx +1 -1
  442. package/src/components/ProfileCard/profile-card.tsx +29 -8
  443. package/src/components/RadioGroup/radio-group.principles.stories.tsx +1 -1
  444. package/src/components/RadioGroup/radio-group.spec.md +2 -2
  445. package/src/components/RadioGroup/radio-group.stories.tsx +3 -1
  446. package/src/components/RadioGroup/radio-group.tsx +37 -3
  447. package/src/components/Rating/rating.anatomy.stories.tsx +11 -8
  448. package/src/components/Rating/rating.principles.stories.tsx +1 -1
  449. package/src/components/Rating/rating.spec.md +2 -2
  450. package/src/components/Rating/rating.stories.tsx +1 -1
  451. package/src/components/Rating/rating.tsx +3 -3
  452. package/src/components/ScrollArea/scroll-area.spec.md +1 -0
  453. package/src/components/ScrollArea/scroll-area.tsx +1 -1
  454. package/src/components/SegmentedControl/segmented-control.anatomy.stories.tsx +3 -3
  455. package/src/components/SegmentedControl/segmented-control.spec.md +7 -3
  456. package/src/components/SegmentedControl/segmented-control.tsx +29 -11
  457. package/src/components/Select/select.anatomy.stories.tsx +2 -2
  458. package/src/components/Select/select.spec.md +3 -2
  459. package/src/components/Select/select.tsx +62 -19
  460. package/src/components/SelectMenu/select-menu.anatomy.stories.tsx +1 -1
  461. package/src/components/SelectMenu/select-menu.principles.stories.tsx +1 -1
  462. package/src/components/SelectMenu/select-menu.spec.md +12 -8
  463. package/src/components/SelectMenu/select-menu.tsx +176 -20
  464. package/src/components/SelectionControl/selection-item.anatomy.stories.tsx +1 -1
  465. package/src/components/SelectionControl/selection-item.principles.stories.tsx +2 -2
  466. package/src/components/SelectionControl/selection-item.spec.md +2 -1
  467. package/src/components/SelectionControl/selection-item.stories.tsx +1 -1
  468. package/src/components/Separator/separator.anatomy.stories.tsx +2 -2
  469. package/src/components/Separator/separator.tsx +1 -1
  470. package/src/components/Sheet/sheet.spec.md +2 -1
  471. package/src/components/Sheet/sheet.tsx +4 -2
  472. package/src/components/Sidebar/sidebar.anatomy.stories.tsx +1 -1
  473. package/src/components/Sidebar/sidebar.spec.md +2 -1
  474. package/src/components/Sidebar/sidebar.stories.tsx +6 -4
  475. package/src/components/Sidebar/sidebar.tsx +41 -13
  476. package/src/components/Skeleton/skeleton.principles.stories.tsx +1 -1
  477. package/src/components/Skeleton/skeleton.spec.md +1 -2
  478. package/src/components/Skeleton/skeleton.tsx +1 -1
  479. package/src/components/Slider/slider.anatomy.stories.tsx +4 -4
  480. package/src/components/Slider/slider.spec.md +19 -85
  481. package/src/components/Slider/slider.tsx +13 -5
  482. package/src/components/Steps/steps.anatomy.stories.tsx +1 -1
  483. package/src/components/Steps/steps.spec.md +6 -4
  484. package/src/components/Steps/steps.tsx +11 -3
  485. package/src/components/Switch/switch.spec.md +4 -3
  486. package/src/components/Switch/switch.tsx +59 -10
  487. package/src/components/Tabs/tabs.anatomy.stories.tsx +7 -6
  488. package/src/components/Tabs/tabs.principles.stories.tsx +5 -3
  489. package/src/components/Tabs/tabs.spec.md +15 -48
  490. package/src/components/Tabs/tabs.tsx +70 -34
  491. package/src/components/Tag/tag.principles.stories.tsx +2 -2
  492. package/src/components/Tag/tag.spec.md +11 -43
  493. package/src/components/Tag/tag.tsx +30 -4
  494. package/src/components/Textarea/textarea.principles.stories.tsx +2 -1
  495. package/src/components/Textarea/textarea.spec.md +2 -1
  496. package/src/components/Textarea/textarea.tsx +20 -10
  497. package/src/components/TimePicker/time-picker.anatomy.stories.tsx +2 -1
  498. package/src/components/TimePicker/time-picker.spec.md +2 -2
  499. package/src/components/TimePicker/time-picker.tsx +35 -16
  500. package/src/components/Toast/toast.anatomy.stories.tsx +5 -6
  501. package/src/components/Toast/toast.principles.stories.tsx +10 -11
  502. package/src/components/Toast/toast.spec.md +5 -5
  503. package/src/components/Toast/toast.stories.tsx +5 -1
  504. package/src/components/Toast/toast.tsx +13 -7
  505. package/src/components/Tooltip/tooltip.spec.md +3 -3
  506. package/src/components/Tooltip/tooltip.tsx +1 -1
  507. package/src/components/TreeView/tree-view.anatomy.stories.tsx +6 -1
  508. package/src/components/TreeView/tree-view.principles.stories.tsx +2 -2
  509. package/src/components/TreeView/tree-view.spec.md +2 -2
  510. package/src/components/TreeView/tree-view.tsx +27 -5
  511. package/src/hooks/use-controllable.ts +7 -2
  512. package/src/hooks/use-is-touch-device.ts +8 -1
  513. package/src/index.ts +2 -1
  514. package/src/patterns/element-anatomy/element-anatomy.spec.md +3 -3
  515. package/src/patterns/element-anatomy/inline-action.spec.md +1 -1
  516. package/src/patterns/element-anatomy/item-anatomy.spec.md +5 -14
  517. package/src/patterns/element-anatomy/item-anatomy.stories.tsx +1 -0
  518. package/src/patterns/element-anatomy/item-anatomy.tsx +5 -6
  519. package/src/patterns/header-canonical/header-canonical.css +1 -2
  520. package/src/patterns/header-canonical/header-canonical.spec.md +5 -5
  521. package/src/patterns/horizontal-overflow/horizontal-overflow.spec.md +5 -4
  522. package/src/patterns/horizontal-overflow/horizontal-overflow.stories.tsx +137 -0
  523. package/src/patterns/overlay-surface/overlay-surface.spec.md +8 -7
  524. package/src/patterns/overlay-surface/overlay-surface.stories.tsx +116 -0
  525. package/src/patterns/overlay-surface/overlay-surface.tsx +5 -5
  526. package/src/patterns/resize-handle/resize-handle.spec.md +1 -1
  527. package/src/tokens/categorical-color.ts +1 -1
  528. package/src/tokens/color/color.spec.md +11 -13
  529. package/src/tokens/color/color.stories.tsx +1 -1
  530. package/src/tokens/color/primitives.css +7 -5
  531. package/src/tokens/color/semantic.css +8 -2
  532. package/src/tokens/density/density.spec.md +2 -0
  533. package/src/tokens/elevation/elevation.spec.md +1 -1
  534. package/src/tokens/layoutSpace/layoutSpace.spec.md +2 -1
  535. package/src/tokens/layoutSpace/layoutSpace.stories.tsx +3 -3
  536. package/src/tokens/motion/motion.spec.md +1 -1
  537. package/src/tokens/motion/motion.stories.tsx +142 -0
  538. package/src/tokens/opacity/opacity.spec.md +1 -1
  539. package/src/tokens/opacity/opacity.stories.tsx +7 -11
  540. package/src/tokens/radius/radius.spec.md +1 -1
  541. package/src/tokens/typography/typography.css +1 -1
  542. package/src/tokens/typography/typography.spec.md +1 -1
  543. package/src/tokens/uiSize/icon-size.ts +2 -2
  544. package/src/tokens/uiSize/uiSize.spec.md +12 -11
  545. package/src/tokens/uiSize/uiSize.stories.tsx +103 -0
  546. package/src/tokens/utility-registry.json +5 -4
  547. package/dist/node_modules/react-hook-form/dist/index.esm.js +0 -1950
  548. package/dist/node_modules/react-hook-form/dist/index.esm.js.map +0 -1
@@ -222,7 +222,8 @@ export const Overview: Story = {
222
222
  <tbody>
223
223
  {([
224
224
  ['files', 'FileInfo[]', '—', '檔案清單(shell 依 index 渲染當前)'],
225
- ['open', 'boolean', '—', 'Modal 開關(controlled)'],
225
+ ['open', 'boolean', '—', 'Modal 開關(controlled;與 defaultOpen 二擇一)'],
226
+ ['defaultOpen', 'boolean', 'false', 'Modal 開關預設(uncontrolled;對齊 Dialog/Sheet/Popover dual-mode canonical)'],
226
227
  ['onOpenChange', '(open: boolean) => void', '—', 'Modal 開關 callback'],
227
228
  ['initialIndex', 'number', '0', '初始 active 索引(uncontrolled)'],
228
229
  ['index', 'number', '—', 'Active 索引(controlled);不傳則 shell 自理'],
@@ -232,6 +233,7 @@ export const Overview: Story = {
232
233
  ['showFilmstrip', 'boolean', 'false', 'true && files.length>1 → 顯示 filmstrip'],
233
234
  ['allowDownload', 'boolean', 'true', 'true → toolbar 顯示 download 按鈕'],
234
235
  ['onDownload', '(file: FileInfo) => void', '—', '自訂 download(跨域檔案必傳)'],
236
+ ['labels', 'FileViewerLabels', 'DEFAULT_LABELS', 'i18n 文案 override(partial,與 DS 預設 merge;15 keys 覆蓋 toolbar / InfoPanel / filmstrip 的 aria-label 與 placeholder)'],
235
237
  ] as const).map(([p, t, d, desc]) => (
236
238
  <tr key={p}>
237
239
  <Td mono>{p}</Td>
@@ -410,7 +412,7 @@ export const Inspector: Story = {
410
412
  </tr>
411
413
  <tr>
412
414
  <Td>InfoPanel 內 padding</Td>
413
- <Td mono>px-[var(--layout-space-loose)] py-[var(--layout-space-tight)]</Td>
415
+ <Td mono>px-[var(--layout-space-loose)] pt-[var(--layout-space-tight)] pb-[var(--layout-space-loose)]</Td>
414
416
  </tr>
415
417
  <tr>
416
418
  <Td>Filmstrip 高</Td>
@@ -703,7 +705,7 @@ export const SizeMatrix: Story = {
703
705
  <tr>
704
706
  <Td>Body 水平 padding</Td>
705
707
  <Td mono>px-[var(--layout-space-loose)]</Td>
706
- <Td>24 / 32 px(隨 density)</Td>
708
+ <Td>16 / 24 px(隨 density)</Td>
707
709
  </tr>
708
710
  <tr>
709
711
  <Td>Body 垂直 padding</Td>
@@ -713,7 +715,7 @@ export const SizeMatrix: Story = {
713
715
  <tr>
714
716
  <Td>Section 間 gap</Td>
715
717
  <Td mono>gap-[var(--layout-space-loose)]</Td>
716
- <Td>說明 ↔ 檔案資訊 24 / 32 px(隨 density;跨範疇 parallel = loose)</Td>
718
+ <Td>說明 ↔ 檔案資訊 16 / 24 px(隨 density;跨範疇 parallel = loose)</Td>
717
719
  </tr>
718
720
  <tr>
719
721
  <Td>檔案資訊 metadata</Td>
@@ -210,7 +210,7 @@ Shell 看到 `pageNumber` capability 時自動在 toolbar 顯示 page navigator(
210
210
  - **100% 重設**(user 按 `0` 鍵;雙擊是 fit↔100% 條件式 toggle,見下「雙擊」bullet)— `centerZoomedOut: true` 讓 ≤100% 時自動 center,避免內容停在容器角落
211
211
  - 結果:user 看哪,zoom 完 **看哪**,視覺焦點不漂移 — 世界級 media viewer 共識
212
212
  - Min scale 10%,max scale 400%
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:835`),兩者不同(`image-renderer.tsx:144`)
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
216
  ---
@@ -362,7 +362,7 @@ Radix DialogPrimitive 自動處理:
362
362
  - `../DropdownMenu/dropdown-menu.spec.md` — ZoomInput preset / fit 選單消費(取代先前 Popover)
363
363
  - `../Empty/empty.spec.md` — FallbackRenderer 佈局
364
364
  - `../AspectRatio/aspect-ratio.spec.md` — Filmstrip thumb 固定比例
365
- - `../ScrollArea/scroll-area.spec.md` — InfoPanel body 高度受限時 scroll(`file-viewer.tsx:478`);filmstrip 不用 ScrollArea(刻意走 fade-mask,屬 horizontal-overflow pattern)
365
+ - `../ScrollArea/scroll-area.spec.md` — InfoPanel body 高度受限時 scroll(`file-viewer.tsx:493`);filmstrip 不用 ScrollArea(刻意走 fade-mask,屬 horizontal-overflow pattern)
366
366
  - `../../patterns/horizontal-overflow/horizontal-overflow.spec.md` — Filmstrip 水平捲動 + fade mask
367
367
  - `../../patterns/action-bar/action-bar.spec.md` — Toolbar 按鈕順序 canonical
368
368
  - `../FileItem/file-item.spec.md` — 上傳清單列元件(與 FileViewer 不同用途,FileItem 是 list item,FileViewer 是 fullscreen preview)
@@ -15,6 +15,7 @@ import {
15
15
  FileText,
16
16
  } from 'lucide-react'
17
17
  import { cn } from '@/lib/utils'
18
+ import { OVERLAY_SIDE_OFFSET } from '@/design-system/tokens/elevation/overlay-geometry'
18
19
  import { Button } from '@/design-system/components/Button/button'
19
20
  import { Separator } from '@/design-system/components/Separator/separator'
20
21
  import { Input } from '@/design-system/components/Input/input'
@@ -33,14 +34,14 @@ import {
33
34
  DropdownMenuItem,
34
35
  DropdownMenuSeparator,
35
36
  } from '@/design-system/components/DropdownMenu/dropdown-menu'
36
- import {
37
+ import { ARROW_BUTTON_WIDTH,
37
38
  useScrollEdges,
38
39
  useScrollByPage,
39
40
  buildFadeMask,
40
41
  OverflowScrollArrow,
41
42
  } from '@/design-system/patterns/horizontal-overflow/horizontal-overflow'
42
43
  import { useControllable } from '@/design-system/hooks/use-controllable'
43
- import { ImageRenderer, canRenderImage } from './image-renderer'
44
+ import { canRenderImage } from './can-render-image'
44
45
  import type {
45
46
  FileInfo,
46
47
  FileRenderer,
@@ -107,10 +108,18 @@ const fallbackRenderer: FileRenderer = {
107
108
  component: FallbackRenderer,
108
109
  }
109
110
 
111
+ // 2026-07-06 D3 bundle fix:ImageRenderer 改 React.lazy 動態 import——FileViewer 是
112
+ // rare-use modal overlay,react-zoom-pan-pinch(~12KB gz)不該在 viewer 從未打開時就進
113
+ // consumer 首屏 bundle。canRenderImage(registry 判斷 + Filmstrip 分流需同步呼叫)已拆至
114
+ // ./can-render-image 保持靜態 import。
115
+ const LazyImageRenderer = React.lazy(() =>
116
+ import('./image-renderer').then((m) => ({ default: m.ImageRenderer })),
117
+ )
118
+
110
119
  const imageRenderer: FileRenderer = {
111
120
  id: 'image',
112
121
  canRender: canRenderImage,
113
- component: ImageRenderer,
122
+ component: LazyImageRenderer,
114
123
  }
115
124
 
116
125
  // Registry 是 module-singleton:新 renderer 透過 registerFileRenderer 加入。
@@ -256,7 +265,7 @@ const ZoomInput: React.FC<ZoomInputProps> = ({ value, onChange, onFit, labels })
256
265
  Tailwind 條件 class + CSS variable 都要一起帶) */}
257
266
  <DropdownMenuContent
258
267
  align="end"
259
- sideOffset={8}
268
+ sideOffset={OVERLAY_SIDE_OFFSET} // 2026-07-04:M17 — elevation.spec「primitive default 必 import 該 const」
260
269
  // minWidth 對齊 trigger(Input autoWidth),menu 寬度 fit-content 更貼近觸發點視覺中心
261
270
  className="min-w-[9rem] w-auto bg-surface-raised text-foreground border-divider"
262
271
  data-theme="dark"
@@ -438,13 +447,16 @@ function formatBytes(bytes: number | undefined): string | undefined {
438
447
  return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)} GB`
439
448
  }
440
449
 
441
- const InfoPanel: React.FC<InfoPanelProps> = ({
450
+ // React.memo(2026-07-06 D3):同 Filmstrip——wheel-zoom 每 tick 全 chrome re-render,InfoPanel
451
+ // 開啟時(Field/Textarea/DescriptionList subtree)白付。onClose 走 shell handleInfoClose
452
+ // useCallback;file(files[activeIndex] identity 穩定)/readOnly/onDescriptionChange/labels 本已穩定。
453
+ const InfoPanel = React.memo(function InfoPanel({
442
454
  file,
443
455
  readOnly,
444
456
  onDescriptionChange,
445
457
  onClose,
446
458
  labels,
447
- }) => {
459
+ }: InfoPanelProps) {
448
460
  const [draft, setDraft] = React.useState(file.description ?? '')
449
461
 
450
462
  React.useEffect(() => {
@@ -537,7 +549,7 @@ const InfoPanel: React.FC<InfoPanelProps> = ({
537
549
  </ScrollArea>
538
550
  </aside>
539
551
  )
540
- }
552
+ })
541
553
 
542
554
  // ─── Filmstrip ────────────────────────────────────────────────────────────────
543
555
 
@@ -551,10 +563,13 @@ interface FilmstripProps {
551
563
  // code-quality-allow: long-function — foundational composite main body — 拆 sub-fn 會複雜化 local state / ref / context binding
552
564
  const THUMB_SIZE = 64 // px, 固定
553
565
 
554
- const Filmstrip: React.FC<FilmstripProps> = ({ files, activeIndex, onSelect, labels }) => {
566
+ // React.memo(2026-07-06 D3):wheel-zoom 每 tick(trackpad 60Hz+)setZoom shell chrome
567
+ // re-render,Filmstrip(N thumbs × cn + buildFadeMask)是最貴的無關 subtree。props 已全穩定
568
+ // (files=prop / activeIndex=primitive / onSelect=setIndex useCallback / labels=useMemo)→ memo 真命中。
569
+ const Filmstrip = React.memo(function Filmstrip({ files, activeIndex, onSelect, labels }: FilmstripProps) {
555
570
  const { scrollRef, atStart, atEnd, canScroll } = useScrollEdges<HTMLDivElement>()
556
571
  const scrollByPage = useScrollByPage(scrollRef)
557
- const maskImage = buildFadeMask({ canScroll, atStart, atEnd, reserveArrowWidth: 32 })
572
+ const maskImage = buildFadeMask({ canScroll, atStart, atEnd, reserveArrowWidth: ARROW_BUTTON_WIDTH }) // 2026-07-04:消 SSOT 硬寫(spec 禁止重新定義 ARROW_BUTTON_WIDTH)
558
573
 
559
574
  // 切換當前檔案時,自動 scroll 讓 active thumb 可見
560
575
  React.useEffect(() => {
@@ -649,7 +664,7 @@ const Filmstrip: React.FC<FilmstripProps> = ({ files, activeIndex, onSelect, lab
649
664
  )}
650
665
  </div>
651
666
  )
652
- }
667
+ })
653
668
 
654
669
  // ─── FileViewer (shell) ───────────────────────────────────────────────────────
655
670
 
@@ -795,6 +810,10 @@ const FileViewer = React.forwardRef<HTMLDivElement, FileViewerProps>(function Fi
795
810
 
796
811
  // Info panel open state(shell own)
797
812
  const [infoOpen, setInfoOpen] = React.useState(false)
813
+ // 2026-07-06 D3 perf:抽 useCallback 穩定 identity——InfoPanel 上了 React.memo(見上),
814
+ // inline arrow 每 render 新 identity 會打穿 memo shallow-compare。
815
+ const handleInfoToggle = React.useCallback(() => setInfoOpen((o) => !o), [])
816
+ const handleInfoClose = React.useCallback(() => setInfoOpen(false), [])
798
817
 
799
818
  // Zoom state(shell own,renderer 消費 + 回報)
800
819
  const [zoom, setZoom] = React.useState(100)
@@ -960,7 +979,7 @@ const FileViewer = React.forwardRef<HTMLDivElement, FileViewerProps>(function Fi
960
979
  onZoomChange={setZoom}
961
980
  onFit={handleFit}
962
981
  infoOpen={infoOpen}
963
- onInfoToggle={() => setInfoOpen((o) => !o)}
982
+ onInfoToggle={handleInfoToggle}
964
983
  onDownload={handleDownload}
965
984
  allowDownload={allowDownload}
966
985
  onClose={() => setOpen(false)}
@@ -1017,13 +1036,18 @@ const FileViewer = React.forwardRef<HTMLDivElement, FileViewerProps>(function Fi
1017
1036
  </div>
1018
1037
  )}
1019
1038
  <div className="w-full h-full">
1020
- <Renderer.component
1021
- file={file}
1022
- zoom={zoom}
1023
- onZoomChange={setZoom}
1024
- fitRequest={fitRequest}
1025
- onCapabilitiesChange={setCapabilities}
1026
- />
1039
+ {/* Suspense 兜 React.lazy renderer(ImageRenderer)chunk 載入。fallback=null
1040
+ = 既有 loading 行為——spec「Loading」段:shell 不提供全頁 loading state,
1041
+ loading 由 renderer 自理;chunk 載入期間的空 viewport 與 <img> 載入期間視覺一致。 */}
1042
+ <React.Suspense fallback={null}>
1043
+ <Renderer.component
1044
+ file={file}
1045
+ zoom={zoom}
1046
+ onZoomChange={setZoom}
1047
+ fitRequest={fitRequest}
1048
+ onCapabilitiesChange={setCapabilities}
1049
+ />
1050
+ </React.Suspense>
1027
1051
  </div>
1028
1052
  {showArrows && activeIndex < files.length - 1 && (
1029
1053
  <div
@@ -1050,7 +1074,7 @@ const FileViewer = React.forwardRef<HTMLDivElement, FileViewerProps>(function Fi
1050
1074
  file={file}
1051
1075
  readOnly={readOnly}
1052
1076
  onDescriptionChange={onDescriptionChange}
1053
- onClose={() => setInfoOpen(false)}
1077
+ onClose={handleInfoClose}
1054
1078
  labels={labels}
1055
1079
  />
1056
1080
  )}
@@ -1083,9 +1107,11 @@ export const fileViewerMeta = {
1083
1107
  sizes: {
1084
1108
 
1085
1109
  },
1086
- states: ['default', 'hover', 'active', 'focus-visible', 'disabled'],
1110
+ // 'selected' = 縮圖列當前頁持續選中(bg-neutral-selected);'active' 移除 — 全檔無按壓視覺
1111
+ // (工具列按壓屬內嵌 Button meta;2026-07-07 詞彙統一 DS-wide 交叉掃補修)。
1112
+ states: ['default', 'hover', 'selected', 'focus-visible', 'disabled'],
1087
1113
  tokens: {
1088
- bg: ['bg-muted', 'bg-overlay', 'bg-surface-raised'],
1114
+ bg: ['bg-muted', 'bg-neutral-selected', 'bg-overlay', 'bg-surface-raised'],
1089
1115
  fg: ['text-fg-muted', 'text-foreground'],
1090
1116
  ring: ['ring-primary', 'ring-ring'],
1091
1117
  },
@@ -246,11 +246,6 @@ export const ImageRenderer: React.FC<FileRendererProps> = ({
246
246
  }
247
247
  ImageRenderer.displayName = 'ImageRenderer'
248
248
 
249
- const IMAGE_EXTS = new Set(['jpg', 'jpeg', 'png', 'gif', 'webp', 'svg', 'avif', 'bmp', 'ico'])
250
-
251
- /** 判斷檔案是否可用 ImageRenderer 渲染。 */
252
- export function canRenderImage(file: { mimeType: string; name: string }): boolean {
253
- if (file.mimeType.startsWith('image/')) return true
254
- const ext = file.name.split('.').pop()?.toLowerCase()
255
- return ext ? IMAGE_EXTS.has(ext) : false
256
- }
249
+ // canRenderImage 已拆至 ./can-render-image(2026-07-06 D3 bundle fix)——
250
+ // shell 需同步呼叫它,而本檔頂層 import react-zoom-pan-pinch;留在本檔會把整個 lib
251
+ // 拉進 shell 的靜態 import graph,React.lazy 切分就失效。
@@ -141,7 +141,7 @@ HoverCard 是**純行為 primitive**(只提供 hover 觸發邏輯 + 定位 + 動
141
141
 
142
142
  ## A11y 預設
143
143
 
144
- **ARIA / Pattern**:Radix `hover-card` 刻意**不**設 `role` / `aria-*`,定位為 sighted-mouse-user 增強,content 不暴露給輔助技術(grep Radix source 0 命中 role/aria-*)。Radix 只提供:hover / focus 開、Esc 關 + `data-state`。Trigger 的可聚焦性需 consumer 自補(如 Avatar `hoverCard` 在 `avatar.tsx` 手動加 `tabIndex=0` + `role="button"` + `aria-haspopup="dialog"`)。詳 [Radix Accessibility docs](https://www.radix-ui.com/primitives/docs/components/hover-card#accessibility)。
144
+ **ARIA / Pattern**:Radix `hover-card` 刻意**不**設 `role` / `aria-*`,定位為 sighted-mouse-user 增強,content 不暴露給輔助技術(grep Radix source 0 命中 role/aria-*)。Radix 只提供:hover / focus 開、Esc 關 + `data-state`。Trigger 的可聚焦性需 consumer 自補(如 Avatar `hoverCard` 在 `avatar.tsx` 手動加 `tabIndex=0` + `aria-label`;**不掛 role/aria-haspopup**——2026-07-06 拆除,無 activation 行為的 role 宣告 = AT 空承諾,對齊 Radix 官方零 role/aria 定位)。詳 [Radix Accessibility docs](https://www.radix-ui.com/primitives/docs/components/hover-card#accessibility)。
145
145
 
146
146
  **Keyboard 行為**:
147
147
 
@@ -54,7 +54,9 @@ const HoverCardContent = React.forwardRef<
54
54
  // 2026-05-04 viewport-aware max-h SSOT(對齊 Popover):header/footer 永遠 in-viewport,body 壓縮 scroll
55
55
  // 2026-05-05 audit dim 35 fix:加 `min-h-0` 完成 M25 chain invariant
56
56
  "max-h-[var(--radix-hover-card-content-available-height,100vh)] flex flex-col overflow-hidden min-h-0",
57
- "data-[state=open]:animate-in data-[state=closed]:animate-out",
57
+ // 2026-07-05 補 motion-reduce 豁免:對齊 overlay 家族 canonical(popover.tsx / dialog.tsx /
58
+ // dropdown-menu.tsx / tooltip.tsx 同位置寫法)— prefers-reduced-motion 下停用進出場動畫。
59
+ "data-[state=open]:animate-in data-[state=closed]:animate-out motion-reduce:animate-none",
58
60
  "data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
59
61
  "data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95",
60
62
  "data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2",
@@ -58,7 +58,7 @@ const SIZE_SPECS: Record<SizeKey, SizeSpec> = {
58
58
 
59
59
  const MODE_DESC: Record<ModeKey, string> = {
60
60
  edit: '表單可編輯欄位 — bg-surface + border + hover/focus 回饋',
61
- display: '純展示資料 — 無 chrome(transparent)+ 文字正常色,空值顯示 —(em dash)',
61
+ display: '純展示資料 — 無 chrome(transparent + !px-0 !py-0 零內距,field-wrapper.tsx Path Ⅰ)+ 文字正常色,空值顯示 —(em dash)',
62
62
  readonly: '不可編輯但可見 — bg-readonly(neutral-2) + 無邊框 + 文字正常色',
63
63
  disabled: '被停用的欄位 — bg-disabled(neutral-2) + 無邊框 + 文字灰化',
64
64
  }
@@ -360,7 +360,7 @@ const InspectorInner = () => {
360
360
  </div>
361
361
  <div className="flex items-center">
362
362
  <div className="flex items-center rounded-md overflow-hidden" style={{ height: 52, outline: `2px solid ${Z.dim.text}22` }}>
363
- <BpZone w={44} color={Z.pad} label={s.pxToken} sub={`${s.px}px`} />
363
+ <BpZone w={44} color={Z.pad} label={mode === 'display' ? '!px-0' : s.pxToken} sub={mode === 'display' ? '0px' : `${s.px}px`} />
364
364
  {hasStartIcon && (
365
365
  <>
366
366
  <BpZone w={44} color={Z.icon} label={`${s.icon}px`} sub="icon" />
@@ -374,7 +374,7 @@ const InspectorInner = () => {
374
374
  <BpZone w={44} color={Z.action} label={`${s.icon}px`} sub="action" />
375
375
  </>
376
376
  )}
377
- <BpZone w={44} color={Z.pad} label={s.pxToken} sub={`${s.px}px`} />
377
+ <BpZone w={44} color={Z.pad} label={mode === 'display' ? '!px-0' : s.pxToken} sub={mode === 'display' ? '0px' : `${s.px}px`} />
378
378
  </div>
379
379
  <div className="ml-3 flex items-center" style={{ height: 52 }}>
380
380
  <svg width="10" height="52" className="shrink-0">
@@ -425,7 +425,7 @@ const InspectorInner = () => {
425
425
  <div className="px-4 py-1">
426
426
  <div className="py-2 border-b border-divider"><span className="text-[10px] font-semibold text-fg-muted uppercase tracking-wider">Layout</span></div>
427
427
  <PropRow label="高度" dot={Z.dim.text}><TkVal token={s.heightToken} value={s.height} /></PropRow>
428
- <PropRow label="左右內距" dot={Z.pad.text}><TkVal token={s.pxToken} value={`${s.px}px`} /></PropRow>
428
+ <PropRow label="左右內距" dot={Z.pad.text}><TkVal token={mode === 'display' ? '!px-0 !py-0' : s.pxToken} value={mode === 'display' ? '0px(零 chrome)' : `${s.px}px`} /></PropRow>
429
429
  <PropRow label="元素間距" dot={Z.gap.text}><TkVal token={s.gapToken} value={`${s.gap}px`} /></PropRow>
430
430
  {hasStartIcon && (
431
431
  <PropRow label="Icon 尺寸" dot={Z.icon.text}>{s.icon}px</PropRow>
@@ -35,7 +35,7 @@ const Label = ({ children, warn }: { children: React.ReactNode; warn?: boolean }
35
35
 
36
36
  const Section = ({ title, children }: { title: string; children: React.ReactNode }) => (
37
37
  <section className="mb-12">
38
- <h2 className="text-heading-3 font-bold text-foreground mb-4 pb-2 border-b border-border">{title}</h2>
38
+ <h2 className="text-h3 font-bold text-foreground mb-4 pb-2 border-b border-border">{title}</h2>
39
39
  {children}
40
40
  </section>
41
41
  )
@@ -109,7 +109,7 @@ endAction(host 內嵌 inline action)vs 獨立 `<Button iconOnly>` 的分界詳 `
109
109
 
110
110
  ## 邊界案例
111
111
 
112
- - **Disabled**:`disabled` prop 由 Field SSOT own(`Field/field.spec.md`「Field state machine SSOT」段)。視覺 token:wrapper bg → `bg-disabled`(neutral-2)、text → `text-fg-disabled`(neutral-6,M24 state>emphasis canonical)、startIcon → `text-fg-disabled`、endAction button 自動 disabled、cursor → `cursor-not-allowed`、border 弱化、無 hover/focus ring。`readOnly` 與 `disabled` 視覺分離:readOnly 維持 default text color(可選取複製)、disabled 全面弱化(不可選)。
112
+ - **Disabled**:`disabled` prop 由 Field SSOT own(`Field/field.spec.md`「Field state machine SSOT」段)。視覺 token:wrapper bg → `bg-disabled`(neutral-2)、text → `text-fg-disabled`(neutral-6,M24 state>emphasis canonical)、startIcon → `text-fg-disabled`、endAction 不渲染(僅 edit mode 渲染)、cursor → `cursor-not-allowed`、border 弱化、無 hover/focus ring。`readOnly` 與 `disabled` 視覺分離:readOnly 維持 default text color(可選取複製)、disabled 全面弱化(不可選)。
113
113
  - **Loading**:已 codify(見「Loading」段)。`loading=true` 時 endAction slot 自動切 `<CircularProgress/>`,input 仍可編輯(Ant Search 派 idiom)。
114
114
  - **Empty(no value)**:placeholder 走 `text-fg-muted`(neutral-7);`disabled + empty` 時 placeholder 切 `text-fg-disabled`(M24 state precedence)。
115
115
  - **Dark mode**:走 semantic token 自動 adapt,無 per-component override。
@@ -2,6 +2,7 @@ import React from 'react'
2
2
  import type { Meta, StoryObj } from '@storybook/react'
3
3
  import { Search, Eye, EyeOff, X } from 'lucide-react'
4
4
  import { Input } from './input'
5
+ import { Field, FieldLabel, FieldError } from '@/design-system/components/Field/field'
5
6
  import { Button } from '@/design-system/components/Button/button'
6
7
  import { Tooltip, TooltipContent, TooltipTrigger } from '@/design-system/components/Tooltip/tooltip'
7
8
 
@@ -114,15 +115,20 @@ export const EndAction: Story = {
114
115
  }
115
116
 
116
117
  /* ── Error ── */
118
+ // 2026-07-04 audit Dim 24/M23(d):錯誤訊息改消費 Field + FieldError primitive(對齊 Textarea / TimePicker /
119
+ // Rating showcase idiom),不手刻 <p text-error>(重複 FieldError 內部樣式 = drift 來源)。
117
120
  export const ErrorState: Story = {
118
121
  name: '錯誤狀態',
119
122
  render: () => (
120
123
  <div className="flex flex-col gap-4 max-w-sm">
121
- <p className="text-caption text-fg-muted">Error 以紅色邊框表示。錯誤訊息由 Form help text 提供,不在 input 內放狀態 icon</p>
122
- <div>
123
- <Input error defaultValue="invalid-email@" />
124
- <p className="text-caption text-error mt-1">請輸入有效的 email 地址</p>
125
- </div>
124
+ <p className="text-caption text-fg-muted">Error 以紅色邊框表示。錯誤訊息由 FieldError 提供,不在 input 內放狀態 icon</p>
125
+ {/* Field invalid 自動 cascade 到 Input(useResolvedFieldInvalid),不需重複傳 error;
126
+ standalone <Input error> 用法由 anatomy「+ error prop」section 展示 */}
127
+ <Field invalid>
128
+ <FieldLabel>Email</FieldLabel>
129
+ <Input defaultValue="invalid-email@" />
130
+ <FieldError>請輸入有效的 email 地址</FieldError>
131
+ </Field>
126
132
  </div>
127
133
  ),
128
134
  }
@@ -155,11 +155,7 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
155
155
  return (
156
156
  <div
157
157
  className={cn(
158
- fieldWrapperStyles({ mode: resolvedMode, variant: variant, size }),
159
- isEditable && resolvedError && [
160
- 'border-error hover:border-error-hover',
161
- 'focus-within:border-error focus-within:hover:border-error',
162
- ],
158
+ fieldWrapperStyles({ mode: resolvedMode, variant: variant, size, error: resolvedError }),
163
159
  // autoWidth:wrapper 縮到 inline-flex + w-auto,讓寬度由 startIcon + input(field-sizing: content)+ endAction 自然累加
164
160
  autoWidth && 'inline-flex w-auto',
165
161
  className,
@@ -215,17 +211,23 @@ Input.displayName = 'Input'
215
211
  // 對齊 Carbon read-only / PatternFly inline-edit hidden-input / Cloudscape display-mode 統一 mode 模型。
216
212
 
217
213
  // Story auto-compile metadata — Phase 1 mechanical migration(2026-04-24)
218
- // Phase 2 fill needed: purpose descriptions + when rationale + world-class refs
214
+ // 2026-07-05 Phase 2 fill(deep-audit A.1b):variants input.tsx variant jsdoc、sizes
215
+ // fieldWrapperStyles cva 真值(h-field-sm/md/lg = 28/32/36,uiSize.css)+ ICON_SIZE SSOT
216
+ // (sm/md 16、lg 20)填。`naked` 為 @internal cell-as-input substrate → 不列入 public variants。
219
217
  export const inputMeta = {
220
218
  component: 'Input',
221
219
  family: 4,
222
220
  variants: {
223
-
221
+ default: { purpose: 'Field wrapper 完整 chrome(bg-surface + border + hover/focus 回饋)— 表單 / Field 內嵌' },
222
+ bare: { purpose: '透明 chrome,hover / focus 才現 border — Toolbar inline editing(zoom input / config toolbar)' },
224
223
  },
225
224
  sizes: {
226
-
225
+ sm: { fieldHeight: 28, iconSize: 16, typography: 'body' },
226
+ md: { fieldHeight: 32, iconSize: 16, typography: 'body' },
227
+ lg: { fieldHeight: 36, iconSize: 20, typography: 'body-lg' },
227
228
  },
228
- states: ['default', 'hover', 'active', 'focus-visible', 'disabled'],
229
+ // 'active' 移除 Field chrome 無按壓態(對齊 Textarea / LinkInput meta 既有寫法)(2026-07-07 詞彙統一 DS-wide 按壓訊號盤點:檔內 0 active: utility / 0 *-active token)。
230
+ states: ['default', 'hover', 'focus-visible', 'disabled'],
229
231
  tokens: {
230
232
  bg: ['bg-surface'],
231
233
  fg: ['text-fg-disabled', 'text-fg-muted'],
@@ -257,6 +257,7 @@ export const Overview = {
257
257
  <tbody>
258
258
  {[
259
259
  ['mode', "'edit' | 'display' | 'readonly' | 'disabled'", "'edit'", 'FieldMode 四模式'],
260
+ ['variant', "'default' | 'bare' | 'naked'", "'default'", 'Field chrome variant(bare:toolbar / cell-as-input 透明 chrome)'],
260
261
  ['size', "'sm' | 'md' | 'lg'", "'md'", '尺寸,與 Button 共用 token'],
261
262
  ['error', 'boolean', 'false', '錯誤狀態,紅色邊框 + aria-invalid'],
262
263
  ['value', 'string | null', '—', 'URL 值'],
@@ -264,6 +265,7 @@ export const Overview = {
264
265
  ['placeholder', 'string', "'https://'", '空值時的佔位文字'],
265
266
  ['disabled', 'boolean', 'false', '等同 mode="disabled"'],
266
267
  ['label', 'string', '—', '自訂顯示文字(覆蓋自動 hostname 提取)'],
268
+ ['showDisplayEndIcon', 'boolean', 'false', 'display 態改包 Field naked wrapper(D-path opt-in,DataTable cell display↔edit 像素對齊;本元件 edit 無 endIcon,故僅 wrapper 無 ItemSuffix)'],
267
269
  ].map(([p, t, d, desc]) => (
268
270
  <tr key={p}><Td mono>{p}</Td><Td mono>{t}</Td><Td mono>{d}</Td><Td>{desc}</Td></tr>
269
271
  ))}
@@ -4,7 +4,7 @@ import type { VariantProps } from 'class-variance-authority'
4
4
  import { cn } from '@/lib/utils'
5
5
  import type { FieldMode, FieldVariant } from '@/design-system/components/Field/field-types'
6
6
  import { fieldWrapperStyles, bareInputStyles, EMPTY_DISPLAY, fieldDisplayTextClass } from '@/design-system/components/Field/field-wrapper'
7
- import { useFieldContext, useResolvedFieldSize, useResolvedFieldDisabled, useResolvedFieldMode, useResolvedFieldVariant } from '@/design-system/components/Field/field-context'
7
+ import { useFieldContext, useResolvedFieldSize, useResolvedFieldDisabled, useResolvedFieldMode, useResolvedFieldVariant, useResolvedFieldInvalid } from '@/design-system/components/Field/field-context'
8
8
  import { ItemInlineAction } from '@/design-system/patterns/element-anatomy/item-anatomy'
9
9
 
10
10
  // ── URL Validation ──────────────────────────────────────────────────────────
@@ -48,7 +48,10 @@ function renderLinkAnchor(value: string, label?: string) {
48
48
  // ── Component ───────────────────────────────────────────────────────────────
49
49
 
50
50
  export interface LinkInputProps
51
- extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'size' | 'value' | 'onChange'>,
51
+ // `defaultValue` 一併 Omit(2026-07-05 deep-audit A.1b):spec.md「controlled-only,不支援
52
+ // defaultValue」宣稱的型別面機械封鎖 — 原本仍在型別 surface 且經 {...props} spread 到已
53
+ // controlled 的 input(React dev warning)。
54
+ extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'size' | 'value' | 'defaultValue' | 'onChange'>,
52
55
  Omit<VariantProps<typeof fieldWrapperStyles>, 'mode' | 'variant'> {
53
56
  mode?: FieldMode
54
57
  /**
@@ -110,6 +113,37 @@ const LinkInput = React.forwardRef<HTMLInputElement, LinkInputProps>(
110
113
  // chrome resolution:per-prop > context > 'default'
111
114
  const resolvedVariant: FieldVariant = useResolvedFieldVariant(variantProp)
112
115
 
116
+ // 2026-07-05 D4:invalid 經 useResolvedFieldInvalid 統一接 fieldCtx.invalid(field-controls.spec.md
117
+ // resolver 契約「error/invalid = prop OR fieldCtx.invalid」)— 原本紅框只吃 errorProp||localError、
118
+ // aria-invalid 卻 inline 加 fieldCtx?.invalid → <Field invalid> 下 AT 聽到 invalid 視覺卻正常框
119
+ // (聚焦還變藍)。紅框與 aria 自此同源。宣告於 display early return 之前(Rules-of-Hooks)。
120
+ const fieldInvalid = useResolvedFieldInvalid(errorProp)
121
+
122
+ // ── Local state / refs ──────────────────────────────────────────────────
123
+ // 2026-07-04 Rules-of-Hooks 修:hooks 必在 display early return 之前宣告,
124
+ // 否則 mode 於 render 間切換 display↔edit 會改變 hook 呼叫數 → React #310 crash
125
+ // (同 beta.76 Combobox 同款修法;僅搬位置,hook 邏輯不變)。
126
+ const [editing, setEditing] = React.useState(false)
127
+ const [localValue, setLocalValue] = React.useState(value ?? '')
128
+ const [localError, setLocalError] = React.useState(false)
129
+ const inputRef = React.useRef<HTMLInputElement | null>(null)
130
+
131
+ // Sync external value → local
132
+ React.useEffect(() => {
133
+ if (!editing) setLocalValue(value ?? '')
134
+ }, [value, editing])
135
+
136
+ // Merge refs
137
+ const setRef = React.useCallback((el: HTMLInputElement | null) => {
138
+ inputRef.current = el
139
+ if (typeof ref === 'function') ref(el)
140
+ else if (ref) (ref as React.MutableRefObject<HTMLInputElement | null>).current = el
141
+ }, [ref])
142
+
143
+ // 2026-05-16 audit codex Round 6:capture rAF + cancel on unmount(defensive hygiene)
144
+ const focusRafIdRef = React.useRef<number>(0)
145
+ React.useEffect(() => () => { if (focusRafIdRef.current) cancelAnimationFrame(focusRafIdRef.current) }, [])
146
+
113
147
  // ── mode='display' ─────────────────────────────────────────────────────
114
148
  // 純展示:無 input chrome / 無 hover affordance / 無 Pencil edit 入口。
115
149
  // 取代既有 LinkInputDisplay sub-component(2026-05-05 Phase B3 retire)。
@@ -140,30 +174,9 @@ const LinkInput = React.forwardRef<HTMLInputElement, LinkInputProps>(
140
174
  )
141
175
  }
142
176
 
143
- const [editing, setEditing] = React.useState(false)
144
- const [localValue, setLocalValue] = React.useState(value ?? '')
145
- const [localError, setLocalError] = React.useState(false)
146
- const inputRef = React.useRef<HTMLInputElement | null>(null)
147
-
148
- // Sync external value → local
149
- React.useEffect(() => {
150
- if (!editing) setLocalValue(value ?? '')
151
- }, [value, editing])
152
-
153
- // Merge refs
154
- const setRef = React.useCallback((el: HTMLInputElement | null) => {
155
- inputRef.current = el
156
- if (typeof ref === 'function') ref(el)
157
- else if (ref) (ref as React.MutableRefObject<HTMLInputElement | null>).current = el
158
- }, [ref])
159
-
160
177
  const hasValidValue = !!value && isValidUrl(value)
161
178
  const showLink = isEditable && hasValidValue && !editing && !localError
162
- const error = errorProp || localError
163
-
164
- // 2026-05-16 audit codex Round 6:capture rAF + cancel on unmount(defensive hygiene)
165
- const focusRafIdRef = React.useRef<number>(0)
166
- React.useEffect(() => () => { if (focusRafIdRef.current) cancelAnimationFrame(focusRafIdRef.current) }, [])
179
+ const error = fieldInvalid || localError
167
180
 
168
181
  const handleEdit = () => {
169
182
  setEditing(true)
@@ -233,8 +246,11 @@ const LinkInput = React.forwardRef<HTMLInputElement, LinkInputProps>(
233
246
  if (showLink) {
234
247
  return (
235
248
  <div
236
- className={cn(fieldWrapperStyles({ mode: 'edit', variant: resolvedVariant, size }), className)}
249
+ // 2026-07-05 D4:link 顯示分支同樣接 error(errorProp / fieldCtx.invalid)紅框,
250
+ // 否則 <Field invalid> 下 link state 完全無 error 呈現
251
+ className={cn(fieldWrapperStyles({ mode: 'edit', variant: resolvedVariant, size, error }), className)}
237
252
  data-field-mode="edit"
253
+ data-error={error ? '' : undefined}
238
254
  >
239
255
  <span className="flex-1 min-w-0">
240
256
  {value && renderLinkAnchor(value, label)}
@@ -251,11 +267,7 @@ const LinkInput = React.forwardRef<HTMLInputElement, LinkInputProps>(
251
267
  return (
252
268
  <div
253
269
  className={cn(
254
- fieldWrapperStyles({ mode: 'edit', variant: resolvedVariant, size }),
255
- error && [
256
- 'border-error hover:border-error-hover',
257
- 'focus-within:border-error focus-within:hover:border-error',
258
- ],
270
+ fieldWrapperStyles({ mode: 'edit', variant: resolvedVariant, size, error }),
259
271
  className,
260
272
  )}
261
273
  data-field-mode="edit"
@@ -270,10 +282,10 @@ const LinkInput = React.forwardRef<HTMLInputElement, LinkInputProps>(
270
282
  onBlur={handleBlur}
271
283
  onKeyDown={handleKeyDown}
272
284
  placeholder={placeholder}
273
- aria-invalid={(error || fieldCtx?.invalid) || undefined}
285
+ aria-invalid={error || undefined}
274
286
  aria-required={fieldCtx?.required || undefined}
275
287
  aria-describedby={ariaDescribedByProp ?? fieldCtx?.descriptionId}
276
- aria-errormessage={ariaErrorMessageProp ?? ((error || fieldCtx?.invalid) ? fieldCtx?.errorId : undefined)}
288
+ aria-errormessage={ariaErrorMessageProp ?? (error ? fieldCtx?.errorId : undefined)}
277
289
  className={bareInputStyles}
278
290
  {...props}
279
291
  />
@@ -294,9 +306,12 @@ export const linkInputMeta = {
294
306
  sizes: {
295
307
 
296
308
  },
297
- states: ['default', 'hover', 'active', 'focus-visible', 'disabled'],
309
+ // states 對齊真實 state 集:text-input 家族無 'active'(按下)專屬視覺態(同 textareaMeta 修法);
310
+ // readonly / error 為 fieldWrapperStyles mode + errorProp/localError 實有狀態(2026-07-04 audit 對齊)。
311
+ states: ['default', 'hover', 'focus-visible', 'readonly', 'disabled', 'error'],
298
312
  tokens: {
299
- bg: [],
313
+ // bg 經 fieldWrapperStyles 實渲:edit bg-surface / readonly bg-readonly / disabled bg-disabled(field-wrapper.tsx)。
314
+ bg: ['bg-surface', 'bg-readonly', 'bg-disabled'],
300
315
  fg: ['text-fg-disabled', 'text-fg-muted', 'text-primary'],
301
316
  ring: [],
302
317
  },
@@ -774,7 +774,7 @@ export const Accessibility = {
774
774
  render: () => (
775
775
  <div className="max-w-3xl text-body text-fg-secondary">
776
776
  <h3 className="text-h5 text-foreground mb-2">無障礙設計</h3>
777
- <p className="whitespace-pre-line">{"MenuItem 只負責單行的視覺排版,本身不接管鍵盤與焦點。真正的鍵盤導覽(上下鍵切換、Enter 選取、Esc 關閉)與焦點管理由外層的選單元件(SelectMenu / DropdownMenu)負責——MenuItem 不重複實作這些行為。\n\n 語意 role :在 listbox 語境(SelectMenu / Combobox)MenuItem 預設渲染為 role=\"option\";在 menu 語境(DropdownMenu)外層會把每個 item 覆蓋為 role=\"presentation\",由 Radix parent 持有 role=\"menuitem\" 語意。哪一項目前被聚焦與選取由外層選單決定。\n\n 焦點外觀 :聚焦時以 bg-neutral-hover 背景高亮標示被聚焦的選項(cva base 為 outline-none + focus-visible:bg-neutral-hover),對齊 menu/listbox option 的 active-highlight 慣例,而非畫 outline ring。\n\n 驗證 :Storybook a11y addon 面板應 0 critical violation;整個選單用鍵盤即可完整操作(無需滑鼠);文字對比 ≥ 4.5:1、介面元素對比 ≥ 3:1(WCAG AA)。"}</p>
777
+ <p className="whitespace-pre-line">{"MenuItem 只負責單行的視覺排版,本身不接管鍵盤與焦點。真正的鍵盤導覽(上下鍵切換、Enter 選取、Esc 關閉)與焦點管理由外層的選單元件(SelectMenu / DropdownMenu)負責——MenuItem 不重複實作這些行為。\n\n 語意 role :直接使用時(如本元件 stories listbox demo)MenuItem 預設渲染為 role=\"option\" 並帶 aria-selected / aria-disabled;巢狀在外層互動節點內時(SelectMenu / Combobox 的 cmdk option、DropdownMenu Radix menuitem)外層會傳 role=\"presentation\",MenuItem 同步抑制 aria-selected / aria-disabled——ARIA 語意由外層節點單一持有,內層僅保留 data-* 樣式屬性。哪一項目前被聚焦與選取由外層選單決定。\n\n 焦點外觀 :聚焦時以 bg-neutral-hover 背景高亮標示被聚焦的選項(cva base 為 outline-none + focus-visible:bg-neutral-hover),對齊 menu/listbox option 的 active-highlight 慣例,而非畫 outline ring。\n\n 驗證 :Storybook a11y addon 面板應 0 critical violation;整個選單用鍵盤即可完整操作(無需滑鼠);文字對比 ≥ 4.5:1、介面元素對比 ≥ 3:1(WCAG AA)。"}</p>
778
778
  </div>
779
779
  ),
780
780
  }