forked from baron/baron-sso
60 lines
1.4 KiB
TypeScript
60 lines
1.4 KiB
TypeScript
import type { ElementType, HTMLAttributes, ReactNode } from "react";
|
|
|
|
function cx(...classNames: Array<string | false | null | undefined>) {
|
|
return classNames.filter(Boolean).join(" ");
|
|
}
|
|
|
|
type PageHeaderProps = Omit<HTMLAttributes<HTMLElement>, "title"> & {
|
|
actions?: ReactNode;
|
|
as?: ElementType;
|
|
description?: ReactNode;
|
|
eyebrow?: ReactNode;
|
|
sticky?: boolean;
|
|
title: ReactNode;
|
|
titleAs?: ElementType;
|
|
};
|
|
|
|
export function PageHeader({
|
|
actions,
|
|
as,
|
|
className,
|
|
description,
|
|
eyebrow,
|
|
sticky = false,
|
|
title,
|
|
titleAs,
|
|
...props
|
|
}: PageHeaderProps) {
|
|
const Root = as ?? "header";
|
|
const Title = titleAs ?? "h1";
|
|
|
|
return (
|
|
<Root
|
|
className={cx(
|
|
"flex flex-wrap items-start justify-between gap-4",
|
|
sticky &&
|
|
"sticky top-[-2.5rem] z-20 -mt-4 bg-background/95 pt-4 pb-2 backdrop-blur",
|
|
className,
|
|
)}
|
|
{...props}
|
|
>
|
|
<div className="space-y-2">
|
|
{eyebrow ? (
|
|
<p className="text-xs uppercase tracking-[0.2em] text-muted-foreground">
|
|
{eyebrow}
|
|
</p>
|
|
) : null}
|
|
<Title className="text-3xl font-semibold tracking-tight">
|
|
{title}
|
|
</Title>
|
|
{description ? (
|
|
<p className="text-sm text-muted-foreground">{description}</p>
|
|
) : null}
|
|
</div>
|
|
{actions ? (
|
|
<div className="flex flex-wrap items-center gap-2">{actions}</div>
|
|
) : null}
|
|
</Root>
|
|
);
|
|
}
|