Calendar
Free, copy-and-go Calendar components built on the SevenUI Calendar primitive.Read the primitive docs.
"use client";
import * as React from "react";
import { Calendar } from "@/components/ui/calendar";
const formatter = new Intl.DateTimeFormat("en-US", {
weekday: "short",
month: "long",
day: "numeric",
});
export default function Calendar01() {
const [date, setDate] = React.useState<Date | undefined>(
new Date(2026, 8, 17),
);
return (
<Calendar
mode="single"
selected={date}
onSelect={setDate}
defaultMonth={new Date(2026, 8, 1)}
className="rounded-xl border shadow-sm"
footer={
<p className="mt-3 border-t px-1 pt-3 text-sm text-muted-foreground">
{date ? (
<>
Review call on{" "}
<span className="font-medium text-foreground">
{formatter.format(date)}
</span>
</>
) : (
"No date picked yet."
)}
</p>
}
/>
);
}
npx shadcn@latest add @sevenui/component/calendar-01pnpm dlx shadcn@latest add @sevenui/component/calendar-01yarn dlx shadcn@latest add @sevenui/component/calendar-01bunx --bun shadcn@latest add @sevenui/component/calendar-01"use client";
import * as React from "react";
import { Button } from "@/components/ui/button";
import { Calendar } from "@/components/ui/calendar";
import {
Card,
CardAction,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card";
const anchor = new Date(2026, 8, 25);
export default function Calendar02() {
const [month, setMonth] = React.useState(new Date(1994, 2, 1));
const [date, setDate] = React.useState<Date | undefined>(
new Date(1994, 2, 14),
);
return (
<Card className="w-full max-w-xs gap-4">
<CardHeader>
<CardTitle>Date of birth</CardTitle>
<CardDescription aria-live="polite">
{date
? date.toLocaleDateString("en-US", {
month: "long",
day: "numeric",
year: "numeric",
})
: "Use the month and year menus to jump."}
</CardDescription>
<CardAction>
<Button
variant="ghost"
size="sm"
disabled={!date}
onClick={() => setDate(undefined)}
>
Clear
</Button>
</CardAction>
</CardHeader>
<CardContent className="flex justify-center">
<Calendar
mode="single"
captionLayout="dropdown"
buttonVariant="outline"
startMonth={new Date(1930, 0)}
endMonth={anchor}
disabled={{ after: anchor }}
month={month}
onMonthChange={setMonth}
selected={date}
onSelect={setDate}
className="p-0 [--cell-size:--spacing(8)]"
/>
</CardContent>
</Card>
);
}
npx shadcn@latest add @sevenui/component/calendar-02pnpm dlx shadcn@latest add @sevenui/component/calendar-02yarn dlx shadcn@latest add @sevenui/component/calendar-02bunx --bun shadcn@latest add @sevenui/component/calendar-02Office days in October
2 of 4Wed, Oct 7Wed, Oct 14
"use client";
import * as React from "react";
import { XIcon } from "lucide-react";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Calendar } from "@/components/ui/calendar";
const MAX_DAYS = 4;
const chipFormatter = new Intl.DateTimeFormat("en-US", {
weekday: "short",
month: "short",
day: "numeric",
});
const initialDays = [new Date(2026, 9, 7), new Date(2026, 9, 14)];
export default function Calendar03() {
const [days, setDays] = React.useState<Date[] | undefined>(initialDays);
const selected = [...(days ?? [])].sort((a, b) => a.getTime() - b.getTime());
const atLimit = selected.length >= MAX_DAYS;
return (
<div className="flex w-full max-w-xs flex-col gap-3 rounded-xl border bg-background p-3 shadow-sm">
<div className="flex items-baseline justify-between gap-2 px-1">
<h3 id="calendar-03-title" className="text-sm font-medium">
Office days in October
</h3>
<span
className="text-xs text-muted-foreground tabular-nums"
aria-live="polite"
>
{selected.length} of {MAX_DAYS}
</span>
</div>
<Calendar
aria-labelledby="calendar-03-title"
mode="multiple"
max={MAX_DAYS}
selected={days}
onSelect={setDays}
// At the limit react-day-picker would replace the whole selection with
// the clicked day, so lock the unpicked days until one is removed.
disabled={
atLimit
? (day) => !selected.some((d) => d.getTime() === day.getTime())
: undefined
}
defaultMonth={new Date(2026, 9, 1)}
disableNavigation
hideNavigation
showOutsideDays={false}
className="w-full p-0 [--cell-size:--spacing(8)]"
classNames={{ root: "w-full" }}
/>
{atLimit ? (
<p className="px-1 text-xs text-muted-foreground">
Limit reached. Remove a day to pick another.
</p>
) : null}
<div className="flex min-h-7 flex-wrap items-center gap-1.5 border-t pt-3">
{selected.length === 0 ? (
<span className="px-1 text-sm text-muted-foreground">
Remote all month.
</span>
) : (
selected.map((day) => (
<Badge
key={day.toISOString()}
variant="secondary"
className="h-6 gap-0.5 pr-0.5"
>
{chipFormatter.format(day)}
<button
type="button"
aria-label={`Remove ${chipFormatter.format(day)}`}
onClick={() =>
setDays(selected.filter((d) => d.getTime() !== day.getTime()))
}
className="inline-flex size-5 items-center justify-center rounded-full text-muted-foreground outline-none hover:bg-background hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring"
>
<XIcon aria-hidden="true" className="size-3" />
</button>
</Badge>
))
)}
{selected.length > 0 ? (
<Button
variant="ghost"
size="xs"
className="ml-auto"
onClick={() => setDays([])}
>
Clear
</Button>
) : null}
</div>
</div>
);
}
npx shadcn@latest add @sevenui/component/calendar-03pnpm dlx shadcn@latest add @sevenui/component/calendar-03yarn dlx shadcn@latest add @sevenui/component/calendar-03bunx --bun shadcn@latest add @sevenui/component/calendar-03Nov 23Dec 6
Holiday promo runs 14 days"use client";
import * as React from "react";
import { ArrowRightIcon } from "lucide-react";
import type { DateRange } from "react-day-picker";
import { Button } from "@/components/ui/button";
import { Calendar } from "@/components/ui/calendar";
const DAY_MS = 24 * 60 * 60 * 1000;
const shortFormatter = new Intl.DateTimeFormat("en-US", {
month: "short",
day: "numeric",
});
// A holiday promo that runs across the November-December boundary.
const initialRange: DateRange = {
from: new Date(2026, 10, 23),
to: new Date(2026, 11, 6),
};
export default function Calendar04() {
const [range, setRange] = React.useState<DateRange | undefined>(
initialRange,
);
// Both the start and the end day are part of the campaign.
const days =
range?.from && range?.to
? Math.round((range.to.getTime() - range.from.getTime()) / DAY_MS) + 1
: 0;
return (
<div className="w-full max-w-fit overflow-hidden rounded-xl border bg-background shadow-sm">
<div className="flex flex-wrap items-center gap-x-3 gap-y-1 border-b px-4 py-3">
<div className="flex items-center gap-2 text-sm font-medium tabular-nums">
<span className={range?.from ? "" : "text-muted-foreground"}>
{range?.from ? shortFormatter.format(range.from) : "Start"}
</span>
<ArrowRightIcon
aria-hidden="true"
className="size-3.5 text-muted-foreground"
/>
<span className={range?.to ? "" : "text-muted-foreground"}>
{range?.to ? shortFormatter.format(range.to) : "End"}
</span>
</div>
<span
className="text-sm text-muted-foreground tabular-nums"
aria-live="polite"
>
{days > 0
? `Holiday promo runs ${days} ${days === 1 ? "day" : "days"}`
: "Pick the first and last day of the promo"}
</span>
<Button
variant="ghost"
size="sm"
className="ml-auto"
disabled={!range?.from}
onClick={() => setRange(undefined)}
>
Clear
</Button>
</div>
<Calendar
mode="range"
numberOfMonths={2}
selected={range}
onSelect={setRange}
defaultMonth={new Date(2026, 10, 1)}
showOutsideDays={false}
className="mx-auto p-3"
/>
</div>
);
}
npx shadcn@latest add @sevenui/component/calendar-04pnpm dlx shadcn@latest add @sevenui/component/calendar-04yarn dlx shadcn@latest add @sevenui/component/calendar-04bunx --bun shadcn@latest add @sevenui/component/calendar-04- Available
- Booked
- Closed
Weekdays only. Booked dates are struck through.
"use client";
import * as React from "react";
import { Calendar } from "@/components/ui/calendar";
// The "today" of this example, pinned so the preview never drifts.
const today = new Date(2026, 8, 25);
const booked = [
new Date(2026, 8, 29),
new Date(2026, 8, 30),
new Date(2026, 9, 6),
new Date(2026, 9, 7),
new Date(2026, 9, 13),
new Date(2026, 9, 21),
new Date(2026, 9, 22),
];
// Each legend sample mirrors how that state renders in the grid.
const legend = [
{ label: "Available", sample: "text-foreground" },
{
label: "Booked",
sample:
"bg-destructive/10 text-destructive line-through dark:bg-destructive/20",
},
{ label: "Closed", sample: "text-muted-foreground opacity-50" },
];
export default function Calendar05() {
const [date, setDate] = React.useState<Date | undefined>();
return (
<div className="flex w-full max-w-fit flex-col gap-3 rounded-xl border bg-background p-3 shadow-sm">
<Calendar
mode="single"
selected={date}
onSelect={setDate}
defaultMonth={new Date(2026, 9, 1)}
startMonth={new Date(2026, 8, 1)}
endMonth={new Date(2026, 11, 1)}
disabled={[{ before: today }, { dayOfWeek: [0, 6] }, ...booked]}
modifiers={{ booked }}
modifiersClassNames={{
booked:
"opacity-100! [&>button]:bg-destructive/10 [&>button]:text-destructive [&>button]:line-through [&>button]:opacity-100! dark:[&>button]:bg-destructive/20",
}}
className="mx-auto p-0 [--cell-size:--spacing(8)]"
/>
<ul
aria-label="Legend"
className="flex flex-wrap gap-x-4 gap-y-1.5 border-t px-1 pt-3 text-xs text-muted-foreground"
>
{legend.map((item) => (
<li key={item.label} className="flex items-center gap-1.5">
<span
aria-hidden="true"
className={`inline-flex size-5 items-center justify-center rounded-sm text-[0.7rem] tabular-nums ${item.sample}`}
>
14
</span>
{item.label}
</li>
))}
</ul>
<p className="px-1 text-sm" aria-live="polite">
{date ? (
<>
Slot held for{" "}
<span className="font-medium">
{date.toLocaleDateString("en-US", {
weekday: "short",
month: "short",
day: "numeric",
})}
</span>
</>
) : (
<span className="text-muted-foreground">
Weekdays only. Booked dates are struck through.
</span>
)}
</p>
</div>
);
}
npx shadcn@latest add @sevenui/component/calendar-05pnpm dlx shadcn@latest add @sevenui/component/calendar-05yarn dlx shadcn@latest add @sevenui/component/calendar-05bunx --bun shadcn@latest add @sevenui/component/calendar-05"use client";
import * as React from "react";
import { Calendar } from "@/components/ui/calendar";
import { Label } from "@/components/ui/label";
import { Switch } from "@/components/ui/switch";
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group";
// Static class strings so Tailwind can see every cell size at build time.
const densities = [
{
value: "compact",
label: "Compact",
className: "[--cell-size:--spacing(6)]",
},
{
value: "default",
label: "Default",
className: "[--cell-size:--spacing(7)]",
},
{
value: "roomy",
label: "Roomy",
className: "[--cell-size:--spacing(8)]",
},
];
export default function Calendar06() {
const [density, setDensity] = React.useState("default");
const [weekNumbers, setWeekNumbers] = React.useState(true);
const [date, setDate] = React.useState<Date | undefined>(
new Date(2026, 11, 9),
);
const active =
densities.find((item) => item.value === density) ?? densities[1];
return (
<div className="flex w-full max-w-xs flex-col items-center gap-4">
<div className="flex w-full flex-wrap items-center justify-between gap-3">
<ToggleGroup
aria-label="Calendar density"
variant="outline"
size="sm"
spacing={0}
value={[density]}
onValueChange={(next) => {
// Keep one density active: ignore attempts to clear the group.
if (next.length > 0) setDensity(next[0]);
}}
>
{densities.map((item) => (
<ToggleGroupItem
key={item.value}
value={item.value}
className="aria-pressed:bg-accent aria-pressed:text-accent-foreground"
>
{item.label}
</ToggleGroupItem>
))}
</ToggleGroup>
<div className="flex items-center gap-2">
<Switch
id="calendar-06-week-numbers"
checked={weekNumbers}
onCheckedChange={setWeekNumbers}
/>
<Label htmlFor="calendar-06-week-numbers" className="text-xs">
Week numbers
</Label>
</div>
</div>
<Calendar
mode="single"
selected={date}
onSelect={setDate}
defaultMonth={new Date(2026, 11, 1)}
weekStartsOn={1}
ISOWeek
showWeekNumber={weekNumbers}
fixedWeeks
// The week-number cell is a flex item in each week row; pin its width
// so the day cells cannot squeeze it under the first column.
classNames={{
week_number:
"w-(--cell-size) shrink-0 text-[0.8rem] text-muted-foreground select-none",
week_number_header: "w-(--cell-size) shrink-0 select-none",
}}
className={`rounded-xl border shadow-sm ${active.className}`}
/>
</div>
);
}
npx shadcn@latest add @sevenui/component/calendar-06pnpm dlx shadcn@latest add @sevenui/component/calendar-06yarn dlx shadcn@latest add @sevenui/component/calendar-06bunx --bun shadcn@latest add @sevenui/component/calendar-06- Deploy
- Review
- Incident
Aug 24 Deploy, Review, Incident
"use client";
import * as React from "react";
import { Calendar, CalendarDayButton } from "@/components/ui/calendar";
type Category = "deploy" | "review" | "incident";
const categories: { value: Category; label: string; dot: string }[] = [
{ value: "deploy", label: "Deploy", dot: "bg-chart-1" },
{ value: "review", label: "Review", dot: "bg-chart-2" },
{ value: "incident", label: "Incident", dot: "bg-destructive" },
];
// Day of month (August 2026) -> categories happening that day.
const events: Record<number, Category[]> = {
3: ["deploy"],
5: ["review"],
10: ["deploy", "review"],
12: ["incident"],
13: ["review", "incident"],
17: ["deploy"],
19: ["review"],
24: ["deploy", "review", "incident"],
27: ["deploy"],
};
const dotClass = Object.fromEntries(
categories.map((item) => [item.value, item.dot]),
) as Record<Category, string>;
function eventsFor(date: Date) {
if (date.getFullYear() !== 2026 || date.getMonth() !== 7) return [];
return events[date.getDate()] ?? [];
}
function EventDayButton({
children,
day,
...props
}: React.ComponentProps<typeof CalendarDayButton>) {
const dayEvents = eventsFor(day.date);
return (
<CalendarDayButton day={day} {...props}>
{children}
<span aria-hidden="true" className="flex h-1 items-center gap-0.5">
{dayEvents.map((category) => (
<span
key={category}
className={`size-1 rounded-full ${dotClass[category]} in-data-[selected-single=true]:bg-primary-foreground`}
/>
))}
</span>
</CalendarDayButton>
);
}
export default function Calendar07() {
const [date, setDate] = React.useState<Date | undefined>(
new Date(2026, 7, 24),
);
const selectedEvents = date ? eventsFor(date) : [];
return (
<div className="flex w-full max-w-fit flex-col gap-3 rounded-xl border bg-background p-2 shadow-sm sm:p-3">
<Calendar
mode="single"
selected={date}
onSelect={setDate}
defaultMonth={new Date(2026, 7, 1)}
showOutsideDays={false}
className="p-0 [--cell-size:--spacing(7)] sm:[--cell-size:--spacing(9)]"
components={{ DayButton: EventDayButton }}
labels={{
labelDayButton: (day, modifiers) => {
const count = eventsFor(day).length;
const base = day.toLocaleDateString("en-US", {
weekday: "long",
month: "long",
day: "numeric",
year: "numeric",
});
const suffix =
count > 0 ? `, ${count} ${count === 1 ? "event" : "events"}` : "";
return `${modifiers.selected ? "Selected, " : ""}${base}${suffix}`;
},
}}
/>
<ul
aria-label="Event types"
className="flex flex-wrap gap-x-4 gap-y-1.5 border-t px-1 pt-3 text-xs text-muted-foreground"
>
{categories.map((item) => (
<li key={item.value} className="flex items-center gap-1.5">
<span
aria-hidden="true"
className={`size-2 rounded-full ${item.dot}`}
/>
{item.label}
</li>
))}
</ul>
<p className="px-1 text-sm" aria-live="polite">
{date ? (
<>
<span className="font-medium">
{date.toLocaleDateString("en-US", {
month: "short",
day: "numeric",
})}
</span>{" "}
<span className="text-muted-foreground">
{selectedEvents.length > 0
? selectedEvents
.map(
(value) =>
categories.find((item) => item.value === value)?.label,
)
.join(", ")
: "Nothing scheduled"}
</span>
</>
) : (
<span className="text-muted-foreground">
Pick a day to see what shipped.
</span>
)}
</p>
</div>
);
}
npx shadcn@latest add @sevenui/component/calendar-07pnpm dlx shadcn@latest add @sevenui/component/calendar-07yarn dlx shadcn@latest add @sevenui/component/calendar-07bunx --bun shadcn@latest add @sevenui/component/calendar-07Checking October availability
"use client";
import * as React from "react";
import {
CalendarXIcon,
CircleAlertIcon,
CircleCheckIcon,
LoaderCircleIcon,
} from "lucide-react";
import { Button } from "@/components/ui/button";
import { Calendar } from "@/components/ui/calendar";
type Status = "loading" | "ready" | "empty" | "error";
const LATENCY_MS = 700;
// Simulated availability: October is open, November fails on the first
// request, December is fully booked.
function openDaysFor(month: Date) {
if (month.getMonth() === 11) return [];
const days: Date[] = [];
const last = new Date(month.getFullYear(), month.getMonth() + 1, 0);
for (let d = 1; d <= last.getDate(); d++) {
const date = new Date(month.getFullYear(), month.getMonth(), d);
const weekday = date.getDay();
if (weekday !== 0 && weekday !== 6 && (d + month.getMonth()) % 3 !== 0) {
days.push(date);
}
}
return days;
}
function isSameDay(a: Date, b: Date) {
return a.toDateString() === b.toDateString();
}
export default function Calendar08() {
const [month, setMonth] = React.useState(new Date(2026, 9, 1));
const [attempt, setAttempt] = React.useState(0);
const [status, setStatus] = React.useState<Status>("loading");
const [openDays, setOpenDays] = React.useState<Date[]>([]);
const [date, setDate] = React.useState<Date | undefined>();
const failedOnce = React.useRef(false);
React.useEffect(() => {
setStatus("loading");
const timer = setTimeout(() => {
// `attempt` is read so Retry re-runs this effect for the same month.
void attempt;
if (month.getMonth() === 10 && !failedOnce.current) {
failedOnce.current = true;
setOpenDays([]);
setStatus("error");
return;
}
const days = openDaysFor(month);
setOpenDays(days);
setStatus(days.length > 0 ? "ready" : "empty");
}, LATENCY_MS);
return () => clearTimeout(timer);
}, [month, attempt]);
const busy = status === "loading";
const monthName = month.toLocaleDateString("en-US", { month: "long" });
return (
<div
aria-busy={busy}
className="flex w-full max-w-fit flex-col gap-3 rounded-xl border bg-background p-3 shadow-sm"
>
<Calendar
mode="single"
animate
month={month}
onMonthChange={(next) => {
setDate(undefined);
setMonth(next);
}}
startMonth={new Date(2026, 9, 1)}
endMonth={new Date(2027, 0, 1)}
selected={date}
onSelect={setDate}
disabled={
status === "ready"
? (day) => !openDays.some((open) => isSameDay(open, day))
: true
}
showOutsideDays={false}
className={`p-0 transition-opacity duration-200 [--cell-size:--spacing(8)] ${
busy ? "opacity-60" : ""
}`}
/>
<div
role="status"
className="flex min-h-9 items-center gap-2 border-t px-1 pt-3 text-sm"
>
{status === "loading" ? (
<>
<LoaderCircleIcon
aria-hidden="true"
className="size-4 animate-spin text-muted-foreground motion-reduce:animate-none"
/>
<span className="text-muted-foreground">
Checking {monthName} availability
</span>
</>
) : null}
{status === "ready" ? (
<>
<CircleCheckIcon
aria-hidden="true"
className="size-4 text-success"
/>
<span>
{date ? (
<>
Booked{" "}
<span className="font-medium">
{date.toLocaleDateString("en-US", {
month: "short",
day: "numeric",
})}
</span>
</>
) : (
<>
<span className="font-medium tabular-nums">
{openDays.length}
</span>{" "}
<span className="text-muted-foreground">
open days in {monthName}
</span>
</>
)}
</span>
</>
) : null}
{status === "empty" ? (
<>
<CalendarXIcon
aria-hidden="true"
className="size-4 text-muted-foreground"
/>
<span className="text-muted-foreground">
{monthName} is fully booked
</span>
<Button
variant="outline"
size="xs"
className="ml-auto"
onClick={() =>
setMonth(new Date(month.getFullYear(), month.getMonth() + 1, 1))
}
>
Next month
</Button>
</>
) : null}
{status === "error" ? (
<>
<CircleAlertIcon
aria-hidden="true"
className="size-4 text-destructive"
/>
<span className="text-destructive">Couldn't load {monthName}</span>
<Button
variant="outline"
size="xs"
className="ml-auto"
onClick={() => setAttempt((n) => n + 1)}
>
Retry
</Button>
</>
) : null}
</div>
</div>
);
}
npx shadcn@latest add @sevenui/component/calendar-08pnpm dlx shadcn@latest add @sevenui/component/calendar-08yarn dlx shadcn@latest add @sevenui/component/calendar-08bunx --bun shadcn@latest add @sevenui/component/calendar-08"use client";
import * as React from "react";
import { CheckIcon, TruckIcon, ZapIcon } from "lucide-react";
import { Button } from "@/components/ui/button";
import { Calendar } from "@/components/ui/calendar";
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/components/ui/card";
// The order is placed on Monday, October 5, 2026. Deliveries start two days
// later, never on Sundays, and nothing is scheduled past the end of October.
const orderDate = new Date(2026, 9, 5);
const firstDelivery = new Date(2026, 9, 7);
const lastDelivery = new Date(2026, 9, 31);
const expressUntil = new Date(2026, 9, 8);
const expressFee = 12;
function isExpress(date: Date) {
return date.getTime() <= expressUntil.getTime();
}
function formatDay(date: Date) {
return date.toLocaleDateString("en-US", {
weekday: "long",
month: "long",
day: "numeric",
});
}
export default function Calendar09() {
const [date, setDate] = React.useState<Date | undefined>(
new Date(2026, 9, 9),
);
const [reserved, setReserved] = React.useState<Date | undefined>();
const express = date ? isExpress(date) : false;
// Picking another day after reserving asks the shopper to confirm again.
const isReserved =
date !== undefined && reserved?.getTime() === date.getTime();
return (
<Card className="w-full max-w-sm">
<CardHeader>
<CardTitle>Choose a delivery date</CardTitle>
<CardDescription>
Oak desk, 140 cm · ships from the Portland warehouse
</CardDescription>
</CardHeader>
<CardContent className="flex flex-col gap-4">
<Calendar
mode="single"
selected={date}
onSelect={setDate}
defaultMonth={orderDate}
startMonth={orderDate}
endMonth={lastDelivery}
disabled={[
{ before: firstDelivery },
{ after: lastDelivery },
{ dayOfWeek: [0] },
]}
modifiers={{ express: { from: firstDelivery, to: expressUntil } }}
modifiersClassNames={{
express:
"after:pointer-events-none after:absolute after:bottom-1 after:left-1/2 after:z-20 after:h-0.5 after:w-3 after:-translate-x-1/2 after:rounded-full after:bg-chart-4",
}}
classNames={{ root: "w-full" }}
className="rounded-lg border p-2 [--cell-size:--spacing(8)] sm:p-3 sm:[--cell-size:--spacing(9)]"
/>
<div className="flex items-center gap-4 text-xs text-muted-foreground">
<span className="flex items-center gap-1.5">
<span
aria-hidden="true"
className="h-0.5 w-3 rounded-full bg-chart-4"
/>
Express, +${expressFee}
</span>
<span>Sundays unavailable</span>
</div>
<div
aria-live="polite"
className="flex items-start gap-3 rounded-lg bg-muted px-3 py-2.5"
>
{express ? (
<ZapIcon
aria-hidden="true"
className="mt-0.5 size-4 shrink-0 text-chart-4"
/>
) : (
<TruckIcon
aria-hidden="true"
className="mt-0.5 size-4 shrink-0 text-muted-foreground"
/>
)}
{date ? (
<div className="flex min-w-0 flex-1 flex-col gap-0.5 text-sm">
<span className="font-medium">{formatDay(date)}</span>
<span className="text-muted-foreground">
Between 8 AM and 6 PM · signature required
</span>
</div>
) : (
<span className="flex-1 text-sm text-muted-foreground">
Pick a day to see the delivery window.
</span>
)}
{date ? (
<span className="text-sm font-medium tabular-nums">
{express ? `$${expressFee}.00` : "Free"}
</span>
) : null}
</div>
</CardContent>
<CardFooter>
<Button
className="w-full"
disabled={!date || isReserved}
onClick={() => setReserved(date)}
>
{isReserved ? (
<>
<CheckIcon aria-hidden="true" data-icon="inline-start" />
Delivery date reserved
</>
) : (
"Continue to payment"
)}
</Button>
</CardFooter>
</Card>
);
}
npx shadcn@latest add @sevenui/component/calendar-09pnpm dlx shadcn@latest add @sevenui/component/calendar-09yarn dlx shadcn@latest add @sevenui/component/calendar-09bunx --bun shadcn@latest add @sevenui/component/calendar-09Out of office
Auto-reply to new messages and hand your open tickets to the on-call agent while you are away.
"use client";
import * as React from "react";
import type { DateRange } from "react-day-picker";
import { Button } from "@/components/ui/button";
import { Calendar } from "@/components/ui/calendar";
import { Label } from "@/components/ui/label";
import { Switch } from "@/components/ui/switch";
import { Textarea } from "@/components/ui/textarea";
const today = new Date(2026, 9, 5);
function countWorkingDays(range: DateRange | undefined) {
if (!range?.from) return 0;
const end = range.to ?? range.from;
let count = 0;
for (
let day = new Date(range.from);
day.getTime() <= end.getTime();
day.setDate(day.getDate() + 1)
) {
const weekday = day.getDay();
if (weekday !== 0 && weekday !== 6) count += 1;
}
return count;
}
function formatShort(date: Date) {
return date.toLocaleDateString("en-US", { month: "short", day: "numeric" });
}
type Settings = {
enabled: boolean;
range: DateRange | undefined;
message: string;
};
const initialSettings: Settings = {
enabled: true,
range: { from: new Date(2026, 9, 12), to: new Date(2026, 9, 20) },
message:
"Thanks for reaching out. I'm away and not checking messages. For billing questions, reply to this email and the on-call team will pick it up.",
};
function sameRange(a: DateRange | undefined, b: DateRange | undefined) {
return (
a?.from?.getTime() === b?.from?.getTime() &&
a?.to?.getTime() === b?.to?.getTime()
);
}
export default function Calendar10() {
const [saved, setSaved] = React.useState<Settings>(initialSettings);
const [enabled, setEnabled] = React.useState(saved.enabled);
const [range, setRange] = React.useState<DateRange | undefined>(
saved.range,
);
const [message, setMessage] = React.useState(saved.message);
const [justSaved, setJustSaved] = React.useState(false);
const dirty =
enabled !== saved.enabled ||
message !== saved.message ||
!sameRange(range, saved.range);
const workingDays = countWorkingDays(range);
const returnDate = range?.to
? new Date(
range.to.getFullYear(),
range.to.getMonth(),
range.to.getDate() + 1,
)
: undefined;
return (
<section
aria-labelledby="calendar-10-title"
className="flex w-full max-w-md flex-col rounded-xl border bg-card text-card-foreground"
>
<div className="flex items-start justify-between gap-4 p-4">
<div className="flex flex-col gap-1">
<h3 id="calendar-10-title" className="text-sm font-medium">
Out of office
</h3>
<p className="text-sm text-muted-foreground">
Auto-reply to new messages and hand your open tickets to the
on-call agent while you are away.
</p>
</div>
<Switch
checked={enabled}
onCheckedChange={(next) => {
setEnabled(next);
setJustSaved(false);
}}
aria-label="Enable out of office"
/>
</div>
<fieldset
disabled={!enabled}
className="flex flex-col gap-4 border-t p-4 transition-opacity disabled:opacity-60"
>
<legend className="sr-only">Out of office details</legend>
<div className="flex flex-col gap-2">
<span id="calendar-10-dates" className="text-sm font-medium">
Away dates
</span>
<Calendar
aria-labelledby="calendar-10-dates"
mode="range"
selected={range}
onSelect={(next) => {
setRange(next);
setJustSaved(false);
}}
defaultMonth={today}
startMonth={today}
disabled={enabled ? { before: today } : true}
classNames={{ root: "w-full" }}
className="rounded-lg border p-3"
/>
<p aria-live="polite" className="text-xs text-muted-foreground">
{range?.from && range.to && returnDate
? `${formatShort(range.from)} – ${formatShort(range.to)} · ${workingDays} working ${workingDays === 1 ? "day" : "days"} · back ${returnDate.toLocaleDateString("en-US", { weekday: "short", month: "short", day: "numeric" })}`
: "Select the first and last day you are away."}
</p>
</div>
<div className="flex flex-col gap-2">
<Label htmlFor="calendar-10-message">Auto-reply message</Label>
<Textarea
id="calendar-10-message"
rows={3}
value={message}
onChange={(event) => {
setMessage(event.target.value);
setJustSaved(false);
}}
/>
</div>
</fieldset>
<div className="flex items-center justify-end gap-2 border-t p-4">
<p
role="status"
className="mr-auto text-xs text-muted-foreground"
>
{dirty ? "Unsaved changes" : justSaved ? "Changes saved" : ""}
</p>
<Button
variant="ghost"
disabled={!dirty}
onClick={() => {
setEnabled(saved.enabled);
setRange(saved.range);
setMessage(saved.message);
}}
>
Discard
</Button>
<Button
disabled={!dirty || (enabled && !range?.to)}
onClick={() => {
setSaved({ enabled, range, message });
setJustSaved(true);
}}
>
Save changes
</Button>
</div>
</section>
);
}
npx shadcn@latest add @sevenui/component/calendar-10pnpm dlx shadcn@latest add @sevenui/component/calendar-10yarn dlx shadcn@latest add @sevenui/component/calendar-10bunx --bun shadcn@latest add @sevenui/component/calendar-10Maya Okafor
Onboarding call
- 30 minutes
- Video call
- Pacific Time (UTC−7)
We will connect your data sources and set up your first dashboard together.
Wed, Oct 7
"use client";
import * as React from "react";
import { CheckIcon, ClockIcon, GlobeIcon, VideoIcon } from "lucide-react";
import { Button } from "@/components/ui/button";
import { Calendar } from "@/components/ui/calendar";
import { Separator } from "@/components/ui/separator";
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group";
const today = new Date(2026, 9, 5);
const lastBookable = new Date(2026, 10, 30);
const allSlots = [
"9:00 AM",
"9:30 AM",
"10:30 AM",
"11:00 AM",
"1:00 PM",
"2:30 PM",
"3:00 PM",
"4:30 PM",
];
// Fully booked days, on top of weekends.
const fullyBooked = [new Date(2026, 9, 8), new Date(2026, 9, 15)];
// Deterministic sample availability: each date hides a different subset.
function slotsFor(date: Date) {
const seed = date.getDate() + date.getMonth();
return allSlots.filter((_, index) => (index + seed) % 3 !== 0);
}
export default function Calendar11() {
const [date, setDate] = React.useState<Date | undefined>(
new Date(2026, 9, 7),
);
const [slot, setSlot] = React.useState<string | undefined>();
const [booked, setBooked] = React.useState(false);
const slots = date ? slotsFor(date) : [];
if (booked && date && slot) {
return (
<div className="flex w-full max-w-sm flex-col items-center gap-3 rounded-xl border bg-card p-6 text-center text-card-foreground">
<span className="flex size-10 items-center justify-center rounded-full bg-primary text-primary-foreground">
<CheckIcon aria-hidden="true" className="size-5" />
</span>
<div className="flex flex-col gap-1" role="status">
<p className="font-medium">Your onboarding call is booked</p>
<p className="text-sm text-muted-foreground">
{date.toLocaleDateString("en-US", {
weekday: "long",
month: "long",
day: "numeric",
})}{" "}
at {slot}. A calendar invite with the video link is on its way.
</p>
</div>
<Button
variant="outline"
onClick={() => {
setBooked(false);
setSlot(undefined);
}}
>
Pick another time
</Button>
</div>
);
}
return (
<section
aria-labelledby="calendar-11-title"
className="flex w-full max-w-2xl flex-col rounded-xl border bg-card text-card-foreground sm:flex-row"
>
<div className="flex flex-col gap-3 p-4 sm:w-52 sm:shrink-0 sm:border-r">
<div className="flex flex-col gap-1">
<p className="text-sm text-muted-foreground">Maya Okafor</p>
<h3 id="calendar-11-title" className="font-medium">
Onboarding call
</h3>
</div>
<ul className="flex flex-col gap-2 text-sm text-muted-foreground">
<li className="flex items-center gap-2">
<ClockIcon aria-hidden="true" className="size-4" />
30 minutes
</li>
<li className="flex items-center gap-2">
<VideoIcon aria-hidden="true" className="size-4" />
Video call
</li>
<li className="flex items-center gap-2">
<GlobeIcon aria-hidden="true" className="size-4" />
Pacific Time (UTC−7)
</li>
</ul>
<p className="text-sm text-muted-foreground">
We will connect your data sources and set up your first dashboard
together.
</p>
</div>
<Separator className="sm:hidden" />
<div className="flex flex-1 flex-col gap-4 p-4 md:flex-row">
<Calendar
mode="single"
required
selected={date}
onSelect={(next) => {
setDate(next);
setSlot(undefined);
}}
defaultMonth={today}
startMonth={today}
endMonth={lastBookable}
disabled={[
{ before: new Date(2026, 9, 6) },
{ after: lastBookable },
{ dayOfWeek: [0, 6] },
...fullyBooked,
]}
className="mx-auto bg-transparent p-0"
/>
<div className="flex min-w-0 flex-1 flex-col gap-3 md:w-36 md:flex-none">
<p id="calendar-11-slots" className="text-sm font-medium">
{date
? date.toLocaleDateString("en-US", {
weekday: "short",
month: "short",
day: "numeric",
})
: "Pick a day"}
</p>
<ToggleGroup
aria-labelledby="calendar-11-slots"
variant="outline"
orientation="vertical"
spacing={2}
value={slot ? [slot] : []}
onValueChange={(next) => setSlot(next[0])}
className="grid w-full grid-cols-2 gap-2 md:grid-cols-1"
>
{slots.map((time) => (
<ToggleGroupItem
key={time}
value={time}
className="w-full tabular-nums data-pressed:border-primary data-pressed:bg-primary data-pressed:text-primary-foreground"
>
{time}
</ToggleGroupItem>
))}
</ToggleGroup>
<Button
className="mt-auto w-full"
disabled={!slot}
onClick={() => setBooked(true)}
>
{slot ? `Book ${slot}` : "Select a time"}
</Button>
</div>
</div>
</section>
);
}
npx shadcn@latest add @sevenui/component/calendar-11pnpm dlx shadcn@latest add @sevenui/component/calendar-11yarn dlx shadcn@latest add @sevenui/component/calendar-11bunx --bun shadcn@latest add @sevenui/component/calendar-11Trial signups
1,274-7.4%vs. previous 30 days
- Daily average
- 42
- Trial-to-paid
- 234 (18.4%)
"use client";
import * as React from "react";
import { CalendarIcon, ChevronDownIcon } from "lucide-react";
import type { DateRange } from "react-day-picker";
import { Button } from "@/components/ui/button";
import { Calendar } from "@/components/ui/calendar";
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover";
// Reporting runs up to yesterday; "today" is pinned so the sample is stable.
const today = new Date(2026, 9, 5);
const dataStart = new Date(2026, 0, 1);
const dayMs = 86_400_000;
function daysAgo(count: number) {
return new Date(today.getFullYear(), today.getMonth(), today.getDate() - count);
}
const presets: { label: string; range: () => DateRange }[] = [
{ label: "Yesterday", range: () => ({ from: daysAgo(1), to: daysAgo(1) }) },
{ label: "Last 7 days", range: () => ({ from: daysAgo(7), to: daysAgo(1) }) },
{
label: "Last 30 days",
range: () => ({ from: daysAgo(30), to: daysAgo(1) }),
},
{
label: "Last month",
range: () => ({
from: new Date(2026, 8, 1),
to: new Date(2026, 8, 30),
}),
},
{
label: "Quarter to date",
range: () => ({ from: new Date(2026, 9, 1), to: daysAgo(1) }),
},
];
// Deterministic daily trial signups for any date.
function signupsOn(date: Date) {
const n = Math.round(date.getTime() / dayMs);
const weekend = date.getDay() === 0 || date.getDay() === 6;
return (weekend ? 22 : 41) + ((n * 37) % 19);
}
function summarize(range: DateRange | undefined) {
if (!range?.from) return null;
const to = range.to ?? range.from;
const length = Math.round((to.getTime() - range.from.getTime()) / dayMs) + 1;
let total = 0;
let previous = 0;
for (let i = 0; i < length; i += 1) {
total += signupsOn(new Date(range.from.getTime() + i * dayMs));
previous += signupsOn(
new Date(range.from.getTime() - (length - i) * dayMs),
);
}
return {
total,
length,
average: Math.round(total / length),
change: previous ? ((total - previous) / previous) * 100 : 0,
};
}
function formatRange(range: DateRange | undefined) {
if (!range?.from) return "Pick a date range";
const short = (date: Date) =>
date.toLocaleDateString("en-US", { month: "short", day: "numeric" });
if (!range.to || range.to.getTime() === range.from.getTime()) {
return short(range.from);
}
return `${short(range.from)} – ${short(range.to)}`;
}
export default function Calendar12() {
const [applied, setApplied] = React.useState<DateRange | undefined>(
presets[2].range(),
);
const [draft, setDraft] = React.useState<DateRange | undefined>(applied);
const [open, setOpen] = React.useState(false);
// Controlled so a quick range brings its last month into view.
const [month, setMonth] = React.useState<Date>(applied?.to ?? today);
const stats = summarize(applied);
const activePreset = presets.find((preset) => {
const range = preset.range();
return (
draft?.from?.getTime() === range.from?.getTime() &&
draft?.to?.getTime() === range.to?.getTime()
);
});
return (
<section
aria-labelledby="calendar-12-title"
className="flex w-full max-w-md flex-col gap-4 rounded-xl border bg-card p-4 text-card-foreground"
>
<div className="flex flex-wrap items-center justify-between gap-2">
<h3 id="calendar-12-title" className="text-sm font-medium">
Trial signups
</h3>
<Popover
open={open}
onOpenChange={(next) => {
setOpen(next);
if (next) {
setDraft(applied);
setMonth(applied?.to ?? today);
}
}}
>
<PopoverTrigger
render={
<Button
variant="outline"
size="sm"
aria-label={`Reporting period: ${formatRange(applied)}`}
/>
}
>
<CalendarIcon aria-hidden="true" data-icon="inline-start" />
<span className="tabular-nums">{formatRange(applied)}</span>
<ChevronDownIcon aria-hidden="true" data-icon="inline-end" />
</PopoverTrigger>
<PopoverContent
align="end"
className="w-auto max-w-[calc(100vw-2rem)] gap-0 p-0"
>
<div className="flex flex-col sm:flex-row">
<fieldset
aria-label="Quick ranges"
className="flex min-w-0 gap-1 overflow-x-auto border-b p-2 sm:w-36 sm:flex-col sm:border-r sm:border-b-0"
>
{presets.map((preset) => (
<Button
key={preset.label}
variant={
activePreset?.label === preset.label
? "secondary"
: "ghost"
}
size="sm"
aria-pressed={activePreset?.label === preset.label}
className="shrink-0 justify-start"
onClick={() => {
const range = preset.range();
setDraft(range);
setMonth(range.to ?? range.from ?? today);
}}
>
{preset.label}
</Button>
))}
</fieldset>
<Calendar
mode="range"
selected={draft}
onSelect={setDraft}
month={month}
onMonthChange={setMonth}
startMonth={dataStart}
endMonth={today}
disabled={[{ before: dataStart }, { after: daysAgo(1) }]}
className="mx-auto p-3"
/>
</div>
<div className="flex items-center justify-between gap-2 border-t p-2">
<span className="pl-1 text-xs text-muted-foreground tabular-nums">
{formatRange(draft)}
</span>
<div className="flex gap-2">
<Button variant="ghost" size="sm" onClick={() => setOpen(false)}>
Cancel
</Button>
<Button
size="sm"
disabled={!draft?.from}
onClick={() => {
setApplied(draft);
setOpen(false);
}}
>
Apply
</Button>
</div>
</div>
</PopoverContent>
</Popover>
</div>
{stats ? (
<div aria-live="polite" className="flex flex-col gap-4">
<div className="flex flex-wrap items-baseline gap-x-2 gap-y-1">
<span className="text-3xl font-semibold tabular-nums">
{stats.total.toLocaleString("en-US")}
</span>
<span
className={
stats.change >= 0
? "text-sm font-medium text-success tabular-nums"
: "text-sm font-medium text-destructive tabular-nums"
}
>
{stats.change >= 0 ? "+" : ""}
{stats.change.toFixed(1)}%
</span>
<span className="text-sm text-muted-foreground">
vs. previous {stats.length} {stats.length === 1 ? "day" : "days"}
</span>
</div>
<dl className="grid grid-cols-2 gap-3 border-t pt-3 text-sm">
<div className="flex flex-col gap-0.5">
<dt className="text-muted-foreground">Daily average</dt>
<dd className="font-medium tabular-nums">{stats.average}</dd>
</div>
<div className="flex flex-col gap-0.5">
<dt className="text-muted-foreground">Trial-to-paid</dt>
<dd className="font-medium tabular-nums">
{Math.round(stats.total * 0.184).toLocaleString("en-US")}{" "}
<span className="text-muted-foreground">(18.4%)</span>
</dd>
</div>
</dl>
</div>
) : null}
</section>
);
}
npx shadcn@latest add @sevenui/component/calendar-12pnpm dlx shadcn@latest add @sevenui/component/calendar-12yarn dlx shadcn@latest add @sevenui/component/calendar-12bunx --bun shadcn@latest add @sevenui/component/calendar-12Team availability
Product design · 6 people
One person outTwo or more out
Wednesday, Oct 14
4 of 6 available
- PRPriya RamanOct 12 – Oct 16Vacation
- DKDaniel KimOct 14 – Oct 15Conference
Design reviews on this day need a stand-in reviewer.
"use client";
import * as React from "react";
import { PlusIcon, XIcon } from "lucide-react";
import { Avatar, AvatarFallback } from "@/components/ui/avatar";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Calendar } from "@/components/ui/calendar";
type Absence = {
name: string;
initials: string;
reason: string;
from: Date;
to: Date;
};
const teamSize = 6;
const absences: Absence[] = [
{
name: "Sofia Alvarez",
initials: "SA",
reason: "Sick leave",
from: new Date(2026, 9, 6),
to: new Date(2026, 9, 6),
},
{
name: "Priya Raman",
initials: "PR",
reason: "Vacation",
from: new Date(2026, 9, 12),
to: new Date(2026, 9, 16),
},
{
name: "Daniel Kim",
initials: "DK",
reason: "Conference",
from: new Date(2026, 9, 14),
to: new Date(2026, 9, 15),
},
{
name: "Liam O'Connor",
initials: "LO",
reason: "Vacation",
from: new Date(2026, 9, 21),
to: new Date(2026, 9, 23),
},
{
name: "Hana Sato",
initials: "HS",
reason: "Parental leave",
from: new Date(2026, 9, 26),
to: new Date(2026, 10, 20),
},
];
function awayOn(date: Date, list: Absence[] = absences) {
const time = date.getTime();
return list.filter(
(absence) => absence.from.getTime() <= time && time <= absence.to.getTime(),
);
}
function formatSpan(absence: Absence) {
const short = (date: Date) =>
date.toLocaleDateString("en-US", { month: "short", day: "numeric" });
return absence.from.getTime() === absence.to.getTime()
? short(absence.from)
: `${short(absence.from)} – ${short(absence.to)}`;
}
const dot =
"after:pointer-events-none after:absolute after:bottom-1 after:left-1/2 after:z-20 after:size-1 after:-translate-x-1/2 after:rounded-full";
export default function Calendar13() {
const [date, setDate] = React.useState<Date>(new Date(2026, 9, 14));
// Your own pending requests, one day each, shown alongside approved leave.
const [requests, setRequests] = React.useState<Absence[]>([]);
const all = [...absences, ...requests];
const away = awayOn(date, all);
const requested = requests.some(
(request) => request.from.getTime() === date.getTime(),
);
const available = teamSize - away.length;
return (
<section
aria-labelledby="calendar-13-title"
className="flex w-full max-w-2xl flex-col rounded-xl border bg-card text-card-foreground"
>
<div className="flex flex-wrap items-center justify-between gap-2 border-b p-4">
<div className="flex flex-col gap-0.5">
<h3 id="calendar-13-title" className="text-sm font-medium">
Team availability
</h3>
<p className="text-sm text-muted-foreground">
Product design · {teamSize} people
</p>
</div>
<Button
variant="outline"
size="sm"
onClick={() =>
setRequests((current) =>
requested
? current.filter(
(request) => request.from.getTime() !== date.getTime(),
)
: [
...current,
{
name: "You",
initials: "ME",
reason: "Pending",
from: date,
to: date,
},
],
)
}
>
{requested ? (
<XIcon aria-hidden="true" data-icon="inline-start" />
) : (
<PlusIcon aria-hidden="true" data-icon="inline-start" />
)}
{requested
? "Withdraw request"
: `Request ${date.toLocaleDateString("en-US", { month: "short", day: "numeric" })} off`}
</Button>
</div>
<div className="flex flex-col sm:flex-row">
<div className="flex flex-col gap-3 p-4 sm:border-r">
<Calendar
mode="single"
required
selected={date}
onSelect={setDate}
defaultMonth={date}
modifiers={{
oneAway: (day) => awayOn(day, all).length === 1,
shortStaffed: (day) => awayOn(day, all).length > 1,
}}
modifiersClassNames={{
oneAway: `${dot} after:bg-chart-2`,
shortStaffed: `${dot} after:bg-warning`,
}}
className="mx-auto bg-transparent p-0"
/>
<div className="flex flex-wrap justify-center gap-x-4 gap-y-1 text-xs text-muted-foreground">
<span className="flex items-center gap-1.5">
<span
aria-hidden="true"
className="size-1.5 rounded-full bg-chart-2"
/>
One person out
</span>
<span className="flex items-center gap-1.5">
<span
aria-hidden="true"
className="size-1.5 rounded-full bg-warning"
/>
Two or more out
</span>
</div>
</div>
<div
aria-live="polite"
className="flex min-w-0 flex-1 flex-col gap-3 border-t p-4 sm:border-t-0"
>
<div className="flex items-baseline justify-between gap-2">
<p className="text-sm font-medium">
{date.toLocaleDateString("en-US", {
weekday: "long",
month: "short",
day: "numeric",
})}
</p>
<p className="text-xs text-muted-foreground tabular-nums">
{available} of {teamSize} available
</p>
</div>
{away.length > 0 ? (
<ul className="flex flex-col gap-3">
{away.map((absence) => (
<li key={absence.name} className="flex items-center gap-3">
<Avatar size="sm">
<AvatarFallback>{absence.initials}</AvatarFallback>
</Avatar>
<div className="flex min-w-0 flex-1 flex-col">
<span className="truncate text-sm">{absence.name}</span>
<span className="text-xs text-muted-foreground tabular-nums">
{formatSpan(absence)}
</span>
</div>
<Badge variant="outline">{absence.reason}</Badge>
</li>
))}
</ul>
) : (
<p className="text-sm text-muted-foreground">
Everyone is in. No approved time off on this day.
</p>
)}
{away.length > 1 ? (
<p className="mt-auto rounded-lg bg-muted px-3 py-2 text-xs text-muted-foreground">
Design reviews on this day need a stand-in reviewer.
</p>
) : null}
</div>
</div>
</section>
);
}
npx shadcn@latest add @sevenui/component/calendar-13pnpm dlx shadcn@latest add @sevenui/component/calendar-13yarn dlx shadcn@latest add @sevenui/component/calendar-13bunx --bun shadcn@latest add @sevenui/component/calendar-13Backup schedule
orders-prod · PostgreSQL 16 · snapshots at 02:00 UTC
Repeat
First snapshot
Cron expression
0 2 * * 512 runs through Dec 31. Next after the first: Fri, Oct 16
"use client";
import * as React from "react";
import { DatabaseBackupIcon } from "lucide-react";
import { Button } from "@/components/ui/button";
import { Calendar } from "@/components/ui/calendar";
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group";
type Frequency = "daily" | "weekly" | "biweekly" | "monthly";
const frequencies: { value: Frequency; label: string }[] = [
{ value: "daily", label: "Daily" },
{ value: "weekly", label: "Weekly" },
{ value: "biweekly", label: "Biweekly" },
{ value: "monthly", label: "Monthly" },
];
const today = new Date(2026, 9, 5);
const horizon = new Date(2026, 11, 31);
// Every run from the start date until the end of the visible horizon.
function runsFrom(start: Date, frequency: Frequency) {
const runs: Date[] = [];
for (let i = 0; runs.length < 400; i += 1) {
let run: Date;
if (frequency === "monthly") {
// Monthly runs are limited to the 1st-28th so every month has the day.
run = new Date(start.getFullYear(), start.getMonth() + i, start.getDate());
} else {
const step = { daily: 1, weekly: 7, biweekly: 14 }[frequency];
run = new Date(
start.getFullYear(),
start.getMonth(),
start.getDate() + i * step,
);
}
if (run.getTime() > horizon.getTime()) break;
runs.push(run);
}
return runs;
}
function cronFor(start: Date, frequency: Frequency) {
if (frequency === "daily") return "0 2 * * *";
if (frequency === "monthly") {
return `0 2 ${start.getDate()} * *`;
}
return `0 2 * * ${start.getDay()}`;
}
export default function Calendar14() {
const [saved, setSaved] = React.useState<{
start: Date;
frequency: Frequency;
}>({ start: new Date(2026, 9, 9), frequency: "weekly" });
const [start, setStart] = React.useState<Date>(saved.start);
const [frequency, setFrequency] = React.useState<Frequency>(saved.frequency);
const [justSaved, setJustSaved] = React.useState(false);
const dirty =
start.getTime() !== saved.start.getTime() ||
frequency !== saved.frequency;
const runs = React.useMemo(
() => runsFrom(start, frequency),
[start, frequency],
);
const cron = cronFor(start, frequency);
return (
<section
aria-labelledby="calendar-14-title"
className="flex w-full max-w-md flex-col rounded-xl border bg-card text-card-foreground"
>
<div className="flex items-start gap-3 border-b p-4">
<span className="flex size-8 shrink-0 items-center justify-center rounded-lg bg-muted">
<DatabaseBackupIcon aria-hidden="true" className="size-4" />
</span>
<div className="flex min-w-0 flex-col gap-0.5">
<h3 id="calendar-14-title" className="text-sm font-medium">
Backup schedule
</h3>
<p className="text-sm text-muted-foreground">
orders-prod · PostgreSQL 16 · snapshots at 02:00 UTC
</p>
</div>
</div>
<div className="flex flex-col gap-4 p-4">
<div className="flex flex-col gap-2">
<span id="calendar-14-frequency" className="text-sm font-medium">
Repeat
</span>
<ToggleGroup
aria-labelledby="calendar-14-frequency"
variant="outline"
size="sm"
spacing={0}
value={[frequency]}
onValueChange={(next) => {
if (next.length === 0) return;
const value = next[0] as Frequency;
setFrequency(value);
setJustSaved(false);
if (value === "monthly" && start.getDate() > 28) {
setStart(
new Date(start.getFullYear(), start.getMonth(), 28),
);
}
}}
className="w-full"
>
{frequencies.map((item) => (
<ToggleGroupItem
key={item.value}
value={item.value}
className="flex-1"
>
{item.label}
</ToggleGroupItem>
))}
</ToggleGroup>
</div>
<div className="flex flex-col gap-2">
<span id="calendar-14-start" className="text-sm font-medium">
First snapshot
</span>
<Calendar
aria-labelledby="calendar-14-start"
mode="single"
required
selected={start}
onSelect={(next) => {
setStart(next);
setJustSaved(false);
}}
defaultMonth={today}
startMonth={today}
endMonth={horizon}
disabled={[
{ before: today },
{ after: horizon },
(day) => frequency === "monthly" && day.getDate() > 28,
]}
modifiers={{ run: runs.slice(1) }}
modifiersClassNames={{
run: "[&>button]:bg-primary/10 [&>button]:font-medium [&>button]:text-foreground",
}}
classNames={{ root: "w-full" }}
className="rounded-lg border p-3"
/>
</div>
<div className="flex flex-col gap-2 rounded-lg bg-muted p-3">
<div className="flex items-center justify-between gap-2">
<span className="text-xs text-muted-foreground">
Cron expression
</span>
<code className="rounded-md bg-background px-1.5 py-0.5 font-mono text-xs">
{cron}
</code>
</div>
<p aria-live="polite" className="text-sm">
<span className="font-medium tabular-nums">{runs.length}</span>{" "}
<span className="text-muted-foreground">
runs through Dec 31. Next after the first:{" "}
</span>
<span className="font-medium">
{runs[1]
? runs[1].toLocaleDateString("en-US", {
weekday: "short",
month: "short",
day: "numeric",
})
: "none"}
</span>
</p>
{frequency === "biweekly" ? (
<p className="text-xs text-muted-foreground">
Cron cannot express every other week; the scheduler skips
alternate matches.
</p>
) : null}
</div>
</div>
<div className="flex items-center justify-end gap-2 border-t p-4">
<p role="status" className="mr-auto text-xs text-muted-foreground">
{dirty ? "Unsaved changes" : justSaved ? "Schedule saved" : ""}
</p>
<Button
variant="ghost"
disabled={!dirty}
onClick={() => {
setStart(saved.start);
setFrequency(saved.frequency);
}}
>
Cancel
</Button>
<Button
disabled={!dirty}
onClick={() => {
setSaved({ start, frequency });
setJustSaved(true);
}}
>
Save schedule
</Button>
</div>
</section>
);
}
npx shadcn@latest add @sevenui/component/calendar-14pnpm dlx shadcn@latest add @sevenui/component/calendar-14yarn dlx shadcn@latest add @sevenui/component/calendar-14bunx --bun shadcn@latest add @sevenui/component/calendar-14Cedar cabin with lake view
Lake Placid, NY · sleeps 4 · 2-night minimum
- Oct 15 – Oct 18 · 3 nights
- $602
- Cleaning fee
- $85
- Service fee
- $72
- Total before taxes
- $759
You will not be charged until the host confirms.
"use client";
import * as React from "react";
import { cn } from "cn";
import { CheckIcon, StarIcon } from "lucide-react";
import type { DateRange } from "react-day-picker";
import { Button } from "@/components/ui/button";
import { Calendar, CalendarDayButton } from "@/components/ui/calendar";
import { Separator } from "@/components/ui/separator";
const today = new Date(2026, 9, 5);
const lastBookable = new Date(2027, 2, 31);
const minNights = 2;
const cleaningFee = 85;
const serviceRate = 0.12;
// Nights already reserved by other guests.
const reserved: DateRange[] = [
{ from: new Date(2026, 9, 9), to: new Date(2026, 9, 11) },
{ from: new Date(2026, 9, 22), to: new Date(2026, 9, 25) },
{ from: new Date(2026, 10, 12), to: new Date(2026, 10, 15) },
];
// Weekend nights cost more, and a few peak dates carry a premium.
function nightlyRate(date: Date) {
const weekday = date.getDay();
const base = weekday === 5 || weekday === 6 ? 219 : 164;
const peak = date.getMonth() === 10 && date.getDate() >= 25 ? 60 : 0;
return base + peak;
}
function PriceDayButton({
children,
day,
modifiers,
...props
}: React.ComponentProps<typeof CalendarDayButton>) {
const showPrice = !modifiers.outside && !modifiers.disabled;
return (
<CalendarDayButton
day={day}
modifiers={modifiers}
{...props}
className={cn(
"[&>span]:text-[0.625rem] sm:[&>span]:text-xs",
props.className,
)}
>
{children}
<span aria-hidden="true" className="tabular-nums">
{showPrice ? `$${nightlyRate(day.date)}` : " "}
</span>
</CalendarDayButton>
);
}
const currency = (value: number) =>
value.toLocaleString("en-US", {
style: "currency",
currency: "USD",
maximumFractionDigits: 0,
});
export default function Calendar15() {
const [range, setRange] = React.useState<DateRange | undefined>({
from: new Date(2026, 9, 15),
to: new Date(2026, 9, 18),
});
const [requested, setRequested] = React.useState<DateRange | undefined>();
const isRequested =
range?.from !== undefined &&
range.to !== undefined &&
requested?.from?.getTime() === range.from.getTime() &&
requested?.to?.getTime() === range.to.getTime();
const nights: Date[] = [];
if (range?.from && range.to) {
for (
let night = new Date(range.from);
night.getTime() < range.to.getTime();
night.setDate(night.getDate() + 1)
) {
nights.push(new Date(night));
}
}
const lodging = nights.reduce((sum, night) => sum + nightlyRate(night), 0);
const service = Math.round(lodging * serviceRate);
const total = lodging + cleaningFee + service;
const format = (date: Date) =>
date.toLocaleDateString("en-US", { month: "short", day: "numeric" });
return (
<section
aria-labelledby="calendar-15-title"
className="flex w-full max-w-md flex-col rounded-xl border bg-card text-card-foreground"
>
<div className="flex items-start justify-between gap-3 p-3 sm:p-4">
<div className="flex min-w-0 flex-col gap-0.5">
<h3 id="calendar-15-title" className="font-medium">
Cedar cabin with lake view
</h3>
<p className="text-sm text-muted-foreground">
Lake Placid, NY · sleeps 4 · {minNights}-night minimum
</p>
</div>
<span className="flex shrink-0 items-center gap-1 text-sm">
<StarIcon aria-hidden="true" className="size-3.5 fill-current" />
<span className="font-medium tabular-nums">4.92</span>
<span className="sr-only">out of 5 stars</span>
</span>
</div>
<Separator />
<div className="p-2 sm:p-4">
<Calendar
mode="range"
min={minNights}
excludeDisabled
selected={range}
onSelect={setRange}
defaultMonth={today}
startMonth={today}
endMonth={lastBookable}
disabled={[
{ before: new Date(2026, 9, 6) },
{ after: lastBookable },
...reserved,
]}
modifiers={{ reserved }}
modifiersClassNames={{
reserved: "[&>button]:line-through",
}}
components={{ DayButton: PriceDayButton }}
classNames={{ root: "w-full" }}
className="bg-transparent p-0 [--cell-size:--spacing(7)] sm:[--cell-size:--spacing(11)]"
/>
</div>
<Separator />
<div aria-live="polite" className="flex flex-col gap-3 p-3 sm:p-4">
{nights.length > 0 && range?.from && range.to ? (
<dl className="grid grid-cols-[1fr_auto] gap-y-1.5 text-sm">
<dt className="text-muted-foreground">
{format(range.from)} – {format(range.to)} · {nights.length}{" "}
nights
</dt>
<dd className="pl-4 text-right tabular-nums">{currency(lodging)}</dd>
<dt className="text-muted-foreground">Cleaning fee</dt>
<dd className="pl-4 text-right tabular-nums">{currency(cleaningFee)}</dd>
<dt className="text-muted-foreground">Service fee</dt>
<dd className="pl-4 text-right tabular-nums">{currency(service)}</dd>
<dt className="border-t pt-2 font-medium">Total before taxes</dt>
<dd className="border-t pt-2 pl-4 text-right font-medium tabular-nums">
{currency(total)}
</dd>
</dl>
) : (
<p className="text-sm text-muted-foreground">
{range?.from
? `Check-in ${format(range.from)}. Now pick a check-out date at least ${minNights} nights later.`
: "Pick your check-in date. Prices shown are per night."}
</p>
)}
<Button
className="w-full"
disabled={nights.length === 0 || isRequested}
onClick={() => setRequested(range)}
>
{isRequested ? (
<>
<CheckIcon aria-hidden="true" data-icon="inline-start" />
Request sent
</>
) : nights.length > 0 ? (
"Reserve"
) : (
"Check availability"
)}
</Button>
<p className="text-center text-xs text-muted-foreground">
{isRequested
? "The host usually replies within a few hours."
: "You will not be charged until the host confirms."}
</p>
</div>
</section>
);
}
npx shadcn@latest add @sevenui/component/calendar-15pnpm dlx shadcn@latest add @sevenui/component/calendar-15yarn dlx shadcn@latest add @sevenui/component/calendar-15bunx --bun shadcn@latest add @sevenui/component/calendar-15