@transferwise/components 46.164.0 → 46.165.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.
Files changed (102) hide show
  1. package/build/ListItem/ListItem.js +2 -0
  2. package/build/ListItem/ListItem.js.map +1 -1
  3. package/build/ListItem/ListItem.mjs +2 -0
  4. package/build/ListItem/ListItem.mjs.map +1 -1
  5. package/build/Snackbar/Snackbar.js +98 -101
  6. package/build/Snackbar/Snackbar.js.map +1 -1
  7. package/build/Snackbar/Snackbar.mjs +99 -102
  8. package/build/Snackbar/Snackbar.mjs.map +1 -1
  9. package/build/main.css +5 -0
  10. package/build/styles/ListItem/ListItem.css +5 -0
  11. package/build/styles/main.css +5 -0
  12. package/build/types/Body/Body.d.ts +2 -2
  13. package/build/types/Carousel/_stories/constants.d.ts +25 -0
  14. package/build/types/Carousel/_stories/constants.d.ts.map +1 -1
  15. package/build/types/IconButton/IconButton.d.ts +1 -1
  16. package/build/types/ListItem/ListItem.d.ts +6 -1
  17. package/build/types/ListItem/ListItem.d.ts.map +1 -1
  18. package/build/types/ListItem/_stories/variants/helpers.d.ts +38 -1
  19. package/build/types/ListItem/_stories/variants/helpers.d.ts.map +1 -1
  20. package/build/types/MoneyInput/MoneyInput.d.ts +1 -1
  21. package/build/types/MoneyInput/MoneyInput.d.ts.map +1 -1
  22. package/build/types/Snackbar/Snackbar.d.ts +1 -15
  23. package/build/types/Snackbar/Snackbar.d.ts.map +1 -1
  24. package/build/types/Title/Title.d.ts +2 -2
  25. package/build/types/Upload/Steps/UploadImageStep/UploadImageStep.d.ts +1 -1
  26. package/build/types/UploadInput/UploadItem/UploadItemLink.d.ts +1 -1
  27. package/package.json +1 -1
  28. package/src/Accordion/_stories/Accordion.test.story.tsx +74 -48
  29. package/src/AvatarLayout/_stories/AvatarLayout.test.story.tsx +46 -30
  30. package/src/Button/LegacyButton/_stories/LegacyButton.test.story.tsx +29 -19
  31. package/src/Button/_stories/Button.test.story.tsx +96 -62
  32. package/src/Calendar/_stories/Calendar.test.story.tsx +34 -21
  33. package/src/Carousel/_stories/Carousel.test.story.tsx +18 -8
  34. package/src/Carousel/_stories/constants.tsx +29 -4
  35. package/src/Checkbox/_stories/Checkbox.test.story.tsx +22 -13
  36. package/src/CheckboxButton/_stories/CheckboxButton.test.story.tsx +23 -14
  37. package/src/Chips/_stories/Chips.test.story.tsx +51 -36
  38. package/src/DateInput/_stories/DateInput.rtl.test.story.tsx +62 -12
  39. package/src/DateLookup/_stories/DateLookup.rtl.test.story.tsx +2 -2
  40. package/src/Decision/_stories/Decision.test.story.tsx +74 -58
  41. package/src/DefinitionList/_stories/DefinitionList.test.story.tsx +58 -42
  42. package/src/Drawer/_stories/Drawer.rtl.test.story.tsx +65 -38
  43. package/src/ExpressiveMoneyInput/_stories/ExpressiveMoneyInput.rtl.test.story.tsx +107 -74
  44. package/src/Field/_stories/Field.test.story.tsx +18 -18
  45. package/src/FlowNavigation/_stories/FlowNavigation.rtl.test.story.tsx +81 -51
  46. package/src/Header/_stories/Header.test.story.tsx +30 -18
  47. package/src/Info/_stories/Info.rtl.test.story.tsx +124 -50
  48. package/src/InputWithDisplayFormat/_stories/InputWithDisplayFormat.test.story.tsx +4 -4
  49. package/src/Inputs/InputGroup/_stories/InputGroup.test.story.tsx +9 -8
  50. package/src/Inputs/SearchInput/_stories/SearchInput.test.story.tsx +5 -3
  51. package/src/Inputs/SelectInput/_stories/SelectInput.rtl.test.story.tsx +108 -79
  52. package/src/Inputs/TextArea/_stories/TextArea.test.story.tsx +10 -10
  53. package/src/InstructionsList/_stories/InstructionsList.test.story.tsx +43 -16
  54. package/src/Label/_stories/Label.test.story.tsx +7 -7
  55. package/src/Link/_stories/Link.test.story.tsx +27 -13
  56. package/src/ListItem/ListItem.css +5 -0
  57. package/src/ListItem/ListItem.less +4 -0
  58. package/src/ListItem/ListItem.test.tsx +17 -0
  59. package/src/ListItem/ListItem.tsx +7 -0
  60. package/src/ListItem/_stories/ListItem.layout.test.story.tsx +23 -0
  61. package/src/ListItem/_stories/ListItem.rtl.test.story.tsx +126 -6
  62. package/src/ListItem/_stories/ListItem.story.tsx +8 -0
  63. package/src/ListItem/_stories/variants/helpers.tsx +174 -126
  64. package/src/Loader/_stories/Loader.test.story.tsx +17 -10
  65. package/src/Modal/_stories/Modal.rtl.test.story.tsx +58 -39
  66. package/src/MoneyInput/_stories/MoneyInput.rtl.test.story.tsx +21 -3
  67. package/src/Nudge/_stories/Nudge.test.story.tsx +70 -21
  68. package/src/PhoneNumberInput/_stories/PhoneNumberInput.rtl.test.story.tsx +1 -1
  69. package/src/Popover/_stories/Popover.rtl.test.story.tsx +92 -49
  70. package/src/ProgressBar/_stories/ProgressBar.test.story.tsx +6 -6
  71. package/src/PromoCard/_stories/PromoCard.test.story.tsx +53 -29
  72. package/src/PromoCard/_stories/PromoCardGroup.test.story.tsx +60 -36
  73. package/src/Prompt/ActionPrompt/_stories/ActionPrompt.test.story.tsx +74 -38
  74. package/src/Prompt/CriticalBanner/_stories/CriticalBanner.test.story.tsx +68 -31
  75. package/src/Prompt/InfoPrompt/_stories/InfoPrompt.test.story.tsx +52 -22
  76. package/src/Prompt/InlinePrompt/_stories/InlinePrompt.test.story.tsx +36 -21
  77. package/src/Radio/_stories/Radio.test.story.tsx +60 -46
  78. package/src/RadioGroup/_stories/RadioGroup.test.story.tsx +70 -51
  79. package/src/Section/_stories/Section.test.story.tsx +30 -14
  80. package/src/SegmentedControl/_stories/SegmentedControl.test.story.tsx +38 -15
  81. package/src/SlidingPanel/_stories/SlidingPanel.rtl.test.story.tsx +58 -34
  82. package/src/Snackbar/Snackbar.test.tsx +16 -0
  83. package/src/Snackbar/Snackbar.tsx +91 -88
  84. package/src/Snackbar/_stories/Snackbar.rtl.test.story.tsx +24 -12
  85. package/src/Stepper/_stories/Stepper.test.story.tsx +27 -9
  86. package/src/Switch/_stories/Switch.test.story.tsx +20 -12
  87. package/src/Table/_stories/Table.test.story.tsx +107 -86
  88. package/src/Tabs/_stories/Tabs.test.story.tsx +92 -52
  89. package/src/Tag/_stories/Tag.test.story.tsx +27 -16
  90. package/src/TextareaWithDisplayFormat/_stories/TextareaWithDisplayFormat.test.story.tsx +4 -4
  91. package/src/Tooltip/_stories/Tooltip.rtl.test.story.tsx +68 -25
  92. package/src/Typeahead/_stories/Typeahead.rtl.test.story.tsx +4 -4
  93. package/src/Upload/_stories/Upload.rtl.test.story.tsx +49 -26
  94. package/src/UploadInput/_stories/UploadInput.test.story.tsx +63 -41
  95. package/src/_deprecated/Alert/_stories/Alert.test.story.tsx +45 -24
  96. package/src/_deprecated/Card/_stories/Card.test.story.tsx +47 -23
  97. package/src/_deprecated/CriticalBanner/_stories/CriticalCommsBanner.test.story.tsx +21 -15
  98. package/src/_deprecated/InlineAlert/_stories/InlineAlert.test.story.tsx +23 -12
  99. package/src/_deprecated/Select/_stories/Select.rtl.test.story.tsx +78 -42
  100. package/src/common/BottomSheet/_stories/BottomSheet.test.story.tsx +38 -20
  101. package/src/common/Panel/_stories/Panel.rtl.test.story.tsx +135 -51
  102. package/src/main.css +5 -0
@@ -2,54 +2,13 @@ import type { Meta, StoryObj } from '@storybook/react-webpack5';
2
2
  import { userEvent, within } from 'storybook/test';
3
3
  import { FastFlag as FastFlagIcon } from '@transferwise/icons';
4
4
 
5
- import { forEachColumn, withRTLDiff } from '../../../.storybook/decorators/RtlDiffDecorator';
5
+ import {
6
+ forEachColumn,
7
+ useRtlDiffI18n,
8
+ withRTLDiff,
9
+ } from '../../../.storybook/decorators/RtlDiffDecorator';
6
10
  import Accordion from '../Accordion';
7
11
 
8
- const items = [
9
- {
10
- title: 'Item with icon',
11
- subtitle: 'Subtitle text',
12
- content: 'Content for item with icon.',
13
- id: 'icon',
14
- icon: <FastFlagIcon size={24} />,
15
- },
16
- {
17
- title: 'Plain item without icon',
18
- content: 'Content for item without icon.',
19
- id: 'no-icon',
20
- },
21
- {
22
- title: 'Media with circle',
23
- subtitle: 'Shows media in a circle',
24
- content: 'Expanded content with media circle.',
25
- id: 'media-circle',
26
- media: (
27
- <img
28
- src="https://wise.com/public-resources/assets/spend/card/asset//digital_card_2023.png"
29
- alt=""
30
- />
31
- ),
32
- showMediaCircle: true,
33
- showMediaAtAllSizes: true,
34
- isContainerAligned: true,
35
- },
36
- {
37
- title: 'Media without circle',
38
- subtitle: 'Not container aligned',
39
- content: 'Expanded content with plain media.',
40
- id: 'media-no-circle',
41
- media: (
42
- <img
43
- src="https://wise.com/public-resources/assets/spend/card/asset//digital_card_2023.png"
44
- alt=""
45
- />
46
- ),
47
- showMediaCircle: false,
48
- showMediaAtAllSizes: false,
49
- isContainerAligned: false,
50
- },
51
- ];
52
-
53
12
  export default {
54
13
  component: Accordion,
55
14
  title: 'Content/Accordion/Tests',
@@ -59,7 +18,54 @@ export default {
59
18
  type Story = StoryObj<typeof Accordion>;
60
19
 
61
20
  export const RTLDiff: Story = {
62
- render: () => <Accordion items={items} indexOpen={0} />,
21
+ render: () => {
22
+ const i18n = useRtlDiffI18n();
23
+ const items = [
24
+ {
25
+ title: i18n.withIconTitle,
26
+ subtitle: i18n.withIconSubtitle,
27
+ content: i18n.withIconContent,
28
+ id: 'icon',
29
+ icon: <FastFlagIcon size={24} />,
30
+ },
31
+ {
32
+ title: i18n.noIconTitle,
33
+ content: i18n.noIconContent,
34
+ id: 'no-icon',
35
+ },
36
+ {
37
+ title: i18n.mediaCircleTitle,
38
+ subtitle: i18n.mediaCircleSubtitle,
39
+ content: i18n.mediaCircleContent,
40
+ id: 'media-circle',
41
+ media: (
42
+ <img
43
+ src="https://wise.com/public-resources/assets/spend/card/asset//digital_card_2023.png"
44
+ alt=""
45
+ />
46
+ ),
47
+ showMediaCircle: true,
48
+ showMediaAtAllSizes: true,
49
+ isContainerAligned: true,
50
+ },
51
+ {
52
+ title: i18n.mediaNoCircleTitle,
53
+ subtitle: i18n.mediaNoCircleSubtitle,
54
+ content: i18n.mediaNoCircleContent,
55
+ id: 'media-no-circle',
56
+ media: (
57
+ <img
58
+ src="https://wise.com/public-resources/assets/spend/card/asset//digital_card_2023.png"
59
+ alt=""
60
+ />
61
+ ),
62
+ showMediaCircle: false,
63
+ showMediaAtAllSizes: false,
64
+ isContainerAligned: false,
65
+ },
66
+ ];
67
+ return <Accordion items={items} indexOpen={0} />;
68
+ },
63
69
  play: async ({ canvasElement }) => {
64
70
  await forEachColumn(canvasElement, async (column) => {
65
71
  const scope = within(column);
@@ -67,5 +73,25 @@ export const RTLDiff: Story = {
67
73
  await userEvent.click(buttons[1]);
68
74
  });
69
75
  },
70
- ...withRTLDiff(),
76
+ ...withRTLDiff({
77
+ i18n: {
78
+ withIconTitle: { en: 'Item with icon', ar: 'عنصر مع أيقونة' },
79
+ withIconSubtitle: { en: 'Subtitle text', ar: 'نص العنوان الفرعي' },
80
+ withIconContent: { en: 'Content for item with icon.', ar: 'محتوى العنصر ذي الأيقونة.' },
81
+ noIconTitle: { en: 'Plain item without icon', ar: 'عنصر عادي بدون أيقونة' },
82
+ noIconContent: { en: 'Content for item without icon.', ar: 'محتوى العنصر بدون أيقونة.' },
83
+ mediaCircleTitle: { en: 'Media with circle', ar: 'وسائط داخل دائرة' },
84
+ mediaCircleSubtitle: { en: 'Shows media in a circle', ar: 'يعرض الوسائط داخل دائرة' },
85
+ mediaCircleContent: {
86
+ en: 'Expanded content with media circle.',
87
+ ar: 'محتوى موسَّع مع وسائط داخل دائرة.',
88
+ },
89
+ mediaNoCircleTitle: { en: 'Media without circle', ar: 'وسائط بدون دائرة' },
90
+ mediaNoCircleSubtitle: { en: 'Not container aligned', ar: 'غير محاذٍ للحاوية' },
91
+ mediaNoCircleContent: {
92
+ en: 'Expanded content with plain media.',
93
+ ar: 'محتوى موسَّع مع وسائط عادية.',
94
+ },
95
+ },
96
+ }),
71
97
  };
@@ -2,7 +2,7 @@ import { Meta, StoryObj } from '@storybook/react-webpack5';
2
2
  import AvatarLayout from '..';
3
3
  import { Freeze, Plane } from '@transferwise/icons';
4
4
  import { Flag } from '@wise/art';
5
- import { withRTLDiff } from '../../../.storybook/decorators/RtlDiffDecorator';
5
+ import { useRtlDiffI18n, withRTLDiff } from '../../../.storybook/decorators/RtlDiffDecorator';
6
6
 
7
7
  const meta: Meta<typeof AvatarLayout> = {
8
8
  component: AvatarLayout,
@@ -124,33 +124,49 @@ const rowStyle = { display: 'flex', alignItems: 'center', gap: '16px', flexWrap:
124
124
  const twoAvatars = [{ asset: <Plane /> }, { asset: <Plane /> }] as const;
125
125
 
126
126
  export const RTLDiff: Story = {
127
- render: () => (
128
- <>
129
- <div style={rowStyle}>
130
- {horizontalSizes.map((size) => (
131
- <AvatarLayout key={size} orientation="horizontal" size={size} avatars={[...twoAvatars]} />
132
- ))}
133
- </div>
134
-
135
- <div style={rowStyle}>
136
- {diagonalSizes.map((size) => (
137
- <AvatarLayout key={size} orientation="diagonal" size={size} avatars={[...twoAvatars]} />
138
- ))}
139
- </div>
140
-
141
- <AvatarLayout
142
- orientation="horizontal"
143
- size={48}
144
- interactive
145
- avatars={[{ asset: <Flag code="gb" /> }, { asset: <Flag code="br" /> }, { asset: '3+' }]}
146
- />
147
-
148
- <AvatarLayout
149
- orientation="horizontal"
150
- size={72}
151
- avatars={[{ asset: <Plane /> }, { asset: <Plane /> }, { asset: <Plane /> }]}
152
- />
153
- </>
154
- ),
155
- ...withRTLDiff(),
127
+ render: () => {
128
+ const i18n = useRtlDiffI18n();
129
+ return (
130
+ <>
131
+ <div style={rowStyle}>
132
+ {horizontalSizes.map((size) => (
133
+ <AvatarLayout
134
+ key={size}
135
+ orientation="horizontal"
136
+ size={size}
137
+ avatars={[...twoAvatars]}
138
+ />
139
+ ))}
140
+ </div>
141
+
142
+ <div style={rowStyle}>
143
+ {diagonalSizes.map((size) => (
144
+ <AvatarLayout key={size} orientation="diagonal" size={size} avatars={[...twoAvatars]} />
145
+ ))}
146
+ </div>
147
+
148
+ <AvatarLayout
149
+ orientation="horizontal"
150
+ size={48}
151
+ interactive
152
+ avatars={[
153
+ { asset: <Flag code="gb" /> },
154
+ { asset: <Flag code="br" /> },
155
+ { asset: i18n.overflowCount },
156
+ ]}
157
+ />
158
+
159
+ <AvatarLayout
160
+ orientation="horizontal"
161
+ size={72}
162
+ avatars={[{ asset: <Plane /> }, { asset: <Plane /> }, { asset: <Plane /> }]}
163
+ />
164
+ </>
165
+ );
166
+ },
167
+ ...withRTLDiff({
168
+ i18n: {
169
+ overflowCount: { en: '3+', ar: '+٣' },
170
+ },
171
+ }),
156
172
  };
@@ -4,7 +4,7 @@ import { useState } from 'react';
4
4
 
5
5
  import Button from '../..';
6
6
  import LegacyButton from '../LegacyButton';
7
- import { withRTLDiff } from '../../../../.storybook/decorators/RtlDiffDecorator';
7
+ import { useRtlDiffI18n, withRTLDiff } from '../../../../.storybook/decorators/RtlDiffDecorator';
8
8
 
9
9
  LegacyButton.displayName = 'Button';
10
10
 
@@ -38,22 +38,32 @@ export const Focused: Story = {
38
38
  };
39
39
 
40
40
  export const RTLDiff: Story = {
41
- render: () => (
42
- <>
43
- {/* default (inline) layout */}
44
- <LegacyButton>Default button</LegacyButton>
45
-
46
- {/* block layout — full width */}
47
- <LegacyButton block>Block button</LegacyButton>
48
-
49
- {/* loading state — appends ProcessIndicator inside button */}
50
- <LegacyButton loading>Loading button</LegacyButton>
51
-
52
- {/* loading + block combined */}
53
- <LegacyButton loading block>
54
- Loading block button
55
- </LegacyButton>
56
- </>
57
- ),
58
- ...withRTLDiff(),
41
+ render: () => {
42
+ const i18n = useRtlDiffI18n();
43
+ return (
44
+ <>
45
+ {/* default (inline) layout */}
46
+ <LegacyButton>{i18n.defaultButton}</LegacyButton>
47
+
48
+ {/* block layout — full width */}
49
+ <LegacyButton block>{i18n.blockButton}</LegacyButton>
50
+
51
+ {/* loading state — appends ProcessIndicator inside button */}
52
+ <LegacyButton loading>{i18n.loadingButton}</LegacyButton>
53
+
54
+ {/* loading + block combined */}
55
+ <LegacyButton loading block>
56
+ {i18n.loadingBlockButton}
57
+ </LegacyButton>
58
+ </>
59
+ );
60
+ },
61
+ ...withRTLDiff({
62
+ i18n: {
63
+ defaultButton: { en: 'Default button', ar: 'زر افتراضي' },
64
+ blockButton: { en: 'Block button', ar: 'زر بعرض كامل' },
65
+ loadingButton: { en: 'Loading button', ar: 'زر قيد التحميل' },
66
+ loadingBlockButton: { en: 'Loading block button', ar: 'زر بعرض كامل قيد التحميل' },
67
+ },
68
+ }),
59
69
  };
@@ -5,7 +5,7 @@ import { expect, userEvent, within } from 'storybook/test';
5
5
  import Button from '../Button.resolver';
6
6
  import { ButtonPriority } from '../Button.types';
7
7
  import { withVariantConfig } from '../../../.storybook/helpers';
8
- import { withRTLDiff } from '../../../.storybook/decorators/RtlDiffDecorator';
8
+ import { useRtlDiffI18n, withRTLDiff } from '../../../.storybook/decorators/RtlDiffDecorator';
9
9
 
10
10
  const meta: Meta<typeof Button> = {
11
11
  component: Button,
@@ -167,66 +167,100 @@ export const AccessibilityAddons: Story = {
167
167
  },
168
168
  };
169
169
 
170
+ type RTLDiffLabels = {
171
+ startIcon: string;
172
+ endIcon: string;
173
+ bothIcons: string;
174
+ avatarStart: string;
175
+ doubleAvatar: string;
176
+ smStartIcon: string;
177
+ smEndIcon: string;
178
+ smBothIcons: string;
179
+ loading: string;
180
+ blockWithStartIcon: string;
181
+ };
182
+
170
183
  export const RTLDiff: Story = {
171
- render: () => (
172
- <>
173
- <Button v2 size="md" addonStart={{ type: 'icon', value: <Freeze /> }}>
174
- Start icon
175
- </Button>
176
-
177
- <Button v2 size="md" addonEnd={{ type: 'icon', value: <ArrowEnd /> }}>
178
- End icon
179
- </Button>
180
-
181
- <Button
182
- v2
183
- size="md"
184
- addonStart={{ type: 'icon', value: <Freeze /> }}
185
- addonEnd={{ type: 'icon', value: <ArrowEnd /> }}
186
- >
187
- Both icons
188
- </Button>
189
-
190
- <Button v2 size="md" addonStart={{ type: 'avatar', value: [{ asset: <Flag code="pl" /> }] }}>
191
- Avatar start
192
- </Button>
193
-
194
- <Button
195
- v2
196
- size="md"
197
- addonStart={{
198
- type: 'avatar',
199
- value: [{ asset: <Flag code="gb" /> }, { asset: <Flag code="br" /> }],
200
- }}
201
- >
202
- Double avatar
203
- </Button>
204
-
205
- <Button v2 size="sm" addonStart={{ type: 'icon', value: <Freeze /> }}>
206
- Sm start icon
207
- </Button>
208
-
209
- <Button v2 size="sm" addonEnd={{ type: 'icon', value: <ArrowEnd /> }}>
210
- Sm end icon
211
- </Button>
212
-
213
- <Button
214
- v2
215
- size="sm"
216
- addonStart={{ type: 'icon', value: <Freeze /> }}
217
- addonEnd={{ type: 'icon', value: <ArrowEnd /> }}
218
- >
219
- Sm both icons
220
- </Button>
221
-
222
- <Button v2 size="md" loading addonEnd={{ type: 'icon', value: <ArrowEnd /> }}>
223
- Loading
224
- </Button>
225
-
226
- <Button v2 size="md" block addonStart={{ type: 'icon', value: <Freeze /> }}>
227
- Block with start icon
228
- </Button>
229
- </>
230
- ),
231
- ...withRTLDiff(),
184
+ render: () => {
185
+ const i18n = useRtlDiffI18n<RTLDiffLabels>();
186
+
187
+ return (
188
+ <>
189
+ <Button v2 size="md" addonStart={{ type: 'icon', value: <Freeze /> }}>
190
+ {i18n.startIcon}
191
+ </Button>
192
+
193
+ <Button v2 size="md" addonEnd={{ type: 'icon', value: <ArrowEnd /> }}>
194
+ {i18n.endIcon}
195
+ </Button>
196
+
197
+ <Button
198
+ v2
199
+ size="md"
200
+ addonStart={{ type: 'icon', value: <Freeze /> }}
201
+ addonEnd={{ type: 'icon', value: <ArrowEnd /> }}
202
+ >
203
+ {i18n.bothIcons}
204
+ </Button>
205
+
206
+ <Button
207
+ v2
208
+ size="md"
209
+ addonStart={{ type: 'avatar', value: [{ asset: <Flag code="pl" /> }] }}
210
+ >
211
+ {i18n.avatarStart}
212
+ </Button>
213
+
214
+ <Button
215
+ v2
216
+ size="md"
217
+ addonStart={{
218
+ type: 'avatar',
219
+ value: [{ asset: <Flag code="gb" /> }, { asset: <Flag code="br" /> }],
220
+ }}
221
+ >
222
+ {i18n.doubleAvatar}
223
+ </Button>
224
+
225
+ <Button v2 size="sm" addonStart={{ type: 'icon', value: <Freeze /> }}>
226
+ {i18n.smStartIcon}
227
+ </Button>
228
+
229
+ <Button v2 size="sm" addonEnd={{ type: 'icon', value: <ArrowEnd /> }}>
230
+ {i18n.smEndIcon}
231
+ </Button>
232
+
233
+ <Button
234
+ v2
235
+ size="sm"
236
+ addonStart={{ type: 'icon', value: <Freeze /> }}
237
+ addonEnd={{ type: 'icon', value: <ArrowEnd /> }}
238
+ >
239
+ {i18n.smBothIcons}
240
+ </Button>
241
+
242
+ <Button v2 size="md" loading addonEnd={{ type: 'icon', value: <ArrowEnd /> }}>
243
+ {i18n.loading}
244
+ </Button>
245
+
246
+ <Button v2 size="md" block addonStart={{ type: 'icon', value: <Freeze /> }}>
247
+ {i18n.blockWithStartIcon}
248
+ </Button>
249
+ </>
250
+ );
251
+ },
252
+ ...withRTLDiff({
253
+ i18n: {
254
+ startIcon: { en: 'Start icon', ar: 'أيقونة البداية' },
255
+ endIcon: { en: 'End icon', ar: 'أيقونة النهاية' },
256
+ bothIcons: { en: 'Both icons', ar: 'كلتا الأيقونتين' },
257
+ avatarStart: { en: 'Avatar start', ar: 'صورة رمزية في البداية' },
258
+ doubleAvatar: { en: 'Double avatar', ar: 'صورتان رمزيتان' },
259
+ smStartIcon: { en: 'Sm start icon', ar: 'أيقونة بداية صغيرة' },
260
+ smEndIcon: { en: 'Sm end icon', ar: 'أيقونة نهاية صغيرة' },
261
+ smBothIcons: { en: 'Sm both icons', ar: 'كلتا الأيقونتين صغيرة' },
262
+ loading: { en: 'Loading', ar: 'جارٍ التحميل' },
263
+ blockWithStartIcon: { en: 'Block with start icon', ar: 'زر بعرض كامل مع أيقونة البداية' },
264
+ },
265
+ }),
232
266
  };
@@ -3,7 +3,11 @@ import { expect, waitFor, within, userEvent } from 'storybook/test';
3
3
 
4
4
  import Calendar from '../Calendar';
5
5
  import type { DisabledRules } from '../Calendar.types';
6
- import { forEachColumn, withRTLDiff } from '../../../.storybook/decorators/RtlDiffDecorator';
6
+ import {
7
+ forEachColumn,
8
+ useRtlDiffI18n,
9
+ withRTLDiff,
10
+ } from '../../../.storybook/decorators/RtlDiffDecorator';
7
11
 
8
12
  export default {
9
13
  component: Calendar,
@@ -125,28 +129,30 @@ const maxDate = new Date(2024, 9, 31);
125
129
  const disabledRulesRtl = {
126
130
  weekdays: ['sat', 'sun'] as ('sat' | 'sun')[],
127
131
  dates: [new Date(2024, 5, 10)],
128
- reason: 'Weekends and specific dates are unavailable.',
129
132
  };
130
133
 
131
134
  export const RTLDiff: Story = {
132
- render: () => (
133
- <>
134
- <div data-testid="day-view">
135
- <Calendar
136
- value={selectedDate}
137
- min={minDate}
138
- max={maxDate}
139
- disabledRules={disabledRulesRtl}
140
- />
141
- </div>
142
- <div data-testid="month-view">
143
- <Calendar value={selectedDate} min={minDate} max={maxDate} />
144
- </div>
145
- <div data-testid="year-view">
146
- <Calendar value={selectedDate} min={minDate} max={maxDate} />
147
- </div>
148
- </>
149
- ),
135
+ render: () => {
136
+ const i18n = useRtlDiffI18n();
137
+ return (
138
+ <>
139
+ <div data-testid="day-view">
140
+ <Calendar
141
+ value={selectedDate}
142
+ min={minDate}
143
+ max={maxDate}
144
+ disabledRules={{ ...disabledRulesRtl, reason: i18n.disabledReason }}
145
+ />
146
+ </div>
147
+ <div data-testid="month-view">
148
+ <Calendar value={selectedDate} min={minDate} max={maxDate} />
149
+ </div>
150
+ <div data-testid="year-view">
151
+ <Calendar value={selectedDate} min={minDate} max={maxDate} />
152
+ </div>
153
+ </>
154
+ );
155
+ },
150
156
  play: async ({ canvasElement }) => {
151
157
  await forEachColumn(canvasElement, async (column) => {
152
158
  const monthContainer = column.querySelector('[data-testid="month-view"]')!;
@@ -159,5 +165,12 @@ export const RTLDiff: Story = {
159
165
  await userEvent.click(yearScope.getByRole('button', { name: /year view/iu }));
160
166
  });
161
167
  },
162
- ...withRTLDiff(),
168
+ ...withRTLDiff({
169
+ i18n: {
170
+ disabledReason: {
171
+ en: 'Weekends and specific dates are unavailable.',
172
+ ar: 'عطلات نهاية الأسبوع وتواريخ محددة غير متاحة.',
173
+ },
174
+ },
175
+ }),
163
176
  };
@@ -1,8 +1,8 @@
1
1
  import type { Meta, StoryObj } from '@storybook/react-webpack5';
2
2
 
3
- import { withRTLDiff } from '../../../.storybook/decorators/RtlDiffDecorator';
3
+ import { useRtlDiffI18n, withRTLDiff } from '../../../.storybook/decorators/RtlDiffDecorator';
4
4
  import Carousel from '..';
5
- import { stubbedCarouselCards } from './constants';
5
+ import { carouselCardsI18n, getStubbedCarouselCards } from './constants';
6
6
 
7
7
  export default {
8
8
  component: Carousel,
@@ -12,12 +12,22 @@ export default {
12
12
 
13
13
  type Story = StoryObj<typeof Carousel>;
14
14
 
15
- const manyCards = [
16
- ...stubbedCarouselCards,
17
- ...stubbedCarouselCards.map((card) => ({ ...card, id: `${card.id}_2` })),
18
- ];
15
+ /** Doubled so the track overflows and both scroll buttons render. */
16
+ const getManyCards = (i18n: Record<string, string>) => {
17
+ const cards = getStubbedCarouselCards(i18n);
18
+ return [...cards, ...cards.map((card) => ({ ...card, id: `${card.id}_2` }))];
19
+ };
19
20
 
20
21
  export const RTLDiff: Story = {
21
- render: () => <Carousel cards={manyCards} header="Pretty nifty stuff" />,
22
- ...withRTLDiff({ width: 'stretch' }),
22
+ render: () => {
23
+ const i18n = useRtlDiffI18n();
24
+ return <Carousel cards={getManyCards(i18n)} header={i18n.header} />;
25
+ },
26
+ ...withRTLDiff({
27
+ width: 'stretch',
28
+ i18n: {
29
+ header: { en: 'Pretty nifty stuff', ar: 'أشياء رائعة حقًا' },
30
+ ...carouselCardsI18n,
31
+ },
32
+ }),
23
33
  };
@@ -2,7 +2,26 @@ import { Illustration } from '@wise/art';
2
2
  import Display from '../../Display';
3
3
  import type { CarouselCard } from '../Carousel';
4
4
 
5
- export const stubbedCarouselCards: CarouselCard[] = [
5
+ /**
6
+ * Every string the stubbed cards render, shaped as a `withRTLDiff({ i18n })` map so the RTL diff
7
+ * can spread it — that call site is what type-checks the shape against `I18nMap`. The type is not
8
+ * imported here: this file is part of the Rollup build, which cannot emit declarations that
9
+ * reference `.storybook/`. The promo card's `Wise` title is a brand name and stays in Latin script.
10
+ */
11
+ export const carouselCardsI18n = {
12
+ globeCardTitle: { en: 'Here there, everywhere', ar: 'هنا وهناك وفي كل مكان' },
13
+ promoDescription: {
14
+ en: 'The best way to move money internationally',
15
+ ar: 'أفضل طريقة لتحويل الأموال دوليًا',
16
+ },
17
+ quickPayCardTitle: { en: 'Get Quick Pay', ar: 'احصل على الدفع السريع' },
18
+ };
19
+
20
+ /**
21
+ * Takes copy already resolved for one language — exactly what `useRtlDiffI18n()` returns — so the
22
+ * same fixture serves the single-direction stories and the RTL diff.
23
+ */
24
+ export const getStubbedCarouselCards = (i18n: Record<string, string>): CarouselCard[] => [
6
25
  {
7
26
  id: 'YOUR_INVOICE',
8
27
  type: 'button',
@@ -15,7 +34,7 @@ export const stubbedCarouselCards: CarouselCard[] = [
15
34
  <Illustration disablePadding name="globe" size="small" alt="" />
16
35
  </div>
17
36
  <div style={{ position: 'absolute', zIndex: '2', bottom: '8px', left: '16px' }}>
18
- <Display type="display-small">Here there, everywhere</Display>
37
+ <Display type="display-small">{i18n.globeCardTitle}</Display>
19
38
  </div>
20
39
  </div>
21
40
  ),
@@ -28,7 +47,7 @@ export const stubbedCarouselCards: CarouselCard[] = [
28
47
  },
29
48
  href: 'http://wise.com',
30
49
  title: 'Wise',
31
- description: 'The best way to move money internationally',
50
+ description: i18n.promoDescription,
32
51
  },
33
52
  {
34
53
  id: 'SHOP_OR_STALL',
@@ -41,6 +60,12 @@ export const stubbedCarouselCards: CarouselCard[] = [
41
60
  'url(https://wise.com/public-resources/assets/team-page/tapestry/team_member_tapestry_01.jpg)',
42
61
  backgroundSize: 'cover',
43
62
  },
44
- content: <Display type="display-small">Get Quick Pay</Display>,
63
+ content: <Display type="display-small">{i18n.quickPayCardTitle}</Display>,
45
64
  },
46
65
  ];
66
+
67
+ const englishCopy = Object.fromEntries(
68
+ Object.entries(carouselCardsI18n).map(([key, entry]) => [key, entry.en]),
69
+ );
70
+
71
+ export const stubbedCarouselCards = getStubbedCarouselCards(englishCopy);