// Shared UI primitives: Button, IconButton, Pill, Badge, Card, Modal, Toggle, TextInput, Textarea, Avatar. // Export to window.UI. const { useState, useEffect, useRef, useMemo, useCallback } = React; function classnames(...args) { return args.filter(Boolean).join(' '); } function Button({ variant = 'primary', size = 'md', children, className = '', icon = null, iconRight = null, full = false, disabled = false, onClick, type = 'button', ...rest }) { const sizes = { sm: 'h-8 px-3 text-[13px] gap-1.5', md: 'h-10 px-4 text-[14px] gap-2', lg: 'h-12 px-5 text-[15px] gap-2', }; const variants = { primary: 'bg-ink-900 text-white hover:bg-ink-800 active:bg-ink-700 shadow-soft', accent: 'bg-violet text-white hover:opacity-90 active:opacity-100 shadow-soft', ghost: 'bg-transparent text-ink-800 hover:bg-ink-100 active:bg-ink-200', outline: 'bg-white text-ink-900 border border-ink-200 hover:bg-ink-50 active:bg-ink-100', danger: 'bg-white text-red-700 border border-red-200 hover:bg-red-50', soft: 'bg-ink-100 text-ink-900 hover:bg-ink-200', }; return ( ); } function IconButton({ children, className = '', onClick, title, variant = 'ghost', size = 'md', ...rest }) { const sizes = { sm: 'h-8 w-8', md: 'h-9 w-9', lg: 'h-10 w-10' }; const variants = { ghost: 'hover:bg-ink-100 text-ink-700', outline: 'border border-ink-200 bg-white hover:bg-ink-50', solid: 'bg-ink-900 text-white hover:bg-ink-800', }; return ( ); } function Pill({ children, active = false, onClick, className = '' }) { return ( ); } function Badge({ children, variant = 'default', className = '' }) { const variants = { default: 'bg-ink-100 text-ink-700', ready: 'bg-emerald-50 text-emerald-700 border border-emerald-100', draft: 'bg-ink-100 text-ink-700', sent: 'bg-violet-soft text-violet-ink', accent: 'bg-violet text-white', }; return {children}; } function Card({ children, className = '', as: As = 'div', ...rest }) { return {children}; } function Avatar({ initial, color = '#0F0F0F', size = 32, className = '' }) { return (
{initial}
); } function Modal({ open, onClose, children, title = null, sub = null, wide = false }) { useEffect(() => { if (!open) return; const onKey = (e) => { if (e.key === 'Escape') onClose && onClose(); }; document.addEventListener('keydown', onKey); document.body.style.overflow = 'hidden'; return () => { document.removeEventListener('keydown', onKey); document.body.style.overflow = ''; }; }, [open]); if (!open) return null; return (
e.stopPropagation()} className={classnames( 'relative bg-white border border-ink-200 rounded-t-3xl sm:rounded-3xl w-full sm:max-w-lg shadow-pop slide-up', wide && 'sm:max-w-2xl' )} > {(title || sub) && (
{title &&

{title}

} {sub &&

{sub}

}
)}
{children}
); } function Toggle({ on, onChange, size = 'md' }) { const w = size === 'sm' ? 'w-9 h-5' : 'w-11 h-6'; const knob = size === 'sm' ? 'h-4 w-4' : 'h-5 w-5'; const tx = size === 'sm' ? (on ? 'translate-x-4' : 'translate-x-0.5') : (on ? 'translate-x-5' : 'translate-x-0.5'); return ( ); } function TextInput({ value, onChange, placeholder, type = 'text', icon = null, className = '', size = 'md', ...rest }) { const sizes = { sm: 'h-9 text-[13.5px]', md: 'h-11 text-[14.5px]', lg: 'h-12 text-[15px]' }; return (
{icon && {icon}} onChange && onChange(e.target.value)} placeholder={placeholder} className={classnames( 'w-full bg-white border border-ink-200 rounded-full px-4 placeholder:text-ink-400 text-ink-900 transition-colors', 'focus:outline-none focus:border-ink-900', icon && 'pl-10', sizes[size], )} {...rest} />
); } function Textarea({ value, onChange, placeholder, rows = 4, className = '', ...rest }) { return (