@seed-ship/mcp-ui-solid 6.19.1 → 6.20.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/CHANGELOG.md +519 -0
- package/README.md +791 -1
- package/dist/adapters/connector.cjs +15 -4
- package/dist/adapters/connector.cjs.map +1 -1
- package/dist/adapters/connector.d.ts +38 -0
- package/dist/adapters/connector.d.ts.map +1 -1
- package/dist/adapters/connector.js +15 -4
- package/dist/adapters/connector.js.map +1 -1
- package/dist/adapters/index.d.ts +4 -3
- package/dist/adapters/index.d.ts.map +1 -1
- package/dist/adapters/macro-run.cjs +25 -13
- package/dist/adapters/macro-run.cjs.map +1 -1
- package/dist/adapters/macro-run.d.ts +37 -2
- package/dist/adapters/macro-run.d.ts.map +1 -1
- package/dist/adapters/macro-run.js +25 -13
- package/dist/adapters/macro-run.js.map +1 -1
- package/dist/adapters.cjs +2 -0
- package/dist/adapters.cjs.map +1 -1
- package/dist/adapters.js +4 -2
- package/dist/components/ActionGroupRenderer.cjs +3 -1
- package/dist/components/ActionGroupRenderer.cjs.map +1 -1
- package/dist/components/ActionGroupRenderer.d.ts.map +1 -1
- package/dist/components/ActionGroupRenderer.js +3 -1
- package/dist/components/ActionGroupRenderer.js.map +1 -1
- package/dist/components/AgentCard.cjs +11 -8
- package/dist/components/AgentCard.cjs.map +1 -1
- package/dist/components/AgentCard.d.ts.map +1 -1
- package/dist/components/AgentCard.js +11 -8
- package/dist/components/AgentCard.js.map +1 -1
- package/dist/components/AgentHandoff.cjs +6 -1
- package/dist/components/AgentHandoff.cjs.map +1 -1
- package/dist/components/AgentHandoff.d.ts.map +1 -1
- package/dist/components/AgentHandoff.js +6 -1
- package/dist/components/AgentHandoff.js.map +1 -1
- package/dist/components/ArtifactRenderer.cjs +25 -6
- package/dist/components/ArtifactRenderer.cjs.map +1 -1
- package/dist/components/ArtifactRenderer.d.ts.map +1 -1
- package/dist/components/ArtifactRenderer.js +25 -6
- package/dist/components/ArtifactRenderer.js.map +1 -1
- package/dist/components/AutocompleteDropdown.cjs +30 -24
- package/dist/components/AutocompleteDropdown.cjs.map +1 -1
- package/dist/components/AutocompleteDropdown.d.ts.map +1 -1
- package/dist/components/AutocompleteDropdown.js +30 -24
- package/dist/components/AutocompleteDropdown.js.map +1 -1
- package/dist/components/AutocompleteFormField.cjs +3 -1
- package/dist/components/AutocompleteFormField.cjs.map +1 -1
- package/dist/components/AutocompleteFormField.d.ts.map +1 -1
- package/dist/components/AutocompleteFormField.js +4 -2
- package/dist/components/AutocompleteFormField.js.map +1 -1
- package/dist/components/BriefingDiff.cjs +45 -39
- package/dist/components/BriefingDiff.cjs.map +1 -1
- package/dist/components/BriefingDiff.d.ts.map +1 -1
- package/dist/components/BriefingDiff.js +45 -39
- package/dist/components/BriefingDiff.js.map +1 -1
- package/dist/components/CarouselRenderer.cjs +8 -2
- package/dist/components/CarouselRenderer.cjs.map +1 -1
- package/dist/components/CarouselRenderer.d.ts.map +1 -1
- package/dist/components/CarouselRenderer.js +8 -2
- package/dist/components/CarouselRenderer.js.map +1 -1
- package/dist/components/ChartJSRenderer.cjs +60 -36
- package/dist/components/ChartJSRenderer.cjs.map +1 -1
- package/dist/components/ChartJSRenderer.d.ts.map +1 -1
- package/dist/components/ChartJSRenderer.js +61 -37
- package/dist/components/ChartJSRenderer.js.map +1 -1
- package/dist/components/ChatPrompt.cjs +15 -9
- package/dist/components/ChatPrompt.cjs.map +1 -1
- package/dist/components/ChatPrompt.d.ts.map +1 -1
- package/dist/components/ChatPrompt.js +15 -9
- package/dist/components/ChatPrompt.js.map +1 -1
- package/dist/components/CodeBlockRenderer.cjs +34 -16
- package/dist/components/CodeBlockRenderer.cjs.map +1 -1
- package/dist/components/CodeBlockRenderer.d.ts.map +1 -1
- package/dist/components/CodeBlockRenderer.js +34 -16
- package/dist/components/CodeBlockRenderer.js.map +1 -1
- package/dist/components/DataPreviewSection.cjs +91 -44
- package/dist/components/DataPreviewSection.cjs.map +1 -1
- package/dist/components/DataPreviewSection.d.ts.map +1 -1
- package/dist/components/DataPreviewSection.js +92 -45
- package/dist/components/DataPreviewSection.js.map +1 -1
- package/dist/components/DegradedFallback.cjs +20 -16
- package/dist/components/DegradedFallback.cjs.map +1 -1
- package/dist/components/DegradedFallback.d.ts.map +1 -1
- package/dist/components/DegradedFallback.js +20 -16
- package/dist/components/DegradedFallback.js.map +1 -1
- package/dist/components/FooterRenderer.cjs +21 -17
- package/dist/components/FooterRenderer.cjs.map +1 -1
- package/dist/components/FooterRenderer.d.ts.map +1 -1
- package/dist/components/FooterRenderer.js +21 -17
- package/dist/components/FooterRenderer.js.map +1 -1
- package/dist/components/FormFieldRenderer.cjs +116 -93
- package/dist/components/FormFieldRenderer.cjs.map +1 -1
- package/dist/components/FormFieldRenderer.d.ts.map +1 -1
- package/dist/components/FormFieldRenderer.js +116 -93
- package/dist/components/FormFieldRenderer.js.map +1 -1
- package/dist/components/FormRenderer.cjs +55 -31
- package/dist/components/FormRenderer.cjs.map +1 -1
- package/dist/components/FormRenderer.d.ts.map +1 -1
- package/dist/components/FormRenderer.js +55 -31
- package/dist/components/FormRenderer.js.map +1 -1
- package/dist/components/GenerativeUIErrorBoundary.cjs +20 -15
- package/dist/components/GenerativeUIErrorBoundary.cjs.map +1 -1
- package/dist/components/GenerativeUIErrorBoundary.d.ts.map +1 -1
- package/dist/components/GenerativeUIErrorBoundary.js +20 -15
- package/dist/components/GenerativeUIErrorBoundary.js.map +1 -1
- package/dist/components/GraphRenderer.cjs +56 -33
- package/dist/components/GraphRenderer.cjs.map +1 -1
- package/dist/components/GraphRenderer.d.ts.map +1 -1
- package/dist/components/GraphRenderer.js +56 -33
- package/dist/components/GraphRenderer.js.map +1 -1
- package/dist/components/ImageGalleryRenderer.cjs +13 -4
- package/dist/components/ImageGalleryRenderer.cjs.map +1 -1
- package/dist/components/ImageGalleryRenderer.d.ts.map +1 -1
- package/dist/components/ImageGalleryRenderer.js +13 -4
- package/dist/components/ImageGalleryRenderer.js.map +1 -1
- package/dist/components/LightboxOverlay.cjs +16 -8
- package/dist/components/LightboxOverlay.cjs.map +1 -1
- package/dist/components/LightboxOverlay.d.ts.map +1 -1
- package/dist/components/LightboxOverlay.js +17 -9
- package/dist/components/LightboxOverlay.js.map +1 -1
- package/dist/components/MapRenderer.cjs +59 -29
- package/dist/components/MapRenderer.cjs.map +1 -1
- package/dist/components/MapRenderer.d.ts +10 -2
- package/dist/components/MapRenderer.d.ts.map +1 -1
- package/dist/components/MapRenderer.js +60 -30
- package/dist/components/MapRenderer.js.map +1 -1
- package/dist/components/ModalRenderer.cjs +4 -1
- package/dist/components/ModalRenderer.cjs.map +1 -1
- package/dist/components/ModalRenderer.d.ts.map +1 -1
- package/dist/components/ModalRenderer.js +5 -2
- package/dist/components/ModalRenderer.js.map +1 -1
- package/dist/components/ScratchpadPanel.cjs +578 -535
- package/dist/components/ScratchpadPanel.cjs.map +1 -1
- package/dist/components/ScratchpadPanel.d.ts.map +1 -1
- package/dist/components/ScratchpadPanel.js +578 -535
- package/dist/components/ScratchpadPanel.js.map +1 -1
- package/dist/components/StreamingUIRenderer.cjs +92 -28
- package/dist/components/StreamingUIRenderer.cjs.map +1 -1
- package/dist/components/StreamingUIRenderer.d.ts.map +1 -1
- package/dist/components/StreamingUIRenderer.js +92 -28
- package/dist/components/StreamingUIRenderer.js.map +1 -1
- package/dist/components/UIResourceRenderer.cjs +456 -363
- package/dist/components/UIResourceRenderer.cjs.map +1 -1
- package/dist/components/UIResourceRenderer.d.ts +21 -0
- package/dist/components/UIResourceRenderer.d.ts.map +1 -1
- package/dist/components/UIResourceRenderer.js +458 -365
- package/dist/components/UIResourceRenderer.js.map +1 -1
- package/dist/components/VerifiedText.cjs +39 -20
- package/dist/components/VerifiedText.cjs.map +1 -1
- package/dist/components/VerifiedText.d.ts +2 -1
- package/dist/components/VerifiedText.d.ts.map +1 -1
- package/dist/components/VerifiedText.js +39 -20
- package/dist/components/VerifiedText.js.map +1 -1
- package/dist/components/VideoRenderer.cjs +10 -5
- package/dist/components/VideoRenderer.cjs.map +1 -1
- package/dist/components/VideoRenderer.d.ts.map +1 -1
- package/dist/components/VideoRenderer.js +10 -5
- package/dist/components/VideoRenderer.js.map +1 -1
- package/dist/components/chart-data-table.cjs +38 -17
- package/dist/components/chart-data-table.cjs.map +1 -1
- package/dist/components/chart-data-table.d.ts +30 -1
- package/dist/components/chart-data-table.d.ts.map +1 -1
- package/dist/components/chart-data-table.js +38 -17
- package/dist/components/chart-data-table.js.map +1 -1
- package/dist/context/MCPActionContext.cjs +3 -1
- package/dist/context/MCPActionContext.cjs.map +1 -1
- package/dist/context/MCPActionContext.d.ts.map +1 -1
- package/dist/context/MCPActionContext.js +3 -1
- package/dist/context/MCPActionContext.js.map +1 -1
- package/dist/context/MCPUIStringsContext.cjs +285 -2
- package/dist/context/MCPUIStringsContext.cjs.map +1 -1
- package/dist/context/MCPUIStringsContext.d.ts +600 -3
- package/dist/context/MCPUIStringsContext.d.ts.map +1 -1
- package/dist/context/MCPUIStringsContext.js +285 -2
- package/dist/context/MCPUIStringsContext.js.map +1 -1
- package/dist/hooks/index.d.ts +2 -2
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/useStreamingUI.cjs +28 -10
- package/dist/hooks/useStreamingUI.cjs.map +1 -1
- package/dist/hooks/useStreamingUI.d.ts +37 -0
- package/dist/hooks/useStreamingUI.d.ts.map +1 -1
- package/dist/hooks/useStreamingUI.js +28 -10
- package/dist/hooks/useStreamingUI.js.map +1 -1
- package/dist/hooks.cjs +1 -0
- package/dist/hooks.cjs.map +1 -1
- package/dist/hooks.js +2 -1
- package/dist/index.cjs +23 -8
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +9 -3
- package/dist/index.d.ts +9 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +18 -3
- package/dist/index.js.map +1 -1
- package/dist/services/validation.cjs +41 -16
- package/dist/services/validation.cjs.map +1 -1
- package/dist/services/validation.d.ts +47 -2
- package/dist/services/validation.d.ts.map +1 -1
- package/dist/services/validation.js +41 -16
- package/dist/services/validation.js.map +1 -1
- package/dist/utils/degraded-projections.cjs +37 -12
- package/dist/utils/degraded-projections.cjs.map +1 -1
- package/dist/utils/degraded-projections.d.ts +39 -3
- package/dist/utils/degraded-projections.d.ts.map +1 -1
- package/dist/utils/degraded-projections.js +37 -12
- package/dist/utils/degraded-projections.js.map +1 -1
- package/dist/utils/format-string.cjs +10 -0
- package/dist/utils/format-string.cjs.map +1 -0
- package/dist/utils/format-string.d.ts +26 -0
- package/dist/utils/format-string.d.ts.map +1 -0
- package/dist/utils/format-string.js +10 -0
- package/dist/utils/format-string.js.map +1 -0
- package/dist/validation.cjs +3 -0
- package/dist/validation.cjs.map +1 -1
- package/dist/validation.d.cts +2 -1
- package/dist/validation.d.ts +2 -1
- package/dist/validation.d.ts.map +1 -1
- package/dist/validation.js +4 -1
- package/package.json +1 -1
- package/src/adapters/connector.ts +58 -4
- package/src/adapters/index.ts +12 -2
- package/src/adapters/macro-run.ts +71 -11
- package/src/components/ActionGroupRenderer.tsx +3 -1
- package/src/components/AgentCard.tsx +27 -9
- package/src/components/AgentHandoff.tsx +4 -1
- package/src/components/ArtifactRenderer.tsx +17 -5
- package/src/components/AutocompleteDropdown.tsx +8 -6
- package/src/components/AutocompleteFormField.tsx +5 -1
- package/src/components/BriefingDiff.tsx +11 -3
- package/src/components/CarouselRenderer.tsx +4 -2
- package/src/components/ChartJSRenderer.tsx +18 -9
- package/src/components/ChatPrompt.tsx +13 -8
- package/src/components/CodeBlockRenderer.tsx +12 -10
- package/src/components/DataPreviewSection.tsx +92 -26
- package/src/components/DegradedFallback.tsx +4 -2
- package/src/components/FooterRenderer.tsx +3 -1
- package/src/components/FormFieldRenderer.tsx +34 -19
- package/src/components/FormRenderer.tsx +38 -10
- package/src/components/GenerativeUIErrorBoundary.tsx +8 -3
- package/src/components/GraphRenderer.tsx +31 -16
- package/src/components/ImageGalleryRenderer.tsx +8 -4
- package/src/components/LightboxOverlay.tsx +6 -4
- package/src/components/MapRenderer.tsx +57 -20
- package/src/components/ModalRenderer.tsx +3 -1
- package/src/components/ScratchpadPanel.tsx +95 -44
- package/src/components/StreamingUIRenderer.tsx +41 -11
- package/src/components/UIResourceRenderer.tsx +145 -63
- package/src/components/VerifiedText.tsx +19 -9
- package/src/components/VideoRenderer.tsx +6 -4
- package/src/components/chart-data-table.ts +68 -14
- package/src/context/MCPActionContext.tsx +4 -1
- package/src/context/MCPUIStringsContext.tsx +1004 -1
- package/src/hooks/index.ts +2 -1
- package/src/hooks/useStreamingUI.ts +64 -10
- package/src/index.ts +23 -0
- package/src/services/validation.ts +93 -16
- package/src/utils/degraded-projections.ts +75 -12
- package/src/utils/format-string.ts +33 -0
- package/src/validation.ts +5 -0
|
@@ -5,8 +5,13 @@
|
|
|
5
5
|
* @experimental
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import { createSignal, createMemo, createEffect, For, Show } from 'solid-js'
|
|
8
|
+
import { createSignal, createMemo, createEffect, For, Show, onMount } from 'solid-js'
|
|
9
9
|
import type { DataPreviewContent, DataPreviewColumn } from '../types/chat-bus'
|
|
10
|
+
import {
|
|
11
|
+
DEFAULT_MCPUI_STRINGS,
|
|
12
|
+
formatMCPUIString,
|
|
13
|
+
useMCPUIStrings,
|
|
14
|
+
} from '../context/MCPUIStringsContext'
|
|
10
15
|
|
|
11
16
|
export interface DataPreviewSectionProps {
|
|
12
17
|
content: DataPreviewContent
|
|
@@ -16,24 +21,62 @@ type SortDir = 'asc' | 'desc' | null
|
|
|
16
21
|
|
|
17
22
|
// ─── Formatting helpers ─────────────────────────────────────
|
|
18
23
|
|
|
19
|
-
|
|
24
|
+
/**
|
|
25
|
+
* `locale` — BCP-47 tag (`MCPUIStrings.locale`, default `'en-US'`). Before
|
|
26
|
+
* 6.20.0 these helpers hardcoded `'fr-FR'` / `'fr'`.
|
|
27
|
+
*/
|
|
28
|
+
function formatNumber(
|
|
29
|
+
value: unknown,
|
|
30
|
+
format?: string,
|
|
31
|
+
locale: string = DEFAULT_MCPUI_STRINGS.locale
|
|
32
|
+
): string {
|
|
20
33
|
if (typeof value !== 'number' || !isFinite(value)) return String(value ?? '')
|
|
21
|
-
if (format === 'percent')
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
34
|
+
if (format === 'percent') {
|
|
35
|
+
return new Intl.NumberFormat(locale, {
|
|
36
|
+
style: 'percent',
|
|
37
|
+
minimumFractionDigits: 1,
|
|
38
|
+
maximumFractionDigits: 1,
|
|
39
|
+
useGrouping: false,
|
|
40
|
+
}).format(value)
|
|
41
|
+
}
|
|
42
|
+
if (format === 'currency') return `${value.toLocaleString(locale)} EUR`
|
|
43
|
+
if (Number.isInteger(value)) return value.toLocaleString(locale)
|
|
44
|
+
return value.toLocaleString(locale, { maximumFractionDigits: 2 })
|
|
25
45
|
}
|
|
26
46
|
|
|
27
|
-
function formatCell(
|
|
47
|
+
function formatCell(
|
|
48
|
+
value: unknown,
|
|
49
|
+
col: DataPreviewColumn,
|
|
50
|
+
locale: string = DEFAULT_MCPUI_STRINGS.locale,
|
|
51
|
+
/** False before mount: date-time values stay raw so SSR and hydration match. */
|
|
52
|
+
zoneAware = true
|
|
53
|
+
): string {
|
|
28
54
|
if (value == null) return '\u2014'
|
|
29
|
-
if (col.type === 'number') return formatNumber(value, col.format)
|
|
55
|
+
if (col.type === 'number') return formatNumber(value, col.format, locale)
|
|
30
56
|
if (col.type === 'date' && typeof value === 'string') {
|
|
31
|
-
|
|
57
|
+
const date = new Date(value)
|
|
58
|
+
if (Number.isNaN(date.getTime())) return value
|
|
59
|
+
// A date-only value (`2026-09-17`) has no time zone: format it in UTC so
|
|
60
|
+
// every viewer, and the server, shows the same calendar date.
|
|
61
|
+
const dateOnly = /^\d{4}-\d{2}-\d{2}$/.test(value)
|
|
62
|
+
// A date-time's calendar date depends on the viewer's time zone, which the
|
|
63
|
+
// server cannot know: keep the raw value until the component has mounted.
|
|
64
|
+
if (!dateOnly && !zoneAware) return value
|
|
65
|
+
try {
|
|
66
|
+
return date.toLocaleDateString(locale, dateOnly ? { timeZone: 'UTC' } : undefined)
|
|
67
|
+
} catch {
|
|
68
|
+
return value
|
|
69
|
+
}
|
|
32
70
|
}
|
|
33
71
|
return String(value)
|
|
34
72
|
}
|
|
35
73
|
|
|
36
|
-
function compareValues(
|
|
74
|
+
function compareValues(
|
|
75
|
+
a: unknown,
|
|
76
|
+
b: unknown,
|
|
77
|
+
type?: string,
|
|
78
|
+
locale: string = DEFAULT_MCPUI_STRINGS.locale
|
|
79
|
+
): number {
|
|
37
80
|
if (a == null && b == null) return 0
|
|
38
81
|
if (a == null) return 1
|
|
39
82
|
if (b == null) return -1
|
|
@@ -53,7 +96,7 @@ function compareValues(a: unknown, b: unknown, type?: string): number {
|
|
|
53
96
|
if (isNaN(db)) return -1
|
|
54
97
|
return da - db
|
|
55
98
|
}
|
|
56
|
-
return String(a).localeCompare(String(b),
|
|
99
|
+
return String(a).localeCompare(String(b), locale, { sensitivity: 'base' })
|
|
57
100
|
}
|
|
58
101
|
|
|
59
102
|
// ─── Export helpers ─────────────────────────────────────────
|
|
@@ -101,6 +144,7 @@ function resolveContent(raw: unknown): DataPreviewContent | null {
|
|
|
101
144
|
// ─── Component ──────────────────────────────────────────────
|
|
102
145
|
|
|
103
146
|
export function DataPreviewSection(props: DataPreviewSectionProps) {
|
|
147
|
+
const strings = useMCPUIStrings()
|
|
104
148
|
const content = createMemo(() => {
|
|
105
149
|
const resolved = resolveContent(props.content)
|
|
106
150
|
if (!resolved) {
|
|
@@ -115,6 +159,8 @@ export function DataPreviewSection(props: DataPreviewSectionProps) {
|
|
|
115
159
|
const showPageInfo = () => content()?.showPageInfo !== false
|
|
116
160
|
|
|
117
161
|
const [page, setPage] = createSignal(content()?.initialPage ?? 0)
|
|
162
|
+
const [mounted, setMounted] = createSignal(false)
|
|
163
|
+
onMount(() => setMounted(true))
|
|
118
164
|
const [sortKey, setSortKey] = createSignal<string | null>(null)
|
|
119
165
|
const [sortDir, setSortDir] = createSignal<SortDir>(null)
|
|
120
166
|
|
|
@@ -142,7 +188,7 @@ export function DataPreviewSection(props: DataPreviewSectionProps) {
|
|
|
142
188
|
if (!key || !dir) return r
|
|
143
189
|
const col = columns().find(c => c.key === key)
|
|
144
190
|
return [...r].sort((a, b) => {
|
|
145
|
-
const cmp = compareValues(a[key], b[key], col?.type)
|
|
191
|
+
const cmp = compareValues(a[key], b[key], col?.type, strings.locale)
|
|
146
192
|
return dir === 'desc' ? -cmp : cmp
|
|
147
193
|
})
|
|
148
194
|
})
|
|
@@ -186,7 +232,7 @@ export function DataPreviewSection(props: DataPreviewSectionProps) {
|
|
|
186
232
|
return (
|
|
187
233
|
<Show when={content()} fallback={
|
|
188
234
|
<div class="text-xs text-amber-600 dark:text-amber-400 p-2">
|
|
189
|
-
|
|
235
|
+
{strings.previewInvalidContent}
|
|
190
236
|
</div>
|
|
191
237
|
}>
|
|
192
238
|
{(c) => (
|
|
@@ -208,14 +254,14 @@ export function DataPreviewSection(props: DataPreviewSectionProps) {
|
|
|
208
254
|
<button
|
|
209
255
|
class="px-2 py-1 text-xs rounded border border-gray-300 dark:border-gray-600 hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors"
|
|
210
256
|
onClick={handleExportCSV}
|
|
211
|
-
title={
|
|
257
|
+
title={formatMCPUIString(strings.exportCsvRows, { count: sortedRows().length })}
|
|
212
258
|
>
|
|
213
259
|
CSV
|
|
214
260
|
</button>
|
|
215
261
|
<button
|
|
216
262
|
class="px-2 py-1 text-xs rounded border border-gray-300 dark:border-gray-600 hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors"
|
|
217
263
|
onClick={handleExportJSON}
|
|
218
|
-
title={
|
|
264
|
+
title={formatMCPUIString(strings.exportJsonRows, { count: sortedRows().length })}
|
|
219
265
|
>
|
|
220
266
|
JSON
|
|
221
267
|
</button>
|
|
@@ -234,7 +280,7 @@ export function DataPreviewSection(props: DataPreviewSectionProps) {
|
|
|
234
280
|
class="px-3 py-2 font-medium text-xs text-gray-600 dark:text-gray-400 uppercase tracking-wider border-b border-gray-200 dark:border-gray-700 cursor-pointer select-none hover:bg-gray-100 dark:hover:bg-gray-700/50 transition-colors"
|
|
235
281
|
style={{ "text-align": columnAlign(col) }}
|
|
236
282
|
onClick={() => handleSort(col.key)}
|
|
237
|
-
title={
|
|
283
|
+
title={formatMCPUIString(strings.sortBy, { column: col.label })}
|
|
238
284
|
>
|
|
239
285
|
<span class="inline-flex items-center gap-1">
|
|
240
286
|
{col.label}
|
|
@@ -266,7 +312,7 @@ export function DataPreviewSection(props: DataPreviewSectionProps) {
|
|
|
266
312
|
class="px-3 py-2 text-gray-800 dark:text-gray-200"
|
|
267
313
|
style={{ "text-align": columnAlign(col) }}
|
|
268
314
|
>
|
|
269
|
-
{formatCell(row[col.key], col)}
|
|
315
|
+
{formatCell(row[col.key], col, strings.locale, mounted())}
|
|
270
316
|
</td>
|
|
271
317
|
)}
|
|
272
318
|
</For>
|
|
@@ -282,13 +328,22 @@ export function DataPreviewSection(props: DataPreviewSectionProps) {
|
|
|
282
328
|
<Show when={showPageInfo()}>
|
|
283
329
|
<span>
|
|
284
330
|
{isPaginated()
|
|
285
|
-
?
|
|
286
|
-
|
|
287
|
-
|
|
331
|
+
? formatMCPUIString(strings.previewShowingRange, {
|
|
332
|
+
start: rangeStart().toLocaleString(strings.locale),
|
|
333
|
+
end: rangeEnd().toLocaleString(strings.locale),
|
|
334
|
+
// `strings.locale` formats the count, so the chrome
|
|
335
|
+
// string and the digits agree (and SSR matches the client).
|
|
336
|
+
total: sortedRows().length.toLocaleString(strings.locale),
|
|
337
|
+
})
|
|
338
|
+
: formatMCPUIString(
|
|
339
|
+
sortedRows().length === 1 ? strings.previewRowsOne : strings.previewRowsMany,
|
|
340
|
+
{ count: sortedRows().length.toLocaleString(strings.locale) }
|
|
341
|
+
)}
|
|
288
342
|
{c().totalRows && c().totalRows! > sortedRows().length
|
|
289
|
-
?
|
|
290
|
-
|
|
291
|
-
|
|
343
|
+
? formatMCPUIString(strings.previewTotalSuffix, {
|
|
344
|
+
total: c().totalRows!.toLocaleString(strings.locale),
|
|
345
|
+
})
|
|
346
|
+
: ''}
|
|
292
347
|
</span>
|
|
293
348
|
</Show>
|
|
294
349
|
<Show when={!showPageInfo()}><span /></Show>
|
|
@@ -296,19 +351,30 @@ export function DataPreviewSection(props: DataPreviewSectionProps) {
|
|
|
296
351
|
<Show when={isPaginated()}>
|
|
297
352
|
<div class="flex items-center gap-1">
|
|
298
353
|
<button
|
|
354
|
+
type="button"
|
|
299
355
|
class="px-2 py-1 rounded hover:bg-gray-100 dark:hover:bg-gray-700 disabled:opacity-40 disabled:cursor-not-allowed transition-colors"
|
|
300
356
|
disabled={page() === 0}
|
|
301
357
|
onClick={() => setPage(p => p - 1)}
|
|
358
|
+
aria-label={strings.paginationPrevious}
|
|
359
|
+
data-mcp-ui-action="page-prev"
|
|
302
360
|
>
|
|
303
|
-
|
|
361
|
+
<span aria-hidden="true">◀</span> {strings.previewPrev}
|
|
304
362
|
</button>
|
|
305
|
-
<span class="px-2"
|
|
363
|
+
<span class="px-2" aria-live="polite">
|
|
364
|
+
{formatMCPUIString(strings.previewPageIndicator, {
|
|
365
|
+
page: page() + 1,
|
|
366
|
+
total: totalPages(),
|
|
367
|
+
})}
|
|
368
|
+
</span>
|
|
306
369
|
<button
|
|
370
|
+
type="button"
|
|
307
371
|
class="px-2 py-1 rounded hover:bg-gray-100 dark:hover:bg-gray-700 disabled:opacity-40 disabled:cursor-not-allowed transition-colors"
|
|
308
372
|
disabled={page() >= totalPages() - 1}
|
|
309
373
|
onClick={() => setPage(p => p + 1)}
|
|
374
|
+
aria-label={strings.paginationNext}
|
|
375
|
+
data-mcp-ui-action="page-next"
|
|
310
376
|
>
|
|
311
|
-
|
|
377
|
+
{strings.previewNext} <span aria-hidden="true">▶</span>
|
|
312
378
|
</button>
|
|
313
379
|
</div>
|
|
314
380
|
</Show>
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
18
|
import { Component, For, Show } from 'solid-js';
|
|
19
|
+
import { formatMCPUIString, useMCPUIStrings } from '../context/MCPUIStringsContext';
|
|
19
20
|
|
|
20
21
|
export interface DegradedFallbackProps {
|
|
21
22
|
/** Short, human-readable reason the native render was skipped/failed. */
|
|
@@ -37,6 +38,7 @@ export interface DegradedFallbackProps {
|
|
|
37
38
|
}
|
|
38
39
|
|
|
39
40
|
export const DegradedFallback: Component<DegradedFallbackProps> = (props) => {
|
|
41
|
+
const strings = useMCPUIStrings();
|
|
40
42
|
const maxRows = () => props.maxRows ?? 50;
|
|
41
43
|
const allRows = () => props.rows ?? [];
|
|
42
44
|
const shownRows = () => allRows().slice(0, maxRows());
|
|
@@ -50,7 +52,7 @@ export const DegradedFallback: Component<DegradedFallbackProps> = (props) => {
|
|
|
50
52
|
>
|
|
51
53
|
<p class="text-sm font-medium text-amber-900 dark:text-amber-100">{props.message}</p>
|
|
52
54
|
<p class="mt-0.5 text-xs text-amber-700 dark:text-amber-300">
|
|
53
|
-
{props.caption ??
|
|
55
|
+
{props.caption ?? strings.degradedCaption}
|
|
54
56
|
</p>
|
|
55
57
|
|
|
56
58
|
<Show when={hasTable()}>
|
|
@@ -84,7 +86,7 @@ export const DegradedFallback: Component<DegradedFallbackProps> = (props) => {
|
|
|
84
86
|
</div>
|
|
85
87
|
<Show when={hiddenCount() > 0}>
|
|
86
88
|
<p class="mt-1 text-[10px] text-amber-600 dark:text-amber-400">
|
|
87
|
-
|
|
89
|
+
{formatMCPUIString(strings.degradedMoreRows, { count: hiddenCount() })}
|
|
88
90
|
</p>
|
|
89
91
|
</Show>
|
|
90
92
|
</Show>
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Component, Show, For } from 'solid-js'
|
|
2
2
|
import { safeUrl } from '../utils/safe-url'
|
|
3
|
+
import { formatMCPUIString, useMCPUIStrings } from '../context/MCPUIStringsContext'
|
|
3
4
|
|
|
4
5
|
export interface FooterComponentParams {
|
|
5
6
|
poweredBy?: string
|
|
@@ -11,6 +12,7 @@ export interface FooterComponentParams {
|
|
|
11
12
|
}
|
|
12
13
|
|
|
13
14
|
export const FooterRenderer: Component<{ params: FooterComponentParams }> = (props) => {
|
|
15
|
+
const strings = useMCPUIStrings()
|
|
14
16
|
return (
|
|
15
17
|
<div class="mt-4 pt-3 border-t border-gray-200 dark:border-gray-700 text-xs text-gray-500 dark:text-gray-400 flex flex-wrap items-center gap-4">
|
|
16
18
|
<Show when={props.params.poweredBy}>
|
|
@@ -40,7 +42,7 @@ export const FooterRenderer: Component<{ params: FooterComponentParams }> = (pro
|
|
|
40
42
|
<svg class="w-3 h-3" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
41
43
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
|
|
42
44
|
</svg>
|
|
43
|
-
{props.params.sourceCount}
|
|
45
|
+
{formatMCPUIString(strings.footerSources, { count: props.params.sourceCount! })}
|
|
44
46
|
</span>
|
|
45
47
|
</Show>
|
|
46
48
|
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
import { Component, Show, For, Switch, Match, Accessor, createSignal, createEffect, onCleanup } from 'solid-js'
|
|
8
8
|
import type { FormFieldParams, PrefillSource } from '../types'
|
|
9
9
|
import { useConditionalField } from '../hooks/useConditionalField'
|
|
10
|
+
import { formatMCPUIString, useMCPUIStrings } from '../context/MCPUIStringsContext'
|
|
10
11
|
|
|
11
12
|
export interface FormFieldRendererProps {
|
|
12
13
|
field: FormFieldParams
|
|
@@ -20,15 +21,24 @@ export interface FormFieldRendererProps {
|
|
|
20
21
|
formData?: Accessor<Record<string, any>>
|
|
21
22
|
}
|
|
22
23
|
|
|
23
|
-
/**
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
24
|
+
/**
|
|
25
|
+
* Badge config by prefill source. Module-level, so the title cannot be read
|
|
26
|
+
* from the context here — each badge carries the `MCPUIStrings` key instead
|
|
27
|
+
* and the rendering component resolves it at render time.
|
|
28
|
+
*/
|
|
29
|
+
const SOURCE_BADGES: Record<
|
|
30
|
+
PrefillSource,
|
|
31
|
+
{ icon: string; titleKey: 'sourceDetected' | 'sourceInferred' | 'sourcePrevious' } | null
|
|
32
|
+
> = {
|
|
33
|
+
detected: { icon: '\u2705', titleKey: 'sourceDetected' },
|
|
34
|
+
inferred: { icon: '\uD83D\uDD17', titleKey: 'sourceInferred' },
|
|
35
|
+
user: { icon: '\u270F\uFE0F', titleKey: 'sourcePrevious' },
|
|
28
36
|
default: null,
|
|
29
37
|
}
|
|
30
38
|
|
|
31
39
|
export const FormFieldRenderer: Component<FormFieldRendererProps> = (props) => {
|
|
40
|
+
const strings = useMCPUIStrings()
|
|
41
|
+
|
|
32
42
|
// Conditional visibility based on showWhen
|
|
33
43
|
const { isVisible } = useConditionalField({
|
|
34
44
|
condition: props.field.showWhen,
|
|
@@ -76,14 +86,14 @@ export const FormFieldRenderer: Component<FormFieldRendererProps> = (props) => {
|
|
|
76
86
|
class={`block text-sm font-medium ${isUnsupported() ? 'text-gray-400 dark:text-gray-500' : isMuted() ? 'text-gray-400 dark:text-gray-500' : 'text-gray-700 dark:text-gray-300'}`}
|
|
77
87
|
>
|
|
78
88
|
<Show when={sourceBadge()}>
|
|
79
|
-
<span class="mr-1" title={sourceBadge()!.
|
|
89
|
+
<span class="mr-1" title={strings[sourceBadge()!.titleKey]}>{sourceBadge()!.icon}</span>
|
|
80
90
|
</Show>
|
|
81
91
|
{props.field.label}
|
|
82
92
|
<Show when={props.field.required || status() === 'required'}>
|
|
83
93
|
<span class="text-red-500 ml-1" aria-hidden="true">*</span>
|
|
84
94
|
</Show>
|
|
85
95
|
<Show when={isUnsupported()}>
|
|
86
|
-
<span class="ml-2 text-[10px] font-medium bg-orange-100 dark:bg-orange-900/30 text-orange-600 dark:text-orange-400 px-1.5 py-0.5 rounded">
|
|
96
|
+
<span class="ml-2 text-[10px] font-medium bg-orange-100 dark:bg-orange-900/30 text-orange-600 dark:text-orange-400 px-1.5 py-0.5 rounded">{strings.fieldNotSupported}</span>
|
|
87
97
|
</Show>
|
|
88
98
|
<Show when={status() === 'unknown'}>
|
|
89
99
|
<span class="ml-2 text-[10px] font-medium bg-yellow-100 dark:bg-yellow-900/30 text-yellow-600 dark:text-yellow-400 px-1.5 py-0.5 rounded">?</span>
|
|
@@ -316,7 +326,7 @@ export const FormFieldRenderer: Component<FormFieldRendererProps> = (props) => {
|
|
|
316
326
|
<Match when={props.field.type === 'fieldset'}>
|
|
317
327
|
<fieldset class="border border-gray-200 dark:border-gray-600 rounded-lg p-3">
|
|
318
328
|
<legend class="text-xs font-medium text-gray-500 dark:text-gray-400 px-1">{props.field.label}</legend>
|
|
319
|
-
<p class="text-xs text-gray-400">{props.field.helpText ||
|
|
329
|
+
<p class="text-xs text-gray-400">{props.field.helpText || strings.fieldGroupContainer}</p>
|
|
320
330
|
</fieldset>
|
|
321
331
|
</Match>
|
|
322
332
|
|
|
@@ -332,7 +342,9 @@ export const FormFieldRenderer: Component<FormFieldRendererProps> = (props) => {
|
|
|
332
342
|
disabled={props.disabled}
|
|
333
343
|
class={baseInputClass()}
|
|
334
344
|
/>
|
|
335
|
-
<p class="text-xs text-amber-500 mt-0.5">
|
|
345
|
+
<p class="text-xs text-amber-500 mt-0.5">
|
|
346
|
+
{formatMCPUIString(strings.fieldUnknownType, { type: props.field.type })}
|
|
347
|
+
</p>
|
|
336
348
|
</Match>
|
|
337
349
|
</Switch>
|
|
338
350
|
|
|
@@ -376,6 +388,7 @@ const MultiSelectField: Component<{
|
|
|
376
388
|
disabled?: boolean
|
|
377
389
|
baseClass: string
|
|
378
390
|
}> = (props) => {
|
|
391
|
+
const strings = useMCPUIStrings()
|
|
379
392
|
const [open, setOpen] = createSignal(false)
|
|
380
393
|
const [filter, setFilter] = createSignal('')
|
|
381
394
|
|
|
@@ -416,7 +429,7 @@ const MultiSelectField: Component<{
|
|
|
416
429
|
type="button"
|
|
417
430
|
onClick={() => removeChip(val)}
|
|
418
431
|
class="hover:text-blue-900 dark:hover:text-blue-100"
|
|
419
|
-
aria-label={
|
|
432
|
+
aria-label={formatMCPUIString(strings.removeItem, { name: getLabel(val) })}
|
|
420
433
|
>
|
|
421
434
|
×
|
|
422
435
|
</button>
|
|
@@ -435,8 +448,8 @@ const MultiSelectField: Component<{
|
|
|
435
448
|
>
|
|
436
449
|
<span class={props.value.length ? 'text-gray-900 dark:text-white' : 'text-gray-400'}>
|
|
437
450
|
{props.value.length
|
|
438
|
-
?
|
|
439
|
-
: props.field.placeholder ||
|
|
451
|
+
? formatMCPUIString(strings.fieldSelectedCount, { count: props.value.length })
|
|
452
|
+
: props.field.placeholder || strings.fieldSelectPlaceholder}
|
|
440
453
|
</span>
|
|
441
454
|
<svg class="w-4 h-4 text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
442
455
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
|
|
@@ -456,7 +469,7 @@ const MultiSelectField: Component<{
|
|
|
456
469
|
type="text"
|
|
457
470
|
value={filter()}
|
|
458
471
|
onInput={(e) => setFilter(e.currentTarget.value)}
|
|
459
|
-
placeholder=
|
|
472
|
+
placeholder={strings.filterPlaceholder}
|
|
460
473
|
class="w-full px-2 py-1 text-sm border border-gray-200 dark:border-gray-600 rounded bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:border-blue-400 outline-none"
|
|
461
474
|
autofocus
|
|
462
475
|
/>
|
|
@@ -478,7 +491,7 @@ const MultiSelectField: Component<{
|
|
|
478
491
|
)}
|
|
479
492
|
</For>
|
|
480
493
|
<Show when={filteredOptions().length === 0}>
|
|
481
|
-
<p class="px-3 py-2 text-sm text-gray-400">
|
|
494
|
+
<p class="px-3 py-2 text-sm text-gray-400">{strings.fieldNoMatches}</p>
|
|
482
495
|
</Show>
|
|
483
496
|
</div>
|
|
484
497
|
</div>
|
|
@@ -496,6 +509,7 @@ const AutocompleteField: Component<{
|
|
|
496
509
|
disabled?: boolean
|
|
497
510
|
baseClass: string
|
|
498
511
|
}> = (props) => {
|
|
512
|
+
const strings = useMCPUIStrings()
|
|
499
513
|
const [query, setQuery] = createSignal('')
|
|
500
514
|
const [suggestions, setSuggestions] = createSignal<Array<{ label: string; value: string }>>([])
|
|
501
515
|
const [isOpen, setIsOpen] = createSignal(false)
|
|
@@ -669,7 +683,7 @@ const AutocompleteField: Component<{
|
|
|
669
683
|
<Show when={resolving()}>
|
|
670
684
|
<div class="flex items-center gap-1 mb-1 text-xs text-gray-400">
|
|
671
685
|
<span class="animate-spin h-3 w-3 border border-gray-400 border-t-transparent rounded-full" />
|
|
672
|
-
|
|
686
|
+
{strings.fieldResolving}
|
|
673
687
|
</div>
|
|
674
688
|
</Show>
|
|
675
689
|
|
|
@@ -684,7 +698,7 @@ const AutocompleteField: Component<{
|
|
|
684
698
|
type="button"
|
|
685
699
|
onClick={() => removeChip(val)}
|
|
686
700
|
class="hover:text-blue-900 dark:hover:text-blue-100"
|
|
687
|
-
aria-label={
|
|
701
|
+
aria-label={formatMCPUIString(strings.removeItem, { name: getLabel(val) })}
|
|
688
702
|
>
|
|
689
703
|
×
|
|
690
704
|
</button>
|
|
@@ -701,7 +715,7 @@ const AutocompleteField: Component<{
|
|
|
701
715
|
onFocus={() => { if (suggestions().length) setIsOpen(true) }}
|
|
702
716
|
onBlur={handleBlur}
|
|
703
717
|
placeholder={isMultiple() && selectedValues().length
|
|
704
|
-
?
|
|
718
|
+
? strings.fieldAddMorePlaceholder
|
|
705
719
|
: props.field.placeholder}
|
|
706
720
|
disabled={props.disabled || resolving()}
|
|
707
721
|
class={props.baseClass}
|
|
@@ -746,6 +760,7 @@ const TagsField: Component<{
|
|
|
746
760
|
disabled?: boolean
|
|
747
761
|
baseClass: string
|
|
748
762
|
}> = (props) => {
|
|
763
|
+
const strings = useMCPUIStrings()
|
|
749
764
|
const [input, setInput] = createSignal('')
|
|
750
765
|
|
|
751
766
|
const addTag = () => {
|
|
@@ -778,7 +793,7 @@ const TagsField: Component<{
|
|
|
778
793
|
{(tag) => (
|
|
779
794
|
<span class="inline-flex items-center gap-1 px-2 py-0.5 text-xs font-medium bg-blue-100 dark:bg-blue-900/30 text-blue-700 dark:text-blue-300 rounded-full">
|
|
780
795
|
{tag}
|
|
781
|
-
<button type="button" onClick={() => removeTag(tag)} class="hover:text-blue-900 dark:hover:text-blue-100" aria-label={
|
|
796
|
+
<button type="button" onClick={() => removeTag(tag)} class="hover:text-blue-900 dark:hover:text-blue-100" aria-label={formatMCPUIString(strings.removeItem, { name: tag })}>×</button>
|
|
782
797
|
</span>
|
|
783
798
|
)}
|
|
784
799
|
</For>
|
|
@@ -790,7 +805,7 @@ const TagsField: Component<{
|
|
|
790
805
|
onInput={(e) => setInput(e.currentTarget.value)}
|
|
791
806
|
onKeyDown={handleKeyDown}
|
|
792
807
|
onBlur={addTag}
|
|
793
|
-
placeholder={props.placeholder ||
|
|
808
|
+
placeholder={props.placeholder || strings.fieldTagsPlaceholder}
|
|
794
809
|
disabled={props.disabled}
|
|
795
810
|
class={props.baseClass}
|
|
796
811
|
/>
|
|
@@ -13,6 +13,7 @@ import { validateFormData } from '../services/validation'
|
|
|
13
13
|
import { evaluateCondition } from '../hooks/useConditionalField'
|
|
14
14
|
import { useFormPersistence } from '../hooks/useFormPersistence'
|
|
15
15
|
import { useTelemetry } from '../context/MCPUITelemetryContext'
|
|
16
|
+
import { formatMCPUIString, useMCPUIStrings } from '../context/MCPUIStringsContext'
|
|
16
17
|
|
|
17
18
|
export interface FormRendererProps {
|
|
18
19
|
component: UIComponent
|
|
@@ -38,6 +39,26 @@ function getFieldDefault(type: FormFieldParams['type']): any {
|
|
|
38
39
|
}
|
|
39
40
|
|
|
40
41
|
export const FormRenderer: Component<FormRendererProps> = (props) => {
|
|
42
|
+
const strings = useMCPUIStrings()
|
|
43
|
+
/**
|
|
44
|
+
* The end-user validation wording, read from the chrome strings and
|
|
45
|
+
* injected into the pure validators (which cannot reach the context).
|
|
46
|
+
*/
|
|
47
|
+
const validationMessages = () => ({
|
|
48
|
+
required: strings.fieldRequired,
|
|
49
|
+
mustBeChecked: strings.fieldMustBeChecked,
|
|
50
|
+
minLength: strings.fieldMinLength,
|
|
51
|
+
maxLength: strings.fieldMaxLength,
|
|
52
|
+
invalidPattern: strings.fieldInvalidPattern,
|
|
53
|
+
invalidEmail: strings.fieldInvalidEmail,
|
|
54
|
+
invalidNumber: strings.fieldInvalidNumber,
|
|
55
|
+
minValue: strings.fieldMinValue,
|
|
56
|
+
maxValue: strings.fieldMaxValue,
|
|
57
|
+
minDate: strings.fieldMinDate,
|
|
58
|
+
maxDate: strings.fieldMaxDate,
|
|
59
|
+
invalidOption: strings.fieldInvalidOption,
|
|
60
|
+
invalidFormat: strings.fieldInvalidFormat,
|
|
61
|
+
})
|
|
41
62
|
const params = () => props.component.params as FormComponentParams
|
|
42
63
|
const [formData, setFormData] = createSignal<Record<string, any>>({})
|
|
43
64
|
const [errors, setErrors] = createSignal<Record<string, string>>({})
|
|
@@ -177,7 +198,7 @@ export const FormRenderer: Component<FormRendererProps> = (props) => {
|
|
|
177
198
|
}
|
|
178
199
|
|
|
179
200
|
// Validate only visible fields
|
|
180
|
-
const validationResult = validateFormData(visibleFormData, visibleFields)
|
|
201
|
+
const validationResult = validateFormData(visibleFormData, visibleFields, validationMessages())
|
|
181
202
|
if (!validationResult.valid) {
|
|
182
203
|
setErrors(validationResult.errors)
|
|
183
204
|
setIsSubmitting(false)
|
|
@@ -193,13 +214,14 @@ export const FormRenderer: Component<FormRendererProps> = (props) => {
|
|
|
193
214
|
formData: visibleFormData,
|
|
194
215
|
})
|
|
195
216
|
if (!result.success) {
|
|
196
|
-
setErrors({ _form: result.error ||
|
|
217
|
+
setErrors({ _form: result.error || strings.formSubmissionFailed })
|
|
197
218
|
setIsSubmitting(false)
|
|
198
|
-
props.onError?.({ _form: result.error ||
|
|
219
|
+
props.onError?.({ _form: result.error || strings.formSubmissionFailed })
|
|
199
220
|
return
|
|
200
221
|
}
|
|
201
222
|
} catch (error) {
|
|
202
|
-
const errorMessage =
|
|
223
|
+
const errorMessage =
|
|
224
|
+
error instanceof Error ? error.message : strings.formSubmissionFailed
|
|
203
225
|
setErrors({ _form: errorMessage })
|
|
204
226
|
setIsSubmitting(false)
|
|
205
227
|
props.onError?.({ _form: errorMessage })
|
|
@@ -255,7 +277,10 @@ export const FormRenderer: Component<FormRendererProps> = (props) => {
|
|
|
255
277
|
const total = params().fields.length
|
|
256
278
|
return (
|
|
257
279
|
<p class="text-xs text-gray-500 dark:text-gray-400 mb-3">
|
|
258
|
-
{
|
|
280
|
+
{formatMCPUIString(
|
|
281
|
+
prefilled === 1 ? strings.formPrefilledOne : strings.formPrefilledMany,
|
|
282
|
+
{ count: prefilled, total }
|
|
283
|
+
)}
|
|
259
284
|
</p>
|
|
260
285
|
)
|
|
261
286
|
})()}
|
|
@@ -287,14 +312,17 @@ export const FormRenderer: Component<FormRendererProps> = (props) => {
|
|
|
287
312
|
<Show when={countdown() != null}>
|
|
288
313
|
<div class="mt-4 flex items-center gap-3 p-3 bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800 rounded-md">
|
|
289
314
|
<span class="text-sm text-blue-700 dark:text-blue-300">
|
|
290
|
-
{
|
|
315
|
+
{formatMCPUIString(strings.formSubmitCountdown, {
|
|
316
|
+
label: params().submitLabel || strings.submit,
|
|
317
|
+
seconds: countdown()!,
|
|
318
|
+
})}
|
|
291
319
|
</span>
|
|
292
320
|
<button
|
|
293
321
|
type="button"
|
|
294
322
|
onClick={() => { cancelCountdown(); setUserInteracted(true) }}
|
|
295
323
|
class="text-sm text-blue-600 dark:text-blue-400 underline hover:text-blue-800 dark:hover:text-blue-200"
|
|
296
324
|
>
|
|
297
|
-
|
|
325
|
+
{strings.cancel}
|
|
298
326
|
</button>
|
|
299
327
|
</div>
|
|
300
328
|
</Show>
|
|
@@ -308,10 +336,10 @@ export const FormRenderer: Component<FormRendererProps> = (props) => {
|
|
|
308
336
|
{isSubmitting() ? (
|
|
309
337
|
<span class="flex items-center gap-2">
|
|
310
338
|
<span class="animate-spin h-4 w-4 border-2 border-white border-t-transparent rounded-full" />
|
|
311
|
-
|
|
339
|
+
{strings.formSubmitting}
|
|
312
340
|
</span>
|
|
313
341
|
) : (
|
|
314
|
-
params().submitLabel ||
|
|
342
|
+
params().submitLabel || strings.submit
|
|
315
343
|
)}
|
|
316
344
|
</button>
|
|
317
345
|
<Show when={params().showReset}>
|
|
@@ -321,7 +349,7 @@ export const FormRenderer: Component<FormRendererProps> = (props) => {
|
|
|
321
349
|
disabled={isSubmitting()}
|
|
322
350
|
class="px-4 py-2 border border-gray-300 dark:border-gray-600 text-gray-700 dark:text-gray-300 rounded-md hover:bg-gray-50 dark:hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-gray-500 focus:ring-offset-2 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
|
|
323
351
|
>
|
|
324
|
-
|
|
352
|
+
{strings.formReset}
|
|
325
353
|
</button>
|
|
326
354
|
</Show>
|
|
327
355
|
</div>
|
|
@@ -15,6 +15,7 @@ import { isServer } from 'solid-js/web'
|
|
|
15
15
|
import { createLogger } from '../utils/logger'
|
|
16
16
|
import type { RendererError, ComponentType } from '../types'
|
|
17
17
|
import { useTelemetry } from '../context/MCPUITelemetryContext'
|
|
18
|
+
import { formatMCPUIString, useMCPUIStrings } from '../context/MCPUIStringsContext'
|
|
18
19
|
|
|
19
20
|
const logger = createLogger('generative-ui')
|
|
20
21
|
|
|
@@ -63,6 +64,7 @@ function DefaultErrorFallback(props: {
|
|
|
63
64
|
allowRetry?: boolean
|
|
64
65
|
onRetry?: () => void
|
|
65
66
|
}) {
|
|
67
|
+
const strings = useMCPUIStrings()
|
|
66
68
|
return (
|
|
67
69
|
<div class="w-full h-full bg-yellow-50 dark:bg-yellow-900/20 border border-yellow-200 dark:border-yellow-800 rounded-lg p-4">
|
|
68
70
|
<div class="flex items-start gap-3">
|
|
@@ -83,10 +85,13 @@ function DefaultErrorFallback(props: {
|
|
|
83
85
|
</div>
|
|
84
86
|
<div class="flex-1 min-w-0">
|
|
85
87
|
<p class="text-sm font-medium text-yellow-900 dark:text-yellow-100">
|
|
86
|
-
|
|
88
|
+
{strings.errorBoundaryTitle}
|
|
87
89
|
</p>
|
|
88
90
|
<p class="text-xs text-yellow-700 dark:text-yellow-300 mt-1">
|
|
89
|
-
|
|
91
|
+
{formatMCPUIString(strings.errorBoundaryMeta, {
|
|
92
|
+
type: props.componentType || strings.unknown,
|
|
93
|
+
id: props.componentId?.slice(0, 8) || strings.unknown,
|
|
94
|
+
})}
|
|
90
95
|
</p>
|
|
91
96
|
<Show when={import.meta.env.DEV}>
|
|
92
97
|
<p class="text-xs text-yellow-600 dark:text-yellow-400 mt-2 font-mono">
|
|
@@ -98,7 +103,7 @@ function DefaultErrorFallback(props: {
|
|
|
98
103
|
onClick={props.onRetry}
|
|
99
104
|
class="mt-3 text-xs font-medium text-yellow-800 dark:text-yellow-200 hover:text-yellow-900 dark:hover:text-yellow-100 underline"
|
|
100
105
|
>
|
|
101
|
-
|
|
106
|
+
{strings.errorBoundaryRetry}
|
|
102
107
|
</button>
|
|
103
108
|
</Show>
|
|
104
109
|
</div>
|