calendar

PreviousNext
Docs
takiui

Preview

Loading preview…
registry/new-york/ui/calendar.tsx
"use client"

import React from "react"
import { ChevronLeft, ChevronRight } from "lucide-react"
import {
  Calendar as AriaCalendar,
  CalendarGridHeader as AriaCalendarGridHeader,
  CalendarProps as AriaCalendarProps,
  CalendarCell,
  CalendarGrid,
  CalendarGridBody,
  CalendarHeaderCell,
  DateValue,
  Heading,
  Text,
  useLocale,
} from "react-aria-components"
import { tv } from "tailwind-variants"

import { cn, focusRing } from "@/lib/utils"
import { Button } from "@/registry/new-york/ui/button"

export const cellStyles = tv({
  extend: focusRing,
  base: "flex h-8 w-8 mt-1 cursor-default items-center justify-center rounded-md text-sm font-normal forced-color-adjust-none transition-[box-shadow]",
  variants: {
    isSelected: {
      false: "hover:bg-accent hover:text-accent-foreground",
      true: "bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground focus:bg-primary focus:text-primary-foreground",
    },
    isDisabled: {
      true: "text-muted-foreground opacity-50",
    },
    isOutsideMonth: {
      true: "text-muted-foreground opacity-50",
    },
    isPressed: {
      true: "ring-ring/50 ring-[3px] border-ring",
    },
    isUnavailable: {
      true: "text-muted-foreground opacity-50",
    },
  },
})

export interface CalendarProps<T extends DateValue>
  extends Omit<AriaCalendarProps<T>, "visibleDuration"> {
  errorMessage?: string
  className?: string
}

export function Calendar<T extends DateValue>({
  errorMessage,
  className,
  ...props
}: CalendarProps<T>) {
  return (
    <AriaCalendar
      className={cn("bg-card flex w-fit flex-col gap-3 p-3", className)}
      {...props}
    >
      <CalendarHeader />
      <CalendarGrid className="mt-1 w-full border-collapse space-y-1">
        <CalendarGridHeader />
        <CalendarGridBody>
          {(date) => <CalendarCell date={date} className={cellStyles} />}
        </CalendarGridBody>
      </CalendarGrid>
      {errorMessage && (
        <Text slot="errorMessage" className="text-destructive text-sm">
          {errorMessage}
        </Text>
      )}
    </AriaCalendar>
  )
}

export function CalendarHeader() {
  const { direction } = useLocale()

  return (
    <header className="flex w-full items-center justify-between">
      <Button variant="outline" size="icon" className="h-7 w-7" slot="previous">
        {direction === "rtl" ? (
          <ChevronRight aria-hidden className="h-4 w-4" />
        ) : (
          <ChevronLeft aria-hidden className="h-4 w-4" />
        )}
      </Button>
      <Heading className="text-sm font-medium" />
      <Button variant="outline" size="icon" className="h-7 w-7" slot="next">
        {direction === "rtl" ? (
          <ChevronLeft aria-hidden className="h-4 w-4" />
        ) : (
          <ChevronRight aria-hidden className="h-4 w-4" />
        )}
      </Button>
    </header>
  )
}

export function CalendarGridHeader() {
  return (
    <AriaCalendarGridHeader>
      {(day) => (
        <CalendarHeaderCell className="text-muted-foreground w-9 text-[0.8rem] font-normal">
          {day}
        </CalendarHeaderCell>
      )}
    </AriaCalendarGridHeader>
  )
}

Installation

npx shadcn@latest add @taki/calendar

Usage

import { Calendar } from "@/components/ui/calendar"
<Calendar />