modern-cms 1.0.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 (180) hide show
  1. package/README.md +41 -0
  2. package/bin/lib.js +97 -0
  3. package/bin/modern-cms.js +197 -0
  4. package/package.json +37 -0
  5. package/template/.env.example +48 -0
  6. package/template/README.md +210 -0
  7. package/template/apps/api/Dockerfile +26 -0
  8. package/template/apps/api/package.json +45 -0
  9. package/template/apps/api/prisma/backfill-search-text.ts +31 -0
  10. package/template/apps/api/prisma/migrations/20260701034530_init/migration.sql +86 -0
  11. package/template/apps/api/prisma/migrations/20260701081942_add_publish_at_and_revisions/migration.sql +19 -0
  12. package/template/apps/api/prisma/migrations/20260701085011_add_form_submissions/migration.sql +13 -0
  13. package/template/apps/api/prisma/migrations/20260702084049_add_global_sections_and_saved_blocks/migration.sql +24 -0
  14. package/template/apps/api/prisma/migrations/20260702085609_add_page_locale_and_translations/migration.sql +6 -0
  15. package/template/apps/api/prisma/migrations/20260702091914_add_collections/migration.sql +38 -0
  16. package/template/apps/api/prisma/migrations/20260702120000_platform_hardening/migration.sql +15 -0
  17. package/template/apps/api/prisma/migrations/20260705031230_add_collections_redirects_media_meta/migration.sql +17 -0
  18. package/template/apps/api/prisma/migrations/20260705033241_add_audit_log/migration.sql +18 -0
  19. package/template/apps/api/prisma/migrations/20260706031301_standard_cms_platform_pass/migration.sql +48 -0
  20. package/template/apps/api/prisma/migrations/migration_lock.toml +3 -0
  21. package/template/apps/api/prisma/reset-admin.ts +19 -0
  22. package/template/apps/api/prisma/schema.prisma +251 -0
  23. package/template/apps/api/prisma/seed-homepage.ts +752 -0
  24. package/template/apps/api/prisma/seed-our-charities.ts +231 -0
  25. package/template/apps/api/prisma/seed-templates.ts +154 -0
  26. package/template/apps/api/prisma/seed.ts +191 -0
  27. package/template/apps/api/src/env.ts +61 -0
  28. package/template/apps/api/src/index.ts +66 -0
  29. package/template/apps/api/src/lib/revalidate.ts +28 -0
  30. package/template/apps/api/src/lib/storage/azure.ts +25 -0
  31. package/template/apps/api/src/lib/storage/cloudinary.ts +42 -0
  32. package/template/apps/api/src/lib/storage/index.ts +25 -0
  33. package/template/apps/api/src/lib/storage/s3.ts +39 -0
  34. package/template/apps/api/src/lib/storage/types.ts +8 -0
  35. package/template/apps/api/src/middleware/auditLog.ts +32 -0
  36. package/template/apps/api/src/middleware/auth.ts +53 -0
  37. package/template/apps/api/src/middleware/rateLimit.ts +31 -0
  38. package/template/apps/api/src/middleware/securityHeaders.ts +10 -0
  39. package/template/apps/api/src/prisma.ts +3 -0
  40. package/template/apps/api/src/routes/audit.routes.ts +26 -0
  41. package/template/apps/api/src/routes/auth.routes.ts +46 -0
  42. package/template/apps/api/src/routes/backup.routes.ts +216 -0
  43. package/template/apps/api/src/routes/blocks.routes.ts +38 -0
  44. package/template/apps/api/src/routes/collections.routes.ts +185 -0
  45. package/template/apps/api/src/routes/forms.routes.ts +114 -0
  46. package/template/apps/api/src/routes/globalSections.routes.ts +43 -0
  47. package/template/apps/api/src/routes/media.routes.ts +77 -0
  48. package/template/apps/api/src/routes/pages.routes.ts +319 -0
  49. package/template/apps/api/src/routes/redirects.routes.ts +52 -0
  50. package/template/apps/api/src/routes/search.routes.ts +83 -0
  51. package/template/apps/api/src/routes/templates.routes.ts +41 -0
  52. package/template/apps/api/src/routes/theme.routes.ts +75 -0
  53. package/template/apps/api/src/routes/users.routes.ts +39 -0
  54. package/template/apps/api/tsconfig.json +11 -0
  55. package/template/apps/web/Dockerfile +26 -0
  56. package/template/apps/web/app/[[...slug]]/page.tsx +254 -0
  57. package/template/apps/web/app/admin/(protected)/audit/page.tsx +80 -0
  58. package/template/apps/web/app/admin/(protected)/collections/item/[itemId]/page.tsx +375 -0
  59. package/template/apps/web/app/admin/(protected)/collections/page.tsx +224 -0
  60. package/template/apps/web/app/admin/(protected)/forms/page.tsx +88 -0
  61. package/template/apps/web/app/admin/(protected)/layout.tsx +5 -0
  62. package/template/apps/web/app/admin/(protected)/media/page.tsx +138 -0
  63. package/template/apps/web/app/admin/(protected)/page.tsx +186 -0
  64. package/template/apps/web/app/admin/(protected)/pages/[id]/page.tsx +560 -0
  65. package/template/apps/web/app/admin/(protected)/settings/backup/page.tsx +97 -0
  66. package/template/apps/web/app/admin/(protected)/settings/global-sections/page.tsx +333 -0
  67. package/template/apps/web/app/admin/(protected)/settings/navigation/page.tsx +192 -0
  68. package/template/apps/web/app/admin/(protected)/settings/redirects/page.tsx +108 -0
  69. package/template/apps/web/app/admin/(protected)/settings/theme/page.tsx +239 -0
  70. package/template/apps/web/app/admin/(protected)/users/page.tsx +94 -0
  71. package/template/apps/web/app/admin/login/page.tsx +76 -0
  72. package/template/apps/web/app/api/revalidate/route.ts +34 -0
  73. package/template/apps/web/app/globals.css +120 -0
  74. package/template/apps/web/app/layout.tsx +39 -0
  75. package/template/apps/web/app/preview/[token]/page.tsx +70 -0
  76. package/template/apps/web/app/robots.ts +11 -0
  77. package/template/apps/web/app/sitemap.ts +27 -0
  78. package/template/apps/web/components/admin/AdminShell.tsx +104 -0
  79. package/template/apps/web/components/builder/BuilderCanvasNode.tsx +546 -0
  80. package/template/apps/web/components/builder/BuilderDeviceContext.tsx +13 -0
  81. package/template/apps/web/components/builder/Canvas.tsx +76 -0
  82. package/template/apps/web/components/builder/CanvasOverlayContext.tsx +22 -0
  83. package/template/apps/web/components/builder/ColorPickerField.tsx +47 -0
  84. package/template/apps/web/components/builder/DroppableChildren.tsx +50 -0
  85. package/template/apps/web/components/builder/FieldRenderer.tsx +277 -0
  86. package/template/apps/web/components/builder/IconRenderer.tsx +7 -0
  87. package/template/apps/web/components/builder/Inspector.tsx +956 -0
  88. package/template/apps/web/components/builder/JsonEditor.tsx +71 -0
  89. package/template/apps/web/components/builder/KeyValueListEditor.tsx +76 -0
  90. package/template/apps/web/components/builder/MediaPickerModal.tsx +95 -0
  91. package/template/apps/web/components/builder/PageSettingsModal.tsx +363 -0
  92. package/template/apps/web/components/builder/Palette.tsx +106 -0
  93. package/template/apps/web/components/builder/RichTextEditor.tsx +140 -0
  94. package/template/apps/web/components/renderer/CmsImage.tsx +46 -0
  95. package/template/apps/web/components/renderer/NodeBackgroundLayers.tsx +35 -0
  96. package/template/apps/web/components/renderer/PublicPageView.tsx +56 -0
  97. package/template/apps/web/components/renderer/Renderer.tsx +146 -0
  98. package/template/apps/web/components/renderer/RendererContext.tsx +20 -0
  99. package/template/apps/web/components/renderer/registry.tsx +87 -0
  100. package/template/apps/web/components/renderer/sections/Accordion.tsx +39 -0
  101. package/template/apps/web/components/renderer/sections/BeforeAfter.tsx +75 -0
  102. package/template/apps/web/components/renderer/sections/Button.tsx +42 -0
  103. package/template/apps/web/components/renderer/sections/ButtonGroup.tsx +22 -0
  104. package/template/apps/web/components/renderer/sections/CardGrid.tsx +66 -0
  105. package/template/apps/web/components/renderer/sections/CollectionList.tsx +84 -0
  106. package/template/apps/web/components/renderer/sections/Column.tsx +26 -0
  107. package/template/apps/web/components/renderer/sections/ContactForm.tsx +96 -0
  108. package/template/apps/web/components/renderer/sections/Container.tsx +44 -0
  109. package/template/apps/web/components/renderer/sections/ContentLinks.tsx +38 -0
  110. package/template/apps/web/components/renderer/sections/Countdown.tsx +69 -0
  111. package/template/apps/web/components/renderer/sections/Cta.tsx +15 -0
  112. package/template/apps/web/components/renderer/sections/Divider.tsx +26 -0
  113. package/template/apps/web/components/renderer/sections/EmbedBlock.tsx +43 -0
  114. package/template/apps/web/components/renderer/sections/Footer.tsx +117 -0
  115. package/template/apps/web/components/renderer/sections/Gallery.tsx +27 -0
  116. package/template/apps/web/components/renderer/sections/GoToTop.tsx +54 -0
  117. package/template/apps/web/components/renderer/sections/Grid.tsx +54 -0
  118. package/template/apps/web/components/renderer/sections/GridCell.tsx +33 -0
  119. package/template/apps/web/components/renderer/sections/Header.tsx +132 -0
  120. package/template/apps/web/components/renderer/sections/Hero.tsx +73 -0
  121. package/template/apps/web/components/renderer/sections/ImageBackgroundSection.tsx +20 -0
  122. package/template/apps/web/components/renderer/sections/ImageBlock.tsx +55 -0
  123. package/template/apps/web/components/renderer/sections/LanguageSwitcher.tsx +51 -0
  124. package/template/apps/web/components/renderer/sections/Logo.tsx +24 -0
  125. package/template/apps/web/components/renderer/sections/LogoCloud.tsx +67 -0
  126. package/template/apps/web/components/renderer/sections/MapEmbed.tsx +24 -0
  127. package/template/apps/web/components/renderer/sections/Marquee.tsx +45 -0
  128. package/template/apps/web/components/renderer/sections/NavLinks.tsx +69 -0
  129. package/template/apps/web/components/renderer/sections/NavZone.tsx +19 -0
  130. package/template/apps/web/components/renderer/sections/PricingTable.tsx +65 -0
  131. package/template/apps/web/components/renderer/sections/RichText.tsx +7 -0
  132. package/template/apps/web/components/renderer/sections/SiteSearch.tsx +67 -0
  133. package/template/apps/web/components/renderer/sections/Slider.tsx +81 -0
  134. package/template/apps/web/components/renderer/sections/Spacer.tsx +8 -0
  135. package/template/apps/web/components/renderer/sections/Stats.tsx +69 -0
  136. package/template/apps/web/components/renderer/sections/Tabs.tsx +59 -0
  137. package/template/apps/web/components/renderer/sections/TeamGrid.tsx +49 -0
  138. package/template/apps/web/components/renderer/sections/Testimonial.tsx +79 -0
  139. package/template/apps/web/components/renderer/sections/ThreeBackground.tsx +43 -0
  140. package/template/apps/web/components/renderer/sections/Timeline.tsx +29 -0
  141. package/template/apps/web/components/renderer/sections/VideoBackgroundSection.tsx +20 -0
  142. package/template/apps/web/components/renderer/sections/VideoEmbed.tsx +70 -0
  143. package/template/apps/web/components/renderer/sections/types.ts +7 -0
  144. package/template/apps/web/components/seo/JsonLd.tsx +111 -0
  145. package/template/apps/web/components/theme/AnalyticsScripts.tsx +68 -0
  146. package/template/apps/web/components/theme/DarkModeToggle.tsx +39 -0
  147. package/template/apps/web/components/theme/SiteDataContext.tsx +39 -0
  148. package/template/apps/web/components/theme/ThemeProvider.tsx +58 -0
  149. package/template/apps/web/eslint.config.mjs +16 -0
  150. package/template/apps/web/lib/api.ts +45 -0
  151. package/template/apps/web/lib/responsiveCss.ts +42 -0
  152. package/template/apps/web/lib/sanitizeHtml.ts +16 -0
  153. package/template/apps/web/lib/serverApi.ts +25 -0
  154. package/template/apps/web/lib/style.ts +73 -0
  155. package/template/apps/web/lib/threeBackgroundScene.ts +298 -0
  156. package/template/apps/web/lib/useCurrentUser.ts +35 -0
  157. package/template/apps/web/lib/useEntranceAnimation.ts +245 -0
  158. package/template/apps/web/next-env.d.ts +6 -0
  159. package/template/apps/web/next.config.mjs +32 -0
  160. package/template/apps/web/package.json +46 -0
  161. package/template/apps/web/postcss.config.mjs +6 -0
  162. package/template/apps/web/proxy.ts +23 -0
  163. package/template/apps/web/tailwind.config.ts +24 -0
  164. package/template/apps/web/tsconfig.json +41 -0
  165. package/template/docker-compose.yml +60 -0
  166. package/template/package.json +29 -0
  167. package/template/packages/shared/package.json +21 -0
  168. package/template/packages/shared/src/components.ts +1626 -0
  169. package/template/packages/shared/src/index.ts +9 -0
  170. package/template/packages/shared/src/schema/collections.ts +70 -0
  171. package/template/packages/shared/src/schema/fields.ts +26 -0
  172. package/template/packages/shared/src/schema/media.ts +13 -0
  173. package/template/packages/shared/src/schema/pageNode.ts +255 -0
  174. package/template/packages/shared/src/schema/templates.ts +20 -0
  175. package/template/packages/shared/src/schema/theme.ts +69 -0
  176. package/template/packages/shared/src/schema/user.ts +25 -0
  177. package/template/packages/shared/src/tree.test.ts +65 -0
  178. package/template/packages/shared/src/tree.ts +303 -0
  179. package/template/packages/shared/tsconfig.json +8 -0
  180. package/template/packages/shared/vitest.config.ts +8 -0
@@ -0,0 +1,1626 @@
1
+ import type { FieldDescriptor } from "./schema/fields.js";
2
+ import type { NodeStyle } from "./schema/pageNode.js";
3
+
4
+ export type ComponentCategory = "Layout" | "Content" | "Media" | "Interactive";
5
+
6
+ export type ComponentDefinition = {
7
+ type: string;
8
+ label: string;
9
+ icon: string; // lucide-react icon name
10
+ category: ComponentCategory;
11
+ allowsChildren: boolean;
12
+ defaultProps: Record<string, unknown>;
13
+ defaultStyle: NodeStyle;
14
+ fields: FieldDescriptor[];
15
+ /** Structural helper types (e.g. "column") that are managed automatically by their
16
+ * parent rather than dragged in from the palette directly. */
17
+ hidden?: boolean;
18
+ /** Structural slots (e.g. a header's left/center/right zones) that the user
19
+ * shouldn't be able to delete, duplicate, or drag out of place. */
20
+ locked?: boolean;
21
+ };
22
+
23
+ const buttonItemFields: FieldDescriptor[] = [
24
+ { key: "text", label: "Button text", type: "text", default: "Click me" },
25
+ { key: "href", label: "Link URL", type: "url", default: "#" },
26
+ {
27
+ key: "size",
28
+ label: "Size",
29
+ type: "select",
30
+ default: "md",
31
+ options: [
32
+ { label: "Small", value: "sm" },
33
+ { label: "Medium", value: "md" },
34
+ { label: "Large", value: "lg" },
35
+ ],
36
+ },
37
+ {
38
+ key: "variant",
39
+ label: "Style",
40
+ type: "select",
41
+ default: "primary",
42
+ options: [
43
+ { label: "Primary", value: "primary" },
44
+ { label: "Secondary", value: "secondary" },
45
+ { label: "Outline", value: "outline" },
46
+ { label: "Text (borderless)", value: "text" },
47
+ ],
48
+ },
49
+ {
50
+ key: "border",
51
+ label: "Border",
52
+ type: "select",
53
+ default: "default",
54
+ options: [
55
+ { label: "Default (from style)", value: "default" },
56
+ { label: "None", value: "none" },
57
+ { label: "Solid", value: "solid" },
58
+ ],
59
+ },
60
+ ];
61
+
62
+ export const COMPONENT_REGISTRY: ComponentDefinition[] = [
63
+ {
64
+ type: "goToTop",
65
+ label: "Go to Top",
66
+ icon: "ArrowUp",
67
+ category: "Interactive",
68
+ allowsChildren: false,
69
+ defaultProps: {
70
+ enabled: true,
71
+ showAfterScroll: 300,
72
+ icon: "ArrowUp",
73
+ size: "md",
74
+ },
75
+ defaultStyle: {
76
+ backgroundColor: "#3b82f6",
77
+ textColor: "#ffffff",
78
+ paddingX: "16px",
79
+ paddingY: "16px",
80
+ borderRadius: "50%",
81
+ },
82
+ fields: [
83
+ { key: "enabled", label: "Enabled", type: "boolean", default: true },
84
+ {
85
+ key: "showAfterScroll",
86
+ label: "Show after scroll (px)",
87
+ type: "number",
88
+ default: 300,
89
+ min: 0,
90
+ },
91
+ {
92
+ key: "icon",
93
+ label: "Icon name (lucide-react)",
94
+ type: "text",
95
+ default: "ArrowUp",
96
+ },
97
+ {
98
+ key: "size",
99
+ label: "Size",
100
+ type: "select",
101
+ default: "md",
102
+ options: [
103
+ { label: "Small", value: "sm" },
104
+ { label: "Medium", value: "md" },
105
+ { label: "Large", value: "lg" },
106
+ ],
107
+ },
108
+ ],
109
+ },
110
+ {
111
+ type: "header",
112
+ label: "Header / Nav",
113
+ icon: "PanelTop",
114
+ category: "Layout",
115
+ allowsChildren: true,
116
+ defaultProps: { sticky: true, zoneCount: 3 },
117
+ defaultStyle: { backgroundColor: "#ffffff", textColor: "#111827" },
118
+ // Content lives in its zones (drag Logo, Nav Links, Buttons, etc. into each one —
119
+ // see the "navZone" type below), not in generic fields here. Zone count is handled
120
+ // by a dedicated stepper in the Inspector, same as Container's column count.
121
+ fields: [
122
+ {
123
+ key: "sticky",
124
+ label: "Sticky on scroll",
125
+ type: "boolean",
126
+ default: true,
127
+ },
128
+ ],
129
+ },
130
+ {
131
+ type: "navZone",
132
+ label: "Nav Zone",
133
+ icon: "Minus",
134
+ category: "Layout",
135
+ allowsChildren: true,
136
+ defaultProps: { zone: "start" },
137
+ defaultStyle: {},
138
+ fields: [],
139
+ // Count/position is managed by the Header itself (see setHeaderZoneCount in tree.ts).
140
+ hidden: true,
141
+ locked: true,
142
+ },
143
+ {
144
+ type: "logo",
145
+ label: "Logo",
146
+ icon: "Image",
147
+ category: "Content",
148
+ allowsChildren: false,
149
+ defaultProps: {
150
+ useSiteLogo: true,
151
+ customImage: "",
152
+ href: "/",
153
+ height: "36px",
154
+ },
155
+ defaultStyle: {},
156
+ fields: [
157
+ {
158
+ key: "useSiteLogo",
159
+ label: "Use site logo (Theme settings)",
160
+ type: "boolean",
161
+ default: true,
162
+ },
163
+ {
164
+ key: "customImage",
165
+ label: "Custom logo image (overrides site logo)",
166
+ type: "image",
167
+ },
168
+ { key: "href", label: "Link URL", type: "url", default: "/" },
169
+ ],
170
+ },
171
+ {
172
+ type: "navLinks",
173
+ label: "Nav Links",
174
+ icon: "Link2",
175
+ category: "Content",
176
+ allowsChildren: false,
177
+ defaultProps: { useSiteNavigation: true, links: [] },
178
+ defaultStyle: {},
179
+ fields: [
180
+ {
181
+ key: "useSiteNavigation",
182
+ label: "Use site navigation (Navigation settings)",
183
+ type: "boolean",
184
+ default: true,
185
+ },
186
+ {
187
+ key: "links",
188
+ label: "Custom links (used when site navigation is off)",
189
+ type: "array",
190
+ default: [],
191
+ itemFields: [
192
+ { key: "label", label: "Label", type: "text", default: "Link" },
193
+ { key: "href", label: "URL", type: "url", default: "#" },
194
+ ],
195
+ },
196
+ ],
197
+ },
198
+ {
199
+ type: "footer",
200
+ label: "Footer",
201
+ icon: "PanelBottom",
202
+ category: "Layout",
203
+ allowsChildren: false,
204
+ defaultProps: {
205
+ text: "© 2026 My Site. All rights reserved.",
206
+ description: "",
207
+ socialLinks: [],
208
+ linkColumns: [],
209
+ contactInfo: { address: "", phone: "", email: "", extraLine: "" },
210
+ bottomLinks: [],
211
+ },
212
+ defaultStyle: {
213
+ backgroundColor: "#0f172a",
214
+ textColor: "#f8fafc",
215
+ paddingY: "48px",
216
+ },
217
+ fields: [
218
+ {
219
+ key: "description",
220
+ label: "Short description / strapline",
221
+ type: "textarea",
222
+ },
223
+ {
224
+ key: "text",
225
+ label: "Copyright text",
226
+ type: "text",
227
+ default: "© 2026 My Site. All rights reserved.",
228
+ },
229
+ {
230
+ key: "linkColumns",
231
+ label: "Link columns",
232
+ type: "array",
233
+ default: [],
234
+ itemFields: [
235
+ {
236
+ key: "title",
237
+ label: "Column title",
238
+ type: "text",
239
+ default: "Links",
240
+ },
241
+ {
242
+ key: "links",
243
+ label: "Links",
244
+ type: "array",
245
+ default: [],
246
+ itemFields: [
247
+ { key: "label", label: "Label", type: "text", default: "Link" },
248
+ { key: "href", label: "URL", type: "url", default: "#" },
249
+ ],
250
+ },
251
+ ],
252
+ },
253
+ {
254
+ key: "bottomLinks",
255
+ label: "Bottom utility links (e.g. Privacy Policy, Site Map)",
256
+ type: "array",
257
+ default: [],
258
+ itemFields: [
259
+ { key: "label", label: "Label", type: "text", default: "Link" },
260
+ { key: "href", label: "URL", type: "url", default: "#" },
261
+ ],
262
+ },
263
+ {
264
+ key: "socialLinks",
265
+ label: "Social links",
266
+ type: "array",
267
+ default: [],
268
+ itemFields: [
269
+ {
270
+ key: "platform",
271
+ label: "Platform",
272
+ type: "select",
273
+ default: "website",
274
+ options: [
275
+ { label: "Website", value: "website" },
276
+ { label: "Facebook", value: "facebook" },
277
+ { label: "Twitter / X", value: "twitter" },
278
+ { label: "Instagram", value: "instagram" },
279
+ { label: "LinkedIn", value: "linkedin" },
280
+ { label: "YouTube", value: "youtube" },
281
+ ],
282
+ },
283
+ { key: "url", label: "URL", type: "url", default: "#" },
284
+ ],
285
+ },
286
+ ],
287
+ },
288
+ {
289
+ type: "container",
290
+ label: "Columns / Container",
291
+ icon: "Columns3",
292
+ category: "Layout",
293
+ allowsChildren: true,
294
+ defaultProps: { columns: 1, gap: "24px", links: [] },
295
+ defaultStyle: { paddingY: "48px", paddingX: "24px" },
296
+ // "columns" is handled by a dedicated control in the Inspector (it also has to
297
+ // create/remove the underlying column nodes), so it's excluded from this generic
298
+ // field list — see Inspector.tsx.
299
+ fields: [
300
+ { key: "gap", label: "Gap", type: "text", default: "24px" },
301
+ {
302
+ key: "links",
303
+ label:
304
+ "Links — one makes the whole container clickable; two or more render as a small link list at the bottom instead (leave empty if its columns already have their own links/buttons)",
305
+ type: "array",
306
+ default: [],
307
+ itemFields: [
308
+ {
309
+ key: "label",
310
+ label: "Label (used for 2+ links)",
311
+ type: "text",
312
+ default: "Link",
313
+ },
314
+ { key: "href", label: "URL", type: "url", default: "#" },
315
+ ],
316
+ },
317
+ ],
318
+ },
319
+ {
320
+ type: "column",
321
+ label: "Column",
322
+ icon: "RectangleVertical",
323
+ category: "Layout",
324
+ allowsChildren: true,
325
+ defaultProps: { links: [] },
326
+ defaultStyle: {},
327
+ fields: [
328
+ {
329
+ key: "links",
330
+ label:
331
+ "Links — one makes the whole column clickable; two or more render as a small link list at the bottom instead (leave empty if its content already has its own links/buttons)",
332
+ type: "array",
333
+ default: [],
334
+ itemFields: [
335
+ {
336
+ key: "label",
337
+ label: "Label (used for 2+ links)",
338
+ type: "text",
339
+ default: "Link",
340
+ },
341
+ { key: "href", label: "URL", type: "url", default: "#" },
342
+ ],
343
+ },
344
+ ],
345
+ // Columns only exist inside a Container's column count — not draggable on their own.
346
+ hidden: true,
347
+ },
348
+ {
349
+ type: "grid",
350
+ label: "Grid Layout",
351
+ icon: "Grid3x3",
352
+ category: "Layout",
353
+ allowsChildren: true,
354
+ defaultProps: { rows: 1, columns: 2, gap: "24px" },
355
+ defaultStyle: { paddingY: "48px", paddingX: "24px" },
356
+ // "rows"/"columns" get a dedicated stepper pair in the Inspector (they also have to
357
+ // create the underlying grid cell nodes), so they're excluded from this generic
358
+ // field list — see Inspector.tsx, same pattern as Container's "columns".
359
+ fields: [{ key: "gap", label: "Gap", type: "text", default: "24px" }],
360
+ },
361
+ {
362
+ type: "gridCell",
363
+ label: "Grid Cell",
364
+ icon: "Square",
365
+ category: "Layout",
366
+ allowsChildren: true,
367
+ defaultProps: {},
368
+ defaultStyle: {},
369
+ fields: [],
370
+ // Cells only exist inside a Grid's row×column count — not draggable on their own.
371
+ // Their column/row span lives in NodeStyle (gridColumnSpan/gridRowSpan) rather than
372
+ // here, since it has to reach nodeStyleToCss to affect the actual CSS grid item —
373
+ // see the "Grid placement" Inspector section.
374
+ hidden: true,
375
+ },
376
+ {
377
+ type: "hero",
378
+ label: "Hero",
379
+ icon: "Sparkles",
380
+ category: "Content",
381
+ allowsChildren: true,
382
+ defaultProps: {
383
+ heading: "Build something great",
384
+ subheading: "A short supporting sentence about your product or mission.",
385
+ primaryButtonText: "Get Started",
386
+ primaryButtonHref: "#",
387
+ secondaryButtonText: "",
388
+ secondaryButtonHref: "",
389
+ backgroundAnimationEnabled: false,
390
+ preset: "gradient",
391
+ accentColor: "#60a5fa",
392
+ secondColor: "#0f172a",
393
+ gradientAngle: 135,
394
+ density: 80,
395
+ speed: 1,
396
+ bgOpacity: 1,
397
+ },
398
+ defaultStyle: {
399
+ backgroundColor: "#111827",
400
+ textColor: "#ffffff",
401
+ paddingY: "120px",
402
+ paddingX: "24px",
403
+ textAlign: "center",
404
+ minHeight: "480px",
405
+ },
406
+ fields: [
407
+ { key: "heading", label: "Heading", type: "text" },
408
+ { key: "subheading", label: "Subheading", type: "textarea" },
409
+ { key: "primaryButtonText", label: "Primary button text", type: "text" },
410
+ { key: "primaryButtonHref", label: "Primary button link", type: "url" },
411
+ {
412
+ key: "secondaryButtonText",
413
+ label: "Secondary button text",
414
+ type: "text",
415
+ },
416
+ {
417
+ key: "secondaryButtonHref",
418
+ label: "Secondary button link",
419
+ type: "url",
420
+ },
421
+ {
422
+ key: "backgroundAnimationEnabled",
423
+ label: "Enable background animation",
424
+ type: "boolean",
425
+ default: false,
426
+ },
427
+ {
428
+ key: "preset",
429
+ label: "Animation style",
430
+ type: "select",
431
+ default: "gradient",
432
+ options: [
433
+ { label: "Gradient", value: "gradient" },
434
+ { label: "3D Particles", value: "particles" },
435
+ { label: "3D Waves", value: "waves" },
436
+ { label: "3D Floating Shapes", value: "shapes" },
437
+ { label: "3D Starfield", value: "stars" },
438
+ { label: "3D Network / Constellation", value: "network" },
439
+ { label: "3D Orbit Rings", value: "rings" },
440
+ ],
441
+ },
442
+ {
443
+ key: "mouseFollow",
444
+ label: "Follow pointer (3D presets)",
445
+ type: "boolean",
446
+ default: false,
447
+ },
448
+ {
449
+ key: "accentColor",
450
+ label: "Color 1",
451
+ type: "color",
452
+ default: "#60a5fa",
453
+ },
454
+ {
455
+ key: "secondColor",
456
+ label: "Color 2 (gradient only)",
457
+ type: "color",
458
+ default: "#0f172a",
459
+ },
460
+ {
461
+ key: "gradientAngle",
462
+ label: "Gradient angle (degrees, gradient only)",
463
+ type: "number",
464
+ default: 135,
465
+ min: 0,
466
+ max: 360,
467
+ },
468
+ {
469
+ key: "gradientAnimated",
470
+ label: "Animate gradient (drifting colors)",
471
+ type: "boolean",
472
+ default: false,
473
+ },
474
+ {
475
+ key: "density",
476
+ label: "Density (3D presets only)",
477
+ type: "number",
478
+ default: 80,
479
+ min: 10,
480
+ max: 400,
481
+ },
482
+ {
483
+ key: "speed",
484
+ label: "Speed (3D presets only)",
485
+ type: "number",
486
+ default: 1,
487
+ min: 0.1,
488
+ max: 5,
489
+ step: 0.1,
490
+ },
491
+ {
492
+ key: "bgOpacity",
493
+ label: "Background opacity",
494
+ type: "number",
495
+ default: 1,
496
+ min: 0.1,
497
+ max: 1,
498
+ step: 0.05,
499
+ },
500
+ ],
501
+ },
502
+ {
503
+ type: "richText",
504
+ label: "Rich Text",
505
+ icon: "Text",
506
+ category: "Content",
507
+ allowsChildren: false,
508
+ defaultProps: { html: "<p>Write something here...</p>" },
509
+ defaultStyle: { paddingY: "48px", paddingX: "24px" },
510
+ fields: [{ key: "html", label: "Content", type: "richtext" }],
511
+ },
512
+ {
513
+ type: "imageBlock",
514
+ label: "Image",
515
+ icon: "Image",
516
+ category: "Media",
517
+ allowsChildren: false,
518
+ defaultProps: {
519
+ src: "",
520
+ alt: "",
521
+ caption: "",
522
+ imgWidth: "",
523
+ imgHeight: "",
524
+ align: "center",
525
+ },
526
+ defaultStyle: { paddingY: "24px", paddingX: "24px" },
527
+ fields: [
528
+ { key: "src", label: "Image", type: "image" },
529
+ { key: "alt", label: "Alt text", type: "text" },
530
+ { key: "caption", label: "Caption", type: "text" },
531
+ {
532
+ key: "imgWidth",
533
+ label: "Image width (e.g. 320px, 50%)",
534
+ type: "text",
535
+ placeholder: "auto",
536
+ },
537
+ {
538
+ key: "imgHeight",
539
+ label: "Image height (e.g. 240px)",
540
+ type: "text",
541
+ placeholder: "auto",
542
+ },
543
+ {
544
+ key: "align",
545
+ label: "Align image",
546
+ type: "select",
547
+ default: "center",
548
+ options: [
549
+ { label: "Left", value: "left" },
550
+ { label: "Center", value: "center" },
551
+ { label: "Right", value: "right" },
552
+ ],
553
+ },
554
+ ],
555
+ },
556
+ {
557
+ type: "slider",
558
+ label: "Image Slider",
559
+ icon: "GalleryHorizontal",
560
+ category: "Media",
561
+ allowsChildren: false,
562
+ defaultProps: {
563
+ autoplay: true,
564
+ intervalMs: 5000,
565
+ slides: [
566
+ {
567
+ image: "",
568
+ heading: "Slide heading",
569
+ subheading: "",
570
+ buttonText: "",
571
+ buttonHref: "",
572
+ },
573
+ ],
574
+ },
575
+ defaultStyle: {
576
+ minHeight: "480px",
577
+ textColor: "#ffffff",
578
+ textAlign: "center",
579
+ },
580
+ fields: [
581
+ { key: "autoplay", label: "Autoplay", type: "boolean", default: true },
582
+ {
583
+ key: "intervalMs",
584
+ label: "Interval (ms)",
585
+ type: "number",
586
+ default: 5000,
587
+ min: 1000,
588
+ max: 20000,
589
+ },
590
+ {
591
+ key: "slides",
592
+ label: "Slides",
593
+ type: "array",
594
+ default: [],
595
+ itemFields: [
596
+ { key: "image", label: "Image", type: "image" },
597
+ { key: "heading", label: "Heading", type: "text" },
598
+ { key: "subheading", label: "Subheading", type: "text" },
599
+ { key: "buttonText", label: "Button text", type: "text" },
600
+ { key: "buttonHref", label: "Button link", type: "url" },
601
+ ],
602
+ },
603
+ ],
604
+ },
605
+ {
606
+ type: "imageBackgroundSection",
607
+ label: "Image Background Section",
608
+ icon: "ImagePlus",
609
+ category: "Media",
610
+ allowsChildren: true,
611
+ defaultProps: {
612
+ heading: "A section with an image background",
613
+ subheading: "",
614
+ buttonText: "",
615
+ buttonHref: "",
616
+ },
617
+ defaultStyle: {
618
+ backgroundImage: "",
619
+ backgroundOverlay: "rgba(0,0,0,0.45)",
620
+ textColor: "#ffffff",
621
+ minHeight: "480px",
622
+ textAlign: "center",
623
+ paddingY: "96px",
624
+ },
625
+ fields: [
626
+ { key: "heading", label: "Heading", type: "text" },
627
+ { key: "subheading", label: "Subheading", type: "textarea" },
628
+ { key: "buttonText", label: "Button text", type: "text" },
629
+ { key: "buttonHref", label: "Button link", type: "url" },
630
+ ],
631
+ },
632
+ {
633
+ type: "videoBackgroundSection",
634
+ label: "Video Background Section",
635
+ icon: "Video",
636
+ category: "Media",
637
+ allowsChildren: true,
638
+ defaultProps: {
639
+ heading: "A section with a video background",
640
+ subheading: "",
641
+ buttonText: "",
642
+ buttonHref: "",
643
+ },
644
+ defaultStyle: {
645
+ backgroundVideo: "",
646
+ backgroundOverlay: "rgba(0,0,0,0.5)",
647
+ textColor: "#ffffff",
648
+ minHeight: "560px",
649
+ textAlign: "center",
650
+ paddingY: "96px",
651
+ },
652
+ fields: [
653
+ { key: "heading", label: "Heading", type: "text" },
654
+ { key: "subheading", label: "Subheading", type: "textarea" },
655
+ { key: "buttonText", label: "Button text", type: "text" },
656
+ { key: "buttonHref", label: "Button link", type: "url" },
657
+ ],
658
+ },
659
+ {
660
+ type: "threeBackground",
661
+ label: "3D Background",
662
+ icon: "Sparkles",
663
+ category: "Media",
664
+ allowsChildren: true,
665
+ defaultProps: {
666
+ enabled: true,
667
+ preset: "particles",
668
+ color: "#60a5fa",
669
+ secondColor: "",
670
+ density: 80,
671
+ speed: 1,
672
+ mouseFollow: false,
673
+ bgOpacity: 1,
674
+ heading: "An animated 3D section",
675
+ subheading: "",
676
+ buttonText: "",
677
+ buttonHref: "",
678
+ },
679
+ defaultStyle: {
680
+ backgroundColor: "#0f172a",
681
+ textColor: "#ffffff",
682
+ minHeight: "480px",
683
+ textAlign: "center",
684
+ paddingY: "96px",
685
+ },
686
+ fields: [
687
+ {
688
+ key: "enabled",
689
+ label: "Animation active",
690
+ type: "boolean",
691
+ default: true,
692
+ },
693
+ {
694
+ key: "preset",
695
+ label: "Preset",
696
+ type: "select",
697
+ default: "particles",
698
+ options: [
699
+ { label: "Particles", value: "particles" },
700
+ { label: "Waves", value: "waves" },
701
+ { label: "Floating Shapes", value: "shapes" },
702
+ { label: "Starfield", value: "stars" },
703
+ { label: "Network / Constellation", value: "network" },
704
+ { label: "Orbit Rings", value: "rings" },
705
+ ],
706
+ },
707
+ {
708
+ key: "color",
709
+ label: "Accent color",
710
+ type: "color",
711
+ default: "#60a5fa",
712
+ },
713
+ {
714
+ key: "secondColor",
715
+ label: "Second color (stars/network/rings/shapes)",
716
+ type: "color",
717
+ },
718
+ {
719
+ key: "density",
720
+ label: "Density",
721
+ type: "number",
722
+ default: 80,
723
+ min: 10,
724
+ max: 400,
725
+ },
726
+ {
727
+ key: "speed",
728
+ label: "Speed",
729
+ type: "number",
730
+ default: 1,
731
+ min: 0.1,
732
+ max: 5,
733
+ step: 0.1,
734
+ },
735
+ {
736
+ key: "mouseFollow",
737
+ label: "Follow pointer (depth parallax)",
738
+ type: "boolean",
739
+ default: false,
740
+ },
741
+ {
742
+ key: "bgOpacity",
743
+ label: "Animation opacity",
744
+ type: "number",
745
+ default: 1,
746
+ min: 0.1,
747
+ max: 1,
748
+ step: 0.05,
749
+ },
750
+ { key: "heading", label: "Heading", type: "text" },
751
+ { key: "subheading", label: "Subheading", type: "textarea" },
752
+ { key: "buttonText", label: "Button text", type: "text" },
753
+ { key: "buttonHref", label: "Button link", type: "url" },
754
+ ],
755
+ },
756
+ {
757
+ type: "mapEmbed",
758
+ label: "Map",
759
+ icon: "MapPin",
760
+ category: "Interactive",
761
+ allowsChildren: false,
762
+ defaultProps: { lat: 23.8103, lng: 90.4125, zoom: 13, embedUrl: "" },
763
+ defaultStyle: { minHeight: "400px" },
764
+ fields: [
765
+ { key: "lat", label: "Latitude", type: "number" },
766
+ { key: "lng", label: "Longitude", type: "number" },
767
+ {
768
+ key: "zoom",
769
+ label: "Zoom",
770
+ type: "number",
771
+ min: 1,
772
+ max: 20,
773
+ default: 13,
774
+ },
775
+ {
776
+ key: "embedUrl",
777
+ label: "Custom embed URL (overrides lat/lng)",
778
+ type: "url",
779
+ placeholder: "https://www.google.com/maps/embed?...",
780
+ },
781
+ ],
782
+ },
783
+ {
784
+ type: "buttonGroup",
785
+ label: "Buttons",
786
+ icon: "MousePointerClick",
787
+ category: "Interactive",
788
+ allowsChildren: false,
789
+ defaultProps: {
790
+ buttons: [{ text: "Click me", href: "#", variant: "primary" }],
791
+ },
792
+ defaultStyle: { paddingY: "24px", paddingX: "24px", textAlign: "center" },
793
+ fields: [
794
+ {
795
+ key: "buttons",
796
+ label: "Buttons",
797
+ type: "array",
798
+ default: [],
799
+ itemFields: buttonItemFields,
800
+ },
801
+ ],
802
+ },
803
+ {
804
+ type: "gallery",
805
+ label: "Gallery",
806
+ icon: "LayoutGrid",
807
+ category: "Media",
808
+ allowsChildren: false,
809
+ defaultProps: { columns: 3, images: [] },
810
+ defaultStyle: { paddingY: "48px", paddingX: "24px" },
811
+ fields: [
812
+ {
813
+ key: "columns",
814
+ label: "Columns",
815
+ type: "number",
816
+ default: 3,
817
+ min: 1,
818
+ max: 6,
819
+ },
820
+ {
821
+ key: "images",
822
+ label: "Images",
823
+ type: "array",
824
+ default: [],
825
+ itemFields: [
826
+ { key: "src", label: "Image", type: "image" },
827
+ { key: "alt", label: "Alt text", type: "text" },
828
+ ],
829
+ },
830
+ ],
831
+ },
832
+ {
833
+ type: "cta",
834
+ label: "Call To Action",
835
+ icon: "Megaphone",
836
+ category: "Content",
837
+ allowsChildren: true,
838
+ defaultProps: {
839
+ heading: "Ready to get started?",
840
+ subheading: "Join us today.",
841
+ buttonText: "Contact Us",
842
+ buttonHref: "#",
843
+ },
844
+ defaultStyle: {
845
+ backgroundColor: "#2563eb",
846
+ textColor: "#ffffff",
847
+ paddingY: "72px",
848
+ paddingX: "24px",
849
+ textAlign: "center",
850
+ },
851
+ fields: [
852
+ { key: "heading", label: "Heading", type: "text" },
853
+ { key: "subheading", label: "Subheading", type: "textarea" },
854
+ { key: "buttonText", label: "Button text", type: "text" },
855
+ { key: "buttonHref", label: "Button link", type: "url" },
856
+ ],
857
+ },
858
+ {
859
+ type: "countdown",
860
+ label: "Countdown",
861
+ icon: "Timer",
862
+ category: "Interactive",
863
+ allowsChildren: false,
864
+ defaultProps: {
865
+ heading: "Time Remaining",
866
+ // Filled in with "30 days from now" at creation time — see createNode in tree.ts.
867
+ targetDate: "",
868
+ expiredText: "This deadline has passed.",
869
+ buttonText: "",
870
+ buttonHref: "",
871
+ },
872
+ defaultStyle: {
873
+ backgroundColor: "#0f172a",
874
+ textColor: "#ffffff",
875
+ paddingY: "64px",
876
+ paddingX: "24px",
877
+ textAlign: "center",
878
+ },
879
+ fields: [
880
+ { key: "heading", label: "Heading", type: "text" },
881
+ { key: "targetDate", label: "Count down to", type: "datetime" },
882
+ { key: "expiredText", label: "Text shown after it passes", type: "text" },
883
+ { key: "buttonText", label: "Button text (optional)", type: "text" },
884
+ { key: "buttonHref", label: "Button link", type: "url" },
885
+ ],
886
+ },
887
+ {
888
+ type: "stats",
889
+ label: "Stats / Counters",
890
+ icon: "TrendingUp",
891
+ category: "Content",
892
+ allowsChildren: false,
893
+ defaultProps: {
894
+ columns: 3,
895
+ items: [
896
+ { value: "500+", label: "Charities Supported" },
897
+ { value: "$2M", label: "Raised This Year" },
898
+ { value: "40", label: "Countries Reached" },
899
+ ],
900
+ },
901
+ defaultStyle: { paddingY: "64px", paddingX: "24px", textAlign: "center" },
902
+ fields: [
903
+ {
904
+ key: "columns",
905
+ label: "Columns",
906
+ type: "number",
907
+ default: 3,
908
+ min: 2,
909
+ max: 4,
910
+ },
911
+ {
912
+ key: "items",
913
+ label: "Stats",
914
+ type: "array",
915
+ default: [],
916
+ itemFields: [
917
+ {
918
+ key: "value",
919
+ label: "Value (e.g. 500+, $2M, 40)",
920
+ type: "text",
921
+ default: "100",
922
+ },
923
+ { key: "label", label: "Label", type: "text", default: "Label" },
924
+ ],
925
+ },
926
+ ],
927
+ },
928
+ {
929
+ type: "cardGrid",
930
+ label: "Card Grid",
931
+ icon: "LayoutList",
932
+ category: "Content",
933
+ allowsChildren: false,
934
+ defaultProps: {
935
+ heading: "Latest News",
936
+ columns: 3,
937
+ cards: [
938
+ {
939
+ image: "",
940
+ title: "Card title",
941
+ excerpt: "A short description of this card goes here.",
942
+ buttonText: "Read more",
943
+ href: "#",
944
+ },
945
+ ],
946
+ },
947
+ defaultStyle: { paddingY: "64px", paddingX: "24px" },
948
+ fields: [
949
+ { key: "heading", label: "Section heading", type: "text" },
950
+ {
951
+ key: "columns",
952
+ label: "Columns",
953
+ type: "number",
954
+ default: 3,
955
+ min: 1,
956
+ max: 4,
957
+ },
958
+ {
959
+ key: "imageHeight",
960
+ label: "Card image height (e.g. 160px)",
961
+ type: "text",
962
+ placeholder: "160px",
963
+ },
964
+ {
965
+ key: "cards",
966
+ label: "Cards",
967
+ type: "array",
968
+ default: [],
969
+ itemFields: [
970
+ { key: "image", label: "Image", type: "image" },
971
+ {
972
+ key: "icon",
973
+ label: "Icon (used when no image is set)",
974
+ type: "text",
975
+ placeholder: "e.g. Globe2 — any lucide-react icon name",
976
+ },
977
+ { key: "title", label: "Title", type: "text" },
978
+ { key: "excerpt", label: "Excerpt", type: "textarea" },
979
+ {
980
+ key: "buttonText",
981
+ label: "Link text",
982
+ type: "text",
983
+ default: "Read more",
984
+ },
985
+ { key: "href", label: "Link URL", type: "url", default: "#" },
986
+ ],
987
+ },
988
+ ],
989
+ },
990
+ {
991
+ type: "accordion",
992
+ label: "Accordion / FAQ",
993
+ icon: "ChevronDown",
994
+ category: "Content",
995
+ allowsChildren: false,
996
+ defaultProps: {
997
+ heading: "Frequently Asked Questions",
998
+ items: [{ question: "Question goes here?", answer: "Answer goes here." }],
999
+ },
1000
+ defaultStyle: { paddingY: "64px", paddingX: "24px" },
1001
+ fields: [
1002
+ { key: "heading", label: "Section heading", type: "text" },
1003
+ {
1004
+ key: "items",
1005
+ label: "Questions",
1006
+ type: "array",
1007
+ default: [],
1008
+ itemFields: [
1009
+ {
1010
+ key: "question",
1011
+ label: "Question",
1012
+ type: "text",
1013
+ default: "Question?",
1014
+ },
1015
+ {
1016
+ key: "answer",
1017
+ label: "Answer",
1018
+ type: "textarea",
1019
+ default: "Answer.",
1020
+ },
1021
+ ],
1022
+ },
1023
+ ],
1024
+ },
1025
+ {
1026
+ type: "collectionList",
1027
+ label: "Collection List",
1028
+ icon: "Newspaper",
1029
+ category: "Content",
1030
+ allowsChildren: false,
1031
+ defaultProps: {
1032
+ collectionSlug: "",
1033
+ columns: 3,
1034
+ limit: 6,
1035
+ showExcerpt: true,
1036
+ showDate: true,
1037
+ },
1038
+ defaultStyle: { paddingY: "64px", paddingX: "24px" },
1039
+ fields: [
1040
+ {
1041
+ key: "collectionSlug",
1042
+ label: "Collection slug",
1043
+ type: "text",
1044
+ placeholder: "e.g. blog",
1045
+ },
1046
+ {
1047
+ key: "columns",
1048
+ label: "Columns",
1049
+ type: "number",
1050
+ default: 3,
1051
+ min: 1,
1052
+ max: 4,
1053
+ },
1054
+ {
1055
+ key: "limit",
1056
+ label: "Max items",
1057
+ type: "number",
1058
+ default: 6,
1059
+ min: 1,
1060
+ max: 50,
1061
+ },
1062
+ {
1063
+ key: "showExcerpt",
1064
+ label: "Show excerpts",
1065
+ type: "boolean",
1066
+ default: true,
1067
+ },
1068
+ {
1069
+ key: "showDate",
1070
+ label: "Show publish dates",
1071
+ type: "boolean",
1072
+ default: true,
1073
+ },
1074
+ ],
1075
+ },
1076
+ {
1077
+ type: "languageSwitcher",
1078
+ label: "Language Switcher",
1079
+ icon: "Languages",
1080
+ category: "Interactive",
1081
+ allowsChildren: false,
1082
+ defaultProps: { compact: false },
1083
+ defaultStyle: { paddingY: "12px", paddingX: "24px", textAlign: "center" },
1084
+ fields: [
1085
+ {
1086
+ key: "compact",
1087
+ label: "Compact (locale codes like EN/ES instead of names)",
1088
+ type: "boolean",
1089
+ default: false,
1090
+ },
1091
+ ],
1092
+ },
1093
+ {
1094
+ type: "marquee",
1095
+ label: "Marquee / Ticker",
1096
+ icon: "MoveRight",
1097
+ category: "Content",
1098
+ allowsChildren: false,
1099
+ defaultProps: {
1100
+ speed: 20,
1101
+ direction: "left",
1102
+ pauseOnHover: true,
1103
+ items: [
1104
+ { text: "Partner One", image: "" },
1105
+ { text: "Partner Two", image: "" },
1106
+ { text: "Partner Three", image: "" },
1107
+ { text: "Partner Four", image: "" },
1108
+ ],
1109
+ },
1110
+ defaultStyle: { paddingY: "32px", paddingX: "0px" },
1111
+ fields: [
1112
+ {
1113
+ key: "speed",
1114
+ label: "Loop duration (seconds)",
1115
+ type: "number",
1116
+ default: 20,
1117
+ min: 4,
1118
+ max: 120,
1119
+ },
1120
+ {
1121
+ key: "direction",
1122
+ label: "Direction",
1123
+ type: "select",
1124
+ default: "left",
1125
+ options: [
1126
+ { label: "Right to left", value: "left" },
1127
+ { label: "Left to right", value: "right" },
1128
+ ],
1129
+ },
1130
+ {
1131
+ key: "pauseOnHover",
1132
+ label: "Pause on hover",
1133
+ type: "boolean",
1134
+ default: true,
1135
+ },
1136
+ {
1137
+ key: "items",
1138
+ label: "Items",
1139
+ type: "array",
1140
+ default: [],
1141
+ itemFields: [
1142
+ {
1143
+ key: "text",
1144
+ label: "Text (used when no image)",
1145
+ type: "text",
1146
+ default: "Item",
1147
+ },
1148
+ { key: "image", label: "Logo / image", type: "image" },
1149
+ ],
1150
+ },
1151
+ ],
1152
+ },
1153
+ {
1154
+ type: "tabs",
1155
+ label: "Tabs",
1156
+ icon: "PanelsTopLeft",
1157
+ category: "Content",
1158
+ allowsChildren: false,
1159
+ defaultProps: {
1160
+ tabTransition: "fade",
1161
+ items: [
1162
+ { label: "Tab 1", content: "<p>First tab content.</p>" },
1163
+ { label: "Tab 2", content: "<p>Second tab content.</p>" },
1164
+ ],
1165
+ },
1166
+ defaultStyle: { paddingY: "48px", paddingX: "24px" },
1167
+ fields: [
1168
+ {
1169
+ key: "tabTransition",
1170
+ label: "Switch animation",
1171
+ type: "select",
1172
+ default: "fade",
1173
+ options: [
1174
+ { label: "None", value: "none" },
1175
+ { label: "Fade", value: "fade" },
1176
+ { label: "Slide", value: "slide" },
1177
+ ],
1178
+ },
1179
+ {
1180
+ key: "items",
1181
+ label: "Tabs",
1182
+ type: "array",
1183
+ default: [],
1184
+ itemFields: [
1185
+ { key: "label", label: "Tab label", type: "text", default: "Tab" },
1186
+ { key: "content", label: "Content", type: "richtext", default: "" },
1187
+ ],
1188
+ },
1189
+ ],
1190
+ },
1191
+ {
1192
+ type: "contactForm",
1193
+ label: "Contact Form",
1194
+ icon: "Mail",
1195
+ category: "Interactive",
1196
+ allowsChildren: false,
1197
+ defaultProps: {
1198
+ heading: "Get in Touch",
1199
+ subheading: "",
1200
+ formName: "Contact",
1201
+ fields: [
1202
+ { key: "name", label: "Name", fieldType: "text", required: true },
1203
+ { key: "email", label: "Email", fieldType: "email", required: true },
1204
+ {
1205
+ key: "phone",
1206
+ label: "Telephone",
1207
+ fieldType: "text",
1208
+ required: false,
1209
+ },
1210
+ {
1211
+ key: "message",
1212
+ label: "Details",
1213
+ fieldType: "textarea",
1214
+ required: true,
1215
+ },
1216
+ ],
1217
+ submitButtonText: "Send Message",
1218
+ successMessage:
1219
+ "Thanks — we've received your message and will be in touch soon.",
1220
+ },
1221
+ defaultStyle: { paddingY: "64px", paddingX: "24px" },
1222
+ fields: [
1223
+ { key: "heading", label: "Heading", type: "text" },
1224
+ { key: "subheading", label: "Subheading", type: "textarea" },
1225
+ {
1226
+ key: "formName",
1227
+ label: "Form name (used to tell submissions apart in the admin)",
1228
+ type: "text",
1229
+ default: "Contact",
1230
+ },
1231
+ {
1232
+ key: "fields",
1233
+ label: "Form fields",
1234
+ type: "array",
1235
+ default: [],
1236
+ itemFields: [
1237
+ {
1238
+ key: "label",
1239
+ label: "Field label",
1240
+ type: "text",
1241
+ default: "Field",
1242
+ },
1243
+ {
1244
+ key: "key",
1245
+ label: "Field key (unique, no spaces)",
1246
+ type: "text",
1247
+ default: "field",
1248
+ },
1249
+ {
1250
+ key: "fieldType",
1251
+ label: "Input type",
1252
+ type: "select",
1253
+ default: "text",
1254
+ options: [
1255
+ { label: "Text", value: "text" },
1256
+ { label: "Email", value: "email" },
1257
+ { label: "Phone", value: "tel" },
1258
+ { label: "Paragraph", value: "textarea" },
1259
+ ],
1260
+ },
1261
+ {
1262
+ key: "required",
1263
+ label: "Required",
1264
+ type: "boolean",
1265
+ default: false,
1266
+ },
1267
+ ],
1268
+ },
1269
+ { key: "submitButtonText", label: "Submit button text", type: "text" },
1270
+ {
1271
+ key: "successMessage",
1272
+ label: "Message shown after submitting",
1273
+ type: "textarea",
1274
+ },
1275
+ ],
1276
+ },
1277
+ {
1278
+ type: "siteSearch",
1279
+ label: "Site Search",
1280
+ icon: "Search",
1281
+ category: "Interactive",
1282
+ allowsChildren: false,
1283
+ defaultProps: { heading: "Search", placeholder: "Search this site…" },
1284
+ defaultStyle: { paddingY: "48px", paddingX: "24px" },
1285
+ fields: [
1286
+ { key: "heading", label: "Heading", type: "text", default: "Search" },
1287
+ {
1288
+ key: "placeholder",
1289
+ label: "Search placeholder",
1290
+ type: "text",
1291
+ default: "Search this site…",
1292
+ },
1293
+ ],
1294
+ },
1295
+ {
1296
+ type: "embed",
1297
+ label: "Embed / Custom HTML",
1298
+ icon: "Code",
1299
+ category: "Interactive",
1300
+ allowsChildren: false,
1301
+ defaultProps: {
1302
+ mode: "iframe",
1303
+ url: "",
1304
+ html: "",
1305
+ height: "400px",
1306
+ title: "Embedded content",
1307
+ },
1308
+ defaultStyle: { paddingY: "32px", paddingX: "24px" },
1309
+ fields: [
1310
+ {
1311
+ key: "mode",
1312
+ label: "Embed mode",
1313
+ type: "select",
1314
+ default: "iframe",
1315
+ options: [
1316
+ { label: "iframe (URL)", value: "iframe" },
1317
+ { label: "Custom HTML", value: "html" },
1318
+ ],
1319
+ },
1320
+ {
1321
+ key: "url",
1322
+ label: "Embed URL (iframe mode)",
1323
+ type: "url",
1324
+ default: "",
1325
+ },
1326
+ {
1327
+ key: "html",
1328
+ label: "Custom HTML (HTML mode)",
1329
+ type: "textarea",
1330
+ default: "",
1331
+ },
1332
+ { key: "height", label: "Height", type: "text", default: "400px" },
1333
+ {
1334
+ key: "title",
1335
+ label: "Accessible title (iframe)",
1336
+ type: "text",
1337
+ default: "Embedded content",
1338
+ },
1339
+ ],
1340
+ },
1341
+ {
1342
+ type: "testimonial",
1343
+ label: "Testimonials",
1344
+ icon: "Quote",
1345
+ category: "Content",
1346
+ allowsChildren: false,
1347
+ defaultProps: {
1348
+ heading: "What people say",
1349
+ layout: "grid",
1350
+ columns: 3,
1351
+ items: [
1352
+ { avatar: "", name: "Jane Doe", role: "CEO, Acme Inc.", quote: "This product changed how we work.", rating: 5 },
1353
+ ],
1354
+ },
1355
+ defaultStyle: { paddingY: "64px", paddingX: "24px" },
1356
+ fields: [
1357
+ { key: "heading", label: "Section heading", type: "text" },
1358
+ {
1359
+ key: "layout",
1360
+ label: "Layout",
1361
+ type: "select",
1362
+ default: "grid",
1363
+ options: [
1364
+ { label: "Grid", value: "grid" },
1365
+ { label: "Carousel", value: "carousel" },
1366
+ ],
1367
+ },
1368
+ { key: "columns", label: "Columns (grid layout)", type: "number", default: 3, min: 1, max: 4 },
1369
+ {
1370
+ key: "items",
1371
+ label: "Testimonials",
1372
+ type: "array",
1373
+ default: [],
1374
+ itemFields: [
1375
+ { key: "avatar", label: "Photo", type: "image" },
1376
+ { key: "name", label: "Name", type: "text" },
1377
+ { key: "role", label: "Role / company", type: "text" },
1378
+ { key: "quote", label: "Quote", type: "textarea" },
1379
+ { key: "rating", label: "Rating (1-5, optional)", type: "number", min: 1, max: 5 },
1380
+ ],
1381
+ },
1382
+ ],
1383
+ },
1384
+ {
1385
+ type: "pricingTable",
1386
+ label: "Pricing Table",
1387
+ icon: "Wallet",
1388
+ category: "Content",
1389
+ allowsChildren: false,
1390
+ defaultProps: {
1391
+ heading: "Simple, transparent pricing",
1392
+ tiers: [
1393
+ {
1394
+ name: "Starter",
1395
+ price: "$9",
1396
+ period: "/mo",
1397
+ features: "1 project\nCommunity support",
1398
+ buttonText: "Get started",
1399
+ buttonHref: "#",
1400
+ highlighted: false,
1401
+ },
1402
+ {
1403
+ name: "Pro",
1404
+ price: "$29",
1405
+ period: "/mo",
1406
+ features: "Unlimited projects\nPriority support\nAdvanced analytics",
1407
+ buttonText: "Get started",
1408
+ buttonHref: "#",
1409
+ highlighted: true,
1410
+ },
1411
+ ],
1412
+ },
1413
+ defaultStyle: { paddingY: "64px", paddingX: "24px" },
1414
+ fields: [
1415
+ { key: "heading", label: "Section heading", type: "text" },
1416
+ {
1417
+ key: "tiers",
1418
+ label: "Pricing tiers",
1419
+ type: "array",
1420
+ default: [],
1421
+ itemFields: [
1422
+ { key: "name", label: "Plan name", type: "text" },
1423
+ { key: "price", label: "Price (e.g. $29)", type: "text" },
1424
+ { key: "period", label: "Billing period (e.g. /mo)", type: "text" },
1425
+ { key: "features", label: "Features (one per line)", type: "textarea" },
1426
+ { key: "buttonText", label: "Button text", type: "text", default: "Get started" },
1427
+ { key: "buttonHref", label: "Button link", type: "url", default: "#" },
1428
+ { key: "highlighted", label: "Highlight this tier", type: "boolean", default: false },
1429
+ ],
1430
+ },
1431
+ ],
1432
+ },
1433
+ {
1434
+ type: "videoEmbed",
1435
+ label: "Video",
1436
+ icon: "PlayCircle",
1437
+ category: "Media",
1438
+ allowsChildren: false,
1439
+ defaultProps: {
1440
+ url: "",
1441
+ thumbnail: "",
1442
+ aspectRatio: "16/9",
1443
+ },
1444
+ defaultStyle: { paddingY: "48px", paddingX: "24px" },
1445
+ fields: [
1446
+ { key: "url", label: "YouTube / Vimeo / MP4 URL", type: "url", placeholder: "https://www.youtube.com/watch?v=..." },
1447
+ { key: "thumbnail", label: "Custom thumbnail (optional)", type: "image" },
1448
+ {
1449
+ key: "aspectRatio",
1450
+ label: "Aspect ratio",
1451
+ type: "select",
1452
+ default: "16/9",
1453
+ options: [
1454
+ { label: "16:9 (widescreen)", value: "16/9" },
1455
+ { label: "4:3 (classic)", value: "4/3" },
1456
+ { label: "1:1 (square)", value: "1/1" },
1457
+ { label: "9:16 (vertical)", value: "9/16" },
1458
+ ],
1459
+ },
1460
+ ],
1461
+ },
1462
+ {
1463
+ type: "teamGrid",
1464
+ label: "Team",
1465
+ icon: "Users",
1466
+ category: "Content",
1467
+ allowsChildren: false,
1468
+ defaultProps: {
1469
+ heading: "Meet the team",
1470
+ columns: 4,
1471
+ members: [{ photo: "", name: "Alex Rivera", role: "Founder", bio: "", linkedinUrl: "", twitterUrl: "" }],
1472
+ },
1473
+ defaultStyle: { paddingY: "64px", paddingX: "24px", textAlign: "center" },
1474
+ fields: [
1475
+ { key: "heading", label: "Section heading", type: "text" },
1476
+ { key: "columns", label: "Columns", type: "number", default: 4, min: 1, max: 5 },
1477
+ {
1478
+ key: "members",
1479
+ label: "Team members",
1480
+ type: "array",
1481
+ default: [],
1482
+ itemFields: [
1483
+ { key: "photo", label: "Photo", type: "image" },
1484
+ { key: "name", label: "Name", type: "text" },
1485
+ { key: "role", label: "Role", type: "text" },
1486
+ { key: "bio", label: "Short bio (optional)", type: "textarea" },
1487
+ { key: "linkedinUrl", label: "LinkedIn URL (optional)", type: "url" },
1488
+ { key: "twitterUrl", label: "Twitter/X URL (optional)", type: "url" },
1489
+ ],
1490
+ },
1491
+ ],
1492
+ },
1493
+ {
1494
+ type: "logoCloud",
1495
+ label: "Logo Cloud",
1496
+ icon: "Building2",
1497
+ category: "Content",
1498
+ allowsChildren: false,
1499
+ defaultProps: {
1500
+ heading: "Trusted by teams at",
1501
+ layout: "grid",
1502
+ grayscale: true,
1503
+ logos: [{ image: "", name: "Client", href: "" }],
1504
+ },
1505
+ defaultStyle: { paddingY: "48px", paddingX: "24px", textAlign: "center" },
1506
+ fields: [
1507
+ { key: "heading", label: "Section heading (optional)", type: "text" },
1508
+ {
1509
+ key: "layout",
1510
+ label: "Layout",
1511
+ type: "select",
1512
+ default: "grid",
1513
+ options: [
1514
+ { label: "Static grid", value: "grid" },
1515
+ { label: "Auto-scrolling row", value: "scroll" },
1516
+ ],
1517
+ },
1518
+ { key: "grayscale", label: "Grayscale until hovered", type: "boolean", default: true },
1519
+ {
1520
+ key: "logos",
1521
+ label: "Logos",
1522
+ type: "array",
1523
+ default: [],
1524
+ itemFields: [
1525
+ { key: "image", label: "Logo image", type: "image" },
1526
+ { key: "name", label: "Company name (alt text)", type: "text" },
1527
+ { key: "href", label: "Link (optional)", type: "url" },
1528
+ ],
1529
+ },
1530
+ ],
1531
+ },
1532
+ {
1533
+ type: "timeline",
1534
+ label: "Timeline",
1535
+ icon: "GitCommitVertical",
1536
+ category: "Content",
1537
+ allowsChildren: false,
1538
+ defaultProps: {
1539
+ heading: "Our journey",
1540
+ items: [{ date: "2020", title: "Founded", description: "The company was founded." }],
1541
+ },
1542
+ defaultStyle: { paddingY: "64px", paddingX: "24px" },
1543
+ fields: [
1544
+ { key: "heading", label: "Section heading", type: "text" },
1545
+ {
1546
+ key: "items",
1547
+ label: "Timeline events",
1548
+ type: "array",
1549
+ default: [],
1550
+ itemFields: [
1551
+ { key: "date", label: "Date / label", type: "text" },
1552
+ { key: "title", label: "Title", type: "text" },
1553
+ { key: "description", label: "Description", type: "textarea" },
1554
+ ],
1555
+ },
1556
+ ],
1557
+ },
1558
+ {
1559
+ type: "divider",
1560
+ label: "Divider",
1561
+ icon: "SeparatorHorizontal",
1562
+ category: "Layout",
1563
+ allowsChildren: false,
1564
+ defaultProps: { lineStyle: "solid", thickness: "1px", color: "#e2e8f0", width: "100%" },
1565
+ defaultStyle: { paddingY: "24px" },
1566
+ fields: [
1567
+ {
1568
+ key: "lineStyle",
1569
+ label: "Line style",
1570
+ type: "select",
1571
+ default: "solid",
1572
+ options: [
1573
+ { label: "Solid", value: "solid" },
1574
+ { label: "Dashed", value: "dashed" },
1575
+ { label: "Dotted", value: "dotted" },
1576
+ ],
1577
+ },
1578
+ { key: "thickness", label: "Thickness", type: "text", default: "1px", placeholder: "1px" },
1579
+ { key: "color", label: "Line color", type: "color", default: "#e2e8f0" },
1580
+ { key: "width", label: "Width", type: "text", default: "100%", placeholder: "100%, 300px..." },
1581
+ ],
1582
+ },
1583
+ {
1584
+ type: "spacer",
1585
+ label: "Spacer",
1586
+ icon: "MoveVertical",
1587
+ category: "Layout",
1588
+ allowsChildren: false,
1589
+ defaultProps: { height: "48px" },
1590
+ defaultStyle: {},
1591
+ fields: [{ key: "height", label: "Height", type: "text", default: "48px", placeholder: "e.g. 48px" }],
1592
+ },
1593
+ {
1594
+ type: "beforeAfter",
1595
+ label: "Before / After",
1596
+ icon: "SplitSquareHorizontal",
1597
+ category: "Media",
1598
+ allowsChildren: false,
1599
+ defaultProps: {
1600
+ beforeImage: "",
1601
+ afterImage: "",
1602
+ beforeLabel: "Before",
1603
+ afterLabel: "After",
1604
+ },
1605
+ defaultStyle: { paddingY: "48px", paddingX: "24px" },
1606
+ fields: [
1607
+ { key: "beforeImage", label: "Before image", type: "image" },
1608
+ { key: "afterImage", label: "After image", type: "image" },
1609
+ { key: "beforeLabel", label: "Before label", type: "text", default: "Before" },
1610
+ { key: "afterLabel", label: "After label", type: "text", default: "After" },
1611
+ ],
1612
+ },
1613
+ ];
1614
+
1615
+ export function getComponentDefinition(
1616
+ type: string,
1617
+ ): ComponentDefinition | undefined {
1618
+ return COMPONENT_REGISTRY.find((c) => c.type === type);
1619
+ }
1620
+
1621
+ export const COMPONENT_CATEGORIES: ComponentCategory[] = [
1622
+ "Layout",
1623
+ "Content",
1624
+ "Media",
1625
+ "Interactive",
1626
+ ];