// VMAX prototype — shared widgets used across both apps: // Avatar, Toaster/pushToast, PinPad, PinDialog, EmptyState, Field, TextInput, // NumberInput, Toggle, Select. (function () { const { Icon } = window.VMAXUI; function Avatar({ name, size = 40, tone = 'mint', style }) { const bg = tone === 'red' ? 'var(--vmax-red)' : tone === 'ground' ? 'var(--surface-ground)' : 'var(--vmax-mint)'; const fg = tone === 'red' ? '#fff' : 'var(--vmax-red-deep)'; return {(name || '?')[0]}; } // ---- Toasts (module event bus) ---- const bus = new EventTarget(); function pushToast(msg, tone) { bus.dispatchEvent(new CustomEvent('t', { detail: { msg, tone, id: Math.random() } })); } function Toaster() { const [items, setItems] = React.useState([]); React.useEffect(() => { const on = (e) => { const it = e.detail; setItems((a) => [...a, it]); setTimeout(() => setItems((a) => a.filter((x) => x.id !== it.id)), 2600); }; bus.addEventListener('t', on); return () => bus.removeEventListener('t', on); }, []); return (
{items.map((it) => (
{it.msg}
))}
); } // ---- PIN pad ---- function PinPad({ value, onChange, max = 4 }) { const press = (d) => { if (value.length < max) onChange(value + d); }; const keys = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '', '0', 'del']; return (
{keys.map((k, i) => k === '' ?
: ( ))}
); } function Dots({ n, max = 4, error }) { return (
{Array.from({ length: max }).map((_, i) => ( ))}
); } // PIN dialog. mode 'verify' (checks via verify(pin)) or 'set' (captures a new PIN). function PinDialog({ person, title, sub, mode = 'verify', verify, onSuccess, onClose }) { const [pin, setPin] = React.useState(''); const [err, setErr] = React.useState(false); React.useEffect(() => { setPin(''); setErr(false); }, [person && person.id]); React.useEffect(() => { if (pin.length === 4) { if (mode === 'set') { const v = pin; setTimeout(() => onSuccess(v), 120); } else if (verify(pin)) { const v = pin; setTimeout(() => onSuccess(v), 120); } else { setErr(true); setTimeout(() => { setErr(false); setPin(''); }, 480); } } else setErr(false); }, [pin]); const { Sheet } = window.VMAXUI; return (
{person && }
{title || (person && person.name)}
{sub &&
{sub}
}
); } function EmptyState({ icon = 'box', title, text, action, tone = 'soft' }) { return (
{title}
{text &&

{text}

} {action &&
{action}
}
); } function Field({ label, hint, children, style }) { return ( ); } const inputBase = { width: '100%', border: '1.5px solid var(--vmax-line)', borderRadius: 'var(--radius-md)', padding: '11px 13px', fontFamily: 'var(--font-body)', fontSize: 14.5, outline: 'none', background: 'var(--surface-card)', color: 'var(--vmax-ink)' }; function TextInput({ value, onChange, placeholder, autoFocus, type = 'text', style }) { return onChange(e.target.value)} style={{ ...inputBase, ...style }} onFocus={(e) => (e.target.style.borderColor = 'var(--vmax-ink-soft)')} onBlur={(e) => (e.target.style.borderColor = 'var(--vmax-line)')} />; } function NumberInput({ value, onChange, prefix, suffix, width = 96 }) { return (
{prefix && {prefix}} onChange(e.target.value === '' ? '' : Number(e.target.value))} style={{ border: 'none', outline: 'none', width, padding: '11px 0', fontFamily: 'var(--font-display)', fontWeight: 700, fontSize: 16, textAlign: 'right', background: 'transparent', color: 'var(--vmax-ink)' }} /> {suffix && {suffix}}
); } function Toggle({ on, onChange, label }) { return ( ); } function Select({ value, onChange, options, style }) { return ( ); } function NotifBadge({ count, dot, tone = 'red', style }) { if (!count && !dot) return null; const bg = tone === 'amber' ? 'var(--vmax-amber)' : 'var(--vmax-red)'; return {dot ? '' : count}; } window.VMAXW = { Avatar, pushToast, Toaster, PinPad, Dots, PinDialog, EmptyState, Field, TextInput, NumberInput, Toggle, Select, NotifBadge }; })();