@miragon/mcp-toolkit-ui 2.6.0 → 2.8.0

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 (511) hide show
  1. package/README.md +14 -5
  2. package/dist/app/adapt-data-widget.d.ts +1 -22
  3. package/dist/app/adapt-data-widget.d.ts.map +1 -1
  4. package/dist/app/adapt-data-widget.js +1 -35
  5. package/dist/app/adapt-data-widget.js.map +1 -1
  6. package/dist/app/builder/BuilderBanners.d.ts +7 -2
  7. package/dist/app/builder/BuilderBanners.d.ts.map +1 -1
  8. package/dist/app/builder/BuilderBanners.js +8 -3
  9. package/dist/app/builder/BuilderBanners.js.map +1 -1
  10. package/dist/app/builder/CanvasRow.d.ts +4 -2
  11. package/dist/app/builder/CanvasRow.d.ts.map +1 -1
  12. package/dist/app/builder/CanvasRow.js +29 -17
  13. package/dist/app/builder/CanvasRow.js.map +1 -1
  14. package/dist/app/builder/CatalogueSheet.d.ts.map +1 -1
  15. package/dist/app/builder/CatalogueSheet.js +14 -6
  16. package/dist/app/builder/CatalogueSheet.js.map +1 -1
  17. package/dist/app/builder/CellPropsSheet.d.ts +7 -3
  18. package/dist/app/builder/CellPropsSheet.d.ts.map +1 -1
  19. package/dist/app/builder/CellPropsSheet.js +10 -3
  20. package/dist/app/builder/CellPropsSheet.js.map +1 -1
  21. package/dist/app/builder/DiscardDialog.d.ts +18 -0
  22. package/dist/app/builder/DiscardDialog.d.ts.map +1 -0
  23. package/dist/app/builder/DiscardDialog.js +18 -0
  24. package/dist/app/builder/DiscardDialog.js.map +1 -0
  25. package/dist/app/builder/EmptyHint.d.ts +4 -0
  26. package/dist/app/builder/EmptyHint.d.ts.map +1 -1
  27. package/dist/app/builder/EmptyHint.js +6 -1
  28. package/dist/app/builder/EmptyHint.js.map +1 -1
  29. package/dist/app/builder/LayoutSurface.d.ts +33 -9
  30. package/dist/app/builder/LayoutSurface.d.ts.map +1 -1
  31. package/dist/app/builder/LayoutSurface.js +74 -23
  32. package/dist/app/builder/LayoutSurface.js.map +1 -1
  33. package/dist/app/builder/PipelineStrip.d.ts +5 -2
  34. package/dist/app/builder/PipelineStrip.d.ts.map +1 -1
  35. package/dist/app/builder/PipelineStrip.js +33 -15
  36. package/dist/app/builder/PipelineStrip.js.map +1 -1
  37. package/dist/app/builder/SaveDialog.d.ts +9 -1
  38. package/dist/app/builder/SaveDialog.d.ts.map +1 -1
  39. package/dist/app/builder/SaveDialog.js +9 -3
  40. package/dist/app/builder/SaveDialog.js.map +1 -1
  41. package/dist/app/builder/Toolbar.d.ts +14 -4
  42. package/dist/app/builder/Toolbar.d.ts.map +1 -1
  43. package/dist/app/builder/Toolbar.js +24 -7
  44. package/dist/app/builder/Toolbar.js.map +1 -1
  45. package/dist/app/builder/Workspace.d.ts +23 -3
  46. package/dist/app/builder/Workspace.d.ts.map +1 -1
  47. package/dist/app/builder/Workspace.js +58 -8
  48. package/dist/app/builder/Workspace.js.map +1 -1
  49. package/dist/app/builder/builder-model.d.ts +146 -3
  50. package/dist/app/builder/builder-model.d.ts.map +1 -1
  51. package/dist/app/builder/builder-model.js +196 -5
  52. package/dist/app/builder/builder-model.js.map +1 -1
  53. package/dist/app/builder/labels.d.ts +13 -62
  54. package/dist/app/builder/labels.d.ts.map +1 -1
  55. package/dist/app/builder/labels.js +9 -58
  56. package/dist/app/builder/labels.js.map +1 -1
  57. package/dist/app/builder/reveal.d.ts +10 -0
  58. package/dist/app/builder/reveal.d.ts.map +1 -0
  59. package/dist/app/builder/reveal.js +10 -0
  60. package/dist/app/builder/reveal.js.map +1 -0
  61. package/dist/app/builder/status-chip.d.ts.map +1 -1
  62. package/dist/app/builder/status-chip.js +3 -2
  63. package/dist/app/builder/status-chip.js.map +1 -1
  64. package/dist/app/builder/use-builder-catalogue.d.ts +5 -1
  65. package/dist/app/builder/use-builder-catalogue.d.ts.map +1 -1
  66. package/dist/app/builder/use-builder-catalogue.js +50 -17
  67. package/dist/app/builder/use-builder-catalogue.js.map +1 -1
  68. package/dist/app/builder/use-builder-session.d.ts +30 -0
  69. package/dist/app/builder/use-builder-session.d.ts.map +1 -0
  70. package/dist/app/builder/use-builder-session.js +58 -0
  71. package/dist/app/builder/use-builder-session.js.map +1 -0
  72. package/dist/app/builder/use-display-resolver.d.ts +17 -0
  73. package/dist/app/builder/use-display-resolver.d.ts.map +1 -0
  74. package/dist/app/builder/use-display-resolver.js +16 -0
  75. package/dist/app/builder/use-display-resolver.js.map +1 -0
  76. package/dist/app/builder/use-save-dashboard.d.ts +3 -1
  77. package/dist/app/builder/use-save-dashboard.d.ts.map +1 -1
  78. package/dist/app/builder/use-save-dashboard.js +3 -1
  79. package/dist/app/builder/use-save-dashboard.js.map +1 -1
  80. package/dist/app/index.d.ts +8 -3
  81. package/dist/app/index.d.ts.map +1 -1
  82. package/dist/app/index.js +8 -2
  83. package/dist/app/index.js.map +1 -1
  84. package/dist/app/layout-builder.d.ts +18 -25
  85. package/dist/app/layout-builder.d.ts.map +1 -1
  86. package/dist/app/layout-builder.js +84 -78
  87. package/dist/app/layout-builder.js.map +1 -1
  88. package/dist/app/mcp-app-view-chrome.d.ts +103 -20
  89. package/dist/app/mcp-app-view-chrome.d.ts.map +1 -1
  90. package/dist/app/mcp-app-view-chrome.js +125 -40
  91. package/dist/app/mcp-app-view-chrome.js.map +1 -1
  92. package/dist/app/mcp-app-view-states.d.ts +23 -0
  93. package/dist/app/mcp-app-view-states.d.ts.map +1 -0
  94. package/dist/app/mcp-app-view-states.js +55 -0
  95. package/dist/app/mcp-app-view-states.js.map +1 -0
  96. package/dist/app/mcp-app-view.d.ts +72 -5
  97. package/dist/app/mcp-app-view.d.ts.map +1 -1
  98. package/dist/app/mcp-app-view.js +241 -141
  99. package/dist/app/mcp-app-view.js.map +1 -1
  100. package/dist/app/mcp-toolkit-app.d.ts +8 -2
  101. package/dist/app/mcp-toolkit-app.d.ts.map +1 -1
  102. package/dist/app/mcp-toolkit-app.js +33 -4
  103. package/dist/app/mcp-toolkit-app.js.map +1 -1
  104. package/dist/app/mcp-use-host-bridge.d.ts +15 -6
  105. package/dist/app/mcp-use-host-bridge.d.ts.map +1 -1
  106. package/dist/app/mcp-use-host-bridge.js +51 -22
  107. package/dist/app/mcp-use-host-bridge.js.map +1 -1
  108. package/dist/app/model-context.d.ts +71 -0
  109. package/dist/app/model-context.d.ts.map +1 -0
  110. package/dist/app/model-context.js +101 -0
  111. package/dist/app/model-context.js.map +1 -0
  112. package/dist/app/use-build-mode-focus.d.ts +33 -0
  113. package/dist/app/use-build-mode-focus.d.ts.map +1 -0
  114. package/dist/app/use-build-mode-focus.js +59 -0
  115. package/dist/app/use-build-mode-focus.js.map +1 -0
  116. package/dist/app/use-view-actions.d.ts +31 -0
  117. package/dist/app/use-view-actions.d.ts.map +1 -0
  118. package/dist/app/use-view-actions.js +81 -0
  119. package/dist/app/use-view-actions.js.map +1 -0
  120. package/dist/app/use-view-lifecycle.d.ts +42 -0
  121. package/dist/app/use-view-lifecycle.d.ts.map +1 -0
  122. package/dist/app/use-view-lifecycle.js +81 -0
  123. package/dist/app/use-view-lifecycle.js.map +1 -0
  124. package/dist/app/view-lifecycle.d.ts +185 -0
  125. package/dist/app/view-lifecycle.d.ts.map +1 -0
  126. package/dist/app/view-lifecycle.js +207 -0
  127. package/dist/app/view-lifecycle.js.map +1 -0
  128. package/dist/app/widget-fixture.d.ts +10 -3
  129. package/dist/app/widget-fixture.d.ts.map +1 -1
  130. package/dist/app/widget-fixture.js +21 -8
  131. package/dist/app/widget-fixture.js.map +1 -1
  132. package/dist/app/widget-props-sheet.d.ts +6 -10
  133. package/dist/app/widget-props-sheet.d.ts.map +1 -1
  134. package/dist/app/widget-props-sheet.js +25 -20
  135. package/dist/app/widget-props-sheet.js.map +1 -1
  136. package/dist/app/widget-renderer.d.ts +41 -1
  137. package/dist/app/widget-renderer.d.ts.map +1 -1
  138. package/dist/app/widget-renderer.js +82 -31
  139. package/dist/app/widget-renderer.js.map +1 -1
  140. package/dist/components/DrillButton.d.ts +9 -6
  141. package/dist/components/DrillButton.d.ts.map +1 -1
  142. package/dist/components/DrillButton.js +9 -6
  143. package/dist/components/DrillButton.js.map +1 -1
  144. package/dist/components/EmptyState.d.ts +33 -0
  145. package/dist/components/EmptyState.d.ts.map +1 -0
  146. package/dist/components/EmptyState.js +19 -0
  147. package/dist/components/EmptyState.js.map +1 -0
  148. package/dist/components/ErrorState.d.ts +53 -0
  149. package/dist/components/ErrorState.d.ts.map +1 -0
  150. package/dist/components/ErrorState.js +77 -0
  151. package/dist/components/ErrorState.js.map +1 -0
  152. package/dist/components/FilterBar.d.ts +16 -5
  153. package/dist/components/FilterBar.d.ts.map +1 -1
  154. package/dist/components/FilterBar.js +13 -5
  155. package/dist/components/FilterBar.js.map +1 -1
  156. package/dist/components/GroupCard.d.ts.map +1 -1
  157. package/dist/components/GroupCard.js +2 -1
  158. package/dist/components/GroupCard.js.map +1 -1
  159. package/dist/components/KpiGrid.d.ts +70 -18
  160. package/dist/components/KpiGrid.d.ts.map +1 -1
  161. package/dist/components/KpiGrid.js +112 -23
  162. package/dist/components/KpiGrid.js.map +1 -1
  163. package/dist/components/ListFooter.d.ts +32 -4
  164. package/dist/components/ListFooter.d.ts.map +1 -1
  165. package/dist/components/ListFooter.js +35 -6
  166. package/dist/components/ListFooter.js.map +1 -1
  167. package/dist/components/PagedListFooter.d.ts +57 -0
  168. package/dist/components/PagedListFooter.d.ts.map +1 -0
  169. package/dist/components/PagedListFooter.js +27 -0
  170. package/dist/components/PagedListFooter.js.map +1 -0
  171. package/dist/components/Pills.d.ts +36 -12
  172. package/dist/components/Pills.d.ts.map +1 -1
  173. package/dist/components/Pills.js +29 -8
  174. package/dist/components/Pills.js.map +1 -1
  175. package/dist/components/SectionHeading.d.ts +3 -1
  176. package/dist/components/SectionHeading.d.ts.map +1 -1
  177. package/dist/components/SectionHeading.js +6 -2
  178. package/dist/components/SectionHeading.js.map +1 -1
  179. package/dist/components/Toast.d.ts +43 -0
  180. package/dist/components/Toast.d.ts.map +1 -0
  181. package/dist/components/Toast.js +106 -0
  182. package/dist/components/Toast.js.map +1 -0
  183. package/dist/components/WidgetHeader.d.ts +34 -10
  184. package/dist/components/WidgetHeader.d.ts.map +1 -1
  185. package/dist/components/WidgetHeader.js +11 -6
  186. package/dist/components/WidgetHeader.js.map +1 -1
  187. package/dist/hooks/index.d.ts +4 -4
  188. package/dist/hooks/index.d.ts.map +1 -1
  189. package/dist/hooks/index.js +1 -1
  190. package/dist/hooks/index.js.map +1 -1
  191. package/dist/hooks/tool-result-recovery-model.d.ts +155 -0
  192. package/dist/hooks/tool-result-recovery-model.d.ts.map +1 -0
  193. package/dist/hooks/tool-result-recovery-model.js +273 -0
  194. package/dist/hooks/tool-result-recovery-model.js.map +1 -0
  195. package/dist/hooks/use-tool-query.d.ts +56 -14
  196. package/dist/hooks/use-tool-query.d.ts.map +1 -1
  197. package/dist/hooks/use-tool-query.js +102 -25
  198. package/dist/hooks/use-tool-query.js.map +1 -1
  199. package/dist/hooks/use-tool-result-recovery.d.ts +61 -44
  200. package/dist/hooks/use-tool-result-recovery.d.ts.map +1 -1
  201. package/dist/hooks/use-tool-result-recovery.js +62 -110
  202. package/dist/hooks/use-tool-result-recovery.js.map +1 -1
  203. package/dist/hooks/use-view-data.d.ts +55 -7
  204. package/dist/hooks/use-view-data.d.ts.map +1 -1
  205. package/dist/hooks/use-view-data.js +81 -11
  206. package/dist/hooks/use-view-data.js.map +1 -1
  207. package/dist/hooks/use-view-tool-query.d.ts +12 -7
  208. package/dist/hooks/use-view-tool-query.d.ts.map +1 -1
  209. package/dist/hooks/use-view-tool-query.js +15 -14
  210. package/dist/hooks/use-view-tool-query.js.map +1 -1
  211. package/dist/index.d.ts +27 -13
  212. package/dist/index.d.ts.map +1 -1
  213. package/dist/index.js +29 -7
  214. package/dist/index.js.map +1 -1
  215. package/dist/lib/focus-return.d.ts +61 -0
  216. package/dist/lib/focus-return.d.ts.map +1 -0
  217. package/dist/lib/focus-return.js +46 -0
  218. package/dist/lib/focus-return.js.map +1 -0
  219. package/dist/lib/focus-ring.d.ts +18 -0
  220. package/dist/lib/focus-ring.d.ts.map +1 -0
  221. package/dist/lib/focus-ring.js +18 -0
  222. package/dist/lib/focus-ring.js.map +1 -0
  223. package/dist/lib/icon-stroke.d.ts +12 -0
  224. package/dist/lib/icon-stroke.d.ts.map +1 -0
  225. package/dist/lib/icon-stroke.js +12 -0
  226. package/dist/lib/icon-stroke.js.map +1 -0
  227. package/dist/lib/parse-tool-result.d.ts +23 -1
  228. package/dist/lib/parse-tool-result.d.ts.map +1 -1
  229. package/dist/lib/parse-tool-result.js +38 -2
  230. package/dist/lib/parse-tool-result.js.map +1 -1
  231. package/dist/lib/toast-model.d.ts +86 -0
  232. package/dist/lib/toast-model.d.ts.map +1 -0
  233. package/dist/lib/toast-model.js +102 -0
  234. package/dist/lib/toast-model.js.map +1 -0
  235. package/dist/lib/tone-utils.d.ts +95 -9
  236. package/dist/lib/tone-utils.d.ts.map +1 -1
  237. package/dist/lib/tone-utils.js +100 -9
  238. package/dist/lib/tone-utils.js.map +1 -1
  239. package/dist/lib/tool-query.d.ts +107 -0
  240. package/dist/lib/tool-query.d.ts.map +1 -0
  241. package/dist/lib/tool-query.js +182 -0
  242. package/dist/lib/tool-query.js.map +1 -0
  243. package/dist/lib/tool-result-observer.d.ts +108 -0
  244. package/dist/lib/tool-result-observer.d.ts.map +1 -0
  245. package/dist/lib/tool-result-observer.js +158 -0
  246. package/dist/lib/tool-result-observer.js.map +1 -0
  247. package/dist/lib/typography.d.ts +37 -0
  248. package/dist/lib/typography.d.ts.map +1 -0
  249. package/dist/lib/typography.js +41 -0
  250. package/dist/lib/typography.js.map +1 -0
  251. package/dist/lib/utils.d.ts.map +1 -1
  252. package/dist/lib/utils.js +11 -1
  253. package/dist/lib/utils.js.map +1 -1
  254. package/dist/locale/index.d.ts +3 -1
  255. package/dist/locale/index.d.ts.map +1 -1
  256. package/dist/locale/index.js +2 -1
  257. package/dist/locale/index.js.map +1 -1
  258. package/dist/locale/locale-provider.d.ts +42 -1
  259. package/dist/locale/locale-provider.d.ts.map +1 -1
  260. package/dist/locale/locale-provider.js +44 -3
  261. package/dist/locale/locale-provider.js.map +1 -1
  262. package/dist/locale/message-types.d.ts +336 -0
  263. package/dist/locale/message-types.d.ts.map +1 -0
  264. package/dist/locale/message-types.js +7 -0
  265. package/dist/locale/message-types.js.map +1 -0
  266. package/dist/locale/messages-de.d.ts +9 -0
  267. package/dist/locale/messages-de.d.ts.map +1 -0
  268. package/dist/locale/messages-de.js +185 -0
  269. package/dist/locale/messages-de.js.map +1 -0
  270. package/dist/locale/messages-en.d.ts +8 -0
  271. package/dist/locale/messages-en.d.ts.map +1 -0
  272. package/dist/locale/messages-en.js +184 -0
  273. package/dist/locale/messages-en.js.map +1 -0
  274. package/dist/locale/toolkit-messages.d.ts +57 -0
  275. package/dist/locale/toolkit-messages.d.ts.map +1 -0
  276. package/dist/locale/toolkit-messages.js +103 -0
  277. package/dist/locale/toolkit-messages.js.map +1 -0
  278. package/dist/primitives/alert.d.ts +1 -1
  279. package/dist/primitives/alert.d.ts.map +1 -1
  280. package/dist/primitives/alert.js +6 -2
  281. package/dist/primitives/alert.js.map +1 -1
  282. package/dist/primitives/badge.d.ts +1 -1
  283. package/dist/primitives/badge.d.ts.map +1 -1
  284. package/dist/primitives/badge.js +19 -6
  285. package/dist/primitives/badge.js.map +1 -1
  286. package/dist/primitives/button.d.ts +1 -1
  287. package/dist/primitives/button.d.ts.map +1 -1
  288. package/dist/primitives/button.js +12 -8
  289. package/dist/primitives/button.js.map +1 -1
  290. package/dist/primitives/card.js +4 -4
  291. package/dist/primitives/card.js.map +1 -1
  292. package/dist/primitives/dialog.d.ts +7 -3
  293. package/dist/primitives/dialog.d.ts.map +1 -1
  294. package/dist/primitives/dialog.js +22 -8
  295. package/dist/primitives/dialog.js.map +1 -1
  296. package/dist/primitives/input.d.ts.map +1 -1
  297. package/dist/primitives/input.js +4 -1
  298. package/dist/primitives/input.js.map +1 -1
  299. package/dist/primitives/scroll-area.js +1 -1
  300. package/dist/primitives/scroll-area.js.map +1 -1
  301. package/dist/primitives/select.d.ts.map +1 -1
  302. package/dist/primitives/select.js +6 -3
  303. package/dist/primitives/select.js.map +1 -1
  304. package/dist/primitives/sheet.d.ts +3 -1
  305. package/dist/primitives/sheet.d.ts.map +1 -1
  306. package/dist/primitives/sheet.js +17 -7
  307. package/dist/primitives/sheet.js.map +1 -1
  308. package/dist/primitives/skeleton.js +1 -1
  309. package/dist/primitives/skeleton.js.map +1 -1
  310. package/dist/primitives/switch.d.ts.map +1 -1
  311. package/dist/primitives/switch.js +2 -1
  312. package/dist/primitives/switch.js.map +1 -1
  313. package/dist/primitives/tabs.d.ts.map +1 -1
  314. package/dist/primitives/tabs.js +5 -3
  315. package/dist/primitives/tabs.js.map +1 -1
  316. package/dist/providers/query-provider.d.ts +32 -1
  317. package/dist/providers/query-provider.d.ts.map +1 -1
  318. package/dist/providers/query-provider.js +53 -4
  319. package/dist/providers/query-provider.js.map +1 -1
  320. package/dist/render-cases-primitives.js +1 -1
  321. package/dist/render-cases-primitives.js.map +1 -1
  322. package/dist/render-cases.d.ts.map +1 -1
  323. package/dist/render-cases.js +48 -6
  324. package/dist/render-cases.js.map +1 -1
  325. package/dist/render-effects.d.ts +42 -0
  326. package/dist/render-effects.d.ts.map +1 -0
  327. package/dist/render-effects.js +81 -0
  328. package/dist/render-effects.js.map +1 -0
  329. package/dist/render-scan.d.ts +25 -0
  330. package/dist/render-scan.d.ts.map +1 -0
  331. package/dist/render-scan.js +66 -0
  332. package/dist/render-scan.js.map +1 -0
  333. package/dist/theme/cd-tokens.d.ts +66 -0
  334. package/dist/theme/cd-tokens.d.ts.map +1 -0
  335. package/dist/theme/cd-tokens.js +64 -0
  336. package/dist/theme/cd-tokens.js.map +1 -0
  337. package/dist/theme/create-theme.d.ts +214 -39
  338. package/dist/theme/create-theme.d.ts.map +1 -1
  339. package/dist/theme/create-theme.js +204 -13
  340. package/dist/theme/create-theme.js.map +1 -1
  341. package/dist/theme/fonts.d.ts +38 -0
  342. package/dist/theme/fonts.d.ts.map +1 -0
  343. package/dist/theme/fonts.js +56 -0
  344. package/dist/theme/fonts.js.map +1 -0
  345. package/dist/theme/host-fonts.d.ts +39 -0
  346. package/dist/theme/host-fonts.d.ts.map +1 -0
  347. package/dist/theme/host-fonts.js +44 -0
  348. package/dist/theme/host-fonts.js.map +1 -0
  349. package/dist/theme/index.d.ts +8 -1
  350. package/dist/theme/index.d.ts.map +1 -1
  351. package/dist/theme/index.js +5 -1
  352. package/dist/theme/index.js.map +1 -1
  353. package/dist/theme/portal-container.d.ts +16 -0
  354. package/dist/theme/portal-container.d.ts.map +1 -0
  355. package/dist/theme/portal-container.js +19 -0
  356. package/dist/theme/portal-container.js.map +1 -0
  357. package/dist/theme/presets.d.ts +24 -9
  358. package/dist/theme/presets.d.ts.map +1 -1
  359. package/dist/theme/presets.js +44 -11
  360. package/dist/theme/presets.js.map +1 -1
  361. package/dist/theme/theme-provider.d.ts +27 -9
  362. package/dist/theme/theme-provider.d.ts.map +1 -1
  363. package/dist/theme/theme-provider.js +42 -20
  364. package/dist/theme/theme-provider.js.map +1 -1
  365. package/package.json +11 -5
  366. package/src/app/adapt-data-widget.tsx +2 -42
  367. package/src/app/builder/BuilderBanners.tsx +26 -13
  368. package/src/app/builder/CanvasRow.tsx +79 -32
  369. package/src/app/builder/CatalogueSheet.tsx +58 -35
  370. package/src/app/builder/CellPropsSheet.tsx +17 -2
  371. package/src/app/builder/DiscardDialog.tsx +59 -0
  372. package/src/app/builder/EmptyHint.tsx +16 -11
  373. package/src/app/builder/LayoutSurface.tsx +207 -67
  374. package/src/app/builder/PipelineStrip.tsx +265 -156
  375. package/src/app/builder/SaveDialog.tsx +25 -14
  376. package/src/app/builder/Toolbar.tsx +154 -57
  377. package/src/app/builder/Workspace.tsx +226 -56
  378. package/src/app/builder/builder-feedback.test.tsx +75 -0
  379. package/src/app/builder/builder-model.test.ts +5 -0
  380. package/src/app/builder/builder-model.ts +346 -6
  381. package/src/app/builder/builder-model.ux.test.ts +456 -0
  382. package/src/app/builder/builder-ux.test.tsx +844 -0
  383. package/src/app/builder/labels.ts +15 -121
  384. package/src/app/builder/reveal.ts +10 -0
  385. package/src/app/builder/status-chip.tsx +8 -4
  386. package/src/app/builder/use-builder-catalogue.ts +62 -19
  387. package/src/app/builder/use-builder-session.ts +68 -0
  388. package/src/app/builder/use-display-resolver.ts +24 -0
  389. package/src/app/builder/use-save-dashboard.ts +5 -0
  390. package/src/app/index.ts +20 -3
  391. package/src/app/layout-builder.tsx +193 -186
  392. package/src/app/mcp-app-view-chrome.test.tsx +487 -14
  393. package/src/app/mcp-app-view-chrome.tsx +328 -100
  394. package/src/app/mcp-app-view-states.tsx +140 -0
  395. package/src/app/mcp-app-view.effects.test.tsx +292 -0
  396. package/src/app/mcp-app-view.test.tsx +478 -0
  397. package/src/app/mcp-app-view.tsx +447 -190
  398. package/src/app/mcp-toolkit-app.test.tsx +40 -0
  399. package/src/app/mcp-toolkit-app.tsx +39 -8
  400. package/src/app/mcp-use-host-bridge.test.tsx +180 -0
  401. package/src/app/mcp-use-host-bridge.tsx +67 -24
  402. package/src/app/model-context.test.tsx +116 -0
  403. package/src/app/model-context.tsx +125 -0
  404. package/src/app/use-build-mode-focus.ts +62 -0
  405. package/src/app/use-view-actions.ts +89 -0
  406. package/src/app/use-view-lifecycle.ts +122 -0
  407. package/src/app/view-lifecycle.test.ts +443 -0
  408. package/src/app/view-lifecycle.ts +334 -0
  409. package/src/app/widget-fixture.effects.test.tsx +51 -0
  410. package/src/app/widget-fixture.render.test.tsx +81 -0
  411. package/src/app/widget-fixture.tsx +34 -11
  412. package/src/app/widget-props-sheet.tsx +67 -39
  413. package/src/app/widget-renderer.render.test.tsx +131 -0
  414. package/src/app/widget-renderer.tsx +136 -38
  415. package/src/components/DrillButton.tsx +24 -10
  416. package/src/components/EmptyState.tsx +83 -0
  417. package/src/components/ErrorState.tsx +204 -0
  418. package/src/components/FilterBar.test.tsx +82 -0
  419. package/src/components/FilterBar.tsx +56 -27
  420. package/src/components/GroupCard.tsx +3 -2
  421. package/src/components/KpiGrid.test.tsx +279 -0
  422. package/src/components/KpiGrid.tsx +329 -91
  423. package/src/components/ListFooter.test.tsx +116 -0
  424. package/src/components/ListFooter.tsx +110 -12
  425. package/src/components/PagedListFooter.test.tsx +140 -0
  426. package/src/components/PagedListFooter.tsx +107 -0
  427. package/src/components/Pills.test.tsx +77 -0
  428. package/src/components/Pills.tsx +78 -16
  429. package/src/components/SectionHeading.tsx +9 -3
  430. package/src/components/Toast.tsx +206 -0
  431. package/src/components/WidgetHeader.test.tsx +78 -0
  432. package/src/components/WidgetHeader.tsx +63 -16
  433. package/src/components/feedback-states.test.tsx +314 -0
  434. package/src/components/tone-figures.test.tsx +58 -0
  435. package/src/fonts.css +19 -0
  436. package/src/globals.css +9 -165
  437. package/src/hooks/index.ts +6 -3
  438. package/src/hooks/tool-result-recovery-model.test.ts +744 -0
  439. package/src/hooks/tool-result-recovery-model.ts +418 -0
  440. package/src/hooks/use-tool-query.effects.test.tsx +205 -0
  441. package/src/hooks/use-tool-query.ts +156 -33
  442. package/src/hooks/use-tool-result-recovery.effects.test.tsx +141 -0
  443. package/src/hooks/use-tool-result-recovery.test.ts +28 -5
  444. package/src/hooks/use-tool-result-recovery.ts +101 -131
  445. package/src/hooks/use-view-data.test.tsx +169 -0
  446. package/src/hooks/use-view-data.ts +131 -10
  447. package/src/hooks/use-view-tool-query.ts +25 -19
  448. package/src/i18n-coverage.test.tsx +425 -0
  449. package/src/index.ts +102 -13
  450. package/src/lib/focus-return.test.ts +288 -0
  451. package/src/lib/focus-return.ts +96 -0
  452. package/src/lib/focus-ring.ts +20 -0
  453. package/src/lib/icon-stroke.test.ts +59 -0
  454. package/src/lib/icon-stroke.ts +13 -0
  455. package/src/lib/parse-tool-result.test.ts +46 -1
  456. package/src/lib/parse-tool-result.ts +40 -2
  457. package/src/lib/toast-model.test.ts +233 -0
  458. package/src/lib/toast-model.ts +165 -0
  459. package/src/lib/tone-utils.test.ts +125 -16
  460. package/src/lib/tone-utils.ts +144 -15
  461. package/src/lib/tool-query.test.ts +569 -0
  462. package/src/lib/tool-query.ts +253 -0
  463. package/src/lib/tool-result-observer.test.ts +296 -0
  464. package/src/lib/tool-result-observer.ts +211 -0
  465. package/src/lib/typography.test.ts +45 -0
  466. package/src/lib/typography.ts +47 -0
  467. package/src/lib/utils.ts +12 -1
  468. package/src/locale/index.ts +14 -1
  469. package/src/locale/locale-provider.test.tsx +87 -0
  470. package/src/locale/locale-provider.tsx +90 -3
  471. package/src/locale/message-types.ts +337 -0
  472. package/src/locale/messages-de.ts +198 -0
  473. package/src/locale/messages-en.ts +196 -0
  474. package/src/locale/toolkit-messages.test.ts +349 -0
  475. package/src/locale/toolkit-messages.ts +143 -0
  476. package/src/no-hardcoded-copy.test.ts +432 -0
  477. package/src/primitives/alert.tsx +7 -2
  478. package/src/primitives/badge.tsx +19 -7
  479. package/src/primitives/button.tsx +13 -8
  480. package/src/primitives/card.tsx +4 -4
  481. package/src/primitives/dialog.tsx +42 -8
  482. package/src/primitives/input.tsx +5 -3
  483. package/src/primitives/scroll-area.tsx +1 -1
  484. package/src/primitives/select.tsx +7 -4
  485. package/src/primitives/sheet.tsx +33 -6
  486. package/src/primitives/skeleton.tsx +1 -1
  487. package/src/primitives/switch.tsx +4 -2
  488. package/src/primitives/tabs.tsx +9 -6
  489. package/src/providers/query-provider.test.tsx +154 -0
  490. package/src/providers/query-provider.tsx +66 -5
  491. package/src/render-cases-primitives.tsx +1 -1
  492. package/src/render-cases.tsx +82 -6
  493. package/src/render-effects.tsx +96 -0
  494. package/src/render-scan.ts +75 -0
  495. package/src/theme/cd-tokens.generated.css +48 -0
  496. package/src/theme/cd-tokens.test.ts +109 -0
  497. package/src/theme/cd-tokens.ts +67 -0
  498. package/src/theme/create-theme.test.ts +253 -5
  499. package/src/theme/create-theme.ts +341 -41
  500. package/src/theme/fonts.test.ts +85 -0
  501. package/src/theme/fonts.ts +72 -0
  502. package/src/theme/host-fonts.test.ts +135 -0
  503. package/src/theme/host-fonts.ts +52 -0
  504. package/src/theme/index.ts +15 -1
  505. package/src/theme/portal-container.ts +20 -0
  506. package/src/theme/presets.ts +44 -11
  507. package/src/theme/theme-css.test.ts +619 -0
  508. package/src/theme/theme-provider.test.tsx +82 -0
  509. package/src/theme/theme-provider.tsx +60 -24
  510. package/src/theme.css +344 -0
  511. package/ui-catalog.json +783 -49
@@ -1,37 +1,51 @@
1
1
  import type { ReactNode } from "react"
2
+ import { ArrowRight } from "lucide-react"
3
+ import { FOCUS_RING } from "../lib/focus-ring.js"
4
+ import { cn } from "../lib/utils.js"
2
5
 
3
6
  export interface DrillButtonProps {
4
7
  /** Button label. */
5
8
  children: ReactNode
6
9
  /** Drill/navigation handler — wire to in-widget navigation (e.g. `showWidget`). */
7
10
  onDrill: () => void
8
- /** Optional leading glyph (e.g. a domain icon). The trailing → is always rendered. */
11
+ /**
12
+ * Optional leading icon — a Lucide icon of the domain, never a typed glyph.
13
+ * Decorative (`aria-hidden`). The trailing arrow is always rendered.
14
+ */
9
15
  icon?: ReactNode
10
16
  /** `sm` for table rows, `md` for surface headers. Default `"sm"`. */
11
17
  size?: "sm" | "md"
12
- /** Accessible label when the visible text is terse. */
18
+ /** Accessible label when the visible text is terse; keep the visible text in it (WCAG 2.5.3). */
13
19
  ariaLabel?: string
14
20
  }
15
21
 
16
22
  /**
17
23
  * The single, deterministic in-widget navigation control: a neutral outline
18
- * button with a trailing → that drills into a detail/sub-view. Use it for
19
- * every in-app drill (row "Open", header nav) so they read identically and stay
20
- * visually quieter than primary/agentic actions. Decoupled from any host — pass
21
- * an `onDrill` callback (e.g. wrapping `useHostActions().showWidget`).
24
+ * button with a trailing Lucide arrow that drills into a detail/sub-view. Use
25
+ * it for every in-app drill (row "Open", header nav) so they read identically
26
+ * and stay visually quieter than primary/agentic actions. Decoupled from any
27
+ * host — pass an `onDrill` callback (e.g. wrapping `useHostActions().showWidget`).
22
28
  */
23
29
  export function DrillButton({ children, onDrill, icon, size = "sm", ariaLabel }: DrillButtonProps) {
24
- const pad = size === "md" ? "px-3 py-1.5 text-sm" : "px-2.5 py-1 text-xs"
30
+ const pad = size === "md" ? "px-3 py-1.5 text-dense" : "px-2.5 py-1 text-small"
25
31
  return (
26
32
  <button
27
33
  type="button"
28
34
  onClick={onDrill}
29
35
  aria-label={ariaLabel}
30
- className={`border-border text-foreground hover:bg-muted focus-visible:ring-ring inline-flex items-center gap-1 rounded-md border font-medium transition-colors outline-none focus-visible:ring-2 ${pad}`}
36
+ className={cn(
37
+ "border-border text-foreground hover:border-focus hover:bg-accent hover:text-accent-foreground inline-flex items-center gap-1 rounded-md border font-medium transition-colors [&_svg:not([class*='size-'])]:size-4",
38
+ FOCUS_RING,
39
+ pad,
40
+ )}
31
41
  >
32
- {icon && <span aria-hidden="true">{icon}</span>}
42
+ {icon && (
43
+ <span aria-hidden="true" className="inline-flex">
44
+ {icon}
45
+ </span>
46
+ )}
33
47
  {children}
34
- <span aria-hidden="true">→</span>
48
+ <ArrowRight aria-hidden="true" className="size-4 shrink-0" />
35
49
  </button>
36
50
  )
37
51
  }
@@ -0,0 +1,83 @@
1
+ import type { ReactNode } from "react"
2
+ import { resolveTone, TONE_ICON } from "../lib/tone-utils.js"
3
+ import { cn } from "../lib/utils.js"
4
+
5
+ /** Heading element for an empty/error state title (the surface decides the outline level). */
6
+ export type StateHeadingLevel = "h2" | "h3" | "h4" | "p"
7
+
8
+ export interface EmptyStateProps {
9
+ /**
10
+ * Icon or mark above the title, rendered decoratively (`aria-hidden`): a
11
+ * Lucide icon for the concrete situation (e.g. `<CircleCheck />` for "all
12
+ * done"), or a brand mark (an app passes its logo; a customer brand passes
13
+ * its own or nothing). An SVG renders at 24 px unless it carries its own
14
+ * `size-*` class, so a mark can be larger (`<Komet className="size-12" />`).
15
+ */
16
+ icon?: ReactNode
17
+ /** Colour of the icon slot: `"neutral"` (muted, default) or `"success"` ("all done"). */
18
+ tone?: "neutral" | "success"
19
+ /** What is empty, in plain words ("No open incidents"). */
20
+ title: ReactNode
21
+ /** One help line: why it is empty or what to do next. */
22
+ description?: ReactNode
23
+ /** The primary next step (a `Button`). */
24
+ action?: ReactNode
25
+ /** An optional second, quieter step. Empty states carry at most two actions. */
26
+ secondaryAction?: ReactNode
27
+ /** Element of the title. Default `"h3"` (inside a widget below its own heading). */
28
+ titleAs?: StateHeadingLevel
29
+ className?: string
30
+ }
31
+
32
+ /** The icon slot in the tone's icon colour (`TONE_ICON`): quiet, or the success tone. */
33
+ const ICON_TONE: Record<NonNullable<EmptyStateProps["tone"]>, string> = {
34
+ neutral: TONE_ICON.neutral,
35
+ success: TONE_ICON.success,
36
+ }
37
+
38
+ /**
39
+ * The one empty-state pattern: icon or mark, a title (600), one help line and
40
+ * up to two actions, centred in the space the content would take. Static (no
41
+ * live region): it replaces content, it does not announce a change.
42
+ */
43
+ export function EmptyState({
44
+ icon,
45
+ tone = "neutral",
46
+ title,
47
+ description,
48
+ action,
49
+ secondaryAction,
50
+ titleAs: Title = "h3",
51
+ className,
52
+ }: EmptyStateProps) {
53
+ // An untyped tone resolves (`ok` → success); anything else stays quiet.
54
+ const iconTone = resolveTone(tone) === "success" ? "success" : "neutral"
55
+ return (
56
+ <div
57
+ data-slot="empty-state"
58
+ className={cn("flex flex-col items-center gap-2 px-4 py-8 text-center", className)}
59
+ >
60
+ {icon && (
61
+ <div
62
+ aria-hidden="true"
63
+ className={cn(
64
+ "mb-1 flex items-center justify-center [&_svg:not([class*='size-'])]:size-6",
65
+ ICON_TONE[iconTone],
66
+ )}
67
+ >
68
+ {icon}
69
+ </div>
70
+ )}
71
+ <Title className="text-foreground text-body font-semibold">{title}</Title>
72
+ {description && (
73
+ <p className="text-muted-foreground text-body max-w-prose text-balance">{description}</p>
74
+ )}
75
+ {(action || secondaryAction) && (
76
+ <div className="mt-2 flex flex-wrap items-center justify-center gap-2">
77
+ {action}
78
+ {secondaryAction}
79
+ </div>
80
+ )}
81
+ </div>
82
+ )
83
+ }
@@ -0,0 +1,204 @@
1
+ import type { ReactNode } from "react"
2
+ import { CircleAlert, Info, RefreshCw, TriangleAlert, type LucideIcon } from "lucide-react"
3
+ import { useToolkitMessages } from "../locale/locale-provider.js"
4
+ import { Button } from "../primitives/button.js"
5
+ import { FOCUS_RING } from "../lib/focus-ring.js"
6
+ import { resolveTone, TONE_BORDER, TONE_ICON, TONE_SOFT } from "../lib/tone-utils.js"
7
+ import { cn } from "../lib/utils.js"
8
+ import type { StateHeadingLevel } from "./EmptyState.js"
9
+
10
+ /** Status tone of an {@link ErrorState}. */
11
+ export type ErrorStateTone = "danger" | "warning" | "info"
12
+
13
+ export interface ErrorStateProps {
14
+ /** What happened, in plain words ("This view couldn't be loaded"). */
15
+ title: ReactNode
16
+ /** What you can do next — or the human reason, when you can map the raw one. */
17
+ description?: ReactNode
18
+ /**
19
+ * The raw technical reason (engine exception, HTTP status), shown collapsed
20
+ * under a "Details" disclosure so it informs without leading.
21
+ */
22
+ details?: ReactNode
23
+ /** Summary text of the details disclosure. Default: the locale's "Details". */
24
+ detailsLabel?: string
25
+ /** Renders a retry button when set. */
26
+ onRetry?: () => void
27
+ /** Retry button label. Default: the locale's "Try again". */
28
+ retryLabel?: string
29
+ /** Further actions after the retry button (e.g. a link to another view). */
30
+ actions?: ReactNode
31
+ /**
32
+ * Default `"danger"`. `"warning"` for partial failures, `"info"` for
33
+ * notices. An untyped retired name still resolves (`error` → `danger`).
34
+ */
35
+ tone?: ErrorStateTone
36
+ /** Replace the tone's default Lucide icon. */
37
+ icon?: LucideIcon
38
+ /**
39
+ * `"block"` (default): the state that replaces a whole view or widget body.
40
+ * `"inline"`: a compact alert above content that still renders.
41
+ */
42
+ variant?: "block" | "inline"
43
+ /**
44
+ * Live-region role. Default `"alert"` — an error that appears after the
45
+ * fact is announced. Use `"status"` for a quieter notice, or `"none"` when
46
+ * the state is part of the first paint.
47
+ */
48
+ role?: "alert" | "status" | "none"
49
+ /** Element of the title. Default `"h2"` for `block`, `"p"` for `inline`. */
50
+ titleAs?: StateHeadingLevel
51
+ className?: string
52
+ }
53
+
54
+ const STATE_ICON: Record<ErrorStateTone, LucideIcon> = {
55
+ danger: CircleAlert,
56
+ warning: TriangleAlert,
57
+ info: Info,
58
+ }
59
+
60
+ /**
61
+ * The state's tone from whatever arrives: its three tones as is, a retired
62
+ * name through `resolveTone` (`error` → `danger`, `notice` → `info`), any
63
+ * other tone or unknown name as the default `danger`. A tone from untyped
64
+ * data (plain JS, the wire) never crashes the view on a missing icon.
65
+ */
66
+ function stateTone(name: unknown): ErrorStateTone {
67
+ const tone = resolveTone(name, "danger")
68
+ return tone === "warning" || tone === "info" ? tone : "danger"
69
+ }
70
+
71
+ /**
72
+ * Per-variant geometry: `block` replaces a surface and reads as running text
73
+ * (16 px), `inline` is the compact alert box above content that still renders
74
+ * (14 px, the dense-controls exception of the type scale).
75
+ */
76
+ const VARIANT = {
77
+ block: {
78
+ box: "gap-4 p-4",
79
+ icon: "size-5",
80
+ title: "text-body",
81
+ description: "text-body",
82
+ defaultTitleAs: "h2",
83
+ retry: "default",
84
+ },
85
+ inline: {
86
+ box: "gap-3 px-4 py-3 text-dense",
87
+ icon: "size-4",
88
+ title: "text-dense",
89
+ description: "text-dense",
90
+ defaultTitleAs: "p",
91
+ retry: "outline",
92
+ },
93
+ } as const satisfies Record<
94
+ NonNullable<ErrorStateProps["variant"]>,
95
+ {
96
+ box: string
97
+ icon: string
98
+ title: string
99
+ description: string
100
+ defaultTitleAs: StateHeadingLevel
101
+ retry: "default" | "outline"
102
+ }
103
+ >
104
+
105
+ /**
106
+ * The one error pattern: a Lucide icon, a title that says what happened, a
107
+ * line that says what you can do, the raw reason collapsed under "Details",
108
+ * and a retry. `inline` is the alert form above content that still renders
109
+ * (failed data steps); `block` replaces a whole view or widget body.
110
+ */
111
+ export function ErrorState({
112
+ title,
113
+ description,
114
+ details,
115
+ detailsLabel,
116
+ onRetry,
117
+ retryLabel,
118
+ actions,
119
+ tone: toneProp,
120
+ icon,
121
+ variant = "block",
122
+ role = "alert",
123
+ titleAs,
124
+ className,
125
+ }: ErrorStateProps) {
126
+ const tone = stateTone(toneProp)
127
+ const style = VARIANT[variant]
128
+ const Icon = icon ?? STATE_ICON[tone]
129
+ const Title = titleAs ?? style.defaultTitleAs
130
+ // The status colour as rim, tint and icon; every word on the tint is the
131
+ // foreground (TONE_SOFT, CI §3.3). On dark the tint is transparent and the
132
+ // rim carries the tone.
133
+ return (
134
+ <div
135
+ data-slot="error-state"
136
+ data-variant={variant}
137
+ data-tone={tone}
138
+ role={role === "none" ? undefined : role}
139
+ className={cn(
140
+ "flex items-start rounded-md border",
141
+ style.box,
142
+ TONE_SOFT[tone],
143
+ TONE_BORDER[tone],
144
+ className,
145
+ )}
146
+ >
147
+ <Icon aria-hidden="true" className={cn("mt-0.5 shrink-0", style.icon, TONE_ICON[tone])} />
148
+ <div className="flex min-w-0 flex-1 flex-col gap-1">
149
+ <Title className={cn("font-semibold", style.title)}>{title}</Title>
150
+ {description && <div className={cn("break-words", style.description)}>{description}</div>}
151
+ {details && <ErrorDetails label={detailsLabel}>{details}</ErrorDetails>}
152
+ <ErrorActions
153
+ onRetry={onRetry}
154
+ retryLabel={retryLabel}
155
+ retryVariant={style.retry}
156
+ actions={actions}
157
+ />
158
+ </div>
159
+ </div>
160
+ )
161
+ }
162
+
163
+ /**
164
+ * The raw reason, collapsed: a native disclosure (keyboard + screen-reader
165
+ * ready). It sits on the tint, so its text stays the foreground; the collapse
166
+ * and the small mono type keep it quieter than the title.
167
+ */
168
+ function ErrorDetails({ label, children }: { label: string | undefined; children: ReactNode }) {
169
+ const messages = useToolkitMessages()
170
+ return (
171
+ <details className="text-small">
172
+ <summary className={`w-fit cursor-pointer rounded-sm py-0.5 select-none ${FOCUS_RING}`}>
173
+ {label ?? messages.common.details}
174
+ </summary>
175
+ <pre className="mt-1 font-mono break-words whitespace-pre-wrap">{children}</pre>
176
+ </details>
177
+ )
178
+ }
179
+
180
+ function ErrorActions({
181
+ onRetry,
182
+ retryLabel,
183
+ retryVariant,
184
+ actions,
185
+ }: {
186
+ onRetry: (() => void) | undefined
187
+ retryLabel: string | undefined
188
+ retryVariant: "default" | "outline"
189
+ actions: ReactNode
190
+ }) {
191
+ const messages = useToolkitMessages()
192
+ if (!onRetry && !actions) return null
193
+ return (
194
+ <div className="mt-2 flex flex-wrap items-center gap-2">
195
+ {onRetry && (
196
+ <Button type="button" size="sm" variant={retryVariant} onClick={onRetry}>
197
+ <RefreshCw aria-hidden="true" />
198
+ {retryLabel ?? messages.common.retry}
199
+ </Button>
200
+ )}
201
+ {actions}
202
+ </div>
203
+ )
204
+ }
@@ -0,0 +1,82 @@
1
+ import { renderToStaticMarkup } from "react-dom/server"
2
+ import { describe, expect, it } from "vitest"
3
+ import { LocaleProvider } from "../locale/locale-provider.js"
4
+ import { TOOLKIT_MESSAGES } from "../locale/toolkit-messages.js"
5
+ import { attribute, openingTags } from "../render-scan.js"
6
+ import { FOCUS_RING } from "../lib/focus-ring.js"
7
+ import { TONE_BORDER, TONE_SOFT } from "../lib/tone-utils.js"
8
+ import { FilterBar } from "./FilterBar.js"
9
+
10
+ const noop = (): void => {}
11
+ const classOf = (tag: string | undefined) => attribute(tag ?? "", "class") ?? ""
12
+
13
+ function render(props: Partial<Parameters<typeof FilterBar>[0]> = {}, locale = "en") {
14
+ return renderToStaticMarkup(
15
+ <LocaleProvider locale={locale}>
16
+ <FilterBar
17
+ search=""
18
+ onSearchChange={noop}
19
+ chips={[
20
+ { id: "open", label: "Open", count: 3, active: true },
21
+ { id: "done", label: "Done" },
22
+ ]}
23
+ onChipToggle={noop}
24
+ {...props}
25
+ />
26
+ </LocaleProvider>,
27
+ )
28
+ }
29
+
30
+ describe("FilterBar: a labelled search and toggle chips", () => {
31
+ it("labels the search input in the locale (a placeholder is no label)", () => {
32
+ const [input] = openingTags(render(), "input")
33
+ expect(attribute(input ?? "", "type")).toBe("search")
34
+ expect(attribute(input ?? "", "aria-label")).toBe(TOOLKIT_MESSAGES.en.filterBar.searchLabel)
35
+ expect(attribute(input ?? "", "placeholder")).toBe("Filter…")
36
+ const [german] = openingTags(render({}, "de-DE"), "input")
37
+ expect(attribute(german ?? "", "aria-label")).toBe("Filtern")
38
+ expect(attribute(german ?? "", "placeholder")).toBe("Filtern…")
39
+ })
40
+
41
+ it("takes the caller's search label and placeholder", () => {
42
+ const [input] = openingTags(
43
+ render({ searchAriaLabel: "Filter tasks", searchPlaceholder: "Filter tasks…" }),
44
+ "input",
45
+ )
46
+ expect(attribute(input ?? "", "aria-label")).toBe("Filter tasks")
47
+ expect(attribute(input ?? "", "placeholder")).toBe("Filter tasks…")
48
+ })
49
+
50
+ it("groups the chips under a name and marks each one's state with aria-pressed", () => {
51
+ const html = render()
52
+ const group = openingTags(html, "div").find((t) => attribute(t, "role") === "group")
53
+ expect(attribute(group ?? "", "aria-label")).toBe("Filters")
54
+ expect(
55
+ attribute(openingTags(render({ chipsAriaLabel: "Priority" }), "div")[1] ?? "", "aria-label"),
56
+ ).toBe("Priority")
57
+ const [open, done] = openingTags(html, "button")
58
+ expect(attribute(open ?? "", "aria-pressed")).toBe("true")
59
+ expect(attribute(done ?? "", "aria-pressed")).toBe("false")
60
+ for (const chip of [open, done]) {
61
+ expect(attribute(chip ?? "", "type")).toBe("button")
62
+ expect(classOf(chip)).toContain(FOCUS_RING)
63
+ expect(classOf(chip)).toContain("text-small")
64
+ }
65
+ })
66
+
67
+ it("shows the active chip as a selection: info tint and edge around foreground text", () => {
68
+ const [open, done] = openingTags(render(), "button")
69
+ expect(classOf(open)).toContain(TONE_SOFT.info)
70
+ expect(classOf(open)).toContain(TONE_BORDER.info)
71
+ expect(classOf(open)).not.toMatch(/text-info|text-accent-foreground/)
72
+ expect(classOf(done)).toContain("text-muted-foreground")
73
+ expect(classOf(done)).not.toContain("bg-info-soft")
74
+ })
75
+
76
+ it("renders no empty group without chips and merges a className", () => {
77
+ const html = render({ chips: [], className: "mb-4" })
78
+ expect(html).not.toContain('role="group"')
79
+ expect(classOf(openingTags(html, "div")[0])).toContain("mb-4")
80
+ expect(html).not.toMatch(/text-xs|text-\[\d+px\]/)
81
+ })
82
+ })
@@ -1,68 +1,97 @@
1
+ import { FOCUS_RING } from "../lib/focus-ring.js"
2
+ import { TONE_BORDER, TONE_SOFT } from "../lib/tone-utils.js"
3
+ import { cn } from "../lib/utils.js"
4
+ import { useToolkitMessages } from "../locale/locale-provider.js"
1
5
  import { Input } from "../primitives/input.js"
2
6
 
3
7
  export interface FilterChip {
4
8
  /** Stable id passed back to `onChipToggle`. */
5
9
  id: string
6
- /** Visible chip label. */
10
+ /** Visible chip label (its accessible name, with the count). */
7
11
  label: string
8
12
  /** Optional count rendered after the label. */
9
13
  count?: number | string
10
- /** Whether the chip is currently active. */
14
+ /** Whether the chip is currently active (`aria-pressed`). */
11
15
  active?: boolean
12
16
  }
13
17
 
14
18
  export interface FilterBarProps {
15
19
  /** Current search text (controlled). */
16
20
  search: string
17
- /** Placeholder for the search input. Default `"Filter…"`. */
21
+ /** Placeholder for the search input. Default: the locale's "Filter…". */
18
22
  searchPlaceholder?: string
23
+ /**
24
+ * Accessible name of the search input. Default: the locale's "Filter" — a
25
+ * placeholder is no label (it disappears while you type).
26
+ */
27
+ searchAriaLabel?: string
19
28
  /** Called with the new search text on every keystroke. */
20
29
  onSearchChange: (next: string) => void
21
30
  /** Filter chips rendered to the right of the search input. */
22
31
  chips: FilterChip[]
23
32
  /** Called with a chip id when it is toggled. */
24
33
  onChipToggle: (id: string) => void
34
+ /** Names the chip row (`role="group"`). Default: the locale's "Filters". */
35
+ chipsAriaLabel?: string
36
+ className?: string
25
37
  }
26
38
 
27
39
  /**
28
40
  * Search input + toggleable chip row for filtering a list/table of widget
29
- * items. Pair with `useDebouncedValue` to avoid re-querying on every keystroke.
41
+ * items. The input is labelled; every chip is a toggle button whose state is
42
+ * `aria-pressed`. An active chip is a selection in the CI pattern: the info
43
+ * tint and edge around foreground text. Pair with `useDebouncedValue` to
44
+ * avoid re-querying on every keystroke.
30
45
  */
31
46
  export function FilterBar({
32
47
  search,
33
- searchPlaceholder = "Filter…",
48
+ searchPlaceholder,
49
+ searchAriaLabel,
34
50
  onSearchChange,
35
51
  chips,
36
52
  onChipToggle,
53
+ chipsAriaLabel,
54
+ className,
37
55
  }: FilterBarProps) {
56
+ const { filterBar } = useToolkitMessages()
38
57
  return (
39
- <div className="flex flex-wrap items-center gap-2">
58
+ <div data-slot="filter-bar" className={cn("flex flex-wrap items-center gap-2", className)}>
40
59
  <Input
41
60
  type="search"
42
61
  value={search}
43
62
  onChange={(e) => onSearchChange(e.target.value)}
44
- placeholder={searchPlaceholder}
45
- className="h-9 min-w-[220px] flex-1 text-sm"
63
+ placeholder={searchPlaceholder ?? filterBar.searchPlaceholder}
64
+ aria-label={searchAriaLabel ?? filterBar.searchLabel}
65
+ className="text-dense md:text-dense h-9 min-w-[220px] flex-1"
46
66
  />
47
- <div className="flex flex-wrap gap-1.5">
48
- {chips.map((chip) => (
49
- <button
50
- type="button"
51
- key={chip.id}
52
- onClick={() => onChipToggle(chip.id)}
53
- className={`inline-flex items-center gap-1.5 rounded-md border px-2.5 py-1 text-xs font-medium transition-colors ${
54
- chip.active
55
- ? "bg-accent border-border text-accent-foreground font-semibold"
56
- : "border-border bg-background text-muted-foreground hover:bg-muted hover:text-foreground"
57
- }`}
58
- >
59
- {chip.label}
60
- {chip.count !== undefined && chip.count !== "" && (
61
- <span className="tabular-nums opacity-60">{chip.count}</span>
62
- )}
63
- </button>
64
- ))}
65
- </div>
67
+ {chips.length > 0 && (
68
+ <div
69
+ role="group"
70
+ aria-label={chipsAriaLabel ?? filterBar.chipsLabel}
71
+ className="flex flex-wrap gap-1.5"
72
+ >
73
+ {chips.map((chip) => (
74
+ <button
75
+ type="button"
76
+ key={chip.id}
77
+ onClick={() => onChipToggle(chip.id)}
78
+ aria-pressed={Boolean(chip.active)}
79
+ className={cn(
80
+ "text-small inline-flex items-center gap-1.5 rounded-sm border px-2.5 py-1 font-medium transition-colors",
81
+ FOCUS_RING,
82
+ chip.active
83
+ ? cn(TONE_SOFT.info, TONE_BORDER.info, "font-semibold")
84
+ : "border-border bg-background text-muted-foreground hover:bg-muted hover:text-foreground",
85
+ )}
86
+ >
87
+ {chip.label}
88
+ {chip.count !== undefined && chip.count !== "" && (
89
+ <span className="font-normal tabular-nums">{chip.count}</span>
90
+ )}
91
+ </button>
92
+ ))}
93
+ </div>
94
+ )}
66
95
  </div>
67
96
  )
68
97
  }
@@ -1,4 +1,5 @@
1
1
  import type { KeyboardEvent, MouseEvent, ReactNode } from "react"
2
+ import { FOCUS_RING_INSET } from "../lib/focus-ring.js"
2
3
 
3
4
  export interface GroupCardProps {
4
5
  /** Whether the body is expanded. */
@@ -33,14 +34,14 @@ export function GroupCard({ expanded, onToggle, summary, children }: GroupCardPr
33
34
  }
34
35
 
35
36
  return (
36
- <div className="border-border mb-2 overflow-hidden rounded-lg border">
37
+ <div className="border-border mb-2 overflow-hidden rounded-md border">
37
38
  <div
38
39
  role="button"
39
40
  tabIndex={0}
40
41
  aria-expanded={expanded}
41
42
  onClick={handleClick}
42
43
  onKeyDown={handleKeyDown}
43
- className={`bg-card hover:bg-muted focus-visible:ring-ring/50 cursor-pointer transition-colors focus:outline-none focus-visible:ring-2 ${
44
+ className={`bg-card hover:bg-muted cursor-pointer transition-colors ${FOCUS_RING_INSET} ${
44
45
  expanded ? "border-border border-b" : ""
45
46
  }`}
46
47
  >