107 lines
3.0 KiB
TypeScript
107 lines
3.0 KiB
TypeScript
'use client';
|
|
|
|
import {Breadcrumbs, BreadcrumbItem} from '@astryxdesign/core/Breadcrumbs';
|
|
import {HStack, VStack} from '@astryxdesign/core/Layout';
|
|
import {Text} from '@astryxdesign/core/Text';
|
|
import {Button} from '@astryxdesign/core/Button';
|
|
import {Icon} from '@astryxdesign/core/Icon';
|
|
|
|
export interface Crumb {
|
|
label: string;
|
|
/** Omit on the last crumb — it is the current page. */
|
|
href?: string;
|
|
}
|
|
|
|
/**
|
|
* Where the operator is, what this page is, and what they can do here.
|
|
*
|
|
* Breadcrumbs appear only when there is a parent: a top-level page with a
|
|
* one-item trail is clutter. Four levels deep (company / store / camera) the
|
|
* trail is the only thing telling an operator which tenant they are looking
|
|
* at, which on a console that can suspend it is not a nicety.
|
|
*/
|
|
export function AdminPageHeader({
|
|
title,
|
|
subtitle,
|
|
back,
|
|
crumbs,
|
|
meta,
|
|
actions,
|
|
}: {
|
|
title: string;
|
|
subtitle?: string;
|
|
/** One level up. A drill-down page needs a way back that is not the
|
|
* browser's — the operator may have arrived from a link. */
|
|
back?: {label: string; href: string};
|
|
crumbs?: Crumb[];
|
|
/** A row under the title — status, slug, dates. */
|
|
meta?: React.ReactNode;
|
|
actions?: React.ReactNode;
|
|
}) {
|
|
return (
|
|
<VStack gap={3} width="100%">
|
|
{back || (crumbs && crumbs.length > 0) ? (
|
|
<HStack gap={3} vAlign="center" className="flex-wrap gap-y-1">
|
|
{back ? <BackButton {...back} /> : null}
|
|
{crumbs && crumbs.length > 0 ? (
|
|
<Breadcrumbs variant="supporting">
|
|
{crumbs.map((c, i) =>
|
|
i === crumbs.length - 1 ? (
|
|
<BreadcrumbItem key={i} isCurrent>
|
|
{c.label}
|
|
</BreadcrumbItem>
|
|
) : (
|
|
<BreadcrumbItem key={i} href={c.href}>
|
|
{c.label}
|
|
</BreadcrumbItem>
|
|
),
|
|
)}
|
|
</Breadcrumbs>
|
|
) : null}
|
|
</HStack>
|
|
) : null}
|
|
|
|
<HStack
|
|
gap={4}
|
|
vAlign="start"
|
|
hAlign="between"
|
|
width="100%"
|
|
className="flex-wrap gap-y-3"
|
|
>
|
|
<VStack gap={1} className="min-w-0">
|
|
<Text type="display-3" weight="medium" className="break-words">
|
|
{title}
|
|
</Text>
|
|
{subtitle ? (
|
|
<Text size="sm" color="secondary">
|
|
{subtitle}
|
|
</Text>
|
|
) : null}
|
|
{meta ? (
|
|
<HStack gap={3} vAlign="center" className="flex-wrap gap-y-1">
|
|
{meta}
|
|
</HStack>
|
|
) : null}
|
|
</VStack>
|
|
{actions ? (
|
|
<HStack gap={2} vAlign="center">
|
|
{actions}
|
|
</HStack>
|
|
) : null}
|
|
</HStack>
|
|
</VStack>
|
|
);
|
|
}
|
|
|
|
export function BackButton({label, href}: {label: string; href: string}) {
|
|
return (
|
|
<Button
|
|
variant="ghost"
|
|
size="sm"
|
|
href={href}
|
|
label={label}
|
|
icon={<Icon icon="chevronLeft" size="sm" />}
|
|
/>
|
|
);
|
|
}
|