@farcaster/snap 2.0.0 → 2.0.2

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.
Files changed (194) hide show
  1. package/dist/colors.d.ts +4 -4
  2. package/dist/colors.js +20 -20
  3. package/dist/constants.d.ts +17 -1
  4. package/dist/constants.js +19 -1
  5. package/dist/index.d.ts +4 -6
  6. package/dist/index.js +2 -4
  7. package/dist/react/accent-context.d.ts +3 -1
  8. package/dist/react/accent-context.js +7 -4
  9. package/dist/react/catalog-renderer.js +4 -0
  10. package/dist/react/components/action-button.d.ts +2 -1
  11. package/dist/react/components/action-button.js +35 -13
  12. package/dist/react/components/badge.js +8 -8
  13. package/dist/react/components/bar-chart.d.ts +5 -0
  14. package/dist/react/components/bar-chart.js +26 -0
  15. package/dist/react/components/cell-grid.d.ts +5 -0
  16. package/dist/react/components/cell-grid.js +87 -0
  17. package/dist/react/components/icon.js +4 -10
  18. package/dist/react/components/input.js +12 -6
  19. package/dist/react/components/item-group.js +3 -1
  20. package/dist/react/components/item.d.ts +3 -3
  21. package/dist/react/components/item.js +4 -3
  22. package/dist/react/components/progress.js +3 -3
  23. package/dist/react/components/separator.js +3 -1
  24. package/dist/react/components/slider.js +15 -10
  25. package/dist/react/components/switch.js +10 -12
  26. package/dist/react/components/text.js +6 -14
  27. package/dist/react/components/toggle-group.js +20 -6
  28. package/dist/react/hooks/use-snap-colors.d.ts +38 -0
  29. package/dist/react/hooks/use-snap-colors.js +81 -0
  30. package/dist/react/index.d.ts +13 -1
  31. package/dist/react/index.js +9 -188
  32. package/dist/react/snap-view-core.d.ts +11 -0
  33. package/dist/react/snap-view-core.js +227 -0
  34. package/dist/react/v1/snap-view.d.ts +16 -0
  35. package/dist/react/v1/snap-view.js +90 -0
  36. package/dist/react/v2/snap-view.d.ts +23 -0
  37. package/dist/react/v2/snap-view.js +91 -0
  38. package/dist/react-native/catalog-renderer.d.ts +5 -0
  39. package/dist/react-native/catalog-renderer.js +40 -0
  40. package/dist/react-native/components/snap-action-button.d.ts +2 -0
  41. package/dist/react-native/components/snap-action-button.js +69 -0
  42. package/dist/react-native/components/snap-badge.d.ts +2 -0
  43. package/dist/react-native/components/snap-badge.js +41 -0
  44. package/dist/react-native/components/snap-bar-chart.d.ts +2 -0
  45. package/dist/react-native/components/snap-bar-chart.js +39 -0
  46. package/dist/react-native/components/snap-cell-grid.d.ts +2 -0
  47. package/dist/react-native/components/snap-cell-grid.js +94 -0
  48. package/dist/react-native/components/snap-icon.d.ts +5 -0
  49. package/dist/react-native/components/snap-icon.js +56 -0
  50. package/dist/react-native/components/snap-image.d.ts +2 -0
  51. package/dist/react-native/components/snap-image.js +23 -0
  52. package/dist/react-native/components/snap-input.d.ts +2 -0
  53. package/dist/react-native/components/snap-input.js +37 -0
  54. package/dist/react-native/components/snap-item-group.d.ts +5 -0
  55. package/dist/react-native/components/snap-item-group.js +23 -0
  56. package/dist/react-native/components/snap-item.d.ts +5 -0
  57. package/dist/react-native/components/snap-item.js +42 -0
  58. package/dist/react-native/components/snap-progress.d.ts +2 -0
  59. package/dist/react-native/components/snap-progress.js +26 -0
  60. package/dist/react-native/components/snap-separator.d.ts +2 -0
  61. package/dist/react-native/components/snap-separator.js +23 -0
  62. package/dist/react-native/components/snap-slider.d.ts +2 -0
  63. package/dist/react-native/components/snap-slider.js +43 -0
  64. package/dist/react-native/components/snap-stack.d.ts +5 -0
  65. package/dist/react-native/components/snap-stack.js +49 -0
  66. package/dist/react-native/components/snap-switch.d.ts +2 -0
  67. package/dist/react-native/components/snap-switch.js +31 -0
  68. package/dist/react-native/components/snap-text.d.ts +2 -0
  69. package/dist/react-native/components/snap-text.js +35 -0
  70. package/dist/react-native/components/snap-toggle-group.d.ts +2 -0
  71. package/dist/react-native/components/snap-toggle-group.js +99 -0
  72. package/dist/react-native/confetti-overlay.d.ts +1 -0
  73. package/dist/react-native/confetti-overlay.js +106 -0
  74. package/dist/react-native/index.d.ts +28 -0
  75. package/dist/react-native/index.js +15 -0
  76. package/dist/react-native/snap-view-core.d.ts +11 -0
  77. package/dist/react-native/snap-view-core.js +156 -0
  78. package/dist/react-native/theme.d.ts +27 -0
  79. package/dist/react-native/theme.js +43 -0
  80. package/dist/react-native/types.d.ts +42 -0
  81. package/dist/react-native/types.js +1 -0
  82. package/dist/react-native/use-snap-palette.d.ts +13 -0
  83. package/dist/react-native/use-snap-palette.js +48 -0
  84. package/dist/react-native/v1/snap-view.d.ts +24 -0
  85. package/dist/react-native/v1/snap-view.js +96 -0
  86. package/dist/react-native/v2/snap-view.d.ts +33 -0
  87. package/dist/react-native/v2/snap-view.js +114 -0
  88. package/dist/schemas.d.ts +100 -13
  89. package/dist/schemas.js +28 -10
  90. package/dist/server/parseRequest.d.ts +10 -0
  91. package/dist/server/parseRequest.js +48 -7
  92. package/dist/server/verify.d.ts +1 -0
  93. package/dist/server/verify.js +1 -0
  94. package/dist/ui/badge.d.ts +7 -2
  95. package/dist/ui/badge.js +2 -0
  96. package/dist/ui/bar-chart.d.ts +30 -0
  97. package/dist/ui/bar-chart.js +30 -0
  98. package/dist/ui/button.d.ts +4 -6
  99. package/dist/ui/button.js +1 -1
  100. package/dist/ui/catalog.d.ts +90 -16
  101. package/dist/ui/catalog.js +17 -3
  102. package/dist/ui/cell-grid.d.ts +34 -0
  103. package/dist/ui/cell-grid.js +39 -0
  104. package/dist/ui/icon.d.ts +2 -2
  105. package/dist/ui/image.d.ts +1 -2
  106. package/dist/ui/image.js +1 -1
  107. package/dist/ui/index.d.ts +4 -0
  108. package/dist/ui/index.js +2 -0
  109. package/dist/ui/item.d.ts +1 -3
  110. package/dist/ui/item.js +1 -1
  111. package/dist/ui/schema.d.ts +6 -2
  112. package/dist/ui/schema.js +2 -2
  113. package/dist/ui/slider.d.ts +1 -0
  114. package/dist/ui/slider.js +2 -0
  115. package/dist/ui/text.d.ts +2 -4
  116. package/dist/ui/text.js +2 -2
  117. package/dist/validator.d.ts +3 -2
  118. package/dist/validator.js +203 -2
  119. package/llms.txt +199 -0
  120. package/package.json +9 -3
  121. package/src/colors.ts +20 -20
  122. package/src/constants.ts +23 -1
  123. package/src/index.ts +16 -13
  124. package/src/react/accent-context.tsx +13 -6
  125. package/src/react/catalog-renderer.tsx +4 -0
  126. package/src/react/components/action-button.tsx +50 -20
  127. package/src/react/components/badge.tsx +14 -18
  128. package/src/react/components/bar-chart.tsx +69 -0
  129. package/src/react/components/cell-grid.tsx +128 -0
  130. package/src/react/components/icon.tsx +5 -18
  131. package/src/react/components/input.tsx +20 -9
  132. package/src/react/components/item-group.tsx +4 -1
  133. package/src/react/components/item.tsx +13 -10
  134. package/src/react/components/progress.tsx +12 -7
  135. package/src/react/components/separator.tsx +8 -1
  136. package/src/react/components/slider.tsx +28 -15
  137. package/src/react/components/switch.tsx +12 -16
  138. package/src/react/components/text.tsx +14 -23
  139. package/src/react/components/toggle-group.tsx +26 -9
  140. package/src/react/hooks/use-snap-colors.ts +128 -0
  141. package/src/react/index.tsx +49 -265
  142. package/src/react/snap-view-core.tsx +343 -0
  143. package/src/react/v1/snap-view.tsx +176 -0
  144. package/src/react/v2/snap-view.tsx +199 -0
  145. package/src/react-native/catalog-renderer.tsx +41 -0
  146. package/src/react-native/components/snap-action-button.tsx +96 -0
  147. package/src/react-native/components/snap-badge.tsx +60 -0
  148. package/src/react-native/components/snap-bar-chart.tsx +73 -0
  149. package/src/react-native/components/snap-cell-grid.tsx +150 -0
  150. package/src/react-native/components/snap-icon.tsx +102 -0
  151. package/src/react-native/components/snap-image.tsx +37 -0
  152. package/src/react-native/components/snap-input.tsx +58 -0
  153. package/src/react-native/components/snap-item-group.tsx +43 -0
  154. package/src/react-native/components/snap-item.tsx +66 -0
  155. package/src/react-native/components/snap-progress.tsx +40 -0
  156. package/src/react-native/components/snap-separator.tsx +32 -0
  157. package/src/react-native/components/snap-slider.tsx +85 -0
  158. package/src/react-native/components/snap-stack.tsx +66 -0
  159. package/src/react-native/components/snap-switch.tsx +46 -0
  160. package/src/react-native/components/snap-text.tsx +51 -0
  161. package/src/react-native/components/snap-toggle-group.tsx +127 -0
  162. package/src/react-native/confetti-overlay.tsx +134 -0
  163. package/src/react-native/index.tsx +83 -0
  164. package/src/react-native/snap-view-core.tsx +212 -0
  165. package/src/react-native/theme.tsx +85 -0
  166. package/src/react-native/types.ts +38 -0
  167. package/src/react-native/use-snap-palette.ts +64 -0
  168. package/src/react-native/v1/snap-view.tsx +229 -0
  169. package/src/react-native/v2/snap-view.tsx +283 -0
  170. package/src/schemas.ts +68 -17
  171. package/src/server/parseRequest.ts +68 -9
  172. package/src/server/verify.ts +2 -0
  173. package/src/ui/README.md +8 -8
  174. package/src/ui/badge.ts +2 -0
  175. package/src/ui/bar-chart.ts +38 -0
  176. package/src/ui/button.ts +1 -1
  177. package/src/ui/catalog.ts +19 -3
  178. package/src/ui/cell-grid.ts +49 -0
  179. package/src/ui/image.ts +1 -1
  180. package/src/ui/index.ts +6 -0
  181. package/src/ui/item.ts +1 -1
  182. package/src/ui/schema.ts +2 -2
  183. package/src/ui/slider.ts +2 -0
  184. package/src/ui/text.ts +2 -2
  185. package/src/validator.ts +251 -2
  186. package/dist/dataStore.d.ts +0 -12
  187. package/dist/dataStore.js +0 -35
  188. package/dist/middleware.d.ts +0 -3
  189. package/dist/middleware.js +0 -3
  190. package/dist/react/hooks/use-snap-accent.d.ts +0 -13
  191. package/dist/react/hooks/use-snap-accent.js +0 -32
  192. package/src/dataStore.ts +0 -62
  193. package/src/middleware.ts +0 -7
  194. package/src/react/hooks/use-snap-accent.ts +0 -45
@@ -0,0 +1,106 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useEffect, useMemo, useRef } from "react";
3
+ import { Animated, StyleSheet, View, useWindowDimensions, } from "react-native";
4
+ const CONFETTI_COLORS = [
5
+ "#907AA9",
6
+ "#EC4899",
7
+ "#3B82F6",
8
+ "#10B981",
9
+ "#F59E0B",
10
+ "#EF4444",
11
+ "#06B6D4",
12
+ ];
13
+ export function ConfettiOverlay() {
14
+ const { width, height } = useWindowDimensions();
15
+ const pieces = useMemo(() => Array.from({ length: 80 }, (_, i) => ({
16
+ id: i,
17
+ left: Math.random() * width,
18
+ delay: Math.random() * 1200,
19
+ duration: 2500 + Math.random() * 2000,
20
+ color: CONFETTI_COLORS[Math.floor(Math.random() * CONFETTI_COLORS.length)],
21
+ size: 6 + Math.random() * 8,
22
+ startRotation: Math.random() * 360,
23
+ driftX: (Math.random() > 0.5 ? 1 : -1) * Math.random() * 40,
24
+ })),
25
+ // width captured once on mount; intentional stable dep
26
+ // eslint-disable-next-line react-hooks/exhaustive-deps
27
+ []);
28
+ const anims = useRef(pieces.map(() => ({
29
+ translateY: new Animated.Value(-20),
30
+ opacity: new Animated.Value(1),
31
+ rotate: new Animated.Value(0),
32
+ translateX: new Animated.Value(0),
33
+ }))).current;
34
+ useEffect(() => {
35
+ const animations = pieces.map((piece, i) => {
36
+ const anim = anims[i];
37
+ anim.translateY.setValue(-20);
38
+ anim.opacity.setValue(1);
39
+ anim.rotate.setValue(0);
40
+ anim.translateX.setValue(0);
41
+ return Animated.sequence([
42
+ Animated.delay(piece.delay),
43
+ Animated.parallel([
44
+ Animated.timing(anim.translateY, {
45
+ toValue: height + 20,
46
+ duration: piece.duration,
47
+ useNativeDriver: true,
48
+ }),
49
+ Animated.timing(anim.opacity, {
50
+ toValue: 0,
51
+ duration: piece.duration,
52
+ useNativeDriver: true,
53
+ }),
54
+ Animated.timing(anim.rotate, {
55
+ toValue: 720,
56
+ duration: piece.duration,
57
+ useNativeDriver: true,
58
+ }),
59
+ Animated.timing(anim.translateX, {
60
+ toValue: piece.driftX,
61
+ duration: piece.duration,
62
+ useNativeDriver: true,
63
+ }),
64
+ ]),
65
+ ]);
66
+ });
67
+ const composite = Animated.parallel(animations);
68
+ composite.start();
69
+ return () => composite.stop();
70
+ }, [pieces, anims, height]);
71
+ return (_jsx(View, { style: [StyleSheet.absoluteFill, styles.container], pointerEvents: "none", children: pieces.map((piece, i) => {
72
+ const anim = anims[i];
73
+ const rotate = anim.rotate.interpolate({
74
+ inputRange: [0, 720],
75
+ outputRange: [
76
+ `${piece.startRotation}deg`,
77
+ `${piece.startRotation + 720}deg`,
78
+ ],
79
+ });
80
+ return (_jsx(Animated.View, { style: [
81
+ styles.piece,
82
+ {
83
+ left: piece.left,
84
+ width: piece.size,
85
+ height: piece.size * 0.6,
86
+ backgroundColor: piece.color,
87
+ opacity: anim.opacity,
88
+ transform: [
89
+ { translateY: anim.translateY },
90
+ { translateX: anim.translateX },
91
+ { rotate },
92
+ ],
93
+ },
94
+ ] }, piece.id));
95
+ }) }));
96
+ }
97
+ const styles = StyleSheet.create({
98
+ container: {
99
+ overflow: "hidden",
100
+ },
101
+ piece: {
102
+ position: "absolute",
103
+ top: 0,
104
+ borderRadius: 2,
105
+ },
106
+ });
@@ -0,0 +1,28 @@
1
+ import type { ReactNode } from "react";
2
+ import type { ValidationResult } from "@farcaster/snap";
3
+ import type { SnapNativeColors } from "./theme.js";
4
+ import type { SnapPage, SnapActionHandlers } from "./types.js";
5
+ import { useSnapTheme } from "./theme.js";
6
+ import { hexToRgba } from "./use-snap-palette.js";
7
+ export type { JsonValue, SnapPage, SnapActionHandlers } from "./types.js";
8
+ export { useSnapTheme, hexToRgba };
9
+ export type { SnapNativeColors };
10
+ export declare function SnapCard({ snap, handlers, loading, appearance, colors, borderRadius, showOverflowWarning, onValidationError, validationErrorFallback, actionError, plain, }: {
11
+ snap: SnapPage;
12
+ handlers: SnapActionHandlers;
13
+ loading?: boolean;
14
+ appearance?: "light" | "dark";
15
+ colors?: Partial<SnapNativeColors>;
16
+ /** Border radius of the card (default 16). */
17
+ borderRadius?: number;
18
+ /** When true (v2 only), extends to 700px and shows a warning overlay below 500px. When false, clips at 500px. */
19
+ showOverflowWarning?: boolean;
20
+ /** Called when snap validation fails (v2 only). */
21
+ onValidationError?: (result: ValidationResult) => void;
22
+ /** Custom fallback rendered when validation fails (v2 only). */
23
+ validationErrorFallback?: ReactNode;
24
+ /** Server-side action error message to display inline. */
25
+ actionError?: string | null;
26
+ /** When true, renders without card frame (no border, background, or padding). */
27
+ plain?: boolean;
28
+ }): import("react").JSX.Element;
@@ -0,0 +1,15 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { SPEC_VERSION_2 } from "@farcaster/snap";
3
+ import { useSnapTheme } from "./theme.js";
4
+ import { hexToRgba } from "./use-snap-palette.js";
5
+ import { SnapCardV1 } from "./v1/snap-view.js";
6
+ import { SnapCardV2 } from "./v2/snap-view.js";
7
+ // ─── Re-exports ───────────────────────────────────────
8
+ export { useSnapTheme, hexToRgba };
9
+ // ─── SnapCard (version-switching) ─────────────────────
10
+ export function SnapCard({ snap, handlers, loading = false, appearance = "dark", colors, borderRadius = 16, showOverflowWarning = false, onValidationError, validationErrorFallback, actionError, plain = false, }) {
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 }));
13
+ }
14
+ return (_jsx(SnapCardV1, { snap: snap, handlers: handlers, loading: loading, appearance: appearance, colors: colors, borderRadius: borderRadius, actionError: actionError, plain: plain }));
15
+ }
@@ -0,0 +1,11 @@
1
+ import type { SnapPage, SnapActionHandlers } from "./types.js";
2
+ export declare function applyStatePaths(model: Record<string, unknown>, changes: {
3
+ path: string;
4
+ value: unknown;
5
+ }[] | Record<string, unknown>): void;
6
+ export declare function resolveAccentHex(accent: string | undefined, appearance: "light" | "dark"): string;
7
+ export declare function SnapViewCoreInner({ snap, handlers, loading, }: {
8
+ snap: SnapPage;
9
+ handlers: SnapActionHandlers;
10
+ loading?: boolean;
11
+ }): import("react").JSX.Element;
@@ -0,0 +1,156 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { snapJsonRenderCatalog } from "@farcaster/snap/ui";
3
+ import { SnapCatalogView } from "./catalog-renderer.js";
4
+ import { useSnapTheme } from "./theme.js";
5
+ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
6
+ import { ActivityIndicator, StyleSheet, View } from "react-native";
7
+ import { DEFAULT_THEME_ACCENT, PALETTE_LIGHT_HEX, PALETTE_DARK_HEX, } from "@farcaster/snap";
8
+ // ─── Shared helpers ──────────────────────────────────
9
+ export function applyStatePaths(model, changes) {
10
+ const entries = Array.isArray(changes)
11
+ ? changes.map((c) => [c.path, c.value])
12
+ : Object.entries(changes);
13
+ for (const [path, value] of entries) {
14
+ const trimmed = path.startsWith("/") ? path : `/${path}`;
15
+ const parts = trimmed.split("/").filter(Boolean);
16
+ if (parts.length < 2)
17
+ continue;
18
+ const [top, ...rest] = parts;
19
+ if (top === "inputs") {
20
+ if (typeof model.inputs !== "object" || model.inputs === null) {
21
+ model.inputs = {};
22
+ }
23
+ const inputs = model.inputs;
24
+ if (rest.length === 1) {
25
+ inputs[rest[0]] = value;
26
+ }
27
+ continue;
28
+ }
29
+ if (top === "theme") {
30
+ if (typeof model.theme !== "object" || model.theme === null) {
31
+ model.theme = {};
32
+ }
33
+ const theme = model.theme;
34
+ if (rest.length === 1) {
35
+ theme[rest[0]] = value;
36
+ }
37
+ }
38
+ }
39
+ }
40
+ export function resolveAccentHex(accent, appearance) {
41
+ const map = appearance === "dark" ? PALETTE_DARK_HEX : PALETTE_LIGHT_HEX;
42
+ const name = accent && Object.hasOwn(map, accent)
43
+ ? accent
44
+ : DEFAULT_THEME_ACCENT;
45
+ return map[name];
46
+ }
47
+ // ─── Core rendering component (no validation) ────────
48
+ export function SnapViewCoreInner({ snap, handlers, loading = false, }) {
49
+ const { mode } = useSnapTheme();
50
+ const spec = snap.ui;
51
+ const accentHex = resolveAccentHex(snap.theme?.accent, mode);
52
+ const initialState = useMemo(() => ({
53
+ ...(spec.state ?? {}),
54
+ inputs: { ...(spec.state?.inputs ?? {}) },
55
+ theme: {
56
+ ...(spec.state?.theme ?? {}),
57
+ ...(snap.theme ? { accent: snap.theme.accent } : {}),
58
+ },
59
+ }), [spec, snap.theme]);
60
+ const stateRef = useRef(initialState);
61
+ useEffect(() => {
62
+ stateRef.current = {
63
+ inputs: {
64
+ ...(initialState.inputs ?? {}),
65
+ },
66
+ theme: {
67
+ ...(initialState.theme ?? {}),
68
+ },
69
+ };
70
+ }, [initialState]);
71
+ useEffect(() => {
72
+ const catalogResult = snapJsonRenderCatalog.validate(spec);
73
+ if (!catalogResult.success) {
74
+ // eslint-disable-next-line no-console
75
+ console.warn("[Snap] catalog validation issues:", catalogResult.error);
76
+ }
77
+ }, [spec]);
78
+ const [pageKey, setPageKey] = useState(0);
79
+ useEffect(() => {
80
+ setPageKey((k) => k + 1);
81
+ }, [spec]);
82
+ const handlersRef = useRef(handlers);
83
+ handlersRef.current = handlers;
84
+ const handleAction = useCallback((name, params) => {
85
+ const inputs = (stateRef.current.inputs ?? {});
86
+ const p = (params ?? {});
87
+ const h = handlersRef.current;
88
+ switch (name) {
89
+ case "submit":
90
+ h.submit(String(p.target ?? ""), inputs);
91
+ break;
92
+ case "open_url":
93
+ h.open_url(String(p.target ?? ""));
94
+ break;
95
+ case "open_snap":
96
+ h.open_snap(String(p.target ?? ""));
97
+ break;
98
+ case "open_mini_app":
99
+ h.open_mini_app(String(p.target ?? ""));
100
+ break;
101
+ case "view_cast":
102
+ h.view_cast({ hash: String(p.hash ?? "") });
103
+ break;
104
+ case "view_profile":
105
+ h.view_profile({ fid: Number(p.fid ?? 0) });
106
+ break;
107
+ case "compose_cast":
108
+ h.compose_cast({
109
+ text: p.text ? String(p.text) : undefined,
110
+ channelKey: p.channelKey ? String(p.channelKey) : undefined,
111
+ embeds: Array.isArray(p.embeds) ? p.embeds : undefined,
112
+ });
113
+ break;
114
+ case "view_token":
115
+ h.view_token({ token: String(p.token ?? "") });
116
+ break;
117
+ case "send_token":
118
+ h.send_token({
119
+ token: String(p.token ?? ""),
120
+ amount: p.amount ? String(p.amount) : undefined,
121
+ recipientFid: p.recipientFid ? Number(p.recipientFid) : undefined,
122
+ recipientAddress: p.recipientAddress
123
+ ? String(p.recipientAddress)
124
+ : undefined,
125
+ });
126
+ break;
127
+ case "swap_token":
128
+ h.swap_token({
129
+ sellToken: p.sellToken ? String(p.sellToken) : undefined,
130
+ buyToken: p.buyToken ? String(p.buyToken) : undefined,
131
+ });
132
+ break;
133
+ default:
134
+ break;
135
+ }
136
+ }, []);
137
+ return (_jsxs(View, { style: styles.container, children: [loading ? (_jsx(View, { style: [
138
+ styles.overlay,
139
+ {
140
+ backgroundColor: mode === "dark" ? "rgba(0,0,0,0.1)" : "rgba(255,255,255,0.2)",
141
+ },
142
+ ], children: _jsx(ActivityIndicator, { size: "large", color: accentHex }) })) : null, _jsx(SnapCatalogView, { spec: spec, state: initialState, loading: false, onStateChange: (changes) => {
143
+ applyStatePaths(stateRef.current, changes);
144
+ }, onAction: handleAction }, pageKey)] }));
145
+ }
146
+ const styles = StyleSheet.create({
147
+ container: {
148
+ width: "100%",
149
+ },
150
+ overlay: {
151
+ ...StyleSheet.absoluteFillObject,
152
+ alignItems: "center",
153
+ justifyContent: "center",
154
+ zIndex: 10,
155
+ },
156
+ });
@@ -0,0 +1,27 @@
1
+ import { type ReactNode } from "react";
2
+ export type SnapNativeColors = {
3
+ bg: string;
4
+ surface: string;
5
+ text: string;
6
+ textSecondary: string;
7
+ border: string;
8
+ inputBg: string;
9
+ muted: string;
10
+ /** Subtle tint for toggle button resting state */
11
+ mutedSubtle: string;
12
+ /** Slightly stronger tint for hover/press state */
13
+ mutedHover: string;
14
+ /** Stronger tint for selected state (toggle group) */
15
+ mutedSelected: string;
16
+ };
17
+ interface SnapThemeValue {
18
+ mode: "light" | "dark";
19
+ colors: SnapNativeColors;
20
+ }
21
+ export declare function SnapThemeProvider({ appearance, colors, children, }: {
22
+ appearance: "light" | "dark";
23
+ colors?: Partial<SnapNativeColors>;
24
+ children: ReactNode;
25
+ }): import("react").JSX.Element;
26
+ export declare function useSnapTheme(): SnapThemeValue;
27
+ export {};
@@ -0,0 +1,43 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { createContext, useContext, useMemo } from "react";
3
+ const DEFAULT_LIGHT = {
4
+ bg: "#dfe3e8",
5
+ surface: "#ffffff",
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
+ inputBg: "rgba(0,0,0,0.06)",
10
+ muted: "rgba(0,0,0,0.08)",
11
+ mutedSubtle: "rgba(0,0,0,0.04)",
12
+ mutedHover: "rgba(0,0,0,0.12)",
13
+ mutedSelected: "rgba(0,0,0,0.16)",
14
+ };
15
+ const DEFAULT_DARK = {
16
+ bg: "#111318",
17
+ surface: "#1a1d24",
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
+ inputBg: "rgba(255,255,255,0.04)",
22
+ muted: "rgba(255,255,255,0.06)",
23
+ mutedSubtle: "rgba(255,255,255,0.03)",
24
+ mutedHover: "rgba(255,255,255,0.08)",
25
+ mutedSelected: "rgba(255,255,255,0.12)",
26
+ };
27
+ const SnapThemeContext = createContext({
28
+ mode: "dark",
29
+ colors: DEFAULT_DARK,
30
+ });
31
+ export function SnapThemeProvider({ appearance, colors, children, }) {
32
+ const value = useMemo(() => {
33
+ const defaults = appearance === "dark" ? DEFAULT_DARK : DEFAULT_LIGHT;
34
+ return {
35
+ mode: appearance,
36
+ colors: colors ? { ...defaults, ...colors } : defaults,
37
+ };
38
+ }, [appearance, colors]);
39
+ return (_jsx(SnapThemeContext.Provider, { value: value, children: children }));
40
+ }
41
+ export function useSnapTheme() {
42
+ return useContext(SnapThemeContext);
43
+ }
@@ -0,0 +1,42 @@
1
+ import type { Spec } from "@json-render/core";
2
+ export type JsonValue = string | number | boolean | null | JsonValue[] | {
3
+ [key: string]: JsonValue;
4
+ };
5
+ export type SnapPage = {
6
+ version: string;
7
+ theme?: {
8
+ accent?: string;
9
+ };
10
+ effects?: string[];
11
+ ui: Spec;
12
+ };
13
+ export type SnapActionHandlers = {
14
+ submit: (target: string, inputs: Record<string, JsonValue>) => void;
15
+ open_url: (target: string) => void;
16
+ open_snap: (target: string) => void;
17
+ open_mini_app: (target: string) => void;
18
+ view_cast: (params: {
19
+ hash: string;
20
+ }) => void;
21
+ view_profile: (params: {
22
+ fid: number;
23
+ }) => void;
24
+ compose_cast: (params: {
25
+ text?: string;
26
+ channelKey?: string;
27
+ embeds?: string[];
28
+ }) => void;
29
+ view_token: (params: {
30
+ token: string;
31
+ }) => void;
32
+ send_token: (params: {
33
+ token: string;
34
+ amount?: string;
35
+ recipientFid?: number;
36
+ recipientAddress?: string;
37
+ }) => void;
38
+ swap_token: (params: {
39
+ sellToken?: string;
40
+ buyToken?: string;
41
+ }) => void;
42
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,13 @@
1
+ export declare function useSnapPalette(): {
2
+ appearance: "light" | "dark";
3
+ accentName: "gray" | "blue" | "red" | "amber" | "green" | "teal" | "purple" | "pink";
4
+ accentHex: string;
5
+ hex: (semantic: string) => string;
6
+ };
7
+ /** `#RRGGBB` + alpha → `rgba(...)` for React Native styles. */
8
+ export declare function hexToRgba(hex: string, alpha: number): string;
9
+ export declare function useSnapPreviewChromePalette(themeAccent: string | undefined): {
10
+ appearance: "light" | "dark";
11
+ accentName: "gray" | "blue" | "red" | "amber" | "green" | "teal" | "purple" | "pink";
12
+ accentHex: string;
13
+ };
@@ -0,0 +1,48 @@
1
+ import { DEFAULT_THEME_ACCENT, PALETTE_COLOR_VALUES, PALETTE_LIGHT_HEX, PALETTE_DARK_HEX, } from "@farcaster/snap";
2
+ import { useStateStore } from "@json-render/react-native";
3
+ import { useSnapTheme } from "./theme.js";
4
+ function resolveHex(name, appearance) {
5
+ const map = appearance === "dark" ? PALETTE_DARK_HEX : PALETTE_LIGHT_HEX;
6
+ if (Object.hasOwn(map, name)) {
7
+ return map[name];
8
+ }
9
+ return map.purple;
10
+ }
11
+ function isPaletteColor(s) {
12
+ return PALETTE_COLOR_VALUES.includes(s);
13
+ }
14
+ function themeAccentFromStore(get) {
15
+ const raw = get("/theme/accent");
16
+ if (typeof raw === "string" && isPaletteColor(raw)) {
17
+ return raw;
18
+ }
19
+ return DEFAULT_THEME_ACCENT;
20
+ }
21
+ export function useSnapPalette() {
22
+ const { mode } = useSnapTheme();
23
+ const { get } = useStateStore();
24
+ const accentName = themeAccentFromStore(get);
25
+ const accentHex = resolveHex(accentName, mode);
26
+ const hex = (semantic) => semantic === "accent" ? accentHex : resolveHex(semantic, mode);
27
+ return { appearance: mode, accentName, accentHex, hex };
28
+ }
29
+ /** `#RRGGBB` + alpha → `rgba(...)` for React Native styles. */
30
+ export function hexToRgba(hex, alpha) {
31
+ const m = /^#([0-9a-fA-F]{6})$/.exec(hex.trim());
32
+ if (!m) {
33
+ return `rgba(0,0,0,${alpha})`;
34
+ }
35
+ const n = Number.parseInt(m[1], 16);
36
+ const r = (n >> 16) & 255;
37
+ const g = (n >> 8) & 255;
38
+ const b = n & 255;
39
+ return `rgba(${r},${g},${b},${alpha})`;
40
+ }
41
+ export function useSnapPreviewChromePalette(themeAccent) {
42
+ const { mode } = useSnapTheme();
43
+ const accentName = typeof themeAccent === "string" && isPaletteColor(themeAccent)
44
+ ? themeAccent
45
+ : DEFAULT_THEME_ACCENT;
46
+ const accentHex = resolveHex(accentName, mode);
47
+ return { appearance: mode, accentName, accentHex };
48
+ }
@@ -0,0 +1,24 @@
1
+ import { type SnapNativeColors } from "../theme.js";
2
+ import type { SnapPage, SnapActionHandlers } from "../types.js";
3
+ export declare function SnapViewV1Inner({ snap, handlers, loading, }: {
4
+ snap: SnapPage;
5
+ handlers: SnapActionHandlers;
6
+ loading?: boolean;
7
+ }): import("react").JSX.Element;
8
+ export declare function SnapViewV1({ snap, handlers, loading, appearance, colors, }: {
9
+ snap: SnapPage;
10
+ handlers: SnapActionHandlers;
11
+ loading?: boolean;
12
+ appearance?: "light" | "dark";
13
+ colors?: Partial<SnapNativeColors>;
14
+ }): import("react").JSX.Element;
15
+ export declare function SnapCardV1({ snap, handlers, loading, appearance, colors, borderRadius, actionError, plain, }: {
16
+ snap: SnapPage;
17
+ handlers: SnapActionHandlers;
18
+ loading?: boolean;
19
+ appearance?: "light" | "dark";
20
+ colors?: Partial<SnapNativeColors>;
21
+ borderRadius?: number;
22
+ actionError?: string | null;
23
+ plain?: boolean;
24
+ }): import("react").JSX.Element;
@@ -0,0 +1,96 @@
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";
4
+ import { SnapThemeProvider, useSnapTheme } from "../theme.js";
5
+ import { SnapViewCoreInner } from "../snap-view-core.js";
6
+ const SNAP_MAX_HEIGHT = 500;
7
+ // ─── SnapViewV1 (no validation) ──────────────────────
8
+ export function SnapViewV1Inner({ snap, handlers, loading = false, }) {
9
+ return (_jsx(SnapViewCoreInner, { snap: snap, handlers: handlers, loading: loading }));
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 }) }));
13
+ }
14
+ // ─── SnapCardV1 (card frame with expandable clipping) ──
15
+ function SnapCardV1Inner({ snap, handlers, loading = false, borderRadius, actionError, appearance, plain, }) {
16
+ const { colors } = useSnapTheme();
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 : {
28
+ borderRadius,
29
+ borderColor: colors.border,
30
+ backgroundColor: colors.surface,
31
+ },
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: [
54
+ cardStyles.actionError,
55
+ {
56
+ color: appearance === "dark"
57
+ ? "rgba(255,100,100,0.9)"
58
+ : "rgba(200,0,0,0.8)",
59
+ },
60
+ ], children: actionError }))] }));
61
+ }
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 }) }));
64
+ }
65
+ const cardStyles = StyleSheet.create({
66
+ frameRing: { alignSelf: "stretch" },
67
+ card: { overflow: "hidden", borderWidth: 1, minHeight: 120 },
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
+ },
95
+ actionError: { paddingHorizontal: 12, paddingVertical: 8, fontSize: 13 },
96
+ });
@@ -0,0 +1,33 @@
1
+ import type { ReactNode } from "react";
2
+ import { type SnapNativeColors } from "../theme.js";
3
+ import { type ValidationResult } from "@farcaster/snap";
4
+ import type { SnapPage, SnapActionHandlers } from "../types.js";
5
+ export declare function SnapViewV2Inner({ snap, handlers, loading, onValidationError, validationErrorFallback, }: {
6
+ snap: SnapPage;
7
+ handlers: SnapActionHandlers;
8
+ loading?: boolean;
9
+ onValidationError?: (result: ValidationResult) => void;
10
+ validationErrorFallback?: ReactNode;
11
+ }): import("react").JSX.Element;
12
+ export declare function SnapViewV2({ snap, handlers, loading, appearance, colors, onValidationError, validationErrorFallback, }: {
13
+ snap: SnapPage;
14
+ handlers: SnapActionHandlers;
15
+ loading?: boolean;
16
+ appearance?: "light" | "dark";
17
+ colors?: Partial<SnapNativeColors>;
18
+ onValidationError?: (result: ValidationResult) => void;
19
+ validationErrorFallback?: ReactNode;
20
+ }): import("react").JSX.Element;
21
+ export declare function SnapCardV2({ snap, handlers, loading, appearance, colors, borderRadius, showOverflowWarning, onValidationError, validationErrorFallback, actionError, plain, }: {
22
+ snap: SnapPage;
23
+ handlers: SnapActionHandlers;
24
+ loading?: boolean;
25
+ appearance?: "light" | "dark";
26
+ colors?: Partial<SnapNativeColors>;
27
+ borderRadius?: number;
28
+ showOverflowWarning?: boolean;
29
+ onValidationError?: (result: ValidationResult) => void;
30
+ validationErrorFallback?: ReactNode;
31
+ actionError?: string | null;
32
+ plain?: boolean;
33
+ }): import("react").JSX.Element;