@farcaster/snap 1.6.0 → 1.7.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/dataStore.d.ts +1 -4
- package/dist/dataStore.js +4 -17
- package/dist/index.d.ts +1 -1
- package/dist/react-native/catalog-renderer.d.ts +5 -0
- package/dist/react-native/catalog-renderer.js +36 -0
- package/dist/react-native/components/snap-action-button.d.ts +2 -0
- package/dist/react-native/components/snap-action-button.js +68 -0
- package/dist/react-native/components/snap-badge.d.ts +2 -0
- package/dist/react-native/components/snap-badge.js +38 -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 +24 -0
- package/dist/react-native/components/snap-input.d.ts +2 -0
- package/dist/react-native/components/snap-input.js +36 -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 +45 -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 +42 -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 +30 -0
- package/dist/react-native/components/snap-text.d.ts +2 -0
- package/dist/react-native/components/snap-text.js +37 -0
- package/dist/react-native/components/snap-toggle-group.d.ts +2 -0
- package/dist/react-native/components/snap-toggle-group.js +100 -0
- package/dist/react-native/index.d.ts +52 -0
- package/dist/react-native/index.js +155 -0
- package/dist/react-native/theme.d.ts +21 -0
- package/dist/react-native/theme.js +37 -0
- package/dist/react-native/use-snap-palette.d.ts +13 -0
- package/dist/react-native/use-snap-palette.js +48 -0
- package/dist/ui/badge.d.ts +2 -2
- package/dist/ui/button.d.ts +2 -2
- package/dist/ui/catalog.d.ts +7 -7
- package/dist/ui/icon.d.ts +2 -2
- package/dist/ui/schema.d.ts +1 -1
- package/package.json +7 -2
- package/src/dataStore.ts +5 -29
- package/src/index.ts +0 -1
- package/src/react-native/catalog-renderer.tsx +37 -0
- package/src/react-native/components/snap-action-button.tsx +92 -0
- package/src/react-native/components/snap-badge.tsx +57 -0
- package/src/react-native/components/snap-icon.tsx +102 -0
- package/src/react-native/components/snap-image.tsx +38 -0
- package/src/react-native/components/snap-input.tsx +57 -0
- package/src/react-native/components/snap-item-group.tsx +43 -0
- package/src/react-native/components/snap-item.tsx +70 -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 +82 -0
- package/src/react-native/components/snap-stack.tsx +66 -0
- package/src/react-native/components/snap-switch.tsx +45 -0
- package/src/react-native/components/snap-text.tsx +53 -0
- package/src/react-native/components/snap-toggle-group.tsx +128 -0
- package/src/react-native/index.tsx +267 -0
- package/src/react-native/theme.tsx +73 -0
- package/src/react-native/use-snap-palette.ts +64 -0
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { StyleSheet, Text, View } from "react-native";
|
|
3
|
+
import { useSnapTheme } from "../theme.js";
|
|
4
|
+
const SIZE_STYLES = {
|
|
5
|
+
lg: { fontSize: 20, fontWeight: "700" },
|
|
6
|
+
md: { fontSize: 16, lineHeight: 24 },
|
|
7
|
+
sm: { fontSize: 13 },
|
|
8
|
+
};
|
|
9
|
+
const WEIGHT_MAP = {
|
|
10
|
+
bold: "700",
|
|
11
|
+
medium: "500",
|
|
12
|
+
normal: "400",
|
|
13
|
+
};
|
|
14
|
+
export function SnapText({ element: { props }, }) {
|
|
15
|
+
const { colors } = useSnapTheme();
|
|
16
|
+
const content = String(props.content ?? "");
|
|
17
|
+
const size = String(props.size ?? "md");
|
|
18
|
+
const weight = props.weight ? String(props.weight) : undefined;
|
|
19
|
+
const align = props.align ?? undefined;
|
|
20
|
+
const sizeStyle = SIZE_STYLES[size] ?? SIZE_STYLES.md;
|
|
21
|
+
const resolvedWeight = weight ? WEIGHT_MAP[weight] : sizeStyle?.fontWeight;
|
|
22
|
+
const textAlign = align === "center" ? "center" : align === "right" ? "right" : "left";
|
|
23
|
+
return (_jsx(View, { style: styles.wrap, children: _jsx(Text, { style: [
|
|
24
|
+
styles.base,
|
|
25
|
+
{
|
|
26
|
+
color: colors.text,
|
|
27
|
+
fontSize: sizeStyle.fontSize,
|
|
28
|
+
lineHeight: sizeStyle.lineHeight,
|
|
29
|
+
fontWeight: resolvedWeight,
|
|
30
|
+
textAlign,
|
|
31
|
+
},
|
|
32
|
+
], children: content }) }));
|
|
33
|
+
}
|
|
34
|
+
const styles = StyleSheet.create({
|
|
35
|
+
wrap: { flex: 1, width: "100%" },
|
|
36
|
+
base: {},
|
|
37
|
+
});
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useStateStore } from "@json-render/react-native";
|
|
3
|
+
import { Pressable, StyleSheet, Text, View } from "react-native";
|
|
4
|
+
import { useSnapPalette } from "../use-snap-palette.js";
|
|
5
|
+
import { useSnapTheme } from "../theme.js";
|
|
6
|
+
export function SnapToggleGroup({ element: { props }, }) {
|
|
7
|
+
const { get, set } = useStateStore();
|
|
8
|
+
const { accentHex } = useSnapPalette();
|
|
9
|
+
const { colors } = useSnapTheme();
|
|
10
|
+
const name = String(props.name ?? "toggle_group");
|
|
11
|
+
const path = `/inputs/${name}`;
|
|
12
|
+
const label = props.label ? String(props.label) : undefined;
|
|
13
|
+
const isMultiple = Boolean(props.multiple);
|
|
14
|
+
const orientation = String(props.orientation ?? "horizontal");
|
|
15
|
+
const options = Array.isArray(props.options)
|
|
16
|
+
? props.options
|
|
17
|
+
: [];
|
|
18
|
+
const raw = get(path);
|
|
19
|
+
const defaultValue = props.defaultValue;
|
|
20
|
+
const selected = (() => {
|
|
21
|
+
if (raw !== undefined && raw !== null) {
|
|
22
|
+
return isMultiple
|
|
23
|
+
? Array.isArray(raw)
|
|
24
|
+
? raw
|
|
25
|
+
: []
|
|
26
|
+
: typeof raw === "string"
|
|
27
|
+
? [raw]
|
|
28
|
+
: [];
|
|
29
|
+
}
|
|
30
|
+
if (defaultValue !== undefined) {
|
|
31
|
+
return Array.isArray(defaultValue)
|
|
32
|
+
? defaultValue
|
|
33
|
+
: [String(defaultValue)];
|
|
34
|
+
}
|
|
35
|
+
return [];
|
|
36
|
+
})();
|
|
37
|
+
const isVertical = orientation === "vertical";
|
|
38
|
+
const handlePress = (opt) => {
|
|
39
|
+
if (isMultiple) {
|
|
40
|
+
const next = selected.includes(opt)
|
|
41
|
+
? selected.filter((s) => s !== opt)
|
|
42
|
+
: [...selected, opt];
|
|
43
|
+
set(path, next);
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
if (opt && opt !== selected[0])
|
|
47
|
+
set(path, opt);
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
return (_jsxs(View, { style: styles.wrap, children: [label ? _jsx(Text, { style: [styles.label, { color: colors.text }], children: label }) : null, _jsx(View, { style: [
|
|
51
|
+
styles.group,
|
|
52
|
+
{ backgroundColor: colors.border + "33" },
|
|
53
|
+
isVertical ? styles.groupVertical : styles.groupHorizontal,
|
|
54
|
+
], children: options.map((opt, index) => {
|
|
55
|
+
const isSelected = selected.includes(opt);
|
|
56
|
+
return (_jsx(Pressable, { style: ({ pressed }) => [
|
|
57
|
+
styles.option,
|
|
58
|
+
isSelected && { backgroundColor: accentHex },
|
|
59
|
+
pressed && styles.pressed,
|
|
60
|
+
!isVertical && styles.optionHorizontal,
|
|
61
|
+
], onPress: () => handlePress(opt), children: _jsx(Text, { style: [
|
|
62
|
+
styles.optionText,
|
|
63
|
+
{ color: colors.text },
|
|
64
|
+
isSelected && styles.optionTextSelected,
|
|
65
|
+
], children: opt }) }, index));
|
|
66
|
+
}) })] }));
|
|
67
|
+
}
|
|
68
|
+
const styles = StyleSheet.create({
|
|
69
|
+
wrap: { width: "100%", gap: 6 },
|
|
70
|
+
label: { fontSize: 13, fontWeight: "500" },
|
|
71
|
+
group: {
|
|
72
|
+
padding: 4,
|
|
73
|
+
borderRadius: 8,
|
|
74
|
+
gap: 4,
|
|
75
|
+
},
|
|
76
|
+
groupHorizontal: {
|
|
77
|
+
flexDirection: "row",
|
|
78
|
+
},
|
|
79
|
+
groupVertical: {
|
|
80
|
+
flexDirection: "column",
|
|
81
|
+
},
|
|
82
|
+
option: {
|
|
83
|
+
paddingVertical: 8,
|
|
84
|
+
paddingHorizontal: 12,
|
|
85
|
+
borderRadius: 6,
|
|
86
|
+
alignItems: "center",
|
|
87
|
+
justifyContent: "center",
|
|
88
|
+
},
|
|
89
|
+
optionHorizontal: {
|
|
90
|
+
flex: 1,
|
|
91
|
+
},
|
|
92
|
+
pressed: { opacity: 0.88 },
|
|
93
|
+
optionText: {
|
|
94
|
+
fontSize: 13,
|
|
95
|
+
fontWeight: "500",
|
|
96
|
+
},
|
|
97
|
+
optionTextSelected: {
|
|
98
|
+
color: "#fff",
|
|
99
|
+
},
|
|
100
|
+
});
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { Spec } from "@json-render/core";
|
|
2
|
+
import { useSnapTheme, type SnapNativeColors } from "./theme.js";
|
|
3
|
+
import { hexToRgba } from "./use-snap-palette.js";
|
|
4
|
+
export type JsonValue = string | number | boolean | null | JsonValue[] | {
|
|
5
|
+
[key: string]: JsonValue;
|
|
6
|
+
};
|
|
7
|
+
export type SnapPage = {
|
|
8
|
+
version: string;
|
|
9
|
+
theme?: {
|
|
10
|
+
accent?: string;
|
|
11
|
+
};
|
|
12
|
+
effects?: string[];
|
|
13
|
+
ui: Spec;
|
|
14
|
+
};
|
|
15
|
+
export type SnapActionHandlers = {
|
|
16
|
+
submit: (target: string, inputs: Record<string, JsonValue>) => void;
|
|
17
|
+
open_url: (target: string) => void;
|
|
18
|
+
open_mini_app: (target: string) => void;
|
|
19
|
+
view_cast: (params: {
|
|
20
|
+
hash: string;
|
|
21
|
+
}) => void;
|
|
22
|
+
view_profile: (params: {
|
|
23
|
+
fid: number;
|
|
24
|
+
}) => void;
|
|
25
|
+
compose_cast: (params: {
|
|
26
|
+
text?: string;
|
|
27
|
+
channelKey?: string;
|
|
28
|
+
embeds?: string[];
|
|
29
|
+
}) => void;
|
|
30
|
+
view_token: (params: {
|
|
31
|
+
token: string;
|
|
32
|
+
}) => void;
|
|
33
|
+
send_token: (params: {
|
|
34
|
+
token: string;
|
|
35
|
+
amount?: string;
|
|
36
|
+
recipientFid?: number;
|
|
37
|
+
recipientAddress?: string;
|
|
38
|
+
}) => void;
|
|
39
|
+
swap_token: (params: {
|
|
40
|
+
sellToken?: string;
|
|
41
|
+
buyToken?: string;
|
|
42
|
+
}) => void;
|
|
43
|
+
};
|
|
44
|
+
export { useSnapTheme, hexToRgba };
|
|
45
|
+
export type { SnapNativeColors };
|
|
46
|
+
export declare function SnapView({ snap, handlers, loading, appearance, colors, }: {
|
|
47
|
+
snap: SnapPage;
|
|
48
|
+
handlers: SnapActionHandlers;
|
|
49
|
+
loading?: boolean;
|
|
50
|
+
appearance?: "light" | "dark";
|
|
51
|
+
colors?: Partial<SnapNativeColors>;
|
|
52
|
+
}): import("react").JSX.Element;
|
|
@@ -0,0 +1,155 @@
|
|
|
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 { SnapThemeProvider, useSnapTheme } from "./theme.js";
|
|
5
|
+
import { hexToRgba } from "./use-snap-palette.js";
|
|
6
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
7
|
+
import { ActivityIndicator, StyleSheet, View } from "react-native";
|
|
8
|
+
import { DEFAULT_THEME_ACCENT, PALETTE_LIGHT_HEX, PALETTE_DARK_HEX, } from "@farcaster/snap";
|
|
9
|
+
// ─── Re-exports ───────────────────────────────────────
|
|
10
|
+
export { useSnapTheme, hexToRgba };
|
|
11
|
+
// ─── Internal helpers ─────────────────────────────────
|
|
12
|
+
function applyStatePaths(model, changes) {
|
|
13
|
+
const entries = Array.isArray(changes)
|
|
14
|
+
? changes.map((c) => [c.path, c.value])
|
|
15
|
+
: Object.entries(changes);
|
|
16
|
+
for (const [path, value] of entries) {
|
|
17
|
+
const trimmed = path.startsWith("/") ? path : `/${path}`;
|
|
18
|
+
const parts = trimmed.split("/").filter(Boolean);
|
|
19
|
+
if (parts.length < 2)
|
|
20
|
+
continue;
|
|
21
|
+
const [top, ...rest] = parts;
|
|
22
|
+
if (top === "inputs") {
|
|
23
|
+
if (typeof model.inputs !== "object" || model.inputs === null) {
|
|
24
|
+
model.inputs = {};
|
|
25
|
+
}
|
|
26
|
+
const inputs = model.inputs;
|
|
27
|
+
if (rest.length === 1) {
|
|
28
|
+
inputs[rest[0]] = value;
|
|
29
|
+
}
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
if (top === "theme") {
|
|
33
|
+
if (typeof model.theme !== "object" || model.theme === null) {
|
|
34
|
+
model.theme = {};
|
|
35
|
+
}
|
|
36
|
+
const theme = model.theme;
|
|
37
|
+
if (rest.length === 1) {
|
|
38
|
+
theme[rest[0]] = value;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
function resolveAccentHex(accent, appearance) {
|
|
44
|
+
const map = appearance === "dark" ? PALETTE_DARK_HEX : PALETTE_LIGHT_HEX;
|
|
45
|
+
const name = accent && Object.hasOwn(map, accent) ? accent : DEFAULT_THEME_ACCENT;
|
|
46
|
+
return map[name];
|
|
47
|
+
}
|
|
48
|
+
// ─── SnapView ─────────────────────────────────────────
|
|
49
|
+
function SnapViewInner({ snap, handlers, loading = false, }) {
|
|
50
|
+
const { mode } = useSnapTheme();
|
|
51
|
+
const spec = snap.ui;
|
|
52
|
+
const accentHex = resolveAccentHex(snap.theme?.accent, mode);
|
|
53
|
+
const initialState = useMemo(() => ({
|
|
54
|
+
...(spec.state ?? {}),
|
|
55
|
+
inputs: { ...(spec.state?.inputs ?? {}) },
|
|
56
|
+
theme: {
|
|
57
|
+
...(spec.state?.theme ?? {}),
|
|
58
|
+
...(snap.theme ? { accent: snap.theme.accent } : {}),
|
|
59
|
+
},
|
|
60
|
+
}), [spec, snap.theme]);
|
|
61
|
+
const stateRef = useRef(initialState);
|
|
62
|
+
useEffect(() => {
|
|
63
|
+
stateRef.current = {
|
|
64
|
+
inputs: {
|
|
65
|
+
...(initialState.inputs ?? {}),
|
|
66
|
+
},
|
|
67
|
+
theme: {
|
|
68
|
+
...(initialState.theme ?? {}),
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
}, [initialState]);
|
|
72
|
+
useEffect(() => {
|
|
73
|
+
const result = snapJsonRenderCatalog.validate(spec);
|
|
74
|
+
if (!result.success) {
|
|
75
|
+
// eslint-disable-next-line no-console
|
|
76
|
+
console.warn("[SnapView] catalog validation issues:", result.error);
|
|
77
|
+
}
|
|
78
|
+
}, [spec]);
|
|
79
|
+
const [pageKey, setPageKey] = useState(0);
|
|
80
|
+
useEffect(() => {
|
|
81
|
+
setPageKey((k) => k + 1);
|
|
82
|
+
}, [spec]);
|
|
83
|
+
const handlersRef = useRef(handlers);
|
|
84
|
+
handlersRef.current = handlers;
|
|
85
|
+
const handleAction = useCallback((name, params) => {
|
|
86
|
+
const inputs = (stateRef.current.inputs ?? {});
|
|
87
|
+
const p = (params ?? {});
|
|
88
|
+
const h = handlersRef.current;
|
|
89
|
+
switch (name) {
|
|
90
|
+
case "submit":
|
|
91
|
+
h.submit(String(p.target ?? ""), inputs);
|
|
92
|
+
break;
|
|
93
|
+
case "open_url":
|
|
94
|
+
h.open_url(String(p.target ?? ""));
|
|
95
|
+
break;
|
|
96
|
+
case "open_mini_app":
|
|
97
|
+
h.open_mini_app(String(p.target ?? ""));
|
|
98
|
+
break;
|
|
99
|
+
case "view_cast":
|
|
100
|
+
h.view_cast({ hash: String(p.hash ?? "") });
|
|
101
|
+
break;
|
|
102
|
+
case "view_profile":
|
|
103
|
+
h.view_profile({ fid: Number(p.fid ?? 0) });
|
|
104
|
+
break;
|
|
105
|
+
case "compose_cast":
|
|
106
|
+
h.compose_cast({
|
|
107
|
+
text: p.text ? String(p.text) : undefined,
|
|
108
|
+
channelKey: p.channelKey ? String(p.channelKey) : undefined,
|
|
109
|
+
embeds: Array.isArray(p.embeds) ? p.embeds : undefined,
|
|
110
|
+
});
|
|
111
|
+
break;
|
|
112
|
+
case "view_token":
|
|
113
|
+
h.view_token({ token: String(p.token ?? "") });
|
|
114
|
+
break;
|
|
115
|
+
case "send_token":
|
|
116
|
+
h.send_token({
|
|
117
|
+
token: String(p.token ?? ""),
|
|
118
|
+
amount: p.amount ? String(p.amount) : undefined,
|
|
119
|
+
recipientFid: p.recipientFid ? Number(p.recipientFid) : undefined,
|
|
120
|
+
recipientAddress: p.recipientAddress ? String(p.recipientAddress) : undefined,
|
|
121
|
+
});
|
|
122
|
+
break;
|
|
123
|
+
case "swap_token":
|
|
124
|
+
h.swap_token({
|
|
125
|
+
sellToken: p.sellToken ? String(p.sellToken) : undefined,
|
|
126
|
+
buyToken: p.buyToken ? String(p.buyToken) : undefined,
|
|
127
|
+
});
|
|
128
|
+
break;
|
|
129
|
+
default:
|
|
130
|
+
break;
|
|
131
|
+
}
|
|
132
|
+
}, []);
|
|
133
|
+
return (_jsxs(View, { style: styles.container, children: [loading && (_jsx(View, { style: [
|
|
134
|
+
styles.overlay,
|
|
135
|
+
{
|
|
136
|
+
backgroundColor: mode === "dark" ? "rgba(0,0,0,0.6)" : "rgba(255,255,255,0.75)",
|
|
137
|
+
},
|
|
138
|
+
], children: _jsx(ActivityIndicator, { size: "large", color: accentHex }) })), _jsx(SnapCatalogView, { spec: spec, state: initialState, loading: false, onStateChange: (changes) => {
|
|
139
|
+
applyStatePaths(stateRef.current, changes);
|
|
140
|
+
}, onAction: handleAction }, pageKey)] }));
|
|
141
|
+
}
|
|
142
|
+
export function SnapView({ snap, handlers, loading = false, appearance = "dark", colors, }) {
|
|
143
|
+
return (_jsx(SnapThemeProvider, { appearance: appearance, colors: colors, children: _jsx(SnapViewInner, { snap: snap, handlers: handlers, loading: loading }) }));
|
|
144
|
+
}
|
|
145
|
+
const styles = StyleSheet.create({
|
|
146
|
+
container: {
|
|
147
|
+
width: "100%",
|
|
148
|
+
},
|
|
149
|
+
overlay: {
|
|
150
|
+
...StyleSheet.absoluteFillObject,
|
|
151
|
+
alignItems: "center",
|
|
152
|
+
justifyContent: "center",
|
|
153
|
+
zIndex: 10,
|
|
154
|
+
},
|
|
155
|
+
});
|
|
@@ -0,0 +1,21 @@
|
|
|
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
|
+
};
|
|
11
|
+
interface SnapThemeValue {
|
|
12
|
+
mode: "light" | "dark";
|
|
13
|
+
colors: SnapNativeColors;
|
|
14
|
+
}
|
|
15
|
+
export declare function SnapThemeProvider({ appearance, colors, children, }: {
|
|
16
|
+
appearance: "light" | "dark";
|
|
17
|
+
colors?: Partial<SnapNativeColors>;
|
|
18
|
+
children: ReactNode;
|
|
19
|
+
}): import("react").JSX.Element;
|
|
20
|
+
export declare function useSnapTheme(): SnapThemeValue;
|
|
21
|
+
export {};
|
|
@@ -0,0 +1,37 @@
|
|
|
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: "#111111",
|
|
7
|
+
textSecondary: "#6b7280",
|
|
8
|
+
border: "#d1d5db",
|
|
9
|
+
inputBg: "#ffffff",
|
|
10
|
+
muted: "#f9fafb",
|
|
11
|
+
};
|
|
12
|
+
const DEFAULT_DARK = {
|
|
13
|
+
bg: "#111318",
|
|
14
|
+
surface: "#1a1d24",
|
|
15
|
+
text: "#fafafa",
|
|
16
|
+
textSecondary: "#a1a1aa",
|
|
17
|
+
border: "#374151",
|
|
18
|
+
inputBg: "#1a1d24",
|
|
19
|
+
muted: "#27272a",
|
|
20
|
+
};
|
|
21
|
+
const SnapThemeContext = createContext({
|
|
22
|
+
mode: "dark",
|
|
23
|
+
colors: DEFAULT_DARK,
|
|
24
|
+
});
|
|
25
|
+
export function SnapThemeProvider({ appearance, colors, children, }) {
|
|
26
|
+
const value = useMemo(() => {
|
|
27
|
+
const defaults = appearance === "dark" ? DEFAULT_DARK : DEFAULT_LIGHT;
|
|
28
|
+
return {
|
|
29
|
+
mode: appearance,
|
|
30
|
+
colors: colors ? { ...defaults, ...colors } : defaults,
|
|
31
|
+
};
|
|
32
|
+
}, [appearance, colors]);
|
|
33
|
+
return (_jsx(SnapThemeContext.Provider, { value: value, children: children }));
|
|
34
|
+
}
|
|
35
|
+
export function useSnapTheme() {
|
|
36
|
+
return useContext(SnapThemeContext);
|
|
37
|
+
}
|
|
@@ -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
|
+
}
|
package/dist/ui/badge.d.ts
CHANGED
|
@@ -14,18 +14,18 @@ export declare const badgeProps: z.ZodObject<{
|
|
|
14
14
|
accent: "accent";
|
|
15
15
|
}>>;
|
|
16
16
|
icon: z.ZodOptional<z.ZodEnum<{
|
|
17
|
-
check: "check";
|
|
18
|
-
repeat: "repeat";
|
|
19
17
|
"arrow-right": "arrow-right";
|
|
20
18
|
"arrow-left": "arrow-left";
|
|
21
19
|
"external-link": "external-link";
|
|
22
20
|
"chevron-right": "chevron-right";
|
|
21
|
+
check: "check";
|
|
23
22
|
x: "x";
|
|
24
23
|
"alert-triangle": "alert-triangle";
|
|
25
24
|
info: "info";
|
|
26
25
|
clock: "clock";
|
|
27
26
|
heart: "heart";
|
|
28
27
|
"message-circle": "message-circle";
|
|
28
|
+
repeat: "repeat";
|
|
29
29
|
share: "share";
|
|
30
30
|
user: "user";
|
|
31
31
|
users: "users";
|
package/dist/ui/button.d.ts
CHANGED
|
@@ -10,18 +10,18 @@ export declare const buttonProps: z.ZodObject<{
|
|
|
10
10
|
ghost: "ghost";
|
|
11
11
|
}>>;
|
|
12
12
|
icon: z.ZodOptional<z.ZodEnum<{
|
|
13
|
-
check: "check";
|
|
14
|
-
repeat: "repeat";
|
|
15
13
|
"arrow-right": "arrow-right";
|
|
16
14
|
"arrow-left": "arrow-left";
|
|
17
15
|
"external-link": "external-link";
|
|
18
16
|
"chevron-right": "chevron-right";
|
|
17
|
+
check: "check";
|
|
19
18
|
x: "x";
|
|
20
19
|
"alert-triangle": "alert-triangle";
|
|
21
20
|
info: "info";
|
|
22
21
|
clock: "clock";
|
|
23
22
|
heart: "heart";
|
|
24
23
|
"message-circle": "message-circle";
|
|
24
|
+
repeat: "repeat";
|
|
25
25
|
share: "share";
|
|
26
26
|
user: "user";
|
|
27
27
|
users: "users";
|
package/dist/ui/catalog.d.ts
CHANGED
|
@@ -14,7 +14,7 @@ export declare const snapJsonRenderCatalog: import("@json-render/core").Catalog<
|
|
|
14
14
|
children: {
|
|
15
15
|
optional: true;
|
|
16
16
|
kind: "array";
|
|
17
|
-
inner?: import("@json-render/core").SchemaType<"string", unknown
|
|
17
|
+
inner?: import("@json-render/core").SchemaType<"string", unknown>;
|
|
18
18
|
};
|
|
19
19
|
}>>;
|
|
20
20
|
}>;
|
|
@@ -49,18 +49,18 @@ export declare const snapJsonRenderCatalog: import("@json-render/core").Catalog<
|
|
|
49
49
|
accent: "accent";
|
|
50
50
|
}>>;
|
|
51
51
|
icon: z.ZodOptional<z.ZodEnum<{
|
|
52
|
-
check: "check";
|
|
53
|
-
repeat: "repeat";
|
|
54
52
|
"arrow-right": "arrow-right";
|
|
55
53
|
"arrow-left": "arrow-left";
|
|
56
54
|
"external-link": "external-link";
|
|
57
55
|
"chevron-right": "chevron-right";
|
|
56
|
+
check: "check";
|
|
58
57
|
x: "x";
|
|
59
58
|
"alert-triangle": "alert-triangle";
|
|
60
59
|
info: "info";
|
|
61
60
|
clock: "clock";
|
|
62
61
|
heart: "heart";
|
|
63
62
|
"message-circle": "message-circle";
|
|
63
|
+
repeat: "repeat";
|
|
64
64
|
share: "share";
|
|
65
65
|
user: "user";
|
|
66
66
|
users: "users";
|
|
@@ -96,18 +96,18 @@ export declare const snapJsonRenderCatalog: import("@json-render/core").Catalog<
|
|
|
96
96
|
ghost: "ghost";
|
|
97
97
|
}>>;
|
|
98
98
|
icon: z.ZodOptional<z.ZodEnum<{
|
|
99
|
-
check: "check";
|
|
100
|
-
repeat: "repeat";
|
|
101
99
|
"arrow-right": "arrow-right";
|
|
102
100
|
"arrow-left": "arrow-left";
|
|
103
101
|
"external-link": "external-link";
|
|
104
102
|
"chevron-right": "chevron-right";
|
|
103
|
+
check: "check";
|
|
105
104
|
x: "x";
|
|
106
105
|
"alert-triangle": "alert-triangle";
|
|
107
106
|
info: "info";
|
|
108
107
|
clock: "clock";
|
|
109
108
|
heart: "heart";
|
|
110
109
|
"message-circle": "message-circle";
|
|
110
|
+
repeat: "repeat";
|
|
111
111
|
share: "share";
|
|
112
112
|
user: "user";
|
|
113
113
|
users: "users";
|
|
@@ -201,18 +201,18 @@ export declare const snapJsonRenderCatalog: import("@json-render/core").Catalog<
|
|
|
201
201
|
icon: {
|
|
202
202
|
props: z.ZodObject<{
|
|
203
203
|
name: z.ZodEnum<{
|
|
204
|
-
check: "check";
|
|
205
|
-
repeat: "repeat";
|
|
206
204
|
"arrow-right": "arrow-right";
|
|
207
205
|
"arrow-left": "arrow-left";
|
|
208
206
|
"external-link": "external-link";
|
|
209
207
|
"chevron-right": "chevron-right";
|
|
208
|
+
check: "check";
|
|
210
209
|
x: "x";
|
|
211
210
|
"alert-triangle": "alert-triangle";
|
|
212
211
|
info: "info";
|
|
213
212
|
clock: "clock";
|
|
214
213
|
heart: "heart";
|
|
215
214
|
"message-circle": "message-circle";
|
|
215
|
+
repeat: "repeat";
|
|
216
216
|
share: "share";
|
|
217
217
|
user: "user";
|
|
218
218
|
users: "users";
|
package/dist/ui/icon.d.ts
CHANGED
|
@@ -3,18 +3,18 @@ export declare const ICON_NAMES: readonly ["arrow-right", "arrow-left", "externa
|
|
|
3
3
|
export declare const ICON_SIZES: readonly ["sm", "md"];
|
|
4
4
|
export declare const iconProps: z.ZodObject<{
|
|
5
5
|
name: z.ZodEnum<{
|
|
6
|
-
check: "check";
|
|
7
|
-
repeat: "repeat";
|
|
8
6
|
"arrow-right": "arrow-right";
|
|
9
7
|
"arrow-left": "arrow-left";
|
|
10
8
|
"external-link": "external-link";
|
|
11
9
|
"chevron-right": "chevron-right";
|
|
10
|
+
check: "check";
|
|
12
11
|
x: "x";
|
|
13
12
|
"alert-triangle": "alert-triangle";
|
|
14
13
|
info: "info";
|
|
15
14
|
clock: "clock";
|
|
16
15
|
heart: "heart";
|
|
17
16
|
"message-circle": "message-circle";
|
|
17
|
+
repeat: "repeat";
|
|
18
18
|
share: "share";
|
|
19
19
|
user: "user";
|
|
20
20
|
users: "users";
|
package/dist/ui/schema.d.ts
CHANGED
|
@@ -11,7 +11,7 @@ export declare const snapJsonRenderSchema: import("@json-render/core").Schema<{
|
|
|
11
11
|
children: {
|
|
12
12
|
optional: true;
|
|
13
13
|
kind: "array";
|
|
14
|
-
inner?: import("@json-render/core").SchemaType<"string", unknown
|
|
14
|
+
inner?: import("@json-render/core").SchemaType<"string", unknown>;
|
|
15
15
|
};
|
|
16
16
|
}>>;
|
|
17
17
|
}>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@farcaster/snap",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.7.1",
|
|
4
4
|
"description": "Farcaster Snaps 🫰",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -100,6 +100,11 @@
|
|
|
100
100
|
"types": "./dist/react/index.d.ts",
|
|
101
101
|
"import": "./dist/react/index.js",
|
|
102
102
|
"default": "./dist/react/index.js"
|
|
103
|
+
},
|
|
104
|
+
"./react-native": {
|
|
105
|
+
"types": "./dist/react-native/index.d.ts",
|
|
106
|
+
"import": "./dist/react-native/index.js",
|
|
107
|
+
"default": "./dist/react-native/index.js"
|
|
103
108
|
}
|
|
104
109
|
},
|
|
105
110
|
"files": [
|
|
@@ -147,7 +152,7 @@
|
|
|
147
152
|
"zod": "^4.0.0"
|
|
148
153
|
},
|
|
149
154
|
"scripts": {
|
|
150
|
-
"build": "tsc && tsc-alias --resolve-full-paths --resolve-full-extension .js",
|
|
155
|
+
"build": "tsc && (tsc -p tsconfig.react-native.json || true) && tsc-alias --resolve-full-paths --resolve-full-extension .js",
|
|
151
156
|
"clean": "rm -rf dist",
|
|
152
157
|
"test": "vitest run",
|
|
153
158
|
"typecheck": "tsc --noEmit"
|