@farcaster/snap 1.18.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.
Files changed (54) hide show
  1. package/dist/react/components/slider.js +2 -1
  2. package/dist/react/index.d.ts +3 -1
  3. package/dist/react/index.js +3 -3
  4. package/dist/react/v1/snap-view.d.ts +2 -1
  5. package/dist/react/v1/snap-view.js +77 -2
  6. package/dist/react/v2/snap-view.d.ts +2 -1
  7. package/dist/react/v2/snap-view.js +11 -3
  8. package/dist/react-native/components/snap-action-button.js +2 -2
  9. package/dist/react-native/components/snap-badge.js +2 -1
  10. package/dist/react-native/components/snap-bar-chart.js +3 -3
  11. package/dist/react-native/components/snap-cell-grid.js +1 -1
  12. package/dist/react-native/components/snap-image.js +0 -1
  13. package/dist/react-native/components/snap-input.js +2 -1
  14. package/dist/react-native/components/snap-item.js +4 -3
  15. package/dist/react-native/components/snap-progress.js +2 -2
  16. package/dist/react-native/components/snap-slider.js +6 -5
  17. package/dist/react-native/components/snap-switch.js +1 -0
  18. package/dist/react-native/components/snap-text.js +2 -2
  19. package/dist/react-native/components/snap-toggle-group.js +2 -1
  20. package/dist/react-native/index.d.ts +3 -1
  21. package/dist/react-native/index.js +3 -3
  22. package/dist/react-native/theme.js +16 -16
  23. package/dist/react-native/v1/snap-view.d.ts +2 -1
  24. package/dist/react-native/v1/snap-view.js +68 -11
  25. package/dist/react-native/v2/snap-view.d.ts +2 -1
  26. package/dist/react-native/v2/snap-view.js +25 -21
  27. package/dist/ui/catalog.d.ts +1 -1
  28. package/dist/ui/catalog.js +1 -2
  29. package/dist/ui/slider.d.ts +1 -0
  30. package/dist/ui/slider.js +2 -0
  31. package/llms.txt +1 -0
  32. package/package.json +1 -1
  33. package/src/react/components/slider.tsx +11 -1
  34. package/src/react/index.tsx +5 -0
  35. package/src/react/v1/snap-view.tsx +117 -7
  36. package/src/react/v2/snap-view.tsx +13 -1
  37. package/src/react-native/components/snap-action-button.tsx +2 -2
  38. package/src/react-native/components/snap-badge.tsx +2 -1
  39. package/src/react-native/components/snap-bar-chart.tsx +3 -3
  40. package/src/react-native/components/snap-cell-grid.tsx +1 -1
  41. package/src/react-native/components/snap-image.tsx +0 -1
  42. package/src/react-native/components/snap-input.tsx +2 -1
  43. package/src/react-native/components/snap-item.tsx +4 -3
  44. package/src/react-native/components/snap-progress.tsx +2 -2
  45. package/src/react-native/components/snap-slider.tsx +10 -7
  46. package/src/react-native/components/snap-switch.tsx +1 -0
  47. package/src/react-native/components/snap-text.tsx +2 -2
  48. package/src/react-native/components/snap-toggle-group.tsx +2 -1
  49. package/src/react-native/index.tsx +5 -0
  50. package/src/react-native/theme.tsx +16 -16
  51. package/src/react-native/v1/snap-view.tsx +102 -7
  52. package/src/react-native/v2/snap-view.tsx +52 -40
  53. package/src/ui/catalog.ts +1 -2
  54. package/src/ui/slider.ts +2 -0
@@ -21,27 +21,27 @@ export type SnapNativeColors = {
21
21
  const DEFAULT_LIGHT: SnapNativeColors = {
22
22
  bg: "#dfe3e8",
23
23
  surface: "#ffffff",
24
- text: "#111111",
25
- textSecondary: "#6b7280",
26
- border: "#E5E7EB",
27
- inputBg: "rgba(0,0,0,0.12)",
28
- muted: "rgba(0,0,0,0.12)",
29
- mutedSubtle: "rgba(0,0,0,0.06)",
30
- mutedHover: "rgba(0,0,0,0.10)",
31
- mutedSelected: "rgba(0,0,0,0.18)",
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
+ inputBg: "rgba(0,0,0,0.06)",
28
+ muted: "rgba(0,0,0,0.08)",
29
+ mutedSubtle: "rgba(0,0,0,0.04)",
30
+ mutedHover: "rgba(0,0,0,0.12)",
31
+ mutedSelected: "rgba(0,0,0,0.16)",
32
32
  };
33
33
 
34
34
  const DEFAULT_DARK: SnapNativeColors = {
35
35
  bg: "#111318",
36
36
  surface: "#1a1d24",
37
- text: "#fafafa",
38
- textSecondary: "#a1a1aa",
39
- border: "#2D2D44",
40
- inputBg: "rgba(255,255,255,0.03)",
41
- muted: "rgba(255,255,255,0.03)",
42
- mutedSubtle: "rgba(255,255,255,0.02)",
43
- mutedHover: "rgba(255,255,255,0.04)",
44
- mutedSelected: "rgba(255,255,255,0.10)",
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
+ inputBg: "rgba(255,255,255,0.04)",
41
+ muted: "rgba(255,255,255,0.06)",
42
+ mutedSubtle: "rgba(255,255,255,0.03)",
43
+ mutedHover: "rgba(255,255,255,0.08)",
44
+ mutedSelected: "rgba(255,255,255,0.12)",
45
45
  };
46
46
 
47
47
  // ─── Context ──────────────────────────────────────────
@@ -1,9 +1,12 @@
1
- import { View, Text, StyleSheet } from "react-native";
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
- // ─── SnapViewV1 (no validation, no height limits) ────
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, no height limits) ───────
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 style={cardStyles.body}>
75
- <SnapViewV1Inner snap={snap} handlers={handlers} loading={loading} />
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 maxHeight = showOverflowWarning ? SNAP_WARNING_HEIGHT : SNAP_MAX_HEIGHT;
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 style={cardStyles.frameRing}>
143
- <View
144
- style={[
145
- cardStyles.card,
146
- {
147
- borderRadius,
148
- maxHeight,
149
- borderColor: colors.border,
150
- backgroundColor: colors.surface,
151
- },
152
- ]}
153
- >
154
- <View style={cardStyles.body}>
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
- cardStyles.actionError,
177
- {
178
- color:
179
- appearance === "dark"
180
- ? "rgba(255,100,100,0.9)"
181
- : "rgba(200,0,0,0.8)",
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: { overflow: "hidden", borderWidth: 1, minHeight: 120 },
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 target snap or external URL.",
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: {
package/src/ui/slider.ts CHANGED
@@ -12,6 +12,8 @@ export const sliderProps = z
12
12
  step: z.number().optional(),
13
13
  defaultValue: z.number().optional(),
14
14
  label: z.string().max(SLIDER_MAX_LABEL_CHARS).optional(),
15
+ /** When true, display the current value next to the label. */
16
+ showValue: z.boolean().optional(),
15
17
  })
16
18
  .superRefine((val, ctx) => {
17
19
  if (val.min > val.max) {