Files
loyaly-merchant/src/features/admin/components/common/AdminPageHeader.tsx
2026-09-29 00:44:48 +05:30

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" />}
/>
);
}