Date Picker
A date picker component with calendar popup for date selection
registry/preskok/ui/preskok-ui/date-picker.tsx
"use client"
import type { DateDuration } from "@internationalized/date"
import { twJoin } from "cn"
import { CalendarDaysIcon } from "lucide-react"
import type {
DatePickerProps as DatePickerPrimitiveProps,
DateValue,
PopoverProps,
} from "react-aria-components/DatePicker"
import { DatePicker as DatePickerPrimitive } from "react-aria-components/DatePicker"
import { useMediaQuery } from "@/hooks/use-media-query"
import { cx } from "@/lib/primitive"
import { Button } from "@/components/ui/preskok-ui/button"
import { DateInput as PrimitiveDateInput } from "@/components/ui/preskok-ui/date-field"
import { fieldStyles } from "@/components/ui/preskok-ui/field"
import { InputGroup } from "@/components/ui/preskok-ui/input"
import { Calendar } from "./calendar"
import { ModalContent } from "./modal"
import { PopoverContent } from "./popover"
import { RangeCalendar } from "./range-calendar"
export interface DatePickerProps<
T extends DateValue,
> extends DatePickerPrimitiveProps<T> {
popover?: Omit<PopoverProps, "children">
}
export function DatePicker<T extends DateValue>({
className,
children,
popover,
...props
}: DatePickerProps<T>) {
return (
<DatePickerPrimitive
data-slot="control"
className={cx(fieldStyles(), className)}
{...props}
>
{(values) => (
<>
{typeof children === "function" ? children(values) : children}
<DatePickerOverlay {...popover} />
</>
)}
</DatePickerPrimitive>
)
}
export interface DatePickerOverlayProps extends Omit<PopoverProps, "children"> {
range?: boolean
visibleDuration?: DateDuration
pageBehavior?: "visible" | "single"
}
export function DatePickerOverlay({
visibleDuration = { months: 1 },
pageBehavior = "visible",
placement = "bottom",
range,
...props
}: DatePickerOverlayProps) {
const isMobile = useMediaQuery("(max-width: 768px)") ?? false
return isMobile ? (
<ModalContent aria-label="Date picker" closeButton={false}>
<div className="flex justify-center p-6">
{range ? (
<RangeCalendar
pageBehavior={pageBehavior}
visibleDuration={visibleDuration}
/>
) : (
<Calendar />
)}
</div>
</ModalContent>
) : (
<PopoverContent
placement={placement}
arrow={false}
className={twJoin(
"flex max-w-none min-w-auto snap-x justify-center p-4 sm:min-w-[16.5rem] sm:p-2 sm:pt-3",
visibleDuration?.months === 1 ? "sm:max-w-2xs" : "sm:max-w-none"
)}
{...props}
>
{range ? (
<RangeCalendar
pageBehavior={pageBehavior}
visibleDuration={visibleDuration}
/>
) : (
<Calendar />
)}
</PopoverContent>
)
}
export function DatePickerTrigger({
className,
...props
}: React.ComponentProps<typeof InputGroup>) {
return (
<InputGroup
className={twJoin(
"flex items-center rounded-lg",
"border-input hover:border-muted-foreground/30 border",
"focus-visible:focus-within:border-ring/70 focus-visible:focus-within:ring-ring/20 focus-visible:focus-within:hover:border-ring/80 focus-visible:focus-within:ring-3 focus-visible:focus-within:outline-hidden",
"invalid:border-destructive/70 focus-visible:focus-within:invalid:border-destructive/70 focus-visible:focus-within:invalid:ring-destructive/20 invalid:hover:border-destructive/80 focus-visible:focus-within:invalid:hover:border-destructive/80",
"disabled:bg-muted disabled:opacity-50",
className
)}
{...props}
>
<div className="flex w-fit flex-1 [scrollbar-width:none] items-center overflow-x-auto overflow-y-clip">
<DateInput className="px-3 pr-2" />
</div>
<Button
intent="plain"
size="sq-xs"
data-slot="date-picker-trigger"
className={twJoin(
"touch-target focus-visible:text-foreground grid place-content-center outline-hidden",
"pressed:text-foreground text-muted-foreground hover:text-foreground focus-visible:text-foreground",
"mr-1 shrink-0"
)}
>
<CalendarDaysIcon data-slot="icon" className="size-4" />
</Button>
</InputGroup>
)
}
function DateInput({
className,
...props
}: React.ComponentProps<typeof PrimitiveDateInput>) {
return (
<PrimitiveDateInput
className={cx(
"rounded-none border-none focus-visible:focus-within:ring-0",
className
)}
{...props}
/>
)
}
- Where it goes
- calendar
- Interaction
- modaldropdown
DependenciesBundle weight · light
- icons
- lucide-react