@transferwise/components 46.27.0 → 46.29.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.
Files changed (206) hide show
  1. package/build/i18n/th.json +2 -2
  2. package/build/index.js +344 -1147
  3. package/build/index.js.map +1 -1
  4. package/build/index.mjs +346 -1147
  5. package/build/index.mjs.map +1 -1
  6. package/build/main.css +16 -5
  7. package/build/styles/logo/Logo.css +16 -0
  8. package/build/styles/main.css +16 -5
  9. package/build/types/alert/Alert.d.ts +47 -58
  10. package/build/types/alert/Alert.d.ts.map +1 -1
  11. package/build/types/alert/index.d.ts +2 -1
  12. package/build/types/alert/index.d.ts.map +1 -1
  13. package/build/types/button/Button.d.ts +7 -9
  14. package/build/types/button/Button.d.ts.map +1 -1
  15. package/build/types/common/dateUtils/isWithinRange/isWithinRange.d.ts +1 -1
  16. package/build/types/common/dateUtils/isWithinRange/isWithinRange.d.ts.map +1 -1
  17. package/build/types/common/dateUtils/moveToWithinRange/moveToWithinRange.d.ts +1 -1
  18. package/build/types/common/dateUtils/moveToWithinRange/moveToWithinRange.d.ts.map +1 -1
  19. package/build/types/common/propsValues/sentiment.d.ts +0 -1
  20. package/build/types/common/propsValues/sentiment.d.ts.map +1 -1
  21. package/build/types/dateLookup/DateLookup.d.ts +75 -28
  22. package/build/types/dateLookup/DateLookup.d.ts.map +1 -1
  23. package/build/types/dateLookup/DateLookup.messages.d.ts +42 -63
  24. package/build/types/dateLookup/DateLookup.messages.d.ts.map +1 -1
  25. package/build/types/dateLookup/dateHeader/DateHeader.d.ts +9 -22
  26. package/build/types/dateLookup/dateHeader/DateHeader.d.ts.map +1 -1
  27. package/build/types/dateLookup/dateHeader/index.d.ts +1 -1
  28. package/build/types/dateLookup/dateHeader/index.d.ts.map +1 -1
  29. package/build/types/dateLookup/dateTrigger/DateTrigger.d.ts +13 -31
  30. package/build/types/dateLookup/dateTrigger/DateTrigger.d.ts.map +1 -1
  31. package/build/types/dateLookup/dateTrigger/index.d.ts +1 -1
  32. package/build/types/dateLookup/dateTrigger/index.d.ts.map +1 -1
  33. package/build/types/dateLookup/dayCalendar/DayCalendar.d.ts +19 -2
  34. package/build/types/dateLookup/dayCalendar/DayCalendar.d.ts.map +1 -1
  35. package/build/types/dateLookup/dayCalendar/index.d.ts +1 -1
  36. package/build/types/dateLookup/dayCalendar/index.d.ts.map +1 -1
  37. package/build/types/dateLookup/dayCalendar/table/DayCalendarTable.d.ts +12 -2
  38. package/build/types/dateLookup/dayCalendar/table/DayCalendarTable.d.ts.map +1 -1
  39. package/build/types/dateLookup/dayCalendar/table/index.d.ts +1 -1
  40. package/build/types/dateLookup/dayCalendar/table/index.d.ts.map +1 -1
  41. package/build/types/dateLookup/getStartOfDay/getStartOfDay.d.ts +1 -1
  42. package/build/types/dateLookup/getStartOfDay/getStartOfDay.d.ts.map +1 -1
  43. package/build/types/dateLookup/getStartOfDay/index.d.ts +1 -1
  44. package/build/types/dateLookup/getStartOfDay/index.d.ts.map +1 -1
  45. package/build/types/dateLookup/index.d.ts +2 -1
  46. package/build/types/dateLookup/index.d.ts.map +1 -1
  47. package/build/types/dateLookup/monthCalendar/MonthCalendar.d.ts +17 -2
  48. package/build/types/dateLookup/monthCalendar/MonthCalendar.d.ts.map +1 -1
  49. package/build/types/dateLookup/monthCalendar/index.d.ts +1 -1
  50. package/build/types/dateLookup/monthCalendar/index.d.ts.map +1 -1
  51. package/build/types/dateLookup/monthCalendar/table/MonthCalendarTable.d.ts +10 -26
  52. package/build/types/dateLookup/monthCalendar/table/MonthCalendarTable.d.ts.map +1 -1
  53. package/build/types/dateLookup/monthCalendar/table/index.d.ts +1 -1
  54. package/build/types/dateLookup/monthCalendar/table/index.d.ts.map +1 -1
  55. package/build/types/dateLookup/yearCalendar/YearCalendar.d.ts +15 -2
  56. package/build/types/dateLookup/yearCalendar/YearCalendar.d.ts.map +1 -1
  57. package/build/types/dateLookup/yearCalendar/index.d.ts +1 -1
  58. package/build/types/dateLookup/yearCalendar/index.d.ts.map +1 -1
  59. package/build/types/dateLookup/yearCalendar/table/YearCalendarTable.d.ts +10 -26
  60. package/build/types/dateLookup/yearCalendar/table/YearCalendarTable.d.ts.map +1 -1
  61. package/build/types/dateLookup/yearCalendar/table/index.d.ts +1 -1
  62. package/build/types/dateLookup/yearCalendar/table/index.d.ts.map +1 -1
  63. package/build/types/dimmer/Dimmer.d.ts.map +1 -1
  64. package/build/types/index.d.ts +2 -2
  65. package/build/types/index.d.ts.map +1 -1
  66. package/build/types/inlineAlert/InlineAlert.d.ts +2 -4
  67. package/build/types/inlineAlert/InlineAlert.d.ts.map +1 -1
  68. package/build/types/inputs/SelectInput.d.ts.map +1 -1
  69. package/build/types/inputs/_BottomSheet.d.ts.map +1 -1
  70. package/build/types/loader/Loader.d.ts.map +1 -1
  71. package/build/types/logo/Logo.d.ts.map +1 -1
  72. package/build/types/phoneNumberInput/PhoneNumberInput.d.ts.map +1 -1
  73. package/build/types/popover/Popover.d.ts.map +1 -1
  74. package/build/types/segmentedControl/SegmentedControl.d.ts +2 -2
  75. package/build/types/segmentedControl/SegmentedControl.d.ts.map +1 -1
  76. package/build/types/select/Select.d.ts.map +1 -1
  77. package/build/types/statusIcon/StatusIcon.d.ts +1 -1
  78. package/build/types/statusIcon/StatusIcon.d.ts.map +1 -1
  79. package/build/types/stepper/deviceDetection.d.ts.map +1 -1
  80. package/build/types/uploadInput/uploadButton/UploadButton.d.ts.map +1 -1
  81. package/package.json +3 -3
  82. package/src/accordion/Accordion.story.tsx +1 -1
  83. package/src/alert/{Alert.spec.js → Alert.spec.tsx} +43 -40
  84. package/src/alert/Alert.story.tsx +1 -2
  85. package/src/alert/Alert.tsx +219 -0
  86. package/src/alert/index.ts +2 -0
  87. package/src/avatar/colors/colors.ts +1 -1
  88. package/src/body/Body.spec.tsx +1 -1
  89. package/src/body/Body.story.tsx +8 -8
  90. package/src/button/Button.tsx +6 -10
  91. package/src/checkbox/Checkbox.js +1 -1
  92. package/src/checkboxButton/CheckboxButton.spec.tsx +0 -1
  93. package/src/common/Option/Option.tsx +1 -1
  94. package/src/common/dateUtils/isWithinRange/isWithinRange.spec.ts +21 -0
  95. package/src/common/dateUtils/isWithinRange/isWithinRange.ts +2 -2
  96. package/src/common/dateUtils/moveToWithinRange/moveToWithinRange.ts +8 -4
  97. package/src/common/deviceDetection/deviceDetection.js +1 -1
  98. package/src/common/deviceDetection/deviceDetection.spec.js +4 -2
  99. package/src/common/propsValues/sentiment.ts +0 -10
  100. package/src/common/responsivePanel/ResponsivePanel.spec.js +11 -15
  101. package/src/dateLookup/DateLookup.state.spec.js +7 -0
  102. package/src/dateLookup/{DateLookup.story.js → DateLookup.story.tsx} +13 -14
  103. package/src/dateLookup/DateLookup.tests.story.tsx +70 -0
  104. package/src/dateLookup/{DateLookup.js → DateLookup.tsx} +115 -81
  105. package/src/dateLookup/dateHeader/{DateHeader.js → DateHeader.tsx} +15 -15
  106. package/src/dateLookup/dateTrigger/DateTrigger.spec.js +0 -22
  107. package/src/dateLookup/dateTrigger/{DateTrigger.js → DateTrigger.tsx} +15 -32
  108. package/src/dateLookup/dayCalendar/{DayCalendar.js → DayCalendar.tsx} +14 -21
  109. package/src/dateLookup/dayCalendar/table/{DayCalendarTable.js → DayCalendarTable.tsx} +26 -37
  110. package/src/dateLookup/getStartOfDay/{getStartOfDay.js → getStartOfDay.tsx} +1 -1
  111. package/src/dateLookup/index.ts +2 -0
  112. package/src/dateLookup/monthCalendar/{MonthCalendar.js → MonthCalendar.tsx} +19 -22
  113. package/src/dateLookup/monthCalendar/table/{MonthCalendarTable.js → MonthCalendarTable.tsx} +31 -30
  114. package/src/dateLookup/yearCalendar/{YearCalendar.js → YearCalendar.tsx} +18 -21
  115. package/src/dateLookup/yearCalendar/table/{YearCalendarTable.js → YearCalendarTable.tsx} +26 -28
  116. package/src/decision/Decision.spec.js +0 -1
  117. package/src/dimmer/Dimmer.tsx +6 -2
  118. package/src/i18n/th.json +2 -2
  119. package/src/index.ts +2 -2
  120. package/src/inlineAlert/InlineAlert.spec.tsx +0 -7
  121. package/src/inlineAlert/InlineAlert.story.tsx +8 -7
  122. package/src/inlineAlert/InlineAlert.tsx +19 -47
  123. package/src/inputs/InputGroup.tsx +3 -3
  124. package/src/inputs/SelectInput.tsx +2 -0
  125. package/src/inputs/_BottomSheet.tsx +21 -26
  126. package/src/inputs/_Popover.tsx +4 -4
  127. package/src/link/Link.story.tsx +16 -16
  128. package/src/loader/Loader.tsx +0 -1
  129. package/src/logo/Logo.css +16 -0
  130. package/src/logo/Logo.js +4 -9
  131. package/src/logo/Logo.less +16 -0
  132. package/src/logo/__snapshots__/Logo.spec.js.snap +104 -8
  133. package/src/main.css +16 -5
  134. package/src/main.less +0 -1
  135. package/src/moneyInput/MoneyInput.story.tsx +3 -3
  136. package/src/nudge/Nudge.spec.tsx +5 -5
  137. package/src/phoneNumberInput/PhoneNumberInput.tsx +2 -1
  138. package/src/phoneNumberInput/utils/cleanNumber/cleanNumber.ts +1 -1
  139. package/src/popover/Popover.tsx +2 -1
  140. package/src/promoCard/PromoCard.tsx +1 -1
  141. package/src/radioGroup/RadioGroup.spec.js +1 -1
  142. package/src/section/Section.story.tsx +2 -1
  143. package/src/segmentedControl/SegmentedControl.spec.tsx +88 -2
  144. package/src/segmentedControl/SegmentedControl.story.tsx +54 -16
  145. package/src/segmentedControl/SegmentedControl.tsx +21 -33
  146. package/src/select/Select.js +2 -3
  147. package/src/statusIcon/StatusIcon.tsx +14 -14
  148. package/src/stepper/deviceDetection.js +1 -2
  149. package/src/stepper/deviceDetection.spec.js +8 -3
  150. package/src/test-utils/index.js +1 -1
  151. package/src/test-utils/story-config.ts +1 -1
  152. package/src/title/Title.spec.tsx +1 -1
  153. package/src/typeahead/Typeahead.spec.js +4 -2
  154. package/src/upload/Upload.spec.js +8 -4
  155. package/src/uploadInput/uploadButton/UploadButton.tsx +1 -0
  156. package/build/styles/dynamicFieldDefinitionList/FormattedValue/FormattedValue.css +0 -5
  157. package/build/types/alert/withArrow/alertArrowPositions.d.ts +0 -9
  158. package/build/types/alert/withArrow/alertArrowPositions.d.ts.map +0 -1
  159. package/build/types/alert/withArrow/index.d.ts +0 -3
  160. package/build/types/alert/withArrow/index.d.ts.map +0 -1
  161. package/build/types/alert/withArrow/withArrow.d.ts +0 -11
  162. package/build/types/alert/withArrow/withArrow.d.ts.map +0 -1
  163. package/build/types/common/requirements.d.ts +0 -3
  164. package/build/types/common/requirements.d.ts.map +0 -1
  165. package/build/types/dynamicFieldDefinitionList/DynamicFieldDefinitionList.d.ts +0 -21
  166. package/build/types/dynamicFieldDefinitionList/DynamicFieldDefinitionList.d.ts.map +0 -1
  167. package/build/types/dynamicFieldDefinitionList/FormattedValue/FormattedValue.d.ts +0 -12
  168. package/build/types/dynamicFieldDefinitionList/FormattedValue/FormattedValue.d.ts.map +0 -1
  169. package/build/types/dynamicFieldDefinitionList/FormattedValue/index.d.ts +0 -2
  170. package/build/types/dynamicFieldDefinitionList/FormattedValue/index.d.ts.map +0 -1
  171. package/build/types/dynamicFieldDefinitionList/index.d.ts +0 -2
  172. package/build/types/dynamicFieldDefinitionList/index.d.ts.map +0 -1
  173. package/build/types/dynamicFieldDefinitionList/utils/createDefinitions.d.ts +0 -2
  174. package/build/types/dynamicFieldDefinitionList/utils/createDefinitions.d.ts.map +0 -1
  175. package/build/types/dynamicFieldDefinitionList/utils/text-format.d.ts +0 -2
  176. package/build/types/dynamicFieldDefinitionList/utils/text-format.d.ts.map +0 -1
  177. package/src/alert/Alert.js +0 -196
  178. package/src/alert/index.js +0 -1
  179. package/src/alert/withArrow/alertArrowPositions.ts +0 -9
  180. package/src/alert/withArrow/index.js +0 -2
  181. package/src/alert/withArrow/withArrow.js +0 -50
  182. package/src/alert/withArrow/withArrow.spec.js +0 -51
  183. package/src/dateLookup/index.js +0 -1
  184. package/src/dynamicFieldDefinitionList/DynamicFieldDefinitionList.js +0 -41
  185. package/src/dynamicFieldDefinitionList/DynamicFieldDefinitionList.spec.js +0 -21
  186. package/src/dynamicFieldDefinitionList/DynamicFieldDefinitionList.story.js +0 -134
  187. package/src/dynamicFieldDefinitionList/FormattedValue/FormattedValue.css +0 -5
  188. package/src/dynamicFieldDefinitionList/FormattedValue/FormattedValue.js +0 -73
  189. package/src/dynamicFieldDefinitionList/FormattedValue/FormattedValue.less +0 -4
  190. package/src/dynamicFieldDefinitionList/FormattedValue/FormattedValue.spec.js +0 -200
  191. package/src/dynamicFieldDefinitionList/FormattedValue/index.js +0 -1
  192. package/src/dynamicFieldDefinitionList/index.js +0 -1
  193. package/src/dynamicFieldDefinitionList/utils/createDefinitions.js +0 -33
  194. package/src/dynamicFieldDefinitionList/utils/createDefinitions.spec.js +0 -83
  195. package/src/dynamicFieldDefinitionList/utils/text-format.js +0 -46
  196. package/src/dynamicFieldDefinitionList/utils/text-format.spec.js +0 -43
  197. /package/src/dateLookup/{DateLookup.messages.js → DateLookup.messages.ts} +0 -0
  198. /package/src/dateLookup/dateHeader/{index.js → index.ts} +0 -0
  199. /package/src/dateLookup/dateTrigger/{index.js → index.ts} +0 -0
  200. /package/src/dateLookup/dayCalendar/{index.js → index.ts} +0 -0
  201. /package/src/dateLookup/dayCalendar/table/{index.js → index.ts} +0 -0
  202. /package/src/dateLookup/getStartOfDay/{index.js → index.ts} +0 -0
  203. /package/src/dateLookup/monthCalendar/{index.js → index.ts} +0 -0
  204. /package/src/dateLookup/monthCalendar/table/{index.js → index.ts} +0 -0
  205. /package/src/dateLookup/yearCalendar/{index.js → index.ts} +0 -0
  206. /package/src/dateLookup/yearCalendar/table/{index.js → index.ts} +0 -0
@@ -2,8 +2,10 @@ import { isIosDevice } from './deviceDetection';
2
2
 
3
3
  describe('Device detection', () => {
4
4
  function fakeUserAgent(userAgent) {
5
- navigator.__defineGetter__('userAgent', () => userAgent);
6
- // need to use this instead of defineProperty, as it's blocked from overriding
5
+ Object.defineProperty(navigator, 'userAgent', {
6
+ value: userAgent,
7
+ configurable: true,
8
+ });
7
9
  }
8
10
 
9
11
  beforeEach(() => {
@@ -18,13 +18,3 @@ export enum Sentiment {
18
18
  */
19
19
  SUCCESS = 'success',
20
20
  }
21
-
22
- export type SentimentString =
23
- | 'negative'
24
- | 'neutral'
25
- | 'positive'
26
- | 'warning'
27
- | 'pending'
28
- | 'info'
29
- | 'error'
30
- | 'success';
@@ -1,5 +1,5 @@
1
1
  import { Breakpoint, Position } from '..';
2
- import { mockMatchMedia, render, waitFor } from '../../test-utils';
2
+ import { mockMatchMedia, render } from '../../test-utils';
3
3
 
4
4
  import ResponsivePanel from './ResponsivePanel';
5
5
 
@@ -45,13 +45,11 @@ describe('ResponsivePanel', () => {
45
45
 
46
46
  describe('on desktop', () => {
47
47
  it('renders Panel', async () => {
48
- await waitFor(() => {
49
- ({ container } = render(
50
- <ResponsivePanel {...props} intialOpen>
51
- children
52
- </ResponsivePanel>,
53
- ));
54
- });
48
+ ({ container } = render(
49
+ <ResponsivePanel {...props} intialOpen>
50
+ children
51
+ </ResponsivePanel>,
52
+ ));
55
53
 
56
54
  expect(container).toMatchSnapshot();
57
55
  });
@@ -63,13 +61,11 @@ describe('ResponsivePanel', () => {
63
61
  });
64
62
 
65
63
  it('renders bottomSheet', async () => {
66
- await waitFor(() => {
67
- ({ container } = render(
68
- <ResponsivePanel {...props} open>
69
- children
70
- </ResponsivePanel>,
71
- ));
72
- });
64
+ ({ container } = render(
65
+ <ResponsivePanel {...props} open>
66
+ children
67
+ </ResponsivePanel>,
68
+ ));
73
69
 
74
70
  expect(container).toMatchSnapshot();
75
71
  });
@@ -66,4 +66,11 @@ describe('DateLookup state', () => {
66
66
  DateLookup.getDerivedStateFromProps(props, defaultState);
67
67
  expect(onChange).toHaveBeenCalledWith(new Date(2018, 10, 1));
68
68
  });
69
+
70
+ it('updates viewMonth and viewYear to date within min/max on Clear', () => {
71
+ const props = { value: null, min: new Date(2018, 11, 26), max: new Date(2018, 11, 26) };
72
+ const newState = DateLookup.getDerivedStateFromProps(props, defaultState);
73
+ expect(newState.viewMonth).toBe(11);
74
+ expect(newState.viewYear).toBe(2018);
75
+ });
69
76
  });
@@ -1,5 +1,5 @@
1
- import { boolean, select, date, text } from '@storybook/addon-knobs';
2
- import { userEvent, within } from '@storybook/test';
1
+ import { boolean, select, text, date } from '@storybook/addon-knobs';
2
+ import { userEvent } from '@storybook/test';
3
3
  import { useState } from 'react';
4
4
 
5
5
  import { Size } from '../common';
@@ -13,21 +13,21 @@ export default {
13
13
  };
14
14
 
15
15
  const epoch = new Date('2011-01-01');
16
- let theFuture = new Date(epoch);
16
+ const theFuture = new Date(epoch);
17
17
  theFuture.setUTCDate(epoch.getUTCDate() + 10);
18
- let thePast = new Date(epoch);
18
+ const thePast = new Date(epoch);
19
19
  thePast.setUTCDate(epoch.getUTCDate() - 10);
20
+ const size = select('size', Object.values([Size.SMALL, Size.MEDIUM, Size.LARGE]), Size.MEDIUM);
20
21
 
21
22
  export const Basic = () => {
22
- const [value, setValue] = useState(epoch);
23
+ const [value, setValue] = useState<Date | null>(epoch);
23
24
  const disabled = boolean('disabled', false);
24
25
  const label = text('label', 'label');
25
- const monthFormat = select('monthFormat', ['long', 'short']);
26
+ const monthFormat = select('monthFormat', ['long', 'short'], 'long');
26
27
  const placeholder = text('placeholder', 'placeholder');
27
- const size = select('size', Object.values(Size), Size.MEDIUM);
28
28
  const id = text('id', 'date-lookup');
29
29
 
30
- const clearable = boolean('clearable', false);
30
+ const clearable = boolean('clearable', true);
31
31
 
32
32
  return (
33
33
  <DateLookup
@@ -35,6 +35,7 @@ export const Basic = () => {
35
35
  id={id}
36
36
  label={label}
37
37
  min={thePast}
38
+ max={theFuture}
38
39
  monthFormat={monthFormat}
39
40
  placeholder={placeholder}
40
41
  size={size}
@@ -48,10 +49,9 @@ export const Basic = () => {
48
49
  );
49
50
  };
50
51
 
51
- Basic.play = async ({ canvasElement }) => {
52
+ Basic.play = async ({ canvasElement }: { canvasElement: HTMLElement }) => {
52
53
  // testing focus state on keyboard nav
53
- const canvas = within(canvasElement);
54
- await userEvent.tab(canvas.getByRole('button'));
54
+ await userEvent.tab();
55
55
  await userEvent.keyboard(' ');
56
56
  };
57
57
 
@@ -61,12 +61,11 @@ export const Basic400Zoom = storyConfig(
61
61
  );
62
62
 
63
63
  export const RightAligned = () => {
64
- const [value, setValue] = useState(epoch);
64
+ const [value, setValue] = useState<Date | null>(epoch);
65
65
  const disabled = boolean('disabled', false);
66
66
  const label = text('label', 'label');
67
- const monthFormat = select('monthFormat', ['long', 'short']);
67
+ const monthFormat = select('monthFormat', ['long', 'short'], 'long');
68
68
  const placeholder = text('placeholder', 'placeholder');
69
- const size = select('size', Object.values(Size), Size.MEDIUM);
70
69
 
71
70
  const minvalue = date('minvalue', thePast);
72
71
  const maxvalue = date('maxvalue', theFuture);
@@ -0,0 +1,70 @@
1
+ import { ReactRenderer, Story } from '@storybook/react';
2
+ import { expect, userEvent, within } from '@storybook/test';
3
+ import { PlayFunctionContext } from '@storybook/types';
4
+ import { useState } from 'react';
5
+
6
+ import DateLookup from './DateLookup';
7
+
8
+ export default {
9
+ component: DateLookup,
10
+ title: 'Forms/DateLookup/Tests',
11
+ };
12
+
13
+ const Template: Story<DateLookup> = () => {
14
+ const [value, setValue] = useState<Date | null>(new Date(1987, 0, 10, 12, 0, 0));
15
+
16
+ return (
17
+ <DateLookup
18
+ value={value}
19
+ clearable
20
+ placeholder="placeholder"
21
+ onChange={(v) => {
22
+ setValue(v);
23
+ }}
24
+ />
25
+ );
26
+ };
27
+
28
+ export const ClearSpace = Template.bind({});
29
+
30
+ ClearSpace.play = async ({
31
+ canvasElement,
32
+ step,
33
+ }: PlayFunctionContext<ReactRenderer, DateLookup>) => {
34
+ const canvas = within(canvasElement);
35
+
36
+ await step('space can activate clear button', async () => {
37
+ // focus on clear button
38
+ const date = await canvas.findByText('January 10, 1987');
39
+ await expect(date).toBeInTheDocument();
40
+ await userEvent.tab();
41
+ await userEvent.tab();
42
+
43
+ // clear with space
44
+ await userEvent.keyboard(' ');
45
+ const placeholder = canvas.getByText('placeholder');
46
+ await expect(placeholder).toBeInTheDocument();
47
+ });
48
+ };
49
+
50
+ export const ClearEnter = Template.bind({});
51
+
52
+ ClearEnter.play = async ({
53
+ canvasElement,
54
+ step,
55
+ }: PlayFunctionContext<ReactRenderer, DateLookup>) => {
56
+ const canvas = within(canvasElement);
57
+
58
+ await step('enter can activate clear button', async () => {
59
+ // focus on clear button
60
+ const date = await canvas.findByText('January 10, 1987');
61
+ await expect(date).toBeInTheDocument();
62
+ await userEvent.tab();
63
+ await userEvent.tab();
64
+
65
+ // clear with space
66
+ await userEvent.keyboard('{enter}');
67
+ const placeholder = canvas.getByText('placeholder');
68
+ await expect(placeholder).toBeInTheDocument();
69
+ });
70
+ };
@@ -1,8 +1,15 @@
1
1
  import classNames from 'classnames';
2
- import PropTypes from 'prop-types';
3
- import { createRef, PureComponent } from 'react';
2
+ import { createRef, PureComponent, KeyboardEvent } from 'react';
4
3
 
5
- import { Size, MonthFormat, Position, Breakpoint } from '../common';
4
+ import {
5
+ Size,
6
+ MonthFormat,
7
+ Position,
8
+ Breakpoint,
9
+ SizeSmall,
10
+ SizeMedium,
11
+ SizeLarge,
12
+ } from '../common';
6
13
  import { isWithinRange, moveToWithinRange } from '../common/dateUtils';
7
14
  import ResponsivePanel from '../common/responsivePanel';
8
15
 
@@ -12,18 +19,55 @@ import { getStartOfDay } from './getStartOfDay';
12
19
  import MonthCalendar from './monthCalendar';
13
20
  import YearCalendar from './yearCalendar';
14
21
 
15
- const MODE = {
16
- DAY: 'day',
17
- MONTH: 'month',
18
- YEAR: 'year',
19
- };
22
+ export interface DateLookupProps {
23
+ id?: string;
24
+ value: Date | null;
25
+ min?: Date | null;
26
+ max?: Date | null;
27
+ size?: SizeSmall | SizeMedium | SizeLarge;
28
+ placeholder?: string;
29
+ label?: string;
30
+ 'aria-labelledby'?: string;
31
+ monthFormat?: `${MonthFormat}`;
32
+ disabled?: boolean;
33
+ clearable?: boolean;
34
+ onChange: (date: Date | null) => void;
35
+ onFocus?: () => void;
36
+ onBlur?: () => void;
37
+ }
38
+
39
+ interface DateLookupState {
40
+ selectedDate: Date | null;
41
+ originalDate: Date | null;
42
+ min: Date | null;
43
+ max: Date | null;
44
+ viewMonth: number;
45
+ viewYear: number;
46
+ open: boolean;
47
+ mode: 'day' | 'month' | 'year';
48
+ isMobile: boolean;
49
+ }
50
+
51
+ class DateLookup extends PureComponent<DateLookupProps, DateLookupState> {
52
+ declare props: DateLookupProps &
53
+ Required<Pick<DateLookupProps, keyof typeof DateLookup.defaultProps>>;
20
54
 
21
- class DateLookup extends PureComponent {
22
- element = createRef();
55
+ static defaultProps = {
56
+ value: null,
57
+ min: null,
58
+ max: null,
59
+ size: Size.MEDIUM,
60
+ placeholder: '',
61
+ label: '',
62
+ monthFormat: MonthFormat.LONG,
63
+ disabled: false,
64
+ clearable: false,
65
+ } satisfies Partial<DateLookupProps>;
23
66
 
24
- dropdown = createRef();
67
+ element = createRef<HTMLDivElement>();
68
+ dropdown = createRef<HTMLDivElement>();
25
69
 
26
- constructor(props) {
70
+ constructor(props: DateLookup['props']) {
27
71
  super(props);
28
72
  this.state = {
29
73
  selectedDate: getStartOfDay(props.value),
@@ -33,40 +77,41 @@ class DateLookup extends PureComponent {
33
77
  viewMonth: (props.value || new Date()).getMonth(),
34
78
  viewYear: (props.value || new Date()).getFullYear(),
35
79
  open: false,
36
- mode: MODE.DAY,
80
+ mode: 'day',
37
81
  isMobile: false,
38
82
  };
39
83
  }
40
84
 
41
- static getDerivedStateFromProps(props, state) {
85
+ static getDerivedStateFromProps(props: DateLookup['props'], state: DateLookupState) {
42
86
  const propsSelected = getStartOfDay(props.value);
43
87
  const propsMin = getStartOfDay(props.min);
44
88
  const propsMax = getStartOfDay(props.max);
45
- const hasSelectedChanged = +state.selectedDate !== +propsSelected;
46
- const hasMinChanged = +state.min !== +propsMin;
47
- const hasMaxChanged = +state.max !== +propsMax;
89
+ const hasSelectedChanged = state.selectedDate?.getTime() !== propsSelected?.getTime();
90
+ const hasMinChanged = state.min?.getTime() !== propsMin?.getTime();
91
+ const hasMaxChanged = state.max?.getTime() !== propsMax?.getTime();
48
92
  if (hasSelectedChanged || hasMinChanged || hasMaxChanged) {
49
93
  const selectedDate = hasSelectedChanged ? propsSelected : state.selectedDate;
50
94
  const min = hasMinChanged ? propsMin : state.min;
51
95
  const max = hasMaxChanged ? propsMax : state.max;
52
- // make sure that selected date is within allowed range
53
- if (!isWithinRange(selectedDate, min, max)) {
96
+ if (selectedDate && !isWithinRange(selectedDate, min, max)) {
54
97
  props.onChange(moveToWithinRange(selectedDate, min, max));
55
98
  return null;
56
99
  }
57
- const viewMonth = (selectedDate || new Date()).getMonth();
58
- const viewYear = (selectedDate || new Date()).getFullYear();
100
+ const viewDateThatIsWithinRange: Date =
101
+ selectedDate || ((min || max) && moveToWithinRange(new Date(), min, max)) || new Date();
102
+ const viewMonth = viewDateThatIsWithinRange.getMonth();
103
+ const viewYear = viewDateThatIsWithinRange.getFullYear();
59
104
  return { selectedDate, min, max, viewMonth, viewYear };
60
105
  }
61
106
  return null;
62
107
  }
63
108
 
64
- componentDidUpdate(previousProps) {
65
- if (+this.props.value !== +previousProps.value && this.state.open) {
109
+ componentDidUpdate(previousProps: DateLookupProps) {
110
+ if (this.props.value?.getTime() !== previousProps.value?.getTime() && this.state.open) {
66
111
  this.focusOn('.active');
67
112
  }
68
- this.mediaQuery = window.matchMedia(`(max-width: ${Breakpoint.SMALL}px)`);
69
- this.setState({ isMobile: this.mediaQuery.matches });
113
+ const mediaQuery = window.matchMedia(`(max-width: ${Breakpoint.SMALL}px)`);
114
+ this.setState({ isMobile: mediaQuery.matches });
70
115
  }
71
116
 
72
117
  componentWillUnmount() {
@@ -77,7 +122,7 @@ class DateLookup extends PureComponent {
77
122
  open = () => {
78
123
  const { onFocus } = this.props;
79
124
 
80
- this.setState({ open: true, mode: MODE.DAY });
125
+ this.setState({ open: true, mode: 'day' });
81
126
  if (onFocus) {
82
127
  onFocus();
83
128
  }
@@ -99,7 +144,7 @@ class DateLookup extends PureComponent {
99
144
  }
100
145
  };
101
146
 
102
- handleKeyDown = (event) => {
147
+ handleKeyDown = (event: KeyboardEvent<HTMLDivElement>) => {
103
148
  const { open, originalDate } = this.state;
104
149
  switch (event.key) {
105
150
  case 'ArrowLeft':
@@ -146,29 +191,31 @@ class DateLookup extends PureComponent {
146
191
  }
147
192
  };
148
193
 
149
- adjustDate = (daysToAdd, monthsToAdd, yearsToAdd) => {
194
+ adjustDate = (daysToAdd: number, monthsToAdd: number, yearsToAdd: number) => {
150
195
  const { selectedDate, min, max, mode, originalDate } = this.state;
151
196
  if (originalDate === null) {
152
197
  this.setState({ originalDate: selectedDate });
153
198
  }
154
- let date;
199
+ let date: Date | null;
155
200
  if (selectedDate) {
156
201
  date = new Date(
157
- mode === MODE.YEAR ? selectedDate.getFullYear() + yearsToAdd : selectedDate.getFullYear(),
158
- mode === MODE.MONTH ? selectedDate.getMonth() + monthsToAdd : selectedDate.getMonth(),
159
- mode === MODE.DAY ? selectedDate.getDate() + daysToAdd : selectedDate.getDate(),
202
+ mode === 'year' ? selectedDate.getFullYear() + yearsToAdd : selectedDate.getFullYear(),
203
+ mode === 'month' ? selectedDate.getMonth() + monthsToAdd : selectedDate.getMonth(),
204
+ mode === 'day' ? selectedDate.getDate() + daysToAdd : selectedDate.getDate(),
160
205
  );
161
206
  } else {
162
207
  date = getStartOfDay(new Date());
163
208
  }
164
- date = moveToWithinRange(date, min, max);
165
- if (+date !== +selectedDate) {
209
+ if (date) {
210
+ date = moveToWithinRange(date, min, max);
211
+ }
212
+ if (date?.getTime() !== selectedDate?.getTime()) {
166
213
  this.props.onChange(date);
167
214
  }
168
215
  };
169
216
 
170
- focusOn = (preferredElement, fallbackElement) => {
171
- const element = this.element.current.querySelector(preferredElement);
217
+ focusOn = (preferredElement: string, fallbackElement?: string) => {
218
+ const element = this.element.current?.querySelector(preferredElement) as HTMLElement | null;
172
219
  if (element) {
173
220
  element.focus();
174
221
  } else if (fallbackElement) {
@@ -176,19 +223,19 @@ class DateLookup extends PureComponent {
176
223
  }
177
224
  };
178
225
 
179
- switchMode = (mode) => {
226
+ switchMode = (mode: 'day' | 'month' | 'year') => {
180
227
  this.setState({ mode }, () => {
181
228
  this.focusOn('.active', '.today');
182
229
  });
183
230
  };
184
231
 
185
- switchToDays = () => this.switchMode(MODE.DAY);
232
+ switchToDays = () => this.switchMode('day');
186
233
 
187
- switchToMonths = () => this.switchMode(MODE.MONTH);
234
+ switchToMonths = () => this.switchMode('month');
188
235
 
189
- switchToYears = () => this.switchMode(MODE.YEAR);
236
+ switchToYears = () => this.switchMode('year');
190
237
 
191
- handleSelectedDateUpdate = (selectedDate) => {
238
+ handleSelectedDateUpdate = (selectedDate: Date) => {
192
239
  this.setState({ selectedDate }, () => {
193
240
  this.props.onChange(selectedDate);
194
241
  this.close();
@@ -205,25 +252,38 @@ class DateLookup extends PureComponent {
205
252
  const { placeholder, monthFormat } = this.props;
206
253
  return (
207
254
  <div className={classNames({ 'p-a-1': !isMobile })}>
208
- {mode === MODE.DAY && (
255
+ {mode === 'day' && (
209
256
  <DayCalendar
210
- {...{ selectedDate, min, max, viewMonth, viewYear, monthFormat }}
257
+ selectedDate={selectedDate}
258
+ min={min}
259
+ max={max}
260
+ viewMonth={viewMonth}
261
+ viewYear={viewYear}
262
+ monthFormat={monthFormat}
211
263
  onSelect={this.handleSelectedDateUpdate}
212
264
  onLabelClick={this.switchToYears}
213
265
  onViewDateUpdate={this.handleViewDateUpdate}
214
266
  />
215
267
  )}
216
- {mode === MODE.MONTH && (
268
+ {mode === 'month' && (
217
269
  <MonthCalendar
218
- {...{ selectedDate, min, max, viewYear, placeholder }}
270
+ selectedDate={selectedDate}
271
+ min={min}
272
+ max={max}
273
+ viewYear={viewYear}
274
+ placeholder={placeholder}
219
275
  onSelect={this.switchToDays}
220
276
  onLabelClick={this.switchToYears}
221
277
  onViewDateUpdate={this.handleViewDateUpdate}
222
278
  />
223
279
  )}
224
- {mode === MODE.YEAR && (
280
+ {mode === 'year' && (
225
281
  <YearCalendar
226
- {...{ selectedDate, min, max, viewYear, placeholder }}
282
+ selectedDate={selectedDate}
283
+ min={min}
284
+ max={max}
285
+ viewYear={viewYear}
286
+ placeholder={placeholder}
227
287
  onSelect={this.switchToMonths}
228
288
  onViewDateUpdate={this.handleViewDateUpdate}
229
289
  />
@@ -251,7 +311,7 @@ class DateLookup extends PureComponent {
251
311
  value,
252
312
  } = this.props;
253
313
  return (
254
- <div // eslint-disable-line jsx-a11y/no-static-element-interactions
314
+ <div
255
315
  ref={this.element}
256
316
  id={this.props.id}
257
317
  aria-labelledby={ariaLabelledBy}
@@ -259,9 +319,14 @@ class DateLookup extends PureComponent {
259
319
  onKeyDown={this.handleKeyDown}
260
320
  >
261
321
  <DateTrigger
262
- {...{ selectedDate, size, placeholder, label, monthFormat, disabled }}
322
+ selectedDate={selectedDate}
323
+ size={size}
324
+ placeholder={placeholder}
325
+ label={label}
326
+ monthFormat={monthFormat}
327
+ disabled={disabled || false}
263
328
  onClick={this.open}
264
- onClear={!disabled && clearable && value ? this.handleClear : null}
329
+ onClear={!disabled && clearable && value ? this.handleClear : undefined}
265
330
  />
266
331
  <ResponsivePanel
267
332
  anchorRef={this.element}
@@ -277,35 +342,4 @@ class DateLookup extends PureComponent {
277
342
  }
278
343
  }
279
344
 
280
- DateLookup.propTypes = {
281
- id: PropTypes.string,
282
- value: PropTypes.instanceOf(Date),
283
- min: PropTypes.instanceOf(Date),
284
- max: PropTypes.instanceOf(Date),
285
- size: PropTypes.oneOf(['sm', 'md', 'lg']),
286
- placeholder: PropTypes.string,
287
- label: PropTypes.string,
288
- 'aria-labelledby': PropTypes.string,
289
- monthFormat: PropTypes.oneOf(['long', 'short']),
290
- disabled: PropTypes.bool,
291
- onChange: PropTypes.func.isRequired,
292
- onFocus: PropTypes.func,
293
- onBlur: PropTypes.func,
294
- clearable: PropTypes.bool,
295
- };
296
-
297
- DateLookup.defaultProps = {
298
- value: null,
299
- min: null,
300
- max: null,
301
- size: Size.MEDIUM,
302
- placeholder: '',
303
- label: '',
304
- monthFormat: MonthFormat.LONG,
305
- disabled: false,
306
- onFocus: null,
307
- onBlur: null,
308
- clearable: false,
309
- };
310
-
311
345
  export default DateLookup;
@@ -1,5 +1,4 @@
1
1
  import classNames from 'classnames';
2
- import PropTypes from 'prop-types';
3
2
  import { useIntl } from 'react-intl';
4
3
 
5
4
  import Chevron from '../../chevron';
@@ -10,7 +9,21 @@ import messages from '../DateLookup.messages';
10
9
 
11
10
  const buttonClasses = 'btn-link p-a-0 text-no-decoration np-text-body-large-bold rounded-sm';
12
11
 
13
- const DateHeader = ({ label, onLabelClick, onPreviousClick, onNextClick, dateMode }) => {
12
+ interface DateHeaderProps {
13
+ label?: string;
14
+ onLabelClick?: () => void;
15
+ onPreviousClick: () => void;
16
+ onNextClick: () => void;
17
+ dateMode: string;
18
+ }
19
+
20
+ const DateHeader: React.FC<DateHeaderProps> = ({
21
+ label,
22
+ onLabelClick,
23
+ onPreviousClick,
24
+ onNextClick,
25
+ dateMode,
26
+ }) => {
14
27
  const intl = useIntl();
15
28
  const { isMobile } = useLayout();
16
29
  return (
@@ -59,17 +72,4 @@ const DateHeader = ({ label, onLabelClick, onPreviousClick, onNextClick, dateMod
59
72
  );
60
73
  };
61
74
 
62
- DateHeader.propTypes = {
63
- label: PropTypes.string,
64
- onLabelClick: PropTypes.func,
65
- onPreviousClick: PropTypes.func.isRequired,
66
- onNextClick: PropTypes.func.isRequired,
67
- dateMode: PropTypes.string,
68
- };
69
-
70
- DateHeader.defaultProps = {
71
- label: null,
72
- onLabelClick: () => {},
73
- };
74
-
75
75
  export default DateHeader;
@@ -2,7 +2,6 @@ import * as formatting from '@transferwise/formatting';
2
2
  import { shallow } from 'enzyme';
3
3
 
4
4
  import Chevron from '../../chevron';
5
- import { fakeKeyDownEventForKey } from '../../common/fakeEvents';
6
5
 
7
6
  import DateTrigger from '.';
8
7
 
@@ -116,27 +115,6 @@ describe('DateTrigger', () => {
116
115
  expect(clearButton()).toHaveLength(1);
117
116
  });
118
117
 
119
- it('calls onClear when user press Space', () => {
120
- const onClear = jest.fn();
121
- component.setProps({ onClear });
122
- clearButton().simulate('keypress', fakeKeyDownEventForKey(' '));
123
- expect(onClear).toHaveBeenCalledTimes(1);
124
- });
125
-
126
- it('calls onClear when user press Enter', () => {
127
- const onClear = jest.fn();
128
- component.setProps({ onClear });
129
- clearButton().simulate('keypress', fakeKeyDownEventForKey('Enter'));
130
- expect(onClear).toHaveBeenCalledTimes(1);
131
- });
132
-
133
- it(`doesn't call onClear when user press a random key`, () => {
134
- const onClear = jest.fn();
135
- component.setProps({ onClear });
136
- clearButton().simulate('keypress', fakeKeyDownEventForKey('r'));
137
- expect(onClear).not.toHaveBeenCalled();
138
- });
139
-
140
118
  const button = () => component.find('.np-date-trigger');
141
119
  const clearButton = () => component.find('.clear-btn');
142
120
  const chevron = () => component.find(Chevron);