@miragon/mcp-toolkit-ui 2.6.0 → 2.7.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 +7 -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.map +1 -1
- package/dist/app/builder/CanvasRow.js +12 -14
- 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 +6 -5
- package/dist/app/builder/CatalogueSheet.js.map +1 -1
- 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.js +3 -3
- package/dist/app/builder/LayoutSurface.js.map +1 -1
- package/dist/app/builder/PipelineStrip.d.ts +3 -1
- package/dist/app/builder/PipelineStrip.d.ts.map +1 -1
- package/dist/app/builder/PipelineStrip.js +11 -6
- package/dist/app/builder/PipelineStrip.js.map +1 -1
- package/dist/app/builder/SaveDialog.d.ts.map +1 -1
- package/dist/app/builder/SaveDialog.js +2 -2
- package/dist/app/builder/SaveDialog.js.map +1 -1
- package/dist/app/builder/Toolbar.d.ts +3 -1
- package/dist/app/builder/Toolbar.d.ts.map +1 -1
- package/dist/app/builder/Toolbar.js +9 -3
- package/dist/app/builder/Toolbar.js.map +1 -1
- package/dist/app/builder/Workspace.js +2 -2
- package/dist/app/builder/Workspace.js.map +1 -1
- package/dist/app/builder/builder-model.d.ts +3 -2
- package/dist/app/builder/builder-model.d.ts.map +1 -1
- package/dist/app/builder/builder-model.js +5 -4
- 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/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 +1 -0
- package/dist/app/builder/use-builder-catalogue.d.ts.map +1 -1
- package/dist/app/builder/use-builder-catalogue.js +6 -0
- package/dist/app/builder/use-builder-catalogue.js.map +1 -1
- 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 +1 -1
- package/dist/app/index.d.ts.map +1 -1
- package/dist/app/index.js.map +1 -1
- package/dist/app/layout-builder.d.ts +7 -2
- package/dist/app/layout-builder.d.ts.map +1 -1
- package/dist/app/layout-builder.js +17 -5
- package/dist/app/layout-builder.js.map +1 -1
- package/dist/app/mcp-app-view-chrome.d.ts +94 -20
- package/dist/app/mcp-app-view-chrome.d.ts.map +1 -1
- package/dist/app/mcp-app-view-chrome.js +109 -40
- package/dist/app/mcp-app-view-chrome.js.map +1 -1
- package/dist/app/mcp-app-view-states.d.ts +15 -0
- package/dist/app/mcp-app-view-states.d.ts.map +1 -0
- package/dist/app/mcp-app-view-states.js +34 -0
- package/dist/app/mcp-app-view-states.js.map +1 -0
- package/dist/app/mcp-app-view.d.ts +40 -5
- package/dist/app/mcp-app-view.d.ts.map +1 -1
- package/dist/app/mcp-app-view.js +174 -128
- package/dist/app/mcp-app-view.js.map +1 -1
- package/dist/app/mcp-toolkit-app.d.ts +2 -1
- package/dist/app/mcp-toolkit-app.d.ts.map +1 -1
- package/dist/app/mcp-toolkit-app.js +25 -3
- package/dist/app/mcp-toolkit-app.js.map +1 -1
- package/dist/app/use-view-lifecycle.d.ts +38 -0
- package/dist/app/use-view-lifecycle.d.ts.map +1 -0
- package/dist/app/use-view-lifecycle.js +79 -0
- package/dist/app/use-view-lifecycle.js.map +1 -0
- package/dist/app/view-lifecycle.d.ts +109 -0
- package/dist/app/view-lifecycle.d.ts.map +1 -0
- package/dist/app/view-lifecycle.js +120 -0
- package/dist/app/view-lifecycle.js.map +1 -0
- package/dist/app/widget-props-sheet.d.ts +3 -9
- package/dist/app/widget-props-sheet.d.ts.map +1 -1
- package/dist/app/widget-props-sheet.js +6 -13
- 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.map +1 -1
- package/dist/components/DrillButton.js +2 -1
- 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 +17 -0
- package/dist/components/EmptyState.js.map +1 -0
- package/dist/components/ErrorState.d.ts +50 -0
- package/dist/components/ErrorState.d.ts.map +1 -0
- package/dist/components/ErrorState.js +60 -0
- package/dist/components/ErrorState.js.map +1 -0
- package/dist/components/FilterBar.d.ts +1 -1
- package/dist/components/FilterBar.d.ts.map +1 -1
- package/dist/components/FilterBar.js +7 -4
- 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 +10 -3
- package/dist/components/KpiGrid.d.ts.map +1 -1
- package/dist/components/KpiGrid.js +31 -9
- package/dist/components/KpiGrid.js.map +1 -1
- package/dist/components/ListFooter.d.ts +3 -2
- package/dist/components/ListFooter.d.ts.map +1 -1
- package/dist/components/ListFooter.js +14 -4
- package/dist/components/ListFooter.js.map +1 -1
- package/dist/components/Pills.d.ts +10 -6
- package/dist/components/Pills.d.ts.map +1 -1
- package/dist/components/Pills.js +12 -6
- package/dist/components/Pills.js.map +1 -1
- package/dist/components/Toast.d.ts +37 -0
- package/dist/components/Toast.d.ts.map +1 -0
- package/dist/components/Toast.js +91 -0
- package/dist/components/Toast.js.map +1 -0
- package/dist/components/WidgetHeader.js +1 -1
- package/dist/hooks/use-tool-result-recovery.d.ts +23 -1
- package/dist/hooks/use-tool-result-recovery.d.ts.map +1 -1
- package/dist/hooks/use-tool-result-recovery.js +21 -7
- package/dist/hooks/use-tool-result-recovery.js.map +1 -1
- package/dist/hooks/use-view-data.d.ts +6 -0
- package/dist/hooks/use-view-data.d.ts.map +1 -1
- package/dist/hooks/use-view-data.js +6 -1
- package/dist/hooks/use-view-data.js.map +1 -1
- package/dist/index.d.ts +12 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +14 -4
- package/dist/index.js.map +1 -1
- 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/toast-model.d.ts +46 -0
- package/dist/lib/toast-model.d.ts.map +1 -0
- package/dist/lib/toast-model.js +36 -0
- package/dist/lib/toast-model.js.map +1 -0
- package/dist/lib/tone-utils.d.ts +43 -9
- package/dist/lib/tone-utils.d.ts.map +1 -1
- package/dist/lib/tone-utils.js +36 -7
- package/dist/lib/tone-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 +223 -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 +151 -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 +150 -0
- package/dist/locale/messages-en.js.map +1 -0
- package/dist/locale/toolkit-messages.d.ts +39 -0
- package/dist/locale/toolkit-messages.d.ts.map +1 -0
- package/dist/locale/toolkit-messages.js +50 -0
- package/dist/locale/toolkit-messages.js.map +1 -0
- package/dist/primitives/alert.d.ts.map +1 -1
- package/dist/primitives/alert.js +4 -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 +14 -6
- package/dist/primitives/badge.js.map +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 +14 -7
- 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 +5 -2
- 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 +12 -6
- 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/render-cases.d.ts.map +1 -1
- package/dist/render-cases.js +20 -2
- package/dist/render-cases.js.map +1 -1
- 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 +196 -39
- package/dist/theme/create-theme.d.ts.map +1 -1
- package/dist/theme/create-theme.js +189 -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 +9 -4
- package/src/app/builder/BuilderBanners.tsx +26 -13
- package/src/app/builder/CanvasRow.tsx +22 -21
- package/src/app/builder/CatalogueSheet.tsx +16 -14
- package/src/app/builder/EmptyHint.tsx +16 -11
- package/src/app/builder/LayoutSurface.tsx +7 -7
- package/src/app/builder/PipelineStrip.tsx +26 -17
- package/src/app/builder/SaveDialog.tsx +11 -12
- package/src/app/builder/Toolbar.tsx +19 -7
- package/src/app/builder/Workspace.tsx +5 -3
- 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 +5 -4
- package/src/app/builder/labels.ts +15 -121
- package/src/app/builder/status-chip.tsx +8 -4
- package/src/app/builder/use-builder-catalogue.ts +6 -0
- package/src/app/builder/use-save-dashboard.ts +5 -0
- package/src/app/index.ts +8 -1
- package/src/app/layout-builder.tsx +23 -3
- package/src/app/mcp-app-view-chrome.test.tsx +347 -14
- package/src/app/mcp-app-view-chrome.tsx +278 -100
- package/src/app/mcp-app-view-states.tsx +100 -0
- package/src/app/mcp-app-view.test.tsx +258 -0
- package/src/app/mcp-app-view.tsx +292 -185
- package/src/app/mcp-toolkit-app.tsx +31 -7
- package/src/app/use-view-lifecycle.ts +116 -0
- package/src/app/view-lifecycle.test.ts +227 -0
- package/src/app/view-lifecycle.ts +174 -0
- package/src/app/widget-props-sheet.tsx +13 -26
- package/src/app/widget-renderer.render.test.tsx +131 -0
- package/src/app/widget-renderer.tsx +136 -38
- package/src/components/DrillButton.tsx +2 -1
- package/src/components/EmptyState.tsx +81 -0
- package/src/components/ErrorState.tsx +182 -0
- package/src/components/FilterBar.tsx +9 -6
- package/src/components/GroupCard.tsx +3 -2
- package/src/components/KpiGrid.tsx +75 -44
- package/src/components/ListFooter.tsx +17 -6
- package/src/components/Pills.tsx +20 -11
- package/src/components/Toast.tsx +188 -0
- package/src/components/WidgetHeader.tsx +1 -1
- package/src/components/feedback-states.test.tsx +225 -0
- package/src/components/tone-figures.test.tsx +54 -0
- package/src/fonts.css +19 -0
- package/src/globals.css +9 -165
- package/src/hooks/use-tool-result-recovery.test.ts +25 -1
- package/src/hooks/use-tool-result-recovery.ts +34 -6
- package/src/hooks/use-view-data.test.tsx +60 -0
- package/src/hooks/use-view-data.ts +12 -1
- package/src/i18n-coverage.test.tsx +323 -0
- package/src/index.ts +57 -5
- 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/toast-model.test.ts +68 -0
- package/src/lib/toast-model.ts +63 -0
- package/src/lib/tone-utils.test.ts +47 -14
- package/src/lib/tone-utils.ts +61 -13
- package/src/locale/index.ts +13 -1
- package/src/locale/locale-provider.test.tsx +87 -0
- package/src/locale/locale-provider.tsx +90 -3
- package/src/locale/message-types.ts +221 -0
- package/src/locale/messages-de.ts +159 -0
- package/src/locale/messages-en.ts +157 -0
- package/src/locale/toolkit-messages.test.ts +216 -0
- package/src/locale/toolkit-messages.ts +82 -0
- package/src/no-hardcoded-copy.test.ts +432 -0
- package/src/primitives/alert.tsx +4 -2
- package/src/primitives/badge.tsx +14 -7
- package/src/primitives/button.tsx +13 -8
- package/src/primitives/card.tsx +4 -4
- package/src/primitives/dialog.tsx +32 -7
- package/src/primitives/input.tsx +5 -3
- package/src/primitives/scroll-area.tsx +1 -1
- package/src/primitives/select.tsx +6 -3
- package/src/primitives/sheet.tsx +26 -6
- package/src/primitives/skeleton.tsx +1 -1
- package/src/primitives/switch.tsx +4 -2
- package/src/primitives/tabs.tsx +9 -6
- package/src/render-cases.tsx +45 -1
- package/src/render-scan.ts +75 -0
- package/src/theme/cd-tokens.generated.css +48 -0
- package/src/theme/cd-tokens.test.ts +107 -0
- package/src/theme/cd-tokens.ts +67 -0
- package/src/theme/create-theme.test.ts +243 -5
- package/src/theme/create-theme.ts +313 -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 +568 -0
- package/src/theme/theme-provider.test.tsx +82 -0
- package/src/theme/theme-provider.tsx +60 -24
- package/src/theme.css +314 -0
- package/ui-catalog.json +344 -17
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { FOCUS_RING } from "../lib/focus-ring.js"
|
|
2
|
+
import { useToolkitMessages } from "../locale/locale-provider.js"
|
|
1
3
|
import { Input } from "../primitives/input.js"
|
|
2
4
|
|
|
3
5
|
export interface FilterChip {
|
|
@@ -14,7 +16,7 @@ export interface FilterChip {
|
|
|
14
16
|
export interface FilterBarProps {
|
|
15
17
|
/** Current search text (controlled). */
|
|
16
18
|
search: string
|
|
17
|
-
/** Placeholder for the search input. Default
|
|
19
|
+
/** Placeholder for the search input. Default: the locale's "Filter…". */
|
|
18
20
|
searchPlaceholder?: string
|
|
19
21
|
/** Called with the new search text on every keystroke. */
|
|
20
22
|
onSearchChange: (next: string) => void
|
|
@@ -30,18 +32,19 @@ export interface FilterBarProps {
|
|
|
30
32
|
*/
|
|
31
33
|
export function FilterBar({
|
|
32
34
|
search,
|
|
33
|
-
searchPlaceholder
|
|
35
|
+
searchPlaceholder,
|
|
34
36
|
onSearchChange,
|
|
35
37
|
chips,
|
|
36
38
|
onChipToggle,
|
|
37
39
|
}: FilterBarProps) {
|
|
40
|
+
const messages = useToolkitMessages()
|
|
38
41
|
return (
|
|
39
42
|
<div className="flex flex-wrap items-center gap-2">
|
|
40
43
|
<Input
|
|
41
44
|
type="search"
|
|
42
45
|
value={search}
|
|
43
46
|
onChange={(e) => onSearchChange(e.target.value)}
|
|
44
|
-
placeholder={searchPlaceholder}
|
|
47
|
+
placeholder={searchPlaceholder ?? messages.filterBar.searchPlaceholder}
|
|
45
48
|
className="h-9 min-w-[220px] flex-1 text-sm"
|
|
46
49
|
/>
|
|
47
50
|
<div className="flex flex-wrap gap-1.5">
|
|
@@ -50,15 +53,15 @@ export function FilterBar({
|
|
|
50
53
|
type="button"
|
|
51
54
|
key={chip.id}
|
|
52
55
|
onClick={() => onChipToggle(chip.id)}
|
|
53
|
-
className={`inline-flex items-center gap-1.5 rounded-
|
|
56
|
+
className={`inline-flex items-center gap-1.5 rounded-sm border px-2.5 py-1 text-xs font-medium transition-colors ${FOCUS_RING} ${
|
|
54
57
|
chip.active
|
|
55
|
-
? "bg-accent border-
|
|
58
|
+
? "bg-accent border-focus text-accent-foreground font-semibold"
|
|
56
59
|
: "border-border bg-background text-muted-foreground hover:bg-muted hover:text-foreground"
|
|
57
60
|
}`}
|
|
58
61
|
>
|
|
59
62
|
{chip.label}
|
|
60
63
|
{chip.count !== undefined && chip.count !== "" && (
|
|
61
|
-
<span className="tabular-nums
|
|
64
|
+
<span className="font-normal tabular-nums">{chip.count}</span>
|
|
62
65
|
)}
|
|
63
66
|
</button>
|
|
64
67
|
))}
|
|
@@ -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
|
>
|
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
import type { ReactNode } from "react"
|
|
2
|
-
import {
|
|
2
|
+
import { FOCUS_RING_INSET } from "../lib/focus-ring.js"
|
|
3
|
+
import { TONE_DOT, type ToneVariant } from "../lib/tone-utils.js"
|
|
3
4
|
|
|
4
|
-
/**
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
/**
|
|
6
|
+
* Trend direction → tone. The tone shows ONLY as a dot before the trend line;
|
|
7
|
+
* the figures stay in the foreground (no coloured digits, CI §3.3).
|
|
8
|
+
*/
|
|
9
|
+
const TREND_TONE: Record<"up" | "down" | "flat", ToneVariant> = {
|
|
10
|
+
up: "danger",
|
|
11
|
+
down: "success",
|
|
12
|
+
flat: "neutral",
|
|
9
13
|
}
|
|
10
14
|
|
|
11
15
|
export interface KpiCell {
|
|
@@ -17,9 +21,15 @@ export interface KpiCell {
|
|
|
17
21
|
fraction?: ReactNode
|
|
18
22
|
/** Optional trend line under the value (e.g. `"+12% vs last week"`). */
|
|
19
23
|
trend?: ReactNode
|
|
20
|
-
/**
|
|
24
|
+
/**
|
|
25
|
+
* Tones the trend line's dot. `up` reads as bad (danger), `down` as good
|
|
26
|
+
* (success); the trend text itself stays in the foreground.
|
|
27
|
+
*/
|
|
21
28
|
trendDirection?: "up" | "down" | "flat"
|
|
22
|
-
/**
|
|
29
|
+
/**
|
|
30
|
+
* Status of the metric (e.g. `"danger"` for a breached one), shown as a dot
|
|
31
|
+
* before the label. The figure itself stays in the foreground colour.
|
|
32
|
+
*/
|
|
23
33
|
tone?: ToneVariant
|
|
24
34
|
/** When set, the cell renders as a button — turns a metric into a nav entry point. */
|
|
25
35
|
onClick?: () => void
|
|
@@ -52,12 +62,65 @@ export interface KpiGridProps {
|
|
|
52
62
|
header?: KpiGridHeader
|
|
53
63
|
}
|
|
54
64
|
|
|
65
|
+
/** A small status dot; decorative — the label next to it carries the meaning. */
|
|
66
|
+
function ToneDot({ tone, size }: { tone: ToneVariant; size: "size-1.5" | "size-2" }) {
|
|
67
|
+
return <span aria-hidden="true" className={`${size} shrink-0 rounded-full ${TONE_DOT[tone]}`} />
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* The trend line under a value: a tone dot plus the trend text in the
|
|
72
|
+
* foreground — never in a tone colour. Without a tone it stays quiet text.
|
|
73
|
+
*/
|
|
74
|
+
function KpiTrend({
|
|
75
|
+
trend,
|
|
76
|
+
direction,
|
|
77
|
+
}: {
|
|
78
|
+
trend: ReactNode
|
|
79
|
+
direction?: KpiCell["trendDirection"]
|
|
80
|
+
}) {
|
|
81
|
+
const tone = direction ? TREND_TONE[direction] : "neutral"
|
|
82
|
+
const textClass = tone === "neutral" ? "text-muted-foreground" : "text-foreground"
|
|
83
|
+
return (
|
|
84
|
+
<div className={`mt-1.5 inline-flex items-center gap-1.5 text-xs ${textClass}`}>
|
|
85
|
+
{tone !== "neutral" && <ToneDot tone={tone} size="size-1.5" />}
|
|
86
|
+
{trend}
|
|
87
|
+
</div>
|
|
88
|
+
)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** One cell's label row, figure and optional trend — status as dots, never as coloured digits. */
|
|
92
|
+
function KpiCellContent({ cell }: { cell: KpiCell }) {
|
|
93
|
+
return (
|
|
94
|
+
<>
|
|
95
|
+
<div className="text-muted-foreground flex items-center justify-between gap-2 text-xs font-medium">
|
|
96
|
+
<span className="inline-flex min-w-0 items-center gap-1.5">
|
|
97
|
+
{cell.tone && cell.tone !== "neutral" && <ToneDot tone={cell.tone} size="size-2" />}
|
|
98
|
+
<span>{cell.label}</span>
|
|
99
|
+
</span>
|
|
100
|
+
{cell.onClick && (
|
|
101
|
+
<span aria-hidden="true" className="text-sm leading-none">
|
|
102
|
+
›
|
|
103
|
+
</span>
|
|
104
|
+
)}
|
|
105
|
+
</div>
|
|
106
|
+
<div className="text-foreground mt-1.5 text-2xl leading-none font-bold tracking-tight tabular-nums">
|
|
107
|
+
{cell.value}
|
|
108
|
+
{cell.fraction && (
|
|
109
|
+
<span className="text-muted-foreground ml-0.5 text-sm font-normal">{cell.fraction}</span>
|
|
110
|
+
)}
|
|
111
|
+
</div>
|
|
112
|
+
{cell.trend && <KpiTrend trend={cell.trend} direction={cell.trendDirection} />}
|
|
113
|
+
</>
|
|
114
|
+
)
|
|
115
|
+
}
|
|
116
|
+
|
|
55
117
|
/**
|
|
56
118
|
* Bordered strip of KPI cells — the canonical way to show a row of headline
|
|
57
119
|
* metrics in a widget (replaces the single-metric KPICard). Cells flow as
|
|
58
120
|
* columns (1–6) and can be made clickable to drill into a detail view. Use
|
|
59
121
|
* `boxed` + `header` for a labelled metric block; unboxed (default) renders the
|
|
60
|
-
* lighter `border-y` strip.
|
|
122
|
+
* lighter `border-y` strip. A cell's `tone` shows as a status dot next to its
|
|
123
|
+
* label; figures stay in the foreground colour.
|
|
61
124
|
*/
|
|
62
125
|
export function KpiGrid({ cells, boxed = false, header }: KpiGridProps) {
|
|
63
126
|
const cols = Math.min(Math.max(cells.length, 1), 6)
|
|
@@ -70,7 +133,7 @@ export function KpiGrid({ cells, boxed = false, header }: KpiGridProps) {
|
|
|
70
133
|
<div className="border-border bg-muted text-muted-foreground flex items-center gap-2 border-b px-4 py-2 text-[11px] font-semibold tracking-wide uppercase">
|
|
71
134
|
<span>{header.label}</span>
|
|
72
135
|
{header.badge && (
|
|
73
|
-
<span className="border-border bg-card text-muted-foreground rounded border px-1.5 py-0 text-[10px] font-medium normal-case">
|
|
136
|
+
<span className="border-border bg-card text-muted-foreground rounded-sm border px-1.5 py-0 text-[10px] font-medium normal-case">
|
|
74
137
|
{header.badge}
|
|
75
138
|
</span>
|
|
76
139
|
)}
|
|
@@ -79,46 +142,14 @@ export function KpiGrid({ cells, boxed = false, header }: KpiGridProps) {
|
|
|
79
142
|
<div className={`grid ${colClass} ${stripClass}`}>
|
|
80
143
|
{cells.map((cell, idx) => {
|
|
81
144
|
const borderClass = idx < cells.length - 1 ? "border-border border-r" : ""
|
|
82
|
-
const content =
|
|
83
|
-
<>
|
|
84
|
-
<div className="text-muted-foreground flex items-center justify-between gap-2 text-xs font-medium">
|
|
85
|
-
<span>{cell.label}</span>
|
|
86
|
-
{cell.onClick && (
|
|
87
|
-
<span aria-hidden="true" className="text-sm leading-none">
|
|
88
|
-
›
|
|
89
|
-
</span>
|
|
90
|
-
)}
|
|
91
|
-
</div>
|
|
92
|
-
<div
|
|
93
|
-
className={`mt-1.5 text-2xl leading-none font-bold tracking-tight tabular-nums ${
|
|
94
|
-
(cell.tone && TONE_TEXT[cell.tone]) || "text-foreground"
|
|
95
|
-
}`}
|
|
96
|
-
>
|
|
97
|
-
{cell.value}
|
|
98
|
-
{cell.fraction && (
|
|
99
|
-
<span className="text-muted-foreground ml-0.5 text-sm font-normal">
|
|
100
|
-
{cell.fraction}
|
|
101
|
-
</span>
|
|
102
|
-
)}
|
|
103
|
-
</div>
|
|
104
|
-
{cell.trend && (
|
|
105
|
-
<div
|
|
106
|
-
className={`mt-1.5 text-xs ${
|
|
107
|
-
cell.trendDirection ? TREND_TONE[cell.trendDirection] : "text-muted-foreground"
|
|
108
|
-
}`}
|
|
109
|
-
>
|
|
110
|
-
{cell.trend}
|
|
111
|
-
</div>
|
|
112
|
-
)}
|
|
113
|
-
</>
|
|
114
|
-
)
|
|
145
|
+
const content = <KpiCellContent cell={cell} />
|
|
115
146
|
return cell.onClick ? (
|
|
116
147
|
<button
|
|
117
148
|
key={idx}
|
|
118
149
|
type="button"
|
|
119
150
|
onClick={cell.onClick}
|
|
120
151
|
aria-label={cell.ariaLabel}
|
|
121
|
-
className={`hover:bg-muted
|
|
152
|
+
className={`hover:bg-muted cursor-pointer px-5 py-4 text-left transition-colors ${FOCUS_RING_INSET} ${borderClass}`}
|
|
122
153
|
>
|
|
123
154
|
{content}
|
|
124
155
|
</button>
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import { FOCUS_RING } from "../lib/focus-ring.js"
|
|
2
|
+
import { useToolkitLocale, useToolkitMessages } from "../locale/locale-provider.js"
|
|
3
|
+
import { formatCount } from "../locale/toolkit-messages.js"
|
|
4
|
+
|
|
1
5
|
export interface ListFooterProps {
|
|
2
6
|
/** How many rows are currently rendered. */
|
|
3
7
|
shown: number
|
|
@@ -9,7 +13,7 @@ export interface ListFooterProps {
|
|
|
9
13
|
loadingMore?: boolean
|
|
10
14
|
/** Called when the user asks for the next page. */
|
|
11
15
|
onLoadMore: () => void
|
|
12
|
-
/** Plural noun for the count, e.g. `"instances"`. Default
|
|
16
|
+
/** Plural noun for the count, e.g. `"instances"`. Default: the locale's word for rows. */
|
|
13
17
|
noun?: string
|
|
14
18
|
}
|
|
15
19
|
|
|
@@ -17,7 +21,8 @@ export interface ListFooterProps {
|
|
|
17
21
|
* Footer for a paginated list: an honest "Showing X of Y" total plus an
|
|
18
22
|
* explicit "Load more" control. Deliberately not infinite scroll — the user
|
|
19
23
|
* gets the total, a reachable footer, and control over fetching. Renders
|
|
20
|
-
* nothing when `total` is 0.
|
|
24
|
+
* nothing when `total` is 0. Text and number format follow the active locale
|
|
25
|
+
* (`LocaleProvider`, else the host's inside `McpAppView`).
|
|
21
26
|
*/
|
|
22
27
|
export function ListFooter({
|
|
23
28
|
shown,
|
|
@@ -25,22 +30,28 @@ export function ListFooter({
|
|
|
25
30
|
hasMore,
|
|
26
31
|
loadingMore = false,
|
|
27
32
|
onLoadMore,
|
|
28
|
-
noun
|
|
33
|
+
noun,
|
|
29
34
|
}: ListFooterProps) {
|
|
35
|
+
const locale = useToolkitLocale()
|
|
36
|
+
const { listFooter } = useToolkitMessages()
|
|
30
37
|
if (total <= 0) return null
|
|
31
38
|
return (
|
|
32
39
|
<div className="text-muted-foreground flex items-center justify-between gap-3 px-1 py-2 text-xs">
|
|
33
40
|
<span>
|
|
34
|
-
|
|
41
|
+
{listFooter.showing({
|
|
42
|
+
shown: formatCount(shown, locale),
|
|
43
|
+
total: formatCount(total, locale),
|
|
44
|
+
noun: noun ?? listFooter.defaultNoun,
|
|
45
|
+
})}
|
|
35
46
|
</span>
|
|
36
47
|
{hasMore && (
|
|
37
48
|
<button
|
|
38
49
|
type="button"
|
|
39
50
|
onClick={onLoadMore}
|
|
40
51
|
disabled={loadingMore}
|
|
41
|
-
className=
|
|
52
|
+
className={`border-border text-foreground hover:border-focus hover:bg-accent hover:text-accent-foreground inline-flex items-center gap-1 rounded-md border px-3 py-1.5 font-medium transition-colors disabled:opacity-50 ${FOCUS_RING}`}
|
|
42
53
|
>
|
|
43
|
-
{loadingMore ?
|
|
54
|
+
{loadingMore ? listFooter.loadingMore : listFooter.loadMore}
|
|
44
55
|
</button>
|
|
45
56
|
)}
|
|
46
57
|
</div>
|
package/src/components/Pills.tsx
CHANGED
|
@@ -1,43 +1,52 @@
|
|
|
1
1
|
import type { ReactNode } from "react"
|
|
2
|
-
import {
|
|
2
|
+
import { useToolkitMessages } from "../locale/locale-provider.js"
|
|
3
|
+
import { TONE_BORDER, TONE_DOT, TONE_SOFT, type ToneVariant } from "../lib/tone-utils.js"
|
|
3
4
|
|
|
4
5
|
export interface LivePillProps {
|
|
5
|
-
/** Status tone driving the dot
|
|
6
|
+
/** Status tone driving the dot, tint and border. Default `"info"`. */
|
|
6
7
|
tone?: ToneVariant
|
|
7
|
-
/** Pill label. Defaults to
|
|
8
|
+
/** Pill label. Defaults to the locale's "Live". */
|
|
8
9
|
children?: ReactNode
|
|
9
10
|
}
|
|
10
11
|
|
|
11
12
|
/**
|
|
12
13
|
* Small tinted pill with a pulsing dot. Use in a widget header to signal that
|
|
13
|
-
* the data is live/real-time. Tone-based,
|
|
14
|
+
* the data is live/real-time. Tone-based (tint, border and dot in the tone,
|
|
15
|
+
* text in the foreground), so it inherits the active theme. The pulse stops
|
|
16
|
+
* under `prefers-reduced-motion`.
|
|
14
17
|
*/
|
|
15
18
|
export function LivePill({ tone = "info", children }: LivePillProps) {
|
|
19
|
+
const messages = useToolkitMessages()
|
|
16
20
|
return (
|
|
17
21
|
<span
|
|
18
|
-
className={`inline-flex items-center gap-1.5 rounded-
|
|
22
|
+
className={`inline-flex items-center gap-1.5 rounded-sm border px-2 py-0.5 text-xs font-semibold ${TONE_SOFT[tone]} ${TONE_BORDER[tone]}`}
|
|
19
23
|
>
|
|
20
|
-
<span
|
|
21
|
-
|
|
24
|
+
<span
|
|
25
|
+
aria-hidden="true"
|
|
26
|
+
className={`size-1.5 animate-pulse rounded-full ${TONE_DOT[tone]}`}
|
|
27
|
+
/>
|
|
28
|
+
{children ?? messages.pills.live}
|
|
22
29
|
</span>
|
|
23
30
|
)
|
|
24
31
|
}
|
|
25
32
|
|
|
26
33
|
export interface CountPillProps {
|
|
27
|
-
/** Status tone driving the tint. Default `"neutral"`. */
|
|
34
|
+
/** Status tone driving the tint and border. Default `"neutral"`. */
|
|
28
35
|
tone?: ToneVariant
|
|
29
36
|
/** The count/value to display (tabular numbers). */
|
|
30
37
|
children: ReactNode
|
|
31
38
|
}
|
|
32
39
|
|
|
33
40
|
/**
|
|
34
|
-
* Compact count badge — tabular numbers on a tinted background
|
|
35
|
-
* right-aligned indicator on a list/group row (e.g. a
|
|
41
|
+
* Compact count badge — tabular numbers on a tinted background with a border
|
|
42
|
+
* in the tone. Use as the right-aligned indicator on a list/group row (e.g. a
|
|
43
|
+
* per-group item count). The number stays in the foreground in every tone
|
|
44
|
+
* (no coloured digits): the tint and the border carry the tone.
|
|
36
45
|
*/
|
|
37
46
|
export function CountPill({ tone = "neutral", children }: CountPillProps) {
|
|
38
47
|
return (
|
|
39
48
|
<span
|
|
40
|
-
className={`inline-flex min-w-[2.5rem] items-center justify-center rounded-
|
|
49
|
+
className={`inline-flex min-w-[2.5rem] items-center justify-center rounded-sm border px-2.5 py-0.5 text-sm font-semibold tabular-nums ${TONE_SOFT[tone]} ${TONE_BORDER[tone]}`}
|
|
41
50
|
>
|
|
42
51
|
{children}
|
|
43
52
|
</span>
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createContext,
|
|
3
|
+
useCallback,
|
|
4
|
+
useContext,
|
|
5
|
+
useEffect,
|
|
6
|
+
useMemo,
|
|
7
|
+
useReducer,
|
|
8
|
+
useRef,
|
|
9
|
+
useState,
|
|
10
|
+
type ReactNode,
|
|
11
|
+
} from "react"
|
|
12
|
+
import { CircleAlert, CircleCheck, Info, TriangleAlert, X, type LucideIcon } from "lucide-react"
|
|
13
|
+
import { useToolkitMessages } from "../locale/locale-provider.js"
|
|
14
|
+
import { FOCUS_RING } from "../lib/focus-ring.js"
|
|
15
|
+
import { TONE_ICON } from "../lib/tone-utils.js"
|
|
16
|
+
import { cn } from "../lib/utils.js"
|
|
17
|
+
import {
|
|
18
|
+
createToast,
|
|
19
|
+
toastReducer,
|
|
20
|
+
type ToastItem,
|
|
21
|
+
type ToastOptions,
|
|
22
|
+
type ToastTone,
|
|
23
|
+
} from "../lib/toast-model.js"
|
|
24
|
+
|
|
25
|
+
export type { ToastOptions, ToastTone } from "../lib/toast-model.js"
|
|
26
|
+
|
|
27
|
+
/** What {@link useToast} returns. */
|
|
28
|
+
export interface ToastApi {
|
|
29
|
+
/** Show a toast; returns its id (for an early {@link ToastApi.dismiss}). */
|
|
30
|
+
show: (options: ToastOptions) => number
|
|
31
|
+
/** Remove a toast before its timer runs out. */
|
|
32
|
+
dismiss: (id: number) => void
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const ToastContext = createContext<ToastApi | null>(null)
|
|
36
|
+
|
|
37
|
+
/** Outside a provider `useToast()` degrades to a no-op instead of throwing. */
|
|
38
|
+
const NOOP_TOASTS: ToastApi = { show: () => -1, dismiss: () => {} }
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Raise toasts from anywhere below a {@link ToastProvider}: `show({ tone,
|
|
42
|
+
* title })` after a write succeeded or failed, never a silent `console` line.
|
|
43
|
+
* Outside a provider it returns a no-op API, so a widget rendered in a bare
|
|
44
|
+
* fixture never crashes on feedback.
|
|
45
|
+
*/
|
|
46
|
+
export function useToast(): ToastApi {
|
|
47
|
+
return useContext(ToastContext) ?? NOOP_TOASTS
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Status edge per tone — the status colour as border, never as text. Repeated
|
|
52
|
+
* under `dark:` so the card's dark 3:1 rim (`dark:border-input`, a later rule)
|
|
53
|
+
* does not paint over it.
|
|
54
|
+
*/
|
|
55
|
+
const TONE_EDGE: Record<ToastTone, string> = {
|
|
56
|
+
success: "border-l-success dark:border-l-success",
|
|
57
|
+
warning: "border-l-warning dark:border-l-warning",
|
|
58
|
+
danger: "border-l-danger dark:border-l-danger",
|
|
59
|
+
info: "border-l-info dark:border-l-info",
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Lucide icon per tone, drawn in the tone colour (`TONE_ICON`, CI §3.3: status
|
|
64
|
+
* as edge and icon). The title next to it carries the message, so success and
|
|
65
|
+
* warning stay CI-exempt from 3:1 on light like every other status icon.
|
|
66
|
+
*/
|
|
67
|
+
const TOAST_ICON: Record<ToastTone, LucideIcon> = {
|
|
68
|
+
success: CircleCheck,
|
|
69
|
+
warning: TriangleAlert,
|
|
70
|
+
danger: CircleAlert,
|
|
71
|
+
info: Info,
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* The stack of toasts — a presentational view over a list (the provider owns
|
|
76
|
+
* the state). The container is ONE persistent live region (`role="status"`,
|
|
77
|
+
* `aria-live="polite"`), so a newly added toast is announced without stealing
|
|
78
|
+
* focus. Bottom centre, above dialogs (z 60), per CI §5/§8.
|
|
79
|
+
*/
|
|
80
|
+
export function ToastViewport({
|
|
81
|
+
toasts,
|
|
82
|
+
onDismiss,
|
|
83
|
+
}: {
|
|
84
|
+
toasts: readonly ToastItem[]
|
|
85
|
+
onDismiss: (id: number) => void
|
|
86
|
+
}) {
|
|
87
|
+
const messages = useToolkitMessages()
|
|
88
|
+
return (
|
|
89
|
+
<div
|
|
90
|
+
data-slot="toast-viewport"
|
|
91
|
+
role="status"
|
|
92
|
+
aria-live="polite"
|
|
93
|
+
aria-label={messages.common.notifications}
|
|
94
|
+
className="pointer-events-none fixed inset-x-0 bottom-4 z-60 flex flex-col items-center gap-2 px-4"
|
|
95
|
+
>
|
|
96
|
+
{toasts.map((toast) => (
|
|
97
|
+
<ToastCard
|
|
98
|
+
key={toast.id}
|
|
99
|
+
toast={toast}
|
|
100
|
+
dismissLabel={messages.common.dismiss}
|
|
101
|
+
onDismiss={onDismiss}
|
|
102
|
+
/>
|
|
103
|
+
))}
|
|
104
|
+
</div>
|
|
105
|
+
)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function ToastCard({
|
|
109
|
+
toast,
|
|
110
|
+
dismissLabel,
|
|
111
|
+
onDismiss,
|
|
112
|
+
}: {
|
|
113
|
+
toast: ToastItem
|
|
114
|
+
dismissLabel: string
|
|
115
|
+
onDismiss: (id: number) => void
|
|
116
|
+
}) {
|
|
117
|
+
// Hover or keyboard focus holds the toast: the timer restarts on leave.
|
|
118
|
+
const [held, setHeld] = useState(false)
|
|
119
|
+
useEffect(() => {
|
|
120
|
+
if (held) return
|
|
121
|
+
const timer = setTimeout(() => onDismiss(toast.id), toast.duration)
|
|
122
|
+
return () => clearTimeout(timer)
|
|
123
|
+
}, [held, onDismiss, toast.id, toast.duration])
|
|
124
|
+
const Icon = TOAST_ICON[toast.tone]
|
|
125
|
+
return (
|
|
126
|
+
<div
|
|
127
|
+
data-slot="toast"
|
|
128
|
+
data-tone={toast.tone}
|
|
129
|
+
onMouseEnter={() => setHeld(true)}
|
|
130
|
+
onMouseLeave={() => setHeld(false)}
|
|
131
|
+
onFocus={() => setHeld(true)}
|
|
132
|
+
onBlur={() => setHeld(false)}
|
|
133
|
+
className={cn(
|
|
134
|
+
// 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 text-sm",
|
|
137
|
+
"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
|
+
TONE_EDGE[toast.tone],
|
|
139
|
+
)}
|
|
140
|
+
>
|
|
141
|
+
<Icon aria-hidden="true" className={cn("mt-0.5 size-4 shrink-0", TONE_ICON[toast.tone])} />
|
|
142
|
+
<div className="min-w-0 flex-1">
|
|
143
|
+
<p className="font-medium break-words">{toast.title}</p>
|
|
144
|
+
{toast.description && (
|
|
145
|
+
<p className="text-muted-foreground mt-0.5 break-words">{toast.description}</p>
|
|
146
|
+
)}
|
|
147
|
+
</div>
|
|
148
|
+
<button
|
|
149
|
+
type="button"
|
|
150
|
+
aria-label={dismissLabel}
|
|
151
|
+
onClick={() => onDismiss(toast.id)}
|
|
152
|
+
className={`text-muted-foreground hover:text-foreground -mr-1 inline-flex size-6 shrink-0 items-center justify-center rounded-sm ${FOCUS_RING}`}
|
|
153
|
+
>
|
|
154
|
+
<X aria-hidden="true" className="size-4" />
|
|
155
|
+
</button>
|
|
156
|
+
</div>
|
|
157
|
+
)
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function ToastRoot({ children }: { children: ReactNode }) {
|
|
161
|
+
const [toasts, dispatch] = useReducer(toastReducer, [])
|
|
162
|
+
const nextId = useRef(0)
|
|
163
|
+
const dismiss = useCallback((id: number) => dispatch({ type: "dismiss", id }), [])
|
|
164
|
+
const show = useCallback((options: ToastOptions) => {
|
|
165
|
+
nextId.current += 1
|
|
166
|
+
const id = nextId.current
|
|
167
|
+
dispatch({ type: "add", toast: createToast(id, options) })
|
|
168
|
+
return id
|
|
169
|
+
}, [])
|
|
170
|
+
const api = useMemo<ToastApi>(() => ({ show, dismiss }), [show, dismiss])
|
|
171
|
+
return (
|
|
172
|
+
<ToastContext.Provider value={api}>
|
|
173
|
+
{children}
|
|
174
|
+
<ToastViewport toasts={toasts} onDismiss={dismiss} />
|
|
175
|
+
</ToastContext.Provider>
|
|
176
|
+
)
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Provide toasts to a subtree and render their stack. Idempotent: nested
|
|
181
|
+
* inside another `ToastProvider` it adds nothing, so the app shell and an
|
|
182
|
+
* embedded builder share one stack and one live region. `McpAppView` mounts
|
|
183
|
+
* one for you.
|
|
184
|
+
*/
|
|
185
|
+
export function ToastProvider({ children }: { children: ReactNode }) {
|
|
186
|
+
const parent = useContext(ToastContext)
|
|
187
|
+
return parent ? children : <ToastRoot>{children}</ToastRoot>
|
|
188
|
+
}
|
|
@@ -31,7 +31,7 @@ export function WidgetHeader({
|
|
|
31
31
|
<div className="min-w-0">
|
|
32
32
|
{icon && (
|
|
33
33
|
<div
|
|
34
|
-
className={`mb-3.5 grid size-11 place-items-center rounded-
|
|
34
|
+
className={`mb-3.5 grid size-11 place-items-center rounded-md text-xl ${TONE_SOFT[iconTone]}`}
|
|
35
35
|
>
|
|
36
36
|
{icon}
|
|
37
37
|
</div>
|