@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.
- package/README.md +36 -0
- package/dist/app/adapt-data-widget.d.ts +33 -0
- package/dist/app/adapt-data-widget.d.ts.map +1 -0
- package/dist/app/adapt-data-widget.js +35 -0
- package/dist/app/adapt-data-widget.js.map +1 -0
- package/dist/app/builder/CatalogueSheet.d.ts +13 -0
- package/dist/app/builder/CatalogueSheet.d.ts.map +1 -0
- package/dist/app/builder/CatalogueSheet.js +33 -0
- package/dist/app/builder/CatalogueSheet.js.map +1 -0
- package/dist/app/builder/PipelineStrip.d.ts +22 -0
- package/dist/app/builder/PipelineStrip.d.ts.map +1 -0
- package/dist/app/builder/PipelineStrip.js +31 -0
- package/dist/app/builder/PipelineStrip.js.map +1 -0
- package/dist/app/builder/PreviewPane.d.ts +13 -0
- package/dist/app/builder/PreviewPane.d.ts.map +1 -0
- package/dist/app/builder/PreviewPane.js +21 -0
- package/dist/app/builder/PreviewPane.js.map +1 -0
- package/dist/app/builder/SaveDialog.d.ts +14 -0
- package/dist/app/builder/SaveDialog.d.ts.map +1 -0
- package/dist/app/builder/SaveDialog.js +14 -0
- package/dist/app/builder/SaveDialog.js.map +1 -0
- package/dist/app/builder/Toolbar.d.ts +17 -0
- package/dist/app/builder/Toolbar.d.ts.map +1 -0
- package/dist/app/builder/Toolbar.js +15 -0
- package/dist/app/builder/Toolbar.js.map +1 -0
- package/dist/app/builder/Workspace.d.ts +28 -0
- package/dist/app/builder/Workspace.d.ts.map +1 -0
- package/dist/app/builder/Workspace.js +117 -0
- package/dist/app/builder/Workspace.js.map +1 -0
- package/dist/app/builder/builder-model.d.ts +86 -0
- package/dist/app/builder/builder-model.d.ts.map +1 -0
- package/dist/app/builder/builder-model.js +174 -0
- package/dist/app/builder/builder-model.js.map +1 -0
- package/dist/app/builder/builder-reducer.d.ts +71 -0
- package/dist/app/builder/builder-reducer.d.ts.map +1 -0
- package/dist/app/builder/builder-reducer.js +201 -0
- package/dist/app/builder/builder-reducer.js.map +1 -0
- package/dist/app/builder/labels.d.ts +78 -0
- package/dist/app/builder/labels.d.ts.map +1 -0
- package/dist/app/builder/labels.js +71 -0
- package/dist/app/builder/labels.js.map +1 -0
- package/dist/app/builder/status-chip.d.ts +10 -0
- package/dist/app/builder/status-chip.d.ts.map +1 -0
- package/dist/app/builder/status-chip.js +16 -0
- package/dist/app/builder/status-chip.js.map +1 -0
- package/dist/app/host-bridge.d.ts +211 -0
- package/dist/app/host-bridge.d.ts.map +1 -0
- package/dist/app/host-bridge.js +247 -0
- package/dist/app/host-bridge.js.map +1 -0
- package/dist/app/index.d.ts +16 -0
- package/dist/app/index.d.ts.map +1 -0
- package/dist/app/index.js +18 -0
- package/dist/app/index.js.map +1 -0
- package/dist/app/layout-builder.d.ts +48 -0
- package/dist/app/layout-builder.d.ts.map +1 -0
- package/dist/app/layout-builder.js +418 -0
- package/dist/app/layout-builder.js.map +1 -0
- package/dist/app/mcp-app-view.d.ts +43 -0
- package/dist/app/mcp-app-view.d.ts.map +1 -0
- package/dist/app/mcp-app-view.js +169 -0
- package/dist/app/mcp-app-view.js.map +1 -0
- package/dist/app/mcp-toolkit-app.d.ts +18 -0
- package/dist/app/mcp-toolkit-app.d.ts.map +1 -0
- package/dist/app/mcp-toolkit-app.js +22 -0
- package/dist/app/mcp-toolkit-app.js.map +1 -0
- package/dist/app/use-host-actions.d.ts +57 -0
- package/dist/app/use-host-actions.d.ts.map +1 -0
- package/dist/app/use-host-actions.js +42 -0
- package/dist/app/use-host-actions.js.map +1 -0
- package/dist/app/widget-fixture.d.ts +180 -0
- package/dist/app/widget-fixture.d.ts.map +1 -0
- package/dist/app/widget-fixture.js +273 -0
- package/dist/app/widget-fixture.js.map +1 -0
- package/dist/app/widget-props-sheet.d.ts +20 -0
- package/dist/app/widget-props-sheet.d.ts.map +1 -0
- package/dist/app/widget-props-sheet.js +87 -0
- package/dist/app/widget-props-sheet.js.map +1 -0
- package/dist/app/widget-renderer.d.ts +87 -0
- package/dist/app/widget-renderer.d.ts.map +1 -0
- package/dist/app/widget-renderer.js +105 -0
- package/dist/app/widget-renderer.js.map +1 -0
- package/dist/components/DrillButton.d.ts +22 -0
- package/dist/components/DrillButton.d.ts.map +1 -0
- package/dist/components/DrillButton.js +13 -0
- package/dist/components/DrillButton.js.map +1 -0
- package/dist/components/FilterBar.d.ts +28 -0
- package/dist/components/FilterBar.d.ts.map +1 -0
- package/dist/components/FilterBar.js +12 -0
- package/dist/components/FilterBar.js.map +1 -0
- package/dist/components/GridLayout.d.ts +26 -0
- package/dist/components/GridLayout.d.ts.map +1 -0
- package/dist/components/GridLayout.js +31 -0
- package/dist/components/GridLayout.js.map +1 -0
- package/dist/components/GroupCard.d.ts +21 -0
- package/dist/components/GroupCard.d.ts.map +1 -0
- package/dist/components/GroupCard.js +24 -0
- package/dist/components/GroupCard.js.map +1 -0
- package/dist/components/KpiGrid.d.ts +43 -0
- package/dist/components/KpiGrid.d.ts.map +1 -0
- package/dist/components/KpiGrid.js +35 -0
- package/dist/components/KpiGrid.js.map +1 -0
- package/dist/components/ListFooter.d.ts +22 -0
- package/dist/components/ListFooter.d.ts.map +1 -0
- package/dist/components/ListFooter.js +13 -0
- package/dist/components/ListFooter.js.map +1 -0
- package/dist/components/Pills.d.ts +25 -0
- package/dist/components/Pills.d.ts.map +1 -0
- package/dist/components/Pills.js +17 -0
- package/dist/components/Pills.js.map +1 -0
- package/dist/components/SectionHeading.d.ts +16 -0
- package/dist/components/SectionHeading.d.ts.map +1 -0
- package/dist/components/SectionHeading.js +10 -0
- package/dist/components/SectionHeading.js.map +1 -0
- package/dist/components/WidgetHeader.d.ts +21 -0
- package/dist/components/WidgetHeader.d.ts.map +1 -0
- package/dist/components/WidgetHeader.js +11 -0
- package/dist/components/WidgetHeader.js.map +1 -0
- package/dist/hooks/index.d.ts +11 -0
- package/dist/hooks/index.d.ts.map +1 -0
- package/dist/hooks/index.js +7 -0
- package/dist/hooks/index.js.map +1 -0
- package/dist/hooks/use-debounced-value.d.ts +7 -0
- package/dist/hooks/use-debounced-value.d.ts.map +1 -0
- package/dist/hooks/use-debounced-value.js +15 -0
- package/dist/hooks/use-debounced-value.js.map +1 -0
- package/dist/hooks/use-mobile.d.ts +6 -0
- package/dist/hooks/use-mobile.d.ts.map +1 -0
- package/dist/hooks/use-mobile.js +19 -0
- package/dist/hooks/use-mobile.js.map +1 -0
- package/dist/hooks/use-tool-query.d.ts +21 -0
- package/dist/hooks/use-tool-query.d.ts.map +1 -0
- package/dist/hooks/use-tool-query.js +47 -0
- package/dist/hooks/use-tool-query.js.map +1 -0
- package/dist/hooks/use-tool-result-recovery.d.ts +54 -0
- package/dist/hooks/use-tool-result-recovery.d.ts.map +1 -0
- package/dist/hooks/use-tool-result-recovery.js +93 -0
- package/dist/hooks/use-tool-result-recovery.js.map +1 -0
- package/dist/hooks/use-view-data.d.ts +19 -0
- package/dist/hooks/use-view-data.d.ts.map +1 -0
- package/dist/hooks/use-view-data.js +18 -0
- package/dist/hooks/use-view-data.js.map +1 -0
- package/dist/hooks/use-view-tool-query.d.ts +18 -0
- package/dist/hooks/use-view-tool-query.d.ts.map +1 -0
- package/dist/hooks/use-view-tool-query.js +29 -0
- package/dist/hooks/use-view-tool-query.js.map +1 -0
- package/dist/index.d.ts +47 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +56 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/parse-tool-result.d.ts +39 -0
- package/dist/lib/parse-tool-result.d.ts.map +1 -0
- package/dist/lib/parse-tool-result.js +66 -0
- package/dist/lib/parse-tool-result.js.map +1 -0
- package/dist/lib/tone-utils.d.ts +18 -0
- package/dist/lib/tone-utils.d.ts.map +1 -0
- package/dist/lib/tone-utils.js +25 -0
- package/dist/lib/tone-utils.js.map +1 -0
- package/dist/lib/utils.d.ts +3 -0
- package/dist/lib/utils.d.ts.map +1 -0
- package/dist/lib/utils.js +6 -0
- package/dist/lib/utils.js.map +1 -0
- package/dist/locale/index.d.ts +3 -0
- package/dist/locale/index.d.ts.map +1 -0
- package/dist/locale/index.js +7 -0
- package/dist/locale/index.js.map +1 -0
- package/dist/locale/locale-provider.d.ts +50 -0
- package/dist/locale/locale-provider.d.ts.map +1 -0
- package/dist/locale/locale-provider.js +47 -0
- package/dist/locale/locale-provider.js.map +1 -0
- package/dist/primitives/alert.d.ts +10 -0
- package/dist/primitives/alert.d.ts.map +1 -0
- package/dist/primitives/alert.js +25 -0
- package/dist/primitives/alert.js.map +1 -0
- package/dist/primitives/badge.d.ts +8 -0
- package/dist/primitives/badge.d.ts.map +1 -0
- package/dist/primitives/badge.js +34 -0
- package/dist/primitives/badge.js.map +1 -0
- package/dist/primitives/button.d.ts +11 -0
- package/dist/primitives/button.d.ts.map +1 -0
- package/dist/primitives/button.js +36 -0
- package/dist/primitives/button.js.map +1 -0
- package/dist/primitives/card.d.ts +12 -0
- package/dist/primitives/card.d.ts.map +1 -0
- package/dist/primitives/card.js +25 -0
- package/dist/primitives/card.js.map +1 -0
- package/dist/primitives/dialog.d.ts +18 -0
- package/dist/primitives/dialog.d.ts.map +1 -0
- package/dist/primitives/dialog.js +38 -0
- package/dist/primitives/dialog.js.map +1 -0
- package/dist/primitives/input.d.ts +4 -0
- package/dist/primitives/input.d.ts.map +1 -0
- package/dist/primitives/input.js +7 -0
- package/dist/primitives/input.js.map +1 -0
- package/dist/primitives/scroll-area.d.ts +6 -0
- package/dist/primitives/scroll-area.d.ts.map +1 -0
- package/dist/primitives/scroll-area.js +11 -0
- package/dist/primitives/scroll-area.js.map +1 -0
- package/dist/primitives/select.d.ts +16 -0
- package/dist/primitives/select.d.ts.map +1 -0
- package/dist/primitives/select.js +38 -0
- package/dist/primitives/select.js.map +1 -0
- package/dist/primitives/separator.d.ts +4 -0
- package/dist/primitives/separator.d.ts.map +1 -0
- package/dist/primitives/separator.js +9 -0
- package/dist/primitives/separator.js.map +1 -0
- package/dist/primitives/sheet.d.ts +15 -0
- package/dist/primitives/sheet.d.ts.map +1 -0
- package/dist/primitives/sheet.js +41 -0
- package/dist/primitives/sheet.js.map +1 -0
- package/dist/primitives/skeleton.d.ts +3 -0
- package/dist/primitives/skeleton.d.ts.map +1 -0
- package/dist/primitives/skeleton.js +7 -0
- package/dist/primitives/skeleton.js.map +1 -0
- package/dist/primitives/switch.d.ts +5 -0
- package/dist/primitives/switch.d.ts.map +1 -0
- package/dist/primitives/switch.js +8 -0
- package/dist/primitives/switch.js.map +1 -0
- package/dist/primitives/table.d.ts +11 -0
- package/dist/primitives/table.d.ts.map +1 -0
- package/dist/primitives/table.js +28 -0
- package/dist/primitives/table.js.map +1 -0
- package/dist/primitives/tabs.d.ts +12 -0
- package/dist/primitives/tabs.d.ts.map +1 -0
- package/dist/primitives/tabs.js +29 -0
- package/dist/primitives/tabs.js.map +1 -0
- package/dist/providers/query-provider.d.ts +11 -0
- package/dist/providers/query-provider.d.ts.map +1 -0
- package/dist/providers/query-provider.js +20 -0
- package/dist/providers/query-provider.js.map +1 -0
- package/dist/theme/create-theme.d.ts +130 -0
- package/dist/theme/create-theme.d.ts.map +1 -0
- package/dist/theme/create-theme.js +65 -0
- package/dist/theme/create-theme.js.map +1 -0
- package/dist/theme/index.d.ts +7 -0
- package/dist/theme/index.d.ts.map +1 -0
- package/dist/theme/index.js +7 -0
- package/dist/theme/index.js.map +1 -0
- package/dist/theme/presets.d.ts +25 -0
- package/dist/theme/presets.d.ts.map +1 -0
- package/dist/theme/presets.js +68 -0
- package/dist/theme/presets.js.map +1 -0
- package/dist/theme/theme-provider.d.ts +69 -0
- package/dist/theme/theme-provider.d.ts.map +1 -0
- package/dist/theme/theme-provider.js +79 -0
- package/dist/theme/theme-provider.js.map +1 -0
- package/package.json +71 -0
- package/src/app/adapt-data-widget.tsx +51 -0
- package/src/app/builder/CatalogueSheet.tsx +230 -0
- package/src/app/builder/PipelineStrip.tsx +260 -0
- package/src/app/builder/PreviewPane.tsx +72 -0
- package/src/app/builder/SaveDialog.tsx +95 -0
- package/src/app/builder/Toolbar.tsx +95 -0
- package/src/app/builder/Workspace.tsx +483 -0
- package/src/app/builder/builder-model.test.ts +351 -0
- package/src/app/builder/builder-model.ts +213 -0
- package/src/app/builder/builder-reducer.test.ts +441 -0
- package/src/app/builder/builder-reducer.ts +289 -0
- package/src/app/builder/labels.ts +139 -0
- package/src/app/builder/status-chip.tsx +37 -0
- package/src/app/host-bridge.test.ts +206 -0
- package/src/app/host-bridge.tsx +371 -0
- package/src/app/index.ts +51 -0
- package/src/app/layout-builder.tsx +872 -0
- package/src/app/mcp-app-view.tsx +367 -0
- package/src/app/mcp-toolkit-app.tsx +25 -0
- package/src/app/use-host-actions.test.ts +16 -0
- package/src/app/use-host-actions.tsx +88 -0
- package/src/app/widget-fixture.test.ts +108 -0
- package/src/app/widget-fixture.tsx +441 -0
- package/src/app/widget-props-sheet.tsx +285 -0
- package/src/app/widget-renderer.test.ts +42 -0
- package/src/app/widget-renderer.tsx +200 -0
- package/src/components/DrillButton.tsx +37 -0
- package/src/components/FilterBar.tsx +68 -0
- package/src/components/GridLayout.tsx +49 -0
- package/src/components/GroupCard.tsx +52 -0
- package/src/components/KpiGrid.tsx +134 -0
- package/src/components/ListFooter.tsx +48 -0
- package/src/components/Pills.tsx +45 -0
- package/src/components/SectionHeading.tsx +26 -0
- package/src/components/WidgetHeader.tsx +51 -0
- package/src/globals.css +168 -0
- package/src/hooks/index.ts +17 -0
- package/src/hooks/use-debounced-value.ts +15 -0
- package/src/hooks/use-mobile.ts +24 -0
- package/src/hooks/use-tool-query.ts +63 -0
- package/src/hooks/use-tool-result-recovery.test.ts +65 -0
- package/src/hooks/use-tool-result-recovery.ts +144 -0
- package/src/hooks/use-view-data.ts +31 -0
- package/src/hooks/use-view-tool-query.ts +38 -0
- package/src/index.ts +134 -0
- package/src/lib/parse-tool-result.test.ts +139 -0
- package/src/lib/parse-tool-result.ts +89 -0
- package/src/lib/tone-utils.test.ts +35 -0
- package/src/lib/tone-utils.ts +38 -0
- package/src/lib/utils.ts +6 -0
- package/src/locale/index.ts +7 -0
- package/src/locale/locale-provider.tsx +81 -0
- package/src/primitives/alert.tsx +60 -0
- package/src/primitives/badge.tsx +49 -0
- package/src/primitives/button.tsx +62 -0
- package/src/primitives/card.tsx +92 -0
- package/src/primitives/dialog.tsx +144 -0
- package/src/primitives/input.tsx +21 -0
- package/src/primitives/scroll-area.tsx +54 -0
- package/src/primitives/select.tsx +173 -0
- package/src/primitives/separator.tsx +21 -0
- package/src/primitives/sheet.tsx +134 -0
- package/src/primitives/skeleton.tsx +13 -0
- package/src/primitives/switch.tsx +24 -0
- package/src/primitives/table.tsx +87 -0
- package/src/primitives/tabs.tsx +79 -0
- package/src/providers/query-provider.tsx +38 -0
- package/src/theme/create-theme.test.ts +108 -0
- package/src/theme/create-theme.ts +158 -0
- package/src/theme/index.ts +9 -0
- package/src/theme/presets.ts +82 -0
- package/src/theme/theme-provider.tsx +152 -0
- package/src/ui-catalog.test.ts +112 -0
- 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
|
+
})
|