Vectrum
Sign In
63 lines · 2.3 KB · TypeScript
import type { ReactNode } from "react";
import { Rule } from "@unlocalhosted/metalui";
import { Box } from "./ui";

/*
 * The settings pages' form plates, in MetalUI's settings anatomy: a heading engraved just above a
 * raised card (Settings.Section's look), for the parts that are forms with Save rather than rows.
 * Server-safe: no hooks, only serializable props on MetalUI parts.
 */

/** A titled group: the engraved heading, then a raised plate holding a form or a list. */
export function PlateSection({ title, children, className = "" }: { title: string; children: ReactNode; className?: string }) {
  return (
    <section aria-label={title} className={`flex flex-col gap-2 ${className}`}>
      <h2 className="px-1.5 type-label engraved">{title}</h2>
      <Box>{children}</Box>
    </section>
  );
}

/** The plate's content: the fields, stacked 20 apart. */
export function PlateBody({ children, className = "" }: { children: ReactNode; className?: string }) {
  return <div className={`flex flex-col gap-5 px-6 py-5 ${className}`}>{children}</div>;
}

/** The plate's actions, at the end under an inset engraved rule: the one that does the thing last. */
export function PlateFooter({ children, className = "" }: { children: ReactNode; className?: string }) {
  return (
    <>
      <Rule className="mx-6" />
      <div className={`flex flex-wrap items-center justify-end gap-2 px-6 py-3.5 ${className}`}>{children}</div>
    </>
  );
}

/** A thing in a settings list: a lead, its name and engraved meta, then one control. */
export function ListRow({
  lead,
  title,
  meta,
  children,
}: {
  lead?: ReactNode;
  title: ReactNode;
  meta?: ReactNode;
  children?: ReactNode;
}) {
  return (
    <div className="flex min-h-[52px] items-center gap-4 px-[18px] py-3.5">
      {lead}
      <div className="flex min-w-0 flex-1 flex-col gap-1">
        <div className="flex min-w-0 flex-wrap items-center gap-2 type-title text-ink">{title}</div>
        {meta}
      </div>
      {children && <div className="flex flex-none items-center gap-2">{children}</div>}
    </div>
  );
}

/** A glyph sunk in a small well, leading a list row. */
export function RowGlyph({ children }: { children: ReactNode }) {
  return <span className="icon-tile size-9 shrink-0 rounded-row [&_svg]:size-4">{children}</span>;
}