UNPKG

rsuite

Version:

A suite of react components

505 lines (487 loc) 15.8 kB
'use client'; import _extends from "@babel/runtime/helpers/esm/extends"; import React, { useMemo } from 'react'; import mapValues from 'lodash/mapValues'; import pick from 'lodash/pick'; import CalenderSimpleIcon from '@rsuite/icons/CalenderSimple'; import TimeIcon from '@rsuite/icons/Time'; import CalendarContainer from "../Calendar/CalendarContainer.js"; import Toolbar from "./Toolbar.js"; import Stack from "../Stack/index.js"; import PredefinedRanges from "./PredefinedRanges.js"; import DateInput from "../DateInput/index.js"; import InputGroup from "../InputGroup/index.js"; import useMonthView from "./hooks/useMonthView.js"; import useFocus from "./hooks/useFocus.js"; import useCustomizedInput from "./hooks/useCustomizedInput.js"; import Box from "../internals/Box/index.js"; import { useCalendarDate } from "../Calendar/hooks/index.js"; import { isEveryDateInMonth } from "../Calendar/utils/index.js"; import { forwardRef, mergeRefs, partitionHTMLProps, createChainedFunction } from "../internals/utils/index.js"; import { useStyles, useCustom, useControlled, useUniqueId, useEventCallback } from "../internals/hooks/index.js"; import { isValid, copyTime, disableTime, DateMode, useDateMode, calendarOnlyProps } from "../internals/utils/date/index.js"; import { PickerPopup, PickerLabel, PickerIndicator, PickerToggleTrigger, triggerPropKeys, usePickerRef, onMenuKeyDown } from "../internals/Picker/index.js"; import { OverlayCloseCause } from "../internals/Overlay/OverlayTrigger.js"; import { splitRanges, getRestProps } from "./utils.js"; import { startOfToday } from "../internals/utils/date/index.js"; /** * A date picker allows users to select a date from a calendar. * * @see https://rsuitejs.com/components/date-picker */ const DatePicker = forwardRef((props, ref) => { const { propsWithDefaults } = useCustom('DatePicker', props); const { as, block, className, classPrefix = 'picker', calendarDefaultDate, cleanable = true, caretAs: caretAsProp, editable = true, defaultValue, disabled, readOnly: readOnly, plaintext, format, id: idProp, isoWeek, weekStart, limitEndYear = 1000, limitStartYear, locale, loading, label, popupClassName, popupStyle, appearance = 'default', placement = 'bottomStart', oneTap, placeholder = '', ranges, value: valueProp, showMeridiem, showWeekNumbers, style, size, monthDropdownProps, shouldDisableDate, shouldDisableHour, shouldDisableMinute, shouldDisableSecond, onChange, onChangeCalendarDate, onClean, onEnter, onExit, onNextMonth, onOk, onPrevMonth, onSelect, onToggleMonthDropdown, onToggleTimeDropdown, onShortcutClick, renderCell, renderValue, ...restProps } = propsWithDefaults; const id = useUniqueId('rs-', idProp); const { trigger, root, target, overlay } = usePickerRef(ref); const formatStr = format || (locale === null || locale === void 0 ? void 0 : locale.shortDateFormat) || 'yyyy-MM-dd'; const { merge, prefix } = useStyles(classPrefix); const [value, setValue] = useControlled(valueProp, defaultValue); const { calendarDate, setCalendarDate, resetCalendarDate } = useCalendarDate(value, calendarDefaultDate); const { setMonthView, monthView, toggleMonthView } = useMonthView({ onToggleMonthDropdown }); const { mode } = useDateMode(formatStr); // Show only the calendar month panel. formatStr = 'yyyy-MM' const showMonth = mode === DateMode.Month || monthView; const { focusInput, focusSelectedDate, onKeyFocusEvent } = useFocus({ target, showMonth, id, locale }); /** * Check whether the date is disabled. */ const isDateDisabled = date => { if (typeof shouldDisableDate === 'function') { return shouldDisableDate(date); } return false; }; /** * Check whether the time is within the time range of the shortcut option in the toolbar. */ const isDatetimeDisabled = date => { return (isDateDisabled === null || isDateDisabled === void 0 ? void 0 : isDateDisabled(date)) || disableTime(props, date); }; /** * Check whether the month is disabled. * If any day in the month is disabled, the entire month is disabled */ const isMonthDisabled = date => { return isEveryDateInMonth(date.getFullYear(), date.getMonth(), isDateDisabled); }; /** * Whether "OK" button is disabled * * - If format is date, disable ok button if selected date is disabled * - If format is month, disable ok button if all dates in the month of selected date are disabled */ const isOkButtonDisabled = selectedDate => { if (mode === DateMode.Month) { return isMonthDisabled(selectedDate); } return isDatetimeDisabled(selectedDate); }; const isErrorValue = value => { if (!isValid(value)) { return true; } else if (value && isDateDisabled(value)) { return true; } return false; }; /** * Switch to the callback triggered after the next month. */ const handleMoveForward = useEventCallback(nextPageDate => { setCalendarDate(nextPageDate); onNextMonth === null || onNextMonth === void 0 || onNextMonth(nextPageDate); onChangeCalendarDate === null || onChangeCalendarDate === void 0 || onChangeCalendarDate(nextPageDate); }); /** * Switch to the callback triggered after the previous month. */ const handleMoveBackward = useEventCallback(nextPageDate => { setCalendarDate(nextPageDate); onPrevMonth === null || onPrevMonth === void 0 || onPrevMonth(nextPageDate); onChangeCalendarDate === null || onChangeCalendarDate === void 0 || onChangeCalendarDate(nextPageDate); }); /** * The callback triggered when the date changes. */ const handleDateChange = useEventCallback((nextValue, event) => { onSelect === null || onSelect === void 0 || onSelect(nextValue, event); onChangeCalendarDate === null || onChangeCalendarDate === void 0 || onChangeCalendarDate(nextValue, event); }); /** * A callback triggered when the time on the calendar changes. */ const handleChangeTime = useEventCallback(nextPageTime => { setCalendarDate(nextPageTime); handleDateChange(nextPageTime); }); /** * Close the calendar panel. */ const handleClose = useEventCallback(() => { var _trigger$current, _trigger$current$clos; (_trigger$current = trigger.current) === null || _trigger$current === void 0 || (_trigger$current$clos = _trigger$current.close) === null || _trigger$current$clos === void 0 || _trigger$current$clos.call(_trigger$current); }); const updateValue = (event, date, closeOverlay = true) => { const nextValue = typeof date !== 'undefined' ? date : calendarDate; setCalendarDate(nextValue || startOfToday()); setValue(nextValue); if (nextValue !== value) { onChange === null || onChange === void 0 || onChange(nextValue, event); } // `closeOverlay` default value is `true` if (closeOverlay !== false) { handleClose(); } }; /** * The callback triggered after the date in the shortcut area is clicked. */ const handleShortcutPageDate = useEventCallback((range, closeOverlay, event) => { const value = range.value; updateValue(event, value, closeOverlay); handleDateChange(value, event); onShortcutClick === null || onShortcutClick === void 0 || onShortcutClick(range, event); }); /** * The callback triggered after clicking the OK button. */ const handleOK = useEventCallback(event => { updateValue(event); onOk === null || onOk === void 0 || onOk(calendarDate, event); focusInput(); }); /** * Callback after clicking the clear button. */ const handleClean = useEventCallback(event => { event === null || event === void 0 || event.stopPropagation(); updateValue(event, null); resetCalendarDate(null); onClean === null || onClean === void 0 || onClean(event); }); const handlePickerPopupKeyDown = useEventCallback(event => { onKeyFocusEvent(event, { date: calendarDate, callback: setCalendarDate }); if (event.key === 'Enter') { handleOK(event); } }); const handleClick = useEventCallback(() => { if (editable) { return; } focusSelectedDate(); }); /** * Callback after the date is selected. */ const handleCalendarSelect = useEventCallback((date, event, updatableValue = true) => { const nextValue = copyTime({ from: calendarDate, to: date }); setCalendarDate(nextValue); handleDateChange(nextValue); if (oneTap && updatableValue) { updateValue(event, nextValue); focusInput(); } }); /** * A callback triggered when the date on the calendar changes. */ const handleChangeMonth = useEventCallback((nextPageDate, event) => { setCalendarDate(nextPageDate); handleDateChange(nextPageDate); focusSelectedDate(); if (oneTap && mode === DateMode.Month) { updateValue(event, nextPageDate); focusInput(); } }); /** * Callback after the input box value is changed. */ const handleInputChange = useEventCallback((value, event) => { if (!isErrorValue(value)) { handleCalendarSelect(value, event); } updateValue(event, value, false); }); const handleInputKeyDown = useEventCallback(event => { onMenuKeyDown(event, { esc: handleClose, enter: () => { var _trigger$current2; const { open } = ((_trigger$current2 = trigger.current) === null || _trigger$current2 === void 0 ? void 0 : _trigger$current2.getState()) || {}; if (open) { if (isValid(calendarDate) && !isDateDisabled(calendarDate)) { updateValue(event); focusInput(); } } else { var _trigger$current3; (_trigger$current3 = trigger.current) === null || _trigger$current3 === void 0 || _trigger$current3.open(); } } }); }); const calendarProps = mapValues(pick(props, calendarOnlyProps), func => (next, date) => { var _func; return (_func = func === null || func === void 0 ? void 0 : func(next, date)) !== null && _func !== void 0 ? _func : false; }); const { sideRanges, bottomRanges } = splitRanges(ranges); const renderCalendarOverlay = (positionProps, speakerRef) => { const { className } = positionProps; const classes = merge(popupClassName, className, prefix('popup-date')); return /*#__PURE__*/React.createElement(PickerPopup, { role: "dialog", "aria-labelledby": label ? `${id}-label` : undefined, tabIndex: -1, className: classes, ref: mergeRefs(overlay, speakerRef), style: popupStyle, target: trigger, onKeyDown: handlePickerPopupKeyDown }, /*#__PURE__*/React.createElement(Stack, { align: "flex-start", h: "100%" }, sideRanges && sideRanges.length > 0 && /*#__PURE__*/React.createElement(PredefinedRanges, { direction: "column", spacing: 0, className: prefix('date-predefined'), ranges: sideRanges, calendarDate: calendarDate, locale: locale, disableShortcut: isDatetimeDisabled, onShortcutClick: handleShortcutPageDate }), /*#__PURE__*/React.createElement(Box, { className: prefix('box') }, /*#__PURE__*/React.createElement(CalendarContainer, _extends({}, calendarProps, { targetId: id, locale: locale, showWeekNumbers: showWeekNumbers, showMeridiem: showMeridiem, disabledDate: isDateDisabled, disabledHours: shouldDisableHour, disabledMinutes: shouldDisableMinute, disabledSeconds: shouldDisableSecond, limitEndYear: limitEndYear, limitStartYear: limitStartYear, format: formatStr, isoWeek: isoWeek, weekStart: weekStart, calendarDate: calendarDate, monthDropdownProps: monthDropdownProps, renderCellOnPicker: renderCell, onMoveForward: handleMoveForward, onMoveBackward: handleMoveBackward, onSelect: handleCalendarSelect, onToggleMonthDropdown: toggleMonthView, onToggleTimeDropdown: onToggleTimeDropdown, onChangeMonth: handleChangeMonth, onChangeTime: handleChangeTime })), /*#__PURE__*/React.createElement(Toolbar, { locale: locale, ranges: bottomRanges, calendarDate: calendarDate, disableOkBtn: isOkButtonDisabled, disableShortcut: isDatetimeDisabled, onShortcutClick: handleShortcutPageDate, onOk: handleOK, hideOkBtn: oneTap })))); }; const hasValue = isValid(value); const caretAs = useMemo(() => { if (caretAsProp === null) { return null; } return caretAsProp || (mode === DateMode.Time ? TimeIcon : CalenderSimpleIcon); }, [caretAsProp, mode]); const handleTriggerClose = useEventCallback(cause => { var _props$onClose; // Unless overlay is closing on user clicking "OK" button, // reset the selected date on calendar panel if (cause !== OverlayCloseCause.ImperativeHandle) { resetCalendarDate(); } setMonthView(false); (_props$onClose = props.onClose) === null || _props$onClose === void 0 || _props$onClose.call(props); }); const showCleanButton = cleanable && hasValue && !readOnly; const [ariaProps, rest] = partitionHTMLProps(restProps, { htmlProps: [], includeAria: true }); const invalidValue = value && isErrorValue(value); const customizedProps = { value, formatStr, renderValue, readOnly, editable, loading }; const { customValue, inputReadOnly, Input, events } = useCustomizedInput(customizedProps); const triggerProps = { ...pick(props, triggerPropKeys), onClose: handleTriggerClose, onEnter: createChainedFunction(events.onActive, onEnter), onExit: createChainedFunction(events.onInactive, onExit) }; return /*#__PURE__*/React.createElement(PickerToggleTrigger, { as: as, pickerType: "date", classPrefix: classPrefix, className: merge(className, { [prefix('error')]: invalidValue }), block: block, disabled: disabled, appearance: appearance, style: style, rootRef: root, trigger: "active", triggerProps: triggerProps, ref: trigger, placement: placement, speaker: renderCalendarOverlay, "data-cleanable": cleanable }, plaintext ? /*#__PURE__*/React.createElement(DateInput, { value: value, format: formatStr, plaintext: plaintext }) : /*#__PURE__*/React.createElement(InputGroup, _extends({}, getRestProps(rest), { inside: true, size: size, disabled: disabled, className: prefix`input-group`, onClick: handleClick }), /*#__PURE__*/React.createElement(PickerLabel, { className: prefix`label`, id: `${id}-label` }, label), /*#__PURE__*/React.createElement(Input, _extends({ "aria-haspopup": "dialog", "aria-invalid": invalidValue, "aria-labelledby": label ? `${id}-label` : undefined }, ariaProps, { ref: target, id: id, value: customValue || value, format: formatStr, placeholder: placeholder ? placeholder : formatStr, disabled: disabled, readOnly: inputReadOnly, onChange: handleInputChange, onKeyDown: handleInputKeyDown })), /*#__PURE__*/React.createElement(PickerIndicator, { size: size, loading: loading, caretAs: caretAs, onClose: handleClean, showCleanButton: showCleanButton }))); }); DatePicker.displayName = 'DatePicker'; export default DatePicker;