mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-26 18:42:49 +00:00
Report descriptions run to a paragraph. ReportDescription wraps Mantine's Spoiler to clamp them to two lines, and the toggle only renders when the text actually overflows. PageHeader kept its subtitle on a hard truncate class, which would have pinned the spoiler to one line, so a ReactNode subtitle now renders as-is and owns its own layout. A string subtitle still truncates as before.
85 lines
2.6 KiB
TypeScript
85 lines
2.6 KiB
TypeScript
import { ActionIcon, Group, Stack, Text, Title } from "@mantine/core";
|
|
import { ArrowLeft } from "lucide-react";
|
|
import type { ReactNode } from "react";
|
|
import { useNavigate } from "react-router-dom";
|
|
|
|
import Breadcrumbs, { type BreadcrumbItem } from "@/components/ui/Breadcrumbs";
|
|
|
|
export interface PageHeaderProps {
|
|
title: string;
|
|
subtitle?: ReactNode;
|
|
/** Breadcrumb trail — pass only on nested pages (details, sub-resources). */
|
|
breadcrumbs?: BreadcrumbItem[];
|
|
/** Route to return to; renders a back arrow before the title. */
|
|
backTo?: string;
|
|
/** Inline content beside the title (e.g. status badges). */
|
|
meta?: ReactNode;
|
|
/** Right-aligned actions — the primary CTA lives here. */
|
|
action?: ReactNode;
|
|
}
|
|
|
|
/**
|
|
* Unified page header: optional breadcrumbs, a title (with optional back arrow
|
|
* and inline meta), a subtitle, and a right-aligned action slot. Keeps title /
|
|
* action placement and spacing identical across every dashboard page.
|
|
*/
|
|
export function PageHeader({
|
|
title,
|
|
subtitle,
|
|
breadcrumbs,
|
|
backTo,
|
|
meta,
|
|
action,
|
|
}: PageHeaderProps) {
|
|
const navigate = useNavigate();
|
|
|
|
return (
|
|
<Stack gap="sm">
|
|
{breadcrumbs?.length ? <Breadcrumbs items={breadcrumbs} /> : null}
|
|
|
|
<Group justify="space-between" align="flex-start" gap="md">
|
|
<Group gap="sm" align="center" wrap="nowrap" style={{ minWidth: 0 }}>
|
|
{backTo ? (
|
|
<ActionIcon
|
|
variant="subtle"
|
|
color="gray"
|
|
onClick={() => navigate(backTo)}
|
|
aria-label="Go back"
|
|
>
|
|
<ArrowLeft size={18} />
|
|
</ActionIcon>
|
|
) : null}
|
|
|
|
<div style={{ minWidth: 0, maxWidth: 640 }}>
|
|
<Group gap="sm" align="center" wrap="nowrap">
|
|
<Title order={2} className="truncate" style={{ minWidth: 0 }}>
|
|
{title}
|
|
</Title>
|
|
{meta}
|
|
</Group>
|
|
{subtitle ? (
|
|
typeof subtitle === "string" ? (
|
|
<Text c="dimmed" size="sm" mt={4} className="truncate">
|
|
{subtitle}
|
|
</Text>
|
|
) : (
|
|
// A component subtitle handles its own layout — truncating it
|
|
// to one line would defeat e.g. an expandable description.
|
|
<div style={{ marginTop: 4 }}>{subtitle}</div>
|
|
)
|
|
) : null}
|
|
</div>
|
|
</Group>
|
|
|
|
{action ? (
|
|
<Group gap="sm" wrap="nowrap">
|
|
{action}
|
|
</Group>
|
|
) : null}
|
|
</Group>
|
|
</Stack>
|
|
);
|
|
}
|
|
|
|
export default PageHeader;
|