@transferwise/components 45.26.0 → 45.26.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/i18n/en.json +1 -0
- package/build/index.esm.js +185 -99
- package/build/index.esm.js.map +1 -1
- package/build/index.js +185 -99
- package/build/index.js.map +1 -1
- package/build/main.css +1 -1
- package/build/styles/dateLookup/DateLookup.css +1 -1
- package/build/styles/main.css +1 -1
- package/build/styles/typeahead/typeaheadOption/TypeaheadOption.css +1 -1
- package/build/types/dateLookup/DateLookup.d.ts.map +1 -1
- package/build/types/dateLookup/getFocusableTime/getFocusableTime.d.ts +9 -0
- package/build/types/dateLookup/getFocusableTime/getFocusableTime.d.ts.map +1 -0
- package/build/types/dateLookup/monthCalendar/table/MonthCalendarTable.d.ts +1 -1
- package/build/types/dateLookup/monthCalendar/table/MonthCalendarTable.d.ts.map +1 -1
- package/build/types/dateLookup/tableLink/TableLink.d.ts +14 -4
- package/build/types/dateLookup/tableLink/TableLink.d.ts.map +1 -1
- package/build/types/dateLookup/tableLink/index.d.ts +1 -1
- package/build/types/dateLookup/tableLink/index.d.ts.map +1 -1
- package/build/types/dateLookup/yearCalendar/table/YearCalendarTable.d.ts.map +1 -1
- package/build/types/info/Info.d.ts +1 -1
- package/build/types/info/Info.d.ts.map +1 -1
- package/build/types/info/Info.messages.d.ts +8 -0
- package/build/types/info/Info.messages.d.ts.map +1 -0
- package/build/types/summary/Summary.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/dateLookup/DateLookup.css +1 -1
- package/src/dateLookup/DateLookup.js +19 -4
- package/src/dateLookup/DateLookup.keyboardEvents.spec.js +12 -0
- package/src/dateLookup/DateLookup.less +39 -49
- package/src/dateLookup/DateLookup.story.js +8 -7
- package/src/dateLookup/dayCalendar/table/DayCalendarTable.js +28 -3
- package/src/dateLookup/dayCalendar/table/DayCalendarTable.spec.js +25 -0
- package/src/dateLookup/getFocusableTime/getFocusable.spec.ts +40 -0
- package/src/dateLookup/getFocusableTime/getFocusableTime.tsx +14 -0
- package/src/dateLookup/monthCalendar/table/MonthCalendarTable.js +33 -20
- package/src/dateLookup/monthCalendar/table/MonthCalendarTable.spec.js +33 -0
- package/src/dateLookup/tableLink/TableLink.spec.js +6 -15
- package/src/dateLookup/tableLink/TableLink.tsx +79 -0
- package/src/dateLookup/yearCalendar/table/YearCalendarTable.js +33 -11
- package/src/dateLookup/yearCalendar/table/YearCalendarTable.spec.js +26 -0
- package/src/i18n/en.json +1 -0
- package/src/info/Info.messages.ts +8 -0
- package/src/info/Info.spec.js +10 -4
- package/src/info/Info.tsx +5 -2
- package/src/main.css +1 -1
- package/src/popover/Popover.js +1 -1
- package/src/popover/__snapshots__/Popover.spec.js.snap +2 -2
- package/src/summary/Summary.tsx +0 -1
- package/src/typeahead/Typeahead.story.js +6 -0
- package/src/typeahead/typeaheadOption/TypeaheadOption.css +1 -1
- package/src/typeahead/typeaheadOption/TypeaheadOption.js +1 -1
- package/src/typeahead/typeaheadOption/TypeaheadOption.less +2 -1
- package/src/dateLookup/tableLink/TableLink.js +0 -70
- /package/src/dateLookup/tableLink/{index.js → index.ts} +0 -0
|
@@ -82,6 +82,39 @@ describe('MonthCalendarTable', () => {
|
|
|
82
82
|
expect(component.find('.sr-only').text()).toBe('Enter date..');
|
|
83
83
|
});
|
|
84
84
|
|
|
85
|
+
it('sets autofocus to true when June is the selected month', () => {
|
|
86
|
+
component.setProps({ selectedDate: new Date(2018, 5, 1) });
|
|
87
|
+
expect(getTableLinkAt(5).prop('autofocus')).toBe(true);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('does not set autofocus to true when June is the selected month but not the right year', () => {
|
|
91
|
+
component.setProps({ selectedDate: new Date(2017, 5, 1) });
|
|
92
|
+
expect(getTableLinkAt(5).prop('autofocus')).toBe(false);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it('sets autofocus to true when selected date is null and current month is visible', () => {
|
|
96
|
+
const today = new Date();
|
|
97
|
+
component.setProps({
|
|
98
|
+
selectedDate: null,
|
|
99
|
+
viewYear: today.getFullYear(),
|
|
100
|
+
viewMonth: today.getMonth(),
|
|
101
|
+
});
|
|
102
|
+
expect(getTableLink().find({ today: true }).prop('autofocus')).toBe(true);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it('sets autofocus to true on the second month when the first month is disabled', () => {
|
|
106
|
+
component.setProps({
|
|
107
|
+
selectedDate: null,
|
|
108
|
+
viewYear: 2022,
|
|
109
|
+
min: new Date(2022, 1, 28),
|
|
110
|
+
viewMonth: 0,
|
|
111
|
+
isDisabled: (month) => month === 0,
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
expect(getTableLinkAt(0).prop('autofocus')).toBe(false);
|
|
115
|
+
expect(getTableLinkAt(1).prop('autofocus')).toBe(true);
|
|
116
|
+
});
|
|
117
|
+
|
|
85
118
|
const getTableLinkAt = (i) => component.find('[title="MonthName"]').at(i);
|
|
86
119
|
const getTableLink = () => component.find('[title="MonthName"]');
|
|
87
120
|
});
|
|
@@ -1,15 +1,9 @@
|
|
|
1
1
|
import { shallow } from 'enzyme';
|
|
2
|
+
import { useIntl } from 'react-intl';
|
|
2
3
|
|
|
3
4
|
import TableLink from '.';
|
|
4
5
|
|
|
5
|
-
|
|
6
|
-
jest.mock('react-intl', () => ({
|
|
7
|
-
injectIntl: (Component) =>
|
|
8
|
-
function (props) {
|
|
9
|
-
return <Component {...props} intl={{ formatMessage }} />;
|
|
10
|
-
},
|
|
11
|
-
defineMessages: (translations) => translations,
|
|
12
|
-
}));
|
|
6
|
+
jest.mock('react-intl');
|
|
13
7
|
|
|
14
8
|
jest.mock('../DateLookup.messages', () => ({
|
|
15
9
|
selected: {
|
|
@@ -29,18 +23,15 @@ jest.mock('../DateLookup.messages', () => ({
|
|
|
29
23
|
defaultMessage: 'year',
|
|
30
24
|
},
|
|
31
25
|
}));
|
|
32
|
-
jest.mock('react-intl', () => ({
|
|
33
|
-
injectIntl: (Component) =>
|
|
34
|
-
function (props) {
|
|
35
|
-
return <Component {...props} intl={{ formatMessage }} />;
|
|
36
|
-
},
|
|
37
|
-
}));
|
|
38
26
|
|
|
39
27
|
describe('TableLink', () => {
|
|
40
28
|
let component;
|
|
41
29
|
let props;
|
|
42
30
|
|
|
43
31
|
beforeEach(() => {
|
|
32
|
+
useIntl.mockReturnValue({
|
|
33
|
+
formatMessage: (message) => message.defaultMessage,
|
|
34
|
+
});
|
|
44
35
|
props = {
|
|
45
36
|
item: 12,
|
|
46
37
|
type: 'day',
|
|
@@ -51,7 +42,7 @@ describe('TableLink', () => {
|
|
|
51
42
|
title: '12',
|
|
52
43
|
longTitle: '12/12/1212',
|
|
53
44
|
};
|
|
54
|
-
component = shallow(<TableLink {...props} />)
|
|
45
|
+
component = shallow(<TableLink {...props} />);
|
|
55
46
|
});
|
|
56
47
|
|
|
57
48
|
it('shows item value', () => {
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import classNames from 'classnames';
|
|
2
|
+
import { useEffect, useRef } from 'react';
|
|
3
|
+
import { useIntl } from 'react-intl';
|
|
4
|
+
|
|
5
|
+
import messages from '../DateLookup.messages';
|
|
6
|
+
|
|
7
|
+
interface TableLinkProps {
|
|
8
|
+
item: number;
|
|
9
|
+
type: 'day' | 'month' | 'year';
|
|
10
|
+
title?: string;
|
|
11
|
+
longTitle?: string;
|
|
12
|
+
active: boolean;
|
|
13
|
+
disabled: boolean;
|
|
14
|
+
today: boolean;
|
|
15
|
+
autofocus?: boolean;
|
|
16
|
+
onClick: (item: number) => void;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const TableLink = ({
|
|
20
|
+
item,
|
|
21
|
+
type,
|
|
22
|
+
title,
|
|
23
|
+
longTitle,
|
|
24
|
+
active,
|
|
25
|
+
disabled,
|
|
26
|
+
today,
|
|
27
|
+
autofocus,
|
|
28
|
+
onClick,
|
|
29
|
+
}: TableLinkProps) => {
|
|
30
|
+
const buttonRef = useRef<HTMLButtonElement>(null);
|
|
31
|
+
const intl = useIntl();
|
|
32
|
+
|
|
33
|
+
useEffect(() => {
|
|
34
|
+
if (autofocus) {
|
|
35
|
+
setTimeout(() => {
|
|
36
|
+
buttonRef.current?.focus();
|
|
37
|
+
}, 0);
|
|
38
|
+
}
|
|
39
|
+
}, [autofocus]);
|
|
40
|
+
|
|
41
|
+
const onCalendarClick = (event: React.MouseEvent<HTMLButtonElement>) => {
|
|
42
|
+
event.preventDefault();
|
|
43
|
+
if (!disabled) {
|
|
44
|
+
onClick(item);
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const calculateAriaLabel = () => {
|
|
49
|
+
if (active) {
|
|
50
|
+
return `${longTitle || title || ''}, ${intl.formatMessage(
|
|
51
|
+
messages.selected,
|
|
52
|
+
)} ${intl.formatMessage(messages[type])}`;
|
|
53
|
+
}
|
|
54
|
+
return longTitle || title;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
return (
|
|
58
|
+
<>
|
|
59
|
+
<button
|
|
60
|
+
ref={buttonRef}
|
|
61
|
+
type="button"
|
|
62
|
+
className={classNames(
|
|
63
|
+
`tw-date-lookup-${type}-option np-text-body-default-bold`,
|
|
64
|
+
{ active: !!active },
|
|
65
|
+
{ today: !!today },
|
|
66
|
+
)}
|
|
67
|
+
disabled={disabled}
|
|
68
|
+
tabIndex={autofocus ? 0 : -1}
|
|
69
|
+
aria-label={calculateAriaLabel()}
|
|
70
|
+
aria-pressed={active}
|
|
71
|
+
onClick={onCalendarClick}
|
|
72
|
+
>
|
|
73
|
+
{title || item}
|
|
74
|
+
</button>
|
|
75
|
+
</>
|
|
76
|
+
);
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
export default TableLink;
|
|
@@ -2,6 +2,7 @@ import { formatDate } from '@transferwise/formatting';
|
|
|
2
2
|
import PropTypes from 'prop-types';
|
|
3
3
|
import { useIntl } from 'react-intl';
|
|
4
4
|
|
|
5
|
+
import { getFocusableTime } from '../../getFocusableTime/getFocusableTime';
|
|
5
6
|
import TableLink from '../../tableLink';
|
|
6
7
|
|
|
7
8
|
const ROWS = 5;
|
|
@@ -11,17 +12,38 @@ const YEAR_ONLY_FORMAT = { year: 'numeric' };
|
|
|
11
12
|
const YearCalendarTable = ({ selectedDate, min, max, viewYear, placeholder, onSelect }) => {
|
|
12
13
|
const { locale } = useIntl();
|
|
13
14
|
const startYear = viewYear - (viewYear % 20);
|
|
14
|
-
const getLink = (year) =>
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
15
|
+
const getLink = (year) => {
|
|
16
|
+
return (
|
|
17
|
+
<TableLink
|
|
18
|
+
item={year}
|
|
19
|
+
type="year"
|
|
20
|
+
title={formatDate(new Date(year, 0), locale, YEAR_ONLY_FORMAT)}
|
|
21
|
+
active={isActive(year)}
|
|
22
|
+
disabled={isDisabled(year)}
|
|
23
|
+
today={isThisYear(year)}
|
|
24
|
+
autofocus={autofocusYear === year}
|
|
25
|
+
onClick={onSelect}
|
|
26
|
+
/>
|
|
27
|
+
);
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const isActive = (year) => {
|
|
31
|
+
return !!(selectedDate && year === selectedDate.getFullYear());
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const isThisYear = (year) => {
|
|
35
|
+
return year === new Date().getFullYear();
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const isDisabled = (year) => {
|
|
39
|
+
return !!((min && year < min.getFullYear()) || (max && year > max.getFullYear()));
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const autofocusYear = (() => {
|
|
43
|
+
const years = [...new Array(ROWS * COLS)].map((_, index) => startYear + index);
|
|
44
|
+
return getFocusableTime({ isActive, isNow: isThisYear, isDisabled, timeSpan: years });
|
|
45
|
+
})();
|
|
46
|
+
|
|
25
47
|
return (
|
|
26
48
|
<table className="table table-condensed table-bordered tw-date-lookup-calendar m-b-0">
|
|
27
49
|
<thead className="sr-only">
|
|
@@ -90,6 +90,32 @@ describe('YearCalendarTable', () => {
|
|
|
90
90
|
expect(component.find('.sr-only').text()).toBe('Enter date..');
|
|
91
91
|
});
|
|
92
92
|
|
|
93
|
+
it('sets autofocus to true when 2002 is the selected year', () => {
|
|
94
|
+
component.setProps({ selectedDate: new Date(2002, 5, 1) });
|
|
95
|
+
expect(getTableLinkAt(2).prop('autofocus')).toBe(true);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('sets autofocus to true when selected year is null and current year is visible', () => {
|
|
99
|
+
const today = new Date();
|
|
100
|
+
component.setProps({
|
|
101
|
+
selectedDate: null,
|
|
102
|
+
viewYear: today.getFullYear(),
|
|
103
|
+
});
|
|
104
|
+
expect(getTableLink().find({ today: true }).prop('autofocus')).toBe(true);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it('sets autofocus to true on the second year when the first year is disabled', () => {
|
|
108
|
+
component.setProps({
|
|
109
|
+
selectedDate: null,
|
|
110
|
+
viewYear: 2000,
|
|
111
|
+
min: new Date(2001, 1, 1),
|
|
112
|
+
isDisabled: (month) => month === 0,
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
expect(getTableLinkAt(0).prop('autofocus')).toBe(false);
|
|
116
|
+
expect(getTableLinkAt(1).prop('autofocus')).toBe(true);
|
|
117
|
+
});
|
|
118
|
+
|
|
93
119
|
const getTableLinkAt = (i) => component.find('[type="year"]').at(i);
|
|
94
120
|
const getTableLink = () => component.find('[type="year"]');
|
|
95
121
|
});
|
package/src/i18n/en.json
CHANGED
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
"neptune.DateLookup.twentyYears": "20 years",
|
|
16
16
|
"neptune.DateLookup.year": "year",
|
|
17
17
|
"neptune.FlowNavigation.back": "back to previous step",
|
|
18
|
+
"neptune.Info.ariaLabel": "More information",
|
|
18
19
|
"neptune.Link.opensInNewTab": "(opens in new tab)",
|
|
19
20
|
"neptune.MoneyInput.Select.placeholder": "Select an option...",
|
|
20
21
|
"neptune.Select.searchPlaceholder": "Search...",
|
package/src/info/Info.spec.js
CHANGED
|
@@ -3,11 +3,11 @@ import { act, render, fireEvent, waitFor, screen } from '../test-utils';
|
|
|
3
3
|
import Info, { InfoPresentation } from '.';
|
|
4
4
|
|
|
5
5
|
describe('Info', () => {
|
|
6
|
+
const defaultScreenReaderLabel = 'More information';
|
|
6
7
|
const props = {
|
|
7
8
|
content: 'content',
|
|
8
9
|
className: 'className',
|
|
9
10
|
title: 'title',
|
|
10
|
-
'aria-label': 'aria-label',
|
|
11
11
|
};
|
|
12
12
|
|
|
13
13
|
it('calls onClick on click event', async () => {
|
|
@@ -57,9 +57,15 @@ describe('Info', () => {
|
|
|
57
57
|
});
|
|
58
58
|
});
|
|
59
59
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
60
|
+
it('accepts custom label for screen readers', () => {
|
|
61
|
+
const customScreenReaderLabel = 'Custom message';
|
|
62
|
+
render(<Info {...props} aria-label={customScreenReaderLabel} />);
|
|
63
|
+
|
|
64
|
+
expect(screen.getByLabelText(customScreenReaderLabel)).toBeInTheDocument();
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
const getTriggerButton = () => screen.queryByLabelText(defaultScreenReaderLabel);
|
|
68
|
+
const getPopover = () => screen.queryByRole('dialog');
|
|
63
69
|
const getModal = () => screen.queryByRole('dialog');
|
|
64
70
|
|
|
65
71
|
const openPopover = async () => {
|
package/src/info/Info.tsx
CHANGED
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
import { QuestionMarkCircle as HelpCircleIcon } from '@transferwise/icons';
|
|
2
2
|
import classNames from 'classnames';
|
|
3
3
|
import { useState } from 'react';
|
|
4
|
+
import { useIntl } from 'react-intl';
|
|
4
5
|
|
|
5
6
|
import { Size, Position, SizeSmall, SizeLarge } from '../common';
|
|
6
7
|
import Modal from '../modal';
|
|
7
8
|
import Popover from '../popover';
|
|
8
9
|
|
|
10
|
+
import messages from './Info.messages';
|
|
9
11
|
import { InfoPresentation } from './infoPresentations';
|
|
10
12
|
|
|
11
13
|
export interface Props {
|
|
12
|
-
'aria-label'
|
|
14
|
+
'aria-label'?: string;
|
|
13
15
|
/**
|
|
14
16
|
* Extra classes applied to Info
|
|
15
17
|
*/
|
|
@@ -47,8 +49,9 @@ const Info = ({
|
|
|
47
49
|
'aria-label': ariaLabel,
|
|
48
50
|
preferredPlacement = Position.BOTTOM,
|
|
49
51
|
}: Props) => {
|
|
52
|
+
const intl = useIntl();
|
|
50
53
|
const [open, setOpen] = useState(false);
|
|
51
|
-
|
|
54
|
+
ariaLabel ??= intl.formatMessage(messages.ariaLabel);
|
|
52
55
|
const isModal = presentation === InfoPresentation.MODAL;
|
|
53
56
|
const isSmall = size === Size.SMALL;
|
|
54
57
|
|