/** * Standalone component templates that don't depend on json-render * These components receive data as props instead of using hooks */ export const componentTemplates: Record = { Card: `"use client"; import { ReactNode } from "react"; interface CardProps { title?: string | null; description?: string | null; padding?: "sm" | "md" | "lg" | null; children?: ReactNode; } export function Card({ title, description, padding, children }: CardProps) { const paddings: Record = { sm: "12px", md: "16px", lg: "24px", }; return (
{(title || description) && (
{title && (

{title}

)} {description && (

{description}

)}
)}
{children}
); } `, Grid: `"use client"; import { ReactNode } from "react"; interface GridProps { columns?: number | null; gap?: "sm" | "md" | "lg" | null; children?: ReactNode; } export function Grid({ columns, gap, children }: GridProps) { const gaps: Record = { sm: "8px", md: "16px", lg: "24px", }; return (
{children}
); } `, Stack: `"use client"; import { ReactNode } from "react"; interface StackProps { direction?: "horizontal" | "vertical" | null; gap?: "sm" | "md" | "lg" | null; align?: "start" | "center" | "end" | "stretch" | null; children?: ReactNode; } export function Stack({ direction, gap, align, children }: StackProps) { const gaps: Record = { sm: "8px", md: "16px", lg: "24px", }; const alignments: Record = { start: "flex-start", center: "center", end: "flex-end", stretch: "stretch", }; return (
{children}
); } `, Metric: `"use client"; interface MetricProps { label: string; valuePath: string; format?: "number" | "currency" | "percent" | null; trend?: "up" | "down" | "neutral" | null; trendValue?: string | null; data?: Record; } function getByPath(obj: unknown, path: string): unknown { if (!path) return obj; const segments = path.startsWith("/") ? path.slice(1).split("/") : path.split("/"); let current: unknown = obj; for (const segment of segments) { if (current === null || current === undefined) return undefined; if (typeof current === "object") { current = (current as Record)[segment]; } else { return undefined; } } return current; } export function Metric({ label, valuePath, format, trend, trendValue, data }: MetricProps) { const rawValue = data ? getByPath(data, valuePath) : undefined; let displayValue = String(rawValue ?? "-"); if (format === "currency" && typeof rawValue === "number") { displayValue = new Intl.NumberFormat("en-US", { style: "currency", currency: "USD", }).format(rawValue); } else if (format === "percent" && typeof rawValue === "number") { displayValue = new Intl.NumberFormat("en-US", { style: "percent", minimumFractionDigits: 1, }).format(rawValue); } else if (format === "number" && typeof rawValue === "number") { displayValue = new Intl.NumberFormat("en-US").format(rawValue); } return (
{label} {displayValue} {(trend || trendValue) && ( {trend === "up" ? "+" : trend === "down" ? "-" : ""} {trendValue} )}
); } `, Chart: `"use client"; interface ChartProps { type?: "bar" | "line" | "pie" | "area"; dataPath: string; title?: string | null; height?: number | null; data?: Record; } function getByPath(obj: unknown, path: string): unknown { if (!path) return obj; const segments = path.startsWith("/") ? path.slice(1).split("/") : path.split("/"); let current: unknown = obj; for (const segment of segments) { if (current === null || current === undefined) return undefined; if (typeof current === "object") { current = (current as Record)[segment]; } else { return undefined; } } return current; } export function Chart({ title, dataPath, height, data }: ChartProps) { const chartData = data ? (getByPath(data, dataPath) as Array<{ label: string; value: number }> | undefined) : undefined; if (!chartData || !Array.isArray(chartData)) { return
No data
; } const maxValue = Math.max(...chartData.map((d) => d.value)); return (
{title && (

{title}

)}
{chartData.map((d, i) => (
{d.label}
))}
); } `, Table: `"use client"; interface TableProps { dataPath: string; columns: Array<{ key: string; label: string; format?: "text" | "currency" | "date" | "badge" | null }>; data?: Record; } function getByPath(obj: unknown, path: string): unknown { if (!path) return obj; const segments = path.startsWith("/") ? path.slice(1).split("/") : path.split("/"); let current: unknown = obj; for (const segment of segments) { if (current === null || current === undefined) return undefined; if (typeof current === "object") { current = (current as Record)[segment]; } else { return undefined; } } return current; } export function Table({ dataPath, columns, data }: TableProps) { const tableData = data ? (getByPath(data, dataPath) as Array> | undefined) : undefined; if (!tableData || !Array.isArray(tableData)) { return
No data
; } const formatCell = (value: unknown, format?: string | null) => { if (value === null || value === undefined) return "-"; if (format === "currency" && typeof value === "number") { return new Intl.NumberFormat("en-US", { style: "currency", currency: "USD", }).format(value); } if (format === "date" && typeof value === "string") { return new Date(value).toLocaleDateString(); } if (format === "badge") { return ( {String(value)} ); } return String(value); }; return ( {columns.map((col) => ( ))} {tableData.map((row, i) => ( {columns.map((col) => ( ))} ))}
{col.label}
{formatCell(row[col.key], col.format)}
); } `, Button: `"use client"; interface ButtonProps { label: string; variant?: "primary" | "secondary" | "danger" | "ghost" | null; size?: "sm" | "md" | "lg" | null; action?: string; disabled?: boolean | null; onClick?: () => void; } export function Button({ label, variant, disabled, onClick }: ButtonProps) { const variants: Record = { primary: { background: "var(--foreground)", color: "var(--background)", border: "none", }, secondary: { background: "transparent", color: "var(--foreground)", border: "1px solid var(--border)", }, danger: { background: "#dc2626", color: "#fff", border: "none" }, ghost: { background: "transparent", color: "var(--muted)", border: "none" }, }; return ( ); } `, Heading: `"use client"; interface HeadingProps { text: string; level?: "h1" | "h2" | "h3" | "h4" | null; } export function Heading({ text, level }: HeadingProps) { const sizes: Record = { h1: { fontSize: 32, fontWeight: 700 }, h2: { fontSize: 24, fontWeight: 600 }, h3: { fontSize: 20, fontWeight: 600 }, h4: { fontSize: 16, fontWeight: 600 }, }; const style = sizes[level || "h2"]; const Tag = (level || "h2") as keyof JSX.IntrinsicElements; return ( {text} ); } `, Text: `"use client"; interface TextProps { content: string; variant?: "body" | "caption" | "label" | null; color?: "default" | "muted" | "success" | "warning" | "danger" | null; } export function Text({ content, variant, color }: TextProps) { const sizes: Record = { body: 14, caption: 12, label: 13, }; const colors: Record = { default: "var(--foreground)", muted: "var(--muted)", success: "#22c55e", warning: "#eab308", danger: "#ef4444", }; return (

{content}

); } `, Badge: `"use client"; interface BadgeProps { text: string; variant?: "default" | "success" | "warning" | "danger" | "info" | null; } export function Badge({ text, variant }: BadgeProps) { const colors: Record = { default: { bg: "var(--border)", fg: "var(--foreground)" }, success: { bg: "#22c55e20", fg: "#22c55e" }, warning: { bg: "#eab30820", fg: "#eab308" }, danger: { bg: "#ef444420", fg: "#ef4444" }, info: { bg: "#3b82f620", fg: "#3b82f6" }, }; const { bg, fg } = colors[variant || "default"]; return ( {text} ); } `, Alert: `"use client"; interface AlertProps { type: "info" | "success" | "warning" | "error"; title: string; message?: string | null; dismissible?: boolean | null; } export function Alert({ type, title, message }: AlertProps) { const colors: Record = { info: "var(--muted)", success: "#22c55e", warning: "#eab308", error: "#ef4444", }; return (
{title}
{message && (
{message}
)}
); } `, Divider: `"use client"; interface DividerProps { label?: string | null; } export function Divider({ label }: DividerProps) { if (label) { return (
{label}
); } return
; } `, Empty: `"use client"; interface EmptyProps { title: string; description?: string | null; action?: string | null; actionLabel?: string | null; } export function Empty({ title, description }: EmptyProps) { return (
{title}
{description && (
{description}
)}
); } `, Select: `"use client"; interface SelectProps { label?: string | null; bindPath: string; options: Array<{ value: string; label: string }>; placeholder?: string | null; value?: string; onChange?: (value: string) => void; } export function Select({ label, options, placeholder, value, onChange }: SelectProps) { return (
{label && ( )}
); } `, DatePicker: `"use client"; interface DatePickerProps { label?: string | null; bindPath: string; placeholder?: string | null; value?: string; onChange?: (value: string) => void; } export function DatePicker({ label, placeholder, value, onChange }: DatePickerProps) { return (
{label && ( )} onChange?.(e.target.value)} placeholder={placeholder || ""} style={{ width: "100%", padding: "8px 12px", background: "var(--card)", border: "1px solid var(--border)", borderRadius: "var(--radius)", color: "var(--foreground)", fontSize: 14, }} />
); } `, List: `"use client"; import { ReactNode } from "react"; interface ListProps { dataPath: string; emptyMessage?: string | null; data?: Record; children?: ReactNode; } function getByPath(obj: unknown, path: string): unknown { if (!path) return obj; const segments = path.startsWith("/") ? path.slice(1).split("/") : path.split("/"); let current: unknown = obj; for (const segment of segments) { if (current === null || current === undefined) return undefined; if (typeof current === "object") { current = (current as Record)[segment]; } else { return undefined; } } return current; } export function List({ dataPath, emptyMessage, data, children }: ListProps) { const listData = data ? (getByPath(data, dataPath) as unknown[] | undefined) : undefined; if (!listData || !Array.isArray(listData) || listData.length === 0) { return (
{emptyMessage || "No items"}
); } return (
{children}
); } `, };