import React, { useState, useEffect } from 'react' import { getFieldType } from '../extensions/registry' import { useFormStore } from '../store/useFormStore' import FontFamilyToggle, { loadGoogleFont } from './FontFamilyToggle' import InfoTip from './property/InfoTip' import { ColorRow, NumberRow, SelectRow, SubHeading, Segmented, inputBase } from './property/Controls' import { RotateCcw } from 'lucide-react' // Theme panel (the reference builder F:8344-10479): accordion General / Colors / Fonts / Input, one section open at a time, // General open first. WP keeps its own settings keys (normalizeSchema maps them to the renderer's keys). const DEFAULT_SETTINGS = { submitButtonBgColor: '#4f46e5', submitButtonTextColor: '#ffffff', formBgColor: '#ffffff', labelColor: '#374151', inputBgColor: '#F9FAFB', inputBorderColor: '#d1d5db', inputTextColor: '#000000', placeholderColor: '#9CA3AF', hoverBorderColor: '#6B7280', focusBorderColor: '#3B82F6', descriptionColor: '#9CA3AF', checkboxSelectedColor: '#2563EB', checkboxUnselectedColor: '#D1D5DB', radioSelectedColor: '#2563EB', selectedValueColor: '#000000', requiredAsteriskColor: '#EF4444', fontSize: 14, lineHeight: 20, letterSpacing: 0, descriptionFontSize: 13, labelFontSize: 14, labelFontWeight: 500, labelLineHeight: 20, headingH1Size: 32, headingH2Size: 28, headingH3Size: 24, headingH4Size: 20, headingLineHeight: 24, headingLetterSpacing: 0, formWidth: 600, formPadding: 24, inputBorderWidth: 1, inputBorderRadius: 8, formBorderRadius: 8, labelPlacement: 'top', descriptionPlacement: 'below', } // the reference builder colorSchemes (F:1179) mapped to the WP theme keys. primaryColor → submit button + focus. const FP_SCHEMES = [ ['Classic Blue', { primaryColor: '#3B82F6', backgroundColor: '#FFFFFF', inputBackgroundColor: '#F9FAFB', primaryTextColor: '#000000', placeholderTextColor: '#9CA3AF', inputBorderColor: '#D1D5DB', hoverBorderColor: '#6B7280', focusBorderColor: '#3B82F6', descriptionColor: '#9CA3AF', submitTextColor: '#ffffff', checkboxSelectedColor: '#2563EB', checkboxUnselectedColor: '#D1D5DB' }], ['Green Serenity', { primaryColor: '#22C55E', backgroundColor: '#ECFDF5', inputBackgroundColor: '#D1FAE5', primaryTextColor: '#065F46', placeholderTextColor: '#6EE7B7', inputBorderColor: '#6EE7B7', hoverBorderColor: '#22C55E', focusBorderColor: '#22C55E', descriptionColor: '#10B981', submitTextColor: '#ffffff', checkboxSelectedColor: '#22C55E', checkboxUnselectedColor: '#6EE7B7' }], ['Elegant Gray', { primaryColor: '#9CA3AF', backgroundColor: '#F9FAFB', inputBackgroundColor: '#E5E7EB', primaryTextColor: '#374151', placeholderTextColor: '#6B7280', inputBorderColor: '#D1D5DB', hoverBorderColor: '#9CA3AF', focusBorderColor: '#6B7280', descriptionColor: '#6B7280', submitTextColor: '#ffffff', checkboxSelectedColor: '#6B7280', checkboxUnselectedColor: '#D1D5DB' }], ['Sunset Glow', { primaryColor: '#F59E0B', backgroundColor: '#FEFCE8', inputBackgroundColor: '#FDF4C5', primaryTextColor: '#92400E', placeholderTextColor: '#D97706', inputBorderColor: '#D1D5DB', hoverBorderColor: '#F59E0B', focusBorderColor: '#F59E0B', descriptionColor: '#D97706', submitTextColor: '#ffffff', checkboxSelectedColor: '#F59E0B', checkboxUnselectedColor: '#FDF4C5' }], ['Warm Sunset', { primaryColor: '#EF4444', backgroundColor: '#FFF5F5', inputBackgroundColor: '#FFE4E4', primaryTextColor: '#991B1B', placeholderTextColor: '#DC2626', inputBorderColor: '#FECACA', hoverBorderColor: '#F87171', focusBorderColor: '#EF4444', descriptionColor: '#DC2626', submitTextColor: '#ffffff', checkboxSelectedColor: '#EF4444', checkboxUnselectedColor: '#FECACA' }], ['Cool Aqua', { primaryColor: '#06B6D4', backgroundColor: '#ECFEFF', inputBackgroundColor: '#CFFAFE', primaryTextColor: '#164E63', placeholderTextColor: '#0E7490', inputBorderColor: '#A7F3D0', hoverBorderColor: '#06B6D4', focusBorderColor: '#06B6D4', descriptionColor: '#0E7490', submitTextColor: '#ffffff', checkboxSelectedColor: '#06B6D4', checkboxUnselectedColor: '#CFFAFE' }], ['Soft Lavender', { primaryColor: '#8B5CF6', backgroundColor: '#F5F3FF', inputBackgroundColor: '#EDE9FE', primaryTextColor: '#4C1D95', placeholderTextColor: '#7C3AED', inputBorderColor: '#DDD6FE', hoverBorderColor: '#C4B5FD', focusBorderColor: '#8B5CF6', descriptionColor: '#7C3AED', submitTextColor: '#ffffff', checkboxSelectedColor: '#8B5CF6', checkboxUnselectedColor: '#EDE9FE' }], ['Rose Gold', { primaryColor: '#F472B6', backgroundColor: '#FFF1F2', inputBackgroundColor: '#FCE7F3', primaryTextColor: '#831843', placeholderTextColor: '#F472B6', inputBorderColor: '#FBCFE8', hoverBorderColor: '#F472B6', focusBorderColor: '#F472B6', descriptionColor: '#F472B6', submitTextColor: '#ffffff', checkboxSelectedColor: '#F472B6', checkboxUnselectedColor: '#FCE7F3' }], ['Midnight', { primaryColor: '#1E293B', backgroundColor: '#0F172A', inputBackgroundColor: '#1E293B', primaryTextColor: '#F1F5F9', placeholderTextColor: '#64748B', inputBorderColor: '#334155', hoverBorderColor: '#64748B', focusBorderColor: '#2563EB', descriptionColor: '#64748B', submitTextColor: '#ffffff', checkboxSelectedColor: '#2563EB', checkboxUnselectedColor: '#334155' }], ['Minty Fresh', { primaryColor: '#34D399', backgroundColor: '#ECFDF5', inputBackgroundColor: '#D1FAE5', primaryTextColor: '#065F46', placeholderTextColor: '#34D399', inputBorderColor: '#A7F3D0', hoverBorderColor: '#34D399', focusBorderColor: '#34D399', descriptionColor: '#34D399', submitTextColor: '#ffffff', checkboxSelectedColor: '#34D399', checkboxUnselectedColor: '#D1FAE5' }], ] export const schemeToSettings = (name, c) => ({ colorSchemeName: name, formBgColor: c.backgroundColor, inputBgColor: c.inputBackgroundColor, inputBorderColor: c.inputBorderColor, hoverBorderColor: c.hoverBorderColor, focusBorderColor: c.focusBorderColor, labelColor: c.primaryTextColor, inputTextColor: c.primaryTextColor, placeholderColor: c.placeholderTextColor, placeholderTextColor: c.placeholderTextColor, descriptionColor: c.descriptionColor, checkboxSelectedColor: c.checkboxSelectedColor, checkboxUnselectedColor: c.checkboxUnselectedColor, radioSelectedColor: c.checkboxSelectedColor, submitButtonBgColor: c.primaryColor, submitButtonTextColor: c.submitTextColor, primaryColor: c.primaryColor, }) // Per-field colour overrides the reference builder resets when a scheme is applied (resetFieldsToScheme F:2737). WP clears them so // the fields follow the new theme colours. const FIELD_SCHEME_KEYS = ['labelTextColor', 'labelColor', 'inputBackgroundColor', 'inputBorderColor', 'textColor', 'placeholderTextColor', 'descriptionColor'] const LAYOUT_TYPES = ['grid', 'static-content', 'html', 'section', 'image', 'submit', 'promo-banner', 'trust-badges', 'faq-accordion', 'countdown-banner'] const isLayoutField = (type) => LAYOUT_TYPES.includes(type) || Boolean(getFieldType(type)?.noValue) function clearFieldColors(list) { return (list || []).map((f) => { let next = f if (!isLayoutField(f.type)) { next = { ...f } for (const k of FIELD_SCHEME_KEYS) if (k in next) next[k] = null } if (f.columns && !Array.isArray(f.columns) && typeof f.columns === 'object') { const cols = {} for (const [k, v] of Object.entries(f.columns)) cols[k] = Array.isArray(v) ? clearFieldColors(v) : v next = { ...next, columns: cols } } return next }) } function Section({ id, title, open, onToggle, tip, children }) { return (
{open &&
{children}
}
) } export default function StylingCustomizer() { const settings = useFormStore((state) => state.settings) const setSettings = useFormStore((state) => state.setSettings) const [open, setOpen] = useState('general') const toggle = (id) => setOpen((cur) => (cur === id ? null : id)) const set = (key, value) => setSettings({ [key]: value }) const reset = (key) => setSettings({ [key]: DEFAULT_SETTINGS[key] }) const val = (key, ...fallbacks) => { for (const k of [key, ...fallbacks]) { const v = settings[k] if (v !== undefined && v !== null && v !== '') return v } return DEFAULT_SETTINGS[key] } useEffect(() => { if (settings.fontFamily && settings.fontFamily !== 'inherit') loadGoogleFont(settings.fontFamily) }, [settings.fontFamily]) const color = (label, key, tip) => ( set(key, v)} onReset={() => reset(key)} /> ) const number = (label, key, min, max, opts = {}) => ( (opts.onChange ? opts.onChange(v) : set(key, v))} onReset={() => (opts.onReset ? opts.onReset() : reset(key))} /> ) // Wrapper max width: the canvas and renderer read formWidth (px); a % width is kept in wrapperMaxWidth. const widthUnit = settings.wrapperMaxWidthUnit === '%' ? '%' : 'px' const widthValue = widthUnit === '%' ? (parseInt(String(settings.wrapperMaxWidth), 10) || 100) : (Number(settings.formWidth) || parseInt(String(settings.wrapperMaxWidth), 10) || 600) const setWidth = (n, unit = widthUnit) => { if (n === null || n === undefined || Number.isNaN(n)) return if (unit === '%') setSettings({ wrapperMaxWidthUnit: '%', wrapperMaxWidth: `${n}%`, formWidth: null }) else setSettings({ wrapperMaxWidthUnit: 'px', wrapperMaxWidth: n, formWidth: n }) } const applyScheme = (name) => { const found = FP_SCHEMES.find(([n]) => n === name) if (!found) return setSettings(schemeToSettings(found[0], found[1])) const state = useFormStore.getState() if (state.setFields) state.setFields(clearFieldColors(state.fields)) } return (
toggle('general')} tip="Form width, spacing and label layout.">
setWidth(parseInt(e.target.value, 10))} className="w-[68px] h-[34px] px-2 text-sm border border-gray-200 rounded-md bg-gray-50 box-border focus:outline-none focus:ring-1 focus:ring-blue-500" />
setWidth(parseInt(e.target.value, 10))} className="w-full mt-1.5 h-1.5 accent-blue-600 cursor-pointer" />
{number('Form Padding', 'formPadding', 0, 100, { value: typeof settings.formPadding === 'object' && settings.formPadding ? settings.formPadding.top : val('formPadding') })} set('labelPlacement', v)} onReset={() => reset('labelPlacement')} /> set('descriptionPlacement', v)} onReset={() => reset('descriptionPlacement')} />
toggle('colors')} tip="Theme colours used by every field unless a field sets its own.">
Background Colors {color('Form Background', 'formBgColor')} {color('Input Background', 'inputBgColor')} Border Colors {color('Input Border', 'inputBorderColor')} {color('Hover Border', 'hoverBorderColor')} {color('Focus Border', 'focusBorderColor')} Text Colors {color('Label Text', 'labelColor')} {color('Input Text', 'inputTextColor')} {color('Placeholder Text', 'placeholderColor')} {color('Description Text', 'descriptionColor')} {color('Required Asterisk', 'requiredAsteriskColor', 'Colour of the * after required labels.')} Dropdown {color('Selected Value Color', 'selectedValueColor')} Choices {color('Checkbox Selected', 'checkboxSelectedColor')} {color('Checkbox Unselected', 'checkboxUnselectedColor')} {color('Radio Selected', 'radioSelectedColor')} Submit Button {color('Background', 'submitButtonBgColor')} {color('Text', 'submitButtonTextColor')}
toggle('fonts')} tip="Font family and sizes.">
set('fontFamily', fontName)} onReset={() => set('fontFamily', 'inherit')} />
General Font Settings {number('Font Size', 'fontSize', 8, 32)} {number('Line Height', 'lineHeight', 10, 48)} {number('Letter Spacing', 'letterSpacing', -2, 6, { step: 0.1 })} {number('Description Font Size', 'descriptionFontSize', 10, 20)} Heading Font Settings {number('H1 Size', 'headingH1Size', 16, 72)} {number('H2 Size', 'headingH2Size', 14, 60)} {number('H3 Size', 'headingH3Size', 12, 48)} {number('H4 Size', 'headingH4Size', 12, 40)} {number('Heading Line Height', 'headingLineHeight', 10, 80)} {number('Heading Letter Spacing', 'headingLetterSpacing', -3, 10, { step: 0.1 })} Label Font Settings {number('Label Font Size', 'labelFontSize', 8, 32)} set('labelFontWeight', parseInt(v, 10))} onReset={() => reset('labelFontWeight')} options={[{ value: 300, label: '300 (Light)' }, { value: 400, label: '400 (Normal)' }, { value: 500, label: '500 (Medium)' }, { value: 600, label: '600 (Semibold)' }, { value: 700, label: '700 (Bold)' }]} /> {number('Label Line Height', 'labelLineHeight', 10, 40)}
toggle('input')} tip="Border width and corner radius of inputs and the form card."> {number('Border Width', 'inputBorderWidth', 0, 6)} {/* Input radius and form card radius are separate keys (legacy forms only had borderRadius for both). */} {number('Border Radius', 'inputBorderRadius', 0, 30, { fallbacks: ['borderRadius'], tip: 'Corner radius of text inputs, dropdowns and text areas.' })} {number('Form Corner Radius', 'formBorderRadius', 0, 48, { fallbacks: ['borderRadius'], tip: 'Corner radius of the form card.' })} Submit Button
set('submitButtonText', e.target.value)} className={`${inputBase} w-full`} />
set('submitButtonAlign', v)} options={[{ value: 'left', label: 'Left' }, { value: 'center', label: 'Center' }, { value: 'right', label: 'Right' }, { value: 'full', label: 'Full' }]} />
) }