@farcaster/snap 1.13.0 → 1.14.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 +8 -12
- 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-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/theme.d.ts +6 -0
- package/dist/react-native/theme.js +12 -6
- 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 +9 -16
- 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-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/theme.tsx +18 -6
- 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
|
@@ -2,10 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { useStateStore } from "@json-render/react";
|
|
4
4
|
import { Label } from "@neynar/ui/label";
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
// TODO: switch back to @neynar/ui/slider once Base UI fixes the inline
|
|
8
|
-
// <script> tag that triggers a React console warning on client render.
|
|
5
|
+
import { useSnapColors } from "../hooks/use-snap-colors";
|
|
9
6
|
|
|
10
7
|
export function SnapSlider({
|
|
11
8
|
element: { props },
|
|
@@ -13,21 +10,24 @@ export function SnapSlider({
|
|
|
13
10
|
element: { props: Record<string, unknown> };
|
|
14
11
|
}) {
|
|
15
12
|
const { get, set } = useStateStore();
|
|
16
|
-
const
|
|
17
|
-
|
|
13
|
+
const colors = useSnapColors();
|
|
18
14
|
const name = String(props.name ?? "slider");
|
|
19
|
-
const path = `/inputs/${name}`;
|
|
20
|
-
const label = props.label ? String(props.label) : undefined;
|
|
21
15
|
const min = Number(props.min ?? 0);
|
|
22
16
|
const max = Number(props.max ?? 100);
|
|
23
|
-
const step =
|
|
24
|
-
const
|
|
17
|
+
const step = Number(props.step ?? 1);
|
|
18
|
+
const label = props.label ? String(props.label) : undefined;
|
|
19
|
+
const path = `/inputs/${name}`;
|
|
25
20
|
const raw = get(path);
|
|
26
|
-
const value =
|
|
21
|
+
const value =
|
|
22
|
+
raw !== undefined
|
|
23
|
+
? Number(raw)
|
|
24
|
+
: props.defaultValue !== undefined
|
|
25
|
+
? Number(props.defaultValue)
|
|
26
|
+
: (min + max) / 2;
|
|
27
27
|
|
|
28
28
|
return (
|
|
29
|
-
<div className="flex w-full flex-col gap-1.5"
|
|
30
|
-
{label && <Label>{label}</Label>}
|
|
29
|
+
<div className="flex w-full flex-col gap-1.5">
|
|
30
|
+
{label && <Label style={{ color: colors.text }}>{label}</Label>}
|
|
31
31
|
<input
|
|
32
32
|
type="range"
|
|
33
33
|
min={min}
|
|
@@ -35,8 +35,11 @@ export function SnapSlider({
|
|
|
35
35
|
step={step}
|
|
36
36
|
value={value}
|
|
37
37
|
onChange={(e) => set(path, Number(e.target.value))}
|
|
38
|
-
className="w-full h-2.5 rounded-full appearance-none
|
|
39
|
-
style={{
|
|
38
|
+
className="w-full h-2.5 rounded-full appearance-none cursor-pointer"
|
|
39
|
+
style={{
|
|
40
|
+
backgroundColor: colors.muted,
|
|
41
|
+
accentColor: colors.accent,
|
|
42
|
+
}}
|
|
40
43
|
/>
|
|
41
44
|
</div>
|
|
42
45
|
);
|
|
@@ -2,32 +2,29 @@
|
|
|
2
2
|
|
|
3
3
|
import { useId } from "react";
|
|
4
4
|
import { useStateStore } from "@json-render/react";
|
|
5
|
-
import { Label } from "@neynar/ui/label";
|
|
6
5
|
import { Switch } from "@neynar/ui/switch";
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import { useSnapAccentScopeStyle } from "../hooks/use-snap-accent";
|
|
6
|
+
import { Label } from "@neynar/ui/label";
|
|
7
|
+
import { useSnapColors } from "../hooks/use-snap-colors";
|
|
10
8
|
|
|
11
9
|
export function SnapSwitch({
|
|
12
10
|
element: { props },
|
|
13
11
|
}: {
|
|
14
12
|
element: { props: Record<string, unknown> };
|
|
15
13
|
}) {
|
|
16
|
-
const id = useId();
|
|
17
14
|
const { get, set } = useStateStore();
|
|
18
|
-
const
|
|
19
|
-
const accentStyle = useSnapAccentScopeStyle();
|
|
15
|
+
const colors = useSnapColors();
|
|
20
16
|
const name = String(props.name ?? "switch");
|
|
21
|
-
const path = `/inputs/${name}`;
|
|
22
17
|
const label = props.label ? String(props.label) : undefined;
|
|
23
|
-
const
|
|
18
|
+
const path = `/inputs/${name}`;
|
|
24
19
|
const raw = get(path);
|
|
25
|
-
const checked =
|
|
20
|
+
const checked =
|
|
21
|
+
raw !== undefined ? Boolean(raw) : Boolean(props.defaultChecked);
|
|
22
|
+
const id = useId();
|
|
26
23
|
|
|
27
24
|
return (
|
|
28
25
|
<div className="flex items-center justify-between gap-3">
|
|
29
26
|
{label && (
|
|
30
|
-
<Label htmlFor={id} className="
|
|
27
|
+
<Label htmlFor={id} className="font-normal" style={{ color: colors.text }}>
|
|
31
28
|
{label}
|
|
32
29
|
</Label>
|
|
33
30
|
)}
|
|
@@ -35,11 +32,10 @@ export function SnapSwitch({
|
|
|
35
32
|
id={id}
|
|
36
33
|
checked={checked}
|
|
37
34
|
onCheckedChange={(v) => set(path, v)}
|
|
38
|
-
style={
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
)}
|
|
35
|
+
style={{
|
|
36
|
+
backgroundColor: checked ? colors.accent : colors.muted,
|
|
37
|
+
borderColor: checked ? colors.accent : colors.inputBorder,
|
|
38
|
+
}}
|
|
43
39
|
/>
|
|
44
40
|
</div>
|
|
45
41
|
);
|
|
@@ -1,15 +1,11 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import { Text } from "@neynar/ui/typography";
|
|
4
|
+
import { useSnapColors } from "../hooks/use-snap-colors";
|
|
4
5
|
|
|
5
6
|
const SIZE_MAP = {
|
|
6
|
-
md: {
|
|
7
|
-
sm: {
|
|
8
|
-
} as const;
|
|
9
|
-
|
|
10
|
-
const WEIGHT_MAP = {
|
|
11
|
-
bold: "bold",
|
|
12
|
-
normal: "normal",
|
|
7
|
+
md: { textSize: "base" as const },
|
|
8
|
+
sm: { textSize: "sm" as const },
|
|
13
9
|
} as const;
|
|
14
10
|
|
|
15
11
|
export function SnapText({
|
|
@@ -22,9 +18,16 @@ export function SnapText({
|
|
|
22
18
|
const weight = props.weight ? String(props.weight) as "bold" | "normal" : undefined;
|
|
23
19
|
const align = (props.align as "left" | "center" | "right") ?? undefined;
|
|
24
20
|
const config = SIZE_MAP[size] ?? SIZE_MAP.md;
|
|
21
|
+
const colors = useSnapColors();
|
|
25
22
|
|
|
26
23
|
return (
|
|
27
|
-
<Text
|
|
24
|
+
<Text
|
|
25
|
+
size={config.textSize}
|
|
26
|
+
weight={weight}
|
|
27
|
+
align={align}
|
|
28
|
+
className="flex-1"
|
|
29
|
+
style={{ color: colors.text }}
|
|
30
|
+
>
|
|
28
31
|
{content}
|
|
29
32
|
</Text>
|
|
30
33
|
);
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
+
import { useState } from "react";
|
|
3
4
|
import { useStateStore } from "@json-render/react";
|
|
4
5
|
import { Label } from "@neynar/ui/label";
|
|
5
6
|
import { cn } from "@neynar/ui/utils";
|
|
6
|
-
import {
|
|
7
|
+
import { useSnapColors } from "../hooks/use-snap-colors";
|
|
7
8
|
|
|
8
9
|
export function SnapToggleGroup({
|
|
9
10
|
element: { props },
|
|
@@ -11,7 +12,7 @@ export function SnapToggleGroup({
|
|
|
11
12
|
element: { props: Record<string, unknown> };
|
|
12
13
|
}) {
|
|
13
14
|
const { get, set } = useStateStore();
|
|
14
|
-
const
|
|
15
|
+
const colors = useSnapColors();
|
|
15
16
|
const name = String(props.name ?? "toggle_group");
|
|
16
17
|
const path = `/inputs/${name}`;
|
|
17
18
|
const label = props.label ? String(props.label) : undefined;
|
|
@@ -50,30 +51,46 @@ export function SnapToggleGroup({
|
|
|
50
51
|
};
|
|
51
52
|
|
|
52
53
|
const isVertical = orientation === "vertical";
|
|
54
|
+
const [hoveredIdx, setHoveredIdx] = useState<number | null>(null);
|
|
53
55
|
|
|
54
56
|
return (
|
|
55
|
-
<div className="w-full space-y-1.5"
|
|
56
|
-
{label && <Label>{label}</Label>}
|
|
57
|
+
<div className="w-full space-y-1.5">
|
|
58
|
+
{label && <Label style={{ color: colors.text }}>{label}</Label>}
|
|
57
59
|
<div
|
|
58
60
|
className={cn(
|
|
59
|
-
"flex gap-1 rounded-lg
|
|
61
|
+
"flex gap-1 rounded-lg p-1",
|
|
60
62
|
isVertical ? "flex-col" : "flex-row",
|
|
61
63
|
)}
|
|
64
|
+
style={{ backgroundColor: colors.muted }}
|
|
62
65
|
>
|
|
63
|
-
{options.map((opt) => {
|
|
66
|
+
{options.map((opt, i) => {
|
|
64
67
|
const isSelected = selected.includes(opt);
|
|
68
|
+
const isHovered = hoveredIdx === i && !isSelected;
|
|
65
69
|
return (
|
|
66
70
|
<button
|
|
67
71
|
key={opt}
|
|
68
72
|
type="button"
|
|
69
73
|
onClick={() => toggle(opt)}
|
|
74
|
+
onPointerEnter={() => setHoveredIdx(i)}
|
|
75
|
+
onPointerLeave={() => setHoveredIdx(null)}
|
|
70
76
|
className={cn(
|
|
71
77
|
"rounded-md px-3 py-2 text-sm font-medium transition-colors",
|
|
72
78
|
isVertical ? "w-full" : "flex-1",
|
|
73
|
-
isSelected
|
|
74
|
-
? "bg-primary text-primary-foreground"
|
|
75
|
-
: "text-foreground hover:bg-border/30",
|
|
76
79
|
)}
|
|
80
|
+
style={{
|
|
81
|
+
transition: "background-color 0.15s, color 0.15s",
|
|
82
|
+
...(isSelected
|
|
83
|
+
? {
|
|
84
|
+
backgroundColor: colors.mode === "dark" ? "rgba(255,255,255,0.10)" : "rgba(0,0,0,0.08)",
|
|
85
|
+
color: colors.text,
|
|
86
|
+
}
|
|
87
|
+
: {
|
|
88
|
+
color: colors.text,
|
|
89
|
+
backgroundColor: isHovered
|
|
90
|
+
? (colors.mode === "dark" ? "rgba(255,255,255,0.04)" : "rgba(0,0,0,0.04)")
|
|
91
|
+
: (colors.mode === "dark" ? "rgba(255,255,255,0.02)" : "rgba(0,0,0,0.02)"),
|
|
92
|
+
}),
|
|
93
|
+
}}
|
|
77
94
|
>
|
|
78
95
|
{opt}
|
|
79
96
|
</button>
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useMemo } from "react";
|
|
4
|
+
import { useStateStore } from "@json-render/react";
|
|
5
|
+
import { useColorMode } from "@neynar/ui/color-mode";
|
|
6
|
+
import { resolveSnapPaletteHex } from "../lib/resolve-palette-hex";
|
|
7
|
+
import { useSnapPreviewPageAccent } from "../accent-context";
|
|
8
|
+
import type { PaletteColor } from "@farcaster/snap";
|
|
9
|
+
import { PALETTE_DARK_HEX, PALETTE_LIGHT_HEX } from "@farcaster/snap";
|
|
10
|
+
|
|
11
|
+
/** Readable foreground color (black or white) for a given hex background. */
|
|
12
|
+
export function pickForegroundForBg(hex: string): string {
|
|
13
|
+
const h = hex.replace(/^#/, "");
|
|
14
|
+
if (h.length !== 6) return "#ffffff";
|
|
15
|
+
const r = Number.parseInt(h.slice(0, 2), 16);
|
|
16
|
+
const g = Number.parseInt(h.slice(2, 4), 16);
|
|
17
|
+
const b = Number.parseInt(h.slice(4, 6), 16);
|
|
18
|
+
const yiq = (r * 299 + g * 587 + b * 114) / 1000;
|
|
19
|
+
return yiq >= 180 ? "#0a0a0a" : "#ffffff";
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const NEUTRAL_LIGHT = {
|
|
23
|
+
text: "#111111",
|
|
24
|
+
textMuted: "#6B7280",
|
|
25
|
+
border: "#E5E7EB",
|
|
26
|
+
muted: "rgba(0,0,0,0.06)",
|
|
27
|
+
surface: "#ffffff",
|
|
28
|
+
inputBorder: "#E5E7EB",
|
|
29
|
+
inputBg: "rgba(0,0,0,0.06)",
|
|
30
|
+
} as const;
|
|
31
|
+
|
|
32
|
+
const NEUTRAL_DARK = {
|
|
33
|
+
text: "#FAFAFA",
|
|
34
|
+
textMuted: "#A1A1AA",
|
|
35
|
+
border: "#2D2D44",
|
|
36
|
+
muted: "rgba(255,255,255,0.03)",
|
|
37
|
+
surface: "#23262f",
|
|
38
|
+
inputBorder: "#3F3F46",
|
|
39
|
+
inputBg: "rgba(255,255,255,0.03)",
|
|
40
|
+
} as const;
|
|
41
|
+
|
|
42
|
+
export type SnapColors = {
|
|
43
|
+
/** Resolved accent hex */
|
|
44
|
+
accent: string;
|
|
45
|
+
/** Readable foreground for accent bg (black or white) */
|
|
46
|
+
accentFg: string;
|
|
47
|
+
/** Primary button hover color */
|
|
48
|
+
accentHover: string;
|
|
49
|
+
/** Secondary/outline button hover color */
|
|
50
|
+
outlineHover: string;
|
|
51
|
+
/** Primary text color */
|
|
52
|
+
text: string;
|
|
53
|
+
/** Muted/secondary text color */
|
|
54
|
+
textMuted: string;
|
|
55
|
+
/** Border color */
|
|
56
|
+
border: string;
|
|
57
|
+
/** Muted background (tracks, containers) */
|
|
58
|
+
muted: string;
|
|
59
|
+
/** Surface/card background */
|
|
60
|
+
surface: string;
|
|
61
|
+
/** Input border */
|
|
62
|
+
inputBorder: string;
|
|
63
|
+
/** Input background */
|
|
64
|
+
inputBg: string;
|
|
65
|
+
/** Current color mode */
|
|
66
|
+
mode: "light" | "dark";
|
|
67
|
+
/** Resolve a palette color name to hex */
|
|
68
|
+
paletteHex: (name: string) => string;
|
|
69
|
+
/** Resolve a palette color name to hex, with accent fallback */
|
|
70
|
+
colorHex: (name: string | undefined) => string;
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
function buildSnapColors(
|
|
74
|
+
accentName: string,
|
|
75
|
+
mode: "light" | "dark",
|
|
76
|
+
): SnapColors {
|
|
77
|
+
const accent = resolveSnapPaletteHex(accentName, mode);
|
|
78
|
+
const accentFg = pickForegroundForBg(accent);
|
|
79
|
+
const neutrals = mode === "dark" ? NEUTRAL_DARK : NEUTRAL_LIGHT;
|
|
80
|
+
const paletteMap = mode === "dark" ? PALETTE_DARK_HEX : PALETTE_LIGHT_HEX;
|
|
81
|
+
|
|
82
|
+
const accentHover =
|
|
83
|
+
mode === "light"
|
|
84
|
+
? `color-mix(in srgb, ${accent} 82%, #000000)`
|
|
85
|
+
: `color-mix(in srgb, ${accent} 78%, #ffffff)`;
|
|
86
|
+
|
|
87
|
+
const outlineHover = `color-mix(in srgb, ${accent} 14%, ${neutrals.surface})`;
|
|
88
|
+
|
|
89
|
+
const paletteHex = (name: string) => resolveSnapPaletteHex(name, mode);
|
|
90
|
+
|
|
91
|
+
const colorHex = (name: string | undefined) => {
|
|
92
|
+
if (!name || name === "accent") return accent;
|
|
93
|
+
if (Object.hasOwn(paletteMap, name)) return paletteMap[name as PaletteColor];
|
|
94
|
+
return accent;
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
return {
|
|
98
|
+
accent,
|
|
99
|
+
accentFg,
|
|
100
|
+
accentHover,
|
|
101
|
+
outlineHover,
|
|
102
|
+
...neutrals,
|
|
103
|
+
mode,
|
|
104
|
+
paletteHex,
|
|
105
|
+
colorHex,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Returns fully resolved color values for snap components.
|
|
111
|
+
* All colors are concrete hex values (or color-mix expressions for hover states)
|
|
112
|
+
* so they can be used as inline styles, independent of host app CSS.
|
|
113
|
+
*/
|
|
114
|
+
export function useSnapColors(): SnapColors {
|
|
115
|
+
const { get } = useStateStore();
|
|
116
|
+
const { mode } = useColorMode();
|
|
117
|
+
const pageAccent = useSnapPreviewPageAccent();
|
|
118
|
+
const fromState = get("/theme/accent");
|
|
119
|
+
const accentRaw =
|
|
120
|
+
(typeof pageAccent === "string" && pageAccent.length > 0
|
|
121
|
+
? pageAccent
|
|
122
|
+
: fromState) ?? undefined;
|
|
123
|
+
const accentName =
|
|
124
|
+
typeof accentRaw === "string" && accentRaw.length > 0
|
|
125
|
+
? accentRaw
|
|
126
|
+
: "purple";
|
|
127
|
+
|
|
128
|
+
return useMemo(() => buildSnapColors(accentName, mode), [accentName, mode]);
|
|
129
|
+
}
|
|
@@ -6,11 +6,6 @@ import { useSnapPalette } from "../use-snap-palette";
|
|
|
6
6
|
import { useSnapTheme } from "../theme";
|
|
7
7
|
import { ICON_MAP } from "./snap-icon";
|
|
8
8
|
|
|
9
|
-
const VARIANT_MAP: Record<string, "primary" | "secondary"> = {
|
|
10
|
-
primary: "primary",
|
|
11
|
-
secondary: "secondary",
|
|
12
|
-
};
|
|
13
|
-
|
|
14
9
|
export function SnapActionButton({
|
|
15
10
|
element: { props },
|
|
16
11
|
emit,
|
|
@@ -18,28 +13,22 @@ export function SnapActionButton({
|
|
|
18
13
|
const { accentHex } = useSnapPalette();
|
|
19
14
|
const { colors } = useSnapTheme();
|
|
20
15
|
const label = String(props.label ?? "Action");
|
|
21
|
-
const variant =
|
|
16
|
+
const variant = String(props.variant ?? "secondary");
|
|
17
|
+
const isPrimary = variant === "primary";
|
|
22
18
|
const iconName = props.icon ? String(props.icon) : undefined;
|
|
23
19
|
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
case "primary":
|
|
27
|
-
return { backgroundColor: accentHex };
|
|
28
|
-
case "secondary":
|
|
29
|
-
return { backgroundColor: "transparent", borderWidth: 1.5, borderColor: accentHex };
|
|
30
|
-
}
|
|
31
|
-
})();
|
|
32
|
-
|
|
33
|
-
const textColor = variant === "primary" ? "#fff" : accentHex;
|
|
34
|
-
const iconColor = variant === "primary" ? "#fff" : accentHex;
|
|
20
|
+
const textColor = isPrimary ? "#fff" : colors.text;
|
|
21
|
+
const iconColor = isPrimary ? "#fff" : colors.text;
|
|
35
22
|
|
|
36
23
|
return (
|
|
37
24
|
<View style={styles.outer}>
|
|
38
25
|
<Pressable
|
|
39
26
|
style={({ pressed }) => [
|
|
40
27
|
styles.btn,
|
|
41
|
-
|
|
42
|
-
|
|
28
|
+
isPrimary ? styles.btnDefault : styles.btnOther,
|
|
29
|
+
isPrimary
|
|
30
|
+
? { backgroundColor: pressed ? accentHex + "DD" : accentHex }
|
|
31
|
+
: { backgroundColor: pressed ? colors.mutedHover : colors.muted },
|
|
43
32
|
pressed && styles.pressed,
|
|
44
33
|
]}
|
|
45
34
|
onPress={() => {
|
|
@@ -48,7 +37,6 @@ export function SnapActionButton({
|
|
|
48
37
|
await emit("press");
|
|
49
38
|
} catch (err: unknown) {
|
|
50
39
|
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
51
|
-
// eslint-disable-next-line no-console
|
|
52
40
|
console.error("[snap] action failed", err);
|
|
53
41
|
}
|
|
54
42
|
}
|
|
@@ -118,7 +118,7 @@ export function SnapCellGrid({
|
|
|
118
118
|
: null;
|
|
119
119
|
|
|
120
120
|
return (
|
|
121
|
-
<View style={[styles.wrap, { gap: gapPx }]}>
|
|
121
|
+
<View style={[styles.wrap, { gap: gapPx, backgroundColor: colors.muted, padding: 4, borderRadius: 8 }]}>
|
|
122
122
|
{rowEls}
|
|
123
123
|
{selectionLabel ? (
|
|
124
124
|
<Text style={[styles.selectionText, { color: colors.textSecondary }]}>
|
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
import type { ComponentRenderProps } from "@json-render/react-native";
|
|
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";
|
|
5
4
|
import { useSnapTheme } from "../theme";
|
|
6
5
|
|
|
7
6
|
export function SnapToggleGroup({
|
|
8
7
|
element: { props },
|
|
9
8
|
}: ComponentRenderProps<Record<string, unknown>>) {
|
|
10
9
|
const { get, set } = useStateStore();
|
|
11
|
-
const { accentHex } = useSnapPalette();
|
|
12
10
|
const { colors } = useSnapTheme();
|
|
13
11
|
const name = String(props.name ?? "toggle_group");
|
|
14
12
|
const path = `/inputs/${name}`;
|
|
@@ -59,7 +57,7 @@ export function SnapToggleGroup({
|
|
|
59
57
|
<View
|
|
60
58
|
style={[
|
|
61
59
|
styles.group,
|
|
62
|
-
{ backgroundColor: colors.
|
|
60
|
+
{ backgroundColor: colors.muted },
|
|
63
61
|
isVertical ? styles.groupVertical : styles.groupHorizontal,
|
|
64
62
|
]}
|
|
65
63
|
>
|
|
@@ -70,8 +68,13 @@ export function SnapToggleGroup({
|
|
|
70
68
|
key={index}
|
|
71
69
|
style={({ pressed }) => [
|
|
72
70
|
styles.option,
|
|
73
|
-
|
|
74
|
-
|
|
71
|
+
{
|
|
72
|
+
backgroundColor: isSelected
|
|
73
|
+
? colors.mutedSelected
|
|
74
|
+
: pressed
|
|
75
|
+
? colors.mutedHover
|
|
76
|
+
: colors.mutedSubtle,
|
|
77
|
+
},
|
|
75
78
|
!isVertical && styles.optionHorizontal,
|
|
76
79
|
]}
|
|
77
80
|
onPress={() => handlePress(opt)}
|
|
@@ -80,7 +83,6 @@ export function SnapToggleGroup({
|
|
|
80
83
|
style={[
|
|
81
84
|
styles.optionText,
|
|
82
85
|
{ color: colors.text },
|
|
83
|
-
isSelected && styles.optionTextSelected,
|
|
84
86
|
]}
|
|
85
87
|
>
|
|
86
88
|
{opt}
|
|
@@ -117,12 +119,8 @@ const styles = StyleSheet.create({
|
|
|
117
119
|
optionHorizontal: {
|
|
118
120
|
flex: 1,
|
|
119
121
|
},
|
|
120
|
-
pressed: { opacity: 0.88 },
|
|
121
122
|
optionText: {
|
|
122
123
|
fontSize: 13,
|
|
123
124
|
fontWeight: "500",
|
|
124
125
|
},
|
|
125
|
-
optionTextSelected: {
|
|
126
|
-
color: "#fff",
|
|
127
|
-
},
|
|
128
126
|
});
|
|
@@ -10,6 +10,12 @@ export type SnapNativeColors = {
|
|
|
10
10
|
border: string;
|
|
11
11
|
inputBg: string;
|
|
12
12
|
muted: string;
|
|
13
|
+
/** Subtle tint for toggle button resting state */
|
|
14
|
+
mutedSubtle: string;
|
|
15
|
+
/** Slightly stronger tint for hover/press state */
|
|
16
|
+
mutedHover: string;
|
|
17
|
+
/** Stronger tint for selected state (toggle group) */
|
|
18
|
+
mutedSelected: string;
|
|
13
19
|
};
|
|
14
20
|
|
|
15
21
|
const DEFAULT_LIGHT: SnapNativeColors = {
|
|
@@ -17,9 +23,12 @@ const DEFAULT_LIGHT: SnapNativeColors = {
|
|
|
17
23
|
surface: "#ffffff",
|
|
18
24
|
text: "#111111",
|
|
19
25
|
textSecondary: "#6b7280",
|
|
20
|
-
border: "#
|
|
21
|
-
inputBg: "
|
|
22
|
-
muted: "
|
|
26
|
+
border: "#E5E7EB",
|
|
27
|
+
inputBg: "rgba(0,0,0,0.12)",
|
|
28
|
+
muted: "rgba(0,0,0,0.12)",
|
|
29
|
+
mutedSubtle: "rgba(0,0,0,0.06)",
|
|
30
|
+
mutedHover: "rgba(0,0,0,0.10)",
|
|
31
|
+
mutedSelected: "rgba(0,0,0,0.18)",
|
|
23
32
|
};
|
|
24
33
|
|
|
25
34
|
const DEFAULT_DARK: SnapNativeColors = {
|
|
@@ -27,9 +36,12 @@ const DEFAULT_DARK: SnapNativeColors = {
|
|
|
27
36
|
surface: "#1a1d24",
|
|
28
37
|
text: "#fafafa",
|
|
29
38
|
textSecondary: "#a1a1aa",
|
|
30
|
-
border: "#
|
|
31
|
-
inputBg: "
|
|
32
|
-
muted: "
|
|
39
|
+
border: "#2D2D44",
|
|
40
|
+
inputBg: "rgba(255,255,255,0.03)",
|
|
41
|
+
muted: "rgba(255,255,255,0.03)",
|
|
42
|
+
mutedSubtle: "rgba(255,255,255,0.02)",
|
|
43
|
+
mutedHover: "rgba(255,255,255,0.04)",
|
|
44
|
+
mutedSelected: "rgba(255,255,255,0.10)",
|
|
33
45
|
};
|
|
34
46
|
|
|
35
47
|
// ─── Context ──────────────────────────────────────────
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import { type CSSProperties } from "react";
|
|
2
|
-
import type { PaletteColor } from "@farcaster/snap";
|
|
3
|
-
/**
|
|
4
|
-
* CSS variables so Neynar controls (`bg-primary`, `data-checked:bg-primary`, etc.)
|
|
5
|
-
* use the snap `theme.accent` inside json-render catalog components.
|
|
6
|
-
*/
|
|
7
|
-
export declare function useSnapAccentScopeStyle(): CSSProperties;
|
|
8
|
-
/** Active snap palette table for the current docs shell theme. */
|
|
9
|
-
export declare function useSnapPalette(): {
|
|
10
|
-
hex: (name: string) => string;
|
|
11
|
-
map: Record<PaletteColor, string>;
|
|
12
|
-
theme: "light" | "dark";
|
|
13
|
-
};
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
import { useMemo } from "react";
|
|
3
|
-
import { useStateStore } from "@json-render/react";
|
|
4
|
-
import { PALETTE_DARK_HEX, PALETTE_LIGHT_HEX } from "@farcaster/snap";
|
|
5
|
-
import { useColorMode } from "@neynar/ui/color-mode";
|
|
6
|
-
import { resolveSnapPaletteHex } from "../lib/resolve-palette-hex.js";
|
|
7
|
-
import { snapPreviewPrimaryCssProperties } from "../lib/preview-primary-css.js";
|
|
8
|
-
import { useSnapPreviewPageAccent } from "../accent-context.js";
|
|
9
|
-
/**
|
|
10
|
-
* CSS variables so Neynar controls (`bg-primary`, `data-checked:bg-primary`, etc.)
|
|
11
|
-
* use the snap `theme.accent` inside json-render catalog components.
|
|
12
|
-
*/
|
|
13
|
-
export function useSnapAccentScopeStyle() {
|
|
14
|
-
const { get } = useStateStore();
|
|
15
|
-
const { mode } = useColorMode();
|
|
16
|
-
const pageAccent = useSnapPreviewPageAccent();
|
|
17
|
-
const fromState = get("/theme/accent");
|
|
18
|
-
const accentRaw = (typeof pageAccent === "string" && pageAccent.length > 0
|
|
19
|
-
? pageAccent
|
|
20
|
-
: fromState) ?? undefined;
|
|
21
|
-
const accentName = typeof accentRaw === "string" && accentRaw.length > 0
|
|
22
|
-
? accentRaw
|
|
23
|
-
: "purple";
|
|
24
|
-
return useMemo(() => snapPreviewPrimaryCssProperties(accentName, mode), [accentName, mode]);
|
|
25
|
-
}
|
|
26
|
-
/** Active snap palette table for the current docs shell theme. */
|
|
27
|
-
export function useSnapPalette() {
|
|
28
|
-
const { mode } = useColorMode();
|
|
29
|
-
const map = mode === "dark" ? PALETTE_DARK_HEX : PALETTE_LIGHT_HEX;
|
|
30
|
-
const hex = (name) => resolveSnapPaletteHex(name, mode);
|
|
31
|
-
return { hex, map, theme: mode };
|
|
32
|
-
}
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import { useMemo, type CSSProperties } from "react";
|
|
4
|
-
import { useStateStore } from "@json-render/react";
|
|
5
|
-
import type { PaletteColor } from "@farcaster/snap";
|
|
6
|
-
import { PALETTE_DARK_HEX, PALETTE_LIGHT_HEX } from "@farcaster/snap";
|
|
7
|
-
import { useColorMode } from "@neynar/ui/color-mode";
|
|
8
|
-
import { resolveSnapPaletteHex } from "../lib/resolve-palette-hex";
|
|
9
|
-
import { snapPreviewPrimaryCssProperties } from "../lib/preview-primary-css";
|
|
10
|
-
import { useSnapPreviewPageAccent } from "../accent-context";
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* CSS variables so Neynar controls (`bg-primary`, `data-checked:bg-primary`, etc.)
|
|
14
|
-
* use the snap `theme.accent` inside json-render catalog components.
|
|
15
|
-
*/
|
|
16
|
-
export function useSnapAccentScopeStyle(): CSSProperties {
|
|
17
|
-
const { get } = useStateStore();
|
|
18
|
-
const { mode } = useColorMode();
|
|
19
|
-
const pageAccent = useSnapPreviewPageAccent();
|
|
20
|
-
const fromState = get("/theme/accent");
|
|
21
|
-
const accentRaw =
|
|
22
|
-
(typeof pageAccent === "string" && pageAccent.length > 0
|
|
23
|
-
? pageAccent
|
|
24
|
-
: fromState) ?? undefined;
|
|
25
|
-
const accentName =
|
|
26
|
-
typeof accentRaw === "string" && accentRaw.length > 0
|
|
27
|
-
? accentRaw
|
|
28
|
-
: "purple";
|
|
29
|
-
return useMemo(
|
|
30
|
-
() => snapPreviewPrimaryCssProperties(accentName, mode),
|
|
31
|
-
[accentName, mode],
|
|
32
|
-
);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/** Active snap palette table for the current docs shell theme. */
|
|
36
|
-
export function useSnapPalette(): {
|
|
37
|
-
hex: (name: string) => string;
|
|
38
|
-
map: Record<PaletteColor, string>;
|
|
39
|
-
theme: "light" | "dark";
|
|
40
|
-
} {
|
|
41
|
-
const { mode } = useColorMode();
|
|
42
|
-
const map = mode === "dark" ? PALETTE_DARK_HEX : PALETTE_LIGHT_HEX;
|
|
43
|
-
const hex = (name: string) => resolveSnapPaletteHex(name, mode);
|
|
44
|
-
return { hex, map, theme: mode };
|
|
45
|
-
}
|