import { useState, useRef, useEffect, type ComponentType } from 'react'; import { motion } from 'motion/react'; import { ArrowRight } from 'lucide-react'; import { YoutubeFilled, InstagramFilled, FacebookFilled, GlobeFilled, VideoFilled, MessageFilled, CheckFilled, CrossFilled, WarningFilled, } from '../icons/FilledIcons'; import { SectionWrapper } from '../report/ui/SectionWrapper'; import type { BrandGuide, ColorSwatch } from '../../types/plan'; import type { BrandInconsistency } from '../../types/report'; interface BrandingGuideProps { data: BrandGuide; } const tabItems = [ { key: 'visual', label: 'Visual Identity', labelKr: '비주얼 아이덴티티' }, { key: 'tone', label: 'Tone & Voice', labelKr: '톤 & 보이스' }, { key: 'channels', label: 'Channel Rules', labelKr: '채널별 규칙' }, { key: 'consistency', label: 'Brand Consistency', labelKr: '브랜드 일관성' }, ] as const; type TabKey = (typeof tabItems)[number]['key']; const channelIconMap: Record> = { youtube: YoutubeFilled, instagram: InstagramFilled, facebook: FacebookFilled, globe: GlobeFilled, video: VideoFilled, messagesquare: MessageFilled, }; function getChannelIcon(icon: string) { return channelIconMap[icon.toLowerCase()] ?? GlobeFilled; } const statusColor: Record = { correct: 'bg-[#F3F0FF] text-[#4A3A7C] border-[#D5CDF5]', incorrect: 'bg-[#FFF0F0] text-[#7C3A4B] border-[#F5D5DC]', missing: 'bg-slate-100 text-slate-500 border-slate-200', }; const statusLabel: Record = { correct: 'Correct', incorrect: 'Incorrect', missing: 'Missing', }; /* ─── Color Swatch Editor Popover ─── */ function ColorSwatchCard({ swatch, onUpdate }: { swatch: ColorSwatch; onUpdate: (newHex: string) => void }) { const [open, setOpen] = useState(false); const [hexInput, setHexInput] = useState(swatch.hex); const popoverRef = useRef(null); const colorInputRef = useRef(null); // Close on outside click useEffect(() => { if (!open) return; function handleClick(e: MouseEvent) { if (popoverRef.current && !popoverRef.current.contains(e.target as Node)) { setOpen(false); } } document.addEventListener('mousedown', handleClick); return () => document.removeEventListener('mousedown', handleClick); }, [open]); // Sync hex input when swatch changes externally useEffect(() => { setHexInput(swatch.hex); }, [swatch.hex]); const applyHex = (value: string) => { const normalized = value.startsWith('#') ? value : `#${value}`; if (/^#[0-9A-Fa-f]{6}$/.test(normalized)) { onUpdate(normalized.toUpperCase()); setHexInput(normalized.toUpperCase()); } }; return (
{/* Swatch — click to open */}

{swatch.hex}

{swatch.name}

{swatch.usage}

{/* Edit Popover */} {open && (
{/* Native color wheel */}
{ onUpdate(e.target.value.toUpperCase()); setHexInput(e.target.value.toUpperCase()); }} className="w-10 h-10 rounded-xl border border-slate-200 cursor-pointer p-0.5" /> 색상 선택
{/* Hex input */}
# setHexInput(`#${e.target.value}`)} onBlur={(e) => applyHex(e.target.value)} onKeyDown={(e) => { if (e.key === 'Enter') applyHex(hexInput); }} maxLength={6} placeholder="6C5CE7" className="flex-1 font-mono text-sm border border-slate-200 rounded-lg px-2 py-1.5 text-[#0A1128] focus:outline-none focus:ring-2 focus:ring-[#6C5CE7]/30 focus:border-[#6C5CE7]" />
)}
); } /* ─── Visual Identity Tab ─── */ function VisualIdentityTab({ data }: { data: BrandGuide }) { const [colors, setColors] = useState(data.colors); const handleColorUpdate = (idx: number, newHex: string) => { setColors((prev) => prev.map((c, i) => i === idx ? { ...c, hex: newHex } : c)); }; return ( {/* Color Palette */}

Color Palette

{colors.map((swatch: ColorSwatch, idx: number) => ( handleColorUpdate(idx, newHex)} /> ))}
{/* Typography */}

Typography

{data.fonts.map((spec) => (

{spec.family}

{spec.sampleText}

{spec.weight} ·{' '} {spec.usage}

))}
{/* Logo Rules — DO / DON'T split columns */}

Logo Rules

{/* DO Column */}
DO
{data.logoRules.filter((r) => r.correct).map((rule) => (

{rule.rule}

{rule.description}

))}
{/* DON'T Column */}
DON'T
{data.logoRules.filter((r) => !r.correct).map((rule) => (

{rule.rule}

{rule.description}

))}
); } /* ─── Tone & Voice Tab ─── */ function ToneVoiceTab({ tone }: { tone: BrandGuide['toneOfVoice'] }) { return ( {/* Personality */}

Personality

{tone.personality.map((trait) => ( {trait} ))}
{/* Communication Style */}

Communication Style

{tone.communicationStyle}

{/* DO / DON'T */}

DO

{tone.doExamples.map((example, i) => (

{example}

))}

DON'T

{tone.dontExamples.map((example, i) => (

{example}

))}
); } /* ─── Channel Rules Tab ─── */ function ChannelRulesTab({ channels }: { channels: BrandGuide['channelBranding'] }) { return (
{channels.map((ch) => { const Icon = getChannelIcon(ch.icon); return (
{/* Header */}

{ch.channel}

{statusLabel[ch.currentStatus]}
{/* Specs */}

Profile Photo

{ch.profilePhoto}

Banner Spec

{ch.bannerSpec}

Bio Template

{ch.bioTemplate}

); })}
); } /* ─── Brand Consistency Tab (accordion) ─── */ function BrandConsistencyTab({ inconsistencies }: { inconsistencies: BrandInconsistency[] }) { const [expanded, setExpanded] = useState(0); return (
{inconsistencies.map((item, i) => (
{/* Header */} {/* Expanded */} {expanded === i && (
{item.values.map((v) => (
{v.channel} {v.value} {v.isCorrect ? ( ) : ( )}
))}
{/* Impact */}

Impact

{item.impact}

{/* Recommendation */}

Recommendation

{item.recommendation}

)}
))}
); } /* ─── Main Component ─── */ export default function BrandingGuide({ data }: BrandingGuideProps) { const [activeTab, setActiveTab] = useState('visual'); return ( {/* Tabs */}
{tabItems.map((tab) => ( ))}
{activeTab === 'visual' && } {activeTab === 'tone' && } {activeTab === 'channels' && } {activeTab === 'consistency' && ( )}
); }