@transferwise/components 46.133.1 → 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/main.css +43 -5
- 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/main.css +43 -5
- package/build/styles/sentimentSurface/SentimentSurface.css +1 -1
- package/build/styles/statusIcon/StatusIcon.css +35 -4
- 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 +6 -6
- package/src/main.css +43 -5
- 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
|
@@ -1,102 +1,142 @@
|
|
|
1
1
|
import { Meta, StoryObj } from '@storybook/react-webpack5';
|
|
2
2
|
|
|
3
|
-
import { Sentiment
|
|
3
|
+
import { Sentiment } from '../common';
|
|
4
|
+
import SentimentSurface from '../sentimentSurface';
|
|
4
5
|
|
|
5
6
|
import StatusIcon, { StatusIconSentiment } from './StatusIcon';
|
|
6
|
-
|
|
7
|
+
|
|
8
|
+
const sentiments = [
|
|
9
|
+
Sentiment.POSITIVE,
|
|
10
|
+
Sentiment.NEGATIVE,
|
|
11
|
+
Sentiment.WARNING,
|
|
12
|
+
Sentiment.NEUTRAL,
|
|
13
|
+
Sentiment.PENDING,
|
|
14
|
+
] as const;
|
|
15
|
+
|
|
16
|
+
const sizes = [16, 24, 32, 40, 48, 56, 72] as const;
|
|
17
|
+
|
|
18
|
+
const label: Record<string, string> = {
|
|
19
|
+
positive: 'Positive',
|
|
20
|
+
negative: 'Negative',
|
|
21
|
+
warning: 'Warning',
|
|
22
|
+
neutral: 'Neutral',
|
|
23
|
+
pending: 'Pending',
|
|
24
|
+
};
|
|
7
25
|
|
|
8
26
|
export default {
|
|
9
27
|
component: StatusIcon,
|
|
10
28
|
title: 'Other/StatusIcon',
|
|
11
|
-
|
|
12
|
-
argTypes: {
|
|
13
|
-
iconLabel: {
|
|
14
|
-
control: 'text',
|
|
15
|
-
},
|
|
16
|
-
},
|
|
17
29
|
} satisfies Meta<typeof StatusIcon>;
|
|
18
30
|
|
|
19
31
|
type Story = StoryObj<typeof StatusIcon>;
|
|
20
32
|
|
|
21
|
-
export const
|
|
33
|
+
export const Playground: Story = {
|
|
34
|
+
args: {
|
|
35
|
+
size: 40,
|
|
36
|
+
},
|
|
37
|
+
};
|
|
22
38
|
|
|
23
39
|
/**
|
|
24
|
-
*
|
|
40
|
+
* All available sentiments at the selected size.
|
|
25
41
|
*/
|
|
26
|
-
export const
|
|
27
|
-
|
|
28
|
-
|
|
42
|
+
export const Sentiments: Story = {
|
|
43
|
+
argTypes: {
|
|
44
|
+
sentiment: { table: { disable: true } },
|
|
29
45
|
},
|
|
46
|
+
render: ({ size }) => (
|
|
47
|
+
<div style={{ display: 'flex', gap: '16px', alignItems: 'center' }}>
|
|
48
|
+
{sentiments.map((sentiment) => (
|
|
49
|
+
<div
|
|
50
|
+
key={sentiment}
|
|
51
|
+
style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: '4px' }}
|
|
52
|
+
>
|
|
53
|
+
<StatusIcon sentiment={sentiment as StatusIconSentiment} size={size} />
|
|
54
|
+
<span style={{ fontSize: '11px' }}>{label[sentiment]}</span>
|
|
55
|
+
</div>
|
|
56
|
+
))}
|
|
57
|
+
</div>
|
|
58
|
+
),
|
|
30
59
|
};
|
|
31
60
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
style={{
|
|
52
|
-
display: 'flex',
|
|
53
|
-
justifyContent: 'space-between',
|
|
54
|
-
flexDirection: 'column',
|
|
55
|
-
minHeight: '150px',
|
|
56
|
-
alignItems: 'center',
|
|
57
|
-
}}
|
|
58
|
-
>
|
|
59
|
-
{([16, 24, 32, 40, 48, 56, 72] as const).map((size) => {
|
|
60
|
-
return (
|
|
61
|
-
<StatusIcon key={size} size={size} sentiment={sentiment as StatusIconSentiment} />
|
|
62
|
-
);
|
|
63
|
-
})}
|
|
64
|
-
</span>
|
|
65
|
-
);
|
|
66
|
-
})}
|
|
67
|
-
</span>
|
|
61
|
+
/**
|
|
62
|
+
* All available sizes at the selected sentiment.
|
|
63
|
+
*/
|
|
64
|
+
export const Sizes: Story = {
|
|
65
|
+
argTypes: {
|
|
66
|
+
size: { table: { disable: true } },
|
|
67
|
+
},
|
|
68
|
+
render: ({ sentiment }) => (
|
|
69
|
+
<div style={{ display: 'flex', gap: '16px', alignItems: 'center' }}>
|
|
70
|
+
{sizes.map((size) => (
|
|
71
|
+
<div
|
|
72
|
+
key={size}
|
|
73
|
+
style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: '4px' }}
|
|
74
|
+
>
|
|
75
|
+
<StatusIcon sentiment={sentiment} size={size} />
|
|
76
|
+
<span style={{ fontSize: '11px' }}>{size}</span>
|
|
77
|
+
</div>
|
|
78
|
+
))}
|
|
79
|
+
</div>
|
|
68
80
|
),
|
|
69
81
|
};
|
|
70
82
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
}}
|
|
83
|
+
/**
|
|
84
|
+
* `StatusIcon` is sentiment-aware and will automatically adjust its colours when placed inside
|
|
85
|
+
* a [SentimentSurface](?path=/docs/foundations-sentimentsurface--docs) component — no extra
|
|
86
|
+
* props needed. Each row below is a `SentimentSurface` at either `base` or `elevated` emphasis (and a last row with no sentiment for reference).
|
|
87
|
+
*/
|
|
88
|
+
export const SentimentAwareness: Story = {
|
|
89
|
+
argTypes: {
|
|
90
|
+
sentiment: { table: { disable: true } },
|
|
91
|
+
},
|
|
92
|
+
render: ({ size }) => {
|
|
93
|
+
const surfaceSentiments = ['success', 'warning', 'negative', 'neutral'] as const;
|
|
94
|
+
|
|
95
|
+
return (
|
|
96
|
+
<div style={{ display: 'flex', flexDirection: 'column' }}>
|
|
97
|
+
{surfaceSentiments.flatMap((sentiment) => [
|
|
98
|
+
<SentimentSurface
|
|
99
|
+
key={`${sentiment}-base`}
|
|
100
|
+
sentiment={sentiment}
|
|
101
|
+
emphasis="base"
|
|
102
|
+
style={{ display: 'flex', alignItems: 'center', padding: '8px', gap: '8px' }}
|
|
91
103
|
>
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
)
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
104
|
+
<div
|
|
105
|
+
style={{ width: '120px', fontSize: '11px', fontWeight: 'bold', paddingLeft: '8px' }}
|
|
106
|
+
>
|
|
107
|
+
{sentiment} (base)
|
|
108
|
+
</div>
|
|
109
|
+
<StatusIcon sentiment={sentiment as StatusIconSentiment} size={size} />
|
|
110
|
+
</SentimentSurface>,
|
|
111
|
+
<SentimentSurface
|
|
112
|
+
key={`${sentiment}-elevated`}
|
|
113
|
+
sentiment={sentiment}
|
|
114
|
+
emphasis="elevated"
|
|
115
|
+
style={{ display: 'flex', alignItems: 'center', padding: '8px', gap: '8px' }}
|
|
116
|
+
>
|
|
117
|
+
<div
|
|
118
|
+
style={{ width: '120px', fontSize: '11px', fontWeight: 'bold', paddingLeft: '8px' }}
|
|
119
|
+
>
|
|
120
|
+
{sentiment} (elevated)
|
|
121
|
+
</div>
|
|
122
|
+
<StatusIcon sentiment={sentiment as StatusIconSentiment} size={size} />
|
|
123
|
+
</SentimentSurface>,
|
|
124
|
+
])}
|
|
125
|
+
|
|
126
|
+
{/* Row without a SentimentSurface wrapper */}
|
|
127
|
+
<div style={{ display: 'flex', alignItems: 'center', padding: '8px', gap: '8px' }}>
|
|
128
|
+
<div style={{ width: '120px', fontSize: '11px', fontWeight: 'bold', paddingLeft: '8px' }}>
|
|
129
|
+
none
|
|
130
|
+
</div>
|
|
131
|
+
<StatusIcon sentiment="pending" size={size} />
|
|
132
|
+
</div>
|
|
133
|
+
</div>
|
|
134
|
+
);
|
|
135
|
+
},
|
|
136
|
+
parameters: {
|
|
137
|
+
docs: {
|
|
138
|
+
source: { type: 'dynamic' },
|
|
139
|
+
canvas: { sourceState: 'hidden' },
|
|
140
|
+
},
|
|
141
|
+
},
|
|
102
142
|
};
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { Meta, StoryObj } from '@storybook/react-webpack5';
|
|
2
|
+
|
|
3
|
+
import { Sentiment, Size } from '../common';
|
|
4
|
+
import SentimentSurface from '../sentimentSurface';
|
|
5
|
+
import { withVariantConfig } from '../../.storybook/helpers';
|
|
6
|
+
import { allModes } from '../../.storybook/modes';
|
|
7
|
+
|
|
8
|
+
import StatusIcon, { StatusIconSentiment } from './StatusIcon';
|
|
9
|
+
|
|
10
|
+
export default {
|
|
11
|
+
title: 'Other/StatusIcon/Tests',
|
|
12
|
+
component: StatusIcon,
|
|
13
|
+
tags: ['!autodocs', '!manifest'],
|
|
14
|
+
} satisfies Meta<typeof StatusIcon>;
|
|
15
|
+
|
|
16
|
+
type Story = StoryObj<typeof StatusIcon>;
|
|
17
|
+
|
|
18
|
+
const sizes = [16, 24, 32, 40, 48, 56, 72] as const;
|
|
19
|
+
|
|
20
|
+
const surfaceSentiments = ['success', 'warning', 'negative', 'neutral'] as const;
|
|
21
|
+
|
|
22
|
+
// Cycle through different sentiments per size so each row shows varied icons
|
|
23
|
+
const iconSentiments: StatusIconSentiment[] = [
|
|
24
|
+
Sentiment.POSITIVE,
|
|
25
|
+
Sentiment.NEGATIVE,
|
|
26
|
+
Sentiment.WARNING,
|
|
27
|
+
Sentiment.NEUTRAL,
|
|
28
|
+
Sentiment.PENDING,
|
|
29
|
+
Sentiment.POSITIVE,
|
|
30
|
+
Sentiment.NEGATIVE,
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
const rowStyle = {
|
|
34
|
+
display: 'flex',
|
|
35
|
+
alignItems: 'center',
|
|
36
|
+
padding: '8px',
|
|
37
|
+
gap: '8px',
|
|
38
|
+
} as const;
|
|
39
|
+
|
|
40
|
+
const labelStyle = {
|
|
41
|
+
width: '120px',
|
|
42
|
+
fontSize: '11px',
|
|
43
|
+
fontWeight: 'bold',
|
|
44
|
+
paddingLeft: '8px',
|
|
45
|
+
flexShrink: 0,
|
|
46
|
+
} as const;
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* All sentiments, emphasis levels, and sizes across all themes for visual regression testing.
|
|
50
|
+
*/
|
|
51
|
+
export const Variants: Story = {
|
|
52
|
+
render: () => (
|
|
53
|
+
<div style={{ display: 'flex', flexDirection: 'column' }}>
|
|
54
|
+
{surfaceSentiments.flatMap((sentiment) => [
|
|
55
|
+
<SentimentSurface
|
|
56
|
+
key={`${sentiment}-base`}
|
|
57
|
+
sentiment={sentiment}
|
|
58
|
+
emphasis="base"
|
|
59
|
+
style={rowStyle}
|
|
60
|
+
>
|
|
61
|
+
<div style={labelStyle}>{sentiment} (base)</div>
|
|
62
|
+
{sizes.map((size, i) => (
|
|
63
|
+
<StatusIcon key={size} sentiment={iconSentiments[i]} size={size} />
|
|
64
|
+
))}
|
|
65
|
+
</SentimentSurface>,
|
|
66
|
+
<SentimentSurface
|
|
67
|
+
key={`${sentiment}-elevated`}
|
|
68
|
+
sentiment={sentiment}
|
|
69
|
+
emphasis="elevated"
|
|
70
|
+
style={rowStyle}
|
|
71
|
+
>
|
|
72
|
+
<div style={labelStyle}>{sentiment} (elevated)</div>
|
|
73
|
+
{sizes.map((size, i) => (
|
|
74
|
+
<StatusIcon key={size} sentiment={iconSentiments[i]} size={size} />
|
|
75
|
+
))}
|
|
76
|
+
</SentimentSurface>,
|
|
77
|
+
])}
|
|
78
|
+
|
|
79
|
+
{/* Row without a SentimentSurface wrapper — standalone fallback colours */}
|
|
80
|
+
<div style={rowStyle}>
|
|
81
|
+
<div style={labelStyle}>none</div>
|
|
82
|
+
{sizes.map((size, i) => (
|
|
83
|
+
<StatusIcon key={size} sentiment={iconSentiments[i]} size={size} />
|
|
84
|
+
))}
|
|
85
|
+
</div>
|
|
86
|
+
</div>
|
|
87
|
+
),
|
|
88
|
+
parameters: {
|
|
89
|
+
padding: '16px',
|
|
90
|
+
variants: ['default', 'dark', 'bright-green', 'forest-green'],
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
export const RTL: Story = {
|
|
95
|
+
render: Variants.render,
|
|
96
|
+
...withVariantConfig(['rtl']),
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* @deprecated Legacy `Size.SMALL | Size.MEDIUM | Size.LARGE` values still work but
|
|
101
|
+
* consumers should migrate to numeric sizes.
|
|
102
|
+
*/
|
|
103
|
+
export const LegacySizes: Story = {
|
|
104
|
+
render: () => {
|
|
105
|
+
const legacySizes = [
|
|
106
|
+
{ value: Size.SMALL, label: 'Size.SMALL' },
|
|
107
|
+
{ value: Size.MEDIUM, label: 'Size.MEDIUM' },
|
|
108
|
+
{ value: Size.LARGE, label: 'Size.LARGE' },
|
|
109
|
+
] as const;
|
|
110
|
+
|
|
111
|
+
return (
|
|
112
|
+
<div style={{ display: 'flex', gap: '16px', alignItems: 'center' }}>
|
|
113
|
+
{legacySizes.map(({ value, label }) => (
|
|
114
|
+
<div
|
|
115
|
+
key={label}
|
|
116
|
+
style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: '4px' }}
|
|
117
|
+
>
|
|
118
|
+
<StatusIcon size={value} />
|
|
119
|
+
<span style={{ fontSize: '11px' }}>{label}</span>
|
|
120
|
+
</div>
|
|
121
|
+
))}
|
|
122
|
+
</div>
|
|
123
|
+
);
|
|
124
|
+
},
|
|
125
|
+
};
|
|
@@ -39,35 +39,28 @@ describe('StatusIcon', () => {
|
|
|
39
39
|
renderStatusIcon({ sentiment: sentiment as StatusIconSentiment });
|
|
40
40
|
|
|
41
41
|
expect(screen.getByTestId('status-icon')).toHaveClass(expectedClass);
|
|
42
|
-
cleanup();
|
|
43
42
|
},
|
|
44
43
|
);
|
|
45
44
|
|
|
46
|
-
it("'warning' and 'pending' sentiments generate 'dark' colored icons", () => {
|
|
47
|
-
renderStatusIcon({ sentiment: Sentiment.WARNING });
|
|
48
|
-
expect(screen.getByTestId('alert-icon')).toHaveClass('dark');
|
|
49
|
-
cleanup();
|
|
50
45
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
cleanup();
|
|
46
|
+
it.each([
|
|
47
|
+
[Sentiment.WARNING, 'alert-icon'],
|
|
48
|
+
[Sentiment.PENDING, 'clock-borderless-icon'],
|
|
49
|
+
[Status.PENDING, 'clock-borderless-icon'],
|
|
50
|
+
[Sentiment.POSITIVE, 'check-icon'],
|
|
51
|
+
[Sentiment.NEGATIVE, 'cross-icon'],
|
|
52
|
+
[Sentiment.NEUTRAL, 'info-icon'],
|
|
53
|
+
])(
|
|
54
|
+
"renders the correct icon for '%s' sentiment",
|
|
55
|
+
(sentiment: Sentiment | Status, expectedIconTestId: string) => {
|
|
56
|
+
renderStatusIcon({ sentiment: sentiment as StatusIconSentiment });
|
|
63
57
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
58
|
+
const icon = screen.getByTestId(expectedIconTestId);
|
|
59
|
+
expect(icon).toBeInTheDocument();
|
|
60
|
+
expect(icon).toHaveClass('status-icon');
|
|
61
|
+
},
|
|
62
|
+
);
|
|
67
63
|
|
|
68
|
-
renderStatusIcon();
|
|
69
|
-
expect(screen.getByTestId('info-icon')).toHaveClass('light');
|
|
70
|
-
});
|
|
71
64
|
|
|
72
65
|
describe('accessible name', () => {
|
|
73
66
|
it.each([
|
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
import { Info, Alert, Cross, Check, ClockBorderless } from '@transferwise/icons';
|
|
2
2
|
import { clsx } from 'clsx';
|
|
3
3
|
import { useIntl } from 'react-intl';
|
|
4
|
-
import { PropsWithChildren } from 'react';
|
|
5
4
|
|
|
6
|
-
import SentimentSurface from '../sentimentSurface';
|
|
7
5
|
import { SizeSmall, SizeMedium, SizeLarge, Sentiment, Size, Breakpoint, Status } from '../common';
|
|
8
6
|
import Circle, { CircleProps } from '../common/circle';
|
|
9
7
|
import { useMedia } from '../common/hooks/useMedia';
|
|
@@ -88,30 +86,18 @@ const StatusIcon = ({
|
|
|
88
86
|
};
|
|
89
87
|
const { Icon, defaultIconLabel } = iconMetaBySentiment[sentiment];
|
|
90
88
|
|
|
91
|
-
const iconColor = sentiment === 'warning' || sentiment === 'pending' ? 'dark' : 'light';
|
|
92
89
|
const isTinyViewport = useMedia(`(max-width: ${Breakpoint.ZOOM_400}px)`);
|
|
93
90
|
const size = mapLegacySize[sizeProp] ?? sizeProp;
|
|
94
|
-
|
|
95
|
-
const SentimentSurfaceSetting = (props: PropsWithChildren<Pick<CircleProps, 'className'>>) => (
|
|
96
|
-
<SentimentSurface
|
|
97
|
-
as="span"
|
|
98
|
-
// @ts-expect-error sentiment and SentimentSurface types mismatch
|
|
99
|
-
sentiment={
|
|
100
|
-
sentiment === 'positive' ? 'success' : sentiment === 'pending' ? 'warning' : sentiment
|
|
101
|
-
}
|
|
102
|
-
{...props}
|
|
103
|
-
/>
|
|
104
|
-
);
|
|
91
|
+
|
|
105
92
|
return (
|
|
106
93
|
<Circle
|
|
107
|
-
as={SentimentSurfaceSetting}
|
|
108
94
|
size={isTinyViewport && size < 40 ? 32 : size}
|
|
109
95
|
data-testid="status-icon"
|
|
110
96
|
className={clsx('status-circle', sentiment)}
|
|
111
97
|
id={id}
|
|
112
98
|
>
|
|
113
99
|
<Icon
|
|
114
|
-
className=
|
|
100
|
+
className="status-icon"
|
|
115
101
|
title={iconLabel === null ? undefined : iconLabel || defaultIconLabel}
|
|
116
102
|
/>
|
|
117
103
|
</Circle>
|