@farcaster/snap 1.3.0 → 1.3.2
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 -3
- package/dist/constants.js +0 -4
- package/dist/schemas.d.ts +1 -41
- package/dist/schemas.js +4 -17
- package/dist/server/parseRequest.d.ts +1 -1
- package/dist/server/parseRequest.js +1 -1
- package/dist/ui/bar-chart.d.ts +30 -0
- package/dist/ui/bar-chart.js +14 -0
- package/dist/ui/button-group.d.ts +11 -0
- package/dist/ui/button-group.js +10 -0
- package/dist/ui/button.d.ts +32 -0
- package/dist/ui/button.js +10 -0
- package/dist/ui/catalog.d.ts +271 -0
- package/dist/ui/catalog.js +114 -0
- package/dist/ui/divider.d.ts +3 -0
- package/dist/ui/divider.js +2 -0
- package/dist/ui/grid.d.ts +22 -0
- package/dist/ui/grid.js +16 -0
- package/dist/ui/group.d.ts +8 -0
- package/dist/ui/group.js +4 -0
- package/dist/ui/image.d.ts +13 -0
- package/dist/ui/image.js +7 -0
- package/dist/ui/index.d.ts +32 -0
- package/dist/ui/index.js +17 -0
- package/dist/ui/list.d.ts +13 -0
- package/dist/ui/list.js +13 -0
- package/dist/ui/progress.d.ts +18 -0
- package/dist/ui/progress.js +8 -0
- package/dist/ui/schema.d.ts +32 -0
- package/dist/ui/schema.js +31 -0
- package/dist/ui/slider.d.ts +12 -0
- package/dist/ui/slider.js +11 -0
- package/dist/ui/spacer.d.ts +9 -0
- package/dist/ui/spacer.js +5 -0
- package/dist/ui/stack.d.ts +3 -0
- package/dist/ui/stack.js +2 -0
- package/dist/ui/text-input.d.ts +7 -0
- package/dist/ui/text-input.js +12 -0
- package/dist/ui/text.d.ts +16 -0
- package/dist/ui/text.js +7 -0
- package/dist/ui/toggle.d.ts +7 -0
- package/dist/ui/toggle.js +6 -0
- package/dist/validator.d.ts +1 -1
- package/dist/validator.js +0 -1
- package/package.json +83 -2
- package/src/constants.ts +0 -4
- package/src/schemas.ts +3 -20
- package/src/server/parseRequest.ts +1 -1
- package/src/ui/README.md +50 -0
- package/src/ui/bar-chart.ts +23 -0
- package/src/ui/button-group.ts +13 -0
- package/src/ui/button.ts +15 -0
- package/src/ui/catalog.ts +128 -0
- package/src/ui/divider.ts +5 -0
- package/src/ui/grid.ts +25 -0
- package/src/ui/group.ts +7 -0
- package/src/ui/image.ts +10 -0
- package/src/ui/index.ts +47 -0
- package/src/ui/list.ts +17 -0
- package/src/ui/progress.ts +11 -0
- package/src/ui/schema.ts +37 -0
- package/src/ui/slider.ts +14 -0
- package/src/ui/spacer.ts +8 -0
- package/src/ui/stack.ts +5 -0
- package/src/ui/text-input.ts +15 -0
- package/src/ui/text.ts +10 -0
- package/src/ui/toggle.ts +9 -0
- package/src/validator.ts +1 -2
package/src/schemas.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { z } from "zod
|
|
1
|
+
import { z } from "zod";
|
|
2
2
|
import {
|
|
3
3
|
BAR_CHART_COLOR_VALUES,
|
|
4
4
|
BUTTON_ACTION,
|
|
@@ -35,7 +35,6 @@ import {
|
|
|
35
35
|
TEXT_CONTENT_MAX,
|
|
36
36
|
TEXT_STYLE,
|
|
37
37
|
TEXT_STYLE_VALUES,
|
|
38
|
-
VIDEO_ASPECT_VALUES,
|
|
39
38
|
} from "./constants";
|
|
40
39
|
|
|
41
40
|
/**
|
|
@@ -94,13 +93,6 @@ const imageUrlSchema = z
|
|
|
94
93
|
"image URL must use HTTPS and end with a supported extension (.jpg, .png, .gif, .webp)",
|
|
95
94
|
});
|
|
96
95
|
|
|
97
|
-
const videoUrlSchema = z
|
|
98
|
-
.string()
|
|
99
|
-
.refine((s) => hasAllowedMediaExtension(s, ["mp4", "webm"]), {
|
|
100
|
-
message:
|
|
101
|
-
"video URL must use HTTPS and end with a supported extension (.mp4, .webm)",
|
|
102
|
-
});
|
|
103
|
-
|
|
104
96
|
const textAlignSchema = z.enum(TEXT_ALIGN_VALUES);
|
|
105
97
|
|
|
106
98
|
const textElementSchema = z
|
|
@@ -128,14 +120,6 @@ const imageElementSchema = z.object({
|
|
|
128
120
|
alt: z.string().optional(),
|
|
129
121
|
});
|
|
130
122
|
|
|
131
|
-
const videoElementSchema = z.object({
|
|
132
|
-
type: z.literal(ELEMENT_TYPE.video),
|
|
133
|
-
url: videoUrlSchema,
|
|
134
|
-
aspect: z.enum(VIDEO_ASPECT_VALUES),
|
|
135
|
-
maxDuration: z.number().max(LIMITS.maxVideoDurationSeconds).optional(),
|
|
136
|
-
alt: z.string().optional(),
|
|
137
|
-
});
|
|
138
|
-
|
|
139
123
|
const dividerElementSchema = z.object({
|
|
140
124
|
type: z.literal(ELEMENT_TYPE.divider),
|
|
141
125
|
});
|
|
@@ -434,7 +418,6 @@ const groupElementSchema = z.object({
|
|
|
434
418
|
const elementSchema = z.discriminatedUnion("type", [
|
|
435
419
|
textElementSchema,
|
|
436
420
|
imageElementSchema,
|
|
437
|
-
videoElementSchema,
|
|
438
421
|
dividerElementSchema,
|
|
439
422
|
spacerElementSchema,
|
|
440
423
|
progressElementSchema,
|
|
@@ -490,7 +473,7 @@ export const rootSchema = z
|
|
|
490
473
|
if (mediaCount > 1) {
|
|
491
474
|
ctx.addIssue({
|
|
492
475
|
code: "custom",
|
|
493
|
-
message: `cannot have more than 1 media element (image
|
|
476
|
+
message: `cannot have more than 1 media element (image or grid)`,
|
|
494
477
|
path: ["elements", "children"],
|
|
495
478
|
});
|
|
496
479
|
}
|
|
@@ -524,7 +507,7 @@ export const firstPageRootSchema = rootSchema.superRefine((root, ctx) => {
|
|
|
524
507
|
ctx.addIssue({
|
|
525
508
|
code: "custom",
|
|
526
509
|
message:
|
|
527
|
-
"first page must have at least one interactive element (button_group, slider, text_input, toggle) or media element (image,
|
|
510
|
+
"first page must have at least one interactive element (button_group, slider, text_input, toggle) or media element (image, grid)",
|
|
528
511
|
path: ["page", "elements", "children"],
|
|
529
512
|
});
|
|
530
513
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { payloadSchema, type SnapAction } from "../schemas";
|
|
2
2
|
import { decodePayload, verifyJFSRequestBody } from "./verify";
|
|
3
|
-
import { z } from "zod
|
|
3
|
+
import { z } from "zod";
|
|
4
4
|
|
|
5
5
|
/** Default replay window per SPEC.md § Replay Protection (5 minutes). */
|
|
6
6
|
const DEFAULT_SNAP_POST_MAX_SKEW_SECONDS = 300 as const;
|
package/src/ui/README.md
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# Theme colors in `@farcaster/snap/ui`
|
|
2
|
+
|
|
3
|
+
This document explains how colors flow through snap pages — from the snap JSON spec to what clients render.
|
|
4
|
+
|
|
5
|
+
## Named palette
|
|
6
|
+
|
|
7
|
+
Snaps use a fixed set of named colors called the **palette**:
|
|
8
|
+
|
|
9
|
+
| Name | Light hex | Dark hex |
|
|
10
|
+
| -------- | --------- | --------- |
|
|
11
|
+
| `gray` | `#8F8F8F` | `#8F8F8F` |
|
|
12
|
+
| `blue` | `#006BFF` | `#006FFE` |
|
|
13
|
+
| `red` | `#FC0036` | `#F13342` |
|
|
14
|
+
| `amber` | `#FFAE00` | `#FFAE00` |
|
|
15
|
+
| `green` | `#28A948` | `#00AC3A` |
|
|
16
|
+
| `teal` | `#00AC96` | `#00AA96` |
|
|
17
|
+
| `purple` | `#8B5CF6` | `#A78BFA` |
|
|
18
|
+
| `pink` | `#F32782` | `#F12B82` |
|
|
19
|
+
|
|
20
|
+
These are exported from `@farcaster/snap` as `PALETTE_LIGHT_HEX`, `PALETTE_DARK_HEX`, and the `PaletteColor` type. Clients resolve the correct hex for their current light/dark mode.
|
|
21
|
+
|
|
22
|
+
## Page-level accent (`page.theme.accent`)
|
|
23
|
+
|
|
24
|
+
A snap page may declare a single accent color for the whole page:
|
|
25
|
+
|
|
26
|
+
```json
|
|
27
|
+
{
|
|
28
|
+
"page": {
|
|
29
|
+
"theme": { "accent": "blue" }
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
`accent` must be a `PaletteColor` name. When `theme` is omitted, schema validation defaults it to `"purple"` (`DEFAULT_THEME_ACCENT`).
|
|
35
|
+
|
|
36
|
+
The accent is the color used for all components on the page that don't have an explicit color set.
|
|
37
|
+
|
|
38
|
+
## Per-element explicit colors
|
|
39
|
+
|
|
40
|
+
Some elements accept a `color` prop:
|
|
41
|
+
|
|
42
|
+
- `progress` — color name or `"accent"`
|
|
43
|
+
- `bar_chart` — color name or `"accent"` at chart level; color name only per-bar
|
|
44
|
+
- `grid` cells — `#rrggbb` hex
|
|
45
|
+
|
|
46
|
+
When `color` is `"accent"` (or omitted), the element uses the accent color. When it is a specific color name or hex value, that color is **explicit** and independent of `page.theme.accent`.
|
|
47
|
+
|
|
48
|
+
## Light/dark mode
|
|
49
|
+
|
|
50
|
+
Mode is determined by the **client**, not by snap JSON. Snap JSON carries no light/dark setting. Clients select `PALETTE_LIGHT_HEX` or `PALETTE_DARK_HEX` based on their current mode.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import {
|
|
3
|
+
BAR_CHART_COLOR_VALUES,
|
|
4
|
+
LIMITS,
|
|
5
|
+
PALETTE_COLOR_VALUES,
|
|
6
|
+
} from "../constants.js";
|
|
7
|
+
|
|
8
|
+
export const barChartProps = z.object({
|
|
9
|
+
bars: z
|
|
10
|
+
.array(
|
|
11
|
+
z.object({
|
|
12
|
+
label: z.string(),
|
|
13
|
+
value: z.number().nonnegative(),
|
|
14
|
+
color: z.enum(PALETTE_COLOR_VALUES).optional(),
|
|
15
|
+
}),
|
|
16
|
+
)
|
|
17
|
+
.min(1)
|
|
18
|
+
.max(LIMITS.maxBarChartBars),
|
|
19
|
+
max: z.number().nonnegative().optional(),
|
|
20
|
+
color: z.enum(BAR_CHART_COLOR_VALUES).optional(),
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
export type BarChartProps = z.infer<typeof barChartProps>;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { BUTTON_GROUP_STYLE_VALUES, LIMITS } from "../constants.js";
|
|
3
|
+
|
|
4
|
+
export const buttonGroupProps = z.object({
|
|
5
|
+
name: z.string().min(1),
|
|
6
|
+
options: z
|
|
7
|
+
.array(z.string())
|
|
8
|
+
.min(LIMITS.minButtonGroupOptions)
|
|
9
|
+
.max(LIMITS.maxButtonGroupOptions),
|
|
10
|
+
style: z.enum(BUTTON_GROUP_STYLE_VALUES).optional(),
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
export type ButtonGroupProps = z.infer<typeof buttonGroupProps>;
|
package/src/ui/button.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { BUTTON_ACTION_VALUES, BUTTON_STYLE_VALUES } from "../constants.js";
|
|
3
|
+
|
|
4
|
+
export const actionButtonProps = z.object({
|
|
5
|
+
label: z.string(),
|
|
6
|
+
action: z.enum(BUTTON_ACTION_VALUES),
|
|
7
|
+
target: z.string(),
|
|
8
|
+
style: z.enum(BUTTON_STYLE_VALUES).optional(),
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
export type ActionButtonProps = z.infer<typeof actionButtonProps>;
|
|
12
|
+
|
|
13
|
+
/** Same schema as `actionButtonProps` (legacy export name). */
|
|
14
|
+
export const buttonProps = actionButtonProps;
|
|
15
|
+
export type ButtonProps = ActionButtonProps;
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { defineCatalog } from "@json-render/core";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { BUTTON_STYLE_VALUES } from "../constants.js";
|
|
4
|
+
import { snapJsonRenderSchema } from "./schema.js";
|
|
5
|
+
import { textProps } from "./text.js";
|
|
6
|
+
import { imageProps } from "./image.js";
|
|
7
|
+
import { dividerProps } from "./divider.js";
|
|
8
|
+
import { spacerProps } from "./spacer.js";
|
|
9
|
+
import { progressProps } from "./progress.js";
|
|
10
|
+
import { listProps } from "./list.js";
|
|
11
|
+
import { gridProps } from "./grid.js";
|
|
12
|
+
import { textInputProps } from "./text-input.js";
|
|
13
|
+
import { sliderProps } from "./slider.js";
|
|
14
|
+
import { buttonGroupProps } from "./button-group.js";
|
|
15
|
+
import { toggleProps } from "./toggle.js";
|
|
16
|
+
import { barChartProps } from "./bar-chart.js";
|
|
17
|
+
import { groupProps } from "./group.js";
|
|
18
|
+
import { stackProps } from "./stack.js";
|
|
19
|
+
import { actionButtonProps } from "./button.js";
|
|
20
|
+
|
|
21
|
+
const snapPostParams = z.object({
|
|
22
|
+
buttonIndex: z.number().int().nonnegative(),
|
|
23
|
+
target: z.string(),
|
|
24
|
+
label: z.string().optional(),
|
|
25
|
+
style: z.enum(BUTTON_STYLE_VALUES).optional(),
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
const snapTargetParams = z.object({
|
|
29
|
+
target: z.string(),
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Basic catalog: one json-render component per snap element type, plus ActionButton for snap buttons.
|
|
34
|
+
* Does not validate cross-field rules (media count, height budget); snap JSON still goes through `@farcaster/snap` validation.
|
|
35
|
+
*/
|
|
36
|
+
export const snapJsonRenderCatalog = defineCatalog(snapJsonRenderSchema, {
|
|
37
|
+
components: {
|
|
38
|
+
Text: {
|
|
39
|
+
props: textProps,
|
|
40
|
+
description:
|
|
41
|
+
"Snap text block — style: title | body | caption | label; optional align.",
|
|
42
|
+
},
|
|
43
|
+
Image: {
|
|
44
|
+
props: imageProps,
|
|
45
|
+
description: "HTTPS image with fixed aspect ratio.",
|
|
46
|
+
},
|
|
47
|
+
Divider: {
|
|
48
|
+
props: dividerProps,
|
|
49
|
+
description: "Horizontal rule between blocks.",
|
|
50
|
+
},
|
|
51
|
+
Spacer: {
|
|
52
|
+
props: spacerProps,
|
|
53
|
+
description: "Vertical whitespace — size small | medium | large.",
|
|
54
|
+
},
|
|
55
|
+
Progress: {
|
|
56
|
+
props: progressProps,
|
|
57
|
+
description:
|
|
58
|
+
"Horizontal progress bar (value/max, optional label and color).",
|
|
59
|
+
},
|
|
60
|
+
List: {
|
|
61
|
+
props: listProps,
|
|
62
|
+
description:
|
|
63
|
+
"Ordered / unordered / plain list; max 4 items per snap spec.",
|
|
64
|
+
},
|
|
65
|
+
Grid: {
|
|
66
|
+
props: gridProps,
|
|
67
|
+
description:
|
|
68
|
+
"Rows×cols cell grid; optional interactive empty cells for games.",
|
|
69
|
+
},
|
|
70
|
+
TextInput: {
|
|
71
|
+
props: textInputProps,
|
|
72
|
+
description: "Single-line input; `name` becomes POST inputs key.",
|
|
73
|
+
},
|
|
74
|
+
Slider: {
|
|
75
|
+
props: sliderProps,
|
|
76
|
+
description: "Numeric slider; `name` becomes POST inputs key.",
|
|
77
|
+
},
|
|
78
|
+
ButtonGroup: {
|
|
79
|
+
props: buttonGroupProps,
|
|
80
|
+
description:
|
|
81
|
+
"Exclusive choice; `name` and selected option go into POST inputs.",
|
|
82
|
+
},
|
|
83
|
+
Toggle: {
|
|
84
|
+
props: toggleProps,
|
|
85
|
+
description: "Boolean toggle; `name` becomes POST inputs key.",
|
|
86
|
+
},
|
|
87
|
+
BarChart: {
|
|
88
|
+
props: barChartProps,
|
|
89
|
+
description:
|
|
90
|
+
"Vertical bar chart for labeled values — poll results, rankings, breakdowns.",
|
|
91
|
+
},
|
|
92
|
+
Group: {
|
|
93
|
+
props: groupProps,
|
|
94
|
+
description:
|
|
95
|
+
"Button row (`layout: row`) or 2-column grid (`layout: grid`); use `children` element ids only (no nested JSON objects).",
|
|
96
|
+
},
|
|
97
|
+
Stack: {
|
|
98
|
+
props: stackProps,
|
|
99
|
+
description:
|
|
100
|
+
"Vertical stack for snap page body; maps from snap `page.elements` (`type: stack`). Children are element ids in order top to bottom.",
|
|
101
|
+
},
|
|
102
|
+
ActionButton: {
|
|
103
|
+
props: actionButtonProps,
|
|
104
|
+
description:
|
|
105
|
+
"Snap action button: post (next page), link (browser), mini_app, sdk — target is HTTPS URL or SDK id.",
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
actions: {
|
|
109
|
+
snap_post: {
|
|
110
|
+
description:
|
|
111
|
+
"POST to snap `target` with signed body (fid, inputs, button_index, timestamp, signature); response is next snap page JSON.",
|
|
112
|
+
params: snapPostParams,
|
|
113
|
+
},
|
|
114
|
+
snap_link: {
|
|
115
|
+
description: "Open `target` in the system browser; no server round-trip.",
|
|
116
|
+
params: snapTargetParams,
|
|
117
|
+
},
|
|
118
|
+
snap_mini_app: {
|
|
119
|
+
description: "Open `target` as an in-app Farcaster mini app.",
|
|
120
|
+
params: snapTargetParams,
|
|
121
|
+
},
|
|
122
|
+
snap_sdk: {
|
|
123
|
+
description:
|
|
124
|
+
"Run a Farcaster client SDK action (cast:view, user:follow, …).",
|
|
125
|
+
params: snapTargetParams,
|
|
126
|
+
},
|
|
127
|
+
},
|
|
128
|
+
});
|
package/src/ui/grid.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import {
|
|
3
|
+
GRID_CELL_SIZE_VALUES,
|
|
4
|
+
GRID_GAP_VALUES,
|
|
5
|
+
HEX_COLOR_6_RE,
|
|
6
|
+
LIMITS,
|
|
7
|
+
} from "../constants.js";
|
|
8
|
+
|
|
9
|
+
const gridCellZ = z.object({
|
|
10
|
+
row: z.number().int().nonnegative(),
|
|
11
|
+
col: z.number().int().nonnegative(),
|
|
12
|
+
color: z.string().regex(HEX_COLOR_6_RE).optional(),
|
|
13
|
+
content: z.string().optional(),
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
export const gridProps = z.object({
|
|
17
|
+
cols: z.number().int().min(LIMITS.minGridCols).max(LIMITS.maxGridCols),
|
|
18
|
+
rows: z.number().int().min(LIMITS.minGridRows).max(LIMITS.maxGridRows),
|
|
19
|
+
cells: z.array(gridCellZ),
|
|
20
|
+
cellSize: z.enum(GRID_CELL_SIZE_VALUES).optional(),
|
|
21
|
+
gap: z.enum(GRID_GAP_VALUES).optional(),
|
|
22
|
+
interactive: z.boolean().optional(),
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
export type GridProps = z.infer<typeof gridProps>;
|
package/src/ui/group.ts
ADDED
package/src/ui/image.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { IMAGE_ASPECT_VALUES } from "../constants.js";
|
|
3
|
+
|
|
4
|
+
export const imageProps = z.object({
|
|
5
|
+
url: z.string(),
|
|
6
|
+
aspect: z.enum(IMAGE_ASPECT_VALUES),
|
|
7
|
+
alt: z.string().optional(),
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
export type ImageProps = z.infer<typeof imageProps>;
|
package/src/ui/index.ts
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
export { snapJsonRenderSchema } from "./schema.js";
|
|
2
|
+
export { snapJsonRenderCatalog } from "./catalog.js";
|
|
3
|
+
|
|
4
|
+
export { textProps } from "./text.js";
|
|
5
|
+
export type { TextProps } from "./text.js";
|
|
6
|
+
|
|
7
|
+
export { imageProps } from "./image.js";
|
|
8
|
+
export type { ImageProps } from "./image.js";
|
|
9
|
+
|
|
10
|
+
export { dividerProps } from "./divider.js";
|
|
11
|
+
export type { DividerProps } from "./divider.js";
|
|
12
|
+
|
|
13
|
+
export { spacerProps } from "./spacer.js";
|
|
14
|
+
export type { SpacerProps } from "./spacer.js";
|
|
15
|
+
|
|
16
|
+
export { progressProps } from "./progress.js";
|
|
17
|
+
export type { ProgressProps } from "./progress.js";
|
|
18
|
+
|
|
19
|
+
export { listProps } from "./list.js";
|
|
20
|
+
export type { ListProps } from "./list.js";
|
|
21
|
+
|
|
22
|
+
export { gridProps } from "./grid.js";
|
|
23
|
+
export type { GridProps } from "./grid.js";
|
|
24
|
+
|
|
25
|
+
export { textInputProps } from "./text-input.js";
|
|
26
|
+
export type { TextInputProps } from "./text-input.js";
|
|
27
|
+
|
|
28
|
+
export { sliderProps } from "./slider.js";
|
|
29
|
+
export type { SliderProps } from "./slider.js";
|
|
30
|
+
|
|
31
|
+
export { buttonGroupProps } from "./button-group.js";
|
|
32
|
+
export type { ButtonGroupProps } from "./button-group.js";
|
|
33
|
+
|
|
34
|
+
export { toggleProps } from "./toggle.js";
|
|
35
|
+
export type { ToggleProps } from "./toggle.js";
|
|
36
|
+
|
|
37
|
+
export { barChartProps } from "./bar-chart.js";
|
|
38
|
+
export type { BarChartProps } from "./bar-chart.js";
|
|
39
|
+
|
|
40
|
+
export { groupProps } from "./group.js";
|
|
41
|
+
export type { GroupProps } from "./group.js";
|
|
42
|
+
|
|
43
|
+
export { stackProps } from "./stack.js";
|
|
44
|
+
export type { StackProps } from "./stack.js";
|
|
45
|
+
|
|
46
|
+
export { actionButtonProps, buttonProps } from "./button.js";
|
|
47
|
+
export type { ActionButtonProps, ButtonProps } from "./button.js";
|
package/src/ui/list.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { LIMITS, LIST_STYLE_VALUES } from "../constants.js";
|
|
3
|
+
|
|
4
|
+
const listItemZ = z.object({
|
|
5
|
+
content: z.string(),
|
|
6
|
+
trailing: z.string().optional(),
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
export const listProps = z.object({
|
|
10
|
+
style: z.enum(LIST_STYLE_VALUES).optional(),
|
|
11
|
+
items: z
|
|
12
|
+
.array(listItemZ)
|
|
13
|
+
.min(LIMITS.minListItems)
|
|
14
|
+
.max(LIMITS.maxListItems),
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
export type ListProps = z.infer<typeof listProps>;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { PROGRESS_COLOR_VALUES } from "../constants.js";
|
|
3
|
+
|
|
4
|
+
export const progressProps = z.object({
|
|
5
|
+
value: z.number(),
|
|
6
|
+
max: z.number(),
|
|
7
|
+
label: z.string().optional(),
|
|
8
|
+
color: z.enum(PROGRESS_COLOR_VALUES).optional(),
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
export type ProgressProps = z.infer<typeof progressProps>;
|
package/src/ui/schema.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { defineSchema } from "@json-render/core";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* json-render spec shape: flat `root` + `elements` map (see [json-render](https://json-render.dev/)).
|
|
5
|
+
* Component `type` strings must match keys in {@link ./catalog.ts}.
|
|
6
|
+
*/
|
|
7
|
+
export const snapJsonRenderSchema = defineSchema(
|
|
8
|
+
(s) => ({
|
|
9
|
+
spec: s.object({
|
|
10
|
+
root: s.string(),
|
|
11
|
+
elements: s.record(
|
|
12
|
+
s.object({
|
|
13
|
+
type: s.ref("catalog.components"),
|
|
14
|
+
props: s.propsOf("catalog.components"),
|
|
15
|
+
children: { ...s.array(s.string()), optional: true },
|
|
16
|
+
}),
|
|
17
|
+
),
|
|
18
|
+
}),
|
|
19
|
+
catalog: s.object({
|
|
20
|
+
components: s.map({
|
|
21
|
+
props: s.zod(),
|
|
22
|
+
description: s.string(),
|
|
23
|
+
}),
|
|
24
|
+
actions: s.map({
|
|
25
|
+
description: s.string(),
|
|
26
|
+
params: { ...s.zod(), optional: true },
|
|
27
|
+
}),
|
|
28
|
+
}),
|
|
29
|
+
}),
|
|
30
|
+
{
|
|
31
|
+
defaultRules: [
|
|
32
|
+
"You are generating auxiliary UI for a Farcaster Snap. Prefer components matching snap element types (Text, Image, ButtonGroup, …).",
|
|
33
|
+
"Snap pages use a Stack root with at most 5 body children and 1 media element (Image or Grid); keep generated trees small.",
|
|
34
|
+
"Bottom-of-card snap buttons are ActionButton components; use actions post / link / mini_app / sdk per SPEC.md.",
|
|
35
|
+
],
|
|
36
|
+
},
|
|
37
|
+
);
|
package/src/ui/slider.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
export const sliderProps = z.object({
|
|
4
|
+
name: z.string().min(1),
|
|
5
|
+
min: z.number(),
|
|
6
|
+
max: z.number(),
|
|
7
|
+
step: z.number().optional(),
|
|
8
|
+
value: z.number().optional(),
|
|
9
|
+
label: z.string().optional(),
|
|
10
|
+
minLabel: z.string().optional(),
|
|
11
|
+
maxLabel: z.string().optional(),
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
export type SliderProps = z.infer<typeof sliderProps>;
|
package/src/ui/spacer.ts
ADDED
package/src/ui/stack.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { LIMITS } from "../constants.js";
|
|
3
|
+
|
|
4
|
+
export const textInputProps = z.object({
|
|
5
|
+
name: z.string().min(1),
|
|
6
|
+
placeholder: z.string().optional(),
|
|
7
|
+
maxLength: z
|
|
8
|
+
.number()
|
|
9
|
+
.int()
|
|
10
|
+
.positive()
|
|
11
|
+
.max(LIMITS.maxTextInputChars)
|
|
12
|
+
.optional(),
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
export type TextInputProps = z.infer<typeof textInputProps>;
|
package/src/ui/text.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { TEXT_ALIGN_VALUES, TEXT_STYLE_VALUES } from "../constants.js";
|
|
3
|
+
|
|
4
|
+
export const textProps = z.object({
|
|
5
|
+
style: z.enum(TEXT_STYLE_VALUES),
|
|
6
|
+
content: z.string(),
|
|
7
|
+
align: z.enum(TEXT_ALIGN_VALUES).optional(),
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
export type TextProps = z.infer<typeof textProps>;
|
package/src/ui/toggle.ts
ADDED
package/src/validator.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { z } from "zod
|
|
1
|
+
import { z } from "zod";
|
|
2
2
|
import {
|
|
3
3
|
BUTTON_GROUP_STYLE,
|
|
4
4
|
DEFAULT_BUTTON_LAYOUT,
|
|
@@ -39,7 +39,6 @@ function estimateElementHeight(el: Element): number {
|
|
|
39
39
|
return 48;
|
|
40
40
|
}
|
|
41
41
|
case ELEMENT_TYPE.image:
|
|
42
|
-
case ELEMENT_TYPE.video:
|
|
43
42
|
return 180;
|
|
44
43
|
case ELEMENT_TYPE.grid:
|
|
45
44
|
return 180;
|