@transferwise/components 45.15.0 → 45.16.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 (111) hide show
  1. package/build/i18n/en.json +8 -0
  2. package/build/index.esm.js +550 -488
  3. package/build/index.esm.js.map +1 -1
  4. package/build/index.js +549 -487
  5. package/build/index.js.map +1 -1
  6. package/build/main.css +1 -1
  7. package/build/styles/dateLookup/DateLookup.css +1 -1
  8. package/build/styles/main.css +1 -1
  9. package/build/styles/popover/Popover.css +1 -1
  10. package/build/styles/promoCard/PromoCard.css +1 -1
  11. package/build/types/alert/Alert.d.ts.map +1 -1
  12. package/build/types/avatarWrapper/AvatarWrapper.d.ts +14 -5
  13. package/build/types/avatarWrapper/AvatarWrapper.d.ts.map +1 -1
  14. package/build/types/common/Option/Option.d.ts.map +1 -1
  15. package/build/types/common/card/Card.d.ts.map +1 -1
  16. package/build/types/common/card/index.d.ts +1 -0
  17. package/build/types/common/card/index.d.ts.map +1 -1
  18. package/build/types/common/focusBoundary/FocusBoundary.d.ts +2 -2
  19. package/build/types/common/focusBoundary/FocusBoundary.d.ts.map +1 -1
  20. package/build/types/dateInput/DateInput.d.ts +2 -0
  21. package/build/types/dateInput/DateInput.d.ts.map +1 -1
  22. package/build/types/dateLookup/DateLookup.messages.d.ts +65 -0
  23. package/build/types/dateLookup/DateLookup.messages.d.ts.map +1 -0
  24. package/build/types/dateLookup/dateHeader/DateHeader.d.ts +3 -1
  25. package/build/types/dateLookup/dateHeader/DateHeader.d.ts.map +1 -1
  26. package/build/types/dateLookup/tableLink/TableLink.d.ts +4 -26
  27. package/build/types/dateLookup/tableLink/TableLink.d.ts.map +1 -1
  28. package/build/types/dateLookup/yearCalendar/YearCalendar.d.ts +4 -29
  29. package/build/types/dateLookup/yearCalendar/YearCalendar.d.ts.map +1 -1
  30. package/build/types/inputs/_BottomSheet.d.ts.map +1 -1
  31. package/build/types/inputs/_Popover.d.ts.map +1 -1
  32. package/build/types/phoneNumberInput/PhoneNumberInput.d.ts.map +1 -1
  33. package/build/types/promoCard/PromoCard.d.ts +9 -3
  34. package/build/types/promoCard/PromoCard.d.ts.map +1 -1
  35. package/build/types/promoCard/PromoCardIndicator.d.ts +5 -3
  36. package/build/types/promoCard/PromoCardIndicator.d.ts.map +1 -1
  37. package/build/types/snackbar/Snackbar.d.ts.map +1 -1
  38. package/build/types/tabs/Tabs.d.ts.map +1 -1
  39. package/build/types/upload/steps/completeStep/completeStep.d.ts.map +1 -1
  40. package/build/types/upload/steps/processingStep/processingStep.d.ts.map +1 -1
  41. package/package.json +4 -3
  42. package/src/accordion/AccordionItem/__snapshots__/AccordionItem.spec.js.snap +6 -6
  43. package/src/alert/Alert.js +11 -9
  44. package/src/alert/Alert.spec.js +22 -13
  45. package/src/alert/withArrow/withArrow.spec.js +4 -4
  46. package/src/avatarWrapper/AvatarWrapper.tsx +20 -8
  47. package/src/avatarWrapper/__snapshots__/AvatarWrapper.spec.tsx.snap +1 -1
  48. package/src/card/Card.spec.js +2 -2
  49. package/src/common/Option/Option.tsx +1 -7
  50. package/src/common/bottomSheet/__snapshots__/BottomSheet.spec.tsx.snap +8 -1
  51. package/src/common/card/Card.tsx +6 -2
  52. package/src/common/card/index.ts +1 -0
  53. package/src/common/focusBoundary/FocusBoundary.tsx +9 -32
  54. package/src/dateInput/DateInput.js +6 -0
  55. package/src/dateInput/DateInput.story.tsx +2 -0
  56. package/src/dateLookup/DateLookup.css +1 -1
  57. package/src/dateLookup/DateLookup.keyboardEvents.spec.js +3 -3
  58. package/src/dateLookup/DateLookup.less +4 -0
  59. package/src/dateLookup/DateLookup.messages.js +44 -0
  60. package/src/dateLookup/DateLookup.testingLibrary.spec.js +39 -0
  61. package/src/dateLookup/dateHeader/DateHeader.js +48 -26
  62. package/src/dateLookup/dateHeader/DateHeader.spec.js +37 -0
  63. package/src/dateLookup/dayCalendar/DayCalendar.js +3 -1
  64. package/src/dateLookup/dayCalendar/DayCalendar.spec.js +7 -1
  65. package/src/dateLookup/dayCalendar/table/DayCalendarTable.js +7 -3
  66. package/src/dateLookup/dayCalendar/table/DayCalendarTable.spec.js +1 -0
  67. package/src/dateLookup/monthCalendar/MonthCalendar.js +3 -1
  68. package/src/dateLookup/monthCalendar/MonthCalendar.spec.js +7 -1
  69. package/src/dateLookup/monthCalendar/table/MonthCalendarTable.spec.js +4 -5
  70. package/src/dateLookup/tableLink/TableLink.js +25 -3
  71. package/src/dateLookup/tableLink/TableLink.spec.js +66 -4
  72. package/src/dateLookup/yearCalendar/YearCalendar.js +16 -3
  73. package/src/dateLookup/yearCalendar/YearCalendar.spec.js +14 -1
  74. package/src/dateLookup/yearCalendar/table/YearCalendarTable.spec.js +4 -5
  75. package/src/i18n/en.json +8 -0
  76. package/src/inputs/SelectInput.story.tsx +36 -9
  77. package/src/inputs/_BottomSheet.less +1 -1
  78. package/src/inputs/_BottomSheet.tsx +57 -52
  79. package/src/inputs/_Popover.less +1 -1
  80. package/src/inputs/_Popover.tsx +31 -24
  81. package/src/listItem/ListItem.story.tsx +1 -1
  82. package/src/main.css +1 -1
  83. package/src/phoneNumberInput/PhoneNumberInput.js +1 -0
  84. package/src/popover/Popover.css +1 -1
  85. package/src/promoCard/PromoCard.css +1 -1
  86. package/src/promoCard/PromoCard.less +9 -9
  87. package/src/promoCard/PromoCard.spec.tsx +1 -0
  88. package/src/promoCard/PromoCard.story.tsx +90 -30
  89. package/src/promoCard/PromoCard.tsx +69 -22
  90. package/src/promoCard/PromoCardIndicator.tsx +20 -8
  91. package/src/snackbar/Snackbar.js +6 -1
  92. package/src/snackbar/Snackbar.spec.js +1 -3
  93. package/src/tabs/Tabs.js +2 -1
  94. package/src/upload/Upload.js +1 -1
  95. package/src/upload/steps/completeStep/completeStep.js +4 -1
  96. package/src/upload/steps/processingStep/processingStep.js +1 -0
  97. package/src/uploadInput/uploadItem/UploadItem.tsx +1 -1
  98. package/build/types/common/focusBoundary/utils/getFocusableElements.d.ts +0 -2
  99. package/build/types/common/focusBoundary/utils/getFocusableElements.d.ts.map +0 -1
  100. package/build/types/common/focusBoundary/utils/index.d.ts +0 -3
  101. package/build/types/common/focusBoundary/utils/index.d.ts.map +0 -1
  102. package/build/types/common/focusBoundary/utils/resetFocus.d.ts +0 -2
  103. package/build/types/common/focusBoundary/utils/resetFocus.d.ts.map +0 -1
  104. package/src/common/focusBoundary/FocusBoundary.spec.tsx +0 -66
  105. package/src/common/focusBoundary/__snapshots__/FocusBoundary.spec.tsx.snap +0 -16
  106. package/src/common/focusBoundary/utils/getFocusableElements.js +0 -25
  107. package/src/common/focusBoundary/utils/getFocusableElements.spec.js +0 -51
  108. package/src/common/focusBoundary/utils/index.js +0 -2
  109. package/src/common/focusBoundary/utils/resetFocus.js +0 -23
  110. package/src/common/focusBoundary/utils/resetFocus.spec.js +0 -103
  111. package/src/snackbar/__snapshots__/Snackbar.spec.js.snap +0 -5
@@ -61,7 +61,7 @@ describe('Alert', () => {
61
61
  <Alert message={message} />
62
62
  </ThemeProvider>,
63
63
  ));
64
- component = screen.getByRole('alert');
64
+ component = screen.getByTestId('alert');
65
65
  });
66
66
 
67
67
  it('the message is rendered', () => {
@@ -85,7 +85,7 @@ describe('Alert', () => {
85
85
  describe('deprecated props', () => {
86
86
  it('renders arrows but logs a warning', () => {
87
87
  render(<Alert arrow={AlertArrowPosition.BOTTOM} message={message} />);
88
- component = screen.getByRole('alert');
88
+ component = screen.getByTestId('alert');
89
89
 
90
90
  expect(component).toHaveClass('arrow');
91
91
  expect(component).toHaveClass('arrow-bottom');
@@ -125,7 +125,7 @@ describe('Alert', () => {
125
125
  it('maps type SUCCESS to type POSITIVE and logs a warning', () => {
126
126
  render(<Alert type={Sentiment.SUCCESS} message={message} />);
127
127
 
128
- const success = screen.getByRole('alert');
128
+ const success = screen.getByTestId('alert');
129
129
 
130
130
  expect(success).toHaveClass(classForType(Sentiment.POSITIVE));
131
131
  expect(screen.getByTestId('check-circle-icon')).toBeInTheDocument();
@@ -137,7 +137,7 @@ describe('Alert', () => {
137
137
  it('maps type INFO to type NEUTRAL and logs a warning', () => {
138
138
  render(<Alert type={Sentiment.INFO} message={message} />);
139
139
 
140
- const info = screen.getByRole('alert');
140
+ const info = screen.getByTestId('alert');
141
141
 
142
142
  expect(info).toHaveClass(classForType(Sentiment.NEUTRAL));
143
143
  expect(screen.getByTestId('info-circle-icon')).toBeInTheDocument();
@@ -150,7 +150,7 @@ describe('Alert', () => {
150
150
  it('maps type ERROR to type NEGATIVE and logs a warning', () => {
151
151
  render(<Alert type={Sentiment.ERROR} message={message} />);
152
152
 
153
- const error = screen.getByRole('alert');
153
+ const error = screen.getByTestId('alert');
154
154
 
155
155
  expect(error).toHaveClass(classForType(Sentiment.NEGATIVE));
156
156
  expect(screen.getByTestId('cross-circle-icon')).toBeInTheDocument();
@@ -197,14 +197,14 @@ describe('Alert', () => {
197
197
  it('converts message to markdown', () => {
198
198
  render(<Alert message={input} />);
199
199
 
200
- expect(screen.getByRole('alert')).toContainHTML(output);
200
+ expect(screen.getByTestId('alert')).toContainHTML(output);
201
201
  });
202
202
 
203
203
  it('does not convert children to markdown', () => {
204
204
  render(<Alert>{input}</Alert>);
205
205
 
206
206
  expect(screen.getByText(input)).toBeInTheDocument();
207
- expect(screen.getByRole('alert')).not.toContainHTML(output);
207
+ expect(screen.getByTestId('alert')).not.toContainHTML(output);
208
208
  });
209
209
  });
210
210
 
@@ -212,7 +212,7 @@ describe('Alert', () => {
212
212
  it('applies provided classes', () => {
213
213
  render(<Alert className="cats" message={message} />);
214
214
 
215
- expect(screen.getByRole('alert')).toHaveClass('cats');
215
+ expect(screen.getByTestId('alert')).toHaveClass('cats');
216
216
  });
217
217
  });
218
218
 
@@ -221,7 +221,7 @@ describe('Alert', () => {
221
221
  const icon = <HappyEmoji />;
222
222
 
223
223
  render(<Alert icon={icon} message={message} />);
224
- component = screen.getByRole('alert');
224
+ component = screen.getByTestId('alert');
225
225
  expect(screen.getByTestId('happy-emoji-icon')).toBeInTheDocument();
226
226
  });
227
227
  });
@@ -247,7 +247,7 @@ describe('Alert', () => {
247
247
  describe('types', () => {
248
248
  const getComponentWithType = (type) => {
249
249
  render(<Alert type={type} message={message} />);
250
- return screen.getByRole('alert');
250
+ return screen.getByTestId('alert');
251
251
  };
252
252
 
253
253
  it('renders neutral', () => {
@@ -277,6 +277,15 @@ describe('Alert', () => {
277
277
  expect(component).toHaveClass(classForType(Sentiment.WARNING));
278
278
  expect(screen.getByTestId('warning-icon')).toBeInTheDocument();
279
279
  });
280
+
281
+ it('renders error alerts with aria-role alert', () => {
282
+ component = getComponentWithType(Sentiment.NEGATIVE);
283
+ expect(screen.getByRole('alert')).toBeInTheDocument();
284
+ });
285
+ it('renders neutral alerts with aria-role status', () => {
286
+ component = getComponentWithType(Sentiment.NEUTRAL);
287
+ expect(screen.getByRole('status')).toBeInTheDocument();
288
+ });
280
289
  });
281
290
 
282
291
  describe('on touch devices', () => {
@@ -304,7 +313,7 @@ describe('Alert', () => {
304
313
 
305
314
  render(<Alert action={action} message={message} onDismiss={jest.fn()} />);
306
315
 
307
- alert = screen.getByRole('alert');
316
+ alert = screen.getByTestId('alert');
308
317
  closeButton = screen.getByLabelText('Close');
309
318
 
310
319
  jest.spyOn(React, 'useRef').mockReturnValue({
@@ -344,7 +353,7 @@ describe('Alert', () => {
344
353
 
345
354
  render(<Alert action={action} message={message} onDismiss={jest.fn()} />);
346
355
 
347
- alert = screen.getByRole('alert');
356
+ alert = screen.getByTestId('alert');
348
357
  closeButton = screen.getByLabelText('Close');
349
358
 
350
359
  jest.spyOn(React, 'useRef').mockReturnValue({
@@ -368,7 +377,7 @@ describe('Alert', () => {
368
377
  beforeEach(() => {
369
378
  render(<Alert message={message} onDismiss={jest.fn()} />);
370
379
 
371
- alert = screen.getByRole('alert');
380
+ alert = screen.getByTestId('alert');
372
381
  closeButton = screen.getByLabelText('Close');
373
382
 
374
383
  jest.spyOn(React, 'useRef').mockReturnValue({
@@ -12,7 +12,7 @@ describe('withArrow', () => {
12
12
  const ArrowLessAlert = withArrow(Alert);
13
13
 
14
14
  render(<ArrowLessAlert message={message} />);
15
- const component = screen.getByRole('alert');
15
+ const component = screen.getByTestId('alert');
16
16
 
17
17
  expect(component).not.toHaveClass('arrow');
18
18
  });
@@ -21,7 +21,7 @@ describe('withArrow', () => {
21
21
  const AlertTopArrow = withArrow(Alert, ArrowPosition.TOP_LEFT);
22
22
 
23
23
  render(<AlertTopArrow message={message} arrow={ArrowPosition.BOTTOM_LEFT} />);
24
- const component = screen.getByRole('alert');
24
+ const component = screen.getByTestId('alert');
25
25
  expect(component).toHaveClass('arrow');
26
26
  expect(component).not.toHaveClass('arrow-bottom');
27
27
  });
@@ -33,7 +33,7 @@ describe('withArrow', () => {
33
33
  it(`returns an bottom arrowed alert if you pass a bottom arrow`, () => {
34
34
  const BottomArrowAlert = getPointyAlert(ArrowPosition.BOTTOM);
35
35
  render(<BottomArrowAlert message={message} />);
36
- const component = screen.getByRole('alert');
36
+ const component = screen.getByTestId('alert');
37
37
 
38
38
  expect(component).toHaveClass('arrow');
39
39
  expect(component).toHaveClass('arrow-bottom');
@@ -42,7 +42,7 @@ describe('withArrow', () => {
42
42
  it(`returns an top-right arrowed alert if you pass a top right arrow`, () => {
43
43
  const BottomArrowAlert = getPointyAlert(ArrowPosition.TOP_RIGHT);
44
44
  render(<BottomArrowAlert message={message} />);
45
- const component = screen.getByRole('alert');
45
+ const component = screen.getByTestId('alert');
46
46
 
47
47
  expect(component).toHaveClass('arrow');
48
48
  expect(component).toHaveClass('arrow-right');
@@ -6,11 +6,10 @@ import Badge, { BadgeProps } from '../badge';
6
6
  import { ProfileType, ProfileTypePersonal, ProfileTypeBusiness, Size, Sentiment } from '../common';
7
7
  import StatusIcon from '../statusIcon/StatusIcon';
8
8
 
9
- interface OptionalBadgeProps {
9
+ interface OptionalBadgeProps extends Omit<BadgeProps, 'badge'> {
10
10
  url?: string;
11
11
  altText?: string;
12
12
  statusIcon?: Sentiment;
13
- children: React.ReactNode;
14
13
  }
15
14
 
16
15
  const OptionalBadge = ({ url, altText, statusIcon, children, ...rest }: OptionalBadgeProps) => {
@@ -31,17 +30,30 @@ const OptionalBadge = ({ url, altText, statusIcon, children, ...rest }: Optional
31
30
  return <>{children}</>;
32
31
  };
33
32
 
34
- export interface AvatarWrapperProps {
33
+ export type AvatarWrapperProps = {
35
34
  url?: string;
36
35
  profileType?: ProfileTypeBusiness | ProfileTypePersonal;
37
36
  profileId?: string;
38
- badgeUrl?: string;
39
- badgeAltText?: string;
40
- badgeStatusIcon?: Sentiment;
41
37
  name?: string;
42
38
  avatarProps?: AvatarProps;
43
39
  badgeProps?: BadgeProps;
44
- }
40
+ } & (
41
+ | {
42
+ badgeUrl: string;
43
+ badgeAltText: string;
44
+ badgeStatusIcon?: never;
45
+ }
46
+ | {
47
+ badgeUrl?: never;
48
+ badgeAltText?: never;
49
+ badgeStatusIcon: Sentiment;
50
+ }
51
+ | {
52
+ badgeUrl?: never;
53
+ badgeAltText?: never;
54
+ badgeStatusIcon?: never;
55
+ }
56
+ );
45
57
 
46
58
  const AvatarWrapper = ({
47
59
  url,
@@ -64,7 +76,7 @@ const AvatarWrapper = ({
64
76
  if (url && !hasImageLoadError) {
65
77
  return {
66
78
  type: AvatarType.THUMBNAIL,
67
- children: <img src={url} alt="avatar" onError={() => setImageLoadError(true)} />,
79
+ children: <img src={url} alt="" onError={() => setImageLoadError(true)} />,
68
80
  ...avatarProps,
69
81
  };
70
82
  }
@@ -8,7 +8,7 @@ exports[`FlowNavigationAvatar with a name AND profileType FlowNavigationAvatar w
8
8
  class="tw-avatar__content"
9
9
  >
10
10
  <img
11
- alt="avatar"
11
+ alt=""
12
12
  src="https://wise.com"
13
13
  />
14
14
  </div>
@@ -56,12 +56,12 @@ describe('Card', () => {
56
56
  });
57
57
 
58
58
  describe('when there is no children prop', () => {
59
- it('redners Option title as a heading 4', () => {
59
+ it('renders Option title as a heading 4', () => {
60
60
  const onClick = jest.fn();
61
61
  renderCard(onClick);
62
62
 
63
63
  expect(screen.queryByRole('button')).not.toBeInTheDocument();
64
- expect(screen.getByText(defaultProps.title).tagName).toBe('SPAN');
64
+ expect(screen.getByText(defaultProps.title).tagName).toBe('H4');
65
65
  });
66
66
 
67
67
  it('has an inactive class', () => {
@@ -85,13 +85,7 @@ const Option = forwardRef<ReferenceType, OptionProps>(
85
85
  </div>
86
86
  )}
87
87
  <div className="media-body">
88
- <Body
89
- as="span"
90
- type={Typography.BODY_LARGE_BOLD}
91
- className="text-primary np-option__title d-block"
92
- >
93
- {title}
94
- </Body>
88
+ <h4 className="np-text-body-large-bold text-primary np-option__title">{title}</h4>
95
89
  {content && <Body className="d-block np-option__body">{content}</Body>}
96
90
  </div>
97
91
  <div className="media-right">{button}</div>
@@ -7,9 +7,12 @@ exports[`BottomSheet renders content when open 1`] = `
7
7
  class="np-theme-light"
8
8
  >
9
9
  <div
10
- class="np-focus-boundary outline-none"
11
10
  tabindex="-1"
12
11
  >
12
+ <span
13
+ data-focus-scope-start="true"
14
+ hidden=""
15
+ />
13
16
  <div
14
17
  class="dimmer"
15
18
  role="presentation"
@@ -63,6 +66,10 @@ exports[`BottomSheet renders content when open 1`] = `
63
66
  </div>
64
67
  </div>
65
68
  </div>
69
+ <span
70
+ data-focus-scope-end="true"
71
+ hidden=""
72
+ />
66
73
  </div>
67
74
  </div>
68
75
  </body>
@@ -2,6 +2,7 @@ import classNames from 'classnames';
2
2
  import { MouseEvent, ReactNode, useRef } from 'react';
3
3
 
4
4
  import { CloseButton } from '../closeButton';
5
+ import { stopPropagation } from '../domHelpers';
5
6
 
6
7
  export interface CardProps {
7
8
  /** Content to display inside Card. */
@@ -49,7 +50,7 @@ export interface CardProps {
49
50
  */
50
51
  const Card: React.FC<CardProps> = ({
51
52
  className,
52
- children,
53
+ children = null,
53
54
  id,
54
55
  isDisabled = false,
55
56
  isSmall = false,
@@ -80,7 +81,10 @@ const Card: React.FC<CardProps> = ({
80
81
  size={isSmall ? 'sm' : 'md'}
81
82
  isDisabled={isDisabled}
82
83
  testId="close-button"
83
- onClick={onDismiss}
84
+ onClick={(e) => {
85
+ stopPropagation(e);
86
+ onDismiss();
87
+ }}
84
88
  />
85
89
  )}
86
90
  {children}
@@ -1 +1,2 @@
1
1
  export { default } from './Card';
2
+ export * from './Card';
@@ -1,44 +1,21 @@
1
- import { isKey, isUndefined } from '@transferwise/neptune-validation';
2
- import { ReactNode, useEffect, useRef, useState } from 'react';
3
-
4
- import { useConditionalListener } from '../hooks';
5
- import { Key } from '../key';
6
-
7
- import { getFocusableElements, resetFocus } from './utils';
8
-
9
- const { TAB } = Key;
1
+ import { FocusScope } from '@react-aria/focus';
2
+ import { useEffect, useRef } from 'react';
10
3
 
11
4
  type FocusBoundaryProps = {
12
- children: ReactNode;
5
+ children: React.ReactNode;
13
6
  };
14
7
 
15
8
  const FocusBoundary = ({ children }: FocusBoundaryProps) => {
16
- const boundaryReference = useRef<HTMLDivElement | null>(null);
17
- const parent = isUndefined(document) ? undefined : document;
18
- const [focusableEls, setFocusableEls] = useState({});
19
-
9
+ const wrapperReference = useRef<HTMLDivElement>(null);
20
10
  useEffect(() => {
21
- if (boundaryReference?.current) {
22
- boundaryReference.current.focus({ preventScroll: true });
23
- setFocusableEls(getFocusableElements(boundaryReference.current));
24
- }
11
+ wrapperReference.current?.focus({ preventScroll: true });
25
12
  }, []);
26
13
 
27
- // If event type is Tab the resetFocus will force the focus to either the first focusable or last in boundaryRef .
28
- useConditionalListener({
29
- eventType: 'keydown',
30
- callback: (event) => {
31
- if (isKey({ keyType: TAB, event })) {
32
- resetFocus({ event, focusableEls });
33
- }
34
- },
35
- attachListener: true,
36
- parent,
37
- });
38
-
39
14
  return (
40
- <div ref={boundaryReference} tabIndex={-1} className="np-focus-boundary outline-none">
41
- {children}
15
+ <div ref={wrapperReference} tabIndex={-1}>
16
+ <FocusScope contain restoreFocus>
17
+ {children}
18
+ </FocusScope>
42
19
  </div>
43
20
  );
44
21
  };
@@ -18,8 +18,10 @@ const DateInput = ({
18
18
  size,
19
19
  value,
20
20
  dayLabel,
21
+ dayAutoComplete,
21
22
  monthLabel,
22
23
  yearLabel,
24
+ yearAutoComplete,
23
25
  monthFormat,
24
26
  mode,
25
27
  onChange,
@@ -239,6 +241,7 @@ const DateInput = ({
239
241
  <Input
240
242
  type="number"
241
243
  name="day"
244
+ autoComplete={dayAutoComplete}
242
245
  value={day || ''}
243
246
  placeholder={placeholders.day}
244
247
  disabled={disabled}
@@ -259,6 +262,7 @@ const DateInput = ({
259
262
  <Input
260
263
  type="number"
261
264
  name="year"
265
+ autoComplete={yearAutoComplete}
262
266
  placeholder={placeholders.year}
263
267
  value={year || ''}
264
268
  disabled={disabled}
@@ -303,8 +307,10 @@ DateInput.propTypes = {
303
307
  onFocus: PropTypes.func,
304
308
  onBlur: PropTypes.func,
305
309
  dayLabel: PropTypes.string,
310
+ dayAutoComplete: PropTypes.string,
306
311
  monthLabel: PropTypes.string,
307
312
  yearLabel: PropTypes.string,
313
+ yearAutoComplete: PropTypes.string,
308
314
  monthFormat: PropTypes.oneOf(['long', 'short']),
309
315
  mode: PropTypes.oneOf(['day-month-year', 'month-year']),
310
316
  placeholders: PropTypes.shape({
@@ -8,8 +8,10 @@ export default {
8
8
  title: 'Forms/DateInput',
9
9
  args: {
10
10
  dayLabel: 'Day input',
11
+ dayAutoComplete: 'bday-day',
11
12
  monthLabel: 'Month select',
12
13
  yearLabel: 'Year input',
14
+ yearAutoComplete: 'bday-year',
13
15
  selectProps: {
14
16
  buttonProps: {
15
17
  'aria-label': 'Select month',
@@ -1 +1 @@
1
- .tw-date-lookup-menu{width:400px}.tw-date-lookup-calendar{min-width:300px;table-layout:fixed;text-align:center}.tw-date-lookup-calendar>tbody>tr>td{padding:4px;padding:var(--size-4)}.tw-date-lookup-calendar>tbody>tr>td button{background-color:transparent;border:transparent;border-radius:10px;border-radius:var(--radius-small);color:#0097c7;color:var(--color-content-accent);font-weight:600;font-weight:var(--font-weight-semi-bold);padding:4px 0;padding:var(--size-4) 0;width:100%}.tw-date-lookup-calendar>tbody>tr>td button:not(.disabled,:disabled).active{background-color:#0081ba;background-color:var(--color-interactive-accent-active);color:#fff}.tw-date-lookup-calendar>tbody>tr>td button:not(.disabled,:disabled):hover{background-color:#008fc9;background-color:var(--color-interactive-accent-hover);color:#fff}.np-theme-personal .tw-date-lookup-calendar>tbody>tr>td button{color:#37517e;color:var(--color-content-primary)}.np-theme-personal .tw-date-lookup-calendar>tbody>tr>td button:not(.disabled,:disabled).active,.np-theme-personal .tw-date-lookup-calendar>tbody>tr>td button:not(.disabled,:disabled):hover{color:var(--color-interactive-control)}.np-theme-personal .tw-date-lookup-calendar>tbody>tr>td:nth-child(7n+6) button,.np-theme-personal .tw-date-lookup-calendar>tbody>tr>td:nth-child(7n+7) button,.np-theme-personal .tw-date-lookup-calendar>thead>tr>th:nth-child(7n+6),.np-theme-personal .tw-date-lookup-calendar>thead>tr>th:nth-child(7n+7){color:#5d7079;color:var(--color-content-secondary);font-weight:400;font-weight:var(--font-weight-regular)}.np-theme-personal .tw-date-lookup-calendar{background-color:inherit}.tw-date-lookup-header-current{font-weight:800;font-weight:var(--font-weight-bold)}.np-theme-personal .tw-date-lookup-menu .table-bordered,.np-theme-personal.tw-date-lookup-menu .table-bordered{border:none}.np-theme-personal .tw-date-lookup-menu thead,.np-theme-personal.tw-date-lookup-menu thead{background-color:unset}.np-theme-personal .tw-date-lookup-menu td,.np-theme-personal.tw-date-lookup-menu td{border:none}.np-theme-personal .tw-date-lookup-menu .tw-date-lookup-header-current,.np-theme-personal.tw-date-lookup-menu .tw-date-lookup-header-current{color:#37517e;color:var(--color-content-primary)}.np-theme-personal .tw-date-lookup-menu .tw-date-lookup-day-option,.np-theme-personal.tw-date-lookup-menu .tw-date-lookup-day-option{align-items:center;color:#37517e;color:var(--color-content-primary);display:inline-flex;height:32px;height:var(--size-32);justify-content:center;line-height:32px;line-height:var(--size-32)}.np-theme-personal .tw-date-lookup-menu .tw-date-lookup-day-option.active,.np-theme-personal.tw-date-lookup-menu .tw-date-lookup-day-option.active{background-color:#00a2dd;background-color:var(--color-interactive-accent);color:var(--color-interactive-primary)}
1
+ .tw-date-lookup-menu{width:400px}.tw-date-lookup-calendar{min-width:300px;table-layout:fixed;text-align:center}.tw-date-lookup-calendar>tbody>tr>td{padding:4px;padding:var(--size-4)}.tw-date-lookup-calendar>tbody>tr>td button{background-color:transparent;border:transparent;border-radius:10px;border-radius:var(--radius-small);color:#0097c7;color:var(--color-content-accent);font-weight:600;font-weight:var(--font-weight-semi-bold);padding:4px 0;padding:var(--size-4) 0;width:100%}.tw-date-lookup-calendar>tbody>tr>td button:not(.disabled,:disabled).active{background-color:#0081ba;background-color:var(--color-interactive-accent-active);color:#fff}.tw-date-lookup-calendar>tbody>tr>td button:not(.disabled,:disabled):hover{background-color:#008fc9;background-color:var(--color-interactive-accent-hover);color:#fff}.np-theme-personal .tw-date-lookup-calendar>tbody>tr>td button{color:#37517e;color:var(--color-content-primary)}.np-theme-personal .tw-date-lookup-calendar>tbody>tr>td button:not(.disabled,:disabled).active,.np-theme-personal .tw-date-lookup-calendar>tbody>tr>td button:not(.disabled,:disabled):hover{color:var(--color-interactive-control)}.np-theme-personal .tw-date-lookup-calendar>tbody>tr>td:nth-child(7n+6) button,.np-theme-personal .tw-date-lookup-calendar>tbody>tr>td:nth-child(7n+7) button,.np-theme-personal .tw-date-lookup-calendar>thead>tr>th:nth-child(7n+6),.np-theme-personal .tw-date-lookup-calendar>thead>tr>th:nth-child(7n+7){color:#5d7079;color:var(--color-content-secondary);font-weight:400;font-weight:var(--font-weight-regular)}.tw-date-lookup-calendar abbr{text-decoration:none}.np-theme-personal .tw-date-lookup-calendar{background-color:inherit}.tw-date-lookup-header-current{font-weight:800;font-weight:var(--font-weight-bold)}.np-theme-personal .tw-date-lookup-menu .table-bordered,.np-theme-personal.tw-date-lookup-menu .table-bordered{border:none}.np-theme-personal .tw-date-lookup-menu thead,.np-theme-personal.tw-date-lookup-menu thead{background-color:unset}.np-theme-personal .tw-date-lookup-menu td,.np-theme-personal.tw-date-lookup-menu td{border:none}.np-theme-personal .tw-date-lookup-menu .tw-date-lookup-header-current,.np-theme-personal.tw-date-lookup-menu .tw-date-lookup-header-current{color:#37517e;color:var(--color-content-primary)}.np-theme-personal .tw-date-lookup-menu .tw-date-lookup-day-option,.np-theme-personal.tw-date-lookup-menu .tw-date-lookup-day-option{align-items:center;color:#37517e;color:var(--color-content-primary);display:inline-flex;height:32px;height:var(--size-32);justify-content:center;line-height:32px;line-height:var(--size-32)}.np-theme-personal .tw-date-lookup-menu .tw-date-lookup-day-option.active,.np-theme-personal.tw-date-lookup-menu .tw-date-lookup-day-option.active{background-color:#00a2dd;background-color:var(--color-interactive-accent);color:var(--color-interactive-primary)}
@@ -7,13 +7,13 @@ import KEY_CODES from '../common/keyCodes';
7
7
  import DateLookup from '.';
8
8
 
9
9
  const defaultLocale = 'en-GB';
10
-
10
+ const formatMessage = (id) => `${id}`;
11
11
  jest.mock('react-intl', () => ({
12
12
  injectIntl: (Component) =>
13
13
  function (props) {
14
- return <Component {...props} intl={{ locale: defaultLocale }} />;
14
+ return <Component {...props} intl={{ locale: defaultLocale, formatMessage }} />;
15
15
  },
16
- useIntl: () => ({ locale: defaultLocale }),
16
+ useIntl: () => ({ locale: defaultLocale, formatMessage }),
17
17
  defineMessages: (translations) => translations,
18
18
  }));
19
19
 
@@ -54,6 +54,10 @@
54
54
  }
55
55
  }
56
56
 
57
+ abbr {
58
+ text-decoration: none;
59
+ }
60
+
57
61
  .np-theme-personal & {
58
62
  background-color: inherit;
59
63
  }
@@ -0,0 +1,44 @@
1
+ import { defineMessages } from 'react-intl';
2
+
3
+ export default defineMessages({
4
+ next: {
5
+ id: 'neptune.DateLookup.next',
6
+ defaultMessage: 'next',
7
+ description: 'Description of next button',
8
+ },
9
+ previous: {
10
+ id: 'neptune.DateLookup.previous',
11
+ defaultMessage: 'previous',
12
+ description: 'Description of previous button',
13
+ },
14
+ day: {
15
+ id: 'neptune.DateLookup.day',
16
+ defaultMessage: 'day',
17
+ description: 'Description of next/previous/selected day button',
18
+ },
19
+ month: {
20
+ id: 'neptune.DateLookup.month',
21
+ defaultMessage: 'month',
22
+ description: 'Description of next/previous/selected month button',
23
+ },
24
+ year: {
25
+ id: 'neptune.DateLookup.year',
26
+ defaultMessage: 'year',
27
+ description: 'Description of next/previous/selected year button',
28
+ },
29
+ twentyYears: {
30
+ id: 'neptune.DateLookup.twentyYears',
31
+ defaultMessage: '20 years',
32
+ description: 'Description of next/previous 20 years button',
33
+ },
34
+ selected: {
35
+ id: 'neptune.DateLookup.selected',
36
+ defaultMessage: 'selected',
37
+ description: 'Description of the selected day/month/year',
38
+ },
39
+ goTo20YearView: {
40
+ id: 'neptune.DateLookup.goTo20YearView',
41
+ defaultMessage: 'Go to 20 year view',
42
+ description: 'Description of the button to go to 20 year view',
43
+ },
44
+ });
@@ -54,6 +54,14 @@ describe('DateLookup (events)', () => {
54
54
  expect(getActiveYearButton()).toHaveFocus();
55
55
  });
56
56
 
57
+ it('has aria-label for 20 years', () => {
58
+ openDateLookup();
59
+ clickDateButton();
60
+
61
+ expect(getButtonByAriaLabel('next 20 years')).toBeInTheDocument();
62
+ expect(getButtonByAriaLabel('previous 20 years')).toBeInTheDocument();
63
+ });
64
+
57
65
  it('switches to months', () => {
58
66
  openDateLookup();
59
67
  clickDateButton();
@@ -62,6 +70,15 @@ describe('DateLookup (events)', () => {
62
70
  expect(getActiveMonthButton()).toHaveFocus();
63
71
  });
64
72
 
73
+ it('has aria label for year', () => {
74
+ openDateLookup();
75
+ clickDateButton();
76
+ user.click(getActiveYearButton());
77
+
78
+ expect(getButtonByAriaLabel('next year')).toBeInTheDocument();
79
+ expect(getButtonByAriaLabel('previous year')).toBeInTheDocument();
80
+ });
81
+
65
82
  it('switches to days', () => {
66
83
  openDateLookup();
67
84
  clickDateButton();
@@ -71,6 +88,16 @@ describe('DateLookup (events)', () => {
71
88
  expect(getActiveDayButton()).toHaveFocus();
72
89
  });
73
90
 
91
+ it('has aria label for month', () => {
92
+ openDateLookup();
93
+ clickDateButton();
94
+ user.click(getActiveYearButton());
95
+ user.click(getActiveMonthButton());
96
+
97
+ expect(getButtonByAriaLabel('next month')).toBeInTheDocument();
98
+ expect(getButtonByAriaLabel('previous month')).toBeInTheDocument();
99
+ });
100
+
74
101
  it('updates selected date and closes', () => {
75
102
  openDateLookup();
76
103
  const d = new Date(2018, 11, 28);
@@ -81,6 +108,15 @@ describe('DateLookup (events)', () => {
81
108
  expect(getOpenButton()).toHaveFocus();
82
109
  });
83
110
 
111
+ it('has aria label on selected date', () => {
112
+ openDateLookup();
113
+ const d = new Date(2018, 11, 28);
114
+ const newDay = screen.getByText(`${d.getDate()}`);
115
+ user.click(newDay);
116
+ openDateLookup();
117
+ expect(screen.getByRole('button', { name: /selected day/i })).toBeInTheDocument();
118
+ });
119
+
84
120
  describe('at extra small breakpoints', () => {
85
121
  it('opens dateLookup using slider on small + 1 width', () => {
86
122
  winWidth(Breakpoint.SMALL + 1);
@@ -148,6 +184,9 @@ describe('DateLookup (events)', () => {
148
184
  return container.querySelector('button.tw-date-lookup-day-option.active');
149
185
  };
150
186
 
187
+ const getButtonByAriaLabel = (ariaLabel) => {
188
+ return screen.getByRole('button', { name: ariaLabel });
189
+ };
151
190
  const getClearButton = () => container.querySelector('.clear-btn');
152
191
  const getOpenButton = () => container.querySelector('button.np-date-trigger');
153
192
  const getDateButton = () => container.querySelector('button.tw-date-lookup-header-current');