51 lines
2.2 KiB
TypeScript
51 lines
2.2 KiB
TypeScript
import React, { useId } from "react";
|
|
import { Box, Typography } from "@mui/material";
|
|
|
|
// Opt-in widths: the shell owns gutters; consumers must not add a second page gutter.
|
|
export type PageWidth = "reading" | "data" | "editor";
|
|
|
|
type HeaderProps = {
|
|
title: string;
|
|
description?: string;
|
|
actions?: React.ReactNode;
|
|
level?: "h1" | "h2" | "h3";
|
|
titleId?: string;
|
|
titleRef?: React.Ref<HTMLHeadingElement>;
|
|
};
|
|
|
|
export function PageHeader({ title, description, actions, level = "h1", titleId, titleRef }: HeaderProps) {
|
|
return (
|
|
<Box component="header" sx={{ display: "flex", flexWrap: "wrap", alignItems: "flex-start", gap: 2, minWidth: 0 }}>
|
|
<Box sx={{ flex: "1 1 16rem", minWidth: 0 }}>
|
|
<Typography ref={titleRef} tabIndex={titleRef ? -1 : undefined} id={titleId} component={level} variant={level === "h1" ? "pageTitle" : "sectionTitle"} sx={{ overflowWrap: "anywhere" }}>
|
|
{title}
|
|
</Typography>
|
|
{description ? <Typography variant="pageDescription" component="p" color="text.secondary" sx={{ mt: 1, maxWidth: "75ch", overflowWrap: "anywhere" }}>
|
|
{description}
|
|
</Typography> : null}
|
|
</Box>
|
|
{actions ? <Box sx={{ display: "flex", flexWrap: "wrap", alignItems: "center", gap: 1, minWidth: 0, maxWidth: "100%", "& > *": { maxWidth: "100%", whiteSpace: "normal", overflowWrap: "anywhere" } }}>
|
|
{actions}
|
|
</Box> : null}
|
|
</Box>
|
|
);
|
|
}
|
|
|
|
// Presentation only: domain components keep their loading, draft and mutation owners.
|
|
export function PageSection({ title, description, actions, children, level = "h2", surface = false }: Omit<HeaderProps, "titleId" | "level" | "titleRef"> & {
|
|
children?: React.ReactNode;
|
|
level?: "h2" | "h3";
|
|
surface?: boolean;
|
|
}) {
|
|
const titleId = useId();
|
|
return (
|
|
<Box component="section" aria-labelledby={titleId} sx={{
|
|
display: "grid", gap: 2, minWidth: 0, alignContent: "start",
|
|
...(surface ? { p: "var(--app-card-padding)", bgcolor: "background.paper", border: "1px solid", borderColor: "divider", borderRadius: 1 } : {}),
|
|
}}>
|
|
<PageHeader title={title} titleId={titleId} description={description} actions={actions} level={level} />
|
|
{children ? <Box sx={{ minWidth: 0 }}>{children}</Box> : null}
|
|
</Box>
|
|
);
|
|
}
|