// Shared UI primitives: Field, Select, MultiSelect (chip input), Modal, Toast. const Field = ({ label, required, optional, hint, error, children }) => (
{label} {required && *} {optional && (optional)}
{children} {error ?
{error}
: hint ?
{hint}
: null}
); const TextInput = React.forwardRef(({ error, ...props }, ref) => ( )); const Select = ({ value, onChange, options, placeholder = "Select…", error, ...rest }) => ( ); // Multi-select chip input with type-ahead suggestions const MultiSelect = ({ values = [], onChange, options = [], placeholder = "Type to add…", allowCustom = false }) => { const [text, setText] = React.useState(""); const [open, setOpen] = React.useState(false); const [activeIdx, setActiveIdx] = React.useState(0); const inputRef = React.useRef(null); const remaining = options.filter(o => !values.includes(o)); const filtered = text.trim() ? remaining.filter(o => o.toLowerCase().includes(text.toLowerCase())) : remaining; const add = (v) => { if (!v) return; if (!values.includes(v)) onChange([...values, v]); setText(""); setActiveIdx(0); inputRef.current?.focus(); }; const remove = (v) => onChange(values.filter(x => x !== v)); const onKey = (e) => { if (e.key === "Backspace" && !text && values.length) { remove(values[values.length - 1]); } else if (e.key === "Enter") { e.preventDefault(); if (filtered[activeIdx]) add(filtered[activeIdx]); else if (allowCustom && text.trim()) add(text.trim()); } else if (e.key === "ArrowDown") { e.preventDefault(); setActiveIdx(i => Math.min(i + 1, filtered.length - 1)); } else if (e.key === "ArrowUp") { e.preventDefault(); setActiveIdx(i => Math.max(i - 1, 0)); } else if (e.key === "Escape") { setOpen(false); } }; return (
inputRef.current?.focus()}> {values.map(v => ( {v} ))} { setText(e.target.value); setOpen(true); setActiveIdx(0); }} onFocus={() => setOpen(true)} onBlur={() => setTimeout(() => setOpen(false), 120)} onKeyDown={onKey} />
{open && filtered.length > 0 && (
{filtered.slice(0, 8).map((o, i) => ( ))}
)}
); }; const Modal = ({ open, onClose, title, sub, children, footer, size = "md" }) => { React.useEffect(() => { if (!open) return; const onKey = (e) => { if (e.key === "Escape") onClose?.(); }; window.addEventListener("keydown", onKey); return () => window.removeEventListener("keydown", onKey); }, [open, onClose]); if (!open) return null; return (
{ if (e.target.classList.contains("modal-backdrop")) onClose?.(); }}>

{title}

{sub &&
{sub}
}
{children} {footer &&
{footer}
}
); }; const Toast = ({ msg, onDone }) => { React.useEffect(() => { if (!msg) return; const t = setTimeout(onDone, 2400); return () => clearTimeout(t); }, [msg, onDone]); if (!msg) return null; return (
{msg}
); }; const Avatar = ({ name, size = 30 }) => { const initials = name.split(" ").map(s => s[0]).join("").slice(0, 2).toUpperCase(); return
{initials}
; }; const ConfDot = ({ level }) => { const map = { public: "Public", internal: "Internal", restricted: "Restricted", confidential: "Confidential", "client confidential": "Confidential" }; const dataLvl = level === "client confidential" ? "confidential" : level; return {map[level] || level}; }; Object.assign(window, { Field, TextInput, Select, MultiSelect, Modal, Toast, Avatar, ConfDot });