@miragon/mcp-toolkit-ui 2.7.0 → 2.8.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 +7 -4
- package/dist/app/adapt-data-widget.d.ts +1 -22
- package/dist/app/adapt-data-widget.d.ts.map +1 -1
- package/dist/app/adapt-data-widget.js +1 -35
- package/dist/app/adapt-data-widget.js.map +1 -1
- package/dist/app/builder/BuilderBanners.js +1 -1
- package/dist/app/builder/BuilderBanners.js.map +1 -1
- package/dist/app/builder/CanvasRow.d.ts +4 -2
- package/dist/app/builder/CanvasRow.d.ts.map +1 -1
- package/dist/app/builder/CanvasRow.js +21 -7
- package/dist/app/builder/CanvasRow.js.map +1 -1
- package/dist/app/builder/CatalogueSheet.d.ts.map +1 -1
- package/dist/app/builder/CatalogueSheet.js +11 -4
- package/dist/app/builder/CatalogueSheet.js.map +1 -1
- package/dist/app/builder/CellPropsSheet.d.ts +7 -3
- package/dist/app/builder/CellPropsSheet.d.ts.map +1 -1
- package/dist/app/builder/CellPropsSheet.js +10 -3
- package/dist/app/builder/CellPropsSheet.js.map +1 -1
- package/dist/app/builder/DiscardDialog.d.ts +18 -0
- package/dist/app/builder/DiscardDialog.d.ts.map +1 -0
- package/dist/app/builder/DiscardDialog.js +18 -0
- package/dist/app/builder/DiscardDialog.js.map +1 -0
- package/dist/app/builder/LayoutSurface.d.ts +33 -9
- package/dist/app/builder/LayoutSurface.d.ts.map +1 -1
- package/dist/app/builder/LayoutSurface.js +74 -23
- package/dist/app/builder/LayoutSurface.js.map +1 -1
- package/dist/app/builder/PipelineStrip.d.ts +2 -1
- package/dist/app/builder/PipelineStrip.d.ts.map +1 -1
- package/dist/app/builder/PipelineStrip.js +26 -13
- package/dist/app/builder/PipelineStrip.js.map +1 -1
- package/dist/app/builder/SaveDialog.d.ts +9 -1
- package/dist/app/builder/SaveDialog.d.ts.map +1 -1
- package/dist/app/builder/SaveDialog.js +8 -2
- package/dist/app/builder/SaveDialog.js.map +1 -1
- package/dist/app/builder/Toolbar.d.ts +12 -4
- package/dist/app/builder/Toolbar.d.ts.map +1 -1
- package/dist/app/builder/Toolbar.js +18 -7
- package/dist/app/builder/Toolbar.js.map +1 -1
- package/dist/app/builder/Workspace.d.ts +23 -3
- package/dist/app/builder/Workspace.d.ts.map +1 -1
- package/dist/app/builder/Workspace.js +58 -8
- package/dist/app/builder/Workspace.js.map +1 -1
- package/dist/app/builder/builder-model.d.ts +143 -1
- package/dist/app/builder/builder-model.d.ts.map +1 -1
- package/dist/app/builder/builder-model.js +191 -1
- package/dist/app/builder/builder-model.js.map +1 -1
- package/dist/app/builder/reveal.d.ts +10 -0
- package/dist/app/builder/reveal.d.ts.map +1 -0
- package/dist/app/builder/reveal.js +10 -0
- package/dist/app/builder/reveal.js.map +1 -0
- package/dist/app/builder/use-builder-catalogue.d.ts +4 -1
- package/dist/app/builder/use-builder-catalogue.d.ts.map +1 -1
- package/dist/app/builder/use-builder-catalogue.js +44 -17
- package/dist/app/builder/use-builder-catalogue.js.map +1 -1
- package/dist/app/builder/use-builder-session.d.ts +30 -0
- package/dist/app/builder/use-builder-session.d.ts.map +1 -0
- package/dist/app/builder/use-builder-session.js +58 -0
- package/dist/app/builder/use-builder-session.js.map +1 -0
- package/dist/app/builder/use-display-resolver.d.ts +17 -0
- package/dist/app/builder/use-display-resolver.d.ts.map +1 -0
- package/dist/app/builder/use-display-resolver.js +16 -0
- package/dist/app/builder/use-display-resolver.js.map +1 -0
- package/dist/app/index.d.ts +8 -3
- package/dist/app/index.d.ts.map +1 -1
- package/dist/app/index.js +8 -2
- package/dist/app/index.js.map +1 -1
- package/dist/app/layout-builder.d.ts +13 -25
- package/dist/app/layout-builder.d.ts.map +1 -1
- package/dist/app/layout-builder.js +75 -81
- package/dist/app/layout-builder.js.map +1 -1
- package/dist/app/mcp-app-view-chrome.d.ts +12 -3
- package/dist/app/mcp-app-view-chrome.d.ts.map +1 -1
- package/dist/app/mcp-app-view-chrome.js +21 -5
- package/dist/app/mcp-app-view-chrome.js.map +1 -1
- package/dist/app/mcp-app-view-states.d.ts +11 -3
- package/dist/app/mcp-app-view-states.d.ts.map +1 -1
- package/dist/app/mcp-app-view-states.js +29 -8
- package/dist/app/mcp-app-view-states.js.map +1 -1
- package/dist/app/mcp-app-view.d.ts +33 -1
- package/dist/app/mcp-app-view.d.ts.map +1 -1
- package/dist/app/mcp-app-view.js +181 -127
- package/dist/app/mcp-app-view.js.map +1 -1
- package/dist/app/mcp-toolkit-app.d.ts +6 -1
- package/dist/app/mcp-toolkit-app.d.ts.map +1 -1
- package/dist/app/mcp-toolkit-app.js +8 -1
- package/dist/app/mcp-toolkit-app.js.map +1 -1
- package/dist/app/mcp-use-host-bridge.d.ts +15 -6
- package/dist/app/mcp-use-host-bridge.d.ts.map +1 -1
- package/dist/app/mcp-use-host-bridge.js +51 -22
- package/dist/app/mcp-use-host-bridge.js.map +1 -1
- package/dist/app/model-context.d.ts +71 -0
- package/dist/app/model-context.d.ts.map +1 -0
- package/dist/app/model-context.js +101 -0
- package/dist/app/model-context.js.map +1 -0
- package/dist/app/use-build-mode-focus.d.ts +33 -0
- package/dist/app/use-build-mode-focus.d.ts.map +1 -0
- package/dist/app/use-build-mode-focus.js +59 -0
- package/dist/app/use-build-mode-focus.js.map +1 -0
- package/dist/app/use-view-actions.d.ts +31 -0
- package/dist/app/use-view-actions.d.ts.map +1 -0
- package/dist/app/use-view-actions.js +81 -0
- package/dist/app/use-view-actions.js.map +1 -0
- package/dist/app/use-view-lifecycle.d.ts +4 -0
- package/dist/app/use-view-lifecycle.d.ts.map +1 -1
- package/dist/app/use-view-lifecycle.js +2 -0
- package/dist/app/use-view-lifecycle.js.map +1 -1
- package/dist/app/view-lifecycle.d.ts +80 -4
- package/dist/app/view-lifecycle.d.ts.map +1 -1
- package/dist/app/view-lifecycle.js +90 -3
- package/dist/app/view-lifecycle.js.map +1 -1
- package/dist/app/widget-fixture.d.ts +10 -3
- package/dist/app/widget-fixture.d.ts.map +1 -1
- package/dist/app/widget-fixture.js +21 -8
- package/dist/app/widget-fixture.js.map +1 -1
- package/dist/app/widget-props-sheet.d.ts +3 -1
- package/dist/app/widget-props-sheet.d.ts.map +1 -1
- package/dist/app/widget-props-sheet.js +22 -10
- package/dist/app/widget-props-sheet.js.map +1 -1
- package/dist/app/widget-renderer.js +1 -1
- package/dist/app/widget-renderer.js.map +1 -1
- package/dist/components/DrillButton.d.ts +14 -6
- package/dist/components/DrillButton.d.ts.map +1 -1
- package/dist/components/DrillButton.js +14 -7
- package/dist/components/DrillButton.js.map +1 -1
- package/dist/components/EmptyState.d.ts.map +1 -1
- package/dist/components/EmptyState.js +4 -2
- package/dist/components/EmptyState.js.map +1 -1
- package/dist/components/ErrorState.d.ts +5 -2
- package/dist/components/ErrorState.d.ts.map +1 -1
- package/dist/components/ErrorState.js +25 -8
- package/dist/components/ErrorState.js.map +1 -1
- package/dist/components/FilterBar.d.ts +15 -4
- package/dist/components/FilterBar.d.ts.map +1 -1
- package/dist/components/FilterBar.js +11 -6
- package/dist/components/FilterBar.js.map +1 -1
- package/dist/components/KpiGrid.d.ts +66 -21
- package/dist/components/KpiGrid.d.ts.map +1 -1
- package/dist/components/KpiGrid.js +101 -34
- package/dist/components/KpiGrid.js.map +1 -1
- package/dist/components/ListFooter.d.ts +31 -4
- package/dist/components/ListFooter.d.ts.map +1 -1
- package/dist/components/ListFooter.js +30 -11
- package/dist/components/ListFooter.js.map +1 -1
- package/dist/components/PagedListFooter.d.ts +57 -0
- package/dist/components/PagedListFooter.d.ts.map +1 -0
- package/dist/components/PagedListFooter.js +27 -0
- package/dist/components/PagedListFooter.js.map +1 -0
- package/dist/components/Pills.d.ts +27 -7
- package/dist/components/Pills.d.ts.map +1 -1
- package/dist/components/Pills.js +21 -6
- package/dist/components/Pills.js.map +1 -1
- package/dist/components/SectionHeading.d.ts +3 -1
- package/dist/components/SectionHeading.d.ts.map +1 -1
- package/dist/components/SectionHeading.js +6 -2
- package/dist/components/SectionHeading.js.map +1 -1
- package/dist/components/Toast.d.ts +9 -3
- package/dist/components/Toast.d.ts.map +1 -1
- package/dist/components/Toast.js +31 -16
- package/dist/components/Toast.js.map +1 -1
- package/dist/components/WidgetHeader.d.ts +34 -10
- package/dist/components/WidgetHeader.d.ts.map +1 -1
- package/dist/components/WidgetHeader.js +11 -6
- package/dist/components/WidgetHeader.js.map +1 -1
- package/dist/hooks/index.d.ts +4 -4
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/index.js +1 -1
- package/dist/hooks/index.js.map +1 -1
- package/dist/hooks/tool-result-recovery-model.d.ts +155 -0
- package/dist/hooks/tool-result-recovery-model.d.ts.map +1 -0
- package/dist/hooks/tool-result-recovery-model.js +273 -0
- package/dist/hooks/tool-result-recovery-model.js.map +1 -0
- package/dist/hooks/use-tool-query.d.ts +56 -14
- package/dist/hooks/use-tool-query.d.ts.map +1 -1
- package/dist/hooks/use-tool-query.js +102 -25
- package/dist/hooks/use-tool-query.js.map +1 -1
- package/dist/hooks/use-tool-result-recovery.d.ts +47 -52
- package/dist/hooks/use-tool-result-recovery.d.ts.map +1 -1
- package/dist/hooks/use-tool-result-recovery.js +58 -120
- package/dist/hooks/use-tool-result-recovery.js.map +1 -1
- package/dist/hooks/use-view-data.d.ts +52 -10
- package/dist/hooks/use-view-data.d.ts.map +1 -1
- package/dist/hooks/use-view-data.js +81 -16
- package/dist/hooks/use-view-data.js.map +1 -1
- package/dist/hooks/use-view-tool-query.d.ts +12 -7
- package/dist/hooks/use-view-tool-query.d.ts.map +1 -1
- package/dist/hooks/use-view-tool-query.js +15 -14
- package/dist/hooks/use-view-tool-query.js.map +1 -1
- package/dist/index.d.ts +17 -10
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +18 -6
- package/dist/index.js.map +1 -1
- package/dist/lib/focus-return.d.ts +61 -0
- package/dist/lib/focus-return.d.ts.map +1 -0
- package/dist/lib/focus-return.js +46 -0
- package/dist/lib/focus-return.js.map +1 -0
- package/dist/lib/parse-tool-result.d.ts +23 -1
- package/dist/lib/parse-tool-result.d.ts.map +1 -1
- package/dist/lib/parse-tool-result.js +38 -2
- package/dist/lib/parse-tool-result.js.map +1 -1
- package/dist/lib/toast-model.d.ts +41 -1
- package/dist/lib/toast-model.d.ts.map +1 -1
- package/dist/lib/toast-model.js +67 -1
- package/dist/lib/toast-model.js.map +1 -1
- package/dist/lib/tone-utils.d.ts +58 -6
- package/dist/lib/tone-utils.d.ts.map +1 -1
- package/dist/lib/tone-utils.js +66 -4
- package/dist/lib/tone-utils.js.map +1 -1
- package/dist/lib/tool-query.d.ts +107 -0
- package/dist/lib/tool-query.d.ts.map +1 -0
- package/dist/lib/tool-query.js +182 -0
- package/dist/lib/tool-query.js.map +1 -0
- package/dist/lib/tool-result-observer.d.ts +108 -0
- package/dist/lib/tool-result-observer.d.ts.map +1 -0
- package/dist/lib/tool-result-observer.js +158 -0
- package/dist/lib/tool-result-observer.js.map +1 -0
- package/dist/lib/typography.d.ts +37 -0
- package/dist/lib/typography.d.ts.map +1 -0
- package/dist/lib/typography.js +41 -0
- package/dist/lib/typography.js.map +1 -0
- package/dist/lib/utils.d.ts.map +1 -1
- package/dist/lib/utils.js +11 -1
- package/dist/lib/utils.js.map +1 -1
- package/dist/locale/index.d.ts +1 -1
- package/dist/locale/index.d.ts.map +1 -1
- package/dist/locale/message-types.d.ts +117 -4
- package/dist/locale/message-types.d.ts.map +1 -1
- package/dist/locale/messages-de.d.ts.map +1 -1
- package/dist/locale/messages-de.js +44 -10
- package/dist/locale/messages-de.js.map +1 -1
- package/dist/locale/messages-en.d.ts.map +1 -1
- package/dist/locale/messages-en.js +44 -10
- package/dist/locale/messages-en.js.map +1 -1
- package/dist/locale/toolkit-messages.d.ts +19 -1
- package/dist/locale/toolkit-messages.d.ts.map +1 -1
- package/dist/locale/toolkit-messages.js +54 -1
- package/dist/locale/toolkit-messages.js.map +1 -1
- package/dist/primitives/alert.d.ts +1 -1
- package/dist/primitives/alert.d.ts.map +1 -1
- package/dist/primitives/alert.js +2 -0
- package/dist/primitives/alert.js.map +1 -1
- package/dist/primitives/badge.d.ts +1 -1
- package/dist/primitives/badge.d.ts.map +1 -1
- package/dist/primitives/badge.js +9 -4
- package/dist/primitives/badge.js.map +1 -1
- package/dist/primitives/button.d.ts +1 -1
- package/dist/primitives/button.js +1 -1
- package/dist/primitives/button.js.map +1 -1
- package/dist/primitives/dialog.d.ts +1 -1
- package/dist/primitives/dialog.d.ts.map +1 -1
- package/dist/primitives/dialog.js +10 -3
- package/dist/primitives/dialog.js.map +1 -1
- package/dist/primitives/select.js +1 -1
- package/dist/primitives/select.js.map +1 -1
- package/dist/primitives/sheet.d.ts +1 -1
- package/dist/primitives/sheet.d.ts.map +1 -1
- package/dist/primitives/sheet.js +6 -2
- package/dist/primitives/sheet.js.map +1 -1
- package/dist/providers/query-provider.d.ts +32 -1
- package/dist/providers/query-provider.d.ts.map +1 -1
- package/dist/providers/query-provider.js +53 -4
- package/dist/providers/query-provider.js.map +1 -1
- package/dist/render-cases-primitives.js +1 -1
- package/dist/render-cases-primitives.js.map +1 -1
- package/dist/render-cases.d.ts.map +1 -1
- package/dist/render-cases.js +30 -6
- package/dist/render-cases.js.map +1 -1
- package/dist/render-effects.d.ts +42 -0
- package/dist/render-effects.d.ts.map +1 -0
- package/dist/render-effects.js +81 -0
- package/dist/render-effects.js.map +1 -0
- package/dist/theme/create-theme.d.ts +19 -1
- package/dist/theme/create-theme.d.ts.map +1 -1
- package/dist/theme/create-theme.js +15 -0
- package/dist/theme/create-theme.js.map +1 -1
- package/package.json +5 -4
- package/src/app/adapt-data-widget.tsx +2 -42
- package/src/app/builder/BuilderBanners.tsx +1 -1
- package/src/app/builder/CanvasRow.tsx +63 -17
- package/src/app/builder/CatalogueSheet.tsx +50 -29
- package/src/app/builder/CellPropsSheet.tsx +17 -2
- package/src/app/builder/DiscardDialog.tsx +59 -0
- package/src/app/builder/LayoutSurface.tsx +206 -66
- package/src/app/builder/PipelineStrip.tsx +253 -153
- package/src/app/builder/SaveDialog.tsx +14 -2
- package/src/app/builder/Toolbar.tsx +144 -59
- package/src/app/builder/Workspace.tsx +226 -58
- package/src/app/builder/builder-model.ts +341 -2
- package/src/app/builder/builder-model.ux.test.ts +456 -0
- package/src/app/builder/builder-ux.test.tsx +844 -0
- package/src/app/builder/reveal.ts +10 -0
- package/src/app/builder/use-builder-catalogue.ts +56 -19
- package/src/app/builder/use-builder-session.ts +68 -0
- package/src/app/builder/use-display-resolver.ts +24 -0
- package/src/app/index.ts +12 -2
- package/src/app/layout-builder.tsx +178 -191
- package/src/app/mcp-app-view-chrome.test.tsx +141 -1
- package/src/app/mcp-app-view-chrome.tsx +56 -6
- package/src/app/mcp-app-view-states.tsx +51 -11
- package/src/app/mcp-app-view.effects.test.tsx +292 -0
- package/src/app/mcp-app-view.test.tsx +222 -2
- package/src/app/mcp-app-view.tsx +267 -117
- package/src/app/mcp-toolkit-app.test.tsx +40 -0
- package/src/app/mcp-toolkit-app.tsx +8 -1
- package/src/app/mcp-use-host-bridge.test.tsx +180 -0
- package/src/app/mcp-use-host-bridge.tsx +67 -24
- package/src/app/model-context.test.tsx +116 -0
- package/src/app/model-context.tsx +125 -0
- package/src/app/use-build-mode-focus.ts +62 -0
- package/src/app/use-view-actions.ts +89 -0
- package/src/app/use-view-lifecycle.ts +6 -0
- package/src/app/view-lifecycle.test.ts +216 -0
- package/src/app/view-lifecycle.ts +165 -5
- package/src/app/widget-fixture.effects.test.tsx +51 -0
- package/src/app/widget-fixture.render.test.tsx +81 -0
- package/src/app/widget-fixture.tsx +34 -11
- package/src/app/widget-props-sheet.tsx +57 -16
- package/src/app/widget-renderer.tsx +1 -1
- package/src/components/DrillButton.test.tsx +48 -0
- package/src/components/DrillButton.tsx +26 -11
- package/src/components/EmptyState.tsx +6 -4
- package/src/components/ErrorState.tsx +31 -9
- package/src/components/FilterBar.test.tsx +82 -0
- package/src/components/FilterBar.tsx +52 -26
- package/src/components/KpiGrid.test.tsx +279 -0
- package/src/components/KpiGrid.tsx +297 -90
- package/src/components/ListFooter.test.tsx +116 -0
- package/src/components/ListFooter.tsx +102 -15
- package/src/components/PagedListFooter.test.tsx +140 -0
- package/src/components/PagedListFooter.tsx +107 -0
- package/src/components/Pills.test.tsx +77 -0
- package/src/components/Pills.tsx +63 -10
- package/src/components/SectionHeading.tsx +9 -3
- package/src/components/Toast.tsx +37 -19
- package/src/components/WidgetHeader.test.tsx +78 -0
- package/src/components/WidgetHeader.tsx +63 -16
- package/src/components/feedback-states.test.tsx +98 -9
- package/src/components/tone-figures.test.tsx +8 -4
- package/src/hooks/index.ts +6 -3
- package/src/hooks/tool-result-recovery-model.test.ts +744 -0
- package/src/hooks/tool-result-recovery-model.ts +418 -0
- package/src/hooks/use-tool-query.effects.test.tsx +205 -0
- package/src/hooks/use-tool-query.ts +156 -33
- package/src/hooks/use-tool-result-recovery.effects.test.tsx +141 -0
- package/src/hooks/use-tool-result-recovery.test.ts +3 -4
- package/src/hooks/use-tool-result-recovery.ts +84 -142
- package/src/hooks/use-view-data.test.tsx +118 -9
- package/src/hooks/use-view-data.ts +128 -18
- package/src/hooks/use-view-tool-query.ts +25 -19
- package/src/i18n-coverage.test.tsx +104 -2
- package/src/index.ts +48 -11
- package/src/lib/focus-return.test.ts +288 -0
- package/src/lib/focus-return.ts +96 -0
- package/src/lib/parse-tool-result.test.ts +46 -1
- package/src/lib/parse-tool-result.ts +40 -2
- package/src/lib/toast-model.test.ts +166 -1
- package/src/lib/toast-model.ts +104 -2
- package/src/lib/tone-utils.test.ts +86 -10
- package/src/lib/tone-utils.ts +89 -8
- package/src/lib/tool-query.test.ts +569 -0
- package/src/lib/tool-query.ts +253 -0
- package/src/lib/tool-result-observer.test.ts +296 -0
- package/src/lib/tool-result-observer.ts +211 -0
- package/src/lib/typography.test.ts +45 -0
- package/src/lib/typography.ts +47 -0
- package/src/lib/utils.ts +12 -1
- package/src/locale/index.ts +1 -0
- package/src/locale/message-types.ts +118 -2
- package/src/locale/messages-de.ts +49 -10
- package/src/locale/messages-en.ts +49 -10
- package/src/locale/toolkit-messages.test.ts +133 -0
- package/src/locale/toolkit-messages.ts +63 -2
- package/src/primitives/alert.tsx +3 -0
- package/src/primitives/badge.tsx +9 -4
- package/src/primitives/button.tsx +1 -1
- package/src/primitives/dialog.tsx +10 -1
- package/src/primitives/select.tsx +1 -1
- package/src/primitives/sheet.tsx +7 -0
- package/src/providers/query-provider.test.tsx +154 -0
- package/src/providers/query-provider.tsx +66 -5
- package/src/render-cases-primitives.tsx +1 -1
- package/src/render-cases.tsx +38 -6
- package/src/render-effects.tsx +96 -0
- package/src/theme/cd-tokens.test.ts +3 -1
- package/src/theme/create-theme.test.ts +10 -0
- package/src/theme/create-theme.ts +28 -0
- package/src/theme/theme-css.test.ts +51 -0
- package/src/theme.css +31 -1
- package/ui-catalog.json +448 -41
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { renderToStaticMarkup } from "react-dom/server"
|
|
2
|
+
import { describe, expect, it } from "vitest"
|
|
3
|
+
import { Check } from "lucide-react"
|
|
4
|
+
import { ListFooter } from "../components/ListFooter.js"
|
|
5
|
+
import { ICON_STROKE } from "../lib/icon-stroke.js"
|
|
6
|
+
import { LocaleProvider, useLocale } from "../locale/locale-provider.js"
|
|
7
|
+
import { useHostBridge } from "./host-bridge.js"
|
|
8
|
+
import { WidgetFixtureHost } from "./widget-fixture.js"
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* The harness gives a widget what `McpAppView` gives it in a host, so a
|
|
12
|
+
* preview in the widget playground shows what a user sees: the toolkit's
|
|
13
|
+
* strings in the host locale (`globals.locale`), the host theme on the
|
|
14
|
+
* bridge (`globals.theme`), the CI icon stroke and a toast stack.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/** A widget made of toolkit chrome: the list footer's text follows the toolkit locale. */
|
|
18
|
+
function Footer() {
|
|
19
|
+
return <ListFooter shown={8} total={1234} hasMore onLoadMore={() => {}} noun="Aufgaben" />
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const render = (globals?: Record<string, unknown>, widget = Footer) =>
|
|
23
|
+
renderToStaticMarkup(<WidgetFixtureHost widget={widget} globals={globals} />)
|
|
24
|
+
|
|
25
|
+
describe("WidgetFixtureHost host scope", () => {
|
|
26
|
+
it("renders the toolkit's strings and number format in globals.locale", () => {
|
|
27
|
+
const html = render({ locale: "de-DE" })
|
|
28
|
+
expect(html).toContain("Zeigt 8 von 1.234 Aufgaben")
|
|
29
|
+
expect(html).toContain("Mehr laden")
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
it("defaults to English and falls back to it for a blank locale", () => {
|
|
33
|
+
for (const globals of [undefined, { locale: "" }, { locale: 42 }]) {
|
|
34
|
+
const html = render(globals)
|
|
35
|
+
expect(html).toContain("Showing 8 of 1,234 Aufgaben")
|
|
36
|
+
expect(html).toContain("Load more")
|
|
37
|
+
}
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
it("lets a LocaleProvider around the harness win over globals.locale", () => {
|
|
41
|
+
const html = renderToStaticMarkup(
|
|
42
|
+
<LocaleProvider locale="de">
|
|
43
|
+
<WidgetFixtureHost widget={Footer} globals={{ locale: "en-US" }} />
|
|
44
|
+
</LocaleProvider>,
|
|
45
|
+
)
|
|
46
|
+
expect(html).toContain("Mehr laden")
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
it("leaves useLocale() of the widget alone, like McpAppView does", () => {
|
|
50
|
+
function ShowsLocale() {
|
|
51
|
+
return <p>{useLocale()}</p>
|
|
52
|
+
}
|
|
53
|
+
expect(render({ locale: "de-DE" }, ShowsLocale)).toContain("<p>en</p>")
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
it("reports globals.theme on the bridge", () => {
|
|
57
|
+
function ShowsTheme() {
|
|
58
|
+
return <p>{useHostBridge().theme}</p>
|
|
59
|
+
}
|
|
60
|
+
expect(render({ theme: "dark" }, ShowsTheme)).toContain("<p>dark</p>")
|
|
61
|
+
expect(render(undefined, ShowsTheme)).toContain("<p>light</p>")
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
it("draws Lucide icons at the view's stroke", () => {
|
|
65
|
+
expect(render(undefined, () => <Check />)).toContain(`stroke-width="${ICON_STROKE}"`)
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
it("mounts the toast stack as the view's one polite live region", () => {
|
|
69
|
+
// Like McpAppView's: fixed attributes, no role and no name, so the first
|
|
70
|
+
// toast lands in a region the browser already tracks. Its words (the
|
|
71
|
+
// dismiss label in the host locale) need a toast, so effects:
|
|
72
|
+
// widget-fixture.effects.test.tsx.
|
|
73
|
+
const html = render()
|
|
74
|
+
const viewport = /<div data-slot="toast-viewport"[^>]*>/.exec(html)?.[0]
|
|
75
|
+
expect(viewport).toBeDefined()
|
|
76
|
+
expect(viewport).toContain('aria-live="polite"')
|
|
77
|
+
expect(viewport).toContain('aria-atomic="false"')
|
|
78
|
+
expect(viewport).not.toContain("aria-label")
|
|
79
|
+
expect(viewport).not.toContain("role=")
|
|
80
|
+
})
|
|
81
|
+
})
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import { createContext, useContext, useMemo, type ComponentType, type ReactNode } from "react"
|
|
2
|
+
import { LucideProvider } from "lucide-react"
|
|
2
3
|
import type { PipelineContext, StepResult, WidgetProps } from "@miragon/mcp-toolkit-core"
|
|
4
|
+
import { ToastProvider } from "../components/Toast.js"
|
|
5
|
+
import { ICON_STROKE } from "../lib/icon-stroke.js"
|
|
6
|
+
import { ToolkitLocaleFallback } from "../locale/locale-provider.js"
|
|
3
7
|
import { AppQueryProvider } from "../providers/query-provider.js"
|
|
4
8
|
import { HostBridgeProvider, createStandaloneHostBridge } from "./host-bridge.js"
|
|
5
9
|
|
|
@@ -143,10 +147,10 @@ export function useFixtureHost(): FixtureHostValue | null {
|
|
|
143
147
|
}
|
|
144
148
|
|
|
145
149
|
/**
|
|
146
|
-
* Default host-context values the harness reports.
|
|
147
|
-
* the mcp-use 2.x move (the other host globals were consumed by
|
|
148
|
-
* `window.openai` shim); the object keeps its shape so existing
|
|
149
|
-
* setups that spread overrides keep compiling.
|
|
150
|
+
* Default host-context values the harness reports. `theme` and `locale` are
|
|
151
|
+
* read since the mcp-use 2.x move (the other host globals were consumed by
|
|
152
|
+
* the 1.x `window.openai` shim); the object keeps its shape so existing
|
|
153
|
+
* playground setups that spread overrides keep compiling.
|
|
150
154
|
*/
|
|
151
155
|
const DEFAULT_GLOBALS = {
|
|
152
156
|
theme: "light",
|
|
@@ -192,9 +196,12 @@ export interface WidgetFixtureHostProps {
|
|
|
192
196
|
*/
|
|
193
197
|
tools?: Record<string, FixtureToolEntry>
|
|
194
198
|
/**
|
|
195
|
-
* Host context overrides, merged over {@link DEFAULT_GLOBALS}.
|
|
196
|
-
*
|
|
197
|
-
*
|
|
199
|
+
* Host context overrides, merged over {@link DEFAULT_GLOBALS}. Two keys
|
|
200
|
+
* influence the simulated host: `theme` (`"light"` | `"dark"`, reported by
|
|
201
|
+
* `useHostBridge().theme`) and `locale` (a BCP 47 tag: the toolkit's own
|
|
202
|
+
* strings and number format follow it, exactly as under `McpAppView`; a
|
|
203
|
+
* `LocaleProvider` around or inside the widget still wins). The other keys
|
|
204
|
+
* are accepted for compatibility with existing playground setups.
|
|
198
205
|
*/
|
|
199
206
|
globals?: Record<string, unknown>
|
|
200
207
|
/**
|
|
@@ -247,6 +254,10 @@ export function buildFixtureWidgetProps(
|
|
|
247
254
|
* would (adapted widgets report through this bridge outside a real view).
|
|
248
255
|
* - **The toolkit `AppQueryProvider`** — so widgets that fetch via `useToolQuery`
|
|
249
256
|
* (e.g. generated `use*` hooks) resolve against the same registry.
|
|
257
|
+
* - **What `McpAppView` gives a widget** — the toolkit's strings in the host
|
|
258
|
+
* locale (`globals.locale`, as `McpAppView` does with the host's), the
|
|
259
|
+
* toast stack (`useToast()` shows real toasts) and the icon stroke of the
|
|
260
|
+
* view (2.5), so the preview matches what a host renders.
|
|
250
261
|
*
|
|
251
262
|
* ### Boundaries
|
|
252
263
|
* The harness simulates the **host-portable pattern** (`useHostBridge`,
|
|
@@ -282,10 +293,14 @@ export function WidgetFixtureHost({
|
|
|
282
293
|
// The standalone host bridge is the harness's single simulation source: it
|
|
283
294
|
// logs every verb through the `HostActionLog` channel for the playground's
|
|
284
295
|
// activity log. Depends on the callback props directly — a fresh Provider
|
|
285
|
-
// value on callback change is fine for a dev harness.
|
|
286
|
-
//
|
|
287
|
-
// `window.openai` shim).
|
|
296
|
+
// value on callback change is fine for a dev harness. `globals.theme` and
|
|
297
|
+
// `globals.locale` influence the simulated host (the remaining globals fed
|
|
298
|
+
// the 1.x `window.openai` shim).
|
|
288
299
|
const bridgeTheme = (globals?.theme ?? DEFAULT_GLOBALS.theme) === "dark" ? "dark" : "light"
|
|
300
|
+
const hostLocale =
|
|
301
|
+
typeof globals?.locale === "string" && globals.locale.trim() !== ""
|
|
302
|
+
? globals.locale
|
|
303
|
+
: DEFAULT_GLOBALS.locale
|
|
289
304
|
const bridge = useMemo(
|
|
290
305
|
() =>
|
|
291
306
|
createStandaloneHostBridge({
|
|
@@ -308,11 +323,19 @@ export function WidgetFixtureHost({
|
|
|
308
323
|
// widgets and raw `({ data })` widgets read their fixture from one mount.
|
|
309
324
|
const RenderWidget = Widget as ComponentType<Record<string, unknown>>
|
|
310
325
|
|
|
326
|
+
// The same scope `McpAppView` puts around a widget: the toolkit's strings
|
|
327
|
+
// in the host locale, the CI icon stroke and a toast stack.
|
|
311
328
|
return (
|
|
312
329
|
<FixtureHostContext.Provider value={value}>
|
|
313
330
|
<HostBridgeProvider bridge={bridge}>
|
|
314
331
|
<AppQueryProvider callTool={callTool}>
|
|
315
|
-
<
|
|
332
|
+
<ToolkitLocaleFallback locale={hostLocale}>
|
|
333
|
+
<LucideProvider strokeWidth={ICON_STROKE}>
|
|
334
|
+
<ToastProvider>
|
|
335
|
+
<RenderWidget {...widgetProps} data={data} />
|
|
336
|
+
</ToastProvider>
|
|
337
|
+
</LucideProvider>
|
|
338
|
+
</ToolkitLocaleFallback>
|
|
316
339
|
</AppQueryProvider>
|
|
317
340
|
</HostBridgeProvider>
|
|
318
341
|
</FixtureHostContext.Provider>
|
|
@@ -18,6 +18,8 @@ import {
|
|
|
18
18
|
import { Switch } from "../primitives/switch.js"
|
|
19
19
|
import { useToolkitMessages } from "../locale/locale-provider.js"
|
|
20
20
|
import type { PropsSheetMessages } from "../locale/toolkit-messages.js"
|
|
21
|
+
import { propFieldText, type PropFieldText } from "./builder/builder-model.js"
|
|
22
|
+
import { useDisplayResolver } from "./builder/use-display-resolver.js"
|
|
21
23
|
|
|
22
24
|
/**
|
|
23
25
|
* Tiny JSON-Schema-driven editor for a layout cell's `props`. The
|
|
@@ -31,6 +33,12 @@ import type { PropsSheetMessages } from "../locale/toolkit-messages.js"
|
|
|
31
33
|
* - Nested objects/arrays/$ref/anyOf are not supported — the field
|
|
32
34
|
* shows an "edit JSON manually" hint so authors know to fall back.
|
|
33
35
|
*
|
|
36
|
+
* Each field is labelled in user words: the property's `x-display` title
|
|
37
|
+
* (`PROPS_SCHEMA_DISPLAY_KEYWORD`, a key or per-locale text), else its JSON
|
|
38
|
+
* Schema `title`, else its name; the raw property name then follows as a
|
|
39
|
+
* quiet hint, and the help line is the `x-display` summary or the
|
|
40
|
+
* `description`.
|
|
41
|
+
*
|
|
34
42
|
* The sheet writes a clean `Record<string, unknown>` (empty values
|
|
35
43
|
* stripped) and reports `undefined` when the user clears every field, so
|
|
36
44
|
* the cell's `props` field stays absent rather than `{}` — matches the
|
|
@@ -39,6 +47,7 @@ import type { PropsSheetMessages } from "../locale/toolkit-messages.js"
|
|
|
39
47
|
|
|
40
48
|
interface JsonSchemaProperty {
|
|
41
49
|
type?: string
|
|
50
|
+
title?: string
|
|
42
51
|
description?: string
|
|
43
52
|
enum?: unknown[]
|
|
44
53
|
default?: unknown
|
|
@@ -59,6 +68,8 @@ export interface WidgetPropsSheetProps {
|
|
|
59
68
|
open: boolean
|
|
60
69
|
onOpenChange: (open: boolean) => void
|
|
61
70
|
widgetId: string
|
|
71
|
+
/** The widget's human name (its title); the id then shows as a quiet hint under it. */
|
|
72
|
+
widgetTitle?: string
|
|
62
73
|
schema: Record<string, unknown> | undefined
|
|
63
74
|
value: Record<string, unknown> | undefined
|
|
64
75
|
onApply: (next: Record<string, unknown> | undefined) => void
|
|
@@ -69,12 +80,14 @@ export function WidgetPropsSheet({
|
|
|
69
80
|
open,
|
|
70
81
|
onOpenChange,
|
|
71
82
|
widgetId,
|
|
83
|
+
widgetTitle,
|
|
72
84
|
schema,
|
|
73
85
|
value,
|
|
74
86
|
onApply,
|
|
75
87
|
labels,
|
|
76
88
|
}: WidgetPropsSheetProps) {
|
|
77
89
|
const messages = useToolkitMessages()
|
|
90
|
+
const { locale, resolveKey } = useDisplayResolver()
|
|
78
91
|
const L = { ...messages.propsSheet, ...labels }
|
|
79
92
|
const obj = (schema ?? {}) as JsonSchemaObject
|
|
80
93
|
const [draft, setDraft] = useState<Record<string, unknown>>(value ?? {})
|
|
@@ -109,7 +122,8 @@ export function WidgetPropsSheet({
|
|
|
109
122
|
<SheetTitle>{L.title}</SheetTitle>
|
|
110
123
|
<SheetDescription className="flex flex-col items-start gap-1">
|
|
111
124
|
<span>{L.description}</span>
|
|
112
|
-
<span className="
|
|
125
|
+
{widgetTitle && <span className="text-foreground font-medium">{widgetTitle}</span>}
|
|
126
|
+
<span className="bg-muted text-small rounded px-1 font-mono">{widgetId}</span>
|
|
113
127
|
</SheetDescription>
|
|
114
128
|
</SheetHeader>
|
|
115
129
|
<div className="flex flex-col gap-4 px-4 py-4">
|
|
@@ -120,6 +134,7 @@ export function WidgetPropsSheet({
|
|
|
120
134
|
<PropertyField
|
|
121
135
|
key={key}
|
|
122
136
|
name={key}
|
|
137
|
+
text={propFieldText(key, prop as Record<string, unknown>, locale, resolveKey)}
|
|
123
138
|
prop={prop}
|
|
124
139
|
value={draft[key]}
|
|
125
140
|
unsetLabel={L.unsetOption}
|
|
@@ -156,6 +171,7 @@ export function WidgetPropsSheet({
|
|
|
156
171
|
|
|
157
172
|
function PropertyField({
|
|
158
173
|
name,
|
|
174
|
+
text,
|
|
159
175
|
prop,
|
|
160
176
|
value,
|
|
161
177
|
unsetLabel,
|
|
@@ -163,6 +179,7 @@ function PropertyField({
|
|
|
163
179
|
onChange,
|
|
164
180
|
}: {
|
|
165
181
|
name: string
|
|
182
|
+
text: PropFieldText
|
|
166
183
|
prop: JsonSchemaProperty
|
|
167
184
|
value: unknown
|
|
168
185
|
unsetLabel: string
|
|
@@ -175,12 +192,12 @@ function PropertyField({
|
|
|
175
192
|
const options = prop.enum.filter((v): v is string => typeof v === "string")
|
|
176
193
|
const current = typeof value === "string" && value !== "" ? value : UNSET_SENTINEL
|
|
177
194
|
return (
|
|
178
|
-
<FieldShell id={id}
|
|
195
|
+
<FieldShell id={id} text={text}>
|
|
179
196
|
<Select
|
|
180
197
|
value={current}
|
|
181
198
|
onValueChange={(v) => onChange(v === UNSET_SENTINEL ? undefined : v)}
|
|
182
199
|
>
|
|
183
|
-
<SelectTrigger id={id}>
|
|
200
|
+
<SelectTrigger id={id} aria-describedby={helpId(id, text)}>
|
|
184
201
|
<SelectValue placeholder={unsetLabel} />
|
|
185
202
|
</SelectTrigger>
|
|
186
203
|
<SelectContent>
|
|
@@ -199,9 +216,10 @@ function PropertyField({
|
|
|
199
216
|
if (prop.type === "string") {
|
|
200
217
|
const current = typeof value === "string" ? value : ""
|
|
201
218
|
return (
|
|
202
|
-
<FieldShell id={id}
|
|
219
|
+
<FieldShell id={id} text={text}>
|
|
203
220
|
<Input
|
|
204
221
|
id={id}
|
|
222
|
+
aria-describedby={helpId(id, text)}
|
|
205
223
|
value={current}
|
|
206
224
|
onChange={(e) => onChange(e.target.value === "" ? undefined : e.target.value)}
|
|
207
225
|
/>
|
|
@@ -212,9 +230,10 @@ function PropertyField({
|
|
|
212
230
|
if (prop.type === "number" || prop.type === "integer") {
|
|
213
231
|
const current = typeof value === "number" ? String(value) : ""
|
|
214
232
|
return (
|
|
215
|
-
<FieldShell id={id}
|
|
233
|
+
<FieldShell id={id} text={text}>
|
|
216
234
|
<Input
|
|
217
235
|
id={id}
|
|
236
|
+
aria-describedby={helpId(id, text)}
|
|
218
237
|
type="number"
|
|
219
238
|
value={current}
|
|
220
239
|
onChange={(e) => {
|
|
@@ -232,9 +251,10 @@ function PropertyField({
|
|
|
232
251
|
if (prop.type === "boolean") {
|
|
233
252
|
const current = value === true
|
|
234
253
|
return (
|
|
235
|
-
<FieldShell id={id}
|
|
254
|
+
<FieldShell id={id} text={text}>
|
|
236
255
|
<Switch
|
|
237
256
|
id={id}
|
|
257
|
+
aria-describedby={helpId(id, text)}
|
|
238
258
|
checked={current}
|
|
239
259
|
onCheckedChange={(checked) => onChange(checked || undefined)}
|
|
240
260
|
/>
|
|
@@ -243,30 +263,51 @@ function PropertyField({
|
|
|
243
263
|
}
|
|
244
264
|
|
|
245
265
|
return (
|
|
246
|
-
<FieldShell id={id}
|
|
247
|
-
<p className="text-muted-foreground text-
|
|
266
|
+
<FieldShell id={id} text={text}>
|
|
267
|
+
<p className="text-muted-foreground text-small italic">
|
|
268
|
+
{unsupportedLabel(prop.type ?? "?")}
|
|
269
|
+
</p>
|
|
248
270
|
</FieldShell>
|
|
249
271
|
)
|
|
250
272
|
}
|
|
251
273
|
|
|
274
|
+
/** The id of a field's help line, when it has one (its `aria-describedby`). */
|
|
275
|
+
function helpId(id: string, text: PropFieldText): string | undefined {
|
|
276
|
+
return text.help ? `${id}-help` : undefined
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Label (the human title), the raw property name as a quiet hint beside it
|
|
281
|
+
* (outside the label, so the accessible name stays the title), the control,
|
|
282
|
+
* and the help line it is described by.
|
|
283
|
+
*/
|
|
252
284
|
function FieldShell({
|
|
253
285
|
id,
|
|
254
|
-
|
|
255
|
-
description,
|
|
286
|
+
text,
|
|
256
287
|
children,
|
|
257
288
|
}: {
|
|
258
289
|
id: string
|
|
259
|
-
|
|
260
|
-
description?: string
|
|
290
|
+
text: PropFieldText
|
|
261
291
|
children: ReactNode
|
|
262
292
|
}) {
|
|
263
293
|
return (
|
|
264
294
|
<div className="flex flex-col gap-1.5">
|
|
265
|
-
<
|
|
266
|
-
{
|
|
267
|
-
|
|
295
|
+
<div className="flex flex-wrap items-baseline gap-x-2">
|
|
296
|
+
<label htmlFor={id} className="text-sm font-medium">
|
|
297
|
+
{text.label}
|
|
298
|
+
</label>
|
|
299
|
+
{text.hint && (
|
|
300
|
+
<code data-slot="prop-name" className="text-muted-foreground text-small font-mono">
|
|
301
|
+
{text.hint}
|
|
302
|
+
</code>
|
|
303
|
+
)}
|
|
304
|
+
</div>
|
|
268
305
|
{children}
|
|
269
|
-
{
|
|
306
|
+
{text.help ? (
|
|
307
|
+
<p id={helpId(id, text)} className="text-muted-foreground text-small">
|
|
308
|
+
{text.help}
|
|
309
|
+
</p>
|
|
310
|
+
) : null}
|
|
270
311
|
</div>
|
|
271
312
|
)
|
|
272
313
|
}
|
|
@@ -126,7 +126,7 @@ export function UnavailableWidget({
|
|
|
126
126
|
return (
|
|
127
127
|
<div data-slot="widget-unavailable" data-widget-id={widgetId} className={className}>
|
|
128
128
|
<p>{messages.renderer.widgetUnavailable}</p>
|
|
129
|
-
<p className="mt-1 font-mono
|
|
129
|
+
<p className="text-small mt-1 font-mono break-all">{widgetId}</p>
|
|
130
130
|
</div>
|
|
131
131
|
)
|
|
132
132
|
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { renderToStaticMarkup } from "react-dom/server"
|
|
2
|
+
import { describe, expect, it } from "vitest"
|
|
3
|
+
import { ExternalLink } from "lucide-react"
|
|
4
|
+
import { attribute, openingTags, userFacingTexts } from "../render-scan.js"
|
|
5
|
+
import { buttonVariants } from "../primitives/button.js"
|
|
6
|
+
import { DrillButton } from "./DrillButton.js"
|
|
7
|
+
|
|
8
|
+
const noop = () => undefined
|
|
9
|
+
const classOf = (tag: string | undefined) => attribute(tag ?? "", "class") ?? ""
|
|
10
|
+
|
|
11
|
+
describe("DrillButton: the outline Button with a trailing arrow (mcp-toolkit#194)", () => {
|
|
12
|
+
it("sm is a Button size sm: 32 px, 14 px, weight 600, like the buttons beside it in a row", () => {
|
|
13
|
+
const html = renderToStaticMarkup(<DrillButton onDrill={noop}>Open</DrillButton>)
|
|
14
|
+
const [button] = openingTags(html, "button")
|
|
15
|
+
expect(classOf(button)).toBe(buttonVariants({ variant: "outline", size: "sm" }))
|
|
16
|
+
expect(classOf(button)).toMatch(/\bh-8\b/)
|
|
17
|
+
expect(classOf(button)).toMatch(/\btext-sm\b/)
|
|
18
|
+
expect(classOf(button)).toMatch(/\bfont-semibold\b/)
|
|
19
|
+
expect(attribute(button ?? "", "type")).toBe("button")
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
it("md is the Button default size (surface headers)", () => {
|
|
23
|
+
const html = renderToStaticMarkup(
|
|
24
|
+
<DrillButton onDrill={noop} size="md">
|
|
25
|
+
View running instances
|
|
26
|
+
</DrillButton>,
|
|
27
|
+
)
|
|
28
|
+
expect(classOf(openingTags(html, "button")[0])).toBe(
|
|
29
|
+
buttonVariants({ variant: "outline", size: "default" }),
|
|
30
|
+
)
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
it("keeps its words: the label, a decorative leading icon and a decorative trailing arrow", () => {
|
|
34
|
+
const html = renderToStaticMarkup(
|
|
35
|
+
<DrillButton onDrill={noop} icon={<ExternalLink />} ariaLabel="Open order o-1001">
|
|
36
|
+
Open
|
|
37
|
+
</DrillButton>,
|
|
38
|
+
)
|
|
39
|
+
const [button] = openingTags(html, "button")
|
|
40
|
+
expect(attribute(button ?? "", "aria-label")).toBe("Open order o-1001")
|
|
41
|
+
expect(userFacingTexts(html).map((t) => t.value)).toContain("Open")
|
|
42
|
+
const svgs = openingTags(html, "svg")
|
|
43
|
+
expect(svgs).toHaveLength(2)
|
|
44
|
+
expect(svgs.at(-1)).toContain("lucide-arrow-right")
|
|
45
|
+
for (const svg of svgs) expect(attribute(svg, "aria-hidden")).toBe("true")
|
|
46
|
+
expect(openingTags(html, "span")[0]).toContain('aria-hidden="true"')
|
|
47
|
+
})
|
|
48
|
+
})
|
|
@@ -1,38 +1,53 @@
|
|
|
1
1
|
import type { ReactNode } from "react"
|
|
2
|
-
import {
|
|
2
|
+
import { ArrowRight } from "lucide-react"
|
|
3
|
+
import { buttonVariants } from "../primitives/button.js"
|
|
3
4
|
|
|
4
5
|
export interface DrillButtonProps {
|
|
5
6
|
/** Button label. */
|
|
6
7
|
children: ReactNode
|
|
7
8
|
/** Drill/navigation handler — wire to in-widget navigation (e.g. `showWidget`). */
|
|
8
9
|
onDrill: () => void
|
|
9
|
-
/**
|
|
10
|
+
/**
|
|
11
|
+
* Optional leading icon — a Lucide icon of the domain, never a typed glyph.
|
|
12
|
+
* Decorative (`aria-hidden`). The trailing arrow is always rendered.
|
|
13
|
+
*/
|
|
10
14
|
icon?: ReactNode
|
|
11
15
|
/** `sm` for table rows, `md` for surface headers. Default `"sm"`. */
|
|
12
16
|
size?: "sm" | "md"
|
|
13
|
-
/** Accessible label when the visible text is terse. */
|
|
17
|
+
/** Accessible label when the visible text is terse; keep the visible text in it (WCAG 2.5.3). */
|
|
14
18
|
ariaLabel?: string
|
|
15
19
|
}
|
|
16
20
|
|
|
21
|
+
/** The `Button` size each drill size is: one scale for every action of a view. */
|
|
22
|
+
const BUTTON_SIZE = { sm: "sm", md: "default" } as const
|
|
23
|
+
|
|
17
24
|
/**
|
|
18
25
|
* The single, deterministic in-widget navigation control: a neutral outline
|
|
19
|
-
* button with a trailing
|
|
20
|
-
* every in-app drill (row "Open", header nav) so they read identically
|
|
21
|
-
* visually quieter than primary/agentic actions. Decoupled from any
|
|
22
|
-
* an `onDrill` callback (e.g. wrapping `useHostActions().showWidget`).
|
|
26
|
+
* button with a trailing Lucide arrow that drills into a detail/sub-view. Use
|
|
27
|
+
* it for every in-app drill (row "Open", header nav) so they read identically
|
|
28
|
+
* and stay visually quieter than primary/agentic actions. Decoupled from any
|
|
29
|
+
* host — pass an `onDrill` callback (e.g. wrapping `useHostActions().showWidget`).
|
|
30
|
+
*
|
|
31
|
+
* It IS the toolkit's outline `Button` (`buttonVariants`): `sm` is 32 px,
|
|
32
|
+
* 14 px, weight 600, the size of a `Button size="sm"` or an `AskAiButton`
|
|
33
|
+
* beside it in a row; `md` is the `Button` default (36 px). The design
|
|
34
|
+
* system gives every button weight 600 and one radius.
|
|
23
35
|
*/
|
|
24
36
|
export function DrillButton({ children, onDrill, icon, size = "sm", ariaLabel }: DrillButtonProps) {
|
|
25
|
-
const pad = size === "md" ? "px-3 py-1.5 text-sm" : "px-2.5 py-1 text-xs"
|
|
26
37
|
return (
|
|
27
38
|
<button
|
|
28
39
|
type="button"
|
|
29
40
|
onClick={onDrill}
|
|
30
41
|
aria-label={ariaLabel}
|
|
31
|
-
className={
|
|
42
|
+
className={buttonVariants({ variant: "outline", size: BUTTON_SIZE[size] })}
|
|
32
43
|
>
|
|
33
|
-
{icon &&
|
|
44
|
+
{icon && (
|
|
45
|
+
<span aria-hidden="true" className="inline-flex">
|
|
46
|
+
{icon}
|
|
47
|
+
</span>
|
|
48
|
+
)}
|
|
34
49
|
{children}
|
|
35
|
-
<
|
|
50
|
+
<ArrowRight aria-hidden="true" className="size-4 shrink-0" />
|
|
36
51
|
</button>
|
|
37
52
|
)
|
|
38
53
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ReactNode } from "react"
|
|
2
|
-
import { TONE_ICON } from "../lib/tone-utils.js"
|
|
2
|
+
import { resolveTone, TONE_ICON } from "../lib/tone-utils.js"
|
|
3
3
|
import { cn } from "../lib/utils.js"
|
|
4
4
|
|
|
5
5
|
/** Heading element for an empty/error state title (the surface decides the outline level). */
|
|
@@ -50,6 +50,8 @@ export function EmptyState({
|
|
|
50
50
|
titleAs: Title = "h3",
|
|
51
51
|
className,
|
|
52
52
|
}: EmptyStateProps) {
|
|
53
|
+
// An untyped tone resolves (`ok` → success); anything else stays quiet.
|
|
54
|
+
const iconTone = resolveTone(tone) === "success" ? "success" : "neutral"
|
|
53
55
|
return (
|
|
54
56
|
<div
|
|
55
57
|
data-slot="empty-state"
|
|
@@ -60,15 +62,15 @@ export function EmptyState({
|
|
|
60
62
|
aria-hidden="true"
|
|
61
63
|
className={cn(
|
|
62
64
|
"mb-1 flex items-center justify-center [&_svg:not([class*='size-'])]:size-6",
|
|
63
|
-
ICON_TONE[
|
|
65
|
+
ICON_TONE[iconTone],
|
|
64
66
|
)}
|
|
65
67
|
>
|
|
66
68
|
{icon}
|
|
67
69
|
</div>
|
|
68
70
|
)}
|
|
69
|
-
<Title className="text-foreground text-
|
|
71
|
+
<Title className="text-foreground text-body font-semibold">{title}</Title>
|
|
70
72
|
{description && (
|
|
71
|
-
<p className="text-muted-foreground max-w-prose text-
|
|
73
|
+
<p className="text-muted-foreground text-body max-w-prose text-balance">{description}</p>
|
|
72
74
|
)}
|
|
73
75
|
{(action || secondaryAction) && (
|
|
74
76
|
<div className="mt-2 flex flex-wrap items-center justify-center gap-2">
|
|
@@ -3,7 +3,7 @@ import { CircleAlert, Info, RefreshCw, TriangleAlert, type LucideIcon } from "lu
|
|
|
3
3
|
import { useToolkitMessages } from "../locale/locale-provider.js"
|
|
4
4
|
import { Button } from "../primitives/button.js"
|
|
5
5
|
import { FOCUS_RING } from "../lib/focus-ring.js"
|
|
6
|
-
import { TONE_BORDER, TONE_ICON, TONE_SOFT } from "../lib/tone-utils.js"
|
|
6
|
+
import { resolveTone, TONE_BORDER, TONE_ICON, TONE_SOFT } from "../lib/tone-utils.js"
|
|
7
7
|
import { cn } from "../lib/utils.js"
|
|
8
8
|
import type { StateHeadingLevel } from "./EmptyState.js"
|
|
9
9
|
|
|
@@ -28,7 +28,10 @@ export interface ErrorStateProps {
|
|
|
28
28
|
retryLabel?: string
|
|
29
29
|
/** Further actions after the retry button (e.g. a link to another view). */
|
|
30
30
|
actions?: ReactNode
|
|
31
|
-
/**
|
|
31
|
+
/**
|
|
32
|
+
* Default `"danger"`. `"warning"` for partial failures, `"info"` for
|
|
33
|
+
* notices. An untyped retired name still resolves (`error` → `danger`).
|
|
34
|
+
*/
|
|
32
35
|
tone?: ErrorStateTone
|
|
33
36
|
/** Replace the tone's default Lucide icon. */
|
|
34
37
|
icon?: LucideIcon
|
|
@@ -54,19 +57,36 @@ const STATE_ICON: Record<ErrorStateTone, LucideIcon> = {
|
|
|
54
57
|
info: Info,
|
|
55
58
|
}
|
|
56
59
|
|
|
57
|
-
/**
|
|
60
|
+
/**
|
|
61
|
+
* The state's tone from whatever arrives: its three tones as is, a retired
|
|
62
|
+
* name through `resolveTone` (`error` → `danger`, `notice` → `info`), any
|
|
63
|
+
* other tone or unknown name as the default `danger`. A tone from untyped
|
|
64
|
+
* data (plain JS, the wire) never crashes the view on a missing icon.
|
|
65
|
+
*/
|
|
66
|
+
function stateTone(name: unknown): ErrorStateTone {
|
|
67
|
+
const tone = resolveTone(name, "danger")
|
|
68
|
+
return tone === "warning" || tone === "info" ? tone : "danger"
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Per-variant geometry: `block` replaces a surface and reads as running text
|
|
73
|
+
* (16 px), `inline` is the compact alert box above content that still renders
|
|
74
|
+
* (14 px, the dense-controls exception of the type scale).
|
|
75
|
+
*/
|
|
58
76
|
const VARIANT = {
|
|
59
77
|
block: {
|
|
60
78
|
box: "gap-4 p-4",
|
|
61
79
|
icon: "size-5",
|
|
62
|
-
title: "text-
|
|
80
|
+
title: "text-body",
|
|
81
|
+
description: "text-body",
|
|
63
82
|
defaultTitleAs: "h2",
|
|
64
83
|
retry: "default",
|
|
65
84
|
},
|
|
66
85
|
inline: {
|
|
67
|
-
box: "gap-3 px-4 py-3 text-
|
|
86
|
+
box: "gap-3 px-4 py-3 text-dense",
|
|
68
87
|
icon: "size-4",
|
|
69
|
-
title: "text-
|
|
88
|
+
title: "text-dense",
|
|
89
|
+
description: "text-dense",
|
|
70
90
|
defaultTitleAs: "p",
|
|
71
91
|
retry: "outline",
|
|
72
92
|
},
|
|
@@ -76,6 +96,7 @@ const VARIANT = {
|
|
|
76
96
|
box: string
|
|
77
97
|
icon: string
|
|
78
98
|
title: string
|
|
99
|
+
description: string
|
|
79
100
|
defaultTitleAs: StateHeadingLevel
|
|
80
101
|
retry: "default" | "outline"
|
|
81
102
|
}
|
|
@@ -95,13 +116,14 @@ export function ErrorState({
|
|
|
95
116
|
onRetry,
|
|
96
117
|
retryLabel,
|
|
97
118
|
actions,
|
|
98
|
-
tone
|
|
119
|
+
tone: toneProp,
|
|
99
120
|
icon,
|
|
100
121
|
variant = "block",
|
|
101
122
|
role = "alert",
|
|
102
123
|
titleAs,
|
|
103
124
|
className,
|
|
104
125
|
}: ErrorStateProps) {
|
|
126
|
+
const tone = stateTone(toneProp)
|
|
105
127
|
const style = VARIANT[variant]
|
|
106
128
|
const Icon = icon ?? STATE_ICON[tone]
|
|
107
129
|
const Title = titleAs ?? style.defaultTitleAs
|
|
@@ -125,7 +147,7 @@ export function ErrorState({
|
|
|
125
147
|
<Icon aria-hidden="true" className={cn("mt-0.5 shrink-0", style.icon, TONE_ICON[tone])} />
|
|
126
148
|
<div className="flex min-w-0 flex-1 flex-col gap-1">
|
|
127
149
|
<Title className={cn("font-semibold", style.title)}>{title}</Title>
|
|
128
|
-
{description && <div className="
|
|
150
|
+
{description && <div className={cn("break-words", style.description)}>{description}</div>}
|
|
129
151
|
{details && <ErrorDetails label={detailsLabel}>{details}</ErrorDetails>}
|
|
130
152
|
<ErrorActions
|
|
131
153
|
onRetry={onRetry}
|
|
@@ -146,7 +168,7 @@ export function ErrorState({
|
|
|
146
168
|
function ErrorDetails({ label, children }: { label: string | undefined; children: ReactNode }) {
|
|
147
169
|
const messages = useToolkitMessages()
|
|
148
170
|
return (
|
|
149
|
-
<details className="text-
|
|
171
|
+
<details className="text-small">
|
|
150
172
|
<summary className={`w-fit cursor-pointer rounded-sm py-0.5 select-none ${FOCUS_RING}`}>
|
|
151
173
|
{label ?? messages.common.details}
|
|
152
174
|
</summary>
|