@farcaster/snap 2.0.1 → 2.0.3
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/react/components/action-button.js +16 -13
- package/dist/react/index.d.ts +3 -1
- package/dist/react/index.js +3 -3
- package/dist/react/snap-view-core.d.ts +12 -1
- package/dist/react/snap-view-core.js +7 -4
- package/dist/react/v1/snap-view.d.ts +7 -2
- package/dist/react/v1/snap-view.js +9 -7
- package/dist/react/v2/snap-view.d.ts +6 -2
- package/dist/react/v2/snap-view.js +7 -5
- package/dist/react-native/index.d.ts +3 -1
- package/dist/react-native/index.js +3 -3
- package/dist/react-native/snap-view-core.d.ts +11 -1
- package/dist/react-native/snap-view-core.js +20 -8
- package/dist/react-native/v1/snap-view.d.ts +9 -3
- package/dist/react-native/v1/snap-view.js +15 -10
- package/dist/react-native/v2/snap-view.d.ts +8 -3
- package/dist/react-native/v2/snap-view.js +21 -12
- package/dist/ui/catalog.js +1 -1
- package/dist/validator.js +7 -2
- package/llms.txt +4 -2
- package/package.json +1 -1
- package/src/react/components/action-button.tsx +16 -13
- package/src/react/index.tsx +5 -0
- package/src/react/snap-view-core.tsx +20 -6
- package/src/react/v1/snap-view.tsx +25 -2
- package/src/react/v2/snap-view.tsx +23 -1
- package/src/react-native/index.tsx +5 -0
- package/src/react-native/snap-view-core.tsx +51 -14
- package/src/react-native/v1/snap-view.tsx +37 -5
- package/src/react-native/v2/snap-view.tsx +41 -4
- package/src/ui/catalog.ts +1 -1
- package/src/validator.ts +7 -2
|
@@ -24,18 +24,21 @@ export function SnapActionButton({ element, emit, }) {
|
|
|
24
24
|
const [hovered, setHovered] = useState(false);
|
|
25
25
|
const Icon = iconName ? ICON_MAP[iconName] : undefined;
|
|
26
26
|
const showExternalIcon = isExternalLinkAction(element.on);
|
|
27
|
-
const style =
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
:
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
27
|
+
const style = {
|
|
28
|
+
cursor: "pointer",
|
|
29
|
+
...(isPrimary
|
|
30
|
+
? {
|
|
31
|
+
backgroundColor: hovered ? colors.accentHover : colors.accent,
|
|
32
|
+
color: colors.accentFg,
|
|
33
|
+
borderColor: "transparent",
|
|
34
|
+
}
|
|
35
|
+
: {
|
|
36
|
+
backgroundColor: hovered
|
|
37
|
+
? `color-mix(in srgb, ${colors.accent} 15%, transparent)`
|
|
38
|
+
: colors.muted,
|
|
39
|
+
color: colors.text,
|
|
40
|
+
borderColor: "transparent",
|
|
41
|
+
}),
|
|
42
|
+
};
|
|
40
43
|
return (_jsx("div", { className: "w-full min-w-0 flex-1", children: _jsxs(Button, { type: "button", variant: isPrimary ? "default" : "secondary", className: cn("w-full gap-2"), style: style, onClick: () => emit("press"), onPointerEnter: () => setHovered(true), onPointerLeave: () => setHovered(false), children: [Icon && _jsx(Icon, { size: 16 }), label, showExternalIcon && (_jsx(ExternalLink, { size: 14, style: { opacity: 0.6 } }))] }) }));
|
|
41
44
|
}
|
package/dist/react/index.d.ts
CHANGED
|
@@ -42,7 +42,7 @@ export type SnapActionHandlers = {
|
|
|
42
42
|
buyToken?: string;
|
|
43
43
|
}) => void;
|
|
44
44
|
};
|
|
45
|
-
export declare function SnapCard({ snap, handlers, loading, appearance, maxWidth, showOverflowWarning, onValidationError, validationErrorFallback, actionError, plain, }: {
|
|
45
|
+
export declare function SnapCard({ snap, handlers, loading, appearance, maxWidth, showOverflowWarning, onValidationError, validationErrorFallback, actionError, plain, loadingOverlay, }: {
|
|
46
46
|
snap: SnapPage;
|
|
47
47
|
handlers: SnapActionHandlers;
|
|
48
48
|
loading?: boolean;
|
|
@@ -56,4 +56,6 @@ export declare function SnapCard({ snap, handlers, loading, appearance, maxWidth
|
|
|
56
56
|
actionError?: string | null;
|
|
57
57
|
/** When true, renders without card frame (no border, background, or padding). */
|
|
58
58
|
plain?: boolean;
|
|
59
|
+
/** Custom content rendered while `loading` is true. Pass `null` to render nothing. */
|
|
60
|
+
loadingOverlay?: ReactNode;
|
|
59
61
|
}): import("react/jsx-runtime").JSX.Element;
|
package/dist/react/index.js
CHANGED
|
@@ -4,9 +4,9 @@ import { SPEC_VERSION_2 } from "../constants.js";
|
|
|
4
4
|
import { SnapCardV1 } from "./v1/snap-view.js";
|
|
5
5
|
import { SnapCardV2 } from "./v2/snap-view.js";
|
|
6
6
|
// ─── SnapCard ────────────────────────────────────────
|
|
7
|
-
export function SnapCard({ snap, handlers, loading = false, appearance = "dark", maxWidth = 480, showOverflowWarning = false, onValidationError, validationErrorFallback, actionError, plain = false, }) {
|
|
7
|
+
export function SnapCard({ snap, handlers, loading = false, appearance = "dark", maxWidth = 480, showOverflowWarning = false, onValidationError, validationErrorFallback, actionError, plain = false, loadingOverlay, }) {
|
|
8
8
|
if (snap.version === SPEC_VERSION_2) {
|
|
9
|
-
return (_jsx(SnapCardV2, { snap: snap, handlers: handlers, loading: loading, appearance: appearance, maxWidth: maxWidth, showOverflowWarning: showOverflowWarning, onValidationError: onValidationError, validationErrorFallback: validationErrorFallback, actionError: actionError, plain: plain }));
|
|
9
|
+
return (_jsx(SnapCardV2, { snap: snap, handlers: handlers, loading: loading, appearance: appearance, maxWidth: maxWidth, showOverflowWarning: showOverflowWarning, onValidationError: onValidationError, validationErrorFallback: validationErrorFallback, actionError: actionError, plain: plain, loadingOverlay: loadingOverlay }));
|
|
10
10
|
}
|
|
11
|
-
return (_jsx(SnapCardV1, { snap: snap, handlers: handlers, loading: loading, appearance: appearance, maxWidth: maxWidth, actionError: actionError, plain: plain }));
|
|
11
|
+
return (_jsx(SnapCardV1, { snap: snap, handlers: handlers, loading: loading, appearance: appearance, maxWidth: maxWidth, actionError: actionError, plain: plain, loadingOverlay: loadingOverlay }));
|
|
12
12
|
}
|
|
@@ -1,11 +1,22 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
1
2
|
import type { SnapActionHandlers, SnapPage } from "./index.js";
|
|
2
3
|
export declare function applyStatePaths(model: Record<string, unknown>, changes: {
|
|
3
4
|
path: string;
|
|
4
5
|
value: unknown;
|
|
5
6
|
}[] | Record<string, unknown>): void;
|
|
6
|
-
export declare function
|
|
7
|
+
export declare function SnapLoadingOverlay({ appearance, accentHex, active, }: {
|
|
8
|
+
appearance: "light" | "dark";
|
|
9
|
+
accentHex: string;
|
|
10
|
+
active: boolean;
|
|
11
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
12
|
+
export declare function SnapViewCore({ snap, handlers, loading, appearance, loadingOverlay, }: {
|
|
7
13
|
snap: SnapPage;
|
|
8
14
|
handlers: SnapActionHandlers;
|
|
9
15
|
loading?: boolean;
|
|
10
16
|
appearance?: "light" | "dark";
|
|
17
|
+
/**
|
|
18
|
+
* Custom content rendered while `loading` is true. When `undefined` (default)
|
|
19
|
+
* the built-in spinner + backdrop is used. Pass `null` to render nothing.
|
|
20
|
+
*/
|
|
21
|
+
loadingOverlay?: ReactNode;
|
|
11
22
|
}): import("react/jsx-runtime").JSX.Element;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
3
|
import { snapJsonRenderCatalog } from "../ui/index.js";
|
|
4
4
|
import { SnapCatalogView } from "./catalog-renderer.js";
|
|
5
5
|
import { SnapPreviewAccentProvider } from "./accent-context.js";
|
|
@@ -75,7 +75,7 @@ function ConfettiOverlay() {
|
|
|
75
75
|
animation: `confettiFall ${duration}s ease-in ${delay}s forwards`,
|
|
76
76
|
} }, id))), _jsx("style", { children: `@keyframes confettiFall{0%{top:-20px;opacity:1;transform:rotate(0deg) translateX(0)}50%{opacity:1}100%{top:110%;opacity:0;transform:rotate(720deg) translateX(${Math.random() > 0.5 ? "" : "-"}40px)}}` })] }));
|
|
77
77
|
}
|
|
78
|
-
function SnapLoadingOverlay({ appearance, accentHex, active, }) {
|
|
78
|
+
export function SnapLoadingOverlay({ appearance, accentHex, active, }) {
|
|
79
79
|
const isDark = appearance === "dark";
|
|
80
80
|
const tint = isDark ? "rgba(0, 0, 0, 0.1)" : "rgba(255, 255, 255, 0.2)";
|
|
81
81
|
const trackColor = isDark
|
|
@@ -130,7 +130,7 @@ const PALETTE = [
|
|
|
130
130
|
];
|
|
131
131
|
// ─── SnapViewCore ────────────────────────────────────
|
|
132
132
|
// Shared rendering logic used by both v1 and v2.
|
|
133
|
-
export function SnapViewCore({ snap, handlers, loading = false, appearance = "dark", }) {
|
|
133
|
+
export function SnapViewCore({ snap, handlers, loading = false, appearance = "dark", loadingOverlay, }) {
|
|
134
134
|
const spec = snap.ui;
|
|
135
135
|
const initialState = useMemo(() => spec.state ?? { inputs: {} }, [spec]);
|
|
136
136
|
const stateRef = useRef(initialState);
|
|
@@ -177,6 +177,9 @@ export function SnapViewCore({ snap, handlers, loading = false, appearance = "da
|
|
|
177
177
|
case "open_url":
|
|
178
178
|
handlers.open_url(String(p.target ?? ""));
|
|
179
179
|
break;
|
|
180
|
+
case "open_snap":
|
|
181
|
+
handlers.open_snap(String(p.target ?? ""));
|
|
182
|
+
break;
|
|
180
183
|
case "open_mini_app":
|
|
181
184
|
handlers.open_mini_app(String(p.target ?? ""));
|
|
182
185
|
break;
|
|
@@ -218,7 +221,7 @@ export function SnapViewCore({ snap, handlers, loading = false, appearance = "da
|
|
|
218
221
|
break;
|
|
219
222
|
}
|
|
220
223
|
}, [handlers]);
|
|
221
|
-
return (_jsxs("div", { style: { position: "relative", width: "100%" }, children: [showConfetti && _jsx(ConfettiOverlay, {}), _jsx(SnapLoadingOverlay, { appearance: appearance, accentHex: accentHex, active: loading }), _jsx("div", { style: previewSurfaceStyle, children: _jsx(SnapPreviewAccentProvider, { pageAccent: snap.theme?.accent, appearance: appearance, children: _jsx(SnapCatalogView, { spec: spec, state: initialState, loading: false, onStateChange: (changes) => {
|
|
224
|
+
return (_jsxs("div", { style: { position: "relative", width: "100%" }, children: [showConfetti && _jsx(ConfettiOverlay, {}), loadingOverlay === undefined ? (_jsx(SnapLoadingOverlay, { appearance: appearance, accentHex: accentHex, active: loading })) : loading ? (_jsx(_Fragment, { children: loadingOverlay })) : null, _jsx("div", { style: previewSurfaceStyle, children: _jsx(SnapPreviewAccentProvider, { pageAccent: snap.theme?.accent, appearance: appearance, children: _jsx(SnapCatalogView, { spec: spec, state: initialState, loading: false, onStateChange: (changes) => {
|
|
222
225
|
applyStatePaths(stateRef.current, changes);
|
|
223
226
|
}, onAction: handleAction }, pageKey) }) })] }));
|
|
224
227
|
}
|
|
@@ -1,11 +1,14 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
1
2
|
import type { SnapPage, SnapActionHandlers } from "../index.js";
|
|
2
|
-
export declare function SnapViewV1({ snap, handlers, loading, appearance, }: {
|
|
3
|
+
export declare function SnapViewV1({ snap, handlers, loading, appearance, loadingOverlay, }: {
|
|
3
4
|
snap: SnapPage;
|
|
4
5
|
handlers: SnapActionHandlers;
|
|
5
6
|
loading?: boolean;
|
|
6
7
|
appearance?: "light" | "dark";
|
|
8
|
+
/** Custom content rendered while `loading` is true. Pass `null` to render nothing. */
|
|
9
|
+
loadingOverlay?: ReactNode;
|
|
7
10
|
}): import("react/jsx-runtime").JSX.Element;
|
|
8
|
-
export declare function SnapCardV1({ snap, handlers, loading, appearance, maxWidth, actionError, plain, }: {
|
|
11
|
+
export declare function SnapCardV1({ snap, handlers, loading, appearance, maxWidth, actionError, plain, loadingOverlay, }: {
|
|
9
12
|
snap: SnapPage;
|
|
10
13
|
handlers: SnapActionHandlers;
|
|
11
14
|
loading?: boolean;
|
|
@@ -13,4 +16,6 @@ export declare function SnapCardV1({ snap, handlers, loading, appearance, maxWid
|
|
|
13
16
|
maxWidth?: number;
|
|
14
17
|
actionError?: string | null;
|
|
15
18
|
plain?: boolean;
|
|
19
|
+
/** Custom content rendered while `loading` is true. Pass `null` to render nothing. */
|
|
20
|
+
loadingOverlay?: ReactNode;
|
|
16
21
|
}): import("react/jsx-runtime").JSX.Element;
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { useEffect, useRef, useState } from "react";
|
|
4
|
-
import { SnapViewCore } from "../snap-view-core.js";
|
|
2
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useEffect, useMemo, useRef, useState } from "react";
|
|
4
|
+
import { SnapViewCore, SnapLoadingOverlay } from "../snap-view-core.js";
|
|
5
|
+
import { resolveSnapPaletteHex } from "../lib/resolve-palette-hex.js";
|
|
5
6
|
const SNAP_MAX_HEIGHT = 500;
|
|
6
|
-
export function SnapViewV1({ snap, handlers, loading = false, appearance = "dark", }) {
|
|
7
|
-
return (_jsx(SnapViewCore, { snap: snap, handlers: handlers, loading: loading, appearance: appearance }));
|
|
7
|
+
export function SnapViewV1({ snap, handlers, loading = false, appearance = "dark", loadingOverlay, }) {
|
|
8
|
+
return (_jsx(SnapViewCore, { snap: snap, handlers: handlers, loading: loading, appearance: appearance, loadingOverlay: loadingOverlay }));
|
|
8
9
|
}
|
|
9
|
-
export function SnapCardV1({ snap, handlers, loading = false, appearance = "dark", maxWidth = 480, actionError, plain = false, }) {
|
|
10
|
+
export function SnapCardV1({ snap, handlers, loading = false, appearance = "dark", maxWidth = 480, actionError, plain = false, loadingOverlay, }) {
|
|
10
11
|
const isDark = appearance === "dark";
|
|
11
12
|
const borderColor = isDark ? "rgba(255,255,255,0.1)" : "rgba(0,0,0,0.1)";
|
|
12
13
|
const surfaceBg = isDark ? "rgba(255,255,255,0.05)" : "rgba(0,0,0,0.02)";
|
|
@@ -43,6 +44,7 @@ export function SnapCardV1({ snap, handlers, loading = false, appearance = "dark
|
|
|
43
44
|
}
|
|
44
45
|
}, [isExpandable]);
|
|
45
46
|
const isClipped = isExpandable && !isExpanded;
|
|
47
|
+
const accentHex = useMemo(() => resolveSnapPaletteHex(snap.theme?.accent ?? "purple", appearance), [snap.theme?.accent, appearance]);
|
|
46
48
|
return (_jsxs("div", { style: {
|
|
47
49
|
position: "relative",
|
|
48
50
|
width: "100%",
|
|
@@ -58,7 +60,7 @@ export function SnapCardV1({ snap, handlers, loading = false, appearance = "dark
|
|
|
58
60
|
maxHeight: SNAP_MAX_HEIGHT,
|
|
59
61
|
overflow: "hidden",
|
|
60
62
|
}
|
|
61
|
-
: undefined, children: _jsx("div", { ref: contentRef, style: plain ? undefined : { padding: 16 }, children: _jsx(SnapViewV1, { snap: snap, handlers: handlers, loading: loading, appearance: appearance }) }) }), isExpandable ? (_jsx("div", { style: {
|
|
63
|
+
: undefined, children: _jsx("div", { ref: contentRef, style: plain ? undefined : { padding: 16 }, children: _jsx(SnapViewV1, { snap: snap, handlers: handlers, loading: loading, appearance: appearance, loadingOverlay: null }) }) }), loadingOverlay === undefined ? (_jsx(SnapLoadingOverlay, { appearance: appearance, accentHex: accentHex, active: loading })) : loading ? (_jsx(_Fragment, { children: loadingOverlay })) : null, isExpandable ? (_jsx("div", { style: {
|
|
62
64
|
display: "flex",
|
|
63
65
|
justifyContent: "center",
|
|
64
66
|
padding: plain ? "8px 0 0" : "10px 16px 12px",
|
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
import { type ReactNode } from "react";
|
|
2
2
|
import type { ValidationResult } from "../../validator.js";
|
|
3
3
|
import type { SnapPage, SnapActionHandlers } from "../index.js";
|
|
4
|
-
export declare function SnapViewV2({ snap, handlers, loading, appearance, onValidationError, validationErrorFallback, }: {
|
|
4
|
+
export declare function SnapViewV2({ snap, handlers, loading, appearance, onValidationError, validationErrorFallback, loadingOverlay, }: {
|
|
5
5
|
snap: SnapPage;
|
|
6
6
|
handlers: SnapActionHandlers;
|
|
7
7
|
loading?: boolean;
|
|
8
8
|
appearance?: "light" | "dark";
|
|
9
9
|
onValidationError?: (result: ValidationResult) => void;
|
|
10
10
|
validationErrorFallback?: ReactNode;
|
|
11
|
+
/** Custom content rendered while `loading` is true. Pass `null` to render nothing. */
|
|
12
|
+
loadingOverlay?: ReactNode;
|
|
11
13
|
}): import("react/jsx-runtime").JSX.Element | null;
|
|
12
|
-
export declare function SnapCardV2({ snap, handlers, loading, appearance, maxWidth, showOverflowWarning, onValidationError, validationErrorFallback, actionError, plain, }: {
|
|
14
|
+
export declare function SnapCardV2({ snap, handlers, loading, appearance, maxWidth, showOverflowWarning, onValidationError, validationErrorFallback, actionError, plain, loadingOverlay, }: {
|
|
13
15
|
snap: SnapPage;
|
|
14
16
|
handlers: SnapActionHandlers;
|
|
15
17
|
loading?: boolean;
|
|
@@ -20,4 +22,6 @@ export declare function SnapCardV2({ snap, handlers, loading, appearance, maxWid
|
|
|
20
22
|
validationErrorFallback?: ReactNode;
|
|
21
23
|
actionError?: string | null;
|
|
22
24
|
plain?: boolean;
|
|
25
|
+
/** Custom content rendered while `loading` is true. Pass `null` to render nothing. */
|
|
26
|
+
loadingOverlay?: ReactNode;
|
|
23
27
|
}): import("react/jsx-runtime").JSX.Element;
|
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useEffect, useMemo } from "react";
|
|
4
4
|
import { validateSnapResponse } from "../../validator.js";
|
|
5
|
-
import { SnapViewCore } from "../snap-view-core.js";
|
|
5
|
+
import { SnapViewCore, SnapLoadingOverlay } from "../snap-view-core.js";
|
|
6
|
+
import { resolveSnapPaletteHex } from "../lib/resolve-palette-hex.js";
|
|
6
7
|
const SNAP_MAX_HEIGHT = 500;
|
|
7
8
|
const SNAP_WARNING_HEIGHT = 700;
|
|
8
9
|
// ─── Default validation error fallback ────────────────
|
|
@@ -19,7 +20,7 @@ function SnapValidationFallback({ appearance, message, }) {
|
|
|
19
20
|
}, children: _jsx("span", { children: message ? `Unable to render snap: ${message}` : "Unable to render snap" }) }));
|
|
20
21
|
}
|
|
21
22
|
// ─── SnapViewV2 ──────────────────────────────────────
|
|
22
|
-
export function SnapViewV2({ snap, handlers, loading = false, appearance = "dark", onValidationError, validationErrorFallback, }) {
|
|
23
|
+
export function SnapViewV2({ snap, handlers, loading = false, appearance = "dark", onValidationError, validationErrorFallback, loadingOverlay, }) {
|
|
23
24
|
const validation = useMemo(() => validateSnapResponse(snap), [snap]);
|
|
24
25
|
const valid = validation.valid;
|
|
25
26
|
const validationMessage = validation.issues[0]?.message;
|
|
@@ -39,15 +40,16 @@ export function SnapViewV2({ snap, handlers, loading = false, appearance = "dark
|
|
|
39
40
|
return null;
|
|
40
41
|
return _jsx(_Fragment, { children: validationErrorFallback ?? _jsx(SnapValidationFallback, { appearance: appearance, message: validationMessage }) });
|
|
41
42
|
}
|
|
42
|
-
return (_jsx(SnapViewCore, { snap: snap, handlers: handlers, loading: loading, appearance: appearance }));
|
|
43
|
+
return (_jsx(SnapViewCore, { snap: snap, handlers: handlers, loading: loading, appearance: appearance, loadingOverlay: loadingOverlay }));
|
|
43
44
|
}
|
|
44
45
|
// ─── SnapCardV2 ──────────────────────────────────────
|
|
45
|
-
export function SnapCardV2({ snap, handlers, loading = false, appearance = "dark", maxWidth = 480, showOverflowWarning = false, onValidationError, validationErrorFallback, actionError, plain = false, }) {
|
|
46
|
+
export function SnapCardV2({ snap, handlers, loading = false, appearance = "dark", maxWidth = 480, showOverflowWarning = false, onValidationError, validationErrorFallback, actionError, plain = false, loadingOverlay, }) {
|
|
46
47
|
const maxHeight = showOverflowWarning ? SNAP_WARNING_HEIGHT : SNAP_MAX_HEIGHT;
|
|
47
48
|
const isDark = appearance === "dark";
|
|
48
49
|
const bg = isDark ? "rgba(0,0,0,0.85)" : "rgba(255,255,255,0.9)";
|
|
49
50
|
const borderColor = isDark ? "rgba(255,255,255,0.1)" : "rgba(0,0,0,0.1)";
|
|
50
51
|
const surfaceBg = isDark ? "rgba(255,255,255,0.05)" : "rgba(0,0,0,0.02)";
|
|
52
|
+
const accentHex = useMemo(() => resolveSnapPaletteHex(snap.theme?.accent ?? "purple", appearance), [snap.theme?.accent, appearance]);
|
|
51
53
|
return (_jsxs(_Fragment, { children: [_jsxs("div", { style: {
|
|
52
54
|
position: "relative",
|
|
53
55
|
width: "100%",
|
|
@@ -59,7 +61,7 @@ export function SnapCardV2({ snap, handlers, loading = false, appearance = "dark
|
|
|
59
61
|
border: `1px solid ${borderColor}`,
|
|
60
62
|
backgroundColor: surfaceBg,
|
|
61
63
|
}),
|
|
62
|
-
}, children: [_jsx("div", { style: plain ? undefined : { padding: 16 }, children: _jsx(SnapViewV2, { snap: snap, handlers: handlers, loading: loading, appearance: appearance, onValidationError: onValidationError, validationErrorFallback: validationErrorFallback }) }), showOverflowWarning && (_jsxs("div", { style: {
|
|
64
|
+
}, children: [_jsx("div", { style: plain ? undefined : { padding: 16 }, children: _jsx(SnapViewV2, { snap: snap, handlers: handlers, loading: loading, appearance: appearance, onValidationError: onValidationError, validationErrorFallback: validationErrorFallback, loadingOverlay: null }) }), loadingOverlay === undefined ? (_jsx(SnapLoadingOverlay, { appearance: appearance, accentHex: accentHex, active: loading })) : loading ? (_jsx(_Fragment, { children: loadingOverlay })) : null, showOverflowWarning && (_jsxs("div", { style: {
|
|
63
65
|
position: "absolute",
|
|
64
66
|
top: SNAP_MAX_HEIGHT,
|
|
65
67
|
left: 0,
|
|
@@ -7,7 +7,7 @@ import { hexToRgba } from "./use-snap-palette.js";
|
|
|
7
7
|
export type { JsonValue, SnapPage, SnapActionHandlers } from "./types.js";
|
|
8
8
|
export { useSnapTheme, hexToRgba };
|
|
9
9
|
export type { SnapNativeColors };
|
|
10
|
-
export declare function SnapCard({ snap, handlers, loading, appearance, colors, borderRadius, showOverflowWarning, onValidationError, validationErrorFallback, actionError, plain, }: {
|
|
10
|
+
export declare function SnapCard({ snap, handlers, loading, appearance, colors, borderRadius, showOverflowWarning, onValidationError, validationErrorFallback, actionError, plain, loadingOverlay, }: {
|
|
11
11
|
snap: SnapPage;
|
|
12
12
|
handlers: SnapActionHandlers;
|
|
13
13
|
loading?: boolean;
|
|
@@ -25,4 +25,6 @@ export declare function SnapCard({ snap, handlers, loading, appearance, colors,
|
|
|
25
25
|
actionError?: string | null;
|
|
26
26
|
/** When true, renders without card frame (no border, background, or padding). */
|
|
27
27
|
plain?: boolean;
|
|
28
|
+
/** Custom content rendered while `loading` is true. Pass `null` to render nothing. */
|
|
29
|
+
loadingOverlay?: ReactNode;
|
|
28
30
|
}): import("react").JSX.Element;
|
|
@@ -7,9 +7,9 @@ import { SnapCardV2 } from "./v2/snap-view.js";
|
|
|
7
7
|
// ─── Re-exports ───────────────────────────────────────
|
|
8
8
|
export { useSnapTheme, hexToRgba };
|
|
9
9
|
// ─── SnapCard (version-switching) ─────────────────────
|
|
10
|
-
export function SnapCard({ snap, handlers, loading = false, appearance = "dark", colors, borderRadius = 16, showOverflowWarning = false, onValidationError, validationErrorFallback, actionError, plain = false, }) {
|
|
10
|
+
export function SnapCard({ snap, handlers, loading = false, appearance = "dark", colors, borderRadius = 16, showOverflowWarning = false, onValidationError, validationErrorFallback, actionError, plain = false, loadingOverlay, }) {
|
|
11
11
|
if (snap.version === SPEC_VERSION_2) {
|
|
12
|
-
return (_jsx(SnapCardV2, { snap: snap, handlers: handlers, loading: loading, appearance: appearance, colors: colors, borderRadius: borderRadius, showOverflowWarning: showOverflowWarning, onValidationError: onValidationError, validationErrorFallback: validationErrorFallback, actionError: actionError, plain: plain }));
|
|
12
|
+
return (_jsx(SnapCardV2, { snap: snap, handlers: handlers, loading: loading, appearance: appearance, colors: colors, borderRadius: borderRadius, showOverflowWarning: showOverflowWarning, onValidationError: onValidationError, validationErrorFallback: validationErrorFallback, actionError: actionError, plain: plain, loadingOverlay: loadingOverlay }));
|
|
13
13
|
}
|
|
14
|
-
return (_jsx(SnapCardV1, { snap: snap, handlers: handlers, loading: loading, appearance: appearance, colors: colors, borderRadius: borderRadius, actionError: actionError, plain: plain }));
|
|
14
|
+
return (_jsx(SnapCardV1, { snap: snap, handlers: handlers, loading: loading, appearance: appearance, colors: colors, borderRadius: borderRadius, actionError: actionError, plain: plain, loadingOverlay: loadingOverlay }));
|
|
15
15
|
}
|
|
@@ -1,11 +1,21 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
1
2
|
import type { SnapPage, SnapActionHandlers } from "./types.js";
|
|
2
3
|
export declare function applyStatePaths(model: Record<string, unknown>, changes: {
|
|
3
4
|
path: string;
|
|
4
5
|
value: unknown;
|
|
5
6
|
}[] | Record<string, unknown>): void;
|
|
6
7
|
export declare function resolveAccentHex(accent: string | undefined, appearance: "light" | "dark"): string;
|
|
7
|
-
export declare function SnapViewCoreInner({ snap, handlers, loading, }: {
|
|
8
|
+
export declare function SnapViewCoreInner({ snap, handlers, loading, loadingOverlay, }: {
|
|
8
9
|
snap: SnapPage;
|
|
9
10
|
handlers: SnapActionHandlers;
|
|
10
11
|
loading?: boolean;
|
|
12
|
+
/**
|
|
13
|
+
* Custom content rendered while `loading` is true. When `undefined` (default)
|
|
14
|
+
* the built-in ActivityIndicator overlay is used. Pass `null` to render nothing.
|
|
15
|
+
*/
|
|
16
|
+
loadingOverlay?: ReactNode;
|
|
17
|
+
}): import("react").JSX.Element;
|
|
18
|
+
export declare function SnapLoadingOverlay({ appearance, accentHex, }: {
|
|
19
|
+
appearance: "light" | "dark";
|
|
20
|
+
accentHex: string;
|
|
11
21
|
}): import("react").JSX.Element;
|
|
@@ -2,7 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import { snapJsonRenderCatalog } from "@farcaster/snap/ui";
|
|
3
3
|
import { SnapCatalogView } from "./catalog-renderer.js";
|
|
4
4
|
import { useSnapTheme } from "./theme.js";
|
|
5
|
-
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
5
|
+
import { useCallback, useEffect, useMemo, useRef, useState, } from "react";
|
|
6
6
|
import { ActivityIndicator, StyleSheet, View } from "react-native";
|
|
7
7
|
import { DEFAULT_THEME_ACCENT, PALETTE_LIGHT_HEX, PALETTE_DARK_HEX, } from "@farcaster/snap";
|
|
8
8
|
// ─── Shared helpers ──────────────────────────────────
|
|
@@ -45,7 +45,7 @@ export function resolveAccentHex(accent, appearance) {
|
|
|
45
45
|
return map[name];
|
|
46
46
|
}
|
|
47
47
|
// ─── Core rendering component (no validation) ────────
|
|
48
|
-
export function SnapViewCoreInner({ snap, handlers, loading = false, }) {
|
|
48
|
+
export function SnapViewCoreInner({ snap, handlers, loading = false, loadingOverlay, }) {
|
|
49
49
|
const { mode } = useSnapTheme();
|
|
50
50
|
const spec = snap.ui;
|
|
51
51
|
const accentHex = resolveAccentHex(snap.theme?.accent, mode);
|
|
@@ -92,6 +92,9 @@ export function SnapViewCoreInner({ snap, handlers, loading = false, }) {
|
|
|
92
92
|
case "open_url":
|
|
93
93
|
h.open_url(String(p.target ?? ""));
|
|
94
94
|
break;
|
|
95
|
+
case "open_snap":
|
|
96
|
+
h.open_snap(String(p.target ?? ""));
|
|
97
|
+
break;
|
|
95
98
|
case "open_mini_app":
|
|
96
99
|
h.open_mini_app(String(p.target ?? ""));
|
|
97
100
|
break;
|
|
@@ -131,15 +134,24 @@ export function SnapViewCoreInner({ snap, handlers, loading = false, }) {
|
|
|
131
134
|
break;
|
|
132
135
|
}
|
|
133
136
|
}, []);
|
|
134
|
-
return (_jsxs(View, { style: styles.container, children: [loading
|
|
135
|
-
|
|
136
|
-
{
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
], children: _jsx(ActivityIndicator, { size: "large", color: accentHex }) })) : null, _jsx(SnapCatalogView, { spec: spec, state: initialState, loading: false, onStateChange: (changes) => {
|
|
137
|
+
return (_jsxs(View, { style: styles.container, children: [loading
|
|
138
|
+
? loadingOverlay === undefined
|
|
139
|
+
? (_jsx(SnapLoadingOverlay, { appearance: mode, accentHex: accentHex }))
|
|
140
|
+
: loadingOverlay
|
|
141
|
+
: null, _jsx(SnapCatalogView, { spec: spec, state: initialState, loading: false, onStateChange: (changes) => {
|
|
140
142
|
applyStatePaths(stateRef.current, changes);
|
|
141
143
|
}, onAction: handleAction }, pageKey)] }));
|
|
142
144
|
}
|
|
145
|
+
export function SnapLoadingOverlay({ appearance, accentHex, }) {
|
|
146
|
+
return (_jsx(View, { style: [
|
|
147
|
+
styles.overlay,
|
|
148
|
+
{
|
|
149
|
+
backgroundColor: appearance === "dark"
|
|
150
|
+
? "rgba(0,0,0,0.1)"
|
|
151
|
+
: "rgba(255,255,255,0.2)",
|
|
152
|
+
},
|
|
153
|
+
], children: _jsx(ActivityIndicator, { size: "large", color: accentHex }) }));
|
|
154
|
+
}
|
|
143
155
|
const styles = StyleSheet.create({
|
|
144
156
|
container: {
|
|
145
157
|
width: "100%",
|
|
@@ -1,18 +1,22 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
1
2
|
import { type SnapNativeColors } from "../theme.js";
|
|
2
3
|
import type { SnapPage, SnapActionHandlers } from "../types.js";
|
|
3
|
-
export declare function SnapViewV1Inner({ snap, handlers, loading, }: {
|
|
4
|
+
export declare function SnapViewV1Inner({ snap, handlers, loading, loadingOverlay, }: {
|
|
4
5
|
snap: SnapPage;
|
|
5
6
|
handlers: SnapActionHandlers;
|
|
6
7
|
loading?: boolean;
|
|
8
|
+
loadingOverlay?: ReactNode;
|
|
7
9
|
}): import("react").JSX.Element;
|
|
8
|
-
export declare function SnapViewV1({ snap, handlers, loading, appearance, colors, }: {
|
|
10
|
+
export declare function SnapViewV1({ snap, handlers, loading, appearance, colors, loadingOverlay, }: {
|
|
9
11
|
snap: SnapPage;
|
|
10
12
|
handlers: SnapActionHandlers;
|
|
11
13
|
loading?: boolean;
|
|
12
14
|
appearance?: "light" | "dark";
|
|
13
15
|
colors?: Partial<SnapNativeColors>;
|
|
16
|
+
/** Custom content rendered while `loading` is true. Pass `null` to render nothing. */
|
|
17
|
+
loadingOverlay?: ReactNode;
|
|
14
18
|
}): import("react").JSX.Element;
|
|
15
|
-
export declare function SnapCardV1({ snap, handlers, loading, appearance, colors, borderRadius, actionError, plain, }: {
|
|
19
|
+
export declare function SnapCardV1({ snap, handlers, loading, appearance, colors, borderRadius, actionError, plain, loadingOverlay, }: {
|
|
16
20
|
snap: SnapPage;
|
|
17
21
|
handlers: SnapActionHandlers;
|
|
18
22
|
loading?: boolean;
|
|
@@ -21,4 +25,6 @@ export declare function SnapCardV1({ snap, handlers, loading, appearance, colors
|
|
|
21
25
|
borderRadius?: number;
|
|
22
26
|
actionError?: string | null;
|
|
23
27
|
plain?: boolean;
|
|
28
|
+
/** Custom content rendered while `loading` is true. Pass `null` to render nothing. */
|
|
29
|
+
loadingOverlay?: ReactNode;
|
|
24
30
|
}): import("react").JSX.Element;
|
|
@@ -2,18 +2,19 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
2
2
|
import { useEffect, useState } from "react";
|
|
3
3
|
import { View, Text, StyleSheet, Pressable } from "react-native";
|
|
4
4
|
import { SnapThemeProvider, useSnapTheme } from "../theme.js";
|
|
5
|
-
import { SnapViewCoreInner } from "../snap-view-core.js";
|
|
5
|
+
import { SnapLoadingOverlay, SnapViewCoreInner, resolveAccentHex, } from "../snap-view-core.js";
|
|
6
6
|
const SNAP_MAX_HEIGHT = 500;
|
|
7
7
|
// ─── SnapViewV1 (no validation) ──────────────────────
|
|
8
|
-
export function SnapViewV1Inner({ snap, handlers, loading = false, }) {
|
|
9
|
-
return (_jsx(SnapViewCoreInner, { snap: snap, handlers: handlers, loading: loading }));
|
|
8
|
+
export function SnapViewV1Inner({ snap, handlers, loading = false, loadingOverlay, }) {
|
|
9
|
+
return (_jsx(SnapViewCoreInner, { snap: snap, handlers: handlers, loading: loading, loadingOverlay: loadingOverlay }));
|
|
10
10
|
}
|
|
11
|
-
export function SnapViewV1({ snap, handlers, loading = false, appearance = "dark", colors, }) {
|
|
12
|
-
return (_jsx(SnapThemeProvider, { appearance: appearance, colors: colors, children: _jsx(SnapViewV1Inner, { snap: snap, handlers: handlers, loading: loading }) }));
|
|
11
|
+
export function SnapViewV1({ snap, handlers, loading = false, appearance = "dark", colors, loadingOverlay, }) {
|
|
12
|
+
return (_jsx(SnapThemeProvider, { appearance: appearance, colors: colors, children: _jsx(SnapViewV1Inner, { snap: snap, handlers: handlers, loading: loading, loadingOverlay: loadingOverlay }) }));
|
|
13
13
|
}
|
|
14
14
|
// ─── SnapCardV1 (card frame with expandable clipping) ──
|
|
15
|
-
function SnapCardV1Inner({ snap, handlers, loading = false, borderRadius, actionError, appearance, plain, }) {
|
|
16
|
-
const { colors } = useSnapTheme();
|
|
15
|
+
function SnapCardV1Inner({ snap, handlers, loading = false, borderRadius, actionError, appearance, plain, loadingOverlay, }) {
|
|
16
|
+
const { colors, mode } = useSnapTheme();
|
|
17
|
+
const accentHex = resolveAccentHex(snap.theme?.accent, mode);
|
|
17
18
|
const [contentHeight, setContentHeight] = useState(0);
|
|
18
19
|
const [isExpanded, setIsExpanded] = useState(false);
|
|
19
20
|
useEffect(() => {
|
|
@@ -36,7 +37,11 @@ function SnapCardV1Inner({ snap, handlers, loading = false, borderRadius, action
|
|
|
36
37
|
: currentHeight === nextHeight
|
|
37
38
|
? currentHeight
|
|
38
39
|
: nextHeight);
|
|
39
|
-
}, style: plain ? undefined : cardStyles.body, children: _jsx(SnapViewV1Inner, { snap: snap, handlers: handlers, loading: loading }) }) }),
|
|
40
|
+
}, style: plain ? undefined : cardStyles.body, children: _jsx(SnapViewV1Inner, { snap: snap, handlers: handlers, loading: loading, loadingOverlay: null }) }) }), loading
|
|
41
|
+
? loadingOverlay === undefined
|
|
42
|
+
? _jsx(SnapLoadingOverlay, { appearance: mode, accentHex: accentHex })
|
|
43
|
+
: loadingOverlay
|
|
44
|
+
: null, isExpandable ? (_jsx(View, { style: [
|
|
40
45
|
cardStyles.expandRow,
|
|
41
46
|
plain
|
|
42
47
|
? cardStyles.expandRowPlain
|
|
@@ -59,8 +64,8 @@ function SnapCardV1Inner({ snap, handlers, loading = false, borderRadius, action
|
|
|
59
64
|
},
|
|
60
65
|
], children: actionError }))] }));
|
|
61
66
|
}
|
|
62
|
-
export function SnapCardV1({ snap, handlers, loading = false, appearance = "dark", colors, borderRadius = 16, actionError, plain = false, }) {
|
|
63
|
-
return (_jsx(SnapThemeProvider, { appearance: appearance, colors: colors, children: _jsx(SnapCardV1Inner, { snap: snap, handlers: handlers, loading: loading, borderRadius: borderRadius, actionError: actionError, appearance: appearance, plain: plain }) }));
|
|
67
|
+
export function SnapCardV1({ snap, handlers, loading = false, appearance = "dark", colors, borderRadius = 16, actionError, plain = false, loadingOverlay, }) {
|
|
68
|
+
return (_jsx(SnapThemeProvider, { appearance: appearance, colors: colors, children: _jsx(SnapCardV1Inner, { snap: snap, handlers: handlers, loading: loading, borderRadius: borderRadius, actionError: actionError, appearance: appearance, plain: plain, loadingOverlay: loadingOverlay }) }));
|
|
64
69
|
}
|
|
65
70
|
const cardStyles = StyleSheet.create({
|
|
66
71
|
frameRing: { alignSelf: "stretch" },
|
|
@@ -2,14 +2,15 @@ import type { ReactNode } from "react";
|
|
|
2
2
|
import { type SnapNativeColors } from "../theme.js";
|
|
3
3
|
import { type ValidationResult } from "@farcaster/snap";
|
|
4
4
|
import type { SnapPage, SnapActionHandlers } from "../types.js";
|
|
5
|
-
export declare function SnapViewV2Inner({ snap, handlers, loading, onValidationError, validationErrorFallback, }: {
|
|
5
|
+
export declare function SnapViewV2Inner({ snap, handlers, loading, onValidationError, validationErrorFallback, loadingOverlay, }: {
|
|
6
6
|
snap: SnapPage;
|
|
7
7
|
handlers: SnapActionHandlers;
|
|
8
8
|
loading?: boolean;
|
|
9
9
|
onValidationError?: (result: ValidationResult) => void;
|
|
10
10
|
validationErrorFallback?: ReactNode;
|
|
11
|
+
loadingOverlay?: ReactNode;
|
|
11
12
|
}): import("react").JSX.Element;
|
|
12
|
-
export declare function SnapViewV2({ snap, handlers, loading, appearance, colors, onValidationError, validationErrorFallback, }: {
|
|
13
|
+
export declare function SnapViewV2({ snap, handlers, loading, appearance, colors, onValidationError, validationErrorFallback, loadingOverlay, }: {
|
|
13
14
|
snap: SnapPage;
|
|
14
15
|
handlers: SnapActionHandlers;
|
|
15
16
|
loading?: boolean;
|
|
@@ -17,8 +18,10 @@ export declare function SnapViewV2({ snap, handlers, loading, appearance, colors
|
|
|
17
18
|
colors?: Partial<SnapNativeColors>;
|
|
18
19
|
onValidationError?: (result: ValidationResult) => void;
|
|
19
20
|
validationErrorFallback?: ReactNode;
|
|
21
|
+
/** Custom content rendered while `loading` is true. Pass `null` to render nothing. */
|
|
22
|
+
loadingOverlay?: ReactNode;
|
|
20
23
|
}): import("react").JSX.Element;
|
|
21
|
-
export declare function SnapCardV2({ snap, handlers, loading, appearance, colors, borderRadius, showOverflowWarning, onValidationError, validationErrorFallback, actionError, plain, }: {
|
|
24
|
+
export declare function SnapCardV2({ snap, handlers, loading, appearance, colors, borderRadius, showOverflowWarning, onValidationError, validationErrorFallback, actionError, plain, loadingOverlay, }: {
|
|
22
25
|
snap: SnapPage;
|
|
23
26
|
handlers: SnapActionHandlers;
|
|
24
27
|
loading?: boolean;
|
|
@@ -30,4 +33,6 @@ export declare function SnapCardV2({ snap, handlers, loading, appearance, colors
|
|
|
30
33
|
validationErrorFallback?: ReactNode;
|
|
31
34
|
actionError?: string | null;
|
|
32
35
|
plain?: boolean;
|
|
36
|
+
/** Custom content rendered while `loading` is true. Pass `null` to render nothing. */
|
|
37
|
+
loadingOverlay?: ReactNode;
|
|
33
38
|
}): import("react").JSX.Element;
|
|
@@ -2,7 +2,7 @@ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-run
|
|
|
2
2
|
import { useEffect, useMemo, useState } from "react";
|
|
3
3
|
import { Platform, StyleSheet, Text, View } from "react-native";
|
|
4
4
|
import { SnapThemeProvider, useSnapTheme } from "../theme.js";
|
|
5
|
-
import { SnapViewCoreInner } from "../snap-view-core.js";
|
|
5
|
+
import { SnapLoadingOverlay, SnapViewCoreInner, resolveAccentHex, } from "../snap-view-core.js";
|
|
6
6
|
import { validateSnapResponse, } from "@farcaster/snap";
|
|
7
7
|
// ─── Constants ───────────────────────────────────────
|
|
8
8
|
const SNAP_MAX_HEIGHT = 500;
|
|
@@ -24,7 +24,7 @@ const fallbackStyles = StyleSheet.create({
|
|
|
24
24
|
},
|
|
25
25
|
});
|
|
26
26
|
// ─── SnapViewV2 (with validation) ────────────────────
|
|
27
|
-
export function SnapViewV2Inner({ snap, handlers, loading = false, onValidationError, validationErrorFallback, }) {
|
|
27
|
+
export function SnapViewV2Inner({ snap, handlers, loading = false, onValidationError, validationErrorFallback, loadingOverlay, }) {
|
|
28
28
|
const validation = useMemo(() => validateSnapResponse(snap), [snap]);
|
|
29
29
|
const valid = validation.valid;
|
|
30
30
|
const validationMessage = validation.issues[0]?.message;
|
|
@@ -44,18 +44,23 @@ export function SnapViewV2Inner({ snap, handlers, loading = false, onValidationE
|
|
|
44
44
|
return null;
|
|
45
45
|
return (_jsx(_Fragment, { children: validationErrorFallback ?? _jsx(SnapValidationFallback, { message: validationMessage }) }));
|
|
46
46
|
}
|
|
47
|
-
return (_jsx(SnapViewCoreInner, { snap: snap, handlers: handlers, loading: loading }));
|
|
47
|
+
return (_jsx(SnapViewCoreInner, { snap: snap, handlers: handlers, loading: loading, loadingOverlay: loadingOverlay }));
|
|
48
48
|
}
|
|
49
|
-
export function SnapViewV2({ snap, handlers, loading = false, appearance = "dark", colors, onValidationError, validationErrorFallback, }) {
|
|
50
|
-
return (_jsx(SnapThemeProvider, { appearance: appearance, colors: colors, children: _jsx(SnapViewV2Inner, { snap: snap, handlers: handlers, loading: loading, onValidationError: onValidationError, validationErrorFallback: validationErrorFallback }) }));
|
|
49
|
+
export function SnapViewV2({ snap, handlers, loading = false, appearance = "dark", colors, onValidationError, validationErrorFallback, loadingOverlay, }) {
|
|
50
|
+
return (_jsx(SnapThemeProvider, { appearance: appearance, colors: colors, children: _jsx(SnapViewV2Inner, { snap: snap, handlers: handlers, loading: loading, onValidationError: onValidationError, validationErrorFallback: validationErrorFallback, loadingOverlay: loadingOverlay }) }));
|
|
51
51
|
}
|
|
52
52
|
// ─── SnapCardV2 (card frame + height limits) ─────────
|
|
53
|
-
function SnapCardV2Inner({ snap, handlers, loading, borderRadius, showOverflowWarning, onValidationError, validationErrorFallback, actionError, appearance, plain, }) {
|
|
54
|
-
const { colors } = useSnapTheme();
|
|
53
|
+
function SnapCardV2Inner({ snap, handlers, loading, borderRadius, showOverflowWarning, onValidationError, validationErrorFallback, actionError, appearance, plain, loadingOverlay, }) {
|
|
54
|
+
const { colors, mode } = useSnapTheme();
|
|
55
|
+
const accentHex = resolveAccentHex(snap.theme?.accent, mode);
|
|
55
56
|
const [contentHeight, setContentHeight] = useState(0);
|
|
56
|
-
const content = (_jsx(SnapViewV2Inner, { snap: snap, handlers: handlers, loading: loading, onValidationError: onValidationError, validationErrorFallback: validationErrorFallback }));
|
|
57
|
+
const content = (_jsx(SnapViewV2Inner, { snap: snap, handlers: handlers, loading: loading, onValidationError: onValidationError, validationErrorFallback: validationErrorFallback, loadingOverlay: null }));
|
|
57
58
|
if (plain) {
|
|
58
|
-
return content
|
|
59
|
+
return (_jsxs(_Fragment, { children: [content, loading
|
|
60
|
+
? loadingOverlay === undefined
|
|
61
|
+
? _jsx(SnapLoadingOverlay, { appearance: mode, accentHex: accentHex })
|
|
62
|
+
: loadingOverlay
|
|
63
|
+
: null] }));
|
|
59
64
|
}
|
|
60
65
|
const overflowAmount = showOverflowWarning ? contentHeight - SNAP_MAX_HEIGHT : 0;
|
|
61
66
|
return (_jsxs(_Fragment, { children: [_jsxs(View, { style: {
|
|
@@ -66,7 +71,11 @@ function SnapCardV2Inner({ snap, handlers, loading, borderRadius, showOverflowWa
|
|
|
66
71
|
maxHeight: showOverflowWarning ? undefined : SNAP_MAX_HEIGHT,
|
|
67
72
|
overflow: "hidden",
|
|
68
73
|
minHeight: 120,
|
|
69
|
-
}, children: [_jsx(View, { collapsable: false, onLayout: (e) => setContentHeight(Math.round(e.nativeEvent.layout.height)), style: { paddingHorizontal: 16, paddingVertical: 16 }, children: content }), showOverflowWarning && contentHeight > SNAP_MAX_HEIGHT && (_jsxs(View, { style: { position: "absolute", top: SNAP_MAX_HEIGHT, left: 0, right: 0, height: overflowAmount, zIndex: 10, pointerEvents: "none" }, children: [_jsx(View, { style: { height: 1, borderTopWidth: 1, borderStyle: "dashed", borderColor: "rgba(255,100,100,0.6)" } }), _jsx(View, { style: { position: "absolute", top: -10, right: 4, backgroundColor: "rgba(0,0,0,0.7)", paddingHorizontal: 4, paddingVertical: 1, borderRadius: 3 }, children: _jsxs(Text, { style: { fontSize: 10, color: "rgba(255,100,100,0.7)", fontFamily: Platform.select({ ios: "Menlo", default: "monospace" }) }, children: [SNAP_MAX_HEIGHT, "px"] }) }), _jsx(View, { style: { flex: 1, backgroundColor: "rgba(255,50,50,0.15)" } })] }))
|
|
74
|
+
}, children: [_jsx(View, { collapsable: false, onLayout: (e) => setContentHeight(Math.round(e.nativeEvent.layout.height)), style: { paddingHorizontal: 16, paddingVertical: 16 }, children: content }), showOverflowWarning && contentHeight > SNAP_MAX_HEIGHT && (_jsxs(View, { style: { position: "absolute", top: SNAP_MAX_HEIGHT, left: 0, right: 0, height: overflowAmount, zIndex: 10, pointerEvents: "none" }, children: [_jsx(View, { style: { height: 1, borderTopWidth: 1, borderStyle: "dashed", borderColor: "rgba(255,100,100,0.6)" } }), _jsx(View, { style: { position: "absolute", top: -10, right: 4, backgroundColor: "rgba(0,0,0,0.7)", paddingHorizontal: 4, paddingVertical: 1, borderRadius: 3 }, children: _jsxs(Text, { style: { fontSize: 10, color: "rgba(255,100,100,0.7)", fontFamily: Platform.select({ ios: "Menlo", default: "monospace" }) }, children: [SNAP_MAX_HEIGHT, "px"] }) }), _jsx(View, { style: { flex: 1, backgroundColor: "rgba(255,50,50,0.15)" } })] })), loading
|
|
75
|
+
? loadingOverlay === undefined
|
|
76
|
+
? _jsx(SnapLoadingOverlay, { appearance: mode, accentHex: accentHex })
|
|
77
|
+
: loadingOverlay
|
|
78
|
+
: null] }), actionError && (_jsx(Text, { style: {
|
|
70
79
|
paddingHorizontal: 12,
|
|
71
80
|
paddingVertical: 8,
|
|
72
81
|
fontSize: 13,
|
|
@@ -75,8 +84,8 @@ function SnapCardV2Inner({ snap, handlers, loading, borderRadius, showOverflowWa
|
|
|
75
84
|
: "rgba(200,0,0,0.8)",
|
|
76
85
|
}, children: actionError }))] }));
|
|
77
86
|
}
|
|
78
|
-
export function SnapCardV2({ snap, handlers, loading = false, appearance = "dark", colors, borderRadius = 16, showOverflowWarning = false, onValidationError, validationErrorFallback, actionError, plain = false, }) {
|
|
79
|
-
return (_jsx(SnapThemeProvider, { appearance: appearance, colors: colors, children: _jsx(SnapCardV2Inner, { snap: snap, handlers: handlers, loading: loading, borderRadius: borderRadius, showOverflowWarning: showOverflowWarning, onValidationError: onValidationError, validationErrorFallback: validationErrorFallback, actionError: actionError, appearance: appearance, plain: plain }) }));
|
|
87
|
+
export function SnapCardV2({ snap, handlers, loading = false, appearance = "dark", colors, borderRadius = 16, showOverflowWarning = false, onValidationError, validationErrorFallback, actionError, plain = false, loadingOverlay, }) {
|
|
88
|
+
return (_jsx(SnapThemeProvider, { appearance: appearance, colors: colors, children: _jsx(SnapCardV2Inner, { snap: snap, handlers: handlers, loading: loading, borderRadius: borderRadius, showOverflowWarning: showOverflowWarning, onValidationError: onValidationError, validationErrorFallback: validationErrorFallback, actionError: actionError, appearance: appearance, plain: plain, loadingOverlay: loadingOverlay }) }));
|
|
80
89
|
}
|
|
81
90
|
const cardStyles = StyleSheet.create({
|
|
82
91
|
frameRing: { alignSelf: "stretch" },
|
package/dist/ui/catalog.js
CHANGED
|
@@ -50,7 +50,7 @@ export const snapJsonRenderCatalog = defineCatalog(snapJsonRenderSchema, {
|
|
|
50
50
|
},
|
|
51
51
|
item: {
|
|
52
52
|
props: itemProps,
|
|
53
|
-
description: "Content row with title and optional description. Children render in the actions slot (right side) —
|
|
53
|
+
description: "Content row with title and optional description. Children render in the actions slot (right side) — badge, button, and icon elements are all valid. The item itself is not interactive, so avoid navigation-style icons (`chevron-right`, `arrow-right`, `external-link`) that imply the row navigates.",
|
|
54
54
|
},
|
|
55
55
|
item_group: {
|
|
56
56
|
props: itemGroupProps,
|