catalog.ts 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516
  1. import { z } from "zod";
  2. // =============================================================================
  3. // Standard Component Definitions for React Native
  4. // =============================================================================
  5. /**
  6. * Standard component definitions for React Native catalogs.
  7. *
  8. * These can be used directly or extended with custom components.
  9. * All components are built using React Native core primitives only.
  10. */
  11. export const standardComponentDefinitions = {
  12. // ==========================================================================
  13. // Layout Components
  14. // ==========================================================================
  15. Container: {
  16. props: z.object({
  17. padding: z.number().nullable(),
  18. paddingHorizontal: z.number().nullable(),
  19. paddingVertical: z.number().nullable(),
  20. margin: z.number().nullable(),
  21. backgroundColor: z.string().nullable(),
  22. borderRadius: z.number().nullable(),
  23. flex: z.number().nullable(),
  24. }),
  25. slots: ["default"],
  26. description:
  27. "Generic container wrapper. Use for grouping elements with padding, margin, and background color.",
  28. example: { padding: 16, backgroundColor: "#FFFFFF" },
  29. },
  30. Row: {
  31. props: z.object({
  32. gap: z.number().nullable(),
  33. alignItems: z
  34. .enum(["flex-start", "center", "flex-end", "stretch", "baseline"])
  35. .nullable(),
  36. justifyContent: z
  37. .enum([
  38. "flex-start",
  39. "center",
  40. "flex-end",
  41. "space-between",
  42. "space-around",
  43. "space-evenly",
  44. ])
  45. .nullable(),
  46. flexWrap: z.enum(["wrap", "nowrap"]).nullable(),
  47. padding: z.number().nullable(),
  48. flex: z.number().nullable(),
  49. }),
  50. slots: ["default"],
  51. description:
  52. "Horizontal flex layout. Use for placing elements side by side.",
  53. example: { gap: 12, alignItems: "center" },
  54. },
  55. Column: {
  56. props: z.object({
  57. gap: z.number().nullable(),
  58. alignItems: z
  59. .enum(["flex-start", "center", "flex-end", "stretch", "baseline"])
  60. .nullable(),
  61. justifyContent: z
  62. .enum([
  63. "flex-start",
  64. "center",
  65. "flex-end",
  66. "space-between",
  67. "space-around",
  68. "space-evenly",
  69. ])
  70. .nullable(),
  71. padding: z.number().nullable(),
  72. flex: z.number().nullable(),
  73. }),
  74. slots: ["default"],
  75. description:
  76. "Vertical flex layout. Use for stacking elements top to bottom.",
  77. example: { gap: 12, padding: 16 },
  78. },
  79. ScrollContainer: {
  80. props: z.object({
  81. horizontal: z.boolean().nullable(),
  82. showsScrollIndicator: z.boolean().nullable(),
  83. padding: z.number().nullable(),
  84. backgroundColor: z.string().nullable(),
  85. }),
  86. slots: ["default"],
  87. description:
  88. "Scrollable container. Use for content that may overflow the screen.",
  89. },
  90. SafeArea: {
  91. props: z.object({
  92. backgroundColor: z.string().nullable(),
  93. }),
  94. slots: ["default"],
  95. description:
  96. "Safe area container that respects device notches and system bars. Use as the outermost wrapper for screens.",
  97. },
  98. Spacer: {
  99. props: z.object({
  100. size: z.number().nullable(),
  101. flex: z.number().nullable(),
  102. }),
  103. slots: [],
  104. description:
  105. "Empty space between elements. Set size for fixed spacing or flex for flexible spacing.",
  106. },
  107. Pressable: {
  108. props: z.object({}),
  109. events: ["press", "longPress"],
  110. slots: ["default"],
  111. description:
  112. "Touchable wrapper that triggers events on press. Wrap any element to make it tappable. Bind on.press to setState to update state for visibility-driven UIs like tabs.",
  113. },
  114. Divider: {
  115. props: z.object({
  116. direction: z.enum(["horizontal", "vertical"]).nullable(),
  117. color: z.string().nullable(),
  118. thickness: z.number().nullable(),
  119. margin: z.number().nullable(),
  120. }),
  121. slots: [],
  122. description: "Thin line separator between content sections.",
  123. },
  124. // ==========================================================================
  125. // Content Components
  126. // ==========================================================================
  127. Heading: {
  128. props: z.object({
  129. text: z.string(),
  130. level: z.enum(["h1", "h2", "h3", "h4"]).nullable(),
  131. color: z.string().nullable(),
  132. align: z.enum(["left", "center", "right"]).nullable(),
  133. }),
  134. slots: [],
  135. description:
  136. "Heading text at various levels. h1 is largest, h4 is smallest.",
  137. example: { text: "Welcome", level: "h1" },
  138. },
  139. Paragraph: {
  140. props: z.object({
  141. text: z.string(),
  142. color: z.string().nullable(),
  143. align: z.enum(["left", "center", "right"]).nullable(),
  144. numberOfLines: z.number().nullable(),
  145. fontSize: z.number().nullable(),
  146. }),
  147. slots: [],
  148. description:
  149. "Body text paragraph. Use for descriptions and longer content.",
  150. example: { text: "This is a paragraph of body text." },
  151. },
  152. Label: {
  153. props: z.object({
  154. text: z.string(),
  155. color: z.string().nullable(),
  156. bold: z.boolean().nullable(),
  157. size: z.enum(["xs", "sm", "md"]).nullable(),
  158. }),
  159. slots: [],
  160. description:
  161. "Small utility text. Use for captions, form labels, and secondary information.",
  162. example: { text: "Status", size: "sm" },
  163. },
  164. Image: {
  165. props: z.object({
  166. src: z.string(),
  167. alt: z.string().nullable(),
  168. width: z.number().nullable(),
  169. height: z.number().nullable(),
  170. resizeMode: z.enum(["cover", "contain", "stretch", "center"]).nullable(),
  171. borderRadius: z.number().nullable(),
  172. }),
  173. slots: [],
  174. description: "Image display. Provide a source URL and optional dimensions.",
  175. example: {
  176. src: "https://picsum.photos/300/200",
  177. alt: "Photo",
  178. width: 300,
  179. height: 200,
  180. },
  181. },
  182. Avatar: {
  183. props: z.object({
  184. src: z.string().nullable(),
  185. initials: z.string().nullable(),
  186. size: z.enum(["sm", "md", "lg", "xl"]).nullable(),
  187. backgroundColor: z.string().nullable(),
  188. }),
  189. slots: [],
  190. description:
  191. "Circular avatar showing an image or initials. Use for user profiles and contacts.",
  192. example: { initials: "JD", size: "md" },
  193. },
  194. Badge: {
  195. props: z.object({
  196. label: z.string(),
  197. variant: z
  198. .enum(["default", "info", "success", "warning", "error"])
  199. .nullable(),
  200. }),
  201. slots: [],
  202. description:
  203. "Small colored indicator with a label. Use for status, counts, and categories.",
  204. example: { label: "New", variant: "info" },
  205. },
  206. Chip: {
  207. props: z.object({
  208. label: z.string(),
  209. selected: z.boolean().nullable(),
  210. backgroundColor: z.string().nullable(),
  211. }),
  212. events: ["press", "remove"],
  213. slots: [],
  214. description:
  215. "Tag or filter chip. Bind on.remove for removable chips, on.press for selectable chips.",
  216. },
  217. // ==========================================================================
  218. // Input Components
  219. // ==========================================================================
  220. Button: {
  221. props: z.object({
  222. label: z.string(),
  223. variant: z
  224. .enum(["primary", "secondary", "danger", "outline", "ghost"])
  225. .nullable(),
  226. size: z.enum(["sm", "md", "lg"]).nullable(),
  227. disabled: z.boolean().nullable(),
  228. loading: z.boolean().nullable(),
  229. }),
  230. events: ["press"],
  231. slots: [],
  232. description:
  233. "Pressable button with label. Set variant for styling. Bind on.press for the handler to call on press.",
  234. example: { label: "Submit", variant: "primary" },
  235. },
  236. TextInput: {
  237. props: z.object({
  238. placeholder: z.string().nullable(),
  239. value: z.string().nullable(),
  240. secureTextEntry: z.boolean().nullable(),
  241. keyboardType: z
  242. .enum(["default", "email-address", "numeric", "phone-pad", "url"])
  243. .nullable(),
  244. multiline: z.boolean().nullable(),
  245. numberOfLines: z.number().nullable(),
  246. label: z.string().nullable(),
  247. flex: z.number().nullable(),
  248. }),
  249. events: ["submit", "focus", "blur"],
  250. slots: [],
  251. description:
  252. "Text input field. Use $bindState to bind to the state model for two-way binding. The value typed by the user is stored at the bound path.",
  253. example: {
  254. placeholder: "Enter text...",
  255. label: "Name",
  256. },
  257. },
  258. Switch: {
  259. props: z.object({
  260. checked: z.boolean().nullable(),
  261. label: z.string().nullable(),
  262. disabled: z.boolean().nullable(),
  263. }),
  264. events: ["change"],
  265. slots: [],
  266. description: "Toggle switch. Use $bindState to bind to the state model.",
  267. },
  268. Checkbox: {
  269. props: z.object({
  270. checked: z.boolean().nullable(),
  271. label: z.string().nullable(),
  272. disabled: z.boolean().nullable(),
  273. }),
  274. events: ["change"],
  275. slots: [],
  276. description:
  277. "Checkbox for boolean selections. Use $bindState to bind to the state model.",
  278. },
  279. Slider: {
  280. props: z.object({
  281. min: z.number().nullable(),
  282. max: z.number().nullable(),
  283. step: z.number().nullable(),
  284. value: z.number().nullable(),
  285. label: z.string().nullable(),
  286. color: z.string().nullable(),
  287. }),
  288. slots: [],
  289. description:
  290. "Range slider for numeric values. Use $bindState to bind to the state model.",
  291. },
  292. SearchBar: {
  293. props: z.object({
  294. placeholder: z.string().nullable(),
  295. value: z.string().nullable(),
  296. }),
  297. events: ["submit"],
  298. slots: [],
  299. description:
  300. "Search input with icon. Bind on.submit to trigger search on submit.",
  301. },
  302. // ==========================================================================
  303. // Feedback Components
  304. // ==========================================================================
  305. Spinner: {
  306. props: z.object({
  307. size: z.enum(["small", "large"]).nullable(),
  308. color: z.string().nullable(),
  309. }),
  310. slots: [],
  311. description: "Loading spinner indicator. Use while content is loading.",
  312. },
  313. ProgressBar: {
  314. props: z.object({
  315. progress: z.number(),
  316. color: z.string().nullable(),
  317. trackColor: z.string().nullable(),
  318. height: z.number().nullable(),
  319. }),
  320. slots: [],
  321. description: "Horizontal progress bar. Set progress from 0 to 1.",
  322. },
  323. // ==========================================================================
  324. // Composite Components
  325. // ==========================================================================
  326. Card: {
  327. props: z.object({
  328. title: z.string().nullable(),
  329. subtitle: z.string().nullable(),
  330. padding: z.number().nullable(),
  331. backgroundColor: z.string().nullable(),
  332. borderRadius: z.number().nullable(),
  333. elevated: z.boolean().nullable(),
  334. }),
  335. slots: ["default"],
  336. description:
  337. "Elevated card container with optional title. Use for grouping related content.",
  338. example: { title: "Details", padding: 16, elevated: true },
  339. },
  340. ListItem: {
  341. props: z.object({
  342. title: z.string(),
  343. subtitle: z.string().nullable(),
  344. leading: z.string().nullable(),
  345. trailing: z.string().nullable(),
  346. showChevron: z.boolean().nullable(),
  347. }),
  348. events: ["press"],
  349. slots: [],
  350. description:
  351. "List row with title, subtitle, and optional leading/trailing text. Bind on.press for the press handler.",
  352. example: {
  353. title: "Settings",
  354. subtitle: "Manage your preferences",
  355. showChevron: true,
  356. },
  357. },
  358. Modal: {
  359. props: z.object({
  360. visible: z.boolean(),
  361. title: z.string().nullable(),
  362. animationType: z.enum(["slide", "fade", "none"]).nullable(),
  363. }),
  364. slots: ["default"],
  365. description:
  366. "Modal overlay dialog. Use $bindState on visible to bind visibility to the state model.",
  367. },
  368. };
  369. // =============================================================================
  370. // Standard Action Definitions for React Native
  371. // =============================================================================
  372. /**
  373. * Standard action definitions for React Native catalogs.
  374. *
  375. * These use React Native core APIs (Alert, Share, Linking) and
  376. * user-provided functions (navigate).
  377. */
  378. export const standardActionDefinitions = {
  379. navigate: {
  380. params: z.object({
  381. screen: z.string(),
  382. params: z.record(z.string(), z.unknown()).nullable(),
  383. }),
  384. description:
  385. "Navigate to a screen. Calls the user-provided navigation function.",
  386. },
  387. goBack: {
  388. params: z.object({}),
  389. description: "Navigate back to the previous screen.",
  390. },
  391. showAlert: {
  392. params: z.object({
  393. title: z.string(),
  394. message: z.string().nullable(),
  395. buttons: z
  396. .array(
  397. z.object({
  398. text: z.string(),
  399. style: z.enum(["default", "cancel", "destructive"]).nullable(),
  400. action: z.string().nullable(),
  401. }),
  402. )
  403. .nullable(),
  404. }),
  405. description: "Show a native alert dialog using Alert.alert().",
  406. },
  407. share: {
  408. params: z.object({
  409. message: z.string(),
  410. url: z.string().nullable(),
  411. title: z.string().nullable(),
  412. }),
  413. description: "Open the native share sheet using Share.share().",
  414. },
  415. openURL: {
  416. params: z.object({
  417. url: z.string(),
  418. }),
  419. description: "Open a URL in the default browser using Linking.openURL().",
  420. },
  421. setState: {
  422. params: z.object({
  423. statePath: z.string(),
  424. value: z.unknown(),
  425. }),
  426. description: "Update a value in the state model at the given statePath.",
  427. },
  428. pushState: {
  429. params: z.object({
  430. statePath: z.string(),
  431. value: z.unknown(),
  432. clearStatePath: z.string().optional(),
  433. }),
  434. description:
  435. 'Append an item to an array in the state model. The value can contain { $state: "/statePath" } references to read from current state, and "$id" to auto-generate a unique ID. Use clearStatePath to reset another path after pushing (e.g. clear an input field). Example: { statePath: "/todos", value: { id: "$id", title: { $state: "/newTodoText" }, completed: false }, clearStatePath: "/newTodoText" }.',
  436. },
  437. removeState: {
  438. params: z.object({
  439. statePath: z.string(),
  440. index: z.number(),
  441. }),
  442. description:
  443. "Remove an item from an array in the state model at the given index.",
  444. },
  445. refresh: {
  446. params: z.object({
  447. target: z.string().nullable(),
  448. }),
  449. description:
  450. "Trigger a data refresh. Optionally specify a target to refresh.",
  451. },
  452. };
  453. // =============================================================================
  454. // Types
  455. // =============================================================================
  456. /**
  457. * Type for a component definition
  458. */
  459. export type ComponentDefinition = {
  460. props: z.ZodType;
  461. slots: string[];
  462. events?: string[];
  463. description: string;
  464. };
  465. /**
  466. * Type for an action definition
  467. */
  468. export type ActionDefinition = {
  469. params: z.ZodType;
  470. description: string;
  471. };