Files
infisical/frontend/src/components/v2/DatePicker/DatePicker.tsx
T

144 lines
4.7 KiB
TypeScript

import { ChangeEventHandler, useState } from "react";
import { DayPicker, DayPickerProps, getDefaultClassNames, TZDate, UI } from "react-day-picker";
import { faCalendar } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { PopoverContentProps, PopoverProps } from "@radix-ui/react-popover";
import { format, setHours, setMinutes } from "date-fns";
import { twMerge } from "tailwind-merge";
import { formatDateTime, Timezone } from "@app/helpers/datetime";
import { Button } from "../Button";
import { Input } from "../Input";
import { Popover, PopoverContent, PopoverTrigger } from "../Popoverv2";
const defaultClassNames = getDefaultClassNames();
export type DatePickerProps = Omit<DayPickerProps, "selected"> & {
value?: Date;
onChange: (date?: Date) => void;
popUpProps: PopoverProps;
popUpContentProps: PopoverContentProps;
dateFormat?: "PPP" | "PP" | "P"; // extend as needed
hideTime?: boolean;
buttonClassName?: string;
timezone?: Timezone;
};
const localTimeToUTC = (timeString: string) => {
const today = new Date();
const [hours, minutes] = timeString.split(":").map(Number);
today.setHours(hours, minutes, 0, 0);
const utcHours = today.getUTCHours().toString().padStart(2, "0");
const utcMinutes = today.getUTCMinutes().toString().padStart(2, "0");
return `${utcHours}:${utcMinutes}`;
};
const utcTimeToLocal = (utcTimeString: string) => {
const today = new Date();
const [hours, minutes] = utcTimeString.split(":").map(Number);
today.setUTCHours(hours, minutes, 0, 0);
const localHours = today.getHours().toString().padStart(2, "0");
const localMinutes = today.getMinutes().toString().padStart(2, "0");
return `${localHours}:${localMinutes}`;
};
// Doc: https://react-day-picker.js.org/
export const DatePicker = ({
value,
onChange,
popUpProps,
popUpContentProps,
dateFormat = "PPP",
hideTime = false,
buttonClassName,
timezone,
...props
}: DatePickerProps) => {
const [timeValue, setTimeValue] = useState<string>(value ? format(value, "HH:mm") : "00:00");
const displayUtc = timezone === Timezone.UTC;
const handleTimeChange: ChangeEventHandler<HTMLInputElement> = (e) => {
const time = displayUtc ? utcTimeToLocal(e.target.value) : e.target.value;
if (time) {
setTimeValue(time);
if (value) {
const [hours, minutes] = time.split(":").map((str) => parseInt(str, 10));
const newSelectedDate = setHours(setMinutes(value, minutes), hours);
onChange(newSelectedDate);
}
}
};
const handleDaySelect = (date: Date | undefined) => {
if (!timeValue || !date) {
onChange(date);
return;
}
const [hours, minutes] = timeValue.split(":").map((str) => parseInt(str, 10));
const newDate = new Date(date.getFullYear(), date.getMonth(), date.getDate(), hours, minutes);
onChange(newDate);
};
return (
<Popover {...popUpProps}>
<PopoverTrigger asChild>
<Button
className={buttonClassName}
variant="outline_bg"
leftIcon={<FontAwesomeIcon icon={faCalendar} />}
>
{value
? formatDateTime({ timestamp: value, timezone, dateFormat })
: `Select Date${hideTime ? "" : " and Time"}`}
</Button>
</PopoverTrigger>
<PopoverContent
{...popUpContentProps}
className={twMerge(
"w-fit border border-mineshaft-600 bg-mineshaft-800 p-2 font-inter",
popUpContentProps.className
)}
>
<div className="px-2 pt-4">
<DayPicker
{...props}
mode="single"
selected={value ? new TZDate(value, displayUtc ? "UTC" : undefined) : undefined}
onSelect={(date) => handleDaySelect(date ? new TZDate(date, undefined) : undefined)}
className="font-inter text-mineshaft-200"
timeZone={displayUtc ? "UTC" : undefined}
classNames={{
today: "text-primary border-primary",
selected: " text-mineshaft-100 bg-mineshaft-500",
root: `text-mineshaft-300 ${defaultClassNames}`,
[UI.DayButton]: "p-3 rounded hover:text-mineshaft-100",
[UI.Weekday]: "px-3 pt-3",
[UI.Chevron]: "fill-mineshaft-300/70 hover:fill-mineshaft-300",
disabled: "text-mineshaft-400 pointer-events-none"
}}
/>
</div>
{!hideTime && (
<div className="mx-4 my-4">
<Input
type="time"
value={displayUtc ? localTimeToUTC(timeValue) : timeValue}
onChange={handleTimeChange}
className="bg-mineshaft-700 text-white [color-scheme:dark]"
/>
</div>
)}
</PopoverContent>
</Popover>
);
};