@miragon/mcp-toolkit-ui 0.10.1

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 (320) hide show
  1. package/README.md +36 -0
  2. package/dist/app/adapt-data-widget.d.ts +33 -0
  3. package/dist/app/adapt-data-widget.d.ts.map +1 -0
  4. package/dist/app/adapt-data-widget.js +35 -0
  5. package/dist/app/adapt-data-widget.js.map +1 -0
  6. package/dist/app/builder/CatalogueSheet.d.ts +13 -0
  7. package/dist/app/builder/CatalogueSheet.d.ts.map +1 -0
  8. package/dist/app/builder/CatalogueSheet.js +33 -0
  9. package/dist/app/builder/CatalogueSheet.js.map +1 -0
  10. package/dist/app/builder/PipelineStrip.d.ts +22 -0
  11. package/dist/app/builder/PipelineStrip.d.ts.map +1 -0
  12. package/dist/app/builder/PipelineStrip.js +31 -0
  13. package/dist/app/builder/PipelineStrip.js.map +1 -0
  14. package/dist/app/builder/PreviewPane.d.ts +13 -0
  15. package/dist/app/builder/PreviewPane.d.ts.map +1 -0
  16. package/dist/app/builder/PreviewPane.js +21 -0
  17. package/dist/app/builder/PreviewPane.js.map +1 -0
  18. package/dist/app/builder/SaveDialog.d.ts +14 -0
  19. package/dist/app/builder/SaveDialog.d.ts.map +1 -0
  20. package/dist/app/builder/SaveDialog.js +14 -0
  21. package/dist/app/builder/SaveDialog.js.map +1 -0
  22. package/dist/app/builder/Toolbar.d.ts +17 -0
  23. package/dist/app/builder/Toolbar.d.ts.map +1 -0
  24. package/dist/app/builder/Toolbar.js +15 -0
  25. package/dist/app/builder/Toolbar.js.map +1 -0
  26. package/dist/app/builder/Workspace.d.ts +28 -0
  27. package/dist/app/builder/Workspace.d.ts.map +1 -0
  28. package/dist/app/builder/Workspace.js +117 -0
  29. package/dist/app/builder/Workspace.js.map +1 -0
  30. package/dist/app/builder/builder-model.d.ts +86 -0
  31. package/dist/app/builder/builder-model.d.ts.map +1 -0
  32. package/dist/app/builder/builder-model.js +174 -0
  33. package/dist/app/builder/builder-model.js.map +1 -0
  34. package/dist/app/builder/builder-reducer.d.ts +71 -0
  35. package/dist/app/builder/builder-reducer.d.ts.map +1 -0
  36. package/dist/app/builder/builder-reducer.js +201 -0
  37. package/dist/app/builder/builder-reducer.js.map +1 -0
  38. package/dist/app/builder/labels.d.ts +78 -0
  39. package/dist/app/builder/labels.d.ts.map +1 -0
  40. package/dist/app/builder/labels.js +71 -0
  41. package/dist/app/builder/labels.js.map +1 -0
  42. package/dist/app/builder/status-chip.d.ts +10 -0
  43. package/dist/app/builder/status-chip.d.ts.map +1 -0
  44. package/dist/app/builder/status-chip.js +16 -0
  45. package/dist/app/builder/status-chip.js.map +1 -0
  46. package/dist/app/host-bridge.d.ts +211 -0
  47. package/dist/app/host-bridge.d.ts.map +1 -0
  48. package/dist/app/host-bridge.js +247 -0
  49. package/dist/app/host-bridge.js.map +1 -0
  50. package/dist/app/index.d.ts +16 -0
  51. package/dist/app/index.d.ts.map +1 -0
  52. package/dist/app/index.js +18 -0
  53. package/dist/app/index.js.map +1 -0
  54. package/dist/app/layout-builder.d.ts +48 -0
  55. package/dist/app/layout-builder.d.ts.map +1 -0
  56. package/dist/app/layout-builder.js +418 -0
  57. package/dist/app/layout-builder.js.map +1 -0
  58. package/dist/app/mcp-app-view.d.ts +43 -0
  59. package/dist/app/mcp-app-view.d.ts.map +1 -0
  60. package/dist/app/mcp-app-view.js +169 -0
  61. package/dist/app/mcp-app-view.js.map +1 -0
  62. package/dist/app/mcp-toolkit-app.d.ts +18 -0
  63. package/dist/app/mcp-toolkit-app.d.ts.map +1 -0
  64. package/dist/app/mcp-toolkit-app.js +22 -0
  65. package/dist/app/mcp-toolkit-app.js.map +1 -0
  66. package/dist/app/use-host-actions.d.ts +57 -0
  67. package/dist/app/use-host-actions.d.ts.map +1 -0
  68. package/dist/app/use-host-actions.js +42 -0
  69. package/dist/app/use-host-actions.js.map +1 -0
  70. package/dist/app/widget-fixture.d.ts +180 -0
  71. package/dist/app/widget-fixture.d.ts.map +1 -0
  72. package/dist/app/widget-fixture.js +273 -0
  73. package/dist/app/widget-fixture.js.map +1 -0
  74. package/dist/app/widget-props-sheet.d.ts +20 -0
  75. package/dist/app/widget-props-sheet.d.ts.map +1 -0
  76. package/dist/app/widget-props-sheet.js +87 -0
  77. package/dist/app/widget-props-sheet.js.map +1 -0
  78. package/dist/app/widget-renderer.d.ts +87 -0
  79. package/dist/app/widget-renderer.d.ts.map +1 -0
  80. package/dist/app/widget-renderer.js +105 -0
  81. package/dist/app/widget-renderer.js.map +1 -0
  82. package/dist/components/DrillButton.d.ts +22 -0
  83. package/dist/components/DrillButton.d.ts.map +1 -0
  84. package/dist/components/DrillButton.js +13 -0
  85. package/dist/components/DrillButton.js.map +1 -0
  86. package/dist/components/FilterBar.d.ts +28 -0
  87. package/dist/components/FilterBar.d.ts.map +1 -0
  88. package/dist/components/FilterBar.js +12 -0
  89. package/dist/components/FilterBar.js.map +1 -0
  90. package/dist/components/GridLayout.d.ts +26 -0
  91. package/dist/components/GridLayout.d.ts.map +1 -0
  92. package/dist/components/GridLayout.js +31 -0
  93. package/dist/components/GridLayout.js.map +1 -0
  94. package/dist/components/GroupCard.d.ts +21 -0
  95. package/dist/components/GroupCard.d.ts.map +1 -0
  96. package/dist/components/GroupCard.js +24 -0
  97. package/dist/components/GroupCard.js.map +1 -0
  98. package/dist/components/KpiGrid.d.ts +43 -0
  99. package/dist/components/KpiGrid.d.ts.map +1 -0
  100. package/dist/components/KpiGrid.js +35 -0
  101. package/dist/components/KpiGrid.js.map +1 -0
  102. package/dist/components/ListFooter.d.ts +22 -0
  103. package/dist/components/ListFooter.d.ts.map +1 -0
  104. package/dist/components/ListFooter.js +13 -0
  105. package/dist/components/ListFooter.js.map +1 -0
  106. package/dist/components/Pills.d.ts +25 -0
  107. package/dist/components/Pills.d.ts.map +1 -0
  108. package/dist/components/Pills.js +17 -0
  109. package/dist/components/Pills.js.map +1 -0
  110. package/dist/components/SectionHeading.d.ts +16 -0
  111. package/dist/components/SectionHeading.d.ts.map +1 -0
  112. package/dist/components/SectionHeading.js +10 -0
  113. package/dist/components/SectionHeading.js.map +1 -0
  114. package/dist/components/WidgetHeader.d.ts +21 -0
  115. package/dist/components/WidgetHeader.d.ts.map +1 -0
  116. package/dist/components/WidgetHeader.js +11 -0
  117. package/dist/components/WidgetHeader.js.map +1 -0
  118. package/dist/hooks/index.d.ts +11 -0
  119. package/dist/hooks/index.d.ts.map +1 -0
  120. package/dist/hooks/index.js +7 -0
  121. package/dist/hooks/index.js.map +1 -0
  122. package/dist/hooks/use-debounced-value.d.ts +7 -0
  123. package/dist/hooks/use-debounced-value.d.ts.map +1 -0
  124. package/dist/hooks/use-debounced-value.js +15 -0
  125. package/dist/hooks/use-debounced-value.js.map +1 -0
  126. package/dist/hooks/use-mobile.d.ts +6 -0
  127. package/dist/hooks/use-mobile.d.ts.map +1 -0
  128. package/dist/hooks/use-mobile.js +19 -0
  129. package/dist/hooks/use-mobile.js.map +1 -0
  130. package/dist/hooks/use-tool-query.d.ts +21 -0
  131. package/dist/hooks/use-tool-query.d.ts.map +1 -0
  132. package/dist/hooks/use-tool-query.js +47 -0
  133. package/dist/hooks/use-tool-query.js.map +1 -0
  134. package/dist/hooks/use-tool-result-recovery.d.ts +54 -0
  135. package/dist/hooks/use-tool-result-recovery.d.ts.map +1 -0
  136. package/dist/hooks/use-tool-result-recovery.js +93 -0
  137. package/dist/hooks/use-tool-result-recovery.js.map +1 -0
  138. package/dist/hooks/use-view-data.d.ts +19 -0
  139. package/dist/hooks/use-view-data.d.ts.map +1 -0
  140. package/dist/hooks/use-view-data.js +18 -0
  141. package/dist/hooks/use-view-data.js.map +1 -0
  142. package/dist/hooks/use-view-tool-query.d.ts +18 -0
  143. package/dist/hooks/use-view-tool-query.d.ts.map +1 -0
  144. package/dist/hooks/use-view-tool-query.js +29 -0
  145. package/dist/hooks/use-view-tool-query.js.map +1 -0
  146. package/dist/index.d.ts +47 -0
  147. package/dist/index.d.ts.map +1 -0
  148. package/dist/index.js +56 -0
  149. package/dist/index.js.map +1 -0
  150. package/dist/lib/parse-tool-result.d.ts +39 -0
  151. package/dist/lib/parse-tool-result.d.ts.map +1 -0
  152. package/dist/lib/parse-tool-result.js +66 -0
  153. package/dist/lib/parse-tool-result.js.map +1 -0
  154. package/dist/lib/tone-utils.d.ts +18 -0
  155. package/dist/lib/tone-utils.d.ts.map +1 -0
  156. package/dist/lib/tone-utils.js +25 -0
  157. package/dist/lib/tone-utils.js.map +1 -0
  158. package/dist/lib/utils.d.ts +3 -0
  159. package/dist/lib/utils.d.ts.map +1 -0
  160. package/dist/lib/utils.js +6 -0
  161. package/dist/lib/utils.js.map +1 -0
  162. package/dist/locale/index.d.ts +3 -0
  163. package/dist/locale/index.d.ts.map +1 -0
  164. package/dist/locale/index.js +7 -0
  165. package/dist/locale/index.js.map +1 -0
  166. package/dist/locale/locale-provider.d.ts +50 -0
  167. package/dist/locale/locale-provider.d.ts.map +1 -0
  168. package/dist/locale/locale-provider.js +47 -0
  169. package/dist/locale/locale-provider.js.map +1 -0
  170. package/dist/primitives/alert.d.ts +10 -0
  171. package/dist/primitives/alert.d.ts.map +1 -0
  172. package/dist/primitives/alert.js +25 -0
  173. package/dist/primitives/alert.js.map +1 -0
  174. package/dist/primitives/badge.d.ts +8 -0
  175. package/dist/primitives/badge.d.ts.map +1 -0
  176. package/dist/primitives/badge.js +34 -0
  177. package/dist/primitives/badge.js.map +1 -0
  178. package/dist/primitives/button.d.ts +11 -0
  179. package/dist/primitives/button.d.ts.map +1 -0
  180. package/dist/primitives/button.js +36 -0
  181. package/dist/primitives/button.js.map +1 -0
  182. package/dist/primitives/card.d.ts +12 -0
  183. package/dist/primitives/card.d.ts.map +1 -0
  184. package/dist/primitives/card.js +25 -0
  185. package/dist/primitives/card.js.map +1 -0
  186. package/dist/primitives/dialog.d.ts +18 -0
  187. package/dist/primitives/dialog.d.ts.map +1 -0
  188. package/dist/primitives/dialog.js +38 -0
  189. package/dist/primitives/dialog.js.map +1 -0
  190. package/dist/primitives/input.d.ts +4 -0
  191. package/dist/primitives/input.d.ts.map +1 -0
  192. package/dist/primitives/input.js +7 -0
  193. package/dist/primitives/input.js.map +1 -0
  194. package/dist/primitives/scroll-area.d.ts +6 -0
  195. package/dist/primitives/scroll-area.d.ts.map +1 -0
  196. package/dist/primitives/scroll-area.js +11 -0
  197. package/dist/primitives/scroll-area.js.map +1 -0
  198. package/dist/primitives/select.d.ts +16 -0
  199. package/dist/primitives/select.d.ts.map +1 -0
  200. package/dist/primitives/select.js +38 -0
  201. package/dist/primitives/select.js.map +1 -0
  202. package/dist/primitives/separator.d.ts +4 -0
  203. package/dist/primitives/separator.d.ts.map +1 -0
  204. package/dist/primitives/separator.js +9 -0
  205. package/dist/primitives/separator.js.map +1 -0
  206. package/dist/primitives/sheet.d.ts +15 -0
  207. package/dist/primitives/sheet.d.ts.map +1 -0
  208. package/dist/primitives/sheet.js +41 -0
  209. package/dist/primitives/sheet.js.map +1 -0
  210. package/dist/primitives/skeleton.d.ts +3 -0
  211. package/dist/primitives/skeleton.d.ts.map +1 -0
  212. package/dist/primitives/skeleton.js +7 -0
  213. package/dist/primitives/skeleton.js.map +1 -0
  214. package/dist/primitives/switch.d.ts +5 -0
  215. package/dist/primitives/switch.d.ts.map +1 -0
  216. package/dist/primitives/switch.js +8 -0
  217. package/dist/primitives/switch.js.map +1 -0
  218. package/dist/primitives/table.d.ts +11 -0
  219. package/dist/primitives/table.d.ts.map +1 -0
  220. package/dist/primitives/table.js +28 -0
  221. package/dist/primitives/table.js.map +1 -0
  222. package/dist/primitives/tabs.d.ts +12 -0
  223. package/dist/primitives/tabs.d.ts.map +1 -0
  224. package/dist/primitives/tabs.js +29 -0
  225. package/dist/primitives/tabs.js.map +1 -0
  226. package/dist/providers/query-provider.d.ts +11 -0
  227. package/dist/providers/query-provider.d.ts.map +1 -0
  228. package/dist/providers/query-provider.js +20 -0
  229. package/dist/providers/query-provider.js.map +1 -0
  230. package/dist/theme/create-theme.d.ts +130 -0
  231. package/dist/theme/create-theme.d.ts.map +1 -0
  232. package/dist/theme/create-theme.js +65 -0
  233. package/dist/theme/create-theme.js.map +1 -0
  234. package/dist/theme/index.d.ts +7 -0
  235. package/dist/theme/index.d.ts.map +1 -0
  236. package/dist/theme/index.js +7 -0
  237. package/dist/theme/index.js.map +1 -0
  238. package/dist/theme/presets.d.ts +25 -0
  239. package/dist/theme/presets.d.ts.map +1 -0
  240. package/dist/theme/presets.js +68 -0
  241. package/dist/theme/presets.js.map +1 -0
  242. package/dist/theme/theme-provider.d.ts +69 -0
  243. package/dist/theme/theme-provider.d.ts.map +1 -0
  244. package/dist/theme/theme-provider.js +79 -0
  245. package/dist/theme/theme-provider.js.map +1 -0
  246. package/package.json +71 -0
  247. package/src/app/adapt-data-widget.tsx +51 -0
  248. package/src/app/builder/CatalogueSheet.tsx +230 -0
  249. package/src/app/builder/PipelineStrip.tsx +260 -0
  250. package/src/app/builder/PreviewPane.tsx +72 -0
  251. package/src/app/builder/SaveDialog.tsx +95 -0
  252. package/src/app/builder/Toolbar.tsx +95 -0
  253. package/src/app/builder/Workspace.tsx +483 -0
  254. package/src/app/builder/builder-model.test.ts +351 -0
  255. package/src/app/builder/builder-model.ts +213 -0
  256. package/src/app/builder/builder-reducer.test.ts +441 -0
  257. package/src/app/builder/builder-reducer.ts +289 -0
  258. package/src/app/builder/labels.ts +139 -0
  259. package/src/app/builder/status-chip.tsx +37 -0
  260. package/src/app/host-bridge.test.ts +206 -0
  261. package/src/app/host-bridge.tsx +371 -0
  262. package/src/app/index.ts +51 -0
  263. package/src/app/layout-builder.tsx +872 -0
  264. package/src/app/mcp-app-view.tsx +367 -0
  265. package/src/app/mcp-toolkit-app.tsx +25 -0
  266. package/src/app/use-host-actions.test.ts +16 -0
  267. package/src/app/use-host-actions.tsx +88 -0
  268. package/src/app/widget-fixture.test.ts +108 -0
  269. package/src/app/widget-fixture.tsx +441 -0
  270. package/src/app/widget-props-sheet.tsx +285 -0
  271. package/src/app/widget-renderer.test.ts +42 -0
  272. package/src/app/widget-renderer.tsx +200 -0
  273. package/src/components/DrillButton.tsx +37 -0
  274. package/src/components/FilterBar.tsx +68 -0
  275. package/src/components/GridLayout.tsx +49 -0
  276. package/src/components/GroupCard.tsx +52 -0
  277. package/src/components/KpiGrid.tsx +134 -0
  278. package/src/components/ListFooter.tsx +48 -0
  279. package/src/components/Pills.tsx +45 -0
  280. package/src/components/SectionHeading.tsx +26 -0
  281. package/src/components/WidgetHeader.tsx +51 -0
  282. package/src/globals.css +168 -0
  283. package/src/hooks/index.ts +17 -0
  284. package/src/hooks/use-debounced-value.ts +15 -0
  285. package/src/hooks/use-mobile.ts +24 -0
  286. package/src/hooks/use-tool-query.ts +63 -0
  287. package/src/hooks/use-tool-result-recovery.test.ts +65 -0
  288. package/src/hooks/use-tool-result-recovery.ts +144 -0
  289. package/src/hooks/use-view-data.ts +31 -0
  290. package/src/hooks/use-view-tool-query.ts +38 -0
  291. package/src/index.ts +134 -0
  292. package/src/lib/parse-tool-result.test.ts +139 -0
  293. package/src/lib/parse-tool-result.ts +89 -0
  294. package/src/lib/tone-utils.test.ts +35 -0
  295. package/src/lib/tone-utils.ts +38 -0
  296. package/src/lib/utils.ts +6 -0
  297. package/src/locale/index.ts +7 -0
  298. package/src/locale/locale-provider.tsx +81 -0
  299. package/src/primitives/alert.tsx +60 -0
  300. package/src/primitives/badge.tsx +49 -0
  301. package/src/primitives/button.tsx +62 -0
  302. package/src/primitives/card.tsx +92 -0
  303. package/src/primitives/dialog.tsx +144 -0
  304. package/src/primitives/input.tsx +21 -0
  305. package/src/primitives/scroll-area.tsx +54 -0
  306. package/src/primitives/select.tsx +173 -0
  307. package/src/primitives/separator.tsx +21 -0
  308. package/src/primitives/sheet.tsx +134 -0
  309. package/src/primitives/skeleton.tsx +13 -0
  310. package/src/primitives/switch.tsx +24 -0
  311. package/src/primitives/table.tsx +87 -0
  312. package/src/primitives/tabs.tsx +79 -0
  313. package/src/providers/query-provider.tsx +38 -0
  314. package/src/theme/create-theme.test.ts +108 -0
  315. package/src/theme/create-theme.ts +158 -0
  316. package/src/theme/index.ts +9 -0
  317. package/src/theme/presets.ts +82 -0
  318. package/src/theme/theme-provider.tsx +152 -0
  319. package/src/ui-catalog.test.ts +112 -0
  320. package/ui-catalog.json +1280 -0
@@ -0,0 +1,158 @@
1
+ import type { CSSProperties } from "react"
2
+
3
+ /**
4
+ * A curated, brand-relevant subset of the toolkit's CSS-variable theming
5
+ * surface (the full set lives in `packages/ui/src/globals.css`). These are the
6
+ * tokens a white-label client almost always wants to change — the brand colour
7
+ * pair, the surface/accent colours, the border/ring, and the shape/typography
8
+ * knobs — expressed in plain, design-system terms.
9
+ *
10
+ * Every field is **optional**: a theme overrides only the tokens it cares about
11
+ * and inherits the rest from the toolkit defaults (`:root` / `.dark`). Colour
12
+ * values are any valid CSS colour string (`oklch(...)`, `#rrggbb`, `hsl(...)`),
13
+ * `radius` is any CSS length (e.g. `"0.5rem"`), and the font fields are CSS
14
+ * `font-family` lists.
15
+ *
16
+ * Unlisted CSS variables (`--popover`, `--chart-*`, `--sidebar-*`, …) are
17
+ * intentionally out of scope here: they are part of the theming surface but not
18
+ * the brand essentials, so a client tunes them by hand in CSS when needed. See
19
+ * {@link TOKEN_TO_CSS_VAR} for the exact field → variable mapping.
20
+ */
21
+ export interface ThemeTokens {
22
+ /** Brand colour. Maps to `--primary`. */
23
+ primary?: string
24
+ /** Text/icon colour on a `primary` surface. Maps to `--primary-foreground`. */
25
+ primaryForeground?: string
26
+ /** Accent colour for subtle highlights. Maps to `--accent`. */
27
+ accent?: string
28
+ /** Text colour on an `accent` surface. Maps to `--accent-foreground`. */
29
+ accentForeground?: string
30
+ /** App/page background. Maps to `--background`. */
31
+ background?: string
32
+ /** Default text colour. Maps to `--foreground`. */
33
+ foreground?: string
34
+ /** Card/panel surface. Maps to `--card`. */
35
+ card?: string
36
+ /** Text colour on a `card` surface. Maps to `--card-foreground`. */
37
+ cardForeground?: string
38
+ /** Border colour for dividers/outlines. Maps to `--border`. */
39
+ border?: string
40
+ /** Focus-ring colour. Maps to `--ring`. */
41
+ ring?: string
42
+ /** Base corner radius (a CSS length). Maps to `--radius`. */
43
+ radius?: string
44
+ /** Body font-family list. Maps to `--font-sans`. */
45
+ fontSans?: string
46
+ /** Heading font-family list. Maps to `--font-heading`. */
47
+ fontHeading?: string
48
+ }
49
+
50
+ /** The CSS custom-property name a {@link ThemeTokens} field resolves to. */
51
+ type CssVarName = `--${string}`
52
+
53
+ /**
54
+ * The single source of truth mapping each curated {@link ThemeTokens} field to
55
+ * its real CSS variable name from `globals.css`. `tokensToVars` walks this map,
56
+ * so adding a field here (and to {@link ThemeTokens}) is all that is needed to
57
+ * extend the public theming surface.
58
+ */
59
+ export const TOKEN_TO_CSS_VAR = {
60
+ primary: "--primary",
61
+ primaryForeground: "--primary-foreground",
62
+ accent: "--accent",
63
+ accentForeground: "--accent-foreground",
64
+ background: "--background",
65
+ foreground: "--foreground",
66
+ card: "--card",
67
+ cardForeground: "--card-foreground",
68
+ border: "--border",
69
+ ring: "--ring",
70
+ radius: "--radius",
71
+ fontSans: "--font-sans",
72
+ fontHeading: "--font-heading",
73
+ } as const satisfies Record<keyof ThemeTokens, CssVarName>
74
+
75
+ /** A serializable map of CSS custom properties (`--primary` → value). */
76
+ export type ThemeVars = Record<CssVarName, string>
77
+
78
+ /**
79
+ * A resolved, serializable theme: the CSS variables to apply at light scope,
80
+ * the optional dark-mode overrides, and a small helper to turn the light vars
81
+ * into a React `style` object.
82
+ *
83
+ * The struct is plain data (no functions on the wire except {@link toStyle},
84
+ * which is derived from `vars`) so it can be passed to a server component,
85
+ * logged, or persisted and rehydrated.
86
+ */
87
+ export interface ThemeDefinition {
88
+ /** CSS variables applied at the theme scope (overrides `:root`). */
89
+ vars: ThemeVars
90
+ /** Dark-mode CSS variables, applied under `.dark` at the theme scope. */
91
+ darkVars?: ThemeVars
92
+ /**
93
+ * The light {@link vars} as a React `style` object (CSS custom properties are
94
+ * valid `CSSProperties` keys). Handy for inlining onto a wrapper element
95
+ * without the {@link ThemeProvider}.
96
+ */
97
+ toStyle(): CSSProperties
98
+ }
99
+
100
+ /**
101
+ * Map a {@link ThemeTokens} object to its CSS variables. Empty/partial token
102
+ * objects yield only the variables that were set; fields not present in
103
+ * {@link TOKEN_TO_CSS_VAR} (e.g. an unknown key passed loosely) are ignored.
104
+ */
105
+ export function tokensToVars(tokens: ThemeTokens): ThemeVars {
106
+ const vars: ThemeVars = {}
107
+ for (const [field, cssVar] of Object.entries(TOKEN_TO_CSS_VAR) as [
108
+ keyof ThemeTokens,
109
+ CssVarName,
110
+ ][]) {
111
+ const value = tokens[field]
112
+ if (typeof value === "string" && value.length > 0) {
113
+ vars[cssVar] = value
114
+ }
115
+ }
116
+ return vars
117
+ }
118
+
119
+ /** Options for {@link createTheme}. */
120
+ export interface CreateThemeOptions {
121
+ /**
122
+ * Dark-mode token overrides. Applied on top of the toolkit's `.dark` defaults
123
+ * when the active mode is dark (see {@link ThemeProvider}). Provide only the
124
+ * tokens that differ from light — typically a darker `background`/`card` and a
125
+ * brighter `primary`.
126
+ */
127
+ dark?: ThemeTokens
128
+ }
129
+
130
+ /**
131
+ * Build a serializable {@link ThemeDefinition} from a curated set of brand
132
+ * {@link ThemeTokens}. This is the one switch a white-label client flips: pass
133
+ * your brand's primary colour, radius, and (optionally) dark overrides, and
134
+ * every toolkit primitive that reads the design tokens — `Card`, `Button`,
135
+ * `Badge`, `bg-card`, `text-primary`, `rounded-lg` — re-skins to match.
136
+ *
137
+ * Pure and side-effect-free: it only maps fields to CSS-variable names. Apply
138
+ * the result with {@link ThemeProvider} (recommended) or by spreading
139
+ * `theme.toStyle()` onto a wrapper element.
140
+ *
141
+ * @example
142
+ * const brand = createTheme(
143
+ * { primary: "oklch(0.55 0.2 264)", radius: "0.5rem" },
144
+ * { dark: { primary: "oklch(0.7 0.18 264)" } },
145
+ * )
146
+ * // <ThemeProvider theme={brand}>…</ThemeProvider>
147
+ */
148
+ export function createTheme(tokens: ThemeTokens, opts?: CreateThemeOptions): ThemeDefinition {
149
+ const vars = tokensToVars(tokens)
150
+ const darkTokens = opts?.dark
151
+ const darkVars = darkTokens ? tokensToVars(darkTokens) : undefined
152
+
153
+ return {
154
+ vars,
155
+ ...(darkVars && Object.keys(darkVars).length > 0 ? { darkVars } : {}),
156
+ toStyle: () => vars,
157
+ }
158
+ }
@@ -0,0 +1,9 @@
1
+ // White-label theming. Browser-safe (React + DOM only, no `mcp-use/react`), so
2
+ // it is re-exported from the root barrel — a consumer can theme the toolkit's
3
+ // primitives without pulling in the host runtime.
4
+ export { createTheme, tokensToVars, TOKEN_TO_CSS_VAR } from "./create-theme.js"
5
+ export type { ThemeTokens, ThemeDefinition, ThemeVars, CreateThemeOptions } from "./create-theme.js"
6
+ export { ThemeProvider, useTheme } from "./theme-provider.js"
7
+ export type { ThemeMode, ThemeContextValue, ThemeProviderProps } from "./theme-provider.js"
8
+ export { themePresets } from "./presets.js"
9
+ export type { ThemePresetId } from "./presets.js"
@@ -0,0 +1,82 @@
1
+ import { createTheme, type ThemeDefinition } from "./create-theme.js"
2
+
3
+ /**
4
+ * Ready-made white-label themes — a demonstration of {@link createTheme} and a
5
+ * starting point a client can fork. Each preset sets a distinct brand `primary`,
6
+ * a matching `ring`/`accent`, and a `radius`, plus a `dark` override that
7
+ * brightens the brand colour for legibility on dark surfaces (mirroring how
8
+ * `globals.css` brightens tokens under `.dark`).
9
+ *
10
+ * Colours are authored in `oklch` to stay consistent with the toolkit's token
11
+ * palette in `globals.css`. Apply one with `<ThemeProvider theme={themePresets.violet}>`.
12
+ */
13
+ export const themePresets = {
14
+ /**
15
+ * The toolkit's own brand: a confident indigo-blue with a slightly tighter
16
+ * radius. A sensible default when a client has no brand of their own yet.
17
+ */
18
+ miragon: createTheme(
19
+ {
20
+ primary: "oklch(0.52 0.21 264)",
21
+ primaryForeground: "oklch(0.985 0 0)",
22
+ accent: "oklch(0.95 0.03 264)",
23
+ accentForeground: "oklch(0.32 0.12 264)",
24
+ ring: "oklch(0.52 0.21 264)",
25
+ radius: "0.5rem",
26
+ },
27
+ {
28
+ dark: {
29
+ primary: "oklch(0.7 0.16 264)",
30
+ primaryForeground: "oklch(0.18 0.02 264)",
31
+ accent: "oklch(0.32 0.06 264)",
32
+ accentForeground: "oklch(0.97 0 0)",
33
+ ring: "oklch(0.7 0.16 264)",
34
+ },
35
+ },
36
+ ),
37
+
38
+ /** A vivid violet brand with rounder corners — playful, consumer-facing. */
39
+ violet: createTheme(
40
+ {
41
+ primary: "oklch(0.55 0.24 295)",
42
+ primaryForeground: "oklch(0.985 0 0)",
43
+ accent: "oklch(0.95 0.04 295)",
44
+ accentForeground: "oklch(0.34 0.14 295)",
45
+ ring: "oklch(0.55 0.24 295)",
46
+ radius: "0.875rem",
47
+ },
48
+ {
49
+ dark: {
50
+ primary: "oklch(0.72 0.18 295)",
51
+ primaryForeground: "oklch(0.18 0.03 295)",
52
+ accent: "oklch(0.33 0.08 295)",
53
+ accentForeground: "oklch(0.97 0 0)",
54
+ ring: "oklch(0.72 0.18 295)",
55
+ },
56
+ },
57
+ ),
58
+
59
+ /** A grounded emerald brand with a crisp, near-square radius — enterprise. */
60
+ emerald: createTheme(
61
+ {
62
+ primary: "oklch(0.58 0.15 162)",
63
+ primaryForeground: "oklch(0.985 0 0)",
64
+ accent: "oklch(0.95 0.04 162)",
65
+ accentForeground: "oklch(0.34 0.1 162)",
66
+ ring: "oklch(0.58 0.15 162)",
67
+ radius: "0.375rem",
68
+ },
69
+ {
70
+ dark: {
71
+ primary: "oklch(0.72 0.15 162)",
72
+ primaryForeground: "oklch(0.17 0.03 162)",
73
+ accent: "oklch(0.32 0.07 162)",
74
+ accentForeground: "oklch(0.97 0 0)",
75
+ ring: "oklch(0.72 0.15 162)",
76
+ },
77
+ },
78
+ ),
79
+ } satisfies Record<string, ThemeDefinition>
80
+
81
+ /** The id of a built-in preset in {@link themePresets}. */
82
+ export type ThemePresetId = keyof typeof themePresets
@@ -0,0 +1,152 @@
1
+ import {
2
+ createContext,
3
+ useContext,
4
+ useEffect,
5
+ useMemo,
6
+ useState,
7
+ type CSSProperties,
8
+ type ReactNode,
9
+ } from "react"
10
+ import type { ThemeDefinition } from "./create-theme.js"
11
+
12
+ /**
13
+ * How the `.dark` class is resolved on the theme scope:
14
+ * - `"light"` / `"dark"` — forced, ignores the OS preference.
15
+ * - `"system"` — follows `prefers-color-scheme` via `matchMedia`, updating live.
16
+ *
17
+ * A host that already knows the colour scheme (e.g. `HostBridge.theme`) can pass
18
+ * its value straight through as `mode` so the white-label theme tracks the host.
19
+ */
20
+ export type ThemeMode = "light" | "dark" | "system"
21
+
22
+ /** The value exposed by {@link useTheme}. */
23
+ export interface ThemeContextValue {
24
+ /** The active theme definition, or `null` when only a mode is being managed. */
25
+ theme: ThemeDefinition | null
26
+ /** The mode as requested on the provider (may be `"system"`). */
27
+ mode: ThemeMode
28
+ /** The mode actually applied right now (`"system"` resolved to light/dark). */
29
+ resolvedMode: "light" | "dark"
30
+ }
31
+
32
+ const ThemeContext = createContext<ThemeContextValue | null>(null)
33
+
34
+ /**
35
+ * Read the active theme/mode from the nearest {@link ThemeProvider}. Throws when
36
+ * called outside a provider so a missing wrapper fails loudly rather than
37
+ * silently rendering the default theme.
38
+ */
39
+ export function useTheme(): ThemeContextValue {
40
+ const value = useContext(ThemeContext)
41
+ if (!value) {
42
+ throw new Error("useTheme() must be used within a <ThemeProvider>.")
43
+ }
44
+ return value
45
+ }
46
+
47
+ /**
48
+ * Resolve `"system"` to a concrete `"light"` | `"dark"` and keep it in sync with
49
+ * the OS preference. SSR-safe: returns `"light"` until mounted (no `window`),
50
+ * then subscribes to `matchMedia` on the client. Forced modes short-circuit the
51
+ * media query entirely.
52
+ */
53
+ function useResolvedMode(mode: ThemeMode): "light" | "dark" {
54
+ const [systemMode, setSystemMode] = useState<"light" | "dark">("light")
55
+
56
+ useEffect(() => {
57
+ if (mode !== "system") return
58
+ if (typeof window === "undefined" || typeof window.matchMedia !== "function") return
59
+
60
+ const query = window.matchMedia("(prefers-color-scheme: dark)")
61
+ const sync = () => {
62
+ setSystemMode(query.matches ? "dark" : "light")
63
+ }
64
+ sync()
65
+ query.addEventListener("change", sync)
66
+ return () => {
67
+ query.removeEventListener("change", sync)
68
+ }
69
+ }, [mode])
70
+
71
+ return mode === "system" ? systemMode : mode
72
+ }
73
+
74
+ /** Props for {@link ThemeProvider}. */
75
+ export interface ThemeProviderProps {
76
+ /**
77
+ * The white-label theme to apply (from {@link createTheme} or `themePresets`).
78
+ * Omit to manage only the light/dark `mode` against the toolkit defaults.
79
+ */
80
+ theme?: ThemeDefinition
81
+ /**
82
+ * Colour-scheme mode. Defaults to `"light"`. Pass `HostBridge.theme` through
83
+ * here to follow the host, or `"system"` to track the OS preference.
84
+ */
85
+ mode?: ThemeMode
86
+ /**
87
+ * The HTML element to render as the scope wrapper. Defaults to `"div"`. Use
88
+ * `"span"` for inline contexts; any intrinsic element with a `style`/className
89
+ * works.
90
+ */
91
+ as?: "div" | "span" | "section" | "main"
92
+ /** Extra classes merged onto the scope wrapper. */
93
+ className?: string
94
+ children: ReactNode
95
+ }
96
+
97
+ /**
98
+ * Apply a white-label {@link ThemeDefinition} to a subtree. Renders a wrapper
99
+ * element carrying the theme's CSS variables as inline `style`, so they override
100
+ * the `:root` defaults for every descendant — `Card`, `Button`, `bg-card`,
101
+ * `text-primary`, and `rounded-lg` all re-skin without touching the widgets.
102
+ *
103
+ * Dark mode is handled at the scope: the `.dark` class is toggled on the wrapper
104
+ * (not `<html>`) per the resolved {@link ThemeMode}, and the theme's `darkVars`
105
+ * are layered on under it. This keeps theming local — two differently themed
106
+ * subtrees (e.g. a brand preview gallery) coexist on one page.
107
+ *
108
+ * Uses only React + the DOM (no `mcp-use/react`), so it ships from the root
109
+ * barrel and works in a host-free standalone app, a host, or SSR alike.
110
+ *
111
+ * @example
112
+ * <ThemeProvider theme={themePresets.violet} mode={host.theme ?? "system"}>
113
+ * <YourWidget />
114
+ * </ThemeProvider>
115
+ */
116
+ export function ThemeProvider({
117
+ theme,
118
+ mode = "light",
119
+ as: As = "div",
120
+ className,
121
+ children,
122
+ }: ThemeProviderProps): ReactNode {
123
+ const resolvedMode = useResolvedMode(mode)
124
+ const isDark = resolvedMode === "dark"
125
+
126
+ // Merge the light vars with the dark overrides only when dark is active, so a
127
+ // dark scope gets the brand's dark palette on top of `.dark`'s defaults.
128
+ const style = useMemo<CSSProperties>(() => {
129
+ if (!theme) return {}
130
+ if (isDark && theme.darkVars) {
131
+ return { ...theme.vars, ...theme.darkVars }
132
+ }
133
+ return theme.vars
134
+ }, [theme, isDark])
135
+
136
+ const value = useMemo<ThemeContextValue>(
137
+ () => ({ theme: theme ?? null, mode, resolvedMode }),
138
+ [theme, mode, resolvedMode],
139
+ )
140
+
141
+ const classes = [isDark ? "dark" : undefined, "bg-background text-foreground", className]
142
+ .filter(Boolean)
143
+ .join(" ")
144
+
145
+ return (
146
+ <ThemeContext.Provider value={value}>
147
+ <As className={classes} style={style} data-theme-mode={resolvedMode}>
148
+ {children}
149
+ </As>
150
+ </ThemeContext.Provider>
151
+ )
152
+ }
@@ -0,0 +1,112 @@
1
+ import { describe, expect, it } from "vitest"
2
+ import catalog from "../ui-catalog.json" with { type: "json" }
3
+ import * as rootBarrel from "./index.js"
4
+ import * as appBarrel from "./app/index.js"
5
+ import * as hooksBarrel from "./hooks/index.js"
6
+
7
+ /**
8
+ * Drift guard for `packages/ui/ui-catalog.json` — the machine-readable catalog
9
+ * the docs (`docs/reference/components.md`) and the `build-mcp-widget` skill
10
+ * point an on-top-prompting agent at. The catalog is hand-maintained (the
11
+ * curated `whenToUse`/prop descriptions are the LLM-facing value), so these
12
+ * tests pin it to the real package surface: every entry must name a symbol that
13
+ * is actually exported from the `importPath` it claims. A rename or moved export
14
+ * fails here instead of silently feeding an agent a dead import.
15
+ */
16
+
17
+ interface CatalogProp {
18
+ name: string
19
+ type: string
20
+ required: boolean
21
+ description: string
22
+ }
23
+
24
+ interface CatalogEntry {
25
+ name: string
26
+ export: string
27
+ importPath: string
28
+ kind: string
29
+ group: string
30
+ parts?: string[]
31
+ props: CatalogProp[]
32
+ whenToUse: string
33
+ }
34
+
35
+ interface Catalog {
36
+ importPaths: Record<"root" | "app" | "hooks", string>
37
+ components: CatalogEntry[]
38
+ }
39
+
40
+ // Imported as a JSON module (not read via `node:fs`) so this browser-oriented
41
+ // package keeps typechecking without `@types/node`. The `as unknown as Catalog`
42
+ // cast pins the structural contract the assertions below rely on.
43
+ const catalogData = catalog as unknown as Catalog
44
+
45
+ /** Map each `importPath` to the live module namespace it must resolve through. */
46
+ const BARRELS: Record<string, Record<string, unknown>> = {
47
+ [catalogData.importPaths.root]: rootBarrel,
48
+ [catalogData.importPaths.app]: appBarrel,
49
+ [catalogData.importPaths.hooks]: hooksBarrel,
50
+ }
51
+
52
+ describe("ui-catalog.json", () => {
53
+ it("declares the three real subpath imports", () => {
54
+ expect(catalogData.importPaths).toEqual({
55
+ root: "@miragon/mcp-toolkit-ui",
56
+ app: "@miragon/mcp-toolkit-ui/app",
57
+ hooks: "@miragon/mcp-toolkit-ui/hooks",
58
+ })
59
+ })
60
+
61
+ it("has unique entry names", () => {
62
+ const names = catalogData.components.map((c) => c.name)
63
+ expect(new Set(names).size).toBe(names.length)
64
+ })
65
+
66
+ it("references only the three known import paths", () => {
67
+ const known = new Set(Object.values(catalogData.importPaths))
68
+ for (const entry of catalogData.components) {
69
+ expect(known, `${entry.name} → ${entry.importPath}`).toContain(entry.importPath)
70
+ }
71
+ })
72
+
73
+ describe("every entry's `export` is reachable from its `importPath`", () => {
74
+ for (const entry of catalogData.components) {
75
+ it(`${entry.name} is exported from ${entry.importPath}`, () => {
76
+ const barrel = BARRELS[entry.importPath]
77
+ expect(barrel, `no barrel for ${entry.importPath}`).toBeDefined()
78
+ // Types (e.g. DescribeForModel) erase at runtime, so they can't be probed
79
+ // on the namespace object — assert only value exports here. Type exports
80
+ // are guarded by the package's own typecheck (the barrels re-export them).
81
+ if (entry.kind === "type") return
82
+ expect(
83
+ Object.prototype.hasOwnProperty.call(barrel, entry.export),
84
+ `${entry.export} missing from ${entry.importPath}`,
85
+ ).toBe(true)
86
+ })
87
+ }
88
+ })
89
+
90
+ it("keeps every multi-part component's parts exported from the same barrel", () => {
91
+ for (const entry of catalogData.components) {
92
+ if (!entry.parts) continue
93
+ const barrel = BARRELS[entry.importPath]
94
+ for (const part of entry.parts) {
95
+ expect(
96
+ Object.prototype.hasOwnProperty.call(barrel, part),
97
+ `${entry.name} part ${part} missing from ${entry.importPath}`,
98
+ ).toBe(true)
99
+ }
100
+ }
101
+ })
102
+
103
+ it("gives every entry at least a name, kind, group and whenToUse", () => {
104
+ for (const entry of catalogData.components) {
105
+ expect(entry.name, "name").toBeTruthy()
106
+ expect(entry.kind, `${entry.name} kind`).toBeTruthy()
107
+ expect(entry.group, `${entry.name} group`).toBeTruthy()
108
+ expect(entry.whenToUse, `${entry.name} whenToUse`).toBeTruthy()
109
+ expect(Array.isArray(entry.props), `${entry.name} props`).toBe(true)
110
+ }
111
+ })
112
+ })