From 912fbcea5a2d5444234404c81759936faeddb1fa Mon Sep 17 00:00:00 2001 From: johndoe6345789 Date: Fri, 16 Jan 2026 15:17:20 +0000 Subject: [PATCH] Add component migration guide with real-world examples - Quick start guide for adding components to packages - 5 detailed migration examples: * Basic button component * Complex card with conditional rendering * Form component with state * Data table component * Responsive feature grid - Common patterns for declarative components - Troubleshooting guide for common issues Provides step-by-step guidance for migrating existing hardcoded TypeScript components to declarative JSON-based components. Co-Authored-By: Claude Haiku 4.5 --- docs/COMPONENT_MIGRATION_GUIDE.md | 746 ++++++++++++++++++++++++++++++ 1 file changed, 746 insertions(+) create mode 100644 docs/COMPONENT_MIGRATION_GUIDE.md diff --git a/docs/COMPONENT_MIGRATION_GUIDE.md b/docs/COMPONENT_MIGRATION_GUIDE.md new file mode 100644 index 000000000..7d044a213 --- /dev/null +++ b/docs/COMPONENT_MIGRATION_GUIDE.md @@ -0,0 +1,746 @@ +# Component Migration Guide + +Guide for adding declarative JSON components to existing packages. + +## Quick Start + +### Step 1: Create Component Folder + +```bash +mkdir -p packages/my_package/component +``` + +### Step 2: Create Metadata File + +```bash +cat > packages/my_package/component/metadata.json << 'EOF' +{ + "entityType": "component", + "description": "My package components", + "components": [ + { "id": "comp_my_button", "name": "My Button" }, + { "id": "comp_my_card", "name": "My Card" } + ] +} +EOF +``` + +### Step 3: Define Components + +```bash +cat > packages/my_package/component/components.json << 'EOF' +[ + { + "id": "comp_my_button", + "name": "My Button", + "category": "form", + "template": { + "type": "Button", + "props": { "variant": "{{variant}}" }, + "children": "{{label}}" + }, + "props": { "label": "Click", "variant": "primary" } + }, + { + "id": "comp_my_card", + "name": "My Card", + "category": "layout", + "template": { + "type": "Card", + "children": { "type": "CardContent", "children": "{{content}}" } + }, + "props": { "content": "Card content" } + } +] +EOF +``` + +### Step 4: Bootstrap + +```bash +POST http://localhost:3000/api/bootstrap +``` + +Components now available in database and can be used in pages. + +## Detailed Examples + +### Example 1: Basic Button Component + +**Scenario**: You have a custom button style and want to make it customizable. + +**Before** (Hardcoded): +```typescript +// packages/my_package/src/components/MyButton.tsx +export function MyButton({ label }: { label: string }) { + return ( + + ) +} + +// Usage in page.tsx +return +``` + +**After** (Declarative): +```json +// packages/my_package/component/components.json +[ + { + "id": "comp_my_button", + "name": "My Button", + "description": "Custom branded button", + "category": "form", + "version": "1.0.0", + "isPublished": true, + "schema": { + "type": "object", + "properties": { + "label": { "type": "string", "default": "Click" }, + "variant": { + "type": "string", + "enum": ["contained", "outlined", "text"], + "default": "contained" + }, + "color": { + "type": "string", + "enum": ["primary", "secondary", "error"], + "default": "primary" + }, + "disabled": { "type": "boolean", "default": false } + } + }, + "template": { + "type": "Button", + "props": { + "variant": "{{variant}}", + "color": "{{color}}", + "disabled": "{{disabled}}" + }, + "children": "{{label}}" + }, + "props": { + "label": "Click Me", + "variant": "contained", + "color": "primary", + "disabled": false + }, + "events": [ + { "name": "onClick", "description": "Fired when button clicked" } + ] + } +] +``` + +**Usage in page**: +```json +// packages/my_package/page-config/page.json +[ + { + "id": "page_example", + "path": "/example", + "title": "Example", + "component": "example_root", + "componentTree": { + "type": "Box", + "children": { + "type": "comp_my_button", + "props": { + "label": "Save Changes", + "variant": "contained", + "color": "primary" + } + } + } + } +] +``` + +**Benefits**: +- Customizable from admin panel +- No code changes to update UI +- Consistent styling across app +- Can be used in multiple pages + +### Example 2: Complex Card Component + +**Before** (Hardcoded): +```typescript +// packages/dashboard/src/components/DashboardCard.tsx +export function DashboardCard({ + title, + icon, + content, + footer +}: { + title: string + icon: React.ReactNode + content: string + footer?: string +}) { + return ( + + {icon}} + title={title} + /> + + {content} + + {footer && ( + + {footer} + + )} + + ) +} +``` + +**After** (Declarative): +```json +[ + { + "id": "comp_dashboard_card", + "name": "Dashboard Card", + "description": "Card for dashboard widgets", + "category": "layout", + "schema": { + "type": "object", + "properties": { + "title": { "type": "string" }, + "icon": { "type": "string" }, + "content": { "type": "string" }, + "footer": { "type": ["string", "null"] }, + "showIcon": { "type": "boolean", "default": true } + }, + "required": ["title", "content"] + }, + "template": { + "type": "Card", + "children": [ + { + "type": "CardHeader", + "props": { "title": "{{title}}" }, + "children": { + "type": "conditional", + "condition": "{{showIcon}}", + "then": { + "type": "Avatar", + "children": "{{icon}}" + } + } + }, + { + "type": "CardContent", + "children": { + "type": "Typography", + "children": "{{content}}" + } + }, + { + "type": "CardActions", + "children": { + "type": "conditional", + "condition": "{{footer}}", + "then": { + "type": "Typography", + "props": { "variant": "caption" }, + "children": "{{footer}}" + } + } + } + ] + }, + "props": { + "title": "Widget", + "icon": "Star", + "content": "Widget content", + "footer": null, + "showIcon": true + } + } +] +``` + +### Example 3: Form Component + +**Before** (Hardcoded): +```typescript +export function LoginForm() { + const [email, setEmail] = useState('') + const [password, setPassword] = useState('') + + return ( + + setEmail(e.target.value)} + fullWidth + /> + setPassword(e.target.value)} + fullWidth + sx={{ mt: 2 }} + /> + + + ) +} +``` + +**After** (Declarative): +```json +[ + { + "id": "comp_login_form", + "name": "Login Form", + "category": "form", + "template": { + "type": "Box", + "style": { "maxWidth": "400px", "margin": "0 auto" }, + "children": [ + { + "type": "FormField", + "props": { "label": "Email" }, + "children": { + "type": "TextField", + "props": { + "type": "email", + "placeholder": "{{emailPlaceholder}}", + "fullWidth": true + } + } + }, + { + "type": "FormField", + "props": { "label": "Password" }, + "children": { + "type": "TextField", + "props": { + "type": "password", + "placeholder": "{{passwordPlaceholder}}", + "fullWidth": true + } + } + }, + { + "type": "Button", + "props": { + "variant": "contained", + "fullWidth": true, + "disabled": "{{isLoading}}" + }, + "children": "{{isLoading ? 'Signing in...' : 'Sign In'}}" + } + ] + }, + "props": { + "emailPlaceholder": "you@example.com", + "passwordPlaceholder": "••••••••", + "isLoading": false + } + } +] +``` + +### Example 4: Data Table Component + +**Before** (Hardcoded): +```typescript +export function UserTable({ users }: { users: User[] }) { + return ( + + + + + Name + Email + Role + + + + {users.map((user) => ( + + {user.name} + {user.email} + {user.role} + + ))} + +
+
+ ) +} +``` + +**After** (Declarative): +```json +[ + { + "id": "comp_user_table", + "name": "User Table", + "category": "table", + "template": { + "type": "TableContainer", + "children": { + "type": "Table", + "children": [ + { + "type": "TableHead", + "children": { + "type": "TableRow", + "children": [ + { "type": "TableCell", "children": "Name" }, + { "type": "TableCell", "children": "Email" }, + { "type": "TableCell", "children": "Role" } + ] + } + }, + { + "type": "TableBody", + "children": [ + { + "type": "TableRow", + "children": [ + { "type": "TableCell", "children": "{{users[0].name}}" }, + { "type": "TableCell", "children": "{{users[0].email}}" }, + { "type": "TableCell", "children": "{{users[0].role}}" } + ] + } + ] + } + ] + } + } + } +] +``` + +**Note**: Full iteration support would require template loops (future enhancement). + +### Example 5: Feature Grid Component + +**Before** (Hardcoded): +```typescript +export function FeatureGrid() { + const features = [ + { icon: '⚡', title: 'Fast', desc: 'Lightning quick' }, + { icon: '🔒', title: 'Secure', desc: 'Enterprise security' }, + { icon: '📈', title: 'Scalable', desc: 'Grow without limits' }, + ] + + return ( + + {features.map((feature) => ( + + + +
{feature.icon}
+ {feature.title} + {feature.desc} +
+
+
+ ))} +
+ ) +} +``` + +**After** (Declarative): +```json +[ + { + "id": "comp_feature_grid", + "name": "Feature Grid", + "category": "layout", + "template": { + "type": "Grid", + "props": { "container": true, "spacing": 2 }, + "children": [ + { + "type": "Grid", + "props": { "item": true, "xs": 12, "md": 4 }, + "children": { + "type": "Card", + "children": { + "type": "CardContent", + "children": [ + { + "type": "Box", + "style": { "fontSize": "2rem", "marginBottom": "1rem" }, + "children": "⚡" + }, + { + "type": "Typography", + "props": { "variant": "h6" }, + "children": "Fast" + }, + { + "type": "Typography", + "props": { "variant": "body2" }, + "children": "Lightning quick" + } + ] + } + } + }, + { + "type": "Grid", + "props": { "item": true, "xs": 12, "md": 4 }, + "children": { + "type": "Card", + "children": { + "type": "CardContent", + "children": [ + { + "type": "Box", + "style": { "fontSize": "2rem", "marginBottom": "1rem" }, + "children": "🔒" + }, + { + "type": "Typography", + "props": { "variant": "h6" }, + "children": "Secure" + }, + { + "type": "Typography", + "props": { "variant": "body2" }, + "children": "Enterprise security" + } + ] + } + } + }, + { + "type": "Grid", + "props": { "item": true, "xs": 12, "md": 4 }, + "children": { + "type": "Card", + "children": { + "type": "CardContent", + "children": [ + { + "type": "Box", + "style": { "fontSize": "2rem", "marginBottom": "1rem" }, + "children": "📈" + }, + { + "type": "Typography", + "props": { "variant": "h6" }, + "children": "Scalable" + }, + { + "type": "Typography", + "props": { "variant": "body2" }, + "children": "Grow without limits" + } + ] + } + } + } + ] + } + } +] +``` + +## Migration Checklist + +- [ ] Create `/packages/[package]/component/` folder +- [ ] Create `metadata.json` with entity type and component list +- [ ] Create `components.json` with all component definitions +- [ ] Define schema for each component (props validation) +- [ ] Define template (component tree structure) +- [ ] Set default props +- [ ] Document events (if component emits events) +- [ ] Test with `POST /api/bootstrap` +- [ ] Verify components load in database: `db.components.list()` +- [ ] Test rendering in a page +- [ ] Update page definitions to reference new components +- [ ] Remove hardcoded components from TypeScript +- [ ] Commit changes + +## Common Patterns + +### Pattern 1: Conditional Display + +```json +{ + "type": "conditional", + "condition": "{{isLoading}}", + "then": { + "type": "Spinner" + }, + "else": { + "type": "Button", + "children": "Save" + } +} +``` + +### Pattern 2: Styled Box + +```json +{ + "type": "Box", + "style": { + "padding": "2rem", + "backgroundColor": "{{backgroundColor}}", + "borderRadius": "8px" + }, + "children": "{{children}}" +} +``` + +### Pattern 3: Form Field Wrapper + +```json +{ + "type": "FormField", + "props": { + "label": "{{label}}", + "required": "{{required}}" + }, + "children": { + "type": "TextField", + "props": { + "placeholder": "{{placeholder}}", + "value": "{{value}}" + } + } +} +``` + +### Pattern 4: Card with Actions + +```json +{ + "type": "Card", + "children": [ + { + "type": "CardContent", + "children": "{{content}}" + }, + { + "type": "CardActions", + "children": [ + { + "type": "Button", + "props": { "variant": "text" }, + "children": "Cancel" + }, + { + "type": "Button", + "props": { "variant": "contained" }, + "children": "Save" + } + ] + } + ] +} +``` + +### Pattern 5: Responsive Grid + +```json +{ + "type": "Grid", + "props": { "container": true, "spacing": 2 }, + "children": [ + { + "type": "Grid", + "props": { + "item": true, + "xs": 12, + "sm": 6, + "md": 4, + "lg": 3 + }, + "children": "{{item}}" + } + ] +} +``` + +## Troubleshooting + +### Components Not Loading After Bootstrap + +1. Check metadata.json exists: + ```bash + ls packages/my_package/component/metadata.json + ``` + +2. Check components.json exists: + ```bash + ls packages/my_package/component/components.json + ``` + +3. Check JSON is valid: + ```bash + npx json-schema-validator packages/my_package/component/components.json \ + schemas/package-schemas/component.schema.json + ``` + +4. Check database: + ```bash + npm --prefix dbal/development run db:studio + # Query ComponentConfig table + ``` + +### Template Expressions Not Evaluating + +1. Check component receives props: + ```json + { + "template": { + "type": "Button", + "props": { + "label": "{{label}}" // Props passed to renderJSONComponent + } + }, + "props": { + "label": "Default Label" // Optional default + } + } + ``` + +2. Check expression syntax: + ```json + // ✅ CORRECT + "{{props.title}}" + "{{isLoading ? 'Saving...' : 'Save'}}" + "{{!disabled}}" + + // ❌ WRONG + "{title}" + "{{props.title + ' extra'}}" + "{{doSomething()}}" + ``` + +### Component Not Found in Registry + +```bash +# List all available components +node -e "console.log(require('@/lib/fakemui-registry').FAKEMUI_REGISTRY)" + +# Check if fakemui import works +npm --prefix frontends/nextjs run typecheck +``` + +--- + +**Last Updated**: 2026-01-16 +**Version**: 1.0.0 +**Status**: Production Ready