@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
package/dist/dataStore.d.ts
CHANGED
|
@@ -1,12 +1,9 @@
|
|
|
1
1
|
export type DataStoreValue = string | number | boolean | null | DataStoreValue[] | {
|
|
2
2
|
[key: string]: DataStoreValue;
|
|
3
3
|
};
|
|
4
|
-
export type
|
|
4
|
+
export type SnapDataStore = {
|
|
5
5
|
get(key: string): Promise<DataStoreValue | null>;
|
|
6
6
|
set(key: string, value: DataStoreValue): Promise<void>;
|
|
7
7
|
};
|
|
8
|
-
export type SnapDataStore = SnapDataStoreOperations & {
|
|
9
|
-
withLock<T>(fn: (store: SnapDataStoreOperations) => Promise<T>): Promise<T>;
|
|
10
|
-
};
|
|
11
8
|
export declare function createDefaultDataStore(): SnapDataStore;
|
|
12
9
|
export declare function createInMemoryDataStore(): SnapDataStore;
|
package/dist/dataStore.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export function createDefaultDataStore() {
|
|
2
|
-
const err = new Error("Data store is not configured. Use
|
|
2
|
+
const err = new Error("Data store is not configured. Use withTursoServerless() from @farcaster/snap-turso or provide a data store implementation.");
|
|
3
3
|
return {
|
|
4
4
|
get(_key) {
|
|
5
5
|
return Promise.reject(err);
|
|
@@ -7,29 +7,16 @@ export function createDefaultDataStore() {
|
|
|
7
7
|
set(_key, _value) {
|
|
8
8
|
return Promise.reject(err);
|
|
9
9
|
},
|
|
10
|
-
withLock(_fn) {
|
|
11
|
-
return Promise.reject(err);
|
|
12
|
-
},
|
|
13
10
|
};
|
|
14
11
|
}
|
|
15
12
|
export function createInMemoryDataStore() {
|
|
16
13
|
const data = new Map();
|
|
17
|
-
|
|
18
|
-
|
|
14
|
+
return {
|
|
15
|
+
async get(key) {
|
|
19
16
|
return data.get(key) ?? null;
|
|
20
17
|
},
|
|
21
|
-
|
|
18
|
+
async set(key, value) {
|
|
22
19
|
data.set(key, value);
|
|
23
20
|
},
|
|
24
21
|
};
|
|
25
|
-
/** Serializes `withLock` callbacks so async work does not interleave across callers. */
|
|
26
|
-
let lockChain = Promise.resolve();
|
|
27
|
-
return {
|
|
28
|
-
...ops,
|
|
29
|
-
withLock(fn) {
|
|
30
|
-
const run = lockChain.then(() => fn(ops));
|
|
31
|
-
lockChain = run.then(() => undefined, () => undefined);
|
|
32
|
-
return run;
|
|
33
|
-
},
|
|
34
|
-
};
|
|
35
22
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -3,5 +3,5 @@ export { SPEC_VERSION, MEDIA_TYPE, EFFECT_VALUES, } from "./constants.js";
|
|
|
3
3
|
export { DEFAULT_THEME_ACCENT, PALETTE_COLOR, PALETTE_COLOR_ACCENT, PALETTE_COLOR_VALUES, PALETTE_LIGHT_HEX, PALETTE_DARK_HEX, type PaletteColor, } from "./colors.js";
|
|
4
4
|
export { ACTION_TYPE_GET, ACTION_TYPE_POST, snapResponseSchema, payloadSchema, type SnapAction, type SnapContext, type SnapResponse, type SnapHandlerResult, type SnapFunction, type SnapPayload, } from "./schemas.js";
|
|
5
5
|
export { validateSnapResponse, type ValidationResult, } from "./validator.js";
|
|
6
|
-
export { type DataStoreValue, type SnapDataStore,
|
|
6
|
+
export { type DataStoreValue, type SnapDataStore, createDefaultDataStore, createInMemoryDataStore, } from "./dataStore.js";
|
|
7
7
|
export { type Middleware, useMiddleware } from "./middleware.js";
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { createRenderer } from "@json-render/react-native";
|
|
2
|
+
import { snapJsonRenderCatalog } from "@farcaster/snap/ui";
|
|
3
|
+
import { SnapActionButton } from "./components/snap-action-button.js";
|
|
4
|
+
import { SnapBadge } from "./components/snap-badge.js";
|
|
5
|
+
import { SnapIcon } from "./components/snap-icon.js";
|
|
6
|
+
import { SnapImage } from "./components/snap-image.js";
|
|
7
|
+
import { SnapInput } from "./components/snap-input.js";
|
|
8
|
+
import { SnapItem } from "./components/snap-item.js";
|
|
9
|
+
import { SnapItemGroup } from "./components/snap-item-group.js";
|
|
10
|
+
import { SnapProgress } from "./components/snap-progress.js";
|
|
11
|
+
import { SnapSeparator } from "./components/snap-separator.js";
|
|
12
|
+
import { SnapSlider } from "./components/snap-slider.js";
|
|
13
|
+
import { SnapStack } from "./components/snap-stack.js";
|
|
14
|
+
import { SnapSwitch } from "./components/snap-switch.js";
|
|
15
|
+
import { SnapText } from "./components/snap-text.js";
|
|
16
|
+
import { SnapToggleGroup } from "./components/snap-toggle-group.js";
|
|
17
|
+
/**
|
|
18
|
+
* Maps snap json-render catalog types to React Native primitives.
|
|
19
|
+
* Keys match the snap wire-format `type` strings exactly (snake_case).
|
|
20
|
+
*/
|
|
21
|
+
export const SnapCatalogView = createRenderer(snapJsonRenderCatalog, {
|
|
22
|
+
badge: SnapBadge,
|
|
23
|
+
button: SnapActionButton,
|
|
24
|
+
icon: SnapIcon,
|
|
25
|
+
image: SnapImage,
|
|
26
|
+
input: SnapInput,
|
|
27
|
+
item: SnapItem,
|
|
28
|
+
item_group: SnapItemGroup,
|
|
29
|
+
progress: SnapProgress,
|
|
30
|
+
separator: SnapSeparator,
|
|
31
|
+
slider: SnapSlider,
|
|
32
|
+
stack: SnapStack,
|
|
33
|
+
switch: SnapSwitch,
|
|
34
|
+
text: SnapText,
|
|
35
|
+
toggle_group: SnapToggleGroup,
|
|
36
|
+
});
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Pressable, StyleSheet, Text, View } from "react-native";
|
|
3
|
+
import { useSnapPalette } from "../use-snap-palette.js";
|
|
4
|
+
import { useSnapTheme } from "../theme.js";
|
|
5
|
+
import { ICON_MAP } from "./snap-icon.js";
|
|
6
|
+
const VARIANT_MAP = {
|
|
7
|
+
default: "default",
|
|
8
|
+
secondary: "secondary",
|
|
9
|
+
outline: "outline",
|
|
10
|
+
ghost: "ghost",
|
|
11
|
+
};
|
|
12
|
+
export function SnapActionButton({ element: { props }, emit, }) {
|
|
13
|
+
const { accentHex } = useSnapPalette();
|
|
14
|
+
const { colors } = useSnapTheme();
|
|
15
|
+
const label = String(props.label ?? "Action");
|
|
16
|
+
const variant = VARIANT_MAP[String(props.variant ?? "default")] ?? "default";
|
|
17
|
+
const iconName = props.icon ? String(props.icon) : undefined;
|
|
18
|
+
const variantStyle = (() => {
|
|
19
|
+
switch (variant) {
|
|
20
|
+
case "default":
|
|
21
|
+
return { backgroundColor: accentHex };
|
|
22
|
+
case "secondary":
|
|
23
|
+
return { backgroundColor: "transparent", borderWidth: 1.5, borderColor: accentHex };
|
|
24
|
+
case "outline":
|
|
25
|
+
return { backgroundColor: "rgba(255,255,255,0.04)", borderWidth: 1, borderColor: colors.border };
|
|
26
|
+
case "ghost":
|
|
27
|
+
return { backgroundColor: "transparent" };
|
|
28
|
+
}
|
|
29
|
+
})();
|
|
30
|
+
const textColor = variant === "default" ? "#fff" : variant === "secondary" ? accentHex : colors.text;
|
|
31
|
+
const iconColor = variant === "default" ? "#fff" : variant === "secondary" ? accentHex : colors.text;
|
|
32
|
+
return (_jsx(View, { style: styles.outer, children: _jsxs(Pressable, { style: ({ pressed }) => [
|
|
33
|
+
styles.btn,
|
|
34
|
+
variant === "default" ? styles.btnDefault : styles.btnOther,
|
|
35
|
+
variantStyle,
|
|
36
|
+
pressed && styles.pressed,
|
|
37
|
+
], onPress: () => {
|
|
38
|
+
void (async () => {
|
|
39
|
+
try {
|
|
40
|
+
await emit("press");
|
|
41
|
+
}
|
|
42
|
+
catch (err) {
|
|
43
|
+
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
44
|
+
// eslint-disable-next-line no-console
|
|
45
|
+
console.error("[snap] action failed", err);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
})();
|
|
49
|
+
}, children: [iconName && ICON_MAP[iconName] ? ((() => { const I = ICON_MAP[iconName]; return _jsx(I, { size: 16, color: iconColor }); })()) : null, _jsx(Text, { style: { color: textColor, fontSize: 14, fontWeight: "600" }, children: label })] }) }));
|
|
50
|
+
}
|
|
51
|
+
const styles = StyleSheet.create({
|
|
52
|
+
outer: { flex: 1, minWidth: 0 },
|
|
53
|
+
btn: {
|
|
54
|
+
paddingHorizontal: 16,
|
|
55
|
+
borderRadius: 10,
|
|
56
|
+
alignItems: "center",
|
|
57
|
+
justifyContent: "center",
|
|
58
|
+
flexDirection: "row",
|
|
59
|
+
gap: 8,
|
|
60
|
+
},
|
|
61
|
+
btnDefault: {
|
|
62
|
+
paddingVertical: 10,
|
|
63
|
+
},
|
|
64
|
+
btnOther: {
|
|
65
|
+
paddingVertical: 8,
|
|
66
|
+
},
|
|
67
|
+
pressed: { opacity: 0.88 },
|
|
68
|
+
});
|
|
@@ -0,0 +1,38 @@
|
|
|
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 { ICON_MAP } from "./snap-icon.js";
|
|
5
|
+
export function SnapBadge({ element: { props }, }) {
|
|
6
|
+
const { accentHex, hex } = useSnapPalette();
|
|
7
|
+
const label = String(props.label ?? "");
|
|
8
|
+
const color = props.color ? String(props.color) : undefined;
|
|
9
|
+
const iconName = props.icon ? String(props.icon) : undefined;
|
|
10
|
+
const isAccent = !color || color === "accent";
|
|
11
|
+
const resolvedColor = isAccent ? accentHex : hex(color);
|
|
12
|
+
const Icon = iconName ? ICON_MAP[iconName] : undefined;
|
|
13
|
+
return (_jsxs(View, { style: [
|
|
14
|
+
styles.badge,
|
|
15
|
+
isAccent
|
|
16
|
+
? { backgroundColor: resolvedColor, borderColor: resolvedColor }
|
|
17
|
+
: { borderColor: resolvedColor },
|
|
18
|
+
], children: [Icon && (_jsx(Icon, { size: 12, color: isAccent ? "#fff" : resolvedColor })), _jsx(Text, { style: [
|
|
19
|
+
styles.label,
|
|
20
|
+
{ color: isAccent ? "#fff" : resolvedColor },
|
|
21
|
+
], children: label })] }));
|
|
22
|
+
}
|
|
23
|
+
const styles = StyleSheet.create({
|
|
24
|
+
badge: {
|
|
25
|
+
alignSelf: "flex-start",
|
|
26
|
+
flexDirection: "row",
|
|
27
|
+
alignItems: "center",
|
|
28
|
+
gap: 4,
|
|
29
|
+
paddingHorizontal: 8,
|
|
30
|
+
paddingVertical: 2,
|
|
31
|
+
borderRadius: 9999,
|
|
32
|
+
borderWidth: 1,
|
|
33
|
+
},
|
|
34
|
+
label: {
|
|
35
|
+
fontSize: 12,
|
|
36
|
+
fontWeight: "500",
|
|
37
|
+
},
|
|
38
|
+
});
|
|
@@ -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,24 @@
|
|
|
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
|
+
flex: 1,
|
|
19
|
+
width: "100%",
|
|
20
|
+
borderRadius: 8,
|
|
21
|
+
overflow: "hidden",
|
|
22
|
+
backgroundColor: "#f3f4f6",
|
|
23
|
+
},
|
|
24
|
+
});
|
|
@@ -0,0 +1,36 @@
|
|
|
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, fontWeight: "500" },
|
|
29
|
+
input: {
|
|
30
|
+
borderWidth: 1,
|
|
31
|
+
borderRadius: 8,
|
|
32
|
+
paddingHorizontal: 12,
|
|
33
|
+
paddingVertical: 10,
|
|
34
|
+
fontSize: 14,
|
|
35
|
+
},
|
|
36
|
+
});
|
|
@@ -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,45 @@
|
|
|
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 = variant === "outline"
|
|
12
|
+
? { borderWidth: 1, borderColor: colors.border + "80", borderRadius: 8, padding: 10 }
|
|
13
|
+
: variant === "muted"
|
|
14
|
+
? { backgroundColor: "rgba(255,255,255,0.04)", borderRadius: 8, padding: 10 }
|
|
15
|
+
: { paddingVertical: 8, paddingHorizontal: 10 };
|
|
16
|
+
return (_jsxs(View, { style: [styles.container, containerVariant], children: [_jsxs(View, { style: styles.content, children: [_jsx(Text, { style: [styles.title, { color: colors.text }], children: title }), 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] }));
|
|
17
|
+
}
|
|
18
|
+
const styles = StyleSheet.create({
|
|
19
|
+
container: {
|
|
20
|
+
flex: 1,
|
|
21
|
+
flexDirection: "row",
|
|
22
|
+
alignItems: "center",
|
|
23
|
+
},
|
|
24
|
+
content: {
|
|
25
|
+
flex: 1,
|
|
26
|
+
},
|
|
27
|
+
title: {
|
|
28
|
+
fontSize: 15,
|
|
29
|
+
fontWeight: "500",
|
|
30
|
+
},
|
|
31
|
+
description: {
|
|
32
|
+
fontSize: 13,
|
|
33
|
+
marginTop: 1,
|
|
34
|
+
},
|
|
35
|
+
actions: {
|
|
36
|
+
marginLeft: "auto",
|
|
37
|
+
paddingLeft: 12,
|
|
38
|
+
flexDirection: "row",
|
|
39
|
+
alignItems: "center",
|
|
40
|
+
flexShrink: 0,
|
|
41
|
+
flexGrow: 0,
|
|
42
|
+
flexBasis: "auto",
|
|
43
|
+
gap: 4,
|
|
44
|
+
},
|
|
45
|
+
});
|
|
@@ -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: { flex: 1, width: "100%", gap: 4 },
|
|
16
|
+
label: { fontSize: 13 },
|
|
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,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,42 @@
|
|
|
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 minLabel = props.minLabel != null ? String(props.minLabel) : null;
|
|
24
|
+
const maxLabel = props.maxLabel != null ? String(props.maxLabel) : null;
|
|
25
|
+
return (_jsxs(View, { style: styles.wrap, children: [label ? (_jsxs(View, { style: styles.labelRow, children: [_jsx(Text, { style: [styles.label, { color: colors.text }], children: label }), _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] }));
|
|
26
|
+
}
|
|
27
|
+
const styles = StyleSheet.create({
|
|
28
|
+
wrap: { width: "100%", gap: 6 },
|
|
29
|
+
labelRow: {
|
|
30
|
+
flexDirection: "row",
|
|
31
|
+
justifyContent: "space-between",
|
|
32
|
+
alignItems: "center",
|
|
33
|
+
},
|
|
34
|
+
label: { fontSize: 13, fontWeight: "500", flex: 1 },
|
|
35
|
+
valueText: { fontSize: 13 },
|
|
36
|
+
slider: { width: "100%", height: 40 },
|
|
37
|
+
minMaxRow: {
|
|
38
|
+
flexDirection: "row",
|
|
39
|
+
justifyContent: "space-between",
|
|
40
|
+
},
|
|
41
|
+
minMax: { fontSize: 12 },
|
|
42
|
+
});
|
|
@@ -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,30 @@
|
|
|
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.border, 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
|
+
fontWeight: "400",
|
|
28
|
+
flex: 1,
|
|
29
|
+
},
|
|
30
|
+
});
|