@transferwise/components 46.125.0 → 46.126.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (141) hide show
  1. package/build/avatarView/AvatarView.js.map +1 -1
  2. package/build/avatarView/AvatarView.mjs.map +1 -1
  3. package/build/common/locale/index.js +13 -0
  4. package/build/common/locale/index.js.map +1 -1
  5. package/build/common/locale/index.mjs +13 -1
  6. package/build/common/locale/index.mjs.map +1 -1
  7. package/build/expressiveMoneyInput/currencySelector/CurrencySelector.js +31 -1
  8. package/build/expressiveMoneyInput/currencySelector/CurrencySelector.js.map +1 -1
  9. package/build/expressiveMoneyInput/currencySelector/CurrencySelector.mjs +32 -2
  10. package/build/expressiveMoneyInput/currencySelector/CurrencySelector.mjs.map +1 -1
  11. package/build/field/Field.js +1 -0
  12. package/build/field/Field.js.map +1 -1
  13. package/build/field/Field.mjs +1 -0
  14. package/build/field/Field.mjs.map +1 -1
  15. package/build/index.js +3 -0
  16. package/build/index.js.map +1 -1
  17. package/build/index.mjs +2 -1
  18. package/build/index.mjs.map +1 -1
  19. package/build/inputs/Input.js.map +1 -1
  20. package/build/inputs/Input.mjs.map +1 -1
  21. package/build/inputs/SearchInput.js.map +1 -1
  22. package/build/inputs/SearchInput.mjs.map +1 -1
  23. package/build/inputs/SelectInput.js.map +1 -1
  24. package/build/inputs/SelectInput.mjs.map +1 -1
  25. package/build/inputs/TextArea.js.map +1 -1
  26. package/build/inputs/TextArea.mjs.map +1 -1
  27. package/build/listItem/ListItem.js +2 -2
  28. package/build/listItem/ListItem.js.map +1 -1
  29. package/build/listItem/ListItem.mjs +2 -2
  30. package/build/listItem/ListItem.mjs.map +1 -1
  31. package/build/listItem/Prompt/ListItemPrompt.js +1 -0
  32. package/build/listItem/Prompt/ListItemPrompt.js.map +1 -1
  33. package/build/listItem/Prompt/ListItemPrompt.mjs +1 -0
  34. package/build/listItem/Prompt/ListItemPrompt.mjs.map +1 -1
  35. package/build/main.css +31 -0
  36. package/build/moneyInput/MoneyInput.js +6 -1
  37. package/build/moneyInput/MoneyInput.js.map +1 -1
  38. package/build/moneyInput/MoneyInput.mjs +6 -1
  39. package/build/moneyInput/MoneyInput.mjs.map +1 -1
  40. package/build/prompt/ActionPrompt/ActionPrompt.js +27 -4
  41. package/build/prompt/ActionPrompt/ActionPrompt.js.map +1 -1
  42. package/build/prompt/ActionPrompt/ActionPrompt.mjs +27 -4
  43. package/build/prompt/ActionPrompt/ActionPrompt.mjs.map +1 -1
  44. package/build/prompt/InfoPrompt/InfoPrompt.js +113 -0
  45. package/build/prompt/InfoPrompt/InfoPrompt.js.map +1 -0
  46. package/build/prompt/InfoPrompt/InfoPrompt.mjs +111 -0
  47. package/build/prompt/InfoPrompt/InfoPrompt.mjs.map +1 -0
  48. package/build/prompt/PrimitivePrompt/PrimitivePrompt.js.map +1 -1
  49. package/build/prompt/PrimitivePrompt/PrimitivePrompt.mjs.map +1 -1
  50. package/build/radioOption/RadioOption.js.map +1 -1
  51. package/build/radioOption/RadioOption.mjs.map +1 -1
  52. package/build/slidingPanel/SlidingPanel.js.map +1 -1
  53. package/build/slidingPanel/SlidingPanel.mjs.map +1 -1
  54. package/build/statusIcon/StatusIcon.js +2 -0
  55. package/build/statusIcon/StatusIcon.js.map +1 -1
  56. package/build/statusIcon/StatusIcon.mjs +2 -0
  57. package/build/statusIcon/StatusIcon.mjs.map +1 -1
  58. package/build/styles/main.css +31 -0
  59. package/build/styles/prompt/InfoPrompt/InfoPrompt.css +31 -0
  60. package/build/table/TableCell.js.map +1 -1
  61. package/build/table/TableCell.mjs.map +1 -1
  62. package/build/typeahead/Typeahead.js +1 -0
  63. package/build/typeahead/Typeahead.js.map +1 -1
  64. package/build/typeahead/Typeahead.mjs +1 -0
  65. package/build/typeahead/Typeahead.mjs.map +1 -1
  66. package/build/types/avatarView/AvatarView.d.ts +1 -1
  67. package/build/types/avatarView/AvatarView.d.ts.map +1 -1
  68. package/build/types/common/locale/index.d.ts +8 -0
  69. package/build/types/common/locale/index.d.ts.map +1 -1
  70. package/build/types/expressiveMoneyInput/currencySelector/CurrencySelector.d.ts.map +1 -1
  71. package/build/types/index.d.ts +3 -2
  72. package/build/types/index.d.ts.map +1 -1
  73. package/build/types/inputs/Input.d.ts.map +1 -1
  74. package/build/types/inputs/SearchInput.d.ts.map +1 -1
  75. package/build/types/inputs/SelectInput.d.ts +1 -1
  76. package/build/types/inputs/SelectInput.d.ts.map +1 -1
  77. package/build/types/inputs/TextArea.d.ts.map +1 -1
  78. package/build/types/moneyInput/MoneyInput.d.ts.map +1 -1
  79. package/build/types/primitives/PrimitiveAnchor/PrimitiveAnchor.types.d.ts.map +1 -1
  80. package/build/types/primitives/PrimitiveButton/PrimitiveButton.types.d.ts.map +1 -1
  81. package/build/types/prompt/ActionPrompt/ActionPrompt.d.ts +4 -2
  82. package/build/types/prompt/ActionPrompt/ActionPrompt.d.ts.map +1 -1
  83. package/build/types/prompt/InfoPrompt/InfoPrompt.d.ts +56 -0
  84. package/build/types/prompt/InfoPrompt/InfoPrompt.d.ts.map +1 -0
  85. package/build/types/prompt/InfoPrompt/index.d.ts +3 -0
  86. package/build/types/prompt/InfoPrompt/index.d.ts.map +1 -0
  87. package/build/types/prompt/PrimitivePrompt/PrimitivePrompt.d.ts +5 -5
  88. package/build/types/prompt/PrimitivePrompt/PrimitivePrompt.d.ts.map +1 -1
  89. package/build/types/prompt/index.d.ts +2 -0
  90. package/build/types/prompt/index.d.ts.map +1 -1
  91. package/build/types/radioOption/RadioOption.d.ts.map +1 -1
  92. package/build/types/slidingPanel/SlidingPanel.d.ts.map +1 -1
  93. package/build/types/statusIcon/StatusIcon.d.ts +2 -1
  94. package/build/types/statusIcon/StatusIcon.d.ts.map +1 -1
  95. package/build/types/table/TableCell.d.ts.map +1 -1
  96. package/build/types/withDisplayFormat/WithDisplayFormat.d.ts.map +1 -1
  97. package/build/withDisplayFormat/WithDisplayFormat.js.map +1 -1
  98. package/build/withDisplayFormat/WithDisplayFormat.mjs.map +1 -1
  99. package/package.json +2 -2
  100. package/src/avatarLayout/AvatarLayout.story.tsx +3 -3
  101. package/src/avatarView/AvatarView.story.tsx +29 -24
  102. package/src/avatarView/AvatarView.tsx +1 -1
  103. package/src/common/bottomSheet/BottomSheet.test.story.tsx +98 -0
  104. package/src/common/locale/index.test.ts +36 -1
  105. package/src/common/locale/index.ts +13 -0
  106. package/src/expressiveMoneyInput/currencySelector/CurrencySelector.tsx +5 -1
  107. package/src/index.ts +3 -1
  108. package/src/inputs/Input.tsx +8 -9
  109. package/src/inputs/SearchInput.tsx +8 -9
  110. package/src/inputs/SelectInput.test.story.tsx +86 -0
  111. package/src/inputs/SelectInput.tsx +1 -1
  112. package/src/inputs/TextArea.tsx +6 -7
  113. package/src/listItem/ListItem.tsx +2 -2
  114. package/src/main.css +31 -0
  115. package/src/main.less +2 -1
  116. package/src/moneyInput/MoneyInput.test.story.tsx +104 -0
  117. package/src/moneyInput/MoneyInput.tsx +20 -2
  118. package/src/primitives/PrimitiveAnchor/PrimitiveAnchor.types.ts +1 -3
  119. package/src/primitives/PrimitiveButton/PrimitiveButton.types.ts +1 -3
  120. package/src/prompt/ActionPrompt/ActionPrompt.accessibility.docs.mdx +65 -0
  121. package/src/prompt/ActionPrompt/ActionPrompt.less +1 -1
  122. package/src/prompt/ActionPrompt/ActionPrompt.story.tsx +4 -1
  123. package/src/prompt/ActionPrompt/ActionPrompt.test.story.tsx +147 -0
  124. package/src/prompt/ActionPrompt/ActionPrompt.test.tsx +2 -7
  125. package/src/prompt/ActionPrompt/ActionPrompt.tsx +48 -7
  126. package/src/prompt/InfoPrompt/InfoPrompt.css +31 -0
  127. package/src/prompt/InfoPrompt/InfoPrompt.less +37 -0
  128. package/src/prompt/InfoPrompt/InfoPrompt.story.tsx +312 -0
  129. package/src/prompt/InfoPrompt/InfoPrompt.test.story.tsx +246 -0
  130. package/src/prompt/InfoPrompt/InfoPrompt.test.tsx +224 -0
  131. package/src/prompt/InfoPrompt/InfoPrompt.tsx +148 -0
  132. package/src/prompt/InfoPrompt/index.ts +2 -0
  133. package/src/prompt/PrimitivePrompt/PrimitivePrompt.less +1 -1
  134. package/src/prompt/PrimitivePrompt/PrimitivePrompt.tsx +5 -5
  135. package/src/prompt/index.ts +5 -0
  136. package/src/radioOption/RadioOption.tsx +2 -1
  137. package/src/slidingPanel/SlidingPanel.tsx +4 -2
  138. package/src/ssr.test.tsx +2 -0
  139. package/src/statusIcon/StatusIcon.tsx +8 -1
  140. package/src/table/TableCell.tsx +1 -3
  141. package/src/withDisplayFormat/WithDisplayFormat.tsx +13 -14
@@ -1,4 +1,4 @@
1
- import { ReactNode } from 'react';
1
+ import { AriaAttributes, ReactNode, useId } from 'react';
2
2
  import { clsx } from 'clsx';
3
3
 
4
4
  import StatusIcon from '../../statusIcon';
@@ -23,6 +23,7 @@ export type ActionPromptProps = {
23
23
  badge?: Pick<BadgeAssetsProps, 'flagCode'>;
24
24
  };
25
25
  'aria-label'?: string;
26
+ 'aria-hidden'?: boolean;
26
27
  };
27
28
  action: Pick<ButtonProps, 'onClick' | 'href' | 'target'> & {
28
29
  label: ButtonProps['children'];
@@ -30,6 +31,7 @@ export type ActionPromptProps = {
30
31
  actionSecondary?: Pick<ButtonProps, 'onClick' | 'href' | 'target'> & {
31
32
  label: ButtonProps['children'];
32
33
  };
34
+ 'aria-label'?: AriaAttributes['aria-label'];
33
35
  } & Pick<PrimitivePromptProps, 'id' | 'className' | 'data-testid' | 'sentiment' | 'onDismiss'>;
34
36
 
35
37
  export const ActionPrompt = ({
@@ -37,19 +39,32 @@ export const ActionPrompt = ({
37
39
  title,
38
40
  description,
39
41
  onDismiss,
40
- media,
42
+ media = {},
41
43
  action,
42
44
  actionSecondary,
43
45
  id,
44
46
  className,
45
47
  'data-testid': testId,
48
+ 'aria-label': ariaLabel,
46
49
  }: ActionPromptProps) => {
47
50
  const isMobile = !useScreenSize(Breakpoint.MEDIUM);
48
51
 
52
+ const mediaId = useId();
53
+ const titleId = useId();
54
+ const descId = useId();
55
+
56
+ const ariaLabelledByIds = [
57
+ media['aria-hidden'] ? undefined : mediaId,
58
+ Boolean(ariaLabel) ? undefined : titleId,
59
+ ]
60
+ .filter(Boolean)
61
+ .join(' ');
62
+
49
63
  const renderMedia = () => {
50
64
  if (media?.imgSrc) {
51
65
  return (
52
66
  <Image
67
+ id={mediaId}
53
68
  src={media.imgSrc}
54
69
  className="wds-action-prompt--media-image"
55
70
  alt={media['aria-label'] ?? ''}
@@ -63,17 +78,34 @@ export const ActionPrompt = ({
63
78
  ? {}
64
79
  : { status: sentiment };
65
80
  return (
66
- <AvatarView {...media.avatar} badge={badge} size={48}>
81
+ <AvatarView
82
+ {...media.avatar}
83
+ badge={badge}
84
+ aria-label={media['aria-label']}
85
+ aria-hidden={media['aria-hidden']}
86
+ id={mediaId}
87
+ size={48}
88
+ >
67
89
  {media.avatar.asset}
68
90
  </AvatarView>
69
91
  );
70
92
  }
71
93
  return sentiment === 'proposition' ? (
72
- <AvatarView size={48}>
94
+ <AvatarView
95
+ id={mediaId}
96
+ size={48}
97
+ aria-label={media['aria-label']}
98
+ aria-hidden={media['aria-hidden']}
99
+ >
73
100
  <GiftBox />
74
101
  </AvatarView>
75
102
  ) : (
76
- <StatusIcon size={48} sentiment={sentiment} />
103
+ <StatusIcon
104
+ id={mediaId}
105
+ size={48}
106
+ sentiment={sentiment}
107
+ iconLabel={Boolean(media['aria-hidden']) ? null : media['aria-label']}
108
+ />
77
109
  );
78
110
  };
79
111
 
@@ -117,10 +149,19 @@ export const ActionPrompt = ({
117
149
  </>
118
150
  }
119
151
  onDismiss={onDismiss}
152
+ role="region"
153
+ {...(Boolean(ariaLabel)
154
+ ? { 'aria-label': ariaLabel }
155
+ : {
156
+ 'aria-labelledby': ariaLabelledByIds,
157
+ 'aria-describedby': descId,
158
+ })}
120
159
  >
121
160
  <div className={clsx('d-flex', 'flex-column', 'justify-content-center')}>
122
- <Body type={Typography.BODY_LARGE_BOLD}>{title}</Body>
123
- {description && <Body>{description}</Body>}
161
+ <Body id={titleId} type={Typography.BODY_LARGE_BOLD}>
162
+ {title}
163
+ </Body>
164
+ {description && <Body id={descId}>{description}</Body>}
124
165
  </div>
125
166
  </PrimitivePrompt>
126
167
  );
@@ -0,0 +1,31 @@
1
+ .wds-info-prompt {
2
+ --Prompt-gap: var(--size-8);
3
+ --Prompt-padding: 12px;
4
+ }
5
+ .wds-info-prompt__content {
6
+ display: flex;
7
+ flex-direction: column;
8
+ justify-content: center;
9
+ }
10
+ .wds-info-prompt__content:has(.wds-info-prompt__title) {
11
+ justify-content: flex-start;
12
+ /* Top align when title exists */
13
+ }
14
+ .wds-info-prompt__title,
15
+ .wds-info-prompt__description {
16
+ display: block;
17
+ color: var(--color-sentiment-primary);
18
+ }
19
+ .wds-info-prompt__action {
20
+ margin-top: var(--Prompt-gap);
21
+ }
22
+ .wds-info-prompt__media {
23
+ display: flex;
24
+ }
25
+ .wds-info-prompt__media svg {
26
+ width: 24px;
27
+ height: 24px;
28
+ }
29
+ .wds-info-prompt .wds-prompt__media-wrapper {
30
+ padding: 0;
31
+ }
@@ -0,0 +1,37 @@
1
+ .wds-info-prompt {
2
+ --Prompt-gap: var(--size-8);
3
+ --Prompt-padding: 12px;
4
+
5
+ &__content {
6
+ display: flex;
7
+ flex-direction: column;
8
+ justify-content: center;
9
+
10
+ &:has(.wds-info-prompt__title) {
11
+ justify-content: flex-start; /* Top align when title exists */
12
+ }
13
+ }
14
+
15
+ &__title,
16
+ &__description {
17
+ display: block;
18
+ color: var(--color-sentiment-primary);
19
+ }
20
+
21
+ &__action {
22
+ margin-top: var(--Prompt-gap);
23
+ }
24
+
25
+ &__media {
26
+ display: flex;
27
+
28
+ svg {
29
+ width: 24px;
30
+ height: 24px;
31
+ }
32
+ }
33
+
34
+ .wds-prompt__media-wrapper {
35
+ padding: 0;
36
+ }
37
+ }
@@ -0,0 +1,312 @@
1
+ import type { ReactElement } from 'react';
2
+ import type { Meta, StoryObj } from '@storybook/react-webpack5';
3
+ import { action } from 'storybook/actions';
4
+ import { Confetti, GiftBox, Star, Travel } from '@transferwise/icons';
5
+ import { InfoPrompt, InfoPromptProps } from './InfoPrompt';
6
+
7
+ const withComponentGrid =
8
+ ({ maxWidth = 'auto', gap = '1rem' } = {}) =>
9
+ (Story: () => ReactElement) => (
10
+ <div
11
+ style={{
12
+ width: '100%',
13
+ display: 'flex',
14
+ flexDirection: 'column',
15
+ justifyContent: 'flex-start',
16
+ alignItems: 'flex-start',
17
+ alignContent: 'flex-start',
18
+ gap,
19
+ maxWidth,
20
+ }}
21
+ >
22
+ <Story />
23
+ </div>
24
+ );
25
+
26
+ export default {
27
+ title: 'Prompts/InfoPrompt',
28
+ component: InfoPrompt,
29
+ tags: ['new'],
30
+ decorators: [withComponentGrid()],
31
+ args: {
32
+ description: 'Your payment is being processed.',
33
+ },
34
+ argTypes: {
35
+ sentiment: {
36
+ control: 'select',
37
+ options: ['success', 'negative', 'neutral', 'warning', 'proposition'],
38
+ },
39
+ title: {
40
+ control: 'text',
41
+ table: {
42
+ type: { summary: 'ReactNode' },
43
+ },
44
+ },
45
+ description: {
46
+ control: 'text',
47
+ table: {
48
+ type: { summary: 'ReactNode' },
49
+ },
50
+ },
51
+ },
52
+ parameters: {
53
+ docs: {
54
+ toc: true,
55
+ },
56
+ },
57
+ } satisfies Meta<InfoPromptProps>;
58
+
59
+ /**
60
+ * Convenience controls for previewing rich markup,
61
+ * not otherwise possible via Storybook
62
+ */
63
+ type PreviewStoryArgs = InfoPromptProps & {
64
+ previewMedia: boolean;
65
+ previewOnDismiss: boolean;
66
+ previewAction: boolean;
67
+ };
68
+
69
+ const previewArgGroup = {
70
+ category: 'Storybook Preview options',
71
+ type: {
72
+ summary: undefined,
73
+ },
74
+ };
75
+
76
+ const previewArgTypes = {
77
+ previewMedia: {
78
+ name: 'Preview with `media`',
79
+ control: 'boolean',
80
+ table: previewArgGroup,
81
+ },
82
+ previewOnDismiss: {
83
+ name: 'Preview with `onDismiss`',
84
+ control: 'boolean',
85
+ table: previewArgGroup,
86
+ },
87
+ previewAction: {
88
+ name: 'Preview with `action`',
89
+ control: 'boolean',
90
+ table: previewArgGroup,
91
+ },
92
+ } as const;
93
+
94
+ const getPropsForPreview = (
95
+ args: PreviewStoryArgs,
96
+ ): [InfoPromptProps, Partial<InfoPromptProps>] => {
97
+ const { previewMedia, previewOnDismiss, previewAction, ...props } = args;
98
+
99
+ return [
100
+ props,
101
+ {
102
+ media: previewMedia ? { asset: <Star title="Starred" /> } : undefined,
103
+ onDismiss: previewOnDismiss ? action('onDismiss') : undefined,
104
+ action: previewAction
105
+ ? { label: 'Learn more', onClick: action('action.onClick') }
106
+ : undefined,
107
+ },
108
+ ];
109
+ };
110
+
111
+ export const Playground: StoryObj<PreviewStoryArgs> = {
112
+ tags: ['!autodocs'],
113
+ argTypes: previewArgTypes,
114
+ args: {
115
+ previewMedia: false,
116
+ previewOnDismiss: false,
117
+ previewAction: false,
118
+ },
119
+ render: (args: PreviewStoryArgs) => {
120
+ const [props, previewProps] = getPropsForPreview(args);
121
+ return <InfoPrompt {...props} {...previewProps} />;
122
+ },
123
+ };
124
+
125
+ /**
126
+ * InfoPrompt supports multiple sentiments to communicate different types of messages:
127
+ * - `neutral` (default): General information
128
+ * - `success`: Success messages
129
+ * - `negative`: Error messages
130
+ * - `warning`: Warning messages
131
+ * - `proposition`: Promotional content (uses GiftBox icon by default)
132
+ */
133
+ export const Sentiments: StoryObj<InfoPromptProps> = {
134
+ render: (args: InfoPromptProps) => (
135
+ <>
136
+ <InfoPrompt {...args} sentiment="neutral" description="This is a neutral message." />
137
+ <InfoPrompt {...args} sentiment="success" description="Your payment was successful!" />
138
+ <InfoPrompt {...args} sentiment="negative" description="Something went wrong." />
139
+ <InfoPrompt {...args} sentiment="warning" description="Please review your details." />
140
+ <InfoPrompt {...args} sentiment="proposition" description="Check out our new features!" />
141
+ </>
142
+ ),
143
+ decorators: [withComponentGrid()],
144
+ };
145
+
146
+ /**
147
+ * InfoPrompt can include both a title and description for more detailed messaging.
148
+ */
149
+ export const WithTitle: StoryObj<InfoPromptProps> = {
150
+ render: (args: InfoPromptProps) => (
151
+ <>
152
+ <InfoPrompt
153
+ {...args}
154
+ sentiment="success"
155
+ title="Payment Successful"
156
+ description="Your money is on its way to the recipient."
157
+ />
158
+ <InfoPrompt
159
+ {...args}
160
+ sentiment="warning"
161
+ title="Payment Delayed"
162
+ description="Payments sent today might not arrive in time for the holidays."
163
+ />
164
+ <InfoPrompt
165
+ {...args}
166
+ sentiment="negative"
167
+ title="Payment Failed"
168
+ description="We couldn't process your payment. Please try again."
169
+ />
170
+ </>
171
+ ),
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
+ };
199
+
200
+ /**
201
+ * Use the `action` prop to add a clickable link below the message.
202
+ * Actions can have an `href` for navigation or an `onClick` for custom behavior.
203
+ */
204
+ export const WithAction: StoryObj<InfoPromptProps> = {
205
+ render: (args: InfoPromptProps) => (
206
+ <>
207
+ <InfoPrompt
208
+ {...args}
209
+ sentiment="neutral"
210
+ description="New feature available"
211
+ action={{ label: 'Learn more', href: '/features', target: '_blank' }}
212
+ />
213
+ <InfoPrompt
214
+ {...args}
215
+ sentiment="warning"
216
+ title="Action Required"
217
+ description="Please verify your identity to continue."
218
+ action={{ label: 'Verify now', onClick: action('verify') }}
219
+ />
220
+ <InfoPrompt
221
+ {...args}
222
+ sentiment="proposition"
223
+ title="Special Offer"
224
+ description="Get 50% off your next transfer!"
225
+ action={{ label: 'Claim offer', onClick: action('claim') }}
226
+ />
227
+ </>
228
+ ),
229
+ decorators: [withComponentGrid()],
230
+ };
231
+
232
+ /**
233
+ * Use the `media` prop to override the default status icon with a custom icon.
234
+ * This is useful for matching the prompt's visual language to its content.
235
+ *
236
+ * Success and proposition sentiments support both standard checkmark and confetti variations.
237
+ */
238
+ export const CustomMedia: StoryObj<InfoPromptProps> = {
239
+ render: (args: InfoPromptProps) => (
240
+ <>
241
+ <InfoPrompt
242
+ {...args}
243
+ sentiment="success"
244
+ description="Saved to favorites!"
245
+ media={{ asset: <Star title="Starred" /> }}
246
+ />
247
+ <InfoPrompt
248
+ {...args}
249
+ sentiment="success"
250
+ title="Congratulations!"
251
+ description="You've completed all the steps."
252
+ media={{ asset: <Confetti size={24} title="Celebration" /> }}
253
+ />
254
+ <InfoPrompt
255
+ {...args}
256
+ sentiment="proposition"
257
+ title="Check out more!"
258
+ description="New ways to send money with Wise!"
259
+ media={{ asset: <GiftBox title="Gift" /> }}
260
+ />
261
+ <InfoPrompt
262
+ {...args}
263
+ sentiment="success"
264
+ description="Your travel account is ready!"
265
+ media={{ asset: <Travel title="Travel" /> }}
266
+ />
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
+ <InfoPrompt
280
+ {...args}
281
+ sentiment="success"
282
+ description="Your profile has been saved"
283
+ role="status"
284
+ />
285
+ <InfoPrompt
286
+ {...args}
287
+ sentiment="negative"
288
+ title="Error"
289
+ description="Your session has expired. Please log in again."
290
+ role="alert"
291
+ />
292
+ </>
293
+ ),
294
+ decorators: [withComponentGrid()],
295
+ };
296
+
297
+ /**
298
+ * Complete example showing all features combined:
299
+ * sentiment, title, description, action, onDismiss, and custom media.
300
+ */
301
+ export const CompleteExample: StoryObj<InfoPromptProps> = {
302
+ render: () => (
303
+ <InfoPrompt
304
+ sentiment="proposition"
305
+ title="Invite Friends & Earn"
306
+ description="Share Wise with friends and get £50 for each friend who makes their first transfer."
307
+ media={{ asset: <GiftBox title="Gift" /> }}
308
+ action={{ label: 'Invite friends', onClick: action('invite') }}
309
+ onDismiss={action('onDismiss')}
310
+ />
311
+ ),
312
+ };