@transferwise/components 46.125.0 → 46.126.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.
- package/build/avatarView/AvatarView.js.map +1 -1
- package/build/avatarView/AvatarView.mjs.map +1 -1
- package/build/common/locale/index.js +13 -0
- package/build/common/locale/index.js.map +1 -1
- package/build/common/locale/index.mjs +13 -1
- package/build/common/locale/index.mjs.map +1 -1
- package/build/expressiveMoneyInput/currencySelector/CurrencySelector.js +31 -1
- package/build/expressiveMoneyInput/currencySelector/CurrencySelector.js.map +1 -1
- package/build/expressiveMoneyInput/currencySelector/CurrencySelector.mjs +32 -2
- package/build/expressiveMoneyInput/currencySelector/CurrencySelector.mjs.map +1 -1
- package/build/field/Field.js +1 -0
- package/build/field/Field.js.map +1 -1
- package/build/field/Field.mjs +1 -0
- package/build/field/Field.mjs.map +1 -1
- package/build/index.js +3 -0
- package/build/index.js.map +1 -1
- package/build/index.mjs +2 -1
- package/build/index.mjs.map +1 -1
- package/build/inputs/Input.js.map +1 -1
- package/build/inputs/Input.mjs.map +1 -1
- package/build/inputs/SearchInput.js.map +1 -1
- package/build/inputs/SearchInput.mjs.map +1 -1
- package/build/inputs/SelectInput.js.map +1 -1
- package/build/inputs/SelectInput.mjs.map +1 -1
- package/build/inputs/TextArea.js.map +1 -1
- package/build/inputs/TextArea.mjs.map +1 -1
- package/build/listItem/ListItem.js +2 -2
- package/build/listItem/ListItem.js.map +1 -1
- package/build/listItem/ListItem.mjs +2 -2
- package/build/listItem/ListItem.mjs.map +1 -1
- package/build/listItem/Prompt/ListItemPrompt.js +1 -0
- package/build/listItem/Prompt/ListItemPrompt.js.map +1 -1
- package/build/listItem/Prompt/ListItemPrompt.mjs +1 -0
- package/build/listItem/Prompt/ListItemPrompt.mjs.map +1 -1
- package/build/main.css +31 -0
- package/build/moneyInput/MoneyInput.js +6 -1
- package/build/moneyInput/MoneyInput.js.map +1 -1
- package/build/moneyInput/MoneyInput.mjs +6 -1
- package/build/moneyInput/MoneyInput.mjs.map +1 -1
- package/build/prompt/ActionPrompt/ActionPrompt.js +27 -4
- package/build/prompt/ActionPrompt/ActionPrompt.js.map +1 -1
- package/build/prompt/ActionPrompt/ActionPrompt.mjs +27 -4
- package/build/prompt/ActionPrompt/ActionPrompt.mjs.map +1 -1
- package/build/prompt/InfoPrompt/InfoPrompt.js +113 -0
- package/build/prompt/InfoPrompt/InfoPrompt.js.map +1 -0
- package/build/prompt/InfoPrompt/InfoPrompt.mjs +111 -0
- package/build/prompt/InfoPrompt/InfoPrompt.mjs.map +1 -0
- package/build/prompt/PrimitivePrompt/PrimitivePrompt.js.map +1 -1
- package/build/prompt/PrimitivePrompt/PrimitivePrompt.mjs.map +1 -1
- package/build/radioOption/RadioOption.js.map +1 -1
- package/build/radioOption/RadioOption.mjs.map +1 -1
- package/build/slidingPanel/SlidingPanel.js.map +1 -1
- package/build/slidingPanel/SlidingPanel.mjs.map +1 -1
- package/build/statusIcon/StatusIcon.js +2 -0
- package/build/statusIcon/StatusIcon.js.map +1 -1
- package/build/statusIcon/StatusIcon.mjs +2 -0
- package/build/statusIcon/StatusIcon.mjs.map +1 -1
- package/build/styles/main.css +31 -0
- package/build/styles/prompt/InfoPrompt/InfoPrompt.css +31 -0
- package/build/table/TableCell.js.map +1 -1
- package/build/table/TableCell.mjs.map +1 -1
- package/build/typeahead/Typeahead.js +1 -0
- package/build/typeahead/Typeahead.js.map +1 -1
- package/build/typeahead/Typeahead.mjs +1 -0
- package/build/typeahead/Typeahead.mjs.map +1 -1
- package/build/types/avatarView/AvatarView.d.ts +1 -1
- package/build/types/avatarView/AvatarView.d.ts.map +1 -1
- package/build/types/common/locale/index.d.ts +8 -0
- package/build/types/common/locale/index.d.ts.map +1 -1
- package/build/types/expressiveMoneyInput/currencySelector/CurrencySelector.d.ts.map +1 -1
- package/build/types/index.d.ts +3 -2
- package/build/types/index.d.ts.map +1 -1
- package/build/types/inputs/Input.d.ts.map +1 -1
- package/build/types/inputs/SearchInput.d.ts.map +1 -1
- package/build/types/inputs/SelectInput.d.ts +1 -1
- package/build/types/inputs/SelectInput.d.ts.map +1 -1
- package/build/types/inputs/TextArea.d.ts.map +1 -1
- package/build/types/moneyInput/MoneyInput.d.ts.map +1 -1
- package/build/types/primitives/PrimitiveAnchor/PrimitiveAnchor.types.d.ts.map +1 -1
- package/build/types/primitives/PrimitiveButton/PrimitiveButton.types.d.ts.map +1 -1
- package/build/types/prompt/ActionPrompt/ActionPrompt.d.ts +4 -2
- package/build/types/prompt/ActionPrompt/ActionPrompt.d.ts.map +1 -1
- package/build/types/prompt/InfoPrompt/InfoPrompt.d.ts +56 -0
- package/build/types/prompt/InfoPrompt/InfoPrompt.d.ts.map +1 -0
- package/build/types/prompt/InfoPrompt/index.d.ts +3 -0
- package/build/types/prompt/InfoPrompt/index.d.ts.map +1 -0
- package/build/types/prompt/PrimitivePrompt/PrimitivePrompt.d.ts +5 -5
- package/build/types/prompt/PrimitivePrompt/PrimitivePrompt.d.ts.map +1 -1
- package/build/types/prompt/index.d.ts +2 -0
- package/build/types/prompt/index.d.ts.map +1 -1
- package/build/types/radioOption/RadioOption.d.ts.map +1 -1
- package/build/types/slidingPanel/SlidingPanel.d.ts.map +1 -1
- package/build/types/statusIcon/StatusIcon.d.ts +2 -1
- package/build/types/statusIcon/StatusIcon.d.ts.map +1 -1
- package/build/types/table/TableCell.d.ts.map +1 -1
- package/build/types/withDisplayFormat/WithDisplayFormat.d.ts.map +1 -1
- package/build/withDisplayFormat/WithDisplayFormat.js.map +1 -1
- package/build/withDisplayFormat/WithDisplayFormat.mjs.map +1 -1
- package/package.json +2 -2
- package/src/avatarLayout/AvatarLayout.story.tsx +3 -3
- package/src/avatarView/AvatarView.story.tsx +29 -24
- package/src/avatarView/AvatarView.tsx +1 -1
- package/src/common/bottomSheet/BottomSheet.test.story.tsx +98 -0
- package/src/common/locale/index.test.ts +36 -1
- package/src/common/locale/index.ts +13 -0
- package/src/expressiveMoneyInput/currencySelector/CurrencySelector.tsx +5 -1
- package/src/index.ts +3 -1
- package/src/inputs/Input.tsx +8 -9
- package/src/inputs/SearchInput.tsx +8 -9
- package/src/inputs/SelectInput.test.story.tsx +86 -0
- package/src/inputs/SelectInput.tsx +1 -1
- package/src/inputs/TextArea.tsx +6 -7
- package/src/listItem/ListItem.tsx +2 -2
- package/src/main.css +31 -0
- package/src/main.less +2 -1
- package/src/moneyInput/MoneyInput.test.story.tsx +104 -0
- package/src/moneyInput/MoneyInput.tsx +20 -2
- package/src/primitives/PrimitiveAnchor/PrimitiveAnchor.types.ts +1 -3
- package/src/primitives/PrimitiveButton/PrimitiveButton.types.ts +1 -3
- package/src/prompt/ActionPrompt/ActionPrompt.accessibility.docs.mdx +65 -0
- package/src/prompt/ActionPrompt/ActionPrompt.less +1 -1
- package/src/prompt/ActionPrompt/ActionPrompt.story.tsx +4 -1
- package/src/prompt/ActionPrompt/ActionPrompt.test.story.tsx +147 -0
- package/src/prompt/ActionPrompt/ActionPrompt.test.tsx +2 -7
- package/src/prompt/ActionPrompt/ActionPrompt.tsx +48 -7
- package/src/prompt/InfoPrompt/InfoPrompt.css +31 -0
- package/src/prompt/InfoPrompt/InfoPrompt.less +37 -0
- package/src/prompt/InfoPrompt/InfoPrompt.story.tsx +312 -0
- package/src/prompt/InfoPrompt/InfoPrompt.test.story.tsx +246 -0
- package/src/prompt/InfoPrompt/InfoPrompt.test.tsx +224 -0
- package/src/prompt/InfoPrompt/InfoPrompt.tsx +148 -0
- package/src/prompt/InfoPrompt/index.ts +2 -0
- package/src/prompt/PrimitivePrompt/PrimitivePrompt.less +1 -1
- package/src/prompt/PrimitivePrompt/PrimitivePrompt.tsx +5 -5
- package/src/prompt/index.ts +5 -0
- package/src/radioOption/RadioOption.tsx +2 -1
- package/src/slidingPanel/SlidingPanel.tsx +4 -2
- package/src/ssr.test.tsx +2 -0
- package/src/statusIcon/StatusIcon.tsx +8 -1
- package/src/table/TableCell.tsx +1 -3
- package/src/withDisplayFormat/WithDisplayFormat.tsx +13 -14
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react-webpack5';
|
|
2
|
+
import { fireEvent, fn, type Mock, within } from 'storybook/test';
|
|
3
|
+
import { allModes } from '../../.storybook/modes';
|
|
4
|
+
import { lorem5, lorem500 } from '../test-utils';
|
|
5
|
+
import { Field } from '../field/Field';
|
|
6
|
+
import Body from '../body';
|
|
7
|
+
import { SelectInput, type SelectInputProps } from './SelectInput';
|
|
8
|
+
|
|
9
|
+
const meta = {
|
|
10
|
+
title: 'Forms/SelectInput/tests',
|
|
11
|
+
component: SelectInput,
|
|
12
|
+
args: {
|
|
13
|
+
onFilterChange: fn() satisfies Mock,
|
|
14
|
+
onChange: fn() satisfies Mock,
|
|
15
|
+
onClose: fn() satisfies Mock,
|
|
16
|
+
onOpen: fn() satisfies Mock,
|
|
17
|
+
},
|
|
18
|
+
tags: ['!autodocs'],
|
|
19
|
+
} satisfies Meta<typeof SelectInput>;
|
|
20
|
+
export default meta;
|
|
21
|
+
|
|
22
|
+
type Story<T, M extends boolean = false> = StoryObj<SelectInputProps<T, M>>;
|
|
23
|
+
|
|
24
|
+
const wait = async (duration = 500) =>
|
|
25
|
+
new Promise<void>((resolve) => {
|
|
26
|
+
setTimeout(resolve, duration);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* This test ensures that when the SelectInput is used within a scrollable page,
|
|
31
|
+
* opening the dropdown does not cause any unwanted scrolling or layout shifts.
|
|
32
|
+
* Expected preview should start with green bar at the top, with yellow section
|
|
33
|
+
* not in the viewport. The issue is particularly prominent on iOS Safari.
|
|
34
|
+
*
|
|
35
|
+
* NB: This test is disabled in Chromatic as there's no obvious way to control
|
|
36
|
+
* <html/> element of a snapshot. It's to be primarily used in manual testing
|
|
37
|
+
* on an actual device or a simulator as it cannot be reproduced with mobile
|
|
38
|
+
* emulation modes on desktop browsers.
|
|
39
|
+
*/
|
|
40
|
+
export const SmoothScrollReset: Story<string> = {
|
|
41
|
+
args: {
|
|
42
|
+
items: Array.from({ length: 15 }).map((_, id) => ({
|
|
43
|
+
type: 'option',
|
|
44
|
+
value: `option ${id + 1}`,
|
|
45
|
+
})),
|
|
46
|
+
placeholder: 'Select option',
|
|
47
|
+
},
|
|
48
|
+
decorators: [
|
|
49
|
+
(Story) => (
|
|
50
|
+
<>
|
|
51
|
+
<style>{`html { scroll-behavior: smooth; }`}</style>
|
|
52
|
+
<div>
|
|
53
|
+
<div
|
|
54
|
+
className="d-flex align-items-center justify-content-center"
|
|
55
|
+
style={{
|
|
56
|
+
backgroundColor: 'var(--color-bright-yellow)',
|
|
57
|
+
height: 400,
|
|
58
|
+
}}
|
|
59
|
+
>
|
|
60
|
+
This block should not be in the viewport.
|
|
61
|
+
</div>
|
|
62
|
+
<div style={{ height: 10, backgroundColor: 'var(--color-bright-green)' }} />
|
|
63
|
+
<Field id="el1" label={lorem5}>
|
|
64
|
+
<Story />
|
|
65
|
+
</Field>
|
|
66
|
+
<Body as="p">{lorem500}</Body>
|
|
67
|
+
</div>
|
|
68
|
+
</>
|
|
69
|
+
),
|
|
70
|
+
],
|
|
71
|
+
play: async ({ canvasElement }) => {
|
|
72
|
+
document.documentElement.scrollTop = 400;
|
|
73
|
+
await wait();
|
|
74
|
+
const canvas = within(canvasElement);
|
|
75
|
+
// cannot use userEvent.click as it crashes on iOS Safari in the simulator
|
|
76
|
+
await fireEvent.click(canvas.getByRole('combobox'));
|
|
77
|
+
},
|
|
78
|
+
globals: {
|
|
79
|
+
viewport: { value: allModes.largeMobile.viewport, isRotated: false },
|
|
80
|
+
},
|
|
81
|
+
parameters: {
|
|
82
|
+
chromatic: {
|
|
83
|
+
disableSnapshot: true,
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
};
|
|
@@ -1127,7 +1127,7 @@ function SelectInputOption<T = string>({ value, disabled, children }: SelectInpu
|
|
|
1127
1127
|
const SelectInputOptionContentWithinTriggerContext = createContext(false);
|
|
1128
1128
|
|
|
1129
1129
|
export interface SelectInputOptionContentProps {
|
|
1130
|
-
title:
|
|
1130
|
+
title: React.ReactNode;
|
|
1131
1131
|
note?: string;
|
|
1132
1132
|
description?: string;
|
|
1133
1133
|
icon?: React.ReactNode;
|
package/src/inputs/TextArea.tsx
CHANGED
|
@@ -5,13 +5,12 @@ import { Merge } from '../utils';
|
|
|
5
5
|
import { inputClassNameBase } from './_common';
|
|
6
6
|
import { useInputAttributes } from './contexts';
|
|
7
7
|
|
|
8
|
-
export interface TextAreaProps
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
> {}
|
|
8
|
+
export interface TextAreaProps extends Merge<
|
|
9
|
+
React.ComponentPropsWithRef<'textarea'>,
|
|
10
|
+
{
|
|
11
|
+
'aria-invalid'?: boolean;
|
|
12
|
+
}
|
|
13
|
+
> {}
|
|
15
14
|
|
|
16
15
|
export const TextArea = forwardRef(function TextArea(
|
|
17
16
|
{ className, ...restProps }: TextAreaProps,
|
|
@@ -406,7 +406,7 @@ function View({
|
|
|
406
406
|
aria-describedby={describedByIds}
|
|
407
407
|
href={(controlProps as ListItemNavigationProps)?.href}
|
|
408
408
|
target={(controlProps as ListItemNavigationProps)?.target}
|
|
409
|
-
className={clsx('wds-list-item-view
|
|
409
|
+
className={clsx('wds-list-item-view', {
|
|
410
410
|
'wds-list-item-control': controlType === 'navigation',
|
|
411
411
|
fullyInteractive: !isPartiallyInteractive,
|
|
412
412
|
})}
|
|
@@ -424,7 +424,7 @@ function View({
|
|
|
424
424
|
if (isPartiallyInteractive || controlType === 'non-interactive') {
|
|
425
425
|
return (
|
|
426
426
|
<div className={clsx('wds-list-item-gridWrapper', className)}>
|
|
427
|
-
<div className=
|
|
427
|
+
<div className="wds-list-item-view">{children}</div>
|
|
428
428
|
|
|
429
429
|
{renderExtras()}
|
|
430
430
|
</div>
|
package/src/main.css
CHANGED
|
@@ -5441,6 +5441,37 @@ html:not([dir="rtl"]) .np-navigation-option {
|
|
|
5441
5441
|
.wds-inline-prompt .wds-inline-prompt-process-indicator .process-circle {
|
|
5442
5442
|
stroke: currentColor;
|
|
5443
5443
|
}
|
|
5444
|
+
.wds-info-prompt {
|
|
5445
|
+
--Prompt-gap: var(--size-8);
|
|
5446
|
+
--Prompt-padding: 12px;
|
|
5447
|
+
}
|
|
5448
|
+
.wds-info-prompt__content {
|
|
5449
|
+
display: flex;
|
|
5450
|
+
flex-direction: column;
|
|
5451
|
+
justify-content: center;
|
|
5452
|
+
}
|
|
5453
|
+
.wds-info-prompt__content:has(.wds-info-prompt__title) {
|
|
5454
|
+
justify-content: flex-start;
|
|
5455
|
+
/* Top align when title exists */
|
|
5456
|
+
}
|
|
5457
|
+
.wds-info-prompt__title,
|
|
5458
|
+
.wds-info-prompt__description {
|
|
5459
|
+
display: block;
|
|
5460
|
+
color: var(--color-sentiment-primary);
|
|
5461
|
+
}
|
|
5462
|
+
.wds-info-prompt__action {
|
|
5463
|
+
margin-top: var(--Prompt-gap);
|
|
5464
|
+
}
|
|
5465
|
+
.wds-info-prompt__media {
|
|
5466
|
+
display: flex;
|
|
5467
|
+
}
|
|
5468
|
+
.wds-info-prompt__media svg {
|
|
5469
|
+
width: 24px;
|
|
5470
|
+
height: 24px;
|
|
5471
|
+
}
|
|
5472
|
+
.wds-info-prompt .wds-prompt__media-wrapper {
|
|
5473
|
+
padding: 0;
|
|
5474
|
+
}
|
|
5444
5475
|
.wds-radio-group .np-radio:last-child label {
|
|
5445
5476
|
margin-bottom: 0;
|
|
5446
5477
|
}
|
package/src/main.less
CHANGED
|
@@ -62,6 +62,7 @@
|
|
|
62
62
|
@import "./promoCard/PromoCard.less";
|
|
63
63
|
@import "./prompt/PrimitivePrompt/PrimitivePrompt.less";
|
|
64
64
|
@import "./prompt/InlinePrompt/InlinePrompt.less";
|
|
65
|
+
@import "./prompt/InfoPrompt/InfoPrompt.less";
|
|
65
66
|
@import "./radioGroup/RadioGroup.less";
|
|
66
67
|
@import "./section/Section.less";
|
|
67
68
|
@import "./slidingPanel/SlidingPanel.less";
|
|
@@ -89,4 +90,4 @@
|
|
|
89
90
|
@import "./prompt/ActionPrompt/ActionPrompt.less";
|
|
90
91
|
|
|
91
92
|
// List all less files in src in alphabetical order: find -s src -type f -name '*.less' ! -name 'main.less'
|
|
92
|
-
// Make sure you are not referencing main.less itself in this file!
|
|
93
|
+
// Make sure you are not referencing main.less itself in this file!
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react-webpack5';
|
|
2
|
+
import { within, fireEvent } from 'storybook/test';
|
|
3
|
+
import { allModes } from '../../.storybook/modes';
|
|
4
|
+
import { lorem500 } from '../test-utils';
|
|
5
|
+
import { Field } from '../field/Field';
|
|
6
|
+
import Body from '../body';
|
|
7
|
+
import MoneyInput from './MoneyInput';
|
|
8
|
+
|
|
9
|
+
const meta = {
|
|
10
|
+
title: 'Forms/MoneyInput/tests',
|
|
11
|
+
component: MoneyInput,
|
|
12
|
+
args: {
|
|
13
|
+
amount: 1000,
|
|
14
|
+
id: 'moneyInput',
|
|
15
|
+
currencies: [
|
|
16
|
+
{
|
|
17
|
+
value: 'EUR',
|
|
18
|
+
label: 'EUR',
|
|
19
|
+
note: 'Euro',
|
|
20
|
+
currency: 'eur',
|
|
21
|
+
searchable: 'Spain, Germany, France, Austria',
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
value: 'GBP',
|
|
25
|
+
label: 'GBP',
|
|
26
|
+
note: 'British pound',
|
|
27
|
+
currency: 'gbp',
|
|
28
|
+
searchable: 'England, Scotland, Wales',
|
|
29
|
+
},
|
|
30
|
+
],
|
|
31
|
+
selectedCurrency: {
|
|
32
|
+
value: 'EUR',
|
|
33
|
+
label: 'EUR',
|
|
34
|
+
note: 'Euro',
|
|
35
|
+
currency: 'eur',
|
|
36
|
+
searchable: 'Spain, Germany, France, Austria',
|
|
37
|
+
},
|
|
38
|
+
searchPlaceholder: '',
|
|
39
|
+
onAmountChange: () => {},
|
|
40
|
+
onCurrencyChange: () => {},
|
|
41
|
+
},
|
|
42
|
+
tags: ['!autodocs'],
|
|
43
|
+
} satisfies Meta<typeof MoneyInput>;
|
|
44
|
+
export default meta;
|
|
45
|
+
|
|
46
|
+
type Story = StoryObj<typeof MoneyInput>;
|
|
47
|
+
|
|
48
|
+
const wait = async (duration = 500) =>
|
|
49
|
+
new Promise<void>((resolve) => {
|
|
50
|
+
setTimeout(resolve, duration);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* This test ensures that when the SelectInput is used within a scrollable page,
|
|
55
|
+
* opening the dropdown does not cause any unwanted scrolling or layout shifts.
|
|
56
|
+
* Expected preview should start with green bar at the top, with yellow section
|
|
57
|
+
* not in the viewport. The issue is particularly prominent on iOS Safari.
|
|
58
|
+
*
|
|
59
|
+
* NB: This test is disabled in Chromatic as there's no obvious way to control
|
|
60
|
+
* <html/> element of a snapshot. It's to be primarily used in manual testing
|
|
61
|
+
* on an actual device or a simulator as it cannot be reproduced with mobile
|
|
62
|
+
* emulation modes on desktop browsers.
|
|
63
|
+
*/
|
|
64
|
+
export const SmoothScrollReset: Story = {
|
|
65
|
+
decorators: [
|
|
66
|
+
(Story) => (
|
|
67
|
+
<>
|
|
68
|
+
<style>{`html { scroll-behavior: smooth; }`}</style>
|
|
69
|
+
<div>
|
|
70
|
+
<div
|
|
71
|
+
className="d-flex align-items-center justify-content-center"
|
|
72
|
+
style={{
|
|
73
|
+
backgroundColor: 'var(--color-bright-yellow)',
|
|
74
|
+
height: 400,
|
|
75
|
+
}}
|
|
76
|
+
>
|
|
77
|
+
This block should not be in the viewport.
|
|
78
|
+
</div>
|
|
79
|
+
<div style={{ height: 10, backgroundColor: 'var(--color-bright-green)' }} />
|
|
80
|
+
<Field id="el1" label="Select currency">
|
|
81
|
+
<Story />
|
|
82
|
+
</Field>
|
|
83
|
+
<Body as="p">{lorem500}</Body>
|
|
84
|
+
</div>
|
|
85
|
+
</>
|
|
86
|
+
),
|
|
87
|
+
],
|
|
88
|
+
play: async ({ canvasElement }) => {
|
|
89
|
+
await wait();
|
|
90
|
+
document.documentElement.scrollTop = 400;
|
|
91
|
+
await wait();
|
|
92
|
+
const canvas = within(canvasElement);
|
|
93
|
+
// cannot use userEvent.click as it crashes on iOS Safari in the simulator
|
|
94
|
+
await fireEvent.click(canvas.getByRole('combobox'));
|
|
95
|
+
},
|
|
96
|
+
globals: {
|
|
97
|
+
viewport: { value: allModes.largeMobile.viewport, isRotated: false },
|
|
98
|
+
},
|
|
99
|
+
parameters: {
|
|
100
|
+
chromatic: {
|
|
101
|
+
disableSnapshot: true,
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
};
|
|
@@ -4,7 +4,14 @@ import { clsx } from 'clsx';
|
|
|
4
4
|
import { Component } from 'react';
|
|
5
5
|
import { injectIntl, WrappedComponentProps } from 'react-intl';
|
|
6
6
|
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
Typography,
|
|
9
|
+
Size,
|
|
10
|
+
SizeLarge,
|
|
11
|
+
SizeMedium,
|
|
12
|
+
SizeSmall,
|
|
13
|
+
getLocaleCurrencyName,
|
|
14
|
+
} from '../common';
|
|
8
15
|
import { withInputAttributes, WithInputAttributesProps } from '../inputs/contexts';
|
|
9
16
|
import { Input } from '../inputs/Input';
|
|
10
17
|
import {
|
|
@@ -372,6 +379,7 @@ class MoneyInput extends Component<MoneyInputPropsWithInputAttributes, MoneyInpu
|
|
|
372
379
|
as="span"
|
|
373
380
|
type={Typography.TITLE_SUBSECTION}
|
|
374
381
|
className={size === 'lg' ? this.style('m-r-1') : ''}
|
|
382
|
+
aria-label={getLocaleCurrencyName(this.props.intl, selectedCurrency.currency)}
|
|
375
383
|
>
|
|
376
384
|
{selectedCurrency.currency.toUpperCase()}
|
|
377
385
|
</Title>
|
|
@@ -399,7 +407,17 @@ class MoneyInput extends Component<MoneyInputPropsWithInputAttributes, MoneyInpu
|
|
|
399
407
|
renderValue={(currency, withinTrigger) => {
|
|
400
408
|
return (
|
|
401
409
|
<SelectInputOptionContent
|
|
402
|
-
title={
|
|
410
|
+
title={
|
|
411
|
+
withinTrigger ? (
|
|
412
|
+
<span
|
|
413
|
+
aria-label={getLocaleCurrencyName(this.props.intl, currency.currency)}
|
|
414
|
+
>
|
|
415
|
+
{currency.currency.toUpperCase()}
|
|
416
|
+
</span>
|
|
417
|
+
) : (
|
|
418
|
+
currency.label
|
|
419
|
+
)
|
|
420
|
+
}
|
|
403
421
|
note={withinTrigger ? undefined : currency.note}
|
|
404
422
|
icon={<Flag code={currency.currency} intrinsicSize={24} />}
|
|
405
423
|
/>
|
|
@@ -11,9 +11,7 @@ export type PrimitiveAnchorElementRef = React.Ref<HTMLAnchorElement>;
|
|
|
11
11
|
* Properties for the anchor component.
|
|
12
12
|
*/
|
|
13
13
|
export interface PrimitiveAnchorProps
|
|
14
|
-
extends BasePrimitiveProps,
|
|
15
|
-
StyleProp,
|
|
16
|
-
Omit<PrimitiveAnchorAttributes, 'role' | 'type'> {
|
|
14
|
+
extends BasePrimitiveProps, StyleProp, Omit<PrimitiveAnchorAttributes, 'role' | 'type'> {
|
|
17
15
|
/** Content of the anchor */
|
|
18
16
|
children?: ReactNode;
|
|
19
17
|
|
|
@@ -11,9 +11,7 @@ export type PrimitiveButtonElementRef = React.Ref<HTMLButtonElement>;
|
|
|
11
11
|
* Properties for the Button component.
|
|
12
12
|
*/
|
|
13
13
|
export interface PrimitiveButtonProps
|
|
14
|
-
extends BasePrimitiveProps,
|
|
15
|
-
StyleProp,
|
|
16
|
-
PrimitiveButtonAttributes {
|
|
14
|
+
extends BasePrimitiveProps, StyleProp, PrimitiveButtonAttributes {
|
|
17
15
|
/** Content of the button */
|
|
18
16
|
children?: ReactNode;
|
|
19
17
|
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { Meta, Canvas, Source } from '@storybook/addon-docs/blocks';
|
|
2
|
+
|
|
3
|
+
<Meta title="Prompts/ActionPrompt/Accessibility" />
|
|
4
|
+
|
|
5
|
+
# Accessibility
|
|
6
|
+
|
|
7
|
+
Under the hood, `ActionPrompt` is marked as `role="region"` for a section of content that users might want to navigate to directly.
|
|
8
|
+
|
|
9
|
+
By default, it's labelled by the `media` asset and `title`, as well as described by the `description` if one is provided.
|
|
10
|
+
|
|
11
|
+
## Announcement Behaviour
|
|
12
|
+
|
|
13
|
+
`ActionPrompt` should be treated as a hint or suggestion as part of the product UX. Even the negative variant still serves as a non-critical message. Therefore, it shouldn't be announced via screen reader—neither assertively (`role="alert"`) nor politely (`role="status"`).
|
|
14
|
+
|
|
15
|
+
**Note:** For immediate user feedback, use [InfoPrompt](?path=/docs/prompts-infoprompt--docs) or [InlinePrompt](https://storybook.wise.design/?path=/docs/prompts-inlineprompt--docs) instead.
|
|
16
|
+
|
|
17
|
+
If you want to provide a custom label for screen readers, you can use the `aria-label` prop. Make sure to include all necessary information in it, because when provided, the default labelling will be removed.
|
|
18
|
+
|
|
19
|
+
<Source
|
|
20
|
+
dark
|
|
21
|
+
code={`
|
|
22
|
+
<ActionPrompt
|
|
23
|
+
aria-label="JFYI: Henry requested 30 USD for lunch"
|
|
24
|
+
media={{ avatar: { imgSrc: 'profile-photo.webp' } }}
|
|
25
|
+
title="Henry requested 30 USD"
|
|
26
|
+
description="Lunch date"
|
|
27
|
+
...
|
|
28
|
+
/>
|
|
29
|
+
`}
|
|
30
|
+
/>
|
|
31
|
+
|
|
32
|
+
## Media
|
|
33
|
+
|
|
34
|
+
You can use the `aria-hidden` attribute on media assets to hide them from screen readers if they're not providing any additional information. This is useful when the media is purely decorative or when its content is already conveyed through other means (e.g., text).
|
|
35
|
+
|
|
36
|
+
<Source
|
|
37
|
+
dark
|
|
38
|
+
code={`
|
|
39
|
+
<ActionPrompt
|
|
40
|
+
media={{
|
|
41
|
+
'aria-hidden': true,
|
|
42
|
+
avatar: { asset: <People /> },
|
|
43
|
+
}}
|
|
44
|
+
title="Sync contacts for a faster experience"
|
|
45
|
+
...
|
|
46
|
+
/>
|
|
47
|
+
`}
|
|
48
|
+
/>
|
|
49
|
+
|
|
50
|
+
You can also use `aria-label` on media assets to provide a custom label for screen readers.
|
|
51
|
+
|
|
52
|
+
<Source
|
|
53
|
+
dark
|
|
54
|
+
code={`
|
|
55
|
+
<ActionPrompt
|
|
56
|
+
media={{
|
|
57
|
+
'aria-label': 'Henry Adams photo',
|
|
58
|
+
avatar: { imgSrc: 'profile-photo.webp' },
|
|
59
|
+
}}
|
|
60
|
+
title="Henry requested 30 USD"
|
|
61
|
+
description="Lunch date"
|
|
62
|
+
...
|
|
63
|
+
/>
|
|
64
|
+
`}
|
|
65
|
+
/>
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import { ReactElement, useState } from 'react';
|
|
2
2
|
import { Meta, StoryObj } from '@storybook/react-webpack5';
|
|
3
3
|
import { fn } from 'storybook/test';
|
|
4
|
-
import { Bank } from '@transferwise/icons';
|
|
4
|
+
import { Bank, Freeze, People } from '@transferwise/icons';
|
|
5
5
|
import { ActionPrompt } from './ActionPrompt';
|
|
6
6
|
import { lorem10 } from '../../test-utils';
|
|
7
|
+
import Body from '../../body';
|
|
8
|
+
import { action } from 'storybook/actions';
|
|
9
|
+
import Title from '../../title';
|
|
7
10
|
import { withVariantConfig } from '../../../.storybook/helpers';
|
|
8
11
|
|
|
9
12
|
const meta: Meta<typeof ActionPrompt> = {
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { Freeze, People } from '@transferwise/icons';
|
|
2
|
+
import { action } from 'storybook/actions';
|
|
3
|
+
import ActionPrompt from './ActionPrompt';
|
|
4
|
+
import { Body, Title } from '../..';
|
|
5
|
+
import { Meta, StoryObj } from '@storybook/react-webpack5';
|
|
6
|
+
|
|
7
|
+
export default {
|
|
8
|
+
title: 'Prompts/ActionPrompt/Tests',
|
|
9
|
+
component: ActionPrompt,
|
|
10
|
+
tags: ['!manifest', '!autodocs'],
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
type Story = StoryObj<typeof ActionPrompt>;
|
|
14
|
+
|
|
15
|
+
export const VariousA11yFeatures: Story = {
|
|
16
|
+
name: 'Various a11y features',
|
|
17
|
+
render: () => {
|
|
18
|
+
return (
|
|
19
|
+
<Body>
|
|
20
|
+
<Title type="title-body">Neutral Prompt with Avatar Image and Custom label for media</Title>
|
|
21
|
+
<ActionPrompt
|
|
22
|
+
className="m-b-2"
|
|
23
|
+
sentiment="neutral"
|
|
24
|
+
media={{
|
|
25
|
+
'aria-label': 'Henry Profile Photo',
|
|
26
|
+
avatar: { imgSrc: '../../avatar-square-dude.webp' },
|
|
27
|
+
}}
|
|
28
|
+
title="Henry requested 30 USD"
|
|
29
|
+
description="Lunch date"
|
|
30
|
+
action={{
|
|
31
|
+
label: 'Sent',
|
|
32
|
+
onClick: () => {
|
|
33
|
+
action('send');
|
|
34
|
+
},
|
|
35
|
+
}}
|
|
36
|
+
actionSecondary={{
|
|
37
|
+
label: 'Decline',
|
|
38
|
+
onClick: () => {
|
|
39
|
+
action('decline');
|
|
40
|
+
},
|
|
41
|
+
}}
|
|
42
|
+
onDismiss={() => action('dismiss')}
|
|
43
|
+
/>
|
|
44
|
+
|
|
45
|
+
<Title type="title-body">Warning Prompt</Title>
|
|
46
|
+
<ActionPrompt
|
|
47
|
+
className="m-b-2"
|
|
48
|
+
sentiment="warning"
|
|
49
|
+
media={{
|
|
50
|
+
'aria-label': 'Image of beautiful Business Wise Card',
|
|
51
|
+
imgSrc: '../../wise-card.svg',
|
|
52
|
+
}}
|
|
53
|
+
title="Your Wise Card expires soon"
|
|
54
|
+
description="Renew your card to keep spending"
|
|
55
|
+
action={{
|
|
56
|
+
label: 'Renew card',
|
|
57
|
+
onClick: () => {
|
|
58
|
+
action('renew');
|
|
59
|
+
},
|
|
60
|
+
}}
|
|
61
|
+
actionSecondary={{
|
|
62
|
+
label: 'Notify later',
|
|
63
|
+
onClick: () => {
|
|
64
|
+
action('notifyLater');
|
|
65
|
+
},
|
|
66
|
+
}}
|
|
67
|
+
onDismiss={() => action('dismiss')}
|
|
68
|
+
/>
|
|
69
|
+
|
|
70
|
+
<Title type="title-body">Warning Prompt Avatar Icon</Title>
|
|
71
|
+
<ActionPrompt
|
|
72
|
+
className="m-b-2"
|
|
73
|
+
sentiment="warning"
|
|
74
|
+
media={{ avatar: { asset: <Freeze /> } }}
|
|
75
|
+
title="Your Wise Card expires soon"
|
|
76
|
+
description="Renew your card to keep spending"
|
|
77
|
+
action={{
|
|
78
|
+
label: 'Renew card',
|
|
79
|
+
onClick: () => {
|
|
80
|
+
action('renew');
|
|
81
|
+
},
|
|
82
|
+
}}
|
|
83
|
+
onDismiss={() => {
|
|
84
|
+
action('dismiss');
|
|
85
|
+
}}
|
|
86
|
+
/>
|
|
87
|
+
|
|
88
|
+
<Title type="title-body">Proposition Prompt Avatar Icon</Title>
|
|
89
|
+
<ActionPrompt
|
|
90
|
+
className="m-b-2"
|
|
91
|
+
sentiment="proposition"
|
|
92
|
+
media={{ avatar: { asset: <People /> } }}
|
|
93
|
+
title="Sync contacts for a faster experience"
|
|
94
|
+
description="Find contacts on Wise — it’s simple, secure and you pick who you add."
|
|
95
|
+
action={{
|
|
96
|
+
label: 'Sync contacts',
|
|
97
|
+
onClick: () => {
|
|
98
|
+
action('sync');
|
|
99
|
+
},
|
|
100
|
+
}}
|
|
101
|
+
onDismiss={() => {
|
|
102
|
+
action('dismiss');
|
|
103
|
+
}}
|
|
104
|
+
/>
|
|
105
|
+
|
|
106
|
+
<Title type="title-body">Negative Prompt Avatar Icon + muted media</Title>
|
|
107
|
+
<ActionPrompt
|
|
108
|
+
className="m-b-2"
|
|
109
|
+
sentiment="negative"
|
|
110
|
+
media={{ avatar: { asset: <People /> }, 'aria-hidden': true }}
|
|
111
|
+
title="Sync contacts for a faster experience"
|
|
112
|
+
description="Find contacts on Wise — it’s simple, secure and you pick who you add."
|
|
113
|
+
action={{
|
|
114
|
+
label: 'Sync contacts',
|
|
115
|
+
onClick: () => {
|
|
116
|
+
action('sync');
|
|
117
|
+
},
|
|
118
|
+
}}
|
|
119
|
+
onDismiss={() => {
|
|
120
|
+
action('dismiss');
|
|
121
|
+
}}
|
|
122
|
+
/>
|
|
123
|
+
|
|
124
|
+
<Title type="title-body">
|
|
125
|
+
Negative Prompt + override content with custom message via aria-label
|
|
126
|
+
</Title>
|
|
127
|
+
<ActionPrompt
|
|
128
|
+
className="m-b-2"
|
|
129
|
+
aria-label="hey customer, here is custom message"
|
|
130
|
+
sentiment="negative"
|
|
131
|
+
media={{ avatar: { asset: <People /> } }}
|
|
132
|
+
title="Sync contacts for a faster experience"
|
|
133
|
+
description="Find contacts on Wise — it’s simple, secure and you pick who you add."
|
|
134
|
+
action={{
|
|
135
|
+
label: 'Sync contacts',
|
|
136
|
+
onClick: () => {
|
|
137
|
+
action('sync');
|
|
138
|
+
},
|
|
139
|
+
}}
|
|
140
|
+
onDismiss={() => {
|
|
141
|
+
action('dismiss');
|
|
142
|
+
}}
|
|
143
|
+
/>
|
|
144
|
+
</Body>
|
|
145
|
+
);
|
|
146
|
+
},
|
|
147
|
+
};
|
|
@@ -56,9 +56,7 @@ describe('ActionPrompt', () => {
|
|
|
56
56
|
it('calls primary action onClick when clicked', async () => {
|
|
57
57
|
const onClick = jest.fn();
|
|
58
58
|
const user = userEvent.setup();
|
|
59
|
-
render(
|
|
60
|
-
<ActionPrompt {...defaultProps} action={{ label: 'Primary Action', onClick }} />,
|
|
61
|
-
);
|
|
59
|
+
render(<ActionPrompt {...defaultProps} action={{ label: 'Primary Action', onClick }} />);
|
|
62
60
|
const button = screen.getByRole('button', { name: 'Primary Action' });
|
|
63
61
|
await user.click(button);
|
|
64
62
|
expect(onClick).toHaveBeenCalledTimes(1);
|
|
@@ -66,10 +64,7 @@ describe('ActionPrompt', () => {
|
|
|
66
64
|
|
|
67
65
|
it('renders primary action as link when href is provided', () => {
|
|
68
66
|
render(
|
|
69
|
-
<ActionPrompt
|
|
70
|
-
{...defaultProps}
|
|
71
|
-
action={{ label: 'Primary Action', href: '/test-url' }}
|
|
72
|
-
/>,
|
|
67
|
+
<ActionPrompt {...defaultProps} action={{ label: 'Primary Action', href: '/test-url' }} />,
|
|
73
68
|
);
|
|
74
69
|
const link = screen.getByRole('link', { name: 'Primary Action' });
|
|
75
70
|
expect(link).toHaveAttribute('href', '/test-url');
|