@farcaster/snap 1.10.0 → 1.13.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/bar-chart.d.ts +5 -0
- package/dist/react/components/bar-chart.js +31 -0
- package/dist/react/components/cell-grid.d.ts +5 -0
- package/dist/react/components/cell-grid.js +86 -0
- package/dist/react-native/catalog-renderer.js +4 -0
- 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/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/bar-chart.tsx +67 -0
- package/src/react/components/cell-grid.tsx +131 -0
- package/src/react-native/catalog-renderer.tsx +4 -0
- package/src/react-native/components/snap-bar-chart.tsx +73 -0
- package/src/react-native/components/snap-cell-grid.tsx +152 -0
- 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/src/middleware.ts +0 -7
package/dist/constants.d.ts
CHANGED
|
@@ -1,3 +1,11 @@
|
|
|
1
1
|
export declare const SPEC_VERSION: "1.0";
|
|
2
2
|
export declare const MEDIA_TYPE: "application/vnd.farcaster.snap+json";
|
|
3
3
|
export declare const EFFECT_VALUES: readonly ["confetti"];
|
|
4
|
+
export declare const POST_GRID_TAP_KEY: "grid_tap";
|
|
5
|
+
export declare const GRID_MIN_COLS = 2;
|
|
6
|
+
export declare const GRID_MAX_COLS = 32;
|
|
7
|
+
export declare const GRID_MIN_ROWS = 2;
|
|
8
|
+
export declare const GRID_MAX_ROWS = 16;
|
|
9
|
+
export declare const GRID_GAP_VALUES: readonly ["none", "sm", "md", "lg"];
|
|
10
|
+
export declare const BAR_CHART_MAX_BARS = 6;
|
|
11
|
+
export declare const BAR_CHART_LABEL_MAX_CHARS = 40;
|
package/dist/constants.js
CHANGED
|
@@ -1,3 +1,13 @@
|
|
|
1
1
|
export const SPEC_VERSION = "1.0";
|
|
2
2
|
export const MEDIA_TYPE = "application/vnd.farcaster.snap+json";
|
|
3
3
|
export const EFFECT_VALUES = ["confetti"];
|
|
4
|
+
// ─── Pixel grid ────────────────────────────────────────
|
|
5
|
+
export const POST_GRID_TAP_KEY = "grid_tap";
|
|
6
|
+
export const GRID_MIN_COLS = 2;
|
|
7
|
+
export const GRID_MAX_COLS = 32;
|
|
8
|
+
export const GRID_MIN_ROWS = 2;
|
|
9
|
+
export const GRID_MAX_ROWS = 16;
|
|
10
|
+
export const GRID_GAP_VALUES = ["none", "sm", "md", "lg"];
|
|
11
|
+
// ─── Bar chart ─────────────────────────────────────────
|
|
12
|
+
export const BAR_CHART_MAX_BARS = 6;
|
|
13
|
+
export const BAR_CHART_LABEL_MAX_CHARS = 40;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
export type { Spec as SnapSpec, UIElement as SnapUIElement, } from "@json-render/core";
|
|
2
|
-
export { SPEC_VERSION, MEDIA_TYPE, EFFECT_VALUES } from "./constants.js";
|
|
2
|
+
export { SPEC_VERSION, MEDIA_TYPE, EFFECT_VALUES, POST_GRID_TAP_KEY, } from "./constants.js";
|
|
3
3
|
export { DEFAULT_THEME_ACCENT, PALETTE_COLOR, PALETTE_COLOR_ACCENT, PALETTE_COLOR_VALUES, PALETTE_LIGHT_HEX, PALETTE_DARK_HEX, type PaletteColor, } from "./colors.js";
|
|
4
4
|
export { ACTION_TYPE_GET, ACTION_TYPE_POST, snapResponseSchema, payloadSchema, type SnapAction, type SnapContext, type SnapResponse, type SnapHandlerResult, type SnapElementInput, type SnapSpecInput, type SnapFunction, type SnapPayload, } from "./schemas.js";
|
|
5
5
|
export { validateSnapResponse, type ValidationResult } from "./validator.js";
|
|
6
|
-
export { type Middleware, useMiddleware } from "./middleware.js";
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
export { SPEC_VERSION, MEDIA_TYPE, EFFECT_VALUES } from "./constants.js";
|
|
1
|
+
export { SPEC_VERSION, MEDIA_TYPE, EFFECT_VALUES, POST_GRID_TAP_KEY, } from "./constants.js";
|
|
2
2
|
export { DEFAULT_THEME_ACCENT, PALETTE_COLOR, PALETTE_COLOR_ACCENT, PALETTE_COLOR_VALUES, PALETTE_LIGHT_HEX, PALETTE_DARK_HEX, } from "./colors.js";
|
|
3
3
|
export { ACTION_TYPE_GET, ACTION_TYPE_POST, snapResponseSchema, payloadSchema, } from "./schemas.js";
|
|
4
4
|
export { validateSnapResponse } from "./validator.js";
|
|
5
|
-
export { useMiddleware } from "./middleware.js";
|
|
@@ -15,6 +15,8 @@ import { SnapStack } from "./components/stack.js";
|
|
|
15
15
|
import { SnapSwitch } from "./components/switch.js";
|
|
16
16
|
import { SnapText } from "./components/text.js";
|
|
17
17
|
import { SnapToggleGroup } from "./components/toggle-group.js";
|
|
18
|
+
import { SnapBarChart } from "./components/bar-chart.js";
|
|
19
|
+
import { SnapCellGrid } from "./components/cell-grid.js";
|
|
18
20
|
/**
|
|
19
21
|
* Maps snap json-render catalog types to React components.
|
|
20
22
|
* Keys match the snap wire-format `type` strings exactly.
|
|
@@ -34,4 +36,6 @@ export const SnapCatalogView = createRenderer(snapJsonRenderCatalog, {
|
|
|
34
36
|
switch: SnapSwitch,
|
|
35
37
|
text: SnapText,
|
|
36
38
|
toggle_group: SnapToggleGroup,
|
|
39
|
+
bar_chart: SnapBarChart,
|
|
40
|
+
cell_grid: SnapCellGrid,
|
|
37
41
|
});
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { PALETTE_LIGHT_HEX } from "@farcaster/snap";
|
|
4
|
+
import { useSnapAccentScopeStyle } from "../hooks/use-snap-accent.js";
|
|
5
|
+
export function SnapBarChart({ element: { props }, }) {
|
|
6
|
+
const accentStyle = useSnapAccentScopeStyle();
|
|
7
|
+
const bars = Array.isArray(props.bars) ? props.bars : [];
|
|
8
|
+
const chartColor = String(props.color ?? "accent");
|
|
9
|
+
const maxVal = props.max != null
|
|
10
|
+
? Number(props.max)
|
|
11
|
+
: Math.max(...bars.map((b) => Number(b.value ?? 0)), 1);
|
|
12
|
+
function barColor(bar) {
|
|
13
|
+
if (bar.color && bar.color in PALETTE_LIGHT_HEX) {
|
|
14
|
+
return `var(--snap-color-${bar.color}, ${PALETTE_LIGHT_HEX[bar.color]})`;
|
|
15
|
+
}
|
|
16
|
+
if (chartColor !== "accent" && chartColor in PALETTE_LIGHT_HEX) {
|
|
17
|
+
return `var(--snap-color-${chartColor}, ${PALETTE_LIGHT_HEX[chartColor]})`;
|
|
18
|
+
}
|
|
19
|
+
return "var(--primary)";
|
|
20
|
+
}
|
|
21
|
+
return (_jsx("div", { className: "flex w-full flex-col gap-2", style: accentStyle, children: bars.map((bar, i) => {
|
|
22
|
+
const value = Number(bar.value ?? 0);
|
|
23
|
+
const pct = maxVal > 0 ? Math.min(100, (value / maxVal) * 100) : 0;
|
|
24
|
+
const fill = barColor(bar);
|
|
25
|
+
return (_jsxs("div", { className: "flex w-full items-center gap-2", children: [_jsx("span", { className: "text-muted-foreground w-20 shrink-0 truncate text-right text-xs", children: String(bar.label ?? "") }), _jsx("div", { className: "bg-muted h-2.5 flex-1 overflow-hidden rounded-full", children: _jsx("div", { className: "h-full rounded-full transition-all", style: {
|
|
26
|
+
width: `${pct}%`,
|
|
27
|
+
minWidth: pct > 0 ? 4 : 0,
|
|
28
|
+
background: fill,
|
|
29
|
+
} }) }), _jsx("span", { className: "text-muted-foreground w-8 shrink-0 text-xs tabular-nums", children: value })] }, i));
|
|
30
|
+
}) }));
|
|
31
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useStateStore } from "@json-render/react";
|
|
4
|
+
import { cn } from "@neynar/ui/utils";
|
|
5
|
+
import { POST_GRID_TAP_KEY, PALETTE_LIGHT_HEX } from "@farcaster/snap";
|
|
6
|
+
import { useSnapAccentScopeStyle } from "../hooks/use-snap-accent.js";
|
|
7
|
+
import { useColorMode } from "@neynar/ui/color-mode";
|
|
8
|
+
export function SnapCellGrid({ element: { props }, }) {
|
|
9
|
+
const { get, set } = useStateStore();
|
|
10
|
+
const accentStyle = useSnapAccentScopeStyle();
|
|
11
|
+
const { mode: appearance } = useColorMode();
|
|
12
|
+
const cols = Number(props.cols ?? 2);
|
|
13
|
+
const rows = Number(props.rows ?? 2);
|
|
14
|
+
const select = String(props.select ?? "off");
|
|
15
|
+
const interactive = select !== "off";
|
|
16
|
+
const isMultiple = select === "multiple";
|
|
17
|
+
const cells = Array.isArray(props.cells) ? props.cells : [];
|
|
18
|
+
const gap = String(props.gap ?? "sm");
|
|
19
|
+
const gapMap = { none: 0, sm: 1, md: 2, lg: 4 };
|
|
20
|
+
const gapPx = gapMap[gap] ?? 1;
|
|
21
|
+
const name = props.name ? String(props.name) : POST_GRID_TAP_KEY;
|
|
22
|
+
const tapPath = `/inputs/${name}`;
|
|
23
|
+
const tapRaw = get(tapPath);
|
|
24
|
+
// Parse selection — single mode: "row,col" string; multi mode: "row,col|row,col|..." string
|
|
25
|
+
const selectedSet = new Set();
|
|
26
|
+
if (typeof tapRaw === "string" && tapRaw.length > 0) {
|
|
27
|
+
for (const part of tapRaw.split("|")) {
|
|
28
|
+
if (part.includes(","))
|
|
29
|
+
selectedSet.add(part);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
const isSelected = (r, c) => selectedSet.has(`${r},${c}`);
|
|
33
|
+
const handleTap = (r, c) => {
|
|
34
|
+
const key = `${r},${c}`;
|
|
35
|
+
if (isMultiple) {
|
|
36
|
+
const next = new Set(selectedSet);
|
|
37
|
+
if (next.has(key))
|
|
38
|
+
next.delete(key);
|
|
39
|
+
else
|
|
40
|
+
next.add(key);
|
|
41
|
+
set(tapPath, [...next].join("|"));
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
set(tapPath, key);
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
const cellMap = new Map();
|
|
48
|
+
for (const c of cells) {
|
|
49
|
+
cellMap.set(`${Number(c.row)},${Number(c.col)}`, {
|
|
50
|
+
color: c.color,
|
|
51
|
+
content: c.content != null ? String(c.content) : undefined,
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
const ringColor = appearance === "dark" ? "#fff" : "#000";
|
|
55
|
+
const cellEls = [];
|
|
56
|
+
for (let r = 0; r < rows; r++) {
|
|
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 && cell.color in PALETTE_LIGHT_HEX
|
|
61
|
+
? `var(--snap-color-${cell.color}, ${PALETTE_LIGHT_HEX[cell.color]})`
|
|
62
|
+
: "transparent";
|
|
63
|
+
cellEls.push(_jsx("div", { role: interactive ? "button" : undefined, tabIndex: interactive ? 0 : undefined, onClick: interactive ? () => handleTap(r, c) : undefined, onKeyDown: interactive
|
|
64
|
+
? (e) => {
|
|
65
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
66
|
+
e.preventDefault();
|
|
67
|
+
handleTap(r, c);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
: undefined, className: cn("flex min-h-7 items-center justify-center rounded text-xs font-semibold", interactive ? "cursor-pointer select-none" : "cursor-default"), style: {
|
|
71
|
+
background: bg,
|
|
72
|
+
// Two-layer ring: 1px white/black inner + 2px accent outer
|
|
73
|
+
boxShadow: selected
|
|
74
|
+
? `inset 0 0 0 1px ${appearance === "dark" ? "#000" : "#fff"}, inset 0 0 0 2px ${appearance === "dark" ? "#fff" : "#000"}`
|
|
75
|
+
: undefined,
|
|
76
|
+
}, children: cell?.content ?? "" }, `${r}-${c}`));
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
const selectionLabel = interactive && selectedSet.size > 0
|
|
80
|
+
? `inputs.${name}: ${[...selectedSet].join(isMultiple ? " | " : "")}`
|
|
81
|
+
: null;
|
|
82
|
+
return (_jsxs("div", { style: accentStyle, children: [_jsx("div", { className: "grid w-full", style: {
|
|
83
|
+
gridTemplateColumns: `repeat(${cols}, minmax(0, 1fr))`,
|
|
84
|
+
gap: gapPx,
|
|
85
|
+
}, children: cellEls }), selectionLabel && (_jsx("div", { className: "text-muted-foreground mt-1.5 truncate text-xs font-mono", children: selectionLabel }))] }));
|
|
86
|
+
}
|
|
@@ -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
|
});
|
|
@@ -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 }], 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
|
+
});
|
|
@@ -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";
|
package/llms.txt
CHANGED
|
@@ -30,7 +30,7 @@ Top-level fields: `version` (required, `"1.0"`), `theme` (optional, `{ accent: P
|
|
|
30
30
|
|
|
31
31
|
`ui.root` is the ID of the root element. `ui.elements` is a flat map of element ID to element definition.
|
|
32
32
|
|
|
33
|
-
## Components (
|
|
33
|
+
## Components (16 total)
|
|
34
34
|
|
|
35
35
|
### Display Components
|
|
36
36
|
|
|
@@ -75,6 +75,21 @@ Top-level fields: `version` (required, `"1.0"`), `theme` (optional, `{ accent: P
|
|
|
75
75
|
- `weight` (optional): `"bold"` | `"normal"`. Default: `"normal"`
|
|
76
76
|
- `align` (optional): `"left"` | `"center"` | `"right"`. Default: `"left"`
|
|
77
77
|
|
|
78
|
+
### Data Components
|
|
79
|
+
|
|
80
|
+
**bar_chart** — Horizontal bar chart with labeled bars.
|
|
81
|
+
- `bars` (array, required, 1–6 items): each `{ label: string (max 40), value: number (≥0), color?: PaletteColor }`
|
|
82
|
+
- `max` (number, optional, ≥0): ceiling value; defaults to max bar value
|
|
83
|
+
- `color` (optional): PaletteColor. Default bar color. Default: `"accent"`
|
|
84
|
+
|
|
85
|
+
**cell_grid** — Colored cell grid, optionally interactive.
|
|
86
|
+
- `name` (string, optional): POST inputs key. Default: `"grid_tap"`
|
|
87
|
+
- `cols` (number, required, 2–32)
|
|
88
|
+
- `rows` (number, required, 2–16)
|
|
89
|
+
- `cells` (array, required): sparse list of `{ row, col, color?: PaletteColor, content?: string }`
|
|
90
|
+
- `gap` (optional): `"none"` (0px) | `"sm"` (1px) | `"md"` (2px) | `"lg"` (4px). Default: `"sm"`
|
|
91
|
+
- `select` (optional): `"off"` | `"single"` | `"multiple"`. Default: `"off"`. Taps write to `inputs[name]`
|
|
92
|
+
|
|
78
93
|
### Container Components
|
|
79
94
|
|
|
80
95
|
**stack** — Layout container.
|