@transferwise/components 0.0.0-experimental-131773e → 0.0.0-experimental-1bec623
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/listItem/Button/ListItemButton.js +2 -2
- package/build/listItem/Button/ListItemButton.js.map +1 -1
- package/build/listItem/Button/ListItemButton.mjs +2 -2
- package/build/listItem/Button/ListItemButton.mjs.map +1 -1
- package/build/listItem/ListItem.js +1 -1
- package/build/listItem/ListItem.js.map +1 -1
- package/build/listItem/ListItem.mjs +1 -1
- package/build/listItem/ListItem.mjs.map +1 -1
- package/build/main.css +5 -0
- package/build/styles/listItem/ListItem.css +5 -0
- package/build/styles/main.css +5 -0
- package/build/types/listItem/Button/ListItemButton.d.ts.map +1 -1
- package/build/types/listItem/ListItem.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/listItem/AdditionalInfo/ListItemAdditionalInfo.spec.tsx +56 -0
- package/src/listItem/AvatarLayout/ListItemAvatarLayout.spec.tsx +59 -0
- package/src/listItem/AvatarView/ListItemAvatarView.spec.tsx +75 -0
- package/src/listItem/AvatarView/ListItemAvatarView.story.tsx +2 -2
- package/src/listItem/Button/ListItemButton.tsx +2 -2
- package/src/listItem/Checkbox/ListItemCheckbox.spec.tsx +82 -0
- package/src/listItem/IconButton/ListItemIconButton.spec.tsx +119 -0
- package/src/listItem/Image/ListItemImage.spec.tsx +27 -0
- package/src/listItem/ListItem.css +5 -0
- package/src/listItem/ListItem.less +8 -1
- package/src/listItem/ListItem.spec.tsx +1406 -7
- package/src/listItem/ListItem.tsx +2 -1
- package/src/listItem/Navigation/ListItemNavigation.spec.tsx +59 -0
- package/src/listItem/Prompt/ListItemPrompt.spec.tsx +14 -27
- package/src/listItem/Prompt/ListItemPrompt.story.tsx +5 -3
- package/src/listItem/Radio/ListItemRadio.spec.tsx +66 -0
- package/src/listItem/Switch/ListItemSwitch.spec.tsx +47 -0
- package/src/listItem/_stories/ListItem.layout.test.story.tsx +21 -1
- package/src/listItem/_stories/ListItem.variants.test.story.tsx +1 -1
- package/src/main.css +5 -0
- package/src/test-utils/Parameters.d.ts +0 -77
|
@@ -216,7 +216,8 @@ export const ListItem = ({
|
|
|
216
216
|
{
|
|
217
217
|
'wds-list-item-interactive': isFullyInteractive,
|
|
218
218
|
'wds-list-item-partially-interactive': isPartiallyInteractive,
|
|
219
|
-
[`wds-list-item-spotlight-${spotlight}`]:
|
|
219
|
+
[`wds-list-item-spotlight wds-list-item-spotlight-${spotlight}`]:
|
|
220
|
+
isFullyInteractive && !!spotlight,
|
|
220
221
|
disabled,
|
|
221
222
|
},
|
|
222
223
|
className,
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { render, screen } from '../../test-utils';
|
|
2
|
+
import userEvent from '@testing-library/user-event';
|
|
3
|
+
import { ListItem, type Props as ListItemProps } from '../ListItem';
|
|
4
|
+
|
|
5
|
+
describe('ListItem.Navigation', () => {
|
|
6
|
+
const renderWith = (overrides: Partial<ListItemProps> = {}) =>
|
|
7
|
+
render(<ListItem title="Test Title" {...overrides} />);
|
|
8
|
+
|
|
9
|
+
describe('as button', () => {
|
|
10
|
+
it('renders as button if onClick is set but no href', () => {
|
|
11
|
+
renderWith({ control: <ListItem.Navigation onClick={() => {}} /> });
|
|
12
|
+
expect(screen.getByRole('button')).toBeInTheDocument();
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it('handles onClick events', async () => {
|
|
16
|
+
const handleClick = jest.fn();
|
|
17
|
+
renderWith({ control: <ListItem.Navigation onClick={handleClick} /> });
|
|
18
|
+
|
|
19
|
+
await userEvent.click(screen.getByRole('button'));
|
|
20
|
+
expect(handleClick).toHaveBeenCalledTimes(1);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('respects disabled state', async () => {
|
|
24
|
+
renderWith({
|
|
25
|
+
disabled: true,
|
|
26
|
+
control: <ListItem.Navigation onClick={jest.fn()} />,
|
|
27
|
+
});
|
|
28
|
+
expect(screen.getByTestId('backslash-circle-icon')).toBeInTheDocument();
|
|
29
|
+
expect(screen.queryByRole('button')).not.toBeInTheDocument();
|
|
30
|
+
expect(screen.queryByRole('link')).not.toBeInTheDocument();
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
describe('as link', () => {
|
|
35
|
+
it('renders a link if href is set', () => {
|
|
36
|
+
renderWith({ control: <ListItem.Navigation href="/test-path" /> });
|
|
37
|
+
|
|
38
|
+
expect(screen.queryByRole('button')).not.toBeInTheDocument();
|
|
39
|
+
expect(screen.getByRole('link')).toBeInTheDocument();
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('respects target prop', () => {
|
|
43
|
+
renderWith({
|
|
44
|
+
control: <ListItem.Navigation href="/test-path" target="_blank" />,
|
|
45
|
+
});
|
|
46
|
+
const link = screen.getByRole('link');
|
|
47
|
+
expect(link).toHaveAttribute('target', '_blank');
|
|
48
|
+
expect(link).toHaveAttribute('rel', 'noopener noreferrer');
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('renders disabled icon when ListItem is disabled', () => {
|
|
52
|
+
renderWith({
|
|
53
|
+
disabled: true,
|
|
54
|
+
control: <ListItem.Navigation href="wise.com" />,
|
|
55
|
+
});
|
|
56
|
+
expect(screen.getByTestId('backslash-circle-icon')).toBeInTheDocument();
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
});
|
|
@@ -1,34 +1,20 @@
|
|
|
1
|
-
import { render, screen
|
|
1
|
+
import { mockMatchMedia, render, screen } from '../../test-utils';
|
|
2
2
|
import { Sentiment } from '../../common';
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import { ListItem } from '../ListItem';
|
|
4
|
+
import type { ListItemPromptProps } from './ListItemPrompt';
|
|
5
5
|
|
|
6
6
|
mockMatchMedia();
|
|
7
7
|
|
|
8
|
-
const mockContext = {
|
|
9
|
-
setControlType: jest.fn(),
|
|
10
|
-
setControlProps: jest.fn(),
|
|
11
|
-
ids: {
|
|
12
|
-
title: 'title',
|
|
13
|
-
control: 'control',
|
|
14
|
-
prompt: 'prompt',
|
|
15
|
-
additionalInfo: 'additionalInfo',
|
|
16
|
-
valueTitle: 'valueTitle',
|
|
17
|
-
valueSubtitle: 'valueSubtitle',
|
|
18
|
-
subtitle: 'subtitle',
|
|
19
|
-
},
|
|
20
|
-
props: {},
|
|
21
|
-
describedByIds: 'described-by-ids',
|
|
22
|
-
};
|
|
23
|
-
|
|
24
8
|
describe('ListItem.Prompt', () => {
|
|
25
|
-
it('renders
|
|
9
|
+
it('renders children content', () => {
|
|
26
10
|
render(
|
|
27
|
-
<
|
|
28
|
-
|
|
29
|
-
|
|
11
|
+
<ListItem
|
|
12
|
+
title="Test Title"
|
|
13
|
+
prompt={<ListItem.Prompt>This is a prompt message</ListItem.Prompt>}
|
|
14
|
+
/>,
|
|
30
15
|
);
|
|
31
|
-
|
|
16
|
+
|
|
17
|
+
expect(screen.getByText('This is a prompt message')).toBeInTheDocument();
|
|
32
18
|
});
|
|
33
19
|
|
|
34
20
|
describe('render icon', () => {
|
|
@@ -39,9 +25,10 @@ describe('ListItem.Prompt', () => {
|
|
|
39
25
|
[Sentiment.WARNING, 'alert-icon'],
|
|
40
26
|
] as [ListItemPromptProps['sentiment'], string][])('renders %s icon', (sentiment, iconId) => {
|
|
41
27
|
render(
|
|
42
|
-
<
|
|
43
|
-
|
|
44
|
-
|
|
28
|
+
<ListItem
|
|
29
|
+
title="Test Title"
|
|
30
|
+
prompt={<ListItem.Prompt sentiment={sentiment}>Message</ListItem.Prompt>}
|
|
31
|
+
/>,
|
|
45
32
|
);
|
|
46
33
|
expect(screen.getByTestId(iconId)).toBeInTheDocument();
|
|
47
34
|
});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Meta, StoryObj } from '@storybook/react-webpack5';
|
|
2
|
-
import {
|
|
2
|
+
import { action } from 'storybook/actions';
|
|
3
3
|
import { lorem10, lorem5 } from '../../test-utils';
|
|
4
4
|
import Link from '../../link';
|
|
5
5
|
import { Sentiment as Sentiments } from '../../common';
|
|
@@ -147,8 +147,10 @@ export const Interactivity: Story = {
|
|
|
147
147
|
prompt={
|
|
148
148
|
<Prompt sentiment={args.sentiment}>
|
|
149
149
|
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events */}
|
|
150
|
-
This prompt includes an <Link onClick={
|
|
151
|
-
|
|
150
|
+
This prompt includes an <Link onClick={action('inline button')}>
|
|
151
|
+
inline button
|
|
152
|
+
</Link>{' '}
|
|
153
|
+
than can e.g. trigger a modal.
|
|
152
154
|
</Prompt>
|
|
153
155
|
}
|
|
154
156
|
/>
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { render, screen } from '../../test-utils';
|
|
2
|
+
import userEvent from '@testing-library/user-event';
|
|
3
|
+
import { ListItem, type Props as ListItemProps } from '../ListItem';
|
|
4
|
+
|
|
5
|
+
describe('ListItem.Radio', () => {
|
|
6
|
+
const renderWith = (overrides: Partial<ListItemProps> = {}) =>
|
|
7
|
+
render(<ListItem title="Test Title" {...overrides} />);
|
|
8
|
+
|
|
9
|
+
it('renders radio button', () => {
|
|
10
|
+
renderWith({
|
|
11
|
+
control: <ListItem.Radio name="test-radio" value="option1" onChange={() => {}} />,
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
expect(screen.getByRole('radio')).toBeInTheDocument();
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
describe('checked state', () => {
|
|
18
|
+
it('reflects checked state', () => {
|
|
19
|
+
renderWith({
|
|
20
|
+
control: <ListItem.Radio name="test-radio" value="option1" checked />,
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
expect(screen.getByRole('radio')).toBeChecked();
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('reflects unchecked state', () => {
|
|
27
|
+
renderWith({
|
|
28
|
+
control: <ListItem.Radio name="test-radio" value="option1" checked={false} />,
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
expect(screen.getByRole('radio')).not.toBeChecked();
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('handles onChange events', async () => {
|
|
36
|
+
const handleChange = jest.fn();
|
|
37
|
+
renderWith({
|
|
38
|
+
control: <ListItem.Radio name="test-radio" value="option1" onChange={handleChange} />,
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
await userEvent.click(screen.getByRole('radio'));
|
|
42
|
+
expect(handleChange).toHaveBeenCalledTimes(1);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('is disabled when ListItem is disabled', async () => {
|
|
46
|
+
const handleChange = jest.fn();
|
|
47
|
+
renderWith({
|
|
48
|
+
disabled: true,
|
|
49
|
+
control: <ListItem.Radio name="test-radio" value="option1" onChange={handleChange} />,
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
const radio = screen.getByRole('radio');
|
|
53
|
+
expect(radio).toBeDisabled();
|
|
54
|
+
await userEvent.click(radio);
|
|
55
|
+
expect(handleChange).not.toHaveBeenCalled();
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('supports name and value attributes', () => {
|
|
59
|
+
renderWith({ control: <ListItem.Radio name="test-radio" value="option1" /> });
|
|
60
|
+
|
|
61
|
+
const radio = screen.getByRole('radio');
|
|
62
|
+
expect(radio).toHaveAttribute('name', 'test-radio');
|
|
63
|
+
// eslint-disable-next-line jest-dom/prefer-to-have-value
|
|
64
|
+
expect(radio).toHaveAttribute('value', 'option1');
|
|
65
|
+
});
|
|
66
|
+
});
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { render, screen } from '../../test-utils';
|
|
2
|
+
import userEvent from '@testing-library/user-event';
|
|
3
|
+
import { ListItem, type Props as ListItemProps } from '../ListItem';
|
|
4
|
+
|
|
5
|
+
describe('ListItem.Switch', () => {
|
|
6
|
+
const renderWith = (overrides: Partial<ListItemProps> = {}) =>
|
|
7
|
+
render(<ListItem title="Test title" {...overrides} />);
|
|
8
|
+
|
|
9
|
+
it('renders switch with correct role', () => {
|
|
10
|
+
renderWith({ control: <ListItem.Switch checked={false} onClick={() => {}} /> });
|
|
11
|
+
expect(screen.getByRole('switch')).toBeInTheDocument();
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
describe('checked state', () => {
|
|
15
|
+
it('reflects checked state', () => {
|
|
16
|
+
renderWith({ control: <ListItem.Switch checked onClick={() => {}} /> });
|
|
17
|
+
expect(screen.getByRole('switch')).toBeChecked();
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('reflects unchecked state', () => {
|
|
21
|
+
renderWith({ control: <ListItem.Switch checked={false} onClick={() => {}} /> });
|
|
22
|
+
expect(screen.getByRole('switch')).not.toBeChecked();
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('handles onClick events', async () => {
|
|
27
|
+
const handleClick = jest.fn();
|
|
28
|
+
renderWith({ control: <ListItem.Switch checked={false} onClick={handleClick} /> });
|
|
29
|
+
|
|
30
|
+
await userEvent.click(screen.getByRole('switch'));
|
|
31
|
+
expect(handleClick).toHaveBeenCalledTimes(1);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('is disabled when ListItem is disabled', async () => {
|
|
35
|
+
const handleClick = jest.fn();
|
|
36
|
+
renderWith({
|
|
37
|
+
disabled: true,
|
|
38
|
+
control: <ListItem.Switch checked={false} onClick={handleClick} />,
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
const switchElement = screen.getByRole('switch');
|
|
42
|
+
expect(switchElement).toBeDisabled();
|
|
43
|
+
|
|
44
|
+
await userEvent.click(screen.getByRole('switch'));
|
|
45
|
+
expect(handleClick).not.toHaveBeenCalled();
|
|
46
|
+
});
|
|
47
|
+
});
|
|
@@ -2,7 +2,8 @@ import { Meta, StoryObj } from '@storybook/react-webpack5';
|
|
|
2
2
|
import { Bank, FastFlag, MultiCurrency, Receipt, Savings } from '@transferwise/icons';
|
|
3
3
|
import Link from '../../link';
|
|
4
4
|
import { ListItem, Props as ItemProps } from '../ListItem';
|
|
5
|
-
import { lorem5 } from '../../test-utils';
|
|
5
|
+
import { lorem10, lorem5 } from '../../test-utils';
|
|
6
|
+
import { SB_LIST_ITEM_CONTROLS as CONTROLS, SB_LIST_ITEM_MEDIA as MEDIA } from './subcomponents';
|
|
6
7
|
|
|
7
8
|
const withSizedContainer = (width: number) => (Story: any) => (
|
|
8
9
|
<ol
|
|
@@ -167,3 +168,22 @@ export const Over400: Story = {
|
|
|
167
168
|
render: () => <>{variants}</>,
|
|
168
169
|
decorators: [withSizedContainer(400)],
|
|
169
170
|
};
|
|
171
|
+
|
|
172
|
+
export const GapsBetweenItems: Story = {
|
|
173
|
+
render: () => {
|
|
174
|
+
const props = {
|
|
175
|
+
title: lorem5,
|
|
176
|
+
subtitle: lorem10,
|
|
177
|
+
media: MEDIA.image,
|
|
178
|
+
control: CONTROLS.switch,
|
|
179
|
+
};
|
|
180
|
+
return (
|
|
181
|
+
<ol className="list-unstyled">
|
|
182
|
+
<ListItem {...props} />
|
|
183
|
+
<ListItem {...props} spotlight="active" />
|
|
184
|
+
<ListItem {...props} spotlight="inactive" />
|
|
185
|
+
<ListItem {...props} />
|
|
186
|
+
</ol>
|
|
187
|
+
);
|
|
188
|
+
},
|
|
189
|
+
};
|
|
@@ -143,7 +143,7 @@ const generateVariantsForControl = (controlType: ControlType): Story => {
|
|
|
143
143
|
return storyConfig(
|
|
144
144
|
{
|
|
145
145
|
render: () => (
|
|
146
|
-
<ol>
|
|
146
|
+
<ol className="list-unstyled">
|
|
147
147
|
{variants.map((variant, variantIndex) => (
|
|
148
148
|
<ListItem
|
|
149
149
|
key={`${controlType}-${variantIndex}-${Math.random()}`}
|
package/src/main.css
CHANGED
|
@@ -3007,6 +3007,11 @@ html:not([dir="rtl"]) .np-flow-navigation--sm .np-flow-navigation__stepper {
|
|
|
3007
3007
|
padding: var(--size-12);
|
|
3008
3008
|
container-type: inline-size;
|
|
3009
3009
|
}
|
|
3010
|
+
.wds-list-item + .wds-list-item-spotlight,
|
|
3011
|
+
.wds-list-item-spotlight + .wds-list-item {
|
|
3012
|
+
margin-top: 12px;
|
|
3013
|
+
margin-top: var(--size-12);
|
|
3014
|
+
}
|
|
3010
3015
|
.wds-list-item:focus-within {
|
|
3011
3016
|
z-index: 1;
|
|
3012
3017
|
}
|
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
declare module '@storybook/react' {
|
|
2
|
-
interface Parameters {
|
|
3
|
-
/** Prefer using `storyConfig` for configuring variants */
|
|
4
|
-
variants?: (
|
|
5
|
-
| 'default'
|
|
6
|
-
| 'light'
|
|
7
|
-
| 'dark'
|
|
8
|
-
| 'bright-green'
|
|
9
|
-
| 'forest-green'
|
|
10
|
-
| 'rtl'
|
|
11
|
-
| (string & Record<string, unknown>)
|
|
12
|
-
)[];
|
|
13
|
-
/** Used by Chromatic */
|
|
14
|
-
chromatic?: ChromaticParameters;
|
|
15
|
-
viewport?: {
|
|
16
|
-
viewports?: Record<string, unknown>;
|
|
17
|
-
defaultViewport?: string;
|
|
18
|
-
};
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Derived from Chromatic's config type definitions
|
|
24
|
-
*
|
|
25
|
-
* @see https://github.com/chromaui/chromatic-cli/blob/main/storybook-addon.d.ts
|
|
26
|
-
*/
|
|
27
|
-
export interface ChromaticParameters {
|
|
28
|
-
/**
|
|
29
|
-
* Prefer using `storyConfig` for configuring viewports if possible.
|
|
30
|
-
*
|
|
31
|
-
* To set a viewport, specify one or more screen widths to the
|
|
32
|
-
* `chromatic.viewports` parameter. When left unspecified, Chromatic
|
|
33
|
-
* uses a default value of `[1200]`. Use `[414]` for 'Large mobile'
|
|
34
|
-
* viewport width.
|
|
35
|
-
*
|
|
36
|
-
* @default [1200]
|
|
37
|
-
*/
|
|
38
|
-
viewports?: [414] | [414, 1200];
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* You can omit stories entirely from Chromatic testing using the disable story parameter.
|
|
42
|
-
*/
|
|
43
|
-
disable?: boolean;
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Chromatic will pause CSS animations and reset them to their beginning state.
|
|
47
|
-
*
|
|
48
|
-
* Some animations are used to "animate in" visible elements. To specify that Chromatic should pause the
|
|
49
|
-
* animation at the end, use the `pauseAnimationAtEnd` story parameter.
|
|
50
|
-
*/
|
|
51
|
-
pauseAnimationAtEnd?: boolean;
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Use story-level delay to ensure a minimum amount of time (in milliseconds) has passed before Chromatic takes a
|
|
55
|
-
* screenshot.
|
|
56
|
-
*/
|
|
57
|
-
delay?: number;
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* The diffThreshold parameter allows you to fine tune the threshold for visual change between snapshots before
|
|
61
|
-
* they’re flagged by Chromatic. Sometimes you need assurance to the sub-pixel and other times you want to skip
|
|
62
|
-
* visual noise generated by non-deterministic rendering such as anti-aliasing.
|
|
63
|
-
*
|
|
64
|
-
* 0 is the most accurate. 1 is the least accurate.
|
|
65
|
-
*
|
|
66
|
-
* @default 0.063
|
|
67
|
-
*/
|
|
68
|
-
diffThreshold?: number;
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* Modes for Chromatic testing.
|
|
72
|
-
*
|
|
73
|
-
* Specify the mode in which Chromatic should run tests. For example, 'light' for light mode,
|
|
74
|
-
* 'dark' for dark mode, or `rtl` for right to left content.
|
|
75
|
-
*/
|
|
76
|
-
modes?: object;
|
|
77
|
-
}
|