@qijenchen/design-system 0.1.0-beta.95 → 0.1.0-beta.99

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 (355) hide show
  1. package/README.md +1 -0
  2. package/dist/components/AccountMenu/account-menu.d.ts +86 -0
  3. package/dist/components/AccountMenu/account-menu.d.ts.map +1 -0
  4. package/dist/components/AccountMenu/account-menu.js +64 -0
  5. package/dist/components/AccountMenu/account-menu.js.map +1 -0
  6. package/dist/components/AccountMenu/index.d.ts +2 -0
  7. package/dist/components/AccountMenu/index.d.ts.map +1 -0
  8. package/dist/components/AccountMenu/index.js +6 -0
  9. package/dist/components/AccountMenu/index.js.map +1 -0
  10. package/dist/components/AppShell/_demo-helpers.d.ts.map +1 -1
  11. package/dist/components/AppShell/app-shell.d.ts.map +1 -1
  12. package/dist/components/AppShell/app-shell.js +2 -2
  13. package/dist/components/AppShell/app-shell.js.map +1 -1
  14. package/dist/components/Badge/badge.d.ts +1 -1
  15. package/dist/components/Button/button.d.ts +3 -3
  16. package/dist/components/Button/button.d.ts.map +1 -1
  17. package/dist/components/Button/button.js +3 -2
  18. package/dist/components/Button/button.js.map +1 -1
  19. package/dist/components/Calendar/calendar.d.ts +10 -21
  20. package/dist/components/Calendar/calendar.d.ts.map +1 -1
  21. package/dist/components/Calendar/calendar.js +52 -59
  22. package/dist/components/Calendar/calendar.js.map +1 -1
  23. package/dist/components/Combobox/combobox.d.ts.map +1 -1
  24. package/dist/components/Combobox/combobox.js +1 -0
  25. package/dist/components/Combobox/combobox.js.map +1 -1
  26. package/dist/components/Command/command.d.ts.map +1 -1
  27. package/dist/components/Command/command.js +3 -1
  28. package/dist/components/Command/command.js.map +1 -1
  29. package/dist/components/DataTable/cell-registry.d.ts.map +1 -1
  30. package/dist/components/DataTable/cell-registry.js +1 -5
  31. package/dist/components/DataTable/cell-registry.js.map +1 -1
  32. package/dist/components/DataTable/data-table-filter-group.d.ts +6 -2
  33. package/dist/components/DataTable/data-table-filter-group.d.ts.map +1 -1
  34. package/dist/components/DataTable/data-table-filter-group.js +32 -27
  35. package/dist/components/DataTable/data-table-filter-group.js.map +1 -1
  36. package/dist/components/DataTable/data-table-filter-labels.d.ts +44 -0
  37. package/dist/components/DataTable/data-table-filter-labels.d.ts.map +1 -0
  38. package/dist/components/DataTable/data-table-filter-labels.js +55 -0
  39. package/dist/components/DataTable/data-table-filter-labels.js.map +1 -0
  40. package/dist/components/DataTable/data-table-filter-panel.d.ts +11 -0
  41. package/dist/components/DataTable/data-table-filter-panel.d.ts.map +1 -1
  42. package/dist/components/DataTable/data-table-filter-panel.js +33 -10
  43. package/dist/components/DataTable/data-table-filter-panel.js.map +1 -1
  44. package/dist/components/DataTable/data-table-filter-value-picker.d.ts +4 -1
  45. package/dist/components/DataTable/data-table-filter-value-picker.d.ts.map +1 -1
  46. package/dist/components/DataTable/data-table-filter-value-picker.js +22 -8
  47. package/dist/components/DataTable/data-table-filter-value-picker.js.map +1 -1
  48. package/dist/components/DataTable/data-table.d.ts.map +1 -1
  49. package/dist/components/DataTable/data-table.js +5 -4
  50. package/dist/components/DataTable/data-table.js.map +1 -1
  51. package/dist/components/DataTable/index.js +2 -0
  52. package/dist/components/DataTable/index.js.map +1 -1
  53. package/dist/components/DatePicker/date-picker.d.ts.map +1 -1
  54. package/dist/components/DatePicker/date-picker.js +187 -156
  55. package/dist/components/DatePicker/date-picker.js.map +1 -1
  56. package/dist/components/Dialog/dialog.d.ts +1 -1
  57. package/dist/components/Dialog/dialog.d.ts.map +1 -1
  58. package/dist/components/Dialog/dialog.js +4 -2
  59. package/dist/components/Dialog/dialog.js.map +1 -1
  60. package/dist/components/DropdownMenu/dropdown-menu.d.ts +4 -1
  61. package/dist/components/DropdownMenu/dropdown-menu.d.ts.map +1 -1
  62. package/dist/components/DropdownMenu/dropdown-menu.js +46 -14
  63. package/dist/components/DropdownMenu/dropdown-menu.js.map +1 -1
  64. package/dist/components/Field/field-context.d.ts +12 -0
  65. package/dist/components/Field/field-context.d.ts.map +1 -1
  66. package/dist/components/Field/field-context.js +10 -1
  67. package/dist/components/Field/field-context.js.map +1 -1
  68. package/dist/components/Field/field-wrapper.d.ts.map +1 -1
  69. package/dist/components/Field/field-wrapper.js +7 -1
  70. package/dist/components/Field/field-wrapper.js.map +1 -1
  71. package/dist/components/Field/field.d.ts.map +1 -1
  72. package/dist/components/Field/field.js +11 -2
  73. package/dist/components/Field/field.js.map +1 -1
  74. package/dist/components/FieldControlGroup/field-control-group.d.ts +2 -5
  75. package/dist/components/FieldControlGroup/field-control-group.d.ts.map +1 -1
  76. package/dist/components/FieldControlGroup/field-control-group.js +2 -2
  77. package/dist/components/FieldControlGroup/field-control-group.js.map +1 -1
  78. package/dist/components/FileUpload/file-upload.js +1 -1
  79. package/dist/components/FileUpload/file-upload.js.map +1 -1
  80. package/dist/components/FileViewer/file-viewer.d.ts +1 -1
  81. package/dist/components/FileViewer/file-viewer.d.ts.map +1 -1
  82. package/dist/components/FileViewer/file-viewer.js +20 -3
  83. package/dist/components/FileViewer/file-viewer.js.map +1 -1
  84. package/dist/components/InlineEdit/inline-edit.d.ts +5 -0
  85. package/dist/components/InlineEdit/inline-edit.d.ts.map +1 -1
  86. package/dist/components/InlineEdit/inline-edit.js +65 -33
  87. package/dist/components/InlineEdit/inline-edit.js.map +1 -1
  88. package/dist/components/Input/input.d.ts.map +1 -1
  89. package/dist/components/Input/input.js +3 -2
  90. package/dist/components/Input/input.js.map +1 -1
  91. package/dist/components/LinkInput/link-input.d.ts.map +1 -1
  92. package/dist/components/LinkInput/link-input.js +2 -1
  93. package/dist/components/LinkInput/link-input.js.map +1 -1
  94. package/dist/components/Menu/menu-item.d.ts.map +1 -1
  95. package/dist/components/Menu/menu-item.js +4 -2
  96. package/dist/components/Menu/menu-item.js.map +1 -1
  97. package/dist/components/PeoplePicker/people-picker.d.ts +2 -0
  98. package/dist/components/PeoplePicker/people-picker.d.ts.map +1 -1
  99. package/dist/components/PeoplePicker/people-picker.js +4 -0
  100. package/dist/components/PeoplePicker/people-picker.js.map +1 -1
  101. package/dist/components/Popover/popover.d.ts.map +1 -1
  102. package/dist/components/Popover/popover.js +4 -2
  103. package/dist/components/Popover/popover.js.map +1 -1
  104. package/dist/components/ScrollArea/scroll-area.d.ts +4 -3
  105. package/dist/components/ScrollArea/scroll-area.d.ts.map +1 -1
  106. package/dist/components/ScrollArea/scroll-area.js +1 -1
  107. package/dist/components/ScrollArea/scroll-area.js.map +1 -1
  108. package/dist/components/Select/select.d.ts.map +1 -1
  109. package/dist/components/Select/select.js +59 -37
  110. package/dist/components/Select/select.js.map +1 -1
  111. package/dist/components/SelectMenu/select-menu.d.ts +2 -0
  112. package/dist/components/SelectMenu/select-menu.d.ts.map +1 -1
  113. package/dist/components/SelectMenu/select-menu.js +3 -1
  114. package/dist/components/SelectMenu/select-menu.js.map +1 -1
  115. package/dist/components/Sheet/sheet.d.ts +1 -1
  116. package/dist/components/Sidebar/sidebar.js +2 -2
  117. package/dist/components/Sidebar/sidebar.js.map +1 -1
  118. package/dist/components/Tabs/tabs.d.ts.map +1 -1
  119. package/dist/components/Tabs/tabs.js +3 -0
  120. package/dist/components/Tabs/tabs.js.map +1 -1
  121. package/dist/components/Tag/tag.d.ts +1 -1
  122. package/dist/components/Tag/tag.d.ts.map +1 -1
  123. package/dist/components/Tag/tag.js +4 -3
  124. package/dist/components/Tag/tag.js.map +1 -1
  125. package/dist/components/Textarea/textarea.d.ts +11 -0
  126. package/dist/components/Textarea/textarea.d.ts.map +1 -1
  127. package/dist/components/Textarea/textarea.js +22 -5
  128. package/dist/components/Textarea/textarea.js.map +1 -1
  129. package/dist/components/TimePicker/time-picker.d.ts.map +1 -1
  130. package/dist/components/TimePicker/time-picker.js +4 -3
  131. package/dist/components/TimePicker/time-picker.js.map +1 -1
  132. package/dist/components/Tooltip/tooltip.d.ts +1 -1
  133. package/dist/components/Tooltip/tooltip.d.ts.map +1 -1
  134. package/dist/components/Tooltip/tooltip.js +26 -1
  135. package/dist/components/Tooltip/tooltip.js.map +1 -1
  136. package/dist/components/TreeView/tree-view.d.ts +2 -0
  137. package/dist/components/TreeView/tree-view.d.ts.map +1 -1
  138. package/dist/components/TreeView/tree-view.js +3 -2
  139. package/dist/components/TreeView/tree-view.js.map +1 -1
  140. package/dist/index.d.ts +5 -3
  141. package/dist/index.d.ts.map +1 -1
  142. package/dist/index.js +4 -0
  143. package/dist/index.js.map +1 -1
  144. package/dist/patterns/element-anatomy/item-anatomy.d.ts +10 -1
  145. package/dist/patterns/element-anatomy/item-anatomy.d.ts.map +1 -1
  146. package/dist/patterns/element-anatomy/item-anatomy.js +77 -29
  147. package/dist/patterns/element-anatomy/item-anatomy.js.map +1 -1
  148. package/dist/patterns/header-canonical/chrome-header.d.ts +11 -4
  149. package/dist/patterns/header-canonical/chrome-header.d.ts.map +1 -1
  150. package/dist/patterns/header-canonical/chrome-header.js +1 -1
  151. package/dist/patterns/header-canonical/chrome-header.js.map +1 -1
  152. package/dist/patterns/overlay-surface/overlay-surface.d.ts +4 -3
  153. package/dist/patterns/overlay-surface/overlay-surface.d.ts.map +1 -1
  154. package/dist/patterns/overlay-surface/overlay-surface.js +1 -0
  155. package/dist/patterns/overlay-surface/overlay-surface.js.map +1 -1
  156. package/dist/patterns/resize-handle/resize-handle.d.ts +9 -11
  157. package/dist/patterns/resize-handle/resize-handle.d.ts.map +1 -1
  158. package/dist/patterns/resize-handle/resize-handle.js +3 -6
  159. package/dist/patterns/resize-handle/resize-handle.js.map +1 -1
  160. package/ds-canonical/adapters/claude/agents/canonical-reviewer.md +1 -1
  161. package/ds-canonical/adapters/claude-root-instructions.md +1 -1
  162. package/ds-canonical/adapters/claude-settings-base.json +2 -5
  163. package/ds-canonical/fork/AGENTS.md +21 -10
  164. package/ds-canonical/fork/codex/hooks.json +1 -1
  165. package/ds-canonical/fork/common/instruction.md +21 -10
  166. package/ds-canonical/fork/consumer/governance-check.mjs +101 -13
  167. package/ds-canonical/fork/consumer/lock.json +14 -15
  168. package/ds-canonical/fork/consumer/managed-files/46f10bc55cdfdf834d0749eebff42ea0bb8576f361b36d2b6ba1851354c50150.blob +26 -3
  169. package/ds-canonical/fork/consumer/managed-files/63061e05969667005020166ea2ae7df3c96663f7a928e7dd3eebc1471022df56.blob +61 -5
  170. package/ds-canonical/fork/consumer/managed-files/93a89f16f6717d1e4e4e27ec20ef916e3355c7aa890ca74cd9b12db30d8a899f.blob +3 -10
  171. package/ds-canonical/fork/consumer/managed-files/bc44cabad181f766611dab6a8f52d81100afadd50ac59799015e8ba7bf46d9eb.blob +3 -2
  172. package/ds-canonical/fork/governance.lock +17 -33
  173. package/ds-canonical/fork/hooks/check_consumer_code_quality.sh +33 -0
  174. package/ds-canonical/fork/hooks/check_pattern_invariants.sh +39 -0
  175. package/ds-canonical/fork/hooks/check_sidebar_menu_button_implicit_wrap.sh +6 -4
  176. package/ds-canonical/fork/launchers/provider-hook-normalization.mjs +5 -11
  177. package/ds-canonical/fork/launchers/settings-hooks.json +2 -2
  178. package/ds-canonical/fork/manifest.json +11 -13
  179. package/ds-canonical/fork/providers/claude/hook-config.json +1 -7
  180. package/ds-canonical/fork/providers/codex/hook-config.json +1 -1
  181. package/ds-canonical/fork/references/story-baseline-registry.json +7 -1
  182. package/ds-canonical/hooks/check_consumer_code_quality.sh +33 -0
  183. package/ds-canonical/hooks/check_pattern_invariants.sh +39 -0
  184. package/ds-canonical/hooks/check_sidebar_menu_button_implicit_wrap.sh +6 -4
  185. package/ds-canonical/hooks/session_start_governance_check.sh +5 -6
  186. package/ds-canonical/hooks/tests/test_check_consumer_code_quality.sh +23 -1
  187. package/ds-canonical/hooks/tests/test_check_pattern_invariants.sh +36 -2
  188. package/ds-canonical/references/audit-coverage-vs-24-checklist.md +1 -1
  189. package/ds-canonical/references/failure-class-registry.json +151 -144
  190. package/ds-canonical/references/preflight-gate-baseline.json +16 -12
  191. package/ds-canonical/references/story-baseline-registry.json +7 -1
  192. package/ds-canonical/rules/meta-patterns.md +3 -3
  193. package/ds-canonical/skills/design-system-audit/SKILL.md +1 -1
  194. package/ds-canonical/skills/design-system-audit/references/audit-prompts.md +1 -1
  195. package/ds-canonical/templates/dashboard-app.tsx +31 -8
  196. package/llms-full.txt +6 -1
  197. package/llms.txt +2 -1
  198. package/package.json +1 -1
  199. package/src/components/Accordion/accordion.principles.stories.tsx +16 -26
  200. package/src/components/Accordion/accordion.stories.tsx +22 -40
  201. package/src/components/AccountMenu/account-menu.anatomy.stories.tsx +115 -0
  202. package/src/components/AccountMenu/account-menu.principles.stories.tsx +99 -0
  203. package/src/components/AccountMenu/account-menu.spec.md +101 -0
  204. package/src/components/AccountMenu/account-menu.stories.tsx +61 -0
  205. package/src/components/AccountMenu/account-menu.tsx +170 -0
  206. package/src/components/AccountMenu/index.ts +5 -0
  207. package/src/components/AppShell/_demo-helpers.tsx +53 -53
  208. package/src/components/AppShell/app-shell.spec.md +5 -1
  209. package/src/components/AppShell/app-shell.tsx +14 -12
  210. package/src/components/Avatar/avatar.principles.stories.tsx +4 -4
  211. package/src/components/Avatar/avatar.stories.tsx +20 -5
  212. package/src/components/Breadcrumb/breadcrumb.spec.md +1 -1
  213. package/src/components/Button/button.spec.md +1 -1
  214. package/src/components/Button/button.stories.tsx +21 -9
  215. package/src/components/Button/button.tsx +5 -4
  216. package/src/components/Calendar/calendar.anatomy.stories.tsx +19 -17
  217. package/src/components/Calendar/calendar.principles.stories.tsx +5 -6
  218. package/src/components/Calendar/calendar.spec.md +25 -29
  219. package/src/components/Calendar/calendar.stories.tsx +5 -7
  220. package/src/components/Calendar/calendar.tsx +75 -87
  221. package/src/components/Carousel/carousel.principles.stories.tsx +5 -1
  222. package/src/components/Chart/chart.spec.md +1 -1
  223. package/src/components/Checkbox/checkbox.anatomy.stories.tsx +14 -5
  224. package/src/components/Chip/chip.spec.md +1 -1
  225. package/src/components/CircularProgress/circular-progress.anatomy.stories.tsx +7 -6
  226. package/src/components/CircularProgress/circular-progress.principles.stories.tsx +8 -6
  227. package/src/components/CircularProgress/circular-progress.stories.tsx +3 -3
  228. package/src/components/Combobox/combobox.tsx +1 -0
  229. package/src/components/Command/command.spec.md +1 -0
  230. package/src/components/Command/command.tsx +4 -1
  231. package/src/components/DataTable/cell-registry.tsx +3 -7
  232. package/src/components/DataTable/data-table-filter-group.tsx +44 -27
  233. package/src/components/DataTable/data-table-filter-labels.ts +100 -0
  234. package/src/components/DataTable/data-table-filter-panel.tsx +48 -8
  235. package/src/components/DataTable/data-table-filter-value-picker.tsx +25 -8
  236. package/src/components/DataTable/data-table.spec.md +18 -1
  237. package/src/components/DataTable/data-table.stories.tsx +346 -33
  238. package/src/components/DataTable/data-table.tsx +11 -4
  239. package/src/components/DataTable/filter-operators.spec.md +2 -2
  240. package/src/components/DateGrid/date-grid.anatomy.stories.tsx +24 -20
  241. package/src/components/DateGrid/date-grid.principles.stories.tsx +27 -21
  242. package/src/components/DateGrid/date-grid.stories.tsx +22 -13
  243. package/src/components/DatePicker/date-picker.anatomy.stories.tsx +3 -3
  244. package/src/components/DatePicker/date-picker.spec.md +4 -4
  245. package/src/components/DatePicker/date-picker.stories.tsx +33 -9
  246. package/src/components/DatePicker/date-picker.tsx +210 -147
  247. package/src/components/Dialog/dialog.spec.md +1 -1
  248. package/src/components/Dialog/dialog.tsx +8 -2
  249. package/src/components/DropdownMenu/dropdown-menu.anatomy.stories.tsx +1 -1
  250. package/src/components/DropdownMenu/dropdown-menu.spec.md +5 -0
  251. package/src/components/DropdownMenu/dropdown-menu.stories.tsx +3 -17
  252. package/src/components/DropdownMenu/dropdown-menu.tsx +58 -16
  253. package/src/components/Field/field-context.ts +25 -1
  254. package/src/components/Field/field-wrapper.tsx +7 -1
  255. package/src/components/Field/field.spec.md +8 -1
  256. package/src/components/Field/field.tsx +14 -2
  257. package/src/components/FieldControlGroup/field-control-group.anatomy.stories.tsx +3 -3
  258. package/src/components/FieldControlGroup/field-control-group.spec.md +2 -3
  259. package/src/components/FieldControlGroup/field-control-group.stories.tsx +2 -1
  260. package/src/components/FieldControlGroup/field-control-group.tsx +6 -9
  261. package/src/components/FileItem/file-item.spec.md +1 -1
  262. package/src/components/FileItem/file-item.stories.tsx +1 -1
  263. package/src/components/FileUpload/file-upload.stories.tsx +32 -0
  264. package/src/components/FileUpload/file-upload.tsx +1 -1
  265. package/src/components/FileViewer/file-viewer.anatomy.stories.tsx +1 -1
  266. package/src/components/FileViewer/file-viewer.spec.md +1 -1
  267. package/src/components/FileViewer/file-viewer.stories.tsx +7 -0
  268. package/src/components/FileViewer/file-viewer.tsx +23 -5
  269. package/src/components/InlineEdit/inline-edit.spec.md +12 -0
  270. package/src/components/InlineEdit/inline-edit.stories.tsx +30 -19
  271. package/src/components/InlineEdit/inline-edit.tsx +94 -31
  272. package/src/components/Input/input.anatomy.stories.tsx +28 -12
  273. package/src/components/Input/input.principles.stories.tsx +23 -18
  274. package/src/components/Input/input.stories.tsx +9 -1
  275. package/src/components/Input/input.tsx +7 -2
  276. package/src/components/LinkInput/link-input.principles.stories.tsx +7 -7
  277. package/src/components/LinkInput/link-input.spec.md +1 -1
  278. package/src/components/LinkInput/link-input.tsx +5 -1
  279. package/src/components/Menu/menu-item.anatomy.stories.tsx +1 -1
  280. package/src/components/Menu/menu-item.tsx +9 -2
  281. package/src/components/NumberInput/number-input.anatomy.stories.tsx +44 -10
  282. package/src/components/NumberInput/number-input.principles.stories.tsx +20 -14
  283. package/src/components/NumberInput/number-input.spec.md +1 -1
  284. package/src/components/NumberInput/number-input.stories.tsx +12 -5
  285. package/src/components/PeoplePicker/people-picker.anatomy.stories.tsx +7 -0
  286. package/src/components/PeoplePicker/people-picker.spec.md +3 -1
  287. package/src/components/PeoplePicker/people-picker.tsx +5 -0
  288. package/src/components/Popover/popover.tsx +7 -2
  289. package/src/components/ProgressBar/progress-bar.principles.stories.tsx +14 -12
  290. package/src/components/RadioGroup/radio-group.anatomy.stories.tsx +5 -1
  291. package/src/components/RadioGroup/radio-group.spec.md +1 -1
  292. package/src/components/Rating/rating.principles.stories.tsx +2 -2
  293. package/src/components/ScrollArea/scroll-area.anatomy.stories.tsx +1 -1
  294. package/src/components/ScrollArea/scroll-area.spec.md +3 -3
  295. package/src/components/ScrollArea/scroll-area.tsx +5 -4
  296. package/src/components/Select/select.principles.stories.tsx +4 -3
  297. package/src/components/Select/select.stories.tsx +7 -6
  298. package/src/components/Select/select.tsx +61 -29
  299. package/src/components/SelectMenu/select-menu.spec.md +1 -1
  300. package/src/components/SelectMenu/select-menu.tsx +4 -1
  301. package/src/components/SelectionControl/selection-item.anatomy.stories.tsx +19 -8
  302. package/src/components/SelectionControl/selection-item.stories.tsx +7 -7
  303. package/src/components/Sheet/sheet.spec.md +1 -1
  304. package/src/components/Sidebar/sidebar.spec.md +3 -2
  305. package/src/components/Sidebar/sidebar.stories.tsx +67 -37
  306. package/src/components/Sidebar/sidebar.tsx +3 -3
  307. package/src/components/Slider/slider.principles.stories.tsx +9 -9
  308. package/src/components/Steps/steps.spec.md +1 -0
  309. package/src/components/Steps/steps.stories.tsx +18 -1
  310. package/src/components/Switch/switch.anatomy.stories.tsx +11 -11
  311. package/src/components/Switch/switch.principles.stories.tsx +11 -8
  312. package/src/components/Tabs/tabs.principles.stories.tsx +24 -0
  313. package/src/components/Tabs/tabs.spec.md +6 -4
  314. package/src/components/Tabs/tabs.tsx +11 -0
  315. package/src/components/Tag/tag.anatomy.stories.tsx +5 -2
  316. package/src/components/Tag/tag.spec.md +4 -3
  317. package/src/components/Tag/tag.stories.tsx +19 -0
  318. package/src/components/Tag/tag.tsx +14 -5
  319. package/src/components/Textarea/textarea.anatomy.stories.tsx +15 -9
  320. package/src/components/Textarea/textarea.principles.stories.tsx +21 -5
  321. package/src/components/Textarea/textarea.spec.md +10 -1
  322. package/src/components/Textarea/textarea.tsx +38 -5
  323. package/src/components/TimePicker/time-picker.tsx +11 -5
  324. package/src/components/Tooltip/tooltip.spec.md +10 -0
  325. package/src/components/Tooltip/tooltip.tsx +34 -3
  326. package/src/components/TreeView/tree-view.spec.md +3 -3
  327. package/src/components/TreeView/tree-view.stories.tsx +68 -9
  328. package/src/components/TreeView/tree-view.tsx +7 -2
  329. package/src/index.ts +9 -1
  330. package/src/patterns/element-anatomy/inline-action.spec.md +12 -8
  331. package/src/patterns/element-anatomy/item-anatomy.spec.md +8 -1
  332. package/src/patterns/element-anatomy/item-anatomy.stories.tsx +24 -5
  333. package/src/patterns/element-anatomy/item-anatomy.tsx +120 -35
  334. package/src/patterns/element-anatomy/truncated-text.spec.md +13 -2
  335. package/src/patterns/header-canonical/chrome-header.tsx +14 -5
  336. package/src/patterns/header-canonical/header-canonical.spec.md +10 -7
  337. package/src/patterns/header-canonical/header-canonical.stories.tsx +77 -0
  338. package/src/patterns/horizontal-overflow/horizontal-overflow.stories.tsx +2 -0
  339. package/src/patterns/overlay-surface/overlay-surface.spec.md +1 -0
  340. package/src/patterns/overlay-surface/overlay-surface.stories.tsx +17 -1
  341. package/src/patterns/overlay-surface/overlay-surface.tsx +8 -4
  342. package/src/patterns/resize-handle/resize-handle.spec.md +11 -13
  343. package/src/patterns/resize-handle/resize-handle.stories.tsx +1 -3
  344. package/src/patterns/resize-handle/resize-handle.tsx +13 -18
  345. package/src/tokens/color/semantic.css +3 -1
  346. package/src/tokens/layoutSpace/layoutSpace.stories.tsx +1 -1
  347. package/src/tokens/opacity/opacity.stories.tsx +1 -1
  348. package/src/tokens/radius/radius.stories.tsx +3 -3
  349. package/src/tokens/uiSize/uiSize.css +11 -0
  350. package/src/tokens/uiSize/uiSize.spec.md +1 -0
  351. package/ds-canonical/commands/gov-status.md +0 -9
  352. package/ds-canonical/fork/consumer/managed-files/77e06f62670dfee01e9b5e8b0f8dad085ce711d75538c1fb0679d9818acaca82.blob +0 -424
  353. package/ds-canonical/fork/preamble.md +0 -500
  354. package/ds-canonical/hooks/check_post_main_ssot_propagate.sh +0 -8
  355. package/ds-canonical/hooks/tests/test_check_post_main_ssot_propagate.sh +0 -37
@@ -23,6 +23,7 @@ import {
23
23
  DATE_RELATIVE_GROUPS,
24
24
  type ValueShape,
25
25
  } from './filter-operators'
26
+ import type { DataTableFilterPanelLabels } from './data-table-filter-labels'
26
27
 
27
28
  // ── FilterValuePicker(value-picker switcher per ValueShape)────────────────
28
29
  //
@@ -47,6 +48,8 @@ export interface FilterValuePickerProps {
47
48
  disabled?: boolean
48
49
  /** 用 column.label 組「{label} 篩選值」(panel 每 row 不顯式 label,a11y 必填) */
49
50
  ariaLabel?: string
51
+ /** Resolved panel labels; placeholders and relative-date labels share one SSOT. */
52
+ labels: DataTableFilterPanelLabels
50
53
  /** Forward 給內部 Field control 的 className(2026-05-04 #2 fix)
51
54
  * 避免外層包 wrapper div 破壞 FieldControlGroup CSS variants(rounded radii / margin overlap) */
52
55
  className?: string
@@ -59,10 +62,11 @@ export function FilterValuePicker({
59
62
  colInfo,
60
63
  disabled,
61
64
  ariaLabel,
65
+ labels,
62
66
  className,
63
67
  }: FilterValuePickerProps) {
64
68
  if (!shape || disabled) {
65
- return <Input size="sm" value="" onChange={() => {}} placeholder="輸入值…" disabled aria-label={ariaLabel} className={className} />
69
+ return <Input size="sm" value="" onChange={() => {}} placeholder={labels.valuePlaceholder} disabled aria-label={ariaLabel} className={className} />
66
70
  }
67
71
 
68
72
  switch (shape) {
@@ -75,7 +79,7 @@ export function FilterValuePicker({
75
79
  size="sm"
76
80
  value={String(value ?? '')}
77
81
  onChange={(e) => onChange(e.target.value)}
78
- placeholder="輸入值…"
82
+ placeholder={labels.valuePlaceholder}
79
83
  aria-label={ariaLabel}
80
84
  className={className}
81
85
  />
@@ -87,7 +91,7 @@ export function FilterValuePicker({
87
91
  size="sm"
88
92
  value={typeof value === 'number' ? value : null}
89
93
  onChange={(v) => onChange(v ?? '')}
90
- placeholder="輸入數字…"
94
+ placeholder={labels.numberPlaceholder}
91
95
  aria-label={ariaLabel}
92
96
  className={className}
93
97
  />
@@ -121,17 +125,21 @@ export function FilterValuePicker({
121
125
  // 群組分類:Past / Current / Future(對齊 Linear / Notion idiom),走 Select.groups → SelectMenu
122
126
  const opts: SelectOption[] = DATE_RELATIVE_OPTIONS.map((o) => ({
123
127
  value: o.value,
124
- label: o.label,
128
+ label: labels.relativeDateOptionLabels[o.value] ?? o.label,
125
129
  group: o.group,
126
130
  }))
131
+ const groups = DATE_RELATIVE_GROUPS.map((group) => ({
132
+ key: group.key,
133
+ label: labels.relativeDateGroupLabels[group.key] ?? group.label,
134
+ }))
127
135
  return (
128
136
  <Select
129
137
  size="sm"
130
138
  options={opts}
131
- groups={DATE_RELATIVE_GROUPS as unknown as Array<{ key: string; label: string }>}
139
+ groups={groups}
132
140
  value={String(value ?? '')}
133
141
  onChange={(v) => onChange(v)}
134
- placeholder="選擇相對日期"
142
+ placeholder={labels.relativeDatePlaceholder}
135
143
  aria-label={ariaLabel}
136
144
  className={className}
137
145
  />
@@ -149,7 +157,7 @@ export function FilterValuePicker({
149
157
  options={opts}
150
158
  value={String(value ?? '')}
151
159
  onChange={(v) => onChange(v)}
152
- placeholder="選擇值"
160
+ placeholder={labels.singleSelectPlaceholder}
153
161
  aria-label={ariaLabel}
154
162
  className={className}
155
163
  />
@@ -168,7 +176,8 @@ export function FilterValuePicker({
168
176
  options={opts}
169
177
  value={arr}
170
178
  onChange={(v) => onChange(v)}
171
- placeholder="選擇值…"
179
+ placeholder={labels.multiSelectPlaceholder}
180
+ emptyText={labels.multiSelectEmptyText}
172
181
  aria-label={ariaLabel}
173
182
  className={className}
174
183
  />
@@ -213,6 +222,10 @@ export function FilterValuePicker({
213
222
  value={v ?? null}
214
223
  people={colInfo?.people ?? []}
215
224
  onChange={(next) => onChange(next[0] ?? null)}
225
+ placeholder={labels.peoplePlaceholder}
226
+ searchPlaceholder={labels.peopleSearchPlaceholder}
227
+ searchAriaLabel={labels.peopleSearchAriaLabel}
228
+ emptyText={labels.peopleEmptyText}
216
229
  aria-label={ariaLabel}
217
230
  className={className}
218
231
  />
@@ -226,6 +239,10 @@ export function FilterValuePicker({
226
239
  value={v}
227
240
  people={colInfo?.people ?? []}
228
241
  onChange={(next) => onChange(next)}
242
+ placeholder={labels.peoplePlaceholder}
243
+ searchPlaceholder={labels.peopleSearchPlaceholder}
244
+ searchAriaLabel={labels.peopleSearchAriaLabel}
245
+ emptyText={labels.peopleEmptyText}
229
246
  aria-label={ariaLabel}
230
247
  className={className}
231
248
  />
@@ -299,6 +299,18 @@ type FilterTreeNested = { mode: 'nested'; conjunction: 'and'|'or'; children: Fil
299
299
 
300
300
  `FilterGroup['children']` 只能 `FilterCondition[]` — TypeScript 編譯就拒 over-nest,不靠 runtime check。
301
301
 
302
+ Panel 額外公開兩個正交控制:
303
+
304
+ ```ts
305
+ interface DataTableFilterPanelProps<TData> {
306
+ maxConditions?: number
307
+ labels?: Partial<DataTableFilterPanelLabels>
308
+ }
309
+ ```
310
+
311
+ - **`maxConditions`**:計算 condition leaf 總數。flat = `tree.children.length`;nested = 所有 `group.children.length` 加總。未傳 = unlimited;有限值先 `floor` 並 clamp 至 ≥ 0,非有限非法值(NaN / -Infinity)fail closed 為 0。到 cap 後 initial-mount auto row、flat add、nested group 內 add、root add group、cell prefill 五條路徑全部拒絕;prefill 即使被拒仍呼叫 `onPrefillConsumed`,避免同一 request 重試迴圈。Controlled `value` 已超 cap 時**不裁資料**,只停用後續新增。
312
+ - **`labels`**:跟 `DATA_TABLE_FILTER_PANEL_DEFAULT_LABELS` shallow + nested maps deep merge。涵蓋 panel chrome、Where/And/Or、欄位/operator/value placeholder 與 accessible name、刪除/新增/移除、multi-select 子 picker 的 trigger / empty 文案、PeoplePicker 的 trigger / search / empty 文案,以及 operator / relative-date group / option label maps。Panel dispatch 必須把這些子 picker 文案完整下傳,不能漏回 Combobox / PeoplePicker 的獨立 zh-TW defaults。Operator 未 override 的 key 回退 `OPERATOR_REGISTRY[].label`,registry 仍是預設文字 SSOT。
313
+
302
314
  ### 二、求值策略
303
315
 
304
316
  採 TanStack `globalFilter` + 自訂 `globalFilterFn(row, _, tree) => evaluateTree(tree, row.original)`,**棄 `columnFilters`**(N 條同 column 不能 OR)。`evaluateTree` SSOT 在 `filter-tree.ts`。**比對精度(2026-07-04 Q6 實作)**:panel 建 condition 時把 `meta.includeTime` 固化為 `condition.datePrecision`('ms' / 'day';`evaluateTree` 簽名不變)— date ops 預設 day 級(本地 `startOfDay` 截斷,AG Grid / MUI X 慣例;`is` 同步走日期比對非字串),`includeTime=true` 才 ms 全精度(避開 Airtable day-precision 漏邊界地雷)。
@@ -325,12 +337,15 @@ ValueShape ↔ DS picker 對照(canonical 2026-05-02):
325
337
 
326
338
  - 第 1 row conjunction 是靜態 `Where` label(`px-[var(--field-px)]` 對齊下方 Field value 起點 = 12px)
327
339
  - field 未選 → operator + value picker disabled;同 group 共用 conjunction(第 2 條 row 是唯一可改的 And/Or Select,改動連動整 group;第 3 條起唯讀顯示當前 conjunction — A6 canonical)
328
- - **空狀態(兩態,G fix 2026-05-04 v2)**:initial mount 且 value 空 → auto-add 1 條空 condition row(field 未選 → operator / value 自動 disabled;讓 user 直接看到 row shape,不必先點 CTA;useRef gate 只 mount 一次);user 手動刪光 → 只顯 inline `+ 加篩選` CTA、不 re-add(尊重清空意圖,對齊 Notion / Airtable / Linear) <!-- @benchmark-unverified: see frontmatter benchmark list for canonical DS source URL -->
340
+ - **空狀態(兩態,G fix 2026-05-04 v2)**:initial mount 且 value 空且 `maxConditions` 尚有容量 → auto-add 1 條空 condition row(field 未選 → operator / value 自動 disabled;讓 user 直接看到 row shape,不必先點 CTA;useRef gate 只 mount 一次);`maxConditions=0` 不 auto-add。user 手動刪光 → 只顯 inline `+ 加篩選` CTA、不 re-add(尊重清空意圖,對齊 Notion / Airtable / Linear) <!-- @benchmark-unverified: see frontmatter benchmark list for canonical DS source URL -->
329
341
  - **CTA 位置**:緊貼最後一條 row(**廢 SurfaceFooter**),條件與「加入」屬同一語境;root-level「加篩選 / 加入篩選器」用 `tertiary`(輕量但有邊界,符合 root-CTA 重量),group 內「加入巢狀篩選」才用 `text`(更輕,inline 於 group 內)
330
342
  - **Trash / 刪除**:row 是 form-control row → text Button(non Inline Action,違 item-anatomy canonical)
331
343
  - **And/Or Select** `minRows={2}`(2 選項顯式縮 menu 高度);**Where padding** `px-[var(--field-px)]` align Field
332
344
  - Header refresh icon:`value !== defaultValue` 顯;ButtonDivider 串接 close X(對齊欄位顯示 chrome canonical)
333
345
  - **Relative date 群組**:`DATE_RELATIVE_GROUPS` Past / Current / Future,走 `<Select groups>`
346
+ - **Labels / i18n**:所有 panel-owned 可見文字與 accessible name 經 `labels`;operator 與 relative-date maps 是 nested partial override。Column header / option label 仍由 consumer 的 `ColumnDef` 提供,不由 panel 翻譯。
347
+ - **Readable secondary copy**:`Where` / 第 3 列起 conjunction 與 field placeholder 用 `fg-secondary`,在 `surface` / nested `muted` 背景維持 WCAG AA 正文對比;不可退回只適合弱化 icon / 非正文 metadata 的 `fg-muted`。
348
+ - **Condition cap**:任何 add CTA 到 cap 時保留於原位置但 disabled;group creation 本身帶 1 個 condition,故消耗 1 容量。刪除後立即恢復新增。
334
349
  - Trigger button checked(`aria-pressed`):`value` 有 ≥ 1 active condition → on(語意:資料被篩,獨立於 refresh)
335
350
 
336
351
  ### 五、Filterable column 判定
@@ -401,6 +416,7 @@ tableOptions={{ getSubRows, getRowCanExpand, state: { expanded }, onExpandedChan
401
416
  - Click 分權:chevron stopPropagation 不 fire select
402
417
  - Leaf placeholder:同層 sibling 有 expandable 時 leaf 也佔位
403
418
  - a11y:`aria-expanded` 套在展開 chevron `<button>`(非 row);`aria-level` 尚未實作(row depth 目前僅以 `--tree-indent-*` 縮排視覺呈現)
419
+ - Visual interaction regression:`NestedRowsExpanderHoverState` 的 play 標記實際 chevron button;`visual-assertions.json` interaction 再由 Playwright 真 pointer hover 並 fail-closed 驗證唯一 target / trusted pointer hit,禁止只 dispatch synthetic hover event 假綠。
404
420
  - Selection cascade:default OFF;`selectionCascade` opt-in 待 v2
405
421
 
406
422
  ### Drag visual SSOT(2026-05-06 v14.5)
@@ -484,6 +500,7 @@ DataTable 是 composite multi-section 元件,**不套 SizeMatrix / StateBehavior
484
500
  **ARIA / Pattern**:DataTable 是 composite tabular widget,**對齊 W3C ARIA Authoring Practices Guide `grid` pattern**(非 `radio-group`——之前 boilerplate 從 RadioGroup spec 誤抄;DataTable 是 multi-row / multi-column composite,not single-choice selection group,a11y semantics 完全不同(grid vs radio-group)<!-- @benchmark-verified: 2026-05-18 D1 rewrite -->):
485
501
 
486
502
  - Root 套 `role="table"`(currently)或 `role="grid"`(future tier,when cell editing 普及)— 詳 [WAI-ARIA APG: grid](https://www.w3.org/WAI/ARIA/apg/patterns/grid/) + [MDN grid role](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Reference/Roles/grid_role)
503
+ - Center body 捲動層套 `role="rowgroup"`(非空表):唯讀模式該層帶 `tabIndex=0` + `aria-label`(scrollable-region-focusable),**focusable 的無 role 中間層會被 axe aria-required-children 判為 table 的不合法 owned child**(2026-07-29 WM beta.95 錨例;與 Tabs 2026-07-18 決策1 同型)。rowgroup = 合法 table 子代 + 可具名([WAI-ARIA 1.2 §5.2.8.4 Name from author](https://www.w3.org/TR/wai-aria-1.2/#namecalculation))。空表例外:Empty 塊非 row → 不掛 rowgroup、亦無 overflow 故一併省 tabIndex/名稱
487
504
  - Column headers:以 `<div role="columnheader">` 顯式標記(本元件用 div + ARIA role,非語義 `<table>` — 見定位段;不渲染 `<th>` / `scope`)
488
505
  - Row headers:目前無對應(平面 row,無 row header 語意;未渲染 `role="rowheader"`)
489
506
  - Sortable column:`aria-sort="none" | "ascending" | "descending"` on 該 column header `<div role="columnheader">`
@@ -2,12 +2,20 @@
2
2
  // same-row-mixed-allow: file 含 toolbar Button iconOnly + row ItemInlineActionButton,但兩者在不同 row(toolbar 跟 panel row 分離)
3
3
  import React from 'react'
4
4
  import type { Meta, StoryObj } from '@storybook/react'
5
+ import { expect, userEvent, waitFor, within } from '@storybook/test'
5
6
  import { createColumnHelper, type ColumnDef } from '@tanstack/react-table'
6
7
  import { Pencil, Trash2, MoreVertical, Search, Filter, Eye, Download, Plus, ArrowUpDown } from 'lucide-react'
7
8
  import { DataTable, type DataTableSelection } from './data-table'
8
9
  import { DataTableSortManager } from './data-table-sort-manager'
9
10
  import { DataTableColumnVisibilityPanel } from './data-table-column-visibility-panel'
10
- import { DataTableFilterPanel, evaluateTree, createEmptyFilterTree, isFilterTreeActive, type FilterTree } from './data-table-filter-panel'
11
+ import {
12
+ DataTableFilterPanel,
13
+ evaluateTree,
14
+ createEmptyFilterTree,
15
+ isFilterTreeActive,
16
+ type DataTableFilterPanelLabels,
17
+ type FilterTree,
18
+ } from './data-table-filter-panel'
11
19
  import { getFilteredRowModel, type SortingState } from '@tanstack/react-table'
12
20
  import { Button } from '@/design-system/components/Button/button'
13
21
  import { Empty } from '@/design-system/components/Empty/empty'
@@ -820,25 +828,23 @@ const NESTED_DATA: TaskRow[] = [
820
828
  },
821
829
  ]
822
830
 
831
+ const NESTED_STATUS_OPTIONS = [
832
+ { value: 'Not started', label: 'Not started' },
833
+ { value: 'In progress', label: 'In progress' },
834
+ { value: 'Blocked', label: 'Blocked' },
835
+ { value: 'Done', label: 'Done' },
836
+ ]
837
+ const nestedTaskColumn = createColumnHelper<TaskRow>()
838
+ const NESTED_COLUMNS = [
839
+ nestedTaskColumn.accessor('task', { header: '任務', meta: { type: 'string', width: 360 } }),
840
+ nestedTaskColumn.accessor('owner', { header: '負責人', meta: { type: 'string', width: 160 } }),
841
+ nestedTaskColumn.accessor('status', { header: '狀態', meta: { type: 'select', options: NESTED_STATUS_OPTIONS, width: 140 } }),
842
+ ]
843
+
823
844
  export const NestedRows: Story = {
824
845
  name: '巢狀列(樹狀表格)',
825
846
  render: () => {
826
847
  const [expanded, setExpanded] = React.useState<Record<string, boolean>>({ 'task-1': true, 'task-1-1': true })
827
- const STATUS_OPTIONS = [
828
- { value: 'Not started', label: 'Not started' },
829
- { value: 'In progress', label: 'In progress' },
830
- { value: 'Blocked', label: 'Blocked' },
831
- { value: 'Done', label: 'Done' },
832
- ]
833
- const taskCol = createColumnHelper<TaskRow>()
834
- const taskColumns = React.useMemo(
835
- () => [
836
- taskCol.accessor('task', { header: '任務', meta: { type: 'string', width: 360 } }),
837
- taskCol.accessor('owner', { header: '負責人', meta: { type: 'string', width: 160 } }),
838
- taskCol.accessor('status', { header: '狀態', meta: { type: 'select', options: STATUS_OPTIONS, width: 140 } }),
839
- ],
840
- []
841
- )
842
848
  return (
843
849
  <div>
844
850
  {/* 實作:forward TanStack getSubRows;縮排共用 --tree-indent-{sm,md,lg} token(跨 TreeView
@@ -848,7 +854,7 @@ export const NestedRows: Story = {
848
854
  點三角形只切換展開,不會順帶選取整列。
849
855
  </p>
850
856
  <DataTable
851
- columns={taskColumns}
857
+ columns={NESTED_COLUMNS}
852
858
  data={NESTED_DATA}
853
859
  height="auto"
854
860
  getRowId={(row) => row.id}
@@ -865,6 +871,42 @@ export const NestedRows: Story = {
865
871
  },
866
872
  }
867
873
 
874
+ export const NestedRowsExpanderHoverState: Story = {
875
+ name: '巢狀列展開鈕:滑鼠移過狀態',
876
+ tags: ['!autodocs'],
877
+ render: () => {
878
+ const [expanded, setExpanded] = React.useState<Record<string, boolean>>({})
879
+ return (
880
+ <div className="max-w-3xl p-6">
881
+ <p className="text-caption text-fg-secondary mb-3">
882
+ 真實滑鼠停在第一列的展開鈕,驗證弱化圖示只前進一階到次要文字色。
883
+ </p>
884
+ <DataTable
885
+ columns={NESTED_COLUMNS}
886
+ data={NESTED_DATA}
887
+ height="auto"
888
+ getRowId={(row) => row.id}
889
+ tableOptions={{
890
+ getSubRows: (row: TaskRow) => row.children,
891
+ getRowCanExpand: (row) => Boolean(row.original.children?.length),
892
+ state: { expanded },
893
+ onExpandedChange: setExpanded as any,
894
+ }}
895
+ />
896
+ </div>
897
+ )
898
+ },
899
+ play: ({ canvasElement }) => {
900
+ const canvas = within(canvasElement)
901
+ const expander = canvas.getAllByRole('button', { name: '展開' })[0]
902
+ expect(expander).toHaveAttribute('aria-expanded', 'false')
903
+ // play 只做 deterministic target selection;真 hover 完全由 visual-audit
904
+ // 的 Playwright locator.hover() 擁有,避免 synthetic userEvent 假綠。
905
+ expander.dataset.visualHoverTarget = 'true'
906
+ expect(canvasElement.querySelectorAll('[data-visual-hover-target]')).toHaveLength(1)
907
+ },
908
+ }
909
+
868
910
  /* ── 巢狀 row × 拖曳重排(tree-table drag)──
869
911
  Tree drag 設計準則(2026-05-06 v14.7,對齊 spec.md「Cross-parent drop 禁止」)
870
912
  - **Top-level rows**:有 drag handle(absolute pinned to row left edge),可拖重排
@@ -876,21 +918,6 @@ export const NestedRowsWithDrag: Story = {
876
918
  render: () => {
877
919
  const [expanded, setExpanded] = React.useState<Record<string, boolean>>({ 'task-1': true })
878
920
  const [list, setList] = React.useState<TaskRow[]>(NESTED_DATA)
879
- const STATUS_OPTIONS = [
880
- { value: 'Not started', label: 'Not started' },
881
- { value: 'In progress', label: 'In progress' },
882
- { value: 'Blocked', label: 'Blocked' },
883
- { value: 'Done', label: 'Done' },
884
- ]
885
- const taskCol = createColumnHelper<TaskRow>()
886
- const taskColumns = React.useMemo(
887
- () => [
888
- taskCol.accessor('task', { header: '任務', meta: { type: 'string', width: 360 } }),
889
- taskCol.accessor('owner', { header: '負責人', meta: { type: 'string', width: 160 } }),
890
- taskCol.accessor('status', { header: '狀態', meta: { type: 'select', options: STATUS_OPTIONS, width: 140 } }),
891
- ],
892
- []
893
- )
894
921
  const handleReorder = (sourceId: string, targetId: string, position: 'before' | 'after') => {
895
922
  setList((prev) => {
896
923
  // top-level reorder only(sub-rows 不可拖)
@@ -913,7 +940,7 @@ export const NestedRowsWithDrag: Story = {
913
940
  顯 invalid signal。Drop indicator 水平 2px primary line — 主檔對齊 TreeView。
914
941
  </p>
915
942
  <DataTable
916
- columns={taskColumns}
943
+ columns={NESTED_COLUMNS}
917
944
  data={list}
918
945
  height="auto"
919
946
  getRowId={(row) => row.id}
@@ -1272,6 +1299,114 @@ const FILTER_COLUMNS = [
1272
1299
  col.accessor('updatedAt', { header: '更新時間', meta: { type: 'date', filterable: true, includeTime: true } }),
1273
1300
  ] as const
1274
1301
 
1302
+ const FILTER_COLUMNS_ENGLISH = [
1303
+ col.accessor('name', { header: 'Name', meta: { type: 'string', filterable: true } }),
1304
+ col.accessor('category', { header: 'Category', meta: {
1305
+ type: 'select',
1306
+ filterable: true,
1307
+ options: [
1308
+ { value: 'Electronics', label: 'Electronics' },
1309
+ { value: 'Furniture', label: 'Furniture' },
1310
+ ],
1311
+ } }),
1312
+ col.accessor('seller', { header: 'Owner', meta: {
1313
+ type: 'person',
1314
+ filterable: true,
1315
+ people: SELLERS,
1316
+ } }),
1317
+ col.accessor('updatedAt', { header: 'Updated at', meta: { type: 'date', filterable: true } }),
1318
+ ] as const
1319
+
1320
+ const FILTER_CHILD_PICKER_COLUMNS = [
1321
+ col.accessor('category', { header: 'Category', meta: {
1322
+ type: 'select',
1323
+ filterable: true,
1324
+ options: [],
1325
+ } }),
1326
+ col.accessor('seller', { header: 'Owner', meta: {
1327
+ type: 'person',
1328
+ filterable: true,
1329
+ people: [],
1330
+ } }),
1331
+ ] as const
1332
+
1333
+ const FILTER_PANEL_ENGLISH_LABELS: Partial<DataTableFilterPanelLabels> = {
1334
+ title: 'Filters',
1335
+ resetToDefault: 'Reset to default',
1336
+ close: 'Close',
1337
+ where: 'Where',
1338
+ and: 'And',
1339
+ or: 'Or',
1340
+ conjunctionAriaLabel: 'Conjunction for this group',
1341
+ fieldPlaceholder: 'Choose field',
1342
+ fieldAriaLabel: 'Filter field',
1343
+ operatorPlaceholder: 'Operator',
1344
+ operatorAriaLabel: 'Filter operator',
1345
+ valuePlaceholder: 'Enter value…',
1346
+ numberPlaceholder: 'Enter number…',
1347
+ relativeDatePlaceholder: 'Choose relative date',
1348
+ singleSelectPlaceholder: 'Choose value',
1349
+ multiSelectPlaceholder: 'Choose values…',
1350
+ multiSelectEmptyText: 'No matching values',
1351
+ peoplePlaceholder: 'Choose people',
1352
+ peopleSearchPlaceholder: 'Search people…',
1353
+ peopleSearchAriaLabel: 'Search filter people',
1354
+ peopleEmptyText: 'No matching people',
1355
+ filterValueAriaLabel: (columnLabel) => columnLabel ? `${columnLabel} filter value` : 'Filter value',
1356
+ deleteCondition: 'Delete condition',
1357
+ addFilter: 'Add filter',
1358
+ addGroup: 'Add filter group',
1359
+ addNestedFilter: 'Add nested filter',
1360
+ removeEmptyGroup: 'Remove empty group',
1361
+ operatorLabels: {
1362
+ contains: 'contains',
1363
+ does_not_contain: 'does not contain',
1364
+ is: 'is',
1365
+ is_not: 'is not',
1366
+ starts_with: 'starts with',
1367
+ ends_with: 'ends with',
1368
+ equals: 'equals',
1369
+ not_equals: 'does not equal',
1370
+ gt: 'is greater than',
1371
+ gte: 'is greater than or equal to',
1372
+ lt: 'is less than',
1373
+ lte: 'is less than or equal to',
1374
+ is_before: 'is before',
1375
+ is_after: 'is after',
1376
+ is_on_or_before: 'is on or before',
1377
+ is_on_or_after: 'is on or after',
1378
+ is_between: 'is between',
1379
+ is_relative: 'is relative',
1380
+ has_any_of: 'has any of',
1381
+ has_all_of: 'has all of',
1382
+ has_none_of: 'has none of',
1383
+ is_set: 'is set',
1384
+ is_not_set: 'is not set',
1385
+ is_true: 'is checked',
1386
+ is_false: 'is not checked',
1387
+ },
1388
+ relativeDateGroupLabels: {
1389
+ past: 'Past',
1390
+ current: 'Current',
1391
+ future: 'Future',
1392
+ },
1393
+ relativeDateOptionLabels: {
1394
+ past_30_days: 'Past 30 days',
1395
+ past_7_days: 'Past 7 days',
1396
+ last_month: 'Last month',
1397
+ last_week: 'Last week',
1398
+ yesterday: 'Yesterday',
1399
+ today: 'Today',
1400
+ this_week: 'This week',
1401
+ this_month: 'This month',
1402
+ tomorrow: 'Tomorrow',
1403
+ next_week: 'Next week',
1404
+ next_month: 'Next month',
1405
+ next_7_days: 'Next 7 days',
1406
+ next_30_days: 'Next 30 days',
1407
+ },
1408
+ }
1409
+
1275
1410
  /* ── 進階篩選 — 空狀態 ── */
1276
1411
  export const FilterPanelEmpty: Story = {
1277
1412
  name: '進階篩選 — 空狀態',
@@ -1442,6 +1577,184 @@ export const FilterPanelLongTagOverflow: Story = {
1442
1577
  },
1443
1578
  }
1444
1579
 
1580
+ const FilterPanelLabelsAndLimitHarness = () => {
1581
+ const [childPickerLabels, setChildPickerLabels] = React.useState<FilterTree>({
1582
+ mode: 'flat',
1583
+ conjunction: 'and',
1584
+ children: [
1585
+ { kind: 'cond', id: 'labels-category', field: 'category', op: 'is', value: [] },
1586
+ { kind: 'cond', id: 'labels-owner', field: 'seller', op: 'is', value: [] },
1587
+ ],
1588
+ })
1589
+ const [flat, setFlat] = React.useState<FilterTree>({
1590
+ mode: 'flat',
1591
+ conjunction: 'and',
1592
+ children: [
1593
+ { kind: 'cond', id: 'flat-1', field: 'updatedAt', op: 'is_relative', value: 'past_7_days' },
1594
+ ],
1595
+ })
1596
+ const [nestedCondition, setNestedCondition] = React.useState<FilterTree>({
1597
+ mode: 'nested',
1598
+ conjunction: 'and',
1599
+ children: [{
1600
+ kind: 'group',
1601
+ id: 'nested-condition-group',
1602
+ conjunction: 'and',
1603
+ children: [{ kind: 'cond', id: 'nested-condition-1', field: 'name', op: 'contains', value: 'phone' }],
1604
+ }],
1605
+ })
1606
+ const [nestedGroup, setNestedGroup] = React.useState<FilterTree>({
1607
+ mode: 'nested',
1608
+ conjunction: 'and',
1609
+ children: [{
1610
+ kind: 'group',
1611
+ id: 'nested-group-1',
1612
+ conjunction: 'and',
1613
+ children: [{ kind: 'cond', id: 'nested-group-cond-1', field: 'name', op: 'contains', value: 'phone' }],
1614
+ }],
1615
+ })
1616
+ const [mountCapZero, setMountCapZero] = React.useState<FilterTree>(() => createEmptyFilterTree('flat'))
1617
+ const [prefillValue, setPrefillValue] = React.useState<FilterTree>({
1618
+ mode: 'flat',
1619
+ conjunction: 'and',
1620
+ children: [{ kind: 'cond', id: 'prefill-1', field: 'name', op: 'contains', value: 'phone' }],
1621
+ })
1622
+ const [prefilledColumnId, setPrefilledColumnId] = React.useState<string | undefined>('category')
1623
+ const [prefillConsumed, setPrefillConsumed] = React.useState(false)
1624
+
1625
+ return (
1626
+ <div className="flex flex-col gap-10">
1627
+ <div data-testid="filter-label-child-pickers">
1628
+ <h3 className="text-body font-medium mb-2">English child-picker labels</h3>
1629
+ <DataTableFilterPanel
1630
+ columns={[...FILTER_CHILD_PICKER_COLUMNS]}
1631
+ value={childPickerLabels}
1632
+ onChange={setChildPickerLabels}
1633
+ maxConditions={2}
1634
+ labels={FILTER_PANEL_ENGLISH_LABELS}
1635
+ />
1636
+ </div>
1637
+
1638
+ <div data-testid="filter-limit-flat">
1639
+ <h3 className="text-body font-medium mb-2">English labels + flat cap</h3>
1640
+ <DataTableFilterPanel
1641
+ columns={[...FILTER_COLUMNS_ENGLISH]}
1642
+ value={flat}
1643
+ onChange={setFlat}
1644
+ maxConditions={2}
1645
+ labels={FILTER_PANEL_ENGLISH_LABELS}
1646
+ />
1647
+ </div>
1648
+
1649
+ <div data-testid="filter-limit-nested-condition">
1650
+ <h3 className="text-body font-medium mb-2">Nested condition cap</h3>
1651
+ <DataTableFilterPanel
1652
+ columns={[...FILTER_COLUMNS_ENGLISH]}
1653
+ value={nestedCondition}
1654
+ onChange={setNestedCondition}
1655
+ maxConditions={2}
1656
+ labels={FILTER_PANEL_ENGLISH_LABELS}
1657
+ />
1658
+ </div>
1659
+
1660
+ <div data-testid="filter-limit-nested-group">
1661
+ <h3 className="text-body font-medium mb-2">Nested group cap</h3>
1662
+ <DataTableFilterPanel
1663
+ columns={[...FILTER_COLUMNS_ENGLISH]}
1664
+ value={nestedGroup}
1665
+ onChange={setNestedGroup}
1666
+ maxConditions={2}
1667
+ labels={FILTER_PANEL_ENGLISH_LABELS}
1668
+ />
1669
+ </div>
1670
+
1671
+ <div data-testid="filter-limit-mount-zero">
1672
+ <h3 className="text-body font-medium mb-2">Initial mount at zero capacity</h3>
1673
+ <DataTableFilterPanel
1674
+ columns={[...FILTER_COLUMNS_ENGLISH]}
1675
+ value={mountCapZero}
1676
+ onChange={setMountCapZero}
1677
+ maxConditions={0}
1678
+ labels={FILTER_PANEL_ENGLISH_LABELS}
1679
+ />
1680
+ </div>
1681
+
1682
+ <div data-testid="filter-limit-prefill">
1683
+ <h3 className="text-body font-medium mb-2">Prefill at capacity</h3>
1684
+ <p data-testid="prefill-consumed" className="text-caption text-fg-secondary mb-2">
1685
+ Prefill consumed: {prefillConsumed ? 'yes' : 'no'}
1686
+ </p>
1687
+ <DataTableFilterPanel
1688
+ columns={[...FILTER_COLUMNS_ENGLISH]}
1689
+ value={prefillValue}
1690
+ onChange={setPrefillValue}
1691
+ prefilledColumnId={prefilledColumnId}
1692
+ onPrefillConsumed={() => {
1693
+ setPrefilledColumnId(undefined)
1694
+ setPrefillConsumed(true)
1695
+ }}
1696
+ maxConditions={1}
1697
+ labels={FILTER_PANEL_ENGLISH_LABELS}
1698
+ />
1699
+ </div>
1700
+ </div>
1701
+ )
1702
+ }
1703
+
1704
+ export const FilterPanelLabelsAndLimit: Story = {
1705
+ name: '進階篩選 — 標籤與條件上限',
1706
+ render: () => <FilterPanelLabelsAndLimitHarness />,
1707
+ play: async ({ canvasElement }) => {
1708
+ const canvas = within(canvasElement)
1709
+ const page = within(canvasElement.ownerDocument.body)
1710
+
1711
+ const childPickers = within(canvas.getByTestId('filter-label-child-pickers'))
1712
+ const categoryValue = childPickers.getByRole('combobox', { name: 'Category filter value' })
1713
+ await expect(categoryValue).toHaveTextContent('Choose values…')
1714
+ await userEvent.click(categoryValue)
1715
+ await expect(page.getAllByText('No matching values')).toHaveLength(2)
1716
+ await userEvent.keyboard('{Escape}')
1717
+
1718
+ await userEvent.click(childPickers.getByRole('combobox', { name: 'Owner filter value' }))
1719
+ const peopleSearch = await page.findByPlaceholderText('Search people…')
1720
+ await expect(peopleSearch).toHaveAccessibleName('Search filter people')
1721
+ await expect(page.getAllByText('No matching people')).toHaveLength(2)
1722
+ await userEvent.keyboard('{Escape}')
1723
+
1724
+ const flat = within(canvas.getByTestId('filter-limit-flat'))
1725
+ await expect(flat.getByText('Filters')).toBeInTheDocument()
1726
+ await expect(flat.getByText('Where')).toBeInTheDocument()
1727
+ await expect(flat.getByText('is relative')).toBeInTheDocument()
1728
+ await expect(flat.getByText('Past 7 days')).toBeInTheDocument()
1729
+ await userEvent.click(flat.getByRole('button', { name: 'Add filter' }))
1730
+ await waitFor(() => expect(flat.getAllByLabelText('Filter field')).toHaveLength(2))
1731
+ await expect(flat.getByRole('button', { name: 'Add filter' })).toBeDisabled()
1732
+
1733
+ const nestedCondition = within(canvas.getByTestId('filter-limit-nested-condition'))
1734
+ await userEvent.click(nestedCondition.getByRole('button', { name: 'Add nested filter' }))
1735
+ await waitFor(() => expect(nestedCondition.getAllByLabelText('Filter field')).toHaveLength(2))
1736
+ await expect(nestedCondition.getByRole('button', { name: 'Add nested filter' })).toBeDisabled()
1737
+ await expect(nestedCondition.getByRole('button', { name: 'Add filter group' })).toBeDisabled()
1738
+
1739
+ const nestedGroup = within(canvas.getByTestId('filter-limit-nested-group'))
1740
+ await userEvent.click(nestedGroup.getByRole('button', { name: 'Add filter group' }))
1741
+ await waitFor(() => expect(nestedGroup.getAllByLabelText('Filter field')).toHaveLength(2))
1742
+ for (const button of nestedGroup.getAllByRole('button', { name: 'Add nested filter' })) {
1743
+ await expect(button).toBeDisabled()
1744
+ }
1745
+ await expect(nestedGroup.getByRole('button', { name: 'Add filter group' })).toBeDisabled()
1746
+
1747
+ const mountCapZero = within(canvas.getByTestId('filter-limit-mount-zero'))
1748
+ await expect(mountCapZero.queryAllByLabelText('Filter field')).toHaveLength(0)
1749
+ await expect(mountCapZero.getByRole('button', { name: 'Add filter' })).toBeDisabled()
1750
+
1751
+ const prefill = within(canvas.getByTestId('filter-limit-prefill'))
1752
+ await waitFor(() => expect(prefill.getByTestId('prefill-consumed')).toHaveTextContent('yes'))
1753
+ await expect(prefill.getAllByLabelText('Filter field')).toHaveLength(1)
1754
+ await expect(prefill.getByRole('button', { name: 'Add filter' })).toBeDisabled()
1755
+ },
1756
+ }
1757
+
1445
1758
  /* ── 列拖曳重排(Jira-style + 3-panel pinned columns)──────────────────────
1446
1759
  enableRowDrag + onRowReorder 整合範例(v3 Jira 設計準則):
1447
1760
  - handle 浮在 row 左緣(fixed-position;Button tertiary iconOnly xs = elevated chip)
@@ -1822,7 +1822,7 @@ function DataTableInner<TData>(
1822
1822
  type="button"
1823
1823
  aria-label={isExpanded ? '收合' : '展開'}
1824
1824
  aria-expanded={isExpanded}
1825
- className="inline-flex items-center justify-center shrink-0 w-4 h-4 mr-2 text-fg-muted hover:text-foreground rounded-md focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring transition-transform"
1825
+ className="inline-flex items-center justify-center shrink-0 w-4 h-4 mr-2 text-fg-muted hover:text-fg-secondary rounded-md focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring transition-transform"
1826
1826
  style={{ transform: isExpanded ? 'rotate(90deg)' : undefined }}
1827
1827
  onClick={(e) => { e.stopPropagation(); toggleExpand?.() }}
1828
1828
  >
@@ -1867,7 +1867,7 @@ function DataTableInner<TData>(
1867
1867
  type="button"
1868
1868
  aria-label={isExpanded ? '收合' : '展開'}
1869
1869
  aria-expanded={isExpanded}
1870
- className="inline-flex items-center justify-center shrink-0 w-4 h-4 mr-2 text-fg-muted hover:text-foreground rounded-md focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring transition-transform"
1870
+ className="inline-flex items-center justify-center shrink-0 w-4 h-4 mr-2 text-fg-muted hover:text-fg-secondary rounded-md focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring transition-transform"
1871
1871
  style={{ transform: isExpanded ? 'rotate(90deg)' : undefined }}
1872
1872
  onClick={(e) => { e.stopPropagation(); toggleExpand?.() }}
1873
1873
  >
@@ -2704,8 +2704,15 @@ function DataTableInner<TData>(
2704
2704
  // (非 enabled / spreadsheet)outer table 也不 focusable → 這裡補 tabIndex=0 + 具名 +
2705
2705
  // inset focus ring,讓鍵盤可聚焦後方向鍵捲動;互動模式已由 outer table keyboard handler
2706
2706
  // + focusable cells 覆蓋,不重複加 tab stop。
2707
- tabIndex={enabled || spreadsheetMode ? undefined : 0}
2708
- aria-label={enabled || spreadsheetMode ? undefined : '表格內容,可捲動'}
2707
+ // **role="rowgroup" 必要**(2026-07-29 WM beta.95 axe 錨例):focusable 的無 role
2708
+ // 中間層會被 axe aria-required-children 視為 role="table" 的不合法 owned child,
2709
+ // 打斷 table→row ownership(修 scrollable-region-focusable 反引爆 required-children,
2710
+ // 與 Tabs 2026-07-18 決策1 同型)。rowgroup = W3C ARIA 1.2 合法 table 子代 + 可具名
2711
+ // (Name from author §5.2.8.4)+ 容納 rows,兩規則唯一同時滿足解。
2712
+ // 空表例外:Empty 塊非 row → 不掛 rowgroup;空表無 overflow 也無需 tabIndex/名稱。
2713
+ role={isEmpty ? undefined : 'rowgroup'}
2714
+ tabIndex={enabled || spreadsheetMode || isEmpty ? undefined : 0}
2715
+ aria-label={enabled || spreadsheetMode || isEmpty ? undefined : '表格內容,可捲動'}
2709
2716
  // overflow-x/y: auto — 沒 overflow 就不顯 bar。wrapper minWidth 仍 trigger H 真 overflow。
2710
2717
  // **不**用 scrollbar-gutter: stable — 那會永遠保留 V 軸 15px 空間,
2711
2718
  // content fit 時看起來像「永遠有 V 捲軸」(Image #5 bug)。
@@ -210,7 +210,7 @@
210
210
  | 4 | `date` 擴 `is_on_or_before` / `is_on_or_after` | ✅ 採用(CU gap) |
211
211
  | 5 | `multiSelect` 走 5 ops(含 `has_all_of`)| ✅ |
212
212
  | 6 | `is_exactly` 全砍 | ✅ |
213
- | 7 | label 暫硬編繁中 + 留 i18n hook | ✅ |
213
+ | 7 | registry 繁中 label 作預設;panel 以 `labels.operatorLabels` / relative-date maps 提供 consumer i18n override | ✅ 2026-07-31 落地 |
214
214
  | 8 | op 命名 snake_case | ✅ |
215
215
  | 9 | 統一 `is_set` / `is_not_set`(不混 `is_empty`) | ✅ |
216
216
 
@@ -253,7 +253,7 @@
253
253
  - [x] InputSurface 議題消失(配合砍 `number.between`)
254
254
  - [x] FilterTree data model + DataTable 耦合 — 已落地 `filter-tree.ts` + `data-table-filter-panel.tsx`(原規劃 `advanced-filter.draft.md` 未另立檔,內容即本 spec + code)
255
255
  - [x] datetime spec — 併入本 spec v3(原規劃 `datetime-picker.draft.md` 不需要,無新元件)
256
- - [x] i18n 策略 — 暫硬編繁中,留 i18n hook(對齊 DS 既有元件慣例)
256
+ - [x] i18n 策略 — registry 繁中 label 保持預設 SSOT;`DataTableFilterPanel labels` 可 deep-partial override operator、relative-date group / option 與全部 panel chrome(2026-07-31)
257
257
  - [→] Phase B.5 lock canonical via `/ensure-canonical`(spec 全收斂後)
258
258
 
259
259
  ## Sources(M8 benchmark)