Files
zeekay 05fc512820 feat: sync upstream shadcn/ui — [email protected], v4 app, font system, chart improvements
Upstream sync from shadcn-ui/ui (1686 commits since last merge base).

Key changes:
- packages/shadcn upgraded to 4.1.0 (CLI font transformers, scaffold from github, chart colors)
- apps/v4 replaces apps/www as the primary docs/registry app
- Font system: 25+ heading font registries, font markers utility, @supports override
- Chart improvements: recharts v3 compat, radial chart fixes
- templates/ updated with monorepo variants (astro, vite, react-router, start)
- skills/shadcn AI skill definitions added
- deprecated/ removed (auth, auth-firebase, cli, www)

Preserved Hanzo-only paths: app/, pkg/, demo/, docs/, template/next/
2026-03-24 19:04:31 -07:00

277 lines
8.5 KiB
TypeScript

"use client"
import * as React from "react"
import { addDays } from "date-fns"
import { Clock2Icon } from "lucide-react"
import { type DateRange } from "react-day-picker"
import { es } from "react-day-picker/locale"
import { Button } from "@/registry/new-york-v4/ui/button"
import { Calendar, CalendarDayButton } from "@/registry/new-york-v4/ui/calendar"
import { Card, CardContent, CardFooter } from "@/registry/new-york-v4/ui/card"
import { Input } from "@/registry/new-york-v4/ui/input"
import { Label } from "@/registry/new-york-v4/ui/label"
export function CalendarDemo() {
return (
<div className="flex flex-1 flex-col flex-wrap justify-center gap-8 bg-muted p-10 lg:flex-row">
<CalendarSingle />
<CalendarMultiple />
<CalendarRange />
<CalendarBookedDates />
<CalendarRangeMultipleMonths />
<CalendarWithTime />
<CalendarWithPresets />
<CalendarCustomDays />
</div>
)
}
function CalendarSingle() {
const [date, setDate] = React.useState<Date | undefined>(
new Date(new Date().getFullYear(), new Date().getMonth(), 12)
)
return (
<div className="flex flex-col gap-3">
<div className="px-2 text-center text-sm">Single Selection</div>
<Calendar
mode="single"
selected={date}
onSelect={setDate}
className="rounded-lg border shadow-sm"
captionLayout="dropdown"
/>
</div>
)
}
function CalendarMultiple() {
return (
<div className="flex flex-col gap-3">
<div className="px-2 text-center text-sm">Multiple Selection</div>
<Calendar mode="multiple" className="rounded-lg border shadow-sm" />
</div>
)
}
function CalendarRange() {
const [dateRange, setDateRange] = React.useState<DateRange | undefined>({
from: new Date(new Date().getFullYear(), 0, 12),
to: addDays(new Date(new Date().getFullYear(), 0, 12), 30),
})
return (
<div className="flex flex-col gap-3">
<div className="px-2 text-center text-sm">Range Selection</div>
<Calendar
mode="range"
defaultMonth={dateRange?.from}
selected={dateRange}
onSelect={setDateRange}
numberOfMonths={2}
disabled={(date) => date > new Date() || date < new Date("1900-01-01")}
className="rounded-lg border shadow-sm"
/>
</div>
)
}
function CalendarRangeMultipleMonths() {
const [range, setRange] = React.useState<DateRange | undefined>({
from: new Date(new Date().getFullYear(), 3, 12),
to: addDays(new Date(new Date().getFullYear(), 3, 12), 60),
})
return (
<div className="flex flex-col gap-3">
<div className="px-2 text-center text-sm">Range Selection + Locale</div>
<Calendar
mode="range"
defaultMonth={range?.from}
selected={range}
onSelect={setRange}
numberOfMonths={3}
locale={es}
fixedWeeks
className="rounded-lg border shadow-sm"
/>
</div>
)
}
function CalendarBookedDates() {
const [date, setDate] = React.useState<Date | undefined>(
new Date(new Date().getFullYear(), 1, 3)
)
const bookedDates = Array.from(
{ length: 15 },
(_, i) => new Date(new Date().getFullYear(), new Date().getMonth(), 12 + i)
)
return (
<div className="flex flex-col gap-3">
<div className="px-2 text-center text-sm">With booked dates</div>
<Calendar
mode="single"
defaultMonth={date}
selected={date}
onSelect={setDate}
disabled={bookedDates}
modifiers={{
booked: bookedDates,
}}
modifiersClassNames={{
booked: "[&>button]:line-through opacity-100",
}}
className="rounded-lg border shadow-sm"
/>
</div>
)
}
function CalendarWithTime() {
const [date, setDate] = React.useState<Date | undefined>(
new Date(new Date().getFullYear(), new Date().getMonth(), 12)
)
return (
<div className="flex flex-col gap-3">
<div className="px-2 text-center text-sm">With Time Input</div>
<Card className="w-fit py-4">
<CardContent className="px-4">
<Calendar
mode="single"
selected={date}
onSelect={setDate}
className="p-0"
/>
</CardContent>
<CardFooter className="flex flex-col gap-3 border-t px-4 pt-4">
<div className="flex w-full flex-col gap-2">
<Label htmlFor="time-from">Start Time</Label>
<div className="relative flex w-full items-center gap-2">
<Clock2Icon className="pointer-events-none absolute left-2.5 size-4 text-muted-foreground select-none" />
<Input
id="time-from"
type="time"
step="1"
defaultValue="10:30:00"
className="appearance-none pl-8 [&::-webkit-calendar-picker-indicator]:hidden [&::-webkit-calendar-picker-indicator]:appearance-none"
/>
</div>
</div>
<div className="flex w-full flex-col gap-2">
<Label htmlFor="time-to">End Time</Label>
<div className="relative flex w-full items-center gap-2">
<Clock2Icon className="pointer-events-none absolute left-2.5 size-4 text-muted-foreground select-none" />
<Input
id="time-to"
type="time"
step="1"
defaultValue="12:30:00"
className="appearance-none pl-8 [&::-webkit-calendar-picker-indicator]:hidden [&::-webkit-calendar-picker-indicator]:appearance-none"
/>
</div>
</div>
</CardFooter>
</Card>
</div>
)
}
function CalendarCustomDays() {
const [range, setRange] = React.useState<DateRange | undefined>({
from: new Date(new Date().getFullYear(), 11, 8),
to: addDays(new Date(new Date().getFullYear(), 11, 8), 10),
})
return (
<div className="flex flex-col gap-3">
<div className="px-2 text-center text-sm">
With Custom Days and Formatters
</div>
<Calendar
mode="range"
defaultMonth={range?.from}
selected={range}
onSelect={setRange}
numberOfMonths={1}
captionLayout="dropdown"
className="rounded-lg border shadow-sm [--cell-size:--spacing(12)]"
formatters={{
formatMonthDropdown: (date) => {
return date.toLocaleString("default", { month: "long" })
},
}}
components={{
DayButton: ({ children, modifiers, day, ...props }) => {
const isWeekend = day.date.getDay() === 0 || day.date.getDay() === 6
return (
<CalendarDayButton day={day} modifiers={modifiers} {...props}>
{children}
{!modifiers.outside && (
<span>{isWeekend ? "$120" : "$100"}</span>
)}
</CalendarDayButton>
)
},
}}
/>
</div>
)
}
function CalendarWithPresets() {
const [date, setDate] = React.useState<Date | undefined>(
new Date(new Date().getFullYear(), 1, 12)
)
const [currentMonth, setCurrentMonth] = React.useState<Date>(
new Date(new Date().getFullYear(), new Date().getMonth(), 1)
)
return (
<div className="flex max-w-[300px] flex-col gap-3">
<div className="px-2 text-center text-sm">With Presets</div>
<Card className="w-fit py-4">
<CardContent className="px-4">
<Calendar
mode="single"
selected={date}
onSelect={setDate}
month={currentMonth}
onMonthChange={setCurrentMonth}
fixedWeeks
className="p-0 [--cell-size:--spacing(9.5)]"
/>
</CardContent>
<CardFooter className="flex flex-wrap gap-2 border-t px-4 pt-4">
{[
{ label: "Today", value: 0 },
{ label: "Tomorrow", value: 1 },
{ label: "In 3 days", value: 3 },
{ label: "In a week", value: 7 },
{ label: "In 2 weeks", value: 14 },
].map((preset) => (
<Button
key={preset.value}
variant="outline"
size="sm"
className="flex-1"
onClick={() => {
const newDate = addDays(new Date(), preset.value)
setDate(newDate)
setCurrentMonth(
new Date(newDate.getFullYear(), newDate.getMonth(), 1)
)
}}
>
{preset.label}
</Button>
))}
</CardFooter>
</Card>
</div>
)
}