@transferwise/components 46.133.0 → 46.134.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.
- package/build/chips/Chips.js.map +1 -1
- package/build/chips/Chips.mjs.map +1 -1
- package/build/label/Label.js +1 -1
- package/build/label/Label.js.map +1 -1
- package/build/label/Label.mjs +1 -1
- package/build/label/Label.mjs.map +1 -1
- package/build/logo/Logo.js +6 -0
- package/build/logo/Logo.js.map +1 -1
- package/build/logo/Logo.mjs +6 -0
- package/build/logo/Logo.mjs.map +1 -1
- package/build/main.css +47 -9
- package/build/moneyInput/MoneyInput.js +28 -12
- package/build/moneyInput/MoneyInput.js.map +1 -1
- package/build/moneyInput/MoneyInput.mjs +30 -14
- package/build/moneyInput/MoneyInput.mjs.map +1 -1
- package/build/moneyInput/currencyFormatting.js +8 -2
- package/build/moneyInput/currencyFormatting.js.map +1 -1
- package/build/moneyInput/currencyFormatting.mjs +5 -4
- package/build/moneyInput/currencyFormatting.mjs.map +1 -1
- package/build/statusIcon/StatusIcon.js +1 -12
- package/build/statusIcon/StatusIcon.js.map +1 -1
- package/build/statusIcon/StatusIcon.mjs +1 -12
- package/build/statusIcon/StatusIcon.mjs.map +1 -1
- package/build/styles/listItem/ListItem.css +4 -4
- package/build/styles/listItem/ListItem.grid.css +3 -3
- package/build/styles/main.css +47 -9
- package/build/styles/sentimentSurface/SentimentSurface.css +1 -1
- package/build/styles/statusIcon/StatusIcon.css +35 -4
- package/build/types/chips/Chips.d.ts +1 -1
- package/build/types/chips/Chips.d.ts.map +1 -1
- package/build/types/common/commonProps.d.ts +0 -6
- package/build/types/common/commonProps.d.ts.map +1 -1
- package/build/types/label/Label.d.ts.map +1 -1
- package/build/types/logo/Logo.d.ts +10 -1
- package/build/types/logo/Logo.d.ts.map +1 -1
- package/build/types/moneyInput/MoneyInput.d.ts +6 -0
- package/build/types/moneyInput/MoneyInput.d.ts.map +1 -1
- package/build/types/moneyInput/currencyFormatting.d.ts +4 -3
- package/build/types/moneyInput/currencyFormatting.d.ts.map +1 -1
- package/build/types/statusIcon/StatusIcon.d.ts.map +1 -1
- package/package.json +8 -8
- package/src/button/_stories/Button.story.tsx +15 -5
- package/src/checkboxButton/CheckboxButton.story.tsx +125 -44
- package/src/checkboxButton/CheckboxButton.test.story.tsx +236 -0
- package/src/chips/Chips.story.tsx +141 -102
- package/src/chips/Chips.test.story.tsx +177 -0
- package/src/chips/Chips.tsx +1 -1
- package/src/circularButton/CircularButton.story.tsx +261 -49
- package/src/circularButton/CircularButton.test.story.tsx +192 -2
- package/src/common/commonProps.ts +0 -6
- package/src/iconButton/IconButton.story.tsx +315 -110
- package/src/iconButton/IconButton.test.story.tsx +217 -44
- package/src/label/Label.tsx +1 -2
- package/src/listItem/ListItem.css +4 -4
- package/src/listItem/ListItem.grid.css +3 -3
- package/src/listItem/ListItem.grid.less +5 -3
- package/src/listItem/ListItem.less +1 -1
- package/src/listItem/ListItem.vars.less +2 -2
- package/src/listItem/_stories/ListItem.layout.test.story.tsx +55 -0
- package/src/logo/Logo.story.tsx +181 -21
- package/src/logo/Logo.test.story.tsx +40 -7
- package/src/logo/Logo.tsx +10 -1
- package/src/main.css +47 -9
- package/src/moneyInput/MoneyInput.story.tsx +10 -1
- package/src/moneyInput/MoneyInput.test.story.tsx +141 -1
- package/src/moneyInput/MoneyInput.test.tsx +45 -0
- package/src/moneyInput/MoneyInput.tsx +27 -3
- package/src/moneyInput/currencyFormatting.ts +11 -5
- package/src/sentimentSurface/SentimentSurface.css +1 -1
- package/src/sentimentSurface/SentimentSurface.less +1 -1
- package/src/statusIcon/StatusIcon.css +35 -4
- package/src/statusIcon/StatusIcon.less +35 -4
- package/src/statusIcon/StatusIcon.story.tsx +119 -79
- package/src/statusIcon/StatusIcon.test.story.tsx +125 -0
- package/src/statusIcon/StatusIcon.test.tsx +16 -23
- package/src/statusIcon/StatusIcon.tsx +2 -16
- package/src/switch/Switch.story.tsx +64 -42
- package/src/switch/Switch.test.story.tsx +123 -0
package/src/logo/Logo.story.tsx
CHANGED
|
@@ -2,36 +2,196 @@ import { Meta, StoryObj } from '@storybook/react-webpack5';
|
|
|
2
2
|
|
|
3
3
|
import Logo, { LogoType } from '.';
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
/**
|
|
6
|
+
* **Design guidance**: <a href="https://wise.design/foundations/logo" target="_blank">wise.design/foundations/logo</a>
|
|
7
|
+
*/
|
|
8
|
+
const meta: Meta<typeof Logo> = {
|
|
6
9
|
component: Logo,
|
|
7
10
|
title: 'Content/Logo',
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
11
|
+
args: {
|
|
12
|
+
type: 'WISE',
|
|
13
|
+
inverse: false,
|
|
14
|
+
},
|
|
15
|
+
argTypes: {
|
|
16
|
+
type: {
|
|
17
|
+
control: 'radio',
|
|
18
|
+
options: Object.values(LogoType),
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
parameters: {
|
|
22
|
+
docs: { toc: true },
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export default meta;
|
|
27
|
+
|
|
28
|
+
type Story = StoryObj<typeof Logo>;
|
|
29
|
+
|
|
30
|
+
/** Explore all props via the controls panel. */
|
|
31
|
+
export const Playground: Story = {
|
|
32
|
+
render: (args) => (
|
|
33
|
+
<div
|
|
34
|
+
style={{
|
|
35
|
+
padding: '2rem',
|
|
36
|
+
...(args.inverse
|
|
37
|
+
? { backgroundColor: 'var(--color-background-screen-dark, #0e0f0c)' }
|
|
38
|
+
: {}),
|
|
39
|
+
}}
|
|
40
|
+
>
|
|
41
|
+
<Logo {...args} />
|
|
42
|
+
</div>
|
|
43
|
+
),
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* The three logo types: standard Wise, Wise Business, and Wise Platform. <br />
|
|
48
|
+
* `WISE_BUSINESS` renders the same SVG as `WISE` but with the accessible label "Wise Business".
|
|
49
|
+
*/
|
|
50
|
+
export const Types: Story = {
|
|
51
|
+
argTypes: {
|
|
52
|
+
type: { table: { disable: true } },
|
|
53
|
+
},
|
|
54
|
+
render: (args) => (
|
|
55
|
+
<div
|
|
56
|
+
style={{
|
|
57
|
+
display: 'flex',
|
|
58
|
+
gap: '3rem',
|
|
59
|
+
alignItems: 'center',
|
|
60
|
+
padding: '2rem',
|
|
61
|
+
borderRadius: '8px',
|
|
62
|
+
...(args.inverse
|
|
63
|
+
? { backgroundColor: 'var(--color-background-screen-dark, #0e0f0c)' }
|
|
64
|
+
: {}),
|
|
65
|
+
}}
|
|
66
|
+
>
|
|
67
|
+
{Object.values(LogoType).map((type) => (
|
|
68
|
+
<div
|
|
69
|
+
key={type}
|
|
70
|
+
style={{
|
|
71
|
+
display: 'flex',
|
|
72
|
+
flexDirection: 'column',
|
|
73
|
+
alignItems: 'center',
|
|
74
|
+
gap: '0.5rem',
|
|
75
|
+
}}
|
|
76
|
+
>
|
|
77
|
+
<Logo {...args} type={type} />
|
|
78
|
+
<span
|
|
79
|
+
style={{
|
|
80
|
+
fontSize: '12px',
|
|
81
|
+
...(args.inverse ? { color: 'rgba(255,255,255,0.6)' } : { opacity: 0.6 }),
|
|
82
|
+
}}
|
|
83
|
+
>
|
|
84
|
+
{type}
|
|
85
|
+
</span>
|
|
86
|
+
</div>
|
|
87
|
+
))}
|
|
88
|
+
</div>
|
|
89
|
+
),
|
|
90
|
+
parameters: {
|
|
91
|
+
docs: {
|
|
92
|
+
source: {
|
|
93
|
+
code: `<Logo type="WISE" />
|
|
94
|
+
<Logo type="WISE_BUSINESS" />
|
|
95
|
+
<Logo type="WISE_PLATFORM" />`,
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Below `576px` the flag-only mark is shown; at `576px` and above the full wordmark is displayed.
|
|
103
|
+
*/
|
|
104
|
+
export const Responsive: Story = {
|
|
105
|
+
argTypes: {
|
|
106
|
+
type: { table: { disable: true } },
|
|
107
|
+
inverse: { table: { disable: true } },
|
|
108
|
+
},
|
|
109
|
+
render: (args) => (
|
|
110
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: '2rem' }}>
|
|
111
|
+
<div>
|
|
112
|
+
<span style={{ fontSize: '12px', opacity: 0.6, display: 'block', marginBottom: '0.5rem' }}>
|
|
113
|
+
{'< 576px (flag only)'}
|
|
114
|
+
</span>
|
|
115
|
+
<div style={{ display: 'flex', gap: '3rem', alignItems: 'center' }}>
|
|
12
116
|
{Object.values(LogoType).map((type) => (
|
|
13
|
-
<
|
|
14
|
-
<
|
|
15
|
-
|
|
117
|
+
<span key={type} className="np-logo" style={{ display: 'inline-block' }}>
|
|
118
|
+
<style>{`.responsive-sm .np-logo-svg--size-md { display: none !important; } .responsive-sm .np-logo-svg--size-sm { display: block !important; }`}</style>
|
|
119
|
+
<span className="responsive-sm">
|
|
120
|
+
<Logo type={type} />
|
|
121
|
+
</span>
|
|
122
|
+
</span>
|
|
16
123
|
))}
|
|
17
124
|
</div>
|
|
18
|
-
|
|
125
|
+
</div>
|
|
126
|
+
<div>
|
|
127
|
+
<span style={{ fontSize: '12px', opacity: 0.6, display: 'block', marginBottom: '0.5rem' }}>
|
|
128
|
+
{'\u2265 576px (full wordmark)'}
|
|
129
|
+
</span>
|
|
130
|
+
<div style={{ display: 'flex', gap: '3rem', alignItems: 'center' }}>
|
|
19
131
|
{Object.values(LogoType).map((type) => (
|
|
20
|
-
<
|
|
21
|
-
<Logo type={type} inverse />
|
|
22
|
-
</div>
|
|
132
|
+
<Logo key={type} type={type} />
|
|
23
133
|
))}
|
|
24
134
|
</div>
|
|
25
|
-
|
|
26
|
-
|
|
135
|
+
</div>
|
|
136
|
+
</div>
|
|
137
|
+
),
|
|
138
|
+
parameters: {
|
|
139
|
+
docs: {
|
|
140
|
+
source: { code: null },
|
|
141
|
+
},
|
|
27
142
|
},
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
type Story = StoryObj<typeof Logo>;
|
|
143
|
+
};
|
|
31
144
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
145
|
+
/**
|
|
146
|
+
* All logo types on a dark background using the `inverse` prop, which renders a
|
|
147
|
+
* light-coloured version suited for dark surfaces.
|
|
148
|
+
*/
|
|
149
|
+
export const Inverse: Story = {
|
|
150
|
+
argTypes: {
|
|
151
|
+
type: { table: { disable: true } },
|
|
152
|
+
inverse: { table: { disable: true } },
|
|
153
|
+
},
|
|
154
|
+
decorators: [
|
|
155
|
+
(Story) => (
|
|
156
|
+
<div
|
|
157
|
+
style={{
|
|
158
|
+
display: 'flex',
|
|
159
|
+
gap: '3rem',
|
|
160
|
+
alignItems: 'center',
|
|
161
|
+
backgroundColor: 'var(--color-background-screen-dark, #0e0f0c)',
|
|
162
|
+
padding: '2rem',
|
|
163
|
+
borderRadius: '8px',
|
|
164
|
+
}}
|
|
165
|
+
>
|
|
166
|
+
<Story />
|
|
167
|
+
</div>
|
|
168
|
+
),
|
|
169
|
+
],
|
|
170
|
+
render: (args) => (
|
|
171
|
+
<>
|
|
172
|
+
{Object.values(LogoType).map((type) => (
|
|
173
|
+
<div
|
|
174
|
+
key={type}
|
|
175
|
+
style={{
|
|
176
|
+
display: 'flex',
|
|
177
|
+
flexDirection: 'column',
|
|
178
|
+
alignItems: 'center',
|
|
179
|
+
gap: '0.5rem',
|
|
180
|
+
}}
|
|
181
|
+
>
|
|
182
|
+
<Logo type={type} inverse />
|
|
183
|
+
<span style={{ fontSize: '12px', color: 'rgba(255,255,255,0.6)' }}>{type}</span>
|
|
184
|
+
</div>
|
|
185
|
+
))}
|
|
186
|
+
</>
|
|
187
|
+
),
|
|
188
|
+
parameters: {
|
|
189
|
+
docs: {
|
|
190
|
+
source: {
|
|
191
|
+
code: `<Logo type="WISE" inverse />
|
|
192
|
+
<Logo type="WISE_BUSINESS" inverse />
|
|
193
|
+
<Logo type="WISE_PLATFORM" inverse />`,
|
|
194
|
+
},
|
|
195
|
+
},
|
|
36
196
|
},
|
|
37
197
|
};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Meta, StoryObj } from '@storybook/react-webpack5';
|
|
2
|
-
|
|
2
|
+
|
|
3
|
+
import Logo, { LogoType } from '.';
|
|
3
4
|
import { withVariantConfig } from '../../.storybook/helpers';
|
|
4
5
|
|
|
5
6
|
const meta: Meta<typeof Logo> = {
|
|
@@ -7,14 +8,46 @@ const meta: Meta<typeof Logo> = {
|
|
|
7
8
|
tags: ['!autodocs', '!manifest'],
|
|
8
9
|
title: 'Content/Logo/Tests',
|
|
9
10
|
};
|
|
11
|
+
|
|
10
12
|
export default meta;
|
|
11
13
|
|
|
12
14
|
type Story = StoryObj<typeof Logo>;
|
|
13
15
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
16
|
+
const AllVariants = () => (
|
|
17
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: '2rem' }}>
|
|
18
|
+
<div style={{ display: 'flex', gap: '2rem', alignItems: 'center' }}>
|
|
19
|
+
{Object.values(LogoType).map((type) => (
|
|
20
|
+
<Logo key={type} type={type} />
|
|
21
|
+
))}
|
|
22
|
+
</div>
|
|
23
|
+
<div
|
|
24
|
+
style={{
|
|
25
|
+
display: 'flex',
|
|
26
|
+
gap: '2rem',
|
|
27
|
+
alignItems: 'center',
|
|
28
|
+
backgroundColor: 'var(--color-content-primary)',
|
|
29
|
+
padding: '1rem',
|
|
30
|
+
borderRadius: '8px',
|
|
31
|
+
}}
|
|
32
|
+
>
|
|
33
|
+
{Object.values(LogoType).map((type) => (
|
|
34
|
+
<Logo key={type} type={type} inverse />
|
|
35
|
+
))}
|
|
36
|
+
</div>
|
|
37
|
+
</div>
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
export const Variants: Story = {
|
|
41
|
+
render: () => <AllVariants />,
|
|
42
|
+
...withVariantConfig(['default', 'dark', 'bright-green', 'forest-green']),
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export const RTL: Story = {
|
|
46
|
+
render: () => <AllVariants />,
|
|
47
|
+
...withVariantConfig(['rtl']),
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export const Zoom400: Story = {
|
|
51
|
+
render: () => <AllVariants />,
|
|
52
|
+
...withVariantConfig(['400%']),
|
|
20
53
|
};
|
package/src/logo/Logo.tsx
CHANGED
|
@@ -33,7 +33,10 @@ export enum LogoType {
|
|
|
33
33
|
export interface LogoProps {
|
|
34
34
|
/** Extra classes applied to Logo */
|
|
35
35
|
className?: string;
|
|
36
|
-
/**
|
|
36
|
+
/**
|
|
37
|
+
* Renders a light-coloured version suited for dark backgrounds.
|
|
38
|
+
* @default false
|
|
39
|
+
*/
|
|
37
40
|
inverse?: boolean;
|
|
38
41
|
/**
|
|
39
42
|
* What type of logo to display
|
|
@@ -48,6 +51,12 @@ const labelByType = {
|
|
|
48
51
|
WISE_PLATFORM: 'Wise Platform',
|
|
49
52
|
} satisfies Record<`${LogoType}`, string>;
|
|
50
53
|
|
|
54
|
+
/**
|
|
55
|
+
* Renders the Wise wordmark logo. Responsive — shows the flag-only mark on small viewports
|
|
56
|
+
* and the full wordmark on ≥576px (small breakpoint and above).
|
|
57
|
+
*
|
|
58
|
+
* @see {@link https://wise.design/foundations/logo Design Spec}
|
|
59
|
+
*/
|
|
51
60
|
export default function Logo({ className, inverse, type = 'WISE' }: LogoProps) {
|
|
52
61
|
const LogoSm =
|
|
53
62
|
svgPaths[`WISE_FLAG${type === 'WISE_PLATFORM' ? '_PLATFORM' : ''}${inverse ? '_INVERSE' : ''}`];
|
package/src/main.css
CHANGED
|
@@ -25824,7 +25824,7 @@ a[data-toggle="tooltip"] {
|
|
|
25824
25824
|
--color-sentiment-interactive-control: #CB272F;
|
|
25825
25825
|
--color-sentiment-interactive-control-hover: #B8232B;
|
|
25826
25826
|
--color-sentiment-interactive-control-active: #A72027;
|
|
25827
|
-
--color-sentiment-background-surface: #
|
|
25827
|
+
--color-sentiment-background-surface: #CB272F;
|
|
25828
25828
|
--color-sentiment-background-surface-hover: #B8232B;
|
|
25829
25829
|
--color-sentiment-background-surface-active: #A72027;
|
|
25830
25830
|
}
|
|
@@ -29430,7 +29430,7 @@ html:not([dir="rtl"]) .np-flow-navigation--sm .np-flow-navigation__stepper {
|
|
|
29430
29430
|
margin-top: -2px;
|
|
29431
29431
|
}
|
|
29432
29432
|
|
|
29433
|
-
@container (
|
|
29433
|
+
@container (width > 308px) {
|
|
29434
29434
|
.wds-list-item-gridWrapper .wds-list-item-control-wrapper {
|
|
29435
29435
|
height: var(--wds-list-item-control-wrapper-height);
|
|
29436
29436
|
align-content: center;
|
|
@@ -29527,7 +29527,7 @@ html:not([dir="rtl"]) .np-flow-navigation--sm .np-flow-navigation__stepper {
|
|
|
29527
29527
|
}
|
|
29528
29528
|
}
|
|
29529
29529
|
|
|
29530
|
-
@container (
|
|
29530
|
+
@container (240px < width <= 308px) {
|
|
29531
29531
|
.wds-list-item-gridWrapper .wds-list-item-media-image {
|
|
29532
29532
|
-o-object-position: bottom left;
|
|
29533
29533
|
object-position: bottom left;
|
|
@@ -29659,7 +29659,7 @@ html:not([dir="rtl"]) .np-flow-navigation--sm .np-flow-navigation__stepper {
|
|
|
29659
29659
|
}
|
|
29660
29660
|
}
|
|
29661
29661
|
|
|
29662
|
-
@container (
|
|
29662
|
+
@container (width <= 240px) {
|
|
29663
29663
|
.wds-list-item-gridWrapper .wds-list-item-control-wrapper {
|
|
29664
29664
|
align-content: start;
|
|
29665
29665
|
}
|
|
@@ -29987,7 +29987,7 @@ html:not([dir="rtl"]) .np-flow-navigation--sm .np-flow-navigation__stepper {
|
|
|
29987
29987
|
justify-content: center;
|
|
29988
29988
|
}
|
|
29989
29989
|
|
|
29990
|
-
@container (
|
|
29990
|
+
@container (width > 308px) {
|
|
29991
29991
|
.wds-list-item-titles,
|
|
29992
29992
|
.wds-list-item-value {
|
|
29993
29993
|
min-height: 100%;
|
|
@@ -32429,12 +32429,50 @@ html:not([dir="rtl"]) .np-navigation-option {
|
|
|
32429
32429
|
}
|
|
32430
32430
|
}
|
|
32431
32431
|
|
|
32432
|
-
.
|
|
32433
|
-
|
|
32432
|
+
.status-circle.negative,
|
|
32433
|
+
.status-circle.error {
|
|
32434
|
+
background-color: var(--color-sentiment-interactive-primary, var(--color-sentiment-negative));
|
|
32434
32435
|
}
|
|
32435
32436
|
|
|
32436
|
-
.
|
|
32437
|
-
|
|
32437
|
+
.status-circle.negative .status-icon,
|
|
32438
|
+
.status-circle.error .status-icon {
|
|
32439
|
+
color: var(--color-sentiment-interactive-control, var(--color-sentiment-negative-secondary));
|
|
32440
|
+
}
|
|
32441
|
+
|
|
32442
|
+
.status-circle.positive,
|
|
32443
|
+
.status-circle.success {
|
|
32444
|
+
background-color: var(--color-sentiment-interactive-primary, var(--color-sentiment-positive));
|
|
32445
|
+
}
|
|
32446
|
+
|
|
32447
|
+
.status-circle.positive .status-icon,
|
|
32448
|
+
.status-circle.success .status-icon {
|
|
32449
|
+
color: var(--color-sentiment-interactive-control, var(--color-sentiment-positive-secondary));
|
|
32450
|
+
}
|
|
32451
|
+
|
|
32452
|
+
.status-circle.warning,
|
|
32453
|
+
.status-circle.pending {
|
|
32454
|
+
background-color: var(--color-sentiment-interactive-primary, var(--color-sentiment-warning));
|
|
32455
|
+
}
|
|
32456
|
+
|
|
32457
|
+
.status-circle.warning .status-icon,
|
|
32458
|
+
.status-circle.pending .status-icon {
|
|
32459
|
+
color: var(--color-sentiment-interactive-control, var(--color-dark));
|
|
32460
|
+
}
|
|
32461
|
+
|
|
32462
|
+
.status-circle.neutral,
|
|
32463
|
+
.status-circle.info {
|
|
32464
|
+
background-color: #5d7079;
|
|
32465
|
+
background-color: var(--color-sentiment-interactive-primary, var(--color-content-secondary));
|
|
32466
|
+
}
|
|
32467
|
+
|
|
32468
|
+
.status-circle.neutral .status-icon,
|
|
32469
|
+
.status-circle.info .status-icon {
|
|
32470
|
+
color: var(--color-sentiment-interactive-control, var(--color-contrast-overlay));
|
|
32471
|
+
}
|
|
32472
|
+
|
|
32473
|
+
.np-theme-personal--bright-green .status-circle.neutral .status-icon,
|
|
32474
|
+
.np-theme-personal--bright-green .status-circle.info .status-icon {
|
|
32475
|
+
color: var(--color-sentiment-interactive-control, var(--color-white));
|
|
32438
32476
|
}
|
|
32439
32477
|
|
|
32440
32478
|
.tw-stepper {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Meta, StoryObj } from '@storybook/react-webpack5';
|
|
2
|
-
import { within, userEvent } from 'storybook/test';
|
|
2
|
+
import { within, userEvent, expect } from 'storybook/test';
|
|
3
3
|
import { Lock } from '@transferwise/icons';
|
|
4
4
|
import { useState } from 'react';
|
|
5
5
|
|
|
@@ -155,6 +155,15 @@ export const BalanceCurrencies: Story = {
|
|
|
155
155
|
},
|
|
156
156
|
};
|
|
157
157
|
|
|
158
|
+
export const WithDecimals: Story = {
|
|
159
|
+
args: {
|
|
160
|
+
currencies: [],
|
|
161
|
+
selectedCurrency: exampleCurrency.eur,
|
|
162
|
+
decimals: 4,
|
|
163
|
+
amount: 1234.1412,
|
|
164
|
+
},
|
|
165
|
+
};
|
|
166
|
+
|
|
158
167
|
export const OpenedInput: Story = {
|
|
159
168
|
...MultipleCurrencies,
|
|
160
169
|
play: async ({ canvasElement }) => {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Meta, StoryObj } from '@storybook/react-webpack5';
|
|
2
|
-
import { within, fireEvent } from 'storybook/test';
|
|
2
|
+
import { within, userEvent, fireEvent, expect } from 'storybook/test';
|
|
3
3
|
import { allModes } from '../../.storybook/modes';
|
|
4
4
|
import { lorem500 } from '../test-utils';
|
|
5
5
|
import { Field } from '../field/Field';
|
|
@@ -102,3 +102,143 @@ export const SmoothScrollReset: Story = {
|
|
|
102
102
|
},
|
|
103
103
|
},
|
|
104
104
|
};
|
|
105
|
+
|
|
106
|
+
export const WithDecimals: Story = {
|
|
107
|
+
args: {
|
|
108
|
+
currencies: [],
|
|
109
|
+
decimals: 4,
|
|
110
|
+
amount: 1234.5678,
|
|
111
|
+
},
|
|
112
|
+
play: async ({ canvas }) => {
|
|
113
|
+
const input = canvas.getByRole('textbox');
|
|
114
|
+
await expect(input).toHaveValue('1,234.5678');
|
|
115
|
+
await userEvent.clear(input);
|
|
116
|
+
await userEvent.type(input, '99.123456789');
|
|
117
|
+
await userEvent.tab();
|
|
118
|
+
await expect(input).toHaveValue('99.1235');
|
|
119
|
+
},
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
const eur = {
|
|
123
|
+
value: 'EUR',
|
|
124
|
+
label: 'EUR',
|
|
125
|
+
note: 'Euro',
|
|
126
|
+
currency: 'eur',
|
|
127
|
+
} as const;
|
|
128
|
+
|
|
129
|
+
const bhd = {
|
|
130
|
+
value: 'BHD',
|
|
131
|
+
label: 'BHD',
|
|
132
|
+
note: 'Bahraini dinar',
|
|
133
|
+
currency: 'bhd',
|
|
134
|
+
} as const;
|
|
135
|
+
|
|
136
|
+
const jpy = {
|
|
137
|
+
value: 'JPY',
|
|
138
|
+
label: 'JPY',
|
|
139
|
+
note: 'Japanese yen',
|
|
140
|
+
currency: 'jpy',
|
|
141
|
+
} as const;
|
|
142
|
+
|
|
143
|
+
const sharedProps = {
|
|
144
|
+
currencies: [],
|
|
145
|
+
onAmountChange: () => {},
|
|
146
|
+
onCurrencyChange: () => {},
|
|
147
|
+
} as const;
|
|
148
|
+
|
|
149
|
+
function Row({
|
|
150
|
+
label,
|
|
151
|
+
amount,
|
|
152
|
+
currency,
|
|
153
|
+
decimals,
|
|
154
|
+
}: {
|
|
155
|
+
label: string;
|
|
156
|
+
amount: number;
|
|
157
|
+
currency: { value: string; label: string; note: string; currency: string };
|
|
158
|
+
decimals: number;
|
|
159
|
+
}) {
|
|
160
|
+
return (
|
|
161
|
+
<div
|
|
162
|
+
style={{
|
|
163
|
+
display: 'grid',
|
|
164
|
+
gridTemplateColumns: '240px 1fr 1fr',
|
|
165
|
+
gap: 12,
|
|
166
|
+
alignItems: 'center',
|
|
167
|
+
}}
|
|
168
|
+
>
|
|
169
|
+
<Body as="span" style={{ fontSize: 13, color: '#6b7280' }}>
|
|
170
|
+
{label}
|
|
171
|
+
</Body>
|
|
172
|
+
<Field label={`No decimals prop (${currency.value})`}>
|
|
173
|
+
<MoneyInput {...sharedProps} selectedCurrency={currency} amount={amount} />
|
|
174
|
+
</Field>
|
|
175
|
+
<Field label={`decimals={${decimals}} (${currency.value})`}>
|
|
176
|
+
<MoneyInput
|
|
177
|
+
{...sharedProps}
|
|
178
|
+
selectedCurrency={currency}
|
|
179
|
+
amount={amount}
|
|
180
|
+
decimals={decimals}
|
|
181
|
+
/>
|
|
182
|
+
</Field>
|
|
183
|
+
</div>
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Side-by-side comparison of formatAmount (no decimals prop) vs formatNumber (with decimals prop)
|
|
189
|
+
* when decimals matches the currency default. Differences indicate a formatting parity issue.
|
|
190
|
+
*
|
|
191
|
+
* Problem 1: Trailing zero stripping — formatAmount strips ".00" for whole numbers,
|
|
192
|
+
* formatNumber always pads to the requested precision.
|
|
193
|
+
*
|
|
194
|
+
* Problem 2: BHD decimal count — formatAmount renders 2 fractional digits for BHD,
|
|
195
|
+
* but the currencyDecimals map says BHD has 3. formatNumber with decimals=3 shows 3.
|
|
196
|
+
*/
|
|
197
|
+
export const DecimalsFormatParity: Story = {
|
|
198
|
+
render: () => (
|
|
199
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
|
|
200
|
+
<Body as="p" style={{ fontWeight: 600, marginTop: 8 }}>
|
|
201
|
+
EUR — whole numbers (trailing zero stripping)
|
|
202
|
+
</Body>
|
|
203
|
+
<Row label="amount=0" amount={0} currency={eur} decimals={2} />
|
|
204
|
+
<Row label="amount=1" amount={1} currency={eur} decimals={2} />
|
|
205
|
+
<Row label="amount=42" amount={42} currency={eur} decimals={2} />
|
|
206
|
+
<Row label="amount=100" amount={100} currency={eur} decimals={2} />
|
|
207
|
+
<Row label="amount=1000" amount={1000} currency={eur} decimals={2} />
|
|
208
|
+
<Row label="amount=9999999" amount={9999999} currency={eur} decimals={2} />
|
|
209
|
+
|
|
210
|
+
<Body as="p" style={{ fontWeight: 600, marginTop: 8 }}>
|
|
211
|
+
EUR — fractional amounts (should match)
|
|
212
|
+
</Body>
|
|
213
|
+
<Row label="amount=0.01" amount={0.01} currency={eur} decimals={2} />
|
|
214
|
+
<Row label="amount=0.1" amount={0.1} currency={eur} decimals={2} />
|
|
215
|
+
<Row label="amount=99.9" amount={99.9} currency={eur} decimals={2} />
|
|
216
|
+
<Row label="amount=1234567.89" amount={1234567.89} currency={eur} decimals={2} />
|
|
217
|
+
|
|
218
|
+
<Body as="p" style={{ fontWeight: 600, marginTop: 8 }}>
|
|
219
|
+
BHD — all amounts (formatAmount uses 2 digits, formatNumber uses 3)
|
|
220
|
+
</Body>
|
|
221
|
+
<Row label="amount=0" amount={0} currency={bhd} decimals={3} />
|
|
222
|
+
<Row label="amount=0.001" amount={0.001} currency={bhd} decimals={3} />
|
|
223
|
+
<Row label="amount=0.01" amount={0.01} currency={bhd} decimals={3} />
|
|
224
|
+
<Row label="amount=0.1" amount={0.1} currency={bhd} decimals={3} />
|
|
225
|
+
<Row label="amount=42" amount={42} currency={bhd} decimals={3} />
|
|
226
|
+
<Row label="amount=99.9" amount={99.9} currency={bhd} decimals={3} />
|
|
227
|
+
<Row label="amount=999.99" amount={999.99} currency={bhd} decimals={3} />
|
|
228
|
+
<Row label="amount=1234567.89" amount={1234567.89} currency={bhd} decimals={3} />
|
|
229
|
+
|
|
230
|
+
<Body as="p" style={{ fontWeight: 600, marginTop: 8 }}>
|
|
231
|
+
JPY — zero-decimal currency (should always match)
|
|
232
|
+
</Body>
|
|
233
|
+
<Row label="amount=0" amount={0} currency={jpy} decimals={0} />
|
|
234
|
+
<Row label="amount=1" amount={1} currency={jpy} decimals={0} />
|
|
235
|
+
<Row label="amount=1000" amount={1000} currency={jpy} decimals={0} />
|
|
236
|
+
<Row label="amount=1234567" amount={1234567} currency={jpy} decimals={0} />
|
|
237
|
+
</div>
|
|
238
|
+
),
|
|
239
|
+
parameters: {
|
|
240
|
+
chromatic: {
|
|
241
|
+
disableSnapshot: true,
|
|
242
|
+
},
|
|
243
|
+
},
|
|
244
|
+
};
|
|
@@ -253,6 +253,45 @@ describe('Money Input', () => {
|
|
|
253
253
|
await userEvent.tab();
|
|
254
254
|
expect(input).toHaveValue('1,234,567.65');
|
|
255
255
|
});
|
|
256
|
+
|
|
257
|
+
it('uses custom decimals when specified', async () => {
|
|
258
|
+
customRender({ decimals: 4 });
|
|
259
|
+
const input = getInput();
|
|
260
|
+
await userEvent.clear(input);
|
|
261
|
+
await userEvent.type(input, '1234.56789');
|
|
262
|
+
await userEvent.tab();
|
|
263
|
+
expect(input).toHaveValue('1,234.5679');
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
it('uses custom decimals of 0', async () => {
|
|
267
|
+
customRender({ decimals: 0 });
|
|
268
|
+
const input = getInput();
|
|
269
|
+
await userEvent.clear(input);
|
|
270
|
+
await userEvent.type(input, '1234.56');
|
|
271
|
+
await userEvent.tab();
|
|
272
|
+
expect(input).toHaveValue('1,235');
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
it('ignores custom decimals override for zero-decimal currencies like JPY', async () => {
|
|
276
|
+
const jpyCurrency: CurrencyOptionItem = {
|
|
277
|
+
value: 'JPY',
|
|
278
|
+
label: 'JPY',
|
|
279
|
+
note: 'Japanese yen',
|
|
280
|
+
currency: 'jpy',
|
|
281
|
+
};
|
|
282
|
+
customRender({
|
|
283
|
+
decimals: 4,
|
|
284
|
+
selectedCurrency: jpyCurrency,
|
|
285
|
+
currencies: [jpyCurrency],
|
|
286
|
+
amount: 1234,
|
|
287
|
+
});
|
|
288
|
+
const input = getInput();
|
|
289
|
+
expect(input).toHaveValue('1,234');
|
|
290
|
+
await userEvent.clear(input);
|
|
291
|
+
await userEvent.type(input, '5678.9999');
|
|
292
|
+
await userEvent.tab();
|
|
293
|
+
expect(input).toHaveValue('5,679');
|
|
294
|
+
});
|
|
256
295
|
});
|
|
257
296
|
|
|
258
297
|
it('calls onAmountChange with parsed and formatted value', async () => {
|
|
@@ -263,6 +302,12 @@ describe('Money Input', () => {
|
|
|
263
302
|
expect(initialProps.onAmountChange).toHaveBeenCalledWith(1000.65);
|
|
264
303
|
});
|
|
265
304
|
|
|
305
|
+
it('calls onAmountChange with value rounded to custom decimals', async () => {
|
|
306
|
+
customRender({ decimals: 4, amount: null });
|
|
307
|
+
await userEvent.type(getInput(), '12.34567');
|
|
308
|
+
expect(initialProps.onAmountChange).toHaveBeenCalledWith(12.3457);
|
|
309
|
+
});
|
|
310
|
+
|
|
266
311
|
it('calls onAmountChange when input value is empty', async () => {
|
|
267
312
|
customRender();
|
|
268
313
|
await userEvent.clear(getInput());
|