본문으로 건너뛰기
언어

Calendarstandard

날짜를 고르는 달력(calendar)입니다. shadcn base-nova 의 원본을 그대로 담은 아이템이며 react-day-picker 10 의 `DayPicker` 에 스타일과 버튼을 얹습니다. 한국 공휴일·주 시작 요일 같은 지역 규칙은 이 아이템에 없습니다.

설치

pnpm
pnpm dlx shadcn@4.23.0 add @uikit/calendar

의존 아이템button

Default

calendar-example.tsx
"use client"
import * as React from "react"import { addDays, format } from "date-fns"import { type DateRange } from "react-day-picker"import { es } from "react-day-picker/locale"
import {  Example,  ExampleWrapper,} from "@/components/example"import { Button } from "@/components/ui/button"import { Calendar, CalendarDayButton } from "@/components/ui/calendar"import { Card, CardContent, CardFooter } from "@/components/ui/card"import { Field, FieldGroup, FieldLabel } from "@/components/ui/field"import {  InputGroup,  InputGroupAddon,  InputGroupInput,} from "@/components/ui/input-group"import {  Popover,  PopoverContent,  PopoverTrigger,} from "@/components/ui/popover"import { CalendarIcon, ChevronDownIcon, Clock2Icon } from "lucide-react"
export default function CalendarExample() {  return (    <ExampleWrapper>      <CalendarSingle />      <CalendarMultiple />      <CalendarWeekNumbers />      <CalendarBookedDates />      <CalendarRange />      <CalendarRangeMultipleMonths />      <CalendarWithTime />      <CalendarWithPresets />      <CalendarCustomDays />      <DatePickerSimple />      <DataPickerWithDropdowns />      <DatePickerWithRange />      <CalendarInCard />      <CalendarInPopover />    </ExampleWrapper>  )}
function CalendarInCard() {  return (    <Example title="In Card">      <Card className="mx-auto w-fit p-0">        <CardContent className="p-0">          <Calendar mode="single" />        </CardContent>      </Card>    </Example>  )}
function CalendarInPopover() {  return (    <Example title="In Popover">      <Popover>        <PopoverTrigger          render={<Button variant="outline" className="px-2.5 font-normal" />}        >          <CalendarIcon data-icon="inline-start" />          Open Calendar        </PopoverTrigger>        <PopoverContent className="w-auto p-0" align="start">          <Calendar mode="single" />        </PopoverContent>      </Popover>    </Example>  )}
function CalendarSingle() {  const [date, setDate] = React.useState<Date | undefined>(    new Date(new Date().getFullYear(), new Date().getMonth(), 12)  )  return (    <Example title="Single">      <Card className="mx-auto w-fit p-0">        <CardContent className="p-0">          <Calendar            mode="single"            selected={date}            onSelect={setDate}            captionLayout="dropdown"          />        </CardContent>      </Card>    </Example>  )}
function CalendarMultiple() {  return (    <Example title="Multiple">      <Card className="mx-auto w-fit p-0">        <CardContent className="p-0">          <Calendar mode="multiple" />        </CardContent>      </Card>    </Example>  )}
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 (    <Example      title="Range"      containerClassName="lg:col-span-full 2xl:col-span-full"      className="p-12"    >      <Card className="mx-auto w-fit p-0">        <CardContent className="p-0">          <Calendar            mode="range"            defaultMonth={dateRange?.from}            selected={dateRange}            onSelect={setDateRange}            numberOfMonths={2}            disabled={(date) =>              date > new Date() || date < new Date("1900-01-01")            }          />        </CardContent>      </Card>    </Example>  )}
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 (    <Example      title="Range Multiple Months"      containerClassName="lg:col-span-full 2xl:col-span-full"      className="p-12"    >      <Card className="mx-auto w-fit p-0">        <CardContent className="p-0">          <Calendar            mode="range"            defaultMonth={range?.from}            selected={range}            onSelect={setRange}            numberOfMonths={3}            locale={es}            fixedWeeks          />        </CardContent>      </Card>    </Example>  )}
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(), 1, 12 + i)  )
  return (    <Example title="Booked Dates">      <Card className="mx-auto w-fit p-0">        <CardContent className="p-0">          <Calendar            mode="single"            defaultMonth={date}            selected={date}            onSelect={setDate}            disabled={bookedDates}            modifiers={{              booked: bookedDates,            }}            modifiersClassNames={{              booked: "[&>button]:line-through opacity-100",            }}          />        </CardContent>      </Card>    </Example>  )}
function CalendarWithTime() {  const [date, setDate] = React.useState<Date | undefined>(    new Date(new Date().getFullYear(), new Date().getMonth(), 12)  )
  return (    <Example title="With Time">      <Card size="sm" className="mx-auto w-fit">        <CardContent>          <Calendar            mode="single"            selected={date}            onSelect={setDate}            className="p-0"          />        </CardContent>        <CardFooter className="border-t bg-card">          <FieldGroup>            <Field>              <FieldLabel htmlFor="time-from">Start Time</FieldLabel>              <InputGroup>                <InputGroupInput                  id="time-from"                  type="time"                  step="1"                  defaultValue="10:30:00"                  className="appearance-none [&::-webkit-calendar-picker-indicator]:hidden [&::-webkit-calendar-picker-indicator]:appearance-none"                />                <InputGroupAddon>                  <Clock2Icon className="text-muted-foreground" />                </InputGroupAddon>              </InputGroup>            </Field>            <Field>              <FieldLabel htmlFor="time-to">End Time</FieldLabel>              <InputGroup>                <InputGroupInput                  id="time-to"                  type="time"                  step="1"                  defaultValue="12:30:00"                  className="appearance-none [&::-webkit-calendar-picker-indicator]:hidden [&::-webkit-calendar-picker-indicator]:appearance-none"                />                <InputGroupAddon>                  <Clock2Icon className="text-muted-foreground" />                </InputGroupAddon>              </InputGroup>            </Field>          </FieldGroup>        </CardFooter>      </Card>    </Example>  )}
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 (    <Example title="Custom Days">      <Card className="mx-auto w-fit p-0">        <CardContent className="p-0">          <Calendar            mode="range"            defaultMonth={range?.from}            selected={range}            onSelect={setRange}            numberOfMonths={1}            captionLayout="dropdown"            className="[--cell-size:--spacing(10)] md:[--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
                const price = isWeekend ? "$120" : "$100"
                return (                  <CalendarDayButton                    day={day}                    modifiers={modifiers}                    {...props}                    aria-label={                      modifiers.outside                        ? props["aria-label"]                        : `${day.date.getDate()} ${price}, ${props["aria-label"]}`                    }                  >                    {children}                    {!modifiers.outside && <span>{price}</span>}                  </CalendarDayButton>                )              },            }}          />        </CardContent>      </Card>    </Example>  )}
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 (    <Example title="With Presets">      <Card className="mx-auto w-fit max-w-[300px]" size="sm">        <CardContent>          <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">          {[            { 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>    </Example>  )}
function DatePickerSimple() {  const [date, setDate] = React.useState<Date>()
  return (    <Example title="Date Picker Simple">      <Field className="mx-auto w-72">        <FieldLabel htmlFor="date-picker-simple">Date</FieldLabel>        <Popover>          <PopoverTrigger            render={              <Button                variant="outline"                id="date-picker-simple"                className="justify-start px-2.5 font-normal"              />            }          >            <CalendarIcon data-icon="inline-start" />            {date ? format(date, "PPP") : <span>Pick a date</span>}          </PopoverTrigger>          <PopoverContent className="w-auto p-0" align="start">            <Calendar mode="single" selected={date} onSelect={setDate} />          </PopoverContent>        </Popover>      </Field>    </Example>  )}
function DatePickerWithRange() {  const [date, setDate] = React.useState<DateRange | undefined>({    from: new Date(new Date().getFullYear(), 0, 20),    to: addDays(new Date(new Date().getFullYear(), 0, 20), 20),  })
  return (    <Example title="Date Picker Range">      <Field className="mx-auto w-72">        <FieldLabel htmlFor="date-picker-range">Date Picker Range</FieldLabel>        <Popover>          <PopoverTrigger            render={              <Button                variant="outline"                id="date-picker-range"                className="justify-start px-2.5 font-normal"              />            }          >            <CalendarIcon data-icon="inline-start" />            {date?.from ? (              date.to ? (                <>                  {format(date.from, "LLL dd, y")} -{" "}                  {format(date.to, "LLL dd, y")}                </>              ) : (                format(date.from, "LLL dd, y")              )            ) : (              <span>Pick a date</span>            )}          </PopoverTrigger>          <PopoverContent className="w-auto p-0" align="start">            <Calendar              mode="range"              defaultMonth={date?.from}              selected={date}              onSelect={setDate}              numberOfMonths={2}            />          </PopoverContent>        </Popover>      </Field>    </Example>  )}
function DataPickerWithDropdowns() {  const [date, setDate] = React.useState<Date>()  const [open, setOpen] = React.useState(false)
  return (    <Example title="Date Picker with Dropdowns">      <Field className="mx-auto w-72">        <Popover open={open} onOpenChange={setOpen}>          <FieldLabel htmlFor="date-picker-with-dropdowns-desktop">            Date          </FieldLabel>          <PopoverTrigger            render={              <Button                variant="outline"                id="date-picker-with-dropdowns-desktop"                className="justify-start px-2.5 font-normal"              />            }          >            {date ? format(date, "PPP") : <span>Pick a date</span>}            <ChevronDownIcon data-icon="inline-start" className="ml-auto" />          </PopoverTrigger>          <PopoverContent className="w-auto p-0" align="start">            <Calendar              mode="single"              selected={date}              onSelect={setDate}              captionLayout="dropdown"            />            <div className="flex gap-2 border-t p-2">              <Button                variant="outline"                size="sm"                className="w-full"                onClick={() => setOpen(false)}              >                Done              </Button>            </div>          </PopoverContent>        </Popover>      </Field>    </Example>  )}
function CalendarWeekNumbers() {  const [date, setDate] = React.useState<Date | undefined>(    new Date(new Date().getFullYear(), 1, 3)  )
  return (    <Example title="Week Numbers" className="justify-center">      <Card className="mx-auto w-fit p-0">        <CardContent className="p-0">          <Calendar            mode="single"            defaultMonth={date}            selected={date}            onSelect={setDate}            showWeekNumber          />        </CardContent>      </Card>    </Example>  )}