@transferwise/components 0.0.0-experimental-bcea1dd → 0.0.0-experimental-1168b1c

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 (175) hide show
  1. package/build/Calendar/Calendar.js +47 -8
  2. package/build/Calendar/Calendar.js.map +1 -1
  3. package/build/Calendar/Calendar.messages.js +3 -0
  4. package/build/Calendar/Calendar.messages.js.map +1 -1
  5. package/build/Calendar/Calendar.messages.mjs +3 -0
  6. package/build/Calendar/Calendar.messages.mjs.map +1 -1
  7. package/build/Calendar/Calendar.mjs +48 -9
  8. package/build/Calendar/Calendar.mjs.map +1 -1
  9. package/build/Calendar/DayCalendar/DayCalendar.js +4 -0
  10. package/build/Calendar/DayCalendar/DayCalendar.js.map +1 -1
  11. package/build/Calendar/DayCalendar/DayCalendar.mjs +4 -0
  12. package/build/Calendar/DayCalendar/DayCalendar.mjs.map +1 -1
  13. package/build/Calendar/DayCalendar/Table/DayCalendarTable.js +30 -18
  14. package/build/Calendar/DayCalendar/Table/DayCalendarTable.js.map +1 -1
  15. package/build/Calendar/DayCalendar/Table/DayCalendarTable.mjs +30 -18
  16. package/build/Calendar/DayCalendar/Table/DayCalendarTable.mjs.map +1 -1
  17. package/build/Calendar/MonthCalendar/Table/MonthCalendarTable.js +1 -1
  18. package/build/Calendar/MonthCalendar/Table/MonthCalendarTable.js.map +1 -1
  19. package/build/Calendar/MonthCalendar/Table/MonthCalendarTable.mjs +1 -1
  20. package/build/Calendar/MonthCalendar/Table/MonthCalendarTable.mjs.map +1 -1
  21. package/build/Calendar/{tableLink → TableLink}/TableLink.js +23 -11
  22. package/build/Calendar/TableLink/TableLink.js.map +1 -0
  23. package/build/Calendar/{tableLink → TableLink}/TableLink.mjs +23 -11
  24. package/build/Calendar/TableLink/TableLink.mjs.map +1 -0
  25. package/build/Calendar/YearCalendar/Table/YearCalendarTable.js +1 -1
  26. package/build/Calendar/YearCalendar/Table/YearCalendarTable.js.map +1 -1
  27. package/build/Calendar/YearCalendar/Table/YearCalendarTable.mjs +1 -1
  28. package/build/Calendar/YearCalendar/Table/YearCalendarTable.mjs.map +1 -1
  29. package/build/DateLookup/DateLookup.js +14 -4
  30. package/build/DateLookup/DateLookup.js.map +1 -1
  31. package/build/DateLookup/DateLookup.mjs +14 -4
  32. package/build/DateLookup/DateLookup.mjs.map +1 -1
  33. package/build/common/DateUtils/hasDisabledDatesInMonth/hasDisabledDatesInMonth.js +33 -0
  34. package/build/common/DateUtils/hasDisabledDatesInMonth/hasDisabledDatesInMonth.js.map +1 -0
  35. package/build/common/DateUtils/hasDisabledDatesInMonth/hasDisabledDatesInMonth.mjs +31 -0
  36. package/build/common/DateUtils/hasDisabledDatesInMonth/hasDisabledDatesInMonth.mjs.map +1 -0
  37. package/build/common/DateUtils/isDateDisabled/isDateDisabled.js +40 -0
  38. package/build/common/DateUtils/isDateDisabled/isDateDisabled.js.map +1 -0
  39. package/build/common/DateUtils/isDateDisabled/isDateDisabled.mjs +38 -0
  40. package/build/common/DateUtils/isDateDisabled/isDateDisabled.mjs.map +1 -0
  41. package/build/i18n/en.json +1 -0
  42. package/build/i18n/en.json.js +1 -0
  43. package/build/i18n/en.json.js.map +1 -1
  44. package/build/i18n/en.json.mjs +1 -0
  45. package/build/i18n/en.json.mjs.map +1 -1
  46. package/build/index.js +2 -4
  47. package/build/index.js.map +1 -1
  48. package/build/index.mjs +1 -2
  49. package/build/index.mjs.map +1 -1
  50. package/build/main.css +114 -76
  51. package/build/styles/Calendar/Calendar.css +104 -35
  52. package/build/styles/Calendar/TableLink/TableLink.css +109 -0
  53. package/build/styles/main.css +114 -76
  54. package/build/types/Body/Body.d.ts +2 -2
  55. package/build/types/Calendar/Calendar.d.ts +4 -2
  56. package/build/types/Calendar/Calendar.d.ts.map +1 -1
  57. package/build/types/Calendar/Calendar.messages.d.ts +5 -0
  58. package/build/types/Calendar/Calendar.messages.d.ts.map +1 -1
  59. package/build/types/Calendar/Calendar.types.d.ts +19 -0
  60. package/build/types/Calendar/Calendar.types.d.ts.map +1 -0
  61. package/build/types/Calendar/DayCalendar/DayCalendar.d.ts +4 -1
  62. package/build/types/Calendar/DayCalendar/DayCalendar.d.ts.map +1 -1
  63. package/build/types/Calendar/DayCalendar/Table/DayCalendarTable.d.ts +3 -0
  64. package/build/types/Calendar/DayCalendar/Table/DayCalendarTable.d.ts.map +1 -1
  65. package/build/types/Calendar/{tableLink → TableLink}/TableLink.d.ts +2 -1
  66. package/build/types/Calendar/TableLink/TableLink.d.ts.map +1 -0
  67. package/build/types/Calendar/TableLink/index.d.ts.map +1 -0
  68. package/build/types/Calendar/index.d.ts +1 -0
  69. package/build/types/Calendar/index.d.ts.map +1 -1
  70. package/build/types/DateLookup/DateLookup.d.ts +5 -2
  71. package/build/types/DateLookup/DateLookup.d.ts.map +1 -1
  72. package/build/types/DateLookup/index.d.ts +1 -0
  73. package/build/types/DateLookup/index.d.ts.map +1 -1
  74. package/build/types/IconButton/IconButton.d.ts +1 -1
  75. package/build/types/Title/Title.d.ts +2 -2
  76. package/build/types/Upload/Steps/UploadImageStep/UploadImageStep.d.ts +1 -1
  77. package/build/types/UploadInput/UploadItem/UploadItemLink.d.ts +1 -1
  78. package/build/types/common/DateUtils/hasDisabledDatesInMonth/hasDisabledDatesInMonth.d.ts +3 -0
  79. package/build/types/common/DateUtils/hasDisabledDatesInMonth/hasDisabledDatesInMonth.d.ts.map +1 -0
  80. package/build/types/common/DateUtils/index.d.ts +2 -0
  81. package/build/types/common/DateUtils/index.d.ts.map +1 -1
  82. package/build/types/common/DateUtils/isDateDisabled/isDateDisabled.d.ts +3 -0
  83. package/build/types/common/DateUtils/isDateDisabled/isDateDisabled.d.ts.map +1 -0
  84. package/build/types/index.d.ts +2 -4
  85. package/build/types/index.d.ts.map +1 -1
  86. package/build/types/test-utils/index.d.ts +2 -0
  87. package/build/types/test-utils/index.d.ts.map +1 -1
  88. package/package.json +3 -3
  89. package/src/Calendar/Calendar.css +104 -35
  90. package/src/Calendar/Calendar.disabledDates.test.tsx +197 -0
  91. package/src/Calendar/Calendar.less +5 -35
  92. package/src/Calendar/Calendar.messages.ts +5 -0
  93. package/src/Calendar/Calendar.test.tsx +9 -9
  94. package/src/Calendar/Calendar.tsx +61 -9
  95. package/src/Calendar/Calendar.types.ts +22 -0
  96. package/src/Calendar/DayCalendar/DayCalendar.tsx +17 -1
  97. package/src/Calendar/DayCalendar/Table/DayCalendarTable.tsx +42 -26
  98. package/src/Calendar/MonthCalendar/Table/MonthCalendarTable.tsx +1 -1
  99. package/src/Calendar/TableLink/TableLink.css +109 -0
  100. package/src/Calendar/TableLink/TableLink.less +60 -0
  101. package/src/Calendar/{tableLink → TableLink}/TableLink.tsx +24 -14
  102. package/src/Calendar/YearCalendar/Table/YearCalendarTable.tsx +1 -1
  103. package/src/Calendar/_stories/Calendar.accessibility.docs.mdx +112 -0
  104. package/src/Calendar/_stories/Calendar.story.tsx +55 -1
  105. package/src/Calendar/index.ts +7 -0
  106. package/src/DateLookup/DateLookup.disabledDates.test.tsx +120 -0
  107. package/src/DateLookup/DateLookup.test.tsx +1 -1
  108. package/src/DateLookup/DateLookup.tsx +11 -4
  109. package/src/DateLookup/_stories/DateLookup.accessibility.docs.mdx +100 -0
  110. package/src/DateLookup/_stories/DateLookup.story.tsx +59 -1
  111. package/src/DateLookup/index.ts +6 -0
  112. package/src/common/DateUtils/hasDisabledDatesInMonth/hasDisabledDatesInMonth.test.ts +98 -0
  113. package/src/common/DateUtils/hasDisabledDatesInMonth/hasDisabledDatesInMonth.ts +45 -0
  114. package/src/common/DateUtils/index.ts +2 -0
  115. package/src/common/DateUtils/isDateDisabled/isDateDisabled.test.ts +159 -0
  116. package/src/common/DateUtils/isDateDisabled/isDateDisabled.ts +59 -0
  117. package/src/i18n/en.json +1 -0
  118. package/src/index.ts +2 -4
  119. package/src/main.css +114 -76
  120. package/src/main.less +0 -1
  121. package/build/Calendar/tableLink/TableLink.js.map +0 -1
  122. package/build/Calendar/tableLink/TableLink.mjs.map +0 -1
  123. package/build/Markup/Markup.js +0 -136
  124. package/build/Markup/Markup.js.map +0 -1
  125. package/build/Markup/Markup.mjs +0 -131
  126. package/build/Markup/Markup.mjs.map +0 -1
  127. package/build/Markup/constants.js +0 -36
  128. package/build/Markup/constants.js.map +0 -1
  129. package/build/Markup/constants.mjs +0 -29
  130. package/build/Markup/constants.mjs.map +0 -1
  131. package/build/Markup/utils/parseMarkup.js +0 -226
  132. package/build/Markup/utils/parseMarkup.js.map +0 -1
  133. package/build/Markup/utils/parseMarkup.mjs +0 -223
  134. package/build/Markup/utils/parseMarkup.mjs.map +0 -1
  135. package/build/Markup/utils/sanitise.js +0 -91
  136. package/build/Markup/utils/sanitise.js.map +0 -1
  137. package/build/Markup/utils/sanitise.mjs +0 -89
  138. package/build/Markup/utils/sanitise.mjs.map +0 -1
  139. package/build/styles/Markup/Markup.css +0 -28
  140. package/build/styles/Markup/_stories/Vulnerability/Vulnerability.css +0 -70
  141. package/build/types/Calendar/tableLink/TableLink.d.ts.map +0 -1
  142. package/build/types/Calendar/tableLink/index.d.ts.map +0 -1
  143. package/build/types/Markup/Markup.d.ts +0 -18
  144. package/build/types/Markup/Markup.d.ts.map +0 -1
  145. package/build/types/Markup/Markup.types.d.ts +0 -68
  146. package/build/types/Markup/Markup.types.d.ts.map +0 -1
  147. package/build/types/Markup/_stories/Vulnerability/Vulnerability.d.ts +0 -12
  148. package/build/types/Markup/_stories/Vulnerability/Vulnerability.d.ts.map +0 -1
  149. package/build/types/Markup/constants.d.ts +0 -20
  150. package/build/types/Markup/constants.d.ts.map +0 -1
  151. package/build/types/Markup/index.d.ts +0 -3
  152. package/build/types/Markup/index.d.ts.map +0 -1
  153. package/build/types/Markup/utils/parseMarkup.d.ts +0 -6
  154. package/build/types/Markup/utils/parseMarkup.d.ts.map +0 -1
  155. package/build/types/Markup/utils/sanitise.d.ts +0 -2
  156. package/build/types/Markup/utils/sanitise.d.ts.map +0 -1
  157. package/src/Markup/Markup.css +0 -28
  158. package/src/Markup/Markup.injection.test.tsx +0 -152
  159. package/src/Markup/Markup.less +0 -29
  160. package/src/Markup/Markup.test.tsx +0 -405
  161. package/src/Markup/Markup.tsx +0 -165
  162. package/src/Markup/Markup.types.ts +0 -53
  163. package/src/Markup/_stories/Markup.docs.mdx +0 -75
  164. package/src/Markup/_stories/Markup.security.docs.mdx +0 -256
  165. package/src/Markup/_stories/Markup.story.tsx +0 -227
  166. package/src/Markup/_stories/Vulnerability/Vulnerability.css +0 -70
  167. package/src/Markup/_stories/Vulnerability/Vulnerability.tsx +0 -30
  168. package/src/Markup/constants.ts +0 -41
  169. package/src/Markup/index.ts +0 -2
  170. package/src/Markup/utils/parseMarkup.test.ts +0 -658
  171. package/src/Markup/utils/parseMarkup.ts +0 -228
  172. package/src/Markup/utils/sanitise.test.ts +0 -375
  173. package/src/Markup/utils/sanitise.ts +0 -107
  174. /package/build/types/Calendar/{tableLink → TableLink}/index.d.ts +0 -0
  175. /package/src/Calendar/{tableLink → TableLink}/index.ts +0 -0
@@ -1,7 +1,15 @@
1
- import { useState, useCallback, useEffect, KeyboardEvent } from 'react';
1
+ import { useState, useCallback, useEffect, useId, useRef, KeyboardEvent } from 'react';
2
2
 
3
3
  import { MonthFormat } from '../common';
4
- import { isWithinRange, moveToWithinRange, returnDateView } from '../common/DateUtils';
4
+ import {
5
+ isWithinRange,
6
+ moveToWithinRange,
7
+ returnDateView,
8
+ isDateDisabled,
9
+ hasDisabledDatesInMonth,
10
+ } from '../common/DateUtils';
11
+ import { InlinePrompt } from '../Prompt/InlinePrompt';
12
+ import type { DateChangeMeta, DisabledRules } from './Calendar.types';
5
13
  import DayCalendar from './DayCalendar';
6
14
  import { getStartOfDay } from './GetStartOfDay';
7
15
  import MonthCalendar from './MonthCalendar';
@@ -13,7 +21,8 @@ export interface CalendarProps {
13
21
  max?: Date | null;
14
22
  monthFormat?: `${MonthFormat}`;
15
23
  placeholder?: string;
16
- onChange: (date: Date | null) => void;
24
+ disabledRules?: DisabledRules;
25
+ onChange: (date: Date | null, meta: DateChangeMeta) => void;
17
26
  onSelect?: (date: Date) => void;
18
27
  }
19
28
 
@@ -23,16 +32,25 @@ const Calendar = ({
23
32
  max: maxProp = null,
24
33
  monthFormat = MonthFormat.LONG,
25
34
  placeholder,
35
+ disabledRules,
26
36
  onChange,
27
37
  onSelect,
28
38
  }: CalendarProps) => {
29
39
  const selectedDate = getStartOfDay(value);
30
40
  const min = getStartOfDay(minProp);
31
41
  const max = getStartOfDay(maxProp);
42
+ const disabledReasonId = useId();
43
+ const [focusedDateDisabled, setFocusedDateDisabled] = useState(false);
44
+ const disabledRulesRef = useRef(disabledRules);
45
+ useEffect(() => {
46
+ disabledRulesRef.current = disabledRules;
47
+ });
32
48
 
33
49
  useEffect(() => {
34
50
  if (selectedDate && !isWithinRange(selectedDate, min, max)) {
35
- onChange(moveToWithinRange(selectedDate, min, max));
51
+ const clamped = moveToWithinRange(selectedDate, min, max);
52
+ const isEnabled = !isDateDisabled(clamped, disabledRulesRef.current);
53
+ onChange(clamped, { isEnabled });
36
54
  }
37
55
  }, [selectedDate?.getTime(), min?.getTime(), max?.getTime(), onChange]); // eslint-disable-line react-hooks/exhaustive-deps
38
56
 
@@ -49,6 +67,16 @@ const Calendar = ({
49
67
  setViewYear(view.getFullYear());
50
68
  }, [selectedDate?.getTime(), min?.getTime(), max?.getTime()]); // eslint-disable-line react-hooks/exhaustive-deps
51
69
 
70
+ useEffect(() => {
71
+ if (selectedDate) {
72
+ const isEnabled =
73
+ isWithinRange(selectedDate, min, max) &&
74
+ !isDateDisabled(selectedDate, disabledRulesRef.current);
75
+
76
+ setFocusedDateDisabled(!isEnabled);
77
+ }
78
+ }, [selectedDate?.getTime(), min?.getTime(), max?.getTime(), disabledRules]); // eslint-disable-line react-hooks/exhaustive-deps
79
+
52
80
  const handleViewDateUpdate = useCallback(
53
81
  // eslint-disable-next-line react-hooks/preserve-manual-memoization
54
82
  ({ month, year }: { month?: number; year?: number }) => {
@@ -63,17 +91,21 @@ const Calendar = ({
63
91
  const switchToYears = useCallback(() => setMode('year'), []);
64
92
 
65
93
  const handleSelectedDateUpdate = useCallback(
94
+ // eslint-disable-next-line react-hooks/preserve-manual-memoization
66
95
  (date: Date) => {
67
- if (onSelect) {
96
+ const isEnabled =
97
+ isWithinRange(date, min, max) && !isDateDisabled(date, disabledRulesRef.current);
98
+ setFocusedDateDisabled(!isEnabled);
99
+ onChange(date, { isEnabled });
100
+ if (isEnabled && onSelect) {
68
101
  onSelect(date);
69
- } else {
70
- onChange(date);
71
102
  }
72
103
  },
73
- [onChange, onSelect],
104
+ [min, max, onChange, onSelect],
74
105
  );
75
106
 
76
107
  const adjustDate = useCallback(
108
+ // eslint-disable-next-line react-hooks/preserve-manual-memoization
77
109
  (daysToAdd: number, monthsToAdd: number, yearsToAdd: number) => {
78
110
  let date: Date | null;
79
111
  if (selectedDate) {
@@ -87,7 +119,11 @@ const Calendar = ({
87
119
  }
88
120
  date &&= moveToWithinRange(date, min, max);
89
121
  if (date?.getTime() !== selectedDate?.getTime()) {
90
- onChange(date);
122
+ const isEnabled = date
123
+ ? isWithinRange(date, min, max) && !isDateDisabled(date, disabledRulesRef.current)
124
+ : true;
125
+ setFocusedDateDisabled(!isEnabled);
126
+ onChange(date, { isEnabled });
91
127
  }
92
128
  },
93
129
  [selectedDate, min, max, mode, onChange],
@@ -119,6 +155,11 @@ const Calendar = ({
119
155
  [adjustDate],
120
156
  );
121
157
 
158
+ const showPrompt =
159
+ mode === 'day' &&
160
+ !!disabledRules?.reason &&
161
+ hasDisabledDatesInMonth(viewMonth, viewYear, disabledRules, min, max);
162
+
122
163
  return (
123
164
  // eslint-disable-next-line jsx-a11y/no-static-element-interactions
124
165
  <div onKeyDown={handleKeyDown}>
@@ -130,6 +171,8 @@ const Calendar = ({
130
171
  viewMonth={viewMonth}
131
172
  viewYear={viewYear}
132
173
  monthFormat={monthFormat}
174
+ disabledRules={disabledRules}
175
+ disabledReasonId={showPrompt ? disabledReasonId : undefined}
133
176
  onSelect={handleSelectedDateUpdate}
134
177
  onLabelClick={switchToYears}
135
178
  onViewDateUpdate={handleViewDateUpdate}
@@ -158,6 +201,15 @@ const Calendar = ({
158
201
  onViewDateUpdate={handleViewDateUpdate}
159
202
  />
160
203
  )}
204
+ {showPrompt && (
205
+ <InlinePrompt
206
+ sentiment={focusedDateDisabled ? 'negative' : 'neutral'}
207
+ width="full"
208
+ id={disabledReasonId}
209
+ >
210
+ {disabledRules.reason}
211
+ </InlinePrompt>
212
+ )}
161
213
  </div>
162
214
  );
163
215
  };
@@ -0,0 +1,22 @@
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
+ weekdays?: WeekDayType[];
10
+ dates?: Date[];
11
+ ranges?: DisabledDateRange[];
12
+ reason: string;
13
+ }
14
+
15
+ export interface DateChangeMeta {
16
+ isEnabled: boolean;
17
+ }
18
+
19
+ export interface OnDateSelectParams {
20
+ date: Date;
21
+ isEnabled: boolean;
22
+ }
@@ -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
 
@@ -15,6 +16,8 @@ interface DayCalendarProps extends WrappedComponentProps {
15
16
  viewMonth: number;
16
17
  viewYear: number;
17
18
  monthFormat: `${MonthFormat}`;
19
+ disabledRules?: DisabledRules;
20
+ disabledReasonId?: string;
18
21
  onSelect: (date: Date) => void;
19
22
  onLabelClick: () => void;
20
23
  onViewDateUpdate: (date: { month: number; year: number }) => void;
@@ -46,6 +49,8 @@ class DayCalendar extends PureComponent<DayCalendarProps> {
46
49
  viewYear,
47
50
  intl: { locale, formatMessage },
48
51
  monthFormat,
52
+ disabledRules,
53
+ disabledReasonId,
49
54
  onLabelClick,
50
55
  onSelect,
51
56
  } = this.props;
@@ -61,7 +66,18 @@ class DayCalendar extends PureComponent<DayCalendarProps> {
61
66
  onPreviousClick={this.selectPreviousMonth}
62
67
  onNextClick={this.selectNextMonth}
63
68
  />
64
- <DayCalendarTable {...{ selectedDate, min, max, viewMonth, viewYear, onSelect }} />
69
+ <DayCalendarTable
70
+ {...{
71
+ selectedDate,
72
+ min,
73
+ max,
74
+ viewMonth,
75
+ viewYear,
76
+ disabledRules,
77
+ disabledReasonId,
78
+ onSelect,
79
+ }}
80
+ />
65
81
  </>
66
82
  );
67
83
  }
@@ -2,12 +2,18 @@ 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;
@@ -15,6 +21,8 @@ interface DayCalendarTableProps extends WrappedComponentProps {
15
21
  max: Date | null;
16
22
  viewMonth: number;
17
23
  viewYear: number;
24
+ disabledRules?: DisabledRules;
25
+ disabledReasonId?: string;
18
26
  onSelect: (date: Date) => void;
19
27
  }
20
28
 
@@ -67,10 +75,10 @@ class DayCalendarTable extends PureComponent<DayCalendarTableProps> {
67
75
  if (day < 0) {
68
76
  return true;
69
77
  }
70
- const { min, max, viewMonth, viewYear } = this.props;
78
+ const { min, max, viewMonth, viewYear, disabledRules } = this.props;
71
79
  const date = new Date(viewYear, viewMonth, day);
72
80
 
73
- return !isWithinRange(date, min, max);
81
+ return !isWithinRange(date, min, max) || isDateDisabled(date, disabledRules);
74
82
  };
75
83
 
76
84
  isActive = (day: number) => {
@@ -94,7 +102,7 @@ class DayCalendarTable extends PureComponent<DayCalendarTableProps> {
94
102
  };
95
103
 
96
104
  render() {
97
- const { viewMonth, viewYear, intl } = this.props;
105
+ const { viewMonth, viewYear, intl, disabledReasonId } = this.props;
98
106
  const weeks = this.getTableStructure();
99
107
  const autoFocusDay = this.getAutofocusDay(weeks);
100
108
  return (
@@ -124,27 +132,35 @@ class DayCalendarTable extends PureComponent<DayCalendarTableProps> {
124
132
  {/* eslint-disable react/no-array-index-key */}
125
133
  {weeks.map((week, weekIndex) => (
126
134
  <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
- ))}
135
+ {week.map((day, dayIndex) => {
136
+ const dayDisabled = this.isDisabled(day);
137
+ return (
138
+ <td key={dayIndex} className={dayIndex > 4 ? 'weekend' : ''}>
139
+ {day !== -1 && (
140
+ <TableLink
141
+ item={day}
142
+ type="day"
143
+ title={formatDate(
144
+ new Date(viewYear, viewMonth, day),
145
+ intl.locale,
146
+ SHORT_DAY_FORMAT,
147
+ )}
148
+ autofocus={day === autoFocusDay}
149
+ longTitle={formatDate(
150
+ new Date(viewYear, viewMonth, day),
151
+ intl.locale,
152
+ LONG_DAY_FORMAT,
153
+ )}
154
+ active={this.isActive(day)}
155
+ disabled={dayDisabled}
156
+ today={this.isToday(day)}
157
+ disabledReasonId={dayDisabled ? disabledReasonId : undefined}
158
+ onClick={this.selectDay}
159
+ />
160
+ )}
161
+ </td>
162
+ );
163
+ })}
148
164
  </tr>
149
165
  ))}
150
166
  {/* eslint-enable react/no-array-index-key */}
@@ -2,7 +2,7 @@ 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;
@@ -0,0 +1,109 @@
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
+ color: #0097c7;
8
+ color: var(--color-content-accent);
9
+ border: transparent;
10
+ border-radius: 10px;
11
+ border-radius: var(--radius-small);
12
+ background-color: transparent;
13
+ font-weight: 600;
14
+ font-weight: var(--font-weight-semi-bold);
15
+ color: #37517e;
16
+ color: var(--color-content-primary);
17
+ }
18
+ @media (max-width: 320px) {
19
+ .wds-Calendar__option--day,
20
+ .wds-Calendar__option--month,
21
+ .wds-Calendar__option--year {
22
+ padding: 0;
23
+ }
24
+ }
25
+ .wds-Calendar__option--day.disabled,
26
+ .wds-Calendar__option--month.disabled,
27
+ .wds-Calendar__option--year.disabled,
28
+ .wds-Calendar__option--day[aria-disabled="true"],
29
+ .wds-Calendar__option--month[aria-disabled="true"],
30
+ .wds-Calendar__option--year[aria-disabled="true"] {
31
+ --ring-outline-color: var(--color-sentiment-content-primary);
32
+ color: #768e9c;
33
+ color: var(--color-content-tertiary);
34
+ cursor: not-allowed;
35
+ }
36
+ .wds-Calendar__option--day.disabled:hover,
37
+ .wds-Calendar__option--month.disabled:hover,
38
+ .wds-Calendar__option--year.disabled:hover,
39
+ .wds-Calendar__option--day[aria-disabled="true"]:hover,
40
+ .wds-Calendar__option--month[aria-disabled="true"]:hover,
41
+ .wds-Calendar__option--year[aria-disabled="true"]:hover,
42
+ .wds-Calendar__option--day.disabled:focus,
43
+ .wds-Calendar__option--month.disabled:focus,
44
+ .wds-Calendar__option--year.disabled:focus,
45
+ .wds-Calendar__option--day[aria-disabled="true"]:focus,
46
+ .wds-Calendar__option--month[aria-disabled="true"]:focus,
47
+ .wds-Calendar__option--year[aria-disabled="true"]:focus {
48
+ background-color: var(--color-sentiment-background-surface);
49
+ color: var(--color-sentiment-content-primary);
50
+ filter: initial;
51
+ opacity: 1;
52
+ opacity: initial;
53
+ }
54
+ .wds-Calendar__option--day.disabled.active,
55
+ .wds-Calendar__option--month.disabled.active,
56
+ .wds-Calendar__option--year.disabled.active,
57
+ .wds-Calendar__option--day[aria-disabled="true"].active,
58
+ .wds-Calendar__option--month[aria-disabled="true"].active,
59
+ .wds-Calendar__option--year[aria-disabled="true"].active,
60
+ .wds-Calendar__option--day.disabled.active:hover,
61
+ .wds-Calendar__option--month.disabled.active:hover,
62
+ .wds-Calendar__option--year.disabled.active:hover,
63
+ .wds-Calendar__option--day[aria-disabled="true"].active:hover,
64
+ .wds-Calendar__option--month[aria-disabled="true"].active:hover,
65
+ .wds-Calendar__option--year[aria-disabled="true"].active:hover,
66
+ .wds-Calendar__option--day.disabled.active:active,
67
+ .wds-Calendar__option--month.disabled.active:active,
68
+ .wds-Calendar__option--year.disabled.active:active,
69
+ .wds-Calendar__option--day[aria-disabled="true"].active:active,
70
+ .wds-Calendar__option--month[aria-disabled="true"].active:active,
71
+ .wds-Calendar__option--year[aria-disabled="true"].active:active,
72
+ .wds-Calendar__option--day.disabled.active:focus,
73
+ .wds-Calendar__option--month.disabled.active:focus,
74
+ .wds-Calendar__option--year.disabled.active:focus,
75
+ .wds-Calendar__option--day[aria-disabled="true"].active:focus,
76
+ .wds-Calendar__option--month[aria-disabled="true"].active:focus,
77
+ .wds-Calendar__option--year[aria-disabled="true"].active:focus {
78
+ background-color: var(--color-sentiment-interactive-primary);
79
+ color: var(--color-sentiment-interactive-secondary);
80
+ }
81
+ .wds-Calendar__option--day.disabled.today,
82
+ .wds-Calendar__option--month.disabled.today,
83
+ .wds-Calendar__option--year.disabled.today,
84
+ .wds-Calendar__option--day[aria-disabled="true"].today,
85
+ .wds-Calendar__option--month[aria-disabled="true"].today,
86
+ .wds-Calendar__option--year[aria-disabled="true"].today {
87
+ text-decoration-color: #768e9c;
88
+ text-decoration-color: var(--color-content-tertiary);
89
+ }
90
+ .wds-Calendar__option--day {
91
+ height: 40px;
92
+ height: var(--size-40);
93
+ border-radius: 9999px;
94
+ border-radius: var(--radius-full);
95
+ line-height: 40px;
96
+ line-height: var(--size-40);
97
+ width: 40px;
98
+ width: var(--size-40);
99
+ padding: 0;
100
+ display: inline-flex;
101
+ align-items: center;
102
+ justify-content: center;
103
+ }
104
+ .wds-Calendar__option--day.today {
105
+ -webkit-text-decoration: underline;
106
+ text-decoration: underline;
107
+ text-decoration-thickness: 2px;
108
+ text-underline-offset: 4px;
109
+ }
@@ -0,0 +1,60 @@
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
+ color: var(--color-content-accent);
12
+ border: transparent;
13
+ border-radius: var(--radius-small);
14
+ background-color: transparent;
15
+ font-weight: var(--font-weight-semi-bold);
16
+ color: var(--color-content-primary);
17
+
18
+ &.disabled,
19
+ &[aria-disabled="true"] {
20
+ --ring-outline-color: var(--color-sentiment-content-primary);
21
+ color: var(--color-content-tertiary);
22
+ cursor: not-allowed;
23
+
24
+ &:hover,
25
+ &:focus {
26
+ background-color: var(--color-sentiment-background-surface);
27
+ color: var(--color-sentiment-content-primary);
28
+ filter: unset;
29
+ opacity: unset;
30
+ }
31
+
32
+ &.active {
33
+ &, &:hover, &:active, &:focus {
34
+ background-color: var(--color-sentiment-interactive-primary);
35
+ color: var(--color-sentiment-interactive-secondary);
36
+ }
37
+ }
38
+
39
+ &.today {
40
+ text-decoration-color: var(--color-content-tertiary);
41
+ }
42
+ }
43
+ }
44
+
45
+ .wds-Calendar__option--day {
46
+ height: var(--size-40);
47
+ border-radius: var(--radius-full);
48
+ line-height: var(--size-40);
49
+ width: var(--size-40);
50
+ padding: 0;
51
+ display: inline-flex;
52
+ align-items: center;
53
+ justify-content: center;
54
+
55
+ &.today {
56
+ text-decoration: underline;
57
+ text-decoration-thickness: 2px;
58
+ text-underline-offset: 4px;
59
+ }
60
+ }
@@ -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;
@@ -13,6 +14,7 @@ interface TableLinkProps {
13
14
  disabled: boolean;
14
15
  today: boolean;
15
16
  autofocus?: boolean;
17
+ disabledReasonId?: string;
16
18
  onClick: (item: number) => void;
17
19
  }
18
20
 
@@ -25,6 +27,7 @@ const TableLink = ({
25
27
  disabled,
26
28
  today,
27
29
  autofocus,
30
+ disabledReasonId,
28
31
  onClick,
29
32
  }: TableLinkProps) => {
30
33
  const buttonRef = useRef<HTMLButtonElement>(null);
@@ -38,39 +41,46 @@ const TableLink = ({
38
41
  }
39
42
  }, [autofocus]);
40
43
 
41
- const onCalendarClick = (event: React.MouseEvent<HTMLButtonElement>) => {
44
+ const onCalendarClick = (event: React.MouseEvent) => {
42
45
  event.preventDefault();
43
- if (!disabled) {
44
- onClick(item);
45
- }
46
+ onClick(item);
46
47
  };
47
48
 
48
49
  const calculateAriaLabel = () => {
50
+ let label = longTitle || title || '';
49
51
  if (active) {
50
- return `${longTitle || title || ''}, ${intl.formatMessage(
51
- messages.selected,
52
- )} ${intl.formatMessage(messages[type])}`;
52
+ label += `, ${intl.formatMessage(messages.selected)} ${intl.formatMessage(messages[type])}`;
53
+ }
54
+ if (disabled) {
55
+ label += `, ${intl.formatMessage(messages.notAvailable)}`;
53
56
  }
54
- return longTitle || title;
57
+ return label;
55
58
  };
56
59
 
57
60
  return (
58
- <button
61
+ <SentimentSurface
62
+ // @ts-expect-error - Generic forwardRef limitation in SentimentSurface
59
63
  ref={buttonRef}
60
- type="button"
64
+ as="button"
65
+ aria-describedby={disabled ? disabledReasonId : undefined}
66
+ aria-disabled={disabled || undefined}
67
+ aria-label={calculateAriaLabel()}
68
+ aria-pressed={active}
61
69
  className={clsx(
70
+ `wds-Calendar__option--${type}`,
62
71
  `tw-date-lookup-${type}-option np-text-body-default-bold`,
63
72
  { active: !!active },
64
73
  { today: !!today },
74
+ { disabled: !!disabled },
65
75
  )}
66
- disabled={disabled}
76
+ hasBaseStyles={false}
77
+ sentiment={disabled ? 'negative' : 'neutral'}
67
78
  tabIndex={autofocus ? 0 : -1}
68
- aria-label={calculateAriaLabel()}
69
- aria-pressed={active}
79
+ type="button"
70
80
  onClick={onCalendarClick}
71
81
  >
72
82
  {title || item}
73
- </button>
83
+ </SentimentSurface>
74
84
  );
75
85
  };
76
86
 
@@ -2,7 +2,7 @@ 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;