72 lines
2.5 KiB
TypeScript
72 lines
2.5 KiB
TypeScript
import type { ReactNode } from "react";
|
|
import { createPortal } from "react-dom";
|
|
import type { Tone } from "./Panel";
|
|
|
|
const TITLE_TONES: Partial<Record<Tone, string>> = {
|
|
amber: "text-amber-300",
|
|
emerald: "text-emerald-300",
|
|
cyan: "text-cyan-200",
|
|
};
|
|
const BORDER_TONES: Partial<Record<Tone, string>> = {
|
|
amber: "border-amber-400/50",
|
|
emerald: "border-emerald-400/40",
|
|
cyan: "border-cyan-400/40",
|
|
};
|
|
|
|
/**
|
|
* A modal dialog: a bottom sheet on phones, a centred card from `sm` up.
|
|
* Labelled by its title, which it renders as an `h2` with `titleId`. Portalled
|
|
* to `body`: a transformed ancestor (the screens' rise-in) would otherwise
|
|
* become the containing block of `position: fixed`. It opens below the top
|
|
* bar (3.5rem, `--header-h` in Layout), so the credits there stay in view.
|
|
*/
|
|
export function Sheet({
|
|
titleId,
|
|
title,
|
|
eyebrow,
|
|
subtitle,
|
|
tone = "default",
|
|
layer = "z-20",
|
|
side = false,
|
|
children,
|
|
}: {
|
|
titleId: string;
|
|
title: ReactNode;
|
|
eyebrow?: ReactNode;
|
|
subtitle?: ReactNode;
|
|
tone?: Tone;
|
|
/** Stacking layer, e.g. `z-30` for a sheet over other sheets. */
|
|
layer?: string;
|
|
/** From `xl`, opens beside the wide panel and leaves it usable (decision 0.5.169). */
|
|
side?: boolean;
|
|
children: ReactNode;
|
|
}) {
|
|
return createPortal(
|
|
<div
|
|
className={`fixed inset-x-0 top-14 bottom-0 ${layer} flex items-end justify-center bg-space-950/70 sm:items-center sm:p-4 ${side ? "xl:pointer-events-none xl:top-20 xl:items-start xl:justify-end xl:bg-transparent xl:pr-[calc(45rem+2rem)]" : ""}`}
|
|
>
|
|
<div
|
|
role="dialog"
|
|
aria-modal="true"
|
|
aria-labelledby={titleId}
|
|
className={`relative max-h-full w-full max-w-md animate-sheet ${side ? "xl:pointer-events-auto xl:max-w-[26rem]" : ""} overflow-y-auto rounded-t-3xl border bg-space-850 p-5 pb-[max(1.25rem,env(safe-area-inset-bottom))] shadow-panel sm:rounded-3xl ${BORDER_TONES[tone] ?? "border-glass-line"}`}
|
|
>
|
|
<div
|
|
aria-hidden="true"
|
|
className="mx-auto -mt-2 mb-3 h-1 w-10 rounded-full bg-white/25 sm:hidden"
|
|
/>
|
|
{eyebrow && <div className="text-xs font-semibold text-ink-400">{eyebrow}</div>}
|
|
<h2
|
|
id={titleId}
|
|
className={`font-title text-lg leading-snug ${TITLE_TONES[tone] ?? "text-ink-50"}`}
|
|
>
|
|
{title}
|
|
</h2>
|
|
{subtitle && <div className="mt-1 text-sm text-ink-300">{subtitle}</div>}
|
|
{children}
|
|
</div>
|
|
</div>,
|
|
document.body,
|
|
);
|
|
}
|