@miragon/mcp-toolkit-ui 2.6.0 → 2.7.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 (348) hide show
  1. package/README.md +7 -1
  2. package/dist/app/builder/BuilderBanners.d.ts +7 -2
  3. package/dist/app/builder/BuilderBanners.d.ts.map +1 -1
  4. package/dist/app/builder/BuilderBanners.js +8 -3
  5. package/dist/app/builder/BuilderBanners.js.map +1 -1
  6. package/dist/app/builder/CanvasRow.d.ts.map +1 -1
  7. package/dist/app/builder/CanvasRow.js +12 -14
  8. package/dist/app/builder/CanvasRow.js.map +1 -1
  9. package/dist/app/builder/CatalogueSheet.d.ts.map +1 -1
  10. package/dist/app/builder/CatalogueSheet.js +6 -5
  11. package/dist/app/builder/CatalogueSheet.js.map +1 -1
  12. package/dist/app/builder/EmptyHint.d.ts +4 -0
  13. package/dist/app/builder/EmptyHint.d.ts.map +1 -1
  14. package/dist/app/builder/EmptyHint.js +6 -1
  15. package/dist/app/builder/EmptyHint.js.map +1 -1
  16. package/dist/app/builder/LayoutSurface.js +3 -3
  17. package/dist/app/builder/LayoutSurface.js.map +1 -1
  18. package/dist/app/builder/PipelineStrip.d.ts +3 -1
  19. package/dist/app/builder/PipelineStrip.d.ts.map +1 -1
  20. package/dist/app/builder/PipelineStrip.js +11 -6
  21. package/dist/app/builder/PipelineStrip.js.map +1 -1
  22. package/dist/app/builder/SaveDialog.d.ts.map +1 -1
  23. package/dist/app/builder/SaveDialog.js +2 -2
  24. package/dist/app/builder/SaveDialog.js.map +1 -1
  25. package/dist/app/builder/Toolbar.d.ts +3 -1
  26. package/dist/app/builder/Toolbar.d.ts.map +1 -1
  27. package/dist/app/builder/Toolbar.js +9 -3
  28. package/dist/app/builder/Toolbar.js.map +1 -1
  29. package/dist/app/builder/Workspace.js +2 -2
  30. package/dist/app/builder/Workspace.js.map +1 -1
  31. package/dist/app/builder/builder-model.d.ts +3 -2
  32. package/dist/app/builder/builder-model.d.ts.map +1 -1
  33. package/dist/app/builder/builder-model.js +5 -4
  34. package/dist/app/builder/builder-model.js.map +1 -1
  35. package/dist/app/builder/labels.d.ts +13 -62
  36. package/dist/app/builder/labels.d.ts.map +1 -1
  37. package/dist/app/builder/labels.js +9 -58
  38. package/dist/app/builder/labels.js.map +1 -1
  39. package/dist/app/builder/status-chip.d.ts.map +1 -1
  40. package/dist/app/builder/status-chip.js +3 -2
  41. package/dist/app/builder/status-chip.js.map +1 -1
  42. package/dist/app/builder/use-builder-catalogue.d.ts +1 -0
  43. package/dist/app/builder/use-builder-catalogue.d.ts.map +1 -1
  44. package/dist/app/builder/use-builder-catalogue.js +6 -0
  45. package/dist/app/builder/use-builder-catalogue.js.map +1 -1
  46. package/dist/app/builder/use-save-dashboard.d.ts +3 -1
  47. package/dist/app/builder/use-save-dashboard.d.ts.map +1 -1
  48. package/dist/app/builder/use-save-dashboard.js +3 -1
  49. package/dist/app/builder/use-save-dashboard.js.map +1 -1
  50. package/dist/app/index.d.ts +1 -1
  51. package/dist/app/index.d.ts.map +1 -1
  52. package/dist/app/index.js.map +1 -1
  53. package/dist/app/layout-builder.d.ts +7 -2
  54. package/dist/app/layout-builder.d.ts.map +1 -1
  55. package/dist/app/layout-builder.js +17 -5
  56. package/dist/app/layout-builder.js.map +1 -1
  57. package/dist/app/mcp-app-view-chrome.d.ts +94 -20
  58. package/dist/app/mcp-app-view-chrome.d.ts.map +1 -1
  59. package/dist/app/mcp-app-view-chrome.js +109 -40
  60. package/dist/app/mcp-app-view-chrome.js.map +1 -1
  61. package/dist/app/mcp-app-view-states.d.ts +15 -0
  62. package/dist/app/mcp-app-view-states.d.ts.map +1 -0
  63. package/dist/app/mcp-app-view-states.js +34 -0
  64. package/dist/app/mcp-app-view-states.js.map +1 -0
  65. package/dist/app/mcp-app-view.d.ts +40 -5
  66. package/dist/app/mcp-app-view.d.ts.map +1 -1
  67. package/dist/app/mcp-app-view.js +174 -128
  68. package/dist/app/mcp-app-view.js.map +1 -1
  69. package/dist/app/mcp-toolkit-app.d.ts +2 -1
  70. package/dist/app/mcp-toolkit-app.d.ts.map +1 -1
  71. package/dist/app/mcp-toolkit-app.js +25 -3
  72. package/dist/app/mcp-toolkit-app.js.map +1 -1
  73. package/dist/app/use-view-lifecycle.d.ts +38 -0
  74. package/dist/app/use-view-lifecycle.d.ts.map +1 -0
  75. package/dist/app/use-view-lifecycle.js +79 -0
  76. package/dist/app/use-view-lifecycle.js.map +1 -0
  77. package/dist/app/view-lifecycle.d.ts +109 -0
  78. package/dist/app/view-lifecycle.d.ts.map +1 -0
  79. package/dist/app/view-lifecycle.js +120 -0
  80. package/dist/app/view-lifecycle.js.map +1 -0
  81. package/dist/app/widget-props-sheet.d.ts +3 -9
  82. package/dist/app/widget-props-sheet.d.ts.map +1 -1
  83. package/dist/app/widget-props-sheet.js +6 -13
  84. package/dist/app/widget-props-sheet.js.map +1 -1
  85. package/dist/app/widget-renderer.d.ts +41 -1
  86. package/dist/app/widget-renderer.d.ts.map +1 -1
  87. package/dist/app/widget-renderer.js +82 -31
  88. package/dist/app/widget-renderer.js.map +1 -1
  89. package/dist/components/DrillButton.d.ts.map +1 -1
  90. package/dist/components/DrillButton.js +2 -1
  91. package/dist/components/DrillButton.js.map +1 -1
  92. package/dist/components/EmptyState.d.ts +33 -0
  93. package/dist/components/EmptyState.d.ts.map +1 -0
  94. package/dist/components/EmptyState.js +17 -0
  95. package/dist/components/EmptyState.js.map +1 -0
  96. package/dist/components/ErrorState.d.ts +50 -0
  97. package/dist/components/ErrorState.d.ts.map +1 -0
  98. package/dist/components/ErrorState.js +60 -0
  99. package/dist/components/ErrorState.js.map +1 -0
  100. package/dist/components/FilterBar.d.ts +1 -1
  101. package/dist/components/FilterBar.d.ts.map +1 -1
  102. package/dist/components/FilterBar.js +7 -4
  103. package/dist/components/FilterBar.js.map +1 -1
  104. package/dist/components/GroupCard.d.ts.map +1 -1
  105. package/dist/components/GroupCard.js +2 -1
  106. package/dist/components/GroupCard.js.map +1 -1
  107. package/dist/components/KpiGrid.d.ts +10 -3
  108. package/dist/components/KpiGrid.d.ts.map +1 -1
  109. package/dist/components/KpiGrid.js +31 -9
  110. package/dist/components/KpiGrid.js.map +1 -1
  111. package/dist/components/ListFooter.d.ts +3 -2
  112. package/dist/components/ListFooter.d.ts.map +1 -1
  113. package/dist/components/ListFooter.js +14 -4
  114. package/dist/components/ListFooter.js.map +1 -1
  115. package/dist/components/Pills.d.ts +10 -6
  116. package/dist/components/Pills.d.ts.map +1 -1
  117. package/dist/components/Pills.js +12 -6
  118. package/dist/components/Pills.js.map +1 -1
  119. package/dist/components/Toast.d.ts +37 -0
  120. package/dist/components/Toast.d.ts.map +1 -0
  121. package/dist/components/Toast.js +91 -0
  122. package/dist/components/Toast.js.map +1 -0
  123. package/dist/components/WidgetHeader.js +1 -1
  124. package/dist/hooks/use-tool-result-recovery.d.ts +23 -1
  125. package/dist/hooks/use-tool-result-recovery.d.ts.map +1 -1
  126. package/dist/hooks/use-tool-result-recovery.js +21 -7
  127. package/dist/hooks/use-tool-result-recovery.js.map +1 -1
  128. package/dist/hooks/use-view-data.d.ts +6 -0
  129. package/dist/hooks/use-view-data.d.ts.map +1 -1
  130. package/dist/hooks/use-view-data.js +6 -1
  131. package/dist/hooks/use-view-data.js.map +1 -1
  132. package/dist/index.d.ts +12 -5
  133. package/dist/index.d.ts.map +1 -1
  134. package/dist/index.js +14 -4
  135. package/dist/index.js.map +1 -1
  136. package/dist/lib/focus-ring.d.ts +18 -0
  137. package/dist/lib/focus-ring.d.ts.map +1 -0
  138. package/dist/lib/focus-ring.js +18 -0
  139. package/dist/lib/focus-ring.js.map +1 -0
  140. package/dist/lib/icon-stroke.d.ts +12 -0
  141. package/dist/lib/icon-stroke.d.ts.map +1 -0
  142. package/dist/lib/icon-stroke.js +12 -0
  143. package/dist/lib/icon-stroke.js.map +1 -0
  144. package/dist/lib/toast-model.d.ts +46 -0
  145. package/dist/lib/toast-model.d.ts.map +1 -0
  146. package/dist/lib/toast-model.js +36 -0
  147. package/dist/lib/toast-model.js.map +1 -0
  148. package/dist/lib/tone-utils.d.ts +43 -9
  149. package/dist/lib/tone-utils.d.ts.map +1 -1
  150. package/dist/lib/tone-utils.js +36 -7
  151. package/dist/lib/tone-utils.js.map +1 -1
  152. package/dist/locale/index.d.ts +3 -1
  153. package/dist/locale/index.d.ts.map +1 -1
  154. package/dist/locale/index.js +2 -1
  155. package/dist/locale/index.js.map +1 -1
  156. package/dist/locale/locale-provider.d.ts +42 -1
  157. package/dist/locale/locale-provider.d.ts.map +1 -1
  158. package/dist/locale/locale-provider.js +44 -3
  159. package/dist/locale/locale-provider.js.map +1 -1
  160. package/dist/locale/message-types.d.ts +223 -0
  161. package/dist/locale/message-types.d.ts.map +1 -0
  162. package/dist/locale/message-types.js +7 -0
  163. package/dist/locale/message-types.js.map +1 -0
  164. package/dist/locale/messages-de.d.ts +9 -0
  165. package/dist/locale/messages-de.d.ts.map +1 -0
  166. package/dist/locale/messages-de.js +151 -0
  167. package/dist/locale/messages-de.js.map +1 -0
  168. package/dist/locale/messages-en.d.ts +8 -0
  169. package/dist/locale/messages-en.d.ts.map +1 -0
  170. package/dist/locale/messages-en.js +150 -0
  171. package/dist/locale/messages-en.js.map +1 -0
  172. package/dist/locale/toolkit-messages.d.ts +39 -0
  173. package/dist/locale/toolkit-messages.d.ts.map +1 -0
  174. package/dist/locale/toolkit-messages.js +50 -0
  175. package/dist/locale/toolkit-messages.js.map +1 -0
  176. package/dist/primitives/alert.d.ts.map +1 -1
  177. package/dist/primitives/alert.js +4 -2
  178. package/dist/primitives/alert.js.map +1 -1
  179. package/dist/primitives/badge.d.ts +1 -1
  180. package/dist/primitives/badge.d.ts.map +1 -1
  181. package/dist/primitives/badge.js +14 -6
  182. package/dist/primitives/badge.js.map +1 -1
  183. package/dist/primitives/button.d.ts.map +1 -1
  184. package/dist/primitives/button.js +12 -8
  185. package/dist/primitives/button.js.map +1 -1
  186. package/dist/primitives/card.js +4 -4
  187. package/dist/primitives/card.js.map +1 -1
  188. package/dist/primitives/dialog.d.ts +7 -3
  189. package/dist/primitives/dialog.d.ts.map +1 -1
  190. package/dist/primitives/dialog.js +14 -7
  191. package/dist/primitives/dialog.js.map +1 -1
  192. package/dist/primitives/input.d.ts.map +1 -1
  193. package/dist/primitives/input.js +4 -1
  194. package/dist/primitives/input.js.map +1 -1
  195. package/dist/primitives/scroll-area.js +1 -1
  196. package/dist/primitives/scroll-area.js.map +1 -1
  197. package/dist/primitives/select.d.ts.map +1 -1
  198. package/dist/primitives/select.js +5 -2
  199. package/dist/primitives/select.js.map +1 -1
  200. package/dist/primitives/sheet.d.ts +3 -1
  201. package/dist/primitives/sheet.d.ts.map +1 -1
  202. package/dist/primitives/sheet.js +12 -6
  203. package/dist/primitives/sheet.js.map +1 -1
  204. package/dist/primitives/skeleton.js +1 -1
  205. package/dist/primitives/skeleton.js.map +1 -1
  206. package/dist/primitives/switch.d.ts.map +1 -1
  207. package/dist/primitives/switch.js +2 -1
  208. package/dist/primitives/switch.js.map +1 -1
  209. package/dist/primitives/tabs.d.ts.map +1 -1
  210. package/dist/primitives/tabs.js +5 -3
  211. package/dist/primitives/tabs.js.map +1 -1
  212. package/dist/render-cases.d.ts.map +1 -1
  213. package/dist/render-cases.js +20 -2
  214. package/dist/render-cases.js.map +1 -1
  215. package/dist/render-scan.d.ts +25 -0
  216. package/dist/render-scan.d.ts.map +1 -0
  217. package/dist/render-scan.js +66 -0
  218. package/dist/render-scan.js.map +1 -0
  219. package/dist/theme/cd-tokens.d.ts +66 -0
  220. package/dist/theme/cd-tokens.d.ts.map +1 -0
  221. package/dist/theme/cd-tokens.js +64 -0
  222. package/dist/theme/cd-tokens.js.map +1 -0
  223. package/dist/theme/create-theme.d.ts +196 -39
  224. package/dist/theme/create-theme.d.ts.map +1 -1
  225. package/dist/theme/create-theme.js +189 -13
  226. package/dist/theme/create-theme.js.map +1 -1
  227. package/dist/theme/fonts.d.ts +38 -0
  228. package/dist/theme/fonts.d.ts.map +1 -0
  229. package/dist/theme/fonts.js +56 -0
  230. package/dist/theme/fonts.js.map +1 -0
  231. package/dist/theme/host-fonts.d.ts +39 -0
  232. package/dist/theme/host-fonts.d.ts.map +1 -0
  233. package/dist/theme/host-fonts.js +44 -0
  234. package/dist/theme/host-fonts.js.map +1 -0
  235. package/dist/theme/index.d.ts +8 -1
  236. package/dist/theme/index.d.ts.map +1 -1
  237. package/dist/theme/index.js +5 -1
  238. package/dist/theme/index.js.map +1 -1
  239. package/dist/theme/portal-container.d.ts +16 -0
  240. package/dist/theme/portal-container.d.ts.map +1 -0
  241. package/dist/theme/portal-container.js +19 -0
  242. package/dist/theme/portal-container.js.map +1 -0
  243. package/dist/theme/presets.d.ts +24 -9
  244. package/dist/theme/presets.d.ts.map +1 -1
  245. package/dist/theme/presets.js +44 -11
  246. package/dist/theme/presets.js.map +1 -1
  247. package/dist/theme/theme-provider.d.ts +27 -9
  248. package/dist/theme/theme-provider.d.ts.map +1 -1
  249. package/dist/theme/theme-provider.js +42 -20
  250. package/dist/theme/theme-provider.js.map +1 -1
  251. package/package.json +9 -4
  252. package/src/app/builder/BuilderBanners.tsx +26 -13
  253. package/src/app/builder/CanvasRow.tsx +22 -21
  254. package/src/app/builder/CatalogueSheet.tsx +16 -14
  255. package/src/app/builder/EmptyHint.tsx +16 -11
  256. package/src/app/builder/LayoutSurface.tsx +7 -7
  257. package/src/app/builder/PipelineStrip.tsx +26 -17
  258. package/src/app/builder/SaveDialog.tsx +11 -12
  259. package/src/app/builder/Toolbar.tsx +19 -7
  260. package/src/app/builder/Workspace.tsx +5 -3
  261. package/src/app/builder/builder-feedback.test.tsx +75 -0
  262. package/src/app/builder/builder-model.test.ts +5 -0
  263. package/src/app/builder/builder-model.ts +5 -4
  264. package/src/app/builder/labels.ts +15 -121
  265. package/src/app/builder/status-chip.tsx +8 -4
  266. package/src/app/builder/use-builder-catalogue.ts +6 -0
  267. package/src/app/builder/use-save-dashboard.ts +5 -0
  268. package/src/app/index.ts +8 -1
  269. package/src/app/layout-builder.tsx +23 -3
  270. package/src/app/mcp-app-view-chrome.test.tsx +347 -14
  271. package/src/app/mcp-app-view-chrome.tsx +278 -100
  272. package/src/app/mcp-app-view-states.tsx +100 -0
  273. package/src/app/mcp-app-view.test.tsx +258 -0
  274. package/src/app/mcp-app-view.tsx +292 -185
  275. package/src/app/mcp-toolkit-app.tsx +31 -7
  276. package/src/app/use-view-lifecycle.ts +116 -0
  277. package/src/app/view-lifecycle.test.ts +227 -0
  278. package/src/app/view-lifecycle.ts +174 -0
  279. package/src/app/widget-props-sheet.tsx +13 -26
  280. package/src/app/widget-renderer.render.test.tsx +131 -0
  281. package/src/app/widget-renderer.tsx +136 -38
  282. package/src/components/DrillButton.tsx +2 -1
  283. package/src/components/EmptyState.tsx +81 -0
  284. package/src/components/ErrorState.tsx +182 -0
  285. package/src/components/FilterBar.tsx +9 -6
  286. package/src/components/GroupCard.tsx +3 -2
  287. package/src/components/KpiGrid.tsx +75 -44
  288. package/src/components/ListFooter.tsx +17 -6
  289. package/src/components/Pills.tsx +20 -11
  290. package/src/components/Toast.tsx +188 -0
  291. package/src/components/WidgetHeader.tsx +1 -1
  292. package/src/components/feedback-states.test.tsx +225 -0
  293. package/src/components/tone-figures.test.tsx +54 -0
  294. package/src/fonts.css +19 -0
  295. package/src/globals.css +9 -165
  296. package/src/hooks/use-tool-result-recovery.test.ts +25 -1
  297. package/src/hooks/use-tool-result-recovery.ts +34 -6
  298. package/src/hooks/use-view-data.test.tsx +60 -0
  299. package/src/hooks/use-view-data.ts +12 -1
  300. package/src/i18n-coverage.test.tsx +323 -0
  301. package/src/index.ts +57 -5
  302. package/src/lib/focus-ring.ts +20 -0
  303. package/src/lib/icon-stroke.test.ts +59 -0
  304. package/src/lib/icon-stroke.ts +13 -0
  305. package/src/lib/toast-model.test.ts +68 -0
  306. package/src/lib/toast-model.ts +63 -0
  307. package/src/lib/tone-utils.test.ts +47 -14
  308. package/src/lib/tone-utils.ts +61 -13
  309. package/src/locale/index.ts +13 -1
  310. package/src/locale/locale-provider.test.tsx +87 -0
  311. package/src/locale/locale-provider.tsx +90 -3
  312. package/src/locale/message-types.ts +221 -0
  313. package/src/locale/messages-de.ts +159 -0
  314. package/src/locale/messages-en.ts +157 -0
  315. package/src/locale/toolkit-messages.test.ts +216 -0
  316. package/src/locale/toolkit-messages.ts +82 -0
  317. package/src/no-hardcoded-copy.test.ts +432 -0
  318. package/src/primitives/alert.tsx +4 -2
  319. package/src/primitives/badge.tsx +14 -7
  320. package/src/primitives/button.tsx +13 -8
  321. package/src/primitives/card.tsx +4 -4
  322. package/src/primitives/dialog.tsx +32 -7
  323. package/src/primitives/input.tsx +5 -3
  324. package/src/primitives/scroll-area.tsx +1 -1
  325. package/src/primitives/select.tsx +6 -3
  326. package/src/primitives/sheet.tsx +26 -6
  327. package/src/primitives/skeleton.tsx +1 -1
  328. package/src/primitives/switch.tsx +4 -2
  329. package/src/primitives/tabs.tsx +9 -6
  330. package/src/render-cases.tsx +45 -1
  331. package/src/render-scan.ts +75 -0
  332. package/src/theme/cd-tokens.generated.css +48 -0
  333. package/src/theme/cd-tokens.test.ts +107 -0
  334. package/src/theme/cd-tokens.ts +67 -0
  335. package/src/theme/create-theme.test.ts +243 -5
  336. package/src/theme/create-theme.ts +313 -41
  337. package/src/theme/fonts.test.ts +85 -0
  338. package/src/theme/fonts.ts +72 -0
  339. package/src/theme/host-fonts.test.ts +135 -0
  340. package/src/theme/host-fonts.ts +52 -0
  341. package/src/theme/index.ts +15 -1
  342. package/src/theme/portal-container.ts +20 -0
  343. package/src/theme/presets.ts +44 -11
  344. package/src/theme/theme-css.test.ts +568 -0
  345. package/src/theme/theme-provider.test.tsx +82 -0
  346. package/src/theme/theme-provider.tsx +60 -24
  347. package/src/theme.css +314 -0
  348. package/ui-catalog.json +344 -17
@@ -1,13 +1,34 @@
1
- import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
2
- import { Component } from "react";
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Component, createRef, useMemo, } from "react";
3
3
  import { normalizeLayout } from "@miragon/mcp-toolkit-core";
4
+ import { ErrorState } from "../components/ErrorState.js";
4
5
  import { GridLayout, GridItem } from "../components/GridLayout.js";
5
- import { Card, CardContent } from "../primitives/card.js";
6
+ import { useToolkitMessages } from "../locale/locale-provider.js";
6
7
  import { Tabs, TabsList, TabsTrigger, TabsContent } from "../primitives/tabs.js";
8
+ /**
9
+ * True when two reset-key lists differ (length or any entry by identity).
10
+ * Exported for tests: it decides when a crashed widget gets a fresh render.
11
+ */
12
+ export function resetKeysChanged(prev, next) {
13
+ if (prev === next)
14
+ return false;
15
+ if (!prev || !next || prev.length !== next.length)
16
+ return true;
17
+ return prev.some((value, idx) => !Object.is(value, next[idx]));
18
+ }
7
19
  /**
8
20
  * Catches render-time crashes from a single widget so one broken widget
9
21
  * can't take down the entire iframe view (or the builder canvas/preview).
10
22
  *
23
+ * The fallback is localized, names what happened, keeps the raw error under
24
+ * "Details" and offers a retry. The boundary resets itself when its
25
+ * `resetKeys` change, so new data (a refresh, a new tool result, a builder
26
+ * edit) gets a fresh render instead of a latched failure (toolkit#176 N24).
27
+ * When a retry crashes again, keyboard focus moves to the new retry button
28
+ * instead of falling back to the document (the pressed one unmounted). After
29
+ * a retry that renders, the widget's own markup is unknown here, so focus
30
+ * returns to the document.
31
+ *
11
32
  * Exported so the visual builder's canvas + preview render sites reuse the
12
33
  * same isolation the rendered `WidgetRenderer` already had — every place a
13
34
  * widget component is rendered goes through {@link RenderedWidget}, which
@@ -15,19 +36,50 @@ import { Tabs, TabsList, TabsTrigger, TabsContent } from "../primitives/tabs.js"
15
36
  */
16
37
  export class WidgetErrorBoundary extends Component {
17
38
  state = { error: null };
39
+ /** Set by the user's retry; consumed by the next update. */
40
+ retryFocusOwed = false;
41
+ fallbackRef = createRef();
18
42
  static getDerivedStateFromError(error) {
19
43
  return { error };
20
44
  }
21
45
  componentDidCatch(error, info) {
22
46
  console.error(`Widget ${this.props.widgetId} crashed:`, error, info);
23
47
  }
48
+ componentDidUpdate(prevProps) {
49
+ if (this.retryFocusOwed) {
50
+ this.retryFocusOwed = false;
51
+ if (this.state.error)
52
+ this.fallbackRef.current?.querySelector("button")?.focus();
53
+ }
54
+ if (this.state.error && resetKeysChanged(prevProps.resetKeys, this.props.resetKeys)) {
55
+ this.reset();
56
+ }
57
+ }
58
+ reset = () => {
59
+ this.setState({ error: null });
60
+ };
61
+ /** The fallback's retry: a reset that keeps keyboard focus if the widget crashes again. */
62
+ retry = () => {
63
+ this.retryFocusOwed = true;
64
+ this.reset();
65
+ };
24
66
  render() {
25
67
  if (this.state.error) {
26
- return (_jsx(Card, { children: _jsxs(CardContent, { className: "text-muted-foreground py-3 text-sm", children: ["Widget \"", this.props.widgetId, "\" failed to render."] }) }));
68
+ return (_jsx("div", { ref: this.fallbackRef, "data-slot": "widget-crash", children: _jsx(WidgetCrashFallback, { widgetId: this.props.widgetId, error: this.state.error, onRetry: this.retry }) }));
27
69
  }
28
70
  return this.props.children;
29
71
  }
30
72
  }
73
+ /** The localized crash fallback of {@link WidgetErrorBoundary}. */
74
+ export function WidgetCrashFallback({ widgetId, error, onRetry, }) {
75
+ const messages = useToolkitMessages();
76
+ return (_jsx(ErrorState, { variant: "inline", title: messages.renderer.widgetCrashed, details: `${widgetId}: ${error.message}`, onRetry: onRetry }));
77
+ }
78
+ /** A layout cell names a widget the bundle does not contain: say so, quietly. */
79
+ export function UnavailableWidget({ widgetId, className = "text-muted-foreground rounded-md border border-dashed p-4 text-sm", }) {
80
+ const messages = useToolkitMessages();
81
+ return (_jsxs("div", { "data-slot": "widget-unavailable", "data-widget-id": widgetId, className: className, children: [_jsx("p", { children: messages.renderer.widgetUnavailable }), _jsx("p", { className: "mt-1 font-mono text-xs break-all", children: widgetId })] }));
82
+ }
31
83
  /**
32
84
  * True when the pipeline context carries no widget data at all — no step
33
85
  * results and no keys. A widget rendered against an empty context typically
@@ -45,7 +97,7 @@ export function contextHasNoData(context) {
45
97
  * The single render site for a layout cell's widget, shared across the
46
98
  * rendered view (`RowsRenderer`), the builder canvas, and the builder
47
99
  * preview so all three isolate crashes identically:
48
- * - missing component → "not bundled" fallback,
100
+ * - missing component → a localized "not available in this version" note,
49
101
  * - render crash → caught by {@link WidgetErrorBoundary} (Finding [1]),
50
102
  * - (builder only, opt-in) empty pipeline context → a dezent
51
103
  * "no data yet" hint (Finding [4]).
@@ -59,43 +111,42 @@ export function contextHasNoData(context) {
59
111
  * `notBundledClassName` lets each surface match its own chrome (the builder
60
112
  * uses denser padding/text than the rendered view).
61
113
  */
62
- export function RenderedWidget({ widget: Widget, widgetId, props, cellProps, showEmptyHint = false, notBundledClassName = "text-muted-foreground rounded border border-dashed p-4 text-center text-xs", }) {
63
- if (!Widget) {
64
- return _jsxs("div", { className: notBundledClassName, children: ["Widget \"", widgetId, "\" not bundled."] });
65
- }
114
+ export function RenderedWidget({ widget: Widget, widgetId, props, cellProps, showEmptyHint = false, notBundledClassName, }) {
115
+ if (!Widget)
116
+ return _jsx(UnavailableWidget, { widgetId: widgetId, className: notBundledClassName });
66
117
  if (showEmptyHint && contextHasNoData(props.context)) {
67
- return (_jsx("div", { className: notBundledClassName, children: "No data for this widget yet \u2014 it appears after a refresh." }));
118
+ return _jsx(NoDataYet, { className: notBundledClassName });
68
119
  }
69
- return (_jsx(WidgetErrorBoundary, { widgetId: widgetId, children: _jsx(Widget, { ...props, widgetProps: cellProps }) }));
120
+ return (_jsx(WidgetErrorBoundary, { widgetId: widgetId, resetKeys: [props.context, props.keys, cellProps], children: _jsx(Widget, { ...props, widgetProps: cellProps }) }));
121
+ }
122
+ function NoDataYet({ className = "text-muted-foreground rounded-md border border-dashed p-4 text-sm", }) {
123
+ const messages = useToolkitMessages();
124
+ return _jsx("div", { className: className, children: messages.renderer.noDataYet });
70
125
  }
71
126
  function RowsRenderer({ rows, props, widgets, }) {
72
127
  return (_jsx("div", { className: "flex flex-col gap-4", children: rows.map((row, rowIdx) => (_jsx(GridLayout, { children: row.row.map((cell, cellIdx) => {
73
128
  const WidgetComponent = widgets[cell.widget];
74
- // Keep the rendered view's original behaviour of skipping cells
75
- // whose component isn't bundled (no placeholder in the shipped
76
- // view), while still routing the resolvable ones through the
77
- // shared `RenderedWidget` for error-boundary + empty-state.
78
- if (!WidgetComponent)
79
- return null;
80
- // Use a composite key so the same widget can appear in multiple
81
- // cells of one row (e.g. side-by-side scoped dashboards) without
82
- // colliding on React's reconciliation key.
129
+ // An unknown widget id renders the localized "not available"
130
+ // note in its cell instead of vanishing (an empty page reads as
131
+ // broken). The composite key lets the same widget appear in
132
+ // several cells of one row (e.g. side-by-side scoped dashboards)
133
+ // without colliding on React's reconciliation key.
83
134
  return (_jsx(GridItem, { span: cell.span ?? 12, children: _jsx(RenderedWidget, { widget: WidgetComponent, widgetId: cell.widget, props: props, cellProps: cell.props }) }, `${cell.widget}-${cellIdx}`));
84
135
  }) }, rowIdx))) }));
85
136
  }
86
- export function WidgetRenderer({ layout, keys, stepData, errors, widgets }) {
137
+ /**
138
+ * The pipeline context widgets read. `WidgetRenderer` rebuilds it only when
139
+ * the view data changes — its identity is the error boundaries' reset key.
140
+ */
141
+ export function buildPipelineContext(keys, stepData, errors) {
87
142
  const steps = {};
88
- if (stepData) {
89
- for (const [id, result] of Object.entries(stepData)) {
90
- steps[id] = { ...result, _step: id };
91
- }
143
+ for (const [id, result] of Object.entries(stepData ?? {})) {
144
+ steps[id] = { ...result, _step: id };
92
145
  }
93
- const pipelineContext = {
94
- steps,
95
- keys,
96
- errors,
97
- };
98
- const widgetProps = { keys, context: pipelineContext };
146
+ return { steps, keys, errors };
147
+ }
148
+ export function WidgetRenderer({ layout, keys, stepData, errors, widgets }) {
149
+ const widgetProps = useMemo(() => ({ keys, context: buildPipelineContext(keys, stepData, errors) }), [keys, stepData, errors]);
99
150
  const normalized = normalizeLayout(layout);
100
151
  if ("tabs" in normalized) {
101
152
  return (_jsxs(Tabs, { defaultValue: normalized.tabs[0]?.label, children: [_jsx(TabsList, { children: normalized.tabs.map((tab) => (_jsx(TabsTrigger, { value: tab.label, children: tab.label }, tab.label))) }), normalized.tabs.map((tab) => (_jsx(TabsContent, { value: tab.label, children: _jsx(RowsRenderer, { rows: tab.rows, props: widgetProps, widgets: widgets }) }, tab.label)))] }));
@@ -1 +1 @@
1
- {"version":3,"file":"widget-renderer.js","sourceRoot":"","sources":["../../src/app/widget-renderer.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"widget-renderer.js","sourceRoot":"","sources":["../../src/app/widget-renderer.tsx"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"DrillButton.d.ts","sourceRoot":"","sources":["../../src/components/DrillButton.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAEtC,MAAM,WAAW,gBAAgB;IAC/B,oBAAoB;IACpB,QAAQ,EAAE,SAAS,CAAA;IACnB,mFAAmF;IACnF,OAAO,EAAE,MAAM,IAAI,CAAA;IACnB,sFAAsF;IACtF,IAAI,CAAC,EAAE,SAAS,CAAA;IAChB,qEAAqE;IACrE,IAAI,CAAC,EAAE,IAAI,GAAG,IAAI,CAAA;IAClB,uDAAuD;IACvD,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,EAAE,QAAQ,EAAE,OAAO,EAAE,IAAI,EAAE,IAAW,EAAE,SAAS,EAAE,EAAE,gBAAgB,+BAchG"}
1
+ {"version":3,"file":"DrillButton.d.ts","sourceRoot":"","sources":["../../src/components/DrillButton.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAGtC,MAAM,WAAW,gBAAgB;IAC/B,oBAAoB;IACpB,QAAQ,EAAE,SAAS,CAAA;IACnB,mFAAmF;IACnF,OAAO,EAAE,MAAM,IAAI,CAAA;IACnB,sFAAsF;IACtF,IAAI,CAAC,EAAE,SAAS,CAAA;IAChB,qEAAqE;IACrE,IAAI,CAAC,EAAE,IAAI,GAAG,IAAI,CAAA;IAClB,uDAAuD;IACvD,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,EAAE,QAAQ,EAAE,OAAO,EAAE,IAAI,EAAE,IAAW,EAAE,SAAS,EAAE,EAAE,gBAAgB,+BAchG"}
@@ -1,4 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { FOCUS_RING } from "../lib/focus-ring.js";
2
3
  /**
3
4
  * The single, deterministic in-widget navigation control: a neutral outline
4
5
  * button with a trailing → that drills into a detail/sub-view. Use it for
@@ -8,6 +9,6 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
8
9
  */
9
10
  export function DrillButton({ children, onDrill, icon, size = "sm", ariaLabel }) {
10
11
  const pad = size === "md" ? "px-3 py-1.5 text-sm" : "px-2.5 py-1 text-xs";
11
- return (_jsxs("button", { type: "button", onClick: onDrill, "aria-label": ariaLabel, 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}`, children: [icon && _jsx("span", { "aria-hidden": "true", children: icon }), children, _jsx("span", { "aria-hidden": "true", children: "\u2192" })] }));
12
+ return (_jsxs("button", { type: "button", onClick: onDrill, "aria-label": ariaLabel, className: `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 ${FOCUS_RING} ${pad}`, children: [icon && _jsx("span", { "aria-hidden": "true", children: icon }), children, _jsx("span", { "aria-hidden": "true", children: "\u2192" })] }));
12
13
  }
13
14
  //# sourceMappingURL=DrillButton.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"DrillButton.js","sourceRoot":"","sources":["../../src/components/DrillButton.tsx"],"names":[],"mappings":";AAeA;;;;;;GAMG;AACH,MAAM,UAAU,WAAW,CAAC,EAAE,QAAQ,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,GAAG,IAAI,EAAE,SAAS,EAAoB;IAC/F,MAAM,GAAG,GAAG,IAAI,KAAK,IAAI,CAAC,CAAC,CAAC,qBAAqB,CAAC,CAAC,CAAC,qBAAqB,CAAA;IACzE,OAAO,CACL,kBACE,IAAI,EAAC,QAAQ,EACb,OAAO,EAAE,OAAO,gBACJ,SAAS,EACrB,SAAS,EAAE,yLAAyL,GAAG,EAAE,aAExM,IAAI,IAAI,8BAAkB,MAAM,YAAE,IAAI,GAAQ,EAC9C,QAAQ,EACT,8BAAkB,MAAM,uBAAS,IAC1B,CACV,CAAA;AACH,CAAC"}
1
+ {"version":3,"file":"DrillButton.js","sourceRoot":"","sources":["../../src/components/DrillButton.tsx"],"names":[],"mappings":";AACA,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAA;AAejD;;;;;;GAMG;AACH,MAAM,UAAU,WAAW,CAAC,EAAE,QAAQ,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,GAAG,IAAI,EAAE,SAAS,EAAoB;IAC/F,MAAM,GAAG,GAAG,IAAI,KAAK,IAAI,CAAC,CAAC,CAAC,qBAAqB,CAAC,CAAC,CAAC,qBAAqB,CAAA;IACzE,OAAO,CACL,kBACE,IAAI,EAAC,QAAQ,EACb,OAAO,EAAE,OAAO,gBACJ,SAAS,EACrB,SAAS,EAAE,gLAAgL,UAAU,IAAI,GAAG,EAAE,aAE7M,IAAI,IAAI,8BAAkB,MAAM,YAAE,IAAI,GAAQ,EAC9C,QAAQ,EACT,8BAAkB,MAAM,uBAAS,IAC1B,CACV,CAAA;AACH,CAAC"}
@@ -0,0 +1,33 @@
1
+ import type { ReactNode } from "react";
2
+ /** Heading element for an empty/error state title (the surface decides the outline level). */
3
+ export type StateHeadingLevel = "h2" | "h3" | "h4" | "p";
4
+ export interface EmptyStateProps {
5
+ /**
6
+ * Icon or mark above the title, rendered decoratively (`aria-hidden`): a
7
+ * Lucide icon for the concrete situation (e.g. `<CircleCheck />` for "all
8
+ * done"), or a brand mark (an app passes its logo; a customer brand passes
9
+ * its own or nothing). An SVG renders at 24 px unless it carries its own
10
+ * `size-*` class, so a mark can be larger (`<Komet className="size-12" />`).
11
+ */
12
+ icon?: ReactNode;
13
+ /** Colour of the icon slot: `"neutral"` (muted, default) or `"success"` ("all done"). */
14
+ tone?: "neutral" | "success";
15
+ /** What is empty, in plain words ("No open incidents"). */
16
+ title: ReactNode;
17
+ /** One help line: why it is empty or what to do next. */
18
+ description?: ReactNode;
19
+ /** The primary next step (a `Button`). */
20
+ action?: ReactNode;
21
+ /** An optional second, quieter step. Empty states carry at most two actions. */
22
+ secondaryAction?: ReactNode;
23
+ /** Element of the title. Default `"h3"` (inside a widget below its own heading). */
24
+ titleAs?: StateHeadingLevel;
25
+ className?: string;
26
+ }
27
+ /**
28
+ * The one empty-state pattern: icon or mark, a title (600), one help line and
29
+ * up to two actions, centred in the space the content would take. Static (no
30
+ * live region): it replaces content, it does not announce a change.
31
+ */
32
+ export declare function EmptyState({ icon, tone, title, description, action, secondaryAction, titleAs: Title, className, }: EmptyStateProps): import("react").JSX.Element;
33
+ //# sourceMappingURL=EmptyState.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"EmptyState.d.ts","sourceRoot":"","sources":["../../src/components/EmptyState.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAItC,8FAA8F;AAC9F,MAAM,MAAM,iBAAiB,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,GAAG,CAAA;AAExD,MAAM,WAAW,eAAe;IAC9B;;;;;;OAMG;IACH,IAAI,CAAC,EAAE,SAAS,CAAA;IAChB,yFAAyF;IACzF,IAAI,CAAC,EAAE,SAAS,GAAG,SAAS,CAAA;IAC5B,2DAA2D;IAC3D,KAAK,EAAE,SAAS,CAAA;IAChB,yDAAyD;IACzD,WAAW,CAAC,EAAE,SAAS,CAAA;IACvB,0CAA0C;IAC1C,MAAM,CAAC,EAAE,SAAS,CAAA;IAClB,gFAAgF;IAChF,eAAe,CAAC,EAAE,SAAS,CAAA;IAC3B,oFAAoF;IACpF,OAAO,CAAC,EAAE,iBAAiB,CAAA;IAC3B,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAQD;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,EACzB,IAAI,EACJ,IAAgB,EAChB,KAAK,EACL,WAAW,EACX,MAAM,EACN,eAAe,EACf,OAAO,EAAE,KAAY,EACrB,SAAS,GACV,EAAE,eAAe,+BA6BjB"}
@@ -0,0 +1,17 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { TONE_ICON } from "../lib/tone-utils.js";
3
+ import { cn } from "../lib/utils.js";
4
+ /** The icon slot in the tone's icon colour (`TONE_ICON`): quiet, or the success tone. */
5
+ const ICON_TONE = {
6
+ neutral: TONE_ICON.neutral,
7
+ success: TONE_ICON.success,
8
+ };
9
+ /**
10
+ * The one empty-state pattern: icon or mark, a title (600), one help line and
11
+ * up to two actions, centred in the space the content would take. Static (no
12
+ * live region): it replaces content, it does not announce a change.
13
+ */
14
+ export function EmptyState({ icon, tone = "neutral", title, description, action, secondaryAction, titleAs: Title = "h3", className, }) {
15
+ return (_jsxs("div", { "data-slot": "empty-state", className: cn("flex flex-col items-center gap-2 px-4 py-8 text-center", className), children: [icon && (_jsx("div", { "aria-hidden": "true", className: cn("mb-1 flex items-center justify-center [&_svg:not([class*='size-'])]:size-6", ICON_TONE[tone]), children: icon })), _jsx(Title, { className: "text-foreground text-base font-semibold", children: title }), description && (_jsx("p", { className: "text-muted-foreground max-w-prose text-sm text-balance", children: description })), (action || secondaryAction) && (_jsxs("div", { className: "mt-2 flex flex-wrap items-center justify-center gap-2", children: [action, secondaryAction] }))] }));
16
+ }
17
+ //# sourceMappingURL=EmptyState.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"EmptyState.js","sourceRoot":"","sources":["../../src/components/EmptyState.tsx"],"names":[],"mappings":";AACA,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAA;AAChD,OAAO,EAAE,EAAE,EAAE,MAAM,iBAAiB,CAAA;AA6BpC,yFAAyF;AACzF,MAAM,SAAS,GAAyD;IACtE,OAAO,EAAE,SAAS,CAAC,OAAO;IAC1B,OAAO,EAAE,SAAS,CAAC,OAAO;CAC3B,CAAA;AAED;;;;GAIG;AACH,MAAM,UAAU,UAAU,CAAC,EACzB,IAAI,EACJ,IAAI,GAAG,SAAS,EAChB,KAAK,EACL,WAAW,EACX,MAAM,EACN,eAAe,EACf,OAAO,EAAE,KAAK,GAAG,IAAI,EACrB,SAAS,GACO;IAChB,OAAO,CACL,4BACY,aAAa,EACvB,SAAS,EAAE,EAAE,CAAC,wDAAwD,EAAE,SAAS,CAAC,aAEjF,IAAI,IAAI,CACP,6BACc,MAAM,EAClB,SAAS,EAAE,EAAE,CACX,4EAA4E,EAC5E,SAAS,CAAC,IAAI,CAAC,CAChB,YAEA,IAAI,GACD,CACP,EACD,KAAC,KAAK,IAAC,SAAS,EAAC,yCAAyC,YAAE,KAAK,GAAS,EACzE,WAAW,IAAI,CACd,YAAG,SAAS,EAAC,wDAAwD,YAAE,WAAW,GAAK,CACxF,EACA,CAAC,MAAM,IAAI,eAAe,CAAC,IAAI,CAC9B,eAAK,SAAS,EAAC,uDAAuD,aACnE,MAAM,EACN,eAAe,IACZ,CACP,IACG,CACP,CAAA;AACH,CAAC"}
@@ -0,0 +1,50 @@
1
+ import type { ReactNode } from "react";
2
+ import { type LucideIcon } from "lucide-react";
3
+ import type { StateHeadingLevel } from "./EmptyState.js";
4
+ /** Status tone of an {@link ErrorState}. */
5
+ export type ErrorStateTone = "danger" | "warning" | "info";
6
+ export interface ErrorStateProps {
7
+ /** What happened, in plain words ("This view couldn't be loaded"). */
8
+ title: ReactNode;
9
+ /** What you can do next — or the human reason, when you can map the raw one. */
10
+ description?: ReactNode;
11
+ /**
12
+ * The raw technical reason (engine exception, HTTP status), shown collapsed
13
+ * under a "Details" disclosure so it informs without leading.
14
+ */
15
+ details?: ReactNode;
16
+ /** Summary text of the details disclosure. Default: the locale's "Details". */
17
+ detailsLabel?: string;
18
+ /** Renders a retry button when set. */
19
+ onRetry?: () => void;
20
+ /** Retry button label. Default: the locale's "Try again". */
21
+ retryLabel?: string;
22
+ /** Further actions after the retry button (e.g. a link to another view). */
23
+ actions?: ReactNode;
24
+ /** Default `"danger"`. `"warning"` for partial failures, `"info"` for notices. */
25
+ tone?: ErrorStateTone;
26
+ /** Replace the tone's default Lucide icon. */
27
+ icon?: LucideIcon;
28
+ /**
29
+ * `"block"` (default): the state that replaces a whole view or widget body.
30
+ * `"inline"`: a compact alert above content that still renders.
31
+ */
32
+ variant?: "block" | "inline";
33
+ /**
34
+ * Live-region role. Default `"alert"` — an error that appears after the
35
+ * fact is announced. Use `"status"` for a quieter notice, or `"none"` when
36
+ * the state is part of the first paint.
37
+ */
38
+ role?: "alert" | "status" | "none";
39
+ /** Element of the title. Default `"h2"` for `block`, `"p"` for `inline`. */
40
+ titleAs?: StateHeadingLevel;
41
+ className?: string;
42
+ }
43
+ /**
44
+ * The one error pattern: a Lucide icon, a title that says what happened, a
45
+ * line that says what you can do, the raw reason collapsed under "Details",
46
+ * and a retry. `inline` is the alert form above content that still renders
47
+ * (failed data steps); `block` replaces a whole view or widget body.
48
+ */
49
+ export declare function ErrorState({ title, description, details, detailsLabel, onRetry, retryLabel, actions, tone, icon, variant, role, titleAs, className, }: ErrorStateProps): import("react").JSX.Element;
50
+ //# sourceMappingURL=ErrorState.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ErrorState.d.ts","sourceRoot":"","sources":["../../src/components/ErrorState.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AACtC,OAAO,EAA+C,KAAK,UAAU,EAAE,MAAM,cAAc,CAAA;AAM3F,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAA;AAExD,4CAA4C;AAC5C,MAAM,MAAM,cAAc,GAAG,QAAQ,GAAG,SAAS,GAAG,MAAM,CAAA;AAE1D,MAAM,WAAW,eAAe;IAC9B,sEAAsE;IACtE,KAAK,EAAE,SAAS,CAAA;IAChB,gFAAgF;IAChF,WAAW,CAAC,EAAE,SAAS,CAAA;IACvB;;;OAGG;IACH,OAAO,CAAC,EAAE,SAAS,CAAA;IACnB,+EAA+E;IAC/E,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,uCAAuC;IACvC,OAAO,CAAC,EAAE,MAAM,IAAI,CAAA;IACpB,6DAA6D;IAC7D,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,4EAA4E;IAC5E,OAAO,CAAC,EAAE,SAAS,CAAA;IACnB,kFAAkF;IAClF,IAAI,CAAC,EAAE,cAAc,CAAA;IACrB,8CAA8C;IAC9C,IAAI,CAAC,EAAE,UAAU,CAAA;IACjB;;;OAGG;IACH,OAAO,CAAC,EAAE,OAAO,GAAG,QAAQ,CAAA;IAC5B;;;;OAIG;IACH,IAAI,CAAC,EAAE,OAAO,GAAG,QAAQ,GAAG,MAAM,CAAA;IAClC,4EAA4E;IAC5E,OAAO,CAAC,EAAE,iBAAiB,CAAA;IAC3B,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAmCD;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,EACzB,KAAK,EACL,WAAW,EACX,OAAO,EACP,YAAY,EACZ,OAAO,EACP,UAAU,EACV,OAAO,EACP,IAAe,EACf,IAAI,EACJ,OAAiB,EACjB,IAAc,EACd,OAAO,EACP,SAAS,GACV,EAAE,eAAe,+BAmCjB"}
@@ -0,0 +1,60 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { CircleAlert, Info, RefreshCw, TriangleAlert } 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 { TONE_BORDER, TONE_ICON, TONE_SOFT } from "../lib/tone-utils.js";
7
+ import { cn } from "../lib/utils.js";
8
+ const STATE_ICON = {
9
+ danger: CircleAlert,
10
+ warning: TriangleAlert,
11
+ info: Info,
12
+ };
13
+ /** Per-variant geometry: `block` replaces a surface, `inline` sits above content. */
14
+ const VARIANT = {
15
+ block: {
16
+ box: "gap-4 p-4",
17
+ icon: "size-5",
18
+ title: "text-base",
19
+ defaultTitleAs: "h2",
20
+ retry: "default",
21
+ },
22
+ inline: {
23
+ box: "gap-3 px-4 py-3 text-sm",
24
+ icon: "size-4",
25
+ title: "text-sm",
26
+ defaultTitleAs: "p",
27
+ retry: "outline",
28
+ },
29
+ };
30
+ /**
31
+ * The one error pattern: a Lucide icon, a title that says what happened, a
32
+ * line that says what you can do, the raw reason collapsed under "Details",
33
+ * and a retry. `inline` is the alert form above content that still renders
34
+ * (failed data steps); `block` replaces a whole view or widget body.
35
+ */
36
+ export function ErrorState({ title, description, details, detailsLabel, onRetry, retryLabel, actions, tone = "danger", icon, variant = "block", role = "alert", titleAs, className, }) {
37
+ const style = VARIANT[variant];
38
+ const Icon = icon ?? STATE_ICON[tone];
39
+ const Title = titleAs ?? style.defaultTitleAs;
40
+ // The status colour as rim, tint and icon; every word on the tint is the
41
+ // foreground (TONE_SOFT, CI §3.3). On dark the tint is transparent and the
42
+ // rim carries the tone.
43
+ return (_jsxs("div", { "data-slot": "error-state", "data-variant": variant, "data-tone": tone, role: role === "none" ? undefined : role, className: cn("flex items-start rounded-md border", style.box, TONE_SOFT[tone], TONE_BORDER[tone], className), children: [_jsx(Icon, { "aria-hidden": "true", className: cn("mt-0.5 shrink-0", style.icon, TONE_ICON[tone]) }), _jsxs("div", { className: "flex min-w-0 flex-1 flex-col gap-1", children: [_jsx(Title, { className: cn("font-semibold", style.title), children: title }), description && _jsx("div", { className: "text-sm break-words", children: description }), details && _jsx(ErrorDetails, { label: detailsLabel, children: details }), _jsx(ErrorActions, { onRetry: onRetry, retryLabel: retryLabel, retryVariant: style.retry, actions: actions })] })] }));
44
+ }
45
+ /**
46
+ * The raw reason, collapsed: a native disclosure (keyboard + screen-reader
47
+ * ready). It sits on the tint, so its text stays the foreground; the collapse
48
+ * and the small mono type keep it quieter than the title.
49
+ */
50
+ function ErrorDetails({ label, children }) {
51
+ const messages = useToolkitMessages();
52
+ return (_jsxs("details", { className: "text-xs", children: [_jsx("summary", { className: `w-fit cursor-pointer rounded-sm py-0.5 select-none ${FOCUS_RING}`, children: label ?? messages.common.details }), _jsx("pre", { className: "mt-1 font-mono break-words whitespace-pre-wrap", children: children })] }));
53
+ }
54
+ function ErrorActions({ onRetry, retryLabel, retryVariant, actions, }) {
55
+ const messages = useToolkitMessages();
56
+ if (!onRetry && !actions)
57
+ return null;
58
+ return (_jsxs("div", { className: "mt-2 flex flex-wrap items-center gap-2", children: [onRetry && (_jsxs(Button, { type: "button", size: "sm", variant: retryVariant, onClick: onRetry, children: [_jsx(RefreshCw, { "aria-hidden": "true" }), retryLabel ?? messages.common.retry] })), actions] }));
59
+ }
60
+ //# sourceMappingURL=ErrorState.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ErrorState.js","sourceRoot":"","sources":["../../src/components/ErrorState.tsx"],"names":[],"mappings":"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"}
@@ -11,7 +11,7 @@ export interface FilterChip {
11
11
  export interface FilterBarProps {
12
12
  /** Current search text (controlled). */
13
13
  search: string;
14
- /** Placeholder for the search input. Default `"Filter…"`. */
14
+ /** Placeholder for the search input. Default: the locale's "Filter…". */
15
15
  searchPlaceholder?: string;
16
16
  /** Called with the new search text on every keystroke. */
17
17
  onSearchChange: (next: string) => void;
@@ -1 +1 @@
1
- {"version":3,"file":"FilterBar.d.ts","sourceRoot":"","sources":["../../src/components/FilterBar.tsx"],"names":[],"mappings":"AAEA,MAAM,WAAW,UAAU;IACzB,+CAA+C;IAC/C,EAAE,EAAE,MAAM,CAAA;IACV,0BAA0B;IAC1B,KAAK,EAAE,MAAM,CAAA;IACb,+CAA+C;IAC/C,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;IACvB,4CAA4C;IAC5C,MAAM,CAAC,EAAE,OAAO,CAAA;CACjB;AAED,MAAM,WAAW,cAAc;IAC7B,wCAAwC;IACxC,MAAM,EAAE,MAAM,CAAA;IACd,6DAA6D;IAC7D,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,0DAA0D;IAC1D,cAAc,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;IACtC,8DAA8D;IAC9D,KAAK,EAAE,UAAU,EAAE,CAAA;IACnB,gDAAgD;IAChD,YAAY,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAA;CACnC;AAED;;;GAGG;AACH,wBAAgB,SAAS,CAAC,EACxB,MAAM,EACN,iBAA6B,EAC7B,cAAc,EACd,KAAK,EACL,YAAY,GACb,EAAE,cAAc,+BA+BhB"}
1
+ {"version":3,"file":"FilterBar.d.ts","sourceRoot":"","sources":["../../src/components/FilterBar.tsx"],"names":[],"mappings":"AAIA,MAAM,WAAW,UAAU;IACzB,+CAA+C;IAC/C,EAAE,EAAE,MAAM,CAAA;IACV,0BAA0B;IAC1B,KAAK,EAAE,MAAM,CAAA;IACb,+CAA+C;IAC/C,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;IACvB,4CAA4C;IAC5C,MAAM,CAAC,EAAE,OAAO,CAAA;CACjB;AAED,MAAM,WAAW,cAAc;IAC7B,wCAAwC;IACxC,MAAM,EAAE,MAAM,CAAA;IACd,yEAAyE;IACzE,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,0DAA0D;IAC1D,cAAc,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;IACtC,8DAA8D;IAC9D,KAAK,EAAE,UAAU,EAAE,CAAA;IACnB,gDAAgD;IAChD,YAAY,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAA;CACnC;AAED;;;GAGG;AACH,wBAAgB,SAAS,CAAC,EACxB,MAAM,EACN,iBAAiB,EACjB,cAAc,EACd,KAAK,EACL,YAAY,GACb,EAAE,cAAc,+BAgChB"}
@@ -1,12 +1,15 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { FOCUS_RING } from "../lib/focus-ring.js";
3
+ import { useToolkitMessages } from "../locale/locale-provider.js";
2
4
  import { Input } from "../primitives/input.js";
3
5
  /**
4
6
  * Search input + toggleable chip row for filtering a list/table of widget
5
7
  * items. Pair with `useDebouncedValue` to avoid re-querying on every keystroke.
6
8
  */
7
- export function FilterBar({ search, searchPlaceholder = "Filter…", onSearchChange, chips, onChipToggle, }) {
8
- return (_jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [_jsx(Input, { type: "search", value: search, onChange: (e) => onSearchChange(e.target.value), placeholder: searchPlaceholder, className: "h-9 min-w-[220px] flex-1 text-sm" }), _jsx("div", { className: "flex flex-wrap gap-1.5", children: chips.map((chip) => (_jsxs("button", { type: "button", onClick: () => onChipToggle(chip.id), className: `inline-flex items-center gap-1.5 rounded-md border px-2.5 py-1 text-xs font-medium transition-colors ${chip.active
9
- ? "bg-accent border-border text-accent-foreground font-semibold"
10
- : "border-border bg-background text-muted-foreground hover:bg-muted hover:text-foreground"}`, children: [chip.label, chip.count !== undefined && chip.count !== "" && (_jsx("span", { className: "tabular-nums opacity-60", children: chip.count }))] }, chip.id))) })] }));
9
+ export function FilterBar({ search, searchPlaceholder, onSearchChange, chips, onChipToggle, }) {
10
+ const messages = useToolkitMessages();
11
+ return (_jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [_jsx(Input, { type: "search", value: search, onChange: (e) => onSearchChange(e.target.value), placeholder: searchPlaceholder ?? messages.filterBar.searchPlaceholder, className: "h-9 min-w-[220px] flex-1 text-sm" }), _jsx("div", { className: "flex flex-wrap gap-1.5", children: chips.map((chip) => (_jsxs("button", { type: "button", onClick: () => onChipToggle(chip.id), className: `inline-flex items-center gap-1.5 rounded-sm border px-2.5 py-1 text-xs font-medium transition-colors ${FOCUS_RING} ${chip.active
12
+ ? "bg-accent border-focus text-accent-foreground font-semibold"
13
+ : "border-border bg-background text-muted-foreground hover:bg-muted hover:text-foreground"}`, children: [chip.label, chip.count !== undefined && chip.count !== "" && (_jsx("span", { className: "font-normal tabular-nums", children: chip.count }))] }, chip.id))) })] }));
11
14
  }
12
15
  //# sourceMappingURL=FilterBar.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"FilterBar.js","sourceRoot":"","sources":["../../src/components/FilterBar.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,wBAAwB,CAAA;AA0B9C;;;GAGG;AACH,MAAM,UAAU,SAAS,CAAC,EACxB,MAAM,EACN,iBAAiB,GAAG,SAAS,EAC7B,cAAc,EACd,KAAK,EACL,YAAY,GACG;IACf,OAAO,CACL,eAAK,SAAS,EAAC,mCAAmC,aAChD,KAAC,KAAK,IACJ,IAAI,EAAC,QAAQ,EACb,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,cAAc,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAC/C,WAAW,EAAE,iBAAiB,EAC9B,SAAS,EAAC,kCAAkC,GAC5C,EACF,cAAK,SAAS,EAAC,wBAAwB,YACpC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CACnB,kBACE,IAAI,EAAC,QAAQ,EAEb,OAAO,EAAE,GAAG,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,EAAE,CAAC,EACpC,SAAS,EAAE,wGACT,IAAI,CAAC,MAAM;wBACT,CAAC,CAAC,8DAA8D;wBAChE,CAAC,CAAC,wFACN,EAAE,aAED,IAAI,CAAC,KAAK,EACV,IAAI,CAAC,KAAK,KAAK,SAAS,IAAI,IAAI,CAAC,KAAK,KAAK,EAAE,IAAI,CAChD,eAAM,SAAS,EAAC,yBAAyB,YAAE,IAAI,CAAC,KAAK,GAAQ,CAC9D,KAXI,IAAI,CAAC,EAAE,CAYL,CACV,CAAC,GACE,IACF,CACP,CAAA;AACH,CAAC"}
1
+ {"version":3,"file":"FilterBar.js","sourceRoot":"","sources":["../../src/components/FilterBar.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAA;AACjD,OAAO,EAAE,kBAAkB,EAAE,MAAM,8BAA8B,CAAA;AACjE,OAAO,EAAE,KAAK,EAAE,MAAM,wBAAwB,CAAA;AA0B9C;;;GAGG;AACH,MAAM,UAAU,SAAS,CAAC,EACxB,MAAM,EACN,iBAAiB,EACjB,cAAc,EACd,KAAK,EACL,YAAY,GACG;IACf,MAAM,QAAQ,GAAG,kBAAkB,EAAE,CAAA;IACrC,OAAO,CACL,eAAK,SAAS,EAAC,mCAAmC,aAChD,KAAC,KAAK,IACJ,IAAI,EAAC,QAAQ,EACb,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,cAAc,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAC/C,WAAW,EAAE,iBAAiB,IAAI,QAAQ,CAAC,SAAS,CAAC,iBAAiB,EACtE,SAAS,EAAC,kCAAkC,GAC5C,EACF,cAAK,SAAS,EAAC,wBAAwB,YACpC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CACnB,kBACE,IAAI,EAAC,QAAQ,EAEb,OAAO,EAAE,GAAG,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,EAAE,CAAC,EACpC,SAAS,EAAE,wGAAwG,UAAU,IAC3H,IAAI,CAAC,MAAM;wBACT,CAAC,CAAC,6DAA6D;wBAC/D,CAAC,CAAC,wFACN,EAAE,aAED,IAAI,CAAC,KAAK,EACV,IAAI,CAAC,KAAK,KAAK,SAAS,IAAI,IAAI,CAAC,KAAK,KAAK,EAAE,IAAI,CAChD,eAAM,SAAS,EAAC,0BAA0B,YAAE,IAAI,CAAC,KAAK,GAAQ,CAC/D,KAXI,IAAI,CAAC,EAAE,CAYL,CACV,CAAC,GACE,IACF,CACP,CAAA;AACH,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"GroupCard.d.ts","sourceRoot":"","sources":["../../src/components/GroupCard.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAA6B,SAAS,EAAE,MAAM,OAAO,CAAA;AAEjE,MAAM,WAAW,cAAc;IAC7B,oCAAoC;IACpC,QAAQ,EAAE,OAAO,CAAA;IACjB,0CAA0C;IAC1C,QAAQ,EAAE,MAAM,IAAI,CAAA;IACpB,sEAAsE;IACtE,OAAO,EAAE,SAAS,CAAA;IAClB,uDAAuD;IACvD,QAAQ,CAAC,EAAE,SAAS,CAAA;CACrB;AAED;;;;;;;GAOG;AACH,wBAAgB,SAAS,CAAC,EAAE,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAE,QAAQ,EAAE,EAAE,cAAc,+BA8BlF"}
1
+ {"version":3,"file":"GroupCard.d.ts","sourceRoot":"","sources":["../../src/components/GroupCard.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAA6B,SAAS,EAAE,MAAM,OAAO,CAAA;AAGjE,MAAM,WAAW,cAAc;IAC7B,oCAAoC;IACpC,QAAQ,EAAE,OAAO,CAAA;IACjB,0CAA0C;IAC1C,QAAQ,EAAE,MAAM,IAAI,CAAA;IACpB,sEAAsE;IACtE,OAAO,EAAE,SAAS,CAAA;IAClB,uDAAuD;IACvD,QAAQ,CAAC,EAAE,SAAS,CAAA;CACrB;AAED;;;;;;;GAOG;AACH,wBAAgB,SAAS,CAAC,EAAE,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAE,QAAQ,EAAE,EAAE,cAAc,+BA8BlF"}
@@ -1,4 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { FOCUS_RING_INSET } from "../lib/focus-ring.js";
2
3
  /**
3
4
  * Bordered card with a clickable summary bar and a collapsible body. Use to
4
5
  * group related rows behind an expandable header (e.g. items grouped by a key).
@@ -19,6 +20,6 @@ export function GroupCard({ expanded, onToggle, summary, children }) {
19
20
  return;
20
21
  onToggle();
21
22
  }
22
- return (_jsxs("div", { className: "border-border mb-2 overflow-hidden rounded-lg border", children: [_jsx("div", { role: "button", tabIndex: 0, "aria-expanded": expanded, onClick: handleClick, onKeyDown: handleKeyDown, className: `bg-card hover:bg-muted focus-visible:ring-ring/50 cursor-pointer transition-colors focus:outline-none focus-visible:ring-2 ${expanded ? "border-border border-b" : ""}`, children: summary }), expanded && children && _jsx("div", { children: children })] }));
23
+ return (_jsxs("div", { className: "border-border mb-2 overflow-hidden rounded-md border", children: [_jsx("div", { role: "button", tabIndex: 0, "aria-expanded": expanded, onClick: handleClick, onKeyDown: handleKeyDown, className: `bg-card hover:bg-muted cursor-pointer transition-colors ${FOCUS_RING_INSET} ${expanded ? "border-border border-b" : ""}`, children: summary }), expanded && children && _jsx("div", { children: children })] }));
23
24
  }
24
25
  //# sourceMappingURL=GroupCard.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"GroupCard.js","sourceRoot":"","sources":["../../src/components/GroupCard.tsx"],"names":[],"mappings":";AAaA;;;;;;;GAOG;AACH,MAAM,UAAU,SAAS,CAAC,EAAE,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAE,QAAQ,EAAkB;IACjF,SAAS,aAAa,CAAC,CAAgC;QACrD,IAAI,CAAC,CAAC,GAAG,KAAK,OAAO,IAAI,CAAC,CAAC,GAAG,KAAK,GAAG,EAAE,CAAC;YACvC,CAAC,CAAC,cAAc,EAAE,CAAA;YAClB,QAAQ,EAAE,CAAA;QACZ,CAAC;IACH,CAAC;IAED,SAAS,WAAW,CAAC,CAA6B;QAChD,IAAK,CAAC,CAAC,MAAsB,CAAC,OAAO,CAAC,2CAA2C,CAAC;YAAE,OAAM;QAC1F,QAAQ,EAAE,CAAA;IACZ,CAAC;IAED,OAAO,CACL,eAAK,SAAS,EAAC,sDAAsD,aACnE,cACE,IAAI,EAAC,QAAQ,EACb,QAAQ,EAAE,CAAC,mBACI,QAAQ,EACvB,OAAO,EAAE,WAAW,EACpB,SAAS,EAAE,aAAa,EACxB,SAAS,EAAE,8HACT,QAAQ,CAAC,CAAC,CAAC,wBAAwB,CAAC,CAAC,CAAC,EACxC,EAAE,YAED,OAAO,GACJ,EACL,QAAQ,IAAI,QAAQ,IAAI,wBAAM,QAAQ,GAAO,IAC1C,CACP,CAAA;AACH,CAAC"}
1
+ {"version":3,"file":"GroupCard.js","sourceRoot":"","sources":["../../src/components/GroupCard.tsx"],"names":[],"mappings":";AACA,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAA;AAavD;;;;;;;GAOG;AACH,MAAM,UAAU,SAAS,CAAC,EAAE,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAE,QAAQ,EAAkB;IACjF,SAAS,aAAa,CAAC,CAAgC;QACrD,IAAI,CAAC,CAAC,GAAG,KAAK,OAAO,IAAI,CAAC,CAAC,GAAG,KAAK,GAAG,EAAE,CAAC;YACvC,CAAC,CAAC,cAAc,EAAE,CAAA;YAClB,QAAQ,EAAE,CAAA;QACZ,CAAC;IACH,CAAC;IAED,SAAS,WAAW,CAAC,CAA6B;QAChD,IAAK,CAAC,CAAC,MAAsB,CAAC,OAAO,CAAC,2CAA2C,CAAC;YAAE,OAAM;QAC1F,QAAQ,EAAE,CAAA;IACZ,CAAC;IAED,OAAO,CACL,eAAK,SAAS,EAAC,sDAAsD,aACnE,cACE,IAAI,EAAC,QAAQ,EACb,QAAQ,EAAE,CAAC,mBACI,QAAQ,EACvB,OAAO,EAAE,WAAW,EACpB,SAAS,EAAE,aAAa,EACxB,SAAS,EAAE,2DAA2D,gBAAgB,IACpF,QAAQ,CAAC,CAAC,CAAC,wBAAwB,CAAC,CAAC,CAAC,EACxC,EAAE,YAED,OAAO,GACJ,EACL,QAAQ,IAAI,QAAQ,IAAI,wBAAM,QAAQ,GAAO,IAC1C,CACP,CAAA;AACH,CAAC"}
@@ -9,9 +9,15 @@ export interface KpiCell {
9
9
  fraction?: ReactNode;
10
10
  /** Optional trend line under the value (e.g. `"+12% vs last week"`). */
11
11
  trend?: ReactNode;
12
- /** Colours the trend line. `up` reads as bad (danger), `down` as good (success). */
12
+ /**
13
+ * Tones the trend line's dot. `up` reads as bad (danger), `down` as good
14
+ * (success); the trend text itself stays in the foreground.
15
+ */
13
16
  trendDirection?: "up" | "down" | "flat";
14
- /** Tone applied to the value text (e.g. `"danger"` for a breached metric). */
17
+ /**
18
+ * Status of the metric (e.g. `"danger"` for a breached one), shown as a dot
19
+ * before the label. The figure itself stays in the foreground colour.
20
+ */
15
21
  tone?: ToneVariant;
16
22
  /** When set, the cell renders as a button — turns a metric into a nav entry point. */
17
23
  onClick?: () => void;
@@ -37,7 +43,8 @@ export interface KpiGridProps {
37
43
  * metrics in a widget (replaces the single-metric KPICard). Cells flow as
38
44
  * columns (1–6) and can be made clickable to drill into a detail view. Use
39
45
  * `boxed` + `header` for a labelled metric block; unboxed (default) renders the
40
- * lighter `border-y` strip.
46
+ * lighter `border-y` strip. A cell's `tone` shows as a status dot next to its
47
+ * label; figures stay in the foreground colour.
41
48
  */
42
49
  export declare function KpiGrid({ cells, boxed, header }: KpiGridProps): import("react").JSX.Element;
43
50
  //# sourceMappingURL=KpiGrid.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"KpiGrid.d.ts","sourceRoot":"","sources":["../../src/components/KpiGrid.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AACtC,OAAO,EAAa,KAAK,WAAW,EAAE,MAAM,sBAAsB,CAAA;AASlE,MAAM,WAAW,OAAO;IACtB,qCAAqC;IACrC,KAAK,EAAE,SAAS,CAAA;IAChB,mEAAmE;IACnE,KAAK,EAAE,SAAS,CAAA;IAChB,2DAA2D;IAC3D,QAAQ,CAAC,EAAE,SAAS,CAAA;IACpB,wEAAwE;IACxE,KAAK,CAAC,EAAE,SAAS,CAAA;IACjB,oFAAoF;IACpF,cAAc,CAAC,EAAE,IAAI,GAAG,MAAM,GAAG,MAAM,CAAA;IACvC,8EAA8E;IAC9E,IAAI,CAAC,EAAE,WAAW,CAAA;IAClB,sFAAsF;IACtF,OAAO,CAAC,EAAE,MAAM,IAAI,CAAA;IACpB,mFAAmF;IACnF,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAWD,MAAM,WAAW,aAAa;IAC5B,2DAA2D;IAC3D,KAAK,EAAE,SAAS,CAAA;IAChB,sDAAsD;IACtD,KAAK,CAAC,EAAE,SAAS,CAAA;CAClB;AAED,MAAM,WAAW,YAAY;IAC3B,0DAA0D;IAC1D,KAAK,EAAE,OAAO,EAAE,CAAA;IAChB,0FAA0F;IAC1F,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,uDAAuD;IACvD,MAAM,CAAC,EAAE,aAAa,CAAA;CACvB;AAED;;;;;;GAMG;AACH,wBAAgB,OAAO,CAAC,EAAE,KAAK,EAAE,KAAa,EAAE,MAAM,EAAE,EAAE,YAAY,+BAwErE"}
1
+ {"version":3,"file":"KpiGrid.d.ts","sourceRoot":"","sources":["../../src/components/KpiGrid.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAEtC,OAAO,EAAY,KAAK,WAAW,EAAE,MAAM,sBAAsB,CAAA;AAYjE,MAAM,WAAW,OAAO;IACtB,qCAAqC;IACrC,KAAK,EAAE,SAAS,CAAA;IAChB,mEAAmE;IACnE,KAAK,EAAE,SAAS,CAAA;IAChB,2DAA2D;IAC3D,QAAQ,CAAC,EAAE,SAAS,CAAA;IACpB,wEAAwE;IACxE,KAAK,CAAC,EAAE,SAAS,CAAA;IACjB;;;OAGG;IACH,cAAc,CAAC,EAAE,IAAI,GAAG,MAAM,GAAG,MAAM,CAAA;IACvC;;;OAGG;IACH,IAAI,CAAC,EAAE,WAAW,CAAA;IAClB,sFAAsF;IACtF,OAAO,CAAC,EAAE,MAAM,IAAI,CAAA;IACpB,mFAAmF;IACnF,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAWD,MAAM,WAAW,aAAa;IAC5B,2DAA2D;IAC3D,KAAK,EAAE,SAAS,CAAA;IAChB,sDAAsD;IACtD,KAAK,CAAC,EAAE,SAAS,CAAA;CAClB;AAED,MAAM,WAAW,YAAY;IAC3B,0DAA0D;IAC1D,KAAK,EAAE,OAAO,EAAE,CAAA;IAChB,0FAA0F;IAC1F,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,uDAAuD;IACvD,MAAM,CAAC,EAAE,aAAa,CAAA;CACvB;AAsDD;;;;;;;GAOG;AACH,wBAAgB,OAAO,CAAC,EAAE,KAAK,EAAE,KAAa,EAAE,MAAM,EAAE,EAAE,YAAY,+BAwCrE"}
@@ -1,10 +1,14 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
- import { TONE_TEXT } from "../lib/tone-utils.js";
3
- /** Trend direction → tone for the small trend line under a value. */
2
+ import { FOCUS_RING_INSET } from "../lib/focus-ring.js";
3
+ import { TONE_DOT } from "../lib/tone-utils.js";
4
+ /**
5
+ * Trend direction → tone. The tone shows ONLY as a dot before the trend line;
6
+ * the figures stay in the foreground (no coloured digits, CI §3.3).
7
+ */
4
8
  const TREND_TONE = {
5
- up: "text-danger",
6
- down: "text-success",
7
- flat: "text-muted-foreground",
9
+ up: "danger",
10
+ down: "success",
11
+ flat: "neutral",
8
12
  };
9
13
  const COL_CLASS = {
10
14
  1: "grid-cols-1",
@@ -14,22 +18,40 @@ const COL_CLASS = {
14
18
  5: "grid-cols-5",
15
19
  6: "grid-cols-6",
16
20
  };
21
+ /** A small status dot; decorative — the label next to it carries the meaning. */
22
+ function ToneDot({ tone, size }) {
23
+ return _jsx("span", { "aria-hidden": "true", className: `${size} shrink-0 rounded-full ${TONE_DOT[tone]}` });
24
+ }
25
+ /**
26
+ * The trend line under a value: a tone dot plus the trend text in the
27
+ * foreground — never in a tone colour. Without a tone it stays quiet text.
28
+ */
29
+ function KpiTrend({ trend, direction, }) {
30
+ const tone = direction ? TREND_TONE[direction] : "neutral";
31
+ const textClass = tone === "neutral" ? "text-muted-foreground" : "text-foreground";
32
+ return (_jsxs("div", { className: `mt-1.5 inline-flex items-center gap-1.5 text-xs ${textClass}`, children: [tone !== "neutral" && _jsx(ToneDot, { tone: tone, size: "size-1.5" }), trend] }));
33
+ }
34
+ /** One cell's label row, figure and optional trend — status as dots, never as coloured digits. */
35
+ function KpiCellContent({ cell }) {
36
+ return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "text-muted-foreground flex items-center justify-between gap-2 text-xs font-medium", children: [_jsxs("span", { className: "inline-flex min-w-0 items-center gap-1.5", children: [cell.tone && cell.tone !== "neutral" && _jsx(ToneDot, { tone: cell.tone, size: "size-2" }), _jsx("span", { children: cell.label })] }), cell.onClick && (_jsx("span", { "aria-hidden": "true", className: "text-sm leading-none", children: "\u203A" }))] }), _jsxs("div", { className: "text-foreground mt-1.5 text-2xl leading-none font-bold tracking-tight tabular-nums", children: [cell.value, cell.fraction && (_jsx("span", { className: "text-muted-foreground ml-0.5 text-sm font-normal", children: cell.fraction }))] }), cell.trend && _jsx(KpiTrend, { trend: cell.trend, direction: cell.trendDirection })] }));
37
+ }
17
38
  /**
18
39
  * Bordered strip of KPI cells — the canonical way to show a row of headline
19
40
  * metrics in a widget (replaces the single-metric KPICard). Cells flow as
20
41
  * columns (1–6) and can be made clickable to drill into a detail view. Use
21
42
  * `boxed` + `header` for a labelled metric block; unboxed (default) renders the
22
- * lighter `border-y` strip.
43
+ * lighter `border-y` strip. A cell's `tone` shows as a status dot next to its
44
+ * label; figures stay in the foreground colour.
23
45
  */
24
46
  export function KpiGrid({ cells, boxed = false, header }) {
25
47
  const cols = Math.min(Math.max(cells.length, 1), 6);
26
48
  const colClass = COL_CLASS[cols] ?? "grid-cols-4";
27
49
  const wrapperClass = boxed ? "border-border overflow-hidden rounded-lg border" : "";
28
50
  const stripClass = boxed ? "" : "border-border border-y";
29
- return (_jsxs("div", { className: wrapperClass, children: [header && (_jsxs("div", { className: "border-border bg-muted text-muted-foreground flex items-center gap-2 border-b px-4 py-2 text-[11px] font-semibold tracking-wide uppercase", children: [_jsx("span", { children: header.label }), header.badge && (_jsx("span", { className: "border-border bg-card text-muted-foreground rounded border px-1.5 py-0 text-[10px] font-medium normal-case", children: header.badge }))] })), _jsx("div", { className: `grid ${colClass} ${stripClass}`, children: cells.map((cell, idx) => {
51
+ return (_jsxs("div", { className: wrapperClass, children: [header && (_jsxs("div", { className: "border-border bg-muted text-muted-foreground flex items-center gap-2 border-b px-4 py-2 text-[11px] font-semibold tracking-wide uppercase", children: [_jsx("span", { children: header.label }), header.badge && (_jsx("span", { className: "border-border bg-card text-muted-foreground rounded-sm border px-1.5 py-0 text-[10px] font-medium normal-case", children: header.badge }))] })), _jsx("div", { className: `grid ${colClass} ${stripClass}`, children: cells.map((cell, idx) => {
30
52
  const borderClass = idx < cells.length - 1 ? "border-border border-r" : "";
31
- const content = (_jsxs(_Fragment, { children: [_jsxs("div", { className: "text-muted-foreground flex items-center justify-between gap-2 text-xs font-medium", children: [_jsx("span", { children: cell.label }), cell.onClick && (_jsx("span", { "aria-hidden": "true", className: "text-sm leading-none", children: "\u203A" }))] }), _jsxs("div", { className: `mt-1.5 text-2xl leading-none font-bold tracking-tight tabular-nums ${(cell.tone && TONE_TEXT[cell.tone]) || "text-foreground"}`, children: [cell.value, cell.fraction && (_jsx("span", { className: "text-muted-foreground ml-0.5 text-sm font-normal", children: cell.fraction }))] }), cell.trend && (_jsx("div", { className: `mt-1.5 text-xs ${cell.trendDirection ? TREND_TONE[cell.trendDirection] : "text-muted-foreground"}`, children: cell.trend }))] }));
32
- return cell.onClick ? (_jsx("button", { type: "button", onClick: cell.onClick, "aria-label": cell.ariaLabel, className: `hover:bg-muted focus-visible:ring-ring cursor-pointer px-5 py-4 text-left transition-colors outline-none focus-visible:ring-2 focus-visible:ring-inset ${borderClass}`, children: content }, idx)) : (_jsx("div", { className: `px-5 py-4 ${borderClass}`, children: content }, idx));
53
+ const content = _jsx(KpiCellContent, { cell: cell });
54
+ return cell.onClick ? (_jsx("button", { type: "button", onClick: cell.onClick, "aria-label": cell.ariaLabel, className: `hover:bg-muted cursor-pointer px-5 py-4 text-left transition-colors ${FOCUS_RING_INSET} ${borderClass}`, children: content }, idx)) : (_jsx("div", { className: `px-5 py-4 ${borderClass}`, children: content }, idx));
33
55
  }) })] }));
34
56
  }
35
57
  //# sourceMappingURL=KpiGrid.js.map