UNPKG

@vertisanpro/flowbite-react

Version:

Non-Official React components built for Flowbite and Tailwind CSS

153 lines (152 loc) 8.42 kB
'use client'; import { HiArrowLeft, HiArrowRight, HiCalendar } from '@vertisanpro/react-icons/hi'; import { twMerge } from '@vertisanpro/tailwind-merge'; import React, { useEffect, useRef, useState } from 'react'; import { mergeDeep } from '../../helpers/merge-deep'; import { getTheme } from '../../theme-store'; import { TextInput } from '../TextInput'; import { DatepickerContext } from './DatepickerContext'; import { DatepickerViewsDays } from './Views/Days'; import { DatepickerViewsDecades } from './Views/Decades'; import { DatepickerViewsMonth } from './Views/Months'; import { DatepickerViewsYears } from './Views/Years'; import { Views, WeekStart, addMonths, addYears, getFirstDateInRange, getFormattedDate, isDateEqual, startOfYearPeriod, } from './helpers'; export const Datepicker = ({ title, open, inline = false, autoHide = true, // Hide when selected the day showClearButton = true, labelClearButton = 'Clear', showTodayButton = true, labelTodayButton = 'Today', defaultDate = new Date(), minDate, maxDate, language = 'en', weekStart = WeekStart.Sunday, className, theme: customTheme = {}, onSelectedDateChanged, ...props }) => { const theme = mergeDeep(getTheme().datepicker, customTheme); // Default date should respect the range defaultDate = getFirstDateInRange(defaultDate, minDate, maxDate); const [isOpen, setIsOpen] = useState(open); const [view, setView] = useState(Views.Days); // selectedDate is the date selected by the user const [selectedDate, setSelectedDate] = useState(defaultDate); // viewDate is only for navigation const [viewDate, setViewDate] = useState(defaultDate); const inputRef = useRef(null); const datepickerRef = useRef(null); // Triggers when user select the date const changeSelectedDate = (date, useAutohide) => { setSelectedDate(date); if (onSelectedDateChanged) { onSelectedDateChanged(date); } if (autoHide && view === Views.Days && useAutohide == true && !inline) { setIsOpen(false); } }; // Render the DatepickerView* node const renderView = (type) => { switch (type) { case Views.Decades: return React.createElement(DatepickerViewsDecades, { theme: theme.views.decades }); case Views.Years: return React.createElement(DatepickerViewsYears, { theme: theme.views.years }); case Views.Months: return React.createElement(DatepickerViewsMonth, { theme: theme.views.months }); case Views.Days: default: return React.createElement(DatepickerViewsDays, { theme: theme.views.days }); } }; // Coordinate the next view based on current view (statemachine-like) const getNextView = () => { switch (view) { case Views.Days: return Views.Months; case Views.Months: return Views.Years; case Views.Years: return Views.Decades; } return view; }; // Get the view title based on active View const getViewTitle = () => { switch (view) { case Views.Decades: return `${startOfYearPeriod(viewDate, 100)} - ${startOfYearPeriod(viewDate, 100) + 90}`; case Views.Years: return `${startOfYearPeriod(viewDate, 10)} - ${startOfYearPeriod(viewDate, 10) + 9}`; case Views.Months: return getFormattedDate(language, viewDate, { year: 'numeric' }); case Views.Days: default: return getFormattedDate(language, viewDate, { month: 'long', year: 'numeric' }); } }; // Navigate to prev/next for given view's date by value const getViewDatePage = (view, date, value) => { switch (view) { case Views.Days: return new Date(addMonths(date, value)); case Views.Months: return new Date(addYears(date, value)); case Views.Years: return new Date(addYears(date, value * 10)); case Views.Decades: return new Date(addYears(date, value * 100)); default: return new Date(addYears(date, value * 10)); } }; useEffect(() => { const handleClickOutside = (event) => { const clickedInsideDatepicker = datepickerRef?.current?.contains(event.target); const clickedInsideInput = inputRef?.current?.contains(event.target); if (!clickedInsideDatepicker && !clickedInsideInput) { setIsOpen(false); } }; document.addEventListener('mousedown', handleClickOutside); return () => { document.removeEventListener('mousedown', handleClickOutside); }; }, [inputRef, datepickerRef, setIsOpen]); return (React.createElement(DatepickerContext.Provider, { value: { theme, language, minDate, maxDate, weekStart, isOpen, setIsOpen, view, setView, viewDate, setViewDate, selectedDate, setSelectedDate, changeSelectedDate, } }, React.createElement("div", { className: twMerge(theme.root.base, className) }, !inline && (React.createElement(TextInput, { theme: theme.root.input, icon: HiCalendar, ref: inputRef, onFocus: () => { if (!isDateEqual(viewDate, selectedDate)) { setViewDate(selectedDate); } setIsOpen(true); }, value: selectedDate && getFormattedDate(language, selectedDate), readOnly: true, ...props })), (isOpen || inline) && (React.createElement("div", { ref: datepickerRef, className: twMerge(theme.popup.root.base, inline && theme.popup.root.inline) }, React.createElement("div", { className: theme.popup.root.inner }, React.createElement("div", { className: theme.popup.header.base }, title && React.createElement("div", { className: theme.popup.header.title }, title), React.createElement("div", { className: theme.popup.header.selectors.base }, React.createElement("button", { type: "button", className: twMerge(theme.popup.header.selectors.button.base, theme.popup.header.selectors.button.prev), onClick: () => setViewDate(getViewDatePage(view, viewDate, -1)) }, React.createElement(HiArrowLeft, null)), React.createElement("button", { type: "button", className: twMerge(theme.popup.header.selectors.button.base, theme.popup.header.selectors.button.view), onClick: () => setView(getNextView()) }, getViewTitle()), React.createElement("button", { type: "button", className: twMerge(theme.popup.header.selectors.button.base, theme.popup.header.selectors.button.next), onClick: () => setViewDate(getViewDatePage(view, viewDate, 1)) }, React.createElement(HiArrowRight, null)))), React.createElement("div", { className: theme.popup.view.base }, renderView(view)), (showClearButton || showTodayButton) && (React.createElement("div", { className: theme.popup.footer.base }, showTodayButton && (React.createElement("button", { type: "button", className: twMerge(theme.popup.footer.button.base, theme.popup.footer.button.today), onClick: () => { const today = new Date(); changeSelectedDate(today, true); setViewDate(today); } }, labelTodayButton)), showClearButton && (React.createElement("button", { type: "button", className: twMerge(theme.popup.footer.button.base, theme.popup.footer.button.clear), onClick: () => { changeSelectedDate(defaultDate, true); if (defaultDate) { setViewDate(defaultDate); } } }, labelClearButton)))))))))); }; Datepicker.displayName = 'Datepicker';