02examples
Calendar in an app layout
The calendar as one route of a product — sidebar nav, a header built from useKloq(), and a container that gives it a real height.
A mock product: a sidebar with four routes, "Calendar" active, and the calendar filling the rest. The header is the product's — its title, ‹ › Today, a view switcher, Share and an avatar — but every control in it reads the calendar it sits inside. Step the range and the heading changes.
"use client";
import { useState } from "react";
import { BarChart3, CalendarDays, ChevronLeft, ChevronRight, FolderKanban, Inbox, Share2 } from "lucide-react";
import { formatViewTitle, Kloq, KloqToolbar, useKloq, type KloqView } from "kloq";
import { Avatar, AvatarFallback } from "@/components/ui/avatar";
import { Button } from "@/components/ui/button";
import { cn } from "@/lib/utils";
import { shellEvents } from "./data";
const NAV = [
{ label: "Inbox", icon: Inbox },
{ label: "Projects", icon: FolderKanban },
{ label: "Calendar", icon: CalendarDays, active: true },
{ label: "Reports", icon: BarChart3 },
];
const VIEWS: { view: KloqView; label: string }[] = [
{ view: "day", label: "Day" },
{ view: 3, label: "3 days" },
{ view: "week", label: "Week" },
{ view: "month", label: "Month" },
];
/** The product's page header, fed by the calendar it sits inside. */
function Header() {
const k = useKloq();
return (
<KloqToolbar className="px-4">
<div className="flex items-center gap-2">
<h1 className="mr-1 font-heading text-base font-semibold tracking-tight" suppressHydrationWarning>
{formatViewTitle(k.view)}
</h1>
<Button variant="ghost" size="icon-xs" aria-label="Previous" onClick={() => k.step(-1)}>
<ChevronLeft />
</Button>
<Button variant="ghost" size="icon-xs" aria-label="Next" onClick={() => k.step(1)}>
<ChevronRight />
</Button>
<Button variant="ghost" size="xs" className="text-muted-foreground" disabled={k.atToday} onClick={k.goToday}>
Today
</Button>
</div>
<div className="flex items-center gap-2">
<div role="group" aria-label="View" className="flex rounded-md border p-0.5">
{VIEWS.map(({ view, label }) => {
const active = k.viewSetting === view;
const fits = k.fits?.(view) ?? true;
return (
<Button
key={label}
variant="ghost"
size="xs"
aria-pressed={active}
disabled={!fits}
title={fits ? undefined : "Too narrow for this view"}
className={cn("h-6 rounded-[5px] text-xs text-muted-foreground", active && "bg-accent text-foreground")}
onClick={() => k.switchView(view)}
>
{label}
</Button>
);
})}
</div>
<Button variant="outline" size="sm">
<Share2 />
Share
</Button>
<Avatar className="size-7 border">
<AvatarFallback className="text-[10px]">SK</AvatarFallback>
</Avatar>
</div>
</KloqToolbar>
);
}
export default function AppShellExample() {
const [events] = useState(() => shellEvents(new Date()));
return (
<div className="grid h-[36rem] grid-cols-[minmax(0,1fr)] sm:grid-cols-[11rem_minmax(0,1fr)]">
<aside className="hidden flex-col border-r bg-muted/30 sm:flex">
<div className="flex items-center gap-2 px-4 py-3.5 font-heading text-sm font-semibold">
<span className="size-2 rounded-sm bg-red-500" />
Orbit
</div>
<nav className="flex flex-col gap-0.5 px-2">
{NAV.map(({ label, icon: Icon, active }) => (
<a
key={label}
href="#"
aria-current={active ? "page" : undefined}
onClick={(e) => e.preventDefault()}
className={cn(
"flex items-center gap-2.5 rounded-md px-2 py-1.5 text-sm transition-colors",
active ? "bg-accent text-foreground" : "text-muted-foreground hover:text-foreground",
)}
>
<Icon className="size-4" />
{label}
</a>
))}
</nav>
<div className="mt-auto border-t px-4 py-3 font-mono text-xs text-muted-foreground">acme · 12 seats</div>
</aside>
{/* the grid cell is the calendar's box: a real height from the grid, `min-h-0` so it can shrink */}
<div className="min-h-0 min-w-0">
<Kloq
defaultEvents={events}
defaultView={3}
// this cell is ~520px wide; the stock 640px tier would force a single day
responsive={{ breakpoint: 480 }}
persistence={false}
designMode={false}
storage={false}
>
<Header />
</Kloq>
</div>
</div>
);
}
import { instantAt, resolveView, type CalEvent, type EventColor } from "kloq";
interface Slot {
title: string;
/** days from today, minutes from midnight, length in minutes */
day: number;
at: number;
minutes: number;
color: EventColor;
location?: string;
}
const h = (hours: number, minutes = 0) => hours * 60 + minutes;
const WEEK: Slot[] = [
{ title: "Standup", day: 0, at: h(9), minutes: 15, color: "blue" },
{ title: "Design crit", day: 0, at: h(11), minutes: 60, color: "violet", location: "Room 4" },
{ title: "Lunch", day: 0, at: h(12, 30), minutes: 45, color: "emerald" },
{ title: "Ship v2.3", day: 0, at: h(15), minutes: 90, color: "amber" },
{ title: "Standup", day: 1, at: h(9), minutes: 15, color: "blue" },
{ title: "1:1 with Priya", day: 1, at: h(10), minutes: 30, color: "blue" },
{ title: "Customer call", day: 1, at: h(14), minutes: 45, color: "rose" },
{ title: "Standup", day: 2, at: h(9), minutes: 15, color: "blue" },
{ title: "Roadmap review", day: 2, at: h(13), minutes: 90, color: "violet" },
{ title: "Retro", day: 2, at: h(16), minutes: 45, color: "emerald" },
];
/** Three days of a product team's week, from today. */
export function shellEvents(now: Date): CalEvent[] {
const view = resolveView(3, 1, now);
return WEEK.map((s, i) => ({
id: `shell-${i}`,
title: s.title,
color: s.color,
location: s.location,
start: instantAt(view, s.day, s.at),
end: instantAt(view, s.day, s.at + s.minutes),
}));
}
How it works
The shell is a CSS grid with a real height: grid h-[36rem] sm:grid-cols-[11rem_minmax(0,1fr)]. The right cell is min-h-0 min-w-0 so
the grid track, not its content, decides its size, and <Kloq> — which is
h-full and never reaches for the window — fills it. That is the whole of
"the container is the contract":
give the component a box with a height and it renders into it.
<Kloq> renders no app bar of its own. Header is passed as children, so
it lands above the surface inside the calendar's provider, and it calls
useKloq() for everything it shows: view for the heading via
formatViewTitle, step / goToday / atToday for the range controls,
viewSetting and switchView for the switcher. KloqToolbar is only the
bordered row. See toolbar & chrome.
The cell is about 520px wide here. The stock
responsive collapse would force a
single day below 640px, but three 150px columns read fine, so
responsive={{ breakpoint: 480 }} lowers that tier for this box — the
medium tier still turns a seven-column week into three days. The switcher
disables what does not fit with k.fits(view), the same call the built-in
switcher makes.
Nothing in the sidebar knows about the calendar. It is plain markup outside
<Kloq>, which is where product chrome that does not need calendar state
belongs.
Take it further
Move view and date into your router and pass them as controlled props —
View and date in the URL does exactly that — and
the header keeps working unchanged, because useKloq() reports the effective
state either way.