@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
|
@@ -4,7 +4,10 @@ import * as React from "react"
|
|
|
4
4
|
import { XIcon } from "lucide-react"
|
|
5
5
|
import { Dialog as DialogPrimitive } from "radix-ui"
|
|
6
6
|
|
|
7
|
+
import { FOCUS_RING } from "../lib/focus-ring.js"
|
|
8
|
+
import { useToolkitMessages } from "../locale/locale-provider.js"
|
|
7
9
|
import { cn } from "../lib/utils.js"
|
|
10
|
+
import { usePortalContainer } from "../theme/portal-container.js"
|
|
8
11
|
import { Button } from "./button.js"
|
|
9
12
|
|
|
10
13
|
function Dialog({ ...props }: React.ComponentProps<typeof DialogPrimitive.Root>) {
|
|
@@ -15,8 +18,19 @@ function DialogTrigger({ ...props }: React.ComponentProps<typeof DialogPrimitive
|
|
|
15
18
|
return <DialogPrimitive.Trigger data-slot="dialog-trigger" {...props} />
|
|
16
19
|
}
|
|
17
20
|
|
|
18
|
-
function DialogPortal({
|
|
19
|
-
|
|
21
|
+
function DialogPortal({
|
|
22
|
+
container,
|
|
23
|
+
...props
|
|
24
|
+
}: React.ComponentProps<typeof DialogPrimitive.Portal>) {
|
|
25
|
+
// Default to the nearest ThemeProvider scope (theme + mode), not <body>.
|
|
26
|
+
const portalContainer = usePortalContainer()
|
|
27
|
+
return (
|
|
28
|
+
<DialogPrimitive.Portal
|
|
29
|
+
data-slot="dialog-portal"
|
|
30
|
+
container={container ?? portalContainer ?? undefined}
|
|
31
|
+
{...props}
|
|
32
|
+
/>
|
|
33
|
+
)
|
|
20
34
|
}
|
|
21
35
|
|
|
22
36
|
function DialogClose({ ...props }: React.ComponentProps<typeof DialogPrimitive.Close>) {
|
|
@@ -31,7 +45,7 @@ function DialogOverlay({
|
|
|
31
45
|
<DialogPrimitive.Overlay
|
|
32
46
|
data-slot="dialog-overlay"
|
|
33
47
|
className={cn(
|
|
34
|
-
"data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:animate-in data-[state=open]:fade-in-0 fixed inset-0 z-50
|
|
48
|
+
"data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:animate-in data-[state=open]:fade-in-0 bg-scrim fixed inset-0 z-50 backdrop-blur-sm",
|
|
35
49
|
className,
|
|
36
50
|
)}
|
|
37
51
|
{...props}
|
|
@@ -43,17 +57,21 @@ function DialogContent({
|
|
|
43
57
|
className,
|
|
44
58
|
children,
|
|
45
59
|
showCloseButton = true,
|
|
60
|
+
closeLabel,
|
|
46
61
|
...props
|
|
47
62
|
}: React.ComponentProps<typeof DialogPrimitive.Content> & {
|
|
48
63
|
showCloseButton?: boolean
|
|
64
|
+
/** Accessible name of the close button. Default: the locale's "Close". */
|
|
65
|
+
closeLabel?: string
|
|
49
66
|
}) {
|
|
67
|
+
const messages = useToolkitMessages()
|
|
50
68
|
return (
|
|
51
69
|
<DialogPortal data-slot="dialog-portal">
|
|
52
70
|
<DialogOverlay />
|
|
53
71
|
<DialogPrimitive.Content
|
|
54
72
|
data-slot="dialog-content"
|
|
55
73
|
className={cn(
|
|
56
|
-
"bg-
|
|
74
|
+
"bg-popover text-popover-foreground dark:border-input data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95 shadow-3 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-md border p-6 outline-hidden duration-(--motion-base) sm:max-w-lg",
|
|
57
75
|
className,
|
|
58
76
|
)}
|
|
59
77
|
{...props}
|
|
@@ -62,10 +80,13 @@ function DialogContent({
|
|
|
62
80
|
{showCloseButton && (
|
|
63
81
|
<DialogPrimitive.Close
|
|
64
82
|
data-slot="dialog-close"
|
|
65
|
-
className=
|
|
83
|
+
className={cn(
|
|
84
|
+
"text-muted-foreground hover:text-foreground absolute top-4 right-4 rounded-sm transition-colors disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
|
85
|
+
FOCUS_RING,
|
|
86
|
+
)}
|
|
66
87
|
>
|
|
67
88
|
<XIcon />
|
|
68
|
-
<span className="sr-only">
|
|
89
|
+
<span className="sr-only">{closeLabel ?? messages.common.close}</span>
|
|
69
90
|
</DialogPrimitive.Close>
|
|
70
91
|
)}
|
|
71
92
|
</DialogPrimitive.Content>
|
|
@@ -86,11 +107,15 @@ function DialogHeader({ className, ...props }: React.ComponentProps<"div">) {
|
|
|
86
107
|
function DialogFooter({
|
|
87
108
|
className,
|
|
88
109
|
showCloseButton = false,
|
|
110
|
+
closeLabel,
|
|
89
111
|
children,
|
|
90
112
|
...props
|
|
91
113
|
}: React.ComponentProps<"div"> & {
|
|
92
114
|
showCloseButton?: boolean
|
|
115
|
+
/** Label of the footer close button. Default: the locale's "Close". */
|
|
116
|
+
closeLabel?: string
|
|
93
117
|
}) {
|
|
118
|
+
const messages = useToolkitMessages()
|
|
94
119
|
return (
|
|
95
120
|
<div
|
|
96
121
|
data-slot="dialog-footer"
|
|
@@ -100,7 +125,7 @@ function DialogFooter({
|
|
|
100
125
|
{children}
|
|
101
126
|
{showCloseButton && (
|
|
102
127
|
<DialogPrimitive.Close asChild>
|
|
103
|
-
<Button variant="outline">
|
|
128
|
+
<Button variant="outline">{closeLabel ?? messages.common.close}</Button>
|
|
104
129
|
</DialogPrimitive.Close>
|
|
105
130
|
)}
|
|
106
131
|
</div>
|
package/src/primitives/input.tsx
CHANGED
|
@@ -8,9 +8,11 @@ function Input({ className, type, ...props }: React.ComponentProps<"input">) {
|
|
|
8
8
|
type={type}
|
|
9
9
|
data-slot="input"
|
|
10
10
|
className={cn(
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
"
|
|
11
|
+
// Input border in --input (3:1 on its surface); focus turns border and a
|
|
12
|
+
// 1px ring --focus: a 2px opaque blue edge (CI §8).
|
|
13
|
+
"border-input selection:bg-primary selection:text-primary-foreground file:text-foreground placeholder:text-muted-foreground bg-background h-9 w-full min-w-0 rounded-md border px-3 py-1 text-base outline-hidden transition-[color,box-shadow,border-color] file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
|
|
14
|
+
"focus-visible:border-focus focus-visible:ring-focus focus-visible:ring-1",
|
|
15
|
+
"aria-invalid:border-danger aria-invalid:ring-danger aria-invalid:ring-1",
|
|
14
16
|
className,
|
|
15
17
|
)}
|
|
16
18
|
{...props}
|
|
@@ -16,7 +16,7 @@ function ScrollArea({
|
|
|
16
16
|
>
|
|
17
17
|
<ScrollAreaPrimitive.Viewport
|
|
18
18
|
data-slot="scroll-area-viewport"
|
|
19
|
-
className="focus-visible:ring-
|
|
19
|
+
className="focus-visible:ring-focus size-full rounded-[inherit] outline-hidden transition-[color,box-shadow] focus-visible:ring-2 focus-visible:ring-inset"
|
|
20
20
|
>
|
|
21
21
|
{children}
|
|
22
22
|
</ScrollAreaPrimitive.Viewport>
|
|
@@ -3,6 +3,7 @@ import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from "lucide-react"
|
|
|
3
3
|
import { Select as SelectPrimitive } from "radix-ui"
|
|
4
4
|
|
|
5
5
|
import { cn } from "../lib/utils.js"
|
|
6
|
+
import { usePortalContainer } from "../theme/portal-container.js"
|
|
6
7
|
|
|
7
8
|
function Select({ ...props }: React.ComponentProps<typeof SelectPrimitive.Root>) {
|
|
8
9
|
return <SelectPrimitive.Root data-slot="select" {...props} />
|
|
@@ -29,7 +30,7 @@ function SelectTrigger({
|
|
|
29
30
|
data-slot="select-trigger"
|
|
30
31
|
data-size={size}
|
|
31
32
|
className={cn(
|
|
32
|
-
"border-input focus-visible:border-
|
|
33
|
+
"border-input focus-visible:border-focus focus-visible:ring-focus aria-invalid:border-danger aria-invalid:ring-danger data-[placeholder]:text-muted-foreground [&_svg:not([class*='text-'])]:text-muted-foreground bg-background hover:border-focus flex w-fit items-center justify-between gap-2 rounded-md border px-3 py-2 text-sm whitespace-nowrap outline-hidden transition-[color,box-shadow,border-color] focus-visible:ring-1 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:ring-1 data-[size=default]:h-9 data-[size=sm]:h-8 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
|
33
34
|
className,
|
|
34
35
|
)}
|
|
35
36
|
{...props}
|
|
@@ -49,12 +50,14 @@ function SelectContent({
|
|
|
49
50
|
align = "center",
|
|
50
51
|
...props
|
|
51
52
|
}: React.ComponentProps<typeof SelectPrimitive.Content>) {
|
|
53
|
+
// Portal into the nearest ThemeProvider scope (theme + mode), not <body>.
|
|
54
|
+
const portalContainer = usePortalContainer()
|
|
52
55
|
return (
|
|
53
|
-
<SelectPrimitive.Portal>
|
|
56
|
+
<SelectPrimitive.Portal container={portalContainer ?? undefined}>
|
|
54
57
|
<SelectPrimitive.Content
|
|
55
58
|
data-slot="select-content"
|
|
56
59
|
className={cn(
|
|
57
|
-
"bg-popover text-popover-foreground data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95 relative z-50 max-h-(--radix-select-content-available-height) min-w-[8rem] origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border
|
|
60
|
+
"bg-popover text-popover-foreground dark:border-input data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95 shadow-3 relative z-50 max-h-(--radix-select-content-available-height) min-w-[8rem] origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border",
|
|
58
61
|
position === "popper" &&
|
|
59
62
|
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
|
|
60
63
|
className,
|
package/src/primitives/sheet.tsx
CHANGED
|
@@ -4,7 +4,10 @@ import * as React from "react"
|
|
|
4
4
|
import { XIcon } from "lucide-react"
|
|
5
5
|
import { Dialog as SheetPrimitive } from "radix-ui"
|
|
6
6
|
|
|
7
|
+
import { FOCUS_RING } from "../lib/focus-ring.js"
|
|
8
|
+
import { useToolkitMessages } from "../locale/locale-provider.js"
|
|
7
9
|
import { cn } from "../lib/utils.js"
|
|
10
|
+
import { usePortalContainer } from "../theme/portal-container.js"
|
|
8
11
|
|
|
9
12
|
function Sheet({ ...props }: React.ComponentProps<typeof SheetPrimitive.Root>) {
|
|
10
13
|
return <SheetPrimitive.Root data-slot="sheet" {...props} />
|
|
@@ -18,8 +21,16 @@ function SheetClose({ ...props }: React.ComponentProps<typeof SheetPrimitive.Clo
|
|
|
18
21
|
return <SheetPrimitive.Close data-slot="sheet-close" {...props} />
|
|
19
22
|
}
|
|
20
23
|
|
|
21
|
-
function SheetPortal({ ...props }: React.ComponentProps<typeof SheetPrimitive.Portal>) {
|
|
22
|
-
|
|
24
|
+
function SheetPortal({ container, ...props }: React.ComponentProps<typeof SheetPrimitive.Portal>) {
|
|
25
|
+
// Default to the nearest ThemeProvider scope (theme + mode), not <body>.
|
|
26
|
+
const portalContainer = usePortalContainer()
|
|
27
|
+
return (
|
|
28
|
+
<SheetPrimitive.Portal
|
|
29
|
+
data-slot="sheet-portal"
|
|
30
|
+
container={container ?? portalContainer ?? undefined}
|
|
31
|
+
{...props}
|
|
32
|
+
/>
|
|
33
|
+
)
|
|
23
34
|
}
|
|
24
35
|
|
|
25
36
|
function SheetOverlay({
|
|
@@ -30,7 +41,7 @@ function SheetOverlay({
|
|
|
30
41
|
<SheetPrimitive.Overlay
|
|
31
42
|
data-slot="sheet-overlay"
|
|
32
43
|
className={cn(
|
|
33
|
-
"data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:animate-in data-[state=open]:fade-in-0 fixed inset-0 z-50
|
|
44
|
+
"data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:animate-in data-[state=open]:fade-in-0 bg-scrim fixed inset-0 z-50 backdrop-blur-sm",
|
|
34
45
|
className,
|
|
35
46
|
)}
|
|
36
47
|
{...props}
|
|
@@ -43,18 +54,22 @@ function SheetContent({
|
|
|
43
54
|
children,
|
|
44
55
|
side = "right",
|
|
45
56
|
showCloseButton = true,
|
|
57
|
+
closeLabel,
|
|
46
58
|
...props
|
|
47
59
|
}: React.ComponentProps<typeof SheetPrimitive.Content> & {
|
|
48
60
|
side?: "top" | "right" | "bottom" | "left"
|
|
49
61
|
showCloseButton?: boolean
|
|
62
|
+
/** Accessible name of the close button. Default: the locale's "Close". */
|
|
63
|
+
closeLabel?: string
|
|
50
64
|
}) {
|
|
65
|
+
const messages = useToolkitMessages()
|
|
51
66
|
return (
|
|
52
67
|
<SheetPortal>
|
|
53
68
|
<SheetOverlay />
|
|
54
69
|
<SheetPrimitive.Content
|
|
55
70
|
data-slot="sheet-content"
|
|
56
71
|
className={cn(
|
|
57
|
-
"bg-
|
|
72
|
+
"bg-popover text-popover-foreground dark:border-input data-[state=closed]:animate-out data-[state=open]:animate-in shadow-3 fixed z-50 flex flex-col gap-4 transition data-[state=closed]:duration-(--motion-base) data-[state=open]:duration-(--motion-slow)",
|
|
58
73
|
side === "right" &&
|
|
59
74
|
"data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right inset-y-0 right-0 h-full w-3/4 border-l sm:max-w-sm",
|
|
60
75
|
side === "left" &&
|
|
@@ -69,9 +84,14 @@ function SheetContent({
|
|
|
69
84
|
>
|
|
70
85
|
{children}
|
|
71
86
|
{showCloseButton && (
|
|
72
|
-
<SheetPrimitive.Close
|
|
87
|
+
<SheetPrimitive.Close
|
|
88
|
+
className={cn(
|
|
89
|
+
"text-muted-foreground hover:text-foreground absolute top-4 right-4 rounded-sm transition-colors disabled:pointer-events-none",
|
|
90
|
+
FOCUS_RING,
|
|
91
|
+
)}
|
|
92
|
+
>
|
|
73
93
|
<XIcon className="size-4" />
|
|
74
|
-
<span className="sr-only">
|
|
94
|
+
<span className="sr-only">{closeLabel ?? messages.common.close}</span>
|
|
75
95
|
</SheetPrimitive.Close>
|
|
76
96
|
)}
|
|
77
97
|
</SheetPrimitive.Content>
|
|
@@ -4,7 +4,7 @@ function Skeleton({ className, ...props }: React.ComponentProps<"div">) {
|
|
|
4
4
|
return (
|
|
5
5
|
<div
|
|
6
6
|
data-slot="skeleton"
|
|
7
|
-
className={cn("bg-
|
|
7
|
+
className={cn("bg-border animate-pulse rounded-md", className)}
|
|
8
8
|
{...props}
|
|
9
9
|
/>
|
|
10
10
|
)
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as React from "react"
|
|
2
2
|
import { Switch as SwitchPrimitive } from "radix-ui"
|
|
3
3
|
|
|
4
|
+
import { FOCUS_RING } from "../lib/focus-ring.js"
|
|
4
5
|
import { cn } from "../lib/utils.js"
|
|
5
6
|
|
|
6
7
|
function Switch({ className, ...props }: React.ComponentProps<typeof SwitchPrimitive.Root>) {
|
|
@@ -8,14 +9,15 @@ function Switch({ className, ...props }: React.ComponentProps<typeof SwitchPrimi
|
|
|
8
9
|
<SwitchPrimitive.Root
|
|
9
10
|
data-slot="switch"
|
|
10
11
|
className={cn(
|
|
11
|
-
"peer
|
|
12
|
+
"peer data-[state=checked]:bg-primary data-[state=unchecked]:bg-input inline-flex h-5 w-9 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors disabled:cursor-not-allowed disabled:opacity-50",
|
|
13
|
+
FOCUS_RING,
|
|
12
14
|
className,
|
|
13
15
|
)}
|
|
14
16
|
{...props}
|
|
15
17
|
>
|
|
16
18
|
<SwitchPrimitive.Thumb
|
|
17
19
|
data-slot="switch-thumb"
|
|
18
|
-
className="bg-
|
|
20
|
+
className="bg-primary-foreground shadow-1 pointer-events-none block h-4 w-4 rounded-full ring-0 transition-transform data-[state=checked]:translate-x-4 data-[state=unchecked]:translate-x-0"
|
|
19
21
|
/>
|
|
20
22
|
</SwitchPrimitive.Root>
|
|
21
23
|
)
|
package/src/primitives/tabs.tsx
CHANGED
|
@@ -2,6 +2,7 @@ import * as React from "react"
|
|
|
2
2
|
import { cva, type VariantProps } from "class-variance-authority"
|
|
3
3
|
import { Tabs as TabsPrimitive } from "radix-ui"
|
|
4
4
|
|
|
5
|
+
import { FOCUS_RING, FOCUS_RING_INSET } from "../lib/focus-ring.js"
|
|
5
6
|
import { cn } from "../lib/utils.js"
|
|
6
7
|
|
|
7
8
|
function Tabs({
|
|
@@ -21,7 +22,7 @@ function Tabs({
|
|
|
21
22
|
}
|
|
22
23
|
|
|
23
24
|
const tabsListVariants = cva(
|
|
24
|
-
"group/tabs-list inline-flex w-fit items-center justify-center rounded-
|
|
25
|
+
"group/tabs-list inline-flex w-fit items-center justify-center rounded-md p-[3px] text-muted-foreground group-data-[orientation=horizontal]/tabs:h-9 group-data-[orientation=vertical]/tabs:h-fit group-data-[orientation=vertical]/tabs:flex-col data-[variant=line]:rounded-none",
|
|
25
26
|
{
|
|
26
27
|
variants: {
|
|
27
28
|
variant: {
|
|
@@ -55,10 +56,11 @@ function TabsTrigger({ className, ...props }: React.ComponentProps<typeof TabsPr
|
|
|
55
56
|
<TabsPrimitive.Trigger
|
|
56
57
|
data-slot="tabs-trigger"
|
|
57
58
|
className={cn(
|
|
58
|
-
"text-foreground
|
|
59
|
-
|
|
60
|
-
"data-[
|
|
61
|
-
"
|
|
59
|
+
"text-muted-foreground hover:text-foreground group-data-[variant=default]/tabs-list:data-[state=active]:shadow-1 relative inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center gap-1.5 rounded-sm border border-transparent px-2 py-1 text-sm font-medium whitespace-nowrap transition-all group-data-[orientation=vertical]/tabs:w-full group-data-[orientation=vertical]/tabs:justify-start disabled:pointer-events-none disabled:opacity-50 group-data-[variant=line]/tabs-list:data-[state=active]:shadow-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
|
60
|
+
FOCUS_RING,
|
|
61
|
+
"group-data-[variant=line]/tabs-list:bg-transparent group-data-[variant=line]/tabs-list:data-[state=active]:border-transparent group-data-[variant=line]/tabs-list:data-[state=active]:bg-transparent",
|
|
62
|
+
"data-[state=active]:border-border data-[state=active]:bg-background data-[state=active]:text-foreground",
|
|
63
|
+
"after:bg-primary after:absolute after:opacity-0 after:transition-opacity group-data-[orientation=horizontal]/tabs:after:inset-x-0 group-data-[orientation=horizontal]/tabs:after:bottom-[-5px] group-data-[orientation=horizontal]/tabs:after:h-0.5 group-data-[orientation=vertical]/tabs:after:inset-y-0 group-data-[orientation=vertical]/tabs:after:-right-1 group-data-[orientation=vertical]/tabs:after:w-0.5 group-data-[variant=line]/tabs-list:data-[state=active]:after:opacity-100",
|
|
62
64
|
className,
|
|
63
65
|
)}
|
|
64
66
|
{...props}
|
|
@@ -67,10 +69,11 @@ function TabsTrigger({ className, ...props }: React.ComponentProps<typeof TabsPr
|
|
|
67
69
|
}
|
|
68
70
|
|
|
69
71
|
function TabsContent({ className, ...props }: React.ComponentProps<typeof TabsPrimitive.Content>) {
|
|
72
|
+
// Radix makes the panel a tab stop (tabIndex 0), so it needs a visible focus.
|
|
70
73
|
return (
|
|
71
74
|
<TabsPrimitive.Content
|
|
72
75
|
data-slot="tabs-content"
|
|
73
|
-
className={cn("flex-1
|
|
76
|
+
className={cn("flex-1", FOCUS_RING_INSET, className)}
|
|
74
77
|
{...props}
|
|
75
78
|
/>
|
|
76
79
|
)
|
package/src/render-cases.tsx
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import type { ReactElement } from "react"
|
|
2
|
+
import { Inbox } from "lucide-react"
|
|
2
3
|
import {
|
|
3
4
|
Button,
|
|
4
5
|
CountPill,
|
|
5
6
|
DrillButton,
|
|
7
|
+
EmptyState,
|
|
8
|
+
ErrorState,
|
|
6
9
|
FilterBar,
|
|
7
10
|
GridItem,
|
|
8
11
|
GridLayout,
|
|
@@ -14,6 +17,7 @@ import {
|
|
|
14
17
|
SectionHeading,
|
|
15
18
|
ThemeProvider,
|
|
16
19
|
themePresets,
|
|
20
|
+
ToastProvider,
|
|
17
21
|
useLocale,
|
|
18
22
|
WidgetHeader,
|
|
19
23
|
} from "./index.js"
|
|
@@ -185,6 +189,46 @@ export const RENDER_CASES: RenderCase[] = [
|
|
|
185
189
|
render: () => <ListFooter shown={8} total={42} hasMore onLoadMore={noop} noun="tasks" />,
|
|
186
190
|
},
|
|
187
191
|
|
|
192
|
+
// ── Feedback and states ──
|
|
193
|
+
{
|
|
194
|
+
// The provider renders its children plus the (empty) live region.
|
|
195
|
+
name: "ToastProvider",
|
|
196
|
+
marker: "RC:ToastProvider",
|
|
197
|
+
render: () => (
|
|
198
|
+
<ToastProvider>
|
|
199
|
+
<div>RC:ToastProvider</div>
|
|
200
|
+
</ToastProvider>
|
|
201
|
+
),
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
name: "EmptyState",
|
|
205
|
+
marker: "RC:EmptyState",
|
|
206
|
+
render: () => (
|
|
207
|
+
<EmptyState
|
|
208
|
+
icon={<Inbox />}
|
|
209
|
+
title="RC:EmptyState"
|
|
210
|
+
description="No orders in this period."
|
|
211
|
+
action={
|
|
212
|
+
<Button size="sm" onClick={noop}>
|
|
213
|
+
New order
|
|
214
|
+
</Button>
|
|
215
|
+
}
|
|
216
|
+
/>
|
|
217
|
+
),
|
|
218
|
+
},
|
|
219
|
+
{
|
|
220
|
+
name: "ErrorState",
|
|
221
|
+
marker: "RC:ErrorState",
|
|
222
|
+
render: () => (
|
|
223
|
+
<ErrorState
|
|
224
|
+
title="RC:ErrorState"
|
|
225
|
+
description="Check the connection and try again."
|
|
226
|
+
details="HTTP 503 Service Unavailable"
|
|
227
|
+
onRetry={noop}
|
|
228
|
+
/>
|
|
229
|
+
),
|
|
230
|
+
},
|
|
231
|
+
|
|
188
232
|
// ── Host seam / providers ──
|
|
189
233
|
{
|
|
190
234
|
name: "HostBridgeProvider",
|
|
@@ -212,7 +256,7 @@ export const RENDER_CASES: RenderCase[] = [
|
|
|
212
256
|
name: "ThemeProvider",
|
|
213
257
|
marker: "RC:ThemeProvider",
|
|
214
258
|
render: () => (
|
|
215
|
-
<ThemeProvider theme={themePresets.
|
|
259
|
+
<ThemeProvider theme={themePresets.emerald} mode="dark">
|
|
216
260
|
<div>RC:ThemeProvider</div>
|
|
217
261
|
</ThemeProvider>
|
|
218
262
|
),
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Read SSR markup (`renderToStaticMarkup`) the way a user — sighted or with a
|
|
3
|
+
* screen reader — meets it: its text nodes plus the attributes that carry
|
|
4
|
+
* text (accessible names, tooltips, placeholders). DATA/TEST support module
|
|
5
|
+
* like `render-cases.tsx`: the a11y and i18n render suites scan through it,
|
|
6
|
+
* so it stays pure and dependency-free. React's SSR output is well-formed
|
|
7
|
+
* (attributes always double-quoted), which is what keeps these regexes honest.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/** Attributes whose value a user reads or hears. */
|
|
11
|
+
export const TEXT_ATTRIBUTES = [
|
|
12
|
+
"aria-label",
|
|
13
|
+
"aria-description",
|
|
14
|
+
"aria-roledescription",
|
|
15
|
+
"aria-valuetext",
|
|
16
|
+
"title",
|
|
17
|
+
"placeholder",
|
|
18
|
+
"alt",
|
|
19
|
+
] as const
|
|
20
|
+
|
|
21
|
+
const NAMED_ENTITIES: Record<string, string> = {
|
|
22
|
+
amp: "&",
|
|
23
|
+
lt: "<",
|
|
24
|
+
gt: ">",
|
|
25
|
+
quot: '"',
|
|
26
|
+
apos: "'",
|
|
27
|
+
nbsp: " ",
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Decode the HTML entities React's SSR emits (named, decimal and hex). */
|
|
31
|
+
export function decodeEntities(text: string): string {
|
|
32
|
+
return text.replace(/&(#x[0-9a-f]+|#\d+|[a-z]+);/gi, (whole, ref: string) => {
|
|
33
|
+
if (ref.startsWith("#x") || ref.startsWith("#X")) {
|
|
34
|
+
return String.fromCodePoint(Number.parseInt(ref.slice(2), 16))
|
|
35
|
+
}
|
|
36
|
+
if (ref.startsWith("#")) return String.fromCodePoint(Number.parseInt(ref.slice(1), 10))
|
|
37
|
+
return NAMED_ENTITIES[ref.toLowerCase()] ?? whole
|
|
38
|
+
})
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** One piece of user-facing text in the markup. */
|
|
42
|
+
export interface MarkupText {
|
|
43
|
+
/** `"text"` for a text node, else the attribute name. */
|
|
44
|
+
source: string
|
|
45
|
+
value: string
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Every non-blank text node and text-bearing attribute value, decoded. */
|
|
49
|
+
export function userFacingTexts(html: string): MarkupText[] {
|
|
50
|
+
const out: MarkupText[] = []
|
|
51
|
+
const withoutRawText = html.replace(/<(script|style)\b[^>]*>[\s\S]*?<\/\1>/gi, "")
|
|
52
|
+
for (const chunk of withoutRawText.split(/<[^>]*>/)) {
|
|
53
|
+
const value = decodeEntities(chunk).trim()
|
|
54
|
+
if (value) out.push({ source: "text", value })
|
|
55
|
+
}
|
|
56
|
+
for (const tag of withoutRawText.match(/<[a-z][^>]*>/gi) ?? []) {
|
|
57
|
+
for (const name of TEXT_ATTRIBUTES) {
|
|
58
|
+
const value = attribute(tag, name)
|
|
59
|
+
if (value?.trim()) out.push({ source: name, value: value.trim() })
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return out
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** The opening tags of every element named `tag` (e.g. `"button"`). */
|
|
66
|
+
export function openingTags(html: string, tag: string): string[] {
|
|
67
|
+
return html.match(new RegExp(`<${tag}(?=[\\s>/])[^>]*>`, "gi")) ?? []
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** The decoded value of `name` on one opening tag, `undefined` when absent. */
|
|
71
|
+
export function attribute(openingTag: string, name: string): string | undefined {
|
|
72
|
+
const escaped = name.replace(/[-]/g, "\\-")
|
|
73
|
+
const match = new RegExp(`\\s${escaped}="([^"]*)"`, "i").exec(openingTag)
|
|
74
|
+
return match ? decodeEntities(match[1] ?? "") : undefined
|
|
75
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/* Aus Miragon/corporate-identity übernommen (miragon-brand 1.8.0, modeler-tool-design/assets/cd-tokens.generated.css). Nicht ändern, zum Aktualisieren neu aus der Skill kopieren. */
|
|
2
|
+
/* AUTOMATISCH GENERIERT aus brand/tokens.json. Nicht von Hand ändern, sondern die Quelle bearbeiten und `npm run tokens:build` laufen lassen. */
|
|
3
|
+
:root {
|
|
4
|
+
--cd-blau: #335DE5;
|
|
5
|
+
--cd-gruen: #00E676;
|
|
6
|
+
--cd-grau: #F9F7F7;
|
|
7
|
+
--cd-schwarz: #1D1D1D;
|
|
8
|
+
--cd-weiss: #FFFFFF;
|
|
9
|
+
--cd-blau-link: #2B50D4;
|
|
10
|
+
--cd-blau-hell: #6B8AFF;
|
|
11
|
+
--cd-success: #00E676;
|
|
12
|
+
--cd-success-soft: #DAFBEB;
|
|
13
|
+
--cd-success-hell: #00E676;
|
|
14
|
+
--cd-warning: #FFB000;
|
|
15
|
+
--cd-warning-soft: #FFF1D2;
|
|
16
|
+
--cd-warning-hell: #FFB000;
|
|
17
|
+
--cd-danger: #CD1839;
|
|
18
|
+
--cd-danger-soft: #FBEEF0;
|
|
19
|
+
--cd-danger-hell: #FB6D73;
|
|
20
|
+
--cd-info: #335DE5;
|
|
21
|
+
--cd-info-soft: #EEF1FD;
|
|
22
|
+
--cd-info-hell: #6B8AFF;
|
|
23
|
+
--cd-linie: #E6E2E2;
|
|
24
|
+
--cd-kontur: #8F8A8A;
|
|
25
|
+
--cd-text-leise: #6B6666;
|
|
26
|
+
--cd-gradient-brand: linear-gradient(120deg, #335DE5 30%, #00E676);
|
|
27
|
+
--cd-gradient-format: linear-gradient(135deg, #335DE5 0%, #00E676 140%);
|
|
28
|
+
--cd-space-1: 4px;
|
|
29
|
+
--cd-space-2: 8px;
|
|
30
|
+
--cd-space-3: 16px;
|
|
31
|
+
--cd-space-4: 24px;
|
|
32
|
+
--cd-space-5: 32px;
|
|
33
|
+
--cd-space-6: 48px;
|
|
34
|
+
--cd-space-7: 64px;
|
|
35
|
+
--cd-space-8: 96px;
|
|
36
|
+
--cd-radius-sm: 6px;
|
|
37
|
+
--cd-radius-md: 12px;
|
|
38
|
+
--cd-radius-lg: 16px;
|
|
39
|
+
--cd-radius-pill: 999px;
|
|
40
|
+
--cd-shadow-1: 0 1px 2px rgba(29, 29, 29, 0.06), 0 1px 3px rgba(29, 29, 29, 0.10);
|
|
41
|
+
--cd-shadow-2: 0 4px 12px rgba(29, 29, 29, 0.10);
|
|
42
|
+
--cd-shadow-3: 0 12px 32px rgba(29, 29, 29, 0.16);
|
|
43
|
+
--cd-motion-fast: 150ms;
|
|
44
|
+
--cd-motion-base: 250ms;
|
|
45
|
+
--cd-motion-slow: 400ms;
|
|
46
|
+
--cd-ease: cubic-bezier(0.4, 0, 0.2, 1);
|
|
47
|
+
--cd-ease-out: cubic-bezier(0, 0, 0.2, 1);
|
|
48
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
import { createHash } from "node:crypto"
|
|
3
|
+
import { existsSync, readdirSync, readFileSync } from "node:fs"
|
|
4
|
+
import { homedir } from "node:os"
|
|
5
|
+
import { join } from "node:path"
|
|
6
|
+
import { describe, expect, it } from "vitest"
|
|
7
|
+
import { CD_TOKENS, CD_TOKENS_VERSION } from "./cd-tokens.js"
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Drift guard for the vendored Miragon corporate-design tokens
|
|
11
|
+
* (modeler-tool-design §13): the copy must stay the skill's file, unchanged.
|
|
12
|
+
*
|
|
13
|
+
* `cd-tokens.generated.css` = ONE marker line + the upstream file byte for
|
|
14
|
+
* byte. Upstream is miragon-brand's `modeler-tool-design/assets/
|
|
15
|
+
* cd-tokens.generated.css`. When a checkout of the plugin is reachable
|
|
16
|
+
* (`MIRAGON_CD_TOKENS` or a Claude plugin cache under $HOME), the copy is
|
|
17
|
+
* compared to it directly; everywhere else (CI) the pinned hash below holds it.
|
|
18
|
+
*
|
|
19
|
+
* To re-vendor: copy the new upstream file below the marker line, update the
|
|
20
|
+
* version in the marker, CD_TOKENS_VERSION, UPSTREAM_SHA256 and CD_TOKENS.
|
|
21
|
+
*/
|
|
22
|
+
const UPSTREAM_SHA256 = "36d76f3a330a1250d332c6862be1dc4f7564d2461e7051d9a565ba723ef2c379"
|
|
23
|
+
const UPSTREAM_REL = join("skills", "modeler-tool-design", "assets", "cd-tokens.generated.css")
|
|
24
|
+
const MARKER =
|
|
25
|
+
`/* Aus Miragon/corporate-identity übernommen (miragon-brand ${CD_TOKENS_VERSION}, ` +
|
|
26
|
+
"modeler-tool-design/assets/cd-tokens.generated.css). Nicht ändern, zum Aktualisieren neu aus der Skill kopieren. */\n"
|
|
27
|
+
|
|
28
|
+
const vendored = readFileSync(new URL("./cd-tokens.generated.css", import.meta.url), "utf8")
|
|
29
|
+
const upstreamPart = vendored.slice(MARKER.length)
|
|
30
|
+
|
|
31
|
+
/** A reachable upstream copy of this version, or null (CI). */
|
|
32
|
+
function findUpstream(): string | null {
|
|
33
|
+
const explicit = process.env.MIRAGON_CD_TOKENS
|
|
34
|
+
if (explicit) return existsSync(explicit) ? explicit : null
|
|
35
|
+
const home = homedir()
|
|
36
|
+
let entries: string[]
|
|
37
|
+
try {
|
|
38
|
+
entries = readdirSync(home).filter((name) => name.startsWith(".claude"))
|
|
39
|
+
} catch {
|
|
40
|
+
return null
|
|
41
|
+
}
|
|
42
|
+
for (const dir of entries) {
|
|
43
|
+
const candidate = join(
|
|
44
|
+
home,
|
|
45
|
+
dir,
|
|
46
|
+
"plugins",
|
|
47
|
+
"cache",
|
|
48
|
+
"miragon-agent-skills",
|
|
49
|
+
"miragon-brand",
|
|
50
|
+
CD_TOKENS_VERSION,
|
|
51
|
+
UPSTREAM_REL,
|
|
52
|
+
)
|
|
53
|
+
if (existsSync(candidate)) return candidate
|
|
54
|
+
}
|
|
55
|
+
return null
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** `--cd-name: value;` declarations of the vendored `:root`. */
|
|
59
|
+
function cssTokens(css: string): Record<string, string> {
|
|
60
|
+
const out: Record<string, string> = {}
|
|
61
|
+
for (const match of css.matchAll(/--cd-([a-z0-9-]+):\s*([^;]+);/g)) {
|
|
62
|
+
out[match[1] as string] = (match[2] as string).trim()
|
|
63
|
+
}
|
|
64
|
+
return out
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
describe("cd-tokens.generated.css (vendored, never edited)", () => {
|
|
68
|
+
it("starts with the generated-file marker naming the brand version", () => {
|
|
69
|
+
expect(vendored.startsWith(MARKER)).toBe(true)
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
it("is the upstream file byte for byte below the marker (pinned hash)", () => {
|
|
73
|
+
const hash = createHash("sha256").update(upstreamPart, "utf8").digest("hex")
|
|
74
|
+
expect(hash, "re-vendor from the skill instead of editing the copy").toBe(UPSTREAM_SHA256)
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
it("is byte-identical to the skill's file when a plugin checkout is reachable", () => {
|
|
78
|
+
const upstream = findUpstream()
|
|
79
|
+
// Off-machine (CI) the pinned hash above is the guard; say so loudly.
|
|
80
|
+
if (!upstream) {
|
|
81
|
+
console.warn(
|
|
82
|
+
`cd-tokens drift: no miragon-brand ${CD_TOKENS_VERSION} checkout found — relying on the pinned hash`,
|
|
83
|
+
)
|
|
84
|
+
return
|
|
85
|
+
}
|
|
86
|
+
expect(upstreamPart).toBe(readFileSync(upstream, "utf8"))
|
|
87
|
+
})
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
describe("CD_TOKENS (the TS mirror of :root)", () => {
|
|
91
|
+
const css = cssTokens(upstreamPart)
|
|
92
|
+
|
|
93
|
+
it("mirrors every --cd-* token value exactly", () => {
|
|
94
|
+
for (const [name, value] of Object.entries(css)) {
|
|
95
|
+
expect(CD_TOKENS[name as keyof typeof CD_TOKENS], `--cd-${name}`).toBe(value)
|
|
96
|
+
}
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
it("declares no token the CSS does not", () => {
|
|
100
|
+
expect(Object.keys(CD_TOKENS).sort()).toEqual(Object.keys(css).sort())
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
it("parses a non-trivial token set (the parser itself is alive)", () => {
|
|
104
|
+
expect(Object.keys(css).length).toBeGreaterThanOrEqual(40)
|
|
105
|
+
expect(css.blau).toBe("#335DE5")
|
|
106
|
+
})
|
|
107
|
+
})
|