const { useState, useEffect, useRef, useMemo, useContext, createContext } = React;
const CodesContext = createContext({ codes: [], colorForCode: () => CODE_PALETTE[CODE_PALETTE.length - 1] });
function useColorForCode() {return useContext(CodesContext).colorForCode;}
function MapCode({ code, size = "md", onClick, dim }) {
const colorForCode = useColorForCode();
if (!code) return null;
const c = colorForCode(code);
return (
{code}
);
}
function StatusPill({ status }) {
const s = STATUSES[status] || STATUSES.idle;
const color = { ok: 'var(--ok)', info: 'var(--ink-3)', warn: 'var(--warn)', bad: 'var(--bad)', mute: 'var(--ink-4)' }[s.dot];
return (
{s.sym}
{s.label}
);
}
function Checkbox({ checked, indeterminate, onClick, size = 17, hit = 9 }) {
return (
{e.stopPropagation();onClick?.(e);}}
className="check-hit"
style={{ padding: hit, margin: -hit }}>
{checked && }
{!checked && indeterminate && }
);
}
function Popover({ open, onClose, anchorRef, children, align = "right", offset = 6, width = 240 }) {
const ref = useRef(null);
const [pos, setPos] = useState(null);
useEffect(() => {
if (!open) return;
const calc = () => {
const a = anchorRef.current?.getBoundingClientRect();
if (!a) return;
let left = align === "right" ? a.right - width : a.left;
const margin = 8;
const maxLeft = window.innerWidth - width - margin;
if (left > maxLeft) left = maxLeft;
if (left < margin) left = margin;
let top = a.bottom + offset;
const estH = 320;
if (top + estH > window.innerHeight - margin && a.top > estH + margin) {
top = a.top - estH - offset;
}
setPos({ top, left });
};
calc();
const onDoc = (e) => {if (ref.current && !ref.current.contains(e.target) && !anchorRef.current?.contains(e.target)) onClose();};
const onKey = (e) => {if (e.key === 'Escape') onClose();};
document.addEventListener('mousedown', onDoc);
document.addEventListener('keydown', onKey);
window.addEventListener('resize', calc);
window.addEventListener('scroll', calc, true);
return () => {document.removeEventListener('mousedown', onDoc);document.removeEventListener('keydown', onKey);window.removeEventListener('resize', calc);window.removeEventListener('scroll', calc, true);};
}, [open, onClose, anchorRef, align, offset, width]);
if (!open || !pos) return null;
return (
{children}
);
}
function MenuItem({ icon: IconCmp, label, danger, onClick, mono }) {
return (
{e.currentTarget.style.background = 'var(--panel-2)';e.currentTarget.style.color = danger ? 'var(--bad)' : 'var(--ink)';}}
onMouseLeave={(e) => {e.currentTarget.style.background = 'transparent';e.currentTarget.style.color = danger ? 'var(--bad)' : 'var(--ink-2)';}}>
{IconCmp && }
{label}
);
}
function SearchInput({ value, onChange, placeholder, width = 300 }) {
return (
onChange(e.target.value)} placeholder={placeholder || "Search…"} />
{value &&
}
);
}
function SingleCodePicker({ open, onClose, anchorRef, value, onChange, codes, align = "left" }) {
const [query, setQuery] = useState("");
const colorForCode = useColorForCode();
useEffect(() => {if (open) setQuery("");}, [open]);
const filtered = codes.filter((c) => !query || c.toLowerCase().includes(query.toLowerCase()));
return (
setQuery(e.target.value)} style={{ fontSize: 13 }} autoFocus />
{filtered.map((c) => {
const on = c === value;
const col = colorForCode(c);
return (
{onChange(c);onClose();}} style={{
display: 'flex', alignItems: 'center', gap: 10, padding: '8px 9px', borderRadius: 5, cursor: 'pointer',
background: on ? 'var(--panel-2)' : 'transparent'
}}
onMouseEnter={(e) => e.currentTarget.style.background = 'var(--panel-2)'}
onMouseLeave={(e) => {if (!on) e.currentTarget.style.background = 'transparent';}}>
{c}
{on && }
);
})}
{filtered.length === 0 &&
// no matches
}
);
}
// Confirm modal — for resource-heavy bulk actions
function ConfirmModal({ open, onClose, onConfirm, title, message, confirmLabel = "Confirm", danger, summary }) {
if (!open) return null;
return (
<>
{title}
{message}
{summary &&
{summary}
}
>);
}
Object.assign(window, { CodesContext, useColorForCode, MapCode, StatusPill, Checkbox, Popover, MenuItem, SearchInput, SingleCodePicker, ConfirmModal });