Skip to content

API Reference

Javier Godoy edited this page Jan 5, 2026 · 2 revisions

API Reference

Complete API documentation for the Stronzi template system.

Core Types

FieldDef<T>

Defines a form field for a template.

interface FieldDef<T> {
  key: keyof T;
  label: string;
  type: 'text' | 'textarea' | 'number';
  placeholder?: string;
}

Properties:

  • key - Property name from the template props interface
  • label - Display label for the form field
  • type - Input type (text, textarea, or number)
  • placeholder - Optional placeholder text

Example:

const fields: Array<FieldDef<MyProps>> = [
  {
    key: 'title',
    label: 'Title',
    type: 'text',
    placeholder: 'Enter title...'
  }
];

TemplateDefinition<T>

Defines a complete template with all metadata.

interface TemplateDefinition<T> {
  id: string;
  name: string;
  component: React.FC<T>;
  defaultProps: T;
  fields: Array<FieldDef<T>>;
}

Properties:

  • id - Unique identifier (URL-safe, kebab-case)
  • name - Display name shown in UI
  • component - React component that renders the template
  • defaultProps - Default values for all props
  • fields - Array of field definitions for form generation

Example:

const templateDef: TemplateDefinition<MyProps> = {
  id: 'my-template',
  name: 'My Template',
  component: MyTemplate,
  defaultProps: { title: 'Default' },
  fields: myFields
};

Template Registry

templates

Central array of all registered templates.

const templates: TemplateDefinition<any>[];

Location: app/templates/index.ts

Usage:

import { templates } from '~/templates';

// Find template by ID
const template = templates.find((t) => t.id === 'my-template');

// Get all template names
const names = templates.map((t) => t.name);

// Render a template
const Template = template.component;
<Template {...values} />;

Utility Functions

exportNodeToPng

Exports an HTML element to a PNG file.

async function exportNodeToPng(node: HTMLElement, filename: string): Promise<void>;

Parameters:

  • node - HTML element to capture
  • filename - Output filename (e.g., 'template.png')

Returns: Promise that resolves when download completes

Throws: Error if capture or download fails

Usage:

import { exportNodeToPng } from '~/utils/exportImage';

const handleExport = async () => {
  const element = document.getElementById('template');
  if (element) {
    await exportNodeToPng(element, 'my-template.png');
  }
};

Configuration:

// Internal settings (configured in function)
{
  width: 1080,         // Output width in pixels
  height: 1920,        // Output height in pixels
  pixelRatio: 1,       // Device pixel ratio
  cacheBust: true,     // Prevent caching issues
  style: {
    transform: 'scale(1)',
    transformOrigin: 'top left'
  }
}

Template Component API

Props Interface

Every template must export a TypeScript interface for its props.

export interface MyTemplateProps {
  title: string;
  subtitle?: string;
  imageUrl: string;
  fontSize?: number;
}

Requirements:

  • Must be exported
  • All editable fields must be included
  • Optional fields marked with ?
  • Type should match field type

Default Props

Every template must export default values.

export const defaultProps: MyTemplateProps = {
  title: 'Default Title',
  subtitle: 'Default Subtitle',
  imageUrl: 'https://example.com/image.jpg',
  fontSize: 48
};

Requirements:

  • Must be exported as defaultProps
  • Must match the props interface
  • Must provide all required fields
  • Optional fields can be included or omitted

Field Definitions

Every template must export field definitions.

export const fields: Array<FieldDef<MyTemplateProps>> = [
  {
    key: 'title',
    label: 'Title',
    type: 'text',
    placeholder: 'Enter title...'
  },
  {
    key: 'subtitle',
    label: 'Subtitle',
    type: 'text',
    placeholder: 'Enter subtitle...'
  },
  {
    key: 'imageUrl',
    label: 'Image URL',
    type: 'text',
    placeholder: 'https://...'
  },
  {
    key: 'fontSize',
    label: 'Font Size',
    type: 'number',
    placeholder: '48'
  }
];

Requirements:

  • Must be exported as fields
  • Must be array of FieldDef<Props>
  • Keys must match props interface
  • Types must be 'text', 'textarea', or 'number'

Component Function

The template component itself.

const MyTemplate: React.FC<MyTemplateProps> = (props) => {
  return <div className='w-[1080px] h-[1920px]'>{/* Template content */}</div>;
};

export default MyTemplate;

Requirements:

  • Must be a React functional component
  • Must accept props of the defined interface
  • Must be exported as default
  • Must render exactly 1080×1920 pixels
  • Should use Tailwind classes for styling

Route Types

Home Route (/)

export function meta({}: Route.MetaArgs) {
  return [{ title: 'Page Title' }, { name: 'description', content: 'Description' }];
}

export default function Home() {
  return <div>Content</div>;
}

Dynamic Route (/preview/:templateId)

import type { Route } from './+types/preview.$templateId';

export function meta({ params }: Route.MetaArgs) {
  return [{ title: `Template ${params.templateId}` }];
}

export default function PreviewTemplate({ params }: Route.ComponentProps) {
  const { templateId } = params;
  return <div>Template {templateId}</div>;
}

Type Guards

Template Type Checking

// Check if object is a valid template
function isTemplateDefinition(obj: any): obj is TemplateDefinition<any> {
  return (
    typeof obj.id === 'string' &&
    typeof obj.name === 'string' &&
    typeof obj.component === 'function' &&
    typeof obj.defaultProps === 'object' &&
    Array.isArray(obj.fields)
  );
}

// Usage
if (isTemplateDefinition(template)) {
  // TypeScript knows template is TemplateDefinition
}

React Hooks Usage

useState for Template Values

const [values, setValues] = useState<Record<string, any>>({});

// Initialize with defaults
useEffect(() => {
  if (template) {
    setValues({ ...template.defaultProps });
  }
}, [template]);

// Update single field
const handleChange = (key: string, value: any) => {
  setValues((prev) => ({ ...prev, [key]: value }));
};

useRef for Export Target

const previewRef = useRef<HTMLDivElement>(null);

// In JSX
<div ref={previewRef}>
  <TemplateComponent {...values} />
</div>;

// Export
const handleExport = async () => {
  if (previewRef.current) {
    await exportNodeToPng(previewRef.current, 'template.png');
  }
};

Form Generation Pattern

// Generate form from fields
{
  template.fields.map((field) => (
    <div key={String(field.key)}>
      <label>{field.label}</label>

      {field.type === 'textarea' ? (
        <textarea value={values[String(field.key)] || ''} onChange={(e) => handleChange(String(field.key), e.target.value)} placeholder={field.placeholder} />
      ) : (
        <input
          type={field.type}
          value={values[String(field.key)] || ''}
          onChange={(e) => {
            const value = field.type === 'number' ? parseFloat(e.target.value) : e.target.value;
            handleChange(String(field.key), value);
          }}
          placeholder={field.placeholder}
        />
      )}
    </div>
  ));
}

Error Handling

Template Not Found

const template = templates.find((t) => t.id === templateId);

if (!template) {
  return <div>Template not found</div>;
}

Export Errors

const handleExport = async () => {
  try {
    await exportNodeToPng(previewRef.current!, filename);
  } catch (error) {
    console.error('Export failed:', error);
    alert('Failed to export template');
  }
};

Type Errors

// Ensure type safety
const value = field.type === 'number' ? parseFloat(inputValue) || 0 : inputValue;

Performance Optimization

Memoization

const TemplateComponent = useMemo(() => {
  return template.component;
}, [template.id]);

Debounced Updates

const debouncedUpdate = useMemo(
  () =>
    debounce((key: string, value: any) => {
      setValues((prev) => ({ ...prev, [key]: value }));
    }, 300),
  []
);

Constants

Template Dimensions

export const TEMPLATE_WIDTH = 1080;
export const TEMPLATE_HEIGHT = 1920;
export const TEMPLATE_ASPECT_RATIO = 9 / 16;

Field Types

export type FieldType = 'text' | 'textarea' | 'number';

Best Practices

Type Safety

// ✅ Good: Fully typed
interface MyProps {
  title: string;
  count: number;
}

// ❌ Bad: Using any
interface MyProps {
  [key: string]: any;
}

Default Values

// ✅ Good: All fields have defaults
export const defaultProps: MyProps = {
  title: 'Default',
  subtitle: 'Default',
  imageUrl: 'https://placeholder.com'
};

// ❌ Bad: Missing defaults
export const defaultProps: MyProps = {
  title: 'Default'
  // Missing other required fields
};

Field Definitions

// ✅ Good: Type matches field type
{
  key: 'count',
  type: 'number' as const,  // Correct type
}

// ❌ Bad: Type mismatch
{
  key: 'count',
  type: 'text',  // Should be 'number'
}

Migration Guide

Adding a New Field Type

  1. Update FieldDef type in types.ts
  2. Update form generation logic in generate.tsx
  3. Update type guards if needed
  4. Document the new type

Changing Template Structure

  1. Update props interface
  2. Update default props
  3. Update field definitions
  4. Update component to use new props
  5. Test in generator

← Back to Design System | Next: Building Production →

Clone this wiki locally