examples.ts 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860
  1. import type { Spec } from "@json-render/core";
  2. export interface Example {
  3. name: string;
  4. description: string;
  5. spec: Spec;
  6. }
  7. export const examples: Example[] = [
  8. {
  9. name: "Confetti",
  10. description: "A button that fires confetti when clicked",
  11. spec: {
  12. root: "btn",
  13. elements: {
  14. btn: {
  15. type: "Button",
  16. props: { label: "Click me", variant: "primary" },
  17. on: { press: { action: "confetti" } },
  18. },
  19. },
  20. },
  21. },
  22. {
  23. name: "User Profile Card",
  24. description: "A card with user info, badges, and a progress bar",
  25. spec: {
  26. root: "card",
  27. elements: {
  28. card: {
  29. type: "Card",
  30. props: {
  31. title: "User Profile",
  32. description: null,
  33. maxWidth: "md",
  34. centered: null,
  35. },
  36. children: ["stack"],
  37. },
  38. stack: {
  39. type: "Stack",
  40. props: {
  41. direction: "vertical",
  42. gap: "md",
  43. align: null,
  44. justify: null,
  45. },
  46. children: ["heading", "text", "badges", "sep", "progress"],
  47. },
  48. heading: {
  49. type: "Heading",
  50. props: { text: "Jane Cooper", level: "h2" },
  51. },
  52. text: {
  53. type: "Text",
  54. props: {
  55. text: "Senior software engineer based in San Francisco. Passionate about building accessible, high-performance web applications.",
  56. variant: "muted",
  57. },
  58. },
  59. badges: {
  60. type: "Stack",
  61. props: {
  62. direction: "horizontal",
  63. gap: "sm",
  64. align: null,
  65. justify: null,
  66. },
  67. children: ["b1", "b2", "b3"],
  68. },
  69. b1: {
  70. type: "Badge",
  71. props: { text: "TypeScript", variant: "default" },
  72. },
  73. b2: { type: "Badge", props: { text: "React", variant: "secondary" } },
  74. b3: { type: "Badge", props: { text: "Node.js", variant: "outline" } },
  75. sep: { type: "Separator", props: { orientation: null } },
  76. progress: {
  77. type: "Progress",
  78. props: { value: 72, max: 100, label: "Profile completion" },
  79. },
  80. },
  81. },
  82. },
  83. {
  84. name: "Settings Form",
  85. description: "A form with inputs, selects, switches, and checkboxes",
  86. spec: {
  87. root: "card",
  88. state: {
  89. name: "Ada Lovelace",
  90. email: "ada@example.com",
  91. role: "Engineer",
  92. notifications: true,
  93. darkMode: false,
  94. },
  95. elements: {
  96. card: {
  97. type: "Card",
  98. props: {
  99. title: "Account Settings",
  100. description: "Manage your preferences",
  101. maxWidth: "md",
  102. centered: null,
  103. },
  104. children: ["form"],
  105. },
  106. form: {
  107. type: "Stack",
  108. props: {
  109. direction: "vertical",
  110. gap: "md",
  111. align: null,
  112. justify: null,
  113. },
  114. children: [
  115. "nameInput",
  116. "emailInput",
  117. "roleSelect",
  118. "sep",
  119. "notifSwitch",
  120. "darkSwitch",
  121. "sep2",
  122. "actions",
  123. ],
  124. },
  125. nameInput: {
  126. type: "Input",
  127. props: {
  128. label: "Full Name",
  129. name: "name",
  130. type: "text",
  131. placeholder: "Your name",
  132. value: { $bindState: "/name" },
  133. checks: null,
  134. },
  135. },
  136. emailInput: {
  137. type: "Input",
  138. props: {
  139. label: "Email",
  140. name: "email",
  141. type: "email",
  142. placeholder: "you@example.com",
  143. value: { $bindState: "/email" },
  144. checks: null,
  145. },
  146. },
  147. roleSelect: {
  148. type: "Select",
  149. props: {
  150. label: "Role",
  151. name: "role",
  152. options: [
  153. "Engineer",
  154. "Designer",
  155. "Product Manager",
  156. "Data Scientist",
  157. ],
  158. placeholder: "Choose a role",
  159. value: { $bindState: "/role" },
  160. checks: null,
  161. },
  162. },
  163. sep: { type: "Separator", props: { orientation: null } },
  164. notifSwitch: {
  165. type: "Switch",
  166. props: {
  167. label: "Email notifications",
  168. name: "notifications",
  169. checked: { $bindState: "/notifications" },
  170. },
  171. },
  172. darkSwitch: {
  173. type: "Switch",
  174. props: {
  175. label: "Dark mode",
  176. name: "darkMode",
  177. checked: { $bindState: "/darkMode" },
  178. },
  179. },
  180. sep2: { type: "Separator", props: { orientation: null } },
  181. actions: {
  182. type: "Stack",
  183. props: {
  184. direction: "horizontal",
  185. gap: "sm",
  186. align: null,
  187. justify: "end",
  188. },
  189. children: ["cancelBtn", "saveBtn"],
  190. },
  191. cancelBtn: {
  192. type: "Button",
  193. props: { label: "Cancel", variant: "secondary", disabled: null },
  194. },
  195. saveBtn: {
  196. type: "Button",
  197. props: { label: "Save Changes", variant: "primary", disabled: null },
  198. },
  199. },
  200. },
  201. },
  202. {
  203. name: "Pricing Table",
  204. description: "A grid of pricing cards with feature lists",
  205. spec: {
  206. root: "outer",
  207. elements: {
  208. outer: {
  209. type: "Stack",
  210. props: {
  211. direction: "vertical",
  212. gap: "lg",
  213. align: null,
  214. justify: null,
  215. },
  216. children: ["header", "grid"],
  217. },
  218. header: {
  219. type: "Stack",
  220. props: {
  221. direction: "vertical",
  222. gap: "sm",
  223. align: "center",
  224. justify: null,
  225. },
  226. children: ["title", "subtitle"],
  227. },
  228. title: {
  229. type: "Heading",
  230. props: { text: "Simple, transparent pricing", level: "h1" },
  231. },
  232. subtitle: {
  233. type: "Text",
  234. props: {
  235. text: "Choose the plan that fits your needs. Upgrade or downgrade at any time.",
  236. variant: "muted",
  237. },
  238. },
  239. grid: {
  240. type: "Grid",
  241. props: { columns: 3, gap: "md" },
  242. children: ["free", "pro", "enterprise"],
  243. },
  244. free: {
  245. type: "Card",
  246. props: {
  247. title: "Free",
  248. description: "$0/month",
  249. maxWidth: null,
  250. centered: null,
  251. },
  252. children: ["freeContent"],
  253. },
  254. freeContent: {
  255. type: "Stack",
  256. props: {
  257. direction: "vertical",
  258. gap: "sm",
  259. align: null,
  260. justify: null,
  261. },
  262. children: ["f1", "f2", "f3", "freeBtn"],
  263. },
  264. f1: {
  265. type: "Text",
  266. props: { text: "Up to 3 projects", variant: "body" },
  267. },
  268. f2: { type: "Text", props: { text: "1 GB storage", variant: "body" } },
  269. f3: {
  270. type: "Text",
  271. props: { text: "Community support", variant: "body" },
  272. },
  273. freeBtn: {
  274. type: "Button",
  275. props: { label: "Get Started", variant: "secondary", disabled: null },
  276. },
  277. pro: {
  278. type: "Card",
  279. props: {
  280. title: "Pro",
  281. description: "$19/month",
  282. maxWidth: null,
  283. centered: null,
  284. },
  285. children: ["proContent"],
  286. },
  287. proContent: {
  288. type: "Stack",
  289. props: {
  290. direction: "vertical",
  291. gap: "sm",
  292. align: null,
  293. justify: null,
  294. },
  295. children: ["p1", "p2", "p3", "p4", "proBtn"],
  296. },
  297. p1: {
  298. type: "Text",
  299. props: { text: "Unlimited projects", variant: "body" },
  300. },
  301. p2: { type: "Text", props: { text: "50 GB storage", variant: "body" } },
  302. p3: {
  303. type: "Text",
  304. props: { text: "Priority support", variant: "body" },
  305. },
  306. p4: {
  307. type: "Text",
  308. props: { text: "Custom domains", variant: "body" },
  309. },
  310. proBtn: {
  311. type: "Button",
  312. props: {
  313. label: "Upgrade to Pro",
  314. variant: "primary",
  315. disabled: null,
  316. },
  317. },
  318. enterprise: {
  319. type: "Card",
  320. props: {
  321. title: "Enterprise",
  322. description: "Custom pricing",
  323. maxWidth: null,
  324. centered: null,
  325. },
  326. children: ["entContent"],
  327. },
  328. entContent: {
  329. type: "Stack",
  330. props: {
  331. direction: "vertical",
  332. gap: "sm",
  333. align: null,
  334. justify: null,
  335. },
  336. children: ["e1", "e2", "e3", "e4", "entBtn"],
  337. },
  338. e1: {
  339. type: "Text",
  340. props: { text: "Everything in Pro", variant: "body" },
  341. },
  342. e2: {
  343. type: "Text",
  344. props: { text: "Unlimited storage", variant: "body" },
  345. },
  346. e3: {
  347. type: "Text",
  348. props: { text: "Dedicated support", variant: "body" },
  349. },
  350. e4: {
  351. type: "Text",
  352. props: { text: "SLA guarantees", variant: "body" },
  353. },
  354. entBtn: {
  355. type: "Button",
  356. props: {
  357. label: "Contact Sales",
  358. variant: "secondary",
  359. disabled: null,
  360. },
  361. },
  362. },
  363. },
  364. },
  365. {
  366. name: "Status Dashboard",
  367. description: "System status with alerts, a table, and an accordion",
  368. spec: {
  369. root: "root",
  370. elements: {
  371. root: {
  372. type: "Stack",
  373. props: {
  374. direction: "vertical",
  375. gap: "lg",
  376. align: null,
  377. justify: null,
  378. },
  379. children: [
  380. "heading",
  381. "alertOk",
  382. "alertWarn",
  383. "sep",
  384. "table",
  385. "sep2",
  386. "accordion",
  387. ],
  388. },
  389. heading: {
  390. type: "Heading",
  391. props: { text: "System Status", level: "h1" },
  392. },
  393. alertOk: {
  394. type: "Alert",
  395. props: {
  396. title: "API",
  397. message: "All systems operational -- 99.98% uptime this month",
  398. type: "success",
  399. },
  400. },
  401. alertWarn: {
  402. type: "Alert",
  403. props: {
  404. title: "Database",
  405. message: "Elevated latency detected in us-east-1 region",
  406. type: "warning",
  407. },
  408. },
  409. sep: { type: "Separator", props: { orientation: null } },
  410. table: {
  411. type: "Table",
  412. props: {
  413. columns: ["Service", "Status", "Latency", "Uptime"],
  414. rows: [
  415. ["API Gateway", "Operational", "12ms", "99.99%"],
  416. ["Auth Service", "Operational", "8ms", "99.98%"],
  417. ["Database", "Degraded", "145ms", "99.85%"],
  418. ["CDN", "Operational", "3ms", "100%"],
  419. ["Workers", "Operational", "22ms", "99.97%"],
  420. ],
  421. caption: "Last updated 2 minutes ago",
  422. },
  423. },
  424. sep2: { type: "Separator", props: { orientation: null } },
  425. accordion: {
  426. type: "Accordion",
  427. props: {
  428. items: [
  429. {
  430. title: "What does 'Degraded' mean?",
  431. content:
  432. "The service is still operational but experiencing slower response times than normal. We are actively investigating.",
  433. },
  434. {
  435. title: "How is uptime calculated?",
  436. content:
  437. "Uptime is measured over the current calendar month based on successful health-check pings every 30 seconds.",
  438. },
  439. {
  440. title: "How do I subscribe to updates?",
  441. content:
  442. "You can subscribe to incident notifications via email or webhook on the Settings page.",
  443. },
  444. ],
  445. type: "single",
  446. },
  447. },
  448. },
  449. },
  450. },
  451. {
  452. name: "Feature Comparison",
  453. description: "Tables inside an accordion for comparing plan tiers",
  454. spec: {
  455. root: "root",
  456. elements: {
  457. root: {
  458. type: "Stack",
  459. props: {
  460. direction: "vertical",
  461. gap: "lg",
  462. align: null,
  463. justify: null,
  464. },
  465. children: ["heading", "subtitle", "accordion"],
  466. },
  467. heading: {
  468. type: "Heading",
  469. props: { text: "Feature Comparison", level: "h1" },
  470. },
  471. subtitle: {
  472. type: "Text",
  473. props: {
  474. text: "Expand each section to see how plans compare.",
  475. variant: "muted",
  476. },
  477. },
  478. accordion: {
  479. type: "Accordion",
  480. props: {
  481. items: [
  482. {
  483. title: "Projects & Team",
  484. content:
  485. "Free: 3 projects, 1 member | Pro: Unlimited projects, 10 members | Enterprise: Unlimited everything",
  486. },
  487. {
  488. title: "Storage & Bandwidth",
  489. content:
  490. "Free: 1 GB storage, 10 GB/mo bandwidth | Pro: 50 GB storage, 500 GB/mo | Enterprise: Unlimited",
  491. },
  492. {
  493. title: "Support & SLA",
  494. content:
  495. "Free: Forum, no SLA | Pro: Email + Chat, 99.9% SLA | Enterprise: Dedicated Slack, 99.99% SLA",
  496. },
  497. ],
  498. type: "single",
  499. },
  500. },
  501. },
  502. },
  503. },
  504. // =========================================================================
  505. // Advanced: Registration form with cross-field validation & $template
  506. // =========================================================================
  507. {
  508. name: "Registration Form",
  509. description:
  510. "Cross-field validation, $template preview, and validateForm action",
  511. spec: {
  512. root: "card",
  513. state: {
  514. form: {
  515. name: "",
  516. email: "",
  517. password: "",
  518. confirmPassword: "",
  519. accountType: "personal",
  520. company: "",
  521. },
  522. result: null,
  523. },
  524. elements: {
  525. card: {
  526. type: "Card",
  527. props: {
  528. title: "Create Account",
  529. description: "Fill out the form below to register",
  530. maxWidth: "md",
  531. centered: null,
  532. },
  533. children: ["formStack"],
  534. },
  535. formStack: {
  536. type: "Stack",
  537. props: {
  538. direction: "vertical",
  539. gap: "md",
  540. align: null,
  541. justify: null,
  542. },
  543. children: [
  544. "preview",
  545. "sep0",
  546. "nameInput",
  547. "emailInput",
  548. "passwordInput",
  549. "confirmInput",
  550. "sep1",
  551. "accountTypeRadio",
  552. "companyInput",
  553. "sep2",
  554. "actions",
  555. "statusText",
  556. ],
  557. },
  558. // $template live preview
  559. preview: {
  560. type: "Text",
  561. props: {
  562. text: {
  563. $template: "Welcome, ${/form/name}! Your email: ${/form/email}",
  564. },
  565. variant: "muted",
  566. },
  567. visible: { $state: "/form/name", neq: "" },
  568. children: [],
  569. },
  570. sep0: { type: "Separator", props: { orientation: null }, children: [] },
  571. nameInput: {
  572. type: "Input",
  573. props: {
  574. label: "Full Name",
  575. name: "name",
  576. type: "text",
  577. placeholder: "Jane Doe",
  578. value: { $bindState: "/form/name" },
  579. checks: [
  580. { type: "required", message: "Name is required" },
  581. {
  582. type: "minLength",
  583. args: { min: 2 },
  584. message: "Name must be at least 2 characters",
  585. },
  586. ],
  587. validateOn: "blur",
  588. },
  589. children: [],
  590. },
  591. emailInput: {
  592. type: "Input",
  593. props: {
  594. label: "Email",
  595. name: "email",
  596. type: "email",
  597. placeholder: "jane@example.com",
  598. value: { $bindState: "/form/email" },
  599. checks: [
  600. { type: "required", message: "Email is required" },
  601. { type: "email", message: "Enter a valid email address" },
  602. ],
  603. validateOn: "blur",
  604. },
  605. children: [],
  606. },
  607. passwordInput: {
  608. type: "Input",
  609. props: {
  610. label: "Password",
  611. name: "password",
  612. type: "password",
  613. placeholder: "At least 8 characters",
  614. value: { $bindState: "/form/password" },
  615. checks: [
  616. { type: "required", message: "Password is required" },
  617. {
  618. type: "minLength",
  619. args: { min: 8 },
  620. message: "Password must be at least 8 characters",
  621. },
  622. ],
  623. validateOn: "blur",
  624. },
  625. children: [],
  626. },
  627. confirmInput: {
  628. type: "Input",
  629. props: {
  630. label: "Confirm Password",
  631. name: "confirmPassword",
  632. type: "password",
  633. placeholder: "Re-enter your password",
  634. value: { $bindState: "/form/confirmPassword" },
  635. checks: [
  636. { type: "required", message: "Please confirm your password" },
  637. {
  638. type: "matches",
  639. args: { other: { $state: "/form/password" } },
  640. message: "Passwords must match",
  641. },
  642. ],
  643. validateOn: "blur",
  644. },
  645. children: [],
  646. },
  647. sep1: { type: "Separator", props: { orientation: null }, children: [] },
  648. accountTypeRadio: {
  649. type: "Radio",
  650. props: {
  651. label: "Account Type",
  652. name: "accountType",
  653. options: ["personal", "business"],
  654. value: { $bindState: "/form/accountType" },
  655. checks: null,
  656. validateOn: null,
  657. },
  658. children: [],
  659. },
  660. companyInput: {
  661. type: "Input",
  662. props: {
  663. label: "Company Name",
  664. name: "company",
  665. type: "text",
  666. placeholder: "Acme Inc.",
  667. value: { $bindState: "/form/company" },
  668. checks: [
  669. {
  670. type: "requiredIf",
  671. args: { field: { $state: "/form/accountType" } },
  672. message: "Company name is required for business accounts",
  673. },
  674. ],
  675. validateOn: "blur",
  676. },
  677. visible: { $state: "/form/accountType", eq: "business" },
  678. children: [],
  679. },
  680. sep2: { type: "Separator", props: { orientation: null }, children: [] },
  681. actions: {
  682. type: "Stack",
  683. props: {
  684. direction: "horizontal",
  685. gap: "sm",
  686. align: null,
  687. justify: "end",
  688. },
  689. children: ["submitBtn"],
  690. },
  691. submitBtn: {
  692. type: "Button",
  693. props: { label: "Register", variant: "primary", disabled: null },
  694. on: {
  695. press: [
  696. {
  697. action: "validateForm",
  698. params: { statePath: "/result" },
  699. },
  700. ],
  701. },
  702. children: [],
  703. },
  704. // Validation result
  705. statusText: {
  706. type: "Alert",
  707. props: {
  708. title: "Validation Result",
  709. message: {
  710. $cond: { $state: "/result/valid", eq: true },
  711. $then: "All fields are valid -- ready to submit!",
  712. $else: "Please fix the errors above before submitting.",
  713. },
  714. type: {
  715. $cond: { $state: "/result/valid", eq: true },
  716. $then: "success",
  717. $else: "error",
  718. },
  719. },
  720. visible: { $state: "/result", neq: null },
  721. children: [],
  722. },
  723. },
  724. },
  725. },
  726. // =========================================================================
  727. // Advanced: Cascading selects with watchers & $computed
  728. // =========================================================================
  729. {
  730. name: "Cascading Selects",
  731. description:
  732. "Watchers reset dependent fields, $computed derives display values",
  733. spec: {
  734. root: "card",
  735. state: {
  736. form: { country: "", city: "" },
  737. availableCities: [],
  738. },
  739. elements: {
  740. card: {
  741. type: "Card",
  742. props: {
  743. title: "Shipping Address",
  744. description: "Select your country to load available cities",
  745. maxWidth: "md",
  746. centered: null,
  747. },
  748. children: ["formStack"],
  749. },
  750. formStack: {
  751. type: "Stack",
  752. props: {
  753. direction: "vertical",
  754. gap: "md",
  755. align: null,
  756. justify: null,
  757. },
  758. children: [
  759. "countrySelect",
  760. "citySelect",
  761. "sep",
  762. "addressPreview",
  763. "templatePreview",
  764. ],
  765. },
  766. countrySelect: {
  767. type: "Select",
  768. props: {
  769. label: "Country",
  770. name: "country",
  771. options: ["US", "Canada", "UK", "Germany", "Japan"],
  772. placeholder: "Choose a country",
  773. value: { $bindState: "/form/country" },
  774. checks: [{ type: "required", message: "Country is required" }],
  775. validateOn: "change",
  776. },
  777. watch: {
  778. "/form/country": [
  779. {
  780. action: "setState",
  781. params: {
  782. statePath: "/availableCities",
  783. value: {
  784. $computed: "citiesForCountry",
  785. args: { country: { $state: "/form/country" } },
  786. },
  787. },
  788. },
  789. {
  790. action: "setState",
  791. params: { statePath: "/form/city", value: "" },
  792. },
  793. ],
  794. },
  795. children: [],
  796. },
  797. citySelect: {
  798. type: "Select",
  799. props: {
  800. label: "City",
  801. name: "city",
  802. options: { $state: "/availableCities" },
  803. placeholder: "Select a city",
  804. value: { $bindState: "/form/city" },
  805. checks: [{ type: "required", message: "City is required" }],
  806. validateOn: "change",
  807. },
  808. children: [],
  809. },
  810. sep: { type: "Separator", props: { orientation: null }, children: [] },
  811. // $computed formatted address
  812. addressPreview: {
  813. type: "Heading",
  814. props: {
  815. text: {
  816. $computed: "formatAddress",
  817. args: {
  818. city: { $state: "/form/city" },
  819. country: { $state: "/form/country" },
  820. },
  821. },
  822. level: "h3",
  823. },
  824. children: [],
  825. },
  826. // $template string interpolation
  827. templatePreview: {
  828. type: "Text",
  829. props: {
  830. text: {
  831. $template:
  832. "Shipping to: ${/form/city} in ${/form/country}. Cities available: ${/availableCities}",
  833. },
  834. variant: "muted",
  835. },
  836. visible: { $state: "/form/country", neq: "" },
  837. children: [],
  838. },
  839. },
  840. },
  841. },
  842. ];