@miragon/mcp-toolkit-ui 2.6.0 → 2.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -5
- 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.d.ts +7 -2
- package/dist/app/builder/BuilderBanners.d.ts.map +1 -1
- package/dist/app/builder/BuilderBanners.js +8 -3
- 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 +29 -17
- 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 +14 -6
- 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/EmptyHint.d.ts +4 -0
- package/dist/app/builder/EmptyHint.d.ts.map +1 -1
- package/dist/app/builder/EmptyHint.js +6 -1
- package/dist/app/builder/EmptyHint.js.map +1 -1
- 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 +5 -2
- package/dist/app/builder/PipelineStrip.d.ts.map +1 -1
- package/dist/app/builder/PipelineStrip.js +33 -15
- 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 +9 -3
- package/dist/app/builder/SaveDialog.js.map +1 -1
- package/dist/app/builder/Toolbar.d.ts +14 -4
- package/dist/app/builder/Toolbar.d.ts.map +1 -1
- package/dist/app/builder/Toolbar.js +24 -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 +146 -3
- package/dist/app/builder/builder-model.d.ts.map +1 -1
- package/dist/app/builder/builder-model.js +196 -5
- package/dist/app/builder/builder-model.js.map +1 -1
- package/dist/app/builder/labels.d.ts +13 -62
- package/dist/app/builder/labels.d.ts.map +1 -1
- package/dist/app/builder/labels.js +9 -58
- package/dist/app/builder/labels.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/status-chip.d.ts.map +1 -1
- package/dist/app/builder/status-chip.js +3 -2
- package/dist/app/builder/status-chip.js.map +1 -1
- package/dist/app/builder/use-builder-catalogue.d.ts +5 -1
- package/dist/app/builder/use-builder-catalogue.d.ts.map +1 -1
- package/dist/app/builder/use-builder-catalogue.js +50 -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/builder/use-save-dashboard.d.ts +3 -1
- package/dist/app/builder/use-save-dashboard.d.ts.map +1 -1
- package/dist/app/builder/use-save-dashboard.js +3 -1
- package/dist/app/builder/use-save-dashboard.js.map +1 -1
- 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 +18 -25
- package/dist/app/layout-builder.d.ts.map +1 -1
- package/dist/app/layout-builder.js +84 -78
- package/dist/app/layout-builder.js.map +1 -1
- package/dist/app/mcp-app-view-chrome.d.ts +103 -20
- package/dist/app/mcp-app-view-chrome.d.ts.map +1 -1
- package/dist/app/mcp-app-view-chrome.js +125 -40
- package/dist/app/mcp-app-view-chrome.js.map +1 -1
- package/dist/app/mcp-app-view-states.d.ts +23 -0
- package/dist/app/mcp-app-view-states.d.ts.map +1 -0
- package/dist/app/mcp-app-view-states.js +55 -0
- package/dist/app/mcp-app-view-states.js.map +1 -0
- package/dist/app/mcp-app-view.d.ts +72 -5
- package/dist/app/mcp-app-view.d.ts.map +1 -1
- package/dist/app/mcp-app-view.js +241 -141
- package/dist/app/mcp-app-view.js.map +1 -1
- package/dist/app/mcp-toolkit-app.d.ts +8 -2
- package/dist/app/mcp-toolkit-app.d.ts.map +1 -1
- package/dist/app/mcp-toolkit-app.js +33 -4
- 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 +42 -0
- package/dist/app/use-view-lifecycle.d.ts.map +1 -0
- package/dist/app/use-view-lifecycle.js +81 -0
- package/dist/app/use-view-lifecycle.js.map +1 -0
- package/dist/app/view-lifecycle.d.ts +185 -0
- package/dist/app/view-lifecycle.d.ts.map +1 -0
- package/dist/app/view-lifecycle.js +207 -0
- package/dist/app/view-lifecycle.js.map +1 -0
- 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 +6 -10
- package/dist/app/widget-props-sheet.d.ts.map +1 -1
- package/dist/app/widget-props-sheet.js +25 -20
- package/dist/app/widget-props-sheet.js.map +1 -1
- package/dist/app/widget-renderer.d.ts +41 -1
- package/dist/app/widget-renderer.d.ts.map +1 -1
- package/dist/app/widget-renderer.js +82 -31
- package/dist/app/widget-renderer.js.map +1 -1
- package/dist/components/DrillButton.d.ts +9 -6
- package/dist/components/DrillButton.d.ts.map +1 -1
- package/dist/components/DrillButton.js +9 -6
- package/dist/components/DrillButton.js.map +1 -1
- package/dist/components/EmptyState.d.ts +33 -0
- package/dist/components/EmptyState.d.ts.map +1 -0
- package/dist/components/EmptyState.js +19 -0
- package/dist/components/EmptyState.js.map +1 -0
- package/dist/components/ErrorState.d.ts +53 -0
- package/dist/components/ErrorState.d.ts.map +1 -0
- package/dist/components/ErrorState.js +77 -0
- package/dist/components/ErrorState.js.map +1 -0
- package/dist/components/FilterBar.d.ts +16 -5
- package/dist/components/FilterBar.d.ts.map +1 -1
- package/dist/components/FilterBar.js +13 -5
- package/dist/components/FilterBar.js.map +1 -1
- package/dist/components/GroupCard.d.ts.map +1 -1
- package/dist/components/GroupCard.js +2 -1
- package/dist/components/GroupCard.js.map +1 -1
- package/dist/components/KpiGrid.d.ts +70 -18
- package/dist/components/KpiGrid.d.ts.map +1 -1
- package/dist/components/KpiGrid.js +112 -23
- package/dist/components/KpiGrid.js.map +1 -1
- package/dist/components/ListFooter.d.ts +32 -4
- package/dist/components/ListFooter.d.ts.map +1 -1
- package/dist/components/ListFooter.js +35 -6
- 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 +36 -12
- package/dist/components/Pills.d.ts.map +1 -1
- package/dist/components/Pills.js +29 -8
- 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 +43 -0
- package/dist/components/Toast.d.ts.map +1 -0
- package/dist/components/Toast.js +106 -0
- package/dist/components/Toast.js.map +1 -0
- 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 +61 -44
- package/dist/hooks/use-tool-result-recovery.d.ts.map +1 -1
- package/dist/hooks/use-tool-result-recovery.js +62 -110
- package/dist/hooks/use-tool-result-recovery.js.map +1 -1
- package/dist/hooks/use-view-data.d.ts +55 -7
- package/dist/hooks/use-view-data.d.ts.map +1 -1
- package/dist/hooks/use-view-data.js +81 -11
- 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 +27 -13
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +29 -7
- 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/focus-ring.d.ts +18 -0
- package/dist/lib/focus-ring.d.ts.map +1 -0
- package/dist/lib/focus-ring.js +18 -0
- package/dist/lib/focus-ring.js.map +1 -0
- package/dist/lib/icon-stroke.d.ts +12 -0
- package/dist/lib/icon-stroke.d.ts.map +1 -0
- package/dist/lib/icon-stroke.js +12 -0
- package/dist/lib/icon-stroke.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 +86 -0
- package/dist/lib/toast-model.d.ts.map +1 -0
- package/dist/lib/toast-model.js +102 -0
- package/dist/lib/toast-model.js.map +1 -0
- package/dist/lib/tone-utils.d.ts +95 -9
- package/dist/lib/tone-utils.d.ts.map +1 -1
- package/dist/lib/tone-utils.js +100 -9
- 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 +3 -1
- package/dist/locale/index.d.ts.map +1 -1
- package/dist/locale/index.js +2 -1
- package/dist/locale/index.js.map +1 -1
- package/dist/locale/locale-provider.d.ts +42 -1
- package/dist/locale/locale-provider.d.ts.map +1 -1
- package/dist/locale/locale-provider.js +44 -3
- package/dist/locale/locale-provider.js.map +1 -1
- package/dist/locale/message-types.d.ts +336 -0
- package/dist/locale/message-types.d.ts.map +1 -0
- package/dist/locale/message-types.js +7 -0
- package/dist/locale/message-types.js.map +1 -0
- package/dist/locale/messages-de.d.ts +9 -0
- package/dist/locale/messages-de.d.ts.map +1 -0
- package/dist/locale/messages-de.js +185 -0
- package/dist/locale/messages-de.js.map +1 -0
- package/dist/locale/messages-en.d.ts +8 -0
- package/dist/locale/messages-en.d.ts.map +1 -0
- package/dist/locale/messages-en.js +184 -0
- package/dist/locale/messages-en.js.map +1 -0
- package/dist/locale/toolkit-messages.d.ts +57 -0
- package/dist/locale/toolkit-messages.d.ts.map +1 -0
- package/dist/locale/toolkit-messages.js +103 -0
- package/dist/locale/toolkit-messages.js.map +1 -0
- package/dist/primitives/alert.d.ts +1 -1
- package/dist/primitives/alert.d.ts.map +1 -1
- package/dist/primitives/alert.js +6 -2
- 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 +19 -6
- package/dist/primitives/badge.js.map +1 -1
- package/dist/primitives/button.d.ts +1 -1
- package/dist/primitives/button.d.ts.map +1 -1
- package/dist/primitives/button.js +12 -8
- package/dist/primitives/button.js.map +1 -1
- package/dist/primitives/card.js +4 -4
- package/dist/primitives/card.js.map +1 -1
- package/dist/primitives/dialog.d.ts +7 -3
- package/dist/primitives/dialog.d.ts.map +1 -1
- package/dist/primitives/dialog.js +22 -8
- package/dist/primitives/dialog.js.map +1 -1
- package/dist/primitives/input.d.ts.map +1 -1
- package/dist/primitives/input.js +4 -1
- package/dist/primitives/input.js.map +1 -1
- package/dist/primitives/scroll-area.js +1 -1
- package/dist/primitives/scroll-area.js.map +1 -1
- package/dist/primitives/select.d.ts.map +1 -1
- package/dist/primitives/select.js +6 -3
- package/dist/primitives/select.js.map +1 -1
- package/dist/primitives/sheet.d.ts +3 -1
- package/dist/primitives/sheet.d.ts.map +1 -1
- package/dist/primitives/sheet.js +17 -7
- package/dist/primitives/sheet.js.map +1 -1
- package/dist/primitives/skeleton.js +1 -1
- package/dist/primitives/skeleton.js.map +1 -1
- package/dist/primitives/switch.d.ts.map +1 -1
- package/dist/primitives/switch.js +2 -1
- package/dist/primitives/switch.js.map +1 -1
- package/dist/primitives/tabs.d.ts.map +1 -1
- package/dist/primitives/tabs.js +5 -3
- package/dist/primitives/tabs.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 +48 -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/render-scan.d.ts +25 -0
- package/dist/render-scan.d.ts.map +1 -0
- package/dist/render-scan.js +66 -0
- package/dist/render-scan.js.map +1 -0
- package/dist/theme/cd-tokens.d.ts +66 -0
- package/dist/theme/cd-tokens.d.ts.map +1 -0
- package/dist/theme/cd-tokens.js +64 -0
- package/dist/theme/cd-tokens.js.map +1 -0
- package/dist/theme/create-theme.d.ts +214 -39
- package/dist/theme/create-theme.d.ts.map +1 -1
- package/dist/theme/create-theme.js +204 -13
- package/dist/theme/create-theme.js.map +1 -1
- package/dist/theme/fonts.d.ts +38 -0
- package/dist/theme/fonts.d.ts.map +1 -0
- package/dist/theme/fonts.js +56 -0
- package/dist/theme/fonts.js.map +1 -0
- package/dist/theme/host-fonts.d.ts +39 -0
- package/dist/theme/host-fonts.d.ts.map +1 -0
- package/dist/theme/host-fonts.js +44 -0
- package/dist/theme/host-fonts.js.map +1 -0
- package/dist/theme/index.d.ts +8 -1
- package/dist/theme/index.d.ts.map +1 -1
- package/dist/theme/index.js +5 -1
- package/dist/theme/index.js.map +1 -1
- package/dist/theme/portal-container.d.ts +16 -0
- package/dist/theme/portal-container.d.ts.map +1 -0
- package/dist/theme/portal-container.js +19 -0
- package/dist/theme/portal-container.js.map +1 -0
- package/dist/theme/presets.d.ts +24 -9
- package/dist/theme/presets.d.ts.map +1 -1
- package/dist/theme/presets.js +44 -11
- package/dist/theme/presets.js.map +1 -1
- package/dist/theme/theme-provider.d.ts +27 -9
- package/dist/theme/theme-provider.d.ts.map +1 -1
- package/dist/theme/theme-provider.js +42 -20
- package/dist/theme/theme-provider.js.map +1 -1
- package/package.json +11 -5
- package/src/app/adapt-data-widget.tsx +2 -42
- package/src/app/builder/BuilderBanners.tsx +26 -13
- package/src/app/builder/CanvasRow.tsx +79 -32
- package/src/app/builder/CatalogueSheet.tsx +58 -35
- package/src/app/builder/CellPropsSheet.tsx +17 -2
- package/src/app/builder/DiscardDialog.tsx +59 -0
- package/src/app/builder/EmptyHint.tsx +16 -11
- package/src/app/builder/LayoutSurface.tsx +207 -67
- package/src/app/builder/PipelineStrip.tsx +265 -156
- package/src/app/builder/SaveDialog.tsx +25 -14
- package/src/app/builder/Toolbar.tsx +154 -57
- package/src/app/builder/Workspace.tsx +226 -56
- package/src/app/builder/builder-feedback.test.tsx +75 -0
- package/src/app/builder/builder-model.test.ts +5 -0
- package/src/app/builder/builder-model.ts +346 -6
- 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/labels.ts +15 -121
- package/src/app/builder/reveal.ts +10 -0
- package/src/app/builder/status-chip.tsx +8 -4
- package/src/app/builder/use-builder-catalogue.ts +62 -19
- package/src/app/builder/use-builder-session.ts +68 -0
- package/src/app/builder/use-display-resolver.ts +24 -0
- package/src/app/builder/use-save-dashboard.ts +5 -0
- package/src/app/index.ts +20 -3
- package/src/app/layout-builder.tsx +193 -186
- package/src/app/mcp-app-view-chrome.test.tsx +487 -14
- package/src/app/mcp-app-view-chrome.tsx +328 -100
- package/src/app/mcp-app-view-states.tsx +140 -0
- package/src/app/mcp-app-view.effects.test.tsx +292 -0
- package/src/app/mcp-app-view.test.tsx +478 -0
- package/src/app/mcp-app-view.tsx +447 -190
- package/src/app/mcp-toolkit-app.test.tsx +40 -0
- package/src/app/mcp-toolkit-app.tsx +39 -8
- 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 +122 -0
- package/src/app/view-lifecycle.test.ts +443 -0
- package/src/app/view-lifecycle.ts +334 -0
- 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 +67 -39
- package/src/app/widget-renderer.render.test.tsx +131 -0
- package/src/app/widget-renderer.tsx +136 -38
- package/src/components/DrillButton.tsx +24 -10
- package/src/components/EmptyState.tsx +83 -0
- package/src/components/ErrorState.tsx +204 -0
- package/src/components/FilterBar.test.tsx +82 -0
- package/src/components/FilterBar.tsx +56 -27
- package/src/components/GroupCard.tsx +3 -2
- package/src/components/KpiGrid.test.tsx +279 -0
- package/src/components/KpiGrid.tsx +329 -91
- package/src/components/ListFooter.test.tsx +116 -0
- package/src/components/ListFooter.tsx +110 -12
- 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 +78 -16
- package/src/components/SectionHeading.tsx +9 -3
- package/src/components/Toast.tsx +206 -0
- package/src/components/WidgetHeader.test.tsx +78 -0
- package/src/components/WidgetHeader.tsx +63 -16
- package/src/components/feedback-states.test.tsx +314 -0
- package/src/components/tone-figures.test.tsx +58 -0
- package/src/fonts.css +19 -0
- package/src/globals.css +9 -165
- 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 +28 -5
- package/src/hooks/use-tool-result-recovery.ts +101 -131
- package/src/hooks/use-view-data.test.tsx +169 -0
- package/src/hooks/use-view-data.ts +131 -10
- package/src/hooks/use-view-tool-query.ts +25 -19
- package/src/i18n-coverage.test.tsx +425 -0
- package/src/index.ts +102 -13
- package/src/lib/focus-return.test.ts +288 -0
- package/src/lib/focus-return.ts +96 -0
- package/src/lib/focus-ring.ts +20 -0
- package/src/lib/icon-stroke.test.ts +59 -0
- package/src/lib/icon-stroke.ts +13 -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 +233 -0
- package/src/lib/toast-model.ts +165 -0
- package/src/lib/tone-utils.test.ts +125 -16
- package/src/lib/tone-utils.ts +144 -15
- 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 +14 -1
- package/src/locale/locale-provider.test.tsx +87 -0
- package/src/locale/locale-provider.tsx +90 -3
- package/src/locale/message-types.ts +337 -0
- package/src/locale/messages-de.ts +198 -0
- package/src/locale/messages-en.ts +196 -0
- package/src/locale/toolkit-messages.test.ts +349 -0
- package/src/locale/toolkit-messages.ts +143 -0
- package/src/no-hardcoded-copy.test.ts +432 -0
- package/src/primitives/alert.tsx +7 -2
- package/src/primitives/badge.tsx +19 -7
- package/src/primitives/button.tsx +13 -8
- package/src/primitives/card.tsx +4 -4
- package/src/primitives/dialog.tsx +42 -8
- package/src/primitives/input.tsx +5 -3
- package/src/primitives/scroll-area.tsx +1 -1
- package/src/primitives/select.tsx +7 -4
- package/src/primitives/sheet.tsx +33 -6
- package/src/primitives/skeleton.tsx +1 -1
- package/src/primitives/switch.tsx +4 -2
- package/src/primitives/tabs.tsx +9 -6
- 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 +82 -6
- package/src/render-effects.tsx +96 -0
- package/src/render-scan.ts +75 -0
- package/src/theme/cd-tokens.generated.css +48 -0
- package/src/theme/cd-tokens.test.ts +109 -0
- package/src/theme/cd-tokens.ts +67 -0
- package/src/theme/create-theme.test.ts +253 -5
- package/src/theme/create-theme.ts +341 -41
- package/src/theme/fonts.test.ts +85 -0
- package/src/theme/fonts.ts +72 -0
- package/src/theme/host-fonts.test.ts +135 -0
- package/src/theme/host-fonts.ts +52 -0
- package/src/theme/index.ts +15 -1
- package/src/theme/portal-container.ts +20 -0
- package/src/theme/presets.ts +44 -11
- package/src/theme/theme-css.test.ts +619 -0
- package/src/theme/theme-provider.test.tsx +82 -0
- package/src/theme/theme-provider.tsx +60 -24
- package/src/theme.css +344 -0
- package/ui-catalog.json +783 -49
|
@@ -1,37 +1,51 @@
|
|
|
1
1
|
import type { ReactNode } from "react"
|
|
2
|
+
import { ArrowRight } from "lucide-react"
|
|
3
|
+
import { FOCUS_RING } from "../lib/focus-ring.js"
|
|
4
|
+
import { cn } from "../lib/utils.js"
|
|
2
5
|
|
|
3
6
|
export interface DrillButtonProps {
|
|
4
7
|
/** Button label. */
|
|
5
8
|
children: ReactNode
|
|
6
9
|
/** Drill/navigation handler — wire to in-widget navigation (e.g. `showWidget`). */
|
|
7
10
|
onDrill: () => void
|
|
8
|
-
/**
|
|
11
|
+
/**
|
|
12
|
+
* Optional leading icon — a Lucide icon of the domain, never a typed glyph.
|
|
13
|
+
* Decorative (`aria-hidden`). The trailing arrow is always rendered.
|
|
14
|
+
*/
|
|
9
15
|
icon?: ReactNode
|
|
10
16
|
/** `sm` for table rows, `md` for surface headers. Default `"sm"`. */
|
|
11
17
|
size?: "sm" | "md"
|
|
12
|
-
/** Accessible label when the visible text is terse. */
|
|
18
|
+
/** Accessible label when the visible text is terse; keep the visible text in it (WCAG 2.5.3). */
|
|
13
19
|
ariaLabel?: string
|
|
14
20
|
}
|
|
15
21
|
|
|
16
22
|
/**
|
|
17
23
|
* The single, deterministic in-widget navigation control: a neutral outline
|
|
18
|
-
* button with a trailing
|
|
19
|
-
* every in-app drill (row "Open", header nav) so they read identically
|
|
20
|
-
* visually quieter than primary/agentic actions. Decoupled from any
|
|
21
|
-
* an `onDrill` callback (e.g. wrapping `useHostActions().showWidget`).
|
|
24
|
+
* button with a trailing Lucide arrow that drills into a detail/sub-view. Use
|
|
25
|
+
* it for every in-app drill (row "Open", header nav) so they read identically
|
|
26
|
+
* and stay visually quieter than primary/agentic actions. Decoupled from any
|
|
27
|
+
* host — pass an `onDrill` callback (e.g. wrapping `useHostActions().showWidget`).
|
|
22
28
|
*/
|
|
23
29
|
export function DrillButton({ children, onDrill, icon, size = "sm", ariaLabel }: DrillButtonProps) {
|
|
24
|
-
const pad = size === "md" ? "px-3 py-1.5 text-
|
|
30
|
+
const pad = size === "md" ? "px-3 py-1.5 text-dense" : "px-2.5 py-1 text-small"
|
|
25
31
|
return (
|
|
26
32
|
<button
|
|
27
33
|
type="button"
|
|
28
34
|
onClick={onDrill}
|
|
29
35
|
aria-label={ariaLabel}
|
|
30
|
-
className={
|
|
36
|
+
className={cn(
|
|
37
|
+
"border-border text-foreground hover:border-focus hover:bg-accent hover:text-accent-foreground inline-flex items-center gap-1 rounded-md border font-medium transition-colors [&_svg:not([class*='size-'])]:size-4",
|
|
38
|
+
FOCUS_RING,
|
|
39
|
+
pad,
|
|
40
|
+
)}
|
|
31
41
|
>
|
|
32
|
-
{icon &&
|
|
42
|
+
{icon && (
|
|
43
|
+
<span aria-hidden="true" className="inline-flex">
|
|
44
|
+
{icon}
|
|
45
|
+
</span>
|
|
46
|
+
)}
|
|
33
47
|
{children}
|
|
34
|
-
<
|
|
48
|
+
<ArrowRight aria-hidden="true" className="size-4 shrink-0" />
|
|
35
49
|
</button>
|
|
36
50
|
)
|
|
37
51
|
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import type { ReactNode } from "react"
|
|
2
|
+
import { resolveTone, TONE_ICON } from "../lib/tone-utils.js"
|
|
3
|
+
import { cn } from "../lib/utils.js"
|
|
4
|
+
|
|
5
|
+
/** Heading element for an empty/error state title (the surface decides the outline level). */
|
|
6
|
+
export type StateHeadingLevel = "h2" | "h3" | "h4" | "p"
|
|
7
|
+
|
|
8
|
+
export interface EmptyStateProps {
|
|
9
|
+
/**
|
|
10
|
+
* Icon or mark above the title, rendered decoratively (`aria-hidden`): a
|
|
11
|
+
* Lucide icon for the concrete situation (e.g. `<CircleCheck />` for "all
|
|
12
|
+
* done"), or a brand mark (an app passes its logo; a customer brand passes
|
|
13
|
+
* its own or nothing). An SVG renders at 24 px unless it carries its own
|
|
14
|
+
* `size-*` class, so a mark can be larger (`<Komet className="size-12" />`).
|
|
15
|
+
*/
|
|
16
|
+
icon?: ReactNode
|
|
17
|
+
/** Colour of the icon slot: `"neutral"` (muted, default) or `"success"` ("all done"). */
|
|
18
|
+
tone?: "neutral" | "success"
|
|
19
|
+
/** What is empty, in plain words ("No open incidents"). */
|
|
20
|
+
title: ReactNode
|
|
21
|
+
/** One help line: why it is empty or what to do next. */
|
|
22
|
+
description?: ReactNode
|
|
23
|
+
/** The primary next step (a `Button`). */
|
|
24
|
+
action?: ReactNode
|
|
25
|
+
/** An optional second, quieter step. Empty states carry at most two actions. */
|
|
26
|
+
secondaryAction?: ReactNode
|
|
27
|
+
/** Element of the title. Default `"h3"` (inside a widget below its own heading). */
|
|
28
|
+
titleAs?: StateHeadingLevel
|
|
29
|
+
className?: string
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** The icon slot in the tone's icon colour (`TONE_ICON`): quiet, or the success tone. */
|
|
33
|
+
const ICON_TONE: Record<NonNullable<EmptyStateProps["tone"]>, string> = {
|
|
34
|
+
neutral: TONE_ICON.neutral,
|
|
35
|
+
success: TONE_ICON.success,
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* The one empty-state pattern: icon or mark, a title (600), one help line and
|
|
40
|
+
* up to two actions, centred in the space the content would take. Static (no
|
|
41
|
+
* live region): it replaces content, it does not announce a change.
|
|
42
|
+
*/
|
|
43
|
+
export function EmptyState({
|
|
44
|
+
icon,
|
|
45
|
+
tone = "neutral",
|
|
46
|
+
title,
|
|
47
|
+
description,
|
|
48
|
+
action,
|
|
49
|
+
secondaryAction,
|
|
50
|
+
titleAs: Title = "h3",
|
|
51
|
+
className,
|
|
52
|
+
}: EmptyStateProps) {
|
|
53
|
+
// An untyped tone resolves (`ok` → success); anything else stays quiet.
|
|
54
|
+
const iconTone = resolveTone(tone) === "success" ? "success" : "neutral"
|
|
55
|
+
return (
|
|
56
|
+
<div
|
|
57
|
+
data-slot="empty-state"
|
|
58
|
+
className={cn("flex flex-col items-center gap-2 px-4 py-8 text-center", className)}
|
|
59
|
+
>
|
|
60
|
+
{icon && (
|
|
61
|
+
<div
|
|
62
|
+
aria-hidden="true"
|
|
63
|
+
className={cn(
|
|
64
|
+
"mb-1 flex items-center justify-center [&_svg:not([class*='size-'])]:size-6",
|
|
65
|
+
ICON_TONE[iconTone],
|
|
66
|
+
)}
|
|
67
|
+
>
|
|
68
|
+
{icon}
|
|
69
|
+
</div>
|
|
70
|
+
)}
|
|
71
|
+
<Title className="text-foreground text-body font-semibold">{title}</Title>
|
|
72
|
+
{description && (
|
|
73
|
+
<p className="text-muted-foreground text-body max-w-prose text-balance">{description}</p>
|
|
74
|
+
)}
|
|
75
|
+
{(action || secondaryAction) && (
|
|
76
|
+
<div className="mt-2 flex flex-wrap items-center justify-center gap-2">
|
|
77
|
+
{action}
|
|
78
|
+
{secondaryAction}
|
|
79
|
+
</div>
|
|
80
|
+
)}
|
|
81
|
+
</div>
|
|
82
|
+
)
|
|
83
|
+
}
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import type { ReactNode } from "react"
|
|
2
|
+
import { CircleAlert, Info, RefreshCw, TriangleAlert, type LucideIcon } from "lucide-react"
|
|
3
|
+
import { useToolkitMessages } from "../locale/locale-provider.js"
|
|
4
|
+
import { Button } from "../primitives/button.js"
|
|
5
|
+
import { FOCUS_RING } from "../lib/focus-ring.js"
|
|
6
|
+
import { resolveTone, TONE_BORDER, TONE_ICON, TONE_SOFT } from "../lib/tone-utils.js"
|
|
7
|
+
import { cn } from "../lib/utils.js"
|
|
8
|
+
import type { StateHeadingLevel } from "./EmptyState.js"
|
|
9
|
+
|
|
10
|
+
/** Status tone of an {@link ErrorState}. */
|
|
11
|
+
export type ErrorStateTone = "danger" | "warning" | "info"
|
|
12
|
+
|
|
13
|
+
export interface ErrorStateProps {
|
|
14
|
+
/** What happened, in plain words ("This view couldn't be loaded"). */
|
|
15
|
+
title: ReactNode
|
|
16
|
+
/** What you can do next — or the human reason, when you can map the raw one. */
|
|
17
|
+
description?: ReactNode
|
|
18
|
+
/**
|
|
19
|
+
* The raw technical reason (engine exception, HTTP status), shown collapsed
|
|
20
|
+
* under a "Details" disclosure so it informs without leading.
|
|
21
|
+
*/
|
|
22
|
+
details?: ReactNode
|
|
23
|
+
/** Summary text of the details disclosure. Default: the locale's "Details". */
|
|
24
|
+
detailsLabel?: string
|
|
25
|
+
/** Renders a retry button when set. */
|
|
26
|
+
onRetry?: () => void
|
|
27
|
+
/** Retry button label. Default: the locale's "Try again". */
|
|
28
|
+
retryLabel?: string
|
|
29
|
+
/** Further actions after the retry button (e.g. a link to another view). */
|
|
30
|
+
actions?: ReactNode
|
|
31
|
+
/**
|
|
32
|
+
* Default `"danger"`. `"warning"` for partial failures, `"info"` for
|
|
33
|
+
* notices. An untyped retired name still resolves (`error` → `danger`).
|
|
34
|
+
*/
|
|
35
|
+
tone?: ErrorStateTone
|
|
36
|
+
/** Replace the tone's default Lucide icon. */
|
|
37
|
+
icon?: LucideIcon
|
|
38
|
+
/**
|
|
39
|
+
* `"block"` (default): the state that replaces a whole view or widget body.
|
|
40
|
+
* `"inline"`: a compact alert above content that still renders.
|
|
41
|
+
*/
|
|
42
|
+
variant?: "block" | "inline"
|
|
43
|
+
/**
|
|
44
|
+
* Live-region role. Default `"alert"` — an error that appears after the
|
|
45
|
+
* fact is announced. Use `"status"` for a quieter notice, or `"none"` when
|
|
46
|
+
* the state is part of the first paint.
|
|
47
|
+
*/
|
|
48
|
+
role?: "alert" | "status" | "none"
|
|
49
|
+
/** Element of the title. Default `"h2"` for `block`, `"p"` for `inline`. */
|
|
50
|
+
titleAs?: StateHeadingLevel
|
|
51
|
+
className?: string
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const STATE_ICON: Record<ErrorStateTone, LucideIcon> = {
|
|
55
|
+
danger: CircleAlert,
|
|
56
|
+
warning: TriangleAlert,
|
|
57
|
+
info: Info,
|
|
58
|
+
}
|
|
59
|
+
|
|
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
|
+
*/
|
|
76
|
+
const VARIANT = {
|
|
77
|
+
block: {
|
|
78
|
+
box: "gap-4 p-4",
|
|
79
|
+
icon: "size-5",
|
|
80
|
+
title: "text-body",
|
|
81
|
+
description: "text-body",
|
|
82
|
+
defaultTitleAs: "h2",
|
|
83
|
+
retry: "default",
|
|
84
|
+
},
|
|
85
|
+
inline: {
|
|
86
|
+
box: "gap-3 px-4 py-3 text-dense",
|
|
87
|
+
icon: "size-4",
|
|
88
|
+
title: "text-dense",
|
|
89
|
+
description: "text-dense",
|
|
90
|
+
defaultTitleAs: "p",
|
|
91
|
+
retry: "outline",
|
|
92
|
+
},
|
|
93
|
+
} as const satisfies Record<
|
|
94
|
+
NonNullable<ErrorStateProps["variant"]>,
|
|
95
|
+
{
|
|
96
|
+
box: string
|
|
97
|
+
icon: string
|
|
98
|
+
title: string
|
|
99
|
+
description: string
|
|
100
|
+
defaultTitleAs: StateHeadingLevel
|
|
101
|
+
retry: "default" | "outline"
|
|
102
|
+
}
|
|
103
|
+
>
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* The one error pattern: a Lucide icon, a title that says what happened, a
|
|
107
|
+
* line that says what you can do, the raw reason collapsed under "Details",
|
|
108
|
+
* and a retry. `inline` is the alert form above content that still renders
|
|
109
|
+
* (failed data steps); `block` replaces a whole view or widget body.
|
|
110
|
+
*/
|
|
111
|
+
export function ErrorState({
|
|
112
|
+
title,
|
|
113
|
+
description,
|
|
114
|
+
details,
|
|
115
|
+
detailsLabel,
|
|
116
|
+
onRetry,
|
|
117
|
+
retryLabel,
|
|
118
|
+
actions,
|
|
119
|
+
tone: toneProp,
|
|
120
|
+
icon,
|
|
121
|
+
variant = "block",
|
|
122
|
+
role = "alert",
|
|
123
|
+
titleAs,
|
|
124
|
+
className,
|
|
125
|
+
}: ErrorStateProps) {
|
|
126
|
+
const tone = stateTone(toneProp)
|
|
127
|
+
const style = VARIANT[variant]
|
|
128
|
+
const Icon = icon ?? STATE_ICON[tone]
|
|
129
|
+
const Title = titleAs ?? style.defaultTitleAs
|
|
130
|
+
// The status colour as rim, tint and icon; every word on the tint is the
|
|
131
|
+
// foreground (TONE_SOFT, CI §3.3). On dark the tint is transparent and the
|
|
132
|
+
// rim carries the tone.
|
|
133
|
+
return (
|
|
134
|
+
<div
|
|
135
|
+
data-slot="error-state"
|
|
136
|
+
data-variant={variant}
|
|
137
|
+
data-tone={tone}
|
|
138
|
+
role={role === "none" ? undefined : role}
|
|
139
|
+
className={cn(
|
|
140
|
+
"flex items-start rounded-md border",
|
|
141
|
+
style.box,
|
|
142
|
+
TONE_SOFT[tone],
|
|
143
|
+
TONE_BORDER[tone],
|
|
144
|
+
className,
|
|
145
|
+
)}
|
|
146
|
+
>
|
|
147
|
+
<Icon aria-hidden="true" className={cn("mt-0.5 shrink-0", style.icon, TONE_ICON[tone])} />
|
|
148
|
+
<div className="flex min-w-0 flex-1 flex-col gap-1">
|
|
149
|
+
<Title className={cn("font-semibold", style.title)}>{title}</Title>
|
|
150
|
+
{description && <div className={cn("break-words", style.description)}>{description}</div>}
|
|
151
|
+
{details && <ErrorDetails label={detailsLabel}>{details}</ErrorDetails>}
|
|
152
|
+
<ErrorActions
|
|
153
|
+
onRetry={onRetry}
|
|
154
|
+
retryLabel={retryLabel}
|
|
155
|
+
retryVariant={style.retry}
|
|
156
|
+
actions={actions}
|
|
157
|
+
/>
|
|
158
|
+
</div>
|
|
159
|
+
</div>
|
|
160
|
+
)
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* The raw reason, collapsed: a native disclosure (keyboard + screen-reader
|
|
165
|
+
* ready). It sits on the tint, so its text stays the foreground; the collapse
|
|
166
|
+
* and the small mono type keep it quieter than the title.
|
|
167
|
+
*/
|
|
168
|
+
function ErrorDetails({ label, children }: { label: string | undefined; children: ReactNode }) {
|
|
169
|
+
const messages = useToolkitMessages()
|
|
170
|
+
return (
|
|
171
|
+
<details className="text-small">
|
|
172
|
+
<summary className={`w-fit cursor-pointer rounded-sm py-0.5 select-none ${FOCUS_RING}`}>
|
|
173
|
+
{label ?? messages.common.details}
|
|
174
|
+
</summary>
|
|
175
|
+
<pre className="mt-1 font-mono break-words whitespace-pre-wrap">{children}</pre>
|
|
176
|
+
</details>
|
|
177
|
+
)
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function ErrorActions({
|
|
181
|
+
onRetry,
|
|
182
|
+
retryLabel,
|
|
183
|
+
retryVariant,
|
|
184
|
+
actions,
|
|
185
|
+
}: {
|
|
186
|
+
onRetry: (() => void) | undefined
|
|
187
|
+
retryLabel: string | undefined
|
|
188
|
+
retryVariant: "default" | "outline"
|
|
189
|
+
actions: ReactNode
|
|
190
|
+
}) {
|
|
191
|
+
const messages = useToolkitMessages()
|
|
192
|
+
if (!onRetry && !actions) return null
|
|
193
|
+
return (
|
|
194
|
+
<div className="mt-2 flex flex-wrap items-center gap-2">
|
|
195
|
+
{onRetry && (
|
|
196
|
+
<Button type="button" size="sm" variant={retryVariant} onClick={onRetry}>
|
|
197
|
+
<RefreshCw aria-hidden="true" />
|
|
198
|
+
{retryLabel ?? messages.common.retry}
|
|
199
|
+
</Button>
|
|
200
|
+
)}
|
|
201
|
+
{actions}
|
|
202
|
+
</div>
|
|
203
|
+
)
|
|
204
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { renderToStaticMarkup } from "react-dom/server"
|
|
2
|
+
import { describe, expect, it } from "vitest"
|
|
3
|
+
import { LocaleProvider } from "../locale/locale-provider.js"
|
|
4
|
+
import { TOOLKIT_MESSAGES } from "../locale/toolkit-messages.js"
|
|
5
|
+
import { attribute, openingTags } from "../render-scan.js"
|
|
6
|
+
import { FOCUS_RING } from "../lib/focus-ring.js"
|
|
7
|
+
import { TONE_BORDER, TONE_SOFT } from "../lib/tone-utils.js"
|
|
8
|
+
import { FilterBar } from "./FilterBar.js"
|
|
9
|
+
|
|
10
|
+
const noop = (): void => {}
|
|
11
|
+
const classOf = (tag: string | undefined) => attribute(tag ?? "", "class") ?? ""
|
|
12
|
+
|
|
13
|
+
function render(props: Partial<Parameters<typeof FilterBar>[0]> = {}, locale = "en") {
|
|
14
|
+
return renderToStaticMarkup(
|
|
15
|
+
<LocaleProvider locale={locale}>
|
|
16
|
+
<FilterBar
|
|
17
|
+
search=""
|
|
18
|
+
onSearchChange={noop}
|
|
19
|
+
chips={[
|
|
20
|
+
{ id: "open", label: "Open", count: 3, active: true },
|
|
21
|
+
{ id: "done", label: "Done" },
|
|
22
|
+
]}
|
|
23
|
+
onChipToggle={noop}
|
|
24
|
+
{...props}
|
|
25
|
+
/>
|
|
26
|
+
</LocaleProvider>,
|
|
27
|
+
)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
describe("FilterBar: a labelled search and toggle chips", () => {
|
|
31
|
+
it("labels the search input in the locale (a placeholder is no label)", () => {
|
|
32
|
+
const [input] = openingTags(render(), "input")
|
|
33
|
+
expect(attribute(input ?? "", "type")).toBe("search")
|
|
34
|
+
expect(attribute(input ?? "", "aria-label")).toBe(TOOLKIT_MESSAGES.en.filterBar.searchLabel)
|
|
35
|
+
expect(attribute(input ?? "", "placeholder")).toBe("Filter…")
|
|
36
|
+
const [german] = openingTags(render({}, "de-DE"), "input")
|
|
37
|
+
expect(attribute(german ?? "", "aria-label")).toBe("Filtern")
|
|
38
|
+
expect(attribute(german ?? "", "placeholder")).toBe("Filtern…")
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
it("takes the caller's search label and placeholder", () => {
|
|
42
|
+
const [input] = openingTags(
|
|
43
|
+
render({ searchAriaLabel: "Filter tasks", searchPlaceholder: "Filter tasks…" }),
|
|
44
|
+
"input",
|
|
45
|
+
)
|
|
46
|
+
expect(attribute(input ?? "", "aria-label")).toBe("Filter tasks")
|
|
47
|
+
expect(attribute(input ?? "", "placeholder")).toBe("Filter tasks…")
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
it("groups the chips under a name and marks each one's state with aria-pressed", () => {
|
|
51
|
+
const html = render()
|
|
52
|
+
const group = openingTags(html, "div").find((t) => attribute(t, "role") === "group")
|
|
53
|
+
expect(attribute(group ?? "", "aria-label")).toBe("Filters")
|
|
54
|
+
expect(
|
|
55
|
+
attribute(openingTags(render({ chipsAriaLabel: "Priority" }), "div")[1] ?? "", "aria-label"),
|
|
56
|
+
).toBe("Priority")
|
|
57
|
+
const [open, done] = openingTags(html, "button")
|
|
58
|
+
expect(attribute(open ?? "", "aria-pressed")).toBe("true")
|
|
59
|
+
expect(attribute(done ?? "", "aria-pressed")).toBe("false")
|
|
60
|
+
for (const chip of [open, done]) {
|
|
61
|
+
expect(attribute(chip ?? "", "type")).toBe("button")
|
|
62
|
+
expect(classOf(chip)).toContain(FOCUS_RING)
|
|
63
|
+
expect(classOf(chip)).toContain("text-small")
|
|
64
|
+
}
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
it("shows the active chip as a selection: info tint and edge around foreground text", () => {
|
|
68
|
+
const [open, done] = openingTags(render(), "button")
|
|
69
|
+
expect(classOf(open)).toContain(TONE_SOFT.info)
|
|
70
|
+
expect(classOf(open)).toContain(TONE_BORDER.info)
|
|
71
|
+
expect(classOf(open)).not.toMatch(/text-info|text-accent-foreground/)
|
|
72
|
+
expect(classOf(done)).toContain("text-muted-foreground")
|
|
73
|
+
expect(classOf(done)).not.toContain("bg-info-soft")
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
it("renders no empty group without chips and merges a className", () => {
|
|
77
|
+
const html = render({ chips: [], className: "mb-4" })
|
|
78
|
+
expect(html).not.toContain('role="group"')
|
|
79
|
+
expect(classOf(openingTags(html, "div")[0])).toContain("mb-4")
|
|
80
|
+
expect(html).not.toMatch(/text-xs|text-\[\d+px\]/)
|
|
81
|
+
})
|
|
82
|
+
})
|
|
@@ -1,68 +1,97 @@
|
|
|
1
|
+
import { FOCUS_RING } from "../lib/focus-ring.js"
|
|
2
|
+
import { TONE_BORDER, TONE_SOFT } from "../lib/tone-utils.js"
|
|
3
|
+
import { cn } from "../lib/utils.js"
|
|
4
|
+
import { useToolkitMessages } from "../locale/locale-provider.js"
|
|
1
5
|
import { Input } from "../primitives/input.js"
|
|
2
6
|
|
|
3
7
|
export interface FilterChip {
|
|
4
8
|
/** Stable id passed back to `onChipToggle`. */
|
|
5
9
|
id: string
|
|
6
|
-
/** Visible chip label. */
|
|
10
|
+
/** Visible chip label (its accessible name, with the count). */
|
|
7
11
|
label: string
|
|
8
12
|
/** Optional count rendered after the label. */
|
|
9
13
|
count?: number | string
|
|
10
|
-
/** Whether the chip is currently active. */
|
|
14
|
+
/** Whether the chip is currently active (`aria-pressed`). */
|
|
11
15
|
active?: boolean
|
|
12
16
|
}
|
|
13
17
|
|
|
14
18
|
export interface FilterBarProps {
|
|
15
19
|
/** Current search text (controlled). */
|
|
16
20
|
search: string
|
|
17
|
-
/** Placeholder for the search input. Default
|
|
21
|
+
/** Placeholder for the search input. Default: the locale's "Filter…". */
|
|
18
22
|
searchPlaceholder?: string
|
|
23
|
+
/**
|
|
24
|
+
* Accessible name of the search input. Default: the locale's "Filter" — a
|
|
25
|
+
* placeholder is no label (it disappears while you type).
|
|
26
|
+
*/
|
|
27
|
+
searchAriaLabel?: string
|
|
19
28
|
/** Called with the new search text on every keystroke. */
|
|
20
29
|
onSearchChange: (next: string) => void
|
|
21
30
|
/** Filter chips rendered to the right of the search input. */
|
|
22
31
|
chips: FilterChip[]
|
|
23
32
|
/** Called with a chip id when it is toggled. */
|
|
24
33
|
onChipToggle: (id: string) => void
|
|
34
|
+
/** Names the chip row (`role="group"`). Default: the locale's "Filters". */
|
|
35
|
+
chipsAriaLabel?: string
|
|
36
|
+
className?: string
|
|
25
37
|
}
|
|
26
38
|
|
|
27
39
|
/**
|
|
28
40
|
* Search input + toggleable chip row for filtering a list/table of widget
|
|
29
|
-
* items.
|
|
41
|
+
* items. The input is labelled; every chip is a toggle button whose state is
|
|
42
|
+
* `aria-pressed`. An active chip is a selection in the CI pattern: the info
|
|
43
|
+
* tint and edge around foreground text. Pair with `useDebouncedValue` to
|
|
44
|
+
* avoid re-querying on every keystroke.
|
|
30
45
|
*/
|
|
31
46
|
export function FilterBar({
|
|
32
47
|
search,
|
|
33
|
-
searchPlaceholder
|
|
48
|
+
searchPlaceholder,
|
|
49
|
+
searchAriaLabel,
|
|
34
50
|
onSearchChange,
|
|
35
51
|
chips,
|
|
36
52
|
onChipToggle,
|
|
53
|
+
chipsAriaLabel,
|
|
54
|
+
className,
|
|
37
55
|
}: FilterBarProps) {
|
|
56
|
+
const { filterBar } = useToolkitMessages()
|
|
38
57
|
return (
|
|
39
|
-
<div className="flex flex-wrap items-center gap-2">
|
|
58
|
+
<div data-slot="filter-bar" className={cn("flex flex-wrap items-center gap-2", className)}>
|
|
40
59
|
<Input
|
|
41
60
|
type="search"
|
|
42
61
|
value={search}
|
|
43
62
|
onChange={(e) => onSearchChange(e.target.value)}
|
|
44
|
-
placeholder={searchPlaceholder}
|
|
45
|
-
|
|
63
|
+
placeholder={searchPlaceholder ?? filterBar.searchPlaceholder}
|
|
64
|
+
aria-label={searchAriaLabel ?? filterBar.searchLabel}
|
|
65
|
+
className="text-dense md:text-dense h-9 min-w-[220px] flex-1"
|
|
46
66
|
/>
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
67
|
+
{chips.length > 0 && (
|
|
68
|
+
<div
|
|
69
|
+
role="group"
|
|
70
|
+
aria-label={chipsAriaLabel ?? filterBar.chipsLabel}
|
|
71
|
+
className="flex flex-wrap gap-1.5"
|
|
72
|
+
>
|
|
73
|
+
{chips.map((chip) => (
|
|
74
|
+
<button
|
|
75
|
+
type="button"
|
|
76
|
+
key={chip.id}
|
|
77
|
+
onClick={() => onChipToggle(chip.id)}
|
|
78
|
+
aria-pressed={Boolean(chip.active)}
|
|
79
|
+
className={cn(
|
|
80
|
+
"text-small inline-flex items-center gap-1.5 rounded-sm border px-2.5 py-1 font-medium transition-colors",
|
|
81
|
+
FOCUS_RING,
|
|
82
|
+
chip.active
|
|
83
|
+
? cn(TONE_SOFT.info, TONE_BORDER.info, "font-semibold")
|
|
84
|
+
: "border-border bg-background text-muted-foreground hover:bg-muted hover:text-foreground",
|
|
85
|
+
)}
|
|
86
|
+
>
|
|
87
|
+
{chip.label}
|
|
88
|
+
{chip.count !== undefined && chip.count !== "" && (
|
|
89
|
+
<span className="font-normal tabular-nums">{chip.count}</span>
|
|
90
|
+
)}
|
|
91
|
+
</button>
|
|
92
|
+
))}
|
|
93
|
+
</div>
|
|
94
|
+
)}
|
|
66
95
|
</div>
|
|
67
96
|
)
|
|
68
97
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { KeyboardEvent, MouseEvent, ReactNode } from "react"
|
|
2
|
+
import { FOCUS_RING_INSET } from "../lib/focus-ring.js"
|
|
2
3
|
|
|
3
4
|
export interface GroupCardProps {
|
|
4
5
|
/** Whether the body is expanded. */
|
|
@@ -33,14 +34,14 @@ export function GroupCard({ expanded, onToggle, summary, children }: GroupCardPr
|
|
|
33
34
|
}
|
|
34
35
|
|
|
35
36
|
return (
|
|
36
|
-
<div className="border-border mb-2 overflow-hidden rounded-
|
|
37
|
+
<div className="border-border mb-2 overflow-hidden rounded-md border">
|
|
37
38
|
<div
|
|
38
39
|
role="button"
|
|
39
40
|
tabIndex={0}
|
|
40
41
|
aria-expanded={expanded}
|
|
41
42
|
onClick={handleClick}
|
|
42
43
|
onKeyDown={handleKeyDown}
|
|
43
|
-
className={`bg-card hover:bg-muted
|
|
44
|
+
className={`bg-card hover:bg-muted cursor-pointer transition-colors ${FOCUS_RING_INSET} ${
|
|
44
45
|
expanded ? "border-border border-b" : ""
|
|
45
46
|
}`}
|
|
46
47
|
>
|