-
Notifications
You must be signed in to change notification settings - Fork 0
API Reference
Javier Godoy edited this page Jan 5, 2026
·
2 revisions
Complete API documentation for the Stronzi template system.
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, ornumber) -
placeholder- Optional placeholder text
Example:
const fields: Array<FieldDef<MyProps>> = [
{
key: 'title',
label: 'Title',
type: 'text',
placeholder: 'Enter title...'
}
];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
};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} />;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'
}
}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
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
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'
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
export function meta({}: Route.MetaArgs) {
return [{ title: 'Page Title' }, { name: 'description', content: 'Description' }];
}
export default function Home() {
return <div>Content</div>;
}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>;
}// 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
}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 }));
};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');
}
};// 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>
));
}const template = templates.find((t) => t.id === templateId);
if (!template) {
return <div>Template not found</div>;
}const handleExport = async () => {
try {
await exportNodeToPng(previewRef.current!, filename);
} catch (error) {
console.error('Export failed:', error);
alert('Failed to export template');
}
};// Ensure type safety
const value = field.type === 'number' ? parseFloat(inputValue) || 0 : inputValue;const TemplateComponent = useMemo(() => {
return template.component;
}, [template.id]);const debouncedUpdate = useMemo(
() =>
debounce((key: string, value: any) => {
setValues((prev) => ({ ...prev, [key]: value }));
}, 300),
[]
);export const TEMPLATE_WIDTH = 1080;
export const TEMPLATE_HEIGHT = 1920;
export const TEMPLATE_ASPECT_RATIO = 9 / 16;export type FieldType = 'text' | 'textarea' | 'number';// ✅ Good: Fully typed
interface MyProps {
title: string;
count: number;
}
// ❌ Bad: Using any
interface MyProps {
[key: string]: any;
}// ✅ 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
};// ✅ Good: Type matches field type
{
key: 'count',
type: 'number' as const, // Correct type
}
// ❌ Bad: Type mismatch
{
key: 'count',
type: 'text', // Should be 'number'
}- Update
FieldDeftype intypes.ts - Update form generation logic in
generate.tsx - Update type guards if needed
- Document the new type
- Update props interface
- Update default props
- Update field definitions
- Update component to use new props
- Test in generator