import { useState, type ReactNode } from "react"; import { RotateCcw } from "lucide-react"; import { Card, CardHeader, CardTitle, CardDescription, CardContent, } from "./card"; import { Accordion, AccordionItem, AccordionContent, AccordionTrigger, } from "./accordion"; import { Button } from "./button"; import { TemplateGrid } from "./template-grid"; import { ProIndicator } from "@/components/pro/pro-indicator"; import { AlertDialog, AlertDialogContent, AlertDialogHeader, AlertDialogTitle, AlertDialogDescription, AlertDialogFooter, AlertDialogCancel, AlertDialogAction, } from "./alert-dialog"; export const APPEARANCE_GRID = "grid min-w-0 grid-cols-1 items-start gap-6 xl:grid-cols-[minmax(0,1fr)_minmax(0,1.2fr)]"; export const APPEARANCE_FIELD_ROW = "grid min-w-0 grid-cols-[minmax(0,1fr)_min(60%,15rem)] items-center gap-4"; export function AppearanceChoices({ value, options, onChange, label = "Design choices", }: { value: string; options: { value: string; label: string; icon?: ReactNode }[]; onChange: (value: string) => void; label?: string; }) { return (
{options.map((option) => ( ))}
); } export function AppearanceSection({ id, title, summary, pro = false, children, }: { id: string; title: string; summary?: string; pro?: boolean; children: ReactNode; }) { return ( {title} {pro && ( <> (Pro) )} {summary && ( {summary} )}
{children}
); } function ReplacementDialog({ open, onClose, onApply, name, }: { open: boolean; onClose: () => void; onApply: () => void; name: string; }) { return ( { if (!value) onClose(); }} > Replace this design? Applying {name} replaces the affected draft appearance values, including manual adjustments. Other feature settings stay unchanged. Nothing is saved until you select Save Changes. Keep current design Apply design ); } export function AppearanceControls({ children, onReset, }: { children: ReactNode; onReset?: () => void; }) { const [confirmReset, setConfirmReset] = useState(false); return (
Design controls Adjust the appearance of this feature.
{onReset && ( )}
{children} setConfirmReset(false)} name="the default design" onApply={() => { onReset?.(); setConfirmReset(false); }} />
); } export interface AppearancePreset { id: string; name: string; description: string; values: Partial; isPro?: boolean; } export function matchesAppearance( current: T, expected: Partial, ) { const normalize = (value: unknown) => typeof value === "string" && value.startsWith("#") ? value.toLowerCase() : value === "inherit" ? "" : value; return (Object.keys(expected) as (keyof T)[]).every( (key) => normalize(current[key]) === normalize(expected[key]), ); } export function AppearancePresets({ feature, current, presets, baseValues, onApply, isPro = false, }: { feature: string; current: T; presets: AppearancePreset[]; baseValues?: Partial; onApply: (values: Partial) => void; isPro?: boolean; }) { const [pending, setPending] = useState(null); const visible = presets.filter((preset) => !preset.isPro || isPro); const values = (preset: AppearancePreset) => ({ ...baseValues, ...preset.values, }); const selected = visible.find((preset) => matchesAppearance(current, values(preset)), ); const pendingPreset = visible.find((preset) => preset.id === pending); return ( Design presets Choose a starting point, then fine-tune the controls below.

Current design: {selected?.name || "Customized"}

{ const preset = visible.find((item) => item.id === id); if (!preset || selected?.id === id) return; if (!selected) setPending(id); else onApply(values(preset)); }} /> setPending(null)} name={pendingPreset?.name || "this preset"} onApply={() => { if (pendingPreset) onApply(values(pendingPreset)); setPending(null); }} />
); }