@farcaster/snap 1.5.1 → 1.6.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/constants.d.ts +0 -107
- package/dist/constants.js +0 -148
- package/dist/dataStore.d.ts +12 -0
- package/dist/dataStore.js +35 -0
- package/dist/index.d.ts +6 -3
- package/dist/index.js +5 -3
- package/dist/middleware.d.ts +3 -0
- package/dist/middleware.js +3 -0
- package/dist/react/accent-context.d.ts +6 -0
- package/dist/react/accent-context.js +10 -0
- package/dist/react/catalog-renderer.d.ts +5 -0
- package/dist/react/catalog-renderer.js +37 -0
- package/dist/react/components/action-button.d.ts +6 -0
- package/dist/react/components/action-button.js +22 -0
- package/dist/react/components/badge.d.ts +5 -0
- package/dist/react/components/badge.js +18 -0
- package/dist/react/components/icon.d.ts +7 -0
- package/dist/react/components/icon.js +60 -0
- package/dist/react/components/image.d.ts +5 -0
- package/dist/react/components/image.js +15 -0
- package/dist/react/components/input.d.ts +5 -0
- package/dist/react/components/input.js +18 -0
- package/dist/react/components/item-group.d.ts +7 -0
- package/dist/react/components/item-group.js +17 -0
- package/dist/react/components/item.d.ts +7 -0
- package/dist/react/components/item.js +9 -0
- package/dist/react/components/progress.d.ts +5 -0
- package/dist/react/components/progress.js +11 -0
- package/dist/react/components/separator.d.ts +5 -0
- package/dist/react/components/separator.js +7 -0
- package/dist/react/components/slider.d.ts +5 -0
- package/dist/react/components/slider.js +21 -0
- package/dist/react/components/stack.d.ts +7 -0
- package/dist/react/components/stack.js +32 -0
- package/dist/react/components/switch.d.ts +5 -0
- package/dist/react/components/switch.js +23 -0
- package/dist/react/components/text.d.ts +5 -0
- package/dist/react/components/text.js +25 -0
- package/dist/react/components/toggle-group.d.ts +5 -0
- package/dist/react/components/toggle-group.js +52 -0
- package/dist/react/hooks/use-snap-accent.d.ts +13 -0
- package/dist/react/hooks/use-snap-accent.js +32 -0
- package/dist/react/index.d.ts +47 -0
- package/dist/react/index.js +191 -0
- package/dist/react/lib/preview-primary-css.d.ts +6 -0
- package/dist/react/lib/preview-primary-css.js +43 -0
- package/dist/react/lib/resolve-palette-hex.d.ts +2 -0
- package/dist/react/lib/resolve-palette-hex.js +10 -0
- package/dist/schemas.d.ts +14 -1629
- package/dist/schemas.js +14 -526
- package/dist/ui/badge.d.ts +52 -0
- package/dist/ui/badge.js +9 -0
- package/dist/ui/button.d.ts +42 -28
- package/dist/ui/button.js +7 -9
- package/dist/ui/catalog.d.ts +280 -155
- package/dist/ui/catalog.js +102 -83
- package/dist/ui/icon.d.ts +56 -0
- package/dist/ui/icon.js +51 -0
- package/dist/ui/image.d.ts +1 -0
- package/dist/ui/image.js +2 -2
- package/dist/ui/index.d.ts +20 -22
- package/dist/ui/index.js +10 -11
- package/dist/ui/input.d.ts +17 -0
- package/dist/ui/input.js +13 -0
- package/dist/ui/item-group.d.ts +12 -0
- package/dist/ui/item-group.js +7 -0
- package/dist/ui/item.d.ts +14 -0
- package/dist/ui/item.js +9 -0
- package/dist/ui/progress.d.ts +1 -11
- package/dist/ui/progress.js +21 -4
- package/dist/ui/schema.js +3 -3
- package/dist/ui/separator.d.ts +9 -0
- package/dist/ui/separator.js +5 -0
- package/dist/ui/slider.d.ts +4 -3
- package/dist/ui/slider.js +34 -5
- package/dist/ui/stack.d.ts +22 -1
- package/dist/ui/stack.js +8 -1
- package/dist/ui/switch.d.ts +8 -0
- package/dist/ui/switch.js +7 -0
- package/dist/ui/text.d.ts +15 -7
- package/dist/ui/text.js +8 -4
- package/dist/ui/toggle-group.d.ts +23 -0
- package/dist/ui/toggle-group.js +19 -0
- package/dist/validator.d.ts +5 -1
- package/dist/validator.js +6 -136
- package/package.json +72 -52
- package/src/constants.ts +0 -179
- package/src/dataStore.ts +62 -0
- package/src/index.ts +11 -20
- package/src/middleware.ts +7 -0
- package/src/react/accent-context.tsx +29 -0
- package/src/react/catalog-renderer.tsx +39 -0
- package/src/react/components/action-button.tsx +48 -0
- package/src/react/components/badge.tsx +37 -0
- package/src/react/components/icon.tsx +115 -0
- package/src/react/components/image.tsx +33 -0
- package/src/react/components/input.tsx +36 -0
- package/src/react/components/item-group.tsx +43 -0
- package/src/react/components/item.tsx +33 -0
- package/src/react/components/progress.tsx +29 -0
- package/src/react/components/separator.tsx +14 -0
- package/src/react/components/slider.tsx +43 -0
- package/src/react/components/stack.tsx +55 -0
- package/src/react/components/switch.tsx +46 -0
- package/src/react/components/text.tsx +43 -0
- package/src/react/components/toggle-group.tsx +85 -0
- package/src/react/hooks/use-snap-accent.ts +45 -0
- package/src/react/index.tsx +321 -0
- package/src/react/lib/preview-primary-css.ts +57 -0
- package/src/react/lib/resolve-palette-hex.ts +20 -0
- package/src/schemas.ts +18 -644
- package/src/ui/badge.ts +13 -0
- package/src/ui/button.ts +9 -12
- package/src/ui/catalog.ts +106 -86
- package/src/ui/icon.ts +56 -0
- package/src/ui/image.ts +3 -2
- package/src/ui/index.ts +26 -29
- package/src/ui/input.ts +17 -0
- package/src/ui/item-group.ts +11 -0
- package/src/ui/item.ts +13 -0
- package/src/ui/progress.ts +25 -7
- package/src/ui/schema.ts +3 -3
- package/src/ui/separator.ts +9 -0
- package/src/ui/slider.ts +40 -10
- package/src/ui/stack.ts +9 -1
- package/src/ui/switch.ts +11 -0
- package/src/ui/text.ts +9 -4
- package/src/ui/toggle-group.ts +23 -0
- package/src/validator.ts +6 -176
- package/dist/ui/bar-chart.d.ts +0 -30
- package/dist/ui/bar-chart.js +0 -15
- package/dist/ui/button-group.d.ts +0 -19
- package/dist/ui/button-group.js +0 -18
- package/dist/ui/divider.d.ts +0 -3
- package/dist/ui/divider.js +0 -2
- package/dist/ui/grid.d.ts +0 -22
- package/dist/ui/grid.js +0 -16
- package/dist/ui/group.d.ts +0 -7
- package/dist/ui/group.js +0 -5
- package/dist/ui/list.d.ts +0 -13
- package/dist/ui/list.js +0 -13
- package/dist/ui/spacer.d.ts +0 -9
- package/dist/ui/spacer.js +0 -5
- package/dist/ui/text-input.d.ts +0 -7
- package/dist/ui/text-input.js +0 -12
- package/dist/ui/toggle.d.ts +0 -7
- package/dist/ui/toggle.js +0 -6
- package/src/ui/bar-chart.ts +0 -20
- package/src/ui/button-group.ts +0 -26
- package/src/ui/divider.ts +0 -5
- package/src/ui/grid.ts +0 -25
- package/src/ui/group.ts +0 -8
- package/src/ui/list.ts +0 -17
- package/src/ui/spacer.ts +0 -8
- package/src/ui/text-input.ts +0 -15
- package/src/ui/toggle.ts +0 -9
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import type { ReactNode } from "react";
|
|
4
|
+
import {
|
|
5
|
+
Item,
|
|
6
|
+
ItemContent,
|
|
7
|
+
ItemTitle,
|
|
8
|
+
ItemDescription,
|
|
9
|
+
ItemActions,
|
|
10
|
+
} from "@neynar/ui/item";
|
|
11
|
+
|
|
12
|
+
export function SnapItem({
|
|
13
|
+
element: { props },
|
|
14
|
+
children,
|
|
15
|
+
}: {
|
|
16
|
+
element: { props: Record<string, unknown> };
|
|
17
|
+
children?: ReactNode;
|
|
18
|
+
}) {
|
|
19
|
+
const title = String(props.title ?? "");
|
|
20
|
+
const description = props.description ? String(props.description) : undefined;
|
|
21
|
+
const variant =
|
|
22
|
+
(props.variant as "default" | "outline" | "muted") ?? "default";
|
|
23
|
+
|
|
24
|
+
return (
|
|
25
|
+
<Item variant={variant} className={`flex-1 py-1.5 px-2.5 ${variant === "muted" ? "!bg-border/20" : ""}`}>
|
|
26
|
+
<ItemContent className="gap-0.5">
|
|
27
|
+
<ItemTitle>{title}</ItemTitle>
|
|
28
|
+
{description && <ItemDescription className="mt-0">{description}</ItemDescription>}
|
|
29
|
+
</ItemContent>
|
|
30
|
+
{children && <ItemActions>{children}</ItemActions>}
|
|
31
|
+
</Item>
|
|
32
|
+
);
|
|
33
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useSnapAccentScopeStyle } from "../hooks/use-snap-accent";
|
|
4
|
+
|
|
5
|
+
export function SnapProgress({
|
|
6
|
+
element: { props },
|
|
7
|
+
}: {
|
|
8
|
+
element: { props: Record<string, unknown> };
|
|
9
|
+
}) {
|
|
10
|
+
const accentStyle = useSnapAccentScopeStyle();
|
|
11
|
+
const value = Number(props.value ?? 0);
|
|
12
|
+
const max = Math.max(1, Number(props.max ?? 100));
|
|
13
|
+
const percent = Math.min(100, Math.max(0, (value / max) * 100));
|
|
14
|
+
const label = props.label ? String(props.label) : null;
|
|
15
|
+
|
|
16
|
+
return (
|
|
17
|
+
<div className="flex w-full flex-1 flex-col gap-1" style={accentStyle}>
|
|
18
|
+
{label && (
|
|
19
|
+
<span className="text-muted-foreground text-xs">{label}</span>
|
|
20
|
+
)}
|
|
21
|
+
<div className="bg-muted h-2.5 w-full overflow-hidden rounded-full">
|
|
22
|
+
<div
|
|
23
|
+
className="h-full rounded-full bg-primary transition-all"
|
|
24
|
+
style={{ width: `${percent}%` }}
|
|
25
|
+
/>
|
|
26
|
+
</div>
|
|
27
|
+
</div>
|
|
28
|
+
);
|
|
29
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { Separator } from "@neynar/ui/separator";
|
|
4
|
+
|
|
5
|
+
export function SnapSeparator({
|
|
6
|
+
element: { props },
|
|
7
|
+
}: {
|
|
8
|
+
element: { props: Record<string, unknown> };
|
|
9
|
+
}) {
|
|
10
|
+
const orientation =
|
|
11
|
+
(props.orientation as "horizontal" | "vertical") ?? "horizontal";
|
|
12
|
+
|
|
13
|
+
return <Separator orientation={orientation} />;
|
|
14
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useStateStore } from "@json-render/react";
|
|
4
|
+
import { Label } from "@neynar/ui/label";
|
|
5
|
+
import { useSnapAccentScopeStyle } from "../hooks/use-snap-accent";
|
|
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.
|
|
9
|
+
|
|
10
|
+
export function SnapSlider({
|
|
11
|
+
element: { props },
|
|
12
|
+
}: {
|
|
13
|
+
element: { props: Record<string, unknown> };
|
|
14
|
+
}) {
|
|
15
|
+
const { get, set } = useStateStore();
|
|
16
|
+
const accentStyle = useSnapAccentScopeStyle();
|
|
17
|
+
|
|
18
|
+
const name = String(props.name ?? "slider");
|
|
19
|
+
const path = `/inputs/${name}`;
|
|
20
|
+
const label = props.label ? String(props.label) : undefined;
|
|
21
|
+
const min = Number(props.min ?? 0);
|
|
22
|
+
const max = Number(props.max ?? 100);
|
|
23
|
+
const step = props.step != null ? Number(props.step) : 1;
|
|
24
|
+
const fallback = props.defaultValue != null ? Number(props.defaultValue) : (min + max) / 2;
|
|
25
|
+
const raw = get(path);
|
|
26
|
+
const value = raw === undefined || raw === null ? fallback : Number(raw);
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
<div className="flex w-full flex-col gap-1.5" style={accentStyle}>
|
|
30
|
+
{label && <Label>{label}</Label>}
|
|
31
|
+
<input
|
|
32
|
+
type="range"
|
|
33
|
+
min={min}
|
|
34
|
+
max={max}
|
|
35
|
+
step={step}
|
|
36
|
+
value={value}
|
|
37
|
+
onChange={(e) => set(path, Number(e.target.value))}
|
|
38
|
+
className="w-full h-2.5 rounded-full appearance-none bg-muted cursor-pointer"
|
|
39
|
+
style={{ accentColor: "var(--primary)" }}
|
|
40
|
+
/>
|
|
41
|
+
</div>
|
|
42
|
+
);
|
|
43
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import type { ReactNode } from "react";
|
|
4
|
+
import { cn } from "@neynar/ui/utils";
|
|
5
|
+
|
|
6
|
+
const VGAP: Record<string, string> = {
|
|
7
|
+
none: "gap-0",
|
|
8
|
+
sm: "gap-2",
|
|
9
|
+
md: "gap-4",
|
|
10
|
+
lg: "gap-6",
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const HGAP: Record<string, string> = {
|
|
14
|
+
none: "gap-0",
|
|
15
|
+
sm: "gap-1",
|
|
16
|
+
md: "gap-2",
|
|
17
|
+
lg: "gap-3",
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const JUSTIFY: Record<string, string> = {
|
|
21
|
+
start: "justify-start",
|
|
22
|
+
center: "justify-center",
|
|
23
|
+
end: "justify-end",
|
|
24
|
+
between: "justify-between",
|
|
25
|
+
around: "justify-around",
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export function SnapStack({
|
|
29
|
+
element: { props },
|
|
30
|
+
children,
|
|
31
|
+
}: {
|
|
32
|
+
element: { props: Record<string, unknown> };
|
|
33
|
+
children?: ReactNode;
|
|
34
|
+
}) {
|
|
35
|
+
const direction = String(props.direction ?? "vertical");
|
|
36
|
+
const gapKey = String(props.gap ?? "md");
|
|
37
|
+
const isHorizontal = direction === "horizontal";
|
|
38
|
+
const gap = isHorizontal
|
|
39
|
+
? (HGAP[gapKey] ?? "gap-2")
|
|
40
|
+
: (VGAP[gapKey] ?? "gap-4");
|
|
41
|
+
const justify = props.justify ? JUSTIFY[String(props.justify)] : undefined;
|
|
42
|
+
|
|
43
|
+
return (
|
|
44
|
+
<div
|
|
45
|
+
className={cn(
|
|
46
|
+
"flex w-full",
|
|
47
|
+
isHorizontal ? "flex-row items-center flex-wrap" : "flex-col",
|
|
48
|
+
gap,
|
|
49
|
+
justify,
|
|
50
|
+
)}
|
|
51
|
+
>
|
|
52
|
+
{children}
|
|
53
|
+
</div>
|
|
54
|
+
);
|
|
55
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useId } from "react";
|
|
4
|
+
import { useStateStore } from "@json-render/react";
|
|
5
|
+
import { Label } from "@neynar/ui/label";
|
|
6
|
+
import { Switch } from "@neynar/ui/switch";
|
|
7
|
+
import { useColorMode } from "@neynar/ui/color-mode";
|
|
8
|
+
import { cn } from "@neynar/ui/utils";
|
|
9
|
+
import { useSnapAccentScopeStyle } from "../hooks/use-snap-accent";
|
|
10
|
+
|
|
11
|
+
export function SnapSwitch({
|
|
12
|
+
element: { props },
|
|
13
|
+
}: {
|
|
14
|
+
element: { props: Record<string, unknown> };
|
|
15
|
+
}) {
|
|
16
|
+
const id = useId();
|
|
17
|
+
const { get, set } = useStateStore();
|
|
18
|
+
const { mode } = useColorMode();
|
|
19
|
+
const accentStyle = useSnapAccentScopeStyle();
|
|
20
|
+
const name = String(props.name ?? "switch");
|
|
21
|
+
const path = `/inputs/${name}`;
|
|
22
|
+
const label = props.label ? String(props.label) : undefined;
|
|
23
|
+
const fallback = Boolean(props.defaultChecked ?? false);
|
|
24
|
+
const raw = get(path);
|
|
25
|
+
const checked = raw === undefined || raw === null ? fallback : Boolean(raw);
|
|
26
|
+
|
|
27
|
+
return (
|
|
28
|
+
<div className="flex items-center justify-between gap-3">
|
|
29
|
+
{label && (
|
|
30
|
+
<Label htmlFor={id} className="text-foreground font-normal">
|
|
31
|
+
{label}
|
|
32
|
+
</Label>
|
|
33
|
+
)}
|
|
34
|
+
<Switch
|
|
35
|
+
id={id}
|
|
36
|
+
checked={checked}
|
|
37
|
+
onCheckedChange={(v) => set(path, v)}
|
|
38
|
+
style={accentStyle}
|
|
39
|
+
className={cn(
|
|
40
|
+
mode === "light" &&
|
|
41
|
+
"data-unchecked:!bg-border data-unchecked:!border-(--input-border)",
|
|
42
|
+
)}
|
|
43
|
+
/>
|
|
44
|
+
</div>
|
|
45
|
+
);
|
|
46
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { Text, Title } from "@neynar/ui/typography";
|
|
4
|
+
|
|
5
|
+
const SIZE_MAP = {
|
|
6
|
+
lg: { component: "title", textSize: undefined, order: 3 },
|
|
7
|
+
md: { component: "text", textSize: "base" as const, order: undefined },
|
|
8
|
+
sm: { component: "text", textSize: "sm" as const, order: undefined },
|
|
9
|
+
} as const;
|
|
10
|
+
|
|
11
|
+
const WEIGHT_MAP = {
|
|
12
|
+
bold: "bold",
|
|
13
|
+
medium: "medium",
|
|
14
|
+
normal: "normal",
|
|
15
|
+
} as const;
|
|
16
|
+
|
|
17
|
+
export function SnapText({
|
|
18
|
+
element: { props },
|
|
19
|
+
}: {
|
|
20
|
+
element: { props: Record<string, unknown> };
|
|
21
|
+
}) {
|
|
22
|
+
const content = String(props.content ?? "");
|
|
23
|
+
const size = String(props.size ?? "md") as "lg" | "md" | "sm";
|
|
24
|
+
const weight = props.weight ? String(props.weight) as "bold" | "medium" | "normal" : undefined;
|
|
25
|
+
const align = (props.align as "left" | "center" | "right") ?? undefined;
|
|
26
|
+
const config = SIZE_MAP[size] ?? SIZE_MAP.md;
|
|
27
|
+
|
|
28
|
+
const alignClass = align === "center" ? "text-center" : align === "right" ? "text-right" : "";
|
|
29
|
+
|
|
30
|
+
if (config.component === "title") {
|
|
31
|
+
return (
|
|
32
|
+
<Title order={config.order} weight={weight ?? "bold"} className={`flex-1 ${alignClass}`}>
|
|
33
|
+
{content}
|
|
34
|
+
</Title>
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<Text size={config.textSize} weight={weight} align={align} className="flex-1">
|
|
40
|
+
{content}
|
|
41
|
+
</Text>
|
|
42
|
+
);
|
|
43
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useStateStore } from "@json-render/react";
|
|
4
|
+
import { Label } from "@neynar/ui/label";
|
|
5
|
+
import { cn } from "@neynar/ui/utils";
|
|
6
|
+
import { useSnapAccentScopeStyle } from "../hooks/use-snap-accent";
|
|
7
|
+
|
|
8
|
+
export function SnapToggleGroup({
|
|
9
|
+
element: { props },
|
|
10
|
+
}: {
|
|
11
|
+
element: { props: Record<string, unknown> };
|
|
12
|
+
}) {
|
|
13
|
+
const { get, set } = useStateStore();
|
|
14
|
+
const accentStyle = useSnapAccentScopeStyle();
|
|
15
|
+
const name = String(props.name ?? "toggle_group");
|
|
16
|
+
const path = `/inputs/${name}`;
|
|
17
|
+
const label = props.label ? String(props.label) : undefined;
|
|
18
|
+
const isMultiple = Boolean(props.multiple);
|
|
19
|
+
const orientation = String(props.orientation ?? "horizontal");
|
|
20
|
+
const options = Array.isArray(props.options)
|
|
21
|
+
? (props.options as string[])
|
|
22
|
+
: [];
|
|
23
|
+
|
|
24
|
+
const raw = get(path);
|
|
25
|
+
const defaultValue = props.defaultValue;
|
|
26
|
+
|
|
27
|
+
const selected = (() => {
|
|
28
|
+
if (raw !== undefined && raw !== null) {
|
|
29
|
+
return isMultiple
|
|
30
|
+
? Array.isArray(raw) ? (raw as string[]) : []
|
|
31
|
+
: typeof raw === "string" ? [raw] : [];
|
|
32
|
+
}
|
|
33
|
+
if (defaultValue !== undefined) {
|
|
34
|
+
return Array.isArray(defaultValue) ? defaultValue as string[] : [String(defaultValue)];
|
|
35
|
+
}
|
|
36
|
+
return [];
|
|
37
|
+
})();
|
|
38
|
+
|
|
39
|
+
const toggle = (opt: string) => {
|
|
40
|
+
if (isMultiple) {
|
|
41
|
+
const current = Array.isArray(raw) ? (raw as string[]) : [];
|
|
42
|
+
if (current.includes(opt)) {
|
|
43
|
+
set(path, current.filter((v) => v !== opt));
|
|
44
|
+
} else {
|
|
45
|
+
set(path, [...current, opt]);
|
|
46
|
+
}
|
|
47
|
+
} else {
|
|
48
|
+
set(path, opt);
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const isVertical = orientation === "vertical";
|
|
53
|
+
|
|
54
|
+
return (
|
|
55
|
+
<div className="w-full space-y-1.5" style={accentStyle}>
|
|
56
|
+
{label && <Label>{label}</Label>}
|
|
57
|
+
<div
|
|
58
|
+
className={cn(
|
|
59
|
+
"flex gap-1 rounded-lg bg-border/20 p-1",
|
|
60
|
+
isVertical ? "flex-col" : "flex-row",
|
|
61
|
+
)}
|
|
62
|
+
>
|
|
63
|
+
{options.map((opt) => {
|
|
64
|
+
const isSelected = selected.includes(opt);
|
|
65
|
+
return (
|
|
66
|
+
<button
|
|
67
|
+
key={opt}
|
|
68
|
+
type="button"
|
|
69
|
+
onClick={() => toggle(opt)}
|
|
70
|
+
className={cn(
|
|
71
|
+
"rounded-md px-3 py-2 text-sm font-medium transition-colors",
|
|
72
|
+
isVertical ? "w-full" : "flex-1",
|
|
73
|
+
isSelected
|
|
74
|
+
? "bg-primary text-primary-foreground"
|
|
75
|
+
: "text-foreground hover:bg-border/30",
|
|
76
|
+
)}
|
|
77
|
+
>
|
|
78
|
+
{opt}
|
|
79
|
+
</button>
|
|
80
|
+
);
|
|
81
|
+
})}
|
|
82
|
+
</div>
|
|
83
|
+
</div>
|
|
84
|
+
);
|
|
85
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
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
|
+
}
|