xtablo-source/ui/src/ui-library/date-range-picker.tsx

151 lines
4.8 KiB
TypeScript
Raw Normal View History

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>
</>
);
}