@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
@@ -4,7 +4,10 @@ import * as React from "react"
4
4
  import { XIcon } from "lucide-react"
5
5
  import { Dialog as DialogPrimitive } from "radix-ui"
6
6
 
7
+ import { FOCUS_RING } from "../lib/focus-ring.js"
8
+ import { useToolkitMessages } from "../locale/locale-provider.js"
7
9
  import { cn } from "../lib/utils.js"
10
+ import { usePortalContainer } from "../theme/portal-container.js"
8
11
  import { Button } from "./button.js"
9
12
 
10
13
  function Dialog({ ...props }: React.ComponentProps<typeof DialogPrimitive.Root>) {
@@ -15,8 +18,19 @@ function DialogTrigger({ ...props }: React.ComponentProps<typeof DialogPrimitive
15
18
  return <DialogPrimitive.Trigger data-slot="dialog-trigger" {...props} />
16
19
  }
17
20
 
18
- function DialogPortal({ ...props }: React.ComponentProps<typeof DialogPrimitive.Portal>) {
19
- return <DialogPrimitive.Portal data-slot="dialog-portal" {...props} />
21
+ function DialogPortal({
22
+ container,
23
+ ...props
24
+ }: React.ComponentProps<typeof DialogPrimitive.Portal>) {
25
+ // Default to the nearest ThemeProvider scope (theme + mode), not <body>.
26
+ const portalContainer = usePortalContainer()
27
+ return (
28
+ <DialogPrimitive.Portal
29
+ data-slot="dialog-portal"
30
+ container={container ?? portalContainer ?? undefined}
31
+ {...props}
32
+ />
33
+ )
20
34
  }
21
35
 
22
36
  function DialogClose({ ...props }: React.ComponentProps<typeof DialogPrimitive.Close>) {
@@ -31,7 +45,7 @@ function DialogOverlay({
31
45
  <DialogPrimitive.Overlay
32
46
  data-slot="dialog-overlay"
33
47
  className={cn(
34
- "data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:animate-in data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50",
48
+ "data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:animate-in data-[state=open]:fade-in-0 bg-scrim fixed inset-0 z-50 backdrop-blur-sm",
35
49
  className,
36
50
  )}
37
51
  {...props}
@@ -43,17 +57,21 @@ function DialogContent({
43
57
  className,
44
58
  children,
45
59
  showCloseButton = true,
60
+ closeLabel,
46
61
  ...props
47
62
  }: React.ComponentProps<typeof DialogPrimitive.Content> & {
48
63
  showCloseButton?: boolean
64
+ /** Accessible name of the close button. Default: the locale's "Close". */
65
+ closeLabel?: string
49
66
  }) {
67
+ const messages = useToolkitMessages()
50
68
  return (
51
69
  <DialogPortal data-slot="dialog-portal">
52
70
  <DialogOverlay />
53
71
  <DialogPrimitive.Content
54
72
  data-slot="dialog-content"
55
73
  className={cn(
56
- "bg-background data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 outline-none sm:max-w-lg",
74
+ "bg-popover text-popover-foreground dark:border-input data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95 shadow-3 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-md border p-6 outline-hidden duration-(--motion-base) sm:max-w-lg",
57
75
  className,
58
76
  )}
59
77
  {...props}
@@ -62,10 +80,13 @@ function DialogContent({
62
80
  {showCloseButton && (
63
81
  <DialogPrimitive.Close
64
82
  data-slot="dialog-close"
65
- className="ring-offset-background focus:ring-ring data-[state=open]:bg-accent data-[state=open]:text-muted-foreground absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4"
83
+ className={cn(
84
+ "text-muted-foreground hover:text-foreground absolute top-4 right-4 rounded-sm transition-colors disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
85
+ FOCUS_RING,
86
+ )}
66
87
  >
67
88
  <XIcon />
68
- <span className="sr-only">Close</span>
89
+ <span className="sr-only">{closeLabel ?? messages.common.close}</span>
69
90
  </DialogPrimitive.Close>
70
91
  )}
71
92
  </DialogPrimitive.Content>
@@ -86,11 +107,15 @@ function DialogHeader({ className, ...props }: React.ComponentProps<"div">) {
86
107
  function DialogFooter({
87
108
  className,
88
109
  showCloseButton = false,
110
+ closeLabel,
89
111
  children,
90
112
  ...props
91
113
  }: React.ComponentProps<"div"> & {
92
114
  showCloseButton?: boolean
115
+ /** Label of the footer close button. Default: the locale's "Close". */
116
+ closeLabel?: string
93
117
  }) {
118
+ const messages = useToolkitMessages()
94
119
  return (
95
120
  <div
96
121
  data-slot="dialog-footer"
@@ -100,7 +125,7 @@ function DialogFooter({
100
125
  {children}
101
126
  {showCloseButton && (
102
127
  <DialogPrimitive.Close asChild>
103
- <Button variant="outline">Close</Button>
128
+ <Button variant="outline">{closeLabel ?? messages.common.close}</Button>
104
129
  </DialogPrimitive.Close>
105
130
  )}
106
131
  </div>
@@ -8,9 +8,11 @@ function Input({ className, type, ...props }: React.ComponentProps<"input">) {
8
8
  type={type}
9
9
  data-slot="input"
10
10
  className={cn(
11
- "border-input selection:bg-primary selection:text-primary-foreground file:text-foreground placeholder:text-muted-foreground dark:bg-input/30 h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
12
- "focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",
13
- "aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40",
11
+ // Input border in --input (3:1 on its surface); focus turns border and a
12
+ // 1px ring --focus: a 2px opaque blue edge (CI §8).
13
+ "border-input selection:bg-primary selection:text-primary-foreground file:text-foreground placeholder:text-muted-foreground bg-background h-9 w-full min-w-0 rounded-md border px-3 py-1 text-base outline-hidden transition-[color,box-shadow,border-color] file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
14
+ "focus-visible:border-focus focus-visible:ring-focus focus-visible:ring-1",
15
+ "aria-invalid:border-danger aria-invalid:ring-danger aria-invalid:ring-1",
14
16
  className,
15
17
  )}
16
18
  {...props}
@@ -16,7 +16,7 @@ function ScrollArea({
16
16
  >
17
17
  <ScrollAreaPrimitive.Viewport
18
18
  data-slot="scroll-area-viewport"
19
- className="focus-visible:ring-ring/50 size-full rounded-[inherit] transition-[color,box-shadow] outline-none focus-visible:ring-[3px] focus-visible:outline-1"
19
+ className="focus-visible:ring-focus size-full rounded-[inherit] outline-hidden transition-[color,box-shadow] focus-visible:ring-2 focus-visible:ring-inset"
20
20
  >
21
21
  {children}
22
22
  </ScrollAreaPrimitive.Viewport>
@@ -3,6 +3,7 @@ import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from "lucide-react"
3
3
  import { Select as SelectPrimitive } from "radix-ui"
4
4
 
5
5
  import { cn } from "../lib/utils.js"
6
+ import { usePortalContainer } from "../theme/portal-container.js"
6
7
 
7
8
  function Select({ ...props }: React.ComponentProps<typeof SelectPrimitive.Root>) {
8
9
  return <SelectPrimitive.Root data-slot="select" {...props} />
@@ -29,7 +30,7 @@ function SelectTrigger({
29
30
  data-slot="select-trigger"
30
31
  data-size={size}
31
32
  className={cn(
32
- "border-input focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 data-[placeholder]:text-muted-foreground dark:bg-input/30 dark:hover:bg-input/50 dark:aria-invalid:ring-destructive/40 [&_svg:not([class*='text-'])]:text-muted-foreground flex w-fit items-center justify-between gap-2 rounded-md border bg-transparent px-3 py-2 text-sm whitespace-nowrap shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 data-[size=default]:h-9 data-[size=sm]:h-8 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
33
+ "border-input focus-visible:border-focus focus-visible:ring-focus aria-invalid:border-danger aria-invalid:ring-danger data-[placeholder]:text-muted-foreground [&_svg:not([class*='text-'])]:text-muted-foreground bg-background hover:border-focus flex w-fit items-center justify-between gap-2 rounded-md border px-3 py-2 text-sm whitespace-nowrap outline-hidden transition-[color,box-shadow,border-color] focus-visible:ring-1 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:ring-1 data-[size=default]:h-9 data-[size=sm]:h-8 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
33
34
  className,
34
35
  )}
35
36
  {...props}
@@ -49,12 +50,14 @@ function SelectContent({
49
50
  align = "center",
50
51
  ...props
51
52
  }: React.ComponentProps<typeof SelectPrimitive.Content>) {
53
+ // Portal into the nearest ThemeProvider scope (theme + mode), not <body>.
54
+ const portalContainer = usePortalContainer()
52
55
  return (
53
- <SelectPrimitive.Portal>
56
+ <SelectPrimitive.Portal container={portalContainer ?? undefined}>
54
57
  <SelectPrimitive.Content
55
58
  data-slot="select-content"
56
59
  className={cn(
57
- "bg-popover text-popover-foreground data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95 relative z-50 max-h-(--radix-select-content-available-height) min-w-[8rem] origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border shadow-md",
60
+ "bg-popover text-popover-foreground dark:border-input data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95 shadow-3 relative z-50 max-h-(--radix-select-content-available-height) min-w-[8rem] origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border",
58
61
  position === "popper" &&
59
62
  "data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
60
63
  className,
@@ -4,7 +4,10 @@ import * as React from "react"
4
4
  import { XIcon } from "lucide-react"
5
5
  import { Dialog as SheetPrimitive } from "radix-ui"
6
6
 
7
+ import { FOCUS_RING } from "../lib/focus-ring.js"
8
+ import { useToolkitMessages } from "../locale/locale-provider.js"
7
9
  import { cn } from "../lib/utils.js"
10
+ import { usePortalContainer } from "../theme/portal-container.js"
8
11
 
9
12
  function Sheet({ ...props }: React.ComponentProps<typeof SheetPrimitive.Root>) {
10
13
  return <SheetPrimitive.Root data-slot="sheet" {...props} />
@@ -18,8 +21,16 @@ function SheetClose({ ...props }: React.ComponentProps<typeof SheetPrimitive.Clo
18
21
  return <SheetPrimitive.Close data-slot="sheet-close" {...props} />
19
22
  }
20
23
 
21
- function SheetPortal({ ...props }: React.ComponentProps<typeof SheetPrimitive.Portal>) {
22
- return <SheetPrimitive.Portal data-slot="sheet-portal" {...props} />
24
+ function SheetPortal({ container, ...props }: React.ComponentProps<typeof SheetPrimitive.Portal>) {
25
+ // Default to the nearest ThemeProvider scope (theme + mode), not <body>.
26
+ const portalContainer = usePortalContainer()
27
+ return (
28
+ <SheetPrimitive.Portal
29
+ data-slot="sheet-portal"
30
+ container={container ?? portalContainer ?? undefined}
31
+ {...props}
32
+ />
33
+ )
23
34
  }
24
35
 
25
36
  function SheetOverlay({
@@ -30,7 +41,7 @@ function SheetOverlay({
30
41
  <SheetPrimitive.Overlay
31
42
  data-slot="sheet-overlay"
32
43
  className={cn(
33
- "data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:animate-in data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50",
44
+ "data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:animate-in data-[state=open]:fade-in-0 bg-scrim fixed inset-0 z-50 backdrop-blur-sm",
34
45
  className,
35
46
  )}
36
47
  {...props}
@@ -43,18 +54,22 @@ function SheetContent({
43
54
  children,
44
55
  side = "right",
45
56
  showCloseButton = true,
57
+ closeLabel,
46
58
  ...props
47
59
  }: React.ComponentProps<typeof SheetPrimitive.Content> & {
48
60
  side?: "top" | "right" | "bottom" | "left"
49
61
  showCloseButton?: boolean
62
+ /** Accessible name of the close button. Default: the locale's "Close". */
63
+ closeLabel?: string
50
64
  }) {
65
+ const messages = useToolkitMessages()
51
66
  return (
52
67
  <SheetPortal>
53
68
  <SheetOverlay />
54
69
  <SheetPrimitive.Content
55
70
  data-slot="sheet-content"
56
71
  className={cn(
57
- "bg-background data-[state=closed]:animate-out data-[state=open]:animate-in fixed z-50 flex flex-col gap-4 shadow-lg transition ease-in-out data-[state=closed]:duration-300 data-[state=open]:duration-500",
72
+ "bg-popover text-popover-foreground dark:border-input data-[state=closed]:animate-out data-[state=open]:animate-in shadow-3 fixed z-50 flex flex-col gap-4 transition data-[state=closed]:duration-(--motion-base) data-[state=open]:duration-(--motion-slow)",
58
73
  side === "right" &&
59
74
  "data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right inset-y-0 right-0 h-full w-3/4 border-l sm:max-w-sm",
60
75
  side === "left" &&
@@ -69,9 +84,14 @@ function SheetContent({
69
84
  >
70
85
  {children}
71
86
  {showCloseButton && (
72
- <SheetPrimitive.Close className="ring-offset-background focus:ring-ring data-[state=open]:bg-secondary absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none">
87
+ <SheetPrimitive.Close
88
+ className={cn(
89
+ "text-muted-foreground hover:text-foreground absolute top-4 right-4 rounded-sm transition-colors disabled:pointer-events-none",
90
+ FOCUS_RING,
91
+ )}
92
+ >
73
93
  <XIcon className="size-4" />
74
- <span className="sr-only">Close</span>
94
+ <span className="sr-only">{closeLabel ?? messages.common.close}</span>
75
95
  </SheetPrimitive.Close>
76
96
  )}
77
97
  </SheetPrimitive.Content>
@@ -4,7 +4,7 @@ function Skeleton({ className, ...props }: React.ComponentProps<"div">) {
4
4
  return (
5
5
  <div
6
6
  data-slot="skeleton"
7
- className={cn("bg-muted animate-pulse rounded-md", className)}
7
+ className={cn("bg-border animate-pulse rounded-md", className)}
8
8
  {...props}
9
9
  />
10
10
  )
@@ -1,6 +1,7 @@
1
1
  import * as React from "react"
2
2
  import { Switch as SwitchPrimitive } from "radix-ui"
3
3
 
4
+ import { FOCUS_RING } from "../lib/focus-ring.js"
4
5
  import { cn } from "../lib/utils.js"
5
6
 
6
7
  function Switch({ className, ...props }: React.ComponentProps<typeof SwitchPrimitive.Root>) {
@@ -8,14 +9,15 @@ function Switch({ className, ...props }: React.ComponentProps<typeof SwitchPrimi
8
9
  <SwitchPrimitive.Root
9
10
  data-slot="switch"
10
11
  className={cn(
11
- "peer focus-visible:ring-ring focus-visible:ring-offset-background data-[state=checked]:bg-primary data-[state=unchecked]:bg-input inline-flex h-5 w-9 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent shadow-xs transition-colors focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50",
12
+ "peer data-[state=checked]:bg-primary data-[state=unchecked]:bg-input inline-flex h-5 w-9 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors disabled:cursor-not-allowed disabled:opacity-50",
13
+ FOCUS_RING,
12
14
  className,
13
15
  )}
14
16
  {...props}
15
17
  >
16
18
  <SwitchPrimitive.Thumb
17
19
  data-slot="switch-thumb"
18
- className="bg-background pointer-events-none block h-4 w-4 rounded-full shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-4 data-[state=unchecked]:translate-x-0"
20
+ className="bg-primary-foreground shadow-1 pointer-events-none block h-4 w-4 rounded-full ring-0 transition-transform data-[state=checked]:translate-x-4 data-[state=unchecked]:translate-x-0"
19
21
  />
20
22
  </SwitchPrimitive.Root>
21
23
  )
@@ -2,6 +2,7 @@ import * as React from "react"
2
2
  import { cva, type VariantProps } from "class-variance-authority"
3
3
  import { Tabs as TabsPrimitive } from "radix-ui"
4
4
 
5
+ import { FOCUS_RING, FOCUS_RING_INSET } from "../lib/focus-ring.js"
5
6
  import { cn } from "../lib/utils.js"
6
7
 
7
8
  function Tabs({
@@ -21,7 +22,7 @@ function Tabs({
21
22
  }
22
23
 
23
24
  const tabsListVariants = cva(
24
- "group/tabs-list inline-flex w-fit items-center justify-center rounded-lg p-[3px] text-muted-foreground group-data-[orientation=horizontal]/tabs:h-9 group-data-[orientation=vertical]/tabs:h-fit group-data-[orientation=vertical]/tabs:flex-col data-[variant=line]:rounded-none",
25
+ "group/tabs-list inline-flex w-fit items-center justify-center rounded-md p-[3px] text-muted-foreground group-data-[orientation=horizontal]/tabs:h-9 group-data-[orientation=vertical]/tabs:h-fit group-data-[orientation=vertical]/tabs:flex-col data-[variant=line]:rounded-none",
25
26
  {
26
27
  variants: {
27
28
  variant: {
@@ -55,10 +56,11 @@ function TabsTrigger({ className, ...props }: React.ComponentProps<typeof TabsPr
55
56
  <TabsPrimitive.Trigger
56
57
  data-slot="tabs-trigger"
57
58
  className={cn(
58
- "text-foreground/60 hover:text-foreground focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:outline-ring dark:text-muted-foreground dark:hover:text-foreground relative inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center gap-1.5 rounded-md border border-transparent px-2 py-1 text-sm font-medium whitespace-nowrap transition-all group-data-[orientation=vertical]/tabs:w-full group-data-[orientation=vertical]/tabs:justify-start focus-visible:ring-[3px] focus-visible:outline-1 disabled:pointer-events-none disabled:opacity-50 group-data-[variant=default]/tabs-list:data-[state=active]:shadow-sm group-data-[variant=line]/tabs-list:data-[state=active]:shadow-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
59
- "group-data-[variant=line]/tabs-list:bg-transparent group-data-[variant=line]/tabs-list:data-[state=active]:bg-transparent dark:group-data-[variant=line]/tabs-list:data-[state=active]:border-transparent dark:group-data-[variant=line]/tabs-list:data-[state=active]:bg-transparent",
60
- "data-[state=active]:bg-background data-[state=active]:text-foreground dark:data-[state=active]:border-input dark:data-[state=active]:bg-input/30 dark:data-[state=active]:text-foreground",
61
- "after:bg-foreground after:absolute after:opacity-0 after:transition-opacity group-data-[orientation=horizontal]/tabs:after:inset-x-0 group-data-[orientation=horizontal]/tabs:after:bottom-[-5px] group-data-[orientation=horizontal]/tabs:after:h-0.5 group-data-[orientation=vertical]/tabs:after:inset-y-0 group-data-[orientation=vertical]/tabs:after:-right-1 group-data-[orientation=vertical]/tabs:after:w-0.5 group-data-[variant=line]/tabs-list:data-[state=active]:after:opacity-100",
59
+ "text-muted-foreground hover:text-foreground group-data-[variant=default]/tabs-list:data-[state=active]:shadow-1 relative inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center gap-1.5 rounded-sm border border-transparent px-2 py-1 text-sm font-medium whitespace-nowrap transition-all group-data-[orientation=vertical]/tabs:w-full group-data-[orientation=vertical]/tabs:justify-start disabled:pointer-events-none disabled:opacity-50 group-data-[variant=line]/tabs-list:data-[state=active]:shadow-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
60
+ FOCUS_RING,
61
+ "group-data-[variant=line]/tabs-list:bg-transparent group-data-[variant=line]/tabs-list:data-[state=active]:border-transparent group-data-[variant=line]/tabs-list:data-[state=active]:bg-transparent",
62
+ "data-[state=active]:border-border data-[state=active]:bg-background data-[state=active]:text-foreground",
63
+ "after:bg-primary after:absolute after:opacity-0 after:transition-opacity group-data-[orientation=horizontal]/tabs:after:inset-x-0 group-data-[orientation=horizontal]/tabs:after:bottom-[-5px] group-data-[orientation=horizontal]/tabs:after:h-0.5 group-data-[orientation=vertical]/tabs:after:inset-y-0 group-data-[orientation=vertical]/tabs:after:-right-1 group-data-[orientation=vertical]/tabs:after:w-0.5 group-data-[variant=line]/tabs-list:data-[state=active]:after:opacity-100",
62
64
  className,
63
65
  )}
64
66
  {...props}
@@ -67,10 +69,11 @@ function TabsTrigger({ className, ...props }: React.ComponentProps<typeof TabsPr
67
69
  }
68
70
 
69
71
  function TabsContent({ className, ...props }: React.ComponentProps<typeof TabsPrimitive.Content>) {
72
+ // Radix makes the panel a tab stop (tabIndex 0), so it needs a visible focus.
70
73
  return (
71
74
  <TabsPrimitive.Content
72
75
  data-slot="tabs-content"
73
- className={cn("flex-1 outline-none", className)}
76
+ className={cn("flex-1", FOCUS_RING_INSET, className)}
74
77
  {...props}
75
78
  />
76
79
  )
@@ -1,8 +1,11 @@
1
1
  import type { ReactElement } from "react"
2
+ import { Inbox } from "lucide-react"
2
3
  import {
3
4
  Button,
4
5
  CountPill,
5
6
  DrillButton,
7
+ EmptyState,
8
+ ErrorState,
6
9
  FilterBar,
7
10
  GridItem,
8
11
  GridLayout,
@@ -14,6 +17,7 @@ import {
14
17
  SectionHeading,
15
18
  ThemeProvider,
16
19
  themePresets,
20
+ ToastProvider,
17
21
  useLocale,
18
22
  WidgetHeader,
19
23
  } from "./index.js"
@@ -185,6 +189,46 @@ export const RENDER_CASES: RenderCase[] = [
185
189
  render: () => <ListFooter shown={8} total={42} hasMore onLoadMore={noop} noun="tasks" />,
186
190
  },
187
191
 
192
+ // ── Feedback and states ──
193
+ {
194
+ // The provider renders its children plus the (empty) live region.
195
+ name: "ToastProvider",
196
+ marker: "RC:ToastProvider",
197
+ render: () => (
198
+ <ToastProvider>
199
+ <div>RC:ToastProvider</div>
200
+ </ToastProvider>
201
+ ),
202
+ },
203
+ {
204
+ name: "EmptyState",
205
+ marker: "RC:EmptyState",
206
+ render: () => (
207
+ <EmptyState
208
+ icon={<Inbox />}
209
+ title="RC:EmptyState"
210
+ description="No orders in this period."
211
+ action={
212
+ <Button size="sm" onClick={noop}>
213
+ New order
214
+ </Button>
215
+ }
216
+ />
217
+ ),
218
+ },
219
+ {
220
+ name: "ErrorState",
221
+ marker: "RC:ErrorState",
222
+ render: () => (
223
+ <ErrorState
224
+ title="RC:ErrorState"
225
+ description="Check the connection and try again."
226
+ details="HTTP 503 Service Unavailable"
227
+ onRetry={noop}
228
+ />
229
+ ),
230
+ },
231
+
188
232
  // ── Host seam / providers ──
189
233
  {
190
234
  name: "HostBridgeProvider",
@@ -212,7 +256,7 @@ export const RENDER_CASES: RenderCase[] = [
212
256
  name: "ThemeProvider",
213
257
  marker: "RC:ThemeProvider",
214
258
  render: () => (
215
- <ThemeProvider theme={themePresets.miragon} mode="dark">
259
+ <ThemeProvider theme={themePresets.emerald} mode="dark">
216
260
  <div>RC:ThemeProvider</div>
217
261
  </ThemeProvider>
218
262
  ),
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Read SSR markup (`renderToStaticMarkup`) the way a user — sighted or with a
3
+ * screen reader — meets it: its text nodes plus the attributes that carry
4
+ * text (accessible names, tooltips, placeholders). DATA/TEST support module
5
+ * like `render-cases.tsx`: the a11y and i18n render suites scan through it,
6
+ * so it stays pure and dependency-free. React's SSR output is well-formed
7
+ * (attributes always double-quoted), which is what keeps these regexes honest.
8
+ */
9
+
10
+ /** Attributes whose value a user reads or hears. */
11
+ export const TEXT_ATTRIBUTES = [
12
+ "aria-label",
13
+ "aria-description",
14
+ "aria-roledescription",
15
+ "aria-valuetext",
16
+ "title",
17
+ "placeholder",
18
+ "alt",
19
+ ] as const
20
+
21
+ const NAMED_ENTITIES: Record<string, string> = {
22
+ amp: "&",
23
+ lt: "<",
24
+ gt: ">",
25
+ quot: '"',
26
+ apos: "'",
27
+ nbsp: " ",
28
+ }
29
+
30
+ /** Decode the HTML entities React's SSR emits (named, decimal and hex). */
31
+ export function decodeEntities(text: string): string {
32
+ return text.replace(/&(#x[0-9a-f]+|#\d+|[a-z]+);/gi, (whole, ref: string) => {
33
+ if (ref.startsWith("#x") || ref.startsWith("#X")) {
34
+ return String.fromCodePoint(Number.parseInt(ref.slice(2), 16))
35
+ }
36
+ if (ref.startsWith("#")) return String.fromCodePoint(Number.parseInt(ref.slice(1), 10))
37
+ return NAMED_ENTITIES[ref.toLowerCase()] ?? whole
38
+ })
39
+ }
40
+
41
+ /** One piece of user-facing text in the markup. */
42
+ export interface MarkupText {
43
+ /** `"text"` for a text node, else the attribute name. */
44
+ source: string
45
+ value: string
46
+ }
47
+
48
+ /** Every non-blank text node and text-bearing attribute value, decoded. */
49
+ export function userFacingTexts(html: string): MarkupText[] {
50
+ const out: MarkupText[] = []
51
+ const withoutRawText = html.replace(/<(script|style)\b[^>]*>[\s\S]*?<\/\1>/gi, "")
52
+ for (const chunk of withoutRawText.split(/<[^>]*>/)) {
53
+ const value = decodeEntities(chunk).trim()
54
+ if (value) out.push({ source: "text", value })
55
+ }
56
+ for (const tag of withoutRawText.match(/<[a-z][^>]*>/gi) ?? []) {
57
+ for (const name of TEXT_ATTRIBUTES) {
58
+ const value = attribute(tag, name)
59
+ if (value?.trim()) out.push({ source: name, value: value.trim() })
60
+ }
61
+ }
62
+ return out
63
+ }
64
+
65
+ /** The opening tags of every element named `tag` (e.g. `"button"`). */
66
+ export function openingTags(html: string, tag: string): string[] {
67
+ return html.match(new RegExp(`<${tag}(?=[\\s>/])[^>]*>`, "gi")) ?? []
68
+ }
69
+
70
+ /** The decoded value of `name` on one opening tag, `undefined` when absent. */
71
+ export function attribute(openingTag: string, name: string): string | undefined {
72
+ const escaped = name.replace(/[-]/g, "\\-")
73
+ const match = new RegExp(`\\s${escaped}="([^"]*)"`, "i").exec(openingTag)
74
+ return match ? decodeEntities(match[1] ?? "") : undefined
75
+ }
@@ -0,0 +1,48 @@
1
+ /* Aus Miragon/corporate-identity übernommen (miragon-brand 1.8.0, modeler-tool-design/assets/cd-tokens.generated.css). Nicht ändern, zum Aktualisieren neu aus der Skill kopieren. */
2
+ /* AUTOMATISCH GENERIERT aus brand/tokens.json. Nicht von Hand ändern, sondern die Quelle bearbeiten und `npm run tokens:build` laufen lassen. */
3
+ :root {
4
+ --cd-blau: #335DE5;
5
+ --cd-gruen: #00E676;
6
+ --cd-grau: #F9F7F7;
7
+ --cd-schwarz: #1D1D1D;
8
+ --cd-weiss: #FFFFFF;
9
+ --cd-blau-link: #2B50D4;
10
+ --cd-blau-hell: #6B8AFF;
11
+ --cd-success: #00E676;
12
+ --cd-success-soft: #DAFBEB;
13
+ --cd-success-hell: #00E676;
14
+ --cd-warning: #FFB000;
15
+ --cd-warning-soft: #FFF1D2;
16
+ --cd-warning-hell: #FFB000;
17
+ --cd-danger: #CD1839;
18
+ --cd-danger-soft: #FBEEF0;
19
+ --cd-danger-hell: #FB6D73;
20
+ --cd-info: #335DE5;
21
+ --cd-info-soft: #EEF1FD;
22
+ --cd-info-hell: #6B8AFF;
23
+ --cd-linie: #E6E2E2;
24
+ --cd-kontur: #8F8A8A;
25
+ --cd-text-leise: #6B6666;
26
+ --cd-gradient-brand: linear-gradient(120deg, #335DE5 30%, #00E676);
27
+ --cd-gradient-format: linear-gradient(135deg, #335DE5 0%, #00E676 140%);
28
+ --cd-space-1: 4px;
29
+ --cd-space-2: 8px;
30
+ --cd-space-3: 16px;
31
+ --cd-space-4: 24px;
32
+ --cd-space-5: 32px;
33
+ --cd-space-6: 48px;
34
+ --cd-space-7: 64px;
35
+ --cd-space-8: 96px;
36
+ --cd-radius-sm: 6px;
37
+ --cd-radius-md: 12px;
38
+ --cd-radius-lg: 16px;
39
+ --cd-radius-pill: 999px;
40
+ --cd-shadow-1: 0 1px 2px rgba(29, 29, 29, 0.06), 0 1px 3px rgba(29, 29, 29, 0.10);
41
+ --cd-shadow-2: 0 4px 12px rgba(29, 29, 29, 0.10);
42
+ --cd-shadow-3: 0 12px 32px rgba(29, 29, 29, 0.16);
43
+ --cd-motion-fast: 150ms;
44
+ --cd-motion-base: 250ms;
45
+ --cd-motion-slow: 400ms;
46
+ --cd-ease: cubic-bezier(0.4, 0, 0.2, 1);
47
+ --cd-ease-out: cubic-bezier(0, 0, 0.2, 1);
48
+ }
@@ -0,0 +1,107 @@
1
+ /// <reference types="node" />
2
+ import { createHash } from "node:crypto"
3
+ import { existsSync, readdirSync, readFileSync } from "node:fs"
4
+ import { homedir } from "node:os"
5
+ import { join } from "node:path"
6
+ import { describe, expect, it } from "vitest"
7
+ import { CD_TOKENS, CD_TOKENS_VERSION } from "./cd-tokens.js"
8
+
9
+ /**
10
+ * Drift guard for the vendored Miragon corporate-design tokens
11
+ * (modeler-tool-design §13): the copy must stay the skill's file, unchanged.
12
+ *
13
+ * `cd-tokens.generated.css` = ONE marker line + the upstream file byte for
14
+ * byte. Upstream is miragon-brand's `modeler-tool-design/assets/
15
+ * cd-tokens.generated.css`. When a checkout of the plugin is reachable
16
+ * (`MIRAGON_CD_TOKENS` or a Claude plugin cache under $HOME), the copy is
17
+ * compared to it directly; everywhere else (CI) the pinned hash below holds it.
18
+ *
19
+ * To re-vendor: copy the new upstream file below the marker line, update the
20
+ * version in the marker, CD_TOKENS_VERSION, UPSTREAM_SHA256 and CD_TOKENS.
21
+ */
22
+ const UPSTREAM_SHA256 = "36d76f3a330a1250d332c6862be1dc4f7564d2461e7051d9a565ba723ef2c379"
23
+ const UPSTREAM_REL = join("skills", "modeler-tool-design", "assets", "cd-tokens.generated.css")
24
+ const MARKER =
25
+ `/* Aus Miragon/corporate-identity übernommen (miragon-brand ${CD_TOKENS_VERSION}, ` +
26
+ "modeler-tool-design/assets/cd-tokens.generated.css). Nicht ändern, zum Aktualisieren neu aus der Skill kopieren. */\n"
27
+
28
+ const vendored = readFileSync(new URL("./cd-tokens.generated.css", import.meta.url), "utf8")
29
+ const upstreamPart = vendored.slice(MARKER.length)
30
+
31
+ /** A reachable upstream copy of this version, or null (CI). */
32
+ function findUpstream(): string | null {
33
+ const explicit = process.env.MIRAGON_CD_TOKENS
34
+ if (explicit) return existsSync(explicit) ? explicit : null
35
+ const home = homedir()
36
+ let entries: string[]
37
+ try {
38
+ entries = readdirSync(home).filter((name) => name.startsWith(".claude"))
39
+ } catch {
40
+ return null
41
+ }
42
+ for (const dir of entries) {
43
+ const candidate = join(
44
+ home,
45
+ dir,
46
+ "plugins",
47
+ "cache",
48
+ "miragon-agent-skills",
49
+ "miragon-brand",
50
+ CD_TOKENS_VERSION,
51
+ UPSTREAM_REL,
52
+ )
53
+ if (existsSync(candidate)) return candidate
54
+ }
55
+ return null
56
+ }
57
+
58
+ /** `--cd-name: value;` declarations of the vendored `:root`. */
59
+ function cssTokens(css: string): Record<string, string> {
60
+ const out: Record<string, string> = {}
61
+ for (const match of css.matchAll(/--cd-([a-z0-9-]+):\s*([^;]+);/g)) {
62
+ out[match[1] as string] = (match[2] as string).trim()
63
+ }
64
+ return out
65
+ }
66
+
67
+ describe("cd-tokens.generated.css (vendored, never edited)", () => {
68
+ it("starts with the generated-file marker naming the brand version", () => {
69
+ expect(vendored.startsWith(MARKER)).toBe(true)
70
+ })
71
+
72
+ it("is the upstream file byte for byte below the marker (pinned hash)", () => {
73
+ const hash = createHash("sha256").update(upstreamPart, "utf8").digest("hex")
74
+ expect(hash, "re-vendor from the skill instead of editing the copy").toBe(UPSTREAM_SHA256)
75
+ })
76
+
77
+ it("is byte-identical to the skill's file when a plugin checkout is reachable", () => {
78
+ const upstream = findUpstream()
79
+ // Off-machine (CI) the pinned hash above is the guard; say so loudly.
80
+ if (!upstream) {
81
+ console.warn(
82
+ `cd-tokens drift: no miragon-brand ${CD_TOKENS_VERSION} checkout found — relying on the pinned hash`,
83
+ )
84
+ return
85
+ }
86
+ expect(upstreamPart).toBe(readFileSync(upstream, "utf8"))
87
+ })
88
+ })
89
+
90
+ describe("CD_TOKENS (the TS mirror of :root)", () => {
91
+ const css = cssTokens(upstreamPart)
92
+
93
+ it("mirrors every --cd-* token value exactly", () => {
94
+ for (const [name, value] of Object.entries(css)) {
95
+ expect(CD_TOKENS[name as keyof typeof CD_TOKENS], `--cd-${name}`).toBe(value)
96
+ }
97
+ })
98
+
99
+ it("declares no token the CSS does not", () => {
100
+ expect(Object.keys(CD_TOKENS).sort()).toEqual(Object.keys(css).sort())
101
+ })
102
+
103
+ it("parses a non-trivial token set (the parser itself is alive)", () => {
104
+ expect(Object.keys(css).length).toBeGreaterThanOrEqual(40)
105
+ expect(css.blau).toBe("#335DE5")
106
+ })
107
+ })