prasi-lib/comps/custom/Datepicker/components/Datepicker.tsx

404 lines
11 KiB
TypeScript
Executable File

import dayjs from "dayjs";
import React, {
useCallback,
useEffect,
useMemo,
useRef,
useState,
} from "react";
import Calendar from "../components/Calendar";
import Footer from "../components/Footer";
import Input from "../components/Input";
import Shortcuts from "../components/Shortcuts";
import { COLORS, DATE_FORMAT, DEFAULT_COLOR, LANGUAGE } from "../constants";
import DatepickerContext from "../contexts/DatepickerContext";
import { formatDate, nextMonth, previousMonth } from "../helpers";
import useOnClickOutside from "../hooks";
import { Period, DatepickerType, ColorKeys } from "../types";
import { Arrow, VerticalDash } from "./utils";
import { createPortal } from "react-dom";
import { Popover } from "../../Popover";
import { useLocal } from "lib/utils/use-local";
const Datepicker: React.FC<DatepickerType> = ({
primaryColor = "blue",
value = null,
onChange,
useRange = true,
showFooter = false,
showShortcuts = false,
configs = undefined,
asSingle = false,
placeholder = null,
separator = "~",
startFrom = null,
i18n = LANGUAGE,
disabled = false,
inputClassName = null,
containerClassName = null,
toggleClassName = null,
toggleIcon = undefined,
displayFormat = DATE_FORMAT,
readOnly = false,
minDate = null,
maxDate = null,
dateLooking = "forward",
disabledDates = null,
inputId,
inputName,
startWeekOn = "sun",
classNames = undefined,
popoverDirection = undefined,
mode="daily"
}) => {
const local = useLocal({ open: false });
// Ref
const containerRef = useRef<HTMLDivElement | null>(null);
const calendarContainerRef = useRef<HTMLDivElement | null>(null);
const arrowRef = useRef<HTMLDivElement | null>(null);
// State
const [firstDate, setFirstDate] = useState<dayjs.Dayjs>(
startFrom && dayjs(startFrom).isValid() ? dayjs(startFrom) : dayjs()
);
const [secondDate, setSecondDate] = useState<dayjs.Dayjs>(
nextMonth(firstDate)
);
const [period, setPeriod] = useState<Period>({
start: null,
end: null,
});
const [dayHover, setDayHover] = useState<string | null>(null);
const [inputText, setInputText] = useState<string>("");
const [inputRef, setInputRef] = useState(React.createRef<HTMLInputElement>());
// Custom Hooks use
useOnClickOutside(calendarContainerRef, () => {
const container = calendarContainerRef.current;
if (container) {
hideDatepicker();
}
});
// Functions
const hideDatepicker = useCallback(() => {
local.open = false;
local.render();
}, []);
/* Start First */
const firstGotoDate = useCallback(
(date: dayjs.Dayjs) => {
const newDate = dayjs(formatDate(date));
const reformatDate = dayjs(formatDate(secondDate));
if (newDate.isSame(reformatDate) || newDate.isAfter(reformatDate)) {
setSecondDate(nextMonth(date));
}
console.log(date)
setFirstDate(date);
},
[secondDate]
);
const previousMonthFirst = useCallback(() => {
setFirstDate(previousMonth(firstDate));
}, [firstDate]);
const nextMonthFirst = useCallback(() => {
firstGotoDate(nextMonth(firstDate));
}, [firstDate, firstGotoDate]);
const changeFirstMonth = useCallback(
(month: number) => {
console.log("HALOOO")
firstGotoDate(
dayjs(`${firstDate.year()}-${month < 10 ? "0" : ""}${month}-01`)
);
},
[firstDate, firstGotoDate]
);
const changeFirstYear = useCallback(
(year: number) => {
firstGotoDate(dayjs(`${year}-${firstDate.month() + 1}-01`));
},
[firstDate, firstGotoDate]
);
/* End First */
/* Start Second */
const secondGotoDate = useCallback(
(date: dayjs.Dayjs) => {
const newDate = dayjs(formatDate(date, displayFormat));
const reformatDate = dayjs(formatDate(firstDate, displayFormat));
if (newDate.isSame(reformatDate) || newDate.isBefore(reformatDate)) {
setFirstDate(previousMonth(date));
}
setSecondDate(date);
},
[firstDate, displayFormat]
);
const previousMonthSecond = useCallback(() => {
secondGotoDate(previousMonth(secondDate));
}, [secondDate, secondGotoDate]);
const nextMonthSecond = useCallback(() => {
setSecondDate(nextMonth(secondDate));
}, [secondDate]);
const changeSecondMonth = useCallback(
(month: number) => {
console.log("ALOO")
secondGotoDate(
dayjs(`${secondDate.year()}-${month < 10 ? "0" : ""}${month}-01`)
);
},
[secondDate, secondGotoDate]
);
const changeSecondYear = useCallback(
(year: number) => {
secondGotoDate(dayjs(`${year}-${secondDate.month() + 1}-01`));
},
[secondDate, secondGotoDate]
);
/* End Second */
// UseEffects & UseLayoutEffect
useEffect(() => {
const container = containerRef.current;
const calendarContainer = calendarContainerRef.current;
const arrow = arrowRef.current;
if (container && calendarContainer && arrow) {
const detail = container.getBoundingClientRect();
const screenCenter = window.innerWidth / 2;
const containerCenter = (detail.right - detail.x) / 2 + detail.x;
if (containerCenter > screenCenter) {
arrow.classList.add("c-right-0");
arrow.classList.add("c-mr-3.5");
calendarContainer.classList.add("c-right-0");
}
}
}, []);
useEffect(() => {
if (value && value.startDate && value.endDate) {
const startDate = dayjs(value.startDate);
const endDate = dayjs(value.endDate);
const validDate = startDate.isValid() && endDate.isValid();
const condition =
validDate && (startDate.isSame(endDate) || startDate.isBefore(endDate));
if (condition) {
setPeriod({
start: formatDate(startDate),
end: formatDate(endDate),
});
setInputText(
`${formatDate(startDate, displayFormat)}${
asSingle
? ""
: ` ${separator} ${formatDate(endDate, displayFormat)}`
}`
);
}
}
if (value && value.startDate === null && value.endDate === null) {
setPeriod({
start: null,
end: null,
});
setInputText("");
}
}, [asSingle, value, displayFormat, separator]);
useEffect(() => {
if (startFrom && dayjs(startFrom).isValid()) {
const startDate = value?.startDate;
const endDate = value?.endDate;
if (startDate && dayjs(startDate).isValid()) {
setFirstDate(dayjs(startDate));
if (!asSingle) {
if (
endDate &&
dayjs(endDate).isValid() &&
dayjs(endDate).startOf("month").isAfter(dayjs(startDate))
) {
setSecondDate(dayjs(endDate));
} else {
setSecondDate(nextMonth(dayjs(startDate)));
}
}
} else {
setFirstDate(dayjs(startFrom));
setSecondDate(nextMonth(dayjs(startFrom)));
}
}
}, [asSingle, startFrom, value]);
// Variables
const safePrimaryColor = useMemo(() => {
if (COLORS.includes(primaryColor)) {
return primaryColor as ColorKeys;
}
return DEFAULT_COLOR;
}, [primaryColor]);
const contextValues = useMemo(() => {
return {
asSingle,
primaryColor: safePrimaryColor,
configs,
calendarContainer: calendarContainerRef,
arrowContainer: arrowRef,
hideDatepicker,
period,
changePeriod: (newPeriod: Period) => setPeriod(newPeriod),
dayHover,
changeDayHover: (newDay: string | null) => setDayHover(newDay),
inputText,
changeInputText: (newText: string) => setInputText(newText),
updateFirstDate: (newDate: dayjs.Dayjs) => firstGotoDate(newDate),
changeDatepickerValue: onChange,
showFooter,
placeholder,
separator,
i18n,
value,
disabled,
inputClassName,
containerClassName,
toggleClassName,
toggleIcon,
readOnly,
displayFormat,
minDate,
maxDate,
dateLooking,
disabledDates,
inputId,
inputName,
startWeekOn,
classNames,
onChange,
input: inputRef,
popoverDirection,
};
}, [
asSingle,
safePrimaryColor,
configs,
hideDatepicker,
period,
dayHover,
inputText,
onChange,
showFooter,
placeholder,
separator,
i18n,
value,
disabled,
inputClassName,
containerClassName,
toggleClassName,
toggleIcon,
readOnly,
displayFormat,
minDate,
maxDate,
dateLooking,
disabledDates,
inputId,
inputName,
startWeekOn,
classNames,
inputRef,
popoverDirection,
firstGotoDate,
]);
const containerClassNameOverload = useMemo(() => {
const defaultContainerClassName = "c-relative c-w-full c-text-gray-700";
return typeof containerClassName === "function"
? containerClassName(defaultContainerClassName)
: typeof containerClassName === "string" && containerClassName !== ""
? containerClassName
: defaultContainerClassName;
}, [containerClassName]);
return (
<DatepickerContext.Provider value={contextValues}>
<Popover
arrow={false}
className="c-rounded-md"
onOpenChange={(open) => {
local.open = open;
local.render();
}}
open={local.open}
content={
<div
className={cx(
"c-text-sm 2xl:c-text-sm",
)}
ref={calendarContainerRef}
>
<div className="c-flex c-flex-col lg:c-flex-row c-py-1">
{showShortcuts && <Shortcuts />}
<div
className={`c-flex c-items-stretch c-flex-col md:c-flex-row c-space-y-4 md:c-space-y-0 md:c-space-x-1.5 ${
showShortcuts ? "md:pl-2" : "md:c-pl-1"
} c-pr-2 lg:c-pr-1`}
>
<Calendar
date={firstDate}
onClickPrevious={previousMonthFirst}
onClickNext={nextMonthFirst}
changeMonth={changeFirstMonth}
changeYear={changeFirstYear}
mode={mode}
minDate={minDate}
maxDate={maxDate}
/>
{useRange && (
<>
<div className="c-flex c-items-center">
<VerticalDash />
</div>
<Calendar
date={secondDate}
onClickPrevious={previousMonthSecond}
onClickNext={nextMonthSecond}
changeMonth={changeSecondMonth}
changeYear={changeSecondYear}
mode={mode}
minDate={minDate}
maxDate={maxDate}
/>
</>
)}
</div>
</div>
{showFooter && <Footer />}
</div>
}
>
<div className={containerClassNameOverload} ref={containerRef}>
<Input setContextRef={setInputRef} />
</div>
</Popover>
</DatepickerContext.Provider>
);
};
export default Datepicker;