Skip to content

Commit 4063a03

Browse files
authored
Merge pull request #32 from olliethedev/docs/pages
docs: pages
2 parents 2bae1f1 + 2450a67 commit 4063a03

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

57 files changed

+4131
-111
lines changed

README.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -632,7 +632,6 @@ npm run test
632632

633633
## Roadmap
634634

635-
- [ ] Documentation site for UI Builder with more hands-on examples
636635
- [ ] Add variable binding to layer children and not just props
637636
- [ ] Update to React 19
638637
- [ ] Update to latest Shadcn/ui + Tailwind CSS v4
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export default function EditLayout({ children }: { children: React.ReactNode }) {
2+
return <div>{children}</div>;
3+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
2+
import { notFound } from "next/navigation";
3+
import { DocEditor } from "@/app/platform/doc-editor";
4+
import { getDocPageForSlug } from "../../../../docs/docs-data/data";
5+
6+
export default async function DocEditPage({
7+
params,
8+
}: {
9+
params: Promise<{ slug: string }>;
10+
}){
11+
const { slug } = await params;
12+
const page = getDocPageForSlug(slug);
13+
if (!page) {
14+
notFound();
15+
}
16+
17+
return <DocEditor page={page} />
18+
}

app/docs/[slug]/page.tsx

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import { Suspense } from "react";
2+
import { DocRenderer } from "@/app/platform/doc-renderer";
3+
import {
4+
getDocPageForSlug,
5+
} from "@/app/docs/docs-data/data";
6+
import { notFound } from "next/navigation";
7+
import { Skeleton } from "@/components/ui/skeleton";
8+
import { Metadata } from "next";
9+
10+
export async function generateMetadata(
11+
{ params }: {
12+
params: Promise<{slug: string}>,
13+
}
14+
): Promise<Metadata> {
15+
const slug = (await params).slug
16+
17+
const page = getDocPageForSlug(slug);
18+
19+
return {
20+
title: page?.name ? `${page.name} - UI Builder` : "Documentation - UI Builder",
21+
description: page?.props["data-group"] ? `Learn about ${page.props["data-group"]} features of the UI Builder component.` : "Documentation - UI Builder",
22+
}
23+
}
24+
25+
export default async function DocPage({
26+
params,
27+
}: {
28+
params: Promise<{ slug: string }>;
29+
}) {
30+
const { slug } = await params;
31+
const page = getDocPageForSlug(slug);
32+
if (!page) {
33+
notFound();
34+
}
35+
36+
return (
37+
<div className="flex-1 overflow-auto">
38+
<Suspense fallback={<DocSkeleton />}>
39+
<DocRenderer className="max-w-6xl mx-auto my-8" page={page} />
40+
</Suspense>
41+
</div>
42+
);
43+
}
44+
45+
function DocSkeleton() {
46+
return (
47+
<div className="flex-1 overflow-auto">
48+
<div className="max-w-6xl mx-auto my-8 flex flex-col gap-4">
49+
<Skeleton className="h-10 w-full" />
50+
<Skeleton className="h-96 w-full" />
51+
<Skeleton className="h-96 w-full" />
52+
</div>
53+
</div>
54+
);
55+
}

app/docs/docs-data/data.ts

Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
import { INTRODUCTION_LAYER } from "@/app/docs/docs-data/docs-page-layers/introduction";
2+
import { QUICK_START_LAYER } from "@/app/docs/docs-data/docs-page-layers/quick-start";
3+
import { COMPONENT_REGISTRY_LAYER } from "@/app/docs/docs-data/docs-page-layers/component-registry";
4+
import { FIELD_OVERRIDES_LAYER } from "@/app/docs/docs-data/docs-page-layers/field-overrides";
5+
import { CUSTOM_COMPONENTS_LAYER } from "@/app/docs/docs-data/docs-page-layers/custom-components";
6+
import { PANEL_CONFIGURATION_LAYER } from "@/app/docs/docs-data/docs-page-layers/panel-configuration";
7+
import { VARIABLES_LAYER } from "@/app/docs/docs-data/docs-page-layers/variables";
8+
import { VARIABLE_BINDING_LAYER } from "@/app/docs/docs-data/docs-page-layers/variable-binding";
9+
import { READ_ONLY_MODE_LAYER } from "@/app/docs/docs-data/docs-page-layers/read-only-mode";
10+
import { LAYER_STRUCTURE_LAYER } from "@/app/docs/docs-data/docs-page-layers/layer-structure";
11+
import { PERSISTENCE_LAYER } from "@/app/docs/docs-data/docs-page-layers/persistence";
12+
import { RENDERING_PAGES_LAYER } from "@/app/docs/docs-data/docs-page-layers/rendering-pages";
13+
14+
export const DOCS_PAGES = [
15+
// Core
16+
INTRODUCTION_LAYER,
17+
QUICK_START_LAYER,
18+
19+
// Component System
20+
COMPONENT_REGISTRY_LAYER,
21+
CUSTOM_COMPONENTS_LAYER,
22+
FIELD_OVERRIDES_LAYER,
23+
PANEL_CONFIGURATION_LAYER,
24+
25+
// Data & Variables
26+
VARIABLES_LAYER,
27+
VARIABLE_BINDING_LAYER,
28+
READ_ONLY_MODE_LAYER,
29+
30+
// Layout & Persistence
31+
LAYER_STRUCTURE_LAYER,
32+
PERSISTENCE_LAYER,
33+
34+
// Rendering
35+
RENDERING_PAGES_LAYER,
36+
37+
] as const;
38+
39+
type ExistingDocPageNames = `${Capitalize<(typeof DOCS_PAGES)[number]["name"]>}`;
40+
type ExistingDocPageIds = (typeof DOCS_PAGES)[number]["id"];
41+
type ExistingDocGroupNames = `${Capitalize<(typeof DOCS_PAGES)[0]["props"]["data-group"]>}`;
42+
43+
44+
type DocPageNavItem = {
45+
title: ExistingDocGroupNames | string;
46+
items: {
47+
title: ExistingDocPageNames ;
48+
url: `/docs/${ExistingDocPageIds}`;
49+
}[];
50+
}
51+
52+
export const MENU_DATA: DocPageNavItem[] = [
53+
{
54+
title: "Core",
55+
items: [
56+
{
57+
title: "Introduction",
58+
url: "/docs/introduction",
59+
},
60+
{
61+
title: "Quick Start",
62+
url: "/docs/quick-start",
63+
},
64+
],
65+
},
66+
{
67+
title: "Component System",
68+
items: [
69+
{
70+
title: "Components Intro",
71+
url: "/docs/component-registry",
72+
},
73+
{
74+
title: "Custom Components",
75+
url: "/docs/custom-components",
76+
},
77+
{
78+
title: "Advanced Configuration",
79+
url: "/docs/field-overrides",
80+
},
81+
{
82+
title: "Panel Configuration",
83+
url: "/docs/panel-configuration",
84+
}
85+
],
86+
},
87+
{
88+
title: "Data & Variables",
89+
items: [
90+
{
91+
title: "Variables",
92+
url: "/docs/variables",
93+
},
94+
{
95+
title: "Variable Binding",
96+
url: "/docs/variable-binding",
97+
},
98+
{
99+
title: "Editing Restrictions",
100+
url: "/docs/read-only-mode",
101+
},
102+
],
103+
},
104+
{
105+
title: "Layout & Persistence",
106+
items: [
107+
{
108+
title: "Layer Structure",
109+
url: "/docs/layer-structure",
110+
},
111+
{
112+
title: "State Management & Persistence",
113+
url: "/docs/persistence",
114+
},
115+
],
116+
},
117+
{
118+
title: "Rendering",
119+
items: [
120+
{
121+
title: "Rendering Pages",
122+
url: "/docs/rendering-pages",
123+
},
124+
],
125+
}
126+
] as const;
127+
128+
129+
130+
// Utility function to generate breadcrumbs from navigation data
131+
export function getBreadcrumbsFromUrl(url: string) {
132+
// Remove leading slash if present
133+
const cleanUrl = url.startsWith('/') ? url.substring(1) : url;
134+
135+
// Find the category and item that matches the URL
136+
for (const category of MENU_DATA) {
137+
for (const item of category.items) {
138+
// Remove leading slash from item URL for comparison
139+
const itemUrl = item.url.startsWith('/') ? item.url.substring(1) : item.url;
140+
141+
if (itemUrl === cleanUrl) {
142+
return {
143+
category: {
144+
title: category.title,
145+
// Create a category URL from the first item in the category
146+
url: category.items[0]?.url || '#'
147+
},
148+
page: {
149+
title: item.title,
150+
url: item.url
151+
}
152+
};
153+
}
154+
}
155+
}
156+
157+
// Fallback if URL not found
158+
return {
159+
category: {
160+
title: "Documentation",
161+
url: "#"
162+
},
163+
page: {
164+
title: "Home",
165+
url: url
166+
}
167+
};
168+
}
169+
170+
export function getDocPageForSlug(slug: string) {
171+
return DOCS_PAGES.find((page) => page.id === slug);
172+
}
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import { ComponentLayer } from "@/components/ui/ui-builder/types";
2+
3+
export const COMPONENT_REGISTRY_LAYER = {
4+
"id": "component-registry",
5+
"type": "div",
6+
"name": "Components Intro",
7+
"props": {
8+
"className": "h-full bg-background px-4 flex flex-col gap-6 min-h-screen",
9+
"data-group": "component-system"
10+
},
11+
"children": [
12+
{
13+
"type": "span",
14+
"children": "Components Introduction",
15+
"id": "component-registry-title",
16+
"name": "Text",
17+
"props": {
18+
"className": "text-4xl"
19+
}
20+
},
21+
{
22+
"id": "component-registry-intro",
23+
"type": "Markdown",
24+
"name": "Markdown",
25+
"props": {},
26+
"children": "The component registry is the heart of UI Builder. It defines which React components are available in the visual editor and how they should be configured. Understanding the registry is essential for using UI Builder effectively with your own components."
27+
},
28+
{
29+
"id": "component-registry-content",
30+
"type": "Markdown",
31+
"name": "Markdown",
32+
"props": {},
33+
"children": "## What is the Component Registry?\n\nThe component registry is a TypeScript object that maps component type names to their definitions. It tells UI Builder:\n\n- **How to render** the component in the editor\n- **What properties** it accepts and their types \n- **How to generate forms** for editing those properties\n- **Import paths** for code generation\n\n```tsx\nimport { ComponentRegistry } from '@/components/ui/ui-builder/types';\n\nconst myComponentRegistry: ComponentRegistry = {\n // Complex component with React component\n 'Button': {\n component: Button, // React component\n schema: z.object({...}), // Zod schema for props\n from: '@/components/ui/button' // Import path\n },\n // Primitive component (no React component needed)\n 'span': {\n schema: z.object({...}) // Just the schema\n }\n};\n```\n\n## Registry Structure\n\nEach registry entry can have these properties:\n\n### Required Properties\n- **`schema`**: Zod schema defining the component's props and their types\n- **`component`**: The React component (required for complex components)\n- **`from`**: Import path for code generation (required for complex components)\n\n### Optional Properties\n- **`isFromDefaultExport`**: Boolean, use default export in generated code\n- **`fieldOverrides`**: Object mapping prop names to custom form fields\n- **`defaultChildren`**: Array of ComponentLayer objects or string\n- **`defaultVariableBindings`**: Array of automatic variable bindings\n\n## Two Types of Components\n\n### Primitive Components\nHTML elements that don't need a React component:\n\n```tsx\nspan: {\n schema: z.object({\n className: z.string().optional(),\n children: z.any().optional(),\n })\n // No 'component' or 'from' needed\n}\n```\n\n### Complex Components\nCustom React components that need to be imported:\n\n```tsx\nButton: {\n component: Button,\n schema: z.object({\n className: z.string().optional(),\n children: z.any().optional(),\n variant: z.enum(['default', 'destructive']).default('default'),\n }),\n from: '@/components/ui/button'\n}\n```\n\n## Pre-built Component Definitions\n\nUI Builder includes example component definitions for testing and getting started:\n\n```tsx\nimport { primitiveComponentDefinitions } from '@/lib/ui-builder/registry/primitive-component-definitions';\nimport { complexComponentDefinitions } from '@/lib/ui-builder/registry/complex-component-definitions';\n\nconst componentRegistry: ComponentRegistry = {\n ...primitiveComponentDefinitions, // div, span, h1, h2, h3, p, ul, ol, li, img, iframe, a\n ...complexComponentDefinitions, // Button, Badge, Card, Icon, Flexbox, Grid, Markdown, etc.\n};\n```\n\n**Available Pre-built Components:**\n\n**Primitive Components:**\n- **Layout**: `div`, `span` \n- **Typography**: `h1`, `h2`, `h3`, `p`\n- **Lists**: `ul`, `ol`, `li`\n- **Media**: `img`, `iframe`\n- **Navigation**: `a` (links)\n\n**Complex Components:**\n- **Layout**: `Flexbox`, `Grid` \n- **Content**: `Markdown`, `CodePanel`\n- **UI Elements**: `Button`, `Badge`\n- **Advanced**: `Card`, `Icon`, `Accordion`\n\n## Simple Registry Example\n\nHere's a minimal registry with one custom component:\n\n```tsx\nimport { z } from 'zod';\nimport { Alert } from '@/components/ui/alert';\nimport { primitiveComponentDefinitions } from '@/lib/ui-builder/registry/primitive-component-definitions';\nimport { commonFieldOverrides } from '@/lib/ui-builder/registry/form-field-overrides';\n\nconst myComponentRegistry: ComponentRegistry = {\n // Include primitive components for basic HTML elements\n ...primitiveComponentDefinitions,\n \n // Add your custom component\n Alert: {\n component: Alert,\n schema: z.object({\n className: z.string().optional(),\n children: z.any().optional(),\n variant: z.enum(['default', 'destructive']).default('default'),\n }),\n from: '@/components/ui/alert',\n fieldOverrides: commonFieldOverrides()\n }\n};\n```\n\n## Component Dependencies\n\n**Important**: Make sure all component types referenced in your `defaultChildren` are included in your registry:\n\n```tsx\nconst componentRegistry: ComponentRegistry = {\n ...primitiveComponentDefinitions, // ← Includes 'span' needed below\n Button: {\n component: Button,\n schema: z.object({...}),\n from: '@/components/ui/button',\n // This Button references 'span' in defaultChildren\n defaultChildren: [{ \n id: 'btn-text',\n type: 'span', // ← Must be in registry\n name: 'Button Text',\n props: {},\n children: 'Click me'\n }]\n }\n};\n```\n\n## Schema Design Principles\n\nThe Zod schema is crucial as it drives the auto-generated form in the properties panel:\n\n```tsx\nschema: z.object({\n // Use .default() values for better UX\n title: z.string().default('Default Title'),\n \n // Use coerce for type conversion from strings\n count: z.coerce.number().default(1),\n \n // Boolean props become toggle switches\n disabled: z.boolean().optional(),\n \n // Enums become select dropdowns\n variant: z.enum(['default', 'destructive']).default('default'),\n \n // Special props need field overrides\n className: z.string().optional(),\n children: z.any().optional(),\n})\n```\n\n## Building Your Own Registry\n\n**For production applications**, you should create your own component registry with your specific components:\n\n```tsx\n// Your production registry\nconst productionRegistry: ComponentRegistry = {\n // Add only the components you need\n MyButton: { /* your button definition */ },\n MyCard: { /* your card definition */ },\n MyModal: { /* your modal definition */ },\n // Include primitives for basic HTML\n ...primitiveComponentDefinitions,\n};\n```\n\n**The pre-built registries are examples** to help you understand the system and test quickly, but you should replace them with your own component definitions that match your design system."
34+
},
35+
{
36+
"id": "component-registry-example",
37+
"type": "div",
38+
"name": "div",
39+
"props": {},
40+
"children": [
41+
{
42+
"id": "component-registry-badge",
43+
"type": "Badge",
44+
"name": "Badge",
45+
"props": {
46+
"variant": "default",
47+
"className": "rounded rounded-b-none"
48+
},
49+
"children": [
50+
{
51+
"id": "component-registry-badge-text",
52+
"type": "span",
53+
"name": "span",
54+
"props": {},
55+
"children": "Live Component Registry"
56+
}
57+
]
58+
},
59+
{
60+
"id": "component-registry-demo",
61+
"type": "div",
62+
"name": "div",
63+
"props": {
64+
"className": "border border-primary shadow-lg rounded-b-sm rounded-tr-sm overflow-hidden"
65+
},
66+
"children": [
67+
{
68+
"id": "component-registry-iframe",
69+
"type": "iframe",
70+
"name": "iframe",
71+
"props": {
72+
"src": "/examples/editor",
73+
"title": "UI Builder Component Registry Demo",
74+
"className": "w-full aspect-video"
75+
},
76+
"children": []
77+
}
78+
]
79+
}
80+
]
81+
},
82+
{
83+
"id": "next-steps-registry",
84+
"type": "Markdown",
85+
"name": "Markdown",
86+
"props": {},
87+
"children": "## Next Steps\n\nNow that you understand the component registry:\n\n- **Custom Components** - Learn how to add complex custom components with advanced features\n- **Advanced Component Config** - Explore field overrides, default children, and variable bindings\n- **Variables** - Create dynamic content with variable binding\n\nRemember: The registry is just a configuration object. The real power comes from how you design your components and their schemas to create the best editing experience for your users."
88+
}
89+
]
90+
} as const satisfies ComponentLayer;

0 commit comments

Comments
 (0)