@farcaster/snap 2.0.1 → 2.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,7 +2,11 @@ import type { ReactNode } from "react";
2
2
  import { useEffect, useMemo, useState } from "react";
3
3
  import { Platform, StyleSheet, Text, View } from "react-native";
4
4
  import { SnapThemeProvider, useSnapTheme, type SnapNativeColors } from "../theme";
5
- import { SnapViewCoreInner } from "../snap-view-core";
5
+ import {
6
+ SnapLoadingOverlay,
7
+ SnapViewCoreInner,
8
+ resolveAccentHex,
9
+ } from "../snap-view-core";
6
10
  import {
7
11
  validateSnapResponse,
8
12
  type ValidationResult,
@@ -47,12 +51,14 @@ export function SnapViewV2Inner({
47
51
  loading = false,
48
52
  onValidationError,
49
53
  validationErrorFallback,
54
+ loadingOverlay,
50
55
  }: {
51
56
  snap: SnapPage;
52
57
  handlers: SnapActionHandlers;
53
58
  loading?: boolean;
54
59
  onValidationError?: (result: ValidationResult) => void;
55
60
  validationErrorFallback?: ReactNode;
61
+ loadingOverlay?: ReactNode;
56
62
  }) {
57
63
  const validation = useMemo(() => validateSnapResponse(snap), [snap]);
58
64
  const valid = validation.valid;
@@ -77,7 +83,12 @@ export function SnapViewV2Inner({
77
83
  }
78
84
 
79
85
  return (
80
- <SnapViewCoreInner snap={snap} handlers={handlers} loading={loading} />
86
+ <SnapViewCoreInner
87
+ snap={snap}
88
+ handlers={handlers}
89
+ loading={loading}
90
+ loadingOverlay={loadingOverlay}
91
+ />
81
92
  );
82
93
  }
83
94
 
@@ -89,6 +100,7 @@ export function SnapViewV2({
89
100
  colors,
90
101
  onValidationError,
91
102
  validationErrorFallback,
103
+ loadingOverlay,
92
104
  }: {
93
105
  snap: SnapPage;
94
106
  handlers: SnapActionHandlers;
@@ -97,6 +109,8 @@ export function SnapViewV2({
97
109
  colors?: Partial<SnapNativeColors>;
98
110
  onValidationError?: (result: ValidationResult) => void;
99
111
  validationErrorFallback?: ReactNode;
112
+ /** Custom content rendered while `loading` is true. Pass `null` to render nothing. */
113
+ loadingOverlay?: ReactNode;
100
114
  }) {
101
115
  return (
102
116
  <SnapThemeProvider appearance={appearance} colors={colors}>
@@ -106,6 +120,7 @@ export function SnapViewV2({
106
120
  loading={loading}
107
121
  onValidationError={onValidationError}
108
122
  validationErrorFallback={validationErrorFallback}
123
+ loadingOverlay={loadingOverlay}
109
124
  />
110
125
  </SnapThemeProvider>
111
126
  );
@@ -124,6 +139,7 @@ function SnapCardV2Inner({
124
139
  actionError,
125
140
  appearance,
126
141
  plain,
142
+ loadingOverlay,
127
143
  }: {
128
144
  snap: SnapPage;
129
145
  handlers: SnapActionHandlers;
@@ -135,8 +151,10 @@ function SnapCardV2Inner({
135
151
  actionError?: string | null;
136
152
  appearance: "light" | "dark";
137
153
  plain: boolean;
154
+ loadingOverlay?: ReactNode;
138
155
  }) {
139
- const { colors } = useSnapTheme();
156
+ const { colors, mode } = useSnapTheme();
157
+ const accentHex = resolveAccentHex(snap.theme?.accent, mode);
140
158
  const [contentHeight, setContentHeight] = useState(0);
141
159
 
142
160
  const content = (
@@ -146,11 +164,21 @@ function SnapCardV2Inner({
146
164
  loading={loading}
147
165
  onValidationError={onValidationError}
148
166
  validationErrorFallback={validationErrorFallback}
167
+ loadingOverlay={null}
149
168
  />
150
169
  );
151
170
 
152
171
  if (plain) {
153
- return content;
172
+ return (
173
+ <>
174
+ {content}
175
+ {loading
176
+ ? loadingOverlay === undefined
177
+ ? <SnapLoadingOverlay appearance={mode} accentHex={accentHex} />
178
+ : loadingOverlay
179
+ : null}
180
+ </>
181
+ );
154
182
  }
155
183
 
156
184
  const overflowAmount = showOverflowWarning ? contentHeight - SNAP_MAX_HEIGHT : 0;
@@ -184,6 +212,11 @@ function SnapCardV2Inner({
184
212
  <View style={{ flex: 1, backgroundColor: "rgba(255,50,50,0.15)" }} />
185
213
  </View>
186
214
  )}
215
+ {loading
216
+ ? loadingOverlay === undefined
217
+ ? <SnapLoadingOverlay appearance={mode} accentHex={accentHex} />
218
+ : loadingOverlay
219
+ : null}
187
220
  </View>
188
221
  {actionError && (
189
222
  <Text
@@ -216,6 +249,7 @@ export function SnapCardV2({
216
249
  validationErrorFallback,
217
250
  actionError,
218
251
  plain = false,
252
+ loadingOverlay,
219
253
  }: {
220
254
  snap: SnapPage;
221
255
  handlers: SnapActionHandlers;
@@ -228,6 +262,8 @@ export function SnapCardV2({
228
262
  validationErrorFallback?: ReactNode;
229
263
  actionError?: string | null;
230
264
  plain?: boolean;
265
+ /** Custom content rendered while `loading` is true. Pass `null` to render nothing. */
266
+ loadingOverlay?: ReactNode;
231
267
  }) {
232
268
  return (
233
269
  <SnapThemeProvider appearance={appearance} colors={colors}>
@@ -242,6 +278,7 @@ export function SnapCardV2({
242
278
  actionError={actionError}
243
279
  appearance={appearance}
244
280
  plain={plain}
281
+ loadingOverlay={loadingOverlay}
245
282
  />
246
283
  </SnapThemeProvider>
247
284
  );
package/src/ui/catalog.ts CHANGED
@@ -58,7 +58,7 @@ export const snapJsonRenderCatalog = defineCatalog(snapJsonRenderSchema, {
58
58
  item: {
59
59
  props: itemProps,
60
60
  description:
61
- "Content row with title and optional description. Children render in the actions slot (right side) — use badge, button, or text elements.",
61
+ "Content row with title and optional description. Children render in the actions slot (right side) — badge, button, and icon elements are all valid. The item itself is not interactive, so avoid navigation-style icons (`chevron-right`, `arrow-right`, `external-link`) that imply the row navigates.",
62
62
  },
63
63
  item_group: {
64
64
  props: itemGroupProps,
package/src/validator.ts CHANGED
@@ -11,7 +11,12 @@ export type ValidationResult = {
11
11
  // ─── Helpers ──────────────────────────────────────────
12
12
 
13
13
  /** Actions whose `params.target` must be a valid URL. */
14
- const URL_TARGET_ACTIONS = new Set(["submit", "open_url", "open_mini_app"]);
14
+ const URL_TARGET_ACTIONS = new Set([
15
+ "submit",
16
+ "open_url",
17
+ "open_snap",
18
+ "open_mini_app",
19
+ ]);
15
20
 
16
21
  /** Image file extensions allowed in image URLs. */
17
22
  const ALLOWED_IMAGE_EXTENSIONS = new Set(["jpg", "jpeg", "png", "gif", "webp"]);
@@ -169,7 +174,7 @@ function validateStructure(
169
174
  /**
170
175
  * Validate all URLs in the snap:
171
176
  * - image.url: must be HTTPS with allowed extension
172
- * - action target URLs (submit, open_url, open_mini_app): must be HTTPS
177
+ * - action target URLs (submit, open_url, open_snap, open_mini_app): must be HTTPS
173
178
  */
174
179
  function validateUrls(
175
180
  elements: Record<string, unknown>,