@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.
Files changed (156) hide show
  1. package/dist/constants.d.ts +0 -107
  2. package/dist/constants.js +0 -148
  3. package/dist/dataStore.d.ts +12 -0
  4. package/dist/dataStore.js +35 -0
  5. package/dist/index.d.ts +6 -3
  6. package/dist/index.js +5 -3
  7. package/dist/middleware.d.ts +3 -0
  8. package/dist/middleware.js +3 -0
  9. package/dist/react/accent-context.d.ts +6 -0
  10. package/dist/react/accent-context.js +10 -0
  11. package/dist/react/catalog-renderer.d.ts +5 -0
  12. package/dist/react/catalog-renderer.js +37 -0
  13. package/dist/react/components/action-button.d.ts +6 -0
  14. package/dist/react/components/action-button.js +22 -0
  15. package/dist/react/components/badge.d.ts +5 -0
  16. package/dist/react/components/badge.js +18 -0
  17. package/dist/react/components/icon.d.ts +7 -0
  18. package/dist/react/components/icon.js +60 -0
  19. package/dist/react/components/image.d.ts +5 -0
  20. package/dist/react/components/image.js +15 -0
  21. package/dist/react/components/input.d.ts +5 -0
  22. package/dist/react/components/input.js +18 -0
  23. package/dist/react/components/item-group.d.ts +7 -0
  24. package/dist/react/components/item-group.js +17 -0
  25. package/dist/react/components/item.d.ts +7 -0
  26. package/dist/react/components/item.js +9 -0
  27. package/dist/react/components/progress.d.ts +5 -0
  28. package/dist/react/components/progress.js +11 -0
  29. package/dist/react/components/separator.d.ts +5 -0
  30. package/dist/react/components/separator.js +7 -0
  31. package/dist/react/components/slider.d.ts +5 -0
  32. package/dist/react/components/slider.js +21 -0
  33. package/dist/react/components/stack.d.ts +7 -0
  34. package/dist/react/components/stack.js +32 -0
  35. package/dist/react/components/switch.d.ts +5 -0
  36. package/dist/react/components/switch.js +23 -0
  37. package/dist/react/components/text.d.ts +5 -0
  38. package/dist/react/components/text.js +25 -0
  39. package/dist/react/components/toggle-group.d.ts +5 -0
  40. package/dist/react/components/toggle-group.js +52 -0
  41. package/dist/react/hooks/use-snap-accent.d.ts +13 -0
  42. package/dist/react/hooks/use-snap-accent.js +32 -0
  43. package/dist/react/index.d.ts +47 -0
  44. package/dist/react/index.js +191 -0
  45. package/dist/react/lib/preview-primary-css.d.ts +6 -0
  46. package/dist/react/lib/preview-primary-css.js +43 -0
  47. package/dist/react/lib/resolve-palette-hex.d.ts +2 -0
  48. package/dist/react/lib/resolve-palette-hex.js +10 -0
  49. package/dist/schemas.d.ts +14 -1629
  50. package/dist/schemas.js +14 -526
  51. package/dist/ui/badge.d.ts +52 -0
  52. package/dist/ui/badge.js +9 -0
  53. package/dist/ui/button.d.ts +42 -28
  54. package/dist/ui/button.js +7 -9
  55. package/dist/ui/catalog.d.ts +280 -155
  56. package/dist/ui/catalog.js +102 -83
  57. package/dist/ui/icon.d.ts +56 -0
  58. package/dist/ui/icon.js +51 -0
  59. package/dist/ui/image.d.ts +1 -0
  60. package/dist/ui/image.js +2 -2
  61. package/dist/ui/index.d.ts +20 -22
  62. package/dist/ui/index.js +10 -11
  63. package/dist/ui/input.d.ts +17 -0
  64. package/dist/ui/input.js +13 -0
  65. package/dist/ui/item-group.d.ts +12 -0
  66. package/dist/ui/item-group.js +7 -0
  67. package/dist/ui/item.d.ts +14 -0
  68. package/dist/ui/item.js +9 -0
  69. package/dist/ui/progress.d.ts +1 -11
  70. package/dist/ui/progress.js +21 -4
  71. package/dist/ui/schema.js +3 -3
  72. package/dist/ui/separator.d.ts +9 -0
  73. package/dist/ui/separator.js +5 -0
  74. package/dist/ui/slider.d.ts +4 -3
  75. package/dist/ui/slider.js +34 -5
  76. package/dist/ui/stack.d.ts +22 -1
  77. package/dist/ui/stack.js +8 -1
  78. package/dist/ui/switch.d.ts +8 -0
  79. package/dist/ui/switch.js +7 -0
  80. package/dist/ui/text.d.ts +15 -7
  81. package/dist/ui/text.js +8 -4
  82. package/dist/ui/toggle-group.d.ts +23 -0
  83. package/dist/ui/toggle-group.js +19 -0
  84. package/dist/validator.d.ts +5 -1
  85. package/dist/validator.js +6 -136
  86. package/package.json +72 -52
  87. package/src/constants.ts +0 -179
  88. package/src/dataStore.ts +62 -0
  89. package/src/index.ts +11 -20
  90. package/src/middleware.ts +7 -0
  91. package/src/react/accent-context.tsx +29 -0
  92. package/src/react/catalog-renderer.tsx +39 -0
  93. package/src/react/components/action-button.tsx +48 -0
  94. package/src/react/components/badge.tsx +37 -0
  95. package/src/react/components/icon.tsx +115 -0
  96. package/src/react/components/image.tsx +33 -0
  97. package/src/react/components/input.tsx +36 -0
  98. package/src/react/components/item-group.tsx +43 -0
  99. package/src/react/components/item.tsx +33 -0
  100. package/src/react/components/progress.tsx +29 -0
  101. package/src/react/components/separator.tsx +14 -0
  102. package/src/react/components/slider.tsx +43 -0
  103. package/src/react/components/stack.tsx +55 -0
  104. package/src/react/components/switch.tsx +46 -0
  105. package/src/react/components/text.tsx +43 -0
  106. package/src/react/components/toggle-group.tsx +85 -0
  107. package/src/react/hooks/use-snap-accent.ts +45 -0
  108. package/src/react/index.tsx +321 -0
  109. package/src/react/lib/preview-primary-css.ts +57 -0
  110. package/src/react/lib/resolve-palette-hex.ts +20 -0
  111. package/src/schemas.ts +18 -644
  112. package/src/ui/badge.ts +13 -0
  113. package/src/ui/button.ts +9 -12
  114. package/src/ui/catalog.ts +106 -86
  115. package/src/ui/icon.ts +56 -0
  116. package/src/ui/image.ts +3 -2
  117. package/src/ui/index.ts +26 -29
  118. package/src/ui/input.ts +17 -0
  119. package/src/ui/item-group.ts +11 -0
  120. package/src/ui/item.ts +13 -0
  121. package/src/ui/progress.ts +25 -7
  122. package/src/ui/schema.ts +3 -3
  123. package/src/ui/separator.ts +9 -0
  124. package/src/ui/slider.ts +40 -10
  125. package/src/ui/stack.ts +9 -1
  126. package/src/ui/switch.ts +11 -0
  127. package/src/ui/text.ts +9 -4
  128. package/src/ui/toggle-group.ts +23 -0
  129. package/src/validator.ts +6 -176
  130. package/dist/ui/bar-chart.d.ts +0 -30
  131. package/dist/ui/bar-chart.js +0 -15
  132. package/dist/ui/button-group.d.ts +0 -19
  133. package/dist/ui/button-group.js +0 -18
  134. package/dist/ui/divider.d.ts +0 -3
  135. package/dist/ui/divider.js +0 -2
  136. package/dist/ui/grid.d.ts +0 -22
  137. package/dist/ui/grid.js +0 -16
  138. package/dist/ui/group.d.ts +0 -7
  139. package/dist/ui/group.js +0 -5
  140. package/dist/ui/list.d.ts +0 -13
  141. package/dist/ui/list.js +0 -13
  142. package/dist/ui/spacer.d.ts +0 -9
  143. package/dist/ui/spacer.js +0 -5
  144. package/dist/ui/text-input.d.ts +0 -7
  145. package/dist/ui/text-input.js +0 -12
  146. package/dist/ui/toggle.d.ts +0 -7
  147. package/dist/ui/toggle.js +0 -6
  148. package/src/ui/bar-chart.ts +0 -20
  149. package/src/ui/button-group.ts +0 -26
  150. package/src/ui/divider.ts +0 -5
  151. package/src/ui/grid.ts +0 -25
  152. package/src/ui/group.ts +0 -8
  153. package/src/ui/list.ts +0 -17
  154. package/src/ui/spacer.ts +0 -8
  155. package/src/ui/text-input.ts +0 -15
  156. package/src/ui/toggle.ts +0 -9
@@ -0,0 +1,13 @@
1
+ import { z } from "zod";
2
+ import { PROGRESS_COLOR_VALUES } from "../colors.js";
3
+ import { ICON_NAMES } from "./icon.js";
4
+
5
+ export const BADGE_MAX_LABEL_CHARS = 30;
6
+
7
+ export const badgeProps = z.object({
8
+ label: z.string().min(1).max(BADGE_MAX_LABEL_CHARS),
9
+ color: z.enum(PROGRESS_COLOR_VALUES).optional(),
10
+ icon: z.enum(ICON_NAMES).optional(),
11
+ });
12
+
13
+ export type BadgeProps = z.infer<typeof badgeProps>;
package/src/ui/button.ts CHANGED
@@ -1,16 +1,13 @@
1
1
  import { z } from "zod";
2
- import { BUTTON_ACTION_VALUES, BUTTON_STYLE_VALUES } from "../constants.js";
2
+ import { ICON_NAMES } from "./icon.js";
3
3
 
4
- export const actionButtonProps = z.object({
5
- label: z.string(),
6
- action: z.enum(BUTTON_ACTION_VALUES),
7
- target: z.string().optional(),
8
- client_action: z.record(z.string(), z.unknown()).optional(),
9
- style: z.enum(BUTTON_STYLE_VALUES).optional(),
10
- });
4
+ export const BUTTON_VARIANTS = ["default", "secondary", "outline", "ghost"] as const;
5
+ export const BUTTON_MAX_LABEL_CHARS = 30;
11
6
 
12
- export type ActionButtonProps = z.infer<typeof actionButtonProps>;
7
+ export const buttonProps = z.object({
8
+ label: z.string().min(1).max(BUTTON_MAX_LABEL_CHARS),
9
+ variant: z.enum(BUTTON_VARIANTS).optional(),
10
+ icon: z.enum(ICON_NAMES).optional(),
11
+ });
13
12
 
14
- /** Same schema as `actionButtonProps` (legacy export name). */
15
- export const buttonProps = actionButtonProps;
16
- export type ButtonProps = ActionButtonProps;
13
+ export type ButtonProps = z.infer<typeof buttonProps>;
package/src/ui/catalog.ts CHANGED
@@ -1,132 +1,152 @@
1
1
  import { defineCatalog } from "@json-render/core";
2
2
  import { z } from "zod";
3
- import { BUTTON_STYLE_VALUES } from "../constants.js";
4
3
  import { snapJsonRenderSchema } from "./schema.js";
5
- import { textProps } from "./text.js";
4
+ import { badgeProps } from "./badge.js";
5
+ import { buttonProps } from "./button.js";
6
+ import { switchProps } from "./switch.js";
7
+ import { toggleGroupProps } from "./toggle-group.js";
8
+ import { iconProps } from "./icon.js";
9
+ import { inputProps } from "./input.js";
10
+ import { itemProps } from "./item.js";
11
+ import { itemGroupProps } from "./item-group.js";
6
12
  import { imageProps } from "./image.js";
7
- import { dividerProps } from "./divider.js";
8
- import { spacerProps } from "./spacer.js";
9
13
  import { progressProps } from "./progress.js";
10
- import { listProps } from "./list.js";
11
- import { gridProps } from "./grid.js";
12
- import { textInputProps } from "./text-input.js";
14
+ import { separatorProps } from "./separator.js";
13
15
  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
16
  import { stackProps } from "./stack.js";
19
- import { actionButtonProps } from "./button.js";
20
-
21
- const snapPostParams = z.object({
22
- button_index: 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
- });
17
+ import { textProps } from "./text.js";
31
18
 
32
19
  const snapClientParams = z.object({
33
20
  client_action: z.record(z.string(), z.unknown()),
34
21
  });
35
22
 
36
23
  /**
37
- * Basic catalog: one json-render component per snap element type, plus ActionButton for snap buttons.
38
- * Does not validate cross-field rules (media count, height budget); snap JSON still goes through `@farcaster/snap` validation.
24
+ * json-render catalog for snap elements.
25
+ *
26
+ * Component keys match the snap wire-format `type` strings.
27
+ * Action names are used directly in `on.press` bindings.
39
28
  */
40
29
  export const snapJsonRenderCatalog = defineCatalog(snapJsonRenderSchema, {
41
30
  components: {
42
- Text: {
43
- props: textProps,
31
+ badge: {
32
+ props: badgeProps,
44
33
  description:
45
- "Snap text block style: title | body | caption | label; optional align.",
34
+ "Inline labelvariant: default | secondary | destructive | outline.",
46
35
  },
47
- Image: {
48
- props: imageProps,
49
- description: "HTTPS image with fixed aspect ratio.",
36
+ button: {
37
+ props: buttonProps,
38
+ description:
39
+ "Action button — use with on.press to bind snap or client actions.",
50
40
  },
51
- Divider: {
52
- props: dividerProps,
53
- description: "Horizontal rule between blocks.",
41
+ switch: {
42
+ props: switchProps,
43
+ description:
44
+ "Boolean toggle; `name` becomes POST inputs key. Optional label.",
54
45
  },
55
- Spacer: {
56
- props: spacerProps,
57
- description: "Vertical whitespace — size small | medium | large.",
46
+ toggle_group: {
47
+ props: toggleGroupProps,
48
+ description:
49
+ "Single or multi-select choice group; `name` becomes POST inputs key. mode: single (default) | multiple. Optional label.",
58
50
  },
59
- Progress: {
60
- props: progressProps,
51
+ input: {
52
+ props: inputProps,
61
53
  description:
62
- "Horizontal progress bar (value/max, optional label and color).",
54
+ "Text input; `name` becomes POST inputs key. Optional label and placeholder.",
63
55
  },
64
- List: {
65
- props: listProps,
56
+ item: {
57
+ props: itemProps,
66
58
  description:
67
- "Ordered / unordered / plain list; max 4 items per snap spec.",
59
+ "Content row with title and optional description. Children render in the actions slot (right side) — use badge, button, or text elements.",
68
60
  },
69
- Grid: {
70
- props: gridProps,
61
+ item_group: {
62
+ props: itemGroupProps,
71
63
  description:
72
- "Rows×cols cell grid; optional interactive empty cells for games.",
64
+ "Groups item children into a styled list. Optional border around the group and separator lines between items.",
73
65
  },
74
- TextInput: {
75
- props: textInputProps,
76
- description: "Single-line input; `name` becomes POST inputs key.",
66
+ icon: {
67
+ props: iconProps,
68
+ description:
69
+ "Inline icon from the curated set. Optional color (palette) and size (sm | md).",
77
70
  },
78
- Slider: {
79
- props: sliderProps,
80
- description: "Numeric slider; `name` becomes POST inputs key.",
71
+ image: {
72
+ props: imageProps,
73
+ description: "HTTPS image with fixed aspect ratio.",
81
74
  },
82
- ButtonGroup: {
83
- props: buttonGroupProps,
75
+ progress: {
76
+ props: progressProps,
84
77
  description:
85
- "Exclusive choice; `name` and selected option go into POST inputs.",
86
- },
87
- Toggle: {
88
- props: toggleProps,
89
- description: "Boolean toggle; `name` becomes POST inputs key.",
78
+ "Horizontal progress bar (value/max, optional label and color).",
90
79
  },
91
- BarChart: {
92
- props: barChartProps,
80
+ separator: {
81
+ props: separatorProps,
93
82
  description:
94
- "Vertical bar chart for labeled values poll results, rankings, breakdowns.",
83
+ "Visual divider orientation: horizontal (default) | vertical.",
95
84
  },
96
- Group: {
97
- props: groupProps,
85
+ slider: {
86
+ props: sliderProps,
98
87
  description:
99
- "Button row (`layout: row`) or 2-column grid (`layout: grid`); use `children` element ids only (no nested JSON objects).",
88
+ "Numeric slider; `name` becomes POST inputs key. Optional label.",
100
89
  },
101
- Stack: {
90
+ stack: {
102
91
  props: stackProps,
103
92
  description:
104
- "Vertical stack for snap page body; maps from snap `page.elements` (`type: stack`). Children are element ids in order top to bottom.",
93
+ "Layout container direction: vertical (default) | horizontal. Children are element ids in order.",
105
94
  },
106
- ActionButton: {
107
- props: actionButtonProps,
95
+ text: {
96
+ props: textProps,
108
97
  description:
109
- "Snap action button: post (next page), link (browser), mini_app, client target is HTTPS URL or client_action object.",
98
+ "Text block — size: lg (heading), md (body, default), sm (caption). Optional weight and align.",
110
99
  },
111
100
  },
112
101
  actions: {
113
- snap_post: {
114
- description:
115
- "POST to snap `target` with signed body (fid, inputs, button_index, timestamp, signature); response is next snap page JSON.",
116
- params: snapPostParams,
117
- },
118
- snap_link: {
119
- description: "Open `target` in the system browser; no server round-trip.",
120
- params: snapTargetParams,
121
- },
122
- snap_mini_app: {
123
- description: "Open `target` as an in-app Farcaster mini app.",
124
- params: snapTargetParams,
125
- },
126
- snap_client: {
102
+ submit: {
127
103
  description:
128
- "Trigger a Farcaster client action (view_cast, view_profile, compose_cast, ).",
129
- params: snapClientParams,
104
+ "POST to snap server with signed body (fid, inputs, timestamp, signature); response is next snap page.",
105
+ params: z.object({ target: z.string() }),
106
+ },
107
+ open_url: {
108
+ description: "Open target URL in the system browser.",
109
+ params: z.object({ target: z.string() }),
110
+ },
111
+ open_mini_app: {
112
+ description: "Open target URL as a Farcaster mini app.",
113
+ params: z.object({ target: z.string() }),
114
+ },
115
+ view_cast: {
116
+ description: "Navigate to a cast by hash.",
117
+ params: z.object({ hash: z.string() }),
118
+ },
119
+ view_profile: {
120
+ description: "Navigate to a user profile by FID.",
121
+ params: z.object({ fid: z.number() }),
122
+ },
123
+ compose_cast: {
124
+ description: "Open the cast composer with optional pre-filled content.",
125
+ params: z.object({
126
+ text: z.string().optional(),
127
+ channelKey: z.string().optional(),
128
+ embeds: z.array(z.string()).optional(),
129
+ }),
130
+ },
131
+ view_token: {
132
+ description: "View a token in the wallet. Token is a CAIP-19 identifier.",
133
+ params: z.object({ token: z.string() }),
134
+ },
135
+ send_token: {
136
+ description: "Open send flow for a token. Token is CAIP-19.",
137
+ params: z.object({
138
+ token: z.string(),
139
+ amount: z.string().optional(),
140
+ recipientFid: z.number().optional(),
141
+ recipientAddress: z.string().optional(),
142
+ }),
143
+ },
144
+ swap_token: {
145
+ description: "Open swap flow between two tokens. Tokens are CAIP-19.",
146
+ params: z.object({
147
+ sellToken: z.string().optional(),
148
+ buyToken: z.string().optional(),
149
+ }),
130
150
  },
131
151
  },
132
152
  });
package/src/ui/icon.ts ADDED
@@ -0,0 +1,56 @@
1
+ import { z } from "zod";
2
+ import { PROGRESS_COLOR_VALUES } from "../colors.js";
3
+
4
+ export const ICON_NAMES = [
5
+ // Navigation/Actions
6
+ "arrow-right",
7
+ "arrow-left",
8
+ "external-link",
9
+ "chevron-right",
10
+ // Status
11
+ "check",
12
+ "x",
13
+ "alert-triangle",
14
+ "info",
15
+ "clock",
16
+ // Social
17
+ "heart",
18
+ "message-circle",
19
+ "repeat",
20
+ "share",
21
+ "user",
22
+ "users",
23
+ // Content
24
+ "star",
25
+ "trophy",
26
+ "zap",
27
+ "flame",
28
+ "gift",
29
+ // Media
30
+ "image",
31
+ "play",
32
+ "pause",
33
+ // Commerce
34
+ "wallet",
35
+ "coins",
36
+ // Common actions
37
+ "plus",
38
+ "minus",
39
+ "refresh-cw",
40
+ "bookmark",
41
+ // Feedback/data
42
+ "thumbs-up",
43
+ "thumbs-down",
44
+ "trending-up",
45
+ "trending-down",
46
+ ] as const;
47
+
48
+ export const ICON_SIZES = ["sm", "md"] as const;
49
+
50
+ export const iconProps = z.object({
51
+ name: z.enum(ICON_NAMES),
52
+ color: z.enum(PROGRESS_COLOR_VALUES).optional(),
53
+ size: z.enum(ICON_SIZES).optional(),
54
+ });
55
+
56
+ export type IconProps = z.infer<typeof iconProps>;
package/src/ui/image.ts CHANGED
@@ -1,9 +1,10 @@
1
1
  import { z } from "zod";
2
- import { IMAGE_ASPECT_VALUES } from "../constants.js";
2
+
3
+ export const IMAGE_ASPECTS = ["1:1", "16:9", "4:3", "3:4", "9:16"] as const;
3
4
 
4
5
  export const imageProps = z.object({
5
6
  url: z.string(),
6
- aspect: z.enum(IMAGE_ASPECT_VALUES),
7
+ aspect: z.enum(IMAGE_ASPECTS),
7
8
  alt: z.string().optional(),
8
9
  });
9
10
 
package/src/ui/index.ts CHANGED
@@ -1,47 +1,44 @@
1
1
  export { snapJsonRenderSchema } from "./schema.js";
2
2
  export { snapJsonRenderCatalog } from "./catalog.js";
3
3
 
4
- export { textProps } from "./text.js";
5
- export type { TextProps } from "./text.js";
4
+ export { badgeProps } from "./badge.js";
5
+ export type { BadgeProps } from "./badge.js";
6
6
 
7
- export { imageProps } from "./image.js";
8
- export type { ImageProps } from "./image.js";
7
+ export { buttonProps } from "./button.js";
8
+ export type { ButtonProps } from "./button.js";
9
9
 
10
- export { dividerProps } from "./divider.js";
11
- export type { DividerProps } from "./divider.js";
10
+ export { switchProps } from "./switch.js";
11
+ export type { SwitchProps } from "./switch.js";
12
12
 
13
- export { spacerProps } from "./spacer.js";
14
- export type { SpacerProps } from "./spacer.js";
13
+ export { toggleGroupProps } from "./toggle-group.js";
14
+ export type { ToggleGroupProps } from "./toggle-group.js";
15
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";
16
+ export { inputProps } from "./input.js";
17
+ export type { InputProps } from "./input.js";
21
18
 
22
- export { gridProps } from "./grid.js";
23
- export type { GridProps } from "./grid.js";
19
+ export { itemProps } from "./item.js";
20
+ export type { ItemProps } from "./item.js";
24
21
 
25
- export { textInputProps } from "./text-input.js";
26
- export type { TextInputProps } from "./text-input.js";
22
+ export { itemGroupProps } from "./item-group.js";
23
+ export type { ItemGroupProps } from "./item-group.js";
27
24
 
28
- export { sliderProps } from "./slider.js";
29
- export type { SliderProps } from "./slider.js";
25
+ export { iconProps, ICON_NAMES } from "./icon.js";
26
+ export type { IconProps } from "./icon.js";
30
27
 
31
- export { buttonGroupProps } from "./button-group.js";
32
- export type { ButtonGroupProps } from "./button-group.js";
28
+ export { imageProps } from "./image.js";
29
+ export type { ImageProps } from "./image.js";
33
30
 
34
- export { toggleProps } from "./toggle.js";
35
- export type { ToggleProps } from "./toggle.js";
31
+ export { progressProps } from "./progress.js";
32
+ export type { ProgressProps } from "./progress.js";
36
33
 
37
- export { barChartProps } from "./bar-chart.js";
38
- export type { BarChartProps } from "./bar-chart.js";
34
+ export { separatorProps } from "./separator.js";
35
+ export type { SeparatorProps } from "./separator.js";
39
36
 
40
- export { groupProps } from "./group.js";
41
- export type { GroupProps } from "./group.js";
37
+ export { sliderProps } from "./slider.js";
38
+ export type { SliderProps } from "./slider.js";
42
39
 
43
40
  export { stackProps } from "./stack.js";
44
41
  export type { StackProps } from "./stack.js";
45
42
 
46
- export { actionButtonProps, buttonProps } from "./button.js";
47
- export type { ActionButtonProps, ButtonProps } from "./button.js";
43
+ export { textProps } from "./text.js";
44
+ export type { TextProps } from "./text.js";
@@ -0,0 +1,17 @@
1
+ import { z } from "zod";
2
+
3
+ export const INPUT_TYPES = ["text", "number"] as const;
4
+ export const INPUT_MAX_CHARS = 280;
5
+ export const INPUT_MAX_LABEL_CHARS = 60;
6
+ export const INPUT_MAX_PLACEHOLDER_CHARS = 60;
7
+
8
+ export const inputProps = z.object({
9
+ name: z.string().min(1),
10
+ type: z.enum(INPUT_TYPES).optional(),
11
+ label: z.string().max(INPUT_MAX_LABEL_CHARS).optional(),
12
+ placeholder: z.string().max(INPUT_MAX_PLACEHOLDER_CHARS).optional(),
13
+ defaultValue: z.string().optional(),
14
+ maxLength: z.number().int().min(1).max(INPUT_MAX_CHARS).optional(),
15
+ });
16
+
17
+ export type InputProps = z.infer<typeof inputProps>;
@@ -0,0 +1,11 @@
1
+ import { z } from "zod";
2
+
3
+ import { STACK_GAPS } from "./stack.js";
4
+
5
+ export const itemGroupProps = z.object({
6
+ border: z.boolean().optional(),
7
+ separator: z.boolean().optional(),
8
+ gap: z.enum(STACK_GAPS).optional(),
9
+ });
10
+
11
+ export type ItemGroupProps = z.infer<typeof itemGroupProps>;
package/src/ui/item.ts ADDED
@@ -0,0 +1,13 @@
1
+ import { z } from "zod";
2
+
3
+ export const ITEM_VARIANTS = ["default", "outline", "muted"] as const;
4
+ export const ITEM_MAX_TITLE_CHARS = 100;
5
+ export const ITEM_MAX_DESCRIPTION_CHARS = 160;
6
+
7
+ export const itemProps = z.object({
8
+ title: z.string().min(1).max(ITEM_MAX_TITLE_CHARS),
9
+ description: z.string().max(ITEM_MAX_DESCRIPTION_CHARS).optional(),
10
+ variant: z.enum(ITEM_VARIANTS).optional(),
11
+ });
12
+
13
+ export type ItemProps = z.infer<typeof itemProps>;
@@ -1,11 +1,29 @@
1
1
  import { z } from "zod";
2
- import { PROGRESS_COLOR_VALUES } from "../colors.js";
3
2
 
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
- });
3
+ export const PROGRESS_MAX_LABEL_CHARS = 60;
4
+
5
+ export const progressProps = z
6
+ .object({
7
+ value: z.number(),
8
+ max: z.number(),
9
+ label: z.string().max(PROGRESS_MAX_LABEL_CHARS).optional(),
10
+ })
11
+ .superRefine((val, ctx) => {
12
+ if (!Number.isFinite(val.max) || val.max <= 0) {
13
+ ctx.addIssue({
14
+ code: "custom",
15
+ message: `progress max must be a finite number > 0 (got ${val.max})`,
16
+ path: ["max"],
17
+ });
18
+ return;
19
+ }
20
+ if (!Number.isFinite(val.value) || val.value < 0 || val.value > val.max) {
21
+ ctx.addIssue({
22
+ code: "custom",
23
+ message: `progress value (${val.value}) must be between 0 and max (${val.max})`,
24
+ path: ["value"],
25
+ });
26
+ }
27
+ });
10
28
 
11
29
  export type ProgressProps = z.infer<typeof progressProps>;
package/src/ui/schema.ts CHANGED
@@ -29,9 +29,9 @@ export const snapJsonRenderSchema = defineSchema(
29
29
  }),
30
30
  {
31
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 / client.",
32
+ "You are generating auxiliary UI for a Farcaster Snap. Prefer components matching snap element types (Item, Badge, ButtonGroup, Input, Switch, ToggleGroup, Slider, Progress, Image, Separator).",
33
+ "Snap pages use a Stack root with at most 6 body children and 1 media element (Image); keep generated trees small.",
34
+ "Bottom-of-card snap buttons are Button components; use actions post / link / mini_app / sdk per SPEC.md.",
35
35
  ],
36
36
  },
37
37
  );
@@ -0,0 +1,9 @@
1
+ import { z } from "zod";
2
+
3
+ export const SEPARATOR_ORIENTATIONS = ["horizontal", "vertical"] as const;
4
+
5
+ export const separatorProps = z.object({
6
+ orientation: z.enum(SEPARATOR_ORIENTATIONS).optional(),
7
+ });
8
+
9
+ export type SeparatorProps = z.infer<typeof separatorProps>;
package/src/ui/slider.ts CHANGED
@@ -1,14 +1,44 @@
1
1
  import { z } from "zod";
2
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
- });
3
+ export const SLIDER_MAX_LABEL_CHARS = 60;
4
+ export const SLIDER_DEFAULT_STEP = 1;
5
+ export const SLIDER_STEP_ALIGN_EPS = 1e-6;
6
+
7
+ export const sliderProps = z
8
+ .object({
9
+ name: z.string().min(1),
10
+ min: z.number(),
11
+ max: z.number(),
12
+ step: z.number().optional(),
13
+ defaultValue: z.number().optional(),
14
+ label: z.string().max(SLIDER_MAX_LABEL_CHARS).optional(),
15
+ })
16
+ .superRefine((val, ctx) => {
17
+ if (val.min > val.max) {
18
+ ctx.addIssue({
19
+ code: "custom",
20
+ message: `slider min (${val.min}) must be <= max (${val.max})`,
21
+ path: ["min"],
22
+ });
23
+ return;
24
+ }
25
+ if (val.step !== undefined && (val.step <= 0 || !Number.isFinite(val.step))) {
26
+ ctx.addIssue({
27
+ code: "custom",
28
+ message: "slider step must be a finite number > 0",
29
+ path: ["step"],
30
+ });
31
+ return;
32
+ }
33
+ if (val.defaultValue !== undefined) {
34
+ if (val.defaultValue < val.min || val.defaultValue > val.max) {
35
+ ctx.addIssue({
36
+ code: "custom",
37
+ message: `slider defaultValue (${val.defaultValue}) must be between min (${val.min}) and max (${val.max})`,
38
+ path: ["defaultValue"],
39
+ });
40
+ }
41
+ }
42
+ });
13
43
 
14
44
  export type SliderProps = z.infer<typeof sliderProps>;
package/src/ui/stack.ts CHANGED
@@ -1,5 +1,13 @@
1
1
  import { z } from "zod";
2
2
 
3
- export const stackProps = z.object({});
3
+ export const STACK_DIRECTIONS = ["vertical", "horizontal"] as const;
4
+ export const STACK_GAPS = ["none", "sm", "md", "lg"] as const;
5
+ export const STACK_JUSTIFY = ["start", "center", "end", "between", "around"] as const;
6
+
7
+ export const stackProps = z.object({
8
+ direction: z.enum(STACK_DIRECTIONS).optional(),
9
+ gap: z.enum(STACK_GAPS).optional(),
10
+ justify: z.enum(STACK_JUSTIFY).optional(),
11
+ });
4
12
 
5
13
  export type StackProps = z.infer<typeof stackProps>;
@@ -0,0 +1,11 @@
1
+ import { z } from "zod";
2
+
3
+ export const SWITCH_MAX_LABEL_CHARS = 60;
4
+
5
+ export const switchProps = z.object({
6
+ name: z.string().min(1),
7
+ label: z.string().max(SWITCH_MAX_LABEL_CHARS).optional(),
8
+ defaultChecked: z.boolean().optional(),
9
+ });
10
+
11
+ export type SwitchProps = z.infer<typeof switchProps>;
package/src/ui/text.ts CHANGED
@@ -1,10 +1,15 @@
1
1
  import { z } from "zod";
2
- import { TEXT_ALIGN_VALUES, TEXT_STYLE_VALUES } from "../constants.js";
2
+
3
+ export const TEXT_SIZES = ["lg", "md", "sm"] as const;
4
+ export const TEXT_WEIGHTS = ["bold", "medium", "normal"] as const;
5
+ export const TEXT_ALIGNS = ["left", "center", "right"] as const;
6
+ export const TEXT_MAX_CONTENT_CHARS = 320;
3
7
 
4
8
  export const textProps = z.object({
5
- style: z.enum(TEXT_STYLE_VALUES),
6
- content: z.string(),
7
- align: z.enum(TEXT_ALIGN_VALUES).optional(),
9
+ content: z.string().min(1).max(TEXT_MAX_CONTENT_CHARS),
10
+ size: z.enum(TEXT_SIZES).optional(),
11
+ weight: z.enum(TEXT_WEIGHTS).optional(),
12
+ align: z.enum(TEXT_ALIGNS).optional(),
8
13
  });
9
14
 
10
15
  export type TextProps = z.infer<typeof textProps>;