# CODE-002: Visual Node Generator ## Overview The Visual Node Generator transforms Noodl's visual component tree (Groups, Text, Images, Buttons, etc.) into clean React components with proper styling. This is the most straightforward part of code export since visual nodes map directly to HTML/React elements. **Estimated Effort:** 1-2 weeks **Priority:** HIGH **Dependencies:** CODE-001 (@nodegx/core) **Blocks:** CODE-006 (Project Scaffolding) --- ## Node → Element Mapping ### Container Nodes | Noodl Node | React Element | CSS Layout | Notes | |------------|---------------|------------|-------| | Group | `
` | Flexbox | Main container | | Page | `
` + Route | Flexbox | Route wrapper | | Columns | `
` | CSS Grid | Multi-column | | Circle | `
` | border-radius: 50% | Shape | | Rectangle | `
` | - | Shape | ### Content Nodes | Noodl Node | React Element | Notes | |------------|---------------|-------| | Text | `` / `

` | Based on multiline | | Image | `` | With loading states | | Video | `

{children}
); } ``` **Generated CSS:** ```css /* components/Card.module.css */ .root { display: flex; flex-direction: column; gap: 8px; padding: 12px 16px; background-color: #ffffff; border-radius: 8px; } ``` ### Text Node **Noodl Node:** ```json { "id": "text-1", "type": "Text", "parameters": { "text": "Hello World", "fontSize": 18, "fontWeight": 600, "color": "#333333" } } ``` **Generated Code:** ```tsx // Inline in parent component Hello World // Or with dynamic text binding {titleVar.get()} ``` **Generated CSS:** ```css .text1 { font-size: 18px; font-weight: 600; color: #333333; } ``` ### Button with States **Noodl Node:** ```json { "id": "button-1", "type": "Button", "parameters": { "label": "Click Me", "backgroundColor": "#3b82f6", "hoverBackgroundColor": "#2563eb", "pressedBackgroundColor": "#1d4ed8", "disabledBackgroundColor": "#94a3b8", "borderRadius": 6, "paddingLeft": 16, "paddingRight": 16, "paddingTop": 8, "paddingBottom": 8 }, "stateParameters": { "hover": { "backgroundColor": "#2563eb" }, "pressed": { "backgroundColor": "#1d4ed8" }, "disabled": { "backgroundColor": "#94a3b8", "opacity": 0.6 } } } ``` **Generated Component:** ```tsx // components/PrimaryButton.tsx import { ButtonHTMLAttributes } from 'react'; import styles from './PrimaryButton.module.css'; interface PrimaryButtonProps extends ButtonHTMLAttributes { label?: string; } export function PrimaryButton({ label = 'Click Me', children, className, ...props }: PrimaryButtonProps) { return ( ); } ``` **Generated CSS:** ```css /* components/PrimaryButton.module.css */ .root { display: flex; align-items: center; justify-content: center; padding: 8px 16px; background-color: #3b82f6; color: white; border: none; border-radius: 6px; cursor: pointer; transition: background-color 0.15s ease; } .root:hover:not(:disabled) { background-color: #2563eb; } .root:active:not(:disabled) { background-color: #1d4ed8; } .root:disabled { background-color: #94a3b8; opacity: 0.6; cursor: not-allowed; } ``` ### Repeater (For Each) **Noodl Node:** ```json { "id": "repeater-1", "type": "For Each", "parameters": { "items": "{{users}}" }, "templateComponent": "UserCard", "children": [] } ``` **Generated Code:** ```tsx import { useArray, RepeaterItemProvider } from '@nodegx/core'; import { usersArray } from '../stores/arrays'; import { UserCard } from './UserCard'; export function UserList() { const users = useArray(usersArray); return (
{users.map((user, index) => ( ))}
); } ``` ### Component Children **Noodl Node Structure:** ``` MyWrapper ├── Header ├── [Component Children] ← Slot for children └── Footer ``` **Generated Code:** ```tsx // components/MyWrapper.tsx import styles from './MyWrapper.module.css'; interface MyWrapperProps { children?: React.ReactNode; } export function MyWrapper({ children }: MyWrapperProps) { return (
{children}
); } ``` --- ## Visual States Handling ### State Transitions Noodl supports animated transitions between visual states. For basic hover/pressed states, we use CSS transitions. For complex state machines, we use the `@nodegx/core` state machine. **CSS Approach (simple states):** ```css .button { background-color: #3b82f6; transition: background-color 0.2s ease, transform 0.15s ease, opacity 0.2s ease; } .button:hover { background-color: #2563eb; transform: scale(1.02); } .button:active { background-color: #1d4ed8; transform: scale(0.98); } ``` **State Machine Approach (complex states):** ```tsx import { useStateMachine, useStateValues, createStateMachine } from '@nodegx/core'; // Define state machine with values for each state const cardState = createStateMachine({ states: ['idle', 'hover', 'expanded', 'loading'], initial: 'idle', values: { idle: { scale: 1, opacity: 1, height: 100 }, hover: { scale: 1.02, opacity: 1, height: 100 }, expanded: { scale: 1, opacity: 1, height: 300 }, loading: { scale: 1, opacity: 0.7, height: 100 } } }); export function Card() { const [state, goTo] = useStateMachine(cardState); const values = useStateValues(cardState); return (
state === 'idle' && goTo('hover')} onMouseLeave={() => state === 'hover' && goTo('idle')} onClick={() => goTo('expanded')} > {/* content */}
); } ``` --- ## Input Handling ### Controlled Components All form inputs are generated as controlled components: ```tsx import { useVariable } from '@nodegx/core'; import { searchQueryVar } from '../stores/variables'; export function SearchInput() { const [query, setQuery] = useVariable(searchQueryVar); return ( setQuery(e.target.value)} placeholder="Search..." /> ); } ``` ### Event Connections Noodl connections from visual node events become props or inline handlers: **Click → Signal:** ```tsx import { onButtonClick } from '../logic/handlers'; ``` **Click → Function:** ```tsx import { handleSubmit } from '../logic/formHandlers'; ``` **Click → Navigate:** ```tsx import { useNavigate } from 'react-router-dom'; export function NavButton() { const navigate = useNavigate(); return ( ); } ``` --- ## Image Handling ### Static Images ```tsx // Image in assets folder Logo ``` ### Dynamic Images ```tsx // From variable or object const user = useObject(currentUser); {user.name} ``` ### Loading States ```tsx import { useState } from 'react'; export function LazyImage({ src, alt, className }: LazyImageProps) { const [loaded, setLoaded] = useState(false); const [error, setError] = useState(false); return (
{!loaded && !error && (
)} {error && (
Failed to load
)} {alt} setLoaded(true)} onError={() => setError(true)} />
); } ``` --- ## Code Generation Algorithm ```typescript interface GenerateVisualNodeOptions { node: NoodlNode; componentName: string; outputDir: string; cssMode: 'modules' | 'tailwind' | 'inline'; } async function generateVisualComponent(options: GenerateVisualNodeOptions) { const { node, componentName, outputDir, cssMode } = options; // 1. Analyze node and children const analysis = analyzeVisualNode(node); // 2. Determine if this needs a separate component file const needsSeparateFile = analysis.hasChildren || analysis.hasStateLogic || analysis.hasEventHandlers || analysis.isReusable; // 3. Generate styles const styles = generateStyles(node, cssMode); // 4. Generate component code const componentCode = generateComponentCode({ node, componentName, styles, analysis }); // 5. Write files if (needsSeparateFile) { await writeFile(`${outputDir}/${componentName}.tsx`, componentCode); if (cssMode === 'modules') { await writeFile(`${outputDir}/${componentName}.module.css`, styles.css); } } return { componentName, inlineCode: needsSeparateFile ? null : componentCode, imports: analysis.imports }; } ``` --- ## Testing Checklist ### Visual Parity Tests - [ ] Group renders with correct flexbox layout - [ ] Text displays with correct typography - [ ] Image loads and displays correctly - [ ] Button states (hover, pressed, disabled) work - [ ] Repeater renders all items - [ ] Component Children slot works - [ ] Nested components render correctly ### Style Tests - [ ] All spacing values (margin, padding, gap) correct - [ ] Border radius renders correctly - [ ] Box shadows render correctly - [ ] Colors match exactly - [ ] Responsive units (%,vh,vw) work - [ ] CSS transitions animate smoothly ### Interaction Tests - [ ] Click handlers fire correctly - [ ] Form inputs are controlled - [ ] Mouse enter/leave events work - [ ] Focus states display correctly - [ ] Keyboard navigation works --- ## Success Criteria 1. **Visual Match** - Exported app looks identical to Noodl preview 2. **Clean Code** - Generated components are readable and maintainable 3. **Proper Typing** - Full TypeScript types for all props 4. **Accessibility** - Proper ARIA attributes, semantic HTML 5. **Performance** - No unnecessary re-renders, proper memoization