@dnb/eufemia
Version:
DNB Eufemia Design System UI Library
404 lines (403 loc) • 14.1 kB
JavaScript
"use client";
import _extends from "@babel/runtime/helpers/esm/extends";
var _AlignmentHelper;
import React, { useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react';
import classnames from 'classnames';
import { warn, extendPropsWithContext, getStatusState, combineDescribedBy, validateDOMAttributes } from "../../shared/component-helper.js";
import AlignmentHelper from "../../shared/AlignmentHelper.js";
import { createSpacingClasses } from "../space/SpacingHelper.js";
import { skeletonDOMAttributes } from "../skeleton/SkeletonHelper.js";
import Context from "../../shared/Context.js";
import Suffix from "../../shared/helpers/Suffix.js";
import FormLabel from "../form-label/FormLabel.js";
import FormStatus from "../form-status/FormStatus.js";
import DatePickerProvider from "./DatePickerProvider.js";
import DatePickerRange from "./DatePickerRange.js";
import DatePickerInput from "./DatePickerInput.js";
import DatePickerAddon from "./DatePickerAddon.js";
import DatePickerFooter from "./DatePickerFooter.js";
import { pickFormElementProps } from "../../shared/helpers/filterValidProps.js";
import { useTranslation } from "../../shared/index.js";
import { convertSnakeCaseProps } from "../../shared/helpers/withSnakeCaseProps.js";
import Popover from "../popover/Popover.js";
import { formatDate, formatDateRange } from "../date-format/DateFormatUtils.js";
import useId from "../../shared/helpers/useId.js";
const defaultProps = {
hideNavigation: false,
hideDays: false,
onlyMonth: false,
hideLastWeek: false,
disableAutofocus: false,
enableKeyboardNav: false,
showInput: false,
inline: false,
resetDate: true,
range: false,
link: false,
sync: true,
statusState: 'error',
opened: false,
noAnimation: false,
direction: 'auto',
skipPortal: false,
yearNavigation: false
};
function DatePicker(externalProps) {
const props = {
...defaultProps,
...externalProps
};
const {
preventClose,
onHide,
onShow,
onSubmit,
onCancel,
onReset,
noAnimation,
showInput,
inline,
alignPicker,
showSubmitButton,
showCancelButton,
range,
hideDays,
hideNavigation,
correctInvalidDate,
opened: openedProp,
endDate: endDateProp
} = convertSnakeCaseProps(props);
const [opened, setOpened] = useState(inline ? true : openedProp);
const [hidden, setHidden] = useState(inline ? false : !opened);
const [dates, setDates] = useState({});
const context = useContext(Context);
const blurDelay = 201;
const id = useId(props.id);
const innerRef = useRef();
const submitButtonRef = useRef();
const getReturnObject = useRef();
const hideTimeout = useRef();
const calendarContainerRef = useRef(null);
const translation = useTranslation().DatePicker;
const focusCalendarTable = useCallback(() => calendarContainerRef.current?.querySelector('table'), []);
if (correctInvalidDate) {
warn(`Use 'Field.Date' instead, for built in validation (https://eufemia.dnb.no/uilib/extensions/forms/feature-fields/Date/#date-limit-validation).`);
}
if (endDateProp && !range) {
warn(`The DatePicker got a "endDate". You have to set range={true} as well!.`);
}
const hidePicker = useCallback(args => {
if (preventClose) {
return;
}
if (args && args.event && args.event.persist) {
args.event.persist();
}
setOpened(false);
hideTimeout.current = setTimeout(() => {
setHidden(true);
onHide?.({
...getReturnObject.current(args)
});
if (args?.['focusOnHide']) {
try {
submitButtonRef.current.focus({
preventScroll: true
});
} catch (e) {
warn(e);
}
}
}, noAnimation ? 1 : blurDelay);
}, [noAnimation, preventClose, onHide]);
const showPicker = useCallback(event => {
if (hideTimeout.current) {
clearTimeout(hideTimeout.current);
}
setOpened(true);
setHidden(false);
onShow?.({
...getReturnObject.current(event)
});
}, [onShow]);
useEffect(() => {
if (openedProp && !inline) {
showPicker();
}
}, [openedProp, showPicker, inline]);
const onPickerChange = useCallback(({
hidePicker: shouldHidePicker = true,
...args
}) => {
if (shouldHidePicker && !showSubmitButton && !showCancelButton) {
hidePicker({
focusOnHide: true
});
}
setDates({
startDate: args.startDate,
endDate: args.endDate
});
}, [hidePicker, showSubmitButton, showCancelButton]);
const onSubmitHandler = useCallback(event => {
if (opened) {
hidePicker(event);
onSubmit?.({
...getReturnObject.current({
event
})
});
} else {
showPicker(event);
}
}, [opened, hidePicker, showPicker, onSubmit]);
const onCancelHandler = useCallback(event => {
hidePicker();
onCancel?.({
...getReturnObject.current(event)
});
}, [hidePicker, onCancel]);
const onResetHandler = useCallback(event => {
hidePicker();
onReset?.({
...getReturnObject.current(event)
});
}, [hidePicker, onReset]);
const togglePicker = useCallback(args => {
!opened ? showPicker(args) : hidePicker(args);
}, [opened, showPicker, hidePicker]);
const extendedProps = extendPropsWithContext(props, defaultProps, {
skeleton: context?.skeleton
}, convertSnakeCaseProps(context.getTranslation(props).DatePicker), pickFormElementProps(context?.FormRow), convertSnakeCaseProps(pickFormElementProps(context?.formElement)), context.DatePicker);
const {
label,
title,
labelDirection,
labelSrOnly,
onlyMonth,
hideLastWeek,
disableAutofocus,
firstDay,
resetDate,
link,
sync,
inputElement,
addonElement,
shortcuts,
disabled,
stretch,
skeleton,
size,
status,
statusState,
statusProps,
statusNoAnimation,
globalStatus,
suffix,
maskOrder,
maskPlaceholder,
submitButtonText,
cancelButtonText,
resetButtonText,
showResetButton,
className,
tooltip,
skipPortal,
labelAlignment,
...restProps
} = extendedProps;
const attributes = useMemo(() => filterOutNonAttributes(restProps), [restProps]);
const showStatus = getStatusState(status);
const pickerParams = {};
if (showStatus || suffix) {
pickerParams['aria-describedby'] = combineDescribedBy(pickerParams, showStatus ? id + '-status' : null, suffix ? id + '-suffix' : null);
}
const submitParams = {
['aria-expanded']: opened,
ref: submitButtonRef,
tabIndex: extendedProps.tabIndex,
tooltip
};
const selectedDateTitle = useMemo(() => {
const {
selectedDate,
selectedDateRange
} = translation;
const {
startDate,
endDate
} = dates;
if (!startDate) {
return '';
}
const options = {
locale: context.locale,
options: {
dateStyle: 'full'
}
};
return range && endDate ? selectedDateRange.replace(/%s/, formatDateRange({
startDate,
endDate
}, options)) : selectedDate.replace(/%s/, formatDate(startDate, options));
}, [range, translation, dates, context.locale]);
const mainParams = {
className: classnames("dnb-date-picker dnb-form-component", createSpacingClasses(props), className, status && `dnb-date-picker__status--${statusState}`, labelDirection && `dnb-date-picker--${labelDirection}`, opened && 'dnb-date-picker--opened', hidden && 'dnb-date-picker--hidden', showInput && 'dnb-date-picker--show-input', inline && 'dnb-date-picker--inline', label && labelAlignment === 'right' && 'dnb-date-picker__input--label-alignment-right', stretch && `dnb-date-picker--stretch`, size && `dnb-date-picker--${size}`),
lang: context.locale
};
const containerClassNames = classnames('dnb-date-picker__container', ((inline ? false : range) || showSubmitButton || showCancelButton || showResetButton) && 'dnb-date-picker__container--show-footer', opened ? 'dnb-date-picker__container--opened' : 'dnb-date-picker__container--closed', hidden && 'dnb-date-picker__container--hidden', showInput && 'dnb-date-picker__container--show-input', size && `dnb-date-picker--${size}`);
const remainingDOMProps = validateDOMAttributes(props, attributes);
const remainingSubmitProps = validateDOMAttributes(null, submitParams);
const remainingPickerProps = validateDOMAttributes(null, skeletonDOMAttributes(pickerParams, skeleton, context));
return React.createElement(DatePickerProvider, _extends({}, props, {
attributes: remainingDOMProps,
setReturnObject: fn => getReturnObject.current = fn,
hidePicker: hidePicker
}), React.createElement("span", mainParams, label && React.createElement(FormLabel, {
id: id + '-label',
forId: id,
text: label,
labelDirection: labelDirection,
srOnly: labelSrOnly,
disabled: disabled,
skeleton: skeleton
}), React.createElement("span", _extends({
className: "dnb-date-picker__inner",
ref: innerRef
}, remainingPickerProps), _AlignmentHelper || (_AlignmentHelper = React.createElement(AlignmentHelper, null)), React.createElement(FormStatus, _extends({
show: showStatus,
id: id + '-form-status',
globalStatus: globalStatus,
label: String(label),
text_id: id + '-status',
width_selector: id + '-shell',
text: status,
state: statusState,
no_animation: statusNoAnimation,
skeleton: skeleton
}, statusProps)), React.createElement("span", {
className: "dnb-date-picker__row"
}, inline ? React.createElement("span", {
className: containerClassNames,
ref: calendarContainerRef
}, React.createElement(DatePickerRange, {
id: id,
firstDayOfWeek: firstDay,
resetDate: resetDate,
isRange: range,
isLink: link,
isSync: sync,
hideDays: hideDays,
hideNavigation: hideNavigation,
onlyMonth: onlyMonth,
hideNextMonthWeek: hideLastWeek,
onPickerChange: onPickerChange,
locale: context.locale
}), (addonElement || shortcuts) && React.createElement(DatePickerAddon, {
renderElement: addonElement,
shortcuts: shortcuts
}), React.createElement(DatePickerFooter, {
isRange: inline ? false : range,
onSubmit: onSubmitHandler,
onCancel: onCancelHandler,
onReset: onResetHandler,
submitButtonText: submitButtonText,
cancelButtonText: cancelButtonText,
resetButtonText: resetButtonText
})) : React.createElement("span", {
className: "dnb-date-picker__shell",
id: `${id}-shell`
}, React.createElement(DatePickerInput, _extends({
id: id,
title: title,
disabled: disabled,
stretch: stretch,
skeleton: skeleton,
maskOrder: maskOrder,
maskPlaceholder: maskPlaceholder,
isRange: range,
showInput: showInput,
selectedDateTitle: selectedDateTitle,
inputElement: inputElement,
opened: opened,
hidden: hidden,
size: size,
status: status ? 'error' : null,
statusState: statusState,
lang: context.locale
}, attributes, {
submitAttributes: remainingSubmitProps,
onSubmit: togglePicker
}, statusProps)), React.createElement(Popover, {
open: opened,
targetElement: {
verticalRef: submitButtonRef,
horizontalRef: innerRef
},
noAnimation: noAnimation,
skipPortal: skipPortal,
keepInDOM: !hidden,
focusOnOpen: !disableAutofocus,
focusOnOpenElement: focusCalendarTable,
alignOnTarget: alignPicker === 'right' || stretch ? 'right' : 'left',
horizontalOffset: showInput ? 8 : -8,
placement: props.direction === 'auto' ? 'bottom' : props.direction,
onOpenChange: isOpen => !isOpen && hidePicker(),
hideCloseButton: true,
hideOutline: true,
preventClose: preventClose,
triggerOffset: 0,
arrowEdgeOffset: 4,
noInnerSpace: true,
noMaxWidth: true,
portalRootClass: "dnb-date-picker__portal",
arrowPosition: alignPicker === 'right' ? 'right' : 'left',
arrowPositionSelector: `#${id}`
}, React.createElement("span", {
className: containerClassNames,
ref: calendarContainerRef
}, React.createElement(DatePickerRange, {
id: id,
firstDayOfWeek: firstDay,
resetDate: resetDate,
isRange: range,
isLink: link,
isSync: sync,
hideDays: hideDays,
hideNavigation: hideNavigation,
onlyMonth: onlyMonth,
hideNextMonthWeek: hideLastWeek,
onPickerChange: onPickerChange,
locale: context.locale
}), (addonElement || shortcuts) && React.createElement(DatePickerAddon, {
renderElement: addonElement,
shortcuts: shortcuts
}), React.createElement(DatePickerFooter, {
isRange: inline ? false : range,
onSubmit: onSubmitHandler,
onCancel: onCancelHandler,
onReset: onResetHandler,
submitButtonText: submitButtonText,
cancelButtonText: cancelButtonText,
resetButtonText: resetButtonText
})))), suffix && React.createElement(Suffix, {
className: "dnb-date-picker__suffix",
id: id + '-suffix',
context: props
}, suffix))), React.createElement("p", {
className: "dnb-sr-only",
"aria-live": "assertive"
}, selectedDateTitle)));
}
const NonAttributes = ['locale', 'id', 'month', 'date', 'startDate', 'endDate', 'minDate', 'maxDate', 'enableKeyboardNav', 'hideNavigation', 'returnFormat', 'dateFormat', 'hideDays', 'correctInvalidDate', 'opened', 'direction', 'range', 'showInput', 'inline', 'noAnimation', 'onDaysRender', 'onShow', 'onType', 'onHide', 'showSubmitButton', 'showCancelButton', 'selectedDate', 'selectedMonth', 'selectedYear', 'nextMonth', 'nextYear', 'openPickerText', 'placeholderCharacters', 'prevMonth', 'prevYear', 'endMonth', 'startMonth', 'alignPicker', 'preventClose', 'selectedDateRange', 'yearNavigation'];
function filterOutNonAttributes(props) {
return Object.keys(props).reduce((attributes, key) => {
if (!NonAttributes.includes(key)) {
attributes[key] = props[key];
}
return attributes;
}, {});
}
export default DatePicker;
DatePicker._supportsSpacingProps = true;
//# sourceMappingURL=DatePicker.js.map