import { useEffect, useRef, useState } from 'react' // One axis's collapse control, as a stepper. // // Rows and columns are the same thing — a depth in ViewConfig, counting the // levels to show — so one component draws both. // // It used to be one button per level, which is N+1 controls that grow with the // pivot: a 5-by-3 layout rendered ten of them, and the active level was a blue // button somewhere among the greys, so reading the current state meant scanning. // A stepper is three controls whatever the depth, and says where you are rather // than making you find it. The full list is still one click away, for jumping // rather than stepping. export default function DepthButtons({ label, levels, depth, onPick, totalLabel = 'Total', children }) { const [open, setOpen] = useState(false) const boxRef = useRef(null) useEffect(() => { if (!open) return const onDown = (e) => { if (!boxRef.current?.contains(e.target)) setOpen(false) } const onKey = (e) => { if (e.key === 'Escape') setOpen(false) } // Capture phase: the pivot lives in a shadow root and retargets its events, // so a bubbled listener sees the host rather than the real target. document.addEventListener('mousedown', onDown, true) window.addEventListener('keydown', onKey) return () => { document.removeEventListener('mousedown', onDown, true) window.removeEventListener('keydown', onKey) } }, [open]) const max = levels.length // depth is null until the first layout has been read back. Showing the last // level would be a guess; an em dash is honest and the arrows still work. const known = depth != null const current = !known ? '—' : (depth === 0 ? totalLabel : levels[depth - 1]) const step = (d) => onPick(Math.min(max, Math.max(0, d))) // Capped so one long field name cannot push the rest of the toolbar off; // past the cap the label truncates, with the full name in the title. const widest = Math.min(18, Math.max(...[totalLabel, ...levels].map(l => String(l).length))) // One joined control, in the style of the operation panel's // Scale | Recode | Clone. The arrows used to be separate outlined boxes a // few pixels wide, which in dark mode were barely visible and looked the // same enabled or disabled; joined, the axis reads as one object and the // arrows are halves of it rather than specks beside it. const seg = 'px-1.5 py-0.5 leading-none bg-white transition-colors ' + 'text-gray-600 hover:bg-gray-100 disabled:text-gray-300 disabled:hover:bg-white disabled:cursor-default' return (
{label}
{/* Sized to the axis's longest level, not the current one. Sized to its content it resized on every step, so the ▸ moved out from under the cursor between clicks. */}
{children} {open && (
{Array.from({ length: max + 1 }, (_, n) => ( ))}
)}
) } // An on/off control that looks pressed when on — the same state the toolbar's // Operations button uses for "open". Replaces the native checkboxes, which // drew in the light style on the dark theme and were the brightest thing on // the toolbar. export function ToggleChip({ on, onChange, title, children }) { return ( ) }