catalog.ts 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228
  1. import { z } from "zod";
  2. /**
  3. * Standard component definitions for image catalogs.
  4. *
  5. * These define the available image components with their Zod prop schemas.
  6. * All components render to Satori-compatible JSX (HTML-like elements with
  7. * inline CSS flexbox styles).
  8. */
  9. export const standardComponentDefinitions = {
  10. // ==========================================================================
  11. // Root
  12. // ==========================================================================
  13. Frame: {
  14. props: z.object({
  15. width: z.number(),
  16. height: z.number(),
  17. backgroundColor: z.string().nullable(),
  18. padding: z.number().nullable(),
  19. display: z.enum(["flex", "none"]).nullable(),
  20. flexDirection: z.enum(["row", "column"]).nullable(),
  21. alignItems: z
  22. .enum(["flex-start", "center", "flex-end", "stretch"])
  23. .nullable(),
  24. justifyContent: z
  25. .enum([
  26. "flex-start",
  27. "center",
  28. "flex-end",
  29. "space-between",
  30. "space-around",
  31. ])
  32. .nullable(),
  33. }),
  34. slots: ["default"],
  35. description:
  36. "Root image container. Defines the output image dimensions and background. Must be the root element.",
  37. example: { width: 1200, height: 630, backgroundColor: "#ffffff" },
  38. },
  39. // ==========================================================================
  40. // Layout Components
  41. // ==========================================================================
  42. Box: {
  43. props: z.object({
  44. padding: z.number().nullable(),
  45. paddingTop: z.number().nullable(),
  46. paddingBottom: z.number().nullable(),
  47. paddingLeft: z.number().nullable(),
  48. paddingRight: z.number().nullable(),
  49. margin: z.number().nullable(),
  50. backgroundColor: z.string().nullable(),
  51. borderWidth: z.number().nullable(),
  52. borderColor: z.string().nullable(),
  53. borderRadius: z.number().nullable(),
  54. flex: z.number().nullable(),
  55. width: z.union([z.number(), z.string()]).nullable(),
  56. height: z.union([z.number(), z.string()]).nullable(),
  57. alignItems: z
  58. .enum(["flex-start", "center", "flex-end", "stretch"])
  59. .nullable(),
  60. justifyContent: z
  61. .enum([
  62. "flex-start",
  63. "center",
  64. "flex-end",
  65. "space-between",
  66. "space-around",
  67. ])
  68. .nullable(),
  69. flexDirection: z.enum(["row", "column"]).nullable(),
  70. position: z.enum(["relative", "absolute"]).nullable(),
  71. top: z.number().nullable(),
  72. left: z.number().nullable(),
  73. right: z.number().nullable(),
  74. bottom: z.number().nullable(),
  75. overflow: z.enum(["visible", "hidden"]).nullable(),
  76. }),
  77. slots: ["default"],
  78. description:
  79. "Generic container with padding, margin, background, border, and flex alignment. Supports absolute positioning.",
  80. example: {
  81. padding: 20,
  82. backgroundColor: "#f9f9f9",
  83. borderRadius: 8,
  84. alignItems: "center",
  85. },
  86. },
  87. Row: {
  88. props: z.object({
  89. gap: z.number().nullable(),
  90. alignItems: z
  91. .enum(["flex-start", "center", "flex-end", "stretch"])
  92. .nullable(),
  93. justifyContent: z
  94. .enum([
  95. "flex-start",
  96. "center",
  97. "flex-end",
  98. "space-between",
  99. "space-around",
  100. ])
  101. .nullable(),
  102. padding: z.number().nullable(),
  103. flex: z.number().nullable(),
  104. wrap: z.boolean().nullable(),
  105. }),
  106. slots: ["default"],
  107. description:
  108. "Horizontal flex layout. Use for placing elements side by side.",
  109. example: { gap: 10, alignItems: "center" },
  110. },
  111. Column: {
  112. props: z.object({
  113. gap: z.number().nullable(),
  114. alignItems: z
  115. .enum(["flex-start", "center", "flex-end", "stretch"])
  116. .nullable(),
  117. justifyContent: z
  118. .enum([
  119. "flex-start",
  120. "center",
  121. "flex-end",
  122. "space-between",
  123. "space-around",
  124. ])
  125. .nullable(),
  126. padding: z.number().nullable(),
  127. flex: z.number().nullable(),
  128. }),
  129. slots: ["default"],
  130. description:
  131. "Vertical flex layout. Use for stacking elements top to bottom.",
  132. example: { gap: 8, padding: 10 },
  133. },
  134. // ==========================================================================
  135. // Content Components
  136. // ==========================================================================
  137. Heading: {
  138. props: z.object({
  139. text: z.string(),
  140. level: z.enum(["h1", "h2", "h3", "h4"]).nullable(),
  141. color: z.string().nullable(),
  142. align: z.enum(["left", "center", "right"]).nullable(),
  143. letterSpacing: z.union([z.number(), z.string()]).nullable(),
  144. lineHeight: z.number().nullable(),
  145. }),
  146. slots: [],
  147. description:
  148. "Heading text at various levels. h1 is largest, h4 is smallest.",
  149. example: { text: "Hello World", level: "h1", color: "#000000" },
  150. },
  151. Text: {
  152. props: z.object({
  153. text: z.string(),
  154. fontSize: z.number().nullable(),
  155. color: z.string().nullable(),
  156. align: z.enum(["left", "center", "right"]).nullable(),
  157. fontWeight: z.enum(["normal", "bold"]).nullable(),
  158. fontStyle: z.enum(["normal", "italic"]).nullable(),
  159. lineHeight: z.number().nullable(),
  160. letterSpacing: z.union([z.number(), z.string()]).nullable(),
  161. textDecoration: z.enum(["none", "underline", "line-through"]).nullable(),
  162. }),
  163. slots: [],
  164. description:
  165. "Body text with configurable size, color, weight, and alignment.",
  166. example: { text: "Some content here.", fontSize: 16, color: "#333333" },
  167. },
  168. Image: {
  169. props: z.object({
  170. src: z.string(),
  171. width: z.number().nullable(),
  172. height: z.number().nullable(),
  173. borderRadius: z.number().nullable(),
  174. objectFit: z.enum(["contain", "cover", "fill", "none"]).nullable(),
  175. }),
  176. slots: [],
  177. description:
  178. "Image from a URL. Specify width and/or height to control size. For placeholder images use https://picsum.photos/{width}/{height}?random={n}.",
  179. example: {
  180. src: "https://picsum.photos/400/300?random=1",
  181. width: 400,
  182. height: 300,
  183. },
  184. },
  185. // ==========================================================================
  186. // Decorative Components
  187. // ==========================================================================
  188. Divider: {
  189. props: z.object({
  190. color: z.string().nullable(),
  191. thickness: z.number().nullable(),
  192. marginTop: z.number().nullable(),
  193. marginBottom: z.number().nullable(),
  194. }),
  195. slots: [],
  196. description: "Horizontal line separator between content sections.",
  197. example: { color: "#e5e7eb", thickness: 1 },
  198. },
  199. Spacer: {
  200. props: z.object({
  201. height: z.number().nullable(),
  202. }),
  203. slots: [],
  204. description: "Empty vertical space between elements.",
  205. example: { height: 20 },
  206. },
  207. };
  208. export type StandardComponentDefinitions = typeof standardComponentDefinitions;
  209. export type StandardComponentProps<
  210. K extends keyof StandardComponentDefinitions,
  211. > = StandardComponentDefinitions[K]["props"] extends { _output: infer O }
  212. ? O
  213. : z.output<StandardComponentDefinitions[K]["props"]>;