2025-10-10 06:21:56 +00:00
|
|
|
|
import React from "react";
|
2025-03-17 17:06:33 +00:00
|
|
|
|
import {
|
|
|
|
|
|
DateRangePicker as AriaDateRangePicker,
|
|
|
|
|
|
DateRangePickerProps as AriaDateRangePickerProps,
|
|
|
|
|
|
DateRangePickerStateContext,
|
|
|
|
|
|
DateValue,
|
|
|
|
|
|
Group,
|
2025-10-10 09:06:44 +00:00
|
|
|
|
useLocale,
|
2025-10-10 06:21:56 +00:00
|
|
|
|
} from "react-aria-components";
|
2025-10-10 09:06:44 +00:00
|
|
|
|
import { twMerge } from "tailwind-merge";
|
2025-10-10 06:21:56 +00:00
|
|
|
|
import { Button } from "./button";
|
|
|
|
|
|
import { DateInput } from "./date-field";
|
|
|
|
|
|
import { Dialog } from "./dialog";
|
2025-10-10 09:06:44 +00:00
|
|
|
|
import { CalendarIcon } from "./icons";
|
2025-10-10 06:21:56 +00:00
|
|
|
|
import { Popover } from "./popover";
|
|
|
|
|
|
import { RangeCalendar } from "./range-calendar";
|
|
|
|
|
|
import { composeTailwindRenderProps, inputField } from "./utils";
|
2025-03-17 17:06:33 +00:00
|
|
|
|
|
2025-10-10 06:21:56 +00:00
|
|
|
|
export interface DateRangePickerProps<T extends DateValue> extends AriaDateRangePickerProps<T> {}
|
2025-03-17 17:06:33 +00:00
|
|
|
|
|
2025-10-10 06:21:56 +00:00
|
|
|
|
export function DateRangePicker<T extends DateValue>({ ...props }: DateRangePickerProps<T>) {
|
2025-03-17 17:06:33 +00:00
|
|
|
|
return (
|
|
|
|
|
|
<AriaDateRangePicker
|
|
|
|
|
|
{...props}
|
|
|
|
|
|
className={composeTailwindRenderProps(props.className, inputField)}
|
|
|
|
|
|
/>
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export function DateRangePickerInput() {
|
|
|
|
|
|
const { locale } = useLocale();
|
|
|
|
|
|
const state = React.useContext(DateRangePickerStateContext);
|
|
|
|
|
|
const formattedValue = state?.formatValue(locale, {});
|
|
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
|
<>
|
|
|
|
|
|
<Group
|
|
|
|
|
|
data-ui="control"
|
|
|
|
|
|
className={({ isFocusWithin }) =>
|
|
|
|
|
|
twMerge(
|
2025-10-10 06:21:56 +00:00
|
|
|
|
"[&:has([aria-valuetext=Empty]:) w-full",
|
|
|
|
|
|
"grid grid-cols-[max-content_16px_max-content_1fr] items-center",
|
|
|
|
|
|
"group border-input relative rounded-md border",
|
|
|
|
|
|
"group-data-invalid:border-destructive",
|
|
|
|
|
|
"[&:has(_input[data-disabled=true])]:border-border/50",
|
|
|
|
|
|
"[&:has([data-ui=date-segment][aria-readonly])]:bg-zinc-50",
|
|
|
|
|
|
"dark:[&:has([data-ui=date-segment][aria-readonly])]:bg-white/10",
|
|
|
|
|
|
formattedValue ? "min-w-60" : "min-w-[278px]",
|
|
|
|
|
|
isFocusWithin && "border-ring ring-ring group-data-invalid:border-ring ring-1"
|
2025-03-17 17:06:33 +00:00
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
>
|
|
|
|
|
|
<DateInput
|
|
|
|
|
|
slot="start"
|
|
|
|
|
|
className={[
|
2025-10-10 06:21:56 +00:00
|
|
|
|
"flex min-w-fit border-none focus-within:ring-0",
|
|
|
|
|
|
"[&:has([data-ui=date-segment][aria-readonly])]:bg-transparent",
|
|
|
|
|
|
"dark:[&:has([data-ui=date-segment][aria-readonly])]:bg-transparent",
|
|
|
|
|
|
].join(" ")}
|
2025-03-17 17:06:33 +00:00
|
|
|
|
/>
|
|
|
|
|
|
<span
|
|
|
|
|
|
aria-hidden="true"
|
|
|
|
|
|
className="text-muted place-self-center group-data-disabled:opacity-50"
|
|
|
|
|
|
>
|
|
|
|
|
|
–
|
|
|
|
|
|
</span>
|
|
|
|
|
|
<DateInput
|
|
|
|
|
|
slot="end"
|
|
|
|
|
|
className={[
|
2025-10-10 06:21:56 +00:00
|
|
|
|
"flex min-w-fit flex-1 border-none opacity-100 focus-within:ring-0",
|
|
|
|
|
|
"[&:has([data-ui=date-segment][aria-readonly])]:bg-transparent",
|
|
|
|
|
|
"dark:[&:has([data-ui=date-segment][aria-readonly])]:bg-transparent",
|
|
|
|
|
|
].join(" ")}
|
2025-03-17 17:06:33 +00:00
|
|
|
|
/>
|
|
|
|
|
|
<Button
|
|
|
|
|
|
variant="plain"
|
|
|
|
|
|
isIconOnly
|
|
|
|
|
|
size="sm"
|
|
|
|
|
|
className="text-muted group-hover:text-foreground me-1 justify-self-end focus-visible:-outline-offset-1"
|
|
|
|
|
|
>
|
|
|
|
|
|
<CalendarIcon />
|
|
|
|
|
|
</Button>
|
|
|
|
|
|
</Group>
|
|
|
|
|
|
<Popover placement="bottom" className="rounded-xl">
|
|
|
|
|
|
<Dialog>
|
|
|
|
|
|
<RangeCalendar />
|
|
|
|
|
|
</Dialog>
|
|
|
|
|
|
</Popover>
|
|
|
|
|
|
</>
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export function DateRangePickerButton({
|
|
|
|
|
|
className,
|
|
|
|
|
|
children,
|
|
|
|
|
|
}: {
|
|
|
|
|
|
className?: string;
|
|
|
|
|
|
children?: React.ReactNode;
|
|
|
|
|
|
}) {
|
|
|
|
|
|
const { locale } = useLocale();
|
|
|
|
|
|
const state = React.useContext(DateRangePickerStateContext);
|
|
|
|
|
|
const formattedValue = state?.formatValue(locale, {});
|
|
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
|
<>
|
|
|
|
|
|
<Group data-ui="control">
|
|
|
|
|
|
<Button
|
|
|
|
|
|
variant="outline"
|
2025-10-10 06:21:56 +00:00
|
|
|
|
className={twMerge("border-input w-full min-w-64 px-0 font-normal sm:px-0", className)}
|
2025-03-17 17:06:33 +00:00
|
|
|
|
>
|
|
|
|
|
|
<div
|
|
|
|
|
|
className={twMerge(
|
2025-10-10 06:21:56 +00:00
|
|
|
|
"grid w-full items-center",
|
|
|
|
|
|
formattedValue ? "grid grid-cols-[1fr_16px_1fr_36px]" : "grid-cols-[1fr_36px]"
|
2025-03-17 17:06:33 +00:00
|
|
|
|
)}
|
|
|
|
|
|
>
|
|
|
|
|
|
{formattedValue ? (
|
|
|
|
|
|
<>
|
|
|
|
|
|
<span className="min-w-fit px-3 text-base/6 sm:text-sm/6">
|
|
|
|
|
|
{formattedValue.start}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
<span
|
|
|
|
|
|
aria-hidden="true"
|
|
|
|
|
|
className="text-muted place-self-center group-data-disabled:opacity-50"
|
|
|
|
|
|
>
|
|
|
|
|
|
–
|
|
|
|
|
|
</span>
|
|
|
|
|
|
<span className="min-w-fit px-3 text-base/6 sm:text-sm/6">
|
|
|
|
|
|
{formattedValue.end}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</>
|
|
|
|
|
|
) : (
|
2025-10-10 06:21:56 +00:00
|
|
|
|
<span className="text-muted justify-self-start px-3">{children}</span>
|
2025-03-17 17:06:33 +00:00
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
|
|
<CalendarIcon className="text-muted group-hover:text-foreground place-self-center" />
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</Button>
|
|
|
|
|
|
|
|
|
|
|
|
<DateInput slot="start" aria-hidden className="hidden" />
|
|
|
|
|
|
<DateInput slot="end" aria-hidden className="hidden" />
|
|
|
|
|
|
</Group>
|
|
|
|
|
|
<Popover placement="bottom" className="rounded-xl">
|
|
|
|
|
|
<Dialog>
|
|
|
|
|
|
<RangeCalendar />
|
|
|
|
|
|
</Dialog>
|
|
|
|
|
|
</Popover>
|
|
|
|
|
|
</>
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|