@transferwise/components 0.0.0-experimental-276a15d → 0.0.0-experimental-dd4f4a5

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.
Files changed (34) hide show
  1. package/build/main.css +1 -3
  2. package/build/prompt/ActionPrompt/ActionPrompt.js +4 -4
  3. package/build/prompt/ActionPrompt/ActionPrompt.js.map +1 -1
  4. package/build/prompt/ActionPrompt/ActionPrompt.mjs +4 -4
  5. package/build/prompt/ActionPrompt/ActionPrompt.mjs.map +1 -1
  6. package/build/prompt/InfoPrompt/InfoPrompt.js.map +1 -1
  7. package/build/prompt/InfoPrompt/InfoPrompt.mjs.map +1 -1
  8. package/build/styles/main.css +1 -3
  9. package/build/styles/prompt/InfoPrompt/InfoPrompt.css +1 -3
  10. package/build/types/prompt/ActionPrompt/ActionPrompt.d.ts +0 -7
  11. package/build/types/prompt/ActionPrompt/ActionPrompt.d.ts.map +1 -1
  12. package/build/types/prompt/InfoPrompt/InfoPrompt.d.ts +2 -4
  13. package/build/types/prompt/InfoPrompt/InfoPrompt.d.ts.map +1 -1
  14. package/package.json +1 -1
  15. package/src/iconButton/IconButton.story.tsx +48 -173
  16. package/src/listItem/_stories/ListItem.story.tsx +1 -0
  17. package/src/main.css +1 -3
  18. package/src/prompt/ActionPrompt/ActionPrompt.accessibility.docs.mdx +17 -1
  19. package/src/prompt/ActionPrompt/ActionPrompt.story.tsx +107 -299
  20. package/src/prompt/ActionPrompt/ActionPrompt.test.story.tsx +3 -86
  21. package/src/prompt/ActionPrompt/ActionPrompt.tsx +4 -11
  22. package/src/prompt/InfoPrompt/InfoPrompt.css +1 -3
  23. package/src/prompt/InfoPrompt/InfoPrompt.less +4 -4
  24. package/src/prompt/InfoPrompt/InfoPrompt.story.tsx +86 -93
  25. package/src/prompt/InfoPrompt/InfoPrompt.test.story.tsx +1 -54
  26. package/src/prompt/InfoPrompt/InfoPrompt.tsx +2 -4
  27. package/src/prompt/InlinePrompt/InlinePrompt.story.tsx +29 -24
  28. package/src/sentimentSurface/SentimentSurface.docs.mdx +1 -1
  29. package/src/sentimentSurface/SentimentSurface.story.tsx +1 -1
  30. package/src/sentimentSurface/SentimentSurface.test.story.tsx +1 -1
  31. package/src/iconButton/IconButton.test.story.tsx +0 -194
  32. package/src/prompt/InfoPrompt/InfoPrompt.accessibility.docs.mdx +0 -79
  33. package/src/prompt/InlinePrompt/InlinePrompt.accessibility.docs.mdx +0 -63
  34. package/src/prompt/InlinePrompt/InlinePrompt.test.story.tsx +0 -21
package/src/main.css CHANGED
@@ -5443,7 +5443,7 @@ html:not([dir="rtl"]) .np-navigation-option {
5443
5443
  }
5444
5444
  .wds-info-prompt {
5445
5445
  --Prompt-gap: var(--size-8);
5446
- --Prompt-padding: var(--size-12);
5446
+ --Prompt-padding: 12px;
5447
5447
  }
5448
5448
  .wds-info-prompt__content {
5449
5449
  display: flex;
@@ -5467,9 +5467,7 @@ html:not([dir="rtl"]) .np-navigation-option {
5467
5467
  }
5468
5468
  .wds-info-prompt__media svg {
5469
5469
  width: 24px;
5470
- width: var(--size-24);
5471
5470
  height: 24px;
5472
- height: var(--size-24);
5473
5471
  }
5474
5472
  .wds-info-prompt .wds-prompt__media-wrapper {
5475
5473
  padding: 0;
@@ -31,7 +31,23 @@ If you want to provide a custom label for screen readers, you can use the `aria-
31
31
 
32
32
  ## Media
33
33
 
34
- You can use `aria-label` on media assets to provide a custom label for screen readers.
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.
35
51
 
36
52
  <Source
37
53
  dark
@@ -1,11 +1,22 @@
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, Star, Travel, Briefcase } from '@transferwise/icons';
5
- import { ActionPrompt, type ActionPromptProps } from './ActionPrompt';
4
+ import { Bank, Freeze, People } from '@transferwise/icons';
5
+ import { ActionPrompt } from './ActionPrompt';
6
6
  import { lorem10 } from '../../test-utils';
7
- import Button from '../../button';
7
+ import Body from '../../body';
8
+ import { action } from 'storybook/actions';
8
9
  import Title from '../../title';
10
+ import { withVariantConfig } from '../../../.storybook/helpers';
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>;
9
20
 
10
21
  const withComponentGrid =
11
22
  ({ maxWidth = 'auto', gap = '1rem' } = {}) =>
@@ -26,354 +37,151 @@ const withComponentGrid =
26
37
  </div>
27
38
  );
28
39
 
29
- const meta: Meta<typeof ActionPrompt> = {
30
- title: 'Prompts/ActionPrompt',
31
- component: ActionPrompt,
32
- tags: ['new'],
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;
103
- };
104
-
105
- const previewArgGroup = {
106
- category: 'Storybook Preview options',
107
- type: {
108
- summary: undefined,
109
- },
110
- };
111
-
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' } },
40
+ export const AllSentiments: Story = {
41
+ render: () => {
42
+ return (
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
+ );
121
60
  },
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' } },
61
+ decorators: [withComponentGrid()],
127
62
  };
128
63
 
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
- ];
64
+ export const Mobile: Story = {
65
+ ...AllSentiments,
66
+ ...withVariantConfig(['mobile'], AllSentiments),
173
67
  };
174
68
 
175
- export const Playground: StoryObj<PreviewStoryArgs> = {
176
- argTypes: {
177
- sentiment: {
178
- control: 'radio',
179
- options: ['success', 'negative', 'neutral', 'warning', 'proposition'],
180
- },
181
- ...previewArgTypes,
182
- },
69
+ export const PrimaryActionOnly: Story = {
183
70
  args: {
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} />;
71
+ sentiment: 'proposition',
72
+ title: 'Payment successful',
73
+ description: 'Your money is on its way',
74
+ action: { label: 'View details', onClick: fn() },
75
+ onDismiss: fn(),
191
76
  },
192
77
  };
193
78
 
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) => (
206
- <ActionPrompt
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() }}
213
- />
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
- ),
240
- };
241
-
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 = {
79
+ export const WithoutDescription: Story = {
251
80
  render: () => {
252
81
  return (
253
82
  <>
254
- <Title type="title-body">Default</Title>
83
+ <ActionPrompt
84
+ sentiment="warning"
85
+ title="Session expiring soon"
86
+ action={{ label: 'Stay logged in', onClick: fn() }}
87
+ actionSecondary={{ label: 'Log out', onClick: fn() }}
88
+ onDismiss={fn()}
89
+ />
255
90
  <ActionPrompt
256
91
  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() }}
92
+ title="Session expiring soon"
93
+ action={{ label: 'Stay logged in', onClick: fn() }}
94
+ />
95
+ <ActionPrompt
96
+ title="Session expiring soon"
97
+ action={{ label: 'Stay logged in', onClick: fn() }}
260
98
  />
99
+ </>
100
+ );
101
+ },
102
+ decorators: [withComponentGrid()],
103
+ };
261
104
 
262
- <Title type="title-body">Icon Overrides</Title>
105
+ export const Media: Story = {
106
+ render: () => {
107
+ return (
108
+ <>
263
109
  <ActionPrompt
264
110
  sentiment="neutral"
265
111
  title="Custom image"
266
112
  description={lorem10}
267
113
  media={{ imgSrc: '../../wise-card.svg' }}
268
- action={{ label: 'View card', onClick: fn() }}
114
+ action={{ label: 'Invite now', onClick: fn() }}
269
115
  />
270
116
  <ActionPrompt
271
117
  sentiment="neutral"
272
- title="Avatar with image"
118
+ title="Profile Image"
273
119
  description={lorem10}
274
120
  media={{ avatar: { imgSrc: '../../avatar-rectangle-fox.webp' } }}
275
- action={{ label: 'View profile', onClick: fn() }}
121
+ action={{ label: 'Invite now', onClick: fn() }}
276
122
  />
277
123
  <ActionPrompt
278
- sentiment="success"
279
- title="Avatar with initials"
124
+ sentiment="proposition"
125
+ title="Business Profile"
280
126
  description={lorem10}
281
- media={{ avatar: { profileName: 'John Doe' } }}
282
- action={{ label: 'Send invite', onClick: fn() }}
127
+ media={{ avatar: { profileType: 'BUSINESS', badge: { flagCode: 'GB' } } }}
128
+ action={{ label: 'Invite now', onClick: fn() }}
283
129
  />
284
130
  <ActionPrompt
285
- sentiment="warning"
286
- title="Avatar with custom icon"
131
+ sentiment="negative"
132
+ title="Personal Profile"
287
133
  description={lorem10}
288
- media={{ avatar: { asset: <Bank /> } }}
289
- action={{ label: 'Connect bank', onClick: fn() }}
134
+ media={{ avatar: { profileType: 'PERSONAL', badge: { flagCode: 'EU' } } }}
135
+ action={{ label: 'Invite now', onClick: fn() }}
290
136
  />
291
137
  <ActionPrompt
292
- sentiment="proposition"
293
- title="Business profile with badge"
138
+ sentiment="warning"
139
+ title="Custom Icon"
294
140
  description={lorem10}
295
- media={{ avatar: { profileType: 'BUSINESS', badge: { flagCode: 'GB' } } }}
296
- action={{ label: 'Open account', onClick: fn() }}
141
+ media={{ avatar: { asset: <Bank /> } }}
142
+ action={{ label: 'Invite now', onClick: fn() }}
297
143
  />
298
144
  <ActionPrompt
299
- sentiment="negative"
300
- title="Personal profile with badge"
145
+ sentiment="success"
146
+ title="Initials"
301
147
  description={lorem10}
302
- media={{ avatar: { profileType: 'PERSONAL', badge: { flagCode: 'EU' } } }}
303
- action={{ label: 'Verify', onClick: fn() }}
148
+ media={{ avatar: { profileName: 'John Doe' } }}
149
+ action={{ label: 'Invite now', onClick: fn() }}
150
+ actionSecondary={{ label: 'Maybe later', onClick: fn() }}
304
151
  />
305
152
  </>
306
153
  );
307
154
  },
155
+ decorators: [withComponentGrid()],
308
156
  };
309
157
 
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);
158
+ export const OnDismiss: Story = {
159
+ render: () => {
160
+ const [showNeutralPrompt, setShowNeutralPrompt] = useState(true);
161
+ const [showSuccessPrompt, setShowSuccessPrompt] = useState(true);
317
162
 
318
163
  return (
319
164
  <>
320
- {showPrompt ? (
165
+ {showNeutralPrompt && (
321
166
  <ActionPrompt
322
167
  sentiment="proposition"
323
- title="Special offer"
324
- description="Get 50% off your next transfer!"
325
- action={{ label: 'Claim now', onClick: fn() }}
326
- onDismiss={() => setShowPrompt(false)}
168
+ title="Payment successful"
169
+ description="Your money is on its way"
170
+ action={{ label: 'View details', onClick: fn() }}
171
+ className="m-b-2"
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)}
327
182
  />
328
- ) : (
329
- <Button v2 onClick={() => setShowPrompt(true)}>
330
- Show prompt again
331
- </Button>
332
183
  )}
333
184
  </>
334
185
  );
335
186
  },
336
187
  };
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
- };
@@ -1,25 +1,13 @@
1
- import { ReactElement } from 'react';
2
1
  import { Freeze, People } from '@transferwise/icons';
3
2
  import { action } from 'storybook/actions';
4
- import { fn } from 'storybook/test';
5
3
  import ActionPrompt from './ActionPrompt';
6
4
  import { Body, Title } from '../..';
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
- );
5
+ import { Meta, StoryObj } from '@storybook/react-webpack5';
18
6
 
19
7
  export default {
20
8
  title: 'Prompts/ActionPrompt/Tests',
21
9
  component: ActionPrompt,
22
- tags: ['!manifest', '!autodocs', 'new'],
10
+ tags: ['!manifest', '!autodocs'],
23
11
  };
24
12
 
25
13
  type Story = StoryObj<typeof ActionPrompt>;
@@ -142,7 +130,7 @@ export const VariousA11yFeatures: Story = {
142
130
  sentiment="negative"
143
131
  media={{ avatar: { asset: <People /> } }}
144
132
  title="Sync contacts for a faster experience"
145
- description="Find contacts on Wise — it's simple, secure and you pick who you add."
133
+ description="Find contacts on Wise — it’s simple, secure and you pick who you add."
146
134
  action={{
147
135
  label: 'Sync contacts',
148
136
  onClick: () => {
@@ -157,74 +145,3 @@ export const VariousA11yFeatures: Story = {
157
145
  );
158
146
  },
159
147
  };
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,13 +34,6 @@ 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
- */
44
37
  export const ActionPrompt = ({
45
38
  sentiment = 'neutral',
46
39
  title,
@@ -62,7 +55,7 @@ export const ActionPrompt = ({
62
55
 
63
56
  const ariaLabelledByIds = [
64
57
  media['aria-hidden'] ? undefined : mediaId,
65
- ariaLabel ? undefined : titleId,
58
+ Boolean(ariaLabel) ? undefined : titleId,
66
59
  ]
67
60
  .filter(Boolean)
68
61
  .join(' ');
@@ -111,7 +104,7 @@ export const ActionPrompt = ({
111
104
  id={mediaId}
112
105
  size={48}
113
106
  sentiment={sentiment}
114
- iconLabel={media['aria-hidden'] ? null : media['aria-label']}
107
+ iconLabel={Boolean(media['aria-hidden']) ? null : media['aria-label']}
115
108
  />
116
109
  );
117
110
  };
@@ -155,9 +148,9 @@ export const ActionPrompt = ({
155
148
  </Button>
156
149
  </>
157
150
  }
158
- role="region"
159
151
  onDismiss={onDismiss}
160
- {...(ariaLabel
152
+ role="region"
153
+ {...(Boolean(ariaLabel)
161
154
  ? { 'aria-label': ariaLabel }
162
155
  : {
163
156
  'aria-labelledby': ariaLabelledByIds,