Files
cadence-ui/apps/docs/src/patterns/page-header.stories.tsx
T

136 lines
5.1 KiB
TypeScript

import {
Badge,
Button,
PageHeader,
PageHeaderActions,
PageHeaderDescription,
PageHeaderEyebrow,
PageHeaderLeading,
PageHeaderMeta,
PageHeaderTitle
} from "@ai-ui/ui";
import type { Meta, StoryObj } from "@storybook/react";
function PageHeaderExample({
align = "start",
density = "comfortable",
variant = "default"
}: {
align?: "start" | "end";
density?: "comfortable" | "compact";
variant?: "hero" | "default" | "compact";
}) {
return (
<div className="w-full max-w-[1100px]">
<PageHeader align={align} density={density} variant={variant}>
<PageHeaderLeading>
<PageHeaderMeta>
<PageHeaderEyebrow>Friday, Dec 5, 2025</PageHeaderEyebrow>
<Badge tone="primary" variant="subtle">
Calm pulse
</Badge>
</PageHeaderMeta>
<PageHeaderTitle>Welcome back, Nathan.</PageHeaderTitle>
<PageHeaderDescription>
Revenue operations is holding a calm pulse today. Growth is still being led by
AI-assisted follow-up, while cost pressure stays within the board target window.
</PageHeaderDescription>
</PageHeaderLeading>
<PageHeaderActions>
<Button>Morning brief</Button>
<Button variant="secondary">Watch anomalies</Button>
</PageHeaderActions>
</PageHeader>
</div>
);
}
const meta = {
title: "Patterns/PageHeader",
component: PageHeaderExample,
parameters: {
docs: {
description: {
component:
"PageHeader is the shared top-of-page pattern for workspace and dashboard views. Use it when a screen needs one primary title stack with supporting context and a separate action cluster, instead of reassembling the same flex and spacing rules in each scene. Its motion should stay structural and calm: the header itself carries only a light ornamental wake-up, while badges and actions inherit a restrained lift that keeps the top rail feeling alive without turning it into a hero animation."
}
},
layout: "centered"
},
tags: ["autodocs"]
} satisfies Meta<typeof PageHeaderExample>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Playground: Story = {};
export const Variants: Story = {
render: () => (
<div className="mx-auto grid w-full max-w-[1100px] gap-8">
<PageHeaderExample align="end" variant="hero" />
<PageHeaderExample variant="default" />
<PageHeaderExample density="compact" variant="compact" />
</div>
)
};
export const Anatomy: Story = {
render: () => (
<div className="mx-auto grid w-full max-w-[1100px] gap-5 rounded-[var(--radius-lg)] border border-[var(--color-border)] bg-[var(--color-card)] p-6 shadow-[var(--shadow-sm)]">
<div className="grid gap-2">
<p className="text-xs uppercase tracking-[var(--tracking-caps)] text-[var(--color-muted-foreground)]">
Page header anatomy
</p>
<p className="text-sm leading-6 text-[var(--color-muted-foreground)]">
Keep the title stack in the leading slot and keep controls in the actions slot. The
pattern stays flexible enough for badges, dates, or status markers through the meta slot
without turning every page header into custom layout glue. Use `variant`, `density`, and
`align` on the root when a page needs hero weight, a quieter default header, or a tighter
compact treatment.
</p>
</div>
<PageHeaderExample />
<div className="grid gap-3 text-sm leading-6 text-[var(--color-muted-foreground)]">
<p>
<code className="text-[var(--color-foreground)]">data-slot="leading"</code> owns the
meta, title, and description stack.
</p>
<p>
<code className="text-[var(--color-foreground)]">data-slot="actions"</code> is reserved
for the page-level control cluster so page scenes stop improvising this split every time.
</p>
</div>
</div>
)
};
export const Motion: Story = {
parameters: {
docs: {
description: {
story:
"Hover the badges and action cluster. The pattern itself should feel gently staged through ornament and shared transition polish, but the main title stack should stay anchored and readable."
}
}
},
render: () => (
<div className="mx-auto grid w-full max-w-[1100px] gap-5 rounded-[var(--radius-xl)] border border-[color-mix(in_oklch,var(--color-border)_72%,transparent)] bg-[linear-gradient(180deg,color-mix(in_oklch,var(--color-surface)_78%,white_22%),color-mix(in_oklch,var(--color-surface-container-low)_86%,white_14%))] p-6 shadow-[var(--shadow-sm)]">
<div className="grid gap-2">
<p className="text-xs uppercase tracking-[var(--tracking-caps)] text-[var(--color-primary)]">
Motion review
</p>
<p className="max-w-2xl text-sm leading-6 text-[var(--color-muted-foreground)]">
PageHeader should not animate like a card. It should feel like polished infrastructure:
the slab wakes up through a light glow and the attached controls carry the interaction lift.
</p>
</div>
<PageHeaderExample align="end" variant="hero" />
</div>
)
};