@transferwise/components 0.0.0-experimental-15273df → 0.0.0-experimental-d6d5b6e

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.
Files changed (155) hide show
  1. package/build/Calendar/Calendar.js +94 -16
  2. package/build/Calendar/Calendar.js.map +1 -1
  3. package/build/Calendar/Calendar.messages.js +12 -0
  4. package/build/Calendar/Calendar.messages.js.map +1 -1
  5. package/build/Calendar/Calendar.messages.mjs +12 -0
  6. package/build/Calendar/Calendar.messages.mjs.map +1 -1
  7. package/build/Calendar/Calendar.mjs +95 -17
  8. package/build/Calendar/Calendar.mjs.map +1 -1
  9. package/build/Calendar/DateHeader/DateHeader.js +5 -1
  10. package/build/Calendar/DateHeader/DateHeader.js.map +1 -1
  11. package/build/Calendar/DateHeader/DateHeader.mjs +5 -1
  12. package/build/Calendar/DateHeader/DateHeader.mjs.map +1 -1
  13. package/build/Calendar/DayCalendar/DayCalendar.js +14 -0
  14. package/build/Calendar/DayCalendar/DayCalendar.js.map +1 -1
  15. package/build/Calendar/DayCalendar/DayCalendar.mjs +14 -0
  16. package/build/Calendar/DayCalendar/DayCalendar.mjs.map +1 -1
  17. package/build/Calendar/DayCalendar/Table/DayCalendarTable.js +53 -19
  18. package/build/Calendar/DayCalendar/Table/DayCalendarTable.js.map +1 -1
  19. package/build/Calendar/DayCalendar/Table/DayCalendarTable.mjs +53 -19
  20. package/build/Calendar/DayCalendar/Table/DayCalendarTable.mjs.map +1 -1
  21. package/build/Calendar/MonthCalendar/MonthCalendar.js +6 -0
  22. package/build/Calendar/MonthCalendar/MonthCalendar.js.map +1 -1
  23. package/build/Calendar/MonthCalendar/MonthCalendar.mjs +6 -0
  24. package/build/Calendar/MonthCalendar/MonthCalendar.mjs.map +1 -1
  25. package/build/Calendar/MonthCalendar/Table/MonthCalendarTable.js +8 -3
  26. package/build/Calendar/MonthCalendar/Table/MonthCalendarTable.js.map +1 -1
  27. package/build/Calendar/MonthCalendar/Table/MonthCalendarTable.mjs +8 -3
  28. package/build/Calendar/MonthCalendar/Table/MonthCalendarTable.mjs.map +1 -1
  29. package/build/Calendar/{tableLink → TableLink}/TableLink.js +26 -11
  30. package/build/Calendar/TableLink/TableLink.js.map +1 -0
  31. package/build/Calendar/{tableLink → TableLink}/TableLink.mjs +26 -11
  32. package/build/Calendar/TableLink/TableLink.mjs.map +1 -0
  33. package/build/Calendar/YearCalendar/Table/YearCalendarTable.js +8 -3
  34. package/build/Calendar/YearCalendar/Table/YearCalendarTable.js.map +1 -1
  35. package/build/Calendar/YearCalendar/Table/YearCalendarTable.mjs +8 -3
  36. package/build/Calendar/YearCalendar/Table/YearCalendarTable.mjs.map +1 -1
  37. package/build/Calendar/YearCalendar/YearCalendar.js +7 -0
  38. package/build/Calendar/YearCalendar/YearCalendar.js.map +1 -1
  39. package/build/Calendar/YearCalendar/YearCalendar.mjs +7 -0
  40. package/build/Calendar/YearCalendar/YearCalendar.mjs.map +1 -1
  41. package/build/DateLookup/DateLookup.js +19 -9
  42. package/build/DateLookup/DateLookup.js.map +1 -1
  43. package/build/DateLookup/DateLookup.mjs +19 -9
  44. package/build/DateLookup/DateLookup.mjs.map +1 -1
  45. package/build/Sticky/Sticky.js +0 -1
  46. package/build/Sticky/Sticky.js.map +1 -1
  47. package/build/Sticky/Sticky.mjs +0 -1
  48. package/build/Sticky/Sticky.mjs.map +1 -1
  49. package/build/common/DateUtils/hasDisabledDatesInMonth/hasDisabledDatesInMonth.js +33 -0
  50. package/build/common/DateUtils/hasDisabledDatesInMonth/hasDisabledDatesInMonth.js.map +1 -0
  51. package/build/common/DateUtils/hasDisabledDatesInMonth/hasDisabledDatesInMonth.mjs +31 -0
  52. package/build/common/DateUtils/hasDisabledDatesInMonth/hasDisabledDatesInMonth.mjs.map +1 -0
  53. package/build/common/DateUtils/isDateDisabled/isDateDisabled.js +40 -0
  54. package/build/common/DateUtils/isDateDisabled/isDateDisabled.js.map +1 -0
  55. package/build/common/DateUtils/isDateDisabled/isDateDisabled.mjs +38 -0
  56. package/build/common/DateUtils/isDateDisabled/isDateDisabled.mjs.map +1 -0
  57. package/build/i18n/en.json +4 -0
  58. package/build/i18n/en.json.js +4 -0
  59. package/build/i18n/en.json.js.map +1 -1
  60. package/build/i18n/en.json.mjs +4 -0
  61. package/build/i18n/en.json.mjs.map +1 -1
  62. package/build/index.js +2 -2
  63. package/build/index.mjs +1 -1
  64. package/build/main.css +123 -48
  65. package/build/styles/Calendar/Calendar.css +112 -37
  66. package/build/styles/Calendar/TableLink/TableLink.css +115 -0
  67. package/build/styles/main.css +123 -48
  68. package/build/types/Calendar/Calendar.d.ts +16 -3
  69. package/build/types/Calendar/Calendar.d.ts.map +1 -1
  70. package/build/types/Calendar/Calendar.messages.d.ts +20 -0
  71. package/build/types/Calendar/Calendar.messages.d.ts.map +1 -1
  72. package/build/types/Calendar/Calendar.types.d.ts +32 -0
  73. package/build/types/Calendar/Calendar.types.d.ts.map +1 -0
  74. package/build/types/Calendar/DateHeader/DateHeader.d.ts +2 -0
  75. package/build/types/Calendar/DateHeader/DateHeader.d.ts.map +1 -1
  76. package/build/types/Calendar/DayCalendar/DayCalendar.d.ts +5 -1
  77. package/build/types/Calendar/DayCalendar/DayCalendar.d.ts.map +1 -1
  78. package/build/types/Calendar/DayCalendar/Table/DayCalendarTable.d.ts +4 -0
  79. package/build/types/Calendar/DayCalendar/Table/DayCalendarTable.d.ts.map +1 -1
  80. package/build/types/Calendar/MonthCalendar/MonthCalendar.d.ts +1 -0
  81. package/build/types/Calendar/MonthCalendar/MonthCalendar.d.ts.map +1 -1
  82. package/build/types/Calendar/MonthCalendar/Table/MonthCalendarTable.d.ts +2 -1
  83. package/build/types/Calendar/MonthCalendar/Table/MonthCalendarTable.d.ts.map +1 -1
  84. package/build/types/Calendar/{tableLink → TableLink}/TableLink.d.ts +3 -1
  85. package/build/types/Calendar/TableLink/TableLink.d.ts.map +1 -0
  86. package/build/types/Calendar/TableLink/index.d.ts.map +1 -0
  87. package/build/types/Calendar/YearCalendar/Table/YearCalendarTable.d.ts +2 -1
  88. package/build/types/Calendar/YearCalendar/Table/YearCalendarTable.d.ts.map +1 -1
  89. package/build/types/Calendar/YearCalendar/YearCalendar.d.ts +1 -0
  90. package/build/types/Calendar/YearCalendar/YearCalendar.d.ts.map +1 -1
  91. package/build/types/Calendar/index.d.ts +1 -0
  92. package/build/types/Calendar/index.d.ts.map +1 -1
  93. package/build/types/DateLookup/DateLookup.d.ts +23 -4
  94. package/build/types/DateLookup/DateLookup.d.ts.map +1 -1
  95. package/build/types/DateLookup/index.d.ts +1 -0
  96. package/build/types/DateLookup/index.d.ts.map +1 -1
  97. package/build/types/Sticky/Sticky.d.ts.map +1 -1
  98. package/build/types/common/DateUtils/hasDisabledDatesInMonth/hasDisabledDatesInMonth.d.ts +3 -0
  99. package/build/types/common/DateUtils/hasDisabledDatesInMonth/hasDisabledDatesInMonth.d.ts.map +1 -0
  100. package/build/types/common/DateUtils/index.d.ts +2 -0
  101. package/build/types/common/DateUtils/index.d.ts.map +1 -1
  102. package/build/types/common/DateUtils/isDateDisabled/isDateDisabled.d.ts +3 -0
  103. package/build/types/common/DateUtils/isDateDisabled/isDateDisabled.d.ts.map +1 -0
  104. package/build/types/index.d.ts +3 -2
  105. package/build/types/index.d.ts.map +1 -1
  106. package/build/types/test-utils/index.d.ts +8 -0
  107. package/build/types/test-utils/index.d.ts.map +1 -1
  108. package/package.json +1 -1
  109. package/src/Calendar/Calendar.css +112 -37
  110. package/src/Calendar/Calendar.disabledDates.test.tsx +518 -0
  111. package/src/Calendar/Calendar.less +5 -35
  112. package/src/Calendar/Calendar.messages.ts +21 -0
  113. package/src/Calendar/Calendar.test.tsx +217 -28
  114. package/src/Calendar/Calendar.tsx +136 -19
  115. package/src/Calendar/Calendar.types.ts +35 -0
  116. package/src/Calendar/DateHeader/DateHeader.tsx +6 -0
  117. package/src/Calendar/DayCalendar/DayCalendar.tsx +33 -1
  118. package/src/Calendar/DayCalendar/Table/DayCalendarTable.tsx +62 -28
  119. package/src/Calendar/MonthCalendar/MonthCalendar.tsx +8 -0
  120. package/src/Calendar/MonthCalendar/Table/MonthCalendarTable.tsx +15 -3
  121. package/src/Calendar/TableLink/TableLink.css +115 -0
  122. package/src/Calendar/TableLink/TableLink.less +68 -0
  123. package/src/Calendar/{tableLink → TableLink}/TableLink.tsx +29 -14
  124. package/src/Calendar/YearCalendar/Table/YearCalendarTable.tsx +15 -3
  125. package/src/Calendar/YearCalendar/YearCalendar.tsx +9 -0
  126. package/src/Calendar/_stories/Calendar.accessibility.docs.mdx +118 -0
  127. package/src/Calendar/_stories/Calendar.story.tsx +138 -4
  128. package/src/Calendar/index.ts +7 -0
  129. package/src/DateLookup/DateLookup.disabledDates.test.tsx +293 -0
  130. package/src/DateLookup/DateLookup.test.tsx +34 -37
  131. package/src/DateLookup/DateLookup.tsx +36 -13
  132. package/src/DateLookup/_stories/DateLookup.accessibility.docs.mdx +108 -0
  133. package/src/DateLookup/_stories/DateLookup.story.tsx +124 -2
  134. package/src/DateLookup/index.ts +6 -0
  135. package/src/Sticky/Sticky.test.tsx +0 -1
  136. package/src/Sticky/Sticky.tsx +0 -1
  137. package/src/common/DateUtils/hasDisabledDatesInMonth/hasDisabledDatesInMonth.test.ts +98 -0
  138. package/src/common/DateUtils/hasDisabledDatesInMonth/hasDisabledDatesInMonth.ts +45 -0
  139. package/src/common/DateUtils/index.ts +2 -0
  140. package/src/common/DateUtils/isDateDisabled/isDateDisabled.test.ts +159 -0
  141. package/src/common/DateUtils/isDateDisabled/isDateDisabled.ts +59 -0
  142. package/src/i18n/en.json +4 -0
  143. package/src/index.ts +3 -2
  144. package/src/main.css +123 -48
  145. package/src/main.less +0 -1
  146. package/build/Calendar/tableLink/TableLink.js.map +0 -1
  147. package/build/Calendar/tableLink/TableLink.mjs.map +0 -1
  148. package/build/styles/Sticky/Sticky.css +0 -4
  149. package/build/types/Calendar/tableLink/TableLink.d.ts.map +0 -1
  150. package/build/types/Calendar/tableLink/index.d.ts.map +0 -1
  151. package/src/Sticky/Sticky.css +0 -4
  152. package/src/Sticky/Sticky.less +0 -6
  153. package/src/Sticky/_stories/Sticky.test.story.tsx +0 -177
  154. /package/build/types/Calendar/{tableLink → TableLink}/index.d.ts +0 -0
  155. /package/src/Calendar/{tableLink → TableLink}/index.ts +0 -0
@@ -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,11 +11,14 @@ 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;
18
22
  onSelect: (date: Date) => void;
19
23
  onLabelClick: () => void;
20
24
  onViewDateUpdate: (date: { month: number; year: number }) => void;
@@ -40,15 +44,29 @@ class DayCalendar extends PureComponent<DayCalendarProps> {
40
44
  render() {
41
45
  const {
42
46
  selectedDate,
47
+ focusedDate,
43
48
  min,
44
49
  max,
45
50
  viewMonth,
46
51
  viewYear,
47
52
  intl: { locale, formatMessage },
48
53
  monthFormat,
54
+ disabledRules,
55
+ disabledReasonId,
49
56
  onLabelClick,
50
57
  onSelect,
51
58
  } = this.props;
59
+ const prevMonth = viewMonth <= 0 ? 11 : viewMonth - 1;
60
+ const prevYear = viewMonth <= 0 ? viewYear - 1 : viewYear;
61
+ const nextMonth = viewMonth >= 11 ? 0 : viewMonth + 1;
62
+ const nextYear = viewMonth >= 11 ? viewYear + 1 : viewYear;
63
+ const isPreviousDisabled = !!(
64
+ min && new Date(prevYear, prevMonth) < new Date(min.getFullYear(), min.getMonth())
65
+ );
66
+ const isNextDisabled = !!(
67
+ max && new Date(nextYear, nextMonth) > new Date(max.getFullYear(), max.getMonth())
68
+ );
69
+
52
70
  return (
53
71
  <>
54
72
  <DateHeader
@@ -57,11 +75,25 @@ class DayCalendar extends PureComponent<DayCalendarProps> {
57
75
  year: 'numeric',
58
76
  })}
59
77
  dateMode={formatMessage(messages.month)}
78
+ isPreviousDisabled={isPreviousDisabled}
79
+ isNextDisabled={isNextDisabled}
60
80
  onLabelClick={onLabelClick}
61
81
  onPreviousClick={this.selectPreviousMonth}
62
82
  onNextClick={this.selectNextMonth}
63
83
  />
64
- <DayCalendarTable {...{ selectedDate, min, max, viewMonth, viewYear, onSelect }} />
84
+ <DayCalendarTable
85
+ {...{
86
+ selectedDate,
87
+ focusedDate,
88
+ min,
89
+ max,
90
+ viewMonth,
91
+ viewYear,
92
+ disabledRules,
93
+ disabledReasonId,
94
+ onSelect,
95
+ }}
96
+ />
65
97
  </>
66
98
  );
67
99
  }
@@ -2,19 +2,28 @@ import { formatDate } from '@transferwise/formatting';
2
2
  import { PureComponent } from 'react';
3
3
  import { injectIntl, type WrappedComponentProps } from '../../../common/injectIntl';
4
4
 
5
- import { getDayNames, isWithinRange } from '../../../common/DateUtils';
5
+ import { getDayNames, isWithinRange, isDateDisabled } from '../../../common/DateUtils';
6
+ import type { DisabledRules } from '../../Calendar.types';
6
7
  import { getFocusableTime } from '../../GetFocusableTime/GetFocusableTime';
7
8
  import { getStartOfDay } from '../../GetStartOfDay';
8
- import TableLink from '../../tableLink';
9
+ import TableLink from '../../TableLink';
9
10
 
10
11
  const SHORT_DAY_FORMAT: Intl.DateTimeFormatOptions = { day: 'numeric' };
12
+ const LONG_DAY_FORMAT: Intl.DateTimeFormatOptions = {
13
+ day: 'numeric',
14
+ month: 'long',
15
+ year: 'numeric',
16
+ };
11
17
 
12
18
  interface DayCalendarTableProps extends WrappedComponentProps {
13
19
  selectedDate: Date | null;
20
+ focusedDate?: Date | null;
14
21
  min: Date | null;
15
22
  max: Date | null;
16
23
  viewMonth: number;
17
24
  viewYear: number;
25
+ disabledRules?: DisabledRules;
26
+ disabledReasonId?: string;
18
27
  onSelect: (date: Date) => void;
19
28
  }
20
29
 
@@ -63,21 +72,36 @@ class DayCalendarTable extends PureComponent<DayCalendarTableProps> {
63
72
  onSelect(new Date(viewYear, viewMonth, day));
64
73
  };
65
74
 
66
- isDisabled = (day: number) => {
75
+ isOutOfRange = (day: number) => {
67
76
  if (day < 0) {
68
77
  return true;
69
78
  }
70
79
  const { min, max, viewMonth, viewYear } = this.props;
71
80
  const date = new Date(viewYear, viewMonth, day);
72
-
73
81
  return !isWithinRange(date, min, max);
74
82
  };
75
83
 
84
+ isDisabled = (day: number) => {
85
+ if (day < 0) {
86
+ return false;
87
+ }
88
+ const { viewMonth, viewYear, disabledRules } = this.props;
89
+ const date = new Date(viewYear, viewMonth, day);
90
+ return isDateDisabled(date, disabledRules);
91
+ };
92
+
93
+ isUnavailable = (day: number) => this.isOutOfRange(day) || this.isDisabled(day);
94
+
76
95
  isActive = (day: number) => {
77
96
  const { selectedDate, viewMonth, viewYear } = this.props;
78
97
  return !!(selectedDate && Number(new Date(viewYear, viewMonth, day)) === Number(selectedDate));
79
98
  };
80
99
 
100
+ isFocused = (day: number) => {
101
+ const { focusedDate, viewMonth, viewYear } = this.props;
102
+ return !!(focusedDate && Number(new Date(viewYear, viewMonth, day)) === Number(focusedDate));
103
+ };
104
+
81
105
  isToday = (day: number) => {
82
106
  const { viewMonth, viewYear } = this.props;
83
107
  return Number(getStartOfDay(new Date())) === Number(new Date(viewYear, viewMonth, day));
@@ -86,15 +110,15 @@ class DayCalendarTable extends PureComponent<DayCalendarTableProps> {
86
110
  getAutofocusDay = (weeks: number[][]) => {
87
111
  const days = weeks.flatMap((week) => week);
88
112
  return getFocusableTime({
89
- isActive: this.isActive,
113
+ isActive: this.isFocused,
90
114
  isNow: this.isToday,
91
- isDisabled: this.isDisabled,
115
+ isDisabled: this.isUnavailable,
92
116
  timeSpan: days,
93
117
  });
94
118
  };
95
119
 
96
120
  render() {
97
- const { viewMonth, viewYear, intl } = this.props;
121
+ const { viewMonth, viewYear, intl, disabledReasonId } = this.props;
98
122
  const weeks = this.getTableStructure();
99
123
  const autoFocusDay = this.getAutofocusDay(weeks);
100
124
  return (
@@ -124,27 +148,37 @@ class DayCalendarTable extends PureComponent<DayCalendarTableProps> {
124
148
  {/* eslint-disable react/no-array-index-key */}
125
149
  {weeks.map((week, weekIndex) => (
126
150
  <tr key={weekIndex}>
127
- {week.map((day, dayIndex) => (
128
- <td key={dayIndex} className={dayIndex > 4 ? 'weekend' : ''}>
129
- {day !== -1 && (
130
- <TableLink
131
- item={day}
132
- type="day"
133
- title={formatDate(
134
- new Date(viewYear, viewMonth, day),
135
- intl.locale,
136
- SHORT_DAY_FORMAT,
137
- )}
138
- autofocus={day === autoFocusDay}
139
- longTitle={formatDate(new Date(viewYear, viewMonth, day), intl.locale)}
140
- active={this.isActive(day)}
141
- disabled={this.isDisabled(day)}
142
- today={this.isToday(day)}
143
- onClick={this.selectDay}
144
- />
145
- )}
146
- </td>
147
- ))}
151
+ {week.map((day, dayIndex) => {
152
+ const dayOutOfRange = this.isOutOfRange(day);
153
+ const dayDisabled = this.isDisabled(day);
154
+ return (
155
+ <td key={dayIndex} className={dayIndex > 4 ? 'weekend' : ''}>
156
+ {day !== -1 && (
157
+ <TableLink
158
+ item={day}
159
+ type="day"
160
+ title={formatDate(
161
+ new Date(viewYear, viewMonth, day),
162
+ intl.locale,
163
+ SHORT_DAY_FORMAT,
164
+ )}
165
+ autofocus={day === autoFocusDay}
166
+ longTitle={formatDate(
167
+ new Date(viewYear, viewMonth, day),
168
+ intl.locale,
169
+ LONG_DAY_FORMAT,
170
+ )}
171
+ active={this.isActive(day)}
172
+ outOfRange={dayOutOfRange}
173
+ disabled={dayDisabled}
174
+ today={this.isToday(day)}
175
+ disabledReasonId={disabledReasonId}
176
+ onClick={this.selectDay}
177
+ />
178
+ )}
179
+ </td>
180
+ );
181
+ })}
148
182
  </tr>
149
183
  ))}
150
184
  {/* 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}
@@ -2,10 +2,11 @@ import { formatDate } from '@transferwise/formatting';
2
2
  import { useIntl } from 'react-intl';
3
3
 
4
4
  import { getFocusableTime } from '../../GetFocusableTime/GetFocusableTime';
5
- import TableLink from '../../tableLink';
5
+ import TableLink from '../../TableLink';
6
6
 
7
7
  interface MonthCalendarTableProps {
8
8
  selectedDate: Date | null;
9
+ focusedDate?: Date | null;
9
10
  min: Date | null;
10
11
  max: Date | null;
11
12
  viewYear: number;
@@ -19,6 +20,7 @@ const MONTH_ONLY_FORMAT: Intl.DateTimeFormatOptions = { month: 'short' };
19
20
 
20
21
  const MonthCalendarTable = ({
21
22
  selectedDate,
23
+ focusedDate,
22
24
  min,
23
25
  max,
24
26
  viewYear,
@@ -34,7 +36,8 @@ const MonthCalendarTable = ({
34
36
  type="month"
35
37
  title={formatDate(new Date(viewYear, month), locale, MONTH_ONLY_FORMAT)}
36
38
  active={isActive(month)}
37
- disabled={isDisabled(month)}
39
+ outOfRange={isDisabled(month)}
40
+ disabled={false}
38
41
  today={viewYear === new Date().getFullYear() && month === new Date().getMonth()}
39
42
  autofocus={autofocusMonth === month}
40
43
  onClick={onSelect}
@@ -46,6 +49,10 @@ const MonthCalendarTable = ({
46
49
  return !!(month === selectedDate?.getMonth() && viewYear === selectedDate?.getFullYear());
47
50
  };
48
51
 
52
+ const isFocused = (month: number) => {
53
+ return !!(month === focusedDate?.getMonth() && viewYear === focusedDate?.getFullYear());
54
+ };
55
+
49
56
  const isThisMonth = (month: number) => {
50
57
  return viewYear === new Date().getFullYear() && month === new Date().getMonth();
51
58
  };
@@ -60,7 +67,12 @@ const MonthCalendarTable = ({
60
67
 
61
68
  const autofocusMonth = (() => {
62
69
  const months = Array.from({ length: ROWS * COLS }, (_, index) => index);
63
- return getFocusableTime({ isActive, isNow: isThisMonth, isDisabled, timeSpan: months });
70
+ return getFocusableTime({
71
+ isActive: isFocused,
72
+ isNow: isThisMonth,
73
+ isDisabled,
74
+ timeSpan: months,
75
+ });
64
76
  })();
65
77
 
66
78
  return (
@@ -0,0 +1,115 @@
1
+ .wds-Calendar__option--day,
2
+ .wds-Calendar__option--month,
3
+ .wds-Calendar__option--year {
4
+ width: 100%;
5
+ padding: 4px 0;
6
+ padding: var(--size-4) 0;
7
+ border: transparent;
8
+ border-radius: 10px;
9
+ border-radius: var(--radius-small);
10
+ background-color: transparent;
11
+ font-weight: 600;
12
+ font-weight: var(--font-weight-semi-bold);
13
+ color: #37517e;
14
+ color: var(--color-content-primary);
15
+ }
16
+ @media (max-width: 320px) {
17
+ .wds-Calendar__option--day,
18
+ .wds-Calendar__option--month,
19
+ .wds-Calendar__option--year {
20
+ padding: 0;
21
+ }
22
+ }
23
+ .wds-Calendar__option--day:focus,
24
+ .wds-Calendar__option--month:focus,
25
+ .wds-Calendar__option--year:focus {
26
+ background-color: var(--color-background-screen-hover);
27
+ }
28
+ .wds-Calendar__option--day.disabled,
29
+ .wds-Calendar__option--month.disabled,
30
+ .wds-Calendar__option--year.disabled,
31
+ .wds-Calendar__option--day[aria-disabled="true"],
32
+ .wds-Calendar__option--month[aria-disabled="true"],
33
+ .wds-Calendar__option--year[aria-disabled="true"] {
34
+ --ring-outline-color: var(--color-sentiment-content-primary);
35
+ color: #768e9c;
36
+ color: var(--color-content-tertiary);
37
+ cursor: not-allowed;
38
+ }
39
+ .wds-Calendar__option--day.disabled:hover,
40
+ .wds-Calendar__option--month.disabled:hover,
41
+ .wds-Calendar__option--year.disabled:hover,
42
+ .wds-Calendar__option--day[aria-disabled="true"]:hover,
43
+ .wds-Calendar__option--month[aria-disabled="true"]:hover,
44
+ .wds-Calendar__option--year[aria-disabled="true"]:hover,
45
+ .wds-Calendar__option--day.disabled:focus,
46
+ .wds-Calendar__option--month.disabled:focus,
47
+ .wds-Calendar__option--year.disabled:focus,
48
+ .wds-Calendar__option--day[aria-disabled="true"]:focus,
49
+ .wds-Calendar__option--month[aria-disabled="true"]:focus,
50
+ .wds-Calendar__option--year[aria-disabled="true"]:focus {
51
+ filter: initial;
52
+ opacity: 1;
53
+ opacity: initial;
54
+ background-color: var(--color-sentiment-background-surface);
55
+ color: var(--color-sentiment-content-primary);
56
+ }
57
+ .wds-Calendar__option--day.disabled.active,
58
+ .wds-Calendar__option--month.disabled.active,
59
+ .wds-Calendar__option--year.disabled.active,
60
+ .wds-Calendar__option--day[aria-disabled="true"].active,
61
+ .wds-Calendar__option--month[aria-disabled="true"].active,
62
+ .wds-Calendar__option--year[aria-disabled="true"].active,
63
+ .wds-Calendar__option--day.disabled.active:hover,
64
+ .wds-Calendar__option--month.disabled.active:hover,
65
+ .wds-Calendar__option--year.disabled.active:hover,
66
+ .wds-Calendar__option--day[aria-disabled="true"].active:hover,
67
+ .wds-Calendar__option--month[aria-disabled="true"].active:hover,
68
+ .wds-Calendar__option--year[aria-disabled="true"].active:hover,
69
+ .wds-Calendar__option--day.disabled.active:active,
70
+ .wds-Calendar__option--month.disabled.active:active,
71
+ .wds-Calendar__option--year.disabled.active:active,
72
+ .wds-Calendar__option--day[aria-disabled="true"].active:active,
73
+ .wds-Calendar__option--month[aria-disabled="true"].active:active,
74
+ .wds-Calendar__option--year[aria-disabled="true"].active:active,
75
+ .wds-Calendar__option--day.disabled.active:focus,
76
+ .wds-Calendar__option--month.disabled.active:focus,
77
+ .wds-Calendar__option--year.disabled.active:focus,
78
+ .wds-Calendar__option--day[aria-disabled="true"].active:focus,
79
+ .wds-Calendar__option--month[aria-disabled="true"].active:focus,
80
+ .wds-Calendar__option--year[aria-disabled="true"].active:focus {
81
+ filter: initial;
82
+ opacity: 1;
83
+ opacity: initial;
84
+ background-color: var(--color-sentiment-interactive-primary);
85
+ color: var(--color-sentiment-interactive-secondary);
86
+ }
87
+ .wds-Calendar__option--day.disabled.today,
88
+ .wds-Calendar__option--month.disabled.today,
89
+ .wds-Calendar__option--year.disabled.today,
90
+ .wds-Calendar__option--day[aria-disabled="true"].today,
91
+ .wds-Calendar__option--month[aria-disabled="true"].today,
92
+ .wds-Calendar__option--year[aria-disabled="true"].today {
93
+ text-decoration-color: #768e9c;
94
+ text-decoration-color: var(--color-content-tertiary);
95
+ }
96
+ .wds-Calendar__option--day {
97
+ height: 40px;
98
+ height: var(--size-40);
99
+ border-radius: 9999px;
100
+ border-radius: var(--radius-full);
101
+ line-height: 40px;
102
+ line-height: var(--size-40);
103
+ width: 40px;
104
+ width: var(--size-40);
105
+ padding: 0;
106
+ display: inline-flex;
107
+ align-items: center;
108
+ justify-content: center;
109
+ }
110
+ .wds-Calendar__option--day.today {
111
+ -webkit-text-decoration: underline;
112
+ text-decoration: underline;
113
+ text-decoration-thickness: 2px;
114
+ text-underline-offset: 4px;
115
+ }
@@ -0,0 +1,68 @@
1
+ @import (reference) "./../../styles/variables/neptune-tokens.less";
2
+
3
+ .wds-Calendar__option--day,
4
+ .wds-Calendar__option--month,
5
+ .wds-Calendar__option--year {
6
+ width: 100%;
7
+ padding: var(--size-4) 0;
8
+ @media (--screen-400-zoom) {
9
+ padding: 0;
10
+ }
11
+ border: transparent;
12
+ border-radius: var(--radius-small);
13
+ background-color: transparent;
14
+ font-weight: var(--font-weight-semi-bold);
15
+ color: var(--color-content-primary);
16
+
17
+ &:focus {
18
+ background-color: var(--color-background-screen-hover);
19
+ }
20
+
21
+ &.disabled,
22
+ &[aria-disabled="true"] {
23
+ --ring-outline-color: var(--color-sentiment-content-primary);
24
+ color: var(--color-content-tertiary);
25
+ cursor: not-allowed;
26
+
27
+ &:hover,
28
+ &:focus {
29
+ filter: unset;
30
+ opacity: unset;
31
+ background-color: var(--color-sentiment-background-surface);
32
+ color: var(--color-sentiment-content-primary);
33
+ }
34
+
35
+ &.active {
36
+ &,
37
+ &:hover,
38
+ &:active,
39
+ &:focus {
40
+ filter: unset;
41
+ opacity: unset;
42
+ background-color: var(--color-sentiment-interactive-primary);
43
+ color: var(--color-sentiment-interactive-secondary);
44
+ }
45
+ }
46
+
47
+ &.today {
48
+ text-decoration-color: var(--color-content-tertiary);
49
+ }
50
+ }
51
+ }
52
+
53
+ .wds-Calendar__option--day {
54
+ height: var(--size-40);
55
+ border-radius: var(--radius-full);
56
+ line-height: var(--size-40);
57
+ width: var(--size-40);
58
+ padding: 0;
59
+ display: inline-flex;
60
+ align-items: center;
61
+ justify-content: center;
62
+
63
+ &.today {
64
+ text-decoration: underline;
65
+ text-decoration-thickness: 2px;
66
+ text-underline-offset: 4px;
67
+ }
68
+ }
@@ -3,6 +3,7 @@ import { useEffect, useRef } from 'react';
3
3
  import { useIntl } from 'react-intl';
4
4
 
5
5
  import messages from '../Calendar.messages';
6
+ import SentimentSurface from '../../SentimentSurface';
6
7
 
7
8
  interface TableLinkProps {
8
9
  item: number;
@@ -10,9 +11,11 @@ interface TableLinkProps {
10
11
  title?: string;
11
12
  longTitle?: string;
12
13
  active: boolean;
14
+ outOfRange: boolean;
13
15
  disabled: boolean;
14
16
  today: boolean;
15
17
  autofocus?: boolean;
18
+ disabledReasonId?: string;
16
19
  onClick: (item: number) => void;
17
20
  }
18
21
 
@@ -22,9 +25,11 @@ const TableLink = ({
22
25
  title,
23
26
  longTitle,
24
27
  active,
28
+ outOfRange,
25
29
  disabled,
26
30
  today,
27
31
  autofocus,
32
+ disabledReasonId,
28
33
  onClick,
29
34
  }: TableLinkProps) => {
30
35
  const buttonRef = useRef<HTMLButtonElement>(null);
@@ -38,39 +43,49 @@ const TableLink = ({
38
43
  }
39
44
  }, [autofocus]);
40
45
 
41
- const onCalendarClick = (event: React.MouseEvent<HTMLButtonElement>) => {
46
+ const onCalendarClick = (event: React.MouseEvent) => {
42
47
  event.preventDefault();
43
- if (!disabled) {
44
- onClick(item);
45
- }
48
+ onClick(item);
46
49
  };
47
50
 
51
+ const isUnavailable = outOfRange || disabled;
52
+
48
53
  const calculateAriaLabel = () => {
54
+ let label = longTitle || title || '';
49
55
  if (active) {
50
- return `${longTitle || title || ''}, ${intl.formatMessage(
51
- messages.selected,
52
- )} ${intl.formatMessage(messages[type])}`;
56
+ label += `, ${intl.formatMessage(messages.selected)} ${intl.formatMessage(messages[type])}`;
57
+ }
58
+ if (isUnavailable) {
59
+ label += `, ${intl.formatMessage(messages.notAvailable)}`;
53
60
  }
54
- return longTitle || title;
61
+ return label;
55
62
  };
56
63
 
57
64
  return (
58
- <button
65
+ <SentimentSurface
66
+ // @ts-expect-error - Generic forwardRef limitation in SentimentSurface
59
67
  ref={buttonRef}
60
- type="button"
68
+ as="button"
69
+ data-calendar-cell
70
+ aria-describedby={isUnavailable ? disabledReasonId : undefined}
71
+ aria-disabled={isUnavailable || undefined}
72
+ aria-label={calculateAriaLabel()}
73
+ aria-pressed={active}
61
74
  className={clsx(
75
+ `wds-Calendar__option--${type}`,
62
76
  `tw-date-lookup-${type}-option np-text-body-default-bold`,
63
77
  { active: !!active },
64
78
  { today: !!today },
79
+ { disabled: !!isUnavailable },
65
80
  )}
66
- disabled={disabled}
81
+ hasBaseStyles={false}
82
+ sentiment={isUnavailable ? 'negative' : 'neutral'}
67
83
  tabIndex={autofocus ? 0 : -1}
68
- aria-label={calculateAriaLabel()}
69
- aria-pressed={active}
84
+ type="button"
70
85
  onClick={onCalendarClick}
71
86
  >
72
87
  {title || item}
73
- </button>
88
+ </SentimentSurface>
74
89
  );
75
90
  };
76
91
 
@@ -2,10 +2,11 @@ import { formatDate } from '@transferwise/formatting';
2
2
  import { useIntl } from 'react-intl';
3
3
 
4
4
  import { getFocusableTime } from '../../GetFocusableTime/GetFocusableTime';
5
- import TableLink from '../../tableLink';
5
+ import TableLink from '../../TableLink';
6
6
 
7
7
  interface YearCalendarTableProps {
8
8
  selectedDate: Date | null;
9
+ focusedDate?: Date | null;
9
10
  min: Date | null;
10
11
  max: Date | null;
11
12
  viewYear: number;
@@ -19,6 +20,7 @@ const YEAR_ONLY_FORMAT: Intl.DateTimeFormatOptions = { year: 'numeric' };
19
20
 
20
21
  const YearCalendarTable = ({
21
22
  selectedDate,
23
+ focusedDate,
22
24
  min,
23
25
  max,
24
26
  viewYear,
@@ -34,7 +36,8 @@ const YearCalendarTable = ({
34
36
  type="year"
35
37
  title={formatDate(new Date(year, 0), locale, YEAR_ONLY_FORMAT)}
36
38
  active={isActive(year)}
37
- disabled={isDisabled(year)}
39
+ outOfRange={isDisabled(year)}
40
+ disabled={false}
38
41
  today={isThisYear(year)}
39
42
  autofocus={autofocusYear === year}
40
43
  onClick={onSelect}
@@ -46,6 +49,10 @@ const YearCalendarTable = ({
46
49
  return !!(year === selectedDate?.getFullYear());
47
50
  };
48
51
 
52
+ const isFocused = (year: number) => {
53
+ return !!(year === focusedDate?.getFullYear());
54
+ };
55
+
49
56
  const isThisYear = (year: number) => {
50
57
  return year === new Date().getFullYear();
51
58
  };
@@ -56,7 +63,12 @@ const YearCalendarTable = ({
56
63
 
57
64
  const autofocusYear = (() => {
58
65
  const years = Array.from({ length: ROWS * COLS }, (_, index) => startYear + index);
59
- return getFocusableTime({ isActive, isNow: isThisYear, isDisabled, timeSpan: years });
66
+ return getFocusableTime({
67
+ isActive: isFocused,
68
+ isNow: isThisYear,
69
+ isDisabled,
70
+ timeSpan: years,
71
+ });
60
72
  })();
61
73
 
62
74
  return (
@@ -8,6 +8,7 @@ import YearCalendarTable from './Table';
8
8
 
9
9
  interface YearCalendarProps extends WrappedComponentProps {
10
10
  selectedDate: Date | null;
11
+ focusedDate?: Date | null;
11
12
  min: Date | null;
12
13
  max: Date | null;
13
14
  viewYear: number;
@@ -33,21 +34,29 @@ class YearCalendar extends PureComponent<YearCalendarProps> {
33
34
  render() {
34
35
  const {
35
36
  selectedDate,
37
+ focusedDate,
36
38
  min,
37
39
  max,
38
40
  viewYear,
39
41
  placeholder,
40
42
  intl: { formatMessage },
41
43
  } = this.props;
44
+ const startYear = viewYear - (viewYear % 20);
45
+ const isPreviousDisabled = !!(min && startYear - 1 < min.getFullYear());
46
+ const isNextDisabled = !!(max && startYear + 20 > max.getFullYear());
47
+
42
48
  return (
43
49
  <>
44
50
  <DateHeader
45
51
  dateMode={formatMessage(messages.twentyYears)}
52
+ isPreviousDisabled={isPreviousDisabled}
53
+ isNextDisabled={isNextDisabled}
46
54
  onPreviousClick={this.selectPreviousYears}
47
55
  onNextClick={this.selectNextYears}
48
56
  />
49
57
  <YearCalendarTable
50
58
  selectedDate={selectedDate}
59
+ focusedDate={focusedDate}
51
60
  min={min}
52
61
  max={max}
53
62
  viewYear={viewYear}