Editor
Editor Package Overview
Puck-based visual page builder for BISO Sites.
Editor Package Overview
The @repo/editor package provides a visual page builder for BISO Sites, powered by Puck. It enables non-technical users to create and edit pages visually in the admin app, which are then rendered in the web app.
When to use this package
- Build or customize drag-and-drop page experiences for editors.
- Render dynamic page content on the public web app.
- Create new reusable blocks that both admin and web apps understand.
Installation
bun add @repo/editorWhere it's used
| Surface | Usage |
|---|---|
| Admin App â Page Builder | Editors compose content with <Editor /> |
| Web App dynamic pages | <Renderer /> turns JSON into live UI |
| Shared packages | Component definitions live alongside shared UI primitives |
đ
Work in Progress
The editor package is actively being developed. More components and features are coming soon!
What's Included
- đ¨ Visual Editor - Drag-and-drop page building
- đĻ Pre-built Components - Button, Heading, Section, Text
- đ§ Extensible - Easy to add custom components
- đž JSON-based Storage - Pages stored as JSON in Appwrite
- đ¯ Type-safe - Full TypeScript support
- đ Fast Rendering - Optimized for performance
Package Structure
packages/editor/
âââ components/ # Available editor components
â âââ button.tsx
â âââ heading.tsx
â âââ section.tsx
â âââ text.tsx
âââ editor.tsx # Editor component (for admin)
âââ renderer.tsx # Renderer component (for web)
âââ page-builder-config.tsx # Puck configuration
âââ types.ts # TypeScript types
âââ package.jsonQuick Start
In Admin App (Editing)
// app/admin/pages/[id]/edit/page.tsx
import { Editor } from '@repo/editor';
export default function PageEditor({ initialData }) {
const handlePublish = async (data) => {
// Save to Appwrite
await updatePageDocument(pageId, {
puck_document: JSON.stringify(data)
});
};
return (
<Editor
data={initialData}
onPublish={handlePublish}
/>
);
}In Web App (Rendering)
// app/[locale]/[slug]/page.tsx
import { Renderer } from '@repo/editor';
export default async function DynamicPage({ params }) {
// Fetch page data from Appwrite
const page = await getPageBySlug(params.slug);
const puckData = JSON.parse(page.puck_document);
return <Renderer data={puckData} />;
}Available Components
Button
Call-to-action button with link.
Props:
text- Button texthref- Link URLvariant- Style variant (default, primary, secondary)size- Button size (sm, md, lg)
Example:
{
"type": "Button",
"props": {
"text": "Learn More",
"href": "/about",
"variant": "primary",
"size": "lg"
}
}Heading
Headings from H1 to H6.
Props:
text- Heading textlevel- Heading level (1-6)align- Text alignment (left, center, right)
Example:
{
"type": "Heading",
"props": {
"text": "Welcome to BISO Sites",
"level": 1,
"align": "center"
}
}Section
Container with customizable background and spacing.
Props:
children- Nested componentsbackground- Background color/stylepadding- Section paddingmaxWidth- Maximum content width
Example:
{
"type": "Section",
"props": {
"background": "gray",
"padding": "large",
"maxWidth": "1200px"
}
}Text
Rich text content.
Props:
text- Text content (supports markdown)size- Text size (sm, md, lg)align- Text alignment
Example:
{
"type": "Text",
"props": {
"text": "This is a paragraph with **bold** and *italic* text.",
"size": "md",
"align": "left"
}
}Data Structure
Pages are stored as JSON with this structure:
interface PuckData {
content: Array<{
type: string;
props: Record<string, any>;
children?: PuckData['content'];
}>;
root: {
props: Record<string, any>;
};
}Example:
{
"content": [
{
"type": "Section",
"props": { "background": "white", "padding": "large" },
"children": [
{
"type": "Heading",
"props": { "text": "Welcome", "level": 1, "align": "center" }
},
{
"type": "Text",
"props": { "text": "This is the homepage.", "size": "md" }
},
{
"type": "Button",
"props": { "text": "Get Started", "href": "/signup", "variant": "primary" }
}
]
}
],
"root": {
"props": {}
}
}Configuration
The Puck configuration defines available components:
// packages/editor/page-builder-config.tsx
import { Config } from '@measured/puck';
import { Button } from './components/button';
import { Heading } from './components/heading';
// ... other components
export const puckConfig: Config = {
components: {
Button,
Heading,
Section,
Text,
},
};Creating Custom Components
Add new components to the editor:
// packages/editor/components/hero.tsx
import { ComponentConfig } from '@measured/puck';
export interface HeroProps {
title: string;
subtitle: string;
imageUrl: string;
ctaText?: string;
ctaHref?: string;
}
export const Hero: ComponentConfig<HeroProps> = {
fields: {
title: { type: 'text', label: 'Title' },
subtitle: { type: 'textarea', label: 'Subtitle' },
imageUrl: { type: 'text', label: 'Image URL' },
ctaText: { type: 'text', label: 'CTA Text' },
ctaHref: { type: 'text', label: 'CTA Link' },
},
render: ({ title, subtitle, imageUrl, ctaText, ctaHref }) => {
return (
<div className="hero">
<img src={imageUrl} alt={title} />
<h1>{title}</h1>
<p>{subtitle}</p>
{ctaText && ctaHref && (
<a href={ctaHref}>{ctaText}</a>
)}
</div>
);
},
};Then add to config:
import { Hero } from './components/hero';
export const puckConfig: Config = {
components: {
Button,
Heading,
Hero, // New component
Section,
Text,
},
};Editor Features
- Drag & Drop - Rearrange components visually
- Live Preview - See changes in real-time
- Component Library - Browse available components
- Props Editor - Edit component properties
- Nested Components - Components within sections
- Undo/Redo - Full edit history
- Responsive Preview - Mobile/tablet/desktop views
Workflow
Create Page in Admin
// Admin: Create new page
const newPage = await db.createDocument(
'database_id',
'pages',
ID.unique(),
{
slug: 'about-us',
title: 'About Us',
status: 'draft',
puck_document: JSON.stringify({
content: [],
root: { props: {} }
})
}
);Edit with Puck
// Admin: Edit page
<Editor
data={JSON.parse(page.puck_document)}
onPublish={async (data) => {
await db.updateDocument('pages', pageId, {
puck_document: JSON.stringify(data),
status: 'published'
});
}}
/>Render in Web App
// Web: Display page
const page = await db.getDocument('pages', pageId);
const puckData = JSON.parse(page.puck_document);
return <Renderer data={puckData} />;Future Enhancements
Planned features for the editor:
- â More Components - Hero, Gallery, Testimonials, Forms
- â @repo/ui Integration - All UI components available in editor
- â Asset Manager - Upload and manage images
- â Templates - Pre-built page templates
- â Component Presets - Save common configurations
- â Multi-language Support - Edit content in multiple languages
- â Version History - Track page changes over time
- â Collaboration - Multiple editors working simultaneously
Best Practices
- Keep components simple - Each component should do one thing well
- Use semantic names -
HeronotBigImageWithText - Provide defaults - Make props optional with sensible defaults
- Test rendering - Ensure components work in both apps
- Document props - Add descriptions to field definitions
- Handle edge cases - Empty content, missing images, etc.
Troubleshooting
Component Not Showing in Editor
- Check it's exported in
page-builder-config.tsx - Verify the component follows Puck's
ComponentConfiginterface - Restart the admin app dev server
Rendering Issues in Web App
- Ensure Renderer has access to all component definitions
- Check for console errors in browser
- Verify JSON data structure is valid
Styling Differences
- Remember editor uses admin app styles, renderer uses web app styles
- Use consistent Tailwind classes
- Test in both environments
Next Steps
âšī¸
- UI Package - Available UI components
- Admin App Guide - Using the admin interface
- Content Management - Managing content
