@farcaster/snap 2.0.0 → 2.0.2

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 (194) hide show
  1. package/dist/colors.d.ts +4 -4
  2. package/dist/colors.js +20 -20
  3. package/dist/constants.d.ts +17 -1
  4. package/dist/constants.js +19 -1
  5. package/dist/index.d.ts +4 -6
  6. package/dist/index.js +2 -4
  7. package/dist/react/accent-context.d.ts +3 -1
  8. package/dist/react/accent-context.js +7 -4
  9. package/dist/react/catalog-renderer.js +4 -0
  10. package/dist/react/components/action-button.d.ts +2 -1
  11. package/dist/react/components/action-button.js +35 -13
  12. package/dist/react/components/badge.js +8 -8
  13. package/dist/react/components/bar-chart.d.ts +5 -0
  14. package/dist/react/components/bar-chart.js +26 -0
  15. package/dist/react/components/cell-grid.d.ts +5 -0
  16. package/dist/react/components/cell-grid.js +87 -0
  17. package/dist/react/components/icon.js +4 -10
  18. package/dist/react/components/input.js +12 -6
  19. package/dist/react/components/item-group.js +3 -1
  20. package/dist/react/components/item.d.ts +3 -3
  21. package/dist/react/components/item.js +4 -3
  22. package/dist/react/components/progress.js +3 -3
  23. package/dist/react/components/separator.js +3 -1
  24. package/dist/react/components/slider.js +15 -10
  25. package/dist/react/components/switch.js +10 -12
  26. package/dist/react/components/text.js +6 -14
  27. package/dist/react/components/toggle-group.js +20 -6
  28. package/dist/react/hooks/use-snap-colors.d.ts +38 -0
  29. package/dist/react/hooks/use-snap-colors.js +81 -0
  30. package/dist/react/index.d.ts +13 -1
  31. package/dist/react/index.js +9 -188
  32. package/dist/react/snap-view-core.d.ts +11 -0
  33. package/dist/react/snap-view-core.js +227 -0
  34. package/dist/react/v1/snap-view.d.ts +16 -0
  35. package/dist/react/v1/snap-view.js +90 -0
  36. package/dist/react/v2/snap-view.d.ts +23 -0
  37. package/dist/react/v2/snap-view.js +91 -0
  38. package/dist/react-native/catalog-renderer.d.ts +5 -0
  39. package/dist/react-native/catalog-renderer.js +40 -0
  40. package/dist/react-native/components/snap-action-button.d.ts +2 -0
  41. package/dist/react-native/components/snap-action-button.js +69 -0
  42. package/dist/react-native/components/snap-badge.d.ts +2 -0
  43. package/dist/react-native/components/snap-badge.js +41 -0
  44. package/dist/react-native/components/snap-bar-chart.d.ts +2 -0
  45. package/dist/react-native/components/snap-bar-chart.js +39 -0
  46. package/dist/react-native/components/snap-cell-grid.d.ts +2 -0
  47. package/dist/react-native/components/snap-cell-grid.js +94 -0
  48. package/dist/react-native/components/snap-icon.d.ts +5 -0
  49. package/dist/react-native/components/snap-icon.js +56 -0
  50. package/dist/react-native/components/snap-image.d.ts +2 -0
  51. package/dist/react-native/components/snap-image.js +23 -0
  52. package/dist/react-native/components/snap-input.d.ts +2 -0
  53. package/dist/react-native/components/snap-input.js +37 -0
  54. package/dist/react-native/components/snap-item-group.d.ts +5 -0
  55. package/dist/react-native/components/snap-item-group.js +23 -0
  56. package/dist/react-native/components/snap-item.d.ts +5 -0
  57. package/dist/react-native/components/snap-item.js +42 -0
  58. package/dist/react-native/components/snap-progress.d.ts +2 -0
  59. package/dist/react-native/components/snap-progress.js +26 -0
  60. package/dist/react-native/components/snap-separator.d.ts +2 -0
  61. package/dist/react-native/components/snap-separator.js +23 -0
  62. package/dist/react-native/components/snap-slider.d.ts +2 -0
  63. package/dist/react-native/components/snap-slider.js +43 -0
  64. package/dist/react-native/components/snap-stack.d.ts +5 -0
  65. package/dist/react-native/components/snap-stack.js +49 -0
  66. package/dist/react-native/components/snap-switch.d.ts +2 -0
  67. package/dist/react-native/components/snap-switch.js +31 -0
  68. package/dist/react-native/components/snap-text.d.ts +2 -0
  69. package/dist/react-native/components/snap-text.js +35 -0
  70. package/dist/react-native/components/snap-toggle-group.d.ts +2 -0
  71. package/dist/react-native/components/snap-toggle-group.js +99 -0
  72. package/dist/react-native/confetti-overlay.d.ts +1 -0
  73. package/dist/react-native/confetti-overlay.js +106 -0
  74. package/dist/react-native/index.d.ts +28 -0
  75. package/dist/react-native/index.js +15 -0
  76. package/dist/react-native/snap-view-core.d.ts +11 -0
  77. package/dist/react-native/snap-view-core.js +156 -0
  78. package/dist/react-native/theme.d.ts +27 -0
  79. package/dist/react-native/theme.js +43 -0
  80. package/dist/react-native/types.d.ts +42 -0
  81. package/dist/react-native/types.js +1 -0
  82. package/dist/react-native/use-snap-palette.d.ts +13 -0
  83. package/dist/react-native/use-snap-palette.js +48 -0
  84. package/dist/react-native/v1/snap-view.d.ts +24 -0
  85. package/dist/react-native/v1/snap-view.js +96 -0
  86. package/dist/react-native/v2/snap-view.d.ts +33 -0
  87. package/dist/react-native/v2/snap-view.js +114 -0
  88. package/dist/schemas.d.ts +100 -13
  89. package/dist/schemas.js +28 -10
  90. package/dist/server/parseRequest.d.ts +10 -0
  91. package/dist/server/parseRequest.js +48 -7
  92. package/dist/server/verify.d.ts +1 -0
  93. package/dist/server/verify.js +1 -0
  94. package/dist/ui/badge.d.ts +7 -2
  95. package/dist/ui/badge.js +2 -0
  96. package/dist/ui/bar-chart.d.ts +30 -0
  97. package/dist/ui/bar-chart.js +30 -0
  98. package/dist/ui/button.d.ts +4 -6
  99. package/dist/ui/button.js +1 -1
  100. package/dist/ui/catalog.d.ts +90 -16
  101. package/dist/ui/catalog.js +17 -3
  102. package/dist/ui/cell-grid.d.ts +34 -0
  103. package/dist/ui/cell-grid.js +39 -0
  104. package/dist/ui/icon.d.ts +2 -2
  105. package/dist/ui/image.d.ts +1 -2
  106. package/dist/ui/image.js +1 -1
  107. package/dist/ui/index.d.ts +4 -0
  108. package/dist/ui/index.js +2 -0
  109. package/dist/ui/item.d.ts +1 -3
  110. package/dist/ui/item.js +1 -1
  111. package/dist/ui/schema.d.ts +6 -2
  112. package/dist/ui/schema.js +2 -2
  113. package/dist/ui/slider.d.ts +1 -0
  114. package/dist/ui/slider.js +2 -0
  115. package/dist/ui/text.d.ts +2 -4
  116. package/dist/ui/text.js +2 -2
  117. package/dist/validator.d.ts +3 -2
  118. package/dist/validator.js +203 -2
  119. package/llms.txt +199 -0
  120. package/package.json +9 -3
  121. package/src/colors.ts +20 -20
  122. package/src/constants.ts +23 -1
  123. package/src/index.ts +16 -13
  124. package/src/react/accent-context.tsx +13 -6
  125. package/src/react/catalog-renderer.tsx +4 -0
  126. package/src/react/components/action-button.tsx +50 -20
  127. package/src/react/components/badge.tsx +14 -18
  128. package/src/react/components/bar-chart.tsx +69 -0
  129. package/src/react/components/cell-grid.tsx +128 -0
  130. package/src/react/components/icon.tsx +5 -18
  131. package/src/react/components/input.tsx +20 -9
  132. package/src/react/components/item-group.tsx +4 -1
  133. package/src/react/components/item.tsx +13 -10
  134. package/src/react/components/progress.tsx +12 -7
  135. package/src/react/components/separator.tsx +8 -1
  136. package/src/react/components/slider.tsx +28 -15
  137. package/src/react/components/switch.tsx +12 -16
  138. package/src/react/components/text.tsx +14 -23
  139. package/src/react/components/toggle-group.tsx +26 -9
  140. package/src/react/hooks/use-snap-colors.ts +128 -0
  141. package/src/react/index.tsx +49 -265
  142. package/src/react/snap-view-core.tsx +343 -0
  143. package/src/react/v1/snap-view.tsx +176 -0
  144. package/src/react/v2/snap-view.tsx +199 -0
  145. package/src/react-native/catalog-renderer.tsx +41 -0
  146. package/src/react-native/components/snap-action-button.tsx +96 -0
  147. package/src/react-native/components/snap-badge.tsx +60 -0
  148. package/src/react-native/components/snap-bar-chart.tsx +73 -0
  149. package/src/react-native/components/snap-cell-grid.tsx +150 -0
  150. package/src/react-native/components/snap-icon.tsx +102 -0
  151. package/src/react-native/components/snap-image.tsx +37 -0
  152. package/src/react-native/components/snap-input.tsx +58 -0
  153. package/src/react-native/components/snap-item-group.tsx +43 -0
  154. package/src/react-native/components/snap-item.tsx +66 -0
  155. package/src/react-native/components/snap-progress.tsx +40 -0
  156. package/src/react-native/components/snap-separator.tsx +32 -0
  157. package/src/react-native/components/snap-slider.tsx +85 -0
  158. package/src/react-native/components/snap-stack.tsx +66 -0
  159. package/src/react-native/components/snap-switch.tsx +46 -0
  160. package/src/react-native/components/snap-text.tsx +51 -0
  161. package/src/react-native/components/snap-toggle-group.tsx +127 -0
  162. package/src/react-native/confetti-overlay.tsx +134 -0
  163. package/src/react-native/index.tsx +83 -0
  164. package/src/react-native/snap-view-core.tsx +212 -0
  165. package/src/react-native/theme.tsx +85 -0
  166. package/src/react-native/types.ts +38 -0
  167. package/src/react-native/use-snap-palette.ts +64 -0
  168. package/src/react-native/v1/snap-view.tsx +229 -0
  169. package/src/react-native/v2/snap-view.tsx +283 -0
  170. package/src/schemas.ts +68 -17
  171. package/src/server/parseRequest.ts +68 -9
  172. package/src/server/verify.ts +2 -0
  173. package/src/ui/README.md +8 -8
  174. package/src/ui/badge.ts +2 -0
  175. package/src/ui/bar-chart.ts +38 -0
  176. package/src/ui/button.ts +1 -1
  177. package/src/ui/catalog.ts +19 -3
  178. package/src/ui/cell-grid.ts +49 -0
  179. package/src/ui/image.ts +1 -1
  180. package/src/ui/index.ts +6 -0
  181. package/src/ui/item.ts +1 -1
  182. package/src/ui/schema.ts +2 -2
  183. package/src/ui/slider.ts +2 -0
  184. package/src/ui/text.ts +2 -2
  185. package/src/validator.ts +251 -2
  186. package/dist/dataStore.d.ts +0 -12
  187. package/dist/dataStore.js +0 -35
  188. package/dist/middleware.d.ts +0 -3
  189. package/dist/middleware.js +0 -3
  190. package/dist/react/hooks/use-snap-accent.d.ts +0 -13
  191. package/dist/react/hooks/use-snap-accent.js +0 -32
  192. package/src/dataStore.ts +0 -62
  193. package/src/middleware.ts +0 -7
  194. package/src/react/hooks/use-snap-accent.ts +0 -45
@@ -0,0 +1,94 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { StyleSheet, Text, View, Pressable } from "react-native";
3
+ import { useStateStore } from "@json-render/react-native";
4
+ import { useSnapPalette } from "../use-snap-palette.js";
5
+ import { useSnapTheme } from "../theme.js";
6
+ import { POST_GRID_TAP_KEY } from "@farcaster/snap";
7
+ export function SnapCellGrid({ element: { props }, }) {
8
+ const { hex, appearance } = useSnapPalette();
9
+ const { colors } = useSnapTheme();
10
+ const { get, set } = useStateStore();
11
+ const cols = Number(props.cols ?? 2);
12
+ const rows = Number(props.rows ?? 2);
13
+ const cells = Array.isArray(props.cells) ? props.cells : [];
14
+ const rowHeight = typeof props.rowHeight === "number" ? props.rowHeight : 28;
15
+ const gap = String(props.gap ?? "sm");
16
+ const gapMap = { none: 0, sm: 1, md: 2, lg: 4 };
17
+ const gapPx = gapMap[gap] ?? 1;
18
+ const select = String(props.select ?? "off");
19
+ const interactive = select !== "off";
20
+ const isMultiple = select === "multiple";
21
+ const name = props.name ? String(props.name) : POST_GRID_TAP_KEY;
22
+ const tapPath = `/inputs/${name}`;
23
+ const tapRaw = get(tapPath);
24
+ const selectedSet = new Set();
25
+ if (typeof tapRaw === "string" && tapRaw.length > 0) {
26
+ for (const part of tapRaw.split("|")) {
27
+ if (part.includes(","))
28
+ selectedSet.add(part);
29
+ }
30
+ }
31
+ const isSelected = (r, c) => selectedSet.has(`${r},${c}`);
32
+ const handleTap = (r, c) => {
33
+ const key = `${r},${c}`;
34
+ if (isMultiple) {
35
+ const next = new Set(selectedSet);
36
+ if (next.has(key))
37
+ next.delete(key);
38
+ else
39
+ next.add(key);
40
+ set(tapPath, [...next].join("|"));
41
+ }
42
+ else {
43
+ set(tapPath, key);
44
+ }
45
+ };
46
+ const cellMap = new Map();
47
+ for (const c of cells) {
48
+ cellMap.set(`${Number(c.row)},${Number(c.col)}`, {
49
+ color: c.color,
50
+ content: c.content != null ? String(c.content) : undefined,
51
+ });
52
+ }
53
+ const ringOuter = appearance === "dark" ? "#fff" : "#000";
54
+ const ringInner = appearance === "dark" ? "#000" : "#fff";
55
+ const rowEls = [];
56
+ for (let r = 0; r < rows; r++) {
57
+ const rowCells = [];
58
+ for (let c = 0; c < cols; c++) {
59
+ const cell = cellMap.get(`${r},${c}`);
60
+ const selected = interactive && isSelected(r, c);
61
+ const bg = cell?.color ? hex(cell.color) : "transparent";
62
+ const cellContent = cell?.content ? (_jsx(Text, { style: [styles.cellText, { color: colors.textPrimary }], children: cell.content })) : null;
63
+ // Two-tone ring: outer View with contrasting border, inner View with inverse border
64
+ const cellView = selected ? (_jsx(View, { style: [styles.cell, { height: rowHeight, borderWidth: 1, borderColor: ringOuter, borderRadius: 4 }], children: _jsx(View, { style: [
65
+ styles.innerCell,
66
+ { backgroundColor: bg, borderWidth: 1, borderColor: ringInner, borderRadius: 3 },
67
+ ], children: cellContent }) })) : (_jsx(View, { style: [styles.cell, { height: rowHeight, backgroundColor: bg }], children: cellContent }));
68
+ rowCells.push(interactive ? (_jsx(Pressable, { onPress: () => handleTap(r, c), style: styles.cellWrap, children: cellView }, `${r}-${c}`)) : (_jsx(View, { style: styles.cellWrap, children: cellView }, `${r}-${c}`)));
69
+ }
70
+ rowEls.push(_jsx(View, { style: [styles.gridRow, { gap: gapPx }], children: rowCells }, r));
71
+ }
72
+ const selectionLabel = interactive && selectedSet.size > 0
73
+ ? `inputs.${name}: ${[...selectedSet].join(isMultiple ? " | " : "")}`
74
+ : null;
75
+ return (_jsxs(View, { style: [styles.wrap, { gap: gapPx, backgroundColor: colors.muted, padding: 4, borderRadius: 8 }], children: [rowEls, selectionLabel ? (_jsx(Text, { style: [styles.selectionText, { color: colors.textSecondary }], children: selectionLabel })) : null] }));
76
+ }
77
+ const styles = StyleSheet.create({
78
+ wrap: { width: "100%" },
79
+ gridRow: { flexDirection: "row" },
80
+ cellWrap: { flex: 1 },
81
+ cell: {
82
+ borderRadius: 4,
83
+ alignItems: "center",
84
+ justifyContent: "center",
85
+ },
86
+ innerCell: {
87
+ width: "100%",
88
+ height: "100%",
89
+ alignItems: "center",
90
+ justifyContent: "center",
91
+ },
92
+ cellText: { fontSize: 12, lineHeight: 16, fontWeight: "600" },
93
+ selectionText: { fontSize: 11, fontFamily: "monospace", marginTop: 6 },
94
+ });
@@ -0,0 +1,5 @@
1
+ import type { ComponentRenderProps } from "@json-render/react-native";
2
+ import { type LucideIcon } from "lucide-react-native";
3
+ declare const ICON_MAP: Record<string, LucideIcon>;
4
+ export declare function SnapIcon({ element: { props }, }: ComponentRenderProps<Record<string, unknown>>): import("react").JSX.Element;
5
+ export { ICON_MAP };
@@ -0,0 +1,56 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { View } from "react-native";
3
+ import { useSnapPalette } from "../use-snap-palette.js";
4
+ import { ArrowRight, ArrowLeft, ExternalLink, ChevronRight, Check, X, AlertTriangle, Info, Clock, Heart, MessageCircle, Repeat, Share, User, Users, Star, Trophy, Zap, Flame, Gift, ImageIcon, Play, Pause, Wallet, Coins, Plus, Minus, RefreshCw, Bookmark, ThumbsUp, ThumbsDown, TrendingUp, TrendingDown, } from "lucide-react-native";
5
+ const ICON_MAP = {
6
+ "arrow-right": ArrowRight,
7
+ "arrow-left": ArrowLeft,
8
+ "external-link": ExternalLink,
9
+ "chevron-right": ChevronRight,
10
+ check: Check,
11
+ x: X,
12
+ "alert-triangle": AlertTriangle,
13
+ info: Info,
14
+ clock: Clock,
15
+ heart: Heart,
16
+ "message-circle": MessageCircle,
17
+ repeat: Repeat,
18
+ share: Share,
19
+ user: User,
20
+ users: Users,
21
+ star: Star,
22
+ trophy: Trophy,
23
+ zap: Zap,
24
+ flame: Flame,
25
+ gift: Gift,
26
+ image: ImageIcon,
27
+ play: Play,
28
+ pause: Pause,
29
+ wallet: Wallet,
30
+ coins: Coins,
31
+ plus: Plus,
32
+ minus: Minus,
33
+ "refresh-cw": RefreshCw,
34
+ bookmark: Bookmark,
35
+ "thumbs-up": ThumbsUp,
36
+ "thumbs-down": ThumbsDown,
37
+ "trending-up": TrendingUp,
38
+ "trending-down": TrendingDown,
39
+ };
40
+ const SIZE_PX = {
41
+ sm: 16,
42
+ md: 20,
43
+ };
44
+ export function SnapIcon({ element: { props }, }) {
45
+ const { accentHex, hex } = useSnapPalette();
46
+ const name = String(props.name ?? "info");
47
+ const size = SIZE_PX[String(props.size ?? "md")] ?? 20;
48
+ const color = props.color ? String(props.color) : undefined;
49
+ const isAccent = !color || color === "accent";
50
+ const resolvedColor = isAccent ? accentHex : hex(color);
51
+ const Icon = ICON_MAP[name];
52
+ if (!Icon)
53
+ return null;
54
+ return (_jsx(View, { style: { alignItems: "center", justifyContent: "center" }, children: _jsx(Icon, { size: size, color: resolvedColor }) }));
55
+ }
56
+ export { ICON_MAP };
@@ -0,0 +1,2 @@
1
+ import type { ComponentRenderProps } from "@json-render/react-native";
2
+ export declare function SnapImage({ element: { props }, }: ComponentRenderProps<Record<string, unknown>>): import("react").JSX.Element;
@@ -0,0 +1,23 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { Image } from "expo-image";
3
+ import { StyleSheet, View } from "react-native";
4
+ function aspectToRatio(aspect) {
5
+ const [w, h] = aspect.split(":").map(Number);
6
+ if (!w || !h)
7
+ return 1;
8
+ return w / h;
9
+ }
10
+ export function SnapImage({ element: { props }, }) {
11
+ const url = String(props.url ?? "");
12
+ const alt = String(props.alt ?? "");
13
+ const ratio = aspectToRatio(String(props.aspect ?? "1:1"));
14
+ return (_jsx(View, { style: [styles.frame, { aspectRatio: ratio }], children: _jsx(Image, { source: { uri: url }, style: StyleSheet.absoluteFill, contentFit: "cover", accessibilityLabel: alt || undefined }) }));
15
+ }
16
+ const styles = StyleSheet.create({
17
+ frame: {
18
+ width: "100%",
19
+ borderRadius: 8,
20
+ overflow: "hidden",
21
+ backgroundColor: "#f3f4f6",
22
+ },
23
+ });
@@ -0,0 +1,2 @@
1
+ import type { ComponentRenderProps } from "@json-render/react-native";
2
+ export declare function SnapInput({ element: { props }, }: ComponentRenderProps<Record<string, unknown>>): import("react").JSX.Element;
@@ -0,0 +1,37 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useStateStore } from "@json-render/react-native";
3
+ import { StyleSheet, Text, TextInput, View } from "react-native";
4
+ import { useSnapTheme } from "../theme.js";
5
+ export function SnapInput({ element: { props }, }) {
6
+ const { get, set } = useStateStore();
7
+ const { colors } = useSnapTheme();
8
+ const name = String(props.name ?? "input");
9
+ const path = `/inputs/${name}`;
10
+ const label = props.label ? String(props.label) : undefined;
11
+ const placeholder = props.placeholder ? String(props.placeholder) : undefined;
12
+ const type = String(props.type ?? "text");
13
+ const maxLength = typeof props.maxLength === "number" ? props.maxLength : undefined;
14
+ const defaultValue = props.defaultValue != null ? String(props.defaultValue) : "";
15
+ const raw = get(path);
16
+ const value = raw !== undefined && raw !== null ? String(raw) : defaultValue;
17
+ return (_jsxs(View, { style: styles.wrap, children: [label ? _jsx(Text, { style: [styles.label, { color: colors.text }], children: label }) : null, _jsx(TextInput, { style: [
18
+ styles.input,
19
+ {
20
+ borderColor: colors.border,
21
+ backgroundColor: colors.inputBg,
22
+ color: colors.text,
23
+ },
24
+ ], value: value, onChangeText: (text) => set(path, type === "number" ? Number(text) || 0 : text), placeholder: placeholder, placeholderTextColor: colors.textSecondary, maxLength: maxLength, autoCapitalize: "none", autoCorrect: false, keyboardType: type === "number" ? "numeric" : "default" })] }));
25
+ }
26
+ const styles = StyleSheet.create({
27
+ wrap: { width: "100%", gap: 4 },
28
+ label: { fontSize: 13, lineHeight: 18, fontWeight: "500" },
29
+ input: {
30
+ borderWidth: 1,
31
+ borderRadius: 8,
32
+ paddingHorizontal: 12,
33
+ paddingVertical: 10,
34
+ fontSize: 14,
35
+ lineHeight: 18,
36
+ },
37
+ });
@@ -0,0 +1,5 @@
1
+ import type { ComponentRenderProps } from "@json-render/react-native";
2
+ import { type ReactNode } from "react";
3
+ export declare function SnapItemGroup({ element: { props }, children, }: ComponentRenderProps<Record<string, unknown>> & {
4
+ children?: ReactNode;
5
+ }): import("react").JSX.Element;
@@ -0,0 +1,23 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Children, Fragment } from "react";
3
+ import { StyleSheet, View } from "react-native";
4
+ import { useSnapTheme } from "../theme.js";
5
+ const GAP_MAP = { none: 0, sm: 4, md: 8, lg: 12 };
6
+ export function SnapItemGroup({ element: { props }, children, }) {
7
+ const { colors } = useSnapTheme();
8
+ const border = Boolean(props.border);
9
+ const separator = Boolean(props.separator);
10
+ const gap = GAP_MAP[String(props.gap ?? "sm")] ?? 4;
11
+ const items = Children.toArray(children);
12
+ return (_jsx(View, { style: [
13
+ styles.group,
14
+ border && { borderWidth: 1, borderColor: colors.border, borderRadius: 12 },
15
+ { gap },
16
+ ], children: items.map((child, i) => (_jsxs(Fragment, { children: [separator && i > 0 && (_jsx(View, { style: { height: 1, backgroundColor: colors.border + "80" } })), child] }, i))) }));
17
+ }
18
+ const styles = StyleSheet.create({
19
+ group: {
20
+ width: "100%",
21
+ overflow: "hidden",
22
+ },
23
+ });
@@ -0,0 +1,5 @@
1
+ import type { ComponentRenderProps } from "@json-render/react-native";
2
+ import type { ReactNode } from "react";
3
+ export declare function SnapItem({ element: { props }, children, }: ComponentRenderProps<Record<string, unknown>> & {
4
+ children?: ReactNode;
5
+ }): import("react").JSX.Element;
@@ -0,0 +1,42 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { StyleSheet, Text, View } from "react-native";
3
+ import { useSnapTheme } from "../theme.js";
4
+ export function SnapItem({ element: { props }, children, }) {
5
+ const { colors } = useSnapTheme();
6
+ const title = String(props.title ?? "");
7
+ const description = props.description
8
+ ? String(props.description)
9
+ : undefined;
10
+ const variant = String(props.variant ?? "default");
11
+ const containerVariant = { paddingVertical: 6, paddingHorizontal: 10 };
12
+ return (_jsxs(View, { style: [styles.container, containerVariant], children: [_jsxs(View, { style: styles.content, children: [title ? _jsx(Text, { style: [styles.title, { color: colors.text }], children: title }) : null, description ? (_jsx(Text, { style: [styles.description, { color: colors.textSecondary }], children: description })) : null] }), children ? (_jsx(View, { style: styles.actions, children: _jsx(View, { style: { flex: 0 }, children: children }) })) : null] }));
13
+ }
14
+ const styles = StyleSheet.create({
15
+ container: {
16
+ flexDirection: "row",
17
+ alignItems: "center",
18
+ },
19
+ content: {
20
+ flex: 1,
21
+ },
22
+ title: {
23
+ fontSize: 15,
24
+ lineHeight: 20,
25
+ fontWeight: "500",
26
+ },
27
+ description: {
28
+ fontSize: 13,
29
+ lineHeight: 18,
30
+ marginTop: 1,
31
+ },
32
+ actions: {
33
+ marginLeft: "auto",
34
+ paddingLeft: 12,
35
+ flexDirection: "row",
36
+ alignItems: "center",
37
+ flexShrink: 0,
38
+ flexGrow: 0,
39
+ flexBasis: "auto",
40
+ gap: 4,
41
+ },
42
+ });
@@ -0,0 +1,2 @@
1
+ import type { ComponentRenderProps } from "@json-render/react-native";
2
+ export declare function SnapProgress({ element: { props }, }: ComponentRenderProps<Record<string, unknown>>): import("react").JSX.Element;
@@ -0,0 +1,26 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { StyleSheet, Text, View } from "react-native";
3
+ import { useSnapPalette } from "../use-snap-palette.js";
4
+ import { useSnapTheme } from "../theme.js";
5
+ export function SnapProgress({ element: { props }, }) {
6
+ const { accentHex } = useSnapPalette();
7
+ const { colors } = useSnapTheme();
8
+ const value = Number(props.value ?? 0);
9
+ const max = Math.max(1, Number(props.max ?? 100));
10
+ const percent = Math.min(100, Math.max(0, (value / max) * 100));
11
+ const label = props.label != null ? String(props.label) : null;
12
+ return (_jsxs(View, { style: styles.wrap, children: [label ? (_jsx(Text, { style: [styles.label, { color: colors.textSecondary }], children: label })) : null, _jsx(View, { style: [styles.track, { backgroundColor: colors.muted }], children: _jsx(View, { style: [styles.fill, { width: `${percent}%`, backgroundColor: accentHex }] }) })] }));
13
+ }
14
+ const styles = StyleSheet.create({
15
+ wrap: { width: "100%", gap: 4 },
16
+ label: { fontSize: 13, lineHeight: 18 },
17
+ track: {
18
+ height: 10,
19
+ borderRadius: 9999,
20
+ overflow: "hidden",
21
+ },
22
+ fill: {
23
+ height: "100%",
24
+ borderRadius: 9999,
25
+ },
26
+ });
@@ -0,0 +1,2 @@
1
+ import type { ComponentRenderProps } from "@json-render/react-native";
2
+ export declare function SnapSeparator({ element: { props }, }: ComponentRenderProps<Record<string, unknown>>): import("react").JSX.Element;
@@ -0,0 +1,23 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { StyleSheet, View } from "react-native";
3
+ import { useSnapTheme } from "../theme.js";
4
+ export function SnapSeparator({ element: { props }, }) {
5
+ const { colors } = useSnapTheme();
6
+ const orientation = String(props.orientation ?? "horizontal");
7
+ const isVertical = orientation === "vertical";
8
+ return (_jsx(View, { style: [
9
+ isVertical ? styles.vertical : styles.horizontal,
10
+ { backgroundColor: colors.border + "80" },
11
+ ] }));
12
+ }
13
+ const styles = StyleSheet.create({
14
+ horizontal: {
15
+ width: "100%",
16
+ height: 1,
17
+ },
18
+ vertical: {
19
+ height: "100%",
20
+ width: 1,
21
+ alignSelf: "stretch",
22
+ },
23
+ });
@@ -0,0 +1,2 @@
1
+ import type { ComponentRenderProps } from "@json-render/react-native";
2
+ export declare function SnapSlider({ element: { props }, }: ComponentRenderProps<Record<string, unknown>>): import("react").JSX.Element;
@@ -0,0 +1,43 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useStateStore } from "@json-render/react-native";
3
+ import Slider from "@react-native-community/slider";
4
+ import { StyleSheet, Text, View } from "react-native";
5
+ import { useSnapPalette } from "../use-snap-palette.js";
6
+ import { useSnapTheme } from "../theme.js";
7
+ export function SnapSlider({ element: { props }, }) {
8
+ const { get, set } = useStateStore();
9
+ const { accentHex } = useSnapPalette();
10
+ const { colors } = useSnapTheme();
11
+ const name = String(props.name ?? "slider");
12
+ const path = `/inputs/${name}`;
13
+ const min = Number(props.min ?? 0);
14
+ const max = Number(props.max ?? 100);
15
+ const step = props.step != null ? Number(props.step) : 1;
16
+ const fallback = props.defaultValue != null ? Number(props.defaultValue) : (min + max) / 2;
17
+ const raw = get(path);
18
+ const value = raw === undefined || raw === null ? fallback : Number(raw);
19
+ const clamped = Number.isFinite(value)
20
+ ? Math.min(max, Math.max(min, value))
21
+ : fallback;
22
+ const label = props.label != null ? String(props.label) : null;
23
+ const showValue = props.showValue === true;
24
+ const minLabel = props.minLabel != null ? String(props.minLabel) : null;
25
+ const maxLabel = props.maxLabel != null ? String(props.maxLabel) : null;
26
+ return (_jsxs(View, { style: styles.wrap, children: [label ? (_jsxs(View, { style: styles.labelRow, children: [_jsx(Text, { style: [styles.label, { color: colors.text }], children: label }), showValue && (_jsx(Text, { style: [styles.valueText, { color: colors.textSecondary }], children: String(Math.round(clamped)) }))] })) : null, _jsx(Slider, { style: styles.slider, minimumValue: min, maximumValue: max, step: step > 0 ? step : 1, value: clamped, onValueChange: (v) => set(path, v), minimumTrackTintColor: accentHex, maximumTrackTintColor: colors.muted, thumbTintColor: accentHex }), minLabel != null || maxLabel != null ? (_jsxs(View, { style: styles.minMaxRow, children: [_jsx(Text, { style: [styles.minMax, { color: colors.textSecondary }], children: minLabel ?? String(min) }), _jsx(Text, { style: [styles.minMax, { color: colors.textSecondary }], children: maxLabel ?? String(max) })] })) : null] }));
27
+ }
28
+ const styles = StyleSheet.create({
29
+ wrap: { width: "100%", gap: 2 },
30
+ labelRow: {
31
+ flexDirection: "row",
32
+ justifyContent: "space-between",
33
+ alignItems: "center",
34
+ },
35
+ label: { fontSize: 13, lineHeight: 18, fontWeight: "500", flex: 1 },
36
+ valueText: { fontSize: 13, lineHeight: 18 },
37
+ slider: { width: "100%", height: 40 },
38
+ minMaxRow: {
39
+ flexDirection: "row",
40
+ justifyContent: "space-between",
41
+ },
42
+ minMax: { fontSize: 12, lineHeight: 16 },
43
+ });
@@ -0,0 +1,5 @@
1
+ import type { ComponentRenderProps } from "@json-render/react-native";
2
+ import type { ReactNode } from "react";
3
+ export declare function SnapStack({ element: { props }, children, }: ComponentRenderProps<Record<string, unknown>> & {
4
+ children?: ReactNode;
5
+ }): import("react").JSX.Element;
@@ -0,0 +1,49 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { StyleSheet, View } from "react-native";
3
+ const VGAP = {
4
+ none: 0,
5
+ sm: 8,
6
+ md: 16,
7
+ lg: 24,
8
+ };
9
+ const HGAP = {
10
+ none: 0,
11
+ sm: 4,
12
+ md: 8,
13
+ lg: 12,
14
+ };
15
+ const JUSTIFY = {
16
+ start: "flex-start",
17
+ center: "center",
18
+ end: "flex-end",
19
+ between: "space-between",
20
+ around: "space-around",
21
+ };
22
+ export function SnapStack({ element: { props }, children, }) {
23
+ const direction = String(props.direction ?? "vertical");
24
+ const rawGap = props.gap;
25
+ const isHorizontal = direction === "horizontal";
26
+ const gapMap = isHorizontal ? HGAP : VGAP;
27
+ const gap = typeof rawGap === "number"
28
+ ? rawGap
29
+ : typeof rawGap === "string" && rawGap in gapMap
30
+ ? gapMap[rawGap]
31
+ : isHorizontal ? HGAP.md : VGAP.md;
32
+ const justify = props.justify ? JUSTIFY[String(props.justify)] : undefined;
33
+ return (_jsx(View, { style: [
34
+ styles.stack,
35
+ isHorizontal ? styles.horizontal : undefined,
36
+ { gap },
37
+ justify ? { justifyContent: justify } : undefined,
38
+ ], children: children }));
39
+ }
40
+ const styles = StyleSheet.create({
41
+ stack: {
42
+ width: "100%",
43
+ },
44
+ horizontal: {
45
+ flexDirection: "row",
46
+ alignItems: "center",
47
+ flexWrap: "wrap",
48
+ },
49
+ });
@@ -0,0 +1,2 @@
1
+ import type { ComponentRenderProps } from "@json-render/react-native";
2
+ export declare function SnapSwitch({ element: { props }, }: ComponentRenderProps<Record<string, unknown>>): import("react").JSX.Element;
@@ -0,0 +1,31 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useStateStore } from "@json-render/react-native";
3
+ import { StyleSheet, Switch, Text, View } from "react-native";
4
+ import { useSnapPalette } from "../use-snap-palette.js";
5
+ import { useSnapTheme } from "../theme.js";
6
+ export function SnapSwitch({ element: { props }, }) {
7
+ const { get, set } = useStateStore();
8
+ const { accentHex } = useSnapPalette();
9
+ const { colors } = useSnapTheme();
10
+ const name = String(props.name ?? "switch");
11
+ const path = `/inputs/${name}`;
12
+ const label = props.label ? String(props.label) : undefined;
13
+ const fallback = Boolean(props.defaultChecked ?? false);
14
+ const raw = get(path);
15
+ const checked = raw === undefined || raw === null ? fallback : Boolean(raw);
16
+ return (_jsxs(View, { style: styles.row, children: [label ? _jsx(Text, { style: [styles.label, { color: colors.text }], children: label }) : null, _jsx(Switch, { value: checked, onValueChange: (v) => set(path, v), trackColor: { false: colors.muted, true: accentHex }, thumbColor: "#fff" })] }));
17
+ }
18
+ const styles = StyleSheet.create({
19
+ row: {
20
+ flexDirection: "row",
21
+ alignItems: "center",
22
+ justifyContent: "space-between",
23
+ gap: 12,
24
+ },
25
+ label: {
26
+ fontSize: 14,
27
+ lineHeight: 18,
28
+ fontWeight: "400",
29
+ flex: 1,
30
+ },
31
+ });
@@ -0,0 +1,2 @@
1
+ import type { ComponentRenderProps } from "@json-render/react-native";
2
+ export declare function SnapText({ element: { props }, }: ComponentRenderProps<Record<string, unknown>>): import("react").JSX.Element;
@@ -0,0 +1,35 @@
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
+ md: { fontSize: 16, lineHeight: 24 },
6
+ sm: { fontSize: 13, lineHeight: 18 },
7
+ };
8
+ const WEIGHT_MAP = {
9
+ bold: "700",
10
+ normal: "400",
11
+ };
12
+ export function SnapText({ element: { props }, }) {
13
+ const { colors } = useSnapTheme();
14
+ const content = String(props.content ?? "");
15
+ const size = String(props.size ?? "md");
16
+ const weight = props.weight ? String(props.weight) : undefined;
17
+ const align = props.align ?? undefined;
18
+ const sizeStyle = SIZE_STYLES[size] ?? SIZE_STYLES.md;
19
+ const resolvedWeight = weight ? WEIGHT_MAP[weight] : sizeStyle?.fontWeight;
20
+ const textAlign = align === "center" ? "center" : align === "right" ? "right" : "left";
21
+ return (_jsx(View, { style: styles.wrap, children: _jsx(Text, { style: [
22
+ styles.base,
23
+ {
24
+ color: colors.text,
25
+ fontSize: sizeStyle.fontSize,
26
+ lineHeight: sizeStyle.lineHeight,
27
+ fontWeight: resolvedWeight,
28
+ textAlign,
29
+ },
30
+ ], children: content }) }));
31
+ }
32
+ const styles = StyleSheet.create({
33
+ wrap: { width: "100%" },
34
+ base: {},
35
+ });
@@ -0,0 +1,2 @@
1
+ import type { ComponentRenderProps } from "@json-render/react-native";
2
+ export declare function SnapToggleGroup({ element: { props }, }: ComponentRenderProps<Record<string, unknown>>): import("react").JSX.Element;
@@ -0,0 +1,99 @@
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 { useSnapTheme } from "../theme.js";
5
+ export function SnapToggleGroup({ element: { props }, }) {
6
+ const { get, set } = useStateStore();
7
+ const { colors } = useSnapTheme();
8
+ const name = String(props.name ?? "toggle_group");
9
+ const path = `/inputs/${name}`;
10
+ const label = props.label ? String(props.label) : undefined;
11
+ const isMultiple = Boolean(props.multiple);
12
+ const orientation = String(props.orientation ?? "horizontal");
13
+ const options = Array.isArray(props.options)
14
+ ? props.options
15
+ : [];
16
+ const raw = get(path);
17
+ const defaultValue = props.defaultValue;
18
+ const selected = (() => {
19
+ if (raw !== undefined && raw !== null) {
20
+ return isMultiple
21
+ ? Array.isArray(raw)
22
+ ? raw
23
+ : []
24
+ : typeof raw === "string"
25
+ ? [raw]
26
+ : [];
27
+ }
28
+ if (defaultValue !== undefined) {
29
+ return Array.isArray(defaultValue)
30
+ ? defaultValue
31
+ : [String(defaultValue)];
32
+ }
33
+ return [];
34
+ })();
35
+ const isVertical = orientation === "vertical";
36
+ const handlePress = (opt) => {
37
+ if (isMultiple) {
38
+ const next = selected.includes(opt)
39
+ ? selected.filter((s) => s !== opt)
40
+ : [...selected, opt];
41
+ set(path, next);
42
+ }
43
+ else {
44
+ if (opt && opt !== selected[0])
45
+ set(path, opt);
46
+ }
47
+ };
48
+ return (_jsxs(View, { style: styles.wrap, children: [label ? _jsx(Text, { style: [styles.label, { color: colors.text }], children: label }) : null, _jsx(View, { style: [
49
+ styles.group,
50
+ { backgroundColor: colors.muted },
51
+ isVertical ? styles.groupVertical : styles.groupHorizontal,
52
+ ], children: options.map((opt, index) => {
53
+ const isSelected = selected.includes(opt);
54
+ return (_jsx(Pressable, { style: ({ pressed }) => [
55
+ styles.option,
56
+ {
57
+ backgroundColor: isSelected
58
+ ? colors.mutedSelected
59
+ : pressed
60
+ ? colors.mutedHover
61
+ : colors.mutedSubtle,
62
+ },
63
+ !isVertical && styles.optionHorizontal,
64
+ ], onPress: () => handlePress(opt), children: _jsx(Text, { style: [
65
+ styles.optionText,
66
+ { color: colors.text },
67
+ ], children: opt }) }, index));
68
+ }) })] }));
69
+ }
70
+ const styles = StyleSheet.create({
71
+ wrap: { width: "100%", gap: 6 },
72
+ label: { fontSize: 13, lineHeight: 18, fontWeight: "500" },
73
+ group: {
74
+ padding: 4,
75
+ borderRadius: 8,
76
+ gap: 4,
77
+ },
78
+ groupHorizontal: {
79
+ flexDirection: "row",
80
+ },
81
+ groupVertical: {
82
+ flexDirection: "column",
83
+ },
84
+ option: {
85
+ paddingVertical: 8,
86
+ paddingHorizontal: 12,
87
+ borderRadius: 6,
88
+ alignItems: "center",
89
+ justifyContent: "center",
90
+ },
91
+ optionHorizontal: {
92
+ flex: 1,
93
+ },
94
+ optionText: {
95
+ fontSize: 13,
96
+ lineHeight: 18,
97
+ fontWeight: "500",
98
+ },
99
+ });
@@ -0,0 +1 @@
1
+ export declare function ConfettiOverlay(): import("react").JSX.Element;