UNPKG

@hitachivantara/uikit-react-core

Version:

Core React components for the NEXT Design System.

345 lines (344 loc) 11.7 kB
import { jsxs, jsx } from "react/jsx-runtime"; import { forwardRef, useRef, useEffect } from "react"; import { useForkRef } from "@mui/material/utils"; import { useDefaultProps } from "@hitachivantara/uikit-react-utils"; import { DEFAULT_LOCALE, isDate } from "../Calendar/utils.js"; import { HvLabelContainer } from "../FormElement/LabelContainer.js"; import { useControlled } from "../hooks/useControlled.js"; import { useLabels } from "../hooks/useLabels.js"; import { useUniqueId } from "../hooks/useUniqueId.js"; import { HvIcon } from "../icons.js"; import { setId } from "../utils/setId.js"; import { useSavedState } from "../utils/useSavedState.js"; import { useClasses } from "./DatePicker.styles.js"; import { staticClasses } from "./DatePicker.styles.js"; import useVisibleDate from "./useVisibleDate.js"; import { getDateLabel } from "./utils.js"; import { HvCalendar } from "../Calendar/Calendar.js"; import { HvButton } from "../Button/Button.js"; import { HvFormElement } from "../FormElement/FormElement.js"; import { HvBaseDropdown } from "../BaseDropdown/BaseDropdown.js"; import { isInvalid } from "../FormElement/utils.js"; import { HvWarningText } from "../FormElement/WarningText/WarningText.js"; import { HvActionBar } from "../ActionBar/ActionBar.js"; const DEFAULT_LABELS = { /** Apply button label. */ applyLabel: "Apply", /** Cancel button label. */ cancelLabel: "Cancel", /** Clear button label. */ clearLabel: "Clear", /** Invalid Date label. */ invalidDateLabel: "Invalid date" }; const HvDatePicker = forwardRef( function HvDatePicker2(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 = DEFAULT_LOCALE, 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( status, "standBy" ); const [validationMessage] = useControlled(statusMessage, "Required"); const [calendarOpen, setCalendarOpen] = useControlled( 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); 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]); 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); }; const handleCancel = () => { rollbackStartDate(); rollbackEndDate(); onCancel?.(); setCalendarOpen(false); }; 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); } }; 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: { modifiers: [ { name: "preventOverflow", enabled: escapeWithReference } ] }, "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 } ) ] } ); } ); export { HvDatePicker, staticClasses as datePickerClasses };