@transferwise/components 0.0.0-experimental-7a83d86 → 0.0.0-experimental-b2a535a
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/index.js +17 -22
- package/build/index.js.map +1 -1
- package/build/index.mjs +17 -22
- package/build/index.mjs.map +1 -1
- package/build/types/alert/Alert.d.ts.map +1 -1
- package/build/types/dimmer/Dimmer.d.ts.map +1 -1
- package/build/types/loader/Loader.d.ts.map +1 -1
- package/build/types/phoneNumberInput/PhoneNumberInput.d.ts.map +1 -1
- package/build/types/popover/Popover.d.ts.map +1 -1
- package/build/types/segmentedControl/SegmentedControl.d.ts +2 -2
- package/build/types/segmentedControl/SegmentedControl.d.ts.map +1 -1
- package/build/types/select/Select.d.ts.map +1 -1
- package/build/types/stepper/deviceDetection.d.ts.map +1 -1
- package/build/types/uploadInput/uploadButton/UploadButton.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/accordion/Accordion.story.tsx +1 -1
- package/src/alert/Alert.tsx +1 -2
- package/src/avatar/colors/colors.ts +1 -1
- package/src/body/Body.spec.tsx +1 -1
- package/src/body/Body.story.tsx +8 -8
- package/src/checkbox/Checkbox.js +1 -1
- package/src/checkboxButton/CheckboxButton.spec.tsx +1 -0
- package/src/common/Option/Option.tsx +1 -1
- package/src/common/deviceDetection/deviceDetection.js +1 -1
- package/src/common/deviceDetection/deviceDetection.spec.js +2 -4
- package/src/common/responsivePanel/ResponsivePanel.spec.js +15 -11
- package/src/decision/Decision.spec.js +1 -0
- package/src/dimmer/Dimmer.tsx +2 -6
- package/src/inlineAlert/InlineAlert.story.tsx +7 -8
- package/src/link/Link.story.tsx +16 -16
- package/src/loader/Loader.tsx +1 -0
- package/src/logo/Logo.js +2 -2
- package/src/moneyInput/MoneyInput.story.tsx +3 -3
- package/src/nudge/Nudge.spec.tsx +5 -5
- package/src/phoneNumberInput/PhoneNumberInput.tsx +1 -2
- package/src/phoneNumberInput/utils/cleanNumber/cleanNumber.ts +1 -1
- package/src/popover/Popover.tsx +1 -2
- package/src/promoCard/PromoCard.tsx +1 -1
- package/src/radioGroup/RadioGroup.spec.js +1 -1
- package/src/section/Section.story.tsx +1 -2
- package/src/segmentedControl/SegmentedControl.spec.tsx +41 -3
- package/src/segmentedControl/SegmentedControl.story.tsx +51 -13
- package/src/segmentedControl/SegmentedControl.tsx +3 -4
- package/src/select/Select.js +3 -2
- package/src/stepper/deviceDetection.js +2 -1
- package/src/stepper/deviceDetection.spec.js +3 -8
- package/src/test-utils/index.js +1 -1
- package/src/test-utils/story-config.ts +1 -1
- package/src/title/Title.spec.tsx +1 -1
- package/src/typeahead/Typeahead.spec.js +2 -4
- package/src/upload/Upload.spec.js +4 -8
- package/src/uploadInput/uploadButton/UploadButton.tsx +0 -1
|
@@ -32,7 +32,7 @@ const onChange = jest.fn();
|
|
|
32
32
|
|
|
33
33
|
const defaultProps: SegmentedControlProps = {
|
|
34
34
|
name: 'segmentedControl',
|
|
35
|
-
|
|
35
|
+
selectedValue: defaultSegments[0].value,
|
|
36
36
|
mode: 'input',
|
|
37
37
|
segments: defaultSegments,
|
|
38
38
|
onChange,
|
|
@@ -96,7 +96,7 @@ describe('SegmentedControl', () => {
|
|
|
96
96
|
|
|
97
97
|
// new function is created on every render
|
|
98
98
|
const onChange = () => {
|
|
99
|
-
onChangeCallCount
|
|
99
|
+
onChangeCallCount++;
|
|
100
100
|
simulateRerender({});
|
|
101
101
|
};
|
|
102
102
|
|
|
@@ -118,7 +118,7 @@ describe('SegmentedControl', () => {
|
|
|
118
118
|
|
|
119
119
|
// a new onChange function is created on every render
|
|
120
120
|
const onChange = () => {
|
|
121
|
-
onChangeCallCount
|
|
121
|
+
onChangeCallCount++;
|
|
122
122
|
simulateRerender({});
|
|
123
123
|
};
|
|
124
124
|
|
|
@@ -134,6 +134,44 @@ describe('SegmentedControl', () => {
|
|
|
134
134
|
});
|
|
135
135
|
});
|
|
136
136
|
|
|
137
|
+
it('updates the selected segment when the selectedValue prop changes', () => {
|
|
138
|
+
const { rerender } = render(<SegmentedControl {...defaultProps} />);
|
|
139
|
+
|
|
140
|
+
const payroll = screen.getByRole('radio', { name: 'Payroll' });
|
|
141
|
+
userEvent.click(payroll);
|
|
142
|
+
|
|
143
|
+
expect(onChange).toHaveBeenCalledWith('payroll');
|
|
144
|
+
|
|
145
|
+
rerender(<SegmentedControl {...defaultProps} selectedValue="reporting" />);
|
|
146
|
+
|
|
147
|
+
const reporting = screen.getByRole('radio', { name: 'Reporting' });
|
|
148
|
+
expect(reporting).toBeChecked();
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it('updates the options when the segments prop changes', () => {
|
|
152
|
+
const { rerender } = render(<SegmentedControl {...defaultProps} />);
|
|
153
|
+
|
|
154
|
+
const newSegments = [
|
|
155
|
+
{
|
|
156
|
+
id: '1',
|
|
157
|
+
value: 'payroll',
|
|
158
|
+
label: 'Payroll',
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
id: '3',
|
|
162
|
+
value: 'anotherOne',
|
|
163
|
+
label: 'Another One',
|
|
164
|
+
},
|
|
165
|
+
];
|
|
166
|
+
|
|
167
|
+
rerender(<SegmentedControl {...defaultProps} segments={newSegments} />);
|
|
168
|
+
|
|
169
|
+
const anotherOne = screen.getByRole('radio', { name: 'Another One' });
|
|
170
|
+
userEvent.click(anotherOne);
|
|
171
|
+
|
|
172
|
+
expect(onChange).toHaveBeenCalledWith('anotherOne');
|
|
173
|
+
});
|
|
174
|
+
|
|
137
175
|
it('throws error if user tries to add too many segments', () => {
|
|
138
176
|
expect(() => {
|
|
139
177
|
renderSegmentedControl({
|
|
@@ -1,33 +1,36 @@
|
|
|
1
1
|
import { StoryFn } from '@storybook/react';
|
|
2
2
|
import React from 'react';
|
|
3
3
|
|
|
4
|
-
import
|
|
4
|
+
import Button from '../button';
|
|
5
|
+
|
|
6
|
+
import SegmentedControl from './SegmentedControl';
|
|
5
7
|
|
|
6
8
|
export default {
|
|
7
9
|
component: SegmentedControl,
|
|
8
10
|
title: 'Forms/SegmentedControl',
|
|
9
11
|
};
|
|
10
12
|
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
const Template: StoryFn = (args) => {
|
|
14
|
+
const [segments, setSegments] = React.useState([
|
|
15
|
+
{ id: 'CUPCAKE', label: 'Cupcakes', value: 'cupcakes' },
|
|
16
|
+
{ id: 'SPONGECAKE', label: 'Sponge cake', value: 'spongecake' },
|
|
17
|
+
{ id: 'CARROT_CAKE', label: 'Carrot cake', value: 'carrotcake' },
|
|
18
|
+
]);
|
|
16
19
|
|
|
17
|
-
const segmentsWithControls
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
];
|
|
20
|
+
const [segmentsWithControls, setSegmentsWithControls] = React.useState([
|
|
21
|
+
{ id: 'CUPCAKE', label: 'Cupcakes', value: 'cupcakes', controls: 'aControlId' },
|
|
22
|
+
{ id: 'SPONGECAKE', label: 'Sponge cake', value: 'spongecake', controls: 'aControlId' },
|
|
23
|
+
{ id: 'CARROT_CAKE', label: 'Carrot cake', value: 'carrotcake', controls: 'aControlId' },
|
|
24
|
+
]);
|
|
22
25
|
|
|
23
|
-
const Template: StoryFn = (args) => {
|
|
24
26
|
const [selectedValue, setSelectedValue] = React.useState(segments[0].value);
|
|
25
27
|
|
|
28
|
+
console.log('render: segments.length', segments.length);
|
|
26
29
|
return (
|
|
27
30
|
<div className="p-a-2">
|
|
28
31
|
<SegmentedControl
|
|
29
32
|
name="aSegmentedControl"
|
|
30
|
-
|
|
33
|
+
selectedValue={selectedValue}
|
|
31
34
|
onChange={setSelectedValue}
|
|
32
35
|
{...(args.mode === 'view'
|
|
33
36
|
? { segments: segmentsWithControls, mode: 'view', controls: 'aControlId' }
|
|
@@ -36,6 +39,41 @@ const Template: StoryFn = (args) => {
|
|
|
36
39
|
<div className="m-a-2" id="aControlId">
|
|
37
40
|
<p>Selected value: {selectedValue}</p>
|
|
38
41
|
</div>
|
|
42
|
+
<div className="m-a-2">
|
|
43
|
+
<p>
|
|
44
|
+
Force the <b>selectedValue</b> to be one of the following:
|
|
45
|
+
<ul>
|
|
46
|
+
{segments.map((segment) => (
|
|
47
|
+
<li key={segment.id}>
|
|
48
|
+
<a
|
|
49
|
+
href="/"
|
|
50
|
+
onClick={(e) => {
|
|
51
|
+
e.preventDefault();
|
|
52
|
+
setSelectedValue(segment.value);
|
|
53
|
+
}}
|
|
54
|
+
>
|
|
55
|
+
{segment.label}
|
|
56
|
+
</a>
|
|
57
|
+
</li>
|
|
58
|
+
))}
|
|
59
|
+
</ul>
|
|
60
|
+
</p>
|
|
61
|
+
</div>
|
|
62
|
+
<div className="m-a-2">
|
|
63
|
+
<Button
|
|
64
|
+
priority="secondary"
|
|
65
|
+
type="danger"
|
|
66
|
+
size="sm"
|
|
67
|
+
disabled={segments.length < 2}
|
|
68
|
+
onClick={() => {
|
|
69
|
+
const index = segments.findIndex((s) => s.value !== selectedValue);
|
|
70
|
+
setSegments((prev) => prev.filter((_, i) => i !== index));
|
|
71
|
+
setSegmentsWithControls((prev) => prev.filter((_, i) => i !== index));
|
|
72
|
+
}}
|
|
73
|
+
>
|
|
74
|
+
Remove one segment
|
|
75
|
+
</Button>
|
|
76
|
+
</div>
|
|
39
77
|
</div>
|
|
40
78
|
);
|
|
41
79
|
};
|
|
@@ -13,7 +13,7 @@ export type Segments = readonly Segment[] | readonly SegmentWithControls[];
|
|
|
13
13
|
|
|
14
14
|
type SegmentedControlPropsBase = {
|
|
15
15
|
name: string;
|
|
16
|
-
|
|
16
|
+
selectedValue: string;
|
|
17
17
|
mode: 'input' | 'view';
|
|
18
18
|
onChange: (value: string) => void;
|
|
19
19
|
};
|
|
@@ -33,12 +33,11 @@ export type SegmentedControlProps = SegmentedControlPropsBase &
|
|
|
33
33
|
|
|
34
34
|
const SegmentedControl = ({
|
|
35
35
|
name,
|
|
36
|
-
|
|
36
|
+
selectedValue,
|
|
37
37
|
mode = 'input',
|
|
38
38
|
segments,
|
|
39
39
|
onChange,
|
|
40
40
|
}: SegmentedControlProps) => {
|
|
41
|
-
const [selectedValue, setSelectedValue] = useState(defaultValue || segments[0].value);
|
|
42
41
|
const [animate, setAnimate] = useState(false);
|
|
43
42
|
|
|
44
43
|
const segmentsRef = useRef<HTMLDivElement>(null);
|
|
@@ -70,6 +69,7 @@ const SegmentedControl = ({
|
|
|
70
69
|
};
|
|
71
70
|
|
|
72
71
|
useEffect(() => {
|
|
72
|
+
setAnimate(true);
|
|
73
73
|
updateSegmentPosition();
|
|
74
74
|
|
|
75
75
|
const handleWindowSizeChange = () => {
|
|
@@ -102,7 +102,6 @@ const SegmentedControl = ({
|
|
|
102
102
|
{segmentsWithRefs.map((segment) => {
|
|
103
103
|
const onSelect = () => {
|
|
104
104
|
setAnimate(true);
|
|
105
|
-
setSelectedValue(segment.value);
|
|
106
105
|
onChange(segment.value);
|
|
107
106
|
};
|
|
108
107
|
return mode === 'input' ? (
|
package/src/select/Select.js
CHANGED
|
@@ -246,8 +246,8 @@ export default function Select({
|
|
|
246
246
|
};
|
|
247
247
|
|
|
248
248
|
function selectKeyboardFocusedOption() {
|
|
249
|
-
if (keyboardFocusedOptionIndex != null
|
|
250
|
-
selectOption(selectableOptions[keyboardFocusedOptionIndex]);
|
|
249
|
+
if (keyboardFocusedOptionIndex != null) {
|
|
250
|
+
selectableOptions.length > 0 && selectOption(selectableOptions[keyboardFocusedOptionIndex]);
|
|
251
251
|
}
|
|
252
252
|
}
|
|
253
253
|
|
|
@@ -511,6 +511,7 @@ export default function Select({
|
|
|
511
511
|
disabled={disabled}
|
|
512
512
|
aria-controls={listboxId}
|
|
513
513
|
aria-expanded={open}
|
|
514
|
+
aria-autocomplete="none"
|
|
514
515
|
onClick={handleOnClick}
|
|
515
516
|
{...buttonProps}
|
|
516
517
|
>
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
function supportsTouchEvents() {
|
|
2
2
|
const onTouchStartIsDefined = typeof window !== 'undefined' && window.ontouchstart !== undefined;
|
|
3
|
+
// eslint-disable-next-line compat/compat
|
|
3
4
|
const maxTouchPointsIsDefined = typeof navigator !== 'undefined' && navigator.maxTouchPoints;
|
|
4
5
|
const documentTouchIsDefined =
|
|
5
6
|
typeof window !== 'undefined' &&
|
|
@@ -20,7 +21,7 @@ function userAgentSuggestsTouchDevice() {
|
|
|
20
21
|
'bada',
|
|
21
22
|
];
|
|
22
23
|
const matchString = sampleTouchDevices.map((device) => `(${device})`).join('|');
|
|
23
|
-
const regex = new RegExp(matchString, '
|
|
24
|
+
const regex = new RegExp(matchString, 'ig');
|
|
24
25
|
return typeof navigator !== 'undefined' && !!navigator.userAgent.match(regex);
|
|
25
26
|
}
|
|
26
27
|
// Important: this is not fool-proof! It gives false positives and negatives, and will be outdated.
|
|
@@ -2,17 +2,12 @@ import { isTouchDevice } from './deviceDetection';
|
|
|
2
2
|
|
|
3
3
|
describe('Device detection', () => {
|
|
4
4
|
function fakeUserAgent(userAgent) {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
configurable: true,
|
|
8
|
-
});
|
|
5
|
+
navigator.__defineGetter__('userAgent', () => userAgent);
|
|
6
|
+
// need to use this instead of defineProperty, as it's blocked from overriding
|
|
9
7
|
}
|
|
10
8
|
|
|
11
9
|
function fakeMaxTouchPoints(maxTouchPoints) {
|
|
12
|
-
|
|
13
|
-
value: maxTouchPoints,
|
|
14
|
-
configurable: true,
|
|
15
|
-
});
|
|
10
|
+
navigator.__defineGetter__('maxTouchPoints', () => maxTouchPoints);
|
|
16
11
|
}
|
|
17
12
|
|
|
18
13
|
// We don't test DocumentTouch api as it's basically impossible to test :(
|
package/src/test-utils/index.js
CHANGED
|
@@ -13,7 +13,7 @@ import en from '../i18n/en.json';
|
|
|
13
13
|
*/
|
|
14
14
|
function customRender(ui, { locale = DEFAULT_LOCALE, messages = en, ...renderOptions } = {}) {
|
|
15
15
|
// eslint-disable-next-line react/prop-types
|
|
16
|
-
|
|
16
|
+
var Wrapper = ({ children }) => {
|
|
17
17
|
return <Provider i18n={{ locale, messages }}>{children}</Provider>;
|
|
18
18
|
};
|
|
19
19
|
return render(ui, { wrapper: Wrapper, ...renderOptions });
|
|
@@ -30,7 +30,7 @@ interface StoryConfig {
|
|
|
30
30
|
|
|
31
31
|
const getViewportWidth = (viewport: (typeof viewports)[keyof typeof viewports]) => {
|
|
32
32
|
if (viewport.styles && typeof viewport.styles === 'object') {
|
|
33
|
-
return
|
|
33
|
+
return parseInt(viewport.styles.width);
|
|
34
34
|
}
|
|
35
35
|
throw new Error('Unknown viewport styles');
|
|
36
36
|
};
|
package/src/title/Title.spec.tsx
CHANGED
|
@@ -34,7 +34,7 @@ describe('Title', () => {
|
|
|
34
34
|
|
|
35
35
|
it('handles unsupported typography type', () => {
|
|
36
36
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
37
|
-
// @ts-
|
|
37
|
+
// @ts-ignore
|
|
38
38
|
render(<Title type={Typography.BODY_LARGE_BOLD}>Test</Title>);
|
|
39
39
|
const copy = screen.getByText('Test');
|
|
40
40
|
expect(copy).toBeInTheDocument();
|
|
@@ -314,9 +314,7 @@ describe('Typeahead', () => {
|
|
|
314
314
|
let selectedOption;
|
|
315
315
|
|
|
316
316
|
component.setProps({
|
|
317
|
-
onChange: (selections) =>
|
|
318
|
-
selectedOption = selections[0];
|
|
319
|
-
},
|
|
317
|
+
onChange: (selections) => (selectedOption = selections[0]),
|
|
320
318
|
options: options,
|
|
321
319
|
});
|
|
322
320
|
|
|
@@ -342,7 +340,7 @@ describe('Typeahead', () => {
|
|
|
342
340
|
it('renders all options', () => {
|
|
343
341
|
const options = option().map((optNode) => optNode.text());
|
|
344
342
|
expect(options).toHaveLength(props.options.length);
|
|
345
|
-
expect(options.every((label, i) => label === props.options[i].label))
|
|
343
|
+
expect(options.every((label, i) => label === props.options[i].label));
|
|
346
344
|
});
|
|
347
345
|
|
|
348
346
|
it('does not render new option if showNewEntry is false', () => {
|
|
@@ -8,7 +8,7 @@ import Upload from '.';
|
|
|
8
8
|
|
|
9
9
|
jest.useFakeTimers();
|
|
10
10
|
jest.mock('./utils/postData', () => ({
|
|
11
|
-
postData:
|
|
11
|
+
postData: () => new Promise((resolve) => resolve('ServerResponse')),
|
|
12
12
|
}));
|
|
13
13
|
|
|
14
14
|
jest.mock('./utils/asyncFileRead');
|
|
@@ -88,7 +88,7 @@ describe('Upload', () => {
|
|
|
88
88
|
let component;
|
|
89
89
|
beforeEach(() => {
|
|
90
90
|
component = shallow(<Upload {...props} />).dive();
|
|
91
|
-
asyncFileRead.mockImplementation(
|
|
91
|
+
asyncFileRead.mockImplementation(() => new Promise((resolve) => resolve('a value')));
|
|
92
92
|
});
|
|
93
93
|
|
|
94
94
|
afterEach(() => {
|
|
@@ -183,9 +183,7 @@ describe('Upload', () => {
|
|
|
183
183
|
});
|
|
184
184
|
|
|
185
185
|
it('step ProcessingStep is called with error props', async () => {
|
|
186
|
-
asyncFileRead.mockImplementation(
|
|
187
|
-
throw 'An error';
|
|
188
|
-
});
|
|
186
|
+
asyncFileRead.mockImplementation(() => new Promise((resolve, reject) => reject('An error')));
|
|
189
187
|
|
|
190
188
|
await component.instance().fileDropped(TEST_FILE);
|
|
191
189
|
jest.advanceTimersByTime(props.animationDelay);
|
|
@@ -232,9 +230,7 @@ describe('Upload', () => {
|
|
|
232
230
|
it('step CompleteStep is called with error props', async () => {
|
|
233
231
|
component = mount(<Upload {...props} />);
|
|
234
232
|
const upload = component.children();
|
|
235
|
-
asyncFileRead.mockImplementation(
|
|
236
|
-
throw 'An error';
|
|
237
|
-
});
|
|
233
|
+
asyncFileRead.mockImplementation(() => new Promise((resolve, reject) => reject('An error')));
|
|
238
234
|
|
|
239
235
|
await upload.instance().fileDropped(TEST_FILE);
|
|
240
236
|
jest.advanceTimersByTime(props.animationDelay + ANIMATION_DELAY);
|
|
@@ -225,7 +225,6 @@ const UploadButton = ({
|
|
|
225
225
|
data-testid={TEST_IDS.uploadInput}
|
|
226
226
|
onChange={filesSelected}
|
|
227
227
|
/>
|
|
228
|
-
{/* eslint-disable-next-line jsx-a11y/label-has-associated-control */}
|
|
229
228
|
<label
|
|
230
229
|
htmlFor={id}
|
|
231
230
|
className={classNames('btn', 'np-upload-accent', 'np-upload-button', {
|