@neo4j-ndl/react
Version:
React implementation of Neo4j Design System
186 lines • 13.7 kB
JavaScript
var __rest = (this && this.__rest) || function (s, e) {
var t = {};
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
t[p] = s[p];
if (s != null && typeof Object.getOwnPropertySymbols === "function")
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
t[p[i]] = s[p[i]];
}
return t;
};
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
// TODO: try to fix as some point
/* eslint-disable @typescript-eslint/naming-convention */
/**
*
* Copyright (c) "Neo4j"
* Neo4j Sweden AB [http://neo4j.com]
*
* This file is part of Neo4j.
*
* Neo4j is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import classNames from 'classnames';
import { format } from 'date-fns';
import { useCallback, useRef, useState } from 'react';
import ReactDatePicker from 'react-datepicker';
import { CleanIconButton } from '../clean-icon-button';
import { useIsInsideDialog } from '../dialog/dialog-context';
import { Divider } from '../divider';
import { ArrowLeftIconOutline, ArrowRightIconOutline, CalendarDaysIconOutline, ChevronDownIconOutline, } from '../icons';
import { TextInput } from '../text-input';
import { NeedleTime, TimePicker } from '../time-picker';
import { TimeZonePicker } from '../timezone-picker';
import { Typography } from '../typography';
import { daysInMonth, getYearsPeriodString } from './utils';
// This wrapper is needed due to react-datepicker injecting html attributes
// into the custom input component on root level, since we use htmlAttributes
// we need to help spread them there instead of the root level
const DatePickerTextInputWrapper = (_a) => {
var { textInputProps, isDisabled, value, placeholder } = _a, restProps = __rest(_a, ["textInputProps", "isDisabled", "value", "placeholder"]);
// deconstruct textInputProps to avoid bugs with react-datepicker injecting props
const _b = textInputProps !== null && textInputProps !== void 0 ? textInputProps : {}, { placeholder: textInputPlaceholder, htmlAttributes: textInputHtmlAttributes, ref } = _b, restTextInputProps = __rest(_b, ["placeholder", "htmlAttributes", "ref"]);
return (_jsx(TextInput, Object.assign({ ref: ref, trailingElement: _jsx(CalendarDaysIconOutline, { className: "ndl-datepicker-icon" }), value: value, placeholder: placeholder !== null && placeholder !== void 0 ? placeholder : textInputPlaceholder, isDisabled: isDisabled,
// react-datepicker injects html attributes into the custom input
htmlAttributes: Object.assign(Object.assign({ 'aria-label': 'Date picker input' }, textInputHtmlAttributes), restProps) }, restTextInputProps)));
};
export const DatePicker = (_a) => {
var _b;
var { reactDatePickerProps, textInputProps, timePickerProps, timeZonePickerProps, isDisabled, ref, className, style, htmlAttributes } = _a, restProps = __rest(_a, ["reactDatePickerProps", "textInputProps", "timePickerProps", "timeZonePickerProps", "isDisabled", "ref", "className", "style", "htmlAttributes"]);
const classes = classNames(`ndl-datepicker`, className, {});
const datetimeRef = useRef(null);
const [picker, setPicker] = useState('day');
const [preSelectedDate, setPreSelectedDate] = useState(reactDatePickerProps.selected ||
reactDatePickerProps.startDate);
const [selectedTimeZone, setSelectedTimeZone] = useState(timeZonePickerProps === null || timeZonePickerProps === void 0 ? void 0 : timeZonePickerProps.value);
const isInsideDialog = useIsInsideDialog();
const headerAction = useCallback((action) => {
setPicker(picker === action ? 'day' : action);
}, [picker]);
const CustomHeader = useCallback((props) => {
const { decreaseMonth, increaseMonth, increaseYear, decreaseYear, nextMonthButtonDisabled, nextYearButtonDisabled, prevMonthButtonDisabled, prevYearButtonDisabled, } = props;
const prevCallback = picker === 'year' ? decreaseYear : decreaseMonth;
const nextCallback = picker === 'year' ? increaseYear : increaseMonth;
const prevDisabled = picker === 'year' ? prevYearButtonDisabled : prevMonthButtonDisabled;
const nextDisabled = picker === 'year' ? nextYearButtonDisabled : nextMonthButtonDisabled;
return (_jsxs("div", { className: "ndl-datepicker-header", children: [_jsxs("div", { className: "ndl-datepicker-selects", children: [picker !== 'year' && (_jsx("button", { "aria-label": `${picker === 'month' ? 'Close' : 'Open'} month picker`, "aria-pressed": picker === 'month', onClick: () => headerAction('month'), children: _jsxs("div", { className: "n-flex n-items-center n-gap-token-4", children: [_jsx(Typography, { variant: "subheading-small", children: format(props.date, 'MMM') }), _jsx(ChevronDownIconOutline, { className: classNames('ndl-datepicker-chevron', {
'n-rotate-180': picker === 'month',
}), "aria-label": "Chevron icon" })] }) })), picker !== 'month' && (_jsx("button", { "aria-label": `${picker === 'year' ? 'Close' : 'Open'} year picker`, onClick: () => headerAction('year'), children: _jsxs("div", { className: "n-flex n-items-center n-gap-token-4", children: [_jsx(Typography, { variant: "subheading-small", children: picker === 'year'
? getYearsPeriodString(props.date, reactDatePickerProps === null || reactDatePickerProps === void 0 ? void 0 : reactDatePickerProps.yearItemNumber)
: format(props.date, 'yyyy') }), _jsx(ChevronDownIconOutline, { className: classNames('ndl-datepicker-chevron', {
'n-rotate-180': picker === 'year',
}), "aria-label": "Chevron icon" })] }) }))] }), picker !== 'month' && (_jsxs("div", { className: "n-flex n-justify-center n-gap-token-16", children: [_jsx(CleanIconButton, { description: picker === 'year' ? 'Previous period' : 'Previous month', onClick: prevCallback, isDisabled: prevDisabled, className: "n-text-neutral-text-weak", size: "small", children: _jsx(ArrowLeftIconOutline, {}) }), _jsx(CleanIconButton, { description: picker === 'year' ? 'Next period' : 'Next month', onClick: nextCallback, isDisabled: nextDisabled, className: "n-text-neutral-text-weak", size: "small", children: _jsx(ArrowRightIconOutline, {}) })] }))] }));
}, [picker, headerAction, reactDatePickerProps === null || reactDatePickerProps === void 0 ? void 0 : reactDatePickerProps.yearItemNumber]);
const handleTimeChange = (newTime) => {
if (reactDatePickerProps.selectsRange ||
reactDatePickerProps.selectsMultiple) {
// this is consistent with the behavior of react-datepicker
return;
}
if (!preSelectedDate) {
return;
}
const newDate = new Date(preSelectedDate);
newDate.setHours(newTime.hour, newTime.minute, 0, 0);
setPreSelectedDate(newDate);
if (reactDatePickerProps.onChange) {
// For single date selection
reactDatePickerProps.onChange(newDate, undefined);
}
};
const handleTimeZoneChange = (timezone) => {
setSelectedTimeZone(timezone);
if (timeZonePickerProps === null || timeZonePickerProps === void 0 ? void 0 : timeZonePickerProps.onChange) {
timeZonePickerProps.onChange(timezone);
}
};
/**
* Intercept onChange so we can work with
* Month and Year pickers
*/
const interceptedChange = useCallback((date, event) => {
if (picker !== 'day') {
// setTimeout to prevent picker change before the handleMonthChange logic
setTimeout(() => {
setPicker('day');
});
}
else if (picker === 'day') {
if (date instanceof Date) {
setPreSelectedDate(date);
}
else if (Array.isArray(date) &&
date.length > 0 &&
date[0] instanceof Date) {
setPreSelectedDate(date[0]);
}
else if (date === null) {
setPreSelectedDate(null);
}
if (reactDatePickerProps.onChange && date !== null) {
reactDatePickerProps.onChange(date, event);
}
}
}, [picker, reactDatePickerProps]);
/**
* Intercept onCalendarClose so we can
* switch to "day" picker if we close on "month" or "year"
* view
*/
const interceptedOnCalendarClose = useCallback(() => {
var _a;
if (picker !== 'day') {
setPicker('day');
}
(_a = reactDatePickerProps === null || reactDatePickerProps === void 0 ? void 0 : reactDatePickerProps.onCalendarClose) === null || _a === void 0 ? void 0 : _a.call(reactDatePickerProps);
}, [picker, reactDatePickerProps]);
const handleMonthChange = useCallback((date) => {
var _a, _b, _c, _d, _e;
if (picker === 'month') {
const selectedDay = (_a = preSelectedDate === null || preSelectedDate === void 0 ? void 0 : preSelectedDate.getDate()) !== null && _a !== void 0 ? _a : 1;
const daysInNewMonth = daysInMonth(date.getMonth(), date.getFullYear());
date.setDate(Math.min(selectedDay, daysInNewMonth));
(_b = reactDatePickerProps === null || reactDatePickerProps === void 0 ? void 0 : reactDatePickerProps.onMonthChange) === null || _b === void 0 ? void 0 : _b.call(reactDatePickerProps, date);
}
else if (picker === 'year') {
const selectedDay = (_c = preSelectedDate === null || preSelectedDate === void 0 ? void 0 : preSelectedDate.getDate()) !== null && _c !== void 0 ? _c : 1;
const selectedMonth = (_d = preSelectedDate === null || preSelectedDate === void 0 ? void 0 : preSelectedDate.getMonth()) !== null && _d !== void 0 ? _d : 0;
const daysInNewMonth = daysInMonth(selectedMonth, date.getFullYear());
date.setMonth(selectedMonth, Math.min(selectedDay, daysInNewMonth));
(_e = reactDatePickerProps === null || reactDatePickerProps === void 0 ? void 0 : reactDatePickerProps.onMonthChange) === null || _e === void 0 ? void 0 : _e.call(reactDatePickerProps, date);
}
setPreSelectedDate(date);
}, [picker, preSelectedDate, reactDatePickerProps]);
/**
* Intercept onChangeRaw to handle clearing the input
* When the user clears the text input, set the date to null
*/
const interceptedOnChangeRaw = useCallback((event) => {
var _a;
const target = event === null || event === void 0 ? void 0 : event.target;
if ((target === null || target === void 0 ? void 0 : target.value) === '') {
setPreSelectedDate(null);
if (reactDatePickerProps.onChange) {
reactDatePickerProps.onChange(null, event);
}
}
(_a = reactDatePickerProps === null || reactDatePickerProps === void 0 ? void 0 : reactDatePickerProps.onChangeRaw) === null || _a === void 0 ? void 0 : _a.call(reactDatePickerProps, event);
}, [reactDatePickerProps]);
const datePickerProps = Object.assign(Object.assign({}, reactDatePickerProps), { onCalendarClose: interceptedOnCalendarClose, onChange: interceptedChange, onChangeRaw: interceptedOnChangeRaw, onMonthChange: handleMonthChange });
return (_jsx("div", Object.assign({ className: classes, ref: ref, style: style }, restProps, htmlAttributes, { children: _jsx(ReactDatePicker, Object.assign({ ref: datetimeRef, customInput: _jsx(DatePickerTextInputWrapper, { isDisabled: isDisabled, textInputProps: textInputProps }), customTimeInput: _jsxs("span", { className: "ndl-time-picker-wrapper", children: [_jsx(Divider, {}), _jsx(TimePicker, Object.assign({ isFluid: true, value: preSelectedDate
? new NeedleTime(preSelectedDate.getHours(), preSelectedDate.getMinutes())
: undefined, onChange: handleTimeChange }, timePickerProps, { floatingStrategy: "absolute", isPortaled: false })), timeZonePickerProps && (_jsx(TimeZonePicker, Object.assign({ isFluid: true, value: selectedTimeZone, onChange: handleTimeZoneChange, referenceDate: preSelectedDate || undefined }, timeZonePickerProps, { floatingStrategy: "absolute", isPortaled: false })))] }), disabled: isDisabled, showPopperArrow: false, showMonthYearPicker: picker === 'month', showYearPicker: picker === 'year', shouldCloseOnSelect: picker === 'day' && !((_b = reactDatePickerProps.showTimeInput) !== null && _b !== void 0 ? _b : false), dayClassName: () => 'ndl-datepicker-day', renderCustomHeader: CustomHeader }, datePickerProps, { popperClassName: classNames('ndl-datepicker-popper', reactDatePickerProps.popperClassName), popperProps: Object.assign({ strategy: isInsideDialog ? 'fixed' : 'absolute' }, reactDatePickerProps.popperProps) })) })));
};
//# sourceMappingURL=DatePicker.js.map