UNPKG

@hitachivantara/uikit-react-core

Version:
293 lines (292 loc) 10.1 kB
import { setId } from "../utils/setId.js"; import { useUniqueId } from "../hooks/useUniqueId.js"; import { isInvalid } from "../FormElement/utils.js"; import { HvFormElement } from "../FormElement/FormElement.js"; import { HvIcon } from "../icons.js"; import { HvWarningText } from "../FormElement/WarningText/WarningText.js"; import { useControlled as useControlled$1 } from "../hooks/useControlled.js"; import { HvBaseDropdown } from "../BaseDropdown/BaseDropdown.js"; import { HvButton } from "../Button/Button.js"; import { HvLabelContainer } from "../FormElement/LabelContainer.js"; import { HvActionBar } from "../ActionBar/ActionBar.js"; import { useLabels } from "../hooks/useLabels.js"; import { isDate } from "../Calendar/utils.js"; import { HvCalendar } from "../Calendar/Calendar.js"; import { useSavedState } from "../utils/useSavedState.js"; import { useClasses } from "./DatePicker.styles.js"; import { getDateLabel } from "./utils.js"; import useVisibleDate from "./useVisibleDate.js"; import { useDefaultProps } from "@hitachivantara/uikit-react-utils"; import { forwardRef, useEffect, useMemo, useRef } from "react"; import { jsx, jsxs } from "react/jsx-runtime"; import { useForkRef } from "@mui/material/utils"; //#region src/DatePicker/DatePicker.tsx var DEFAULT_LABELS = { /** Apply button label. */ applyLabel: "Apply", /** Cancel button label. */ cancelLabel: "Cancel", /** Clear button label. */ clearLabel: "Clear", /** Invalid Date label. */ invalidDateLabel: "Invalid date" }; /** * A date picker, popup calendar or date range picker is a graphical user * interface widget which allows the user to select a date from a calendar. */ var HvDatePicker = forwardRef(function HvDatePicker(props, ref) { const { classes: classesProp, className, id, name, required, disabled, readOnly, label, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, description, "aria-describedby": ariaDescribedBy, onChange, onCancel, onClear, status, statusMessage, "aria-errormessage": ariaErrorMessage, placeholder, labels: labelsProp, value, startValue, endValue, expanded, defaultExpanded, onToggle, rangeMode = false, startAdornment, horizontalPlacement = "right", locale = "en", showActions, showClear, disablePortal = true, escapeWithReference = true, dropdownProps = {}, calendarProps, ...others } = useDefaultProps("HvDatePicker", props); const { classes, cx } = useClasses(classesProp); const labels = useLabels(DEFAULT_LABELS, labelsProp); const elementId = useUniqueId(id); const [validationState, setValidationState] = useControlled$1(status, "standBy"); const [validationMessage] = useControlled$1(statusMessage, "Required"); const [calendarOpen, setCalendarOpen] = useControlled$1(expanded, Boolean(defaultExpanded)); const [startDate, setStartDate, rollbackStartDate] = useSavedState(rangeMode ? startValue : value); const [endDate, setEndDate, rollbackEndDate] = useSavedState(endValue); const [visibleDate, dispatchAction] = useVisibleDate(startDate, endDate); const focusTarget = useRef(null); const { ref: refProp, ...otherDropdownProps } = dropdownProps; const dropdownForkedRef = useForkRef(ref, refProp); const popperProps = useMemo(() => { return { modifiers: [{ name: "preventOverflow", enabled: escapeWithReference }] }; }, [escapeWithReference]); useEffect(() => { setStartDate(rangeMode ? startValue : value, true); setEndDate(endValue, true); }, [ value, startValue, endValue, rangeMode, setStartDate, setEndDate ]); const endDateIsSet = useRef(false); endDateIsSet.current = endDate != null; useEffect(() => { if (startDate != null) dispatchAction({ type: "month_year", target: endDateIsSet.current ? "left" : "best", year: startDate.getFullYear(), month: startDate.getMonth() + 1 }); }, [dispatchAction, startDate]); useEffect(() => { if (endDate != null) dispatchAction({ type: "month_year", target: "right", year: endDate.getFullYear(), month: endDate.getMonth() + 1 }); }, [dispatchAction, endDate]); /** * Handles the `Apply` action. Both single and ranged modes are handled here. */ const handleApply = () => { setStartDate(startDate, true); setEndDate(endDate ?? startDate, true); onChange?.(startDate, endDate); setValidationState(() => { if (required && (!isDate(startDate) || rangeMode && !isDate(endDate))) return "invalid"; return "valid"; }); setCalendarOpen(false); }; /** * Handles the `Cancel` action. Both single and ranged modes are handled here. */ const handleCancel = () => { rollbackStartDate(); rollbackEndDate(); onCancel?.(); setCalendarOpen(false); }; /** * Handles the `Cancel` action. Both single and ranged modes are handled here. */ const handleClear = () => { setStartDate(void 0, false); setEndDate(void 0, false); onClear?.(); }; const handleCalendarClose = () => { if (rangeMode || showActions) handleCancel(); }; const handleToggle = (evt, open) => { if (evt === null) return; onToggle?.(evt, open); setCalendarOpen(open); if (!open) handleCalendarClose(); }; const focusOnContainer = () => { focusTarget.current?.focus(); }; const handleDateChange = (event, newDate) => { if (!isDate(newDate)) return; const autoSave = !showActions && !rangeMode; if (rangeMode) if (!startDate || startDate && endDate || newDate < startDate) { setStartDate(newDate); setEndDate(void 0); } else setEndDate(newDate); else setStartDate(newDate, autoSave); if (autoSave) { onChange?.(newDate); setValidationState(() => { if (required && !isDate(newDate)) return "invalid"; return "valid"; }); setCalendarOpen(false); } }; const handleInputDateChange = (event, newDate, position) => { if (!isDate(newDate)) return; if (!rangeMode) { handleDateChange(event, newDate); return; } if (position === "left") { if (endDate) setStartDate(newDate > endDate ? endDate : newDate); } else if (position === "right") { if (!startDate) { if (endDate) setStartDate(newDate > endDate ? endDate : newDate); return; } setEndDate(newDate < startDate ? startDate : newDate); } }; /** * Renders the container for the action elements. */ const renderActions = () => /* @__PURE__ */ jsxs(HvActionBar, { className: cx({ [classes.actionContainer]: showClear }), children: [showClear && /* @__PURE__ */ jsx("div", { className: classes.leftContainer, children: /* @__PURE__ */ jsx(HvButton, { id: setId(id, "action", "clear"), className: classes.action, variant: "primaryGhost", onClick: handleClear, children: labels?.clearLabel }) }), /* @__PURE__ */ jsxs("div", { className: classes.rightContainer, children: [/* @__PURE__ */ jsx(HvButton, { id: setId(id, "action", "apply"), className: classes.action, variant: "primaryGhost", onClick: handleApply, children: labels?.applyLabel }), /* @__PURE__ */ jsx(HvButton, { id: setId(id, "action", "cancel"), className: classes.action, variant: "primaryGhost", onClick: handleCancel, children: labels?.cancelLabel })] })] }); const dateValue = rangeMode ? { startDate, endDate } : startDate; const dateString = getDateLabel(dateValue, rangeMode, locale); const canShowError = ariaErrorMessage == null && (status !== void 0 && statusMessage !== void 0 || status === void 0 && required); const isStateInvalid = isInvalid(validationState); let errorMessageId; if (isStateInvalid) errorMessageId = canShowError ? setId(elementId, "error") : ariaErrorMessage; return /* @__PURE__ */ jsxs(HvFormElement, { id, name, value: dateValue, status: validationState, disabled, required, className: cx(classes.root, className), readOnly, ...others, children: [ /* @__PURE__ */ jsx(HvLabelContainer, { label, description, labelId: setId(elementId, "label"), descriptionId: setId(elementId, "description"), classes: { root: classes.labelContainer, label: classes.label, description: classes.description } }), /* @__PURE__ */ jsxs(HvBaseDropdown, { ref: dropdownForkedRef, role: "combobox", classes: { root: classes.dropdown, panel: classes.panel, header: cx({ [classes.dropdownHeaderInvalid]: isStateInvalid }), headerOpen: classes.dropdownHeaderOpen, placeholder: cx(classes.inputText, { [classes.dateText]: dateString }), container: classes.container }, readOnly, disabled, disablePortal, variableWidth: true, placement: horizontalPlacement, expanded: calendarOpen, onToggle: handleToggle, onClickOutside: handleCalendarClose, onContainerCreation: focusOnContainer, placeholder: dateString || placeholder || "", adornment: /* @__PURE__ */ jsx(HvIcon, { name: "Calendar", className: classes.icon }), popperProps, "aria-haspopup": "dialog", "aria-label": ariaLabel, "aria-labelledby": [label && setId(elementId, "label"), ariaLabelledBy].join(" ").trim() || void 0, "aria-invalid": isStateInvalid ? true : void 0, "aria-errormessage": errorMessageId, "aria-describedby": [description && setId(elementId, "description"), ariaDescribedBy].join(" ").trim() || void 0, ...otherDropdownProps, children: [ /* @__PURE__ */ jsx("div", { ref: focusTarget, tabIndex: -1 }), /* @__PURE__ */ jsx(HvCalendar, { id: setId(id, "calendar"), startAdornment, onChange: handleDateChange, onInputChange: handleInputDateChange, onVisibleDateChange: (_event, type, month, target) => { dispatchAction({ type, target, month }); }, locale, ...visibleDate, ...calendarProps, invalidDateLabel: labels?.invalidDateLabel }), (rangeMode || showActions) && renderActions() ] }), canShowError && /* @__PURE__ */ jsx(HvWarningText, { id: setId(elementId, "error"), disableBorder: true, className: cx(classes.error), children: validationMessage }) ] }); }); //#endregion export { HvDatePicker };