@avocadostudio-ai/shared 0.1.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 (76) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +80 -0
  3. package/contract/operation.schema.json +752 -0
  4. package/dist/api-responses.d.ts +62 -0
  5. package/dist/api-responses.js +68 -0
  6. package/dist/block-manifest.d.ts +28 -0
  7. package/dist/block-manifest.js +247 -0
  8. package/dist/block-names.d.ts +19 -0
  9. package/dist/block-names.js +52 -0
  10. package/dist/blocks/_helpers.d.ts +14 -0
  11. package/dist/blocks/_helpers.js +26 -0
  12. package/dist/blocks/_registry.d.ts +116 -0
  13. package/dist/blocks/_registry.js +271 -0
  14. package/dist/blocks/banner.d.ts +1 -0
  15. package/dist/blocks/banner.js +34 -0
  16. package/dist/blocks/card-grid.d.ts +1 -0
  17. package/dist/blocks/card-grid.js +73 -0
  18. package/dist/blocks/card.d.ts +1 -0
  19. package/dist/blocks/card.js +37 -0
  20. package/dist/blocks/carousel.d.ts +1 -0
  21. package/dist/blocks/carousel.js +51 -0
  22. package/dist/blocks/cta.d.ts +1 -0
  23. package/dist/blocks/cta.js +35 -0
  24. package/dist/blocks/embed.d.ts +1 -0
  25. package/dist/blocks/embed.js +30 -0
  26. package/dist/blocks/faq-accordion.d.ts +1 -0
  27. package/dist/blocks/faq-accordion.js +30 -0
  28. package/dist/blocks/feature-grid.d.ts +1 -0
  29. package/dist/blocks/feature-grid.js +46 -0
  30. package/dist/blocks/footer.d.ts +1 -0
  31. package/dist/blocks/footer.js +31 -0
  32. package/dist/blocks/gallery.d.ts +1 -0
  33. package/dist/blocks/gallery.js +47 -0
  34. package/dist/blocks/hero.d.ts +1 -0
  35. package/dist/blocks/hero.js +48 -0
  36. package/dist/blocks/index.d.ts +3 -0
  37. package/dist/blocks/index.js +53 -0
  38. package/dist/blocks/quote.d.ts +1 -0
  39. package/dist/blocks/quote.js +32 -0
  40. package/dist/blocks/rich-text.d.ts +1 -0
  41. package/dist/blocks/rich-text.js +41 -0
  42. package/dist/blocks/site-header.d.ts +1 -0
  43. package/dist/blocks/site-header.js +48 -0
  44. package/dist/blocks/stats.d.ts +1 -0
  45. package/dist/blocks/stats.js +42 -0
  46. package/dist/blocks/table.d.ts +1 -0
  47. package/dist/blocks/table.js +37 -0
  48. package/dist/blocks/tabs.d.ts +1 -0
  49. package/dist/blocks/tabs.js +39 -0
  50. package/dist/blocks/testimonials.d.ts +1 -0
  51. package/dist/blocks/testimonials.js +45 -0
  52. package/dist/blocks/two-column.d.ts +1 -0
  53. package/dist/blocks/two-column.js +61 -0
  54. package/dist/blocks/video.d.ts +1 -0
  55. package/dist/blocks/video.js +33 -0
  56. package/dist/chat-events.d.ts +475 -0
  57. package/dist/chat-events.js +137 -0
  58. package/dist/demo-seed-content.d.ts +3 -0
  59. package/dist/demo-seed-content.js +1128 -0
  60. package/dist/draft-mode.d.ts +10 -0
  61. package/dist/draft-mode.js +29 -0
  62. package/dist/editable-path.d.ts +20 -0
  63. package/dist/editable-path.js +101 -0
  64. package/dist/index.d.ts +13 -0
  65. package/dist/index.js +26 -0
  66. package/dist/ops/builders.d.ts +62 -0
  67. package/dist/ops/builders.js +111 -0
  68. package/dist/ops/theme-tokens.d.ts +50 -0
  69. package/dist/ops/theme-tokens.js +73 -0
  70. package/dist/protocol.d.ts +7 -0
  71. package/dist/protocol.js +7 -0
  72. package/dist/publish-diff.d.ts +67 -0
  73. package/dist/publish-diff.js +9 -0
  74. package/dist/schemas.d.ts +321 -0
  75. package/dist/schemas.js +238 -0
  76. package/package.json +48 -0
@@ -0,0 +1,31 @@
1
+ import { z } from "zod";
2
+ import { registerBlock } from "./_registry.js";
3
+ import { f } from "./_helpers.js";
4
+ registerBlock("Footer", {
5
+ schema: z.object({
6
+ copyright: z.string().min(1),
7
+ columns: z.array(z.object({ id: z.string().optional(), title: z.string().min(1), links: z.string().min(1) })).min(1)
8
+ }),
9
+ meta: {
10
+ displayName: "Footer",
11
+ description: "Multi-column footer with link groups and copyright.",
12
+ category: "navigation",
13
+ chrome: true,
14
+ fields: { copyright: f.text("Copyright text"), headingLevel: f.headingLevel() },
15
+ listFields: {
16
+ columns: {
17
+ label: "Footer columns",
18
+ itemFields: { title: f.text("Column title"), links: f.richtext("Links (one label|url per line)") }
19
+ }
20
+ }
21
+ }
22
+ });
23
+ export function footerDefaultProps() {
24
+ return {
25
+ copyright: "\u00a9 2026 Your Company",
26
+ columns: [
27
+ { title: "Product", links: "Features|/features\nPricing|/pricing" },
28
+ { title: "Company", links: "About|/about\nContact|/contact" }
29
+ ]
30
+ };
31
+ }
@@ -0,0 +1 @@
1
+ export declare function galleryDefaultProps(): Record<string, unknown>;
@@ -0,0 +1,47 @@
1
+ import { z } from "zod";
2
+ import { registerBlock, IMAGE_PLACEHOLDER } from "./_registry.js";
3
+ import { f } from "./_helpers.js";
4
+ registerBlock("Gallery", {
5
+ schema: z.object({
6
+ title: z.string().optional(),
7
+ headingLevel: z.string().optional(),
8
+ columns: z.enum(["2", "3", "4"]).default("3").catch("3"),
9
+ images: z.array(z.object({
10
+ id: z.string().optional(),
11
+ imageUrl: z.string().min(1),
12
+ alt: z.string().optional(),
13
+ caption: z.string().optional(),
14
+ })).min(1),
15
+ }),
16
+ meta: {
17
+ displayName: "Gallery",
18
+ description: "Image grid with configurable columns and optional captions.",
19
+ category: "media",
20
+ fields: {
21
+ title: f.text("Section title"),
22
+ columns: { kind: "enum", label: "Columns", options: ["2", "3", "4"], inlineEditable: false },
23
+ headingLevel: f.headingLevel(),
24
+ },
25
+ listFields: {
26
+ images: {
27
+ label: "Images",
28
+ itemFields: {
29
+ imageUrl: f.image("Image", { aspectRatio: "landscape", width: 800, height: 600 }),
30
+ alt: f.imageAlt("Alt text"),
31
+ caption: f.text("Caption"),
32
+ }
33
+ }
34
+ }
35
+ }
36
+ });
37
+ export function galleryDefaultProps() {
38
+ return {
39
+ title: "",
40
+ columns: "3",
41
+ images: [
42
+ { imageUrl: IMAGE_PLACEHOLDER, alt: "Gallery image 1", caption: "" },
43
+ { imageUrl: IMAGE_PLACEHOLDER, alt: "Gallery image 2", caption: "" },
44
+ { imageUrl: IMAGE_PLACEHOLDER, alt: "Gallery image 3", caption: "" },
45
+ ],
46
+ };
47
+ }
@@ -0,0 +1 @@
1
+ export declare function heroDefaultProps(): Record<string, unknown>;
@@ -0,0 +1,48 @@
1
+ import { z } from "zod";
2
+ import { registerBlock, IMAGE_PLACEHOLDER } from "./_registry.js";
3
+ import { f } from "./_helpers.js";
4
+ registerBlock("Hero", {
5
+ schema: z.object({
6
+ heading: z.string().min(1),
7
+ subheading: z.string().min(1),
8
+ ctaText: z.string().min(1),
9
+ ctaHref: z.string().min(1),
10
+ imageUrl: z.string().min(1),
11
+ imageAlt: z.string().min(1),
12
+ imagePosition: z.enum(["left", "right", "full"]).default("right").catch("right"),
13
+ textAlign: z.enum(["left", "center"]).default("left").catch("left"),
14
+ eyebrow: z.string().optional(),
15
+ secondaryCtaText: z.string().optional(),
16
+ secondaryCtaHref: z.string().optional()
17
+ }),
18
+ meta: {
19
+ displayName: "Hero",
20
+ description: "Full-width hero section with headline, subheading, CTA buttons, and image.",
21
+ category: "content",
22
+ fields: {
23
+ heading: f.text("Heading"),
24
+ subheading: f.text("Subheading"),
25
+ ctaText: f.text("CTA button text"),
26
+ ctaHref: f.url("CTA link"),
27
+ imageUrl: f.image("Hero image", { aspectRatio: "landscape", width: 1536, height: 1024 }),
28
+ imageAlt: f.imageAlt("Hero image alt text"),
29
+ imagePosition: { kind: "enum", label: "Image position", options: ["left", "right", "full"], inlineEditable: false },
30
+ textAlign: { kind: "enum", label: "Text alignment", options: ["left", "center"], inlineEditable: false },
31
+ eyebrow: f.text("Eyebrow text"),
32
+ secondaryCtaText: f.text("Secondary CTA text"),
33
+ secondaryCtaHref: f.url("Secondary CTA link"),
34
+ headingLevel: f.headingLevel(),
35
+ }
36
+ }
37
+ });
38
+ export function heroDefaultProps() {
39
+ return {
40
+ heading: "Build with confidence",
41
+ subheading: "Make changes safely with instant preview.",
42
+ ctaText: "Get Started",
43
+ ctaHref: "/",
44
+ imageUrl: IMAGE_PLACEHOLDER,
45
+ imageAlt: "Abstract generated illustration",
46
+ imagePosition: "right"
47
+ };
48
+ }
@@ -0,0 +1,3 @@
1
+ import type { BlockType } from "./_registry.ts";
2
+ export declare function defaultPropsForType(type: BlockType): Record<string, unknown>;
3
+ export { DEFAULT_HEADING_LEVELS, resolveHeadingTag, resolveItemHeadingTag } from "./_helpers.ts";
@@ -0,0 +1,53 @@
1
+ // Named imports trigger each module's registerBlock() side effect
2
+ import { heroDefaultProps } from "./hero.js";
3
+ import { featureGridDefaultProps } from "./feature-grid.js";
4
+ import { testimonialsDefaultProps } from "./testimonials.js";
5
+ import { faqAccordionDefaultProps } from "./faq-accordion.js";
6
+ import { ctaDefaultProps } from "./cta.js";
7
+ import { cardDefaultProps } from "./card.js";
8
+ import { cardGridDefaultProps } from "./card-grid.js";
9
+ import { richTextDefaultProps } from "./rich-text.js";
10
+ import { statsDefaultProps } from "./stats.js";
11
+ import { twoColumnDefaultProps } from "./two-column.js";
12
+ import { footerDefaultProps } from "./footer.js";
13
+ import { siteHeaderDefaultProps } from "./site-header.js";
14
+ import { embedDefaultProps } from "./embed.js";
15
+ import { bannerDefaultProps } from "./banner.js";
16
+ import { carouselDefaultProps } from "./carousel.js";
17
+ import { galleryDefaultProps } from "./gallery.js";
18
+ import { tabsDefaultProps } from "./tabs.js";
19
+ import { tableDefaultProps } from "./table.js";
20
+ import { quoteDefaultProps } from "./quote.js";
21
+ import { videoDefaultProps } from "./video.js";
22
+ const defaults = {
23
+ Hero: heroDefaultProps,
24
+ FeatureGrid: featureGridDefaultProps,
25
+ Testimonials: testimonialsDefaultProps,
26
+ FAQAccordion: faqAccordionDefaultProps,
27
+ CTA: ctaDefaultProps,
28
+ Card: cardDefaultProps,
29
+ CardGrid: cardGridDefaultProps,
30
+ RichText: richTextDefaultProps,
31
+ Stats: statsDefaultProps,
32
+ TwoColumn: twoColumnDefaultProps,
33
+ Footer: footerDefaultProps,
34
+ SiteHeader: siteHeaderDefaultProps,
35
+ Embed: embedDefaultProps,
36
+ Banner: bannerDefaultProps,
37
+ Carousel: carouselDefaultProps,
38
+ Gallery: galleryDefaultProps,
39
+ Tabs: tabsDefaultProps,
40
+ Table: tableDefaultProps,
41
+ Quote: quoteDefaultProps,
42
+ Video: videoDefaultProps,
43
+ };
44
+ const fallback = {
45
+ title: "Ready to get started?",
46
+ description: "Apply your next change in seconds.",
47
+ ctaText: "Start now",
48
+ ctaHref: "/"
49
+ };
50
+ export function defaultPropsForType(type) {
51
+ return defaults[type]?.() ?? { ...fallback };
52
+ }
53
+ export { DEFAULT_HEADING_LEVELS, resolveHeadingTag, resolveItemHeadingTag } from "./_helpers.js";
@@ -0,0 +1 @@
1
+ export declare function quoteDefaultProps(): Record<string, unknown>;
@@ -0,0 +1,32 @@
1
+ import { z } from "zod";
2
+ import { registerBlock } from "./_registry.js";
3
+ import { f } from "./_helpers.js";
4
+ registerBlock("Quote", {
5
+ schema: z.object({
6
+ quote: z.string().min(1),
7
+ author: z.string().optional(),
8
+ role: z.string().optional(),
9
+ imageUrl: z.string().optional(),
10
+ imageAlt: z.string().optional(),
11
+ }),
12
+ meta: {
13
+ displayName: "Quote",
14
+ description: "Pull quote or blockquote with optional author attribution and avatar.",
15
+ category: "content",
16
+ fields: {
17
+ quote: f.richtext("Quote text"),
18
+ author: f.text("Author name"),
19
+ role: f.text("Author role / title"),
20
+ imageUrl: f.image("Author avatar", { aspectRatio: "square", width: 128, height: 128 }),
21
+ imageAlt: f.imageAlt("Avatar alt text"),
22
+ },
23
+ }
24
+ });
25
+ export function quoteDefaultProps() {
26
+ return {
27
+ quote: "The best way to predict the future is to create it.",
28
+ author: "Peter Drucker",
29
+ role: "Management Consultant",
30
+ imageUrl: "https://placehold.co/300x300/e2e8f0/475569?text=PD",
31
+ };
32
+ }
@@ -0,0 +1 @@
1
+ export declare function richTextDefaultProps(): Record<string, unknown>;
@@ -0,0 +1,41 @@
1
+ import { z } from "zod";
2
+ import { registerBlock } from "./_registry.js";
3
+ import { f } from "./_helpers.js";
4
+ registerBlock("RichText", {
5
+ schema: z.object({
6
+ title: z.string(),
7
+ body: z.string().min(1)
8
+ }),
9
+ meta: {
10
+ displayName: "Rich Text",
11
+ description: "Freeform text content with markdown-style formatting.",
12
+ category: "content",
13
+ fields: {
14
+ title: f.text("Section title"),
15
+ body: f.richtext("Body"),
16
+ headingLevel: f.headingLevel(),
17
+ }
18
+ }
19
+ });
20
+ export function richTextDefaultProps() {
21
+ return {
22
+ title: "About this page",
23
+ body: [
24
+ "Rich text supports **bold**, *italic*, and [inline links](https://example.com) so you can weave emphasis and references into prose without leaving the editor.",
25
+ "## What you can format",
26
+ "Use short paragraphs to keep things scannable. Break ideas apart so readers can skim before they commit to reading.",
27
+ [
28
+ "- Unordered lists for grouped, non-sequential points",
29
+ "- Mix **bold** items with plain ones to highlight the important bits",
30
+ "- Keep each bullet to a single idea",
31
+ ].join("\n"),
32
+ "### When order matters",
33
+ [
34
+ "1. Start with the outcome you want",
35
+ "2. Describe the steps in the order a reader should follow",
36
+ "3. End with a link to [learn more](https://example.com/docs)",
37
+ ].join("\n"),
38
+ "Use rich text for long-form content — essays, documentation, changelogs, or anywhere a grid of cards would feel too rigid."
39
+ ].join("\n\n")
40
+ };
41
+ }
@@ -0,0 +1 @@
1
+ export declare function siteHeaderDefaultProps(): Record<string, unknown>;
@@ -0,0 +1,48 @@
1
+ import { z } from "zod";
2
+ import { registerBlock } from "./_registry.js";
3
+ import { f } from "./_helpers.js";
4
+ const navLinkLeaf = z.object({
5
+ id: z.string().optional(),
6
+ label: z.string().min(1),
7
+ href: z.string().min(1),
8
+ });
9
+ registerBlock("SiteHeader", {
10
+ schema: z.object({
11
+ siteName: z.string().min(1),
12
+ logoUrl: z.string().min(1),
13
+ links: z.array(navLinkLeaf.extend({
14
+ href: z.string().optional(), // parent dropdown items have no href
15
+ children: z.array(navLinkLeaf).optional(), // 2nd-level dropdown items
16
+ })).min(1),
17
+ }),
18
+ meta: {
19
+ displayName: "Site Header",
20
+ description: "Global site navigation bar with logo, site name, and nav links.",
21
+ category: "navigation",
22
+ chrome: true,
23
+ fields: {
24
+ siteName: f.text("Site name"),
25
+ logoUrl: f.image("Logo"),
26
+ },
27
+ listFields: {
28
+ links: {
29
+ label: "Nav links",
30
+ itemFields: {
31
+ label: f.text("Link label"),
32
+ href: f.url("Link URL"),
33
+ },
34
+ },
35
+ },
36
+ },
37
+ });
38
+ export function siteHeaderDefaultProps() {
39
+ return {
40
+ siteName: "My Site",
41
+ logoUrl: "/logo.svg",
42
+ links: [
43
+ { label: "Home", href: "/" },
44
+ { label: "About", href: "/about" },
45
+ { label: "Contact", href: "/contact" },
46
+ ],
47
+ };
48
+ }
@@ -0,0 +1 @@
1
+ export declare function statsDefaultProps(): Record<string, unknown>;
@@ -0,0 +1,42 @@
1
+ import { z } from "zod";
2
+ import { registerBlock } from "./_registry.js";
3
+ import { f } from "./_helpers.js";
4
+ registerBlock("Stats", {
5
+ schema: z.object({
6
+ title: z.string().optional(),
7
+ stats: z.array(z.object({
8
+ id: z.string().optional(),
9
+ icon: z.string().optional(),
10
+ value: z.string().min(1),
11
+ label: z.string().min(1),
12
+ description: z.string().optional()
13
+ })).min(1)
14
+ }),
15
+ meta: {
16
+ displayName: "Stats",
17
+ description: "Row of big numbers with labels (e.g. 10K+ Users).",
18
+ category: "content",
19
+ fields: { title: f.text("Section title"), headingLevel: f.headingLevel() },
20
+ listFields: {
21
+ stats: {
22
+ label: "Stats",
23
+ itemFields: {
24
+ icon: f.text("Icon (single emoji)"),
25
+ value: f.text("Value"),
26
+ label: f.text("Label"),
27
+ description: f.text("Description"),
28
+ }
29
+ }
30
+ }
31
+ }
32
+ });
33
+ export function statsDefaultProps() {
34
+ return {
35
+ title: "By the numbers",
36
+ stats: [
37
+ { value: "10k+", label: "Active users" },
38
+ { value: "99.9%", label: "Uptime" },
39
+ { value: "24/7", label: "Support" }
40
+ ]
41
+ };
42
+ }
@@ -0,0 +1 @@
1
+ export declare function tableDefaultProps(): Record<string, unknown>;
@@ -0,0 +1,37 @@
1
+ import { z } from "zod";
2
+ import { registerBlock } from "./_registry.js";
3
+ import { f } from "./_helpers.js";
4
+ registerBlock("Table", {
5
+ schema: z.object({
6
+ title: z.string().optional(),
7
+ headingLevel: z.string().optional(),
8
+ headers: z.array(z.string().min(1)).min(1),
9
+ rows: z.array(z.array(z.coerce.string())).min(1),
10
+ striped: z.enum(["true", "false"]).default("false").catch("false"),
11
+ }),
12
+ meta: {
13
+ displayName: "Table",
14
+ description: "Data table with column headers, rows, and optional stripe styling. Headers is a string array. Rows is a 2D string array where each inner array is one row matching the headers order.",
15
+ category: "content",
16
+ fields: {
17
+ title: f.text("Section title"),
18
+ headingLevel: f.headingLevel(),
19
+ headers: f.text("Column headers"),
20
+ rows: f.text("Table rows"),
21
+ striped: { kind: "enum", label: "Striped rows", options: ["true", "false"], inlineEditable: false },
22
+ },
23
+ }
24
+ });
25
+ export function tableDefaultProps() {
26
+ return {
27
+ title: "",
28
+ headers: ["Feature", "Starter", "Pro", "Enterprise"],
29
+ rows: [
30
+ ["Users", "Up to 3", "Up to 20", "Unlimited"],
31
+ ["Storage", "1 GB", "10 GB", "100 GB"],
32
+ ["Support", "Community", "Email", "Dedicated"],
33
+ ["API access", "—", "Yes", "Yes"],
34
+ ],
35
+ striped: "true",
36
+ };
37
+ }
@@ -0,0 +1 @@
1
+ export declare function tabsDefaultProps(): Record<string, unknown>;
@@ -0,0 +1,39 @@
1
+ import { z } from "zod";
2
+ import { registerBlock } from "./_registry.js";
3
+ import { f } from "./_helpers.js";
4
+ registerBlock("Tabs", {
5
+ schema: z.object({
6
+ title: z.string().optional(),
7
+ tabs: z.array(z.object({
8
+ id: z.string().optional(),
9
+ label: z.string().min(1),
10
+ content: z.string().min(1),
11
+ })).min(1),
12
+ }),
13
+ meta: {
14
+ displayName: "Tabs",
15
+ description: "Switchable tabbed content panels with rich text in each tab.",
16
+ category: "content",
17
+ fields: { title: f.text("Section title"), headingLevel: f.headingLevel() },
18
+ listFields: {
19
+ tabs: {
20
+ label: "Tabs",
21
+ itemFields: {
22
+ label: f.text("Tab label"),
23
+ content: f.richtext("Tab content"),
24
+ }
25
+ }
26
+ }
27
+ }
28
+ });
29
+ export function tabsDefaultProps() {
30
+ return {
31
+ tabs: [
32
+ { label: "Overview", content: "This is the overview tab with **general information** about the topic.\n\nKey highlights:\n\n- Easy to get started\n- Works across all devices\n- Built for teams of any size" },
33
+ { label: "Features", content: "## Core Features\n\n- **Real-time collaboration** — edit together with your team\n- **Version history** — roll back to any previous state\n- **API access** — integrate with your existing tools\n\nAll features are included in every plan." },
34
+ { label: "Pricing", content: "View our flexible pricing plans designed for teams of all sizes.\n\n1. **Starter** — Free for up to 3 users\n2. **Pro** — $12/user/month\n3. **Enterprise** — Custom pricing" },
35
+ { label: "Integrations", content: "Connect with the tools you already use:\n\n- Slack\n- GitHub\n- Jira\n- Figma\n- Google Workspace" },
36
+ { label: "Support", content: "Get help from our team:\n\n- **Live chat** — available 9am–6pm ET\n- **Email** — support@example.com\n- **Community forum** — [Visit the forum](https://example.com)" },
37
+ ],
38
+ };
39
+ }
@@ -0,0 +1 @@
1
+ export declare function testimonialsDefaultProps(): Record<string, unknown>;
@@ -0,0 +1,45 @@
1
+ import { z } from "zod";
2
+ import { registerBlock } from "./_registry.js";
3
+ import { f } from "./_helpers.js";
4
+ registerBlock("Testimonials", {
5
+ schema: z.object({
6
+ title: z.string().min(1),
7
+ items: z.array(z.object({
8
+ id: z.string().optional(),
9
+ icon: z.string().optional(),
10
+ quote: z.string().min(1),
11
+ author: z.string().min(1),
12
+ role: z.string().optional(),
13
+ imageUrl: z.string().optional(),
14
+ imageAlt: z.string().optional()
15
+ })).min(1)
16
+ }),
17
+ meta: {
18
+ displayName: "Testimonials",
19
+ description: "Grid of testimonial cards with quotes and authors.",
20
+ category: "content",
21
+ fields: { title: f.text("Section title"), headingLevel: f.headingLevel() },
22
+ listFields: {
23
+ items: {
24
+ label: "Testimonials",
25
+ itemFields: {
26
+ icon: f.text("Icon (single emoji)"),
27
+ quote: f.longtext("Quote"),
28
+ author: f.text("Author"),
29
+ role: f.text("Role / Company"),
30
+ imageUrl: f.image("Author photo", { aspectRatio: "square", width: 64, height: 64 }),
31
+ imageAlt: f.imageAlt("Author photo alt"),
32
+ }
33
+ }
34
+ }
35
+ }
36
+ });
37
+ export function testimonialsDefaultProps() {
38
+ return {
39
+ title: "What customers say",
40
+ items: [
41
+ { quote: "We launched faster than expected.", author: "Alex" },
42
+ { quote: "Editing is straightforward for the whole team.", author: "Jordan" }
43
+ ]
44
+ };
45
+ }
@@ -0,0 +1 @@
1
+ export declare function twoColumnDefaultProps(): Record<string, unknown>;
@@ -0,0 +1,61 @@
1
+ import { z } from "zod";
2
+ import { registerBlock, IMAGE_PLACEHOLDER } from "./_registry.js";
3
+ import { f } from "./_helpers.js";
4
+ const twoColumnChild = z.object({
5
+ id: z.string().optional(),
6
+ // Required, no natural default — coerce an out-of-range/legacy type to "paragraph"
7
+ // (a safe text component that keeps the item's `text`) so a single drifted child
8
+ // can't reject the whole block on re-validation during update_props/item ops (#21).
9
+ type: z.enum(["heading", "subheading", "paragraph", "list", "cta", "ctas", "image", "video"]).catch("paragraph"),
10
+ text: z.string().optional(),
11
+ label: z.string().optional(),
12
+ href: z.string().optional(),
13
+ src: z.string().optional(),
14
+ alt: z.string().optional(),
15
+ poster: z.string().optional(),
16
+ items: z.array(z.string()).optional(),
17
+ buttons: z.array(z.object({ label: z.string(), href: z.string(), variant: z.string().optional() })).optional(),
18
+ variant: z.string().optional()
19
+ });
20
+ const twoColumnItemFields = {
21
+ type: { kind: "enum", label: "Component type", options: ["heading", "subheading", "paragraph", "list", "cta", "ctas", "image", "video"] },
22
+ text: f.longtext("Text content"),
23
+ label: f.text("Button label"),
24
+ href: f.url("Link URL"),
25
+ src: f.image("Media source", { aspectRatio: "portrait", width: 768, height: 1024 }),
26
+ alt: f.imageAlt("Alt text"),
27
+ poster: f.image("Video poster image", { aspectRatio: "landscape", width: 768, height: 512 }),
28
+ };
29
+ registerBlock("TwoColumn", {
30
+ schema: z.object({
31
+ variant: z.enum(["default", "accent"]).default("default").catch("default"),
32
+ left: z.array(twoColumnChild).min(1),
33
+ right: z.array(twoColumnChild).min(1)
34
+ }),
35
+ meta: {
36
+ displayName: "Two Column",
37
+ description: "Composite two-column layout with typed child components in each column.",
38
+ category: "layout",
39
+ fields: {
40
+ variant: { kind: "enum", label: "Style variant", options: ["default", "accent"], inlineEditable: false },
41
+ headingLevel: f.headingLevel(),
42
+ },
43
+ listFields: {
44
+ left: { label: "Left column items", itemFields: twoColumnItemFields },
45
+ right: { label: "Right column items", itemFields: twoColumnItemFields }
46
+ }
47
+ }
48
+ });
49
+ export function twoColumnDefaultProps() {
50
+ return {
51
+ variant: "default",
52
+ left: [
53
+ { type: "heading", text: "Built for teams" },
54
+ { type: "paragraph", text: "Ship changes quickly with a clear, reliable workflow." },
55
+ { type: "cta", label: "Learn more", href: "/" }
56
+ ],
57
+ right: [
58
+ { type: "image", src: IMAGE_PLACEHOLDER, alt: "Team collaborating on a website update" }
59
+ ]
60
+ };
61
+ }
@@ -0,0 +1 @@
1
+ export declare function videoDefaultProps(): Record<string, unknown>;
@@ -0,0 +1,33 @@
1
+ import { z } from "zod";
2
+ import { registerBlock } from "./_registry.js";
3
+ import { f } from "./_helpers.js";
4
+ registerBlock("Video", {
5
+ schema: z.object({
6
+ src: z.string().min(1),
7
+ title: z.string().optional(),
8
+ posterUrl: z.string().optional(),
9
+ autoplay: z.enum(["true", "false"]).default("false").catch("false"),
10
+ loop: z.enum(["true", "false"]).default("false").catch("false"),
11
+ }),
12
+ meta: {
13
+ displayName: "Video",
14
+ description: "Video player — supports YouTube, Vimeo URLs, or direct video files (mp4, webm). Auto-detects source type.",
15
+ category: "media",
16
+ fields: {
17
+ src: f.url("Video URL"),
18
+ title: f.text("Title / caption"),
19
+ posterUrl: f.image("Poster image", { aspectRatio: "landscape", width: 1200, height: 675 }),
20
+ autoplay: { kind: "enum", label: "Autoplay", options: ["true", "false"], inlineEditable: false },
21
+ loop: { kind: "enum", label: "Loop", options: ["true", "false"], inlineEditable: false },
22
+ },
23
+ }
24
+ });
25
+ export function videoDefaultProps() {
26
+ return {
27
+ src: "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
28
+ title: "",
29
+ posterUrl: "",
30
+ autoplay: "false",
31
+ loop: "false",
32
+ };
33
+ }