// ============================================================ // JuFlow — Shared UI Components // ============================================================ const { useState, useEffect, useRef, useCallback } = React; const U = window.JFUtils; // ─── ICONS (safe wrapper around lucide-react UMD) ───────── const _LR = window.LucideReact || window.lucideReact || {}; const Icon = ({ name, size = 18, color, strokeWidth = 2, style = {}, className = '' }) => { const Comp = _LR[name]; if (!Comp) return ; return ; }; // ─── AVATAR ─────────────────────────────────────────────── const Avatar = ({ name, color, size = 36, src, style = {} }) => { const bg = color || '#0A1F44'; const sz = size; return (
{src ? {name} : U.initials(name) }
); }; // ─── BADGE (status pill) ────────────────────────────────── const Badge = ({ status, label, style = {} }) => { const colors = U.statusColor(status); return ( {label || U.statusLabel(status)} ); }; // ─── TAG CHIP ───────────────────────────────────────────── const TAG_COLORS = { 'VIP': { bg:'#FFF7ED', text:'#C2410C', border:'#FED7AA' }, 'Aniversariante': { bg:'#FDF4FF', text:'#7E22CE', border:'#E9D5FF' }, 'Inadimplente': { bg:'#FEF2F2', text:'#B91C1C', border:'#FECACA' }, 'Regular': { bg:'#F0FDF4', text:'#15803D', border:'#BBF7D0' }, }; const TagChip = ({ tag }) => { const c = TAG_COLORS[tag] || { bg:'#F3F4F6', text:'#374151', border:'#D1D5DB' }; return ( {tag === 'VIP' && '★ '}{tag} ); }; // ─── BUTTON ─────────────────────────────────────────────── const Button = ({ children, variant='primary', size='md', onClick, disabled, icon, style={}, type='button' }) => { const [hovered, setHovered] = useState(false); const sizes = { sm:'6px 12px', md:'9px 18px', lg:'12px 24px' }; const fSizes = { sm:12, md:13, lg:14 }; const variants = { primary: { bg: hovered ? '#E55A20' : '#FF6B35', color:'#fff', border:'transparent', shadow: hovered ? '0 4px 14px rgba(255,107,53,.35)' : '0 2px 8px rgba(255,107,53,.25)' }, secondary: { bg: hovered ? '#f0f4f8' : '#fff', color:'#0A1F44', border:'#E2E8F0', shadow: 'none' }, ghost: { bg: hovered ? '#f0f4f8' : 'transparent', color:'#0A1F44', border:'transparent', shadow:'none' }, danger: { bg: hovered ? '#DC2626' : '#EF4444', color:'#fff', border:'transparent', shadow:'none' }, outline: { bg:'transparent', color: hovered ? '#FF6B35' : '#0A1F44', border: hovered ? '#FF6B35' : '#CBD5E1', shadow:'none' }, }; const v = variants[variant] || variants.primary; return ( ); }; // ─── INPUT ──────────────────────────────────────────────── const Input = ({ label, value, onChange, placeholder, type='text', icon, required, error, style={}, inputStyle={}, autoFocus, onKeyDown }) => { const [focused, setFocused] = useState(false); return (
{label && }
{icon && } onChange && onChange(e.target.value)} placeholder={placeholder} autoFocus={autoFocus} onKeyDown={onKeyDown} onFocus={() => setFocused(true)} onBlur={() => setFocused(false)} style={{ width:'100%', boxSizing:'border-box', padding: icon ? '9px 12px 9px 34px' : '9px 12px', borderRadius:8, fontSize:13, border:`1.5px solid ${error ? '#EF4444' : focused ? '#FF6B35' : '#E2E8F0'}`, outline:'none', color:'#0A1F44', background:'#fff', transition:'border-color .15s', ...inputStyle }} />
{error && {error}}
); }; // ─── SELECT ─────────────────────────────────────────────── const Select = ({ label, value, onChange, options, placeholder, required, style={} }) => { const [focused, setFocused] = useState(false); return (
{label && }
); }; // ─── TEXTAREA ───────────────────────────────────────────── const Textarea = ({ label, value, onChange, placeholder, rows=3, style={} }) => { const [focused, setFocused] = useState(false); return (
{label && }