import type { LucideIcon } from 'lucide-react'; import { ChevronDown } from 'lucide-react'; /** * One titled block of Administration. * * Every setting gets one, so the second and third cost nothing to add — as against a single form that * has to be broken up the first time anything joins it. * * ── `status` is top-right, and it is for things that come and go ── * * Beside the title rather than under the controls: it is a remark about the block, not part of what * you are doing, and put in the flow it pushes everything below it down by a line and then pulls it * back up — a layout that jumps every time somebody saves. * * Each block renders its OWN Block, rather than the panel wrapping them. The status belongs to the * setting that produced it, and lifting that state up to the panel would put one block's timer in a * component that has nothing to do with it. * * ── Collapsing is CONTROLLED, and that is the whole design ── * * This component does not decide when a block is finished — it cannot, because "finished" is different * for every setting and only the block itself knows. So the owner passes `collapsed` and gets told * about a click, and what remains here is the chrome: a header that is a button, and children that are * not rendered while it is shut. * * NOT RENDERED rather than hidden. A collapsed block's children keep polling if they are merely * display:none, which is how a settled panel goes on making requests all afternoon about something * nobody is looking at. */ export const Block = ({ icon: Icon, title, hint, status, collapsible = false, collapsed = false, onCollapsedChange, children, }: { icon: LucideIcon; title: string; hint: string; /** Top-right. Usually ephemeral — see `useFlash`. */ status?: React.ReactNode; /** The header becomes a button. The owner decides when this is worth offering. */ collapsible?: boolean; collapsed?: boolean; onCollapsedChange?: (collapsed: boolean) => void; children?: React.ReactNode; }) => { const shut = collapsible && collapsed; const header = (

{title} {collapsible && ( )}

{/* The hint is what the block is FOR, so it stays whether or not the block is open — a shut row with only a title says nothing to somebody who has not opened it before. */}

{hint}

{/* Reserves nothing when empty. The status is short and the title is not, so there is no reflow to guard against — and an always-present placeholder would read as a field. */} {status &&
{status}
}
); return (
{collapsible ? ( ) : ( header )} {!shut && children}
); };