Page header
Grid band with a date and a pixel title.
Commit overflow
ada grace
Code
Copy into src/components/ph/ .
dates.ts
const asDate = (d: Date | string) => (typeof d === "string" ? new Date(d) : d);
export function dateBlock(d: Date | string) {
const date = asDate(d);
return {
month: date.toLocaleDateString("en-US", { month: "short", timeZone: "UTC" }).toUpperCase(),
dayYear: `${date.getUTCDate()}, ${date.getUTCFullYear()}`,
iso: date.toISOString(),
};
}
export function longDate(d: Date | string) {
const date = asDate(d);
return {
label: date.toLocaleDateString("en-US", { year: "numeric", month: "long", day: "numeric", timeZone: "UTC" }),
iso: date.toISOString(),
};
}
PageHeader.astro
---
import { longDate } from "./dates";
type Props = {
title: string;
date?: Date | string;
updated?: Date | string;
align?: "start"| "center";
class?: string;
};
const { title, date, updated, align = "start", class: className } = Astro.props;
const published = date ? longDate(date) : null;
const edited = updated ? longDate(updated) : null;
---
<header
class:list={[
"grid-bg-animated border-b border-border [--grid-size:26px]",
className,
]}
>
<div
class:list={[
"mx-auto flex max-w-5xl flex-col gap-5 px-4 pt-20 pb-12 sm:px-8 md:px-14 md:pt-28 md:pb-16",
align === "center"? "items-center text-center": "items-start",
]}
>
{
published && (
<div class="border border-foreground bg-card font-pixel text-xs text-card-foreground uppercase sm:text-sm">
<time datetime={published.iso} class="block px-2 py-0.5">{published.label}</time>
{edited && (
<div class="border-t border-foreground/30 px-2 py-0.5">
Updated <time datetime={edited.iso}>{edited.label}</time>
</div>
)}
</div>
)
}
<h1 class="font-pixel text-4xl leading-[1.05] uppercase sm:text-6xl md:text-7xl dark:text-ph-yellow">{title}</h1>
{Astro.slots.has("default") && <div class="flex flex-wrap gap-4 md:gap-x-5"><slot /></div>}
</div>
</header>
Copy into components/ph/ (or src/components/ph/).
dates.ts
const asDate = (d: Date | string) => (typeof d === "string" ? new Date(d) : d);
export function dateBlock(d: Date | string) {
const date = asDate(d);
return {
month: date.toLocaleDateString("en-US", { month: "short", timeZone: "UTC" }).toUpperCase(),
dayYear: `${date.getUTCDate()}, ${date.getUTCFullYear()}`,
iso: date.toISOString(),
};
}
export function longDate(d: Date | string) {
const date = asDate(d);
return {
label: date.toLocaleDateString("en-US", { year: "numeric", month: "long", day: "numeric", timeZone: "UTC" }),
iso: date.toISOString(),
};
}
page-header.tsx
import type { ReactNode } from "react";
import { cn } from "./cn";
import { longDate } from "./dates";
type PageHeaderProps = {
title: string;
date?: Date | string;
updated?: Date | string;
align?: "start"| "center";
children?: ReactNode;
className?: string;
};
export function PageHeader({ title, date, updated, align = "start", children, className }: PageHeaderProps) {
const published = date ? longDate(date) : null;
const edited = updated ? longDate(updated) : null;
return (
<header className={cn("grid-bg-animated border-b border-border [--grid-size:26px]", className)}>
<div
className={cn(
"mx-auto flex max-w-5xl flex-col gap-5 px-4 pt-20 pb-12 sm:px-8 md:px-14 md:pt-28 md:pb-16",
align === "center"? "items-center text-center": "items-start",
)}
>
{published && (
<div className="border border-foreground bg-card font-pixel text-xs text-card-foreground uppercase sm:text-sm">
<time dateTime={published.iso} className="block px-2 py-0.5">
{published.label}
</time>
{edited && (
<div className="border-t border-foreground/30 px-2 py-0.5">
Updated <time dateTime={edited.iso}>{edited.label}</time>
</div>
)}
</div>
)}
<h1 className="font-pixel text-4xl leading-[1.05] uppercase sm:text-6xl md:text-7xl dark:text-ph-yellow">{title}</h1>
{children && <div className="flex flex-wrap gap-4 md:gap-x-5">{children}</div>}
</div>
</header>
);
}
cn.ts
export const cn = (...parts: (string | false | null | undefined)[]) => parts.filter(Boolean).join(" ");