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

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 (46) hide show
  1. package/build/main.css +5 -11
  2. package/build/prompt/ActionPrompt/ActionPrompt.js +0 -2
  3. package/build/prompt/ActionPrompt/ActionPrompt.js.map +1 -1
  4. package/build/prompt/ActionPrompt/ActionPrompt.mjs +0 -2
  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/prompt/InlinePrompt/InlinePrompt.js +1 -1
  9. package/build/prompt/InlinePrompt/InlinePrompt.js.map +1 -1
  10. package/build/prompt/InlinePrompt/InlinePrompt.mjs +1 -1
  11. package/build/prompt/InlinePrompt/InlinePrompt.mjs.map +1 -1
  12. package/build/styles/main.css +5 -11
  13. package/build/styles/prompt/ActionPrompt/ActionPrompt.css +0 -4
  14. package/build/styles/prompt/InfoPrompt/InfoPrompt.css +3 -3
  15. package/build/styles/prompt/InlinePrompt/InlinePrompt.css +2 -3
  16. package/build/styles/prompt/PrimitivePrompt/PrimitivePrompt.css +0 -1
  17. package/build/types/prompt/ActionPrompt/ActionPrompt.d.ts +7 -0
  18. package/build/types/prompt/ActionPrompt/ActionPrompt.d.ts.map +1 -1
  19. package/build/types/prompt/InfoPrompt/InfoPrompt.d.ts +4 -2
  20. package/build/types/prompt/InfoPrompt/InfoPrompt.d.ts.map +1 -1
  21. package/package.json +1 -1
  22. package/src/iconButton/IconButton.story.tsx +173 -48
  23. package/src/iconButton/IconButton.test.story.tsx +194 -0
  24. package/src/listItem/_stories/ListItem.story.tsx +0 -1
  25. package/src/main.css +5 -11
  26. package/src/prompt/ActionPrompt/ActionPrompt.accessibility.docs.mdx +1 -17
  27. package/src/prompt/ActionPrompt/ActionPrompt.css +0 -4
  28. package/src/prompt/ActionPrompt/ActionPrompt.less +1 -5
  29. package/src/prompt/ActionPrompt/ActionPrompt.story.tsx +290 -119
  30. package/src/prompt/ActionPrompt/ActionPrompt.test.story.tsx +86 -3
  31. package/src/prompt/ActionPrompt/ActionPrompt.tsx +9 -6
  32. package/src/prompt/InfoPrompt/InfoPrompt.accessibility.docs.mdx +79 -0
  33. package/src/prompt/InfoPrompt/InfoPrompt.css +3 -3
  34. package/src/prompt/InfoPrompt/InfoPrompt.less +3 -4
  35. package/src/prompt/InfoPrompt/InfoPrompt.story.tsx +93 -109
  36. package/src/prompt/InfoPrompt/InfoPrompt.test.story.tsx +54 -1
  37. package/src/prompt/InfoPrompt/InfoPrompt.tsx +4 -2
  38. package/src/prompt/InlinePrompt/InlinePrompt.accessibility.docs.mdx +63 -0
  39. package/src/prompt/InlinePrompt/InlinePrompt.css +2 -3
  40. package/src/prompt/InlinePrompt/InlinePrompt.less +2 -2
  41. package/src/prompt/InlinePrompt/InlinePrompt.story.tsx +25 -30
  42. package/src/prompt/InlinePrompt/InlinePrompt.test.story.tsx +21 -0
  43. package/src/prompt/InlinePrompt/InlinePrompt.test.tsx +3 -10
  44. package/src/prompt/InlinePrompt/InlinePrompt.tsx +1 -1
  45. package/src/prompt/PrimitivePrompt/PrimitivePrompt.css +0 -1
  46. package/src/prompt/PrimitivePrompt/PrimitivePrompt.less +1 -2
@@ -1,20 +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 } from '@transferwise/icons';
5
- import { ActionPrompt } from './ActionPrompt';
6
- import { lorem10, lorem20 } from '../../test-utils';
7
-
8
- import { withVariantConfig } from '../../../.storybook/helpers';
9
-
10
- const meta: Meta<typeof ActionPrompt> = {
11
- title: 'Prompts/ActionPrompt',
12
- component: ActionPrompt,
13
- tags: ['new'],
14
- };
15
-
16
- export default meta;
17
- type Story = StoryObj<typeof ActionPrompt>;
4
+ import { Bank, Star, Travel, Briefcase } from '@transferwise/icons';
5
+ import { ActionPrompt, type ActionPromptProps } from './ActionPrompt';
6
+ import { lorem10 } from '../../test-utils';
7
+ import Button from '../../button';
8
+ import Title from '../../title';
18
9
 
19
10
  const withComponentGrid =
20
11
  ({ maxWidth = 'auto', gap = '1rem' } = {}) =>
@@ -35,149 +26,309 @@ const withComponentGrid =
35
26
  </div>
36
27
  );
37
28
 
38
- export const AllSentiments: Story = {
39
- render: () => {
40
- return (
41
- <>
42
- {(['negative', 'warning', 'neutral', 'success', 'proposition'] as const).map(
43
- (sentiment) => (
44
- <ActionPrompt
45
- key={sentiment}
46
- sentiment={sentiment}
47
- title="Title"
48
- description={lorem10}
49
- action={{ label: 'Primary', onClick: fn() }}
50
- actionSecondary={{ label: 'Secondary', onClick: fn() }}
51
- className="m-b-2"
52
- onDismiss={fn()}
53
- />
54
- ),
55
- )}
56
- </>
57
- );
58
- },
29
+ const meta: Meta<typeof ActionPrompt> = {
30
+ title: 'Prompts/ActionPrompt',
31
+ component: ActionPrompt,
32
+ tags: ['new'],
59
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;
60
103
  };
61
104
 
62
- export const Mobile: Story = {
63
- ...AllSentiments,
64
- ...withVariantConfig(['mobile'], AllSentiments),
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' } },
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' } },
65
127
  };
66
128
 
67
- export const PrimaryActionOnly: Story = {
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
+ },
68
183
  args: {
69
- sentiment: 'proposition',
70
- title: 'Payment successful',
71
- description: 'Your money is on its way',
72
- action: { label: 'View details', onClick: fn() },
73
- onDismiss: fn(),
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} />;
74
191
  },
75
192
  };
76
193
 
77
- export const WithoutDescription: Story = {
78
- render: () => {
79
- return (
80
- <>
81
- <ActionPrompt
82
- sentiment="warning"
83
- title="Session expiring soon"
84
- action={{ label: 'Stay logged in', onClick: fn() }}
85
- actionSecondary={{ label: 'Log out', onClick: fn() }}
86
- onDismiss={fn()}
87
- />
88
- <ActionPrompt
89
- sentiment="success"
90
- title="Session expiring soon"
91
- action={{ label: 'Stay logged in', onClick: fn() }}
92
- />
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) => (
93
206
  <ActionPrompt
94
- title="Session expiring soon"
95
- 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() }}
96
213
  />
97
- </>
98
- );
99
- },
100
- decorators: [withComponentGrid()],
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
+ ),
101
240
  };
102
241
 
103
- export const Media: Story = {
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 = {
104
251
  render: () => {
105
252
  return (
106
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>
107
263
  <ActionPrompt
108
264
  sentiment="neutral"
109
265
  title="Custom image"
110
266
  description={lorem10}
111
267
  media={{ imgSrc: '../../wise-card.svg' }}
112
- action={{ label: 'Invite now', onClick: fn() }}
268
+ action={{ label: 'View card', onClick: fn() }}
113
269
  />
114
270
  <ActionPrompt
115
271
  sentiment="neutral"
116
- title="Profile Image"
272
+ title="Avatar with image"
117
273
  description={lorem10}
118
274
  media={{ avatar: { imgSrc: '../../avatar-rectangle-fox.webp' } }}
119
- action={{ label: 'Invite now', onClick: fn() }}
275
+ action={{ label: 'View profile', onClick: fn() }}
120
276
  />
121
277
  <ActionPrompt
122
- sentiment="proposition"
123
- title="Business Profile"
278
+ sentiment="success"
279
+ title="Avatar with initials"
124
280
  description={lorem10}
125
- media={{ avatar: { profileType: 'BUSINESS', badge: { flagCode: 'GB' } } }}
126
- action={{ label: 'Invite now', onClick: fn() }}
281
+ media={{ avatar: { profileName: 'John Doe' } }}
282
+ action={{ label: 'Send invite', onClick: fn() }}
127
283
  />
128
284
  <ActionPrompt
129
- sentiment="negative"
130
- title="Personal Profile"
285
+ sentiment="warning"
286
+ title="Avatar with custom icon"
131
287
  description={lorem10}
132
- media={{ avatar: { profileType: 'PERSONAL', badge: { flagCode: 'EU' } } }}
133
- action={{ label: 'Invite now', onClick: fn() }}
288
+ media={{ avatar: { asset: <Bank /> } }}
289
+ action={{ label: 'Connect bank', onClick: fn() }}
134
290
  />
135
291
  <ActionPrompt
136
- sentiment="warning"
137
- title="Custom Icon"
292
+ sentiment="proposition"
293
+ title="Business profile with badge"
138
294
  description={lorem10}
139
- media={{ avatar: { asset: <Bank /> } }}
140
- action={{ label: 'Invite now', onClick: fn() }}
295
+ media={{ avatar: { profileType: 'BUSINESS', badge: { flagCode: 'GB' } } }}
296
+ action={{ label: 'Open account', onClick: fn() }}
141
297
  />
142
298
  <ActionPrompt
143
- sentiment="success"
144
- title="Initials"
299
+ sentiment="negative"
300
+ title="Personal profile with badge"
145
301
  description={lorem10}
146
- media={{ avatar: { profileName: 'John Doe' } }}
147
- action={{ label: 'Invite now', onClick: fn() }}
148
- actionSecondary={{ label: 'Maybe later', onClick: fn() }}
302
+ media={{ avatar: { profileType: 'PERSONAL', badge: { flagCode: 'EU' } } }}
303
+ action={{ label: 'Verify', onClick: fn() }}
149
304
  />
150
305
  </>
151
306
  );
152
307
  },
153
- decorators: [withComponentGrid()],
154
308
  };
155
309
 
156
- export const OnDismiss: Story = {
157
- render: () => {
158
- const [showNeutralPrompt, setShowNeutralPrompt] = useState(true);
159
- const [showSuccessPrompt, setShowSuccessPrompt] = useState(true);
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);
160
317
 
161
318
  return (
162
319
  <>
163
- {showNeutralPrompt && (
320
+ {showPrompt ? (
164
321
  <ActionPrompt
165
322
  sentiment="proposition"
166
- title="Payment successful"
167
- description="Your money is on its way"
168
- action={{ label: 'View details', onClick: fn() }}
169
- className="m-b-2"
170
- onDismiss={() => setShowNeutralPrompt(false)}
171
- />
172
- )}
173
- {showSuccessPrompt && (
174
- <ActionPrompt
175
- sentiment="success"
176
- title="Payment successful"
177
- description="Your money is on its way"
178
- action={{ label: 'View details', onClick: fn() }}
179
- 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)}
180
327
  />
328
+ ) : (
329
+ <Button v2 onClick={() => setShowPrompt(true)}>
330
+ Show prompt again
331
+ </Button>
181
332
  )}
182
333
  </>
183
334
  );
@@ -185,12 +336,39 @@ export const OnDismiss: Story = {
185
336
  };
186
337
 
187
338
  /**
188
- * While the component itself will stretch to the full available width, the text container will be
189
- * capped at `480px` to ensure optimal readability.
190
- *
191
- * [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.
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.
192
341
  */
193
- export const ParagraphWidth: Story = {
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
+ ),
194
372
  parameters: {
195
373
  docs: {
196
374
  canvas: {
@@ -198,11 +376,4 @@ export const ParagraphWidth: Story = {
198
376
  },
199
377
  },
200
378
  },
201
- args: {
202
- title: lorem10,
203
- description: lorem20,
204
- sentiment: 'success',
205
- action: { label: 'View details', onClick: fn() },
206
- onDismiss: () => {},
207
- },
208
379
  };
@@ -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 { Meta, StoryObj } from '@storybook/react-webpack5';
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’s simple, secure and you pick who you add."
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,
@@ -158,14 +165,10 @@ 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} className="wds-action-prompt__content">
168
+ <Body id={titleId} type={Typography.BODY_LARGE_BOLD}>
162
169
  {title}
163
170
  </Body>
164
- {description && (
165
- <Body id={descId} className="wds-action-prompt__content">
166
- {description}
167
- </Body>
168
- )}
171
+ {description && <Body id={descId}>{description}</Body>}
169
172
  </div>
170
173
  </PrimitivePrompt>
171
174
  );