mirror of
https://github.com/hanzoai/shadcn.git
synced 2026-11-03 03:18:52 +00:00
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/
277 lines
8.5 KiB
TypeScript
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>
|
|
)
|
|
}
|