@farcaster/snap 1.10.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/constants.d.ts +8 -0
- package/dist/constants.js +10 -0
- package/dist/index.d.ts +1 -2
- package/dist/index.js +1 -2
- package/dist/react/catalog-renderer.js +4 -0
- package/dist/react/components/action-button.js +18 -10
- package/dist/react/components/badge.js +8 -6
- package/dist/react/components/bar-chart.d.ts +5 -0
- package/dist/react/components/bar-chart.js +26 -0
- package/dist/react/components/cell-grid.d.ts +5 -0
- package/dist/react/components/cell-grid.js +82 -0
- 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/catalog-renderer.js +4 -0
- package/dist/react-native/components/snap-action-button.js +8 -18
- package/dist/react-native/components/snap-bar-chart.d.ts +2 -0
- package/dist/react-native/components/snap-bar-chart.js +39 -0
- package/dist/react-native/components/snap-cell-grid.d.ts +2 -0
- package/dist/react-native/components/snap-cell-grid.js +96 -0
- 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/dist/ui/bar-chart.d.ts +30 -0
- package/dist/ui/bar-chart.js +30 -0
- package/dist/ui/catalog.d.ts +65 -0
- package/dist/ui/catalog.js +10 -0
- package/dist/ui/cell-grid.d.ts +33 -0
- package/dist/ui/cell-grid.js +38 -0
- package/dist/ui/index.d.ts +4 -0
- package/dist/ui/index.js +2 -0
- package/llms.txt +16 -1
- package/package.json +1 -1
- package/src/constants.ts +12 -0
- package/src/index.ts +6 -2
- package/src/react/catalog-renderer.tsx +4 -0
- package/src/react/components/action-button.tsx +24 -17
- package/src/react/components/badge.tsx +14 -17
- package/src/react/components/bar-chart.tsx +69 -0
- package/src/react/components/cell-grid.tsx +124 -0
- 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/catalog-renderer.tsx +4 -0
- package/src/react-native/components/snap-action-button.tsx +8 -20
- package/src/react-native/components/snap-bar-chart.tsx +73 -0
- package/src/react-native/components/snap-cell-grid.tsx +152 -0
- 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/src/ui/bar-chart.ts +38 -0
- package/src/ui/catalog.ts +12 -0
- package/src/ui/cell-grid.ts +48 -0
- package/src/ui/index.ts +6 -0
- package/dist/middleware.d.ts +0 -3
- package/dist/middleware.js +0 -3
- package/dist/react/hooks/use-snap-accent.d.ts +0 -13
- package/dist/react/hooks/use-snap-accent.js +0 -32
- package/src/middleware.ts +0 -7
- package/src/react/hooks/use-snap-accent.ts +0 -45
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { useMemo } from "react";
|
|
3
|
+
import { useStateStore } from "@json-render/react";
|
|
4
|
+
import { useColorMode } from "@neynar/ui/color-mode";
|
|
5
|
+
import { resolveSnapPaletteHex } from "../lib/resolve-palette-hex.js";
|
|
6
|
+
import { useSnapPreviewPageAccent } from "../accent-context.js";
|
|
7
|
+
import { PALETTE_DARK_HEX, PALETTE_LIGHT_HEX } from "@farcaster/snap";
|
|
8
|
+
/** Readable foreground color (black or white) for a given hex background. */
|
|
9
|
+
export function pickForegroundForBg(hex) {
|
|
10
|
+
const h = hex.replace(/^#/, "");
|
|
11
|
+
if (h.length !== 6)
|
|
12
|
+
return "#ffffff";
|
|
13
|
+
const r = Number.parseInt(h.slice(0, 2), 16);
|
|
14
|
+
const g = Number.parseInt(h.slice(2, 4), 16);
|
|
15
|
+
const b = Number.parseInt(h.slice(4, 6), 16);
|
|
16
|
+
const yiq = (r * 299 + g * 587 + b * 114) / 1000;
|
|
17
|
+
return yiq >= 180 ? "#0a0a0a" : "#ffffff";
|
|
18
|
+
}
|
|
19
|
+
const NEUTRAL_LIGHT = {
|
|
20
|
+
text: "#111111",
|
|
21
|
+
textMuted: "#6B7280",
|
|
22
|
+
border: "#E5E7EB",
|
|
23
|
+
muted: "rgba(0,0,0,0.06)",
|
|
24
|
+
surface: "#ffffff",
|
|
25
|
+
inputBorder: "#E5E7EB",
|
|
26
|
+
inputBg: "rgba(0,0,0,0.06)",
|
|
27
|
+
};
|
|
28
|
+
const NEUTRAL_DARK = {
|
|
29
|
+
text: "#FAFAFA",
|
|
30
|
+
textMuted: "#A1A1AA",
|
|
31
|
+
border: "#2D2D44",
|
|
32
|
+
muted: "rgba(255,255,255,0.03)",
|
|
33
|
+
surface: "#23262f",
|
|
34
|
+
inputBorder: "#3F3F46",
|
|
35
|
+
inputBg: "rgba(255,255,255,0.03)",
|
|
36
|
+
};
|
|
37
|
+
function buildSnapColors(accentName, mode) {
|
|
38
|
+
const accent = resolveSnapPaletteHex(accentName, mode);
|
|
39
|
+
const accentFg = pickForegroundForBg(accent);
|
|
40
|
+
const neutrals = mode === "dark" ? NEUTRAL_DARK : NEUTRAL_LIGHT;
|
|
41
|
+
const paletteMap = mode === "dark" ? PALETTE_DARK_HEX : PALETTE_LIGHT_HEX;
|
|
42
|
+
const accentHover = mode === "light"
|
|
43
|
+
? `color-mix(in srgb, ${accent} 82%, #000000)`
|
|
44
|
+
: `color-mix(in srgb, ${accent} 78%, #ffffff)`;
|
|
45
|
+
const outlineHover = `color-mix(in srgb, ${accent} 14%, ${neutrals.surface})`;
|
|
46
|
+
const paletteHex = (name) => resolveSnapPaletteHex(name, mode);
|
|
47
|
+
const colorHex = (name) => {
|
|
48
|
+
if (!name || name === "accent")
|
|
49
|
+
return accent;
|
|
50
|
+
if (Object.hasOwn(paletteMap, name))
|
|
51
|
+
return paletteMap[name];
|
|
52
|
+
return accent;
|
|
53
|
+
};
|
|
54
|
+
return {
|
|
55
|
+
accent,
|
|
56
|
+
accentFg,
|
|
57
|
+
accentHover,
|
|
58
|
+
outlineHover,
|
|
59
|
+
...neutrals,
|
|
60
|
+
mode,
|
|
61
|
+
paletteHex,
|
|
62
|
+
colorHex,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Returns fully resolved color values for snap components.
|
|
67
|
+
* All colors are concrete hex values (or color-mix expressions for hover states)
|
|
68
|
+
* so they can be used as inline styles, independent of host app CSS.
|
|
69
|
+
*/
|
|
70
|
+
export function useSnapColors() {
|
|
71
|
+
const { get } = useStateStore();
|
|
72
|
+
const { mode } = useColorMode();
|
|
73
|
+
const pageAccent = useSnapPreviewPageAccent();
|
|
74
|
+
const fromState = get("/theme/accent");
|
|
75
|
+
const accentRaw = (typeof pageAccent === "string" && pageAccent.length > 0
|
|
76
|
+
? pageAccent
|
|
77
|
+
: fromState) ?? undefined;
|
|
78
|
+
const accentName = typeof accentRaw === "string" && accentRaw.length > 0
|
|
79
|
+
? accentRaw
|
|
80
|
+
: "purple";
|
|
81
|
+
return useMemo(() => buildSnapColors(accentName, mode), [accentName, mode]);
|
|
82
|
+
}
|
|
@@ -14,6 +14,8 @@ import { SnapStack } from "./components/snap-stack.js";
|
|
|
14
14
|
import { SnapSwitch } from "./components/snap-switch.js";
|
|
15
15
|
import { SnapText } from "./components/snap-text.js";
|
|
16
16
|
import { SnapToggleGroup } from "./components/snap-toggle-group.js";
|
|
17
|
+
import { SnapBarChart } from "./components/snap-bar-chart.js";
|
|
18
|
+
import { SnapCellGrid } from "./components/snap-cell-grid.js";
|
|
17
19
|
/**
|
|
18
20
|
* Maps snap json-render catalog types to React Native primitives.
|
|
19
21
|
* Keys match the snap wire-format `type` strings exactly (snake_case).
|
|
@@ -33,4 +35,6 @@ export const SnapCatalogView = createRenderer(snapJsonRenderCatalog, {
|
|
|
33
35
|
switch: SnapSwitch,
|
|
34
36
|
text: SnapText,
|
|
35
37
|
toggle_group: SnapToggleGroup,
|
|
38
|
+
bar_chart: SnapBarChart,
|
|
39
|
+
cell_grid: SnapCellGrid,
|
|
36
40
|
});
|
|
@@ -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
|
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { StyleSheet, Text, View } from "react-native";
|
|
3
|
+
import { useSnapPalette } from "../use-snap-palette.js";
|
|
4
|
+
import { useSnapTheme } from "../theme.js";
|
|
5
|
+
export function SnapBarChart({ element: { props }, }) {
|
|
6
|
+
const { accentHex, hex } = useSnapPalette();
|
|
7
|
+
const { colors } = useSnapTheme();
|
|
8
|
+
const bars = Array.isArray(props.bars) ? props.bars : [];
|
|
9
|
+
const chartColor = String(props.color ?? "accent");
|
|
10
|
+
const maxVal = props.max != null
|
|
11
|
+
? Number(props.max)
|
|
12
|
+
: Math.max(...bars.map((b) => Number(b.value ?? 0)), 1);
|
|
13
|
+
function barFill(bar) {
|
|
14
|
+
if (bar.color)
|
|
15
|
+
return hex(bar.color);
|
|
16
|
+
if (chartColor !== "accent")
|
|
17
|
+
return hex(chartColor);
|
|
18
|
+
return accentHex;
|
|
19
|
+
}
|
|
20
|
+
return (_jsx(View, { style: styles.wrap, children: bars.map((bar, i) => {
|
|
21
|
+
const value = Number(bar.value ?? 0);
|
|
22
|
+
const pct = maxVal > 0 ? Math.min(100, (value / maxVal) * 100) : 0;
|
|
23
|
+
return (_jsxs(View, { style: styles.row, children: [_jsx(Text, { style: [styles.label, { color: colors.textSecondary }], numberOfLines: 1, children: String(bar.label ?? "") }), _jsx(View, { style: [styles.track, { backgroundColor: colors.muted }], children: _jsx(View, { style: [
|
|
24
|
+
styles.fill,
|
|
25
|
+
{
|
|
26
|
+
width: `${pct}%`,
|
|
27
|
+
backgroundColor: barFill(bar),
|
|
28
|
+
},
|
|
29
|
+
] }) }), _jsx(Text, { style: [styles.value, { color: colors.textSecondary }], children: value })] }, i));
|
|
30
|
+
}) }));
|
|
31
|
+
}
|
|
32
|
+
const styles = StyleSheet.create({
|
|
33
|
+
wrap: { flex: 1, width: "100%", gap: 8 },
|
|
34
|
+
row: { flexDirection: "row", alignItems: "center", gap: 8 },
|
|
35
|
+
label: { width: 80, fontSize: 12, textAlign: "right" },
|
|
36
|
+
track: { flex: 1, height: 10, borderRadius: 9999, overflow: "hidden" },
|
|
37
|
+
fill: { height: "100%", borderRadius: 9999 },
|
|
38
|
+
value: { width: 32, fontSize: 12, fontVariant: ["tabular-nums"] },
|
|
39
|
+
});
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { StyleSheet, Text, View, Pressable } from "react-native";
|
|
3
|
+
import { useStateStore } from "@json-render/react-native";
|
|
4
|
+
import { useSnapPalette } from "../use-snap-palette.js";
|
|
5
|
+
import { useSnapTheme } from "../theme.js";
|
|
6
|
+
import { POST_GRID_TAP_KEY } from "@farcaster/snap";
|
|
7
|
+
export function SnapCellGrid({ element: { props }, }) {
|
|
8
|
+
const { hex, appearance } = useSnapPalette();
|
|
9
|
+
const { colors } = useSnapTheme();
|
|
10
|
+
const { get, set } = useStateStore();
|
|
11
|
+
const cols = Number(props.cols ?? 2);
|
|
12
|
+
const rows = Number(props.rows ?? 2);
|
|
13
|
+
const cells = Array.isArray(props.cells) ? props.cells : [];
|
|
14
|
+
const gap = String(props.gap ?? "sm");
|
|
15
|
+
const gapMap = { none: 0, sm: 1, md: 2, lg: 4 };
|
|
16
|
+
const gapPx = gapMap[gap] ?? 1;
|
|
17
|
+
const select = String(props.select ?? "off");
|
|
18
|
+
const interactive = select !== "off";
|
|
19
|
+
const isMultiple = select === "multiple";
|
|
20
|
+
const name = props.name ? String(props.name) : POST_GRID_TAP_KEY;
|
|
21
|
+
const tapPath = `/inputs/${name}`;
|
|
22
|
+
const tapRaw = get(tapPath);
|
|
23
|
+
const selectedSet = new Set();
|
|
24
|
+
if (typeof tapRaw === "string" && tapRaw.length > 0) {
|
|
25
|
+
for (const part of tapRaw.split("|")) {
|
|
26
|
+
if (part.includes(","))
|
|
27
|
+
selectedSet.add(part);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
const isSelected = (r, c) => selectedSet.has(`${r},${c}`);
|
|
31
|
+
const handleTap = (r, c) => {
|
|
32
|
+
const key = `${r},${c}`;
|
|
33
|
+
if (isMultiple) {
|
|
34
|
+
const next = new Set(selectedSet);
|
|
35
|
+
if (next.has(key))
|
|
36
|
+
next.delete(key);
|
|
37
|
+
else
|
|
38
|
+
next.add(key);
|
|
39
|
+
set(tapPath, [...next].join("|"));
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
set(tapPath, key);
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
const cellMap = new Map();
|
|
46
|
+
for (const c of cells) {
|
|
47
|
+
cellMap.set(`${Number(c.row)},${Number(c.col)}`, {
|
|
48
|
+
color: c.color,
|
|
49
|
+
content: c.content != null ? String(c.content) : undefined,
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
const ringOuter = appearance === "dark" ? "#fff" : "#000";
|
|
53
|
+
const ringInner = appearance === "dark" ? "#000" : "#fff";
|
|
54
|
+
const rowEls = [];
|
|
55
|
+
for (let r = 0; r < rows; r++) {
|
|
56
|
+
const rowCells = [];
|
|
57
|
+
for (let c = 0; c < cols; c++) {
|
|
58
|
+
const cell = cellMap.get(`${r},${c}`);
|
|
59
|
+
const selected = interactive && isSelected(r, c);
|
|
60
|
+
const bg = cell?.color ? hex(cell.color) : "transparent";
|
|
61
|
+
const cellContent = cell?.content ? (_jsx(Text, { style: [styles.cellText, { color: colors.textPrimary }], children: cell.content })) : null;
|
|
62
|
+
// Two-tone ring: outer View with contrasting border, inner View with inverse border
|
|
63
|
+
const cellView = selected ? (_jsx(View, { style: [styles.cell, { borderWidth: 1, borderColor: ringOuter, borderRadius: 4 }], children: _jsx(View, { style: [
|
|
64
|
+
styles.innerCell,
|
|
65
|
+
{ backgroundColor: bg, borderWidth: 1, borderColor: ringInner, borderRadius: 3 },
|
|
66
|
+
], children: cellContent }) })) : (_jsx(View, { style: [styles.cell, { backgroundColor: bg }], children: cellContent }));
|
|
67
|
+
rowCells.push(interactive ? (_jsx(Pressable, { onPress: () => handleTap(r, c), style: styles.cellWrap, children: cellView }, `${r}-${c}`)) : (_jsx(View, { style: styles.cellWrap, children: cellView }, `${r}-${c}`)));
|
|
68
|
+
}
|
|
69
|
+
rowEls.push(_jsx(View, { style: [styles.gridRow, { gap: gapPx }], children: rowCells }, r));
|
|
70
|
+
}
|
|
71
|
+
const selectionLabel = interactive && selectedSet.size > 0
|
|
72
|
+
? `inputs.${name}: ${[...selectedSet].join(isMultiple ? " | " : "")}`
|
|
73
|
+
: 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
|
+
}
|
|
76
|
+
const styles = StyleSheet.create({
|
|
77
|
+
wrap: { width: "100%" },
|
|
78
|
+
gridRow: { flexDirection: "row" },
|
|
79
|
+
cellWrap: { flex: 1 },
|
|
80
|
+
cell: {
|
|
81
|
+
flex: 1,
|
|
82
|
+
minHeight: 28,
|
|
83
|
+
borderRadius: 4,
|
|
84
|
+
alignItems: "center",
|
|
85
|
+
justifyContent: "center",
|
|
86
|
+
},
|
|
87
|
+
innerCell: {
|
|
88
|
+
flex: 1,
|
|
89
|
+
width: "100%",
|
|
90
|
+
minHeight: 26,
|
|
91
|
+
alignItems: "center",
|
|
92
|
+
justifyContent: "center",
|
|
93
|
+
},
|
|
94
|
+
cellText: { fontSize: 12, fontWeight: "600" },
|
|
95
|
+
selectionText: { fontSize: 11, fontFamily: "monospace", marginTop: 6 },
|
|
96
|
+
});
|
|
@@ -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
|
});
|
|
@@ -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",
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export declare const barChartProps: z.ZodObject<{
|
|
3
|
+
bars: z.ZodArray<z.ZodObject<{
|
|
4
|
+
label: z.ZodString;
|
|
5
|
+
value: z.ZodNumber;
|
|
6
|
+
color: z.ZodOptional<z.ZodEnum<{
|
|
7
|
+
gray: "gray";
|
|
8
|
+
blue: "blue";
|
|
9
|
+
red: "red";
|
|
10
|
+
amber: "amber";
|
|
11
|
+
green: "green";
|
|
12
|
+
teal: "teal";
|
|
13
|
+
purple: "purple";
|
|
14
|
+
pink: "pink";
|
|
15
|
+
}>>;
|
|
16
|
+
}, z.core.$strip>>;
|
|
17
|
+
max: z.ZodOptional<z.ZodNumber>;
|
|
18
|
+
color: z.ZodOptional<z.ZodEnum<{
|
|
19
|
+
gray: "gray";
|
|
20
|
+
blue: "blue";
|
|
21
|
+
red: "red";
|
|
22
|
+
amber: "amber";
|
|
23
|
+
green: "green";
|
|
24
|
+
teal: "teal";
|
|
25
|
+
purple: "purple";
|
|
26
|
+
pink: "pink";
|
|
27
|
+
accent: "accent";
|
|
28
|
+
}>>;
|
|
29
|
+
}, z.core.$strip>;
|
|
30
|
+
export type BarChartProps = z.infer<typeof barChartProps>;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { BAR_CHART_COLOR_VALUES, PALETTE_COLOR_VALUES } from "../colors.js";
|
|
3
|
+
import { BAR_CHART_MAX_BARS, BAR_CHART_LABEL_MAX_CHARS, } from "../constants.js";
|
|
4
|
+
export const barChartProps = z
|
|
5
|
+
.object({
|
|
6
|
+
bars: z
|
|
7
|
+
.array(z.object({
|
|
8
|
+
label: z.string().min(1).max(BAR_CHART_LABEL_MAX_CHARS),
|
|
9
|
+
value: z.number().nonnegative(),
|
|
10
|
+
color: z.enum(PALETTE_COLOR_VALUES).optional(),
|
|
11
|
+
}))
|
|
12
|
+
.min(1)
|
|
13
|
+
.max(BAR_CHART_MAX_BARS),
|
|
14
|
+
max: z.number().nonnegative().optional(),
|
|
15
|
+
color: z.enum(BAR_CHART_COLOR_VALUES).optional(),
|
|
16
|
+
})
|
|
17
|
+
.superRefine((val, ctx) => {
|
|
18
|
+
if (val.max !== undefined) {
|
|
19
|
+
for (let i = 0; i < val.bars.length; i++) {
|
|
20
|
+
const bar = val.bars[i];
|
|
21
|
+
if (bar.value > val.max) {
|
|
22
|
+
ctx.addIssue({
|
|
23
|
+
code: "custom",
|
|
24
|
+
message: `bar value (${bar.value}) exceeds chart max (${val.max})`,
|
|
25
|
+
path: ["bars", i, "value"],
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
});
|
package/dist/ui/catalog.d.ts
CHANGED
|
@@ -335,6 +335,71 @@ export declare const snapJsonRenderCatalog: import("@json-render/core").Catalog<
|
|
|
335
335
|
}, z.core.$strip>;
|
|
336
336
|
description: string;
|
|
337
337
|
};
|
|
338
|
+
bar_chart: {
|
|
339
|
+
props: z.ZodObject<{
|
|
340
|
+
bars: z.ZodArray<z.ZodObject<{
|
|
341
|
+
label: z.ZodString;
|
|
342
|
+
value: z.ZodNumber;
|
|
343
|
+
color: z.ZodOptional<z.ZodEnum<{
|
|
344
|
+
gray: "gray";
|
|
345
|
+
blue: "blue";
|
|
346
|
+
red: "red";
|
|
347
|
+
amber: "amber";
|
|
348
|
+
green: "green";
|
|
349
|
+
teal: "teal";
|
|
350
|
+
purple: "purple";
|
|
351
|
+
pink: "pink";
|
|
352
|
+
}>>;
|
|
353
|
+
}, z.core.$strip>>;
|
|
354
|
+
max: z.ZodOptional<z.ZodNumber>;
|
|
355
|
+
color: z.ZodOptional<z.ZodEnum<{
|
|
356
|
+
gray: "gray";
|
|
357
|
+
blue: "blue";
|
|
358
|
+
red: "red";
|
|
359
|
+
amber: "amber";
|
|
360
|
+
green: "green";
|
|
361
|
+
teal: "teal";
|
|
362
|
+
purple: "purple";
|
|
363
|
+
pink: "pink";
|
|
364
|
+
accent: "accent";
|
|
365
|
+
}>>;
|
|
366
|
+
}, z.core.$strip>;
|
|
367
|
+
description: string;
|
|
368
|
+
};
|
|
369
|
+
cell_grid: {
|
|
370
|
+
props: z.ZodObject<{
|
|
371
|
+
name: z.ZodOptional<z.ZodString>;
|
|
372
|
+
cols: z.ZodNumber;
|
|
373
|
+
rows: z.ZodNumber;
|
|
374
|
+
cells: z.ZodArray<z.ZodObject<{
|
|
375
|
+
row: z.ZodNumber;
|
|
376
|
+
col: z.ZodNumber;
|
|
377
|
+
color: z.ZodOptional<z.ZodEnum<{
|
|
378
|
+
gray: "gray";
|
|
379
|
+
blue: "blue";
|
|
380
|
+
red: "red";
|
|
381
|
+
amber: "amber";
|
|
382
|
+
green: "green";
|
|
383
|
+
teal: "teal";
|
|
384
|
+
purple: "purple";
|
|
385
|
+
pink: "pink";
|
|
386
|
+
}>>;
|
|
387
|
+
content: z.ZodOptional<z.ZodString>;
|
|
388
|
+
}, z.core.$strip>>;
|
|
389
|
+
gap: z.ZodOptional<z.ZodEnum<{
|
|
390
|
+
sm: "sm";
|
|
391
|
+
md: "md";
|
|
392
|
+
none: "none";
|
|
393
|
+
lg: "lg";
|
|
394
|
+
}>>;
|
|
395
|
+
select: z.ZodOptional<z.ZodEnum<{
|
|
396
|
+
multiple: "multiple";
|
|
397
|
+
off: "off";
|
|
398
|
+
single: "single";
|
|
399
|
+
}>>;
|
|
400
|
+
}, z.core.$strip>;
|
|
401
|
+
description: string;
|
|
402
|
+
};
|
|
338
403
|
};
|
|
339
404
|
actions: {
|
|
340
405
|
submit: {
|
package/dist/ui/catalog.js
CHANGED
|
@@ -15,6 +15,8 @@ import { separatorProps } from "./separator.js";
|
|
|
15
15
|
import { sliderProps } from "./slider.js";
|
|
16
16
|
import { stackProps } from "./stack.js";
|
|
17
17
|
import { textProps } from "./text.js";
|
|
18
|
+
import { barChartProps } from "./bar-chart.js";
|
|
19
|
+
import { cellGridProps } from "./cell-grid.js";
|
|
18
20
|
const snapClientParams = z.object({
|
|
19
21
|
client_action: z.record(z.string(), z.unknown()),
|
|
20
22
|
});
|
|
@@ -82,6 +84,14 @@ export const snapJsonRenderCatalog = defineCatalog(snapJsonRenderSchema, {
|
|
|
82
84
|
props: textProps,
|
|
83
85
|
description: "Text block — size: md (body, default), sm (caption). Optional weight and align.",
|
|
84
86
|
},
|
|
87
|
+
bar_chart: {
|
|
88
|
+
props: barChartProps,
|
|
89
|
+
description: "Horizontal bar chart — 1–6 bars with label, value, and optional per-bar color. Optional max and default color.",
|
|
90
|
+
},
|
|
91
|
+
cell_grid: {
|
|
92
|
+
props: cellGridProps,
|
|
93
|
+
description: "Cell grid — sparse colored cells on a rows×cols grid. Optional gap and selection mode (taps write to inputs[name]).",
|
|
94
|
+
},
|
|
85
95
|
},
|
|
86
96
|
actions: {
|
|
87
97
|
submit: {
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export declare const cellGridProps: z.ZodObject<{
|
|
3
|
+
name: z.ZodOptional<z.ZodString>;
|
|
4
|
+
cols: z.ZodNumber;
|
|
5
|
+
rows: z.ZodNumber;
|
|
6
|
+
cells: z.ZodArray<z.ZodObject<{
|
|
7
|
+
row: z.ZodNumber;
|
|
8
|
+
col: z.ZodNumber;
|
|
9
|
+
color: z.ZodOptional<z.ZodEnum<{
|
|
10
|
+
gray: "gray";
|
|
11
|
+
blue: "blue";
|
|
12
|
+
red: "red";
|
|
13
|
+
amber: "amber";
|
|
14
|
+
green: "green";
|
|
15
|
+
teal: "teal";
|
|
16
|
+
purple: "purple";
|
|
17
|
+
pink: "pink";
|
|
18
|
+
}>>;
|
|
19
|
+
content: z.ZodOptional<z.ZodString>;
|
|
20
|
+
}, z.core.$strip>>;
|
|
21
|
+
gap: z.ZodOptional<z.ZodEnum<{
|
|
22
|
+
sm: "sm";
|
|
23
|
+
md: "md";
|
|
24
|
+
none: "none";
|
|
25
|
+
lg: "lg";
|
|
26
|
+
}>>;
|
|
27
|
+
select: z.ZodOptional<z.ZodEnum<{
|
|
28
|
+
multiple: "multiple";
|
|
29
|
+
off: "off";
|
|
30
|
+
single: "single";
|
|
31
|
+
}>>;
|
|
32
|
+
}, z.core.$strip>;
|
|
33
|
+
export type CellGridProps = z.infer<typeof cellGridProps>;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { PALETTE_COLOR_VALUES } from "../colors.js";
|
|
3
|
+
import { GRID_MIN_COLS, GRID_MAX_COLS, GRID_MIN_ROWS, GRID_MAX_ROWS, GRID_GAP_VALUES, } from "../constants.js";
|
|
4
|
+
const cellGridCellSchema = z.object({
|
|
5
|
+
row: z.number().int().nonnegative(),
|
|
6
|
+
col: z.number().int().nonnegative(),
|
|
7
|
+
color: z.enum(PALETTE_COLOR_VALUES).optional(),
|
|
8
|
+
content: z.string().optional(),
|
|
9
|
+
});
|
|
10
|
+
export const cellGridProps = z
|
|
11
|
+
.object({
|
|
12
|
+
name: z.string().min(1).optional(),
|
|
13
|
+
cols: z.number().int().min(GRID_MIN_COLS).max(GRID_MAX_COLS),
|
|
14
|
+
rows: z.number().int().min(GRID_MIN_ROWS).max(GRID_MAX_ROWS),
|
|
15
|
+
cells: z.array(cellGridCellSchema),
|
|
16
|
+
gap: z.enum(GRID_GAP_VALUES).optional(),
|
|
17
|
+
select: z.enum(["off", "single", "multiple"]).optional(),
|
|
18
|
+
})
|
|
19
|
+
.superRefine((val, ctx) => {
|
|
20
|
+
const { cols, rows, cells } = val;
|
|
21
|
+
for (let i = 0; i < cells.length; i++) {
|
|
22
|
+
const c = cells[i];
|
|
23
|
+
if (c.row < 0 || c.row >= rows) {
|
|
24
|
+
ctx.addIssue({
|
|
25
|
+
code: "custom",
|
|
26
|
+
message: `cell_grid cell row ${c.row} out of bounds (0–${rows - 1})`,
|
|
27
|
+
path: ["cells", i, "row"],
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
if (c.col < 0 || c.col >= cols) {
|
|
31
|
+
ctx.addIssue({
|
|
32
|
+
code: "custom",
|
|
33
|
+
message: `cell_grid cell col ${c.col} out of bounds (0–${cols - 1})`,
|
|
34
|
+
path: ["cells", i, "col"],
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
});
|
package/dist/ui/index.d.ts
CHANGED
|
@@ -28,3 +28,7 @@ export { stackProps } from "./stack.js";
|
|
|
28
28
|
export type { StackProps } from "./stack.js";
|
|
29
29
|
export { textProps } from "./text.js";
|
|
30
30
|
export type { TextProps } from "./text.js";
|
|
31
|
+
export { barChartProps } from "./bar-chart.js";
|
|
32
|
+
export type { BarChartProps } from "./bar-chart.js";
|
|
33
|
+
export { cellGridProps } from "./cell-grid.js";
|
|
34
|
+
export type { CellGridProps } from "./cell-grid.js";
|
package/dist/ui/index.js
CHANGED
|
@@ -14,3 +14,5 @@ export { separatorProps } from "./separator.js";
|
|
|
14
14
|
export { sliderProps } from "./slider.js";
|
|
15
15
|
export { stackProps } from "./stack.js";
|
|
16
16
|
export { textProps } from "./text.js";
|
|
17
|
+
export { barChartProps } from "./bar-chart.js";
|
|
18
|
+
export { cellGridProps } from "./cell-grid.js";
|