@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
|
@@ -1,22 +1,11 @@
|
|
|
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,
|
|
5
|
-
import {
|
|
6
|
-
import
|
|
7
|
-
import Body from '../../body';
|
|
8
|
-
import { action } from 'storybook/actions';
|
|
4
|
+
import { Bank, Star, Travel, Briefcase } from '@transferwise/icons';
|
|
5
|
+
import { lorem10, lorem20 } from '../../test-utils';
|
|
6
|
+
import Button from '../../button';
|
|
9
7
|
import Title from '../../title';
|
|
10
|
-
import {
|
|
11
|
-
|
|
12
|
-
const meta: Meta<typeof ActionPrompt> = {
|
|
13
|
-
title: 'Prompts/ActionPrompt',
|
|
14
|
-
component: ActionPrompt,
|
|
15
|
-
tags: ['new'],
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
export default meta;
|
|
19
|
-
type Story = StoryObj<typeof ActionPrompt>;
|
|
8
|
+
import { ActionPrompt, type ActionPromptProps } from './ActionPrompt';
|
|
20
9
|
|
|
21
10
|
const withComponentGrid =
|
|
22
11
|
({ maxWidth = 'auto', gap = '1rem' } = {}) =>
|
|
@@ -37,151 +26,377 @@ const withComponentGrid =
|
|
|
37
26
|
</div>
|
|
38
27
|
);
|
|
39
28
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
{(['negative', 'warning', 'neutral', 'success', 'proposition'] as const).map(
|
|
45
|
-
(sentiment) => (
|
|
46
|
-
<ActionPrompt
|
|
47
|
-
key={sentiment}
|
|
48
|
-
sentiment={sentiment}
|
|
49
|
-
title="Title"
|
|
50
|
-
description={lorem10}
|
|
51
|
-
action={{ label: 'Primary', onClick: fn() }}
|
|
52
|
-
actionSecondary={{ label: 'Secondary', onClick: fn() }}
|
|
53
|
-
className="m-b-2"
|
|
54
|
-
onDismiss={fn()}
|
|
55
|
-
/>
|
|
56
|
-
),
|
|
57
|
-
)}
|
|
58
|
-
</>
|
|
59
|
-
);
|
|
60
|
-
},
|
|
29
|
+
const meta: Meta<typeof ActionPrompt> = {
|
|
30
|
+
title: 'Prompts/ActionPrompt',
|
|
31
|
+
component: ActionPrompt,
|
|
32
|
+
tags: ['new'],
|
|
61
33
|
decorators: [withComponentGrid()],
|
|
34
|
+
args: {
|
|
35
|
+
title: 'Action Required',
|
|
36
|
+
description: 'Please complete the following action to continue.',
|
|
37
|
+
action: { label: 'Continue', onClick: fn() },
|
|
38
|
+
},
|
|
39
|
+
argTypes: {
|
|
40
|
+
sentiment: {
|
|
41
|
+
control: 'select',
|
|
42
|
+
options: ['success', 'negative', 'neutral', 'warning', 'proposition'],
|
|
43
|
+
},
|
|
44
|
+
title: {
|
|
45
|
+
control: 'text',
|
|
46
|
+
table: {
|
|
47
|
+
type: { summary: 'ReactNode' },
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
description: {
|
|
51
|
+
control: 'text',
|
|
52
|
+
table: {
|
|
53
|
+
type: { summary: 'ReactNode' },
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
media: {
|
|
57
|
+
control: false,
|
|
58
|
+
table: {
|
|
59
|
+
type: {
|
|
60
|
+
summary: 'ActionPromptMedia',
|
|
61
|
+
detail:
|
|
62
|
+
'{ imgSrc?: string; avatar?: { imgSrc?: string; profileName?: string; profileType?: string; asset?: ReactNode; badge?: { flagCode: string } }; aria-label?: string }',
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
action: {
|
|
67
|
+
control: false,
|
|
68
|
+
table: {
|
|
69
|
+
type: {
|
|
70
|
+
summary: 'ActionPromptAction',
|
|
71
|
+
detail: '{ label: ReactNode; onClick?: () => void; href?: string; target?: string }',
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
actionSecondary: {
|
|
76
|
+
control: false,
|
|
77
|
+
table: {
|
|
78
|
+
type: {
|
|
79
|
+
summary: 'ActionPromptActionSecondary',
|
|
80
|
+
detail: '{ label: ReactNode; onClick?: () => void; href?: string; target?: string }',
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
parameters: {
|
|
86
|
+
docs: {
|
|
87
|
+
toc: true,
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
export default meta;
|
|
93
|
+
type Story = StoryObj<typeof ActionPrompt>;
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Convenience controls for previewing rich markup,
|
|
97
|
+
* not otherwise possible via Storybook
|
|
98
|
+
*/
|
|
99
|
+
type PreviewStoryArgs = ActionPromptProps & {
|
|
100
|
+
previewMedia: ActionPromptProps['media'];
|
|
101
|
+
previewOnDismiss: boolean;
|
|
102
|
+
previewSecondaryAction: boolean;
|
|
62
103
|
};
|
|
63
104
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
105
|
+
const previewArgGroup = {
|
|
106
|
+
category: 'Storybook Preview options',
|
|
107
|
+
type: {
|
|
108
|
+
summary: undefined,
|
|
109
|
+
},
|
|
67
110
|
};
|
|
68
111
|
|
|
69
|
-
|
|
112
|
+
const MEDIA_OPTIONS: Record<string, ActionPromptProps['media']> = {
|
|
113
|
+
undefined,
|
|
114
|
+
'Custom image (no badge)': { imgSrc: '../../wise-card.svg' },
|
|
115
|
+
'Avatar: Profile image': { avatar: { imgSrc: '../../avatar-rectangle-fox.webp' } },
|
|
116
|
+
'Avatar: Business (GB badge)': {
|
|
117
|
+
avatar: { profileType: 'BUSINESS', badge: { flagCode: 'GB' } },
|
|
118
|
+
},
|
|
119
|
+
'Avatar: Personal (EU badge)': {
|
|
120
|
+
avatar: { profileType: 'PERSONAL', badge: { flagCode: 'EU' } },
|
|
121
|
+
},
|
|
122
|
+
'Avatar: Icon (Bank)': { avatar: { asset: <Bank /> } },
|
|
123
|
+
'Avatar: Icon (Star)': { avatar: { asset: <Star /> } },
|
|
124
|
+
'Avatar: Icon (Travel)': { avatar: { asset: <Travel /> } },
|
|
125
|
+
'Avatar: Icon (Briefcase)': { avatar: { asset: <Briefcase /> } },
|
|
126
|
+
'Avatar: Initials': { avatar: { profileName: 'John Doe' } },
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
const previewArgTypes = {
|
|
130
|
+
previewMedia: {
|
|
131
|
+
name: 'Preview with `media`',
|
|
132
|
+
control: 'select',
|
|
133
|
+
options: [
|
|
134
|
+
'undefined',
|
|
135
|
+
'Custom image (no badge)',
|
|
136
|
+
'Avatar: Profile image',
|
|
137
|
+
'Avatar: Business (GB badge)',
|
|
138
|
+
'Avatar: Personal (EU badge)',
|
|
139
|
+
'Avatar: Icon (Bank)',
|
|
140
|
+
'Avatar: Icon (Star)',
|
|
141
|
+
'Avatar: Icon (Travel)',
|
|
142
|
+
'Avatar: Icon (Briefcase)',
|
|
143
|
+
'Avatar: Initials',
|
|
144
|
+
],
|
|
145
|
+
mapping: MEDIA_OPTIONS,
|
|
146
|
+
table: previewArgGroup,
|
|
147
|
+
},
|
|
148
|
+
previewOnDismiss: {
|
|
149
|
+
name: 'Preview with `onDismiss`',
|
|
150
|
+
control: 'boolean',
|
|
151
|
+
table: previewArgGroup,
|
|
152
|
+
},
|
|
153
|
+
previewSecondaryAction: {
|
|
154
|
+
name: 'Preview with `actionSecondary`',
|
|
155
|
+
control: 'boolean',
|
|
156
|
+
table: previewArgGroup,
|
|
157
|
+
},
|
|
158
|
+
} as const;
|
|
159
|
+
|
|
160
|
+
const getPropsForPreview = (
|
|
161
|
+
args: PreviewStoryArgs,
|
|
162
|
+
): [ActionPromptProps, Partial<ActionPromptProps>] => {
|
|
163
|
+
const { previewMedia, previewOnDismiss, previewSecondaryAction, ...props } = args;
|
|
164
|
+
|
|
165
|
+
return [
|
|
166
|
+
props,
|
|
167
|
+
{
|
|
168
|
+
media: previewMedia,
|
|
169
|
+
onDismiss: previewOnDismiss ? fn() : undefined,
|
|
170
|
+
actionSecondary: previewSecondaryAction ? { label: 'Cancel', onClick: fn() } : undefined,
|
|
171
|
+
},
|
|
172
|
+
];
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
export const Playground: StoryObj<PreviewStoryArgs> = {
|
|
176
|
+
argTypes: {
|
|
177
|
+
sentiment: {
|
|
178
|
+
control: 'radio',
|
|
179
|
+
options: ['success', 'negative', 'neutral', 'warning', 'proposition'],
|
|
180
|
+
},
|
|
181
|
+
...previewArgTypes,
|
|
182
|
+
},
|
|
70
183
|
args: {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
184
|
+
previewMedia: { avatar: undefined },
|
|
185
|
+
previewOnDismiss: true,
|
|
186
|
+
previewSecondaryAction: true,
|
|
187
|
+
},
|
|
188
|
+
render: (args: PreviewStoryArgs) => {
|
|
189
|
+
const [props, previewProps] = getPropsForPreview(args);
|
|
190
|
+
return <ActionPrompt {...props} {...previewProps} />;
|
|
76
191
|
},
|
|
77
192
|
};
|
|
78
193
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
194
|
+
/**
|
|
195
|
+
* ActionPrompt supports multiple sentiments to communicate different types of messages:
|
|
196
|
+
* - `neutral` (default): General prompts
|
|
197
|
+
* - `success`: Success confirmations
|
|
198
|
+
* - `negative`: Error or critical actions
|
|
199
|
+
* - `warning`: Warning messages
|
|
200
|
+
* - `proposition`: Promotional or feature prompts
|
|
201
|
+
*/
|
|
202
|
+
export const Sentiments: Story = {
|
|
203
|
+
render: (args) => (
|
|
204
|
+
<>
|
|
205
|
+
{(['success', 'warning', 'negative', 'neutral', 'proposition'] as const).map((sentiment) => (
|
|
90
206
|
<ActionPrompt
|
|
91
|
-
sentiment
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
action={{ label: 'Stay logged in', onClick: fn() }}
|
|
207
|
+
key={sentiment}
|
|
208
|
+
sentiment={sentiment}
|
|
209
|
+
title={`${sentiment.charAt(0).toUpperCase() + sentiment.slice(1)} Action`}
|
|
210
|
+
description={lorem10}
|
|
211
|
+
action={{ label: 'Primary', onClick: fn() }}
|
|
212
|
+
actionSecondary={{ label: 'Secondary', onClick: fn() }}
|
|
98
213
|
/>
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
214
|
+
))}
|
|
215
|
+
</>
|
|
216
|
+
),
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* ActionPrompt can have just a primary action, or both primary and secondary actions.
|
|
221
|
+
*/
|
|
222
|
+
export const Actions: Story = {
|
|
223
|
+
render: () => (
|
|
224
|
+
<>
|
|
225
|
+
<ActionPrompt
|
|
226
|
+
sentiment="proposition"
|
|
227
|
+
title="Primary action only"
|
|
228
|
+
description="This prompt has only one action button."
|
|
229
|
+
action={{ label: 'Get started', onClick: fn() }}
|
|
230
|
+
/>
|
|
231
|
+
<ActionPrompt
|
|
232
|
+
sentiment="warning"
|
|
233
|
+
title="Primary and secondary actions"
|
|
234
|
+
description="This prompt has both action buttons."
|
|
235
|
+
action={{ label: 'Confirm', onClick: fn() }}
|
|
236
|
+
actionSecondary={{ label: 'Cancel', onClick: fn() }}
|
|
237
|
+
/>
|
|
238
|
+
</>
|
|
239
|
+
),
|
|
103
240
|
};
|
|
104
241
|
|
|
105
|
-
|
|
242
|
+
/**
|
|
243
|
+
* ActionPrompt supports various media types to enhance visual communication.
|
|
244
|
+
*
|
|
245
|
+
* **Default**: Each sentiment has a default status icon that appears when no media is provided.
|
|
246
|
+
*
|
|
247
|
+
* **Icon Overrides**: Replace default icons with custom images, avatars with images, avatars with initials,
|
|
248
|
+
* avatars with custom icons, or avatars with profile types and badges.
|
|
249
|
+
*/
|
|
250
|
+
export const MediaTypes: Story = {
|
|
106
251
|
render: () => {
|
|
107
252
|
return (
|
|
108
253
|
<>
|
|
254
|
+
<Title type="title-body">Default</Title>
|
|
255
|
+
<ActionPrompt
|
|
256
|
+
sentiment="success"
|
|
257
|
+
title="Default status icon"
|
|
258
|
+
description="When no media is provided, the sentiment's default status icon is displayed."
|
|
259
|
+
action={{ label: 'Continue', onClick: fn() }}
|
|
260
|
+
/>
|
|
261
|
+
|
|
262
|
+
<Title type="title-body">Icon Overrides</Title>
|
|
109
263
|
<ActionPrompt
|
|
110
264
|
sentiment="neutral"
|
|
111
265
|
title="Custom image"
|
|
112
266
|
description={lorem10}
|
|
113
267
|
media={{ imgSrc: '../../wise-card.svg' }}
|
|
114
|
-
action={{ label: '
|
|
268
|
+
action={{ label: 'View card', onClick: fn() }}
|
|
115
269
|
/>
|
|
116
270
|
<ActionPrompt
|
|
117
271
|
sentiment="neutral"
|
|
118
|
-
title="
|
|
272
|
+
title="Avatar with image"
|
|
119
273
|
description={lorem10}
|
|
120
274
|
media={{ avatar: { imgSrc: '../../avatar-rectangle-fox.webp' } }}
|
|
121
|
-
action={{ label: '
|
|
275
|
+
action={{ label: 'View profile', onClick: fn() }}
|
|
122
276
|
/>
|
|
123
277
|
<ActionPrompt
|
|
124
|
-
sentiment="
|
|
125
|
-
title="
|
|
278
|
+
sentiment="success"
|
|
279
|
+
title="Avatar with initials"
|
|
126
280
|
description={lorem10}
|
|
127
|
-
media={{ avatar: {
|
|
128
|
-
action={{ label: '
|
|
281
|
+
media={{ avatar: { profileName: 'John Doe' } }}
|
|
282
|
+
action={{ label: 'Send invite', onClick: fn() }}
|
|
129
283
|
/>
|
|
130
284
|
<ActionPrompt
|
|
131
|
-
sentiment="
|
|
132
|
-
title="
|
|
285
|
+
sentiment="warning"
|
|
286
|
+
title="Avatar with custom icon"
|
|
133
287
|
description={lorem10}
|
|
134
|
-
media={{ avatar: {
|
|
135
|
-
action={{ label: '
|
|
288
|
+
media={{ avatar: { asset: <Bank /> } }}
|
|
289
|
+
action={{ label: 'Connect bank', onClick: fn() }}
|
|
136
290
|
/>
|
|
137
291
|
<ActionPrompt
|
|
138
|
-
sentiment="
|
|
139
|
-
title="
|
|
292
|
+
sentiment="proposition"
|
|
293
|
+
title="Business profile with badge"
|
|
140
294
|
description={lorem10}
|
|
141
|
-
media={{ avatar: {
|
|
142
|
-
action={{ label: '
|
|
295
|
+
media={{ avatar: { profileType: 'BUSINESS', badge: { flagCode: 'GB' } } }}
|
|
296
|
+
action={{ label: 'Open account', onClick: fn() }}
|
|
143
297
|
/>
|
|
144
298
|
<ActionPrompt
|
|
145
|
-
sentiment="
|
|
146
|
-
title="
|
|
299
|
+
sentiment="negative"
|
|
300
|
+
title="Personal profile with badge"
|
|
147
301
|
description={lorem10}
|
|
148
|
-
media={{ avatar: {
|
|
149
|
-
action={{ label: '
|
|
150
|
-
actionSecondary={{ label: 'Maybe later', onClick: fn() }}
|
|
302
|
+
media={{ avatar: { profileType: 'PERSONAL', badge: { flagCode: 'EU' } } }}
|
|
303
|
+
action={{ label: 'Verify', onClick: fn() }}
|
|
151
304
|
/>
|
|
152
305
|
</>
|
|
153
306
|
);
|
|
154
307
|
},
|
|
155
|
-
decorators: [withComponentGrid()],
|
|
156
308
|
};
|
|
157
309
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
310
|
+
/**
|
|
311
|
+
* When `onDismiss` is provided, a close button appears allowing users to dismiss the prompt.
|
|
312
|
+
* Note: The component itself is not automatically removed - you must handle state management.
|
|
313
|
+
*/
|
|
314
|
+
export const Dismissible: Story = {
|
|
315
|
+
render: (args) => {
|
|
316
|
+
const [showPrompt, setShowPrompt] = useState(true);
|
|
162
317
|
|
|
163
318
|
return (
|
|
164
319
|
<>
|
|
165
|
-
{
|
|
320
|
+
{showPrompt ? (
|
|
166
321
|
<ActionPrompt
|
|
167
322
|
sentiment="proposition"
|
|
168
|
-
title="
|
|
169
|
-
description="
|
|
170
|
-
action={{ label: '
|
|
171
|
-
|
|
172
|
-
onDismiss={() => setShowNeutralPrompt(false)}
|
|
173
|
-
/>
|
|
174
|
-
)}
|
|
175
|
-
{showSuccessPrompt && (
|
|
176
|
-
<ActionPrompt
|
|
177
|
-
sentiment="success"
|
|
178
|
-
title="Payment successful"
|
|
179
|
-
description="Your money is on its way"
|
|
180
|
-
action={{ label: 'View details', onClick: fn() }}
|
|
181
|
-
onDismiss={() => setShowSuccessPrompt(false)}
|
|
323
|
+
title="Special offer"
|
|
324
|
+
description="Get 50% off your next transfer!"
|
|
325
|
+
action={{ label: 'Claim now', onClick: fn() }}
|
|
326
|
+
onDismiss={() => setShowPrompt(false)}
|
|
182
327
|
/>
|
|
328
|
+
) : (
|
|
329
|
+
<Button v2 onClick={() => setShowPrompt(true)}>
|
|
330
|
+
Show prompt again
|
|
331
|
+
</Button>
|
|
183
332
|
)}
|
|
184
333
|
</>
|
|
185
334
|
);
|
|
186
335
|
},
|
|
187
336
|
};
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* ActionPrompt adapts its layout based on available width. At narrow widths (mobile),
|
|
340
|
+
* the buttons stack vertically. At wider widths (desktop), they appear side by side.
|
|
341
|
+
*/
|
|
342
|
+
export const Responsiveness: Story = {
|
|
343
|
+
render: (args) => (
|
|
344
|
+
<div
|
|
345
|
+
style={{
|
|
346
|
+
display: 'grid',
|
|
347
|
+
gridTemplateColumns: '320px 480px',
|
|
348
|
+
gap: '1rem',
|
|
349
|
+
alignItems: 'start',
|
|
350
|
+
}}
|
|
351
|
+
>
|
|
352
|
+
<ActionPrompt
|
|
353
|
+
sentiment="proposition"
|
|
354
|
+
title="Stacked (320px)"
|
|
355
|
+
description={lorem10}
|
|
356
|
+
media={{ avatar: { asset: <Travel /> } }}
|
|
357
|
+
action={{ label: 'Primary action', onClick: fn() }}
|
|
358
|
+
actionSecondary={{ label: 'Secondary', onClick: fn() }}
|
|
359
|
+
onDismiss={fn()}
|
|
360
|
+
/>
|
|
361
|
+
<ActionPrompt
|
|
362
|
+
sentiment="proposition"
|
|
363
|
+
title="Side-by-side (480px)"
|
|
364
|
+
description={lorem10}
|
|
365
|
+
media={{ avatar: { asset: <Travel /> } }}
|
|
366
|
+
action={{ label: 'Primary action', onClick: fn() }}
|
|
367
|
+
actionSecondary={{ label: 'Secondary', onClick: fn() }}
|
|
368
|
+
onDismiss={fn()}
|
|
369
|
+
/>
|
|
370
|
+
</div>
|
|
371
|
+
),
|
|
372
|
+
parameters: {
|
|
373
|
+
docs: {
|
|
374
|
+
canvas: {
|
|
375
|
+
sourceState: 'hidden',
|
|
376
|
+
},
|
|
377
|
+
},
|
|
378
|
+
},
|
|
379
|
+
};
|
|
380
|
+
|
|
381
|
+
/**
|
|
382
|
+
* While the component itself will stretch to the full available width, the text container will be
|
|
383
|
+
* capped at `480px` to ensure optimal readability.
|
|
384
|
+
*
|
|
385
|
+
* [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.
|
|
386
|
+
*/
|
|
387
|
+
export const ParagraphWidth: Story = {
|
|
388
|
+
parameters: {
|
|
389
|
+
docs: {
|
|
390
|
+
canvas: {
|
|
391
|
+
sourceState: 'hidden',
|
|
392
|
+
},
|
|
393
|
+
},
|
|
394
|
+
},
|
|
395
|
+
args: {
|
|
396
|
+
title: lorem10,
|
|
397
|
+
description: lorem20,
|
|
398
|
+
sentiment: 'success',
|
|
399
|
+
action: { label: 'View details', onClick: fn() },
|
|
400
|
+
onDismiss: () => {},
|
|
401
|
+
},
|
|
402
|
+
};
|
|
@@ -1,13 +1,25 @@
|
|
|
1
|
+
import { ReactElement } from 'react';
|
|
1
2
|
import { Freeze, People } from '@transferwise/icons';
|
|
2
3
|
import { action } from 'storybook/actions';
|
|
4
|
+
import { fn } from 'storybook/test';
|
|
3
5
|
import ActionPrompt from './ActionPrompt';
|
|
4
6
|
import { Body, Title } from '../..';
|
|
5
|
-
import {
|
|
7
|
+
import { StoryObj } from '@storybook/react-webpack5';
|
|
8
|
+
import { allModes } from '../../../.storybook/modes';
|
|
9
|
+
import { withVariantConfig } from '../../../.storybook/helpers';
|
|
10
|
+
|
|
11
|
+
const withComponentGrid =
|
|
12
|
+
({ gap = '1rem' } = {}) =>
|
|
13
|
+
(Story: () => ReactElement) => (
|
|
14
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap }}>
|
|
15
|
+
<Story />
|
|
16
|
+
</div>
|
|
17
|
+
);
|
|
6
18
|
|
|
7
19
|
export default {
|
|
8
20
|
title: 'Prompts/ActionPrompt/Tests',
|
|
9
21
|
component: ActionPrompt,
|
|
10
|
-
tags: ['!manifest', '!autodocs'],
|
|
22
|
+
tags: ['!manifest', '!autodocs', 'new'],
|
|
11
23
|
};
|
|
12
24
|
|
|
13
25
|
type Story = StoryObj<typeof ActionPrompt>;
|
|
@@ -130,7 +142,7 @@ export const VariousA11yFeatures: Story = {
|
|
|
130
142
|
sentiment="negative"
|
|
131
143
|
media={{ avatar: { asset: <People /> } }}
|
|
132
144
|
title="Sync contacts for a faster experience"
|
|
133
|
-
description="Find contacts on Wise — it
|
|
145
|
+
description="Find contacts on Wise — it's simple, secure and you pick who you add."
|
|
134
146
|
action={{
|
|
135
147
|
label: 'Sync contacts',
|
|
136
148
|
onClick: () => {
|
|
@@ -145,3 +157,74 @@ export const VariousA11yFeatures: Story = {
|
|
|
145
157
|
);
|
|
146
158
|
},
|
|
147
159
|
};
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* ActionPrompt can be shown with or without a description.
|
|
163
|
+
*/
|
|
164
|
+
export const WithoutDescription: Story = {
|
|
165
|
+
render: () => {
|
|
166
|
+
return (
|
|
167
|
+
<>
|
|
168
|
+
<ActionPrompt
|
|
169
|
+
sentiment="warning"
|
|
170
|
+
title="Session expiring soon"
|
|
171
|
+
action={{ label: 'Stay logged in', onClick: fn() }}
|
|
172
|
+
actionSecondary={{ label: 'Log out', onClick: fn() }}
|
|
173
|
+
/>
|
|
174
|
+
<ActionPrompt
|
|
175
|
+
sentiment="success"
|
|
176
|
+
title="Payment successful"
|
|
177
|
+
action={{ label: 'View details', onClick: fn() }}
|
|
178
|
+
/>
|
|
179
|
+
<ActionPrompt title="Quick action" action={{ label: 'Continue', onClick: fn() }} />
|
|
180
|
+
</>
|
|
181
|
+
);
|
|
182
|
+
},
|
|
183
|
+
decorators: [withComponentGrid()],
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
export const AllThemesAndSentiments: Story = {
|
|
187
|
+
render: () => {
|
|
188
|
+
return (
|
|
189
|
+
<>
|
|
190
|
+
{(['negative', 'warning', 'neutral', 'success', 'proposition'] as const).map(
|
|
191
|
+
(sentiment) => (
|
|
192
|
+
<ActionPrompt
|
|
193
|
+
key={sentiment}
|
|
194
|
+
sentiment={sentiment}
|
|
195
|
+
title={`${sentiment.charAt(0).toUpperCase() + sentiment.slice(1)} action`}
|
|
196
|
+
description="This demonstrates the prompt appearance in different themes."
|
|
197
|
+
action={{ label: 'Primary', onClick: fn() }}
|
|
198
|
+
actionSecondary={{ label: 'Secondary', onClick: fn() }}
|
|
199
|
+
onDismiss={fn()}
|
|
200
|
+
/>
|
|
201
|
+
),
|
|
202
|
+
)}
|
|
203
|
+
</>
|
|
204
|
+
);
|
|
205
|
+
},
|
|
206
|
+
decorators: [withComponentGrid({ gap: '1.5rem' })],
|
|
207
|
+
parameters: {
|
|
208
|
+
padding: '16px',
|
|
209
|
+
variants: ['default', 'dark', 'bright-green', 'forest-green'],
|
|
210
|
+
chromatic: {
|
|
211
|
+
dark: allModes.dark,
|
|
212
|
+
brightGreen: allModes.brightGreen,
|
|
213
|
+
forestGreen: allModes.forestGreen,
|
|
214
|
+
},
|
|
215
|
+
},
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
export const TinyScreen: Story = {
|
|
219
|
+
render: () => (
|
|
220
|
+
<ActionPrompt
|
|
221
|
+
sentiment="warning"
|
|
222
|
+
title="Complete your profile"
|
|
223
|
+
description="Add your business details to unlock all features and start sending money internationally."
|
|
224
|
+
action={{ label: 'Complete profile', onClick: fn() }}
|
|
225
|
+
actionSecondary={{ label: 'Remind me later', onClick: fn() }}
|
|
226
|
+
onDismiss={fn()}
|
|
227
|
+
/>
|
|
228
|
+
),
|
|
229
|
+
...withVariantConfig(['400%']),
|
|
230
|
+
};
|
|
@@ -34,6 +34,13 @@ export type ActionPromptProps = {
|
|
|
34
34
|
'aria-label'?: AriaAttributes['aria-label'];
|
|
35
35
|
} & Pick<PrimitivePromptProps, 'id' | 'className' | 'data-testid' | 'sentiment' | 'onDismiss'>;
|
|
36
36
|
|
|
37
|
+
/**
|
|
38
|
+
* Use an action prompt for optional feedback that doesn't require immediate action, such as feature upsells, warnings, or suggestions. These prompts are typically used outside of core product flows (e.g., Launchpad, Recipient, or Transaction screens) and can be addressed at the user's convenience.
|
|
39
|
+
*
|
|
40
|
+
* If your message is about immediate user feedback (e.g., form submission errors, download failures, missing data warnings), use an [info prompt](https://storybook.wise.design/?path=/docs/prompts-infoprompt--docs) instead.
|
|
41
|
+
*
|
|
42
|
+
* Guidance can be found in the [design system](https://wise.design/components/action-prompt).
|
|
43
|
+
*/
|
|
37
44
|
export const ActionPrompt = ({
|
|
38
45
|
sentiment = 'neutral',
|
|
39
46
|
title,
|
|
@@ -55,7 +62,7 @@ export const ActionPrompt = ({
|
|
|
55
62
|
|
|
56
63
|
const ariaLabelledByIds = [
|
|
57
64
|
media['aria-hidden'] ? undefined : mediaId,
|
|
58
|
-
|
|
65
|
+
ariaLabel ? undefined : titleId,
|
|
59
66
|
]
|
|
60
67
|
.filter(Boolean)
|
|
61
68
|
.join(' ');
|
|
@@ -104,7 +111,7 @@ export const ActionPrompt = ({
|
|
|
104
111
|
id={mediaId}
|
|
105
112
|
size={48}
|
|
106
113
|
sentiment={sentiment}
|
|
107
|
-
iconLabel={
|
|
114
|
+
iconLabel={media['aria-hidden'] ? null : media['aria-label']}
|
|
108
115
|
/>
|
|
109
116
|
);
|
|
110
117
|
};
|
|
@@ -148,9 +155,9 @@ export const ActionPrompt = ({
|
|
|
148
155
|
</Button>
|
|
149
156
|
</>
|
|
150
157
|
}
|
|
151
|
-
onDismiss={onDismiss}
|
|
152
158
|
role="region"
|
|
153
|
-
{
|
|
159
|
+
onDismiss={onDismiss}
|
|
160
|
+
{...(ariaLabel
|
|
154
161
|
? { 'aria-label': ariaLabel }
|
|
155
162
|
: {
|
|
156
163
|
'aria-labelledby': ariaLabelledByIds,
|
|
@@ -158,10 +165,14 @@ export const ActionPrompt = ({
|
|
|
158
165
|
})}
|
|
159
166
|
>
|
|
160
167
|
<div className={clsx('d-flex', 'flex-column', 'justify-content-center')}>
|
|
161
|
-
<Body id={titleId} type={Typography.BODY_LARGE_BOLD}>
|
|
168
|
+
<Body id={titleId} type={Typography.BODY_LARGE_BOLD} className="wds-action-prompt__content">
|
|
162
169
|
{title}
|
|
163
170
|
</Body>
|
|
164
|
-
{description &&
|
|
171
|
+
{description && (
|
|
172
|
+
<Body id={descId} className="wds-action-prompt__content">
|
|
173
|
+
{description}
|
|
174
|
+
</Body>
|
|
175
|
+
)}
|
|
165
176
|
</div>
|
|
166
177
|
</PrimitivePrompt>
|
|
167
178
|
);
|