@farcaster/snap 1.5.2 → 1.7.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 (208) hide show
  1. package/dist/constants.d.ts +0 -107
  2. package/dist/constants.js +0 -148
  3. package/dist/dataStore.d.ts +12 -0
  4. package/dist/dataStore.js +35 -0
  5. package/dist/index.d.ts +5 -3
  6. package/dist/index.js +4 -3
  7. package/dist/react/accent-context.d.ts +6 -0
  8. package/dist/react/accent-context.js +10 -0
  9. package/dist/react/catalog-renderer.d.ts +5 -0
  10. package/dist/react/catalog-renderer.js +37 -0
  11. package/dist/react/components/action-button.d.ts +6 -0
  12. package/dist/react/components/action-button.js +22 -0
  13. package/dist/react/components/badge.d.ts +5 -0
  14. package/dist/react/components/badge.js +18 -0
  15. package/dist/react/components/icon.d.ts +7 -0
  16. package/dist/react/components/icon.js +60 -0
  17. package/dist/react/components/image.d.ts +5 -0
  18. package/dist/react/components/image.js +15 -0
  19. package/dist/react/components/input.d.ts +5 -0
  20. package/dist/react/components/input.js +18 -0
  21. package/dist/react/components/item-group.d.ts +7 -0
  22. package/dist/react/components/item-group.js +17 -0
  23. package/dist/react/components/item.d.ts +7 -0
  24. package/dist/react/components/item.js +9 -0
  25. package/dist/react/components/progress.d.ts +5 -0
  26. package/dist/react/components/progress.js +11 -0
  27. package/dist/react/components/separator.d.ts +5 -0
  28. package/dist/react/components/separator.js +7 -0
  29. package/dist/react/components/slider.d.ts +5 -0
  30. package/dist/react/components/slider.js +21 -0
  31. package/dist/react/components/stack.d.ts +7 -0
  32. package/dist/react/components/stack.js +32 -0
  33. package/dist/react/components/switch.d.ts +5 -0
  34. package/dist/react/components/switch.js +23 -0
  35. package/dist/react/components/text.d.ts +5 -0
  36. package/dist/react/components/text.js +25 -0
  37. package/dist/react/components/toggle-group.d.ts +5 -0
  38. package/dist/react/components/toggle-group.js +52 -0
  39. package/dist/react/hooks/use-snap-accent.d.ts +13 -0
  40. package/dist/react/hooks/use-snap-accent.js +32 -0
  41. package/dist/react/index.d.ts +47 -0
  42. package/dist/react/index.js +191 -0
  43. package/dist/react/lib/preview-primary-css.d.ts +6 -0
  44. package/dist/react/lib/preview-primary-css.js +43 -0
  45. package/dist/react/lib/resolve-palette-hex.d.ts +2 -0
  46. package/dist/react/lib/resolve-palette-hex.js +10 -0
  47. package/dist/react-native/catalog-renderer.d.ts +5 -0
  48. package/dist/react-native/catalog-renderer.js +36 -0
  49. package/dist/react-native/components/snap-action-button.d.ts +2 -0
  50. package/dist/react-native/components/snap-action-button.js +68 -0
  51. package/dist/react-native/components/snap-badge.d.ts +2 -0
  52. package/dist/react-native/components/snap-badge.js +38 -0
  53. package/dist/react-native/components/snap-icon.d.ts +5 -0
  54. package/dist/react-native/components/snap-icon.js +56 -0
  55. package/dist/react-native/components/snap-image.d.ts +2 -0
  56. package/dist/react-native/components/snap-image.js +24 -0
  57. package/dist/react-native/components/snap-input.d.ts +2 -0
  58. package/dist/react-native/components/snap-input.js +36 -0
  59. package/dist/react-native/components/snap-item-group.d.ts +5 -0
  60. package/dist/react-native/components/snap-item-group.js +23 -0
  61. package/dist/react-native/components/snap-item.d.ts +5 -0
  62. package/dist/react-native/components/snap-item.js +45 -0
  63. package/dist/react-native/components/snap-progress.d.ts +2 -0
  64. package/dist/react-native/components/snap-progress.js +26 -0
  65. package/dist/react-native/components/snap-separator.d.ts +2 -0
  66. package/dist/react-native/components/snap-separator.js +23 -0
  67. package/dist/react-native/components/snap-slider.d.ts +2 -0
  68. package/dist/react-native/components/snap-slider.js +42 -0
  69. package/dist/react-native/components/snap-stack.d.ts +5 -0
  70. package/dist/react-native/components/snap-stack.js +49 -0
  71. package/dist/react-native/components/snap-switch.d.ts +2 -0
  72. package/dist/react-native/components/snap-switch.js +30 -0
  73. package/dist/react-native/components/snap-text.d.ts +2 -0
  74. package/dist/react-native/components/snap-text.js +37 -0
  75. package/dist/react-native/components/snap-toggle-group.d.ts +2 -0
  76. package/dist/react-native/components/snap-toggle-group.js +100 -0
  77. package/dist/react-native/index.d.ts +52 -0
  78. package/dist/react-native/index.js +155 -0
  79. package/dist/react-native/theme.d.ts +21 -0
  80. package/dist/react-native/theme.js +37 -0
  81. package/dist/react-native/use-snap-palette.d.ts +13 -0
  82. package/dist/react-native/use-snap-palette.js +48 -0
  83. package/dist/schemas.d.ts +14 -1629
  84. package/dist/schemas.js +14 -526
  85. package/dist/ui/badge.d.ts +52 -0
  86. package/dist/ui/badge.js +9 -0
  87. package/dist/ui/button.d.ts +42 -28
  88. package/dist/ui/button.js +7 -9
  89. package/dist/ui/catalog.d.ts +281 -156
  90. package/dist/ui/catalog.js +102 -83
  91. package/dist/ui/icon.d.ts +56 -0
  92. package/dist/ui/icon.js +51 -0
  93. package/dist/ui/image.d.ts +1 -0
  94. package/dist/ui/image.js +2 -2
  95. package/dist/ui/index.d.ts +20 -22
  96. package/dist/ui/index.js +10 -11
  97. package/dist/ui/input.d.ts +17 -0
  98. package/dist/ui/input.js +13 -0
  99. package/dist/ui/item-group.d.ts +12 -0
  100. package/dist/ui/item-group.js +7 -0
  101. package/dist/ui/item.d.ts +14 -0
  102. package/dist/ui/item.js +9 -0
  103. package/dist/ui/progress.d.ts +1 -11
  104. package/dist/ui/progress.js +21 -4
  105. package/dist/ui/schema.d.ts +1 -1
  106. package/dist/ui/schema.js +3 -3
  107. package/dist/ui/separator.d.ts +9 -0
  108. package/dist/ui/separator.js +5 -0
  109. package/dist/ui/slider.d.ts +4 -3
  110. package/dist/ui/slider.js +34 -5
  111. package/dist/ui/stack.d.ts +22 -1
  112. package/dist/ui/stack.js +8 -1
  113. package/dist/ui/switch.d.ts +8 -0
  114. package/dist/ui/switch.js +7 -0
  115. package/dist/ui/text.d.ts +15 -7
  116. package/dist/ui/text.js +8 -4
  117. package/dist/ui/toggle-group.d.ts +23 -0
  118. package/dist/ui/toggle-group.js +19 -0
  119. package/dist/validator.d.ts +5 -1
  120. package/dist/validator.js +6 -136
  121. package/package.json +78 -53
  122. package/src/constants.ts +0 -179
  123. package/src/dataStore.ts +62 -0
  124. package/src/index.ts +10 -20
  125. package/src/react/accent-context.tsx +29 -0
  126. package/src/react/catalog-renderer.tsx +39 -0
  127. package/src/react/components/action-button.tsx +48 -0
  128. package/src/react/components/badge.tsx +37 -0
  129. package/src/react/components/icon.tsx +115 -0
  130. package/src/react/components/image.tsx +33 -0
  131. package/src/react/components/input.tsx +36 -0
  132. package/src/react/components/item-group.tsx +43 -0
  133. package/src/react/components/item.tsx +33 -0
  134. package/src/react/components/progress.tsx +29 -0
  135. package/src/react/components/separator.tsx +14 -0
  136. package/src/react/components/slider.tsx +43 -0
  137. package/src/react/components/stack.tsx +55 -0
  138. package/src/react/components/switch.tsx +46 -0
  139. package/src/react/components/text.tsx +43 -0
  140. package/src/react/components/toggle-group.tsx +85 -0
  141. package/src/react/hooks/use-snap-accent.ts +45 -0
  142. package/src/react/index.tsx +321 -0
  143. package/src/react/lib/preview-primary-css.ts +57 -0
  144. package/src/react/lib/resolve-palette-hex.ts +20 -0
  145. package/src/react-native/catalog-renderer.tsx +37 -0
  146. package/src/react-native/components/snap-action-button.tsx +92 -0
  147. package/src/react-native/components/snap-badge.tsx +57 -0
  148. package/src/react-native/components/snap-icon.tsx +102 -0
  149. package/src/react-native/components/snap-image.tsx +38 -0
  150. package/src/react-native/components/snap-input.tsx +57 -0
  151. package/src/react-native/components/snap-item-group.tsx +43 -0
  152. package/src/react-native/components/snap-item.tsx +70 -0
  153. package/src/react-native/components/snap-progress.tsx +40 -0
  154. package/src/react-native/components/snap-separator.tsx +32 -0
  155. package/src/react-native/components/snap-slider.tsx +82 -0
  156. package/src/react-native/components/snap-stack.tsx +66 -0
  157. package/src/react-native/components/snap-switch.tsx +45 -0
  158. package/src/react-native/components/snap-text.tsx +53 -0
  159. package/src/react-native/components/snap-toggle-group.tsx +128 -0
  160. package/src/react-native/index.tsx +267 -0
  161. package/src/react-native/theme.tsx +73 -0
  162. package/src/react-native/use-snap-palette.ts +64 -0
  163. package/src/schemas.ts +18 -644
  164. package/src/ui/badge.ts +13 -0
  165. package/src/ui/button.ts +9 -12
  166. package/src/ui/catalog.ts +106 -86
  167. package/src/ui/icon.ts +56 -0
  168. package/src/ui/image.ts +3 -2
  169. package/src/ui/index.ts +26 -29
  170. package/src/ui/input.ts +17 -0
  171. package/src/ui/item-group.ts +11 -0
  172. package/src/ui/item.ts +13 -0
  173. package/src/ui/progress.ts +25 -7
  174. package/src/ui/schema.ts +3 -3
  175. package/src/ui/separator.ts +9 -0
  176. package/src/ui/slider.ts +40 -10
  177. package/src/ui/stack.ts +9 -1
  178. package/src/ui/switch.ts +11 -0
  179. package/src/ui/text.ts +9 -4
  180. package/src/ui/toggle-group.ts +23 -0
  181. package/src/validator.ts +6 -176
  182. package/dist/ui/bar-chart.d.ts +0 -30
  183. package/dist/ui/bar-chart.js +0 -15
  184. package/dist/ui/button-group.d.ts +0 -19
  185. package/dist/ui/button-group.js +0 -18
  186. package/dist/ui/divider.d.ts +0 -3
  187. package/dist/ui/divider.js +0 -2
  188. package/dist/ui/grid.d.ts +0 -22
  189. package/dist/ui/grid.js +0 -16
  190. package/dist/ui/group.d.ts +0 -7
  191. package/dist/ui/group.js +0 -5
  192. package/dist/ui/list.d.ts +0 -13
  193. package/dist/ui/list.js +0 -13
  194. package/dist/ui/spacer.d.ts +0 -9
  195. package/dist/ui/spacer.js +0 -5
  196. package/dist/ui/text-input.d.ts +0 -7
  197. package/dist/ui/text-input.js +0 -12
  198. package/dist/ui/toggle.d.ts +0 -7
  199. package/dist/ui/toggle.js +0 -6
  200. package/src/ui/bar-chart.ts +0 -20
  201. package/src/ui/button-group.ts +0 -26
  202. package/src/ui/divider.ts +0 -5
  203. package/src/ui/grid.ts +0 -25
  204. package/src/ui/group.ts +0 -8
  205. package/src/ui/list.ts +0 -17
  206. package/src/ui/spacer.ts +0 -8
  207. package/src/ui/text-input.ts +0 -15
  208. package/src/ui/toggle.ts +0 -9
@@ -0,0 +1,267 @@
1
+ import type { Spec } from "@json-render/core";
2
+ import { snapJsonRenderCatalog } from "@farcaster/snap/ui";
3
+ import { SnapCatalogView } from "./catalog-renderer";
4
+ import { SnapThemeProvider, useSnapTheme, type SnapNativeColors } from "./theme";
5
+ import { hexToRgba } from "./use-snap-palette";
6
+ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
7
+ import { ActivityIndicator, StyleSheet, View } from "react-native";
8
+ import {
9
+ DEFAULT_THEME_ACCENT,
10
+ PALETTE_LIGHT_HEX,
11
+ PALETTE_DARK_HEX,
12
+ type PaletteColor,
13
+ } from "@farcaster/snap";
14
+
15
+ // ─── Public types ──────────────────────────────────────
16
+
17
+ export type JsonValue =
18
+ | string
19
+ | number
20
+ | boolean
21
+ | null
22
+ | JsonValue[]
23
+ | { [key: string]: JsonValue };
24
+
25
+ export type SnapPage = {
26
+ version: string;
27
+ theme?: { accent?: string };
28
+ effects?: string[];
29
+ ui: Spec;
30
+ };
31
+
32
+ export type SnapActionHandlers = {
33
+ submit: (target: string, inputs: Record<string, JsonValue>) => void;
34
+ open_url: (target: string) => void;
35
+ open_mini_app: (target: string) => void;
36
+ view_cast: (params: { hash: string }) => void;
37
+ view_profile: (params: { fid: number }) => void;
38
+ compose_cast: (params: {
39
+ text?: string;
40
+ channelKey?: string;
41
+ embeds?: string[];
42
+ }) => void;
43
+ view_token: (params: { token: string }) => void;
44
+ send_token: (params: {
45
+ token: string;
46
+ amount?: string;
47
+ recipientFid?: number;
48
+ recipientAddress?: string;
49
+ }) => void;
50
+ swap_token: (params: {
51
+ sellToken?: string;
52
+ buyToken?: string;
53
+ }) => void;
54
+ };
55
+
56
+ // ─── Re-exports ───────────────────────────────────────
57
+
58
+ export { useSnapTheme, hexToRgba };
59
+ export type { SnapNativeColors };
60
+
61
+ // ─── Internal helpers ─────────────────────────────────
62
+
63
+ function applyStatePaths(
64
+ model: Record<string, unknown>,
65
+ changes: { path: string; value: unknown }[] | Record<string, unknown>,
66
+ ): void {
67
+ const entries = Array.isArray(changes)
68
+ ? changes.map((c) => [c.path, c.value] as const)
69
+ : Object.entries(changes);
70
+ for (const [path, value] of entries) {
71
+ const trimmed = path.startsWith("/") ? path : `/${path}`;
72
+ const parts = trimmed.split("/").filter(Boolean);
73
+ if (parts.length < 2) continue;
74
+ const [top, ...rest] = parts;
75
+ if (top === "inputs") {
76
+ if (typeof model.inputs !== "object" || model.inputs === null) {
77
+ model.inputs = {};
78
+ }
79
+ const inputs = model.inputs as Record<string, unknown>;
80
+ if (rest.length === 1) {
81
+ inputs[rest[0]!] = value;
82
+ }
83
+ continue;
84
+ }
85
+ if (top === "theme") {
86
+ if (typeof model.theme !== "object" || model.theme === null) {
87
+ model.theme = {};
88
+ }
89
+ const theme = model.theme as Record<string, unknown>;
90
+ if (rest.length === 1) {
91
+ theme[rest[0]!] = value;
92
+ }
93
+ }
94
+ }
95
+ }
96
+
97
+ function resolveAccentHex(accent: string | undefined, appearance: "light" | "dark"): string {
98
+ const map = appearance === "dark" ? PALETTE_DARK_HEX : PALETTE_LIGHT_HEX;
99
+ const name = accent && Object.hasOwn(map, accent) ? (accent as PaletteColor) : DEFAULT_THEME_ACCENT;
100
+ return map[name];
101
+ }
102
+
103
+ // ─── SnapView ─────────────────────────────────────────
104
+
105
+ function SnapViewInner({
106
+ snap,
107
+ handlers,
108
+ loading = false,
109
+ }: {
110
+ snap: SnapPage;
111
+ handlers: SnapActionHandlers;
112
+ loading?: boolean;
113
+ }) {
114
+ const { mode } = useSnapTheme();
115
+ const spec = snap.ui;
116
+ const accentHex = resolveAccentHex(snap.theme?.accent, mode);
117
+
118
+ const initialState = useMemo(
119
+ () => ({
120
+ ...(spec.state ?? {}),
121
+ inputs: { ...((spec.state?.inputs ?? {}) as Record<string, unknown>) },
122
+ theme: {
123
+ ...((spec.state?.theme ?? {}) as Record<string, unknown>),
124
+ ...(snap.theme ? { accent: snap.theme.accent } : {}),
125
+ },
126
+ }),
127
+ [spec, snap.theme],
128
+ );
129
+
130
+ const stateRef = useRef<Record<string, unknown>>(initialState);
131
+
132
+ useEffect(() => {
133
+ stateRef.current = {
134
+ inputs: {
135
+ ...((initialState.inputs ?? {}) as Record<string, unknown>),
136
+ },
137
+ theme: {
138
+ ...((initialState.theme ?? {}) as Record<string, unknown>),
139
+ },
140
+ };
141
+ }, [initialState]);
142
+
143
+ useEffect(() => {
144
+ const result = snapJsonRenderCatalog.validate(spec);
145
+ if (!result.success) {
146
+ // eslint-disable-next-line no-console
147
+ console.warn("[SnapView] catalog validation issues:", result.error);
148
+ }
149
+ }, [spec]);
150
+
151
+ const [pageKey, setPageKey] = useState(0);
152
+ useEffect(() => {
153
+ setPageKey((k) => k + 1);
154
+ }, [spec]);
155
+
156
+ const handlersRef = useRef(handlers);
157
+ handlersRef.current = handlers;
158
+
159
+ const handleAction = useCallback((name: unknown, params: unknown) => {
160
+ const inputs = (stateRef.current.inputs ?? {}) as Record<string, JsonValue>;
161
+ const p = (params ?? {}) as Record<string, unknown>;
162
+ const h = handlersRef.current;
163
+ switch (name) {
164
+ case "submit":
165
+ h.submit(String(p.target ?? ""), inputs);
166
+ break;
167
+ case "open_url":
168
+ h.open_url(String(p.target ?? ""));
169
+ break;
170
+ case "open_mini_app":
171
+ h.open_mini_app(String(p.target ?? ""));
172
+ break;
173
+ case "view_cast":
174
+ h.view_cast({ hash: String(p.hash ?? "") });
175
+ break;
176
+ case "view_profile":
177
+ h.view_profile({ fid: Number(p.fid ?? 0) });
178
+ break;
179
+ case "compose_cast":
180
+ h.compose_cast({
181
+ text: p.text ? String(p.text) : undefined,
182
+ channelKey: p.channelKey ? String(p.channelKey) : undefined,
183
+ embeds: Array.isArray(p.embeds) ? (p.embeds as string[]) : undefined,
184
+ });
185
+ break;
186
+ case "view_token":
187
+ h.view_token({ token: String(p.token ?? "") });
188
+ break;
189
+ case "send_token":
190
+ h.send_token({
191
+ token: String(p.token ?? ""),
192
+ amount: p.amount ? String(p.amount) : undefined,
193
+ recipientFid: p.recipientFid ? Number(p.recipientFid) : undefined,
194
+ recipientAddress: p.recipientAddress ? String(p.recipientAddress) : undefined,
195
+ });
196
+ break;
197
+ case "swap_token":
198
+ h.swap_token({
199
+ sellToken: p.sellToken ? String(p.sellToken) : undefined,
200
+ buyToken: p.buyToken ? String(p.buyToken) : undefined,
201
+ });
202
+ break;
203
+ default:
204
+ break;
205
+ }
206
+ }, []);
207
+
208
+ return (
209
+ <View style={styles.container}>
210
+ {loading && (
211
+ <View
212
+ style={[
213
+ styles.overlay,
214
+ {
215
+ backgroundColor:
216
+ mode === "dark" ? "rgba(0,0,0,0.6)" : "rgba(255,255,255,0.75)",
217
+ },
218
+ ]}
219
+ >
220
+ <ActivityIndicator size="large" color={accentHex} />
221
+ </View>
222
+ )}
223
+ <SnapCatalogView
224
+ key={pageKey}
225
+ spec={spec}
226
+ state={initialState}
227
+ loading={false}
228
+ onStateChange={(changes) => {
229
+ applyStatePaths(stateRef.current, changes);
230
+ }}
231
+ onAction={handleAction}
232
+ />
233
+ </View>
234
+ );
235
+ }
236
+
237
+ export function SnapView({
238
+ snap,
239
+ handlers,
240
+ loading = false,
241
+ appearance = "dark",
242
+ colors,
243
+ }: {
244
+ snap: SnapPage;
245
+ handlers: SnapActionHandlers;
246
+ loading?: boolean;
247
+ appearance?: "light" | "dark";
248
+ colors?: Partial<SnapNativeColors>;
249
+ }) {
250
+ return (
251
+ <SnapThemeProvider appearance={appearance} colors={colors}>
252
+ <SnapViewInner snap={snap} handlers={handlers} loading={loading} />
253
+ </SnapThemeProvider>
254
+ );
255
+ }
256
+
257
+ const styles = StyleSheet.create({
258
+ container: {
259
+ width: "100%",
260
+ },
261
+ overlay: {
262
+ ...StyleSheet.absoluteFillObject,
263
+ alignItems: "center",
264
+ justifyContent: "center",
265
+ zIndex: 10,
266
+ },
267
+ });
@@ -0,0 +1,73 @@
1
+ import { createContext, useContext, useMemo, type ReactNode } from "react";
2
+
3
+ // ─── Color tokens ─────────────────────────────────────
4
+
5
+ export type SnapNativeColors = {
6
+ bg: string;
7
+ surface: string;
8
+ text: string;
9
+ textSecondary: string;
10
+ border: string;
11
+ inputBg: string;
12
+ muted: string;
13
+ };
14
+
15
+ const DEFAULT_LIGHT: SnapNativeColors = {
16
+ bg: "#dfe3e8",
17
+ surface: "#ffffff",
18
+ text: "#111111",
19
+ textSecondary: "#6b7280",
20
+ border: "#d1d5db",
21
+ inputBg: "#ffffff",
22
+ muted: "#f9fafb",
23
+ };
24
+
25
+ const DEFAULT_DARK: SnapNativeColors = {
26
+ bg: "#111318",
27
+ surface: "#1a1d24",
28
+ text: "#fafafa",
29
+ textSecondary: "#a1a1aa",
30
+ border: "#374151",
31
+ inputBg: "#1a1d24",
32
+ muted: "#27272a",
33
+ };
34
+
35
+ // ─── Context ──────────────────────────────────────────
36
+
37
+ interface SnapThemeValue {
38
+ mode: "light" | "dark";
39
+ colors: SnapNativeColors;
40
+ }
41
+
42
+ const SnapThemeContext = createContext<SnapThemeValue>({
43
+ mode: "dark",
44
+ colors: DEFAULT_DARK,
45
+ });
46
+
47
+ export function SnapThemeProvider({
48
+ appearance,
49
+ colors,
50
+ children,
51
+ }: {
52
+ appearance: "light" | "dark";
53
+ colors?: Partial<SnapNativeColors>;
54
+ children: ReactNode;
55
+ }) {
56
+ const value = useMemo<SnapThemeValue>(() => {
57
+ const defaults = appearance === "dark" ? DEFAULT_DARK : DEFAULT_LIGHT;
58
+ return {
59
+ mode: appearance,
60
+ colors: colors ? { ...defaults, ...colors } : defaults,
61
+ };
62
+ }, [appearance, colors]);
63
+
64
+ return (
65
+ <SnapThemeContext.Provider value={value}>
66
+ {children}
67
+ </SnapThemeContext.Provider>
68
+ );
69
+ }
70
+
71
+ export function useSnapTheme(): SnapThemeValue {
72
+ return useContext(SnapThemeContext);
73
+ }
@@ -0,0 +1,64 @@
1
+ import {
2
+ DEFAULT_THEME_ACCENT,
3
+ PALETTE_COLOR_VALUES,
4
+ PALETTE_LIGHT_HEX,
5
+ PALETTE_DARK_HEX,
6
+ type PaletteColor,
7
+ } from "@farcaster/snap";
8
+ import { useStateStore } from "@json-render/react-native";
9
+ import { useSnapTheme } from "./theme";
10
+
11
+ function resolveHex(name: string, appearance: "light" | "dark"): string {
12
+ const map = appearance === "dark" ? PALETTE_DARK_HEX : PALETTE_LIGHT_HEX;
13
+ if (Object.hasOwn(map, name)) {
14
+ return map[name as PaletteColor];
15
+ }
16
+ return map.purple;
17
+ }
18
+
19
+ function isPaletteColor(s: string): s is PaletteColor {
20
+ return (PALETTE_COLOR_VALUES as readonly string[]).includes(s);
21
+ }
22
+
23
+ function themeAccentFromStore(get: (path: string) => unknown): PaletteColor {
24
+ const raw = get("/theme/accent");
25
+ if (typeof raw === "string" && isPaletteColor(raw)) {
26
+ return raw;
27
+ }
28
+ return DEFAULT_THEME_ACCENT;
29
+ }
30
+
31
+ export function useSnapPalette() {
32
+ const { mode } = useSnapTheme();
33
+ const { get } = useStateStore();
34
+ const accentName = themeAccentFromStore(get);
35
+ const accentHex = resolveHex(accentName, mode);
36
+
37
+ const hex = (semantic: string) =>
38
+ semantic === "accent" ? accentHex : resolveHex(semantic, mode);
39
+
40
+ return { appearance: mode, accentName, accentHex, hex };
41
+ }
42
+
43
+ /** `#RRGGBB` + alpha → `rgba(...)` for React Native styles. */
44
+ export function hexToRgba(hex: string, alpha: number): string {
45
+ const m = /^#([0-9a-fA-F]{6})$/.exec(hex.trim());
46
+ if (!m) {
47
+ return `rgba(0,0,0,${alpha})`;
48
+ }
49
+ const n = Number.parseInt(m[1]!, 16);
50
+ const r = (n >> 16) & 255;
51
+ const g = (n >> 8) & 255;
52
+ const b = n & 255;
53
+ return `rgba(${r},${g},${b},${alpha})`;
54
+ }
55
+
56
+ export function useSnapPreviewChromePalette(themeAccent: string | undefined) {
57
+ const { mode } = useSnapTheme();
58
+ const accentName =
59
+ typeof themeAccent === "string" && isPaletteColor(themeAccent)
60
+ ? themeAccent
61
+ : DEFAULT_THEME_ACCENT;
62
+ const accentHex = resolveHex(accentName, mode);
63
+ return { appearance: mode, accentName, accentHex };
64
+ }