import { CSSProperties } from 'react'; import { escapeHtml, escapeAttr, safeUrl, cssValue } from '../../utils/escape'; export type CtaVariant = 'primary' | 'outline' | 'ghost'; export interface CtaButton { text: string; href: string; variant?: CtaVariant; target?: '_blank'; } export interface CtaStyleDefaults { primaryBg: string; primaryText: string; outlineText: string; } /** * Read the effective list of CTAs for a section, falling back to legacy * primary/secondary props when ctas[] is absent. New sections write ctas[] * directly; old sections keep rendering until the user touches the settings. */ export function normalizeCtas(props: { ctas?: CtaButton[]; buttonText?: string; buttonHref?: string; secondaryButtonText?: string; secondaryButtonHref?: string; }): CtaButton[] { if (Array.isArray(props.ctas) && props.ctas.length > 0) { return props.ctas.filter((c) => c && (c.text || c.href)); } const legacy: CtaButton[] = []; if (props.buttonText) legacy.push({ text: props.buttonText, href: props.buttonHref || '#', variant: 'primary' }); if (props.secondaryButtonText) legacy.push({ text: props.secondaryButtonText, href: props.secondaryButtonHref || '#', variant: 'outline' }); return legacy; } export function ctaInlineStyle(cta: CtaButton, defaults: CtaStyleDefaults): CSSProperties { const variant = cta.variant || 'primary'; switch (variant) { case 'outline': return { display: 'inline-block', padding: '14px 36px', backgroundColor: 'transparent', color: defaults.outlineText, textDecoration: 'none', borderRadius: '8px', fontWeight: 600, fontSize: '16px', border: `2px solid ${defaults.outlineText}`, }; case 'ghost': return { display: 'inline-block', padding: '14px 24px', backgroundColor: 'transparent', color: defaults.outlineText, textDecoration: 'underline', borderRadius: '8px', fontWeight: 600, fontSize: '16px', }; case 'primary': default: return { display: 'inline-block', padding: '14px 36px', backgroundColor: defaults.primaryBg, color: defaults.primaryText, textDecoration: 'none', borderRadius: '8px', fontWeight: 600, fontSize: '16px', }; } } export function ctaCssString(cta: CtaButton, defaults: CtaStyleDefaults): string { const variant = cta.variant || 'primary'; // Sanitized -- these are raw string-interpolation sinks into style="...". // Callers pass user-controlled design-token colors (e.g. HeroSimple's // buttonBgColor/buttonTextColor/textColor) through CtaStyleDefaults, so // sanitize once here rather than at every call site. const outlineText = cssValue(defaults.outlineText) || '#000000'; const primaryBg = cssValue(defaults.primaryBg) || '#000000'; const primaryText = cssValue(defaults.primaryText) || '#ffffff'; switch (variant) { case 'outline': return `display:inline-block;padding:14px 36px;background-color:transparent;color:${outlineText};text-decoration:none;border-radius:8px;font-weight:600;font-size:16px;border:2px solid ${outlineText}`; case 'ghost': return `display:inline-block;padding:14px 24px;background-color:transparent;color:${outlineText};text-decoration:underline;border-radius:8px;font-weight:600;font-size:16px`; case 'primary': default: return `display:inline-block;padding:14px 36px;background-color:${primaryBg};color:${primaryText};text-decoration:none;border-radius:8px;font-weight:600;font-size:16px`; } } export function ctasToHtml(ctas: CtaButton[], defaults: CtaStyleDefaults): string { return ctas.map((c) => { const target = c.target === '_blank' ? ' target="_blank" rel="noopener noreferrer"' : ''; return `${escapeHtml(c.text || '')}`; }).join(''); }