/** * Shipment Tracking Appearance Tab * * Full appearance customization with live preview. * Follows the Product Addons Customization Tab pattern. * - Customize Design on the left with collapsible accordion sections * - Live Preview on the right (sticky) * - Free: Colors + Form Content * - Pro: Typography, Spacing & Dimensions, Layout */ import { useState, useMemo } from "react" import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" import { Label } from "@/components/ui/label" import { Input } from "@/components/ui/input" import { Textarea } from "@/components/ui/textarea" import { Button } from "@/components/ui/button" import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select" import { Collapsible, CollapsibleContent, CollapsibleTrigger, } from "@/components/ui/collapsible" import { LivePreview } from "@/components/ui/live-preview" import { ChevronDown, Code, Minus, Palette, Plus, RotateCcw } from "lucide-react" import { ProIndicator } from "@/components/pro/pro-indicator" import { GlobalColorPicker } from "@/components/ui/global-color-picker" import { GlobalTypographyPicker } from "@/components/ui/global-typography-picker" import { getAllGoogleFontsPreviewUrls } from "@/lib/google-fonts" import { usePro } from "@/contexts/pro-context" import { TrackingSettings, TrackingFormCustomization, defaultCustomization, quickStartPresets, } from "../../types" interface AppearanceTabProps { settings: TrackingSettings updateSetting: (key: K, value: TrackingSettings[K]) => void } /* ------------------------------------------------------------------ */ /* Reusable inline helpers */ /* ------------------------------------------------------------------ */ function NumberStepper({ label, value, onChange, min, max, step = 1, unit = 'px', }: { label: string value: number onChange: (v: number) => void min: number max: number step?: number unit?: string }) { const clamp = (v: number) => Math.min(max, Math.max(min, v)) return (
{ const parsed = parseInt(e.target.value, 10) if (!isNaN(parsed)) onChange(clamp(parsed)) }} className="h-8 w-14 text-center text-sm border-x bg-transparent outline-none" />
) } function SegmentedControl({ value, onChange, options, }: { value: T onChange: (v: T) => void options: { value: T; label: string }[] }) { return (
{options.map(opt => ( ))}
) } /* ------------------------------------------------------------------ */ /* Main component */ /* ------------------------------------------------------------------ */ export function AppearanceTab({ settings, updateSetting }: AppearanceTabProps) { const { isPro } = usePro() const c = settings.customization || defaultCustomization const [expandedSections, setExpandedSections] = useState>({ content: false, colors: false, typography: false, spacing: false, layout: false, }) const googleFontsUrls = useMemo(() => getAllGoogleFontsPreviewUrls(), []) const updateC = (key: K, value: TrackingFormCustomization[K]) => { updateSetting('customization', { ...c, [key]: value }) } const resetCustomization = () => { updateSetting('customization', { ...defaultCustomization }) } const applyPreset = (presetId: string) => { const preset = quickStartPresets.find(p => p.id === presetId) if (preset) { const updated = { ...c } Object.entries(preset.customization).forEach(([key, value]) => { (updated as any)[key] = value }) updateSetting('customization', updated) } } const toggleSection = (section: string) => { setExpandedSections(prev => ({ ...prev, [section]: !prev[section] })) } /* ------------------------------------------------------------------ */ /* Live Preview */ /* ------------------------------------------------------------------ */ const renderLivePreview = () => { const formStyle: React.CSSProperties = { maxWidth: `${c.formMaxWidth}px`, margin: c.formAlignment === 'center' ? '0 auto' : c.formAlignment === 'right' ? '0 0 0 auto' : '0', padding: `${c.formPadding * 0.75}px`, background: c.formBackground, borderRadius: `${c.formBorderRadius}px`, border: `1px solid ${c.formBorderColor}`, fontFamily: c.fontFamily || undefined, boxShadow: '0 1px 3px 0 rgb(0 0 0 / 0.05)', } const titleStyle: React.CSSProperties = { fontSize: `${c.titleFontSize * 0.85}px`, fontWeight: c.titleFontWeight, color: c.titleColor, textAlign: c.titleAlignment, marginBottom: '4px', fontFamily: c.fontFamily || undefined, } const descStyle: React.CSSProperties = { fontSize: `${c.descriptionFontSize * 0.85}px`, color: c.descriptionColor, textAlign: c.titleAlignment, marginBottom: `${c.fieldSpacing * 0.75}px`, fontFamily: c.fontFamily || undefined, } const labelStyle: React.CSSProperties = { display: 'block', fontSize: `${c.labelFontSize * 0.85}px`, fontWeight: c.labelFontWeight, color: c.labelColor, marginBottom: '4px', fontFamily: c.fontFamily || undefined, } const inputStyle: React.CSSProperties = { width: '100%', padding: `${c.inputPadding * 0.75}px`, border: `1px solid ${c.inputBorderColor}`, borderRadius: `${c.inputBorderRadius}px`, fontSize: `${c.inputFontSize * 0.85}px`, color: c.inputTextColor, background: c.inputBackground, outline: 'none', boxSizing: 'border-box', fontFamily: c.fontFamily || undefined, } const buttonStyle: React.CSSProperties = { width: '100%', padding: `${c.buttonPadding * 0.75}px`, background: c.buttonBackground, color: c.buttonTextColor, border: 'none', borderRadius: `${c.buttonBorderRadius}px`, fontSize: `${c.buttonFontSize * 0.85}px`, fontWeight: c.buttonFontWeight, cursor: 'pointer', fontFamily: c.fontFamily || undefined, } return (
{googleFontsUrls.map((url, i) => ( ))}

{settings.form_title || 'Track Your Order'}

{settings.form_description || 'Enter your tracking number below to see the delivery status of your order.'}

) } /* ------------------------------------------------------------------ */ /* Render */ /* ------------------------------------------------------------------ */ return (
{/* Left Column */}
{/* Customize Design */}
Customize Design Fine-tune every aspect of your addon appearance
{/* Quick Start Presets */}
Quick start: {quickStartPresets.map(preset => ( ))}
{/* Form Content — Free */} toggleSection('content')}> Form Content
updateSetting('form_title', e.target.value)} placeholder="Track Your Order" />