@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
package/src/components/Toast.tsx
CHANGED
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
useMemo,
|
|
7
7
|
useReducer,
|
|
8
8
|
useRef,
|
|
9
|
-
|
|
9
|
+
type FocusEvent,
|
|
10
10
|
type ReactNode,
|
|
11
11
|
} from "react"
|
|
12
12
|
import { CircleAlert, CircleCheck, Info, TriangleAlert, X, type LucideIcon } from "lucide-react"
|
|
@@ -16,9 +16,11 @@ import { TONE_ICON } from "../lib/tone-utils.js"
|
|
|
16
16
|
import { cn } from "../lib/utils.js"
|
|
17
17
|
import {
|
|
18
18
|
createToast,
|
|
19
|
+
createToastTimer,
|
|
19
20
|
toastReducer,
|
|
20
21
|
type ToastItem,
|
|
21
22
|
type ToastOptions,
|
|
23
|
+
type ToastTimer,
|
|
22
24
|
type ToastTone,
|
|
23
25
|
} from "../lib/toast-model.js"
|
|
24
26
|
|
|
@@ -73,9 +75,15 @@ const TOAST_ICON: Record<ToastTone, LucideIcon> = {
|
|
|
73
75
|
|
|
74
76
|
/**
|
|
75
77
|
* The stack of toasts — a presentational view over a list (the provider owns
|
|
76
|
-
* the state). The container is ONE persistent live region
|
|
77
|
-
* `aria-live="polite"
|
|
78
|
-
*
|
|
78
|
+
* the state). The container is ONE persistent live region
|
|
79
|
+
* (`aria-live="polite"`, mounted before the first toast so that toast is
|
|
80
|
+
* announced, each new toast on its own: `aria-atomic="false"`), without
|
|
81
|
+
* stealing focus. Its attributes never change: a role or name that switched
|
|
82
|
+
* on with the first toast would make the browser rebuild the region in the
|
|
83
|
+
* same commit that inserts the toast, and a region created with its content
|
|
84
|
+
* already in it is not announced. So it carries no role (a generic element
|
|
85
|
+
* takes no name either): an idle stack is no second "status" next to a view's
|
|
86
|
+
* own loading status. Bottom centre, above dialogs (z 60), per CI §5/§8.
|
|
79
87
|
*/
|
|
80
88
|
export function ToastViewport({
|
|
81
89
|
toasts,
|
|
@@ -88,9 +96,8 @@ export function ToastViewport({
|
|
|
88
96
|
return (
|
|
89
97
|
<div
|
|
90
98
|
data-slot="toast-viewport"
|
|
91
|
-
role="status"
|
|
92
99
|
aria-live="polite"
|
|
93
|
-
aria-
|
|
100
|
+
aria-atomic="false"
|
|
94
101
|
className="pointer-events-none fixed inset-x-0 bottom-4 z-60 flex flex-col items-center gap-2 px-4"
|
|
95
102
|
>
|
|
96
103
|
{toasts.map((toast) => (
|
|
@@ -114,31 +121,42 @@ function ToastCard({
|
|
|
114
121
|
dismissLabel: string
|
|
115
122
|
onDismiss: (id: number) => void
|
|
116
123
|
}) {
|
|
117
|
-
// Hover or keyboard focus holds the toast
|
|
118
|
-
|
|
124
|
+
// Hover or keyboard focus holds the toast; on leave it resumes with the
|
|
125
|
+
// time that was LEFT (createToastTimer), never a fresh full duration.
|
|
126
|
+
const timer = useRef<ToastTimer | null>(null)
|
|
119
127
|
useEffect(() => {
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
return () =>
|
|
123
|
-
|
|
128
|
+
const countdown = createToastTimer(toast.duration, () => onDismiss(toast.id))
|
|
129
|
+
timer.current = countdown
|
|
130
|
+
return () => {
|
|
131
|
+
countdown.cancel()
|
|
132
|
+
timer.current = null
|
|
133
|
+
}
|
|
134
|
+
}, [onDismiss, toast.id, toast.duration])
|
|
135
|
+
const releaseFocus = (event: FocusEvent<HTMLDivElement>) => {
|
|
136
|
+
// Focus moving inside the toast (card → close button) keeps the hold.
|
|
137
|
+
if (!event.currentTarget.contains(event.relatedTarget)) {
|
|
138
|
+
timer.current?.release("focus")
|
|
139
|
+
}
|
|
140
|
+
}
|
|
124
141
|
const Icon = TOAST_ICON[toast.tone]
|
|
125
142
|
return (
|
|
126
143
|
<div
|
|
127
144
|
data-slot="toast"
|
|
128
145
|
data-tone={toast.tone}
|
|
129
|
-
onMouseEnter={() =>
|
|
130
|
-
onMouseLeave={() =>
|
|
131
|
-
onFocus={() =>
|
|
132
|
-
onBlur={
|
|
146
|
+
onMouseEnter={() => timer.current?.hold("pointer")}
|
|
147
|
+
onMouseLeave={() => timer.current?.release("pointer")}
|
|
148
|
+
onFocus={() => timer.current?.hold("focus")}
|
|
149
|
+
onBlur={releaseFocus}
|
|
133
150
|
className={cn(
|
|
134
151
|
// An overlay like Dialog/Sheet: shadow-2 on light, the 3:1 input rim on
|
|
135
|
-
// dark (shadows vanish on the ground).
|
|
136
|
-
"bg-popover text-popover-foreground dark:border-input shadow-2 pointer-events-auto flex w-full max-w-sm items-start gap-3 rounded-md border border-l-4 px-4 py-3
|
|
152
|
+
// dark (shadows vanish on the ground). Its words are running text (16 px).
|
|
153
|
+
"bg-popover text-popover-foreground dark:border-input shadow-2 text-body pointer-events-auto flex w-full max-w-sm items-start gap-3 rounded-md border border-l-4 px-4 py-3",
|
|
137
154
|
"motion-safe:animate-in motion-safe:fade-in-0 motion-safe:slide-in-from-bottom-2 motion-safe:duration-(--motion-base) motion-safe:ease-(--ease)",
|
|
138
155
|
TONE_EDGE[toast.tone],
|
|
139
156
|
)}
|
|
140
157
|
>
|
|
141
|
-
|
|
158
|
+
{/* mt-1 centres the 16 px icon on the first 16/1.6 line. */}
|
|
159
|
+
<Icon aria-hidden="true" className={cn("mt-1 size-4 shrink-0", TONE_ICON[toast.tone])} />
|
|
142
160
|
<div className="min-w-0 flex-1">
|
|
143
161
|
<p className="font-medium break-words">{toast.title}</p>
|
|
144
162
|
{toast.description && (
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { renderToStaticMarkup } from "react-dom/server"
|
|
2
|
+
import { describe, expect, it } from "vitest"
|
|
3
|
+
import { MICRO_LABEL } from "../lib/typography.js"
|
|
4
|
+
import { attribute, openingTags } from "../render-scan.js"
|
|
5
|
+
import { StatusBadge } from "./Pills.js"
|
|
6
|
+
import { SectionHeading } from "./SectionHeading.js"
|
|
7
|
+
import { WidgetHeader, type WidgetHeadingLevel } from "./WidgetHeader.js"
|
|
8
|
+
|
|
9
|
+
const classOf = (tag: string | undefined) => attribute(tag ?? "", "class") ?? ""
|
|
10
|
+
|
|
11
|
+
describe("WidgetHeader: one title at the level of its surface", () => {
|
|
12
|
+
it("is an h1 at the page-title size by default", () => {
|
|
13
|
+
const html = renderToStaticMarkup(<WidgetHeader title="Incidents" />)
|
|
14
|
+
expect(html).toMatch(/<h1 class="[^"]*text-title[^"]*">Incidents<\/h1>/)
|
|
15
|
+
expect(html).not.toMatch(/text-3xl|text-xs/)
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
it.each(["h2", "h3", "h4", "h5", "h6"] satisfies WidgetHeadingLevel[])(
|
|
19
|
+
"takes the heading level %s from `as`, keeping the size",
|
|
20
|
+
(level) => {
|
|
21
|
+
const html = renderToStaticMarkup(<WidgetHeader as={level} title="Incidents" />)
|
|
22
|
+
expect(html).toMatch(
|
|
23
|
+
new RegExp(`<${level} class="[^"]*text-title[^"]*">Incidents</${level}>`),
|
|
24
|
+
)
|
|
25
|
+
expect(html).not.toContain("<h1")
|
|
26
|
+
},
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
it("renders the compact detail size at the section step", () => {
|
|
30
|
+
const html = renderToStaticMarkup(<WidgetHeader as="h2" size="detail" title="Instance" />)
|
|
31
|
+
expect(classOf(openingTags(html, "h2")[0])).toContain("text-section")
|
|
32
|
+
expect(classOf(openingTags(html, "h2")[0])).not.toContain("text-title")
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
it("puts a state badge above the title and a suffix inside the heading", () => {
|
|
36
|
+
const html = renderToStaticMarkup(
|
|
37
|
+
<WidgetHeader
|
|
38
|
+
title="Invoice"
|
|
39
|
+
titleSuffix={<span>v3</span>}
|
|
40
|
+
badge={<StatusBadge tone="warning">Suspended</StatusBadge>}
|
|
41
|
+
sub="Updated 14:32"
|
|
42
|
+
actions={<button type="button">Refresh</button>}
|
|
43
|
+
/>,
|
|
44
|
+
)
|
|
45
|
+
expect(html.indexOf("Suspended")).toBeLessThan(html.indexOf("<h1"))
|
|
46
|
+
expect(html).toMatch(/<h1[^>]*>Invoice<span>v3<\/span><\/h1>/)
|
|
47
|
+
expect(html).toContain("Updated 14:32")
|
|
48
|
+
expect(html).toContain("Refresh</button>")
|
|
49
|
+
expect(html).not.toContain("size-11")
|
|
50
|
+
// The meta row is a caption at the 13 px step, not running text in the
|
|
51
|
+
// dense-table size.
|
|
52
|
+
const meta = openingTags(html, "div").find((t) => /flex-wrap items-center gap-2/.test(t))
|
|
53
|
+
expect(classOf(meta)).toMatch(/(^| )text-small( |$)/)
|
|
54
|
+
expect(html).not.toMatch(/\btext-dense\b/)
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
it("still renders the deprecated icon tile, hidden from assistive tech", () => {
|
|
58
|
+
const html = renderToStaticMarkup(<WidgetHeader icon={<svg />} iconTone="info" title="Tasks" />)
|
|
59
|
+
const tile = openingTags(html, "div").find((t) => /size-11/.test(t))
|
|
60
|
+
expect(attribute(tile ?? "", "aria-hidden")).toBe("true")
|
|
61
|
+
expect(classOf(tile)).toContain("bg-info-soft")
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
it("merges a className onto the header", () => {
|
|
65
|
+
const html = renderToStaticMarkup(<WidgetHeader title="x" className="mb-6" />)
|
|
66
|
+
expect(classOf(openingTags(html, "header")[0])).toContain("mb-6")
|
|
67
|
+
})
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
describe("SectionHeading: a block label inside a widget body", () => {
|
|
71
|
+
it("is a 13 px caption (MICRO_LABEL), never in the dense-table size", () => {
|
|
72
|
+
const html = renderToStaticMarkup(<SectionHeading title="Recent" hint="3 new" />)
|
|
73
|
+
const row = classOf(openingTags(html, "div")[0])
|
|
74
|
+
expect(row).toContain(MICRO_LABEL)
|
|
75
|
+
expect(row).toContain("text-muted-foreground")
|
|
76
|
+
expect(html).not.toMatch(/\btext-dense\b/)
|
|
77
|
+
})
|
|
78
|
+
})
|
|
@@ -1,46 +1,93 @@
|
|
|
1
1
|
import type { ReactNode } from "react"
|
|
2
|
-
import { TONE_SOFT, type
|
|
2
|
+
import { resolveTone, TONE_SOFT, type ToneName } from "../lib/tone-utils.js"
|
|
3
|
+
import { cn } from "../lib/utils.js"
|
|
4
|
+
|
|
5
|
+
/** Heading element of a {@link WidgetHeader} title. */
|
|
6
|
+
export type WidgetHeadingLevel = "h1" | "h2" | "h3" | "h4" | "h5" | "h6"
|
|
3
7
|
|
|
4
8
|
export interface WidgetHeaderProps {
|
|
5
|
-
/** Optional icon/glyph rendered in a tinted tile above the title. */
|
|
6
|
-
icon?: ReactNode
|
|
7
|
-
/** Tone for the icon tile. Default `"neutral"`. */
|
|
8
|
-
iconTone?: ToneVariant
|
|
9
9
|
/** The widget title. */
|
|
10
10
|
title: ReactNode
|
|
11
|
-
/**
|
|
11
|
+
/**
|
|
12
|
+
* Heading element of the title. Default `"h1"`: the widget is the page.
|
|
13
|
+
* Pick the level that keeps ONE outline per view — below a toolbar title
|
|
14
|
+
* (`McpAppView` `titleMode="show"`, an h2 by default) use `"h3"`, inside a
|
|
15
|
+
* composed view under its own heading one level below it. The size follows
|
|
16
|
+
* `size`, not the level.
|
|
17
|
+
*/
|
|
18
|
+
as?: WidgetHeadingLevel
|
|
19
|
+
/**
|
|
20
|
+
* `"default"`: the page title (28 px / 700, the CI h2 style). `"detail"`:
|
|
21
|
+
* the compact detail-page title (21 px / 600, the CI h3 style).
|
|
22
|
+
*/
|
|
23
|
+
size?: "default" | "detail"
|
|
24
|
+
/** Inline after the title inside the heading (e.g. a version chip). */
|
|
25
|
+
titleSuffix?: ReactNode
|
|
26
|
+
/** Above the title — a `StatusBadge` that says the state in words. */
|
|
27
|
+
badge?: ReactNode
|
|
28
|
+
/** Subtitle/meta row under the title (e.g. a live pill + counts), a 13 px caption row. */
|
|
12
29
|
sub?: ReactNode
|
|
13
30
|
/** Right-aligned actions slot (buttons, drill controls). */
|
|
14
31
|
actions?: ReactNode
|
|
32
|
+
/**
|
|
33
|
+
* @deprecated A glyph in a tinted tile above every title is the icon-tile
|
|
34
|
+
* pattern the CI rules out (anti-slop U2). Say a state in words with a
|
|
35
|
+
* `StatusBadge` in {@link WidgetHeaderProps.badge}. Still rendered when set.
|
|
36
|
+
*/
|
|
37
|
+
icon?: ReactNode
|
|
38
|
+
/** @deprecated Tone of the deprecated {@link WidgetHeaderProps.icon} tile. */
|
|
39
|
+
iconTone?: ToneName
|
|
40
|
+
className?: string
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const TITLE_SIZE: Record<NonNullable<WidgetHeaderProps["size"]>, string> = {
|
|
44
|
+
default: "text-title",
|
|
45
|
+
detail: "text-section",
|
|
15
46
|
}
|
|
16
47
|
|
|
17
48
|
/**
|
|
18
|
-
* Page-level header
|
|
19
|
-
*
|
|
20
|
-
* Use once per widget surface to anchor it.
|
|
49
|
+
* Page-level header at the top of a widget: an optional state badge, the
|
|
50
|
+
* title (heading level per `as`, size per `size`), a subtitle/meta row and a
|
|
51
|
+
* right-aligned actions slot. Use once per widget surface to anchor it.
|
|
21
52
|
*/
|
|
22
53
|
export function WidgetHeader({
|
|
23
|
-
icon,
|
|
24
|
-
iconTone = "neutral",
|
|
25
54
|
title,
|
|
55
|
+
as: Heading = "h1",
|
|
56
|
+
size = "default",
|
|
57
|
+
titleSuffix,
|
|
58
|
+
badge,
|
|
26
59
|
sub,
|
|
27
60
|
actions,
|
|
61
|
+
icon,
|
|
62
|
+
iconTone = "neutral",
|
|
63
|
+
className,
|
|
28
64
|
}: WidgetHeaderProps) {
|
|
29
65
|
return (
|
|
30
|
-
<header
|
|
66
|
+
<header
|
|
67
|
+
data-slot="widget-header"
|
|
68
|
+
className={cn("flex flex-wrap items-start justify-between gap-4", className)}
|
|
69
|
+
>
|
|
31
70
|
<div className="min-w-0">
|
|
32
71
|
{icon && (
|
|
33
72
|
<div
|
|
34
|
-
|
|
73
|
+
aria-hidden="true"
|
|
74
|
+
className={cn(
|
|
75
|
+
"text-section mb-3.5 grid size-11 place-items-center rounded-md",
|
|
76
|
+
TONE_SOFT[resolveTone(iconTone)],
|
|
77
|
+
)}
|
|
35
78
|
>
|
|
36
79
|
{icon}
|
|
37
80
|
</div>
|
|
38
81
|
)}
|
|
39
|
-
<
|
|
82
|
+
{badge && <div className="mb-3">{badge}</div>}
|
|
83
|
+
<Heading
|
|
84
|
+
className={cn("text-foreground mb-1.5 tracking-tight break-words", TITLE_SIZE[size])}
|
|
85
|
+
>
|
|
40
86
|
{title}
|
|
41
|
-
|
|
87
|
+
{titleSuffix}
|
|
88
|
+
</Heading>
|
|
42
89
|
{sub && (
|
|
43
|
-
<div className="text-muted-foreground flex flex-wrap items-center gap-2
|
|
90
|
+
<div className="text-muted-foreground text-small flex flex-wrap items-center gap-2">
|
|
44
91
|
{sub}
|
|
45
92
|
</div>
|
|
46
93
|
)}
|
|
@@ -8,8 +8,8 @@ import { FOCUS_RING } from "../lib/focus-ring.js"
|
|
|
8
8
|
import { createToast } from "../lib/toast-model.js"
|
|
9
9
|
import { TONE_BORDER, TONE_ICON, TONE_SOFT } from "../lib/tone-utils.js"
|
|
10
10
|
import { attribute, openingTags } from "../render-scan.js"
|
|
11
|
-
import { EmptyState } from "./EmptyState.js"
|
|
12
|
-
import { ErrorState } from "./ErrorState.js"
|
|
11
|
+
import { EmptyState, type EmptyStateProps } from "./EmptyState.js"
|
|
12
|
+
import { ErrorState, type ErrorStateTone } from "./ErrorState.js"
|
|
13
13
|
import { ToastProvider, ToastViewport, useToast, type ToastApi } from "./Toast.js"
|
|
14
14
|
|
|
15
15
|
/**
|
|
@@ -38,16 +38,41 @@ describe("Toast", () => {
|
|
|
38
38
|
createToast(2, { tone: "danger", title: "Retry failed.", description: "Engine down (503)" }),
|
|
39
39
|
]
|
|
40
40
|
|
|
41
|
-
|
|
41
|
+
/** The stack's region, found by its slot (it has no role to query by). */
|
|
42
|
+
const regionOf = (html: string) =>
|
|
43
|
+
openingTags(html, "div").find((t) => attribute(t, "data-slot") === "toast-viewport") ?? ""
|
|
44
|
+
|
|
45
|
+
it("renders ONE persistent live region: aria-live=polite, each toast on its own", () => {
|
|
42
46
|
const html = inGerman(<ToastViewport toasts={toasts} onDismiss={noop} />)
|
|
43
|
-
const [region, ...others] = openingTags(html, "div").filter((t) => /
|
|
47
|
+
const [region, ...others] = openingTags(html, "div").filter((t) => /aria-live=/.test(t))
|
|
44
48
|
expect(others).toHaveLength(0)
|
|
49
|
+
expect(attribute(region ?? "", "data-slot")).toBe("toast-viewport")
|
|
45
50
|
expect(attribute(region ?? "", "aria-live")).toBe("polite")
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
+
// Each new toast is announced on its own, not the whole stack again.
|
|
52
|
+
expect(attribute(region ?? "", "aria-atomic")).toBe("false")
|
|
53
|
+
expect(html).not.toContain('role="status"')
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
it("keeps the region's attributes fixed from idle to the first toast", () => {
|
|
57
|
+
// An empty stack keeps the live region, so the first toast is announced.
|
|
58
|
+
// A role or name that switched on with that toast would make the browser
|
|
59
|
+
// rebuild the region in the same commit, with the toast already inside:
|
|
60
|
+
// not announced. So idle and busy carry exactly the same attributes…
|
|
61
|
+
const idle = regionOf(inGerman(<ToastViewport toasts={[]} onDismiss={noop} />))
|
|
62
|
+
const busy = regionOf(inGerman(<ToastViewport toasts={toasts} onDismiss={noop} />))
|
|
63
|
+
expect(idle).not.toBe("")
|
|
64
|
+
expect(busy).toBe(idle)
|
|
65
|
+
// …and no role or name at all: an idle stack is no second "status" beside
|
|
66
|
+
// a view's own loading status (a generic element takes no aria-label).
|
|
67
|
+
expect(attribute(idle, "role")).toBeUndefined()
|
|
68
|
+
expect(attribute(idle, "aria-label")).toBeUndefined()
|
|
69
|
+
const loading = inGerman(
|
|
70
|
+
<ToastProvider>
|
|
71
|
+
<p role="status">Ansicht wird geladen…</p>
|
|
72
|
+
</ToastProvider>,
|
|
73
|
+
)
|
|
74
|
+
expect(loading.match(/role="status"/g)).toHaveLength(1)
|
|
75
|
+
expect(loading.match(/aria-live="polite"/g)).toHaveLength(1)
|
|
51
76
|
})
|
|
52
77
|
|
|
53
78
|
it("shows the status as edge + icon next to plain text, never as coloured text", () => {
|
|
@@ -69,6 +94,14 @@ describe("Toast", () => {
|
|
|
69
94
|
expect(html).toContain("Engine down (503)")
|
|
70
95
|
})
|
|
71
96
|
|
|
97
|
+
it("sets its words as running text (16 px), not in the dense-table size", () => {
|
|
98
|
+
const html = inGerman(<ToastViewport toasts={toasts} onDismiss={noop} />)
|
|
99
|
+
for (const card of openingTags(html, "div").filter((t) => /data-slot="toast"/.test(t))) {
|
|
100
|
+
expect(attribute(card, "class")).toMatch(/(^| )text-body( |$)/)
|
|
101
|
+
expect(attribute(card, "class")).not.toMatch(/\btext-dense\b/)
|
|
102
|
+
}
|
|
103
|
+
})
|
|
104
|
+
|
|
72
105
|
it("gives every toast a native close button named in the locale, and steals no focus", () => {
|
|
73
106
|
const html = inGerman(<ToastViewport toasts={toasts} onDismiss={noop} />)
|
|
74
107
|
const buttons = openingTags(html, "button")
|
|
@@ -150,6 +183,29 @@ describe("EmptyState", () => {
|
|
|
150
183
|
expect(openingTags(html, "button")).toHaveLength(2)
|
|
151
184
|
expect(renderToStaticMarkup(<EmptyState title="Bare" />)).not.toContain("<button")
|
|
152
185
|
})
|
|
186
|
+
|
|
187
|
+
it("sets its help line as running text (16 px), not in the dense-table size", () => {
|
|
188
|
+
const html = renderToStaticMarkup(<EmptyState title="Nothing" description="Add one." />)
|
|
189
|
+
const line = openingTags(html, "p").find((t) => /max-w-prose/.test(t)) ?? ""
|
|
190
|
+
expect(attribute(line, "class")).toMatch(/(^| )text-body( |$)/)
|
|
191
|
+
expect(html).not.toMatch(/\btext-dense\b/)
|
|
192
|
+
})
|
|
193
|
+
|
|
194
|
+
it("resolves an untyped tone: a retired success name colours the icon, anything else stays quiet", () => {
|
|
195
|
+
const iconClass = (tone: string) => {
|
|
196
|
+
const html = renderToStaticMarkup(
|
|
197
|
+
<EmptyState
|
|
198
|
+
icon={<CircleCheck />}
|
|
199
|
+
tone={tone as unknown as EmptyStateProps["tone"]}
|
|
200
|
+
title="x"
|
|
201
|
+
/>,
|
|
202
|
+
)
|
|
203
|
+
return openingTags(html, "div").find((t) => /aria-hidden="true"/.test(t)) ?? ""
|
|
204
|
+
}
|
|
205
|
+
expect(iconClass("ok")).toContain(TONE_ICON.success)
|
|
206
|
+
expect(iconClass("danger")).toContain(TONE_ICON.neutral)
|
|
207
|
+
expect(iconClass("purple")).toContain(TONE_ICON.neutral)
|
|
208
|
+
})
|
|
153
209
|
})
|
|
154
210
|
|
|
155
211
|
describe("ErrorState", () => {
|
|
@@ -222,4 +278,37 @@ describe("ErrorState", () => {
|
|
|
222
278
|
expect(html).toContain("Reload</button>")
|
|
223
279
|
expect(html).toContain('<a href="#help">Help</a>')
|
|
224
280
|
})
|
|
281
|
+
|
|
282
|
+
it("sets the block's description as running text (16 px); the inline alert box stays dense", () => {
|
|
283
|
+
const descriptionClass = (html: string) =>
|
|
284
|
+
/<div class="([^"]*)">what to do<\/div>/.exec(html)?.[1] ?? ""
|
|
285
|
+
const block = renderToStaticMarkup(<ErrorState title="x" description="what to do" />)
|
|
286
|
+
expect(descriptionClass(block)).toMatch(/(^| )text-body( |$)/)
|
|
287
|
+
const inline = renderToStaticMarkup(
|
|
288
|
+
<ErrorState variant="inline" title="x" description="what to do" />,
|
|
289
|
+
)
|
|
290
|
+
expect(descriptionClass(inline)).toMatch(/(^| )text-dense( |$)/)
|
|
291
|
+
})
|
|
292
|
+
|
|
293
|
+
it.each([
|
|
294
|
+
["error", "danger"],
|
|
295
|
+
["critical", "danger"],
|
|
296
|
+
["warn", "warning"],
|
|
297
|
+
["notice", "info"],
|
|
298
|
+
["success", "danger"],
|
|
299
|
+
["purple", "danger"],
|
|
300
|
+
])(
|
|
301
|
+
"resolves an untyped tone %j to its %s state instead of crashing on a missing icon",
|
|
302
|
+
(name, tone) => {
|
|
303
|
+
const html = renderToStaticMarkup(
|
|
304
|
+
<ErrorState title="x" tone={name as unknown as ErrorStateTone} />,
|
|
305
|
+
)
|
|
306
|
+
const root = openingTags(html, "div")[0] ?? ""
|
|
307
|
+
expect(attribute(root, "data-tone")).toBe(tone)
|
|
308
|
+
expect(attribute(root, "class")).toContain(TONE_BORDER[tone as ErrorStateTone])
|
|
309
|
+
expect(attribute(openingTags(html, "svg")[0] ?? "", "class")).toContain(
|
|
310
|
+
TONE_ICON[tone as ErrorStateTone],
|
|
311
|
+
)
|
|
312
|
+
},
|
|
313
|
+
)
|
|
225
314
|
})
|
|
@@ -28,13 +28,17 @@ describe("figures never take a tone colour", () => {
|
|
|
28
28
|
]}
|
|
29
29
|
/>,
|
|
30
30
|
)
|
|
31
|
-
|
|
32
|
-
|
|
31
|
+
// Icons draw in the tone (SVG currentColor, TONE_ICON); no WORD or digit does.
|
|
32
|
+
const words = html.replace(/<svg[\s\S]*?<\/svg>/g, "")
|
|
33
|
+
expect(words).not.toMatch(TONE_AS_TEXT)
|
|
34
|
+
// The tone is still there — as a dot next to a label, an icon next to a trend.
|
|
33
35
|
expect(html).toMatch(/bg-danger\b/)
|
|
34
36
|
expect(html).toMatch(/bg-success\b/)
|
|
37
|
+
expect(html).toMatch(/<svg[^>]*class="lucide lucide-trending-up[^"]*text-danger"/)
|
|
38
|
+
expect(html).toMatch(/<svg[^>]*class="lucide lucide-trending-down[^"]*text-success"/)
|
|
35
39
|
// A toned trend reads in the foreground, an untoned one stays quiet.
|
|
36
|
-
expect(html).toMatch(
|
|
37
|
-
expect(html).toMatch(
|
|
40
|
+
expect(html).toMatch(/<span class="text-foreground">\+12% vs last week<\/span>/)
|
|
41
|
+
expect(html).toMatch(/<span class="text-muted-foreground">±0<\/span>/)
|
|
38
42
|
})
|
|
39
43
|
|
|
40
44
|
it.each(TONES)(
|
package/src/hooks/index.ts
CHANGED
|
@@ -1,17 +1,20 @@
|
|
|
1
1
|
export { useIsMobile } from "./use-mobile.js"
|
|
2
2
|
export { useDebouncedValue } from "./use-debounced-value.js"
|
|
3
|
-
export { useToolQuery, useToolMutation } from "./use-tool-query.js"
|
|
4
|
-
export type { UseToolQueryOptions } from "./use-tool-query.js"
|
|
3
|
+
export { useToolQuery, useToolQueryKey, useToolMutation } from "./use-tool-query.js"
|
|
4
|
+
export type { UseToolQueryOptions, UseToolMutationOptions } from "./use-tool-query.js"
|
|
5
5
|
export { useViewToolQuery } from "./use-view-tool-query.js"
|
|
6
6
|
export type { UseViewToolQueryOptions } from "./use-view-tool-query.js"
|
|
7
7
|
export { useViewData } from "./use-view-data.js"
|
|
8
|
-
export type { ViewDataResult } from "./use-view-data.js"
|
|
8
|
+
export type { ViewDataResult, UseViewDataOptions, ViewDataMode } from "./use-view-data.js"
|
|
9
9
|
export {
|
|
10
10
|
useToolResultRecovery,
|
|
11
11
|
resolveRecoveryToolName,
|
|
12
12
|
decodeRecoveredResult,
|
|
13
13
|
} from "./use-tool-result-recovery.js"
|
|
14
14
|
export type {
|
|
15
|
+
RecoveryBlockReason,
|
|
16
|
+
RecoveryPolicy,
|
|
15
17
|
ToolResultRecoveryOptions,
|
|
16
18
|
ToolResultRecoveryState,
|
|
19
|
+
ToolSafety,
|
|
17
20
|
} from "./use-tool-result-recovery.js"
|