import clsx from "clsx";
/*
* The surface every screen is built from.
*
* Before this there were four panel treatments in the app: `rounded border
* border-rule bg-panel p-5` with an `
`, the same thing
* with `text-[0.95rem] font-medium`, a bare ``
* with no border at all, and a table wrapper that was a panel in everything but
* name. They were all trying to be the same object.
*
* The header is title-left, meta-right. Meta is the keyed idiom — mono, small,
* tracked, dimmed — because it is always a count, a scope or an identifier,
* never prose.
*/
export function Panel({
title,
meta,
actions,
tone,
children,
bodyless,
className,
}: {
title?: string;
meta?: React.ReactNode;
actions?: React.ReactNode;
/** Draws the panel's own border in a state colour. For a panel that IS the warning. */
tone?: "warn" | "expired";
children: React.ReactNode;
/** Skip the padded body — for a panel whose content is a full-bleed table. */
bodyless?: boolean;
className?: string;
}) {
const head = title || meta || actions;
return (
{head && (
)}
{bodyless ? children : {children}
}
);
}
/*
* An aside that is part of the argument rather than beside it: the consequence
* of the action on screen, or the constraint the reader is about to hit. The
* left rule carries the tone, so the note reads as annotation and never as a
* second panel competing with the one it sits in.
*/
export function Note({ tone = "accent", children }: { tone?: "accent" | "warn" | "expired"; children: React.ReactNode }) {
return (
{children}
);
}
/*
* An empty screen is an invitation to act. Every one of these says what the
* thing is before offering to make one — "No licences match those filters" on
* its own tells someone the filter worked, not what to do about it.
*/
export function EmptyState({ title, body, action }: { title: string; body?: React.ReactNode; action?: React.ReactNode }) {
return (
{title}
{body &&
{body}
}
{action &&
{action}
}
);
}