catalog.ts 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294
  1. import { z } from "zod";
  2. /**
  3. * Standard component definitions for React PDF catalogs.
  4. *
  5. * These define the available PDF components with their Zod prop schemas.
  6. * All components render using @react-pdf/renderer primitives.
  7. */
  8. export const standardComponentDefinitions = {
  9. // ==========================================================================
  10. // Document Structure
  11. // ==========================================================================
  12. Document: {
  13. props: z.object({
  14. title: z.string().nullable(),
  15. author: z.string().nullable(),
  16. subject: z.string().nullable(),
  17. }),
  18. slots: ["default"],
  19. description:
  20. "Top-level PDF document wrapper. Must be the root element. Children must be Page components.",
  21. example: { title: "Invoice #1234", author: "Acme Corp" },
  22. },
  23. Page: {
  24. props: z.object({
  25. size: z.enum(["A4", "A3", "A5", "LETTER", "LEGAL", "TABLOID"]).nullable(),
  26. orientation: z.enum(["portrait", "landscape"]).nullable(),
  27. marginTop: z.number().nullable(),
  28. marginBottom: z.number().nullable(),
  29. marginLeft: z.number().nullable(),
  30. marginRight: z.number().nullable(),
  31. backgroundColor: z.string().nullable(),
  32. }),
  33. slots: ["default"],
  34. description:
  35. "A page in the PDF document. Set size and orientation. Children are laid out vertically by default.",
  36. example: { size: "A4", orientation: "portrait" },
  37. },
  38. // ==========================================================================
  39. // Layout Components
  40. // ==========================================================================
  41. View: {
  42. props: z.object({
  43. padding: z.number().nullable(),
  44. paddingTop: z.number().nullable(),
  45. paddingBottom: z.number().nullable(),
  46. paddingLeft: z.number().nullable(),
  47. paddingRight: z.number().nullable(),
  48. margin: z.number().nullable(),
  49. backgroundColor: z.string().nullable(),
  50. borderWidth: z.number().nullable(),
  51. borderColor: z.string().nullable(),
  52. borderRadius: z.number().nullable(),
  53. flex: z.number().nullable(),
  54. alignItems: z
  55. .enum(["flex-start", "center", "flex-end", "stretch"])
  56. .nullable(),
  57. justifyContent: z
  58. .enum([
  59. "flex-start",
  60. "center",
  61. "flex-end",
  62. "space-between",
  63. "space-around",
  64. ])
  65. .nullable(),
  66. }),
  67. slots: ["default"],
  68. description:
  69. "Generic container for grouping elements. Supports padding, margin, background, border, and flex alignment.",
  70. example: { padding: 10, backgroundColor: "#f9f9f9", alignItems: "center" },
  71. },
  72. Row: {
  73. props: z.object({
  74. gap: z.number().nullable(),
  75. alignItems: z
  76. .enum(["flex-start", "center", "flex-end", "stretch"])
  77. .nullable(),
  78. justifyContent: z
  79. .enum([
  80. "flex-start",
  81. "center",
  82. "flex-end",
  83. "space-between",
  84. "space-around",
  85. ])
  86. .nullable(),
  87. padding: z.number().nullable(),
  88. flex: z.number().nullable(),
  89. wrap: z.boolean().nullable(),
  90. }),
  91. slots: ["default"],
  92. description:
  93. "Horizontal flex layout. Use for placing elements side by side.",
  94. example: { gap: 10, alignItems: "center" },
  95. },
  96. Column: {
  97. props: z.object({
  98. gap: z.number().nullable(),
  99. alignItems: z
  100. .enum(["flex-start", "center", "flex-end", "stretch"])
  101. .nullable(),
  102. justifyContent: z
  103. .enum([
  104. "flex-start",
  105. "center",
  106. "flex-end",
  107. "space-between",
  108. "space-around",
  109. ])
  110. .nullable(),
  111. padding: z.number().nullable(),
  112. flex: z.number().nullable(),
  113. }),
  114. slots: ["default"],
  115. description:
  116. "Vertical flex layout. Use for stacking elements top to bottom.",
  117. example: { gap: 8, padding: 10 },
  118. },
  119. // ==========================================================================
  120. // Content Components
  121. // ==========================================================================
  122. Heading: {
  123. props: z.object({
  124. text: z.string(),
  125. level: z.enum(["h1", "h2", "h3", "h4"]).nullable(),
  126. color: z.string().nullable(),
  127. align: z.enum(["left", "center", "right"]).nullable(),
  128. }),
  129. slots: [],
  130. description:
  131. "Heading text at various levels. h1 is largest, h4 is smallest.",
  132. example: { text: "Invoice", level: "h1" },
  133. },
  134. Text: {
  135. props: z.object({
  136. text: z.string(),
  137. fontSize: z.number().nullable(),
  138. color: z.string().nullable(),
  139. align: z.enum(["left", "center", "right"]).nullable(),
  140. fontWeight: z.enum(["normal", "bold"]).nullable(),
  141. fontStyle: z.enum(["normal", "italic"]).nullable(),
  142. lineHeight: z.number().nullable(),
  143. }),
  144. slots: [],
  145. description:
  146. "Body text with configurable size, color, weight, and alignment.",
  147. example: { text: "Thank you for your business." },
  148. },
  149. Image: {
  150. props: z.object({
  151. src: z.string(),
  152. width: z.number().nullable(),
  153. height: z.number().nullable(),
  154. objectFit: z.enum(["contain", "cover", "fill", "none"]).nullable(),
  155. }),
  156. slots: [],
  157. description:
  158. "Image from a URL. Specify width and/or height to control size. For placeholder/stock images use https://picsum.photos/{width}/{height}?random={n} where {n} is a unique number per image.",
  159. example: {
  160. src: "https://picsum.photos/400/300?random=1",
  161. width: 400,
  162. height: 300,
  163. },
  164. },
  165. Link: {
  166. props: z.object({
  167. text: z.string(),
  168. href: z.string(),
  169. fontSize: z.number().nullable(),
  170. color: z.string().nullable(),
  171. }),
  172. slots: [],
  173. description: "Hyperlink with visible text and a URL.",
  174. example: { text: "Visit our website", href: "https://example.com" },
  175. },
  176. // ==========================================================================
  177. // Data Components
  178. // ==========================================================================
  179. Table: {
  180. props: z.object({
  181. columns: z.array(
  182. z.object({
  183. header: z.string(),
  184. width: z.string().nullable(),
  185. align: z.enum(["left", "center", "right"]).nullable(),
  186. }),
  187. ),
  188. rows: z.array(z.array(z.string())),
  189. headerBackgroundColor: z.string().nullable(),
  190. headerTextColor: z.string().nullable(),
  191. borderColor: z.string().nullable(),
  192. fontSize: z.number().nullable(),
  193. striped: z.boolean().nullable(),
  194. }),
  195. slots: [],
  196. description:
  197. "Data table with typed columns and rows. Each row is a string array matching the column count.",
  198. example: {
  199. columns: [
  200. { header: "Item", width: "60%" },
  201. { header: "Price", width: "40%", align: "right" },
  202. ],
  203. rows: [
  204. ["Widget A", "$10.00"],
  205. ["Widget B", "$25.00"],
  206. ],
  207. },
  208. },
  209. List: {
  210. props: z.object({
  211. items: z.array(z.string()),
  212. ordered: z.boolean().nullable(),
  213. fontSize: z.number().nullable(),
  214. color: z.string().nullable(),
  215. spacing: z.number().nullable(),
  216. }),
  217. slots: [],
  218. description:
  219. "Ordered or unordered list. Each item is rendered as a text line with a bullet or number.",
  220. example: {
  221. items: ["First item", "Second item", "Third item"],
  222. ordered: false,
  223. },
  224. },
  225. // ==========================================================================
  226. // Decorative Components
  227. // ==========================================================================
  228. Divider: {
  229. props: z.object({
  230. color: z.string().nullable(),
  231. thickness: z.number().nullable(),
  232. marginTop: z.number().nullable(),
  233. marginBottom: z.number().nullable(),
  234. }),
  235. slots: [],
  236. description: "Horizontal line separator between content sections.",
  237. example: { color: "#e5e7eb", thickness: 1 },
  238. },
  239. Spacer: {
  240. props: z.object({
  241. height: z.number().nullable(),
  242. }),
  243. slots: [],
  244. description: "Empty vertical space between elements.",
  245. example: { height: 20 },
  246. },
  247. // ==========================================================================
  248. // Page-Level Components
  249. // ==========================================================================
  250. PageNumber: {
  251. props: z.object({
  252. format: z.string().nullable(),
  253. fontSize: z.number().nullable(),
  254. color: z.string().nullable(),
  255. align: z.enum(["left", "center", "right"]).nullable(),
  256. }),
  257. slots: [],
  258. description:
  259. 'Renders the current page number and total pages. Format uses {pageNumber} and {totalPages} placeholders, e.g. "Page {pageNumber} of {totalPages}". Default: "{pageNumber} / {totalPages}".',
  260. example: {
  261. format: "Page {pageNumber} of {totalPages}",
  262. align: "center",
  263. fontSize: 10,
  264. },
  265. },
  266. };
  267. export type StandardComponentDefinitions = typeof standardComponentDefinitions;
  268. export type StandardComponentProps<
  269. K extends keyof StandardComponentDefinitions,
  270. > = StandardComponentDefinitions[K]["props"] extends { _output: infer O }
  271. ? O
  272. : z.output<StandardComponentDefinitions[K]["props"]>;