@transferwise/components 46.160.5 → 46.160.7
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/ExpressiveMoneyInput/AmountInput/AmountInput.js +2 -0
- package/build/ExpressiveMoneyInput/AmountInput/AmountInput.js.map +1 -1
- package/build/ExpressiveMoneyInput/AmountInput/AmountInput.mjs +2 -0
- package/build/ExpressiveMoneyInput/AmountInput/AmountInput.mjs.map +1 -1
- package/build/ExpressiveMoneyInput/hooks/useInputStyle.js +1 -1
- package/build/ExpressiveMoneyInput/hooks/useInputStyle.js.map +1 -1
- package/build/ExpressiveMoneyInput/hooks/useInputStyle.mjs +1 -1
- package/build/ExpressiveMoneyInput/hooks/useInputStyle.mjs.map +1 -1
- package/build/Inputs/Input/Input.js +1 -0
- package/build/Inputs/Input/Input.js.map +1 -1
- package/build/Inputs/Input/Input.mjs +1 -0
- package/build/Inputs/Input/Input.mjs.map +1 -1
- package/build/Inputs/TextArea/TextArea.js +1 -0
- package/build/Inputs/TextArea/TextArea.js.map +1 -1
- package/build/Inputs/TextArea/TextArea.mjs +1 -0
- package/build/Inputs/TextArea/TextArea.mjs.map +1 -1
- package/build/MoneyInput/MoneyInput.js +1 -0
- package/build/MoneyInput/MoneyInput.js.map +1 -1
- package/build/MoneyInput/MoneyInput.mjs +1 -0
- package/build/MoneyInput/MoneyInput.mjs.map +1 -1
- package/build/PhoneNumberInput/PhoneNumberInput.js +3 -2
- package/build/PhoneNumberInput/PhoneNumberInput.js.map +1 -1
- package/build/PhoneNumberInput/PhoneNumberInput.messages.js +3 -0
- package/build/PhoneNumberInput/PhoneNumberInput.messages.js.map +1 -1
- package/build/PhoneNumberInput/PhoneNumberInput.messages.mjs +3 -0
- package/build/PhoneNumberInput/PhoneNumberInput.messages.mjs.map +1 -1
- package/build/PhoneNumberInput/PhoneNumberInput.mjs +3 -2
- package/build/PhoneNumberInput/PhoneNumberInput.mjs.map +1 -1
- package/build/Prompt/InfoPrompt/InfoPrompt.js +13 -6
- package/build/Prompt/InfoPrompt/InfoPrompt.js.map +1 -1
- package/build/Prompt/InfoPrompt/InfoPrompt.mjs +13 -6
- package/build/Prompt/InfoPrompt/InfoPrompt.mjs.map +1 -1
- package/build/Prompt/PrimitivePrompt/PrimitivePrompt.js +1 -0
- package/build/Prompt/PrimitivePrompt/PrimitivePrompt.js.map +1 -1
- package/build/Prompt/PrimitivePrompt/PrimitivePrompt.mjs +1 -0
- package/build/Prompt/PrimitivePrompt/PrimitivePrompt.mjs.map +1 -1
- package/build/Typeahead/TypeaheadInput/TypeaheadInput.js +50 -20
- package/build/Typeahead/TypeaheadInput/TypeaheadInput.js.map +1 -1
- package/build/Typeahead/TypeaheadInput/TypeaheadInput.mjs +50 -20
- package/build/Typeahead/TypeaheadInput/TypeaheadInput.mjs.map +1 -1
- package/build/i18n/en.json +1 -0
- package/build/i18n/en.json.js +1 -0
- package/build/i18n/en.json.js.map +1 -1
- package/build/i18n/en.json.mjs +1 -0
- package/build/i18n/en.json.mjs.map +1 -1
- package/build/main.css +68 -1
- package/build/styles/ExpressiveMoneyInput/AmountInput/AmountInput.css +6 -0
- package/build/styles/ExpressiveMoneyInput/ExpressiveMoneyInput.css +6 -0
- package/build/styles/Inputs/Input/Input.css +19 -0
- package/build/styles/Inputs/TextArea/TextArea.css +16 -1
- package/build/styles/MoneyInput/MoneyInput.css +3 -0
- package/build/styles/PhoneNumberInput/PhoneNumberInput.css +7 -0
- package/build/styles/Prompt/PrimitivePrompt/PrimitivePrompt.css +17 -0
- package/build/styles/main.css +68 -1
- package/build/types/ExpressiveMoneyInput/AmountInput/AmountInput.d.ts.map +1 -1
- package/build/types/ExpressiveMoneyInput/hooks/useInputStyle.d.ts.map +1 -1
- package/build/types/Inputs/Input/Input.d.ts.map +1 -1
- package/build/types/Inputs/TextArea/TextArea.d.ts.map +1 -1
- package/build/types/MoneyInput/MoneyInput.d.ts.map +1 -1
- package/build/types/PhoneNumberInput/PhoneNumberInput.d.ts.map +1 -1
- package/build/types/PhoneNumberInput/PhoneNumberInput.messages.d.ts +5 -0
- package/build/types/PhoneNumberInput/PhoneNumberInput.messages.d.ts.map +1 -1
- package/build/types/Prompt/InfoPrompt/InfoPrompt.d.ts.map +1 -1
- package/build/types/Prompt/PrimitivePrompt/PrimitivePrompt.d.ts.map +1 -1
- package/build/types/Typeahead/TypeaheadInput/TypeaheadInput.d.ts +1 -1
- package/build/types/Typeahead/TypeaheadInput/TypeaheadInput.d.ts.map +1 -1
- package/build/types/test-utils/index.d.ts +2 -0
- package/build/types/test-utils/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/ExpressiveMoneyInput/AmountInput/AmountInput.css +6 -0
- package/src/ExpressiveMoneyInput/AmountInput/AmountInput.less +7 -0
- package/src/ExpressiveMoneyInput/AmountInput/AmountInput.tsx +2 -1
- package/src/ExpressiveMoneyInput/ExpressiveMoneyInput.css +6 -0
- package/src/ExpressiveMoneyInput/hooks/useInputStyle.ts +1 -2
- package/src/Field/_stories/Field.test.story.tsx +27 -0
- package/src/Inputs/Input/Input.css +19 -0
- package/src/Inputs/Input/Input.less +16 -0
- package/src/Inputs/Input/Input.tsx +1 -0
- package/src/Inputs/{_stories → InputGroup/_stories}/InputGroup.story.tsx +9 -9
- package/src/Inputs/InputGroup/_stories/InputGroup.test.story.tsx +80 -0
- package/src/Inputs/{_stories → SearchInput/_stories}/SearchInput.story.tsx +4 -4
- package/src/Inputs/SearchInput/_stories/SearchInput.test.story.tsx +38 -0
- package/src/Inputs/SelectInput/_stories/SelectInput.rtl.test.story.tsx +31 -19
- package/src/Inputs/TextArea/TextArea.css +16 -1
- package/src/Inputs/TextArea/TextArea.less +6 -1
- package/src/Inputs/TextArea/TextArea.tsx +1 -0
- package/src/Inputs/{_stories → TextArea/_stories}/TextArea.story.tsx +4 -4
- package/src/Inputs/{_stories → TextArea/_stories}/TextArea.test.story.tsx +50 -26
- package/src/MoneyInput/MoneyInput.css +3 -0
- package/src/MoneyInput/MoneyInput.less +6 -0
- package/src/MoneyInput/MoneyInput.tsx +1 -0
- package/src/MoneyInput/_stories/MoneyInput.rtl.test.story.tsx +14 -10
- package/src/PhoneNumberInput/PhoneNumberInput.css +7 -0
- package/src/PhoneNumberInput/PhoneNumberInput.less +13 -0
- package/src/PhoneNumberInput/PhoneNumberInput.messages.ts +5 -0
- package/src/PhoneNumberInput/PhoneNumberInput.tsx +3 -2
- package/src/PhoneNumberInput/_stories/PhoneNumberInput.rtl.test.story.tsx +15 -7
- package/src/Prompt/InfoPrompt/InfoPrompt.test.tsx +56 -2
- package/src/Prompt/InfoPrompt/InfoPrompt.tsx +14 -7
- package/src/Prompt/InfoPrompt/_stories/InfoPrompt.story.tsx +6 -1
- package/src/Prompt/PrimitivePrompt/PrimitivePrompt.css +17 -0
- package/src/Prompt/PrimitivePrompt/PrimitivePrompt.less +15 -0
- package/src/Prompt/PrimitivePrompt/PrimitivePrompt.tsx +1 -0
- package/src/TextareaWithDisplayFormat/_stories/TextareaWithDisplayFormat.test.story.tsx +49 -34
- package/src/Typeahead/TypeaheadInput/TypeaheadInput.tsx +31 -20
- package/src/Typeahead/_stories/Typeahead.rtl.test.story.tsx +90 -57
- package/src/_deprecated/Select/_stories/Select.rtl.test.story.tsx +8 -1
- package/src/main.css +68 -1
- package/src/Inputs/_stories/InputGroup.test.story.tsx +0 -69
- package/src/Inputs/_stories/SearchInput.test.story.tsx +0 -32
|
@@ -6,6 +6,12 @@
|
|
|
6
6
|
.tw-telephone__number-input {
|
|
7
7
|
margin-left: var(--size-12);
|
|
8
8
|
}
|
|
9
|
+
|
|
10
|
+
&__number-input-field{
|
|
11
|
+
[dir="rtl"] & {
|
|
12
|
+
text-align: end;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
9
15
|
}
|
|
10
16
|
|
|
11
17
|
.tw-telephone__country-select {
|
|
@@ -15,6 +21,13 @@
|
|
|
15
21
|
.np-input-group {
|
|
16
22
|
width: 100%;
|
|
17
23
|
}
|
|
24
|
+
|
|
25
|
+
.np-select-input-content{
|
|
26
|
+
[dir="rtl"] & {
|
|
27
|
+
text-align: end;
|
|
28
|
+
direction: ltr;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
18
31
|
}
|
|
19
32
|
|
|
20
33
|
.tw-telephone__country-select--with-input-group-addon-start {
|
|
@@ -13,4 +13,9 @@ export default defineMessages({
|
|
|
13
13
|
id: 'neptune.PhoneNumberInput.phoneNumberLabel',
|
|
14
14
|
defaultMessage: 'Phone number',
|
|
15
15
|
},
|
|
16
|
+
phoneNumberPrefix: {
|
|
17
|
+
id: 'neptune.PhoneNumberInput.phoneNumberPrefix',
|
|
18
|
+
defaultMessage: 'Prefix',
|
|
19
|
+
description: 'phone number prefix / country code',
|
|
20
|
+
},
|
|
16
21
|
});
|
|
@@ -59,7 +59,7 @@ const PhoneNumberInput = ({
|
|
|
59
59
|
onFocus,
|
|
60
60
|
onBlur,
|
|
61
61
|
countryCode,
|
|
62
|
-
searchPlaceholder
|
|
62
|
+
searchPlaceholder,
|
|
63
63
|
size = Size.MEDIUM,
|
|
64
64
|
placeholder,
|
|
65
65
|
selectProps = defaultSelectProps,
|
|
@@ -232,7 +232,7 @@ const PhoneNumberInput = ({
|
|
|
232
232
|
/>
|
|
233
233
|
)}
|
|
234
234
|
filterable
|
|
235
|
-
filterPlaceholder={searchPlaceholder}
|
|
235
|
+
filterPlaceholder={searchPlaceholder ?? formatMessage(messages.phoneNumberPrefix)}
|
|
236
236
|
disabled={disabled}
|
|
237
237
|
size={size}
|
|
238
238
|
id={ids.countryCode.select}
|
|
@@ -261,6 +261,7 @@ const PhoneNumberInput = ({
|
|
|
261
261
|
<div className={`input-group input-group-${size} ${disabled ? 'disabled' : ''}`}>
|
|
262
262
|
<Input
|
|
263
263
|
ref={phoneNumberInputRef}
|
|
264
|
+
className="tw-telephone__number-input-field"
|
|
264
265
|
id={ids.phoneNumber.input}
|
|
265
266
|
autoComplete="tel-national"
|
|
266
267
|
name="phoneNumber"
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { Meta, StoryObj } from '@storybook/react-webpack5';
|
|
2
2
|
import { userEvent, within } from 'storybook/test';
|
|
3
3
|
|
|
4
|
-
import { withRTLDiff } from '../../../.storybook/decorators/RtlDiffDecorator';
|
|
4
|
+
import { useRtlDiffI18n, withRTLDiff } from '../../../.storybook/decorators/RtlDiffDecorator';
|
|
5
5
|
import Field from '../../Field/Field';
|
|
6
6
|
import PhoneNumberInput from '..';
|
|
7
7
|
|
|
@@ -37,16 +37,24 @@ export const Sizes: Story = {
|
|
|
37
37
|
};
|
|
38
38
|
|
|
39
39
|
export const DropdownOpen: Story = {
|
|
40
|
-
render: () =>
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
40
|
+
render: () => {
|
|
41
|
+
const i18n = useRtlDiffI18n();
|
|
42
|
+
|
|
43
|
+
return (
|
|
44
|
+
<Field label="Phone number">
|
|
45
|
+
<PhoneNumberInput searchPlaceholder={i18n.searchPlaceholder} onChange={() => {}} />
|
|
46
|
+
</Field>
|
|
47
|
+
);
|
|
48
|
+
},
|
|
45
49
|
play: async ({ canvasElement }) => {
|
|
46
50
|
const canvas = within(canvasElement);
|
|
47
51
|
const trigger = canvas.queryByRole('combobox');
|
|
48
52
|
if (!trigger) return;
|
|
49
53
|
await userEvent.click(trigger);
|
|
50
54
|
},
|
|
51
|
-
...withRTLDiff({
|
|
55
|
+
...withRTLDiff({
|
|
56
|
+
renderAs: 'iframe',
|
|
57
|
+
hAlign: 'center',
|
|
58
|
+
i18n: { searchPlaceholder: { ltr: 'Country code', rtl: 'رمز الدولة' } },
|
|
59
|
+
}),
|
|
52
60
|
};
|
|
@@ -274,11 +274,12 @@ describe('InfoPrompt', () => {
|
|
|
274
274
|
expect(window.location.assign).not.toHaveBeenCalled();
|
|
275
275
|
});
|
|
276
276
|
|
|
277
|
-
it('should
|
|
277
|
+
it('should call onClick on touch tap when action has no href', () => {
|
|
278
|
+
const onClick = jest.fn();
|
|
278
279
|
render(
|
|
279
280
|
<InfoPrompt
|
|
280
281
|
{...defaultProps}
|
|
281
|
-
action={{ label: 'Click me', onClick
|
|
282
|
+
action={{ label: 'Click me', onClick }}
|
|
282
283
|
data-testid="prompt"
|
|
283
284
|
/>,
|
|
284
285
|
);
|
|
@@ -286,8 +287,61 @@ describe('InfoPrompt', () => {
|
|
|
286
287
|
|
|
287
288
|
fireEvent.touchStart(prompt);
|
|
288
289
|
fireEvent.touchEnd(prompt);
|
|
290
|
+
expect(onClick).toHaveBeenCalledTimes(1);
|
|
289
291
|
expect(window.location.assign).not.toHaveBeenCalled();
|
|
290
292
|
expect(window.open).not.toHaveBeenCalled();
|
|
291
293
|
});
|
|
294
|
+
|
|
295
|
+
it('should call onClick and navigate when action has both href and onClick', () => {
|
|
296
|
+
const onClick = jest.fn();
|
|
297
|
+
render(
|
|
298
|
+
<InfoPrompt
|
|
299
|
+
{...defaultProps}
|
|
300
|
+
action={{ label: 'Learn more', href: '/learn-more', onClick }}
|
|
301
|
+
data-testid="prompt"
|
|
302
|
+
/>,
|
|
303
|
+
);
|
|
304
|
+
const prompt = screen.getByTestId('prompt');
|
|
305
|
+
|
|
306
|
+
fireEvent.touchStart(prompt);
|
|
307
|
+
fireEvent.touchEnd(prompt);
|
|
308
|
+
expect(onClick).toHaveBeenCalledTimes(1);
|
|
309
|
+
expect(window.location.assign).toHaveBeenCalledWith('/learn-more');
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
it('should not fire action when tapping dismiss button', () => {
|
|
313
|
+
const onClick = jest.fn();
|
|
314
|
+
render(
|
|
315
|
+
<InfoPrompt
|
|
316
|
+
{...defaultProps}
|
|
317
|
+
action={{ label: 'Learn more', href: '/learn-more', onClick }}
|
|
318
|
+
data-testid="prompt"
|
|
319
|
+
onDismiss={jest.fn()}
|
|
320
|
+
/>,
|
|
321
|
+
);
|
|
322
|
+
const dismissButton = screen.getByRole('button', { name: 'Close', hidden: true });
|
|
323
|
+
|
|
324
|
+
fireEvent.touchStart(dismissButton);
|
|
325
|
+
fireEvent.touchEnd(dismissButton);
|
|
326
|
+
expect(onClick).not.toHaveBeenCalled();
|
|
327
|
+
expect(window.location.assign).not.toHaveBeenCalled();
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
it('should not double-fire onClick when tapping the action element directly', () => {
|
|
331
|
+
const onClick = jest.fn();
|
|
332
|
+
render(
|
|
333
|
+
<InfoPrompt
|
|
334
|
+
{...defaultProps}
|
|
335
|
+
action={{ label: 'Learn more', href: '/learn-more', onClick }}
|
|
336
|
+
data-testid="prompt"
|
|
337
|
+
/>,
|
|
338
|
+
);
|
|
339
|
+
const actionLink = screen.getByRole('link', { name: 'Learn more', hidden: true });
|
|
340
|
+
|
|
341
|
+
fireEvent.touchStart(actionLink);
|
|
342
|
+
fireEvent.touchEnd(actionLink);
|
|
343
|
+
expect(onClick).not.toHaveBeenCalled();
|
|
344
|
+
expect(window.location.assign).not.toHaveBeenCalled();
|
|
345
|
+
});
|
|
292
346
|
});
|
|
293
347
|
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Children, HTMLAttributes, ReactNode, isValidElement, useState } from 'react';
|
|
1
|
+
import { Children, HTMLAttributes, ReactNode, TouchEvent, isValidElement, useState } from 'react';
|
|
2
2
|
import { LiveRegion, Sentiment, Typography } from '../../common';
|
|
3
3
|
import type { AriaLive } from '../../common';
|
|
4
4
|
import { GiftBox } from '@transferwise/icons';
|
|
@@ -103,16 +103,23 @@ export const InfoPrompt = ({
|
|
|
103
103
|
? Sentiment.POSITIVE
|
|
104
104
|
: (sentiment as Exclude<SurfaceSentiment, 'proposition'>);
|
|
105
105
|
|
|
106
|
-
const handleTouchStart = () => {
|
|
106
|
+
const handleTouchStart = (event: TouchEvent<HTMLDivElement>) => {
|
|
107
|
+
const target = event.target as HTMLElement;
|
|
108
|
+
if (target.closest('.wds-prompt__dismiss') || target.closest('.wds-info-prompt__action')) {
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
107
111
|
setShouldFire(true);
|
|
108
112
|
};
|
|
109
113
|
|
|
110
114
|
const handleTouchEnd = () => {
|
|
111
|
-
if (shouldFire && action
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
115
|
+
if (shouldFire && action) {
|
|
116
|
+
(action.onClick as (() => void) | undefined)?.();
|
|
117
|
+
if (action.href) {
|
|
118
|
+
if (action.target === '_blank') {
|
|
119
|
+
window.top?.open(action.href, '_blank', 'noopener, noreferrer');
|
|
120
|
+
} else {
|
|
121
|
+
window.top?.location.assign(action.href);
|
|
122
|
+
}
|
|
116
123
|
}
|
|
117
124
|
}
|
|
118
125
|
setShouldFire(false);
|
|
@@ -236,7 +236,11 @@ export const WithTitle: Story = {
|
|
|
236
236
|
|
|
237
237
|
/**
|
|
238
238
|
* Use the `action` prop to add a clickable link below the message.
|
|
239
|
-
* Actions can have an `href` for navigation
|
|
239
|
+
* Actions can have an `href` for navigation, an `onClick` for custom behavior, or both.
|
|
240
|
+
*
|
|
241
|
+
* On touch devices the entire prompt surface acts as a tap target for the action,
|
|
242
|
+
* making it easier to interact with on small screens. A scroll gesture (touch move)
|
|
243
|
+
* cancels the tap. Tapping the dismiss button does not trigger the action.
|
|
240
244
|
*/
|
|
241
245
|
export const WithAction: Story = {
|
|
242
246
|
render: (args: InfoPromptProps) => (
|
|
@@ -322,6 +326,7 @@ export const Dismissible: Story = {
|
|
|
322
326
|
sentiment="success"
|
|
323
327
|
title="Payment successful"
|
|
324
328
|
description="Your payment was successful."
|
|
329
|
+
action={{ label: 'Learn more', href: '#', target: '_blank' }}
|
|
325
330
|
onDismiss={() => setShowPrompt(false)}
|
|
326
331
|
/>
|
|
327
332
|
) : (
|
|
@@ -51,3 +51,20 @@
|
|
|
51
51
|
grid-column: span 3;
|
|
52
52
|
}
|
|
53
53
|
}
|
|
54
|
+
.wds-prompt__dismiss {
|
|
55
|
+
position: relative;
|
|
56
|
+
}
|
|
57
|
+
.wds-prompt__dismiss:before {
|
|
58
|
+
--size: calc(var(--size-24) + var(--Prompt-padding-x, var(--padding-x-small)) * 2);
|
|
59
|
+
--offset: calc(-1 * var(--Prompt-padding-x, var(--padding-x-small)));
|
|
60
|
+
content: "";
|
|
61
|
+
position: absolute;
|
|
62
|
+
inset: calc(-1 * 8px);
|
|
63
|
+
inset: var(--offset);
|
|
64
|
+
inline-size: calc(24px + 8px * 2);
|
|
65
|
+
inline-size: var(--size);
|
|
66
|
+
block-size: calc(24px + 8px * 2);
|
|
67
|
+
block-size: var(--size);
|
|
68
|
+
border-radius: 10px;
|
|
69
|
+
border-radius: var(--Prompt-border-radius, var(--radius-small));
|
|
70
|
+
}
|
|
@@ -46,4 +46,19 @@
|
|
|
46
46
|
}
|
|
47
47
|
}
|
|
48
48
|
}
|
|
49
|
+
|
|
50
|
+
&__dismiss {
|
|
51
|
+
position: relative;
|
|
52
|
+
|
|
53
|
+
&:before {
|
|
54
|
+
--size: calc(var(--size-24) + var(--Prompt-padding-x, var(--padding-x-small)) * 2);
|
|
55
|
+
--offset: calc(-1 * var(--Prompt-padding-x, var(--padding-x-small)));
|
|
56
|
+
content: "";
|
|
57
|
+
position: absolute;
|
|
58
|
+
inset: var(--offset);
|
|
59
|
+
width: var(--size);
|
|
60
|
+
height: var(--size);
|
|
61
|
+
border-radius: var(--Prompt-border-radius, var(--radius-small));
|
|
62
|
+
}
|
|
63
|
+
}
|
|
49
64
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type { Meta, StoryObj } from '@storybook/react-webpack5';
|
|
2
2
|
|
|
3
|
-
import { withRTLDiff } from '../../../.storybook/decorators/RtlDiffDecorator';
|
|
4
|
-
import TextareaWithDisplayFormat from '..';
|
|
3
|
+
import { useRtlDiffI18n, withRTLDiff } from '../../../.storybook/decorators/RtlDiffDecorator';
|
|
5
4
|
import Field from '../../Field/Field';
|
|
5
|
+
import TextareaWithDisplayFormat from '..';
|
|
6
6
|
|
|
7
7
|
export default {
|
|
8
8
|
component: TextareaWithDisplayFormat,
|
|
@@ -13,36 +13,51 @@ export default {
|
|
|
13
13
|
type Story = StoryObj<typeof TextareaWithDisplayFormat>;
|
|
14
14
|
|
|
15
15
|
export const RTLDiff: Story = {
|
|
16
|
-
render: () =>
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
16
|
+
render: () => {
|
|
17
|
+
const i18n = useRtlDiffI18n();
|
|
18
|
+
|
|
19
|
+
return (
|
|
20
|
+
<>
|
|
21
|
+
<Field id="empty" label="Empty with placeholder">
|
|
22
|
+
<TextareaWithDisplayFormat
|
|
23
|
+
id="empty"
|
|
24
|
+
value=""
|
|
25
|
+
displayPattern="**** - **** - ****"
|
|
26
|
+
placeholder={i18n.placeholder}
|
|
27
|
+
onChange={() => {}}
|
|
28
|
+
/>
|
|
29
|
+
</Field>
|
|
30
|
+
<Field id="card-code" label="Localised pattern">
|
|
31
|
+
<TextareaWithDisplayFormat
|
|
32
|
+
id="card-code"
|
|
33
|
+
value={i18n.value}
|
|
34
|
+
maxLength={16}
|
|
35
|
+
displayPattern="**** - **** - ****"
|
|
36
|
+
onChange={() => {}}
|
|
37
|
+
/>
|
|
38
|
+
</Field>
|
|
39
|
+
<Field id="iban" label="IBAN (not localised)">
|
|
40
|
+
<TextareaWithDisplayFormat
|
|
41
|
+
id="iban"
|
|
42
|
+
value="GB29NWBK60161331926819"
|
|
43
|
+
maxLength={22}
|
|
44
|
+
displayPattern="** **** **** **** **** **"
|
|
45
|
+
onChange={() => {}}
|
|
46
|
+
/>
|
|
47
|
+
</Field>
|
|
48
|
+
</>
|
|
49
|
+
);
|
|
50
|
+
},
|
|
51
|
+
...withRTLDiff({
|
|
52
|
+
i18n: {
|
|
53
|
+
value: {
|
|
54
|
+
ltr: 'XKQMRTBWJNFL',
|
|
55
|
+
rtl: 'خكقمرتبوجنفل',
|
|
56
|
+
},
|
|
57
|
+
placeholder: {
|
|
58
|
+
ltr: 'Enter code',
|
|
59
|
+
rtl: 'أدخل الرمز',
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
}),
|
|
48
63
|
};
|
|
@@ -6,6 +6,8 @@ import { Component, createRef, ReactNode } from 'react';
|
|
|
6
6
|
|
|
7
7
|
import { Input } from '../../Inputs/Input/Input';
|
|
8
8
|
import { TypeaheadOption, TypeaheadProps } from '../Typeahead';
|
|
9
|
+
import { Direction } from '../../common';
|
|
10
|
+
import { DirectionContext } from '../../Provider/Direction';
|
|
9
11
|
|
|
10
12
|
const DEFAULT_INPUT_MIN_WIDTH = 10;
|
|
11
13
|
|
|
@@ -67,7 +69,7 @@ export default class TypeaheadInput<T> extends Component<
|
|
|
67
69
|
});
|
|
68
70
|
};
|
|
69
71
|
|
|
70
|
-
renderInput = () => {
|
|
72
|
+
renderInput = (isRTL: boolean) => {
|
|
71
73
|
const {
|
|
72
74
|
typeaheadId,
|
|
73
75
|
autoFocus,
|
|
@@ -90,6 +92,7 @@ export default class TypeaheadInput<T> extends Component<
|
|
|
90
92
|
return (
|
|
91
93
|
<Input
|
|
92
94
|
ref={this.inputRef}
|
|
95
|
+
dir={isRTL ? 'rtl' : 'auto'}
|
|
93
96
|
className={clsx(multiple && 'typeahead__input')}
|
|
94
97
|
name={name}
|
|
95
98
|
id={`input-${typeaheadId}`}
|
|
@@ -116,26 +119,34 @@ export default class TypeaheadInput<T> extends Component<
|
|
|
116
119
|
render() {
|
|
117
120
|
const { multiple, selected, value, maxHeight, renderChip } = this.props;
|
|
118
121
|
|
|
119
|
-
return
|
|
120
|
-
<
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
onClick={() => {
|
|
124
|
-
this.inputRef.current?.focus();
|
|
125
|
-
}}
|
|
126
|
-
>
|
|
127
|
-
<div className="typeahead__input-wrapper">
|
|
128
|
-
{selected && selected.map((chip, idx) => renderChip(chip, idx))}
|
|
122
|
+
return (
|
|
123
|
+
<DirectionContext.Consumer>
|
|
124
|
+
{(direction) => {
|
|
125
|
+
const isRTL = direction === Direction.RTL;
|
|
129
126
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
127
|
+
return multiple ? (
|
|
128
|
+
<div
|
|
129
|
+
className="form-control typeahead__input-container"
|
|
130
|
+
style={{ maxHeight }}
|
|
131
|
+
onClick={() => {
|
|
132
|
+
this.inputRef.current?.focus();
|
|
133
|
+
}}
|
|
134
|
+
>
|
|
135
|
+
<div className="typeahead__input-wrapper">
|
|
136
|
+
{selected && selected.map((chip, idx) => renderChip(chip, idx))}
|
|
137
|
+
|
|
138
|
+
{this.renderInput(isRTL)}
|
|
139
|
+
<div className="typeahead__input-aligner" />
|
|
140
|
+
</div>
|
|
141
|
+
<div ref={this.sizerRef} className="sizer form-control typeahead__input">
|
|
142
|
+
{value}
|
|
143
|
+
</div>
|
|
144
|
+
</div>
|
|
145
|
+
) : (
|
|
146
|
+
this.renderInput(isRTL)
|
|
147
|
+
);
|
|
148
|
+
}}
|
|
149
|
+
</DirectionContext.Consumer>
|
|
139
150
|
);
|
|
140
151
|
}
|
|
141
152
|
}
|
|
@@ -2,7 +2,11 @@ import type { Meta, StoryObj } from '@storybook/react-webpack5';
|
|
|
2
2
|
import { userEvent, within } from 'storybook/test';
|
|
3
3
|
import { Search as SearchIcon } from '@transferwise/icons';
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
forEachColumn,
|
|
7
|
+
useRtlDiffI18n,
|
|
8
|
+
withRTLDiff,
|
|
9
|
+
} from '../../../.storybook/decorators/RtlDiffDecorator';
|
|
6
10
|
import { Size } from '../../common';
|
|
7
11
|
import Typeahead from '..';
|
|
8
12
|
|
|
@@ -14,70 +18,99 @@ export default {
|
|
|
14
18
|
|
|
15
19
|
type Story = StoryObj<typeof Typeahead>;
|
|
16
20
|
|
|
17
|
-
const
|
|
18
|
-
{
|
|
19
|
-
{
|
|
20
|
-
{
|
|
21
|
-
|
|
21
|
+
const localisedStrings = {
|
|
22
|
+
gbp: { ltr: 'British pound', rtl: 'جنيه إسترليني' },
|
|
23
|
+
eur: { ltr: 'Euro', rtl: 'يورو' },
|
|
24
|
+
usd: { ltr: 'US Dollar', rtl: 'دولار أمريكي' },
|
|
25
|
+
placeholder: { ltr: 'Search currencies…', rtl: 'ابحث عن العملات...' },
|
|
26
|
+
};
|
|
22
27
|
|
|
23
28
|
export const TriggerVariants: Story = {
|
|
24
|
-
render: () =>
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
29
|
+
render: () => {
|
|
30
|
+
const i18n = useRtlDiffI18n();
|
|
31
|
+
|
|
32
|
+
const baseOptions = [
|
|
33
|
+
{ label: i18n.gbp, note: 'GBP' },
|
|
34
|
+
{ label: i18n.eur, note: 'EUR' },
|
|
35
|
+
{ label: i18n.usd, note: 'USD' },
|
|
36
|
+
];
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<>
|
|
40
|
+
<Typeahead
|
|
41
|
+
id="rtl-addon-placeholder"
|
|
42
|
+
name="rtl-addon-placeholder"
|
|
43
|
+
placeholder={i18n.placeholder}
|
|
44
|
+
size={Size.MEDIUM}
|
|
45
|
+
addon={<SearchIcon size={24} />}
|
|
46
|
+
clearable
|
|
47
|
+
options={baseOptions}
|
|
48
|
+
onChange={() => {}}
|
|
49
|
+
/>
|
|
50
|
+
<Typeahead
|
|
51
|
+
id="rtl-addon-md"
|
|
52
|
+
name="rtl-addon-md"
|
|
53
|
+
placeholder={i18n.placeholder}
|
|
54
|
+
size={Size.MEDIUM}
|
|
55
|
+
addon={<SearchIcon size={24} />}
|
|
56
|
+
clearable
|
|
57
|
+
options={baseOptions}
|
|
58
|
+
initialValue={[{ label: i18n.gbp, note: 'GBP' }]}
|
|
59
|
+
onChange={() => {}}
|
|
60
|
+
/>
|
|
61
|
+
<Typeahead
|
|
62
|
+
id="rtl-addon-lg"
|
|
63
|
+
name="rtl-addon-lg"
|
|
64
|
+
placeholder={i18n.placeholder}
|
|
65
|
+
size={Size.LARGE}
|
|
66
|
+
addon={<SearchIcon size={24} />}
|
|
67
|
+
clearable
|
|
68
|
+
options={baseOptions}
|
|
69
|
+
initialValue={[{ label: i18n.eur, note: 'EUR' }]}
|
|
70
|
+
onChange={() => {}}
|
|
71
|
+
/>
|
|
72
|
+
<Typeahead
|
|
73
|
+
id="rtl-multiple"
|
|
74
|
+
name="rtl-multiple"
|
|
75
|
+
placeholder={i18n.placeholder}
|
|
76
|
+
size={Size.MEDIUM}
|
|
77
|
+
addon={<SearchIcon size={24} />}
|
|
78
|
+
multiple
|
|
79
|
+
clearable
|
|
80
|
+
options={baseOptions}
|
|
81
|
+
initialValue={[
|
|
82
|
+
{ label: i18n.gbp, note: 'GBP' },
|
|
83
|
+
{ label: i18n.eur, note: 'EUR' },
|
|
84
|
+
]}
|
|
85
|
+
onChange={() => {}}
|
|
86
|
+
/>
|
|
87
|
+
</>
|
|
88
|
+
);
|
|
89
|
+
},
|
|
90
|
+
...withRTLDiff({ width: '80%', i18n: localisedStrings }),
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
export const OpenDropdown: Story = {
|
|
94
|
+
render: () => {
|
|
95
|
+
const i18n = useRtlDiffI18n();
|
|
96
|
+
|
|
97
|
+
return (
|
|
37
98
|
<Typeahead
|
|
38
|
-
id="rtl-
|
|
39
|
-
name="rtl-
|
|
40
|
-
placeholder=
|
|
99
|
+
id="rtl-open"
|
|
100
|
+
name="rtl-open"
|
|
101
|
+
placeholder={i18n.placeholder}
|
|
41
102
|
size={Size.LARGE}
|
|
42
103
|
addon={<SearchIcon size={24} />}
|
|
43
104
|
clearable
|
|
44
|
-
options={
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
<Typeahead
|
|
49
|
-
id="rtl-multiple"
|
|
50
|
-
name="rtl-multiple"
|
|
51
|
-
placeholder="Search currencies…"
|
|
52
|
-
size={Size.MEDIUM}
|
|
53
|
-
addon={<SearchIcon size={24} />}
|
|
54
|
-
multiple
|
|
55
|
-
clearable
|
|
56
|
-
options={baseOptions}
|
|
57
|
-
initialValue={[
|
|
58
|
-
{ label: 'British pound', note: 'GBP' },
|
|
59
|
-
{ label: 'Euro', note: 'EUR' },
|
|
105
|
+
options={[
|
|
106
|
+
{ label: i18n.gbp, note: 'GBP' },
|
|
107
|
+
{ label: i18n.eur, note: 'EUR' },
|
|
108
|
+
{ label: i18n.usd, note: 'USD' },
|
|
60
109
|
]}
|
|
61
110
|
onChange={() => {}}
|
|
62
111
|
/>
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
...withRTLDiff({ width: '80%' }),
|
|
66
|
-
};
|
|
67
|
-
|
|
68
|
-
export const OpenDropdown: Story = {
|
|
69
|
-
render: () => (
|
|
70
|
-
<Typeahead
|
|
71
|
-
id="rtl-open"
|
|
72
|
-
name="rtl-open"
|
|
73
|
-
placeholder="Search currencies…"
|
|
74
|
-
size={Size.LARGE}
|
|
75
|
-
addon={<SearchIcon size={24} />}
|
|
76
|
-
clearable
|
|
77
|
-
options={baseOptions}
|
|
78
|
-
onChange={() => {}}
|
|
79
|
-
/>
|
|
80
|
-
),
|
|
112
|
+
);
|
|
113
|
+
},
|
|
81
114
|
play: async ({ canvasElement }) => {
|
|
82
115
|
await forEachColumn(canvasElement, async (column) => {
|
|
83
116
|
const scope = within(column);
|
|
@@ -86,5 +119,5 @@ export const OpenDropdown: Story = {
|
|
|
86
119
|
await userEvent.keyboard('Brit');
|
|
87
120
|
});
|
|
88
121
|
},
|
|
89
|
-
...withRTLDiff({ width: '80%' }),
|
|
122
|
+
...withRTLDiff({ width: '80%', i18n: localisedStrings }),
|
|
90
123
|
};
|
|
@@ -67,5 +67,12 @@ export const OpenDropdown: Story = {
|
|
|
67
67
|
if (!trigger) return;
|
|
68
68
|
await userEvent.click(trigger);
|
|
69
69
|
},
|
|
70
|
-
...withRTLDiff({
|
|
70
|
+
...withRTLDiff({
|
|
71
|
+
renderAs: 'iframe',
|
|
72
|
+
width: '80%',
|
|
73
|
+
i18n: {
|
|
74
|
+
searchPlaceholder: { ltr: 'Type a currency', rtl: 'اكتب عملة' },
|
|
75
|
+
placeholder: { ltr: 'Select currency', rtl: 'اختر العملة' },
|
|
76
|
+
},
|
|
77
|
+
}),
|
|
71
78
|
};
|