@farcaster/snap 1.15.3 → 1.16.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/dist/constants.d.ts +8 -0
- package/dist/constants.js +9 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/react/components/action-button.d.ts +2 -1
- package/dist/react/components/action-button.js +16 -3
- package/dist/react/components/badge.js +2 -3
- package/dist/react/index.d.ts +8 -1
- package/dist/react/index.js +9 -228
- package/dist/react/snap-view-core.d.ts +11 -0
- package/dist/react/snap-view-core.js +224 -0
- package/dist/react/v1/snap-view.d.ts +14 -0
- package/dist/react/v1/snap-view.js +9 -0
- package/dist/react/v2/snap-view.d.ts +21 -0
- package/dist/react/v2/snap-view.js +76 -0
- package/dist/react-native/components/snap-action-button.d.ts +1 -1
- package/dist/react-native/components/snap-action-button.js +19 -2
- package/dist/react-native/components/snap-badge.js +3 -3
- package/dist/react-native/index.d.ts +15 -43
- package/dist/react-native/index.js +10 -164
- package/dist/react-native/snap-view-core.d.ts +11 -0
- package/dist/react-native/snap-view-core.js +153 -0
- package/dist/react-native/types.d.ts +41 -0
- package/dist/react-native/types.js +1 -0
- package/dist/react-native/v1/snap-view.d.ts +22 -0
- package/dist/react-native/v1/snap-view.js +31 -0
- package/dist/react-native/v2/snap-view.d.ts +31 -0
- package/dist/react-native/v2/snap-view.js +101 -0
- package/dist/schemas.d.ts +15 -9
- package/dist/schemas.js +7 -8
- package/dist/server/parseRequest.d.ts +7 -0
- package/dist/server/parseRequest.js +27 -0
- package/dist/ui/catalog.d.ts +1 -0
- package/dist/ui/catalog.js +5 -2
- package/dist/ui/schema.js +1 -1
- package/dist/validator.d.ts +3 -2
- package/dist/validator.js +193 -2
- package/llms.txt +9 -0
- package/package.json +1 -1
- package/src/constants.ts +11 -1
- package/src/index.ts +8 -0
- package/src/react/accent-context.tsx +1 -1
- package/src/react/components/action-button.tsx +25 -3
- package/src/react/components/badge.tsx +2 -3
- package/src/react/index.tsx +36 -327
- package/src/react/snap-view-core.tsx +340 -0
- package/src/react/v1/snap-view.tsx +50 -0
- package/src/react/v2/snap-view.tsx +168 -0
- package/src/react-native/components/snap-action-button.tsx +26 -4
- package/src/react-native/components/snap-badge.tsx +3 -3
- package/src/react-native/index.tsx +47 -263
- package/src/react-native/snap-view-core.tsx +209 -0
- package/src/react-native/types.ts +37 -0
- package/src/react-native/v1/snap-view.tsx +108 -0
- package/src/react-native/v2/snap-view.tsx +239 -0
- package/src/schemas.ts +9 -10
- package/src/server/parseRequest.ts +39 -0
- package/src/ui/catalog.ts +5 -2
- package/src/ui/schema.ts +1 -1
- package/src/validator.ts +240 -2
|
@@ -1,18 +1,34 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import { useState } from "react";
|
|
4
|
+
import { ExternalLink } from "lucide-react";
|
|
4
5
|
import { Button } from "@neynar/ui/button";
|
|
5
6
|
import { cn } from "@neynar/ui/utils";
|
|
6
7
|
import { useSnapColors } from "../hooks/use-snap-colors";
|
|
7
8
|
import { ICON_MAP } from "./icon";
|
|
8
9
|
|
|
10
|
+
function isExternalLinkAction(
|
|
11
|
+
on: Record<string, unknown> | undefined,
|
|
12
|
+
): boolean {
|
|
13
|
+
if (!on) return false;
|
|
14
|
+
const press = on.press as
|
|
15
|
+
| { action?: string; params?: Record<string, unknown> }
|
|
16
|
+
| undefined;
|
|
17
|
+
if (!press || press.action !== "open_url") return false;
|
|
18
|
+
return press.params?.isSnap !== true;
|
|
19
|
+
}
|
|
20
|
+
|
|
9
21
|
export function SnapActionButton({
|
|
10
|
-
element
|
|
22
|
+
element,
|
|
11
23
|
emit,
|
|
12
24
|
}: {
|
|
13
|
-
element: {
|
|
25
|
+
element: {
|
|
26
|
+
props: Record<string, unknown>;
|
|
27
|
+
on?: Record<string, unknown>;
|
|
28
|
+
};
|
|
14
29
|
emit: (name: string) => void;
|
|
15
30
|
}) {
|
|
31
|
+
const { props } = element;
|
|
16
32
|
const label = String(props.label ?? "Action");
|
|
17
33
|
const variant = String(props.variant ?? "secondary");
|
|
18
34
|
const isPrimary = variant === "primary";
|
|
@@ -21,6 +37,7 @@ export function SnapActionButton({
|
|
|
21
37
|
const [hovered, setHovered] = useState(false);
|
|
22
38
|
|
|
23
39
|
const Icon = iconName ? ICON_MAP[iconName] : undefined;
|
|
40
|
+
const showExternalIcon = isExternalLinkAction(element.on);
|
|
24
41
|
|
|
25
42
|
const style = isPrimary
|
|
26
43
|
? {
|
|
@@ -29,7 +46,9 @@ export function SnapActionButton({
|
|
|
29
46
|
borderColor: "transparent",
|
|
30
47
|
}
|
|
31
48
|
: {
|
|
32
|
-
backgroundColor: hovered
|
|
49
|
+
backgroundColor: hovered
|
|
50
|
+
? `color-mix(in srgb, ${colors.accent} 15%, transparent)`
|
|
51
|
+
: colors.muted,
|
|
33
52
|
color: colors.text,
|
|
34
53
|
borderColor: "transparent",
|
|
35
54
|
};
|
|
@@ -47,6 +66,9 @@ export function SnapActionButton({
|
|
|
47
66
|
>
|
|
48
67
|
{Icon && <Icon size={16} />}
|
|
49
68
|
{label}
|
|
69
|
+
{showExternalIcon && (
|
|
70
|
+
<ExternalLink size={14} style={{ opacity: 0.6 }} />
|
|
71
|
+
)}
|
|
50
72
|
</Button>
|
|
51
73
|
</div>
|
|
52
74
|
);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import { Badge } from "@neynar/ui/badge";
|
|
4
|
-
import { useSnapColors
|
|
4
|
+
import { useSnapColors } from "../hooks/use-snap-colors";
|
|
5
5
|
import { ICON_MAP } from "./icon";
|
|
6
6
|
|
|
7
7
|
export function SnapBadge({
|
|
@@ -16,14 +16,13 @@ export function SnapBadge({
|
|
|
16
16
|
const colors = useSnapColors();
|
|
17
17
|
|
|
18
18
|
const badgeColor = colors.colorHex(color);
|
|
19
|
-
const badgeFg = variant === "default" ? pickForegroundForBg(badgeColor) : badgeColor;
|
|
20
19
|
|
|
21
20
|
const Icon = iconName ? ICON_MAP[iconName] : undefined;
|
|
22
21
|
|
|
23
22
|
const style =
|
|
24
23
|
variant === "outline"
|
|
25
24
|
? { borderColor: badgeColor, color: badgeColor, backgroundColor: "transparent" }
|
|
26
|
-
: { backgroundColor: badgeColor
|
|
25
|
+
: { backgroundColor: `${badgeColor}20`, color: badgeColor, borderColor: "transparent" };
|
|
27
26
|
|
|
28
27
|
return (
|
|
29
28
|
<Badge variant={variant} className="gap-1" style={style}>
|
package/src/react/index.tsx
CHANGED
|
@@ -1,19 +1,11 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import type { Spec } from "@json-render/core";
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
type CSSProperties,
|
|
11
|
-
useCallback,
|
|
12
|
-
useEffect,
|
|
13
|
-
useMemo,
|
|
14
|
-
useRef,
|
|
15
|
-
useState,
|
|
16
|
-
} from "react";
|
|
4
|
+
import type { ReactNode } from "react";
|
|
5
|
+
import type { ValidationResult } from "../validator.js";
|
|
6
|
+
import { SPEC_VERSION_2 } from "../constants";
|
|
7
|
+
import { SnapCardV1 } from "./v1/snap-view";
|
|
8
|
+
import { SnapCardV2 } from "./v2/snap-view";
|
|
17
9
|
|
|
18
10
|
// ─── Public types ──────────────────────────────────────
|
|
19
11
|
|
|
@@ -53,333 +45,50 @@ export type SnapActionHandlers = {
|
|
|
53
45
|
swap_token: (params: { sellToken?: string; buyToken?: string }) => void;
|
|
54
46
|
};
|
|
55
47
|
|
|
56
|
-
// ───
|
|
48
|
+
// ─── SnapCard ────────────────────────────────────────
|
|
57
49
|
|
|
58
|
-
function
|
|
59
|
-
model: Record<string, unknown>,
|
|
60
|
-
changes: { path: string; value: unknown }[] | Record<string, unknown>,
|
|
61
|
-
): void {
|
|
62
|
-
const entries = Array.isArray(changes)
|
|
63
|
-
? changes.map((c) => [c.path, c.value] as const)
|
|
64
|
-
: Object.entries(changes);
|
|
65
|
-
for (const [path, value] of entries) {
|
|
66
|
-
const trimmed = path.startsWith("/") ? path : `/${path}`;
|
|
67
|
-
const parts = trimmed.split("/").filter(Boolean);
|
|
68
|
-
if (parts.length < 2) continue;
|
|
69
|
-
const [top, ...rest] = parts;
|
|
70
|
-
if (top === "inputs") {
|
|
71
|
-
if (typeof model.inputs !== "object" || model.inputs === null) {
|
|
72
|
-
model.inputs = {};
|
|
73
|
-
}
|
|
74
|
-
const inputs = model.inputs as Record<string, unknown>;
|
|
75
|
-
if (rest.length === 1) {
|
|
76
|
-
inputs[rest[0]!] = value;
|
|
77
|
-
}
|
|
78
|
-
continue;
|
|
79
|
-
}
|
|
80
|
-
if (top === "theme") {
|
|
81
|
-
if (typeof model.theme !== "object" || model.theme === null) {
|
|
82
|
-
model.theme = {};
|
|
83
|
-
}
|
|
84
|
-
const theme = model.theme as Record<string, unknown>;
|
|
85
|
-
if (rest.length === 1) {
|
|
86
|
-
theme[rest[0]!] = value;
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
const CONFETTI_COLORS = [
|
|
93
|
-
"#8B5CF6",
|
|
94
|
-
"#EC4899",
|
|
95
|
-
"#3B82F6",
|
|
96
|
-
"#10B981",
|
|
97
|
-
"#F59E0B",
|
|
98
|
-
"#EF4444",
|
|
99
|
-
"#06B6D4",
|
|
100
|
-
];
|
|
101
|
-
|
|
102
|
-
function ConfettiOverlay() {
|
|
103
|
-
const pieces = useMemo(
|
|
104
|
-
() =>
|
|
105
|
-
Array.from({ length: 80 }, (_, i) => ({
|
|
106
|
-
id: i,
|
|
107
|
-
left: Math.random() * 100,
|
|
108
|
-
delay: Math.random() * 1.2,
|
|
109
|
-
duration: 2.5 + Math.random() * 2,
|
|
110
|
-
color:
|
|
111
|
-
CONFETTI_COLORS[Math.floor(Math.random() * CONFETTI_COLORS.length)],
|
|
112
|
-
size: 6 + Math.random() * 8,
|
|
113
|
-
rotation: Math.random() * 360,
|
|
114
|
-
})),
|
|
115
|
-
[],
|
|
116
|
-
);
|
|
117
|
-
|
|
118
|
-
return (
|
|
119
|
-
<div
|
|
120
|
-
style={{
|
|
121
|
-
position: "absolute",
|
|
122
|
-
inset: 0,
|
|
123
|
-
overflow: "hidden",
|
|
124
|
-
pointerEvents: "none",
|
|
125
|
-
zIndex: 20,
|
|
126
|
-
}}
|
|
127
|
-
>
|
|
128
|
-
{pieces.map(({ id, left, delay, duration, color, size, rotation }) => (
|
|
129
|
-
<div
|
|
130
|
-
key={id}
|
|
131
|
-
style={{
|
|
132
|
-
position: "absolute",
|
|
133
|
-
left: `${left}%`,
|
|
134
|
-
top: -20,
|
|
135
|
-
width: size,
|
|
136
|
-
height: size * 0.6,
|
|
137
|
-
backgroundColor: color,
|
|
138
|
-
borderRadius: 2,
|
|
139
|
-
transform: `rotate(${rotation}deg)`,
|
|
140
|
-
animation: `confettiFall ${duration}s ease-in ${delay}s forwards`,
|
|
141
|
-
}}
|
|
142
|
-
/>
|
|
143
|
-
))}
|
|
144
|
-
<style>{`@keyframes confettiFall{0%{top:-20px;opacity:1;transform:rotate(0deg) translateX(0)}50%{opacity:1}100%{top:110%;opacity:0;transform:rotate(720deg) translateX(${
|
|
145
|
-
Math.random() > 0.5 ? "" : "-"
|
|
146
|
-
}40px)}}`}</style>
|
|
147
|
-
</div>
|
|
148
|
-
);
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
function SnapLoadingOverlay({
|
|
152
|
-
appearance,
|
|
153
|
-
accentHex,
|
|
154
|
-
active,
|
|
155
|
-
}: {
|
|
156
|
-
appearance: "light" | "dark";
|
|
157
|
-
accentHex: string;
|
|
158
|
-
active: boolean;
|
|
159
|
-
}) {
|
|
160
|
-
const isDark = appearance === "dark";
|
|
161
|
-
const tint = isDark ? "rgba(0, 0, 0, 0.1)" : "rgba(255, 255, 255, 0.2)";
|
|
162
|
-
const trackColor = isDark
|
|
163
|
-
? "rgba(255, 255, 255, 0.12)"
|
|
164
|
-
: "rgba(15, 23, 42, 0.1)";
|
|
165
|
-
|
|
166
|
-
return (
|
|
167
|
-
<div
|
|
168
|
-
style={{
|
|
169
|
-
position: "absolute",
|
|
170
|
-
inset: 0,
|
|
171
|
-
display: "flex",
|
|
172
|
-
alignItems: "center",
|
|
173
|
-
justifyContent: "center",
|
|
174
|
-
zIndex: 10,
|
|
175
|
-
background: tint,
|
|
176
|
-
backdropFilter: active ? "blur(10px) saturate(1.05)" : "none",
|
|
177
|
-
WebkitBackdropFilter: active
|
|
178
|
-
? "blur(10px) saturate(1.05)"
|
|
179
|
-
: "none",
|
|
180
|
-
opacity: active ? 1 : 0,
|
|
181
|
-
pointerEvents: active ? "auto" : "none",
|
|
182
|
-
transition: "opacity 0.28s ease, backdrop-filter 0.28s ease",
|
|
183
|
-
}}
|
|
184
|
-
aria-hidden={!active}
|
|
185
|
-
aria-busy={active ? true : undefined}
|
|
186
|
-
aria-live={active ? "polite" : undefined}
|
|
187
|
-
aria-label={active ? "Loading" : undefined}
|
|
188
|
-
>
|
|
189
|
-
<div
|
|
190
|
-
data-snap-loading-spinner
|
|
191
|
-
style={{
|
|
192
|
-
width: 30,
|
|
193
|
-
height: 30,
|
|
194
|
-
borderRadius: "50%",
|
|
195
|
-
border: `2.5px solid ${trackColor}`,
|
|
196
|
-
borderTopColor: accentHex,
|
|
197
|
-
opacity: 0.88,
|
|
198
|
-
animation: "snapViewSpin 0.75s linear infinite",
|
|
199
|
-
flexShrink: 0,
|
|
200
|
-
}}
|
|
201
|
-
/>
|
|
202
|
-
<style>{`
|
|
203
|
-
@keyframes snapViewSpin {
|
|
204
|
-
to { transform: rotate(360deg); }
|
|
205
|
-
}
|
|
206
|
-
@media (prefers-reduced-motion: reduce) {
|
|
207
|
-
[data-snap-loading-spinner] {
|
|
208
|
-
animation: none;
|
|
209
|
-
border-top-color: ${accentHex};
|
|
210
|
-
opacity: 0.75;
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
`}</style>
|
|
214
|
-
</div>
|
|
215
|
-
);
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
const PALETTE = [
|
|
219
|
-
"gray",
|
|
220
|
-
"blue",
|
|
221
|
-
"red",
|
|
222
|
-
"amber",
|
|
223
|
-
"green",
|
|
224
|
-
"teal",
|
|
225
|
-
"purple",
|
|
226
|
-
"pink",
|
|
227
|
-
] as const;
|
|
228
|
-
|
|
229
|
-
// ─── SnapView ──────────────────────────────────────────
|
|
230
|
-
|
|
231
|
-
export function SnapView({
|
|
50
|
+
export function SnapCard({
|
|
232
51
|
snap,
|
|
233
52
|
handlers,
|
|
234
53
|
loading = false,
|
|
235
54
|
appearance = "dark",
|
|
55
|
+
maxWidth = 480,
|
|
56
|
+
showOverflowWarning = false,
|
|
57
|
+
onValidationError,
|
|
58
|
+
validationErrorFallback,
|
|
236
59
|
}: {
|
|
237
60
|
snap: SnapPage;
|
|
238
61
|
handlers: SnapActionHandlers;
|
|
239
62
|
loading?: boolean;
|
|
240
63
|
appearance?: "light" | "dark";
|
|
64
|
+
maxWidth?: number;
|
|
65
|
+
/** When true, extends to 700px and shows a warning overlay below 500px. When false, clips at 500px. Only applies to v2 snaps. */
|
|
66
|
+
showOverflowWarning?: boolean;
|
|
67
|
+
onValidationError?: (result: ValidationResult) => void;
|
|
68
|
+
validationErrorFallback?: ReactNode;
|
|
241
69
|
}) {
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
stateRef.current = {
|
|
249
|
-
inputs: {
|
|
250
|
-
...((initialState.inputs ?? {}) as Record<string, unknown>),
|
|
251
|
-
},
|
|
252
|
-
theme: {
|
|
253
|
-
...((initialState.theme ?? {}) as Record<string, unknown>),
|
|
254
|
-
},
|
|
255
|
-
};
|
|
256
|
-
}, [initialState]);
|
|
257
|
-
|
|
258
|
-
useEffect(() => {
|
|
259
|
-
const result = snapJsonRenderCatalog.validate(spec);
|
|
260
|
-
if (!result.success) {
|
|
261
|
-
// eslint-disable-next-line no-console
|
|
262
|
-
console.warn("[SnapView] catalog validation issues:", result.error);
|
|
263
|
-
}
|
|
264
|
-
}, [spec]);
|
|
265
|
-
|
|
266
|
-
const [pageKey, setPageKey] = useState(0);
|
|
267
|
-
useEffect(() => {
|
|
268
|
-
setPageKey((k) => k + 1);
|
|
269
|
-
}, [spec]);
|
|
270
|
-
|
|
271
|
-
const showConfetti = snap.effects?.includes("confetti");
|
|
272
|
-
|
|
273
|
-
// Increment key each time a new snap with confetti arrives so the overlay
|
|
274
|
-
// unmounts/remounts and restarts its animation on every trigger.
|
|
275
|
-
const confettiEpochRef = useRef(0);
|
|
276
|
-
const lastConfettiSnapRef = useRef<typeof snap | null>(null);
|
|
277
|
-
if (showConfetti && snap !== lastConfettiSnapRef.current) {
|
|
278
|
-
confettiEpochRef.current++;
|
|
279
|
-
lastConfettiSnapRef.current = snap;
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
const accentName = snap.theme?.accent ?? "purple";
|
|
283
|
-
|
|
284
|
-
const accentHex = useMemo(
|
|
285
|
-
() => resolveSnapPaletteHex(accentName, appearance),
|
|
286
|
-
[accentName, appearance],
|
|
287
|
-
);
|
|
288
|
-
|
|
289
|
-
const previewSurfaceStyle = useMemo(() => {
|
|
290
|
-
const vars: Record<string, string> = {};
|
|
291
|
-
for (const c of PALETTE)
|
|
292
|
-
vars[`--snap-color-${c}`] = resolveSnapPaletteHex(c, appearance);
|
|
293
|
-
return {
|
|
294
|
-
...snapPreviewPrimaryCssProperties(accentName, appearance),
|
|
295
|
-
...vars,
|
|
296
|
-
} as CSSProperties;
|
|
297
|
-
}, [accentName, appearance]);
|
|
298
|
-
|
|
299
|
-
const handleAction = useCallback(
|
|
300
|
-
(name: unknown, params: unknown) => {
|
|
301
|
-
const inputs = (stateRef.current.inputs ?? {}) as Record<
|
|
302
|
-
string,
|
|
303
|
-
JsonValue
|
|
304
|
-
>;
|
|
305
|
-
const p = (params ?? {}) as Record<string, unknown>;
|
|
306
|
-
switch (name) {
|
|
307
|
-
case "submit":
|
|
308
|
-
handlers.submit(String(p.target ?? ""), inputs);
|
|
309
|
-
break;
|
|
310
|
-
case "open_url":
|
|
311
|
-
handlers.open_url(String(p.target ?? ""));
|
|
312
|
-
break;
|
|
313
|
-
case "open_mini_app":
|
|
314
|
-
handlers.open_mini_app(String(p.target ?? ""));
|
|
315
|
-
break;
|
|
316
|
-
case "view_cast":
|
|
317
|
-
handlers.view_cast({ hash: String(p.hash ?? "") });
|
|
318
|
-
break;
|
|
319
|
-
case "view_profile":
|
|
320
|
-
handlers.view_profile({ fid: Number(p.fid ?? 0) });
|
|
321
|
-
break;
|
|
322
|
-
case "compose_cast":
|
|
323
|
-
handlers.compose_cast({
|
|
324
|
-
text: p.text ? String(p.text) : undefined,
|
|
325
|
-
channelKey: p.channelKey ? String(p.channelKey) : undefined,
|
|
326
|
-
embeds: Array.isArray(p.embeds)
|
|
327
|
-
? (p.embeds as string[])
|
|
328
|
-
: undefined,
|
|
329
|
-
});
|
|
330
|
-
break;
|
|
331
|
-
case "view_token":
|
|
332
|
-
handlers.view_token({ token: String(p.token ?? "") });
|
|
333
|
-
break;
|
|
334
|
-
case "send_token":
|
|
335
|
-
handlers.send_token({
|
|
336
|
-
token: String(p.token ?? ""),
|
|
337
|
-
amount: p.amount ? String(p.amount) : undefined,
|
|
338
|
-
recipientFid: p.recipientFid ? Number(p.recipientFid) : undefined,
|
|
339
|
-
recipientAddress: p.recipientAddress
|
|
340
|
-
? String(p.recipientAddress)
|
|
341
|
-
: undefined,
|
|
342
|
-
});
|
|
343
|
-
break;
|
|
344
|
-
case "swap_token":
|
|
345
|
-
handlers.swap_token({
|
|
346
|
-
sellToken: p.sellToken ? String(p.sellToken) : undefined,
|
|
347
|
-
buyToken: p.buyToken ? String(p.buyToken) : undefined,
|
|
348
|
-
});
|
|
349
|
-
break;
|
|
350
|
-
default:
|
|
351
|
-
break;
|
|
352
|
-
}
|
|
353
|
-
},
|
|
354
|
-
[handlers],
|
|
355
|
-
);
|
|
356
|
-
|
|
357
|
-
return (
|
|
358
|
-
<div style={{ position: "relative", width: "100%" }}>
|
|
359
|
-
{showConfetti && <ConfettiOverlay key={`confetti-${confettiEpochRef.current}`} />}
|
|
360
|
-
<SnapLoadingOverlay
|
|
70
|
+
if (snap.version === SPEC_VERSION_2) {
|
|
71
|
+
return (
|
|
72
|
+
<SnapCardV2
|
|
73
|
+
snap={snap}
|
|
74
|
+
handlers={handlers}
|
|
75
|
+
loading={loading}
|
|
361
76
|
appearance={appearance}
|
|
362
|
-
|
|
363
|
-
|
|
77
|
+
maxWidth={maxWidth}
|
|
78
|
+
showOverflowWarning={showOverflowWarning}
|
|
79
|
+
onValidationError={onValidationError}
|
|
80
|
+
validationErrorFallback={validationErrorFallback}
|
|
364
81
|
/>
|
|
82
|
+
);
|
|
83
|
+
}
|
|
365
84
|
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
state={initialState}
|
|
375
|
-
loading={false}
|
|
376
|
-
onStateChange={(changes) => {
|
|
377
|
-
applyStatePaths(stateRef.current, changes);
|
|
378
|
-
}}
|
|
379
|
-
onAction={handleAction}
|
|
380
|
-
/>
|
|
381
|
-
</SnapPreviewAccentProvider>
|
|
382
|
-
</div>
|
|
383
|
-
</div>
|
|
85
|
+
return (
|
|
86
|
+
<SnapCardV1
|
|
87
|
+
snap={snap}
|
|
88
|
+
handlers={handlers}
|
|
89
|
+
loading={loading}
|
|
90
|
+
appearance={appearance}
|
|
91
|
+
maxWidth={maxWidth}
|
|
92
|
+
/>
|
|
384
93
|
);
|
|
385
94
|
}
|