@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
@@ -62,7 +62,7 @@ export const UsageGuidance: Story = {
62
62
  note="語意鎖「正在載入、正在處理」。永遠旋轉的裝飾會讓 a11y 使用者(螢幕閱讀器)持續收到 loading 通知,也讓視覺使用者無法判斷何時結束"
63
63
  >
64
64
  <div className="flex items-center gap-2 border border-border rounded-lg p-3 w-72">
65
- <CircularProgress />
65
+ <CircularProgress aria-label="載入中(裝飾誤用示範)" />
66
66
  <span className="text-body">歡迎使用本系統</span>
67
67
  </div>
68
68
  <Label warn>Welcome banner 裡無限轉的 CircularProgress → 使用者以為系統卡住</Label>
@@ -76,7 +76,7 @@ export const UsageGuidance: Story = {
76
76
  {Array.from({ length: 4 }).map((_, i) => (
77
77
  <div key={i} className="flex items-center gap-2 border border-border rounded-md px-3 py-2">
78
78
  {/* size=16 = field-inline canonical;此例為反例(不該多個一起轉)。 */}
79
- <CircularProgress size={16} />
79
+ <CircularProgress size={16} aria-label={`載入項目 ${i + 1}`} />
80
80
  <span className="text-body text-fg-muted">載入項目 {i + 1}</span>
81
81
  </div>
82
82
  ))}
@@ -104,7 +104,7 @@ export const UsageGuidance: Story = {
104
104
  >
105
105
  <div className="flex items-center gap-6">
106
106
  <div className="flex flex-col items-center gap-2">
107
- <CircularProgress value={100} size={24} />
107
+ <CircularProgress value={100} size={24} aria-label="進度 100%(穩態誤用)" />
108
108
  <Label warn>❌ 穩態停留 100% → 使用者困惑</Label>
109
109
  </div>
110
110
  <div className="flex flex-col items-center gap-2">
@@ -166,7 +166,7 @@ export const IndeterminateVsDeterminateRule: Story = {
166
166
  <Label>✅ 第三方金流驗證:不知道要多久 → 不傳 value</Label>
167
167
  </div>
168
168
  <div className="flex flex-col items-center gap-2">
169
- <CircularProgress value={65} affix="value" />
169
+ <CircularProgress value={65} affix="value" aria-label="檔案上傳進度 65%" />
170
170
  <Label>✅ 檔案上傳:bytes 已知 → 傳 value={'{N}'}</Label>
171
171
  </div>
172
172
  </Rule>
@@ -176,7 +176,7 @@ export const IndeterminateVsDeterminateRule: Story = {
176
176
  note="value 永遠停在 0% 或隨機亂跳會讓使用者懷疑 app 壞掉。若操作本質不可量化,維持 indeterminate 到底,不要硬傳 value 假裝。"
177
177
  >
178
178
  <div className="flex flex-col items-center gap-2">
179
- <CircularProgress value={0} />
179
+ <CircularProgress value={0} aria-label="報表產生進度 0%(假進度誤用)" />
180
180
  <Label warn>❌ 生成報表中卻永遠卡 0% → 改用 indeterminate(不傳 value)</Label>
181
181
  </div>
182
182
  </Rule>
@@ -209,6 +209,7 @@ export const ConsumptionScenarioRule: Story = {
209
209
  loading
210
210
  defaultValue="cq@"
211
211
  placeholder="驗證 email 唯一性..."
212
+ aria-label="Email"
212
213
  className="w-72"
213
214
  />
214
215
  <Label>驗證 email 唯一性(GitHub 註冊場景)</Label>
@@ -244,6 +245,7 @@ export const SizeMatchContextRule: Story = {
244
245
  loading
245
246
  defaultValue="驗證中..."
246
247
  placeholder="驗證 email..."
248
+ aria-label="Email"
247
249
  className="w-60"
248
250
  />
249
251
  </Rule>
@@ -253,7 +255,7 @@ export const SizeMatchContextRule: Story = {
253
255
  note="中等尺寸,放在 row / card 內容區,視覺重量足夠吸引注意但不喧賓奪主"
254
256
  >
255
257
  <div className="flex items-center justify-center gap-2 border border-border rounded-lg p-4 w-72">
256
- <CircularProgress size={24} />
258
+ <CircularProgress size={24} aria-label="載入更多留言" />
257
259
  <span className="text-body text-fg-muted">載入更多留言...</span>
258
260
  </div>
259
261
  <Label>Slack channel 載入更多訊息的 footer</Label>
@@ -35,7 +35,7 @@ export const Default: Story = {
35
35
  (Button loading = iconSize / Input loading = iconSize / Empty = iconSize)。
36
36
  不 parallel 展示多 sizes 因為 DS 不「提供各種 sizes」讓 consumer 挑,而是透過原則
37
37
  在 consumer 端自動決定(見 spec.md「Size canonical」)。 */}
38
- <CircularProgress />
38
+ <CircularProgress aria-label="載入中" />
39
39
  <CircularProgress value={60} aria-label="進度 60%" />
40
40
  <CircularProgress value={90} affix="value" aria-label="進度 90%" />
41
41
  </div>
@@ -65,6 +65,7 @@ export const InlineAction: Story = {
65
65
  loading
66
66
  defaultValue="react-"
67
67
  placeholder="搜尋 GitHub repositories..."
68
+ aria-label="搜尋 GitHub repositories"
68
69
  />
69
70
 
70
71
  <div className="flex flex-col gap-2">
@@ -107,7 +108,7 @@ const syncColumns: ColumnDef<SyncRow>[] = [
107
108
  // 再加 leading-none 嘗試「壓縮行高」——那會讓 svg(16px) 與 text line box 互搶
108
109
  // 基線,反而歪掉。
109
110
  <span className="inline-flex items-center gap-2 text-fg-muted">
110
- <CircularProgress size={16} />
111
+ <CircularProgress size={16} aria-label="同步中" />
111
112
  同步中
112
113
  </span>
113
114
  ) : (
@@ -142,4 +143,3 @@ export const FullScreenOverlay: Story = {
142
143
  </div>
143
144
  ),
144
145
  }
145
-
@@ -880,6 +880,7 @@ function CustomCombobox({
880
880
  createLabel={createLabel}
881
881
  searchable={searchable && searchIn === 'menu'}
882
882
  searchPlaceholder={searchPlaceholder}
883
+ searchAriaLabel={searchAriaLabel}
883
884
  size={size}
884
885
  open={open}
885
886
  onOpenChange={(o) => { setOpen(o); onOpenChange?.(o) }}
@@ -87,6 +87,7 @@ cmdk 自動處理:
87
87
  - **搜尋框**:`role="combobox"` + `aria-expanded` / `aria-controls` 指向 list
88
88
  - **鍵盤導覽**:cmdk 提供 ↑ / ↓ 移動 highlight、Enter 選取(另支援 vim-style Ctrl+n/p/j/k、Home/End)。cmdk 本身無 Esc handler — Esc 關閉僅在 `CommandDialog`(Cmd+K)模式由 Radix Dialog 的 DismissableLayer 提供;inline `<Command>` 模式按 Esc 無反應
89
89
  - **空狀態**:`<CommandEmpty>` 自動帶 `role="presentation"`,不干擾 screen reader 的 list 朗讀
90
+ - **分隔線**:`<CommandSeparator>` 是純視覺分組線,固定 `role="presentation"`;不冒充 listbox 選項或其他可導覽項目
90
91
 
91
92
  Consumer 無需額外處理 a11y,保留 cmdk 原結構 + 使用 `<CommandInput>` / `<CommandList>` / `<CommandItem>` 即可。
92
93
 
@@ -131,7 +131,10 @@ const CommandSeparator = React.forwardRef<
131
131
  ref={ref}
132
132
  className={cn("h-px bg-divider", className)}
133
133
  {...props}
134
- />
134
+ asChild
135
+ >
136
+ <div role="presentation" />
137
+ </CommandPrimitive.Separator>
135
138
  ))
136
139
  CommandSeparator.displayName = CommandPrimitive.Separator.displayName
137
140
 
@@ -148,20 +148,16 @@ function StringCell({ value, meta, mode, size, autoRowHeight, onCommit, onCancel
148
148
  // → cell 進 edit shrink)。改 CSS `field-sizing: content`(Chrome 123+ / FF 122+ /
149
149
  // Safari 17+)讓 textarea 自動 grow to content,匹配 view wrap 真實高度。
150
150
  // Fallback rows 仍保留給舊 browser(rows attr 在 field-sizing 支援時被覆蓋)。
151
- const newlineRows = (v.match(/\n/g) || []).length + 1
152
- const wrapRows = Math.ceil(v.length / 40)
153
- const estimateRows = Math.min(10, Math.max(1, newlineRows, wrapRows))
154
151
  return (
155
152
  <Textarea
156
153
  autoFocus
157
154
  variant="naked"
158
155
  aria-label={ariaLabel}
159
156
  size={sizeForInput(size)}
160
- rows={estimateRows}
157
+ // 2026-07-28:原本 field-sizing + rows 估算是本檔的呼叫端膠水,只有 cell 有、InlineEdit 沒有
158
+ // → 兩者 edit 高度漂移。已提升為 Textarea `autoSize` 能力,兩個語境共用同一份實作。
159
+ autoSize
161
160
  defaultValue={v}
162
- // any-allow: CSS `field-sizing` 屬性 Chrome 123+/FF 122+/Safari 17+ 支援但 TypeScript lib.dom
163
- // 尚未加 type;narrow 到 CSSProperties 仍需 cast,保留 single-site any 較 type aug 簡潔。
164
- style={{ fieldSizing: 'content' } as React.CSSProperties}
165
161
  onChange={(e) => onDraft?.((e.target as HTMLTextAreaElement).value)}
166
162
  onBlur={(e) => onCommit?.((e.target as HTMLTextAreaElement).value)}
167
163
  // 多行:走 edit-in-place 鍵盤結算 SSOT(IME guard + Esc + Cmd/Ctrl+Enter commit;
@@ -27,6 +27,7 @@ import {
27
27
  } from './filter-operators'
28
28
  import type { Conjunction, FilterCondition, FilterGroup } from './filter-tree'
29
29
  import { FilterValuePicker } from './data-table-filter-value-picker'
30
+ import type { DataTableFilterPanelLabels } from './data-table-filter-labels'
30
31
 
31
32
  // ── Shared internal types + operator helpers(panel orchestration 也消費)────
32
33
 
@@ -40,9 +41,12 @@ export interface FilterColumn {
40
41
  includeTime?: boolean
41
42
  }
42
43
 
43
- function getOperatorOptions(type?: ColumnType): SelectOption[] {
44
+ function getOperatorOptions(
45
+ type: ColumnType | undefined,
46
+ operatorLabels: DataTableFilterPanelLabels['operatorLabels'],
47
+ ): SelectOption[] {
44
48
  const registry = type && OPERATOR_REGISTRY[type] ? OPERATOR_REGISTRY[type] : OPERATOR_REGISTRY.string
45
- return registry.map((op) => ({ value: op.op, label: op.label }))
49
+ return registry.map((op) => ({ value: op.op, label: operatorLabels[op.op] ?? op.label }))
46
50
  }
47
51
 
48
52
  export function getDefaultOperator(type?: ColumnType): string {
@@ -56,25 +60,25 @@ export const datePrecisionOf = (col: FilterColumn | undefined): 'day' | 'ms' | u
56
60
 
57
61
  // ── ConjunctionLabel ───────────────────────────────────────────────────
58
62
 
59
- const CONJ_OPTIONS: SelectOption[] = [
60
- { value: 'and', label: 'And' },
61
- { value: 'or', label: 'Or' },
62
- ]
63
-
64
63
  function ConjunctionLabel({
65
- index, conjunction, onChange,
66
- }: { index: number; conjunction: Conjunction; onChange: (c: Conjunction) => void }) {
64
+ index, conjunction, onChange, labels,
65
+ }: {
66
+ index: number
67
+ conjunction: Conjunction
68
+ onChange: (c: Conjunction) => void
69
+ labels: DataTableFilterPanelLabels
70
+ }) {
67
71
  // index === 0:首 row 顯示靜態「Where」label
68
72
  // index === 1:**唯一可改**的 AND/OR Select(連動整 group conjunction)
69
73
  // index ≥ 2:被連動的 row,read-only 顯示當前 conjunction 文字(同 Where 視覺,A6 canonical)
70
74
  // 對齊 Airtable / Notion / Linear 共識 @benchmark-unverified(non-OSS)
71
75
  // px-[var(--field-px)] 對齊 Field 內部 padding 12px(Q13)
72
76
  if (index === 0) {
73
- return <div className="w-20 shrink-0 text-body text-fg-muted px-[var(--field-px)] self-center">Where</div>
77
+ return <div className="w-20 shrink-0 text-body text-fg-secondary px-[var(--field-px)] self-center">{labels.where}</div>
74
78
  }
75
79
  if (index >= 2) {
76
- const label = conjunction === 'and' ? 'And' : 'Or'
77
- return <div className="w-20 shrink-0 text-body text-fg-muted px-[var(--field-px)] self-center">{label}</div>
80
+ const label = conjunction === 'and' ? labels.and : labels.or
81
+ return <div className="w-20 shrink-0 text-body text-fg-secondary px-[var(--field-px)] self-center">{label}</div>
78
82
  }
79
83
  // index === 1:可切換的 AND/OR Select
80
84
  // minRows={2} — And/Or 2 選項,顯式縮 menu 高度避免 reserve 3 row 空白(Q5)
@@ -82,11 +86,14 @@ function ConjunctionLabel({
82
86
  <div className="w-20 shrink-0">
83
87
  <Select
84
88
  size="sm"
85
- options={CONJ_OPTIONS}
89
+ options={[
90
+ { value: 'and', label: labels.and },
91
+ { value: 'or', label: labels.or },
92
+ ]}
86
93
  value={conjunction}
87
94
  onChange={(v) => onChange(v as Conjunction)}
88
95
  minRows={2}
89
- aria-label="連接詞 — 同 group 共用"
96
+ aria-label={labels.conjunctionAriaLabel}
90
97
  />
91
98
  </div>
92
99
  )
@@ -96,7 +103,7 @@ function ConjunctionLabel({
96
103
 
97
104
  export function FilterRow({
98
105
  index, condition, conjunction, filterableColumns, fieldOptions,
99
- onChangeConjunction, onChangeField, onChangeOp, onChangeValue, onRemove,
106
+ onChangeConjunction, onChangeField, onChangeOp, onChangeValue, onRemove, labels,
100
107
  }: {
101
108
  index: number
102
109
  condition: FilterCondition
@@ -108,9 +115,10 @@ export function FilterRow({
108
115
  onChangeOp: (v: string) => void
109
116
  onChangeValue: (v: unknown) => void
110
117
  onRemove: () => void
118
+ labels: DataTableFilterPanelLabels
111
119
  }) {
112
120
  const colInfo = filterableColumns.find((c) => c.id === condition.field)
113
- const operatorOptions = getOperatorOptions(colInfo?.type)
121
+ const operatorOptions = getOperatorOptions(colInfo?.type, labels.operatorLabels)
114
122
  const hasField = !!condition.field
115
123
  const opSpec = colInfo ? getOperatorSpec(colInfo.type, condition.op) : null
116
124
  const valueShape: ValueShape | null = colInfo && opSpec
@@ -129,7 +137,7 @@ export function FilterRow({
129
137
  // - **#2 fix**:FilterValuePicker 直接是 FieldControlGroup direct child(無 wrapper div),CSS variants 命中正確
130
138
  return (
131
139
  <div className="flex items-center gap-2">
132
- <ConjunctionLabel index={index} conjunction={conjunction} onChange={onChangeConjunction} />
140
+ <ConjunctionLabel index={index} conjunction={conjunction} onChange={onChangeConjunction} labels={labels} />
133
141
  {/* **#9 fix(2026-05-04 v4)**:Field controls trigger `w-full` override 外 className,改用 Tailwind `!`
134
142
  important 強制 override(`!w-[160px]` / `!w-[120px]`),value 用 `!flex-1 !min-w-0`。
135
143
  Select 元件本身沒 destructure `style` prop 所以 inline style flex-basis 行不通,只能用 className。 */}
@@ -138,13 +146,13 @@ export function FilterRow({
138
146
  `--data-table-filter-field-width` / `--data-table-filter-op-width`(SSOT in uiSize.css)
139
147
  Behavior preserved 完好如初:flat + nested 同 width(token 是 design constant) */}
140
148
  <Select
141
- className="!w-[var(--data-table-filter-field-width)] flex-shrink-0"
149
+ className="!w-[var(--data-table-filter-field-width)] flex-shrink-0 [&_.text-fg-muted]:!text-fg-secondary"
142
150
  size="sm"
143
151
  options={fieldOptions}
144
152
  value={condition.field}
145
153
  onChange={onChangeField}
146
- placeholder="選擇欄位"
147
- aria-label="篩選欄位"
154
+ placeholder={labels.fieldPlaceholder}
155
+ aria-label={labels.fieldAriaLabel}
148
156
  />
149
157
  <Select
150
158
  className={hasValueCell ? '!w-[var(--data-table-filter-op-width)] flex-shrink-0' : '!flex-1 !min-w-0'}
@@ -153,8 +161,8 @@ export function FilterRow({
153
161
  value={condition.op}
154
162
  onChange={onChangeOp}
155
163
  disabled={!hasField}
156
- placeholder="運算子"
157
- aria-label="篩選運算子"
164
+ placeholder={labels.operatorPlaceholder}
165
+ aria-label={labels.operatorAriaLabel}
158
166
  />
159
167
  {hasValueCell && (
160
168
  <FilterValuePicker
@@ -163,13 +171,14 @@ export function FilterRow({
163
171
  onChange={onChangeValue}
164
172
  colInfo={colInfo}
165
173
  disabled={!hasField}
166
- ariaLabel={colInfo ? `${colInfo.label} 篩選值` : '篩選值'}
174
+ ariaLabel={labels.filterValueAriaLabel(colInfo?.label)}
175
+ labels={labels}
167
176
  className="!flex-1 !min-w-0"
168
177
  />
169
178
  )}
170
179
  </FieldControlGroup>
171
180
  {/* Trash 用 text Button — filter row 是 form-control row,Field 同高對齊(28 md) */}
172
- <Button variant="text" size="sm" iconOnly startIcon={Trash2} aria-label="刪除" onClick={onRemove} />
181
+ <Button variant="text" size="sm" iconOnly startIcon={Trash2} aria-label={labels.deleteCondition} onClick={onRemove} />
173
182
  </div>
174
183
  )
175
184
  }
@@ -180,6 +189,7 @@ export function GroupBlock({
180
189
  index, group, rootConjunction, filterableColumns, fieldOptions,
181
190
  onChangeRootConjunction, onChangeGroupConjunction,
182
191
  onChangeCondition, onRemoveCondition, onAddCondition, onRemoveGroup,
192
+ canAddCondition, labels,
183
193
  }: {
184
194
  index: number
185
195
  group: FilterGroup
@@ -192,11 +202,13 @@ export function GroupBlock({
192
202
  onRemoveCondition: (condId: string) => void
193
203
  onAddCondition: () => void
194
204
  onRemoveGroup: () => void
205
+ canAddCondition: boolean
206
+ labels: DataTableFilterPanelLabels
195
207
  }) {
196
208
  return (
197
209
  <div className="flex items-start gap-2">
198
210
  <div className="pt-2">
199
- <ConjunctionLabel index={index} conjunction={rootConjunction} onChange={onChangeRootConjunction} />
211
+ <ConjunctionLabel index={index} conjunction={rootConjunction} onChange={onChangeRootConjunction} labels={labels} />
200
212
  </div>
201
213
  {/* Group container 灰底 — `bg-muted`(`--muted` neutral-2,user 2026-05-09 拍板 Q3 A)。對齊 color.spec.md L651-654 「table header / tab / code block / skeleton」靜態低重要 surface semantic */}
202
214
  <div className="flex-1 min-w-0 rounded-md bg-muted p-2 flex flex-col gap-2">
@@ -216,13 +228,18 @@ export function GroupBlock({
216
228
  onChangeOp={(v) => onChangeCondition(cond.id, { op: v, value: '' })}
217
229
  onChangeValue={(v) => onChangeCondition(cond.id, { value: v })}
218
230
  onRemove={() => onRemoveCondition(cond.id)}
231
+ labels={labels}
219
232
  />
220
233
  ))}
221
234
  {/* Q9 — text variant 對齊 inline 派 + 視覺輕量 */}
222
235
  <div className="flex items-center justify-between">
223
- <Button variant="text" size="sm" startIcon={Plus} onClick={onAddCondition}>加入巢狀篩選</Button>
236
+ <Button variant="text" size="sm" startIcon={Plus} onClick={onAddCondition} disabled={!canAddCondition}>
237
+ {labels.addNestedFilter}
238
+ </Button>
224
239
  {group.children.length === 0 && (
225
- <Button variant="text" size="sm" startIcon={Trash2} danger onClick={onRemoveGroup}>移除空群組</Button>
240
+ <Button variant="text" size="sm" startIcon={Trash2} danger onClick={onRemoveGroup}>
241
+ {labels.removeEmptyGroup}
242
+ </Button>
226
243
  )}
227
244
  </div>
228
245
  </div>
@@ -0,0 +1,100 @@
1
+ // @internal — DataTableFilterPanel chrome/operator/value-picker labels shared by
2
+ // panel orchestration, row/group rendering, and value-picker dispatch.
3
+
4
+ import type { DATE_RELATIVE_GROUPS, DATE_RELATIVE_OPTIONS } from './filter-operators'
5
+
6
+ type RelativeDateGroupKey = (typeof DATE_RELATIVE_GROUPS)[number]['key']
7
+ type RelativeDateValue = (typeof DATE_RELATIVE_OPTIONS)[number]['value']
8
+
9
+ export interface DataTableFilterPanelLabels {
10
+ title: string
11
+ resetToDefault: string
12
+ close: string
13
+ where: string
14
+ and: string
15
+ or: string
16
+ conjunctionAriaLabel: string
17
+ fieldPlaceholder: string
18
+ fieldAriaLabel: string
19
+ operatorPlaceholder: string
20
+ operatorAriaLabel: string
21
+ valuePlaceholder: string
22
+ numberPlaceholder: string
23
+ relativeDatePlaceholder: string
24
+ singleSelectPlaceholder: string
25
+ multiSelectPlaceholder: string
26
+ multiSelectEmptyText: string
27
+ peoplePlaceholder: string
28
+ peopleSearchPlaceholder: string
29
+ peopleSearchAriaLabel: string
30
+ peopleEmptyText: string
31
+ filterValueAriaLabel: (columnLabel?: string) => string
32
+ deleteCondition: string
33
+ addFilter: string
34
+ addGroup: string
35
+ addNestedFilter: string
36
+ removeEmptyGroup: string
37
+ /**
38
+ * Operator key → visible label override. Missing keys fall back to
39
+ * `OPERATOR_REGISTRY` labels, preserving the registry as the default SSOT.
40
+ */
41
+ operatorLabels: Partial<Record<string, string>>
42
+ /** Relative-date group/value overrides; missing keys retain registry defaults. */
43
+ relativeDateGroupLabels: Partial<Record<RelativeDateGroupKey, string>>
44
+ relativeDateOptionLabels: Partial<Record<RelativeDateValue, string>>
45
+ }
46
+
47
+ // code-quality-allow: dead-export — public i18n defaults, re-exported by panel.
48
+ export const DATA_TABLE_FILTER_PANEL_DEFAULT_LABELS: DataTableFilterPanelLabels = {
49
+ title: '篩選',
50
+ resetToDefault: '恢復預設',
51
+ close: '關閉',
52
+ where: 'Where',
53
+ and: 'And',
54
+ or: 'Or',
55
+ conjunctionAriaLabel: '連接詞 — 同 group 共用',
56
+ fieldPlaceholder: '選擇欄位',
57
+ fieldAriaLabel: '篩選欄位',
58
+ operatorPlaceholder: '運算子',
59
+ operatorAriaLabel: '篩選運算子',
60
+ valuePlaceholder: '輸入值…',
61
+ numberPlaceholder: '輸入數字…',
62
+ relativeDatePlaceholder: '選擇相對日期',
63
+ singleSelectPlaceholder: '選擇值',
64
+ multiSelectPlaceholder: '選擇值…',
65
+ multiSelectEmptyText: '沒有符合的值',
66
+ peoplePlaceholder: '選擇人員',
67
+ peopleSearchPlaceholder: '搜尋人員…',
68
+ peopleSearchAriaLabel: '搜尋篩選人員',
69
+ peopleEmptyText: '沒有符合的人員',
70
+ filterValueAriaLabel: (columnLabel) => columnLabel ? `${columnLabel} 篩選值` : '篩選值',
71
+ deleteCondition: '刪除',
72
+ addFilter: '加篩選',
73
+ addGroup: '加入篩選器',
74
+ addNestedFilter: '加入巢狀篩選',
75
+ removeEmptyGroup: '移除空群組',
76
+ operatorLabels: {},
77
+ relativeDateGroupLabels: {},
78
+ relativeDateOptionLabels: {},
79
+ }
80
+
81
+ export function resolveDataTableFilterPanelLabels(
82
+ overrides?: Partial<DataTableFilterPanelLabels>,
83
+ ): DataTableFilterPanelLabels {
84
+ return {
85
+ ...DATA_TABLE_FILTER_PANEL_DEFAULT_LABELS,
86
+ ...overrides,
87
+ operatorLabels: {
88
+ ...DATA_TABLE_FILTER_PANEL_DEFAULT_LABELS.operatorLabels,
89
+ ...overrides?.operatorLabels,
90
+ },
91
+ relativeDateGroupLabels: {
92
+ ...DATA_TABLE_FILTER_PANEL_DEFAULT_LABELS.relativeDateGroupLabels,
93
+ ...overrides?.relativeDateGroupLabels,
94
+ },
95
+ relativeDateOptionLabels: {
96
+ ...DATA_TABLE_FILTER_PANEL_DEFAULT_LABELS.relativeDateOptionLabels,
97
+ ...overrides?.relativeDateOptionLabels,
98
+ },
99
+ }
100
+ }
@@ -31,6 +31,11 @@ import {
31
31
  type FilterTreeFlat,
32
32
  type FilterTreeNested,
33
33
  } from './filter-tree'
34
+ import {
35
+ DATA_TABLE_FILTER_PANEL_DEFAULT_LABELS,
36
+ resolveDataTableFilterPanelLabels,
37
+ type DataTableFilterPanelLabels,
38
+ } from './data-table-filter-labels'
34
39
 
35
40
  // Re-export public API from filter-tree(consumer 既有 import path 不變)
36
41
  export {
@@ -40,6 +45,8 @@ export {
40
45
  dataTableFilterMatch,
41
46
  }
42
47
  export type { Conjunction, FilterCondition, FilterGroup, FilterTree, FilterTreeFlat, FilterTreeNested }
48
+ export { DATA_TABLE_FILTER_PANEL_DEFAULT_LABELS }
49
+ export type { DataTableFilterPanelLabels }
43
50
 
44
51
  /**
45
52
  * DataTableFilterPanel — ClickUp-style 進階篩選 panel
@@ -123,6 +130,17 @@ const newEmptyGroup = (): FilterGroup => ({
123
130
  children: [newEmptyCondition()],
124
131
  })
125
132
 
133
+ const countFilterConditions = (tree: FilterTree): number =>
134
+ tree.mode === 'flat'
135
+ ? tree.children.length
136
+ : tree.children.reduce((total, group) => total + group.children.length, 0)
137
+
138
+ const normalizeMaxConditions = (maxConditions: number | undefined): number => {
139
+ if (maxConditions === undefined || maxConditions === Number.POSITIVE_INFINITY) return Number.POSITIVE_INFINITY
140
+ if (!Number.isFinite(maxConditions)) return 0
141
+ return Math.max(0, Math.floor(maxConditions))
142
+ }
143
+
126
144
  // ── Component Props ─────────────────────────────────────────────────────
127
145
 
128
146
  export interface DataTableFilterPanelProps<TData> {
@@ -144,6 +162,14 @@ export interface DataTableFilterPanelProps<TData> {
144
162
  /** Cell ⌄ menu「Filter by this」帶入的 column id(自動 add 一條 condition) */
145
163
  prefilledColumnId?: string
146
164
  onPrefillConsumed?: () => void
165
+ /**
166
+ * Condition leaf 上限(flat children 或 nested 所有 group.children 的總和)。
167
+ * 預設不限。到上限後所有 add 路徑與 prefill 都 fail closed;controlled value
168
+ * 已超過上限時保留原資料、不裁切,只停用後續新增。
169
+ */
170
+ maxConditions?: number
171
+ /** Panel chrome/operator/value-picker labels. Nested maps are deep-merged with defaults. */
172
+ labels?: Partial<DataTableFilterPanelLabels>
147
173
  onClose?: () => void
148
174
  className?: string
149
175
  }
@@ -158,6 +184,8 @@ function DataTableFilterPanelInner<TData>({
158
184
  defaultValue,
159
185
  prefilledColumnId,
160
186
  onPrefillConsumed,
187
+ maxConditions,
188
+ labels: labelsOverride,
161
189
  onClose,
162
190
  className,
163
191
  }: DataTableFilterPanelProps<TData>, ref: React.ForwardedRef<HTMLDivElement>): React.ReactElement {
@@ -166,6 +194,13 @@ function DataTableFilterPanelInner<TData>({
166
194
  () => filterableColumns.map((c) => ({ value: c.id, label: c.label })),
167
195
  [filterableColumns],
168
196
  )
197
+ const labels = React.useMemo(
198
+ () => resolveDataTableFilterPanelLabels(labelsOverride),
199
+ [labelsOverride],
200
+ )
201
+ const conditionCount = countFilterConditions(value)
202
+ const normalizedMaxConditions = normalizeMaxConditions(maxConditions)
203
+ const canAddCondition = conditionCount < normalizedMaxConditions
169
204
  // K13 後 firstCol 不再被 add* 消費(改用 newEmpty*),這裡只留 prefill effect 用(已直接讀 prefilledColumnId)。
170
205
 
171
206
  // **G fix(2026-05-04 v2)**:initial-mount 預設 1 empty row(field 未選 → op+value 自動 disabled)
@@ -179,6 +214,7 @@ function DataTableFilterPanelInner<TData>({
179
214
  initialMountDoneRef.current = true
180
215
  if (filterableColumns.length === 0) return
181
216
  if (value.children.length > 0) return
217
+ if (!canAddCondition) return
182
218
  if (value.mode === 'flat') {
183
219
  onChange({ ...value, children: [newEmptyCondition()] } as FilterTreeFlat)
184
220
  } else {
@@ -191,7 +227,7 @@ function DataTableFilterPanelInner<TData>({
191
227
  React.useEffect(() => {
192
228
  if (!prefilledColumnId) return
193
229
  const colInfo = filterableColumns.find((c) => c.id === prefilledColumnId)
194
- if (colInfo) {
230
+ if (colInfo && canAddCondition) {
195
231
  const cond: FilterCondition = {
196
232
  kind: 'cond',
197
233
  id: newId(),
@@ -234,7 +270,7 @@ function DataTableFilterPanelInner<TData>({
234
270
  onChange({ ...flatTree, children: flatTree.children.filter((c) => c.id !== id) })
235
271
  }
236
272
  const addFlatCondition = () => {
237
- if (!flatTree) return
273
+ if (!flatTree || !canAddCondition) return
238
274
  // K13 fix(2026-05-04):加篩選 → empty row(field 未選 → op+value disabled)
239
275
  // World-class:Notion / Coda / ClickUp 不 auto-select;對齊 initial mount canonical
240
276
  onChange({ ...flatTree, children: [...flatTree.children, newEmptyCondition()] })
@@ -275,7 +311,7 @@ function DataTableFilterPanelInner<TData>({
275
311
  }
276
312
  // K13 fix(2026-05-04):同 addFlatCondition,巢狀內加條件也 empty row
277
313
  const addConditionToGroup = (groupId: string) => {
278
- if (!nestedTree) return
314
+ if (!nestedTree || !canAddCondition) return
279
315
  onChange({
280
316
  ...nestedTree,
281
317
  children: nestedTree.children.map((g) =>
@@ -288,7 +324,7 @@ function DataTableFilterPanelInner<TData>({
288
324
  onChange({ ...nestedTree, children: nestedTree.children.filter((g) => g.id !== groupId) })
289
325
  }
290
326
  const addGroup = () => {
291
- if (!nestedTree) return
327
+ if (!nestedTree || !canAddCondition) return
292
328
  // K13:加群組也用 empty group
293
329
  onChange({ ...nestedTree, children: [...nestedTree.children, newEmptyGroup()] })
294
330
  }
@@ -321,14 +357,14 @@ function DataTableFilterPanelInner<TData>({
321
357
  )}>
322
358
  {/* Popover 派輕量 chrome — slot 走 COMPACT_HEADER_SLOT(=21,衍生自 PopoverTitle text-body line-box),header 自然 ~45px */}
323
359
  <SurfaceHeader className={COMPACT_HEADER_SLOT}>
324
- <PopoverTitle className="flex-1">篩選</PopoverTitle>
360
+ <PopoverTitle className="flex-1">{labels.title}</PopoverTitle>
325
361
  {/* Refresh icon — 只在 value ≠ defaultValue 時顯示(對齊 sort modified-from-default UX)
326
362
  含 ButtonDivider 對齊「欄位顯示」+「排序」chrome corner action canonical(2026-05-04) */}
327
363
  {defaultValue && !isFilterTreeEqual(value, defaultValue) && (
328
364
  <>
329
365
  <Button
330
366
  variant="text" size="sm" iconOnly startIcon={RotateCcw}
331
- aria-label="恢復預設"
367
+ aria-label={labels.resetToDefault}
332
368
  onClick={() => onChange(defaultValue)}
333
369
  />
334
370
  {onClose && <ButtonDivider />}
@@ -336,7 +372,7 @@ function DataTableFilterPanelInner<TData>({
336
372
  )}
337
373
  {onClose && (
338
374
  <PopoverClose asChild>
339
- <Button data-dismiss iconOnly dismiss size="sm" startIcon={XIcon} aria-label="關閉" onClick={onClose} />
375
+ <Button data-dismiss iconOnly dismiss size="sm" startIcon={XIcon} aria-label={labels.close} onClick={onClose} />
340
376
  </PopoverClose>
341
377
  )}
342
378
  </SurfaceHeader>
@@ -361,6 +397,7 @@ function DataTableFilterPanelInner<TData>({
361
397
  onChangeOp={(v) => updateFlatCondition(cond.id, { op: v, value: '' })}
362
398
  onChangeValue={(v) => updateFlatCondition(cond.id, { value: v })}
363
399
  onRemove={() => removeFlatCondition(cond.id)}
400
+ labels={labels}
364
401
  />
365
402
  ))}
366
403
 
@@ -378,6 +415,8 @@ function DataTableFilterPanelInner<TData>({
378
415
  onRemoveCondition={(condId) => removeGroupCondition(group.id, condId)}
379
416
  onAddCondition={() => addConditionToGroup(group.id)}
380
417
  onRemoveGroup={() => removeGroup(group.id)}
418
+ canAddCondition={canAddCondition}
419
+ labels={labels}
381
420
  />
382
421
  ))}
383
422
 
@@ -388,8 +427,9 @@ function DataTableFilterPanelInner<TData>({
388
427
  <Button
389
428
  variant="tertiary" size="sm" startIcon={Plus}
390
429
  onClick={value.mode === 'flat' ? addFlatCondition : addGroup}
430
+ disabled={!canAddCondition}
391
431
  >
392
- {value.mode === 'nested' ? '加入篩選器' : '加篩選'}
432
+ {value.mode === 'nested' ? labels.addGroup : labels.addFilter}
393
433
  </Button>
394
434
  </div>
395
435
  </SurfaceBody>