/* ============ Shared UI components ============ */
const Pill = ({ tone = 'gray', children }) => (
{children}
);
const Button = ({ variant = 'default', size, icon, children, ...rest }) => {
const cls = ['btn'];
if (variant === 'primary') cls.push('primary');
if (variant === 'tinted') cls.push('tinted');
if (variant === 'bordered') cls.push('bordered');
if (variant === 'danger') cls.push('danger');
if (size === 'lg') cls.push('lg');
return (
);
};
const IconButton = ({ name, size = 16, ...rest }) => (
);
const Segmented = ({ value, onChange, options }) => (
{options.map(o => (
))}
);
const Field = ({ label, children }) => (
{children}
);
const Input = (props) => ;
const Select = ({ options, children, ...rest }) => (
);
const Textarea = (props) => ;
const Checkbox = ({ checked, onChange, ...rest }) => (
onChange(e.target.checked)} {...rest} />
);
/* Modal */
const Modal = ({ title, onClose, children, footer, width }) => {
React.useEffect(() => {
const h = (e) => { if (e.key === 'Escape') onClose(); };
document.addEventListener('keydown', h);
return () => document.removeEventListener('keydown', h);
}, [onClose]);
return (
{ if (e.target === e.currentTarget) onClose(); }}>
{children}
{footer &&
{footer}
}
);
};
/* Stat card */
const Stat = ({ label, value, delta, deltaTone = 'up', iconName, iconColor = 'var(--blue)' }) => (
{iconName && }
{label}
{value}
{delta &&
{delta}
}
);
/* Card */
const Card = ({ title, action, children, pad = true, sub }) => (
{title && (
)}
{children}
);
/* Empty */
const Empty = ({ icon = 'sparkle', title, hint, action }) => (
{title}
{hint &&
{hint}
}
{action}
);
/* Toaster */
const ToastCtx = React.createContext(null);
const ToastProvider = ({ children }) => {
const [items, setItems] = React.useState([]);
const push = React.useCallback((msg) => {
const id = uid();
setItems(prev => [...prev, { id, msg }]);
setTimeout(() => setItems(prev => prev.filter(t => t.id !== id)), 2200);
}, []);
return (
{children}
{items.map(t =>
{t.msg}
)}
);
};
const useToast = () => React.useContext(ToastCtx);
Object.assign(window, { Pill, Button, IconButton, Segmented, Field, Input, Select, Textarea, Checkbox, Modal, Stat, Card, Empty, ToastProvider, useToast });