@transferwise/components 46.154.0 → 46.155.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.
- package/build/AvatarLayout/AvatarLayout.js.map +1 -1
- package/build/AvatarLayout/AvatarLayout.mjs.map +1 -1
- package/build/AvatarView/AvatarView.js.map +1 -1
- package/build/AvatarView/AvatarView.mjs.map +1 -1
- package/build/AvatarView/Dot/Dot.js.map +1 -1
- package/build/AvatarView/Dot/Dot.mjs.map +1 -1
- package/build/Body/Body.js.map +1 -1
- package/build/Body/Body.mjs.map +1 -1
- package/build/Chips/Chip/Chip.js.map +1 -1
- package/build/Chips/Chip/Chip.mjs.map +1 -1
- package/build/DefinitionList/DefinitionList.js +2 -1
- package/build/DefinitionList/DefinitionList.js.map +1 -1
- package/build/DefinitionList/DefinitionList.mjs +2 -1
- package/build/DefinitionList/DefinitionList.mjs.map +1 -1
- package/build/Display/Display.js.map +1 -1
- package/build/Display/Display.mjs.map +1 -1
- package/build/ExpressiveMoneyInput/AmountInput/AmountInput.js +2 -2
- package/build/ExpressiveMoneyInput/AmountInput/AmountInput.js.map +1 -1
- package/build/ExpressiveMoneyInput/AmountInput/AmountInput.mjs +1 -1
- package/build/ExpressiveMoneyInput/AmountInput/AmountInput.mjs.map +1 -1
- package/build/ExpressiveMoneyInput/AnimatedNumber/AnimatedNumber.js +4 -4
- package/build/ExpressiveMoneyInput/AnimatedNumber/AnimatedNumber.js.map +1 -1
- package/build/ExpressiveMoneyInput/AnimatedNumber/AnimatedNumber.mjs +1 -1
- package/build/ExpressiveMoneyInput/AnimatedNumber/AnimatedNumber.mjs.map +1 -1
- package/build/ExpressiveMoneyInput/Chevron/Chevron.js +2 -2
- package/build/ExpressiveMoneyInput/Chevron/Chevron.js.map +1 -1
- package/build/ExpressiveMoneyInput/Chevron/Chevron.mjs +1 -1
- package/build/ExpressiveMoneyInput/Chevron/Chevron.mjs.map +1 -1
- package/build/ExpressiveMoneyInput/CurrencySelector/CurrencySelector.js.map +1 -1
- package/build/ExpressiveMoneyInput/CurrencySelector/CurrencySelector.mjs.map +1 -1
- package/build/ExpressiveMoneyInput/ExpressiveMoneyInput.js +3 -3
- package/build/ExpressiveMoneyInput/ExpressiveMoneyInput.js.map +1 -1
- package/build/ExpressiveMoneyInput/ExpressiveMoneyInput.mjs +1 -1
- package/build/ExpressiveMoneyInput/ExpressiveMoneyInput.mjs.map +1 -1
- package/build/ExpressiveMoneyInput/hooks/useInputStyle.js.map +1 -1
- package/build/ExpressiveMoneyInput/hooks/useInputStyle.mjs.map +1 -1
- package/build/IconButton/IconButton.js.map +1 -1
- package/build/IconButton/IconButton.mjs.map +1 -1
- package/build/Link/Link.js.map +1 -1
- package/build/Link/Link.mjs.map +1 -1
- package/build/ListItem/Navigation/ListItemNavigation.js +4 -2
- package/build/ListItem/Navigation/ListItemNavigation.js.map +1 -1
- package/build/ListItem/Navigation/ListItemNavigation.mjs +4 -2
- package/build/ListItem/Navigation/ListItemNavigation.mjs.map +1 -1
- package/build/Nudge/Nudge.js +11 -2
- package/build/Nudge/Nudge.js.map +1 -1
- package/build/Nudge/Nudge.mjs +12 -3
- package/build/Nudge/Nudge.mjs.map +1 -1
- package/build/Sticky/Sticky.js +1 -0
- package/build/Sticky/Sticky.js.map +1 -1
- package/build/Sticky/Sticky.mjs +1 -0
- package/build/Sticky/Sticky.mjs.map +1 -1
- package/build/Title/Title.js +1 -0
- package/build/Title/Title.js.map +1 -1
- package/build/Title/Title.mjs +1 -0
- package/build/Title/Title.mjs.map +1 -1
- package/build/_deprecated/Select/Option/Option.js.map +1 -1
- package/build/_deprecated/Select/Option/Option.mjs.map +1 -1
- package/build/_deprecated/Select/SearchBox/SearchBox.js.map +1 -1
- package/build/_deprecated/Select/SearchBox/SearchBox.mjs.map +1 -1
- package/build/common/Circle/Circle.js.map +1 -1
- package/build/common/Circle/Circle.mjs.map +1 -1
- package/build/main.css +36 -1
- package/build/styles/DefinitionList/DefinitionList.css +30 -0
- package/build/styles/Nudge/Nudge.css +1 -1
- package/build/styles/Sticky/Sticky.css +4 -0
- package/build/styles/main.css +36 -1
- package/build/types/AvatarLayout/AvatarLayout.d.ts +2 -2
- package/build/types/AvatarLayout/AvatarLayout.d.ts.map +1 -1
- package/build/types/AvatarLayout/index.d.ts +1 -1
- package/build/types/AvatarLayout/index.d.ts.map +1 -1
- package/build/types/AvatarView/AvatarView.d.ts +2 -2
- package/build/types/AvatarView/AvatarView.d.ts.map +1 -1
- package/build/types/AvatarView/Dot/Dot.d.ts +1 -1
- package/build/types/AvatarView/Dot/Dot.d.ts.map +1 -1
- package/build/types/AvatarView/index.d.ts +1 -1
- package/build/types/AvatarView/index.d.ts.map +1 -1
- package/build/types/Body/Body.d.ts +11 -0
- package/build/types/Body/Body.d.ts.map +1 -1
- package/build/types/Body/index.d.ts +1 -0
- package/build/types/Body/index.d.ts.map +1 -1
- package/build/types/Chips/Chip/Chip.d.ts +2 -2
- package/build/types/Chips/Chip/Chip.d.ts.map +1 -1
- package/build/types/Chips/index.d.ts +1 -1
- package/build/types/Chips/index.d.ts.map +1 -1
- package/build/types/DefinitionList/DefinitionList.d.ts.map +1 -1
- package/build/types/Display/Display.d.ts +2 -2
- package/build/types/Display/Display.d.ts.map +1 -1
- package/build/types/Display/index.d.ts +1 -0
- package/build/types/Display/index.d.ts.map +1 -1
- package/build/types/ExpressiveMoneyInput/AmountInput/AmountInput.d.ts +3 -3
- package/build/types/ExpressiveMoneyInput/AmountInput/AmountInput.d.ts.map +1 -1
- package/build/types/ExpressiveMoneyInput/CurrencySelector/CurrencySelector.d.ts +3 -3
- package/build/types/ExpressiveMoneyInput/CurrencySelector/CurrencySelector.d.ts.map +1 -1
- package/build/types/ExpressiveMoneyInput/ExpressiveMoneyInput.d.ts +4 -4
- package/build/types/ExpressiveMoneyInput/ExpressiveMoneyInput.d.ts.map +1 -1
- package/build/types/ExpressiveMoneyInput/hooks/useInputStyle.d.ts +1 -1
- package/build/types/ExpressiveMoneyInput/hooks/useInputStyle.d.ts.map +1 -1
- package/build/types/ExpressiveMoneyInput/index.d.ts +1 -1
- package/build/types/ExpressiveMoneyInput/index.d.ts.map +1 -1
- package/build/types/IconButton/IconButton.d.ts +1 -1
- package/build/types/IconButton/IconButton.d.ts.map +1 -1
- package/build/types/IconButton/index.d.ts +1 -1
- package/build/types/IconButton/index.d.ts.map +1 -1
- package/build/types/Link/Link.d.ts +2 -2
- package/build/types/Link/Link.d.ts.map +1 -1
- package/build/types/Link/index.d.ts +1 -1
- package/build/types/Link/index.d.ts.map +1 -1
- package/build/types/ListItem/ListItem.d.ts +1 -1
- package/build/types/ListItem/Navigation/ListItemNavigation.d.ts +12 -2
- package/build/types/ListItem/Navigation/ListItemNavigation.d.ts.map +1 -1
- package/build/types/Nudge/Nudge.d.ts +28 -5
- package/build/types/Nudge/Nudge.d.ts.map +1 -1
- package/build/types/Nudge/index.d.ts +1 -1
- package/build/types/Nudge/index.d.ts.map +1 -1
- package/build/types/Sticky/Sticky.d.ts.map +1 -1
- package/build/types/Title/Title.d.ts +12 -2
- package/build/types/Title/Title.d.ts.map +1 -1
- package/build/types/Title/index.d.ts +1 -0
- package/build/types/Title/index.d.ts.map +1 -1
- package/build/types/_deprecated/Select/Option/Option.d.ts +2 -2
- package/build/types/_deprecated/Select/Option/Option.d.ts.map +1 -1
- package/build/types/common/Circle/Circle.d.ts +1 -1
- package/build/types/common/Circle/Circle.d.ts.map +1 -1
- package/build/types/common/Circle/index.d.ts +1 -1
- package/build/types/common/Circle/index.d.ts.map +1 -1
- package/build/types/index.d.ts +4 -0
- package/build/types/index.d.ts.map +1 -1
- package/package.json +5 -5
- package/src/AvatarLayout/AvatarLayout.tsx +3 -3
- package/src/AvatarLayout/index.ts +1 -1
- package/src/AvatarView/AvatarView.tsx +4 -4
- package/src/AvatarView/Dot/Dot.tsx +1 -1
- package/src/AvatarView/index.ts +1 -1
- package/src/Body/Body.tsx +2 -2
- package/src/Body/index.ts +1 -0
- package/src/Chips/Chip/Chip.tsx +2 -2
- package/src/Chips/index.ts +1 -1
- package/src/DefinitionList/DefinitionList.css +30 -0
- package/src/DefinitionList/DefinitionList.less +24 -0
- package/src/DefinitionList/DefinitionList.tsx +1 -0
- package/src/DefinitionList/_stories/DefinitionList.test.story.tsx +168 -0
- package/src/Display/Display.tsx +2 -2
- package/src/Display/index.ts +1 -0
- package/src/Drawer/Drawer.test.tsx +2 -3
- package/src/ExpressiveMoneyInput/AmountInput/AmountInput.tsx +4 -4
- package/src/ExpressiveMoneyInput/AnimatedNumber/AnimatedNumber.tsx +1 -1
- package/src/ExpressiveMoneyInput/Chevron/Chevron.tsx +1 -1
- package/src/ExpressiveMoneyInput/CurrencySelector/CurrencySelector.tsx +3 -3
- package/src/ExpressiveMoneyInput/ExpressiveMoneyInput.test.tsx +1 -1
- package/src/ExpressiveMoneyInput/ExpressiveMoneyInput.tsx +5 -8
- package/src/ExpressiveMoneyInput/_stories/ExpressiveMoneyInput.story.tsx +1 -1
- package/src/ExpressiveMoneyInput/_stories/ExpressiveMoneyInput.test.story.tsx +1 -1
- package/src/ExpressiveMoneyInput/hooks/useInputStyle.ts +1 -1
- package/src/ExpressiveMoneyInput/index.ts +1 -1
- package/src/IconButton/IconButton.tsx +2 -2
- package/src/IconButton/_stories/IconButton.story.tsx +8 -8
- package/src/IconButton/index.ts +1 -1
- package/src/Link/Link.tsx +2 -2
- package/src/Link/index.ts +1 -1
- package/src/ListItem/Navigation/ListItemNavigation.test.tsx +23 -0
- package/src/ListItem/Navigation/ListItemNavigation.tsx +17 -3
- package/src/ListItem/Navigation/_stories/ListItemNavigation.story.tsx +33 -0
- package/src/Nudge/Nudge.css +1 -1
- package/src/Nudge/Nudge.less +1 -1
- package/src/Nudge/Nudge.test.tsx +43 -0
- package/src/Nudge/Nudge.tsx +43 -8
- package/src/Nudge/index.ts +1 -1
- package/src/Sticky/Sticky.css +4 -0
- package/src/Sticky/Sticky.less +6 -0
- package/src/Sticky/Sticky.test.tsx +1 -0
- package/src/Sticky/Sticky.tsx +1 -0
- package/src/Sticky/_stories/Sticky.test.story.tsx +177 -0
- package/src/Title/Title.tsx +25 -20
- package/src/Title/index.ts +1 -0
- package/src/_deprecated/LegacyListItem/_stories/LegacyListItem.story.tsx +2 -3
- package/src/_deprecated/Select/Option/Option.test.tsx +1 -1
- package/src/_deprecated/Select/Option/Option.tsx +2 -2
- package/src/_deprecated/Select/SearchBox/SearchBox.tsx +2 -2
- package/src/common/Circle/Circle.tsx +2 -2
- package/src/common/Circle/index.ts +1 -1
- package/src/index.ts +4 -0
- package/src/main.css +36 -1
- package/src/main.less +1 -0
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react-webpack5';
|
|
2
|
+
import { expect } from 'storybook/test';
|
|
3
|
+
|
|
4
|
+
import { Layout } from '../../common';
|
|
5
|
+
|
|
6
|
+
import DefinitionList, { type DefinitionListDefinition } from '..';
|
|
7
|
+
|
|
8
|
+
const longValues = {
|
|
9
|
+
amount: '1,234,567.89 GBP from your Wise Business account ending in 4821',
|
|
10
|
+
arrival: 'Tuesday, 14 July 2026 at 18:30 Eastern European Summer Time',
|
|
11
|
+
method: 'Business account ending in 4821 · Manual bank transfer',
|
|
12
|
+
recipients: 'Thirty-two recipients across twelve destination currencies',
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const longTitles = {
|
|
16
|
+
amount: "Total amount we'll convert before sending this batch payment",
|
|
17
|
+
arrival: 'Estimated arrival time for all recipients in this batch',
|
|
18
|
+
method: 'Payment method used to fund this batch transfer',
|
|
19
|
+
recipients: 'Number of recipients included in this batch payment',
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const longTitlesLongValues: DefinitionListDefinition[] = [
|
|
23
|
+
{ key: 'amount', title: longTitles.amount, value: longValues.amount },
|
|
24
|
+
{ key: 'arrival', title: longTitles.arrival, value: longValues.arrival },
|
|
25
|
+
{ key: 'method', title: longTitles.method, value: longValues.method },
|
|
26
|
+
{ key: 'recipients', title: longTitles.recipients, value: longValues.recipients },
|
|
27
|
+
];
|
|
28
|
+
|
|
29
|
+
const longTitlesShortValues: DefinitionListDefinition[] = [
|
|
30
|
+
{ key: 'amount', title: longTitles.amount, value: '1,234.56 GBP' },
|
|
31
|
+
{ key: 'arrival', title: longTitles.arrival, value: 'Today' },
|
|
32
|
+
{ key: 'method', title: longTitles.method, value: 'Bank transfer' },
|
|
33
|
+
{ key: 'recipients', title: longTitles.recipients, value: '32' },
|
|
34
|
+
];
|
|
35
|
+
|
|
36
|
+
const shortTitlesLongValues: DefinitionListDefinition[] = [
|
|
37
|
+
{ key: 'amount', title: 'Amount', value: longValues.amount },
|
|
38
|
+
{ key: 'arrival', title: 'Arrival', value: longValues.arrival },
|
|
39
|
+
{ key: 'method', title: 'Method', value: longValues.method },
|
|
40
|
+
{ key: 'recipients', title: 'Recipients', value: longValues.recipients },
|
|
41
|
+
];
|
|
42
|
+
|
|
43
|
+
const mixedContentLengths: DefinitionListDefinition[] = [
|
|
44
|
+
{ key: 'ticket-regression', title: "Total amount we'll convert", value: '1,234.56 GBP' },
|
|
45
|
+
{ key: 'short-short', title: 'Fees', value: '12.34 GBP' },
|
|
46
|
+
{ key: 'long-short', title: longTitles.arrival, value: 'Today' },
|
|
47
|
+
{ key: 'short-long', title: 'Method', value: longValues.method },
|
|
48
|
+
{ key: 'long-long', title: longTitles.recipients, value: longValues.recipients },
|
|
49
|
+
{
|
|
50
|
+
key: 'styled-content',
|
|
51
|
+
title: <strong>Current available balance before this payment</strong>,
|
|
52
|
+
value: <strong>98,765.43 GBP</strong>,
|
|
53
|
+
},
|
|
54
|
+
];
|
|
55
|
+
|
|
56
|
+
type TestCaseProps = {
|
|
57
|
+
definitions: DefinitionListDefinition[];
|
|
58
|
+
label: string;
|
|
59
|
+
width?: number;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const TestCase = ({ definitions, label, width = 640 }: TestCaseProps) => (
|
|
63
|
+
<section style={{ maxWidth: '100%', width }}>
|
|
64
|
+
<h2 style={{ fontSize: 16, margin: '0 0 var(--size-16)' }}>{label}</h2>
|
|
65
|
+
<DefinitionList definitions={definitions} layout={Layout.HORIZONTAL_RIGHT_ALIGNED} />
|
|
66
|
+
</section>
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
const meta = {
|
|
70
|
+
component: DefinitionList,
|
|
71
|
+
parameters: { controls: { disable: true } },
|
|
72
|
+
tags: ['!autodocs', '!manifest'],
|
|
73
|
+
title: 'Typography/DefinitionList/Tests',
|
|
74
|
+
} satisfies Meta<typeof DefinitionList>;
|
|
75
|
+
|
|
76
|
+
export default meta;
|
|
77
|
+
|
|
78
|
+
type Story = StoryObj<typeof meta>;
|
|
79
|
+
|
|
80
|
+
const assertSharedColumnBoundary = async (canvasElement: HTMLElement) => {
|
|
81
|
+
const lists = [
|
|
82
|
+
...canvasElement.querySelectorAll<HTMLElement>('.tw-definition-list--horizontal-right-aligned'),
|
|
83
|
+
];
|
|
84
|
+
|
|
85
|
+
await expect(lists.length).toBeGreaterThan(0);
|
|
86
|
+
|
|
87
|
+
for (const list of lists) {
|
|
88
|
+
const rows = [...list.querySelectorAll<HTMLElement>('.tw-definition-list__item')];
|
|
89
|
+
const columns = rows.map((row) => ({
|
|
90
|
+
row,
|
|
91
|
+
title: row.querySelector<HTMLElement>('dt'),
|
|
92
|
+
value: row.querySelector<HTMLElement>('dd'),
|
|
93
|
+
}));
|
|
94
|
+
|
|
95
|
+
await expect(columns.every(({ title, value }) => title && value)).toBe(true);
|
|
96
|
+
|
|
97
|
+
const horizontalColumns = columns.filter(({ row }) =>
|
|
98
|
+
['flex', 'grid'].includes(getComputedStyle(row).display),
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
if (horizontalColumns.length > 0) {
|
|
102
|
+
const titleRects = horizontalColumns.map(({ title }) => title?.getBoundingClientRect());
|
|
103
|
+
const valueRects = horizontalColumns.map(({ value }) => value?.getBoundingClientRect());
|
|
104
|
+
const isRtl = getComputedStyle(list).direction === 'rtl';
|
|
105
|
+
|
|
106
|
+
if (isRtl) {
|
|
107
|
+
const valueColumnEnd = Math.max(...valueRects.map((rect) => rect?.right ?? 0));
|
|
108
|
+
const titleColumnStart = Math.min(
|
|
109
|
+
...titleRects.map((rect) => rect?.left ?? Number.POSITIVE_INFINITY),
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
await expect(valueColumnEnd).toBeLessThanOrEqual(titleColumnStart + 1);
|
|
113
|
+
await expect(titleColumnStart - valueColumnEnd).toBeGreaterThanOrEqual(51);
|
|
114
|
+
} else {
|
|
115
|
+
const titleColumnEnd = Math.max(...titleRects.map((rect) => rect?.right ?? 0));
|
|
116
|
+
const valueColumnStart = Math.min(
|
|
117
|
+
...valueRects.map((rect) => rect?.left ?? Number.POSITIVE_INFINITY),
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
await expect(titleColumnEnd).toBeLessThanOrEqual(valueColumnStart + 1);
|
|
121
|
+
await expect(valueColumnStart - titleColumnEnd).toBeGreaterThanOrEqual(51);
|
|
122
|
+
}
|
|
123
|
+
} else {
|
|
124
|
+
for (const { title, value } of columns) {
|
|
125
|
+
if (title && value) {
|
|
126
|
+
const titleEnd = title.getBoundingClientRect().bottom;
|
|
127
|
+
const valueStart = value.getBoundingClientRect().top;
|
|
128
|
+
|
|
129
|
+
await expect(titleEnd).toBeLessThanOrEqual(valueStart + 1);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
for (const { title, value } of columns) {
|
|
135
|
+
if (title && value) {
|
|
136
|
+
await expect(title.scrollWidth).toBeLessThanOrEqual(title.clientWidth);
|
|
137
|
+
await expect(value.scrollWidth).toBeLessThanOrEqual(value.clientWidth);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
const verifySharedColumnBoundary: NonNullable<Story['play']> = async ({ canvasElement, step }) => {
|
|
144
|
+
await step('titles and values stay inside separate shared columns', async () => {
|
|
145
|
+
await assertSharedColumnBoundary(canvasElement);
|
|
146
|
+
});
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
export const HORIZONTAL_RIGHT_ALIGNED: Story = {
|
|
150
|
+
name: 'HORIZONTAL_RIGHT_ALIGNED',
|
|
151
|
+
render: () => (
|
|
152
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: 'var(--size-48)' }}>
|
|
153
|
+
<TestCase
|
|
154
|
+
definitions={longTitlesLongValues}
|
|
155
|
+
label="Long titles with long values"
|
|
156
|
+
width={720}
|
|
157
|
+
/>
|
|
158
|
+
<TestCase definitions={shortTitlesLongValues} label="All short titles with long values" />
|
|
159
|
+
<TestCase definitions={longTitlesShortValues} label="All long titles with short values" />
|
|
160
|
+
<TestCase
|
|
161
|
+
definitions={mixedContentLengths}
|
|
162
|
+
label="Mixed title and value lengths"
|
|
163
|
+
width={520}
|
|
164
|
+
/>
|
|
165
|
+
</div>
|
|
166
|
+
),
|
|
167
|
+
play: verifySharedColumnBoundary,
|
|
168
|
+
};
|
package/src/Display/Display.tsx
CHANGED
|
@@ -3,7 +3,7 @@ import { PropsWithChildren } from 'react';
|
|
|
3
3
|
|
|
4
4
|
import { DisplayTypes, Typography } from '../common';
|
|
5
5
|
|
|
6
|
-
type
|
|
6
|
+
export type DisplayProps = PropsWithChildren<{
|
|
7
7
|
as?: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6';
|
|
8
8
|
id?: string;
|
|
9
9
|
type?: DisplayTypes;
|
|
@@ -16,7 +16,7 @@ function Display({
|
|
|
16
16
|
children,
|
|
17
17
|
className,
|
|
18
18
|
id,
|
|
19
|
-
}:
|
|
19
|
+
}: DisplayProps) {
|
|
20
20
|
return (
|
|
21
21
|
<Heading id={id} className={clsx('np-display', `np-text-${type}`, 'text-primary', className)}>
|
|
22
22
|
{children}
|
package/src/Display/index.ts
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
import { render, screen, userEvent, mockMatchMedia } from '../test-utils';
|
|
2
2
|
import { Position } from '../common';
|
|
3
3
|
|
|
4
|
-
import Drawer from './Drawer';
|
|
4
|
+
import Drawer, { type DrawerProps } from './Drawer';
|
|
5
5
|
|
|
6
6
|
jest.mock('../common/DOMOperations');
|
|
7
7
|
mockMatchMedia();
|
|
8
8
|
|
|
9
9
|
describe('Drawer', () => {
|
|
10
|
-
const renderedComponent = (props:
|
|
11
|
-
render(<Drawer {...props} />);
|
|
10
|
+
const renderedComponent = (props: DrawerProps) => render(<Drawer {...props} />);
|
|
12
11
|
|
|
13
12
|
const props = {
|
|
14
13
|
open: true,
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { formatAmount } from '@transferwise/formatting';
|
|
2
2
|
import { clsx } from 'clsx';
|
|
3
|
-
import { AnimatePresence } from '
|
|
3
|
+
import { AnimatePresence } from 'motion/react';
|
|
4
4
|
import { type ChangeEvent, type KeyboardEvent, useEffect, useMemo, useRef, useState } from 'react';
|
|
5
5
|
import { useIntl } from 'react-intl';
|
|
6
6
|
|
|
7
|
-
import {
|
|
7
|
+
import type { ExpressiveMoneyInputProps } from '../ExpressiveMoneyInput';
|
|
8
8
|
import { AnimatedNumber } from '../AnimatedNumber/AnimatedNumber';
|
|
9
9
|
import { useFocus } from '../hooks/useFocus';
|
|
10
10
|
import { useInputStyle } from '../hooks/useInputStyle';
|
|
@@ -19,7 +19,7 @@ import {
|
|
|
19
19
|
isInputPossiblyOverflowing,
|
|
20
20
|
} from './utils';
|
|
21
21
|
|
|
22
|
-
type
|
|
22
|
+
type AmountInputProps = {
|
|
23
23
|
id: string;
|
|
24
24
|
describedById?: string;
|
|
25
25
|
amount?: number | null;
|
|
@@ -38,7 +38,7 @@ export const AmountInput = ({
|
|
|
38
38
|
onChange,
|
|
39
39
|
onFocusChange,
|
|
40
40
|
loading,
|
|
41
|
-
}:
|
|
41
|
+
}: AmountInputProps) => {
|
|
42
42
|
const intl = useIntl();
|
|
43
43
|
const { focus, setFocus, visualFocus, setVisualFocus } = useFocus();
|
|
44
44
|
|
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
SelectInputOptionContent,
|
|
6
6
|
SelectInputTriggerButton,
|
|
7
7
|
} from '../../Inputs/SelectInput';
|
|
8
|
-
import { CurrencyType,
|
|
8
|
+
import type { CurrencyType, ExpressiveMoneyInputProps } from '../ExpressiveMoneyInput';
|
|
9
9
|
import { ChevronDown } from '@transferwise/icons';
|
|
10
10
|
import { Flag } from '@wise/art';
|
|
11
11
|
import {
|
|
@@ -34,7 +34,7 @@ export interface CurrencySection {
|
|
|
34
34
|
|
|
35
35
|
export type CurrencyOptions = CurrencySection[];
|
|
36
36
|
|
|
37
|
-
export type
|
|
37
|
+
export type CurrencySelectorProps = {
|
|
38
38
|
id: string;
|
|
39
39
|
labelId: string;
|
|
40
40
|
options?: CurrencyOptions;
|
|
@@ -53,7 +53,7 @@ export const CurrencySelector = ({
|
|
|
53
53
|
addons,
|
|
54
54
|
onOpen,
|
|
55
55
|
onSearchChange,
|
|
56
|
-
}:
|
|
56
|
+
}: CurrencySelectorProps) => {
|
|
57
57
|
const intl = useIntl();
|
|
58
58
|
|
|
59
59
|
const allCurrencyOptions = useMemo(() => getUniqueCurrencies(options), [options]);
|
|
@@ -2,7 +2,7 @@ import { screen, render, mockMatchMedia, mockResizeObserver, waitFor } from '../
|
|
|
2
2
|
import { Sentiment } from '../common';
|
|
3
3
|
|
|
4
4
|
import ExpressiveMoneyInput from './ExpressiveMoneyInput';
|
|
5
|
-
import type {
|
|
5
|
+
import type { ExpressiveMoneyInputProps } from './ExpressiveMoneyInput';
|
|
6
6
|
import { Confetti } from '@transferwise/icons';
|
|
7
7
|
|
|
8
8
|
const defaultProps: ExpressiveMoneyInputProps = {
|
|
@@ -1,12 +1,9 @@
|
|
|
1
1
|
import Body from '../Body';
|
|
2
2
|
import { clsx } from 'clsx';
|
|
3
|
-
import { AnimatePresence, motion } from '
|
|
3
|
+
import { AnimatePresence, motion } from 'motion/react';
|
|
4
4
|
import { useId, type ReactNode } from 'react';
|
|
5
5
|
|
|
6
|
-
import {
|
|
7
|
-
type Props as CurrencySelectorProps,
|
|
8
|
-
CurrencySelector,
|
|
9
|
-
} from './CurrencySelector/CurrencySelector';
|
|
6
|
+
import { CurrencySelector, type CurrencySelectorProps } from './CurrencySelector/CurrencySelector';
|
|
10
7
|
import { CommonProps } from '../common';
|
|
11
8
|
import { AmountInput } from './AmountInput/AmountInput';
|
|
12
9
|
import { Chevron } from './Chevron/Chevron';
|
|
@@ -26,7 +23,7 @@ type CustomCurrencySelectorInstanceType = {
|
|
|
26
23
|
type CurrencySelectorType = DefaultCurrencySelectorInstanceType &
|
|
27
24
|
CustomCurrencySelectorInstanceType;
|
|
28
25
|
|
|
29
|
-
export type
|
|
26
|
+
export type ExpressiveMoneyInputProps = {
|
|
30
27
|
label?: ReactNode;
|
|
31
28
|
currencySelector?: CurrencySelectorType;
|
|
32
29
|
amount?: AmountType;
|
|
@@ -65,7 +62,7 @@ export type Props = {
|
|
|
65
62
|
* direct contributions by consuming product teams are highly encouraged.
|
|
66
63
|
*
|
|
67
64
|
* > ⚠️ **Prerequisite:** <br />The component depends on the
|
|
68
|
-
* [
|
|
65
|
+
* [motion](https://www.npmjs.com/package/motion) package, which has been
|
|
69
66
|
* deliberately excluded from the Design System bundle and kept as an opt-in only.
|
|
70
67
|
* **Make sure to add it to your project dependencies.**
|
|
71
68
|
*/
|
|
@@ -81,7 +78,7 @@ export default function ExpressiveMoneyInput({
|
|
|
81
78
|
autoFocus,
|
|
82
79
|
loading,
|
|
83
80
|
onFocusChange,
|
|
84
|
-
}:
|
|
81
|
+
}: ExpressiveMoneyInputProps) {
|
|
85
82
|
const inputId = useId();
|
|
86
83
|
const labelId = useId();
|
|
87
84
|
const customAlertId = useId();
|
|
@@ -19,7 +19,7 @@ import Modal from '../../Modal';
|
|
|
19
19
|
import ListItem from '../../ListItem';
|
|
20
20
|
import List from '../../List';
|
|
21
21
|
import ExpressiveMoneyInput, { type ExpressiveMoneyInputProps } from '..';
|
|
22
|
-
import type {
|
|
22
|
+
import type { CurrencySelectorProps } from '../CurrencySelector/CurrencySelector';
|
|
23
23
|
|
|
24
24
|
const getCurrencySelectorProps = ({
|
|
25
25
|
logEvents = false,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Meta, StoryObj } from '@storybook/react-webpack5';
|
|
2
2
|
import { fn } from 'storybook/test';
|
|
3
|
-
import ExpressiveMoneyInput, {
|
|
3
|
+
import ExpressiveMoneyInput, { type ExpressiveMoneyInputProps } from '../ExpressiveMoneyInput';
|
|
4
4
|
|
|
5
5
|
const meta: Meta<typeof ExpressiveMoneyInput> = {
|
|
6
6
|
component: ExpressiveMoneyInput,
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export type {
|
|
1
|
+
export type { ExpressiveMoneyInputProps, CurrencyType } from './ExpressiveMoneyInput';
|
|
2
2
|
export { default } from './ExpressiveMoneyInput';
|
|
@@ -3,7 +3,7 @@ import { PrimitiveAnchor, PrimitiveButton } from '../Primitives';
|
|
|
3
3
|
import Circle from '../common/Circle';
|
|
4
4
|
import { clsx } from 'clsx';
|
|
5
5
|
|
|
6
|
-
export type
|
|
6
|
+
export type IconButtonProps = {
|
|
7
7
|
/** @default 48 */
|
|
8
8
|
size?: 16 | 24 | 32 | 40 | 48 | 56 | 72;
|
|
9
9
|
/** @default 'primary' */
|
|
@@ -27,7 +27,7 @@ const IconButton = forwardRef(function IconButton(
|
|
|
27
27
|
priority = 'primary',
|
|
28
28
|
type = 'default',
|
|
29
29
|
...props
|
|
30
|
-
}:
|
|
30
|
+
}: IconButtonProps,
|
|
31
31
|
ref,
|
|
32
32
|
) {
|
|
33
33
|
const isLink = href !== '#';
|
|
@@ -18,7 +18,7 @@ import {
|
|
|
18
18
|
createSandboxStory,
|
|
19
19
|
globalScope,
|
|
20
20
|
} from '../../../.storybook/components/sandbox/SandboxEditor';
|
|
21
|
-
import IconButton, {
|
|
21
|
+
import IconButton, { type IconButtonProps } from '../IconButton';
|
|
22
22
|
import SentimentSurface from '../../SentimentSurface';
|
|
23
23
|
|
|
24
24
|
/**
|
|
@@ -46,7 +46,7 @@ export default {
|
|
|
46
46
|
|
|
47
47
|
type Story = StoryObj<typeof IconButton>;
|
|
48
48
|
|
|
49
|
-
const sizes:
|
|
49
|
+
const sizes: IconButtonProps['size'][] = [16, 24, 32, 40, 48, 56, 72];
|
|
50
50
|
|
|
51
51
|
/**
|
|
52
52
|
* Explore size, priority, type, and disabled state via the controls panel.
|
|
@@ -107,7 +107,7 @@ export const Priority: Story = {
|
|
|
107
107
|
</div>
|
|
108
108
|
),
|
|
109
109
|
],
|
|
110
|
-
render: (args:
|
|
110
|
+
render: (args: IconButtonProps) => (
|
|
111
111
|
<>
|
|
112
112
|
<IconButton priority="primary" type={args.type} aria-label="Primary" onClick={fn()}>
|
|
113
113
|
<Plus />
|
|
@@ -152,7 +152,7 @@ export const Type: Story = {
|
|
|
152
152
|
</div>
|
|
153
153
|
),
|
|
154
154
|
],
|
|
155
|
-
render: (args:
|
|
155
|
+
render: (args: IconButtonProps) => (
|
|
156
156
|
<>
|
|
157
157
|
<IconButton priority={args.priority} type="default" aria-label="Default" onClick={fn()}>
|
|
158
158
|
<Plus />
|
|
@@ -195,7 +195,7 @@ export const Size: Story = {
|
|
|
195
195
|
</div>
|
|
196
196
|
),
|
|
197
197
|
],
|
|
198
|
-
render: (args:
|
|
198
|
+
render: (args: IconButtonProps) => (
|
|
199
199
|
<>
|
|
200
200
|
{sizes.map((size) => (
|
|
201
201
|
<div
|
|
@@ -254,7 +254,7 @@ export const Disabled: Story = {
|
|
|
254
254
|
target: { table: { disable: true } },
|
|
255
255
|
onClick: { table: { disable: true } },
|
|
256
256
|
},
|
|
257
|
-
render: (args:
|
|
257
|
+
render: (args: IconButtonProps) => (
|
|
258
258
|
<IconButton disabled priority={args.priority} type={args.type} aria-label="Disabled">
|
|
259
259
|
<Plus />
|
|
260
260
|
</IconButton>
|
|
@@ -284,7 +284,7 @@ export const AsAnchor: Story = {
|
|
|
284
284
|
disabled: { table: { disable: true } },
|
|
285
285
|
onClick: { table: { disable: true } },
|
|
286
286
|
},
|
|
287
|
-
render: (args:
|
|
287
|
+
render: (args: IconButtonProps) => (
|
|
288
288
|
<IconButton {...args} aria-label="External link">
|
|
289
289
|
<ArrowLeft />
|
|
290
290
|
</IconButton>
|
|
@@ -305,7 +305,7 @@ export const SentimentAwareness: Story = {
|
|
|
305
305
|
options: [16, 24, 32, 40, 48, 56, 72],
|
|
306
306
|
},
|
|
307
307
|
},
|
|
308
|
-
render: (args:
|
|
308
|
+
render: (args: IconButtonProps) => {
|
|
309
309
|
const priorities = [
|
|
310
310
|
{ key: 'primary', label: 'primary' },
|
|
311
311
|
{ key: 'secondary', label: 'secondary' },
|
package/src/IconButton/index.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export type {
|
|
1
|
+
export type { IconButtonProps } from './IconButton';
|
|
2
2
|
export { default } from './IconButton';
|
package/src/Link/Link.tsx
CHANGED
|
@@ -8,7 +8,7 @@ import { LinkLarge, LinkDefault, Typography } from '../common';
|
|
|
8
8
|
|
|
9
9
|
import messages from './Link.messages';
|
|
10
10
|
|
|
11
|
-
export type
|
|
11
|
+
export type LinkProps = PrimitiveAnchorProps &
|
|
12
12
|
Pick<PrimitiveButtonProps, 'onClick'> & { type?: LinkLarge | LinkDefault };
|
|
13
13
|
|
|
14
14
|
/**
|
|
@@ -26,7 +26,7 @@ const Link = ({
|
|
|
26
26
|
onClick,
|
|
27
27
|
disabled,
|
|
28
28
|
...props
|
|
29
|
-
}:
|
|
29
|
+
}: LinkProps) => {
|
|
30
30
|
const { formatMessage } = useIntl();
|
|
31
31
|
const isButton = Boolean(onClick) && href === undefined;
|
|
32
32
|
const isBlank = target === '_blank';
|
package/src/Link/index.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export { default } from './Link';
|
|
2
|
-
export type {
|
|
2
|
+
export type { LinkProps } from './Link';
|
|
@@ -30,6 +30,29 @@ describe('ListItem.Navigation', () => {
|
|
|
30
30
|
});
|
|
31
31
|
});
|
|
32
32
|
|
|
33
|
+
describe('hideIndicator', () => {
|
|
34
|
+
it('renders the chevron by default for a button', () => {
|
|
35
|
+
renderWith({ control: <ListItem.Navigation onClick={() => {}} /> });
|
|
36
|
+
expect(screen.getByTestId('NavigationIndicator')).toBeInTheDocument();
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('renders the chevron by default for a link', () => {
|
|
40
|
+
renderWith({ control: <ListItem.Navigation href="/test-path" /> });
|
|
41
|
+
expect(screen.getByTestId('NavigationIndicator')).toBeInTheDocument();
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('hides the chevron when hideIndicator is true on a button', () => {
|
|
45
|
+
renderWith({ control: <ListItem.Navigation hideIndicator onClick={() => {}} /> });
|
|
46
|
+
expect(screen.queryByTestId('NavigationIndicator')).not.toBeInTheDocument();
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('hides the chevron when hideIndicator is true on a link', () => {
|
|
50
|
+
renderWith({ control: <ListItem.Navigation hideIndicator href="/test-path" /> });
|
|
51
|
+
expect(screen.getByRole('link')).toBeInTheDocument();
|
|
52
|
+
expect(screen.queryByTestId('NavigationIndicator')).not.toBeInTheDocument();
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
|
|
33
56
|
describe('as link', () => {
|
|
34
57
|
it('renders a link if href is set', () => {
|
|
35
58
|
renderWith({ control: <ListItem.Navigation href="/test-path" /> });
|
|
@@ -5,7 +5,17 @@ import { PrimitiveButton } from '../../Primitives';
|
|
|
5
5
|
import { useContext } from 'react';
|
|
6
6
|
import { ListItemContext } from '../ListItemContext';
|
|
7
7
|
|
|
8
|
-
export type ListItemNavigationProps = Pick<ButtonProps, 'onClick' | 'href' | 'target'
|
|
8
|
+
export type ListItemNavigationProps = Pick<ButtonProps, 'onClick' | 'href' | 'target'> & {
|
|
9
|
+
/**
|
|
10
|
+
* When `true`, the chevron indicator is hidden. Use when surrounding context makes the
|
|
11
|
+
* interactive affordance clear without a directional cue (e.g. a widget row with trailing
|
|
12
|
+
* value content).<br />
|
|
13
|
+
* **NB:** This feature should be used with great care as it can result in the item looking
|
|
14
|
+
* as non-interactive, significantly degrading accessibility, UX, and conversion,
|
|
15
|
+
* especially if used on busy screens with multiple Lists.
|
|
16
|
+
*/
|
|
17
|
+
hideIndicator?: boolean;
|
|
18
|
+
};
|
|
9
19
|
|
|
10
20
|
/**
|
|
11
21
|
* This component allows for rendering a control functionally synonymous with HTML `anchor` or a
|
|
@@ -15,10 +25,14 @@ export type ListItemNavigationProps = Pick<ButtonProps, 'onClick' | 'href' | 'ta
|
|
|
15
25
|
* <br />
|
|
16
26
|
* Please refer to the [Design documentation](https://docs.wise.design/components/list-item---navigation) for details.
|
|
17
27
|
*/
|
|
18
|
-
export const Navigation = function Navigation({
|
|
28
|
+
export const Navigation = function Navigation({
|
|
29
|
+
href,
|
|
30
|
+
hideIndicator,
|
|
31
|
+
...props
|
|
32
|
+
}: ListItemNavigationProps) {
|
|
19
33
|
const { baseItemProps } = useListItemControl('navigation', { href, ...props });
|
|
20
34
|
const { ids, describedByIds } = useContext(ListItemContext);
|
|
21
|
-
const icon = <ChevronRight size={16} />;
|
|
35
|
+
const icon = hideIndicator ? null : <ChevronRight size={16} data-testid="NavigationIndicator" />;
|
|
22
36
|
|
|
23
37
|
return href ? (
|
|
24
38
|
<>{icon}</>
|
|
@@ -50,6 +50,39 @@ export const Playground: Story = {
|
|
|
50
50
|
},
|
|
51
51
|
};
|
|
52
52
|
|
|
53
|
+
/**
|
|
54
|
+
* Use `hideIndicator` when surrounding context (e.g. trailing value content) makes the
|
|
55
|
+
* interactive affordance clear without a chevron. <br />
|
|
56
|
+
* **NB:** This feature should be used with great care as it can result in the item looking
|
|
57
|
+
* as non-interactive, significantly degrading accessibility, UX, and conversion,
|
|
58
|
+
* especially if used on busy screens with multiple Lists.
|
|
59
|
+
*/
|
|
60
|
+
export const WithoutIndicator: Story = {
|
|
61
|
+
parameters: {
|
|
62
|
+
controls: { disable: true },
|
|
63
|
+
},
|
|
64
|
+
render: (args) => {
|
|
65
|
+
return (
|
|
66
|
+
<List>
|
|
67
|
+
<ListItem
|
|
68
|
+
control={<ListItem.Navigation href="#target" hideIndicator onClick={args.onClick} />}
|
|
69
|
+
title="Navigation without chevron"
|
|
70
|
+
subtitle="Interactive row — no directional indicator"
|
|
71
|
+
valueTitle="£1,234.56"
|
|
72
|
+
media={MEDIA.avatarSingle}
|
|
73
|
+
/>
|
|
74
|
+
|
|
75
|
+
<ListItem
|
|
76
|
+
control={<ListItem.Navigation hideIndicator onClick={args.onClick} />}
|
|
77
|
+
title="Navigation without chevron (button)"
|
|
78
|
+
subtitle="Triggers a click handler"
|
|
79
|
+
media={MEDIA.avatarSingle}
|
|
80
|
+
/>
|
|
81
|
+
</List>
|
|
82
|
+
);
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
|
|
53
86
|
/**
|
|
54
87
|
* Navigation control can be rendered as HTML anchor or a button
|
|
55
88
|
*/
|
package/src/Nudge/Nudge.css
CHANGED
package/src/Nudge/Nudge.less
CHANGED
package/src/Nudge/Nudge.test.tsx
CHANGED
|
@@ -42,6 +42,49 @@ describe('Nudge', () => {
|
|
|
42
42
|
expect(defaultProps.onDismiss).toHaveBeenCalledTimes(1);
|
|
43
43
|
});
|
|
44
44
|
|
|
45
|
+
it('renders action only when both action and link/href are supplied', () => {
|
|
46
|
+
const consoleWarn = jest.spyOn(console, 'warn').mockImplementation(() => {});
|
|
47
|
+
|
|
48
|
+
render(
|
|
49
|
+
<Nudge
|
|
50
|
+
{...defaultProps}
|
|
51
|
+
action={{
|
|
52
|
+
text: 'Learn more',
|
|
53
|
+
href: '#action-link',
|
|
54
|
+
'aria-label': 'Learn more about rewards',
|
|
55
|
+
}}
|
|
56
|
+
/>,
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
expect(screen.queryByText('CTA')).not.toBeInTheDocument();
|
|
60
|
+
|
|
61
|
+
const actionLink = screen.getByText('Learn more');
|
|
62
|
+
expect(actionLink).toBeInTheDocument();
|
|
63
|
+
expect(actionLink).toHaveAttribute('href', '#action-link');
|
|
64
|
+
|
|
65
|
+
consoleWarn.mockRestore();
|
|
66
|
+
});
|
|
67
|
+
it('shows a warning in development when both action and link/href are supplied', () => {
|
|
68
|
+
const consoleWarn = jest.spyOn(console, 'warn').mockImplementation(() => {});
|
|
69
|
+
|
|
70
|
+
render(
|
|
71
|
+
<Nudge
|
|
72
|
+
{...defaultProps}
|
|
73
|
+
action={{
|
|
74
|
+
text: 'Learn more',
|
|
75
|
+
href: '#action-link',
|
|
76
|
+
'aria-label': 'Learn more about rewards',
|
|
77
|
+
}}
|
|
78
|
+
/>,
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
expect(consoleWarn).toHaveBeenCalledWith(
|
|
82
|
+
'Nudge received both action and link/href props. Rendering action only. Remove link/href to silence this warning.',
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
consoleWarn.mockRestore();
|
|
86
|
+
});
|
|
87
|
+
|
|
45
88
|
describe('Nudge persistance', () => {
|
|
46
89
|
beforeEach(() => {
|
|
47
90
|
jest.resetAllMocks();
|