import React from 'react'; import { variableLabel } from '@/features/cards/editor/variables'; interface SlateLeaf { text: string; bold?: boolean; italic?: boolean; underline?: boolean; // 'primary'|'success'|'warning'|'destructive' 프리셋 토큰 또는 임의 CSS 색(예: '#ED2024') color?: string; } // 프리셋 색 토큰 → 클래스. 카드 에디터는 헥스(#ED2024)를 쓰므로 토큰에 없으면 인라인 style 로 처리. const NAMED_COLOR: Record = { primary: 'text-primary font-bold underline decoration-2 decoration-gray-400 dark:decoration-gray-600', success: 'text-success font-semibold px-1 py-0.5 rounded bg-emerald-50 dark:bg-emerald-950/40 border border-emerald-200/50 dark:border-emerald-900/40', warning: 'text-warning font-semibold px-1 py-0.5 rounded bg-amber-50 dark:bg-amber-950/40 border border-amber-200/50', destructive: 'text-destructive font-semibold', }; interface SlateNode { type?: 'paragraph' | 'block-quote' | 'heading-one' | 'heading-two' | 'list-item'; children: (SlateLeaf | SlateNode)[]; } interface SlateRendererProps { nodes?: any[]; variables?: Record; } export default function SlateRenderer({ nodes, variables = {} }: SlateRendererProps) { if (!nodes || !Array.isArray(nodes)) { return null; } // Helper to replace variable strings like {target_price} or {partner_type} const replaceVariables = (text: string): string => { let result = text; Object.entries(variables).forEach(([key, val]) => { const placeholder = `{${key}}`; if (result.includes(placeholder)) { if (typeof val === 'number') { result = result.replace(new RegExp(placeholder, 'g'), val.toLocaleString('ko-KR')); } else { result = result.replace(new RegExp(placeholder, 'g'), String(val)); } } }); return result; }; const renderLeaf = (leaf: SlateLeaf, key: string) => { const text = replaceVariables(leaf.text); let classes = ''; if (leaf.bold) classes += ' font-bold'; if (leaf.italic) classes += ' italic'; if (leaf.underline) classes += ' underline'; // color 가 프리셋 토큰이면 클래스, 임의 CSS 색(헥스 등)이면 인라인 style 로 적용. const named = leaf.color ? NAMED_COLOR[leaf.color] : undefined; const style = leaf.color && !named ? { color: leaf.color } : undefined; const cls = `${classes}${named ? ` ${named}` : ''}`.trim(); return ( {text} ); }; const renderNode = (node: any, index: number): React.ReactNode => { const key = `node-${index}`; // If it's pure leaf structure (contains no children arrays, but has text attribute) if (node.text !== undefined) { return renderLeaf(node as SlateLeaf, key); } // 변수 노드(void inline): {type:'variable', name}. variables 에 값 있으면 치환, 없으면 라벨 칩. if (node.type === 'variable') { const raw = variables[node.name]; const hasValue = raw !== undefined && raw !== null && raw !== ''; if (hasValue) { const text = typeof raw === 'number' ? raw.toLocaleString('ko-KR') : String(raw); return {text}; } // 값 없으면 치환하지 않고 {라벨} 형태로 그대로 노출. return ( {`{${variableLabel(node.name)}}`} ); } const childrenElements = (node.children || []).map((child: any, childIdx: number) => { if (child.text !== undefined) { return renderLeaf(child as SlateLeaf, `${key}-${childIdx}`); } return renderNode(child, childIdx); }); switch (node.type) { case 'heading-one': return

{childrenElements}

; case 'heading-two': return

{childrenElements}

; case 'block-quote': return (
{childrenElements}
); case 'list-item': return
  • {childrenElements}
  • ; case 'paragraph': default: return

    {childrenElements}

    ; } }; return (
    {nodes.map((node, i) => renderNode(node, i))}
    ); }