From a7ce93963f61810c5443ddb4e79dc3bd0689899c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 25 Jul 2025 05:05:20 +0000 Subject: [PATCH 1/4] Initial plan From 81a64692e3e2b1b707b757f206fa35ee47345c66 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 25 Jul 2025 05:19:57 +0000 Subject: [PATCH 2/4] Add support for template label field Co-authored-by: jeremydw <646525+jeremydw@users.noreply.github.com> --- examples/blog/templates/Template5050/Template5050.schema.ts | 1 + examples/blog/templates/TemplateHero/TemplateHero.schema.ts | 1 + packages/root-cms/core/schema.ts | 1 + packages/root-cms/ui/components/DocEditor/DocEditor.tsx | 2 +- 4 files changed, 4 insertions(+), 1 deletion(-) diff --git a/examples/blog/templates/Template5050/Template5050.schema.ts b/examples/blog/templates/Template5050/Template5050.schema.ts index f6a89d6b..d91d0270 100644 --- a/examples/blog/templates/Template5050/Template5050.schema.ts +++ b/examples/blog/templates/Template5050/Template5050.schema.ts @@ -10,6 +10,7 @@ const assets = Object.values(assetModules).map( export default schema.define({ name: 'Template5050', + label: 'Two Column Layout', description: 'Basic 50x50.', fields: [ schema.string({ diff --git a/examples/blog/templates/TemplateHero/TemplateHero.schema.ts b/examples/blog/templates/TemplateHero/TemplateHero.schema.ts index 52ec210e..5d0d6562 100644 --- a/examples/blog/templates/TemplateHero/TemplateHero.schema.ts +++ b/examples/blog/templates/TemplateHero/TemplateHero.schema.ts @@ -2,6 +2,7 @@ import {schema} from '@blinkk/root-cms'; export default schema.define({ name: 'TemplateHero', + label: 'Hero Section', description: 'Basic hero.', fields: [ schema.string({ diff --git a/packages/root-cms/core/schema.ts b/packages/root-cms/core/schema.ts index bc6b5731..b22e72d7 100644 --- a/packages/root-cms/core/schema.ts +++ b/packages/root-cms/core/schema.ts @@ -248,6 +248,7 @@ export type ObjectLikeField = export interface Schema { name: string; + label?: string; description?: string; fields: FieldWithId[]; } diff --git a/packages/root-cms/ui/components/DocEditor/DocEditor.tsx b/packages/root-cms/ui/components/DocEditor/DocEditor.tsx index 66ef13cb..7ea8e113 100644 --- a/packages/root-cms/ui/components/DocEditor/DocEditor.tsx +++ b/packages/root-cms/ui/components/DocEditor/DocEditor.tsx @@ -1130,7 +1130,7 @@ DocEditor.OneOfField = (props: FieldProps) => { ]; field.types.forEach((type) => { typesMap[type.name] = type; - dropdownValues.push({value: type.name, label: type.name}); + dropdownValues.push({value: type.name, label: type.label || type.name}); }); const selectedType = typesMap[type || '']; From d19977fef9303bcff9c433154086f98a34e441dc Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 25 Jul 2025 05:24:30 +0000 Subject: [PATCH 3/4] Add tests for template label functionality Co-authored-by: jeremydw <646525+jeremydw@users.noreply.github.com> --- packages/root-cms/core/schema.test.ts | 33 +++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/packages/root-cms/core/schema.test.ts b/packages/root-cms/core/schema.test.ts index 1c5ce130..433e3398 100644 --- a/packages/root-cms/core/schema.test.ts +++ b/packages/root-cms/core/schema.test.ts @@ -416,3 +416,36 @@ test('define schema', () => { } `); }); + +test('define schema with label', () => { + const schemaWithLabel = schema.define({ + name: 'TemplateHomepageHero', + label: 'Home Hero', + description: 'Homepage hero section', + fields: [] + }); + + expect(schemaWithLabel).toEqual({ + name: 'TemplateHomepageHero', + label: 'Home Hero', + description: 'Homepage hero section', + fields: [] + }); +}); + +test('define schema without label', () => { + const schemaWithoutLabel = schema.define({ + name: 'TemplateBasic', + description: 'Basic template', + fields: [] + }); + + expect(schemaWithoutLabel).toEqual({ + name: 'TemplateBasic', + description: 'Basic template', + fields: [] + }); + + // Verify that label is undefined (backward compatibility) + expect(schemaWithoutLabel.label).toBeUndefined(); +}); From cf803a0b1da1a65e318bf6a55359e0b157f1031e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 25 Jul 2025 05:42:39 +0000 Subject: [PATCH 4/4] Implement metadata object and modal template selector Co-authored-by: jeremydw <646525+jeremydw@users.noreply.github.com> --- .../Template5050/Template5050.schema.ts | 6 +- .../TemplateHero/TemplateHero.schema.ts | 6 +- packages/root-cms/core/schema.test.ts | 28 +++-- packages/root-cms/core/schema.ts | 6 +- .../ui/components/DocEditor/DocEditor.tsx | 112 ++++++++++++++++-- 5 files changed, 133 insertions(+), 25 deletions(-) diff --git a/examples/blog/templates/Template5050/Template5050.schema.ts b/examples/blog/templates/Template5050/Template5050.schema.ts index d91d0270..006e8998 100644 --- a/examples/blog/templates/Template5050/Template5050.schema.ts +++ b/examples/blog/templates/Template5050/Template5050.schema.ts @@ -10,7 +10,11 @@ const assets = Object.values(assetModules).map( export default schema.define({ name: 'Template5050', - label: 'Two Column Layout', + metadata: { + title: 'Two Column Layout', + description: 'A balanced two-column layout with customizable content and assets', + image: 'https://images.unsplash.com/photo-1586953208448-b95a79798f07?w=300&h=200&fit=crop&auto=format&q=80' + }, description: 'Basic 50x50.', fields: [ schema.string({ diff --git a/examples/blog/templates/TemplateHero/TemplateHero.schema.ts b/examples/blog/templates/TemplateHero/TemplateHero.schema.ts index 5d0d6562..5682e203 100644 --- a/examples/blog/templates/TemplateHero/TemplateHero.schema.ts +++ b/examples/blog/templates/TemplateHero/TemplateHero.schema.ts @@ -2,7 +2,11 @@ import {schema} from '@blinkk/root-cms'; export default schema.define({ name: 'TemplateHero', - label: 'Hero Section', + metadata: { + title: 'Hero Section', + description: 'A prominent hero section with customizable content and image', + image: 'https://images.unsplash.com/photo-1618005182384-a83a8bd57fbe?w=300&h=200&fit=crop&auto=format&q=80' + }, description: 'Basic hero.', fields: [ schema.string({ diff --git a/packages/root-cms/core/schema.test.ts b/packages/root-cms/core/schema.test.ts index 433e3398..c501cea6 100644 --- a/packages/root-cms/core/schema.test.ts +++ b/packages/root-cms/core/schema.test.ts @@ -417,35 +417,43 @@ test('define schema', () => { `); }); -test('define schema with label', () => { - const schemaWithLabel = schema.define({ +test('define schema with metadata', () => { + const schemaWithMetadata = schema.define({ name: 'TemplateHomepageHero', - label: 'Home Hero', + metadata: { + title: 'Home Hero', + description: 'A prominent hero section for the homepage', + image: '/images/hero-thumbnail.jpg' + }, description: 'Homepage hero section', fields: [] }); - expect(schemaWithLabel).toEqual({ + expect(schemaWithMetadata).toEqual({ name: 'TemplateHomepageHero', - label: 'Home Hero', + metadata: { + title: 'Home Hero', + description: 'A prominent hero section for the homepage', + image: '/images/hero-thumbnail.jpg' + }, description: 'Homepage hero section', fields: [] }); }); -test('define schema without label', () => { - const schemaWithoutLabel = schema.define({ +test('define schema without metadata', () => { + const schemaWithoutMetadata = schema.define({ name: 'TemplateBasic', description: 'Basic template', fields: [] }); - expect(schemaWithoutLabel).toEqual({ + expect(schemaWithoutMetadata).toEqual({ name: 'TemplateBasic', description: 'Basic template', fields: [] }); - // Verify that label is undefined (backward compatibility) - expect(schemaWithoutLabel.label).toBeUndefined(); + // Verify that metadata is undefined (backward compatibility) + expect(schemaWithoutMetadata.metadata).toBeUndefined(); }); diff --git a/packages/root-cms/core/schema.ts b/packages/root-cms/core/schema.ts index b22e72d7..2f6f6567 100644 --- a/packages/root-cms/core/schema.ts +++ b/packages/root-cms/core/schema.ts @@ -248,7 +248,11 @@ export type ObjectLikeField = export interface Schema { name: string; - label?: string; + metadata?: { + title?: string; + description?: string; + image?: string; + }; description?: string; fields: FieldWithId[]; } diff --git a/packages/root-cms/ui/components/DocEditor/DocEditor.tsx b/packages/root-cms/ui/components/DocEditor/DocEditor.tsx index 7ea8e113..470351e4 100644 --- a/packages/root-cms/ui/components/DocEditor/DocEditor.tsx +++ b/packages/root-cms/ui/components/DocEditor/DocEditor.tsx @@ -5,6 +5,7 @@ import { Button, LoadingOverlay, Menu, + Modal, Select, Tooltip, } from '@mantine/core'; @@ -17,6 +18,7 @@ import { IconClipboardCopy, IconCopy, IconDotsVertical, + IconGridDots, IconLanguage, IconLock, IconPlanet, @@ -1124,13 +1126,17 @@ DocEditor.ArrayField = (props: FieldProps) => { DocEditor.OneOfField = (props: FieldProps) => { const field = props.field as schema.OneOfField; const [type, setType] = useState(''); + const [modalOpen, setModalOpen] = useState(false); const typesMap: Record = {}; const dropdownValues: Array<{value: string; label: string}> = [ {value: '', label: field.placeholder || 'Select type'}, ]; field.types.forEach((type) => { typesMap[type.name] = type; - dropdownValues.push({value: type.name, label: type.label || type.name}); + dropdownValues.push({ + value: type.name, + label: type.metadata?.title || type.name + }); }); const selectedType = typesMap[type || '']; @@ -1174,18 +1180,100 @@ DocEditor.OneOfField = (props: FieldProps) => {
Type:
- onTypeChange(e || '')} + size="xs" + radius={0} + searchable + // Due to issues with preact/compat, use a div for the dropdown el. + dropdownComponent="div" + /> + + setModalOpen(true)} + > + + + +
+ + setModalOpen(false)} + title="Select Template" + size="lg" + > +
+ {field.types.map((schemaType) => ( +
{ + onTypeChange(schemaType.name); + setModalOpen(false); + }} + > +
+ {schemaType.metadata?.image && ( + {schemaType.metadata.title + )} +
+
+ {schemaType.metadata?.title || schemaType.name} +
+ {schemaType.metadata?.description && ( +
+ {schemaType.metadata.description} +
+ )} + {!schemaType.metadata?.description && schemaType.description && ( +
+ {schemaType.description} +
+ )} +
+
+
+ ))} +
+
+ {selectedType && (
{selectedType.fields.map((field) => (