// UI primitives — LIGHT enterprise theme. const { useState, useEffect, useRef, useMemo, useCallback, createContext, useContext } = React; // ---------- Toast ---------- const ToastCtx = createContext(null); function ToastProvider({ children }) { const [toasts, setToasts] = useState([]); const push = useCallback((t) => { const id = Math.random().toString(36).slice(2, 9); setToasts((cur) => [...cur, { id, ...t }]); setTimeout(() => setToasts((cur) => cur.filter((x) => x.id !== id)), t.duration || 3800); }, []); return ( {children}
{toasts.map((t) => (
{t.title}
{t.body &&
{t.body}
}
))}
); } const useToast = () => useContext(ToastCtx); // ---------- Button ---------- function Button({ children, variant = 'default', size = 'md', icon, iconRight, className = '', onClick, disabled, type = 'button', }) { const sizes = { sm: 'h-7 px-2.5 text-xs gap-1.5 rounded-md', md: 'h-9 px-3.5 text-sm gap-2 rounded-lg', lg: 'h-11 px-5 text-sm gap-2 rounded-lg', }; const variants = { default: 'bg-white text-ink-800 border border-line hover:bg-ink-50 hover:border-ink-200', primary: 'gradient-bg text-white border border-brand-700/30 hover:opacity-95 shadow-sm', soft: 'bg-brand-50 text-brand-600 border border-brand-100 hover:bg-brand-100', amber: 'bg-ai-50 text-ai-700 border border-ai-100 hover:bg-ai-100', ghost: 'text-ink-700 hover:bg-ink-50 border border-transparent', success: 'bg-emerald-50 text-emerald-700 border border-emerald-100 hover:bg-emerald-100', danger: 'bg-rose-50 text-rose-700 border border-rose-100 hover:bg-rose-100', whatsapp: 'bg-emerald-600 text-white hover:bg-emerald-700 border border-emerald-700/40', call: 'bg-brand-500 text-white border border-brand-600/40 hover:bg-brand-600', }; return ( ); } // ---------- IconButton ---------- function IconButton({ icon, onClick, title, variant = 'default', size = 'md', className = '' }) { const sizes = { sm: 'h-7 w-7', md: 'h-9 w-9', lg: 'h-10 w-10' }; const variants = { default: 'bg-white border border-line text-ink-700 hover:bg-ink-50 hover:text-ink-900', ghost: 'text-ink-500 hover:bg-ink-50 hover:text-ink-900 border border-transparent', primary: 'gradient-bg text-white border border-brand-700/30 shadow-sm', soft: 'bg-brand-50 text-brand-600 border border-brand-100 hover:bg-brand-100', }; return ( ); } // ---------- Pill ---------- function Pill({ children, tone = 'slate', icon, size = 'md', className = '' }) { const tones = { slate: 'bg-ink-50 text-ink-700 border-line', brand: 'bg-brand-50 text-brand-600 border-brand-100', amber: 'bg-ai-50 text-ai-700 border-ai-100', emerald: 'bg-emerald-50 text-emerald-700 border-emerald-100', sky: 'bg-sky-50 text-sky-700 border-sky-100', rose: 'bg-rose-50 text-rose-700 border-rose-100', fuchsia: 'bg-fuchsia-50 text-fuchsia-700 border-fuchsia-100', white: 'bg-white text-ink-700 border-line', }; const sizes = { sm: 'h-5 text-[10px] px-1.5 gap-1', md: 'h-6 text-xs px-2 gap-1.5', lg: 'h-7 text-[13px] px-2.5 gap-1.5', }; return ( {icon && {icon}} {children} ); } function StageBadge({ stage }) { const map = { 'Applied': { tone: 'slate', icon: }, 'AI Screened': { tone: 'brand', icon: }, 'Shortlisted': { tone: 'amber', icon: }, 'Interview': { tone: 'sky', icon: }, 'Submitted': { tone: 'fuchsia', icon: }, 'Hired': { tone: 'emerald', icon: }, }; const cfg = map[stage] || { tone: 'slate', icon: null }; return {stage}; } function RecoBadge({ value }) { if (value === 'Advance') return }>Advance; if (value === 'Hold') return }>Hold; if (value === 'Reject') return }>Reject; return ; } function PriorityBadge({ value }) { if (value === 'High') return } size="sm">Alta; if (value === 'Medium') return Media; return Baja; } // ---------- Avatar ---------- function Avatar({ name, initials, color = 'from-indigo-500 to-violet-400', size = 36, online, ring }) { const ini = initials || (name ? name.split(' ').filter(Boolean).slice(0, 2).map((s) => s[0]).join('') : '?'); return (
{ini}
{online && ( )}
); } // ---------- Card ---------- function Card({ children, className = '', interactive, onClick }) { return (
{children}
); } // ---------- KPI ---------- function KpiCard({ label, value, sub, icon, trend, accent }) { return ( {accent && (
)}
{label}
{icon}
{value}
{trend && ( {trend.dir === 'up' ? : trend.dir === 'down' ? : null} {trend.value} )}
{sub &&
{sub}
} ); } // ---------- Sparkline ---------- function Sparkline({ data, color = '#4f46e5', height = 28, width = 120 }) { const max = Math.max(...data), min = Math.min(...data); const span = Math.max(1, max - min); const pts = data.map((v, i) => { const x = (i / (data.length - 1)) * width; const y = height - ((v - min) / span) * (height - 4) - 2; return `${x},${y}`; }).join(' '); const area = `M0,${height} L${pts.split(' ').join(' L')} L${width},${height} Z`; const gid = `sg-${color.replace('#', '')}`; return ( ); } // ---------- Match bar ---------- function MatchBar({ value, size = 'md', showLabel = true }) { const color = value >= 90 ? '#4f46e5' : value >= 80 ? '#7c3aed' : value >= 70 ? '#a855f7' : value >= 60 ? '#d97706' : '#ef4444'; const heights = { sm: 4, md: 6, lg: 8 }; return (
{showLabel &&
{value}%
}
); } // ---------- Tabs ---------- function Tabs({ tabs, value, onChange }) { return (
{tabs.map((t) => ( ))}
); } // ---------- Input ---------- function Input({ value, onChange, placeholder, icon, className = '', size = 'md', type = 'text', onKeyDown }) { const sizes = { sm: 'h-8 text-xs', md: 'h-9 text-sm', lg: 'h-11 text-sm' }; return (
{icon && {icon}} onChange?.(e.target.value)} onKeyDown={onKeyDown} type={type} placeholder={placeholder} className="bg-transparent outline-none flex-1 text-ink-900 placeholder:text-ink-400" />
); } // ---------- Checkbox ---------- function Check({ checked, onChange, label, className = '' }) { return ( ); } // ---------- Funnel ---------- function Funnel({ data }) { const max = Math.max(...data.map((d) => d.count)); return (
{data.map((d, i) => (
{d.stage}
{d.count.toLocaleString()} {d.conv}%
{i < data.length - 1 && (
)}
))}
); } // ---------- Skeleton ---------- function Skeleton({ className = '' }) { return
; } // ---------- Radar ---------- function Radar({ axes, data, size = 240 }) { const cx = size / 2, cy = size / 2; const r = size / 2 - 30; const n = axes.length; const angle = (i) => (Math.PI * 2 * i) / n - Math.PI / 2; const polyPoints = (vals) => vals.map((v, i) => { const rr = (v / 100) * r; return `${cx + Math.cos(angle(i)) * rr},${cy + Math.sin(angle(i)) * rr}`; }).join(' '); return ( {[20, 40, 60, 80, 100].map((p) => ( { const rr = (p / 100) * r; return `${cx + Math.cos(angle(i)) * rr},${cy + Math.sin(angle(i)) * rr}`; }).join(' ')} /> ))} {axes.map((_, i) => ( ))} {axes.map((label, i) => { const lx = cx + Math.cos(angle(i)) * (r + 18); const ly = cy + Math.sin(angle(i)) * (r + 18); return ( {label} ); })} {data.candidate.map((v, i) => { const rr = (v / 100) * r; return ; })} ); } // ---------- Modal ---------- function Modal({ open, onClose, children, size = 'md', className = '' }) { useEffect(() => { if (!open) return; const h = (e) => { if (e.key === 'Escape') onClose?.(); }; document.addEventListener('keydown', h); return () => document.removeEventListener('keydown', h); }, [open, onClose]); if (!open) return null; const widths = { sm: 'max-w-md', md: 'max-w-xl', lg: 'max-w-3xl', xl: 'max-w-5xl' }; return (
{children}
); } function ModalHeader({ icon, title, subtitle, onClose }) { return (
{icon &&
{icon}
}
{title}
{subtitle &&
{subtitle}
}
} variant="ghost" onClick={onClose} />
); } function Divider({ className = '' }) { return
; } // ---------- Section header ---------- function SectionHeader({ kicker, title, action }) { return (
{kicker &&
{kicker}
} {title &&

{title}

}
{action}
); } Object.assign(window, { ToastProvider, useToast, Button, IconButton, Pill, StageBadge, RecoBadge, PriorityBadge, Avatar, Card, SectionHeader, KpiCard, Sparkline, MatchBar, Tabs, Input, Check, Funnel, Skeleton, Radar, Modal, ModalHeader, Divider, });