@hitachivantara/uikit-react-core
Version:
UI Kit Core React components.
293 lines (292 loc) • 10.1 kB
JavaScript
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 };