import * as React from "react"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
const sectionHeaderVariants = cva("flex flex-wrap items-end justify-between", {
variants: {
size: { default: "gap-4", sm: "gap-3" },
// A rule under the header rather than a Separator beside it, so the line
// always tracks the header's own width and spacing — and it is `--rule`,
// the section-break weight, not the row hairline.
divider: { true: "border-b border-rule pb-3", false: "" },
},
defaultVariants: { size: "default", divider: false },
})
// Two registers, one step apart: the section title is the serif at 20/500 —
// the first size the display face is used at — and `sm` drops all the way to
// the eyebrow, 11/600 in caps, for the label above a group of controls.
const TITLE_SIZE = {
default: "type-display text-xl text-foreground",
sm: "type-eyebrow",
} as const
const DESCRIPTION_SIZE = { default: "text-sm", sm: "text-xs" } as const
export type SectionHeaderProps = React.ComponentProps<"div"> & {
title: React.ReactNode
/** The heading's id, so a table or a list under it can be named by it (`aria-labelledby`). */
titleId?: string
/**
* A short caps line above the title — the group this section belongs to.
* Ignored at `sm`, where the title is itself the eyebrow.
*/
eyebrow?: React.ReactNode
description?: React.ReactNode
actions?: React.ReactNode
size?: NonNullable<VariantProps<typeof sectionHeaderVariants>["size"]>
/** Pick the level that keeps the page's heading order intact. */
as?: "h2" | "h3"
divider?: boolean
}
/** The heading above one section of a page, with room for its own actions. */
function SectionHeader({
className,
title,
titleId,
eyebrow,
description,
actions,
size = "default",
as: Heading = "h2",
divider = false,
...props
}: SectionHeaderProps) {
return (
<div
data-slot="section-header"
data-size={size}
data-divided={divider || undefined}
className={cn(sectionHeaderVariants({ size, divider }), className)}
{...props}
>
<div className="flex min-w-0 flex-col gap-1">
{eyebrow && size !== "sm" ? (
<p data-slot="section-header-eyebrow" className="type-eyebrow">
{eyebrow}
</p>
) : null}
<Heading
id={titleId}
data-slot="section-header-title"
className={TITLE_SIZE[size]}
>
{title}
</Heading>
{description ? (
<p
data-slot="section-header-description"
className={cn("max-w-prose text-muted-foreground", DESCRIPTION_SIZE[size])}
>
{description}
</p>
) : null}
</div>
{actions ? (
<div
data-slot="section-header-actions"
className="flex shrink-0 items-center gap-2"
>
{actions}
</div>
) : null}
</div>
)
}
export { SectionHeader, sectionHeaderVariants }