/** * Preview tab for the form-widget builder. Renders the in-flight * definition using the SAME field-rendering pipeline + inline CSS as * the embed route at `/embed/forms/$formKey`. That is intentional: * what the customer sees in their iframe must match what the operator * sees in the admin preview. * * The component is deliberately self-contained — it is rendered * outside the sandboxed iframe so no postMessage / parent-resize * concerns apply, and form-submission is disabled (preview is for * visual inspection only). */ import type * as React from "react"; import { useMemo, useState } from "react"; import { useTranslation } from "react-i18next"; import { HONEYPOT_FIELD_KEY, type FormDefinition, type FormField, type FormTheme } from "@toolctrl/form-widget"; export type PreviewTabProps = { definition: FormDefinition; theme: FormTheme; }; function themeCssVars(theme: FormTheme): React.CSSProperties { const vars: Record = {}; if (theme.primaryColor) vars["--tcform-primary"] = theme.primaryColor; if (theme.backgroundColor) vars["--tcform-bg"] = theme.backgroundColor; if (theme.textColor) vars["--tcform-text"] = theme.textColor; if (theme.fontFamily) vars["--tcform-font"] = theme.fontFamily; if (theme.borderRadius) vars["--tcform-radius"] = theme.borderRadius; return vars as React.CSSProperties; } function PreviewField({ field, value, onChange, }: { field: FormField; value: string | boolean; onChange: (next: string | boolean) => void; }): React.JSX.Element | null { const inputId = `tcform-preview-${field.key}`; if (field.type === "honeypot") { return ( onChange(e.target.value)} style={{ position: "absolute", left: "-9999px", width: 1, height: 1, opacity: 0 }} /> ); } const labelEl = ( ); const helpEl = field.helpText ? (
{field.helpText}
) : null; if (field.type === "textarea") { return (
{labelEl}