@farcaster/snap 1.19.0 → 1.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/dist/react/index.d.ts +3 -1
- package/dist/react/index.js +3 -3
- package/dist/react/v1/snap-view.d.ts +2 -1
- package/dist/react/v1/snap-view.js +77 -2
- package/dist/react/v2/snap-view.d.ts +2 -1
- package/dist/react/v2/snap-view.js +11 -3
- package/dist/react-native/components/snap-action-button.js +1 -1
- package/dist/react-native/components/snap-bar-chart.js +1 -1
- package/dist/react-native/components/snap-image.js +0 -1
- package/dist/react-native/components/snap-progress.js +1 -1
- package/dist/react-native/components/snap-text.js +1 -1
- package/dist/react-native/index.d.ts +3 -1
- package/dist/react-native/index.js +3 -3
- package/dist/react-native/theme.js +6 -6
- package/dist/react-native/v1/snap-view.d.ts +2 -1
- package/dist/react-native/v1/snap-view.js +68 -11
- package/dist/react-native/v2/snap-view.d.ts +2 -1
- package/dist/react-native/v2/snap-view.js +25 -21
- package/dist/ui/catalog.d.ts +0 -1
- package/dist/ui/catalog.js +1 -2
- package/package.json +1 -1
- package/src/react/index.tsx +5 -0
- package/src/react/v1/snap-view.tsx +117 -7
- package/src/react/v2/snap-view.tsx +13 -1
- package/src/react-native/components/snap-action-button.tsx +1 -1
- package/src/react-native/components/snap-bar-chart.tsx +1 -1
- package/src/react-native/components/snap-image.tsx +0 -1
- package/src/react-native/components/snap-progress.tsx +1 -1
- package/src/react-native/components/snap-text.tsx +1 -1
- package/src/react-native/index.tsx +5 -0
- package/src/react-native/theme.tsx +6 -6
- package/src/react-native/v1/snap-view.tsx +102 -7
- package/src/react-native/v2/snap-view.tsx +52 -40
- package/src/ui/catalog.ts +1 -2
package/dist/react/index.d.ts
CHANGED
|
@@ -41,7 +41,7 @@ export type SnapActionHandlers = {
|
|
|
41
41
|
buyToken?: string;
|
|
42
42
|
}) => void;
|
|
43
43
|
};
|
|
44
|
-
export declare function SnapCard({ snap, handlers, loading, appearance, maxWidth, showOverflowWarning, onValidationError, validationErrorFallback, actionError, }: {
|
|
44
|
+
export declare function SnapCard({ snap, handlers, loading, appearance, maxWidth, showOverflowWarning, onValidationError, validationErrorFallback, actionError, plain, }: {
|
|
45
45
|
snap: SnapPage;
|
|
46
46
|
handlers: SnapActionHandlers;
|
|
47
47
|
loading?: boolean;
|
|
@@ -53,4 +53,6 @@ export declare function SnapCard({ snap, handlers, loading, appearance, maxWidth
|
|
|
53
53
|
validationErrorFallback?: ReactNode;
|
|
54
54
|
/** Server-side action error message to display inline. */
|
|
55
55
|
actionError?: string | null;
|
|
56
|
+
/** When true, renders without card frame (no border, background, or padding). */
|
|
57
|
+
plain?: boolean;
|
|
56
58
|
}): 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, }) {
|
|
7
|
+
export function SnapCard({ snap, handlers, loading = false, appearance = "dark", maxWidth = 480, showOverflowWarning = false, onValidationError, validationErrorFallback, actionError, plain = false, }) {
|
|
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 }));
|
|
9
|
+
return (_jsx(SnapCardV2, { snap: snap, handlers: handlers, loading: loading, appearance: appearance, maxWidth: maxWidth, showOverflowWarning: showOverflowWarning, onValidationError: onValidationError, validationErrorFallback: validationErrorFallback, actionError: actionError, plain: plain }));
|
|
10
10
|
}
|
|
11
|
-
return (_jsx(SnapCardV1, { snap: snap, handlers: handlers, loading: loading, appearance: appearance, maxWidth: maxWidth, actionError: actionError }));
|
|
11
|
+
return (_jsx(SnapCardV1, { snap: snap, handlers: handlers, loading: loading, appearance: appearance, maxWidth: maxWidth, actionError: actionError, plain: plain }));
|
|
12
12
|
}
|
|
@@ -5,11 +5,12 @@ export declare function SnapViewV1({ snap, handlers, loading, appearance, }: {
|
|
|
5
5
|
loading?: boolean;
|
|
6
6
|
appearance?: "light" | "dark";
|
|
7
7
|
}): import("react/jsx-runtime").JSX.Element;
|
|
8
|
-
export declare function SnapCardV1({ snap, handlers, loading, appearance, maxWidth, actionError, }: {
|
|
8
|
+
export declare function SnapCardV1({ snap, handlers, loading, appearance, maxWidth, actionError, plain, }: {
|
|
9
9
|
snap: SnapPage;
|
|
10
10
|
handlers: SnapActionHandlers;
|
|
11
11
|
loading?: boolean;
|
|
12
12
|
appearance?: "light" | "dark";
|
|
13
13
|
maxWidth?: number;
|
|
14
14
|
actionError?: string | null;
|
|
15
|
+
plain?: boolean;
|
|
15
16
|
}): import("react/jsx-runtime").JSX.Element;
|
|
@@ -1,11 +1,86 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useEffect, useRef, useState } from "react";
|
|
3
4
|
import { SnapViewCore } from "../snap-view-core.js";
|
|
5
|
+
const SNAP_MAX_HEIGHT = 500;
|
|
4
6
|
export function SnapViewV1({ snap, handlers, loading = false, appearance = "dark", }) {
|
|
5
7
|
return (_jsx(SnapViewCore, { snap: snap, handlers: handlers, loading: loading, appearance: appearance }));
|
|
6
8
|
}
|
|
7
|
-
export function SnapCardV1({ snap, handlers, loading = false, appearance = "dark", maxWidth = 480, actionError, }) {
|
|
8
|
-
|
|
9
|
+
export function SnapCardV1({ snap, handlers, loading = false, appearance = "dark", maxWidth = 480, actionError, plain = false, }) {
|
|
10
|
+
const isDark = appearance === "dark";
|
|
11
|
+
const borderColor = isDark ? "rgba(255,255,255,0.1)" : "rgba(0,0,0,0.1)";
|
|
12
|
+
const surfaceBg = isDark ? "rgba(255,255,255,0.05)" : "rgba(0,0,0,0.02)";
|
|
13
|
+
const toggleBg = isDark ? "rgba(255,255,255,0.06)" : "rgba(0,0,0,0.05)";
|
|
14
|
+
const toggleBgHover = isDark
|
|
15
|
+
? "rgba(255,255,255,0.1)"
|
|
16
|
+
: "rgba(0,0,0,0.08)";
|
|
17
|
+
const toggleText = isDark ? "rgba(255,255,255,0.82)" : "rgba(0,0,0,0.72)";
|
|
18
|
+
const contentRef = useRef(null);
|
|
19
|
+
const [isExpandable, setIsExpandable] = useState(false);
|
|
20
|
+
const [isExpanded, setIsExpanded] = useState(false);
|
|
21
|
+
useEffect(() => {
|
|
22
|
+
setIsExpanded(false);
|
|
23
|
+
}, [snap]);
|
|
24
|
+
useEffect(() => {
|
|
25
|
+
const node = contentRef.current;
|
|
26
|
+
if (!node)
|
|
27
|
+
return;
|
|
28
|
+
const measure = () => {
|
|
29
|
+
setIsExpandable(node.scrollHeight > SNAP_MAX_HEIGHT + 1);
|
|
30
|
+
};
|
|
31
|
+
measure();
|
|
32
|
+
if (typeof ResizeObserver === "undefined")
|
|
33
|
+
return;
|
|
34
|
+
const observer = new ResizeObserver(() => {
|
|
35
|
+
measure();
|
|
36
|
+
});
|
|
37
|
+
observer.observe(node);
|
|
38
|
+
return () => observer.disconnect();
|
|
39
|
+
}, [snap, plain]);
|
|
40
|
+
useEffect(() => {
|
|
41
|
+
if (!isExpandable) {
|
|
42
|
+
setIsExpanded(false);
|
|
43
|
+
}
|
|
44
|
+
}, [isExpandable]);
|
|
45
|
+
const isClipped = isExpandable && !isExpanded;
|
|
46
|
+
return (_jsxs("div", { style: {
|
|
47
|
+
position: "relative",
|
|
48
|
+
width: "100%",
|
|
49
|
+
maxWidth,
|
|
50
|
+
overflow: "hidden",
|
|
51
|
+
...(plain ? {} : {
|
|
52
|
+
borderRadius: 16,
|
|
53
|
+
border: `1px solid ${borderColor}`,
|
|
54
|
+
backgroundColor: surfaceBg,
|
|
55
|
+
}),
|
|
56
|
+
}, children: [_jsx("div", { style: isClipped
|
|
57
|
+
? {
|
|
58
|
+
maxHeight: SNAP_MAX_HEIGHT,
|
|
59
|
+
overflow: "hidden",
|
|
60
|
+
}
|
|
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: {
|
|
62
|
+
display: "flex",
|
|
63
|
+
justifyContent: "center",
|
|
64
|
+
padding: plain ? "8px 0 0" : "10px 16px 12px",
|
|
65
|
+
...(plain
|
|
66
|
+
? {}
|
|
67
|
+
: { borderTop: `1px solid ${borderColor}` }),
|
|
68
|
+
}, children: _jsx("button", { type: "button", "aria-expanded": isExpanded, onClick: () => setIsExpanded((value) => !value), style: {
|
|
69
|
+
appearance: "none",
|
|
70
|
+
border: "none",
|
|
71
|
+
borderRadius: 9999,
|
|
72
|
+
backgroundColor: toggleBg,
|
|
73
|
+
color: toggleText,
|
|
74
|
+
padding: "6px 10px",
|
|
75
|
+
fontSize: 13,
|
|
76
|
+
lineHeight: "18px",
|
|
77
|
+
fontWeight: 600,
|
|
78
|
+
cursor: "pointer",
|
|
79
|
+
}, onMouseEnter: (event) => {
|
|
80
|
+
event.currentTarget.style.backgroundColor = toggleBgHover;
|
|
81
|
+
}, onMouseLeave: (event) => {
|
|
82
|
+
event.currentTarget.style.backgroundColor = toggleBg;
|
|
83
|
+
}, children: isExpanded ? "Show less" : "Show more" }) })) : null, actionError && (_jsx("div", { style: {
|
|
9
84
|
padding: "8px 12px",
|
|
10
85
|
fontSize: 13,
|
|
11
86
|
color: appearance === "dark"
|
|
@@ -9,7 +9,7 @@ export declare function SnapViewV2({ snap, handlers, loading, appearance, onVali
|
|
|
9
9
|
onValidationError?: (result: ValidationResult) => void;
|
|
10
10
|
validationErrorFallback?: ReactNode;
|
|
11
11
|
}): import("react/jsx-runtime").JSX.Element | null;
|
|
12
|
-
export declare function SnapCardV2({ snap, handlers, loading, appearance, maxWidth, showOverflowWarning, onValidationError, validationErrorFallback, actionError, }: {
|
|
12
|
+
export declare function SnapCardV2({ snap, handlers, loading, appearance, maxWidth, showOverflowWarning, onValidationError, validationErrorFallback, actionError, plain, }: {
|
|
13
13
|
snap: SnapPage;
|
|
14
14
|
handlers: SnapActionHandlers;
|
|
15
15
|
loading?: boolean;
|
|
@@ -19,4 +19,5 @@ export declare function SnapCardV2({ snap, handlers, loading, appearance, maxWid
|
|
|
19
19
|
onValidationError?: (result: ValidationResult) => void;
|
|
20
20
|
validationErrorFallback?: ReactNode;
|
|
21
21
|
actionError?: string | null;
|
|
22
|
+
plain?: boolean;
|
|
22
23
|
}): import("react/jsx-runtime").JSX.Element;
|
|
@@ -42,16 +42,24 @@ export function SnapViewV2({ snap, handlers, loading = false, appearance = "dark
|
|
|
42
42
|
return (_jsx(SnapViewCore, { snap: snap, handlers: handlers, loading: loading, appearance: appearance }));
|
|
43
43
|
}
|
|
44
44
|
// ─── SnapCardV2 ──────────────────────────────────────
|
|
45
|
-
export function SnapCardV2({ snap, handlers, loading = false, appearance = "dark", maxWidth = 480, showOverflowWarning = false, onValidationError, validationErrorFallback, actionError, }) {
|
|
45
|
+
export function SnapCardV2({ snap, handlers, loading = false, appearance = "dark", maxWidth = 480, showOverflowWarning = false, onValidationError, validationErrorFallback, actionError, plain = false, }) {
|
|
46
46
|
const maxHeight = showOverflowWarning ? SNAP_WARNING_HEIGHT : SNAP_MAX_HEIGHT;
|
|
47
|
-
const
|
|
47
|
+
const isDark = appearance === "dark";
|
|
48
|
+
const bg = isDark ? "rgba(0,0,0,0.85)" : "rgba(255,255,255,0.9)";
|
|
49
|
+
const borderColor = isDark ? "rgba(255,255,255,0.1)" : "rgba(0,0,0,0.1)";
|
|
50
|
+
const surfaceBg = isDark ? "rgba(255,255,255,0.05)" : "rgba(0,0,0,0.02)";
|
|
48
51
|
return (_jsxs(_Fragment, { children: [_jsxs("div", { style: {
|
|
49
52
|
position: "relative",
|
|
50
53
|
width: "100%",
|
|
51
54
|
maxWidth,
|
|
52
55
|
maxHeight,
|
|
53
56
|
overflow: "hidden",
|
|
54
|
-
|
|
57
|
+
...(plain ? {} : {
|
|
58
|
+
borderRadius: 16,
|
|
59
|
+
border: `1px solid ${borderColor}`,
|
|
60
|
+
backgroundColor: surfaceBg,
|
|
61
|
+
}),
|
|
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: {
|
|
55
63
|
position: "absolute",
|
|
56
64
|
top: SNAP_MAX_HEIGHT,
|
|
57
65
|
left: 0,
|
|
@@ -50,7 +50,7 @@ export function SnapActionButton({ element, emit, }) {
|
|
|
50
50
|
: null, _jsx(Text, { style: { color: textColor, fontSize: 14, lineHeight: 18, fontWeight: "600" }, children: label }), showExternalIcon ? (_jsx(ExternalLink, { size: 14, color: iconColor, style: { opacity: 0.6 } })) : null] }) }));
|
|
51
51
|
}
|
|
52
52
|
const styles = StyleSheet.create({
|
|
53
|
-
outer: {
|
|
53
|
+
outer: { minWidth: 0 },
|
|
54
54
|
btn: {
|
|
55
55
|
paddingHorizontal: 16,
|
|
56
56
|
borderRadius: 10,
|
|
@@ -30,7 +30,7 @@ export function SnapBarChart({ element: { props }, }) {
|
|
|
30
30
|
}) }));
|
|
31
31
|
}
|
|
32
32
|
const styles = StyleSheet.create({
|
|
33
|
-
wrap: {
|
|
33
|
+
wrap: { width: "100%", gap: 8 },
|
|
34
34
|
row: { flexDirection: "row", alignItems: "center", gap: 8 },
|
|
35
35
|
label: { width: 80, fontSize: 12, lineHeight: 16, textAlign: "right" },
|
|
36
36
|
track: { flex: 1, height: 10, borderRadius: 9999, overflow: "hidden" },
|
|
@@ -12,7 +12,7 @@ export function SnapProgress({ element: { props }, }) {
|
|
|
12
12
|
return (_jsxs(View, { style: styles.wrap, children: [label ? (_jsx(Text, { style: [styles.label, { color: colors.textSecondary }], children: label })) : null, _jsx(View, { style: [styles.track, { backgroundColor: colors.muted }], children: _jsx(View, { style: [styles.fill, { width: `${percent}%`, backgroundColor: accentHex }] }) })] }));
|
|
13
13
|
}
|
|
14
14
|
const styles = StyleSheet.create({
|
|
15
|
-
wrap: {
|
|
15
|
+
wrap: { width: "100%", gap: 4 },
|
|
16
16
|
label: { fontSize: 13, lineHeight: 18 },
|
|
17
17
|
track: {
|
|
18
18
|
height: 10,
|
|
@@ -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, }: {
|
|
10
|
+
export declare function SnapCard({ snap, handlers, loading, appearance, colors, borderRadius, showOverflowWarning, onValidationError, validationErrorFallback, actionError, plain, }: {
|
|
11
11
|
snap: SnapPage;
|
|
12
12
|
handlers: SnapActionHandlers;
|
|
13
13
|
loading?: boolean;
|
|
@@ -23,4 +23,6 @@ export declare function SnapCard({ snap, handlers, loading, appearance, colors,
|
|
|
23
23
|
validationErrorFallback?: ReactNode;
|
|
24
24
|
/** Server-side action error message to display inline. */
|
|
25
25
|
actionError?: string | null;
|
|
26
|
+
/** When true, renders without card frame (no border, background, or padding). */
|
|
27
|
+
plain?: boolean;
|
|
26
28
|
}): 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, }) {
|
|
10
|
+
export function SnapCard({ snap, handlers, loading = false, appearance = "dark", colors, borderRadius = 16, showOverflowWarning = false, onValidationError, validationErrorFallback, actionError, plain = false, }) {
|
|
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 }));
|
|
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 }));
|
|
13
13
|
}
|
|
14
|
-
return (_jsx(SnapCardV1, { snap: snap, handlers: handlers, loading: loading, appearance: appearance, colors: colors, borderRadius: borderRadius, actionError: actionError }));
|
|
14
|
+
return (_jsx(SnapCardV1, { snap: snap, handlers: handlers, loading: loading, appearance: appearance, colors: colors, borderRadius: borderRadius, actionError: actionError, plain: plain }));
|
|
15
15
|
}
|
|
@@ -3,9 +3,9 @@ import { createContext, useContext, useMemo } from "react";
|
|
|
3
3
|
const DEFAULT_LIGHT = {
|
|
4
4
|
bg: "#dfe3e8",
|
|
5
5
|
surface: "#ffffff",
|
|
6
|
-
text: "
|
|
7
|
-
textSecondary: "
|
|
8
|
-
border: "
|
|
6
|
+
text: "rgba(0,0,0,0.9)",
|
|
7
|
+
textSecondary: "rgba(0,0,0,0.5)",
|
|
8
|
+
border: "rgba(0,0,0,0.1)",
|
|
9
9
|
inputBg: "rgba(0,0,0,0.06)",
|
|
10
10
|
muted: "rgba(0,0,0,0.08)",
|
|
11
11
|
mutedSubtle: "rgba(0,0,0,0.04)",
|
|
@@ -15,9 +15,9 @@ const DEFAULT_LIGHT = {
|
|
|
15
15
|
const DEFAULT_DARK = {
|
|
16
16
|
bg: "#111318",
|
|
17
17
|
surface: "#1a1d24",
|
|
18
|
-
text: "
|
|
19
|
-
textSecondary: "
|
|
20
|
-
border: "
|
|
18
|
+
text: "rgba(255,255,255,0.9)",
|
|
19
|
+
textSecondary: "rgba(255,255,255,0.5)",
|
|
20
|
+
border: "rgba(255,255,255,0.1)",
|
|
21
21
|
inputBg: "rgba(255,255,255,0.04)",
|
|
22
22
|
muted: "rgba(255,255,255,0.06)",
|
|
23
23
|
mutedSubtle: "rgba(255,255,255,0.03)",
|
|
@@ -12,7 +12,7 @@ export declare function SnapViewV1({ snap, handlers, loading, appearance, colors
|
|
|
12
12
|
appearance?: "light" | "dark";
|
|
13
13
|
colors?: Partial<SnapNativeColors>;
|
|
14
14
|
}): import("react").JSX.Element;
|
|
15
|
-
export declare function SnapCardV1({ snap, handlers, loading, appearance, colors, borderRadius, actionError, }: {
|
|
15
|
+
export declare function SnapCardV1({ snap, handlers, loading, appearance, colors, borderRadius, actionError, plain, }: {
|
|
16
16
|
snap: SnapPage;
|
|
17
17
|
handlers: SnapActionHandlers;
|
|
18
18
|
loading?: boolean;
|
|
@@ -20,4 +20,5 @@ export declare function SnapCardV1({ snap, handlers, loading, appearance, colors
|
|
|
20
20
|
colors?: Partial<SnapNativeColors>;
|
|
21
21
|
borderRadius?: number;
|
|
22
22
|
actionError?: string | null;
|
|
23
|
+
plain?: boolean;
|
|
23
24
|
}): import("react").JSX.Element;
|
|
@@ -1,25 +1,56 @@
|
|
|
1
|
-
import { jsx as _jsx,
|
|
2
|
-
import {
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useState } from "react";
|
|
3
|
+
import { View, Text, StyleSheet, Pressable } from "react-native";
|
|
3
4
|
import { SnapThemeProvider, useSnapTheme } from "../theme.js";
|
|
4
5
|
import { SnapViewCoreInner } from "../snap-view-core.js";
|
|
5
|
-
|
|
6
|
+
const SNAP_MAX_HEIGHT = 500;
|
|
7
|
+
// ─── SnapViewV1 (no validation) ──────────────────────
|
|
6
8
|
export function SnapViewV1Inner({ snap, handlers, loading = false, }) {
|
|
7
9
|
return (_jsx(SnapViewCoreInner, { snap: snap, handlers: handlers, loading: loading }));
|
|
8
10
|
}
|
|
9
11
|
export function SnapViewV1({ snap, handlers, loading = false, appearance = "dark", colors, }) {
|
|
10
12
|
return (_jsx(SnapThemeProvider, { appearance: appearance, colors: colors, children: _jsx(SnapViewV1Inner, { snap: snap, handlers: handlers, loading: loading }) }));
|
|
11
13
|
}
|
|
12
|
-
// ─── SnapCardV1 (card frame
|
|
13
|
-
function SnapCardV1Inner({ snap, handlers, loading = false, borderRadius, actionError, appearance, }) {
|
|
14
|
+
// ─── SnapCardV1 (card frame with expandable clipping) ──
|
|
15
|
+
function SnapCardV1Inner({ snap, handlers, loading = false, borderRadius, actionError, appearance, plain, }) {
|
|
14
16
|
const { colors } = useSnapTheme();
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
17
|
+
const [contentHeight, setContentHeight] = useState(0);
|
|
18
|
+
const [isExpanded, setIsExpanded] = useState(false);
|
|
19
|
+
useEffect(() => {
|
|
20
|
+
setIsExpanded(false);
|
|
21
|
+
setContentHeight(0);
|
|
22
|
+
}, [snap]);
|
|
23
|
+
const isExpandable = contentHeight > SNAP_MAX_HEIGHT + 1;
|
|
24
|
+
const isClipped = isExpandable && !isExpanded;
|
|
25
|
+
return (_jsxs(_Fragment, { children: [_jsx(View, { style: cardStyles.frameRing, children: _jsxs(View, { style: [
|
|
26
|
+
plain ? undefined : cardStyles.card,
|
|
27
|
+
plain ? undefined : {
|
|
18
28
|
borderRadius,
|
|
19
29
|
borderColor: colors.border,
|
|
20
30
|
backgroundColor: colors.surface,
|
|
21
31
|
},
|
|
22
|
-
], children: _jsx(View, { style:
|
|
32
|
+
], children: [_jsx(View, { style: isClipped ? { maxHeight: SNAP_MAX_HEIGHT, overflow: "hidden" } : undefined, children: _jsx(View, { collapsable: false, onLayout: (event) => {
|
|
33
|
+
const nextHeight = Math.round(event.nativeEvent.layout.height);
|
|
34
|
+
setContentHeight((currentHeight) => isClipped
|
|
35
|
+
? Math.max(currentHeight, nextHeight)
|
|
36
|
+
: currentHeight === nextHeight
|
|
37
|
+
? currentHeight
|
|
38
|
+
: nextHeight);
|
|
39
|
+
}, style: plain ? undefined : cardStyles.body, children: _jsx(SnapViewV1Inner, { snap: snap, handlers: handlers, loading: loading }) }) }), isExpandable ? (_jsx(View, { style: [
|
|
40
|
+
cardStyles.expandRow,
|
|
41
|
+
plain
|
|
42
|
+
? cardStyles.expandRowPlain
|
|
43
|
+
: { borderTopColor: colors.border },
|
|
44
|
+
], children: _jsx(Pressable, { style: ({ pressed }) => [
|
|
45
|
+
cardStyles.expandButton,
|
|
46
|
+
{
|
|
47
|
+
backgroundColor: pressed
|
|
48
|
+
? colors.mutedHover
|
|
49
|
+
: colors.muted,
|
|
50
|
+
},
|
|
51
|
+
], onPress: () => {
|
|
52
|
+
setIsExpanded((value) => !value);
|
|
53
|
+
}, children: _jsx(Text, { style: [cardStyles.expandButtonText, { color: colors.text }], children: isExpanded ? "Show less" : "Show more" }) }) })) : null] }) }), actionError && (_jsx(Text, { style: [
|
|
23
54
|
cardStyles.actionError,
|
|
24
55
|
{
|
|
25
56
|
color: appearance === "dark"
|
|
@@ -28,12 +59,38 @@ function SnapCardV1Inner({ snap, handlers, loading = false, borderRadius, action
|
|
|
28
59
|
},
|
|
29
60
|
], children: actionError }))] }));
|
|
30
61
|
}
|
|
31
|
-
export function SnapCardV1({ snap, handlers, loading = false, appearance = "dark", colors, borderRadius = 16, actionError, }) {
|
|
32
|
-
return (_jsx(SnapThemeProvider, { appearance: appearance, colors: colors, children: _jsx(SnapCardV1Inner, { snap: snap, handlers: handlers, loading: loading, borderRadius: borderRadius, actionError: actionError, appearance: appearance }) }));
|
|
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 }) }));
|
|
33
64
|
}
|
|
34
65
|
const cardStyles = StyleSheet.create({
|
|
35
66
|
frameRing: { alignSelf: "stretch" },
|
|
36
67
|
card: { overflow: "hidden", borderWidth: 1, minHeight: 120 },
|
|
37
68
|
body: { paddingHorizontal: 16, paddingVertical: 16 },
|
|
69
|
+
expandRow: {
|
|
70
|
+
alignItems: "center",
|
|
71
|
+
paddingHorizontal: 16,
|
|
72
|
+
paddingTop: 10,
|
|
73
|
+
paddingBottom: 12,
|
|
74
|
+
borderTopWidth: StyleSheet.hairlineWidth,
|
|
75
|
+
},
|
|
76
|
+
expandRowPlain: {
|
|
77
|
+
paddingHorizontal: 0,
|
|
78
|
+
paddingTop: 8,
|
|
79
|
+
paddingBottom: 0,
|
|
80
|
+
borderTopWidth: 0,
|
|
81
|
+
},
|
|
82
|
+
expandButton: {
|
|
83
|
+
minWidth: 92,
|
|
84
|
+
alignItems: "center",
|
|
85
|
+
justifyContent: "center",
|
|
86
|
+
borderRadius: 9999,
|
|
87
|
+
paddingHorizontal: 10,
|
|
88
|
+
paddingVertical: 6,
|
|
89
|
+
},
|
|
90
|
+
expandButtonText: {
|
|
91
|
+
fontSize: 13,
|
|
92
|
+
lineHeight: 18,
|
|
93
|
+
fontWeight: "600",
|
|
94
|
+
},
|
|
38
95
|
actionError: { paddingHorizontal: 12, paddingVertical: 8, fontSize: 13 },
|
|
39
96
|
});
|
|
@@ -18,7 +18,7 @@ export declare function SnapViewV2({ snap, handlers, loading, appearance, colors
|
|
|
18
18
|
onValidationError?: (result: ValidationResult) => void;
|
|
19
19
|
validationErrorFallback?: ReactNode;
|
|
20
20
|
}): import("react").JSX.Element;
|
|
21
|
-
export declare function SnapCardV2({ snap, handlers, loading, appearance, colors, borderRadius, showOverflowWarning, onValidationError, validationErrorFallback, actionError, }: {
|
|
21
|
+
export declare function SnapCardV2({ snap, handlers, loading, appearance, colors, borderRadius, showOverflowWarning, onValidationError, validationErrorFallback, actionError, plain, }: {
|
|
22
22
|
snap: SnapPage;
|
|
23
23
|
handlers: SnapActionHandlers;
|
|
24
24
|
loading?: boolean;
|
|
@@ -29,4 +29,5 @@ export declare function SnapCardV2({ snap, handlers, loading, appearance, colors
|
|
|
29
29
|
onValidationError?: (result: ValidationResult) => void;
|
|
30
30
|
validationErrorFallback?: ReactNode;
|
|
31
31
|
actionError?: string | null;
|
|
32
|
+
plain?: boolean;
|
|
32
33
|
}): import("react").JSX.Element;
|
|
@@ -50,32 +50,36 @@ export function SnapViewV2({ snap, handlers, loading = false, appearance = "dark
|
|
|
50
50
|
return (_jsx(SnapThemeProvider, { appearance: appearance, colors: colors, children: _jsx(SnapViewV2Inner, { snap: snap, handlers: handlers, loading: loading, onValidationError: onValidationError, validationErrorFallback: validationErrorFallback }) }));
|
|
51
51
|
}
|
|
52
52
|
// ─── SnapCardV2 (card frame + height limits) ─────────
|
|
53
|
-
function SnapCardV2Inner({ snap, handlers, loading, borderRadius, showOverflowWarning, onValidationError, validationErrorFallback, actionError, appearance, }) {
|
|
53
|
+
function SnapCardV2Inner({ snap, handlers, loading, borderRadius, showOverflowWarning, onValidationError, validationErrorFallback, actionError, appearance, plain, }) {
|
|
54
54
|
const { colors } = useSnapTheme();
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
55
|
+
const clipHeight = showOverflowWarning ? undefined : SNAP_MAX_HEIGHT;
|
|
56
|
+
const content = (_jsx(SnapViewV2Inner, { snap: snap, handlers: handlers, loading: loading, onValidationError: onValidationError, validationErrorFallback: validationErrorFallback }));
|
|
57
|
+
if (plain) {
|
|
58
|
+
return content;
|
|
59
|
+
}
|
|
60
|
+
return (_jsxs(_Fragment, { children: [_jsxs(View, { style: {
|
|
61
|
+
borderRadius,
|
|
62
|
+
borderWidth: 1,
|
|
63
|
+
borderColor: colors.border,
|
|
64
|
+
backgroundColor: colors.surface,
|
|
65
|
+
maxHeight: clipHeight,
|
|
66
|
+
overflow: "hidden",
|
|
67
|
+
minHeight: 120,
|
|
68
|
+
}, children: [_jsx(View, { style: { paddingHorizontal: 16, paddingVertical: 16 }, children: content }), showOverflowWarning && (_jsxs(View, { style: { position: "absolute", top: SNAP_MAX_HEIGHT, left: 0, right: 0, bottom: 0, 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)" } })] }))] }), actionError && (_jsx(Text, { style: {
|
|
69
|
+
paddingHorizontal: 12,
|
|
70
|
+
paddingVertical: 8,
|
|
71
|
+
fontSize: 13,
|
|
72
|
+
color: appearance === "dark"
|
|
73
|
+
? "rgba(255,100,100,0.9)"
|
|
74
|
+
: "rgba(200,0,0,0.8)",
|
|
75
|
+
}, children: actionError }))] }));
|
|
72
76
|
}
|
|
73
|
-
export function SnapCardV2({ snap, handlers, loading = false, appearance = "dark", colors, borderRadius = 16, showOverflowWarning = false, onValidationError, validationErrorFallback, actionError, }) {
|
|
74
|
-
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 }) }));
|
|
77
|
+
export function SnapCardV2({ snap, handlers, loading = false, appearance = "dark", colors, borderRadius = 16, showOverflowWarning = false, onValidationError, validationErrorFallback, actionError, plain = false, }) {
|
|
78
|
+
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 }) }));
|
|
75
79
|
}
|
|
76
80
|
const cardStyles = StyleSheet.create({
|
|
77
81
|
frameRing: { alignSelf: "stretch" },
|
|
78
|
-
card: {
|
|
82
|
+
card: { borderWidth: 1, minHeight: 120, overflow: "hidden" },
|
|
79
83
|
body: { paddingHorizontal: 16, paddingVertical: 16 },
|
|
80
84
|
actionError: { paddingHorizontal: 12, paddingVertical: 8, fontSize: 13 },
|
|
81
85
|
warningOverlay: {
|
package/dist/ui/catalog.d.ts
CHANGED
package/dist/ui/catalog.js
CHANGED
|
@@ -99,10 +99,9 @@ export const snapJsonRenderCatalog = defineCatalog(snapJsonRenderSchema, {
|
|
|
99
99
|
params: z.object({ target: z.string() }),
|
|
100
100
|
},
|
|
101
101
|
open_url: {
|
|
102
|
-
description: "Open
|
|
102
|
+
description: "Open URL in browser.",
|
|
103
103
|
params: z.object({
|
|
104
104
|
target: z.string(),
|
|
105
|
-
isSnap: z.boolean().optional(),
|
|
106
105
|
}),
|
|
107
106
|
},
|
|
108
107
|
open_mini_app: {
|
package/package.json
CHANGED
package/src/react/index.tsx
CHANGED
|
@@ -57,6 +57,7 @@ export function SnapCard({
|
|
|
57
57
|
onValidationError,
|
|
58
58
|
validationErrorFallback,
|
|
59
59
|
actionError,
|
|
60
|
+
plain = false,
|
|
60
61
|
}: {
|
|
61
62
|
snap: SnapPage;
|
|
62
63
|
handlers: SnapActionHandlers;
|
|
@@ -69,6 +70,8 @@ export function SnapCard({
|
|
|
69
70
|
validationErrorFallback?: ReactNode;
|
|
70
71
|
/** Server-side action error message to display inline. */
|
|
71
72
|
actionError?: string | null;
|
|
73
|
+
/** When true, renders without card frame (no border, background, or padding). */
|
|
74
|
+
plain?: boolean;
|
|
72
75
|
}) {
|
|
73
76
|
if (snap.version === SPEC_VERSION_2) {
|
|
74
77
|
return (
|
|
@@ -82,6 +85,7 @@ export function SnapCard({
|
|
|
82
85
|
onValidationError={onValidationError}
|
|
83
86
|
validationErrorFallback={validationErrorFallback}
|
|
84
87
|
actionError={actionError}
|
|
88
|
+
plain={plain}
|
|
85
89
|
/>
|
|
86
90
|
);
|
|
87
91
|
}
|
|
@@ -94,6 +98,7 @@ export function SnapCard({
|
|
|
94
98
|
appearance={appearance}
|
|
95
99
|
maxWidth={maxWidth}
|
|
96
100
|
actionError={actionError}
|
|
101
|
+
plain={plain}
|
|
97
102
|
/>
|
|
98
103
|
);
|
|
99
104
|
}
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
+
import { useEffect, useRef, useState } from "react";
|
|
3
4
|
import { SnapViewCore } from "../snap-view-core";
|
|
4
5
|
import type { SnapPage, SnapActionHandlers } from "../index";
|
|
5
6
|
|
|
7
|
+
const SNAP_MAX_HEIGHT = 500;
|
|
8
|
+
|
|
6
9
|
export function SnapViewV1({
|
|
7
10
|
snap,
|
|
8
11
|
handlers,
|
|
@@ -31,6 +34,7 @@ export function SnapCardV1({
|
|
|
31
34
|
appearance = "dark",
|
|
32
35
|
maxWidth = 480,
|
|
33
36
|
actionError,
|
|
37
|
+
plain = false,
|
|
34
38
|
}: {
|
|
35
39
|
snap: SnapPage;
|
|
36
40
|
handlers: SnapActionHandlers;
|
|
@@ -38,15 +42,121 @@ export function SnapCardV1({
|
|
|
38
42
|
appearance?: "light" | "dark";
|
|
39
43
|
maxWidth?: number;
|
|
40
44
|
actionError?: string | null;
|
|
45
|
+
plain?: boolean;
|
|
41
46
|
}) {
|
|
47
|
+
const isDark = appearance === "dark";
|
|
48
|
+
const borderColor = isDark ? "rgba(255,255,255,0.1)" : "rgba(0,0,0,0.1)";
|
|
49
|
+
const surfaceBg = isDark ? "rgba(255,255,255,0.05)" : "rgba(0,0,0,0.02)";
|
|
50
|
+
const toggleBg = isDark ? "rgba(255,255,255,0.06)" : "rgba(0,0,0,0.05)";
|
|
51
|
+
const toggleBgHover = isDark
|
|
52
|
+
? "rgba(255,255,255,0.1)"
|
|
53
|
+
: "rgba(0,0,0,0.08)";
|
|
54
|
+
const toggleText = isDark ? "rgba(255,255,255,0.82)" : "rgba(0,0,0,0.72)";
|
|
55
|
+
const contentRef = useRef<HTMLDivElement>(null);
|
|
56
|
+
const [isExpandable, setIsExpandable] = useState(false);
|
|
57
|
+
const [isExpanded, setIsExpanded] = useState(false);
|
|
58
|
+
|
|
59
|
+
useEffect(() => {
|
|
60
|
+
setIsExpanded(false);
|
|
61
|
+
}, [snap]);
|
|
62
|
+
|
|
63
|
+
useEffect(() => {
|
|
64
|
+
const node = contentRef.current;
|
|
65
|
+
if (!node) return;
|
|
66
|
+
|
|
67
|
+
const measure = () => {
|
|
68
|
+
setIsExpandable(node.scrollHeight > SNAP_MAX_HEIGHT + 1);
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
measure();
|
|
72
|
+
|
|
73
|
+
if (typeof ResizeObserver === "undefined") return;
|
|
74
|
+
const observer = new ResizeObserver(() => {
|
|
75
|
+
measure();
|
|
76
|
+
});
|
|
77
|
+
observer.observe(node);
|
|
78
|
+
return () => observer.disconnect();
|
|
79
|
+
}, [snap, plain]);
|
|
80
|
+
|
|
81
|
+
useEffect(() => {
|
|
82
|
+
if (!isExpandable) {
|
|
83
|
+
setIsExpanded(false);
|
|
84
|
+
}
|
|
85
|
+
}, [isExpandable]);
|
|
86
|
+
|
|
87
|
+
const isClipped = isExpandable && !isExpanded;
|
|
88
|
+
|
|
42
89
|
return (
|
|
43
|
-
<div
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
90
|
+
<div
|
|
91
|
+
style={{
|
|
92
|
+
position: "relative",
|
|
93
|
+
width: "100%",
|
|
94
|
+
maxWidth,
|
|
95
|
+
overflow: "hidden",
|
|
96
|
+
...(plain ? {} : {
|
|
97
|
+
borderRadius: 16,
|
|
98
|
+
border: `1px solid ${borderColor}`,
|
|
99
|
+
backgroundColor: surfaceBg,
|
|
100
|
+
}),
|
|
101
|
+
}}
|
|
102
|
+
>
|
|
103
|
+
<div
|
|
104
|
+
style={
|
|
105
|
+
isClipped
|
|
106
|
+
? {
|
|
107
|
+
maxHeight: SNAP_MAX_HEIGHT,
|
|
108
|
+
overflow: "hidden",
|
|
109
|
+
}
|
|
110
|
+
: undefined
|
|
111
|
+
}
|
|
112
|
+
>
|
|
113
|
+
<div ref={contentRef} style={plain ? undefined : { padding: 16 }}>
|
|
114
|
+
<SnapViewV1
|
|
115
|
+
snap={snap}
|
|
116
|
+
handlers={handlers}
|
|
117
|
+
loading={loading}
|
|
118
|
+
appearance={appearance}
|
|
119
|
+
/>
|
|
120
|
+
</div>
|
|
121
|
+
</div>
|
|
122
|
+
{isExpandable ? (
|
|
123
|
+
<div
|
|
124
|
+
style={{
|
|
125
|
+
display: "flex",
|
|
126
|
+
justifyContent: "center",
|
|
127
|
+
padding: plain ? "8px 0 0" : "10px 16px 12px",
|
|
128
|
+
...(plain
|
|
129
|
+
? {}
|
|
130
|
+
: { borderTop: `1px solid ${borderColor}` }),
|
|
131
|
+
}}
|
|
132
|
+
>
|
|
133
|
+
<button
|
|
134
|
+
type="button"
|
|
135
|
+
aria-expanded={isExpanded}
|
|
136
|
+
onClick={() => setIsExpanded((value) => !value)}
|
|
137
|
+
style={{
|
|
138
|
+
appearance: "none",
|
|
139
|
+
border: "none",
|
|
140
|
+
borderRadius: 9999,
|
|
141
|
+
backgroundColor: toggleBg,
|
|
142
|
+
color: toggleText,
|
|
143
|
+
padding: "6px 10px",
|
|
144
|
+
fontSize: 13,
|
|
145
|
+
lineHeight: "18px",
|
|
146
|
+
fontWeight: 600,
|
|
147
|
+
cursor: "pointer",
|
|
148
|
+
}}
|
|
149
|
+
onMouseEnter={(event) => {
|
|
150
|
+
event.currentTarget.style.backgroundColor = toggleBgHover;
|
|
151
|
+
}}
|
|
152
|
+
onMouseLeave={(event) => {
|
|
153
|
+
event.currentTarget.style.backgroundColor = toggleBg;
|
|
154
|
+
}}
|
|
155
|
+
>
|
|
156
|
+
{isExpanded ? "Show less" : "Show more"}
|
|
157
|
+
</button>
|
|
158
|
+
</div>
|
|
159
|
+
) : null}
|
|
50
160
|
{actionError && (
|
|
51
161
|
<div
|
|
52
162
|
style={{
|
|
@@ -95,6 +95,7 @@ export function SnapCardV2({
|
|
|
95
95
|
onValidationError,
|
|
96
96
|
validationErrorFallback,
|
|
97
97
|
actionError,
|
|
98
|
+
plain = false,
|
|
98
99
|
}: {
|
|
99
100
|
snap: SnapPage;
|
|
100
101
|
handlers: SnapActionHandlers;
|
|
@@ -105,9 +106,13 @@ export function SnapCardV2({
|
|
|
105
106
|
onValidationError?: (result: ValidationResult) => void;
|
|
106
107
|
validationErrorFallback?: ReactNode;
|
|
107
108
|
actionError?: string | null;
|
|
109
|
+
plain?: boolean;
|
|
108
110
|
}) {
|
|
109
111
|
const maxHeight = showOverflowWarning ? SNAP_WARNING_HEIGHT : SNAP_MAX_HEIGHT;
|
|
110
|
-
const
|
|
112
|
+
const isDark = appearance === "dark";
|
|
113
|
+
const bg = isDark ? "rgba(0,0,0,0.85)" : "rgba(255,255,255,0.9)";
|
|
114
|
+
const borderColor = isDark ? "rgba(255,255,255,0.1)" : "rgba(0,0,0,0.1)";
|
|
115
|
+
const surfaceBg = isDark ? "rgba(255,255,255,0.05)" : "rgba(0,0,0,0.02)";
|
|
111
116
|
|
|
112
117
|
return (
|
|
113
118
|
<>
|
|
@@ -118,8 +123,14 @@ export function SnapCardV2({
|
|
|
118
123
|
maxWidth,
|
|
119
124
|
maxHeight,
|
|
120
125
|
overflow: "hidden",
|
|
126
|
+
...(plain ? {} : {
|
|
127
|
+
borderRadius: 16,
|
|
128
|
+
border: `1px solid ${borderColor}`,
|
|
129
|
+
backgroundColor: surfaceBg,
|
|
130
|
+
}),
|
|
121
131
|
}}
|
|
122
132
|
>
|
|
133
|
+
<div style={plain ? undefined : { padding: 16 }}>
|
|
123
134
|
<SnapViewV2
|
|
124
135
|
snap={snap}
|
|
125
136
|
handlers={handlers}
|
|
@@ -128,6 +139,7 @@ export function SnapCardV2({
|
|
|
128
139
|
onValidationError={onValidationError}
|
|
129
140
|
validationErrorFallback={validationErrorFallback}
|
|
130
141
|
/>
|
|
142
|
+
</div>
|
|
131
143
|
{showOverflowWarning && (
|
|
132
144
|
<div
|
|
133
145
|
style={{
|
|
@@ -64,7 +64,7 @@ export function SnapBarChart({
|
|
|
64
64
|
}
|
|
65
65
|
|
|
66
66
|
const styles = StyleSheet.create({
|
|
67
|
-
wrap: {
|
|
67
|
+
wrap: { width: "100%", gap: 8 },
|
|
68
68
|
row: { flexDirection: "row", alignItems: "center", gap: 8 },
|
|
69
69
|
label: { width: 80, fontSize: 12, lineHeight: 16, textAlign: "right" },
|
|
70
70
|
track: { flex: 1, height: 10, borderRadius: 9999, overflow: "hidden" },
|
|
@@ -30,6 +30,7 @@ export function SnapCard({
|
|
|
30
30
|
onValidationError,
|
|
31
31
|
validationErrorFallback,
|
|
32
32
|
actionError,
|
|
33
|
+
plain = false,
|
|
33
34
|
}: {
|
|
34
35
|
snap: SnapPage;
|
|
35
36
|
handlers: SnapActionHandlers;
|
|
@@ -46,6 +47,8 @@ export function SnapCard({
|
|
|
46
47
|
validationErrorFallback?: ReactNode;
|
|
47
48
|
/** Server-side action error message to display inline. */
|
|
48
49
|
actionError?: string | null;
|
|
50
|
+
/** When true, renders without card frame (no border, background, or padding). */
|
|
51
|
+
plain?: boolean;
|
|
49
52
|
}) {
|
|
50
53
|
if (snap.version === SPEC_VERSION_2) {
|
|
51
54
|
return (
|
|
@@ -60,6 +63,7 @@ export function SnapCard({
|
|
|
60
63
|
onValidationError={onValidationError}
|
|
61
64
|
validationErrorFallback={validationErrorFallback}
|
|
62
65
|
actionError={actionError}
|
|
66
|
+
plain={plain}
|
|
63
67
|
/>
|
|
64
68
|
);
|
|
65
69
|
}
|
|
@@ -73,6 +77,7 @@ export function SnapCard({
|
|
|
73
77
|
colors={colors}
|
|
74
78
|
borderRadius={borderRadius}
|
|
75
79
|
actionError={actionError}
|
|
80
|
+
plain={plain}
|
|
76
81
|
/>
|
|
77
82
|
);
|
|
78
83
|
}
|
|
@@ -21,9 +21,9 @@ export type SnapNativeColors = {
|
|
|
21
21
|
const DEFAULT_LIGHT: SnapNativeColors = {
|
|
22
22
|
bg: "#dfe3e8",
|
|
23
23
|
surface: "#ffffff",
|
|
24
|
-
text: "
|
|
25
|
-
textSecondary: "
|
|
26
|
-
border: "
|
|
24
|
+
text: "rgba(0,0,0,0.9)",
|
|
25
|
+
textSecondary: "rgba(0,0,0,0.5)",
|
|
26
|
+
border: "rgba(0,0,0,0.1)",
|
|
27
27
|
inputBg: "rgba(0,0,0,0.06)",
|
|
28
28
|
muted: "rgba(0,0,0,0.08)",
|
|
29
29
|
mutedSubtle: "rgba(0,0,0,0.04)",
|
|
@@ -34,9 +34,9 @@ const DEFAULT_LIGHT: SnapNativeColors = {
|
|
|
34
34
|
const DEFAULT_DARK: SnapNativeColors = {
|
|
35
35
|
bg: "#111318",
|
|
36
36
|
surface: "#1a1d24",
|
|
37
|
-
text: "
|
|
38
|
-
textSecondary: "
|
|
39
|
-
border: "
|
|
37
|
+
text: "rgba(255,255,255,0.9)",
|
|
38
|
+
textSecondary: "rgba(255,255,255,0.5)",
|
|
39
|
+
border: "rgba(255,255,255,0.1)",
|
|
40
40
|
inputBg: "rgba(255,255,255,0.04)",
|
|
41
41
|
muted: "rgba(255,255,255,0.06)",
|
|
42
42
|
mutedSubtle: "rgba(255,255,255,0.03)",
|
|
@@ -1,9 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useEffect, useState } from "react";
|
|
2
|
+
import { View, Text, StyleSheet, Pressable } from "react-native";
|
|
2
3
|
import { SnapThemeProvider, useSnapTheme, type SnapNativeColors } from "../theme";
|
|
3
4
|
import { SnapViewCoreInner } from "../snap-view-core";
|
|
4
5
|
import type { SnapPage, SnapActionHandlers } from "../types";
|
|
5
6
|
|
|
6
|
-
|
|
7
|
+
const SNAP_MAX_HEIGHT = 500;
|
|
8
|
+
|
|
9
|
+
// ─── SnapViewV1 (no validation) ──────────────────────
|
|
7
10
|
|
|
8
11
|
export function SnapViewV1Inner({
|
|
9
12
|
snap,
|
|
@@ -39,7 +42,7 @@ export function SnapViewV1({
|
|
|
39
42
|
);
|
|
40
43
|
}
|
|
41
44
|
|
|
42
|
-
// ─── SnapCardV1 (card frame
|
|
45
|
+
// ─── SnapCardV1 (card frame with expandable clipping) ──
|
|
43
46
|
|
|
44
47
|
function SnapCardV1Inner({
|
|
45
48
|
snap,
|
|
@@ -48,6 +51,7 @@ function SnapCardV1Inner({
|
|
|
48
51
|
borderRadius,
|
|
49
52
|
actionError,
|
|
50
53
|
appearance,
|
|
54
|
+
plain,
|
|
51
55
|
}: {
|
|
52
56
|
snap: SnapPage;
|
|
53
57
|
handlers: SnapActionHandlers;
|
|
@@ -55,25 +59,87 @@ function SnapCardV1Inner({
|
|
|
55
59
|
borderRadius: number;
|
|
56
60
|
actionError?: string | null;
|
|
57
61
|
appearance: "light" | "dark";
|
|
62
|
+
plain: boolean;
|
|
58
63
|
}) {
|
|
59
64
|
const { colors } = useSnapTheme();
|
|
65
|
+
const [contentHeight, setContentHeight] = useState(0);
|
|
66
|
+
const [isExpanded, setIsExpanded] = useState(false);
|
|
67
|
+
|
|
68
|
+
useEffect(() => {
|
|
69
|
+
setIsExpanded(false);
|
|
70
|
+
setContentHeight(0);
|
|
71
|
+
}, [snap]);
|
|
72
|
+
|
|
73
|
+
const isExpandable = contentHeight > SNAP_MAX_HEIGHT + 1;
|
|
74
|
+
const isClipped = isExpandable && !isExpanded;
|
|
60
75
|
|
|
61
76
|
return (
|
|
62
77
|
<>
|
|
63
78
|
<View style={cardStyles.frameRing}>
|
|
64
79
|
<View
|
|
65
80
|
style={[
|
|
66
|
-
cardStyles.card,
|
|
67
|
-
{
|
|
81
|
+
plain ? undefined : cardStyles.card,
|
|
82
|
+
plain ? undefined : {
|
|
68
83
|
borderRadius,
|
|
69
84
|
borderColor: colors.border,
|
|
70
85
|
backgroundColor: colors.surface,
|
|
71
86
|
},
|
|
72
87
|
]}
|
|
73
88
|
>
|
|
74
|
-
<View
|
|
75
|
-
|
|
89
|
+
<View
|
|
90
|
+
style={isClipped ? { maxHeight: SNAP_MAX_HEIGHT, overflow: "hidden" } : undefined}
|
|
91
|
+
>
|
|
92
|
+
<View
|
|
93
|
+
collapsable={false}
|
|
94
|
+
onLayout={(event) => {
|
|
95
|
+
const nextHeight = Math.round(event.nativeEvent.layout.height);
|
|
96
|
+
setContentHeight((currentHeight) =>
|
|
97
|
+
isClipped
|
|
98
|
+
? Math.max(currentHeight, nextHeight)
|
|
99
|
+
: currentHeight === nextHeight
|
|
100
|
+
? currentHeight
|
|
101
|
+
: nextHeight,
|
|
102
|
+
);
|
|
103
|
+
}}
|
|
104
|
+
style={plain ? undefined : cardStyles.body}
|
|
105
|
+
>
|
|
106
|
+
<SnapViewV1Inner
|
|
107
|
+
snap={snap}
|
|
108
|
+
handlers={handlers}
|
|
109
|
+
loading={loading}
|
|
110
|
+
/>
|
|
111
|
+
</View>
|
|
76
112
|
</View>
|
|
113
|
+
{isExpandable ? (
|
|
114
|
+
<View
|
|
115
|
+
style={[
|
|
116
|
+
cardStyles.expandRow,
|
|
117
|
+
plain
|
|
118
|
+
? cardStyles.expandRowPlain
|
|
119
|
+
: { borderTopColor: colors.border },
|
|
120
|
+
]}
|
|
121
|
+
>
|
|
122
|
+
<Pressable
|
|
123
|
+
style={({ pressed }) => [
|
|
124
|
+
cardStyles.expandButton,
|
|
125
|
+
{
|
|
126
|
+
backgroundColor: pressed
|
|
127
|
+
? colors.mutedHover
|
|
128
|
+
: colors.muted,
|
|
129
|
+
},
|
|
130
|
+
]}
|
|
131
|
+
onPress={() => {
|
|
132
|
+
setIsExpanded((value) => !value);
|
|
133
|
+
}}
|
|
134
|
+
>
|
|
135
|
+
<Text
|
|
136
|
+
style={[cardStyles.expandButtonText, { color: colors.text }]}
|
|
137
|
+
>
|
|
138
|
+
{isExpanded ? "Show less" : "Show more"}
|
|
139
|
+
</Text>
|
|
140
|
+
</Pressable>
|
|
141
|
+
</View>
|
|
142
|
+
) : null}
|
|
77
143
|
</View>
|
|
78
144
|
</View>
|
|
79
145
|
{actionError && (
|
|
@@ -103,6 +169,7 @@ export function SnapCardV1({
|
|
|
103
169
|
colors,
|
|
104
170
|
borderRadius = 16,
|
|
105
171
|
actionError,
|
|
172
|
+
plain = false,
|
|
106
173
|
}: {
|
|
107
174
|
snap: SnapPage;
|
|
108
175
|
handlers: SnapActionHandlers;
|
|
@@ -111,6 +178,7 @@ export function SnapCardV1({
|
|
|
111
178
|
colors?: Partial<SnapNativeColors>;
|
|
112
179
|
borderRadius?: number;
|
|
113
180
|
actionError?: string | null;
|
|
181
|
+
plain?: boolean;
|
|
114
182
|
}) {
|
|
115
183
|
return (
|
|
116
184
|
<SnapThemeProvider appearance={appearance} colors={colors}>
|
|
@@ -121,6 +189,7 @@ export function SnapCardV1({
|
|
|
121
189
|
borderRadius={borderRadius}
|
|
122
190
|
actionError={actionError}
|
|
123
191
|
appearance={appearance}
|
|
192
|
+
plain={plain}
|
|
124
193
|
/>
|
|
125
194
|
</SnapThemeProvider>
|
|
126
195
|
);
|
|
@@ -130,5 +199,31 @@ const cardStyles = StyleSheet.create({
|
|
|
130
199
|
frameRing: { alignSelf: "stretch" },
|
|
131
200
|
card: { overflow: "hidden", borderWidth: 1, minHeight: 120 },
|
|
132
201
|
body: { paddingHorizontal: 16, paddingVertical: 16 },
|
|
202
|
+
expandRow: {
|
|
203
|
+
alignItems: "center",
|
|
204
|
+
paddingHorizontal: 16,
|
|
205
|
+
paddingTop: 10,
|
|
206
|
+
paddingBottom: 12,
|
|
207
|
+
borderTopWidth: StyleSheet.hairlineWidth,
|
|
208
|
+
},
|
|
209
|
+
expandRowPlain: {
|
|
210
|
+
paddingHorizontal: 0,
|
|
211
|
+
paddingTop: 8,
|
|
212
|
+
paddingBottom: 0,
|
|
213
|
+
borderTopWidth: 0,
|
|
214
|
+
},
|
|
215
|
+
expandButton: {
|
|
216
|
+
minWidth: 92,
|
|
217
|
+
alignItems: "center",
|
|
218
|
+
justifyContent: "center",
|
|
219
|
+
borderRadius: 9999,
|
|
220
|
+
paddingHorizontal: 10,
|
|
221
|
+
paddingVertical: 6,
|
|
222
|
+
},
|
|
223
|
+
expandButtonText: {
|
|
224
|
+
fontSize: 13,
|
|
225
|
+
lineHeight: 18,
|
|
226
|
+
fontWeight: "600",
|
|
227
|
+
},
|
|
133
228
|
actionError: { paddingHorizontal: 12, paddingVertical: 8, fontSize: 13 },
|
|
134
229
|
});
|
|
@@ -123,6 +123,7 @@ function SnapCardV2Inner({
|
|
|
123
123
|
validationErrorFallback,
|
|
124
124
|
actionError,
|
|
125
125
|
appearance,
|
|
126
|
+
plain,
|
|
126
127
|
}: {
|
|
127
128
|
snap: SnapPage;
|
|
128
129
|
handlers: SnapActionHandlers;
|
|
@@ -133,54 +134,62 @@ function SnapCardV2Inner({
|
|
|
133
134
|
validationErrorFallback?: ReactNode;
|
|
134
135
|
actionError?: string | null;
|
|
135
136
|
appearance: "light" | "dark";
|
|
137
|
+
plain: boolean;
|
|
136
138
|
}) {
|
|
137
139
|
const { colors } = useSnapTheme();
|
|
138
|
-
const
|
|
140
|
+
const clipHeight = showOverflowWarning ? undefined : SNAP_MAX_HEIGHT;
|
|
141
|
+
|
|
142
|
+
const content = (
|
|
143
|
+
<SnapViewV2Inner
|
|
144
|
+
snap={snap}
|
|
145
|
+
handlers={handlers}
|
|
146
|
+
loading={loading}
|
|
147
|
+
onValidationError={onValidationError}
|
|
148
|
+
validationErrorFallback={validationErrorFallback}
|
|
149
|
+
/>
|
|
150
|
+
);
|
|
151
|
+
|
|
152
|
+
if (plain) {
|
|
153
|
+
return content;
|
|
154
|
+
}
|
|
139
155
|
|
|
140
156
|
return (
|
|
141
157
|
<>
|
|
142
|
-
<View
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
>
|
|
154
|
-
|
|
155
|
-
<SnapViewV2Inner
|
|
156
|
-
snap={snap}
|
|
157
|
-
handlers={handlers}
|
|
158
|
-
loading={loading}
|
|
159
|
-
onValidationError={onValidationError}
|
|
160
|
-
validationErrorFallback={validationErrorFallback}
|
|
161
|
-
/>
|
|
162
|
-
</View>
|
|
163
|
-
{showOverflowWarning && (
|
|
164
|
-
<View style={cardStyles.warningOverlay}>
|
|
165
|
-
<View style={cardStyles.warningLine} />
|
|
166
|
-
<View style={cardStyles.warningLabel}>
|
|
167
|
-
<Text style={cardStyles.warningLabelText}>{SNAP_MAX_HEIGHT}px</Text>
|
|
168
|
-
</View>
|
|
169
|
-
</View>
|
|
170
|
-
)}
|
|
158
|
+
<View
|
|
159
|
+
style={{
|
|
160
|
+
borderRadius,
|
|
161
|
+
borderWidth: 1,
|
|
162
|
+
borderColor: colors.border,
|
|
163
|
+
backgroundColor: colors.surface,
|
|
164
|
+
maxHeight: clipHeight,
|
|
165
|
+
overflow: "hidden",
|
|
166
|
+
minHeight: 120,
|
|
167
|
+
}}
|
|
168
|
+
>
|
|
169
|
+
<View style={{ paddingHorizontal: 16, paddingVertical: 16 }}>
|
|
170
|
+
{content}
|
|
171
171
|
</View>
|
|
172
|
+
{showOverflowWarning && (
|
|
173
|
+
<View style={{ position: "absolute", top: SNAP_MAX_HEIGHT, left: 0, right: 0, bottom: 0, zIndex: 10, pointerEvents: "none" }}>
|
|
174
|
+
<View style={{ height: 1, borderTopWidth: 1, borderStyle: "dashed", borderColor: "rgba(255,100,100,0.6)" }} />
|
|
175
|
+
<View style={{ position: "absolute", top: -10, right: 4, backgroundColor: "rgba(0,0,0,0.7)", paddingHorizontal: 4, paddingVertical: 1, borderRadius: 3 }}>
|
|
176
|
+
<Text style={{ fontSize: 10, color: "rgba(255,100,100,0.7)", fontFamily: Platform.select({ ios: "Menlo", default: "monospace" }) }}>{SNAP_MAX_HEIGHT}px</Text>
|
|
177
|
+
</View>
|
|
178
|
+
<View style={{ flex: 1, backgroundColor: "rgba(255,50,50,0.15)" }} />
|
|
179
|
+
</View>
|
|
180
|
+
)}
|
|
172
181
|
</View>
|
|
173
182
|
{actionError && (
|
|
174
183
|
<Text
|
|
175
|
-
style={
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
+
style={{
|
|
185
|
+
paddingHorizontal: 12,
|
|
186
|
+
paddingVertical: 8,
|
|
187
|
+
fontSize: 13,
|
|
188
|
+
color:
|
|
189
|
+
appearance === "dark"
|
|
190
|
+
? "rgba(255,100,100,0.9)"
|
|
191
|
+
: "rgba(200,0,0,0.8)",
|
|
192
|
+
}}
|
|
184
193
|
>
|
|
185
194
|
{actionError}
|
|
186
195
|
</Text>
|
|
@@ -200,6 +209,7 @@ export function SnapCardV2({
|
|
|
200
209
|
onValidationError,
|
|
201
210
|
validationErrorFallback,
|
|
202
211
|
actionError,
|
|
212
|
+
plain = false,
|
|
203
213
|
}: {
|
|
204
214
|
snap: SnapPage;
|
|
205
215
|
handlers: SnapActionHandlers;
|
|
@@ -211,6 +221,7 @@ export function SnapCardV2({
|
|
|
211
221
|
onValidationError?: (result: ValidationResult) => void;
|
|
212
222
|
validationErrorFallback?: ReactNode;
|
|
213
223
|
actionError?: string | null;
|
|
224
|
+
plain?: boolean;
|
|
214
225
|
}) {
|
|
215
226
|
return (
|
|
216
227
|
<SnapThemeProvider appearance={appearance} colors={colors}>
|
|
@@ -224,6 +235,7 @@ export function SnapCardV2({
|
|
|
224
235
|
validationErrorFallback={validationErrorFallback}
|
|
225
236
|
actionError={actionError}
|
|
226
237
|
appearance={appearance}
|
|
238
|
+
plain={plain}
|
|
227
239
|
/>
|
|
228
240
|
</SnapThemeProvider>
|
|
229
241
|
);
|
|
@@ -231,7 +243,7 @@ export function SnapCardV2({
|
|
|
231
243
|
|
|
232
244
|
const cardStyles = StyleSheet.create({
|
|
233
245
|
frameRing: { alignSelf: "stretch" },
|
|
234
|
-
card: {
|
|
246
|
+
card: { borderWidth: 1, minHeight: 120, overflow: "hidden" },
|
|
235
247
|
body: { paddingHorizontal: 16, paddingVertical: 16 },
|
|
236
248
|
actionError: { paddingHorizontal: 12, paddingVertical: 8, fontSize: 13 },
|
|
237
249
|
warningOverlay: {
|
package/src/ui/catalog.ts
CHANGED
|
@@ -117,10 +117,9 @@ export const snapJsonRenderCatalog = defineCatalog(snapJsonRenderSchema, {
|
|
|
117
117
|
params: z.object({ target: z.string() }),
|
|
118
118
|
},
|
|
119
119
|
open_url: {
|
|
120
|
-
description: "Open
|
|
120
|
+
description: "Open URL in browser.",
|
|
121
121
|
params: z.object({
|
|
122
122
|
target: z.string(),
|
|
123
|
-
isSnap: z.boolean().optional(),
|
|
124
123
|
}),
|
|
125
124
|
},
|
|
126
125
|
open_mini_app: {
|