diff --git a/examples/blog/templates/Template5050/Template5050.schema.ts b/examples/blog/templates/Template5050/Template5050.schema.ts index f6a89d6b..006e8998 100644 --- a/examples/blog/templates/Template5050/Template5050.schema.ts +++ b/examples/blog/templates/Template5050/Template5050.schema.ts @@ -10,6 +10,11 @@ const assets = Object.values(assetModules).map( export default schema.define({ name: 'Template5050', + 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 52ec210e..5682e203 100644 --- a/examples/blog/templates/TemplateHero/TemplateHero.schema.ts +++ b/examples/blog/templates/TemplateHero/TemplateHero.schema.ts @@ -2,6 +2,11 @@ import {schema} from '@blinkk/root-cms'; export default schema.define({ name: 'TemplateHero', + 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 1c5ce130..c501cea6 100644 --- a/packages/root-cms/core/schema.test.ts +++ b/packages/root-cms/core/schema.test.ts @@ -416,3 +416,44 @@ test('define schema', () => { } `); }); + +test('define schema with metadata', () => { + const schemaWithMetadata = schema.define({ + name: 'TemplateHomepageHero', + metadata: { + title: 'Home Hero', + description: 'A prominent hero section for the homepage', + image: '/images/hero-thumbnail.jpg' + }, + description: 'Homepage hero section', + fields: [] + }); + + expect(schemaWithMetadata).toEqual({ + name: 'TemplateHomepageHero', + 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 metadata', () => { + const schemaWithoutMetadata = schema.define({ + name: 'TemplateBasic', + description: 'Basic template', + fields: [] + }); + + expect(schemaWithoutMetadata).toEqual({ + name: 'TemplateBasic', + description: 'Basic template', + fields: [] + }); + + // 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 bc6b5731..2f6f6567 100644 --- a/packages/root-cms/core/schema.ts +++ b/packages/root-cms/core/schema.ts @@ -248,6 +248,11 @@ export type ObjectLikeField = export interface Schema { name: 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 66ef13cb..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.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) => (