@transferwise/components 46.156.0 → 46.157.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/Calendar/Calendar.constants.js +10 -0
- package/build/Calendar/Calendar.constants.js.map +1 -0
- package/build/Calendar/Calendar.constants.mjs +8 -0
- package/build/Calendar/Calendar.constants.mjs.map +1 -0
- package/build/Calendar/Calendar.js +121 -17
- package/build/Calendar/Calendar.js.map +1 -1
- package/build/Calendar/Calendar.messages.js +12 -0
- package/build/Calendar/Calendar.messages.js.map +1 -1
- package/build/Calendar/Calendar.messages.mjs +12 -0
- package/build/Calendar/Calendar.messages.mjs.map +1 -1
- package/build/Calendar/Calendar.mjs +122 -18
- package/build/Calendar/Calendar.mjs.map +1 -1
- package/build/Calendar/DateHeader/DateHeader.js +5 -1
- package/build/Calendar/DateHeader/DateHeader.js.map +1 -1
- package/build/Calendar/DateHeader/DateHeader.mjs +5 -1
- package/build/Calendar/DateHeader/DateHeader.mjs.map +1 -1
- package/build/Calendar/DayCalendar/DayCalendar.js +20 -2
- package/build/Calendar/DayCalendar/DayCalendar.js.map +1 -1
- package/build/Calendar/DayCalendar/DayCalendar.mjs +20 -2
- package/build/Calendar/DayCalendar/DayCalendar.mjs.map +1 -1
- package/build/Calendar/DayCalendar/Table/DayCalendarTable.js +60 -19
- package/build/Calendar/DayCalendar/Table/DayCalendarTable.js.map +1 -1
- package/build/Calendar/DayCalendar/Table/DayCalendarTable.mjs +60 -19
- package/build/Calendar/DayCalendar/Table/DayCalendarTable.mjs.map +1 -1
- package/build/Calendar/MonthCalendar/MonthCalendar.js +6 -0
- package/build/Calendar/MonthCalendar/MonthCalendar.js.map +1 -1
- package/build/Calendar/MonthCalendar/MonthCalendar.mjs +6 -0
- package/build/Calendar/MonthCalendar/MonthCalendar.mjs.map +1 -1
- package/build/Calendar/MonthCalendar/Table/MonthCalendarTable.js +8 -3
- package/build/Calendar/MonthCalendar/Table/MonthCalendarTable.js.map +1 -1
- package/build/Calendar/MonthCalendar/Table/MonthCalendarTable.mjs +8 -3
- package/build/Calendar/MonthCalendar/Table/MonthCalendarTable.mjs.map +1 -1
- package/build/Calendar/TableLink/TableLink.js +95 -0
- package/build/Calendar/TableLink/TableLink.js.map +1 -0
- package/build/Calendar/TableLink/TableLink.mjs +91 -0
- package/build/Calendar/TableLink/TableLink.mjs.map +1 -0
- package/build/Calendar/YearCalendar/Table/YearCalendarTable.js +8 -3
- package/build/Calendar/YearCalendar/Table/YearCalendarTable.js.map +1 -1
- package/build/Calendar/YearCalendar/Table/YearCalendarTable.mjs +8 -3
- package/build/Calendar/YearCalendar/Table/YearCalendarTable.mjs.map +1 -1
- package/build/Calendar/YearCalendar/YearCalendar.js +7 -0
- package/build/Calendar/YearCalendar/YearCalendar.js.map +1 -1
- package/build/Calendar/YearCalendar/YearCalendar.mjs +7 -0
- package/build/Calendar/YearCalendar/YearCalendar.mjs.map +1 -1
- package/build/Calendar/_helpers/clampToDayViewBoundary/clampToDayViewBoundary.js +24 -0
- package/build/Calendar/_helpers/clampToDayViewBoundary/clampToDayViewBoundary.js.map +1 -0
- package/build/Calendar/_helpers/clampToDayViewBoundary/clampToDayViewBoundary.mjs +22 -0
- package/build/Calendar/_helpers/clampToDayViewBoundary/clampToDayViewBoundary.mjs.map +1 -0
- package/build/Calendar/_helpers/hasDisabledDatesInMonth/hasDisabledDatesInMonth.js +33 -0
- package/build/Calendar/_helpers/hasDisabledDatesInMonth/hasDisabledDatesInMonth.js.map +1 -0
- package/build/Calendar/_helpers/hasDisabledDatesInMonth/hasDisabledDatesInMonth.mjs +31 -0
- package/build/Calendar/_helpers/hasDisabledDatesInMonth/hasDisabledDatesInMonth.mjs.map +1 -0
- package/build/Calendar/_helpers/isDateDisabled/isDateDisabled.js +40 -0
- package/build/Calendar/_helpers/isDateDisabled/isDateDisabled.js.map +1 -0
- package/build/Calendar/_helpers/isDateDisabled/isDateDisabled.mjs +38 -0
- package/build/Calendar/_helpers/isDateDisabled/isDateDisabled.mjs.map +1 -0
- package/build/DateLookup/DateLookup.js +20 -9
- package/build/DateLookup/DateLookup.js.map +1 -1
- package/build/DateLookup/DateLookup.mjs +20 -9
- package/build/DateLookup/DateLookup.mjs.map +1 -1
- package/build/ExpressiveMoneyInput/CurrencySelector/CurrencySelector.js +3 -5
- package/build/ExpressiveMoneyInput/CurrencySelector/CurrencySelector.js.map +1 -1
- package/build/ExpressiveMoneyInput/CurrencySelector/CurrencySelector.mjs +3 -5
- package/build/ExpressiveMoneyInput/CurrencySelector/CurrencySelector.mjs.map +1 -1
- package/build/Inputs/SelectInput/TriggerButton/SelectInputTriggerButton.js.map +1 -1
- package/build/Inputs/SelectInput/TriggerButton/SelectInputTriggerButton.mjs.map +1 -1
- package/build/Typeahead/Typeahead.js +53 -27
- package/build/Typeahead/Typeahead.js.map +1 -1
- package/build/Typeahead/Typeahead.messages.js +3 -0
- package/build/Typeahead/Typeahead.messages.js.map +1 -1
- package/build/Typeahead/Typeahead.messages.mjs +3 -0
- package/build/Typeahead/Typeahead.messages.mjs.map +1 -1
- package/build/Typeahead/Typeahead.mjs +53 -27
- package/build/Typeahead/Typeahead.mjs.map +1 -1
- package/build/Typeahead/TypeaheadInput/TypeaheadInput.js +2 -2
- package/build/Typeahead/TypeaheadInput/TypeaheadInput.js.map +1 -1
- package/build/Typeahead/TypeaheadInput/TypeaheadInput.mjs +2 -2
- package/build/Typeahead/TypeaheadInput/TypeaheadInput.mjs.map +1 -1
- package/build/Typeahead/TypeaheadOption/TypeaheadOption.js +3 -1
- package/build/Typeahead/TypeaheadOption/TypeaheadOption.js.map +1 -1
- package/build/Typeahead/TypeaheadOption/TypeaheadOption.mjs +3 -1
- package/build/Typeahead/TypeaheadOption/TypeaheadOption.mjs.map +1 -1
- package/build/i18n/en.json +5 -0
- package/build/i18n/en.json.js +5 -0
- package/build/i18n/en.json.js.map +1 -1
- package/build/i18n/en.json.mjs +5 -0
- package/build/i18n/en.json.mjs.map +1 -1
- package/build/index.js +2 -2
- package/build/index.mjs +1 -1
- package/build/main.css +133 -43
- package/build/styles/Calendar/Calendar.css +112 -37
- package/build/styles/Calendar/TableLink/TableLink.css +115 -0
- package/build/styles/Typeahead/Typeahead.css +4 -0
- package/build/styles/Typeahead/TypeaheadOption/TypeaheadOption.css +5 -0
- package/build/styles/main.css +133 -43
- package/build/types/Body/Body.d.ts +2 -2
- package/build/types/Calendar/Calendar.constants.d.ts +2 -0
- package/build/types/Calendar/Calendar.constants.d.ts.map +1 -0
- package/build/types/Calendar/Calendar.d.ts +16 -3
- package/build/types/Calendar/Calendar.d.ts.map +1 -1
- package/build/types/Calendar/Calendar.messages.d.ts +20 -0
- package/build/types/Calendar/Calendar.messages.d.ts.map +1 -1
- package/build/types/Calendar/Calendar.types.d.ts +32 -0
- package/build/types/Calendar/Calendar.types.d.ts.map +1 -0
- package/build/types/Calendar/DateHeader/DateHeader.d.ts +2 -0
- package/build/types/Calendar/DateHeader/DateHeader.d.ts.map +1 -1
- package/build/types/Calendar/DayCalendar/DayCalendar.d.ts +7 -1
- package/build/types/Calendar/DayCalendar/DayCalendar.d.ts.map +1 -1
- package/build/types/Calendar/DayCalendar/Table/DayCalendarTable.d.ts +6 -0
- package/build/types/Calendar/DayCalendar/Table/DayCalendarTable.d.ts.map +1 -1
- package/build/types/Calendar/MonthCalendar/MonthCalendar.d.ts +1 -0
- package/build/types/Calendar/MonthCalendar/MonthCalendar.d.ts.map +1 -1
- package/build/types/Calendar/MonthCalendar/Table/MonthCalendarTable.d.ts +2 -1
- package/build/types/Calendar/MonthCalendar/Table/MonthCalendarTable.d.ts.map +1 -1
- package/build/types/Calendar/{tableLink → TableLink}/TableLink.d.ts +5 -1
- package/build/types/Calendar/TableLink/TableLink.d.ts.map +1 -0
- package/build/types/Calendar/TableLink/index.d.ts.map +1 -0
- package/build/types/Calendar/YearCalendar/Table/YearCalendarTable.d.ts +2 -1
- package/build/types/Calendar/YearCalendar/Table/YearCalendarTable.d.ts.map +1 -1
- package/build/types/Calendar/YearCalendar/YearCalendar.d.ts +1 -0
- package/build/types/Calendar/YearCalendar/YearCalendar.d.ts.map +1 -1
- package/build/types/Calendar/_helpers/clampToDayViewBoundary/clampToDayViewBoundary.d.ts +8 -0
- package/build/types/Calendar/_helpers/clampToDayViewBoundary/clampToDayViewBoundary.d.ts.map +1 -0
- package/build/types/Calendar/_helpers/hasDisabledDatesInMonth/hasDisabledDatesInMonth.d.ts +3 -0
- package/build/types/Calendar/_helpers/hasDisabledDatesInMonth/hasDisabledDatesInMonth.d.ts.map +1 -0
- package/build/types/Calendar/_helpers/index.d.ts +4 -0
- package/build/types/Calendar/_helpers/index.d.ts.map +1 -0
- package/build/types/Calendar/_helpers/isDateDisabled/isDateDisabled.d.ts +3 -0
- package/build/types/Calendar/_helpers/isDateDisabled/isDateDisabled.d.ts.map +1 -0
- package/build/types/Calendar/index.d.ts +1 -0
- package/build/types/Calendar/index.d.ts.map +1 -1
- package/build/types/DateLookup/DateLookup.d.ts +24 -5
- package/build/types/DateLookup/DateLookup.d.ts.map +1 -1
- package/build/types/DateLookup/index.d.ts +1 -0
- package/build/types/DateLookup/index.d.ts.map +1 -1
- package/build/types/ExpressiveMoneyInput/CurrencySelector/CurrencySelector.d.ts +6 -4
- package/build/types/ExpressiveMoneyInput/CurrencySelector/CurrencySelector.d.ts.map +1 -1
- package/build/types/IconButton/IconButton.d.ts +1 -1
- package/build/types/Inputs/SelectInput/TriggerButton/SelectInputTriggerButton.d.ts +1 -1
- package/build/types/Inputs/SelectInput/TriggerButton/SelectInputTriggerButton.d.ts.map +1 -1
- package/build/types/MoneyInput/MoneyInput.d.ts +1 -1
- package/build/types/MoneyInput/MoneyInput.d.ts.map +1 -1
- package/build/types/Title/Title.d.ts +2 -2
- package/build/types/Typeahead/Typeahead.d.ts.map +1 -1
- package/build/types/Typeahead/Typeahead.messages.d.ts +5 -0
- package/build/types/Typeahead/Typeahead.messages.d.ts.map +1 -1
- package/build/types/Typeahead/TypeaheadInput/TypeaheadInput.d.ts.map +1 -1
- package/build/types/Typeahead/TypeaheadOption/TypeaheadOption.d.ts +1 -0
- package/build/types/Typeahead/TypeaheadOption/TypeaheadOption.d.ts.map +1 -1
- package/build/types/Upload/Steps/UploadImageStep/UploadImageStep.d.ts +1 -1
- package/build/types/UploadInput/UploadItem/UploadItemLink.d.ts +1 -1
- package/build/types/index.d.ts +3 -2
- package/build/types/index.d.ts.map +1 -1
- package/build/types/test-utils/index.d.ts +10 -0
- package/build/types/test-utils/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/Calendar/Calendar.constants.ts +5 -0
- package/src/Calendar/Calendar.css +112 -37
- package/src/Calendar/Calendar.disabledDates.test.tsx +610 -0
- package/src/Calendar/Calendar.less +5 -35
- package/src/Calendar/Calendar.messages.ts +21 -0
- package/src/Calendar/Calendar.test.tsx +342 -32
- package/src/Calendar/Calendar.tsx +169 -19
- package/src/Calendar/Calendar.types.ts +35 -0
- package/src/Calendar/DateHeader/DateHeader.tsx +6 -0
- package/src/Calendar/DayCalendar/DayCalendar.tsx +39 -1
- package/src/Calendar/DayCalendar/Table/DayCalendarTable.tsx +67 -27
- package/src/Calendar/MonthCalendar/MonthCalendar.tsx +8 -0
- package/src/Calendar/MonthCalendar/Table/MonthCalendarTable.tsx +15 -3
- package/src/Calendar/TableLink/TableLink.css +115 -0
- package/src/Calendar/TableLink/TableLink.less +68 -0
- package/src/Calendar/TableLink/TableLink.tsx +117 -0
- package/src/Calendar/YearCalendar/Table/YearCalendarTable.tsx +15 -3
- package/src/Calendar/YearCalendar/YearCalendar.tsx +9 -0
- package/src/Calendar/_helpers/clampToDayViewBoundary/clampToDayViewBoundary.test.ts +90 -0
- package/src/Calendar/_helpers/clampToDayViewBoundary/clampToDayViewBoundary.ts +25 -0
- package/src/Calendar/_helpers/hasDisabledDatesInMonth/hasDisabledDatesInMonth.test.ts +98 -0
- package/src/Calendar/_helpers/hasDisabledDatesInMonth/hasDisabledDatesInMonth.ts +45 -0
- package/src/Calendar/_helpers/index.ts +3 -0
- package/src/Calendar/_helpers/isDateDisabled/isDateDisabled.test.ts +169 -0
- package/src/Calendar/_helpers/isDateDisabled/isDateDisabled.ts +59 -0
- package/src/Calendar/_stories/Calendar.accessibility.docs.mdx +118 -0
- package/src/Calendar/_stories/Calendar.story.tsx +144 -4
- package/src/Calendar/_stories/Calendar.test.story.tsx +114 -0
- package/src/Calendar/index.ts +7 -0
- package/src/DateLookup/DateLookup.disabledDates.test.tsx +312 -0
- package/src/DateLookup/DateLookup.test.tsx +34 -37
- package/src/DateLookup/DateLookup.tsx +37 -13
- package/src/DateLookup/_stories/DateLookup.accessibility.docs.mdx +108 -0
- package/src/DateLookup/_stories/DateLookup.story.tsx +126 -2
- package/src/DateLookup/index.ts +6 -0
- package/src/ExpressiveMoneyInput/CurrencySelector/CurrencySelector.tsx +12 -13
- package/src/Inputs/SelectInput/SelectInput.test.tsx +37 -0
- package/src/Inputs/SelectInput/TriggerButton/SelectInputTriggerButton.tsx +1 -1
- package/src/Typeahead/Typeahead.css +4 -0
- package/src/Typeahead/Typeahead.less +2 -0
- package/src/Typeahead/Typeahead.messages.ts +5 -0
- package/src/Typeahead/Typeahead.test.tsx +80 -3
- package/src/Typeahead/Typeahead.tsx +50 -27
- package/src/Typeahead/TypeaheadInput/TypeaheadInput.tsx +2 -6
- package/src/Typeahead/TypeaheadOption/TypeaheadOption.css +5 -0
- package/src/Typeahead/TypeaheadOption/TypeaheadOption.less +9 -0
- package/src/Typeahead/TypeaheadOption/TypeaheadOption.tsx +4 -2
- package/src/i18n/en.json +4 -0
- package/src/index.ts +3 -2
- package/src/main.css +133 -43
- package/build/Calendar/tableLink/TableLink.js +0 -61
- package/build/Calendar/tableLink/TableLink.js.map +0 -1
- package/build/Calendar/tableLink/TableLink.mjs +0 -57
- package/build/Calendar/tableLink/TableLink.mjs.map +0 -1
- package/build/types/Calendar/tableLink/TableLink.d.ts.map +0 -1
- package/build/types/Calendar/tableLink/index.d.ts.map +0 -1
- package/src/Calendar/tableLink/TableLink.tsx +0 -77
- /package/build/types/Calendar/{tableLink → TableLink}/index.d.ts +0 -0
- /package/src/Calendar/{tableLink → TableLink}/index.ts +0 -0
|
@@ -1,40 +1,91 @@
|
|
|
1
|
-
import { useState, useCallback, useEffect, KeyboardEvent } from 'react';
|
|
1
|
+
import { useState, useCallback, useEffect, useId, useRef, KeyboardEvent } from 'react';
|
|
2
|
+
import { useIntl } from 'react-intl';
|
|
3
|
+
import { formatDate } from '@transferwise/formatting';
|
|
2
4
|
|
|
3
5
|
import { MonthFormat } from '../common';
|
|
4
6
|
import { isWithinRange, moveToWithinRange, returnDateView } from '../common/DateUtils';
|
|
7
|
+
import { clampToDayViewBoundary, isDateDisabled, hasDisabledDatesInMonth } from './_helpers';
|
|
8
|
+
import { LONG_DAY_FORMAT } from './Calendar.constants';
|
|
9
|
+
import { InlinePrompt } from '../Prompt/InlinePrompt';
|
|
10
|
+
import type { DateChangeMeta, DisabledRules } from './Calendar.types';
|
|
5
11
|
import DayCalendar from './DayCalendar';
|
|
6
12
|
import { getStartOfDay } from './GetStartOfDay';
|
|
13
|
+
import messages from './Calendar.messages';
|
|
7
14
|
import MonthCalendar from './MonthCalendar';
|
|
8
15
|
import YearCalendar from './YearCalendar';
|
|
9
16
|
|
|
10
17
|
export interface CalendarProps {
|
|
18
|
+
/** The currently selected date, or `null` for no selection. */
|
|
11
19
|
value: Date | null;
|
|
20
|
+
/** Earliest selectable date (inclusive). Dates before this are interactive but not selectable, and show a boundary prompt. */
|
|
12
21
|
min?: Date | null;
|
|
22
|
+
/** Latest selectable date (inclusive). Dates after this are interactive but not selectable, and show a boundary prompt. */
|
|
13
23
|
max?: Date | null;
|
|
24
|
+
/** Month name format in the header. Defaults to `'long'` (e.g. "January"). */
|
|
14
25
|
monthFormat?: `${MonthFormat}`;
|
|
26
|
+
/** Placeholder text shown in the month/year cells when no date is selected. */
|
|
15
27
|
placeholder?: string;
|
|
16
|
-
|
|
17
|
-
|
|
28
|
+
/** Rules for disabling specific weekdays, individual dates, or date ranges. The required `reason` is shown in an inline prompt and linked to disabled cells via `aria-describedby` for screen reader accessibility. */
|
|
29
|
+
disabledRules?: DisabledRules;
|
|
30
|
+
/** Called whenever the focused date changes — including disabled and out-of-range dates. `meta.isEnabled` is `false` when the date is not selectable. */
|
|
31
|
+
onDateFocus?: (date: Date | null, meta: DateChangeMeta) => void;
|
|
32
|
+
/** Called when a date is confirmed (click or Enter). Only fires for enabled dates. */
|
|
33
|
+
onChange?: (date: Date | null) => void;
|
|
18
34
|
}
|
|
19
35
|
|
|
36
|
+
/**
|
|
37
|
+
* Temporary toggle — set `true` to restore strict min/max keyboard clamping.
|
|
38
|
+
* @see https://transferwise.atlassian.net/browse/DS-8518
|
|
39
|
+
*/
|
|
40
|
+
const STRICT_KEYBOARD_CLAMPING = false;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Inline calendar for selecting a single date. Powers [DateLookup](?path=/docs/forms-datelookup--docs).
|
|
44
|
+
*/
|
|
20
45
|
const Calendar = ({
|
|
21
46
|
value,
|
|
22
47
|
min: minProp = null,
|
|
23
48
|
max: maxProp = null,
|
|
24
49
|
monthFormat = MonthFormat.LONG,
|
|
25
50
|
placeholder,
|
|
51
|
+
disabledRules,
|
|
52
|
+
onDateFocus,
|
|
26
53
|
onChange,
|
|
27
|
-
onSelect,
|
|
28
54
|
}: CalendarProps) => {
|
|
55
|
+
const intl = useIntl();
|
|
29
56
|
const selectedDate = getStartOfDay(value);
|
|
30
57
|
const min = getStartOfDay(minProp);
|
|
31
58
|
const max = getStartOfDay(maxProp);
|
|
59
|
+
const disabledReasonId = useId();
|
|
60
|
+
const disabledRulesRef = useRef(disabledRules);
|
|
61
|
+
useEffect(() => {
|
|
62
|
+
disabledRulesRef.current = disabledRules;
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
const [hasInteracted, setHasInteracted] = useState(false);
|
|
66
|
+
const [focusedDate, setFocusedDate] = useState<Date | null>(selectedDate);
|
|
67
|
+
|
|
68
|
+
useEffect(() => {
|
|
69
|
+
// eslint-disable-next-line react-hooks/set-state-in-effect
|
|
70
|
+
setFocusedDate(selectedDate);
|
|
71
|
+
}, [selectedDate?.getTime()]); // eslint-disable-line react-hooks/exhaustive-deps
|
|
72
|
+
|
|
73
|
+
const checkIsEnabled = (date: Date | null) => {
|
|
74
|
+
if (!date) return true;
|
|
75
|
+
return isWithinRange(date, min, max) && !isDateDisabled(date, disabledRulesRef.current);
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const focusedDateDisabled = focusedDate
|
|
79
|
+
? !isWithinRange(focusedDate, min, max) || isDateDisabled(focusedDate, disabledRules)
|
|
80
|
+
: false;
|
|
32
81
|
|
|
33
82
|
useEffect(() => {
|
|
34
83
|
if (selectedDate && !isWithinRange(selectedDate, min, max)) {
|
|
35
|
-
|
|
84
|
+
const clamped = moveToWithinRange(selectedDate, min, max);
|
|
85
|
+
setFocusedDate(clamped); // eslint-disable-line react-hooks/set-state-in-effect
|
|
86
|
+
onDateFocus?.(clamped, { isEnabled: checkIsEnabled(clamped) });
|
|
36
87
|
}
|
|
37
|
-
}, [selectedDate?.getTime(), min?.getTime(), max?.getTime(),
|
|
88
|
+
}, [selectedDate?.getTime(), min?.getTime(), max?.getTime(), onDateFocus]); // eslint-disable-line react-hooks/exhaustive-deps
|
|
38
89
|
|
|
39
90
|
const dateView = returnDateView(selectedDate, min, max);
|
|
40
91
|
const [viewMonth, setViewMonth] = useState(dateView.getMonth());
|
|
@@ -49,11 +100,20 @@ const Calendar = ({
|
|
|
49
100
|
setViewYear(view.getFullYear());
|
|
50
101
|
}, [selectedDate?.getTime(), min?.getTime(), max?.getTime()]); // eslint-disable-line react-hooks/exhaustive-deps
|
|
51
102
|
|
|
103
|
+
useEffect(() => {
|
|
104
|
+
if (focusedDate) {
|
|
105
|
+
// eslint-disable-next-line react-hooks/set-state-in-effect
|
|
106
|
+
setViewMonth(focusedDate.getMonth());
|
|
107
|
+
setViewYear(focusedDate.getFullYear());
|
|
108
|
+
}
|
|
109
|
+
}, [focusedDate?.getTime()]); // eslint-disable-line react-hooks/exhaustive-deps
|
|
110
|
+
|
|
52
111
|
const handleViewDateUpdate = useCallback(
|
|
53
112
|
// eslint-disable-next-line react-hooks/preserve-manual-memoization
|
|
54
113
|
({ month, year }: { month?: number; year?: number }) => {
|
|
55
114
|
if (month !== undefined) setViewMonth(month);
|
|
56
115
|
if (year !== undefined) setViewYear(year);
|
|
116
|
+
setHasInteracted(false);
|
|
57
117
|
},
|
|
58
118
|
[],
|
|
59
119
|
);
|
|
@@ -64,33 +124,63 @@ const Calendar = ({
|
|
|
64
124
|
|
|
65
125
|
const handleSelectedDateUpdate = useCallback(
|
|
66
126
|
(date: Date) => {
|
|
67
|
-
if (
|
|
68
|
-
|
|
69
|
-
|
|
127
|
+
if (!hasInteracted) setHasInteracted(true);
|
|
128
|
+
const isEnabled = checkIsEnabled(date);
|
|
129
|
+
setFocusedDate(date);
|
|
130
|
+
onDateFocus?.(date, { isEnabled });
|
|
131
|
+
if (isEnabled && onChange) {
|
|
70
132
|
onChange(date);
|
|
71
133
|
}
|
|
72
134
|
},
|
|
73
|
-
[
|
|
135
|
+
[onDateFocus, onChange], // eslint-disable-line react-hooks/exhaustive-deps
|
|
74
136
|
);
|
|
75
137
|
|
|
76
138
|
const adjustDate = useCallback(
|
|
77
139
|
(daysToAdd: number, monthsToAdd: number, yearsToAdd: number) => {
|
|
140
|
+
const base = focusedDate ?? selectedDate;
|
|
78
141
|
let date: Date | null;
|
|
79
|
-
if (
|
|
142
|
+
if (base) {
|
|
80
143
|
date = new Date(
|
|
81
|
-
mode === 'year' ?
|
|
82
|
-
mode === 'month' ?
|
|
83
|
-
mode === 'day' ?
|
|
144
|
+
mode === 'year' ? base.getFullYear() + yearsToAdd : base.getFullYear(),
|
|
145
|
+
mode === 'month' ? base.getMonth() + monthsToAdd : base.getMonth(),
|
|
146
|
+
mode === 'day' ? base.getDate() + daysToAdd : base.getDate(),
|
|
84
147
|
);
|
|
85
148
|
} else {
|
|
86
149
|
date = getStartOfDay(new Date());
|
|
87
150
|
}
|
|
88
|
-
date &&=
|
|
89
|
-
|
|
90
|
-
|
|
151
|
+
date &&=
|
|
152
|
+
STRICT_KEYBOARD_CLAMPING || mode !== 'day'
|
|
153
|
+
? moveToWithinRange(date, min, max)
|
|
154
|
+
: clampToDayViewBoundary(date, min, max);
|
|
155
|
+
if (date?.getTime() !== focusedDate?.getTime()) {
|
|
156
|
+
const monthChanged = date
|
|
157
|
+
? date.getMonth() !== focusedDate?.getMonth() ||
|
|
158
|
+
date.getFullYear() !== focusedDate?.getFullYear()
|
|
159
|
+
: false;
|
|
160
|
+
setFocusedDate(date);
|
|
161
|
+
if (date) {
|
|
162
|
+
setViewMonth(date.getMonth());
|
|
163
|
+
setViewYear(date.getFullYear());
|
|
164
|
+
}
|
|
165
|
+
if (monthChanged) {
|
|
166
|
+
setHasInteracted(false);
|
|
167
|
+
} else if (!hasInteracted) {
|
|
168
|
+
setHasInteracted(true);
|
|
169
|
+
}
|
|
170
|
+
onDateFocus?.(date, { isEnabled: checkIsEnabled(date) });
|
|
91
171
|
}
|
|
92
172
|
},
|
|
93
|
-
[selectedDate, min, max, mode,
|
|
173
|
+
[focusedDate, selectedDate, min, max, mode, onDateFocus], // eslint-disable-line react-hooks/exhaustive-deps
|
|
174
|
+
);
|
|
175
|
+
|
|
176
|
+
const handleDayFocus = useCallback(
|
|
177
|
+
(date: Date) => {
|
|
178
|
+
if (date.getTime() !== focusedDate?.getTime()) {
|
|
179
|
+
setFocusedDate(date);
|
|
180
|
+
onDateFocus?.(date, { isEnabled: checkIsEnabled(date) });
|
|
181
|
+
}
|
|
182
|
+
},
|
|
183
|
+
[focusedDate, onDateFocus], // eslint-disable-line react-hooks/exhaustive-deps
|
|
94
184
|
);
|
|
95
185
|
|
|
96
186
|
const handleKeyDown = useCallback(
|
|
@@ -112,25 +202,73 @@ const Calendar = ({
|
|
|
112
202
|
adjustDate(7, 4, 4);
|
|
113
203
|
event.preventDefault();
|
|
114
204
|
break;
|
|
205
|
+
case 'Enter':
|
|
206
|
+
case ' ':
|
|
207
|
+
if (
|
|
208
|
+
mode === 'day' &&
|
|
209
|
+
focusedDate &&
|
|
210
|
+
(event.target as HTMLElement).hasAttribute('data-calendar-cell')
|
|
211
|
+
) {
|
|
212
|
+
handleSelectedDateUpdate(focusedDate);
|
|
213
|
+
event.preventDefault();
|
|
214
|
+
}
|
|
215
|
+
break;
|
|
115
216
|
default:
|
|
116
217
|
break;
|
|
117
218
|
}
|
|
118
219
|
},
|
|
119
|
-
[adjustDate],
|
|
220
|
+
[adjustDate, focusedDate, mode, handleSelectedDateUpdate],
|
|
120
221
|
);
|
|
121
222
|
|
|
223
|
+
const focusedDateOutOfRange = focusedDate ? !isWithinRange(focusedDate, min, max) : false;
|
|
224
|
+
|
|
225
|
+
const formatDateForPrompt = (date: Date) => formatDate(date, intl.locale, LONG_DAY_FORMAT);
|
|
226
|
+
|
|
227
|
+
const outOfRangePromptText =
|
|
228
|
+
min && max
|
|
229
|
+
? intl.formatMessage(messages.outOfRangeBetween, {
|
|
230
|
+
min: formatDateForPrompt(min),
|
|
231
|
+
max: formatDateForPrompt(max),
|
|
232
|
+
})
|
|
233
|
+
: min
|
|
234
|
+
? intl.formatMessage(messages.outOfRangeAfter, { min: formatDateForPrompt(min) })
|
|
235
|
+
: max
|
|
236
|
+
? intl.formatMessage(messages.outOfRangeBefore, { max: formatDateForPrompt(max) })
|
|
237
|
+
: '';
|
|
238
|
+
|
|
239
|
+
const hasOutOfRangeDates = mode === 'day' && !!(min || max);
|
|
240
|
+
const showRulesPrompt =
|
|
241
|
+
mode === 'day' &&
|
|
242
|
+
!!disabledRules &&
|
|
243
|
+
hasDisabledDatesInMonth(viewMonth, viewYear, disabledRules, min, max);
|
|
244
|
+
const showOutOfRangePrompt = mode === 'day' && focusedDateOutOfRange;
|
|
245
|
+
const showPrompt = hasOutOfRangeDates || showRulesPrompt;
|
|
246
|
+
|
|
247
|
+
const promptText = showOutOfRangePrompt
|
|
248
|
+
? outOfRangePromptText
|
|
249
|
+
: (disabledRules?.reason ?? outOfRangePromptText);
|
|
250
|
+
const promptSentiment = showOutOfRangePrompt || focusedDateDisabled ? 'negative' : 'neutral';
|
|
251
|
+
// Visible when there's an actionable message; sr-only when only hasOutOfRangeDates is true
|
|
252
|
+
// (prompt exists for aria-describedby but no visual cue needed until an out-of-range date is focused)
|
|
253
|
+
const promptClassName = showOutOfRangePrompt || showRulesPrompt ? 'm-t-1' : 'sr-only';
|
|
254
|
+
|
|
122
255
|
return (
|
|
123
256
|
// eslint-disable-next-line jsx-a11y/no-static-element-interactions
|
|
124
257
|
<div onKeyDown={handleKeyDown}>
|
|
125
258
|
{mode === 'day' && (
|
|
126
259
|
<DayCalendar
|
|
127
260
|
selectedDate={selectedDate}
|
|
261
|
+
focusedDate={focusedDate}
|
|
128
262
|
min={min}
|
|
129
263
|
max={max}
|
|
130
264
|
viewMonth={viewMonth}
|
|
131
265
|
viewYear={viewYear}
|
|
132
266
|
monthFormat={monthFormat}
|
|
267
|
+
disabledRules={disabledRules}
|
|
268
|
+
disabledReasonId={showPrompt ? disabledReasonId : undefined}
|
|
269
|
+
announceReasonOnFocus={!hasInteracted}
|
|
133
270
|
onSelect={handleSelectedDateUpdate}
|
|
271
|
+
onDayFocus={handleDayFocus}
|
|
134
272
|
onLabelClick={switchToYears}
|
|
135
273
|
onViewDateUpdate={handleViewDateUpdate}
|
|
136
274
|
/>
|
|
@@ -138,6 +276,7 @@ const Calendar = ({
|
|
|
138
276
|
{mode === 'month' && (
|
|
139
277
|
<MonthCalendar
|
|
140
278
|
selectedDate={selectedDate}
|
|
279
|
+
focusedDate={focusedDate}
|
|
141
280
|
min={min}
|
|
142
281
|
max={max}
|
|
143
282
|
viewYear={viewYear}
|
|
@@ -150,6 +289,7 @@ const Calendar = ({
|
|
|
150
289
|
{mode === 'year' && (
|
|
151
290
|
<YearCalendar
|
|
152
291
|
selectedDate={selectedDate}
|
|
292
|
+
focusedDate={focusedDate}
|
|
153
293
|
min={min}
|
|
154
294
|
max={max}
|
|
155
295
|
viewYear={viewYear}
|
|
@@ -158,6 +298,16 @@ const Calendar = ({
|
|
|
158
298
|
onViewDateUpdate={handleViewDateUpdate}
|
|
159
299
|
/>
|
|
160
300
|
)}
|
|
301
|
+
{showPrompt && (
|
|
302
|
+
<InlinePrompt
|
|
303
|
+
sentiment={promptSentiment}
|
|
304
|
+
width="full"
|
|
305
|
+
id={disabledReasonId}
|
|
306
|
+
className={promptClassName}
|
|
307
|
+
>
|
|
308
|
+
{promptText}
|
|
309
|
+
</InlinePrompt>
|
|
310
|
+
)}
|
|
161
311
|
</div>
|
|
162
312
|
);
|
|
163
313
|
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export interface DisabledDateRange {
|
|
2
|
+
start: Date;
|
|
3
|
+
end: Date;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export type WeekDayType = 'mon' | 'tue' | 'wed' | 'thu' | 'fri' | 'sat' | 'sun';
|
|
7
|
+
|
|
8
|
+
export interface DisabledRules {
|
|
9
|
+
/** Days of the week to disable (e.g. `['sat', 'sun']` for weekends). */
|
|
10
|
+
weekdays?: WeekDayType[];
|
|
11
|
+
/** Specific calendar dates to disable. */
|
|
12
|
+
dates?: Date[];
|
|
13
|
+
/** Inclusive date ranges to disable. */
|
|
14
|
+
ranges?: DisabledDateRange[];
|
|
15
|
+
/**
|
|
16
|
+
* Human-readable explanation shown in an InlinePrompt below the calendar grid when
|
|
17
|
+
* disabled dates exist in the current month. Required for accessibility: it is linked
|
|
18
|
+
* to disabled cells via `aria-describedby` so screen reader users understand why a
|
|
19
|
+
* date is unavailable. When an out-of-range date is focused, the built-in boundary
|
|
20
|
+
* message takes precedence over this text.
|
|
21
|
+
*/
|
|
22
|
+
reason: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface DateChangeMeta {
|
|
26
|
+
/** Whether the focused date is selectable (not disabled or out of range). */
|
|
27
|
+
isEnabled: boolean;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface OnDateSelectParams {
|
|
31
|
+
/** The date that was focused or clicked. */
|
|
32
|
+
date: Date;
|
|
33
|
+
/** Whether the date is selectable (not disabled or out of range). */
|
|
34
|
+
isEnabled: boolean;
|
|
35
|
+
}
|
|
@@ -15,6 +15,8 @@ interface DateHeaderProps {
|
|
|
15
15
|
onPreviousClick: () => void;
|
|
16
16
|
onNextClick: () => void;
|
|
17
17
|
dateMode: string;
|
|
18
|
+
isPreviousDisabled?: boolean;
|
|
19
|
+
isNextDisabled?: boolean;
|
|
18
20
|
}
|
|
19
21
|
|
|
20
22
|
const DateHeader: React.FC<DateHeaderProps> = ({
|
|
@@ -23,6 +25,8 @@ const DateHeader: React.FC<DateHeaderProps> = ({
|
|
|
23
25
|
onPreviousClick,
|
|
24
26
|
onNextClick,
|
|
25
27
|
dateMode,
|
|
28
|
+
isPreviousDisabled,
|
|
29
|
+
isNextDisabled,
|
|
26
30
|
}) => {
|
|
27
31
|
const intl = useIntl();
|
|
28
32
|
const { isMobile } = useLayout();
|
|
@@ -34,6 +38,7 @@ const DateHeader: React.FC<DateHeaderProps> = ({
|
|
|
34
38
|
priority="minimal"
|
|
35
39
|
className="d-inline-flex"
|
|
36
40
|
aria-label={`${intl.formatMessage(messages.previous)} ${dateMode}`}
|
|
41
|
+
disabled={isPreviousDisabled}
|
|
37
42
|
onClick={onPreviousClick}
|
|
38
43
|
>
|
|
39
44
|
<ChevronLeft className="left-single-direction" />
|
|
@@ -61,6 +66,7 @@ const DateHeader: React.FC<DateHeaderProps> = ({
|
|
|
61
66
|
className="d-inline-flex"
|
|
62
67
|
aria-label={`${intl.formatMessage(messages.next)} ${dateMode}`}
|
|
63
68
|
priority="minimal"
|
|
69
|
+
disabled={isNextDisabled}
|
|
64
70
|
onClick={onNextClick}
|
|
65
71
|
>
|
|
66
72
|
<ChevronRight className="right-single-direction" />
|
|
@@ -2,6 +2,7 @@ import { formatDate } from '@transferwise/formatting';
|
|
|
2
2
|
import { PureComponent } from 'react';
|
|
3
3
|
|
|
4
4
|
import { MonthFormat } from '../../common';
|
|
5
|
+
import type { DisabledRules } from '../Calendar.types';
|
|
5
6
|
import messages from '../Calendar.messages';
|
|
6
7
|
import DateHeader from '../DateHeader';
|
|
7
8
|
|
|
@@ -10,12 +11,17 @@ import { injectIntl, type WrappedComponentProps } from '../../common/injectIntl'
|
|
|
10
11
|
|
|
11
12
|
interface DayCalendarProps extends WrappedComponentProps {
|
|
12
13
|
selectedDate: Date | null;
|
|
14
|
+
focusedDate?: Date | null;
|
|
13
15
|
min: Date | null;
|
|
14
16
|
max: Date | null;
|
|
15
17
|
viewMonth: number;
|
|
16
18
|
viewYear: number;
|
|
17
19
|
monthFormat: `${MonthFormat}`;
|
|
20
|
+
disabledRules?: DisabledRules;
|
|
21
|
+
disabledReasonId?: string;
|
|
22
|
+
announceReasonOnFocus?: boolean;
|
|
18
23
|
onSelect: (date: Date) => void;
|
|
24
|
+
onDayFocus?: (date: Date) => void;
|
|
19
25
|
onLabelClick: () => void;
|
|
20
26
|
onViewDateUpdate: (date: { month: number; year: number }) => void;
|
|
21
27
|
}
|
|
@@ -40,15 +46,31 @@ class DayCalendar extends PureComponent<DayCalendarProps> {
|
|
|
40
46
|
render() {
|
|
41
47
|
const {
|
|
42
48
|
selectedDate,
|
|
49
|
+
focusedDate,
|
|
43
50
|
min,
|
|
44
51
|
max,
|
|
45
52
|
viewMonth,
|
|
46
53
|
viewYear,
|
|
47
54
|
intl: { locale, formatMessage },
|
|
48
55
|
monthFormat,
|
|
56
|
+
disabledRules,
|
|
57
|
+
disabledReasonId,
|
|
58
|
+
announceReasonOnFocus,
|
|
49
59
|
onLabelClick,
|
|
50
60
|
onSelect,
|
|
61
|
+
onDayFocus,
|
|
51
62
|
} = this.props;
|
|
63
|
+
const prevMonth = viewMonth <= 0 ? 11 : viewMonth - 1;
|
|
64
|
+
const prevYear = viewMonth <= 0 ? viewYear - 1 : viewYear;
|
|
65
|
+
const nextMonth = viewMonth >= 11 ? 0 : viewMonth + 1;
|
|
66
|
+
const nextYear = viewMonth >= 11 ? viewYear + 1 : viewYear;
|
|
67
|
+
const isPreviousDisabled = !!(
|
|
68
|
+
min && new Date(prevYear, prevMonth) < new Date(min.getFullYear(), min.getMonth())
|
|
69
|
+
);
|
|
70
|
+
const isNextDisabled = !!(
|
|
71
|
+
max && new Date(nextYear, nextMonth) > new Date(max.getFullYear(), max.getMonth())
|
|
72
|
+
);
|
|
73
|
+
|
|
52
74
|
return (
|
|
53
75
|
<>
|
|
54
76
|
<DateHeader
|
|
@@ -57,11 +79,27 @@ class DayCalendar extends PureComponent<DayCalendarProps> {
|
|
|
57
79
|
year: 'numeric',
|
|
58
80
|
})}
|
|
59
81
|
dateMode={formatMessage(messages.month)}
|
|
82
|
+
isPreviousDisabled={isPreviousDisabled}
|
|
83
|
+
isNextDisabled={isNextDisabled}
|
|
60
84
|
onLabelClick={onLabelClick}
|
|
61
85
|
onPreviousClick={this.selectPreviousMonth}
|
|
62
86
|
onNextClick={this.selectNextMonth}
|
|
63
87
|
/>
|
|
64
|
-
<DayCalendarTable
|
|
88
|
+
<DayCalendarTable
|
|
89
|
+
{...{
|
|
90
|
+
selectedDate,
|
|
91
|
+
focusedDate,
|
|
92
|
+
min,
|
|
93
|
+
max,
|
|
94
|
+
viewMonth,
|
|
95
|
+
viewYear,
|
|
96
|
+
disabledRules,
|
|
97
|
+
disabledReasonId,
|
|
98
|
+
announceReasonOnFocus,
|
|
99
|
+
onSelect,
|
|
100
|
+
onDayFocus,
|
|
101
|
+
}}
|
|
102
|
+
/>
|
|
65
103
|
</>
|
|
66
104
|
);
|
|
67
105
|
}
|
|
@@ -3,19 +3,27 @@ import { PureComponent } from 'react';
|
|
|
3
3
|
import { injectIntl, type WrappedComponentProps } from '../../../common/injectIntl';
|
|
4
4
|
|
|
5
5
|
import { getDayNames, isWithinRange } from '../../../common/DateUtils';
|
|
6
|
+
import { isDateDisabled } from '../../_helpers';
|
|
7
|
+
import { LONG_DAY_FORMAT } from '../../Calendar.constants';
|
|
8
|
+
import type { DisabledRules } from '../../Calendar.types';
|
|
6
9
|
import { getFocusableTime } from '../../GetFocusableTime/GetFocusableTime';
|
|
7
10
|
import { getStartOfDay } from '../../GetStartOfDay';
|
|
8
|
-
import TableLink from '../../
|
|
11
|
+
import TableLink from '../../TableLink';
|
|
9
12
|
|
|
10
13
|
const SHORT_DAY_FORMAT: Intl.DateTimeFormatOptions = { day: 'numeric' };
|
|
11
14
|
|
|
12
15
|
interface DayCalendarTableProps extends WrappedComponentProps {
|
|
13
16
|
selectedDate: Date | null;
|
|
17
|
+
focusedDate?: Date | null;
|
|
14
18
|
min: Date | null;
|
|
15
19
|
max: Date | null;
|
|
16
20
|
viewMonth: number;
|
|
17
21
|
viewYear: number;
|
|
22
|
+
disabledRules?: DisabledRules;
|
|
23
|
+
disabledReasonId?: string;
|
|
24
|
+
announceReasonOnFocus?: boolean;
|
|
18
25
|
onSelect: (date: Date) => void;
|
|
26
|
+
onDayFocus?: (date: Date) => void;
|
|
19
27
|
}
|
|
20
28
|
|
|
21
29
|
class DayCalendarTable extends PureComponent<DayCalendarTableProps> {
|
|
@@ -63,21 +71,41 @@ class DayCalendarTable extends PureComponent<DayCalendarTableProps> {
|
|
|
63
71
|
onSelect(new Date(viewYear, viewMonth, day));
|
|
64
72
|
};
|
|
65
73
|
|
|
66
|
-
|
|
74
|
+
focusDay = (day: number) => {
|
|
75
|
+
const { viewMonth, viewYear, onDayFocus } = this.props;
|
|
76
|
+
onDayFocus?.(new Date(viewYear, viewMonth, day));
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
isOutOfRange = (day: number) => {
|
|
67
80
|
if (day < 0) {
|
|
68
81
|
return true;
|
|
69
82
|
}
|
|
70
83
|
const { min, max, viewMonth, viewYear } = this.props;
|
|
71
84
|
const date = new Date(viewYear, viewMonth, day);
|
|
72
|
-
|
|
73
85
|
return !isWithinRange(date, min, max);
|
|
74
86
|
};
|
|
75
87
|
|
|
88
|
+
isDisabled = (day: number) => {
|
|
89
|
+
if (day < 0) {
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
const { viewMonth, viewYear, disabledRules } = this.props;
|
|
93
|
+
const date = new Date(viewYear, viewMonth, day);
|
|
94
|
+
return isDateDisabled(date, disabledRules);
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
isUnavailable = (day: number) => this.isOutOfRange(day) || this.isDisabled(day);
|
|
98
|
+
|
|
76
99
|
isActive = (day: number) => {
|
|
77
100
|
const { selectedDate, viewMonth, viewYear } = this.props;
|
|
78
101
|
return !!(selectedDate && Number(new Date(viewYear, viewMonth, day)) === Number(selectedDate));
|
|
79
102
|
};
|
|
80
103
|
|
|
104
|
+
isFocused = (day: number) => {
|
|
105
|
+
const { focusedDate, viewMonth, viewYear } = this.props;
|
|
106
|
+
return !!(focusedDate && Number(new Date(viewYear, viewMonth, day)) === Number(focusedDate));
|
|
107
|
+
};
|
|
108
|
+
|
|
81
109
|
isToday = (day: number) => {
|
|
82
110
|
const { viewMonth, viewYear } = this.props;
|
|
83
111
|
return Number(getStartOfDay(new Date())) === Number(new Date(viewYear, viewMonth, day));
|
|
@@ -86,15 +114,15 @@ class DayCalendarTable extends PureComponent<DayCalendarTableProps> {
|
|
|
86
114
|
getAutofocusDay = (weeks: number[][]) => {
|
|
87
115
|
const days = weeks.flatMap((week) => week);
|
|
88
116
|
return getFocusableTime({
|
|
89
|
-
isActive: this.
|
|
117
|
+
isActive: this.isFocused,
|
|
90
118
|
isNow: this.isToday,
|
|
91
|
-
isDisabled: this.
|
|
119
|
+
isDisabled: this.isUnavailable,
|
|
92
120
|
timeSpan: days,
|
|
93
121
|
});
|
|
94
122
|
};
|
|
95
123
|
|
|
96
124
|
render() {
|
|
97
|
-
const { viewMonth, viewYear, intl } = this.props;
|
|
125
|
+
const { viewMonth, viewYear, intl, disabledReasonId, announceReasonOnFocus } = this.props;
|
|
98
126
|
const weeks = this.getTableStructure();
|
|
99
127
|
const autoFocusDay = this.getAutofocusDay(weeks);
|
|
100
128
|
return (
|
|
@@ -124,27 +152,39 @@ class DayCalendarTable extends PureComponent<DayCalendarTableProps> {
|
|
|
124
152
|
{/* eslint-disable react/no-array-index-key */}
|
|
125
153
|
{weeks.map((week, weekIndex) => (
|
|
126
154
|
<tr key={weekIndex}>
|
|
127
|
-
{week.map((day, dayIndex) =>
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
155
|
+
{week.map((day, dayIndex) => {
|
|
156
|
+
const dayOutOfRange = this.isOutOfRange(day);
|
|
157
|
+
const dayDisabled = this.isDisabled(day);
|
|
158
|
+
return (
|
|
159
|
+
<td key={dayIndex} className={dayIndex > 4 ? 'weekend' : ''}>
|
|
160
|
+
{day !== -1 && (
|
|
161
|
+
<TableLink
|
|
162
|
+
item={day}
|
|
163
|
+
type="day"
|
|
164
|
+
title={formatDate(
|
|
165
|
+
new Date(viewYear, viewMonth, day),
|
|
166
|
+
intl.locale,
|
|
167
|
+
SHORT_DAY_FORMAT,
|
|
168
|
+
)}
|
|
169
|
+
autofocus={day === autoFocusDay}
|
|
170
|
+
longTitle={formatDate(
|
|
171
|
+
new Date(viewYear, viewMonth, day),
|
|
172
|
+
intl.locale,
|
|
173
|
+
LONG_DAY_FORMAT,
|
|
174
|
+
)}
|
|
175
|
+
active={this.isActive(day)}
|
|
176
|
+
outOfRange={dayOutOfRange}
|
|
177
|
+
disabled={dayDisabled}
|
|
178
|
+
today={this.isToday(day)}
|
|
179
|
+
disabledReasonId={disabledReasonId}
|
|
180
|
+
announceReasonOnFocus={announceReasonOnFocus}
|
|
181
|
+
onClick={this.selectDay}
|
|
182
|
+
onFocus={this.focusDay}
|
|
183
|
+
/>
|
|
184
|
+
)}
|
|
185
|
+
</td>
|
|
186
|
+
);
|
|
187
|
+
})}
|
|
148
188
|
</tr>
|
|
149
189
|
))}
|
|
150
190
|
{/* eslint-enable react/no-array-index-key */}
|
|
@@ -9,6 +9,7 @@ import MonthCalendarTable from './Table';
|
|
|
9
9
|
|
|
10
10
|
interface MonthCalendarProps extends WrappedComponentProps {
|
|
11
11
|
selectedDate: Date | null;
|
|
12
|
+
focusedDate?: Date | null;
|
|
12
13
|
min: Date | null;
|
|
13
14
|
max: Date | null;
|
|
14
15
|
viewYear: number;
|
|
@@ -35,6 +36,7 @@ class MonthCalendar extends PureComponent<MonthCalendarProps> {
|
|
|
35
36
|
render() {
|
|
36
37
|
const {
|
|
37
38
|
selectedDate,
|
|
39
|
+
focusedDate,
|
|
38
40
|
min,
|
|
39
41
|
max,
|
|
40
42
|
viewYear,
|
|
@@ -42,17 +44,23 @@ class MonthCalendar extends PureComponent<MonthCalendarProps> {
|
|
|
42
44
|
placeholder,
|
|
43
45
|
onLabelClick,
|
|
44
46
|
} = this.props;
|
|
47
|
+
const isPreviousDisabled = !!(min && viewYear - 1 < min.getFullYear());
|
|
48
|
+
const isNextDisabled = !!(max && viewYear + 1 > max.getFullYear());
|
|
49
|
+
|
|
45
50
|
return (
|
|
46
51
|
<>
|
|
47
52
|
<DateHeader
|
|
48
53
|
dateMode={formatMessage(messages.year)}
|
|
49
54
|
label={formatDate(new Date(viewYear, 0), locale, { year: 'numeric' })}
|
|
55
|
+
isPreviousDisabled={isPreviousDisabled}
|
|
56
|
+
isNextDisabled={isNextDisabled}
|
|
50
57
|
onLabelClick={onLabelClick}
|
|
51
58
|
onPreviousClick={this.selectPreviousYear}
|
|
52
59
|
onNextClick={this.selectNextYear}
|
|
53
60
|
/>
|
|
54
61
|
<MonthCalendarTable
|
|
55
62
|
selectedDate={selectedDate}
|
|
63
|
+
focusedDate={focusedDate}
|
|
56
64
|
min={min}
|
|
57
65
|
max={max}
|
|
58
66
|
viewYear={viewYear}
|