41 lines · 1.6 KB
import { cn } from "~/utils/cn";

const PADDING = { none: "", sm: "p-3", md: "p-4", lg: "p-5", xl: "p-8" } as const;

/** Card surface shared by `Panel` and ad-hoc containers (popovers, link cards). */
export function panelClass(className?: string): string {
  return cn("rounded-lg border border-gray-800 bg-gray-950/60", className);
}

type Props = {
  /** `danger` frames destructive settings (danger zone, transfers, renames). */
  tone?: "neutral" | "danger";
  title?: React.ReactNode;
  actions?: React.ReactNode;
  /** Inner padding. With a header, it pads the body below the header rather than the whole panel. */
  padding?: keyof typeof PADDING;
  children: React.ReactNode;
  className?: string;
};

export function Panel({ tone = "neutral", title, actions, padding = "none", children, className }: Props) {
  const hasHeader = Boolean(title || actions);
  return (
    <section
      className={panelClass(
        cn("overflow-hidden", tone === "danger" && "border-danger-900/60", !hasHeader && PADDING[padding], className),
      )}
    >
      {hasHeader && (
        <header className="flex items-center justify-between gap-3 border-b border-gray-800 bg-gray-900/60 px-4 py-2.5">
          <div className={cn("min-w-0 text-sm font-medium", tone === "danger" ? "text-danger-300" : "text-gray-200")}>
            {title}
          </div>
          {actions && <div className="flex shrink-0 items-center gap-2">{actions}</div>}
        </header>
      )}
      {hasHeader && padding !== "none" ? <div className={PADDING[padding]}>{children}</div> : children}
    </section>
  );
}