Skip to main content

Technical Implementation & Developer Guide

Comprehensive technical documentation for developers working with Page Designer, including architecture, renderer patterns, component structure, and extension guidelines.

Architecture Overview

File Structure

page-designer/
├── core/
│ ├── PageDesignerCore.tsx # Main orchestrator component
│ ├── PageDesignerStudio.tsx # Studio wrapper
│ └── JsonSchemaGenerator.ts # Schema validation
├── editors/
│ ├── FieldEditor.tsx # Two-pane field editor
│ ├── FieldPreview.tsx # Left pane preview
│ ├── field-editor.css # Editor styling
│ ├── AdvancedStylingEditor.tsx # Advanced CSS editor
│ ├── AdvancedStylingEditor.tsx # Advanced CSS editor
│ └── [15+ field-specific editors] # Specialized editors
├── renderers/
│ ├── FormRenderer.tsx # Form rendering engine
│ ├── DashboardRenderer.tsx # Dashboard widget rendering
│ ├── ReportRenderer.tsx # Report document rendering
│ └── PageRenderer.tsx # Content page rendering
├── sidebar/
│ ├── FieldPalette.tsx # Drag-drop field palette
│ ├── PageTreeView.tsx # Page structure tree
│ └── CompactSidebar.tsx # Collapsed sidebar
├── dialogs/
│ ├── SettingsDialog.tsx # Field settings dialog
│ ├── PreviewDialog.tsx # Live preview
│ ├── VersionHistoryDialog.tsx # Version management
│ └── [5+ other dialogs] # Configuration dialogs
├── canvas/
│ ├── PageCanvas.tsx # Design canvas
│ └── useDragAndDrop.ts # Drag-drop logic
├── managers/
│ ├── DataSourceManager.tsx # Data source config
│ ├── VariablesManager.tsx # Form variables
│ ├── ActionsManager.tsx # Form actions
│ └── ChangeLogManager.ts # Change tracking
├── types.ts # TypeScript definitions
├── utils/
│ ├── field-templates.ts # Field presets
│ ├── schema-validator.ts # Schema validation
│ └── [helper utilities]
└── hooks/
├── useUndoRedo.ts # Undo/redo state
└── useDragAndDrop.ts # Drag-drop hook

Core Components

PageDesignerCore

Main orchestrator managing state, interactions, and sub-components.

Responsibilities:

  • Form state management (fields, data sources, variables, actions)
  • Field selection and editing
  • Undo/redo history
  • Mode switching (design/preview)
  • Device preview
  • Save/publish coordination

Key Props:

interface PageDesignerProps {
open: boolean;
onClose?: () => void;
pageId: string;
entityCode: string;
initialSchema: FormSchema;
initialDataSources?: ManagedDataSource[];
initialVariables?: FormVariable[];
initialActions?: FormAction[];
onSave?: (schema: FormSchema) => Promise<void>;
onChange?: (schema: FormSchema) => void;
viewMode?: "design" | "preview";
deviceType?: "mobile" | "tablet" | "laptop" | "desktop";
}

FieldEditor

Two-pane field property editor with live preview.

Layout:

  • Left Pane (380px): Live field preview
  • Right Pane (flex): Property tabs

Tabs:

  1. Basic - Label, placeholder, options, data source
  2. Display - Visibility, styling, decorations
  3. Data - Default value, persistence, binding
  4. Validation - Rules, constraints, error messages
  5. Conditional - Show/hide logic
  6. Calculated - Formula-based computation
  7. Layout - Container-specific properties
  8. Workflows - Trigger actions
  9. Help - User documentation
  10. Permissions - Access control
  11. Advanced - CSS, custom code

Renderers

Three specialized renderers handle different page types:

FormRenderer - Interactive form display

export const FormRenderer: React.FC<FormRendererProps> = ({
schema: FormSchema;
mode?: 'design' | 'view' | 'edit';
onSubmit?: (data: any) => Promise<void>;
onFieldChange?: (fieldId: string, value: any) => void;
readOnly?: boolean;
})

DashboardRenderer - Widget composition

export const DashboardRenderer: React.FC<DashboardRendererProps> = ({
schema: FormSchema;
dataSource?: any;
refreshInterval?: number;
onWidgetInteraction?: (widgetId: string, action: string) => void;
})

ReportRenderer - Document generation

export const ReportRenderer: React.FC<ReportRendererProps> = ({
schema: FormSchema;
data: any;
format?: 'pdf' | 'html' | 'excel';
onExport?: (format: string) => void;
})

Field System

Field Interface

export interface FormField {
// Identity
id: string;
name?: string;
type: FieldType;

// Display
label: string;
placeholder?: string;
description?: string;
tooltip?: string;
prefix?: string;
suffix?: string;

// State
defaultValue?: any;
required?: boolean;
disabled?: boolean;
hidden?: boolean;
readOnly?: boolean;

// Validation
validation?: ValidationConfig;
validationRules?: ValidationRule[];

// Logic
conditional?: ConditionalLogic;
calculated?: CalculatedValue;

// Data
options?: FieldOption[];
dataSource?: DataSource;
multiple?: boolean;

// Specific Properties
rows?: number; // textarea
min?: number; // number, slider
max?: number; // number, slider
step?: number; // slider
currency?: string; // currency field
[key: string]: any; // Field-specific props

// Container
children?: FormField[];
containerProps?: ContainerProps;

// Advanced
helpContent?: HelpConfig;
fieldPermissions?: PermissionConfig;
workflows?: WorkflowConfig[];
style?: StyleConfig;
}

Field Categories

Input Fields (25+)

  • Basic: text, textarea, number, email, password
  • Selection: select, radio, checkbox, checkbox-group, autocomplete
  • Temporal: date, daterange, time, datetime
  • File: file, multifile
  • Special: currency, phone, url, tags, rating, slider, signature, html, switch

Layout (20+) Containers for grouping and organizing fields

  • Grid-based: grid, columns, responsive-grid
  • Tabbed: tabs, accordion, wizard, stepper
  • Other: panel, section, card, divider, spacer, repeater, table, datagrid, hero, card-container, fieldset, split-pane

Widgets (10+) Data visualization and dashboards

  • Charts: chart, kpi, gauge, metric
  • Interactive: calendar-widget, map-widget, timeline, kanban, progress-tracker, activity-log
  • Tables: table-widget, list-widget

Content (15+) Static and dynamic content display

  • Text: heading, paragraph, text-content, blockquote
  • Media: image, video, carousel, gallery
  • Navigation: navigation, header, footer, sidebar, breadcrumb
  • Other: alert, badge, chip, code-block, content-card

Interactive (5+) User interaction elements

  • dialog, popup, drawer, dataview, tree-view

Validation System

Validation Rule Engine

export interface ValidationRule {
type: 'required' | 'minLength' | 'maxLength' | 'min' | 'max' |
'pattern' | 'email' | 'url' | 'comparison' | 'custom' | 'async';

// Common
message: string; // Error message
trigger: 'blur' | 'change' | 'submit' | 'realtime';

// Type-specific
minLength?: number;
maxLength?: number;
min?: number;
max?: number;
pattern?: string | RegExp;
compareField?: string; // For comparison
operator?: 'equals' | 'notEquals' | 'lessThan' | ...;
customValidation?: string; // JavaScript function
asyncValidationUrl?: string; // For async
asyncMethod?: 'GET' | 'POST';
debounceMs?: number;
}

Validation Execution

// Validate single field
const validateField = (field: FormField, value: any): ValidationError[] => {
const errors: ValidationError[] = [];

// Built-in validation
if (field.required && !value) {
errors.push({ fieldId: field.id, message: "Required" });
}

// Validation rules
for (const rule of field.validationRules || []) {
const ruleError = validateRule(rule, value, formData);
if (ruleError) errors.push(ruleError);
}

return errors;
};

// Validate entire form
const validateForm = (schema: FormSchema, data: any): ValidationResult => {
const errors: Record<string, ValidationError[]> = {};

for (const field of flattenFields(schema.fields)) {
const fieldErrors = validateField(
field,
data[field.propertyKey || field.id],
);
if (fieldErrors.length > 0) {
errors[field.id] = fieldErrors;
}
}

return {
valid: Object.keys(errors).length === 0,
errors,
};
};

Conditional Logic System

Conditional Evaluation

export interface ConditionalLogic {
rules: ConditionalRule[];
conjunction: 'AND' | 'OR';
}

export interface ConditionalRule {
when: string; // Field ID to watch
operator: 'equals' | 'notEquals' | 'contains' | 'greaterThan' | ...;
value: any; // Expected value
action: 'show' | 'hide' | 'require' | 'disable' | 'enable';
childRules?: ConditionalRule[]; // Nested rules
}

Evaluation Engine

const evaluateCondition = (rule: ConditionalRule, data: any): boolean => {
const fieldValue = data[rule.when];

switch (rule.operator) {
case "equals":
return fieldValue === rule.value;
case "notEquals":
return fieldValue !== rule.value;
case "contains":
return String(fieldValue).includes(rule.value);
case "greaterThan":
return Number(fieldValue) > Number(rule.value);
case "lessThan":
return Number(fieldValue) < Number(rule.value);
case "isEmpty":
return !fieldValue || fieldValue === "";
case "isNotEmpty":
return !!fieldValue && fieldValue !== "";
default:
return false;
}
};

const evaluateConditionalLogic = (
logic: ConditionalLogic,
data: any,
): boolean => {
const results = logic.rules.map((rule) => evaluateCondition(rule, data));

if (logic.conjunction === "AND") {
return results.every((r) => r);
} else {
return results.some((r) => r);
}
};

Rendering Strategy

Component Renderer Pattern

Each field type has a corresponding renderer component:

interface FieldRendererProps {
field: FormField;
value: any;
onChange: (value: any) => void;
errors?: ValidationError[];
disabled?: boolean;
readOnly?: boolean;
context?: RenderContext;
}

type FieldRenderer<T extends FormField = FormField> =
React.FC<FieldRendererProps & T>;

// Registry pattern
const fieldRenderers: Record<FieldType, FieldRenderer> = {
text: TextFieldRenderer,
email: EmailFieldRenderer,
select: SelectFieldRenderer,
date: DateFieldRenderer,
// ... 60+ renderers
};

// Dispatch factory
const renderField = (field: FormField, props: FieldRendererProps) => {
const Renderer = fieldRenderers[field.type];
if (!Renderer) {
return <UnsupportedFieldWarning field={field} />;
}
return <Renderer {...props} field={field} />;
};

Composition Pattern for Containers

Layout fields render their children recursively:

const ContainerRenderer: React.FC<FieldRendererProps> = ({ field, ...props }) => {
if (!field.children) return null;

return (
<Container field={field}>
{field.children.map(child => (
<RenderField
key={child.id}
field={child}
{...props}
/>
))}
</Container>
);
};

State Management

Redux Store Structure

store/
├── slices/
│ ├── form.slice.ts # Form schema state
│ ├── selection.slice.ts # Selected field
│ ├── clipboard.slice.ts # Copy/paste state
│ ├── history.slice.ts # Undo/redo
│ ├── dataSource.slice.ts # Data sources
│ ├── preview.slice.ts # Preview mode
│ └── ui.slice.ts # UI state
└── hooks/
├── useFormState.ts
├── useFieldSelection.ts
└── useUndo history.ts

Form State

interface FormState {
schema: FormSchema;
selectedFieldId: string | null;
editingFieldId: string | null;
hasChanges: boolean;
dataSources: ManagedDataSource[];
variables: FormVariable[];
actions: FormAction[];
mode: PageMode;
viewMode: "design" | "preview";
device: DeviceType;
}

Extensibility

Custom Field Types

Create new field types by implementing the FieldRenderer interface:

// 1. Define field type
export type MyCustomFieldType = {
type: 'my-custom-field';
customProperty: string;
};

// 2. Create renderer
export const MyCustomFieldRenderer: FieldRenderer = ({
field,
value,
onChange,
errors,
...props
}) => {
return (
<div className="my-custom-field">
<input
value={value}
onChange={(e) => onChange(e.target.value)}
data-custom={field.customProperty}
/>
{errors && <ErrorMessage>{errors[0].message}</ErrorMessage>}
</div>
);
};

// 3. Register renderer
fieldRenderers['my-custom-field'] = MyCustomFieldRenderer;

// 4. Add editor component
export const MyCustomFieldEditor: FieldEditor = ({ field, onUpdate }) => {
return (
<>
<TextField
label="Custom Property"
value={field.customProperty}
onChange={(e) => onUpdate({ customProperty: e.target.value })}
/>
</>
);
};

// 5. Add to palette
<FieldPaletteItem
title="My Custom Field"
type="my-custom-field"
category="custom"
icon={<CustomIcon />}
/>

Custom Validation Rules

const customValidators: Record<string, Validator> = {
"credit-card": (value: string) => {
// Luhn algorithm validation
return isValidCreditCard(value);
},
"phone-us": (value: string) => {
return /^\+?1?\d{9,15}$/.test(value);
},
"username-available": async (value: string) => {
const response = await fetch(`/api/users/check?username=${value}`);
return response.ok;
},
};

Custom Data Sources

const customDataSources: Record<string, DataSourceAdapter> = {
graphql: new GraphQLDataSourceAdapter(),
mongodb: new MongoDBDataSourceAdapter(),
elasticsearch: new ElasticsearchAdapter(),
firebase: new FirebaseAdapter(),
};

Performance Considerations

Memoization

// Memoize field renderer
const MemoizedFieldRenderer = React.memo(FieldRenderer, (prev, next) => {
return (
prev.field === next.field &&
prev.value === next.value &&
prev.disabled === next.disabled
);
});

Virtual Scrolling for Large Forms

import { List, AutoSizer } from 'react-virtualized';

const FormRenderer = ({ fields }) => {
return (
<AutoSizer>
{({ height, width }) => (
<List
width={width}
height={height}
rowCount={fields.length}
rowRenderer={({ index }) => renderField(fields[index])}
rowHeight={60}
/>
)}
</AutoSizer>
);
};

Lazy Loading Data Sources

interface DataSource {
lazyLoad?: boolean; // Load only when field becomes visible
requestDelay?: number; // Debounce API calls
cache?: "memory" | "session" | "none"; // Caching strategy
}

Testing

Unit Testing Fields

import { render, screen, fireEvent } from '@testing-library/react';

describe('TextFieldRenderer', () => {
it('renders with label', () => {
render(
<TextFieldRenderer
field={{ id: '1', type: 'text', label: 'Name' }}
value=""
onChange={jest.fn()}
/>
);
expect(screen.getByText('Name')).toBeInTheDocument();
});

it('calls onChange on input change', () => {
const onChange = jest.fn();
render(
<TextFieldRenderer
field={{ id: '1', type: 'text', label: 'Name' }}
value=""
onChange={onChange}
/>
);
fireEvent.change(screen.getByRole('textbox'), { target: { value: 'John' } });
expect(onChange).toHaveBeenCalledWith('John');
});

it('displays validation errors', () => {
render(
<TextFieldRenderer
field={{ id: '1', type: 'text', label: 'Name' }}
value=""
onChange={jest.fn()}
errors={[{ fieldId: '1', message: 'Required' }]}
/>
);
expect(screen.getByText('Required')).toBeInTheDocument();
});
});

Integration Testing

describe('FormRenderer', () => {
it('submits form with valid data', async () => {
const onSubmit = jest.fn();
render(
<FormRenderer
schema={mockSchema}
onSubmit={onSubmit}
/>
);

fireEvent.change(screen.getByLabelText('Email'), {
target: { value: 'test@example.com' }
});
fireEvent.click(screen.getByText('Submit'));

await waitFor(() => {
expect(onSubmit).toHaveBeenCalledWith({
email: 'test@example.com'
});
});
});
});

Standards & Best Practices

Naming Conventions

  • Field IDs: camelCase, no spaces

    • firstName, orderTotal, shippingAddress
    • first_name, First Name, FirstName
  • Property Keys: snake_case for database

    • first_name, order_total, user_id
    • firstName, orderTotal, userId
  • CSS Classes: kebab-case

    • .custom-field, .form-group, .field-error
    • .customField, .FormGroup, .fieldError
  • Component Names: PascalCase

    • TextFieldRenderer, FormRenderer
    • textFieldRenderer, form_renderer

Documentation Standards

Every field should have:

  • Label and description
  • Placeholder for input fields
  • Help content for complex fields
  • Validation error messages
{
"type": "text",
"label": "Email Address",
"placeholder": "user@example.com",
"description": "Your primary contact email",
"tooltip": "Used for account notifications",
"helpContent": {
"title": "Email Address Help",
"content": "Provide a valid email address..."
},
"validationRules": [
{
"type": "email",
"message": "Please enter a valid email address"
}
]
}

Default Values

Always provide sensible defaults:

{
"defaultValue": null,
"required": false,
"disabled": false,
"hidden": false,
"readOnly": false,
"min": 0,
"max": 100,
"step": 1
}