@farcaster/snap 1.13.0 → 1.15.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.
- package/dist/react/components/action-button.js +18 -10
- package/dist/react/components/badge.js +8 -6
- package/dist/react/components/bar-chart.js +12 -17
- package/dist/react/components/cell-grid.js +16 -15
- package/dist/react/components/icon.js +4 -10
- package/dist/react/components/input.js +12 -6
- package/dist/react/components/item-group.js +3 -1
- package/dist/react/components/item.d.ts +3 -3
- package/dist/react/components/item.js +4 -3
- package/dist/react/components/progress.js +3 -3
- package/dist/react/components/separator.js +3 -1
- package/dist/react/components/slider.js +14 -10
- package/dist/react/components/switch.js +10 -12
- package/dist/react/components/text.js +5 -7
- package/dist/react/components/toggle-group.js +20 -6
- package/dist/react/hooks/use-snap-colors.d.ts +38 -0
- package/dist/react/hooks/use-snap-colors.js +82 -0
- package/dist/react/index.js +8 -5
- package/dist/react-native/components/snap-action-button.js +8 -18
- package/dist/react-native/components/snap-cell-grid.js +1 -1
- package/dist/react-native/components/snap-switch.js +1 -1
- package/dist/react-native/components/snap-toggle-group.js +8 -10
- package/dist/react-native/index.js +1 -1
- package/dist/react-native/theme.d.ts +6 -0
- package/dist/react-native/theme.js +12 -6
- package/dist/ui/catalog.d.ts +1 -0
- package/dist/ui/cell-grid.d.ts +1 -0
- package/dist/ui/cell-grid.js +1 -0
- package/llms.txt +1 -0
- package/package.json +1 -1
- package/src/react/components/action-button.tsx +24 -17
- package/src/react/components/badge.tsx +14 -17
- package/src/react/components/bar-chart.tsx +21 -19
- package/src/react/components/cell-grid.tsx +16 -19
- package/src/react/components/icon.tsx +5 -18
- package/src/react/components/input.tsx +20 -9
- package/src/react/components/item-group.tsx +4 -1
- package/src/react/components/item.tsx +13 -10
- package/src/react/components/progress.tsx +12 -7
- package/src/react/components/separator.tsx +8 -1
- package/src/react/components/slider.tsx +18 -15
- package/src/react/components/switch.tsx +12 -16
- package/src/react/components/text.tsx +11 -8
- package/src/react/components/toggle-group.tsx +26 -9
- package/src/react/hooks/use-snap-colors.ts +129 -0
- package/src/react/index.tsx +19 -18
- package/src/react-native/components/snap-action-button.tsx +8 -20
- package/src/react-native/components/snap-cell-grid.tsx +1 -1
- package/src/react-native/components/snap-switch.tsx +1 -1
- package/src/react-native/components/snap-toggle-group.tsx +8 -10
- package/src/react-native/index.tsx +1 -1
- package/src/react-native/theme.tsx +18 -6
- package/src/ui/cell-grid.ts +1 -0
- package/dist/react/hooks/use-snap-accent.d.ts +0 -13
- package/dist/react/hooks/use-snap-accent.js +0 -32
- package/src/react/hooks/use-snap-accent.ts +0 -45
|
@@ -3,30 +3,21 @@ import { Pressable, StyleSheet, Text, View } from "react-native";
|
|
|
3
3
|
import { useSnapPalette } from "../use-snap-palette.js";
|
|
4
4
|
import { useSnapTheme } from "../theme.js";
|
|
5
5
|
import { ICON_MAP } from "./snap-icon.js";
|
|
6
|
-
const VARIANT_MAP = {
|
|
7
|
-
primary: "primary",
|
|
8
|
-
secondary: "secondary",
|
|
9
|
-
};
|
|
10
6
|
export function SnapActionButton({ element: { props }, emit, }) {
|
|
11
7
|
const { accentHex } = useSnapPalette();
|
|
12
8
|
const { colors } = useSnapTheme();
|
|
13
9
|
const label = String(props.label ?? "Action");
|
|
14
|
-
const variant =
|
|
10
|
+
const variant = String(props.variant ?? "secondary");
|
|
11
|
+
const isPrimary = variant === "primary";
|
|
15
12
|
const iconName = props.icon ? String(props.icon) : undefined;
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
case "primary":
|
|
19
|
-
return { backgroundColor: accentHex };
|
|
20
|
-
case "secondary":
|
|
21
|
-
return { backgroundColor: "transparent", borderWidth: 1.5, borderColor: accentHex };
|
|
22
|
-
}
|
|
23
|
-
})();
|
|
24
|
-
const textColor = variant === "primary" ? "#fff" : accentHex;
|
|
25
|
-
const iconColor = variant === "primary" ? "#fff" : accentHex;
|
|
13
|
+
const textColor = isPrimary ? "#fff" : colors.text;
|
|
14
|
+
const iconColor = isPrimary ? "#fff" : colors.text;
|
|
26
15
|
return (_jsx(View, { style: styles.outer, children: _jsxs(Pressable, { style: ({ pressed }) => [
|
|
27
16
|
styles.btn,
|
|
28
|
-
|
|
29
|
-
|
|
17
|
+
isPrimary ? styles.btnDefault : styles.btnOther,
|
|
18
|
+
isPrimary
|
|
19
|
+
? { backgroundColor: pressed ? accentHex + "DD" : accentHex }
|
|
20
|
+
: { backgroundColor: pressed ? colors.mutedHover : colors.muted },
|
|
30
21
|
pressed && styles.pressed,
|
|
31
22
|
], onPress: () => {
|
|
32
23
|
void (async () => {
|
|
@@ -35,7 +26,6 @@ export function SnapActionButton({ element: { props }, emit, }) {
|
|
|
35
26
|
}
|
|
36
27
|
catch (err) {
|
|
37
28
|
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
38
|
-
// eslint-disable-next-line no-console
|
|
39
29
|
console.error("[snap] action failed", err);
|
|
40
30
|
}
|
|
41
31
|
}
|
|
@@ -71,7 +71,7 @@ export function SnapCellGrid({ element: { props }, }) {
|
|
|
71
71
|
const selectionLabel = interactive && selectedSet.size > 0
|
|
72
72
|
? `inputs.${name}: ${[...selectedSet].join(isMultiple ? " | " : "")}`
|
|
73
73
|
: null;
|
|
74
|
-
return (_jsxs(View, { style: [styles.wrap, { gap: gapPx }], children: [rowEls, selectionLabel ? (_jsx(Text, { style: [styles.selectionText, { color: colors.textSecondary }], children: selectionLabel })) : null] }));
|
|
74
|
+
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] }));
|
|
75
75
|
}
|
|
76
76
|
const styles = StyleSheet.create({
|
|
77
77
|
wrap: { width: "100%" },
|
|
@@ -13,7 +13,7 @@ export function SnapSwitch({ element: { props }, }) {
|
|
|
13
13
|
const fallback = Boolean(props.defaultChecked ?? false);
|
|
14
14
|
const raw = get(path);
|
|
15
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.
|
|
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
17
|
}
|
|
18
18
|
const styles = StyleSheet.create({
|
|
19
19
|
row: {
|
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useStateStore } from "@json-render/react-native";
|
|
3
3
|
import { Pressable, StyleSheet, Text, View } from "react-native";
|
|
4
|
-
import { useSnapPalette } from "../use-snap-palette.js";
|
|
5
4
|
import { useSnapTheme } from "../theme.js";
|
|
6
5
|
export function SnapToggleGroup({ element: { props }, }) {
|
|
7
6
|
const { get, set } = useStateStore();
|
|
8
|
-
const { accentHex } = useSnapPalette();
|
|
9
7
|
const { colors } = useSnapTheme();
|
|
10
8
|
const name = String(props.name ?? "toggle_group");
|
|
11
9
|
const path = `/inputs/${name}`;
|
|
@@ -49,19 +47,23 @@ export function SnapToggleGroup({ element: { props }, }) {
|
|
|
49
47
|
};
|
|
50
48
|
return (_jsxs(View, { style: styles.wrap, children: [label ? _jsx(Text, { style: [styles.label, { color: colors.text }], children: label }) : null, _jsx(View, { style: [
|
|
51
49
|
styles.group,
|
|
52
|
-
{ backgroundColor: colors.
|
|
50
|
+
{ backgroundColor: colors.muted },
|
|
53
51
|
isVertical ? styles.groupVertical : styles.groupHorizontal,
|
|
54
52
|
], children: options.map((opt, index) => {
|
|
55
53
|
const isSelected = selected.includes(opt);
|
|
56
54
|
return (_jsx(Pressable, { style: ({ pressed }) => [
|
|
57
55
|
styles.option,
|
|
58
|
-
|
|
59
|
-
|
|
56
|
+
{
|
|
57
|
+
backgroundColor: isSelected
|
|
58
|
+
? colors.mutedSelected
|
|
59
|
+
: pressed
|
|
60
|
+
? colors.mutedHover
|
|
61
|
+
: colors.mutedSubtle,
|
|
62
|
+
},
|
|
60
63
|
!isVertical && styles.optionHorizontal,
|
|
61
64
|
], onPress: () => handlePress(opt), children: _jsx(Text, { style: [
|
|
62
65
|
styles.optionText,
|
|
63
66
|
{ color: colors.text },
|
|
64
|
-
isSelected && styles.optionTextSelected,
|
|
65
67
|
], children: opt }) }, index));
|
|
66
68
|
}) })] }));
|
|
67
69
|
}
|
|
@@ -89,12 +91,8 @@ const styles = StyleSheet.create({
|
|
|
89
91
|
optionHorizontal: {
|
|
90
92
|
flex: 1,
|
|
91
93
|
},
|
|
92
|
-
pressed: { opacity: 0.88 },
|
|
93
94
|
optionText: {
|
|
94
95
|
fontSize: 13,
|
|
95
96
|
fontWeight: "500",
|
|
96
97
|
},
|
|
97
|
-
optionTextSelected: {
|
|
98
|
-
color: "#fff",
|
|
99
|
-
},
|
|
100
98
|
});
|
|
@@ -133,7 +133,7 @@ function SnapViewInner({ snap, handlers, loading = false, }) {
|
|
|
133
133
|
return (_jsxs(View, { style: styles.container, children: [loading && (_jsx(View, { style: [
|
|
134
134
|
styles.overlay,
|
|
135
135
|
{
|
|
136
|
-
backgroundColor: mode === "dark" ? "rgba(0,0,0,0.
|
|
136
|
+
backgroundColor: mode === "dark" ? "rgba(0,0,0,0.3)" : "rgba(255,255,255,0.5)",
|
|
137
137
|
},
|
|
138
138
|
], children: _jsx(ActivityIndicator, { size: "large", color: accentHex }) })), _jsx(SnapCatalogView, { spec: spec, state: initialState, loading: false, onStateChange: (changes) => {
|
|
139
139
|
applyStatePaths(stateRef.current, changes);
|
|
@@ -7,6 +7,12 @@ export type SnapNativeColors = {
|
|
|
7
7
|
border: string;
|
|
8
8
|
inputBg: string;
|
|
9
9
|
muted: string;
|
|
10
|
+
/** Subtle tint for toggle button resting state */
|
|
11
|
+
mutedSubtle: string;
|
|
12
|
+
/** Slightly stronger tint for hover/press state */
|
|
13
|
+
mutedHover: string;
|
|
14
|
+
/** Stronger tint for selected state (toggle group) */
|
|
15
|
+
mutedSelected: string;
|
|
10
16
|
};
|
|
11
17
|
interface SnapThemeValue {
|
|
12
18
|
mode: "light" | "dark";
|
|
@@ -5,18 +5,24 @@ const DEFAULT_LIGHT = {
|
|
|
5
5
|
surface: "#ffffff",
|
|
6
6
|
text: "#111111",
|
|
7
7
|
textSecondary: "#6b7280",
|
|
8
|
-
border: "#
|
|
9
|
-
inputBg: "
|
|
10
|
-
muted: "
|
|
8
|
+
border: "#E5E7EB",
|
|
9
|
+
inputBg: "rgba(0,0,0,0.12)",
|
|
10
|
+
muted: "rgba(0,0,0,0.12)",
|
|
11
|
+
mutedSubtle: "rgba(0,0,0,0.06)",
|
|
12
|
+
mutedHover: "rgba(0,0,0,0.10)",
|
|
13
|
+
mutedSelected: "rgba(0,0,0,0.18)",
|
|
11
14
|
};
|
|
12
15
|
const DEFAULT_DARK = {
|
|
13
16
|
bg: "#111318",
|
|
14
17
|
surface: "#1a1d24",
|
|
15
18
|
text: "#fafafa",
|
|
16
19
|
textSecondary: "#a1a1aa",
|
|
17
|
-
border: "#
|
|
18
|
-
inputBg: "
|
|
19
|
-
muted: "
|
|
20
|
+
border: "#2D2D44",
|
|
21
|
+
inputBg: "rgba(255,255,255,0.03)",
|
|
22
|
+
muted: "rgba(255,255,255,0.03)",
|
|
23
|
+
mutedSubtle: "rgba(255,255,255,0.02)",
|
|
24
|
+
mutedHover: "rgba(255,255,255,0.04)",
|
|
25
|
+
mutedSelected: "rgba(255,255,255,0.10)",
|
|
20
26
|
};
|
|
21
27
|
const SnapThemeContext = createContext({
|
|
22
28
|
mode: "dark",
|
package/dist/ui/catalog.d.ts
CHANGED
package/dist/ui/cell-grid.d.ts
CHANGED
package/dist/ui/cell-grid.js
CHANGED
|
@@ -14,6 +14,7 @@ export const cellGridProps = z
|
|
|
14
14
|
rows: z.number().int().min(GRID_MIN_ROWS).max(GRID_MAX_ROWS),
|
|
15
15
|
cells: z.array(cellGridCellSchema),
|
|
16
16
|
gap: z.enum(GRID_GAP_VALUES).optional(),
|
|
17
|
+
rowHeight: z.number().int().min(8).max(64).optional(),
|
|
17
18
|
select: z.enum(["off", "single", "multiple"]).optional(),
|
|
18
19
|
})
|
|
19
20
|
.superRefine((val, ctx) => {
|
package/llms.txt
CHANGED
|
@@ -88,6 +88,7 @@ Top-level fields: `version` (required, `"1.0"`), `theme` (optional, `{ accent: P
|
|
|
88
88
|
- `rows` (number, required, 2–16)
|
|
89
89
|
- `cells` (array, required): sparse list of `{ row, col, color?: PaletteColor, content?: string }`
|
|
90
90
|
- `gap` (optional): `"none"` (0px) | `"sm"` (1px) | `"md"` (2px) | `"lg"` (4px). Default: `"sm"`
|
|
91
|
+
- `rowHeight` (number, optional, 8–64): pixel height per row. Default: 28. Grid height = rows × rowHeight
|
|
91
92
|
- `select` (optional): `"off"` | `"single"` | `"multiple"`. Default: `"off"`. Taps write to `inputs[name]`
|
|
92
93
|
|
|
93
94
|
### Container Components
|
package/package.json
CHANGED
|
@@ -1,15 +1,11 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
+
import { useState } from "react";
|
|
3
4
|
import { Button } from "@neynar/ui/button";
|
|
4
5
|
import { cn } from "@neynar/ui/utils";
|
|
5
|
-
import {
|
|
6
|
+
import { useSnapColors } from "../hooks/use-snap-colors";
|
|
6
7
|
import { ICON_MAP } from "./icon";
|
|
7
8
|
|
|
8
|
-
const VARIANT_MAP: Record<string, "default" | "secondary"> = {
|
|
9
|
-
primary: "default",
|
|
10
|
-
secondary: "secondary",
|
|
11
|
-
};
|
|
12
|
-
|
|
13
9
|
export function SnapActionButton({
|
|
14
10
|
element: { props },
|
|
15
11
|
emit,
|
|
@@ -18,25 +14,36 @@ export function SnapActionButton({
|
|
|
18
14
|
emit: (name: string) => void;
|
|
19
15
|
}) {
|
|
20
16
|
const label = String(props.label ?? "Action");
|
|
21
|
-
const variant =
|
|
17
|
+
const variant = String(props.variant ?? "secondary");
|
|
18
|
+
const isPrimary = variant === "primary";
|
|
22
19
|
const iconName = props.icon ? String(props.icon) : undefined;
|
|
23
|
-
const
|
|
20
|
+
const colors = useSnapColors();
|
|
21
|
+
const [hovered, setHovered] = useState(false);
|
|
24
22
|
|
|
25
23
|
const Icon = iconName ? ICON_MAP[iconName] : undefined;
|
|
26
24
|
|
|
25
|
+
const style = isPrimary
|
|
26
|
+
? {
|
|
27
|
+
backgroundColor: hovered ? colors.accentHover : colors.accent,
|
|
28
|
+
color: colors.accentFg,
|
|
29
|
+
borderColor: "transparent",
|
|
30
|
+
}
|
|
31
|
+
: {
|
|
32
|
+
backgroundColor: hovered ? `color-mix(in srgb, ${colors.accent} 15%, transparent)` : colors.muted,
|
|
33
|
+
color: colors.text,
|
|
34
|
+
borderColor: "transparent",
|
|
35
|
+
};
|
|
36
|
+
|
|
27
37
|
return (
|
|
28
|
-
<div className="w-full min-w-0 flex-1"
|
|
38
|
+
<div className="w-full min-w-0 flex-1">
|
|
29
39
|
<Button
|
|
30
40
|
type="button"
|
|
31
|
-
variant={
|
|
32
|
-
className={cn(
|
|
33
|
-
|
|
34
|
-
variant === "default" &&
|
|
35
|
-
"hover:!bg-[var(--snap-action-primary-hover)]",
|
|
36
|
-
variant !== "default" &&
|
|
37
|
-
"hover:!bg-[var(--snap-action-outline-hover)]",
|
|
38
|
-
)}
|
|
41
|
+
variant={isPrimary ? "default" : "secondary"}
|
|
42
|
+
className={cn("w-full gap-2")}
|
|
43
|
+
style={style}
|
|
39
44
|
onClick={() => emit("press")}
|
|
45
|
+
onPointerEnter={() => setHovered(true)}
|
|
46
|
+
onPointerLeave={() => setHovered(false)}
|
|
40
47
|
>
|
|
41
48
|
{Icon && <Icon size={16} />}
|
|
42
49
|
{label}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import { Badge } from "@neynar/ui/badge";
|
|
4
|
-
import {
|
|
4
|
+
import { useSnapColors, pickForegroundForBg } from "../hooks/use-snap-colors";
|
|
5
5
|
import { ICON_MAP } from "./icon";
|
|
6
6
|
|
|
7
7
|
export function SnapBadge({
|
|
@@ -13,25 +13,22 @@ export function SnapBadge({
|
|
|
13
13
|
const variant = String(props.variant ?? "default") as "default" | "outline";
|
|
14
14
|
const color = props.color ? String(props.color) : undefined;
|
|
15
15
|
const iconName = props.icon ? String(props.icon) : undefined;
|
|
16
|
-
const
|
|
16
|
+
const colors = useSnapColors();
|
|
17
|
+
|
|
18
|
+
const badgeColor = colors.colorHex(color);
|
|
19
|
+
const badgeFg = variant === "default" ? pickForegroundForBg(badgeColor) : badgeColor;
|
|
17
20
|
|
|
18
|
-
const isAccent = !color || color === "accent";
|
|
19
21
|
const Icon = iconName ? ICON_MAP[iconName] : undefined;
|
|
20
22
|
|
|
23
|
+
const style =
|
|
24
|
+
variant === "outline"
|
|
25
|
+
? { borderColor: badgeColor, color: badgeColor, backgroundColor: "transparent" }
|
|
26
|
+
: { backgroundColor: badgeColor, color: badgeFg, borderColor: "transparent" };
|
|
27
|
+
|
|
21
28
|
return (
|
|
22
|
-
<
|
|
23
|
-
<
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
style={
|
|
27
|
-
variant === "outline" && !isAccent
|
|
28
|
-
? { borderColor: `var(--snap-color-${color})`, color: `var(--snap-color-${color})` }
|
|
29
|
-
: undefined
|
|
30
|
-
}
|
|
31
|
-
>
|
|
32
|
-
{Icon && <Icon size={12} />}
|
|
33
|
-
{content}
|
|
34
|
-
</Badge>
|
|
35
|
-
</span>
|
|
29
|
+
<Badge variant={variant} className="gap-1" style={style}>
|
|
30
|
+
{Icon && <Icon size={12} />}
|
|
31
|
+
{content}
|
|
32
|
+
</Badge>
|
|
36
33
|
);
|
|
37
34
|
}
|
|
@@ -1,17 +1,15 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import
|
|
4
|
-
import { PALETTE_LIGHT_HEX } from "@farcaster/snap";
|
|
5
|
-
import { useSnapAccentScopeStyle } from "../hooks/use-snap-accent";
|
|
3
|
+
import { useSnapColors } from "../hooks/use-snap-colors";
|
|
6
4
|
|
|
7
5
|
export function SnapBarChart({
|
|
8
6
|
element: { props },
|
|
9
7
|
}: {
|
|
10
8
|
element: { props: Record<string, unknown> };
|
|
11
9
|
}) {
|
|
12
|
-
const
|
|
10
|
+
const colors = useSnapColors();
|
|
13
11
|
const bars = Array.isArray(props.bars) ? props.bars : [];
|
|
14
|
-
const chartColor = String(props.color
|
|
12
|
+
const chartColor = props.color ? String(props.color) : undefined;
|
|
15
13
|
const maxVal =
|
|
16
14
|
props.max != null
|
|
17
15
|
? Number(props.max)
|
|
@@ -20,18 +18,13 @@ export function SnapBarChart({
|
|
|
20
18
|
1,
|
|
21
19
|
);
|
|
22
20
|
|
|
23
|
-
function
|
|
24
|
-
if (bar.color
|
|
25
|
-
|
|
26
|
-
}
|
|
27
|
-
if (chartColor !== "accent" && chartColor in PALETTE_LIGHT_HEX) {
|
|
28
|
-
return `var(--snap-color-${chartColor}, ${PALETTE_LIGHT_HEX[chartColor as PaletteColor]})`;
|
|
29
|
-
}
|
|
30
|
-
return "var(--primary)";
|
|
21
|
+
function barFill(bar: { color?: string }): string {
|
|
22
|
+
if (bar.color) return colors.colorHex(bar.color);
|
|
23
|
+
return colors.colorHex(chartColor);
|
|
31
24
|
}
|
|
32
25
|
|
|
33
26
|
return (
|
|
34
|
-
<div className="flex w-full flex-col gap-2"
|
|
27
|
+
<div className="flex w-full flex-col gap-2">
|
|
35
28
|
{bars.map(
|
|
36
29
|
(
|
|
37
30
|
bar: { label?: string; value?: number; color?: string },
|
|
@@ -39,23 +32,32 @@ export function SnapBarChart({
|
|
|
39
32
|
) => {
|
|
40
33
|
const value = Number(bar.value ?? 0);
|
|
41
34
|
const pct = maxVal > 0 ? Math.min(100, (value / maxVal) * 100) : 0;
|
|
42
|
-
const fill =
|
|
35
|
+
const fill = barFill(bar);
|
|
43
36
|
return (
|
|
44
37
|
<div key={i} className="flex w-full items-center gap-2">
|
|
45
|
-
<span
|
|
38
|
+
<span
|
|
39
|
+
className="w-20 shrink-0 truncate text-right text-xs"
|
|
40
|
+
style={{ color: colors.textMuted }}
|
|
41
|
+
>
|
|
46
42
|
{String(bar.label ?? "")}
|
|
47
43
|
</span>
|
|
48
|
-
<div
|
|
44
|
+
<div
|
|
45
|
+
className="h-2.5 flex-1 overflow-hidden rounded-full"
|
|
46
|
+
style={{ backgroundColor: colors.muted }}
|
|
47
|
+
>
|
|
49
48
|
<div
|
|
50
49
|
className="h-full rounded-full transition-all"
|
|
51
50
|
style={{
|
|
52
51
|
width: `${pct}%`,
|
|
53
52
|
minWidth: pct > 0 ? 4 : 0,
|
|
54
|
-
|
|
53
|
+
backgroundColor: fill,
|
|
55
54
|
}}
|
|
56
55
|
/>
|
|
57
56
|
</div>
|
|
58
|
-
<span
|
|
57
|
+
<span
|
|
58
|
+
className="w-8 shrink-0 text-xs tabular-nums"
|
|
59
|
+
style={{ color: colors.textMuted }}
|
|
60
|
+
>
|
|
59
61
|
{value}
|
|
60
62
|
</span>
|
|
61
63
|
</div>
|
|
@@ -3,10 +3,8 @@
|
|
|
3
3
|
import type { ReactNode } from "react";
|
|
4
4
|
import { useStateStore } from "@json-render/react";
|
|
5
5
|
import { cn } from "@neynar/ui/utils";
|
|
6
|
-
import { POST_GRID_TAP_KEY
|
|
7
|
-
import
|
|
8
|
-
import { useSnapAccentScopeStyle } from "../hooks/use-snap-accent";
|
|
9
|
-
import { useColorMode } from "@neynar/ui/color-mode";
|
|
6
|
+
import { POST_GRID_TAP_KEY } from "@farcaster/snap";
|
|
7
|
+
import { useSnapColors } from "../hooks/use-snap-colors";
|
|
10
8
|
|
|
11
9
|
export function SnapCellGrid({
|
|
12
10
|
element: { props },
|
|
@@ -14,8 +12,7 @@ export function SnapCellGrid({
|
|
|
14
12
|
element: { props: Record<string, unknown> };
|
|
15
13
|
}) {
|
|
16
14
|
const { get, set } = useStateStore();
|
|
17
|
-
const
|
|
18
|
-
const { mode: appearance } = useColorMode();
|
|
15
|
+
const colors = useSnapColors();
|
|
19
16
|
const cols = Number(props.cols ?? 2);
|
|
20
17
|
const rows = Number(props.rows ?? 2);
|
|
21
18
|
const select = String(props.select ?? "off");
|
|
@@ -25,6 +22,7 @@ export function SnapCellGrid({
|
|
|
25
22
|
const gap = String(props.gap ?? "sm");
|
|
26
23
|
const gapMap: Record<string, number> = { none: 0, sm: 1, md: 2, lg: 4 };
|
|
27
24
|
const gapPx = gapMap[gap] ?? 1;
|
|
25
|
+
const rowHeight = typeof props.rowHeight === "number" ? props.rowHeight : 28;
|
|
28
26
|
|
|
29
27
|
const name = props.name ? String(props.name) : POST_GRID_TAP_KEY;
|
|
30
28
|
const tapPath = `/inputs/${name}`;
|
|
@@ -60,17 +58,12 @@ export function SnapCellGrid({
|
|
|
60
58
|
});
|
|
61
59
|
}
|
|
62
60
|
|
|
63
|
-
const ringColor = appearance === "dark" ? "#fff" : "#000";
|
|
64
|
-
|
|
65
61
|
const cellEls: ReactNode[] = [];
|
|
66
62
|
for (let r = 0; r < rows; r++) {
|
|
67
63
|
for (let c = 0; c < cols; c++) {
|
|
68
64
|
const cell = cellMap.get(`${r},${c}`);
|
|
69
65
|
const selected = interactive && isSelected(r, c);
|
|
70
|
-
const bg =
|
|
71
|
-
cell?.color && cell.color in PALETTE_LIGHT_HEX
|
|
72
|
-
? `var(--snap-color-${cell.color}, ${PALETTE_LIGHT_HEX[cell.color as PaletteColor]})`
|
|
73
|
-
: "transparent";
|
|
66
|
+
const bg = cell?.color ? colors.colorHex(cell.color) : "transparent";
|
|
74
67
|
|
|
75
68
|
cellEls.push(
|
|
76
69
|
<div
|
|
@@ -89,14 +82,14 @@ export function SnapCellGrid({
|
|
|
89
82
|
: undefined
|
|
90
83
|
}
|
|
91
84
|
className={cn(
|
|
92
|
-
"flex
|
|
85
|
+
"flex items-center justify-center rounded text-xs font-semibold",
|
|
93
86
|
interactive ? "cursor-pointer select-none" : "cursor-default",
|
|
94
87
|
)}
|
|
95
88
|
style={{
|
|
89
|
+
height: rowHeight,
|
|
96
90
|
background: bg,
|
|
97
|
-
// Two-layer ring: 1px white/black inner + 2px accent outer
|
|
98
91
|
boxShadow: selected
|
|
99
|
-
? `inset 0 0 0 1px ${
|
|
92
|
+
? `inset 0 0 0 1px ${colors.mode === "dark" ? "#000" : "#fff"}, inset 0 0 0 2px ${colors.mode === "dark" ? "#fff" : "#000"}`
|
|
100
93
|
: undefined,
|
|
101
94
|
}}
|
|
102
95
|
>
|
|
@@ -111,18 +104,22 @@ export function SnapCellGrid({
|
|
|
111
104
|
: null;
|
|
112
105
|
|
|
113
106
|
return (
|
|
114
|
-
<div
|
|
107
|
+
<div>
|
|
115
108
|
<div
|
|
116
|
-
className="grid w-full"
|
|
117
109
|
style={{
|
|
118
|
-
|
|
110
|
+
display: "grid",
|
|
111
|
+
width: "100%",
|
|
112
|
+
gridTemplateColumns: `repeat(${cols}, 1fr)`,
|
|
119
113
|
gap: gapPx,
|
|
114
|
+
padding: 4,
|
|
115
|
+
borderRadius: 8,
|
|
116
|
+
backgroundColor: colors.muted,
|
|
120
117
|
}}
|
|
121
118
|
>
|
|
122
119
|
{cellEls}
|
|
123
120
|
</div>
|
|
124
121
|
{selectionLabel && (
|
|
125
|
-
<div className="
|
|
122
|
+
<div className="mt-1.5 truncate text-xs font-mono" style={{ color: colors.textMuted }}>
|
|
126
123
|
{selectionLabel}
|
|
127
124
|
</div>
|
|
128
125
|
)}
|
|
@@ -36,7 +36,7 @@ import {
|
|
|
36
36
|
TrendingDown,
|
|
37
37
|
type LucideIcon,
|
|
38
38
|
} from "lucide-react";
|
|
39
|
-
import {
|
|
39
|
+
import { useSnapColors } from "../hooks/use-snap-colors";
|
|
40
40
|
|
|
41
41
|
export const ICON_MAP: Record<string, LucideIcon> = {
|
|
42
42
|
"arrow-right": ArrowRight,
|
|
@@ -87,29 +87,16 @@ export function SnapIcon({
|
|
|
87
87
|
const name = String(props.name ?? "info");
|
|
88
88
|
const size = SIZE_PX[String(props.size ?? "md")] ?? 20;
|
|
89
89
|
const color = props.color ? String(props.color) : undefined;
|
|
90
|
-
const
|
|
90
|
+
const colors = useSnapColors();
|
|
91
91
|
|
|
92
92
|
const Icon = ICON_MAP[name];
|
|
93
93
|
if (!Icon) return null;
|
|
94
94
|
|
|
95
|
-
const
|
|
95
|
+
const iconColor = colors.colorHex(color);
|
|
96
96
|
|
|
97
97
|
return (
|
|
98
|
-
<span
|
|
99
|
-
style={{
|
|
100
|
-
display: "inline-flex",
|
|
101
|
-
alignItems: "center",
|
|
102
|
-
...(isAccent ? accentStyle : {}),
|
|
103
|
-
}}
|
|
104
|
-
>
|
|
105
|
-
<Icon
|
|
106
|
-
size={size}
|
|
107
|
-
style={
|
|
108
|
-
isAccent
|
|
109
|
-
? { color: "var(--snap-accent, currentColor)" }
|
|
110
|
-
: { color: `var(--snap-color-${color}, currentColor)` }
|
|
111
|
-
}
|
|
112
|
-
/>
|
|
98
|
+
<span style={{ display: "inline-flex", alignItems: "center" }}>
|
|
99
|
+
<Icon size={size} style={{ color: iconColor }} />
|
|
113
100
|
</span>
|
|
114
101
|
);
|
|
115
102
|
}
|
|
@@ -4,32 +4,43 @@ import { useId } from "react";
|
|
|
4
4
|
import { useStateStore } from "@json-render/react";
|
|
5
5
|
import { Input } from "@neynar/ui/input";
|
|
6
6
|
import { Label } from "@neynar/ui/label";
|
|
7
|
+
import { useSnapColors } from "../hooks/use-snap-colors";
|
|
7
8
|
|
|
8
9
|
export function SnapInput({
|
|
9
10
|
element: { props },
|
|
10
11
|
}: {
|
|
11
12
|
element: { props: Record<string, unknown> };
|
|
12
13
|
}) {
|
|
13
|
-
const id = useId();
|
|
14
14
|
const { get, set } = useStateStore();
|
|
15
|
+
const colors = useSnapColors();
|
|
15
16
|
const name = String(props.name ?? "input");
|
|
16
|
-
const
|
|
17
|
+
const type = String(props.type ?? "text");
|
|
17
18
|
const label = props.label ? String(props.label) : undefined;
|
|
18
19
|
const placeholder = props.placeholder ? String(props.placeholder) : undefined;
|
|
19
|
-
const maxLength =
|
|
20
|
-
|
|
21
|
-
const
|
|
22
|
-
const
|
|
20
|
+
const maxLength = props.maxLength ? Number(props.maxLength) : undefined;
|
|
21
|
+
const path = `/inputs/${name}`;
|
|
22
|
+
const value = (get(path) as string) ?? (props.defaultValue != null ? String(props.defaultValue) : "");
|
|
23
|
+
const id = useId();
|
|
23
24
|
|
|
24
25
|
return (
|
|
25
26
|
<div className="w-full space-y-1.5">
|
|
26
|
-
{label &&
|
|
27
|
+
{label && (
|
|
28
|
+
<Label htmlFor={id} style={{ color: colors.text }}>
|
|
29
|
+
{label}
|
|
30
|
+
</Label>
|
|
31
|
+
)}
|
|
27
32
|
<Input
|
|
28
33
|
id={id}
|
|
29
|
-
|
|
30
|
-
onChange={(e) => set(path, e.target.value)}
|
|
34
|
+
type={type === "number" ? "number" : "text"}
|
|
31
35
|
placeholder={placeholder}
|
|
32
36
|
maxLength={maxLength}
|
|
37
|
+
value={value}
|
|
38
|
+
onChange={(e) => set(path, e.target.value)}
|
|
39
|
+
style={{
|
|
40
|
+
backgroundColor: colors.inputBg,
|
|
41
|
+
borderColor: colors.inputBorder,
|
|
42
|
+
color: colors.text,
|
|
43
|
+
}}
|
|
33
44
|
/>
|
|
34
45
|
</div>
|
|
35
46
|
);
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { Children, type ReactNode, Fragment } from "react";
|
|
4
4
|
import { cn } from "@neynar/ui/utils";
|
|
5
|
+
import { useSnapColors } from "../hooks/use-snap-colors";
|
|
5
6
|
|
|
6
7
|
const GAP_MAP: Record<string, string> = {
|
|
7
8
|
none: "gap-0",
|
|
@@ -21,6 +22,7 @@ export function SnapItemGroup({
|
|
|
21
22
|
const separator = Boolean(props.separator);
|
|
22
23
|
const gap = GAP_MAP[String(props.gap ?? "sm")] ?? "gap-1";
|
|
23
24
|
const items = Children.toArray(children);
|
|
25
|
+
const colors = useSnapColors();
|
|
24
26
|
|
|
25
27
|
return (
|
|
26
28
|
<div
|
|
@@ -29,11 +31,12 @@ export function SnapItemGroup({
|
|
|
29
31
|
border && "rounded-lg border",
|
|
30
32
|
gap,
|
|
31
33
|
)}
|
|
34
|
+
style={border ? { borderColor: colors.border } : undefined}
|
|
32
35
|
>
|
|
33
36
|
{items.map((child, i) => (
|
|
34
37
|
<Fragment key={i}>
|
|
35
38
|
{separator && i > 0 && (
|
|
36
|
-
<div className="h-px
|
|
39
|
+
<div className="h-px" style={{ backgroundColor: colors.border }} />
|
|
37
40
|
)}
|
|
38
41
|
{child}
|
|
39
42
|
</Fragment>
|