@transferwise/components 46.127.1 → 46.128.1
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/alert/Alert.js +3 -0
- package/build/alert/Alert.js.map +1 -1
- package/build/alert/Alert.mjs +3 -0
- package/build/alert/Alert.mjs.map +1 -1
- package/build/index.js +1 -0
- package/build/index.js.map +1 -1
- package/build/index.mjs +1 -1
- package/build/inputs/SelectInput.js +81 -12
- package/build/inputs/SelectInput.js.map +1 -1
- package/build/inputs/SelectInput.mjs +81 -13
- package/build/inputs/SelectInput.mjs.map +1 -1
- package/build/listItem/Button/ListItemButton.js +4 -3
- package/build/listItem/Button/ListItemButton.js.map +1 -1
- package/build/listItem/Button/ListItemButton.mjs +5 -4
- package/build/listItem/Button/ListItemButton.mjs.map +1 -1
- package/build/main.css +15 -7
- package/build/prompt/ActionPrompt/ActionPrompt.js +6 -4
- package/build/prompt/ActionPrompt/ActionPrompt.js.map +1 -1
- package/build/prompt/ActionPrompt/ActionPrompt.mjs +6 -4
- package/build/prompt/ActionPrompt/ActionPrompt.mjs.map +1 -1
- package/build/prompt/InfoPrompt/InfoPrompt.js.map +1 -1
- package/build/prompt/InfoPrompt/InfoPrompt.mjs.map +1 -1
- package/build/prompt/InlinePrompt/InlinePrompt.js +1 -1
- package/build/prompt/InlinePrompt/InlinePrompt.js.map +1 -1
- package/build/prompt/InlinePrompt/InlinePrompt.mjs +1 -1
- package/build/prompt/InlinePrompt/InlinePrompt.mjs.map +1 -1
- package/build/styles/main.css +15 -7
- package/build/styles/prompt/ActionPrompt/ActionPrompt.css +4 -0
- package/build/styles/prompt/InfoPrompt/InfoPrompt.css +7 -5
- package/build/styles/prompt/InlinePrompt/InlinePrompt.css +3 -2
- package/build/styles/prompt/PrimitivePrompt/PrimitivePrompt.css +1 -0
- package/build/types/alert/Alert.d.ts +15 -0
- package/build/types/alert/Alert.d.ts.map +1 -1
- package/build/types/index.d.ts +1 -1
- package/build/types/index.d.ts.map +1 -1
- package/build/types/inputs/SelectInput.d.ts +19 -0
- package/build/types/inputs/SelectInput.d.ts.map +1 -1
- package/build/types/listItem/Button/ListItemButton.d.ts +7 -4
- package/build/types/listItem/Button/ListItemButton.d.ts.map +1 -1
- package/build/types/listItem/ListItem.d.ts +4 -4
- package/build/types/prompt/ActionPrompt/ActionPrompt.d.ts +7 -0
- package/build/types/prompt/ActionPrompt/ActionPrompt.d.ts.map +1 -1
- package/build/types/prompt/InfoPrompt/InfoPrompt.d.ts +4 -2
- package/build/types/prompt/InfoPrompt/InfoPrompt.d.ts.map +1 -1
- package/package.json +5 -5
- package/src/alert/Alert.story.tsx +4 -0
- package/src/alert/Alert.test.story.tsx +1 -1
- package/src/alert/Alert.tsx +16 -0
- package/src/iconButton/IconButton.story.tsx +173 -48
- package/src/iconButton/IconButton.test.story.tsx +194 -0
- package/src/index.ts +1 -0
- package/src/inputs/SelectInput.story.tsx +33 -20
- package/src/inputs/SelectInput.test.story.tsx +1285 -5
- package/src/inputs/SelectInput.tsx +93 -15
- package/src/listItem/Button/ListItemButton.tsx +30 -28
- package/src/listItem/_stories/ListItem.story.tsx +0 -1
- package/src/main.css +15 -7
- package/src/prompt/ActionPrompt/ActionPrompt.accessibility.docs.mdx +2 -18
- package/src/prompt/ActionPrompt/ActionPrompt.css +4 -0
- package/src/prompt/ActionPrompt/ActionPrompt.less +5 -1
- package/src/prompt/ActionPrompt/ActionPrompt.story.tsx +323 -108
- package/src/prompt/ActionPrompt/ActionPrompt.test.story.tsx +86 -3
- package/src/prompt/ActionPrompt/ActionPrompt.tsx +17 -6
- package/src/prompt/InfoPrompt/InfoPrompt.accessibility.docs.mdx +79 -0
- package/src/prompt/InfoPrompt/InfoPrompt.css +7 -5
- package/src/prompt/InfoPrompt/InfoPrompt.less +8 -8
- package/src/prompt/InfoPrompt/InfoPrompt.story.tsx +112 -82
- package/src/prompt/InfoPrompt/InfoPrompt.test.story.tsx +54 -1
- package/src/prompt/InfoPrompt/InfoPrompt.tsx +4 -2
- package/src/prompt/InlinePrompt/InlinePrompt.accessibility.docs.mdx +63 -0
- package/src/prompt/InlinePrompt/InlinePrompt.css +3 -2
- package/src/prompt/InlinePrompt/InlinePrompt.less +2 -2
- package/src/prompt/InlinePrompt/InlinePrompt.story.tsx +25 -30
- package/src/prompt/InlinePrompt/InlinePrompt.test.story.tsx +21 -0
- package/src/prompt/InlinePrompt/InlinePrompt.test.tsx +10 -3
- package/src/prompt/InlinePrompt/InlinePrompt.tsx +1 -1
- package/src/prompt/PrimitivePrompt/PrimitivePrompt.css +1 -0
- package/src/prompt/PrimitivePrompt/PrimitivePrompt.less +2 -1
- package/src/sentimentSurface/SentimentSurface.docs.mdx +1 -1
- package/src/sentimentSurface/SentimentSurface.story.tsx +1 -1
- package/src/sentimentSurface/SentimentSurface.test.story.tsx +1 -1
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { Meta, Source } from '@storybook/addon-docs/blocks';
|
|
2
|
+
|
|
3
|
+
<Meta title="Prompts/InfoPrompt/Accessibility" tags={['new']} />
|
|
4
|
+
|
|
5
|
+
# Accessibility
|
|
6
|
+
|
|
7
|
+
Under the hood, `InfoPrompt` 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 `title` and described by the `description`.
|
|
10
|
+
|
|
11
|
+
## Announcement Behaviour
|
|
12
|
+
|
|
13
|
+
`InfoPrompt` is designed for contextual messages that appear within a screen. Unlike `ActionPrompt`, it can be configured to announce to screen readers using ARIA roles:
|
|
14
|
+
|
|
15
|
+
- **`role="status"`**: Politely announces updates when the screen reader is idle. Use for non-critical success messages or informational updates (e.g., "Your profile has been saved").
|
|
16
|
+
- **`role="alert"`**: Immediately interrupts to announce urgent messages. Use for time-sensitive information that requires immediate attention (e.g., errors, warnings, deadlines).
|
|
17
|
+
|
|
18
|
+
**Default behavior:** Without a role, the prompt won't be announced automatically. Users will encounter it when navigating the page.
|
|
19
|
+
|
|
20
|
+
<Source
|
|
21
|
+
dark
|
|
22
|
+
code={`
|
|
23
|
+
// Polite announcement for non-critical updates
|
|
24
|
+
<InfoPrompt
|
|
25
|
+
sentiment="success"
|
|
26
|
+
description="Your profile has been saved"
|
|
27
|
+
role="status"
|
|
28
|
+
/>
|
|
29
|
+
|
|
30
|
+
// Immediate announcement for urgent messages
|
|
31
|
+
|
|
32
|
+
<InfoPrompt
|
|
33
|
+
sentiment="negative"
|
|
34
|
+
title="Session expired"
|
|
35
|
+
description="Your session has expired. Please log in again."
|
|
36
|
+
role="alert"
|
|
37
|
+
/>
|
|
38
|
+
`}
|
|
39
|
+
/>
|
|
40
|
+
|
|
41
|
+
## Custom Labels
|
|
42
|
+
|
|
43
|
+
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.
|
|
44
|
+
|
|
45
|
+
<Source
|
|
46
|
+
dark
|
|
47
|
+
code={`
|
|
48
|
+
<InfoPrompt
|
|
49
|
+
sentiment="negative"
|
|
50
|
+
title="Payment failed"
|
|
51
|
+
description="Unable to process payment."
|
|
52
|
+
aria-label="Critical error: Your payment could not be processed. Please update your payment method and try again within 24 hours."
|
|
53
|
+
/>
|
|
54
|
+
`}
|
|
55
|
+
/>
|
|
56
|
+
|
|
57
|
+
## Media
|
|
58
|
+
|
|
59
|
+
Custom media icons should include their own accessibility attributes. Use the `title` prop on icons to provide accessible names for screen readers:
|
|
60
|
+
|
|
61
|
+
<Source
|
|
62
|
+
dark
|
|
63
|
+
code={`
|
|
64
|
+
<InfoPrompt
|
|
65
|
+
sentiment="success"
|
|
66
|
+
description="Your travel account is ready!"
|
|
67
|
+
media={{ asset: <Travel title="Travel feature" /> }}
|
|
68
|
+
/>
|
|
69
|
+
|
|
70
|
+
<InfoPrompt
|
|
71
|
+
sentiment="warning"
|
|
72
|
+
title="Business travel"
|
|
73
|
+
description="Book your business trip with Wise."
|
|
74
|
+
media={{ asset: <Briefcase title="Business" /> }}
|
|
75
|
+
/>
|
|
76
|
+
`}
|
|
77
|
+
/>
|
|
78
|
+
|
|
79
|
+
**Note:** Unlike `ActionPrompt` (which uses `media['aria-label']`) and `InlinePrompt` (which uses `mediaLabel`), `InfoPrompt` requires you to add accessibility attributes directly to the icon component you pass.
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
.wds-info-prompt {
|
|
2
|
+
--Prompt-border-radius: var(--radius-medium);
|
|
2
3
|
--Prompt-gap: var(--size-8);
|
|
3
|
-
--Prompt-padding:
|
|
4
|
+
--Prompt-padding: var(--size-12);
|
|
4
5
|
}
|
|
5
6
|
.wds-info-prompt__content {
|
|
6
7
|
display: flex;
|
|
7
8
|
flex-direction: column;
|
|
8
9
|
justify-content: center;
|
|
10
|
+
max-width: calc(48px * 10);
|
|
11
|
+
max-width: calc(var(--size-48) * 10);
|
|
9
12
|
}
|
|
10
13
|
.wds-info-prompt__content:has(.wds-info-prompt__title) {
|
|
11
14
|
justify-content: flex-start;
|
|
@@ -14,17 +17,16 @@
|
|
|
14
17
|
.wds-info-prompt__title,
|
|
15
18
|
.wds-info-prompt__description {
|
|
16
19
|
display: block;
|
|
17
|
-
color: var(--color-sentiment-primary);
|
|
18
20
|
}
|
|
19
21
|
.wds-info-prompt__action {
|
|
22
|
+
align-self: flex-start;
|
|
20
23
|
margin-top: var(--Prompt-gap);
|
|
21
24
|
}
|
|
22
|
-
.wds-info-prompt__media {
|
|
23
|
-
display: flex;
|
|
24
|
-
}
|
|
25
25
|
.wds-info-prompt__media svg {
|
|
26
26
|
width: 24px;
|
|
27
|
+
width: var(--size-24);
|
|
27
28
|
height: 24px;
|
|
29
|
+
height: var(--size-24);
|
|
28
30
|
}
|
|
29
31
|
.wds-info-prompt .wds-prompt__media-wrapper {
|
|
30
32
|
padding: 0;
|
|
@@ -1,37 +1,37 @@
|
|
|
1
1
|
.wds-info-prompt {
|
|
2
|
+
--Prompt-border-radius: var(--radius-medium);
|
|
2
3
|
--Prompt-gap: var(--size-8);
|
|
3
|
-
--Prompt-padding:
|
|
4
|
+
--Prompt-padding: var(--size-12);
|
|
4
5
|
|
|
5
6
|
&__content {
|
|
6
7
|
display: flex;
|
|
7
8
|
flex-direction: column;
|
|
8
9
|
justify-content: center;
|
|
10
|
+
max-width: calc(var(--size-48) * 10);
|
|
9
11
|
|
|
10
12
|
&:has(.wds-info-prompt__title) {
|
|
11
13
|
justify-content: flex-start; /* Top align when title exists */
|
|
12
14
|
}
|
|
13
15
|
}
|
|
14
|
-
|
|
15
16
|
&__title,
|
|
16
17
|
&__description {
|
|
17
18
|
display: block;
|
|
18
|
-
color: var(--color-sentiment-primary);
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
&__action {
|
|
22
|
+
align-self: flex-start;
|
|
22
23
|
margin-top: var(--Prompt-gap);
|
|
23
24
|
}
|
|
24
25
|
|
|
25
26
|
&__media {
|
|
26
|
-
|
|
27
|
-
|
|
27
|
+
// To ensure icons are sized correctly, we set a fixed size for SVGs within the media wrapper
|
|
28
28
|
svg {
|
|
29
|
-
width:
|
|
30
|
-
height:
|
|
29
|
+
width: var(--size-24);
|
|
30
|
+
height: var(--size-24);
|
|
31
31
|
}
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
.wds-prompt__media-wrapper {
|
|
35
35
|
padding: 0;
|
|
36
36
|
}
|
|
37
|
-
}
|
|
37
|
+
}
|
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
import type { ReactElement } from 'react';
|
|
2
|
+
import { useState } from 'react';
|
|
2
3
|
import type { Meta, StoryObj } from '@storybook/react-webpack5';
|
|
3
4
|
import { action } from 'storybook/actions';
|
|
4
|
-
import { Confetti, GiftBox, Star, Travel } from '@transferwise/icons';
|
|
5
|
-
import {
|
|
5
|
+
import { Confetti, GiftBox, Star, Travel, Briefcase, Plane } from '@transferwise/icons';
|
|
6
|
+
import { lorem10, lorem20 } from '../../test-utils';
|
|
7
|
+
import Button from '../../button';
|
|
8
|
+
import Title from '../../title';
|
|
9
|
+
import { InfoPrompt, InfoPromptProps, InfoPromptMedia } from './InfoPrompt';
|
|
6
10
|
|
|
7
11
|
const withComponentGrid =
|
|
8
12
|
({ maxWidth = 'auto', gap = '1rem' } = {}) =>
|
|
@@ -48,6 +52,19 @@ export default {
|
|
|
48
52
|
type: { summary: 'ReactNode' },
|
|
49
53
|
},
|
|
50
54
|
},
|
|
55
|
+
media: {
|
|
56
|
+
table: {
|
|
57
|
+
type: { summary: 'InfoPromptMedia', detail: '{ asset: ReactNode }' },
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
action: {
|
|
61
|
+
table: {
|
|
62
|
+
type: {
|
|
63
|
+
summary: 'InfoPromptAction',
|
|
64
|
+
detail: '{ label: string; href?: string; target?: string; onClick?: () => void }',
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
},
|
|
51
68
|
},
|
|
52
69
|
parameters: {
|
|
53
70
|
docs: {
|
|
@@ -61,7 +78,7 @@ export default {
|
|
|
61
78
|
* not otherwise possible via Storybook
|
|
62
79
|
*/
|
|
63
80
|
type PreviewStoryArgs = InfoPromptProps & {
|
|
64
|
-
previewMedia:
|
|
81
|
+
previewMedia: InfoPromptMedia | undefined;
|
|
65
82
|
previewOnDismiss: boolean;
|
|
66
83
|
previewAction: boolean;
|
|
67
84
|
};
|
|
@@ -73,10 +90,22 @@ const previewArgGroup = {
|
|
|
73
90
|
},
|
|
74
91
|
};
|
|
75
92
|
|
|
93
|
+
const MEDIA_OPTIONS: Record<string, { asset: ReactElement } | undefined> = {
|
|
94
|
+
undefined,
|
|
95
|
+
star: { asset: <Star title="Starred" /> },
|
|
96
|
+
confetti: { asset: <Confetti size={24} title="Celebration" /> },
|
|
97
|
+
giftbox: { asset: <GiftBox title="Gift" /> },
|
|
98
|
+
travel: { asset: <Travel title="Travel" /> },
|
|
99
|
+
briefcase: { asset: <Briefcase title="Business" /> },
|
|
100
|
+
plane: { asset: <Plane title="Travel" /> },
|
|
101
|
+
};
|
|
102
|
+
|
|
76
103
|
const previewArgTypes = {
|
|
77
104
|
previewMedia: {
|
|
78
105
|
name: 'Preview with `media`',
|
|
79
|
-
control: '
|
|
106
|
+
control: 'select',
|
|
107
|
+
options: ['undefined', 'star', 'confetti', 'giftbox', 'travel', 'briefcase', 'plane'],
|
|
108
|
+
mapping: MEDIA_OPTIONS,
|
|
80
109
|
table: previewArgGroup,
|
|
81
110
|
},
|
|
82
111
|
previewOnDismiss: {
|
|
@@ -99,7 +128,7 @@ const getPropsForPreview = (
|
|
|
99
128
|
return [
|
|
100
129
|
props,
|
|
101
130
|
{
|
|
102
|
-
media: previewMedia
|
|
131
|
+
media: previewMedia,
|
|
103
132
|
onDismiss: previewOnDismiss ? action('onDismiss') : undefined,
|
|
104
133
|
action: previewAction
|
|
105
134
|
? { label: 'Learn more', onClick: action('action.onClick') }
|
|
@@ -109,12 +138,19 @@ const getPropsForPreview = (
|
|
|
109
138
|
};
|
|
110
139
|
|
|
111
140
|
export const Playground: StoryObj<PreviewStoryArgs> = {
|
|
112
|
-
|
|
113
|
-
|
|
141
|
+
argTypes: {
|
|
142
|
+
sentiment: {
|
|
143
|
+
control: 'radio',
|
|
144
|
+
options: ['success', 'negative', 'neutral', 'warning', 'proposition'],
|
|
145
|
+
},
|
|
146
|
+
...previewArgTypes,
|
|
147
|
+
},
|
|
114
148
|
args: {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
149
|
+
title: 'Payment successful',
|
|
150
|
+
description: 'Your payment is being processed.',
|
|
151
|
+
previewMedia: { asset: <Star title="Starred" /> },
|
|
152
|
+
previewOnDismiss: true,
|
|
153
|
+
previewAction: true,
|
|
118
154
|
},
|
|
119
155
|
render: (args: PreviewStoryArgs) => {
|
|
120
156
|
const [props, previewProps] = getPropsForPreview(args);
|
|
@@ -140,7 +176,6 @@ export const Sentiments: StoryObj<InfoPromptProps> = {
|
|
|
140
176
|
<InfoPrompt {...args} sentiment="proposition" description="Check out our new features!" />
|
|
141
177
|
</>
|
|
142
178
|
),
|
|
143
|
-
decorators: [withComponentGrid()],
|
|
144
179
|
};
|
|
145
180
|
|
|
146
181
|
/**
|
|
@@ -169,32 +204,6 @@ export const WithTitle: StoryObj<InfoPromptProps> = {
|
|
|
169
204
|
/>
|
|
170
205
|
</>
|
|
171
206
|
),
|
|
172
|
-
decorators: [withComponentGrid()],
|
|
173
|
-
};
|
|
174
|
-
|
|
175
|
-
/**
|
|
176
|
-
* When `onDismiss` is provided, a close button appears allowing users to dismiss the prompt.
|
|
177
|
-
* Note: The component itself is not automatically removed - you must handle state management.
|
|
178
|
-
*/
|
|
179
|
-
export const Dismissible: StoryObj<InfoPromptProps> = {
|
|
180
|
-
render: (args: InfoPromptProps) => (
|
|
181
|
-
<>
|
|
182
|
-
<InfoPrompt
|
|
183
|
-
{...args}
|
|
184
|
-
sentiment="success"
|
|
185
|
-
title="Success"
|
|
186
|
-
description="Your payment was successful."
|
|
187
|
-
onDismiss={action('onDismiss')}
|
|
188
|
-
/>
|
|
189
|
-
<InfoPrompt
|
|
190
|
-
{...args}
|
|
191
|
-
sentiment="neutral"
|
|
192
|
-
description="This message can be dismissed."
|
|
193
|
-
onDismiss={action('onDismiss')}
|
|
194
|
-
/>
|
|
195
|
-
</>
|
|
196
|
-
),
|
|
197
|
-
decorators: [withComponentGrid()],
|
|
198
207
|
};
|
|
199
208
|
|
|
200
209
|
/**
|
|
@@ -226,87 +235,108 @@ export const WithAction: StoryObj<InfoPromptProps> = {
|
|
|
226
235
|
/>
|
|
227
236
|
</>
|
|
228
237
|
),
|
|
229
|
-
decorators: [withComponentGrid()],
|
|
230
238
|
};
|
|
231
239
|
|
|
232
240
|
/**
|
|
233
|
-
*
|
|
234
|
-
*
|
|
241
|
+
* When `onDismiss` is provided, a close button appears allowing users to dismiss the prompt.
|
|
242
|
+
* Note: The component itself is not automatically removed - you must handle state management.
|
|
243
|
+
*/
|
|
244
|
+
export const Dismissible: StoryObj<InfoPromptProps> = {
|
|
245
|
+
render: function Render() {
|
|
246
|
+
const [showPrompt, setShowPrompt] = useState(true);
|
|
247
|
+
|
|
248
|
+
return (
|
|
249
|
+
<>
|
|
250
|
+
{showPrompt ? (
|
|
251
|
+
<InfoPrompt
|
|
252
|
+
sentiment="success"
|
|
253
|
+
title="Payment successful"
|
|
254
|
+
description="Your payment was successful."
|
|
255
|
+
onDismiss={() => setShowPrompt(false)}
|
|
256
|
+
/>
|
|
257
|
+
) : (
|
|
258
|
+
<Button v2 onClick={() => setShowPrompt(true)}>
|
|
259
|
+
Show prompt again
|
|
260
|
+
</Button>
|
|
261
|
+
)}
|
|
262
|
+
</>
|
|
263
|
+
);
|
|
264
|
+
},
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* InfoPrompt supports various media types to enhance visual communication.
|
|
235
269
|
*
|
|
270
|
+
* **Default**: Each sentiment has a default status icon that appears when no media is provided.
|
|
271
|
+
*
|
|
272
|
+
* **Icon Overrides**: Replace default icons with custom icons to match the prompt's visual language to its content.
|
|
236
273
|
* Success and proposition sentiments support both standard checkmark and confetti variations.
|
|
237
274
|
*/
|
|
238
|
-
export const
|
|
239
|
-
render: (args
|
|
275
|
+
export const MediaTypes: StoryObj<InfoPromptProps> = {
|
|
276
|
+
render: (args) => (
|
|
240
277
|
<>
|
|
278
|
+
<Title type="title-body">Default</Title>
|
|
279
|
+
<InfoPrompt
|
|
280
|
+
sentiment="success"
|
|
281
|
+
description="When no media is provided, the sentiment's default status icon is displayed."
|
|
282
|
+
/>
|
|
283
|
+
|
|
284
|
+
<Title type="title-body">Icon Overrides</Title>
|
|
241
285
|
<InfoPrompt
|
|
242
|
-
{...args}
|
|
243
286
|
sentiment="success"
|
|
244
287
|
description="Saved to favorites!"
|
|
245
288
|
media={{ asset: <Star title="Starred" /> }}
|
|
246
289
|
/>
|
|
247
290
|
<InfoPrompt
|
|
248
|
-
{...args}
|
|
249
291
|
sentiment="success"
|
|
250
292
|
title="Congratulations!"
|
|
251
293
|
description="You've completed all the steps."
|
|
252
294
|
media={{ asset: <Confetti size={24} title="Celebration" /> }}
|
|
253
295
|
/>
|
|
254
296
|
<InfoPrompt
|
|
255
|
-
{...args}
|
|
256
297
|
sentiment="proposition"
|
|
257
|
-
title="
|
|
298
|
+
title="Special offer"
|
|
258
299
|
description="New ways to send money with Wise!"
|
|
259
300
|
media={{ asset: <GiftBox title="Gift" /> }}
|
|
260
301
|
/>
|
|
261
302
|
<InfoPrompt
|
|
262
|
-
{...args}
|
|
263
303
|
sentiment="success"
|
|
264
304
|
description="Your travel account is ready!"
|
|
265
305
|
media={{ asset: <Travel title="Travel" /> }}
|
|
266
306
|
/>
|
|
267
|
-
</>
|
|
268
|
-
),
|
|
269
|
-
decorators: [withComponentGrid()],
|
|
270
|
-
};
|
|
271
|
-
|
|
272
|
-
/**
|
|
273
|
-
* InfoPrompt fully supports accessibility attributes through spread props.
|
|
274
|
-
* Use `role="status"` for non-critical updates or `role="alert"` for important messages.
|
|
275
|
-
*/
|
|
276
|
-
export const Accessibility: StoryObj<InfoPromptProps> = {
|
|
277
|
-
render: (args: InfoPromptProps) => (
|
|
278
|
-
<>
|
|
279
307
|
<InfoPrompt
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
description="
|
|
283
|
-
|
|
308
|
+
sentiment="warning"
|
|
309
|
+
title="Business travel"
|
|
310
|
+
description="Book your business trip with Wise."
|
|
311
|
+
media={{ asset: <Briefcase title="Business" /> }}
|
|
284
312
|
/>
|
|
285
313
|
<InfoPrompt
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
title="
|
|
289
|
-
description="Your session has expired. Please log in again."
|
|
290
|
-
role="alert"
|
|
314
|
+
sentiment="neutral"
|
|
315
|
+
description="Your flight is scheduled."
|
|
316
|
+
media={{ asset: <Plane title="Flight" /> }}
|
|
291
317
|
/>
|
|
292
318
|
</>
|
|
293
319
|
),
|
|
294
|
-
decorators: [withComponentGrid()],
|
|
295
320
|
};
|
|
296
321
|
|
|
297
322
|
/**
|
|
298
|
-
*
|
|
299
|
-
*
|
|
323
|
+
* While the component itself will stretch to the full available width, the text container will be
|
|
324
|
+
* capped at `480px` to ensure optimal readability.
|
|
325
|
+
*
|
|
326
|
+
* [Visit wise.design](https://wise.design/components/info-prompt#writing-an-info-prompt) for guidance on writing effective prompt messages that are concise and easy to understand.
|
|
300
327
|
*/
|
|
301
|
-
export const
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
328
|
+
export const ParagraphWidth: StoryObj<PreviewStoryArgs> = {
|
|
329
|
+
parameters: {
|
|
330
|
+
docs: {
|
|
331
|
+
canvas: {
|
|
332
|
+
sourceState: 'hidden',
|
|
333
|
+
},
|
|
334
|
+
},
|
|
335
|
+
},
|
|
336
|
+
args: {
|
|
337
|
+
title: lorem10,
|
|
338
|
+
description: lorem20,
|
|
339
|
+
sentiment: 'success',
|
|
340
|
+
onDismiss: () => {},
|
|
341
|
+
},
|
|
312
342
|
};
|
|
@@ -1,7 +1,19 @@
|
|
|
1
|
-
import { useState, MouseEvent } from 'react';
|
|
1
|
+
import { useState, MouseEvent, ReactElement } from 'react';
|
|
2
2
|
import { userEvent, within, expect, waitFor } from 'storybook/test';
|
|
3
3
|
import { Meta, StoryObj } from '@storybook/react-webpack5';
|
|
4
|
+
import { action } from 'storybook/actions';
|
|
5
|
+
import { Star } from '@transferwise/icons';
|
|
4
6
|
import { InfoPrompt, type InfoPromptProps } from './InfoPrompt';
|
|
7
|
+
import { allModes } from '../../../.storybook/modes';
|
|
8
|
+
import { withVariantConfig } from '../../../.storybook/helpers';
|
|
9
|
+
|
|
10
|
+
const withComponentGrid =
|
|
11
|
+
({ gap = '1rem' } = {}) =>
|
|
12
|
+
(Story: () => ReactElement) => (
|
|
13
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap }}>
|
|
14
|
+
<Story />
|
|
15
|
+
</div>
|
|
16
|
+
);
|
|
5
17
|
|
|
6
18
|
const meta = {
|
|
7
19
|
title: 'Prompts/InfoPrompt/Tests',
|
|
@@ -244,3 +256,44 @@ export const TouchInteraction: Story = {
|
|
|
244
256
|
);
|
|
245
257
|
},
|
|
246
258
|
};
|
|
259
|
+
|
|
260
|
+
export const AllThemesAndSentiments: Story = {
|
|
261
|
+
render: () => (
|
|
262
|
+
<>
|
|
263
|
+
{(['success', 'warning', 'negative', 'neutral', 'proposition'] as const).map((sentiment) => (
|
|
264
|
+
<InfoPrompt
|
|
265
|
+
key={sentiment}
|
|
266
|
+
sentiment={sentiment}
|
|
267
|
+
title={`${sentiment.charAt(0).toUpperCase() + sentiment.slice(1)} prompt`}
|
|
268
|
+
description="This demonstrates the prompt appearance in different themes."
|
|
269
|
+
media={sentiment === 'success' ? { asset: <Star title="Star" /> } : undefined}
|
|
270
|
+
action={{ label: 'Action', onClick: action('action') }}
|
|
271
|
+
onDismiss={action('dismiss')}
|
|
272
|
+
/>
|
|
273
|
+
))}
|
|
274
|
+
</>
|
|
275
|
+
),
|
|
276
|
+
decorators: [withComponentGrid({ gap: '1.5rem' })],
|
|
277
|
+
parameters: {
|
|
278
|
+
padding: '16px',
|
|
279
|
+
variants: ['default', 'dark', 'bright-green', 'forest-green'],
|
|
280
|
+
chromatic: {
|
|
281
|
+
dark: allModes.dark,
|
|
282
|
+
brightGreen: allModes.brightGreen,
|
|
283
|
+
forestGreen: allModes.forestGreen,
|
|
284
|
+
},
|
|
285
|
+
},
|
|
286
|
+
};
|
|
287
|
+
|
|
288
|
+
export const TinyScreen: Story = {
|
|
289
|
+
render: () => (
|
|
290
|
+
<InfoPrompt
|
|
291
|
+
sentiment="success"
|
|
292
|
+
title="Payment received"
|
|
293
|
+
description="Your transfer of 500 GBP has been successfully received and is now available in your account."
|
|
294
|
+
action={{ label: 'View details', onClick: action('view-details') }}
|
|
295
|
+
onDismiss={action('dismiss')}
|
|
296
|
+
/>
|
|
297
|
+
),
|
|
298
|
+
...withVariantConfig(['400%']),
|
|
299
|
+
};
|
|
@@ -18,8 +18,7 @@ export type InfoPromptAction = Pick<LinkProps, 'href' | 'target' | 'onClick'> &
|
|
|
18
18
|
export type InfoPromptMedia = {
|
|
19
19
|
/**
|
|
20
20
|
* The icon/image asset to display.
|
|
21
|
-
* The asset should include its own accessibility attributes (e.g. title, aria-label)
|
|
22
|
-
* if it conveys meaning, or aria-hidden="true" if decorative.
|
|
21
|
+
* The asset should include its own accessibility attributes (e.g. title, aria-label) if it conveys meaning.
|
|
23
22
|
*/
|
|
24
23
|
asset: ReactNode;
|
|
25
24
|
};
|
|
@@ -53,6 +52,7 @@ export type InfoPromptProps = Omit<HTMLAttributes<HTMLDivElement>, 'title'> &
|
|
|
53
52
|
* Description text for the prompt (required)
|
|
54
53
|
*/
|
|
55
54
|
description: string;
|
|
55
|
+
className?: string;
|
|
56
56
|
};
|
|
57
57
|
|
|
58
58
|
/**
|
|
@@ -61,6 +61,8 @@ export type InfoPromptProps = Omit<HTMLAttributes<HTMLDivElement>, 'title'> &
|
|
|
61
61
|
* or positive feedback with optional actions and dismissal capabilities.
|
|
62
62
|
*
|
|
63
63
|
* Use this component to replace the deprecated Alert component.
|
|
64
|
+
*
|
|
65
|
+
* Guidance can be found in the [design system](https://wise.design/components/info-prompt).
|
|
64
66
|
*/
|
|
65
67
|
export const InfoPrompt = ({
|
|
66
68
|
sentiment = 'neutral',
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { Meta, Source } from '@storybook/addon-docs/blocks';
|
|
2
|
+
|
|
3
|
+
<Meta title="Prompts/InlinePrompt/Accessibility" />
|
|
4
|
+
|
|
5
|
+
# Accessibility
|
|
6
|
+
|
|
7
|
+
Under the hood, `InlinePrompt` 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` icon and the prompt's text content.
|
|
10
|
+
|
|
11
|
+
## Announcement Behaviour
|
|
12
|
+
|
|
13
|
+
`InlinePrompt` appears alongside a specific component on the screen (e.g., input fields, list items). It should not be announced via screen reader—neither assertively (`role="alert"`) nor politely (`role="status"`).
|
|
14
|
+
|
|
15
|
+
**Note:** For immediate user feedback that requires announcement, use [InfoPrompt](?path=/docs/prompts-infoprompt--docs) instead.
|
|
16
|
+
|
|
17
|
+
## Media Label
|
|
18
|
+
|
|
19
|
+
You can use the `mediaLabel` prop to override the icon's accessible name announced by screen readers. This is especially useful for custom icons or when you want to provide more context than the default sentiment label.
|
|
20
|
+
|
|
21
|
+
By default, sentiment icons have these labels:
|
|
22
|
+
|
|
23
|
+
- `positive`: "Success:"
|
|
24
|
+
- `negative`: "Error:"
|
|
25
|
+
- `warning`: "Warning:"
|
|
26
|
+
- `neutral`: "Information:"
|
|
27
|
+
- `proposition`: No label (GiftBox icon is decorative by default)
|
|
28
|
+
|
|
29
|
+
<Source
|
|
30
|
+
dark
|
|
31
|
+
code={`
|
|
32
|
+
<InlinePrompt
|
|
33
|
+
media={<Travel />}
|
|
34
|
+
sentiment="positive"
|
|
35
|
+
mediaLabel="Travel feature enabled: "
|
|
36
|
+
>
|
|
37
|
+
Your travel account is set up and ready to use.
|
|
38
|
+
</InlinePrompt>
|
|
39
|
+
|
|
40
|
+
<InlinePrompt
|
|
41
|
+
media={<Clock />}
|
|
42
|
+
sentiment="warning"
|
|
43
|
+
mediaLabel="Delayed processing: "
|
|
44
|
+
>
|
|
45
|
+
The account verification is taking longer than usual.
|
|
46
|
+
</InlinePrompt>
|
|
47
|
+
`}
|
|
48
|
+
/>
|
|
49
|
+
|
|
50
|
+
## Muted State
|
|
51
|
+
|
|
52
|
+
When a prompt is associated with a disabled component, use the `muted` prop to visually indicate the relationship while keeping the prompt accessible and interactive. The prompt remains keyboard accessible and can include interactive elements like links.
|
|
53
|
+
|
|
54
|
+
<Source
|
|
55
|
+
dark
|
|
56
|
+
code={`
|
|
57
|
+
<InlinePrompt muted sentiment="warning" mediaLabel="Feature locked: ">
|
|
58
|
+
Please <Link href="#">confirm your residential address</Link> to activate this feature.
|
|
59
|
+
</InlinePrompt>
|
|
60
|
+
`}
|
|
61
|
+
/>
|
|
62
|
+
|
|
63
|
+
The muted state uses a special backslash circle icon to indicate the disabled relationship, but the prompt itself remains fully accessible to screen readers and keyboard users.
|
|
@@ -18,8 +18,9 @@
|
|
|
18
18
|
.wds-inline-prompt:has(button):active {
|
|
19
19
|
background-color: var(--color-sentiment-background-surface-active);
|
|
20
20
|
}
|
|
21
|
-
.wds-inline-prompt--
|
|
22
|
-
width:
|
|
21
|
+
.wds-inline-prompt--auto-width {
|
|
22
|
+
width: auto;
|
|
23
|
+
width: initial;
|
|
23
24
|
}
|
|
24
25
|
.wds-inline-prompt--muted {
|
|
25
26
|
opacity: 0.93;
|