@farcaster/snap 2.0.0 → 2.0.1
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/colors.d.ts +4 -4
- package/dist/colors.js +20 -20
- package/dist/constants.d.ts +17 -1
- package/dist/constants.js +19 -1
- package/dist/index.d.ts +4 -6
- package/dist/index.js +2 -4
- package/dist/react/accent-context.d.ts +3 -1
- package/dist/react/accent-context.js +7 -4
- package/dist/react/catalog-renderer.js +4 -0
- package/dist/react/components/action-button.d.ts +2 -1
- package/dist/react/components/action-button.js +32 -13
- package/dist/react/components/badge.js +8 -8
- package/dist/react/components/bar-chart.d.ts +5 -0
- package/dist/react/components/bar-chart.js +26 -0
- package/dist/react/components/cell-grid.d.ts +5 -0
- package/dist/react/components/cell-grid.js +87 -0
- package/dist/react/components/icon.js +4 -10
- package/dist/react/components/input.js +12 -6
- package/dist/react/components/item-group.js +3 -1
- package/dist/react/components/item.d.ts +3 -3
- package/dist/react/components/item.js +4 -3
- package/dist/react/components/progress.js +3 -3
- package/dist/react/components/separator.js +3 -1
- package/dist/react/components/slider.js +15 -10
- package/dist/react/components/switch.js +10 -12
- package/dist/react/components/text.js +6 -14
- package/dist/react/components/toggle-group.js +20 -6
- package/dist/react/hooks/use-snap-colors.d.ts +38 -0
- package/dist/react/hooks/use-snap-colors.js +81 -0
- package/dist/react/index.d.ts +13 -1
- package/dist/react/index.js +9 -188
- package/dist/react/snap-view-core.d.ts +11 -0
- package/dist/react/snap-view-core.js +224 -0
- package/dist/react/v1/snap-view.d.ts +16 -0
- package/dist/react/v1/snap-view.js +90 -0
- package/dist/react/v2/snap-view.d.ts +23 -0
- package/dist/react/v2/snap-view.js +91 -0
- package/dist/react-native/catalog-renderer.d.ts +5 -0
- package/dist/react-native/catalog-renderer.js +40 -0
- package/dist/react-native/components/snap-action-button.d.ts +2 -0
- package/dist/react-native/components/snap-action-button.js +69 -0
- package/dist/react-native/components/snap-badge.d.ts +2 -0
- package/dist/react-native/components/snap-badge.js +41 -0
- package/dist/react-native/components/snap-bar-chart.d.ts +2 -0
- package/dist/react-native/components/snap-bar-chart.js +39 -0
- package/dist/react-native/components/snap-cell-grid.d.ts +2 -0
- package/dist/react-native/components/snap-cell-grid.js +94 -0
- package/dist/react-native/components/snap-icon.d.ts +5 -0
- package/dist/react-native/components/snap-icon.js +56 -0
- package/dist/react-native/components/snap-image.d.ts +2 -0
- package/dist/react-native/components/snap-image.js +23 -0
- package/dist/react-native/components/snap-input.d.ts +2 -0
- package/dist/react-native/components/snap-input.js +37 -0
- package/dist/react-native/components/snap-item-group.d.ts +5 -0
- package/dist/react-native/components/snap-item-group.js +23 -0
- package/dist/react-native/components/snap-item.d.ts +5 -0
- package/dist/react-native/components/snap-item.js +42 -0
- package/dist/react-native/components/snap-progress.d.ts +2 -0
- package/dist/react-native/components/snap-progress.js +26 -0
- package/dist/react-native/components/snap-separator.d.ts +2 -0
- package/dist/react-native/components/snap-separator.js +23 -0
- package/dist/react-native/components/snap-slider.d.ts +2 -0
- package/dist/react-native/components/snap-slider.js +43 -0
- package/dist/react-native/components/snap-stack.d.ts +5 -0
- package/dist/react-native/components/snap-stack.js +49 -0
- package/dist/react-native/components/snap-switch.d.ts +2 -0
- package/dist/react-native/components/snap-switch.js +31 -0
- package/dist/react-native/components/snap-text.d.ts +2 -0
- package/dist/react-native/components/snap-text.js +35 -0
- package/dist/react-native/components/snap-toggle-group.d.ts +2 -0
- package/dist/react-native/components/snap-toggle-group.js +99 -0
- package/dist/react-native/confetti-overlay.d.ts +1 -0
- package/dist/react-native/confetti-overlay.js +106 -0
- package/dist/react-native/index.d.ts +28 -0
- package/dist/react-native/index.js +15 -0
- package/dist/react-native/snap-view-core.d.ts +11 -0
- package/dist/react-native/snap-view-core.js +153 -0
- package/dist/react-native/theme.d.ts +27 -0
- package/dist/react-native/theme.js +43 -0
- package/dist/react-native/types.d.ts +42 -0
- package/dist/react-native/types.js +1 -0
- package/dist/react-native/use-snap-palette.d.ts +13 -0
- package/dist/react-native/use-snap-palette.js +48 -0
- package/dist/react-native/v1/snap-view.d.ts +24 -0
- package/dist/react-native/v1/snap-view.js +96 -0
- package/dist/react-native/v2/snap-view.d.ts +33 -0
- package/dist/react-native/v2/snap-view.js +114 -0
- package/dist/schemas.d.ts +100 -13
- package/dist/schemas.js +28 -10
- package/dist/server/parseRequest.d.ts +10 -0
- package/dist/server/parseRequest.js +48 -7
- package/dist/server/verify.d.ts +1 -0
- package/dist/server/verify.js +1 -0
- package/dist/ui/badge.d.ts +7 -2
- package/dist/ui/badge.js +2 -0
- package/dist/ui/bar-chart.d.ts +30 -0
- package/dist/ui/bar-chart.js +30 -0
- package/dist/ui/button.d.ts +4 -6
- package/dist/ui/button.js +1 -1
- package/dist/ui/catalog.d.ts +90 -16
- package/dist/ui/catalog.js +17 -3
- package/dist/ui/cell-grid.d.ts +34 -0
- package/dist/ui/cell-grid.js +39 -0
- package/dist/ui/icon.d.ts +2 -2
- package/dist/ui/image.d.ts +1 -2
- package/dist/ui/image.js +1 -1
- package/dist/ui/index.d.ts +4 -0
- package/dist/ui/index.js +2 -0
- package/dist/ui/item.d.ts +1 -3
- package/dist/ui/item.js +1 -1
- package/dist/ui/schema.d.ts +6 -2
- package/dist/ui/schema.js +2 -2
- package/dist/ui/slider.d.ts +1 -0
- package/dist/ui/slider.js +2 -0
- package/dist/ui/text.d.ts +2 -4
- package/dist/ui/text.js +2 -2
- package/dist/validator.d.ts +3 -2
- package/dist/validator.js +198 -2
- package/llms.txt +199 -0
- package/package.json +9 -3
- package/src/colors.ts +20 -20
- package/src/constants.ts +23 -1
- package/src/index.ts +16 -13
- package/src/react/accent-context.tsx +13 -6
- package/src/react/catalog-renderer.tsx +4 -0
- package/src/react/components/action-button.tsx +47 -20
- package/src/react/components/badge.tsx +14 -18
- package/src/react/components/bar-chart.tsx +69 -0
- package/src/react/components/cell-grid.tsx +128 -0
- package/src/react/components/icon.tsx +5 -18
- package/src/react/components/input.tsx +20 -9
- package/src/react/components/item-group.tsx +4 -1
- package/src/react/components/item.tsx +13 -10
- package/src/react/components/progress.tsx +12 -7
- package/src/react/components/separator.tsx +8 -1
- package/src/react/components/slider.tsx +28 -15
- package/src/react/components/switch.tsx +12 -16
- package/src/react/components/text.tsx +14 -23
- package/src/react/components/toggle-group.tsx +26 -9
- package/src/react/hooks/use-snap-colors.ts +128 -0
- package/src/react/index.tsx +49 -265
- package/src/react/snap-view-core.tsx +340 -0
- package/src/react/v1/snap-view.tsx +176 -0
- package/src/react/v2/snap-view.tsx +199 -0
- package/src/react-native/catalog-renderer.tsx +41 -0
- package/src/react-native/components/snap-action-button.tsx +96 -0
- package/src/react-native/components/snap-badge.tsx +60 -0
- package/src/react-native/components/snap-bar-chart.tsx +73 -0
- package/src/react-native/components/snap-cell-grid.tsx +150 -0
- package/src/react-native/components/snap-icon.tsx +102 -0
- package/src/react-native/components/snap-image.tsx +37 -0
- package/src/react-native/components/snap-input.tsx +58 -0
- package/src/react-native/components/snap-item-group.tsx +43 -0
- package/src/react-native/components/snap-item.tsx +66 -0
- package/src/react-native/components/snap-progress.tsx +40 -0
- package/src/react-native/components/snap-separator.tsx +32 -0
- package/src/react-native/components/snap-slider.tsx +85 -0
- package/src/react-native/components/snap-stack.tsx +66 -0
- package/src/react-native/components/snap-switch.tsx +46 -0
- package/src/react-native/components/snap-text.tsx +51 -0
- package/src/react-native/components/snap-toggle-group.tsx +127 -0
- package/src/react-native/confetti-overlay.tsx +134 -0
- package/src/react-native/index.tsx +83 -0
- package/src/react-native/snap-view-core.tsx +209 -0
- package/src/react-native/theme.tsx +85 -0
- package/src/react-native/types.ts +38 -0
- package/src/react-native/use-snap-palette.ts +64 -0
- package/src/react-native/v1/snap-view.tsx +229 -0
- package/src/react-native/v2/snap-view.tsx +283 -0
- package/src/schemas.ts +68 -17
- package/src/server/parseRequest.ts +68 -9
- package/src/server/verify.ts +2 -0
- package/src/ui/README.md +8 -8
- package/src/ui/badge.ts +2 -0
- package/src/ui/bar-chart.ts +38 -0
- package/src/ui/button.ts +1 -1
- package/src/ui/catalog.ts +19 -3
- package/src/ui/cell-grid.ts +49 -0
- package/src/ui/image.ts +1 -1
- package/src/ui/index.ts +6 -0
- package/src/ui/item.ts +1 -1
- package/src/ui/schema.ts +2 -2
- package/src/ui/slider.ts +2 -0
- package/src/ui/text.ts +2 -2
- package/src/validator.ts +246 -2
- package/dist/dataStore.d.ts +0 -12
- package/dist/dataStore.js +0 -35
- package/dist/middleware.d.ts +0 -3
- package/dist/middleware.js +0 -3
- package/dist/react/hooks/use-snap-accent.d.ts +0 -13
- package/dist/react/hooks/use-snap-accent.js +0 -32
- package/src/dataStore.ts +0 -62
- package/src/middleware.ts +0 -7
- package/src/react/hooks/use-snap-accent.ts +0 -45
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { SnapPage, SnapActionHandlers } from "../index.js";
|
|
2
|
+
export declare function SnapViewV1({ snap, handlers, loading, appearance, }: {
|
|
3
|
+
snap: SnapPage;
|
|
4
|
+
handlers: SnapActionHandlers;
|
|
5
|
+
loading?: boolean;
|
|
6
|
+
appearance?: "light" | "dark";
|
|
7
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
8
|
+
export declare function SnapCardV1({ snap, handlers, loading, appearance, maxWidth, actionError, plain, }: {
|
|
9
|
+
snap: SnapPage;
|
|
10
|
+
handlers: SnapActionHandlers;
|
|
11
|
+
loading?: boolean;
|
|
12
|
+
appearance?: "light" | "dark";
|
|
13
|
+
maxWidth?: number;
|
|
14
|
+
actionError?: string | null;
|
|
15
|
+
plain?: boolean;
|
|
16
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,90 @@
|
|
|
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";
|
|
5
|
+
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 }));
|
|
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: {
|
|
84
|
+
padding: "8px 12px",
|
|
85
|
+
fontSize: 13,
|
|
86
|
+
color: appearance === "dark"
|
|
87
|
+
? "rgba(255,100,100,0.9)"
|
|
88
|
+
: "rgba(200,0,0,0.8)",
|
|
89
|
+
}, children: actionError }))] }));
|
|
90
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
import type { ValidationResult } from "../../validator.js";
|
|
3
|
+
import type { SnapPage, SnapActionHandlers } from "../index.js";
|
|
4
|
+
export declare function SnapViewV2({ snap, handlers, loading, appearance, onValidationError, validationErrorFallback, }: {
|
|
5
|
+
snap: SnapPage;
|
|
6
|
+
handlers: SnapActionHandlers;
|
|
7
|
+
loading?: boolean;
|
|
8
|
+
appearance?: "light" | "dark";
|
|
9
|
+
onValidationError?: (result: ValidationResult) => void;
|
|
10
|
+
validationErrorFallback?: ReactNode;
|
|
11
|
+
}): import("react/jsx-runtime").JSX.Element | null;
|
|
12
|
+
export declare function SnapCardV2({ snap, handlers, loading, appearance, maxWidth, showOverflowWarning, onValidationError, validationErrorFallback, actionError, plain, }: {
|
|
13
|
+
snap: SnapPage;
|
|
14
|
+
handlers: SnapActionHandlers;
|
|
15
|
+
loading?: boolean;
|
|
16
|
+
appearance?: "light" | "dark";
|
|
17
|
+
maxWidth?: number;
|
|
18
|
+
showOverflowWarning?: boolean;
|
|
19
|
+
onValidationError?: (result: ValidationResult) => void;
|
|
20
|
+
validationErrorFallback?: ReactNode;
|
|
21
|
+
actionError?: string | null;
|
|
22
|
+
plain?: boolean;
|
|
23
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useEffect, useMemo } from "react";
|
|
4
|
+
import { validateSnapResponse } from "../../validator.js";
|
|
5
|
+
import { SnapViewCore } from "../snap-view-core.js";
|
|
6
|
+
const SNAP_MAX_HEIGHT = 500;
|
|
7
|
+
const SNAP_WARNING_HEIGHT = 700;
|
|
8
|
+
// ─── Default validation error fallback ────────────────
|
|
9
|
+
function SnapValidationFallback({ appearance, message, }) {
|
|
10
|
+
const isDark = appearance === "dark";
|
|
11
|
+
return (_jsx("div", { style: {
|
|
12
|
+
width: "100%",
|
|
13
|
+
padding: 16,
|
|
14
|
+
display: "flex",
|
|
15
|
+
alignItems: "center",
|
|
16
|
+
justifyContent: "center",
|
|
17
|
+
color: isDark ? "rgba(255,255,255,0.5)" : "rgba(0,0,0,0.4)",
|
|
18
|
+
fontSize: 14,
|
|
19
|
+
}, children: _jsx("span", { children: message ? `Unable to render snap: ${message}` : "Unable to render snap" }) }));
|
|
20
|
+
}
|
|
21
|
+
// ─── SnapViewV2 ──────────────────────────────────────
|
|
22
|
+
export function SnapViewV2({ snap, handlers, loading = false, appearance = "dark", onValidationError, validationErrorFallback, }) {
|
|
23
|
+
const validation = useMemo(() => validateSnapResponse(snap), [snap]);
|
|
24
|
+
const valid = validation.valid;
|
|
25
|
+
const validationMessage = validation.issues[0]?.message;
|
|
26
|
+
useEffect(() => {
|
|
27
|
+
if (!valid) {
|
|
28
|
+
if (onValidationError) {
|
|
29
|
+
onValidationError(validation);
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
// eslint-disable-next-line no-console
|
|
33
|
+
console.warn("[Snap] validation issues:", validation.issues);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}, [valid, validation, onValidationError]);
|
|
37
|
+
if (!valid) {
|
|
38
|
+
if (validationErrorFallback === null)
|
|
39
|
+
return null;
|
|
40
|
+
return _jsx(_Fragment, { children: validationErrorFallback ?? _jsx(SnapValidationFallback, { appearance: appearance, message: validationMessage }) });
|
|
41
|
+
}
|
|
42
|
+
return (_jsx(SnapViewCore, { snap: snap, handlers: handlers, loading: loading, appearance: appearance }));
|
|
43
|
+
}
|
|
44
|
+
// ─── SnapCardV2 ──────────────────────────────────────
|
|
45
|
+
export function SnapCardV2({ snap, handlers, loading = false, appearance = "dark", maxWidth = 480, showOverflowWarning = false, onValidationError, validationErrorFallback, actionError, plain = false, }) {
|
|
46
|
+
const maxHeight = showOverflowWarning ? SNAP_WARNING_HEIGHT : SNAP_MAX_HEIGHT;
|
|
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)";
|
|
51
|
+
return (_jsxs(_Fragment, { children: [_jsxs("div", { style: {
|
|
52
|
+
position: "relative",
|
|
53
|
+
width: "100%",
|
|
54
|
+
maxWidth,
|
|
55
|
+
maxHeight,
|
|
56
|
+
overflow: "hidden",
|
|
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: {
|
|
63
|
+
position: "absolute",
|
|
64
|
+
top: SNAP_MAX_HEIGHT,
|
|
65
|
+
left: 0,
|
|
66
|
+
right: 0,
|
|
67
|
+
bottom: 0,
|
|
68
|
+
pointerEvents: "none",
|
|
69
|
+
zIndex: 10,
|
|
70
|
+
}, children: [_jsx("div", { style: { borderTop: "1px dashed rgba(255,100,100,0.6)", position: "relative" }, children: _jsxs("span", { style: {
|
|
71
|
+
position: "absolute",
|
|
72
|
+
top: -10,
|
|
73
|
+
right: 0,
|
|
74
|
+
fontSize: 10,
|
|
75
|
+
fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace",
|
|
76
|
+
color: "rgba(255,100,100,0.7)",
|
|
77
|
+
background: bg,
|
|
78
|
+
padding: "1px 4px",
|
|
79
|
+
borderRadius: 3,
|
|
80
|
+
}, children: [SNAP_MAX_HEIGHT, "px"] }) }), _jsx("div", { style: {
|
|
81
|
+
height: "100%",
|
|
82
|
+
background: "repeating-linear-gradient(-45deg, transparent, transparent 8px, rgba(255,100,100,0.06) 8px, rgba(255,100,100,0.06) 16px)",
|
|
83
|
+
} })] }))] }), actionError && (_jsx("div", { style: {
|
|
84
|
+
maxWidth,
|
|
85
|
+
padding: "8px 12px",
|
|
86
|
+
fontSize: 13,
|
|
87
|
+
color: appearance === "dark"
|
|
88
|
+
? "rgba(255,100,100,0.9)"
|
|
89
|
+
: "rgba(200,0,0,0.8)",
|
|
90
|
+
}, children: actionError }))] }));
|
|
91
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { createRenderer } from "@json-render/react-native";
|
|
2
|
+
import { snapJsonRenderCatalog } from "@farcaster/snap/ui";
|
|
3
|
+
import { SnapActionButton } from "./components/snap-action-button.js";
|
|
4
|
+
import { SnapBadge } from "./components/snap-badge.js";
|
|
5
|
+
import { SnapIcon } from "./components/snap-icon.js";
|
|
6
|
+
import { SnapImage } from "./components/snap-image.js";
|
|
7
|
+
import { SnapInput } from "./components/snap-input.js";
|
|
8
|
+
import { SnapItem } from "./components/snap-item.js";
|
|
9
|
+
import { SnapItemGroup } from "./components/snap-item-group.js";
|
|
10
|
+
import { SnapProgress } from "./components/snap-progress.js";
|
|
11
|
+
import { SnapSeparator } from "./components/snap-separator.js";
|
|
12
|
+
import { SnapSlider } from "./components/snap-slider.js";
|
|
13
|
+
import { SnapStack } from "./components/snap-stack.js";
|
|
14
|
+
import { SnapSwitch } from "./components/snap-switch.js";
|
|
15
|
+
import { SnapText } from "./components/snap-text.js";
|
|
16
|
+
import { SnapToggleGroup } from "./components/snap-toggle-group.js";
|
|
17
|
+
import { SnapBarChart } from "./components/snap-bar-chart.js";
|
|
18
|
+
import { SnapCellGrid } from "./components/snap-cell-grid.js";
|
|
19
|
+
/**
|
|
20
|
+
* Maps snap json-render catalog types to React Native primitives.
|
|
21
|
+
* Keys match the snap wire-format `type` strings exactly (snake_case).
|
|
22
|
+
*/
|
|
23
|
+
export const SnapCatalogView = createRenderer(snapJsonRenderCatalog, {
|
|
24
|
+
badge: SnapBadge,
|
|
25
|
+
button: SnapActionButton,
|
|
26
|
+
icon: SnapIcon,
|
|
27
|
+
image: SnapImage,
|
|
28
|
+
input: SnapInput,
|
|
29
|
+
item: SnapItem,
|
|
30
|
+
item_group: SnapItemGroup,
|
|
31
|
+
progress: SnapProgress,
|
|
32
|
+
separator: SnapSeparator,
|
|
33
|
+
slider: SnapSlider,
|
|
34
|
+
stack: SnapStack,
|
|
35
|
+
switch: SnapSwitch,
|
|
36
|
+
text: SnapText,
|
|
37
|
+
toggle_group: SnapToggleGroup,
|
|
38
|
+
bar_chart: SnapBarChart,
|
|
39
|
+
cell_grid: SnapCellGrid,
|
|
40
|
+
});
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Pressable, StyleSheet, Text, View } from "react-native";
|
|
3
|
+
import { ExternalLink } from "lucide-react-native";
|
|
4
|
+
import { useSnapPalette } from "../use-snap-palette.js";
|
|
5
|
+
import { useSnapTheme } from "../theme.js";
|
|
6
|
+
import { ICON_MAP } from "./snap-icon.js";
|
|
7
|
+
function isExternalLinkAction(on) {
|
|
8
|
+
if (!on)
|
|
9
|
+
return false;
|
|
10
|
+
const press = on.press;
|
|
11
|
+
if (!press)
|
|
12
|
+
return false;
|
|
13
|
+
return press.action === "open_url";
|
|
14
|
+
}
|
|
15
|
+
export function SnapActionButton({ element, emit, }) {
|
|
16
|
+
const { accentHex } = useSnapPalette();
|
|
17
|
+
const { colors } = useSnapTheme();
|
|
18
|
+
const { props } = element;
|
|
19
|
+
const label = String(props.label ?? "Action");
|
|
20
|
+
const variant = String(props.variant ?? "secondary");
|
|
21
|
+
const isPrimary = variant === "primary";
|
|
22
|
+
const iconName = props.icon ? String(props.icon) : undefined;
|
|
23
|
+
const textColor = isPrimary ? "#fff" : colors.text;
|
|
24
|
+
const iconColor = isPrimary ? "#fff" : colors.text;
|
|
25
|
+
const on = element.on;
|
|
26
|
+
const showExternalIcon = isExternalLinkAction(on);
|
|
27
|
+
return (_jsx(View, { style: styles.outer, children: _jsxs(Pressable, { style: ({ pressed }) => [
|
|
28
|
+
styles.btn,
|
|
29
|
+
isPrimary ? styles.btnDefault : styles.btnOther,
|
|
30
|
+
isPrimary
|
|
31
|
+
? { backgroundColor: pressed ? accentHex + "DD" : accentHex }
|
|
32
|
+
: { backgroundColor: pressed ? colors.mutedHover : colors.muted },
|
|
33
|
+
pressed && styles.pressed,
|
|
34
|
+
], onPress: () => {
|
|
35
|
+
void (async () => {
|
|
36
|
+
try {
|
|
37
|
+
await emit("press");
|
|
38
|
+
}
|
|
39
|
+
catch (err) {
|
|
40
|
+
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
41
|
+
console.error("[snap] action failed", err);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
})();
|
|
45
|
+
}, children: [iconName && ICON_MAP[iconName]
|
|
46
|
+
? (() => {
|
|
47
|
+
const I = ICON_MAP[iconName];
|
|
48
|
+
return _jsx(I, { size: 16, color: iconColor });
|
|
49
|
+
})()
|
|
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
|
+
}
|
|
52
|
+
const styles = StyleSheet.create({
|
|
53
|
+
outer: { minWidth: 0 },
|
|
54
|
+
btn: {
|
|
55
|
+
paddingHorizontal: 16,
|
|
56
|
+
borderRadius: 10,
|
|
57
|
+
alignItems: "center",
|
|
58
|
+
justifyContent: "center",
|
|
59
|
+
flexDirection: "row",
|
|
60
|
+
gap: 8,
|
|
61
|
+
},
|
|
62
|
+
btnDefault: {
|
|
63
|
+
paddingVertical: 10,
|
|
64
|
+
},
|
|
65
|
+
btnOther: {
|
|
66
|
+
paddingVertical: 8,
|
|
67
|
+
},
|
|
68
|
+
pressed: { opacity: 0.88 },
|
|
69
|
+
});
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { StyleSheet, Text, View } from "react-native";
|
|
3
|
+
import { useSnapPalette } from "../use-snap-palette.js";
|
|
4
|
+
import { ICON_MAP } from "./snap-icon.js";
|
|
5
|
+
export function SnapBadge({ element: { props }, }) {
|
|
6
|
+
const { accentHex, hex } = useSnapPalette();
|
|
7
|
+
const label = String(props.label ?? "");
|
|
8
|
+
const variant = String(props.variant ?? "default");
|
|
9
|
+
const color = props.color ? String(props.color) : undefined;
|
|
10
|
+
const iconName = props.icon ? String(props.icon) : undefined;
|
|
11
|
+
const isAccent = !color || color === "accent";
|
|
12
|
+
const resolvedColor = isAccent ? accentHex : hex(color);
|
|
13
|
+
const isFilled = variant !== "outline";
|
|
14
|
+
const Icon = iconName ? ICON_MAP[iconName] : undefined;
|
|
15
|
+
return (_jsxs(View, { style: [
|
|
16
|
+
styles.badge,
|
|
17
|
+
isFilled
|
|
18
|
+
? { backgroundColor: resolvedColor + "20", borderColor: "transparent" }
|
|
19
|
+
: { borderColor: resolvedColor },
|
|
20
|
+
], children: [Icon && (_jsx(Icon, { size: 12, color: resolvedColor })), _jsx(Text, { style: [
|
|
21
|
+
styles.label,
|
|
22
|
+
{ color: resolvedColor },
|
|
23
|
+
], children: label })] }));
|
|
24
|
+
}
|
|
25
|
+
const styles = StyleSheet.create({
|
|
26
|
+
badge: {
|
|
27
|
+
alignSelf: "flex-start",
|
|
28
|
+
flexDirection: "row",
|
|
29
|
+
alignItems: "center",
|
|
30
|
+
gap: 4,
|
|
31
|
+
paddingHorizontal: 8,
|
|
32
|
+
paddingVertical: 2,
|
|
33
|
+
borderRadius: 9999,
|
|
34
|
+
borderWidth: 1,
|
|
35
|
+
},
|
|
36
|
+
label: {
|
|
37
|
+
fontSize: 12,
|
|
38
|
+
lineHeight: 16,
|
|
39
|
+
fontWeight: "500",
|
|
40
|
+
},
|
|
41
|
+
});
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { StyleSheet, Text, View } from "react-native";
|
|
3
|
+
import { useSnapPalette } from "../use-snap-palette.js";
|
|
4
|
+
import { useSnapTheme } from "../theme.js";
|
|
5
|
+
export function SnapBarChart({ element: { props }, }) {
|
|
6
|
+
const { accentHex, hex } = useSnapPalette();
|
|
7
|
+
const { colors } = useSnapTheme();
|
|
8
|
+
const bars = Array.isArray(props.bars) ? props.bars : [];
|
|
9
|
+
const chartColor = String(props.color ?? "accent");
|
|
10
|
+
const maxVal = props.max != null
|
|
11
|
+
? Number(props.max)
|
|
12
|
+
: Math.max(...bars.map((b) => Number(b.value ?? 0)), 1);
|
|
13
|
+
function barFill(bar) {
|
|
14
|
+
if (bar.color)
|
|
15
|
+
return hex(bar.color);
|
|
16
|
+
if (chartColor !== "accent")
|
|
17
|
+
return hex(chartColor);
|
|
18
|
+
return accentHex;
|
|
19
|
+
}
|
|
20
|
+
return (_jsx(View, { style: styles.wrap, children: bars.map((bar, i) => {
|
|
21
|
+
const value = Number(bar.value ?? 0);
|
|
22
|
+
const pct = maxVal > 0 ? Math.min(100, (value / maxVal) * 100) : 0;
|
|
23
|
+
return (_jsxs(View, { style: styles.row, children: [_jsx(Text, { style: [styles.label, { color: colors.textSecondary }], numberOfLines: 1, children: String(bar.label ?? "") }), _jsx(View, { style: [styles.track, { backgroundColor: colors.muted }], children: _jsx(View, { style: [
|
|
24
|
+
styles.fill,
|
|
25
|
+
{
|
|
26
|
+
width: `${pct}%`,
|
|
27
|
+
backgroundColor: barFill(bar),
|
|
28
|
+
},
|
|
29
|
+
] }) }), _jsx(Text, { style: [styles.value, { color: colors.textSecondary }], children: value })] }, i));
|
|
30
|
+
}) }));
|
|
31
|
+
}
|
|
32
|
+
const styles = StyleSheet.create({
|
|
33
|
+
wrap: { width: "100%", gap: 8 },
|
|
34
|
+
row: { flexDirection: "row", alignItems: "center", gap: 8 },
|
|
35
|
+
label: { width: 80, fontSize: 12, lineHeight: 16, textAlign: "right" },
|
|
36
|
+
track: { flex: 1, height: 10, borderRadius: 9999, overflow: "hidden" },
|
|
37
|
+
fill: { height: "100%", borderRadius: 9999 },
|
|
38
|
+
value: { width: 32, fontSize: 12, lineHeight: 16, fontVariant: ["tabular-nums"] },
|
|
39
|
+
});
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { StyleSheet, Text, View, Pressable } from "react-native";
|
|
3
|
+
import { useStateStore } from "@json-render/react-native";
|
|
4
|
+
import { useSnapPalette } from "../use-snap-palette.js";
|
|
5
|
+
import { useSnapTheme } from "../theme.js";
|
|
6
|
+
import { POST_GRID_TAP_KEY } from "@farcaster/snap";
|
|
7
|
+
export function SnapCellGrid({ element: { props }, }) {
|
|
8
|
+
const { hex, appearance } = useSnapPalette();
|
|
9
|
+
const { colors } = useSnapTheme();
|
|
10
|
+
const { get, set } = useStateStore();
|
|
11
|
+
const cols = Number(props.cols ?? 2);
|
|
12
|
+
const rows = Number(props.rows ?? 2);
|
|
13
|
+
const cells = Array.isArray(props.cells) ? props.cells : [];
|
|
14
|
+
const rowHeight = typeof props.rowHeight === "number" ? props.rowHeight : 28;
|
|
15
|
+
const gap = String(props.gap ?? "sm");
|
|
16
|
+
const gapMap = { none: 0, sm: 1, md: 2, lg: 4 };
|
|
17
|
+
const gapPx = gapMap[gap] ?? 1;
|
|
18
|
+
const select = String(props.select ?? "off");
|
|
19
|
+
const interactive = select !== "off";
|
|
20
|
+
const isMultiple = select === "multiple";
|
|
21
|
+
const name = props.name ? String(props.name) : POST_GRID_TAP_KEY;
|
|
22
|
+
const tapPath = `/inputs/${name}`;
|
|
23
|
+
const tapRaw = get(tapPath);
|
|
24
|
+
const selectedSet = new Set();
|
|
25
|
+
if (typeof tapRaw === "string" && tapRaw.length > 0) {
|
|
26
|
+
for (const part of tapRaw.split("|")) {
|
|
27
|
+
if (part.includes(","))
|
|
28
|
+
selectedSet.add(part);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
const isSelected = (r, c) => selectedSet.has(`${r},${c}`);
|
|
32
|
+
const handleTap = (r, c) => {
|
|
33
|
+
const key = `${r},${c}`;
|
|
34
|
+
if (isMultiple) {
|
|
35
|
+
const next = new Set(selectedSet);
|
|
36
|
+
if (next.has(key))
|
|
37
|
+
next.delete(key);
|
|
38
|
+
else
|
|
39
|
+
next.add(key);
|
|
40
|
+
set(tapPath, [...next].join("|"));
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
set(tapPath, key);
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
const cellMap = new Map();
|
|
47
|
+
for (const c of cells) {
|
|
48
|
+
cellMap.set(`${Number(c.row)},${Number(c.col)}`, {
|
|
49
|
+
color: c.color,
|
|
50
|
+
content: c.content != null ? String(c.content) : undefined,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
const ringOuter = appearance === "dark" ? "#fff" : "#000";
|
|
54
|
+
const ringInner = appearance === "dark" ? "#000" : "#fff";
|
|
55
|
+
const rowEls = [];
|
|
56
|
+
for (let r = 0; r < rows; r++) {
|
|
57
|
+
const rowCells = [];
|
|
58
|
+
for (let c = 0; c < cols; c++) {
|
|
59
|
+
const cell = cellMap.get(`${r},${c}`);
|
|
60
|
+
const selected = interactive && isSelected(r, c);
|
|
61
|
+
const bg = cell?.color ? hex(cell.color) : "transparent";
|
|
62
|
+
const cellContent = cell?.content ? (_jsx(Text, { style: [styles.cellText, { color: colors.textPrimary }], children: cell.content })) : null;
|
|
63
|
+
// Two-tone ring: outer View with contrasting border, inner View with inverse border
|
|
64
|
+
const cellView = selected ? (_jsx(View, { style: [styles.cell, { height: rowHeight, borderWidth: 1, borderColor: ringOuter, borderRadius: 4 }], children: _jsx(View, { style: [
|
|
65
|
+
styles.innerCell,
|
|
66
|
+
{ backgroundColor: bg, borderWidth: 1, borderColor: ringInner, borderRadius: 3 },
|
|
67
|
+
], children: cellContent }) })) : (_jsx(View, { style: [styles.cell, { height: rowHeight, backgroundColor: bg }], children: cellContent }));
|
|
68
|
+
rowCells.push(interactive ? (_jsx(Pressable, { onPress: () => handleTap(r, c), style: styles.cellWrap, children: cellView }, `${r}-${c}`)) : (_jsx(View, { style: styles.cellWrap, children: cellView }, `${r}-${c}`)));
|
|
69
|
+
}
|
|
70
|
+
rowEls.push(_jsx(View, { style: [styles.gridRow, { gap: gapPx }], children: rowCells }, r));
|
|
71
|
+
}
|
|
72
|
+
const selectionLabel = interactive && selectedSet.size > 0
|
|
73
|
+
? `inputs.${name}: ${[...selectedSet].join(isMultiple ? " | " : "")}`
|
|
74
|
+
: null;
|
|
75
|
+
return (_jsxs(View, { style: [styles.wrap, { gap: gapPx, backgroundColor: colors.muted, padding: 4, borderRadius: 8 }], children: [rowEls, selectionLabel ? (_jsx(Text, { style: [styles.selectionText, { color: colors.textSecondary }], children: selectionLabel })) : null] }));
|
|
76
|
+
}
|
|
77
|
+
const styles = StyleSheet.create({
|
|
78
|
+
wrap: { width: "100%" },
|
|
79
|
+
gridRow: { flexDirection: "row" },
|
|
80
|
+
cellWrap: { flex: 1 },
|
|
81
|
+
cell: {
|
|
82
|
+
borderRadius: 4,
|
|
83
|
+
alignItems: "center",
|
|
84
|
+
justifyContent: "center",
|
|
85
|
+
},
|
|
86
|
+
innerCell: {
|
|
87
|
+
width: "100%",
|
|
88
|
+
height: "100%",
|
|
89
|
+
alignItems: "center",
|
|
90
|
+
justifyContent: "center",
|
|
91
|
+
},
|
|
92
|
+
cellText: { fontSize: 12, lineHeight: 16, fontWeight: "600" },
|
|
93
|
+
selectionText: { fontSize: 11, fontFamily: "monospace", marginTop: 6 },
|
|
94
|
+
});
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { ComponentRenderProps } from "@json-render/react-native";
|
|
2
|
+
import { type LucideIcon } from "lucide-react-native";
|
|
3
|
+
declare const ICON_MAP: Record<string, LucideIcon>;
|
|
4
|
+
export declare function SnapIcon({ element: { props }, }: ComponentRenderProps<Record<string, unknown>>): import("react").JSX.Element;
|
|
5
|
+
export { ICON_MAP };
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { View } from "react-native";
|
|
3
|
+
import { useSnapPalette } from "../use-snap-palette.js";
|
|
4
|
+
import { ArrowRight, ArrowLeft, ExternalLink, ChevronRight, Check, X, AlertTriangle, Info, Clock, Heart, MessageCircle, Repeat, Share, User, Users, Star, Trophy, Zap, Flame, Gift, ImageIcon, Play, Pause, Wallet, Coins, Plus, Minus, RefreshCw, Bookmark, ThumbsUp, ThumbsDown, TrendingUp, TrendingDown, } from "lucide-react-native";
|
|
5
|
+
const ICON_MAP = {
|
|
6
|
+
"arrow-right": ArrowRight,
|
|
7
|
+
"arrow-left": ArrowLeft,
|
|
8
|
+
"external-link": ExternalLink,
|
|
9
|
+
"chevron-right": ChevronRight,
|
|
10
|
+
check: Check,
|
|
11
|
+
x: X,
|
|
12
|
+
"alert-triangle": AlertTriangle,
|
|
13
|
+
info: Info,
|
|
14
|
+
clock: Clock,
|
|
15
|
+
heart: Heart,
|
|
16
|
+
"message-circle": MessageCircle,
|
|
17
|
+
repeat: Repeat,
|
|
18
|
+
share: Share,
|
|
19
|
+
user: User,
|
|
20
|
+
users: Users,
|
|
21
|
+
star: Star,
|
|
22
|
+
trophy: Trophy,
|
|
23
|
+
zap: Zap,
|
|
24
|
+
flame: Flame,
|
|
25
|
+
gift: Gift,
|
|
26
|
+
image: ImageIcon,
|
|
27
|
+
play: Play,
|
|
28
|
+
pause: Pause,
|
|
29
|
+
wallet: Wallet,
|
|
30
|
+
coins: Coins,
|
|
31
|
+
plus: Plus,
|
|
32
|
+
minus: Minus,
|
|
33
|
+
"refresh-cw": RefreshCw,
|
|
34
|
+
bookmark: Bookmark,
|
|
35
|
+
"thumbs-up": ThumbsUp,
|
|
36
|
+
"thumbs-down": ThumbsDown,
|
|
37
|
+
"trending-up": TrendingUp,
|
|
38
|
+
"trending-down": TrendingDown,
|
|
39
|
+
};
|
|
40
|
+
const SIZE_PX = {
|
|
41
|
+
sm: 16,
|
|
42
|
+
md: 20,
|
|
43
|
+
};
|
|
44
|
+
export function SnapIcon({ element: { props }, }) {
|
|
45
|
+
const { accentHex, hex } = useSnapPalette();
|
|
46
|
+
const name = String(props.name ?? "info");
|
|
47
|
+
const size = SIZE_PX[String(props.size ?? "md")] ?? 20;
|
|
48
|
+
const color = props.color ? String(props.color) : undefined;
|
|
49
|
+
const isAccent = !color || color === "accent";
|
|
50
|
+
const resolvedColor = isAccent ? accentHex : hex(color);
|
|
51
|
+
const Icon = ICON_MAP[name];
|
|
52
|
+
if (!Icon)
|
|
53
|
+
return null;
|
|
54
|
+
return (_jsx(View, { style: { alignItems: "center", justifyContent: "center" }, children: _jsx(Icon, { size: size, color: resolvedColor }) }));
|
|
55
|
+
}
|
|
56
|
+
export { ICON_MAP };
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Image } from "expo-image";
|
|
3
|
+
import { StyleSheet, View } from "react-native";
|
|
4
|
+
function aspectToRatio(aspect) {
|
|
5
|
+
const [w, h] = aspect.split(":").map(Number);
|
|
6
|
+
if (!w || !h)
|
|
7
|
+
return 1;
|
|
8
|
+
return w / h;
|
|
9
|
+
}
|
|
10
|
+
export function SnapImage({ element: { props }, }) {
|
|
11
|
+
const url = String(props.url ?? "");
|
|
12
|
+
const alt = String(props.alt ?? "");
|
|
13
|
+
const ratio = aspectToRatio(String(props.aspect ?? "1:1"));
|
|
14
|
+
return (_jsx(View, { style: [styles.frame, { aspectRatio: ratio }], children: _jsx(Image, { source: { uri: url }, style: StyleSheet.absoluteFill, contentFit: "cover", accessibilityLabel: alt || undefined }) }));
|
|
15
|
+
}
|
|
16
|
+
const styles = StyleSheet.create({
|
|
17
|
+
frame: {
|
|
18
|
+
width: "100%",
|
|
19
|
+
borderRadius: 8,
|
|
20
|
+
overflow: "hidden",
|
|
21
|
+
backgroundColor: "#f3f4f6",
|
|
22
|
+
},
|
|
23
|
+
});
|