@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.
Files changed (47) hide show
  1. package/dist/react/components/action-button.js +18 -10
  2. package/dist/react/components/badge.js +8 -6
  3. package/dist/react/components/bar-chart.js +12 -17
  4. package/dist/react/components/cell-grid.js +8 -12
  5. package/dist/react/components/icon.js +4 -10
  6. package/dist/react/components/input.js +12 -6
  7. package/dist/react/components/item-group.js +3 -1
  8. package/dist/react/components/item.d.ts +3 -3
  9. package/dist/react/components/item.js +4 -3
  10. package/dist/react/components/progress.js +3 -3
  11. package/dist/react/components/separator.js +3 -1
  12. package/dist/react/components/slider.js +14 -10
  13. package/dist/react/components/switch.js +10 -12
  14. package/dist/react/components/text.js +5 -7
  15. package/dist/react/components/toggle-group.js +20 -6
  16. package/dist/react/hooks/use-snap-colors.d.ts +38 -0
  17. package/dist/react/hooks/use-snap-colors.js +82 -0
  18. package/dist/react-native/components/snap-action-button.js +8 -18
  19. package/dist/react-native/components/snap-cell-grid.js +1 -1
  20. package/dist/react-native/components/snap-switch.js +1 -1
  21. package/dist/react-native/components/snap-toggle-group.js +8 -10
  22. package/dist/react-native/theme.d.ts +6 -0
  23. package/dist/react-native/theme.js +12 -6
  24. package/package.json +1 -1
  25. package/src/react/components/action-button.tsx +24 -17
  26. package/src/react/components/badge.tsx +14 -17
  27. package/src/react/components/bar-chart.tsx +21 -19
  28. package/src/react/components/cell-grid.tsx +9 -16
  29. package/src/react/components/icon.tsx +5 -18
  30. package/src/react/components/input.tsx +20 -9
  31. package/src/react/components/item-group.tsx +4 -1
  32. package/src/react/components/item.tsx +13 -10
  33. package/src/react/components/progress.tsx +12 -7
  34. package/src/react/components/separator.tsx +8 -1
  35. package/src/react/components/slider.tsx +18 -15
  36. package/src/react/components/switch.tsx +12 -16
  37. package/src/react/components/text.tsx +11 -8
  38. package/src/react/components/toggle-group.tsx +26 -9
  39. package/src/react/hooks/use-snap-colors.ts +129 -0
  40. package/src/react-native/components/snap-action-button.tsx +8 -20
  41. package/src/react-native/components/snap-cell-grid.tsx +1 -1
  42. package/src/react-native/components/snap-switch.tsx +1 -1
  43. package/src/react-native/components/snap-toggle-group.tsx +8 -10
  44. package/src/react-native/theme.tsx +18 -6
  45. package/dist/react/hooks/use-snap-accent.d.ts +0 -13
  46. package/dist/react/hooks/use-snap-accent.js +0 -32
  47. package/src/react/hooks/use-snap-accent.ts +0 -45
@@ -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.border, true: accentHex }, thumbColor: "#fff" })] }));
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.border + "33" },
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
- isSelected && { backgroundColor: accentHex },
59
- pressed && styles.pressed,
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
  });
@@ -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: "#d1d5db",
9
- inputBg: "#ffffff",
10
- muted: "#f9fafb",
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: "#374151",
18
- inputBg: "#1a1d24",
19
- muted: "#27272a",
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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@farcaster/snap",
3
- "version": "1.13.0",
3
+ "version": "1.14.0",
4
4
  "description": "Farcaster Snaps 🫰",
5
5
  "repository": {
6
6
  "type": "git",
@@ -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 { useSnapAccentScopeStyle } from "../hooks/use-snap-accent";
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 = VARIANT_MAP[String(props.variant ?? "secondary")] ?? "secondary";
17
+ const variant = String(props.variant ?? "secondary");
18
+ const isPrimary = variant === "primary";
22
19
  const iconName = props.icon ? String(props.icon) : undefined;
23
- const accentStyle = useSnapAccentScopeStyle();
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" style={accentStyle}>
38
+ <div className="w-full min-w-0 flex-1">
29
39
  <Button
30
40
  type="button"
31
- variant={variant}
32
- className={cn(
33
- "w-full gap-2",
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 { useSnapAccentScopeStyle } from "../hooks/use-snap-accent";
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 accentStyle = useSnapAccentScopeStyle();
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
- <span style={isAccent ? accentStyle : undefined}>
23
- <Badge
24
- variant={variant}
25
- className="gap-1"
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 type { PaletteColor } from "@farcaster/snap";
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 accentStyle = useSnapAccentScopeStyle();
10
+ const colors = useSnapColors();
13
11
  const bars = Array.isArray(props.bars) ? props.bars : [];
14
- const chartColor = String(props.color ?? "accent");
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 barColor(bar: { color?: string }): string {
24
- if (bar.color && bar.color in PALETTE_LIGHT_HEX) {
25
- return `var(--snap-color-${bar.color}, ${PALETTE_LIGHT_HEX[bar.color as PaletteColor]})`;
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" style={accentStyle}>
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 = barColor(bar);
35
+ const fill = barFill(bar);
43
36
  return (
44
37
  <div key={i} className="flex w-full items-center gap-2">
45
- <span className="text-muted-foreground w-20 shrink-0 truncate text-right text-xs">
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 className="bg-muted h-2.5 flex-1 overflow-hidden rounded-full">
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
- background: fill,
53
+ backgroundColor: fill,
55
54
  }}
56
55
  />
57
56
  </div>
58
- <span className="text-muted-foreground w-8 shrink-0 text-xs tabular-nums">
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, PALETTE_LIGHT_HEX } from "@farcaster/snap";
7
- import type { PaletteColor } from "@farcaster/snap";
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 accentStyle = useSnapAccentScopeStyle();
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");
@@ -60,17 +57,12 @@ export function SnapCellGrid({
60
57
  });
61
58
  }
62
59
 
63
- const ringColor = appearance === "dark" ? "#fff" : "#000";
64
-
65
60
  const cellEls: ReactNode[] = [];
66
61
  for (let r = 0; r < rows; r++) {
67
62
  for (let c = 0; c < cols; c++) {
68
63
  const cell = cellMap.get(`${r},${c}`);
69
64
  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";
65
+ const bg = cell?.color ? colors.colorHex(cell.color) : "transparent";
74
66
 
75
67
  cellEls.push(
76
68
  <div
@@ -96,7 +88,7 @@ export function SnapCellGrid({
96
88
  background: bg,
97
89
  // Two-layer ring: 1px white/black inner + 2px accent outer
98
90
  boxShadow: selected
99
- ? `inset 0 0 0 1px ${appearance === "dark" ? "#000" : "#fff"}, inset 0 0 0 2px ${appearance === "dark" ? "#fff" : "#000"}`
91
+ ? `inset 0 0 0 1px ${colors.mode === "dark" ? "#000" : "#fff"}, inset 0 0 0 2px ${colors.mode === "dark" ? "#fff" : "#000"}`
100
92
  : undefined,
101
93
  }}
102
94
  >
@@ -111,18 +103,19 @@ export function SnapCellGrid({
111
103
  : null;
112
104
 
113
105
  return (
114
- <div style={accentStyle}>
106
+ <div>
115
107
  <div
116
- className="grid w-full"
108
+ className="grid w-full rounded-lg p-1"
117
109
  style={{
118
110
  gridTemplateColumns: `repeat(${cols}, minmax(0, 1fr))`,
119
111
  gap: gapPx,
112
+ backgroundColor: colors.muted,
120
113
  }}
121
114
  >
122
115
  {cellEls}
123
116
  </div>
124
117
  {selectionLabel && (
125
- <div className="text-muted-foreground mt-1.5 truncate text-xs font-mono">
118
+ <div className="mt-1.5 truncate text-xs font-mono" style={{ color: colors.textMuted }}>
126
119
  {selectionLabel}
127
120
  </div>
128
121
  )}
@@ -36,7 +36,7 @@ import {
36
36
  TrendingDown,
37
37
  type LucideIcon,
38
38
  } from "lucide-react";
39
- import { useSnapAccentScopeStyle } from "../hooks/use-snap-accent";
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 accentStyle = useSnapAccentScopeStyle();
90
+ const colors = useSnapColors();
91
91
 
92
92
  const Icon = ICON_MAP[name];
93
93
  if (!Icon) return null;
94
94
 
95
- const isAccent = !color || color === "accent";
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 path = `/inputs/${name}`;
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
- typeof props.maxLength === "number" ? props.maxLength : undefined;
21
- const raw = get(path);
22
- const value = typeof raw === "string" ? raw : "";
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 && <Label htmlFor={id}>{label}</Label>}
27
+ {label && (
28
+ <Label htmlFor={id} style={{ color: colors.text }}>
29
+ {label}
30
+ </Label>
31
+ )}
27
32
  <Input
28
33
  id={id}
29
- value={value}
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 bg-border" />
39
+ <div className="h-px" style={{ backgroundColor: colors.border }} />
37
40
  )}
38
41
  {child}
39
42
  </Fragment>
@@ -1,6 +1,5 @@
1
1
  "use client";
2
2
 
3
- import type { ReactNode } from "react";
4
3
  import {
5
4
  Item,
6
5
  ItemContent,
@@ -8,26 +7,30 @@ import {
8
7
  ItemDescription,
9
8
  ItemActions,
10
9
  } from "@neynar/ui/item";
10
+ import { useSnapColors } from "../hooks/use-snap-colors";
11
11
 
12
12
  export function SnapItem({
13
- element: { props },
13
+ element: { props, children: childIds },
14
14
  children,
15
15
  }: {
16
- element: { props: Record<string, unknown> };
17
- children?: ReactNode;
16
+ element: { props: Record<string, unknown>; children?: string[] };
17
+ children?: React.ReactNode;
18
18
  }) {
19
19
  const title = String(props.title ?? "");
20
20
  const description = props.description ? String(props.description) : undefined;
21
- const variant =
22
- (props.variant as "default") ?? "default";
21
+ const colors = useSnapColors();
23
22
 
24
23
  return (
25
- <Item variant={variant} className="flex-1 py-1.5 px-2.5">
24
+ <Item className="flex-1 py-1.5 px-2.5">
26
25
  <ItemContent className="gap-0.5">
27
- <ItemTitle>{title}</ItemTitle>
28
- {description && <ItemDescription className="mt-0">{description}</ItemDescription>}
26
+ <ItemTitle style={{ color: colors.text }}>{title}</ItemTitle>
27
+ {description && (
28
+ <ItemDescription className="mt-0" style={{ color: colors.textMuted }}>
29
+ {description}
30
+ </ItemDescription>
31
+ )}
29
32
  </ItemContent>
30
- {children && <ItemActions>{children}</ItemActions>}
33
+ {childIds && childIds.length > 0 && <ItemActions>{children}</ItemActions>}
31
34
  </Item>
32
35
  );
33
36
  }
@@ -1,27 +1,32 @@
1
1
  "use client";
2
2
 
3
- import { useSnapAccentScopeStyle } from "../hooks/use-snap-accent";
3
+ import { useSnapColors } from "../hooks/use-snap-colors";
4
4
 
5
5
  export function SnapProgress({
6
6
  element: { props },
7
7
  }: {
8
8
  element: { props: Record<string, unknown> };
9
9
  }) {
10
- const accentStyle = useSnapAccentScopeStyle();
10
+ const colors = useSnapColors();
11
11
  const value = Number(props.value ?? 0);
12
12
  const max = Math.max(1, Number(props.max ?? 100));
13
13
  const percent = Math.min(100, Math.max(0, (value / max) * 100));
14
14
  const label = props.label ? String(props.label) : null;
15
15
 
16
16
  return (
17
- <div className="flex w-full flex-1 flex-col gap-1" style={accentStyle}>
17
+ <div className="flex w-full flex-1 flex-col gap-1">
18
18
  {label && (
19
- <span className="text-muted-foreground text-xs">{label}</span>
19
+ <span className="text-xs" style={{ color: colors.textMuted }}>
20
+ {label}
21
+ </span>
20
22
  )}
21
- <div className="bg-muted h-2.5 w-full overflow-hidden rounded-full">
23
+ <div
24
+ className="h-2.5 w-full overflow-hidden rounded-full"
25
+ style={{ backgroundColor: colors.muted }}
26
+ >
22
27
  <div
23
- className="h-full rounded-full bg-primary transition-all"
24
- style={{ width: `${percent}%` }}
28
+ className="h-full rounded-full transition-all"
29
+ style={{ width: `${percent}%`, backgroundColor: colors.accent }}
25
30
  />
26
31
  </div>
27
32
  </div>
@@ -1,6 +1,7 @@
1
1
  "use client";
2
2
 
3
3
  import { Separator } from "@neynar/ui/separator";
4
+ import { useSnapColors } from "../hooks/use-snap-colors";
4
5
 
5
6
  export function SnapSeparator({
6
7
  element: { props },
@@ -9,6 +10,12 @@ export function SnapSeparator({
9
10
  }) {
10
11
  const orientation =
11
12
  (props.orientation as "horizontal" | "vertical") ?? "horizontal";
13
+ const colors = useSnapColors();
12
14
 
13
- return <Separator orientation={orientation} />;
15
+ return (
16
+ <Separator
17
+ orientation={orientation}
18
+ style={{ backgroundColor: colors.border }}
19
+ />
20
+ );
14
21
  }