1 | import _objectSpread from '@babel/runtime/helpers/esm/objectSpread2';
|
2 | import _slicedToArray from '@babel/runtime/helpers/esm/slicedToArray';
|
3 | import _objectWithoutProperties from '@babel/runtime/helpers/esm/objectWithoutProperties';
|
4 | import { Stack } from '@spark-web/stack';
|
5 | import { forwardRef, useMemo, useState, useCallback, useEffect, useRef } from 'react';
|
6 | import { usePopper } from 'react-popper';
|
7 | import { ChevronRightIcon, ChevronLeftIcon, CalendarIcon } from '@spark-web/icon';
|
8 | import { DayPicker } from 'react-day-picker';
|
9 | import FocusLock from 'react-focus-lock';
|
10 | import { css } from '@emotion/css';
|
11 | import { useFocusRing, visuallyHiddenStyles } from '@spark-web/a11y';
|
12 | import { Box } from '@spark-web/box';
|
13 | import { useButtonStyles, BaseButton } from '@spark-web/button';
|
14 | import { useHeading } from '@spark-web/heading';
|
15 | import { useText } from '@spark-web/text';
|
16 | import { useTheme } from '@spark-web/theme';
|
17 | import { jsx, jsxs } from 'react/jsx-runtime';
|
18 | import { useFieldContext } from '@spark-web/field';
|
19 | import { TextInput, InputAdornment } from '@spark-web/text-input';
|
20 | import { format, parse, isValid, isBefore, isAfter, isDate as isDate$1 } from 'date-fns';
|
21 |
|
22 | function CalendarContainer(_ref) {
|
23 | var children = _ref.children;
|
24 | var dayPickerStyles = useDayPickerStyles();
|
25 | return jsx(Box, {
|
26 | background: "surface",
|
27 | border: "standard",
|
28 | borderRadius: "medium",
|
29 | display: "inline-block",
|
30 | padding: "small",
|
31 | position: "relative",
|
32 | shadow: "medium",
|
33 | className: css(dayPickerStyles),
|
34 | children: children
|
35 | });
|
36 | }
|
37 | function useDayPickerStyles() {
|
38 | var theme = useTheme();
|
39 | var cellSize = theme.sizing.medium;
|
40 | var _useHeading = useHeading({
|
41 | level: '3',
|
42 | align: 'left'
|
43 | }),
|
44 | _useHeading2 = _slicedToArray(_useHeading, 2),
|
45 | typographyHeadingStyles = _useHeading2[0],
|
46 | responsiveHeadingStyles = _useHeading2[1];
|
47 | var _useText = useText({
|
48 | baseline: true,
|
49 | tone: 'neutral',
|
50 | size: 'small',
|
51 | weight: 'regular'
|
52 | }),
|
53 | _useText2 = _slicedToArray(_useText, 2),
|
54 | typographyTextStyles = _useText2[0],
|
55 | responsiveTextStyles = _useText2[1];
|
56 | var _useButtonStyles = useButtonStyles({
|
57 | iconOnly: false,
|
58 | prominence: 'none',
|
59 | size: 'medium',
|
60 | tone: 'primary'
|
61 | }),
|
62 | _useButtonStyles2 = _slicedToArray(_useButtonStyles, 2),
|
63 | buttonStyles = _useButtonStyles2[1];
|
64 | var focusStyles = useFocusRing({
|
65 | always: true
|
66 | });
|
67 | return {
|
68 | '.rdp-vhidden': visuallyHiddenStyles,
|
69 |
|
70 | '.rdp-button_reset': {
|
71 | appearance: 'none',
|
72 | background: 'none',
|
73 | border: 'none',
|
74 | margin: 0,
|
75 | padding: 0,
|
76 | cursor: 'pointer',
|
77 | color: 'inherit',
|
78 | font: 'inherit'
|
79 | },
|
80 |
|
81 | '.rdp-caption': {
|
82 | display: 'flex',
|
83 | alignItems: 'center',
|
84 | justifyContent: 'center',
|
85 | height: theme.sizing.medium,
|
86 | position: 'relative'
|
87 | },
|
88 | '.rdp-caption_label': _objectSpread(_objectSpread(_objectSpread({}, typographyHeadingStyles), responsiveHeadingStyles), {}, {
|
89 | margin: 0,
|
90 | whiteSpace: 'nowrap'
|
91 | }),
|
92 |
|
93 | '.rdp-nav': {
|
94 | position: 'absolute',
|
95 | top: 0,
|
96 | bottom: 0,
|
97 | left: 0,
|
98 | right: 0,
|
99 | display: 'flex',
|
100 | alignItems: 'center',
|
101 | justifyContent: 'space-between',
|
102 | paddingLeft: theme.spacing.medium - theme.spacing.small,
|
103 | paddingRight: theme.spacing.medium - theme.spacing.small
|
104 | },
|
105 | '.rdp-nav_button': _objectSpread(_objectSpread({}, buttonStyles), {}, {
|
106 | display: 'inline-flex',
|
107 | alignItems: 'center',
|
108 | justifyContent: 'center',
|
109 | cursor: 'pointer',
|
110 | height: theme.sizing.small,
|
111 | width: theme.sizing.small,
|
112 | borderRadius: theme.border.radius.full
|
113 | }),
|
114 | '.rdp-nav_button:focus': _objectSpread(_objectSpread({}, focusStyles), {}, {
|
115 | position: 'relative',
|
116 | backgroundColor: theme.backgroundInteractions.primaryLowHover
|
117 | }),
|
118 |
|
119 | '.rdp-head_cell': _objectSpread(_objectSpread(_objectSpread({}, typographyTextStyles), responsiveTextStyles), {}, {
|
120 | fontWeight: theme.typography.fontWeight.semibold,
|
121 | margin: 0,
|
122 | padding: 0,
|
123 | textAlign: 'center',
|
124 | verticalAlign: 'middle',
|
125 | height: cellSize,
|
126 | width: cellSize
|
127 | }),
|
128 |
|
129 | '.rdp-day': _objectSpread(_objectSpread(_objectSpread(_objectSpread({}, typographyTextStyles), responsiveTextStyles), buttonStyles), {}, {
|
130 | borderRadius: theme.border.radius.small
|
131 | }),
|
132 | '.rdp-day:focus': _objectSpread(_objectSpread({}, focusStyles), {}, {
|
133 | position: 'relative',
|
134 | backgroundColor: theme.backgroundInteractions.primaryLowHover
|
135 | }),
|
136 | ".rdp-button:disabled, .rdp-button[aria-disabled='true']": {
|
137 | color: theme.color.foreground.disabled,
|
138 | pointerEvents: 'none',
|
139 | userSelect: 'none'
|
140 | },
|
141 | '.rdp-weeknumber, .rdp-day': {
|
142 | display: 'flex',
|
143 | justifyContent: 'center',
|
144 | alignItems: 'center',
|
145 | width: cellSize,
|
146 | height: cellSize
|
147 | },
|
148 |
|
149 | '.rdp-months': {
|
150 | display: 'flex'
|
151 | },
|
152 | '.rdp-month:first-of-type': {
|
153 | marginLeft: 0
|
154 | },
|
155 | '.rdp-month:last-of-type': {
|
156 | marginRight: 0
|
157 | },
|
158 | '.rdp-table': {
|
159 | margin: 0,
|
160 | maxWidth: "calc(".concat(cellSize, " * 7)"),
|
161 | borderCollapse: 'collapse'
|
162 | },
|
163 | '.rdp-tbody': {
|
164 | border: 0
|
165 | },
|
166 | '.rdp-cell': {
|
167 | width: cellSize,
|
168 | height: cellSize,
|
169 | padding: 0,
|
170 | textAlign: 'center'
|
171 | },
|
172 | ".rdp-day_selected:not([aria-disabled='true']), .rdp-day_selected:focus:not([aria-disabled='true']), .rdp-day_selected:active:not([aria-disabled='true']), .rdp-day_selected:hover:not([aria-disabled='true']), .rdp-day_selected:hover:not([aria-disabled='true'])": {
|
173 | backgroundColor: theme.color.background.primary,
|
174 | color: theme.color.foreground.neutralInverted
|
175 | }
|
176 | };
|
177 | }
|
178 |
|
179 | function CalendarSingle(props) {
|
180 | return jsx(FocusLock, {
|
181 | autoFocus: false,
|
182 | returnFocus: true,
|
183 | children: jsx(CalendarContainer, {
|
184 | children: jsx(DayPicker, _objectSpread(_objectSpread({}, props), {}, {
|
185 | mode: "single",
|
186 | components: calendarComponents
|
187 | }))
|
188 | })
|
189 | });
|
190 | }
|
191 | var calendarComponents = {
|
192 | IconRight: function IconRight() {
|
193 | return jsx(ChevronRightIcon, {
|
194 | size: "xsmall"
|
195 | });
|
196 | },
|
197 | IconLeft: function IconLeft() {
|
198 | return jsx(ChevronLeftIcon, {
|
199 | size: "xsmall"
|
200 | });
|
201 | }
|
202 | };
|
203 |
|
204 |
|
205 | var dateFormat = 'dd/MM/yyyy';
|
206 |
|
207 |
|
208 | function formatDate(date) {
|
209 | return format(new Date(date), dateFormat);
|
210 | }
|
211 |
|
212 |
|
213 | function formatHumanReadableDate(date) {
|
214 | return format(date, 'eeee MMMM do, yyyy');
|
215 | }
|
216 |
|
217 |
|
218 | function isDate(value) {
|
219 | return isDate$1(value);
|
220 | }
|
221 |
|
222 |
|
223 |
|
224 |
|
225 |
|
226 |
|
227 | function parseDate(value) {
|
228 | if (value.length !== dateFormat.length) {
|
229 | return undefined;
|
230 | }
|
231 | var parsedDate = parse(value, dateFormat, new Date());
|
232 | if (isDate(parsedDate) && isValid(parsedDate)) {
|
233 | return parsedDate;
|
234 | }
|
235 | return undefined;
|
236 | }
|
237 |
|
238 |
|
239 |
|
240 |
|
241 |
|
242 |
|
243 |
|
244 |
|
245 | function constrainDate(date, minDate, maxDate) {
|
246 | if (!date) {
|
247 | return date;
|
248 | }
|
249 | if (minDate && isBefore(date, minDate)) {
|
250 | return minDate;
|
251 | }
|
252 | if (maxDate && isAfter(date, maxDate)) {
|
253 | return maxDate;
|
254 | }
|
255 | return date;
|
256 | }
|
257 |
|
258 |
|
259 |
|
260 |
|
261 | function getSeparatorIndexes() {
|
262 | var indexes = [];
|
263 | for (var i = 0; i < dateFormat.length; i++) {
|
264 | if (dateFormat[i] === '/') {
|
265 | indexes.push(i);
|
266 | }
|
267 | }
|
268 | return indexes;
|
269 | }
|
270 |
|
271 |
|
272 |
|
273 |
|
274 |
|
275 |
|
276 |
|
277 | function formatDateOnChange(date, inputValue, cursorPosition) {
|
278 | var indexes = getSeparatorIndexes();
|
279 | var format = dateFormat.toUpperCase();
|
280 | var dateFormatParts = format.split('/');
|
281 | var dateParts = date.split('/');
|
282 | var newDate = [];
|
283 |
|
284 |
|
285 | if (!inputValue || !date) return format;
|
286 | if (indexes.includes(cursorPosition)) {
|
287 | return date.slice(0, cursorPosition) + inputValue.slice(cursorPosition);
|
288 | }
|
289 | dateFormatParts.forEach(function (part, index) {
|
290 | var cleanValue = part;
|
291 | var cleanDate = dateParts[index].replace(/\D/g, '');
|
292 | var length = part.length;
|
293 | if (Boolean(cleanDate)) {
|
294 | var _char = part.charAt(0);
|
295 | var formattedDate = cleanDate.padEnd(length, _char).slice(0, length);
|
296 | cleanValue = formattedDate;
|
297 | }
|
298 | newDate.push(cleanValue);
|
299 | });
|
300 | return newDate.join('/');
|
301 | }
|
302 |
|
303 | var _excluded$1 = ["buttonRef", "buttonOnClick", "value"];
|
304 | var DateInput = forwardRef(function DateInput(_ref, forwardedRef) {
|
305 | var buttonRef = _ref.buttonRef,
|
306 | buttonOnClick = _ref.buttonOnClick,
|
307 | value = _ref.value,
|
308 | consumerProps = _objectWithoutProperties(_ref, _excluded$1);
|
309 | var _useIconButtonStyles = useIconButtonStyles(),
|
310 | _useIconButtonStyles2 = _slicedToArray(_useIconButtonStyles, 2),
|
311 | boxProps = _useIconButtonStyles2[0],
|
312 | buttonStyles = _useIconButtonStyles2[1];
|
313 | var _useFieldContext = useFieldContext(),
|
314 | _useFieldContext2 = _slicedToArray(_useFieldContext, 1),
|
315 | disabled = _useFieldContext2[0].disabled;
|
316 | var buttonLabel = useMemo(function () {
|
317 | if (typeof value !== 'string') {
|
318 | return 'Choose date';
|
319 | }
|
320 | var parsed = parseDate(value);
|
321 | if (!parsed) {
|
322 | return 'Choose date';
|
323 | }
|
324 | return "Change Date, ".concat(formatHumanReadableDate(parsed));
|
325 | }, [value]);
|
326 | return jsx(TextInput, _objectSpread(_objectSpread({}, consumerProps), {}, {
|
327 | ref: forwardedRef,
|
328 | value: value,
|
329 | children: jsx(InputAdornment, {
|
330 | placement: "end",
|
331 | children: jsx(BaseButton, _objectSpread(_objectSpread({}, boxProps), {}, {
|
332 | "aria-label": buttonLabel,
|
333 | onClick: buttonOnClick,
|
334 | ref: buttonRef,
|
335 | disabled: disabled,
|
336 | className: css(buttonStyles)
|
337 |
|
338 |
|
339 |
|
340 | ,
|
341 | tabIndex: disabled ? -1 : undefined,
|
342 | children: jsx(CalendarIcon, {
|
343 | tone: disabled ? 'disabled' : 'neutral'
|
344 | })
|
345 | }))
|
346 | })
|
347 | }));
|
348 | });
|
349 | function useIconButtonStyles() {
|
350 | var _useButtonStyles = useButtonStyles({
|
351 | iconOnly: false,
|
352 | prominence: 'none',
|
353 | size: 'medium',
|
354 | tone: 'neutral'
|
355 | }),
|
356 | _useButtonStyles2 = _slicedToArray(_useButtonStyles, 2),
|
357 | buttonStyles = _useButtonStyles2[1];
|
358 | return [{
|
359 | alignItems: 'center',
|
360 | borderRadius: 'full',
|
361 | cursor: 'pointer',
|
362 | display: 'inline-flex',
|
363 | gap: 'small',
|
364 | height: 'small',
|
365 | justifyContent: 'center',
|
366 | paddingX: 'xsmall',
|
367 | position: 'relative',
|
368 | width: 'small'
|
369 | }, buttonStyles];
|
370 | }
|
371 |
|
372 |
|
373 |
|
374 |
|
375 |
|
376 |
|
377 |
|
378 | function useTernaryState(initialValue) {
|
379 | var _useState = useState(initialValue),
|
380 | _useState2 = _slicedToArray(_useState, 2),
|
381 | state = _useState2[0],
|
382 | setState = _useState2[1];
|
383 | var setTrue = useCallback(function () {
|
384 | return setState(true);
|
385 | }, []);
|
386 | var setFalse = useCallback(function () {
|
387 | return setState(false);
|
388 | }, []);
|
389 | return [state, setTrue, setFalse];
|
390 | }
|
391 |
|
392 |
|
393 |
|
394 |
|
395 |
|
396 |
|
397 | function useClickOutside(ref, handler) {
|
398 | useEffect(function () {
|
399 | function listener(event) {
|
400 | var element = ref === null || ref === void 0 ? void 0 : ref.current;
|
401 |
|
402 |
|
403 | if (!element || element.contains(event.target)) {
|
404 | return;
|
405 | }
|
406 | handler(event);
|
407 | }
|
408 | window.addEventListener('mousedown', listener);
|
409 | return function () {
|
410 | return window.removeEventListener('mousedown', listener);
|
411 | };
|
412 | }, [handler, ref]);
|
413 | }
|
414 |
|
415 | var _excluded = ["data", "initialMonth", "maxDate", "minDate", "onChange", "value"];
|
416 | var DatePicker = forwardRef(function DatePicker(_ref, forwardedRef) {
|
417 | var data = _ref.data,
|
418 | initialMonth = _ref.initialMonth,
|
419 | maxDate = _ref.maxDate,
|
420 | minDate = _ref.minDate,
|
421 | onChange = _ref.onChange,
|
422 | value = _ref.value,
|
423 | consumerProps = _objectWithoutProperties(_ref, _excluded);
|
424 | var _useTernaryState = useTernaryState(false),
|
425 | _useTernaryState2 = _slicedToArray(_useTernaryState, 3),
|
426 | isCalendarOpen = _useTernaryState2[0],
|
427 | openCalendar = _useTernaryState2[1],
|
428 | closeCalendar = _useTernaryState2[2];
|
429 |
|
430 |
|
431 | var triggerRef = useRef(null);
|
432 | var _useState = useState(null),
|
433 | _useState2 = _slicedToArray(_useState, 2),
|
434 | refEl = _useState2[0],
|
435 | setRefEl = _useState2[1];
|
436 | var _useState3 = useState(null),
|
437 | _useState4 = _slicedToArray(_useState3, 2),
|
438 | popperEl = _useState4[0],
|
439 | setPopperEl = _useState4[1];
|
440 | var _usePopper = usePopper(refEl, popperEl, {
|
441 | placement: 'bottom-start',
|
442 | modifiers: [{
|
443 | name: 'offset',
|
444 | options: {
|
445 | offset: [0, 8]
|
446 | }
|
447 | }]
|
448 | }),
|
449 | styles = _usePopper.styles,
|
450 | attributes = _usePopper.attributes;
|
451 | var defaultValue = dateFormat.toUpperCase();
|
452 | var _useState5 = useState(''),
|
453 | _useState6 = _slicedToArray(_useState5, 2),
|
454 | inputValue = _useState6[0],
|
455 | setInputValue = _useState6[1];
|
456 | var onSelect = useCallback(function (_, selectedDay, modifiers) {
|
457 |
|
458 | if (modifiers.disabled) {
|
459 | return;
|
460 | }
|
461 |
|
462 | setInputValue(formatDate(selectedDay));
|
463 |
|
464 | onChange(selectedDay);
|
465 |
|
466 | closeCalendar();
|
467 | }, [onChange, closeCalendar]);
|
468 | var onInputChange = useCallback(function (event) {
|
469 | var _event$target$selecti;
|
470 | var indexes = getSeparatorIndexes();
|
471 | var eventValue = event.target.value;
|
472 | var startPos = (_event$target$selecti = event.target.selectionStart) !== null && _event$target$selecti !== void 0 ? _event$target$selecti : 0;
|
473 | var formattedDate = formatDateOnChange(eventValue, inputValue, startPos);
|
474 | var nextPos = startPos;
|
475 |
|
476 |
|
477 | if (indexes.includes(startPos) && eventValue.length > inputValue.length) {
|
478 | nextPos = startPos + 1;
|
479 | }
|
480 | setInputValue(formattedDate);
|
481 | setCursorPosition(event, nextPos);
|
482 | var parsedDate = parseDate(formattedDate);
|
483 | var constrainedDate = constrainDate(parsedDate, minDate, maxDate);
|
484 | onChange(constrainedDate);
|
485 | }, [maxDate, minDate, onChange, inputValue]);
|
486 |
|
487 |
|
488 | useEffect(function () {
|
489 | if (value) {
|
490 | setInputValue(formatDate(value));
|
491 | }
|
492 | }, [value]);
|
493 |
|
494 |
|
495 | var clickOutsideRef = useRef(popperEl);
|
496 | clickOutsideRef.current = popperEl;
|
497 | var handleClickOutside = useCallback(function () {
|
498 | if (isCalendarOpen) {
|
499 | closeCalendar();
|
500 | }
|
501 | }, [isCalendarOpen, closeCalendar]);
|
502 | useClickOutside(clickOutsideRef, handleClickOutside);
|
503 |
|
504 |
|
505 | var handleEscape = useCallback(function (event) {
|
506 | if (isCalendarOpen && event.code === 'Escape') {
|
507 | event.preventDefault();
|
508 | event.stopPropagation();
|
509 |
|
510 | closeCalendar();
|
511 | }
|
512 | }, [isCalendarOpen, closeCalendar]);
|
513 | var disabledCalendarDays = useMemo(function () {
|
514 | if (!(minDate || maxDate)) {
|
515 | return;
|
516 | }
|
517 | return [minDate ? {
|
518 | before: minDate
|
519 | } : undefined, maxDate ? {
|
520 | after: maxDate
|
521 | } : undefined].filter(function (x) {
|
522 | return Boolean(x);
|
523 | });
|
524 | }, [minDate, maxDate]);
|
525 |
|
526 |
|
527 | var setCursorPosition = function setCursorPosition(event, position) {
|
528 | setTimeout(function () {
|
529 | event.target.setSelectionRange(position, position);
|
530 | }, 0);
|
531 | };
|
532 | return jsxs(Stack, {
|
533 | ref: setRefEl,
|
534 | onKeyDown: handleEscape,
|
535 | data: data,
|
536 | width: "full",
|
537 | children: [jsx(DateInput, _objectSpread(_objectSpread({}, consumerProps), {}, {
|
538 | buttonOnClick: openCalendar,
|
539 | buttonRef: triggerRef,
|
540 | onChange: onInputChange,
|
541 | ref: forwardedRef,
|
542 | value: inputValue,
|
543 | placeholder: defaultValue,
|
544 | onFocus: function onFocus(e) {
|
545 | if (!inputValue) setInputValue(defaultValue);
|
546 | setCursorPosition(e, 0);
|
547 | },
|
548 | onBlur: function onBlur() {
|
549 | if (inputValue === defaultValue) setInputValue('');
|
550 | }
|
551 | })), isCalendarOpen && jsx("div", _objectSpread(_objectSpread({}, attributes.popper), {}, {
|
552 | ref: setPopperEl,
|
553 | style: _objectSpread(_objectSpread({}, styles.popper), {}, {
|
554 | zIndex: 1
|
555 | }),
|
556 | children: jsx(CalendarSingle, {
|
557 | defaultMonth: value || initialMonth,
|
558 | disabled: disabledCalendarDays,
|
559 | initialFocus: true,
|
560 | numberOfMonths: 1,
|
561 | onSelect: onSelect,
|
562 | selected: value
|
563 | })
|
564 | }))]
|
565 | });
|
566 | });
|
567 |
|
568 | export { DatePicker };
|