// 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 };
})();