@farcaster/snap 1.14.0 → 1.15.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.
@@ -1,6 +1,8 @@
1
1
  import { type ReactNode } from "react";
2
- export declare function SnapPreviewAccentProvider({ pageAccent, children, }: {
2
+ export declare function SnapPreviewAccentProvider({ pageAccent, appearance, children, }: {
3
3
  pageAccent: string | undefined;
4
+ appearance?: "light" | "dark";
4
5
  children: ReactNode;
5
6
  }): import("react/jsx-runtime").JSX.Element;
6
7
  export declare function useSnapPreviewPageAccent(): string | undefined;
8
+ export declare function useSnapAppearance(): "light" | "dark";
@@ -1,10 +1,13 @@
1
1
  "use client";
2
2
  import { jsx as _jsx } from "react/jsx-runtime";
3
3
  import { createContext, useContext } from "react";
4
- const SnapPreviewAccentContext = createContext(null);
5
- export function SnapPreviewAccentProvider({ pageAccent, children, }) {
6
- return (_jsx(SnapPreviewAccentContext.Provider, { value: { pageAccent }, children: children }));
4
+ const SnapPreviewContext = createContext(null);
5
+ export function SnapPreviewAccentProvider({ pageAccent, appearance = "dark", children, }) {
6
+ return (_jsx(SnapPreviewContext.Provider, { value: { pageAccent, appearance }, children: children }));
7
7
  }
8
8
  export function useSnapPreviewPageAccent() {
9
- return useContext(SnapPreviewAccentContext)?.pageAccent;
9
+ return useContext(SnapPreviewContext)?.pageAccent;
10
+ }
11
+ export function useSnapAppearance() {
12
+ return useContext(SnapPreviewContext)?.appearance ?? "dark";
10
13
  }
@@ -16,6 +16,7 @@ export function SnapCellGrid({ element: { props }, }) {
16
16
  const gap = String(props.gap ?? "sm");
17
17
  const gapMap = { none: 0, sm: 1, md: 2, lg: 4 };
18
18
  const gapPx = gapMap[gap] ?? 1;
19
+ const rowHeight = typeof props.rowHeight === "number" ? props.rowHeight : 28;
19
20
  const name = props.name ? String(props.name) : POST_GRID_TAP_KEY;
20
21
  const tapPath = `/inputs/${name}`;
21
22
  const tapRaw = get(tapPath);
@@ -62,9 +63,9 @@ export function SnapCellGrid({ element: { props }, }) {
62
63
  handleTap(r, c);
63
64
  }
64
65
  }
65
- : undefined, className: cn("flex min-h-7 items-center justify-center rounded text-xs font-semibold", interactive ? "cursor-pointer select-none" : "cursor-default"), style: {
66
+ : undefined, className: cn("flex items-center justify-center rounded text-xs font-semibold", interactive ? "cursor-pointer select-none" : "cursor-default"), style: {
67
+ height: rowHeight,
66
68
  background: bg,
67
- // Two-layer ring: 1px white/black inner + 2px accent outer
68
69
  boxShadow: selected
69
70
  ? `inset 0 0 0 1px ${colors.mode === "dark" ? "#000" : "#fff"}, inset 0 0 0 2px ${colors.mode === "dark" ? "#fff" : "#000"}`
70
71
  : undefined,
@@ -74,9 +75,13 @@ export function SnapCellGrid({ element: { props }, }) {
74
75
  const selectionLabel = interactive && selectedSet.size > 0
75
76
  ? `inputs.${name}: ${[...selectedSet].join(isMultiple ? " | " : "")}`
76
77
  : null;
77
- return (_jsxs("div", { children: [_jsx("div", { className: "grid w-full rounded-lg p-1", style: {
78
- gridTemplateColumns: `repeat(${cols}, minmax(0, 1fr))`,
78
+ return (_jsxs("div", { children: [_jsx("div", { style: {
79
+ display: "grid",
80
+ width: "100%",
81
+ gridTemplateColumns: `repeat(${cols}, 1fr)`,
79
82
  gap: gapPx,
83
+ padding: 4,
84
+ borderRadius: 8,
80
85
  backgroundColor: colors.muted,
81
86
  }, children: cellEls }), selectionLabel && (_jsx("div", { className: "mt-1.5 truncate text-xs font-mono", style: { color: colors.textMuted }, children: selectionLabel }))] }));
82
87
  }
@@ -1,9 +1,8 @@
1
1
  "use client";
2
2
  import { useMemo } from "react";
3
3
  import { useStateStore } from "@json-render/react";
4
- import { useColorMode } from "@neynar/ui/color-mode";
5
4
  import { resolveSnapPaletteHex } from "../lib/resolve-palette-hex.js";
6
- import { useSnapPreviewPageAccent } from "../accent-context.js";
5
+ import { useSnapPreviewPageAccent, useSnapAppearance } from "../accent-context.js";
7
6
  import { PALETTE_DARK_HEX, PALETTE_LIGHT_HEX } from "@farcaster/snap";
8
7
  /** Readable foreground color (black or white) for a given hex background. */
9
8
  export function pickForegroundForBg(hex) {
@@ -69,7 +68,7 @@ function buildSnapColors(accentName, mode) {
69
68
  */
70
69
  export function useSnapColors() {
71
70
  const { get } = useStateStore();
72
- const { mode } = useColorMode();
71
+ const mode = useSnapAppearance();
73
72
  const pageAccent = useSnapPreviewPageAccent();
74
73
  const fromState = get("/theme/accent");
75
74
  const accentRaw = (typeof pageAccent === "string" && pageAccent.length > 0
@@ -174,7 +174,7 @@ export function SnapView({ snap, handlers, loading = false, appearance = "dark",
174
174
  break;
175
175
  }
176
176
  }, [handlers]);
177
- return (_jsxs("div", { style: { position: "relative", width: "100%" }, children: [showConfetti && _jsx(ConfettiOverlay, {}), loading && (_jsx("div", { style: {
177
+ return (_jsxs("div", { style: { position: "relative", width: "100%" }, children: [showConfetti && _jsx(ConfettiOverlay, {}), _jsx("div", { style: {
178
178
  position: "absolute",
179
179
  inset: 0,
180
180
  display: "flex",
@@ -182,10 +182,13 @@ export function SnapView({ snap, handlers, loading = false, appearance = "dark",
182
182
  justifyContent: "center",
183
183
  zIndex: 10,
184
184
  fontSize: 14,
185
- color: "var(--text-muted)",
186
- background: "var(--bg-primary, rgba(0,0,0,0.6))",
187
- backdropFilter: "blur(4px)",
188
- }, children: "Loading..." })), _jsx("div", { style: previewSurfaceStyle, children: _jsx(SnapPreviewAccentProvider, { pageAccent: snap.theme?.accent, children: _jsx(SnapCatalogView, { spec: spec, state: initialState, loading: false, onStateChange: (changes) => {
185
+ color: appearance === "dark" ? "rgba(255,255,255,0.5)" : "rgba(0,0,0,0.4)",
186
+ background: appearance === "dark" ? "rgba(0,0,0,0.3)" : "rgba(255,255,255,0.5)",
187
+ backdropFilter: loading ? "blur(8px)" : "blur(0px)",
188
+ opacity: loading ? 1 : 0,
189
+ pointerEvents: loading ? "auto" : "none",
190
+ transition: "opacity 0.3s ease, backdrop-filter 0.3s ease",
191
+ }, children: "Loading..." }), _jsx("div", { style: previewSurfaceStyle, children: _jsx(SnapPreviewAccentProvider, { pageAccent: snap.theme?.accent, appearance: appearance, children: _jsx(SnapCatalogView, { spec: spec, state: initialState, loading: false, onStateChange: (changes) => {
189
192
  applyStatePaths(stateRef.current, changes);
190
193
  }, onAction: handleAction }, pageKey) }) })] }));
191
194
  }
@@ -133,7 +133,7 @@ function SnapViewInner({ snap, handlers, loading = false, }) {
133
133
  return (_jsxs(View, { style: styles.container, children: [loading && (_jsx(View, { style: [
134
134
  styles.overlay,
135
135
  {
136
- backgroundColor: mode === "dark" ? "rgba(0,0,0,0.6)" : "rgba(255,255,255,0.75)",
136
+ backgroundColor: mode === "dark" ? "rgba(0,0,0,0.3)" : "rgba(255,255,255,0.5)",
137
137
  },
138
138
  ], children: _jsx(ActivityIndicator, { size: "large", color: accentHex }) })), _jsx(SnapCatalogView, { spec: spec, state: initialState, loading: false, onStateChange: (changes) => {
139
139
  applyStatePaths(stateRef.current, changes);
@@ -392,6 +392,7 @@ export declare const snapJsonRenderCatalog: import("@json-render/core").Catalog<
392
392
  none: "none";
393
393
  lg: "lg";
394
394
  }>>;
395
+ rowHeight: z.ZodOptional<z.ZodNumber>;
395
396
  select: z.ZodOptional<z.ZodEnum<{
396
397
  multiple: "multiple";
397
398
  off: "off";
@@ -24,6 +24,7 @@ export declare const cellGridProps: z.ZodObject<{
24
24
  none: "none";
25
25
  lg: "lg";
26
26
  }>>;
27
+ rowHeight: z.ZodOptional<z.ZodNumber>;
27
28
  select: z.ZodOptional<z.ZodEnum<{
28
29
  multiple: "multiple";
29
30
  off: "off";
@@ -14,6 +14,7 @@ export const cellGridProps = z
14
14
  rows: z.number().int().min(GRID_MIN_ROWS).max(GRID_MAX_ROWS),
15
15
  cells: z.array(cellGridCellSchema),
16
16
  gap: z.enum(GRID_GAP_VALUES).optional(),
17
+ rowHeight: z.number().int().min(8).max(64).optional(),
17
18
  select: z.enum(["off", "single", "multiple"]).optional(),
18
19
  })
19
20
  .superRefine((val, ctx) => {
package/llms.txt CHANGED
@@ -88,6 +88,7 @@ Top-level fields: `version` (required, `"1.0"`), `theme` (optional, `{ accent: P
88
88
  - `rows` (number, required, 2–16)
89
89
  - `cells` (array, required): sparse list of `{ row, col, color?: PaletteColor, content?: string }`
90
90
  - `gap` (optional): `"none"` (0px) | `"sm"` (1px) | `"md"` (2px) | `"lg"` (4px). Default: `"sm"`
91
+ - `rowHeight` (number, optional, 8–64): pixel height per row. Default: 28. Grid height = rows × rowHeight
91
92
  - `select` (optional): `"off"` | `"single"` | `"multiple"`. Default: `"off"`. Taps write to `inputs[name]`
92
93
 
93
94
  ### Container Components
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@farcaster/snap",
3
- "version": "1.14.0",
3
+ "version": "1.15.1",
4
4
  "description": "Farcaster Snaps 🫰",
5
5
  "repository": {
6
6
  "type": "git",
@@ -2,28 +2,35 @@
2
2
 
3
3
  import { createContext, useContext, type ReactNode } from "react";
4
4
 
5
- type SnapPreviewAccentContextValue = {
5
+ type SnapPreviewContextValue = {
6
6
  /** From loaded snap `page.theme.accent` (undefined if the snap omits it). */
7
7
  pageAccent: string | undefined;
8
+ /** Light/dark appearance passed from SnapView. */
9
+ appearance: "light" | "dark";
8
10
  };
9
11
 
10
- const SnapPreviewAccentContext =
11
- createContext<SnapPreviewAccentContextValue | null>(null);
12
+ const SnapPreviewContext = createContext<SnapPreviewContextValue | null>(null);
12
13
 
13
14
  export function SnapPreviewAccentProvider({
14
15
  pageAccent,
16
+ appearance = "dark",
15
17
  children,
16
18
  }: {
17
19
  pageAccent: string | undefined;
20
+ appearance?: "light" | "dark";
18
21
  children: ReactNode;
19
22
  }) {
20
23
  return (
21
- <SnapPreviewAccentContext.Provider value={{ pageAccent }}>
24
+ <SnapPreviewContext.Provider value={{ pageAccent, appearance }}>
22
25
  {children}
23
- </SnapPreviewAccentContext.Provider>
26
+ </SnapPreviewContext.Provider>
24
27
  );
25
28
  }
26
29
 
27
30
  export function useSnapPreviewPageAccent(): string | undefined {
28
- return useContext(SnapPreviewAccentContext)?.pageAccent;
31
+ return useContext(SnapPreviewContext)?.pageAccent;
32
+ }
33
+
34
+ export function useSnapAppearance(): "light" | "dark" {
35
+ return useContext(SnapPreviewContext)?.appearance ?? "dark";
29
36
  }
@@ -22,6 +22,7 @@ export function SnapCellGrid({
22
22
  const gap = String(props.gap ?? "sm");
23
23
  const gapMap: Record<string, number> = { none: 0, sm: 1, md: 2, lg: 4 };
24
24
  const gapPx = gapMap[gap] ?? 1;
25
+ const rowHeight = typeof props.rowHeight === "number" ? props.rowHeight : 28;
25
26
 
26
27
  const name = props.name ? String(props.name) : POST_GRID_TAP_KEY;
27
28
  const tapPath = `/inputs/${name}`;
@@ -81,12 +82,12 @@ export function SnapCellGrid({
81
82
  : undefined
82
83
  }
83
84
  className={cn(
84
- "flex min-h-7 items-center justify-center rounded text-xs font-semibold",
85
+ "flex items-center justify-center rounded text-xs font-semibold",
85
86
  interactive ? "cursor-pointer select-none" : "cursor-default",
86
87
  )}
87
88
  style={{
89
+ height: rowHeight,
88
90
  background: bg,
89
- // Two-layer ring: 1px white/black inner + 2px accent outer
90
91
  boxShadow: selected
91
92
  ? `inset 0 0 0 1px ${colors.mode === "dark" ? "#000" : "#fff"}, inset 0 0 0 2px ${colors.mode === "dark" ? "#fff" : "#000"}`
92
93
  : undefined,
@@ -105,10 +106,13 @@ export function SnapCellGrid({
105
106
  return (
106
107
  <div>
107
108
  <div
108
- className="grid w-full rounded-lg p-1"
109
109
  style={{
110
- gridTemplateColumns: `repeat(${cols}, minmax(0, 1fr))`,
110
+ display: "grid",
111
+ width: "100%",
112
+ gridTemplateColumns: `repeat(${cols}, 1fr)`,
111
113
  gap: gapPx,
114
+ padding: 4,
115
+ borderRadius: 8,
112
116
  backgroundColor: colors.muted,
113
117
  }}
114
118
  >
@@ -2,9 +2,8 @@
2
2
 
3
3
  import { useMemo } from "react";
4
4
  import { useStateStore } from "@json-render/react";
5
- import { useColorMode } from "@neynar/ui/color-mode";
6
5
  import { resolveSnapPaletteHex } from "../lib/resolve-palette-hex";
7
- import { useSnapPreviewPageAccent } from "../accent-context";
6
+ import { useSnapPreviewPageAccent, useSnapAppearance } from "../accent-context";
8
7
  import type { PaletteColor } from "@farcaster/snap";
9
8
  import { PALETTE_DARK_HEX, PALETTE_LIGHT_HEX } from "@farcaster/snap";
10
9
 
@@ -113,7 +112,7 @@ function buildSnapColors(
113
112
  */
114
113
  export function useSnapColors(): SnapColors {
115
114
  const { get } = useStateStore();
116
- const { mode } = useColorMode();
115
+ const mode = useSnapAppearance();
117
116
  const pageAccent = useSnapPreviewPageAccent();
118
117
  const fromState = get("/theme/accent");
119
118
  const accentRaw =
@@ -283,27 +283,28 @@ export function SnapView({
283
283
  return (
284
284
  <div style={{ position: "relative", width: "100%" }}>
285
285
  {showConfetti && <ConfettiOverlay />}
286
- {loading && (
287
- <div
288
- style={{
289
- position: "absolute",
290
- inset: 0,
291
- display: "flex",
292
- alignItems: "center",
293
- justifyContent: "center",
294
- zIndex: 10,
295
- fontSize: 14,
296
- color: "var(--text-muted)",
297
- background: "var(--bg-primary, rgba(0,0,0,0.6))",
298
- backdropFilter: "blur(4px)",
299
- }}
300
- >
301
- Loading...
302
- </div>
303
- )}
286
+ <div
287
+ style={{
288
+ position: "absolute",
289
+ inset: 0,
290
+ display: "flex",
291
+ alignItems: "center",
292
+ justifyContent: "center",
293
+ zIndex: 10,
294
+ fontSize: 14,
295
+ color: appearance === "dark" ? "rgba(255,255,255,0.5)" : "rgba(0,0,0,0.4)",
296
+ background: appearance === "dark" ? "rgba(0,0,0,0.3)" : "rgba(255,255,255,0.5)",
297
+ backdropFilter: loading ? "blur(8px)" : "blur(0px)",
298
+ opacity: loading ? 1 : 0,
299
+ pointerEvents: loading ? "auto" : "none",
300
+ transition: "opacity 0.3s ease, backdrop-filter 0.3s ease",
301
+ }}
302
+ >
303
+ Loading...
304
+ </div>
304
305
 
305
306
  <div style={previewSurfaceStyle}>
306
- <SnapPreviewAccentProvider pageAccent={snap.theme?.accent}>
307
+ <SnapPreviewAccentProvider pageAccent={snap.theme?.accent} appearance={appearance}>
307
308
  <SnapCatalogView
308
309
  key={pageKey}
309
310
  spec={spec}
@@ -213,7 +213,7 @@ function SnapViewInner({
213
213
  styles.overlay,
214
214
  {
215
215
  backgroundColor:
216
- mode === "dark" ? "rgba(0,0,0,0.6)" : "rgba(255,255,255,0.75)",
216
+ mode === "dark" ? "rgba(0,0,0,0.3)" : "rgba(255,255,255,0.5)",
217
217
  },
218
218
  ]}
219
219
  >
@@ -22,6 +22,7 @@ export const cellGridProps = z
22
22
  rows: z.number().int().min(GRID_MIN_ROWS).max(GRID_MAX_ROWS),
23
23
  cells: z.array(cellGridCellSchema),
24
24
  gap: z.enum(GRID_GAP_VALUES).optional(),
25
+ rowHeight: z.number().int().min(8).max(64).optional(),
25
26
  select: z.enum(["off", "single", "multiple"]).optional(),
26
27
  })
27
28
  .superRefine((val, ctx) => {