@transferwise/components 46.103.0 → 46.104.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/index.js +2 -0
- package/build/index.js.map +1 -1
- package/build/index.mjs +1 -0
- package/build/index.mjs.map +1 -1
- package/build/listItem/AdditionalInfo/ListItemAdditionalInfo.js +56 -0
- package/build/listItem/AdditionalInfo/ListItemAdditionalInfo.js.map +1 -0
- package/build/listItem/AdditionalInfo/ListItemAdditionalInfo.mjs +54 -0
- package/build/listItem/AdditionalInfo/ListItemAdditionalInfo.mjs.map +1 -0
- package/build/listItem/AvatarLayout/ListItemAvatarLayout.js +23 -0
- package/build/listItem/AvatarLayout/ListItemAvatarLayout.js.map +1 -0
- package/build/listItem/AvatarLayout/ListItemAvatarLayout.mjs +21 -0
- package/build/listItem/AvatarLayout/ListItemAvatarLayout.mjs.map +1 -0
- package/build/listItem/AvatarView/ListItemAvatarView.js +23 -0
- package/build/listItem/AvatarView/ListItemAvatarView.js.map +1 -0
- package/build/listItem/AvatarView/ListItemAvatarView.mjs +21 -0
- package/build/listItem/AvatarView/ListItemAvatarView.mjs.map +1 -0
- package/build/listItem/Button/ListItemButton.js +43 -0
- package/build/listItem/Button/ListItemButton.js.map +1 -0
- package/build/listItem/Button/ListItemButton.mjs +41 -0
- package/build/listItem/Button/ListItemButton.mjs.map +1 -0
- package/build/listItem/Checkbox/ListItemCheckbox.js +30 -0
- package/build/listItem/Checkbox/ListItemCheckbox.js.map +1 -0
- package/build/listItem/Checkbox/ListItemCheckbox.mjs +28 -0
- package/build/listItem/Checkbox/ListItemCheckbox.mjs.map +1 -0
- package/build/listItem/IconButton/ListItemIconButton.js +56 -0
- package/build/listItem/IconButton/ListItemIconButton.js.map +1 -0
- package/build/listItem/IconButton/ListItemIconButton.mjs +54 -0
- package/build/listItem/IconButton/ListItemIconButton.mjs.map +1 -0
- package/build/listItem/Image/ListItemImage.js +31 -0
- package/build/listItem/Image/ListItemImage.js.map +1 -0
- package/build/listItem/Image/ListItemImage.mjs +29 -0
- package/build/listItem/Image/ListItemImage.mjs.map +1 -0
- package/build/listItem/ListItem.js +309 -0
- package/build/listItem/ListItem.js.map +1 -0
- package/build/listItem/ListItem.mjs +304 -0
- package/build/listItem/ListItem.mjs.map +1 -0
- package/build/listItem/ListItemContext.js +8 -0
- package/build/listItem/ListItemContext.js.map +1 -0
- package/build/listItem/ListItemContext.mjs +6 -0
- package/build/listItem/ListItemContext.mjs.map +1 -0
- package/build/listItem/Navigation/ListItemNavigation.js +44 -0
- package/build/listItem/Navigation/ListItemNavigation.js.map +1 -0
- package/build/listItem/Navigation/ListItemNavigation.mjs +42 -0
- package/build/listItem/Navigation/ListItemNavigation.mjs.map +1 -0
- package/build/listItem/Prompt/ListItemPrompt.js +59 -0
- package/build/listItem/Prompt/ListItemPrompt.js.map +1 -0
- package/build/listItem/Prompt/ListItemPrompt.mjs +54 -0
- package/build/listItem/Prompt/ListItemPrompt.mjs.map +1 -0
- package/build/listItem/Radio/ListItemRadio.js +30 -0
- package/build/listItem/Radio/ListItemRadio.js.map +1 -0
- package/build/listItem/Radio/ListItemRadio.mjs +28 -0
- package/build/listItem/Radio/ListItemRadio.mjs.map +1 -0
- package/build/listItem/Switch/ListItemSwitch.js +30 -0
- package/build/listItem/Switch/ListItemSwitch.js.map +1 -0
- package/build/listItem/Switch/ListItemSwitch.mjs +28 -0
- package/build/listItem/Switch/ListItemSwitch.mjs.map +1 -0
- package/build/listItem/useListItemControl.js +22 -0
- package/build/listItem/useListItemControl.js.map +1 -0
- package/build/listItem/useListItemControl.mjs +20 -0
- package/build/listItem/useListItemControl.mjs.map +1 -0
- package/build/listItem/useListItemMedia.js +21 -0
- package/build/listItem/useListItemMedia.js.map +1 -0
- package/build/listItem/useListItemMedia.mjs +19 -0
- package/build/listItem/useListItemMedia.mjs.map +1 -0
- package/build/main.css +771 -1
- package/build/styles/button/Button.css +1 -1
- package/build/styles/listItem/ListItem.css +770 -0
- package/build/styles/listItem/ListItem.grid.css +370 -0
- package/build/styles/listItem/Prompt/ListItemPrompt.css +157 -0
- package/build/styles/main.css +771 -1
- package/build/types/index.d.ts +2 -0
- package/build/types/index.d.ts.map +1 -1
- package/build/types/listItem/AdditionalInfo/ListItemAdditionalInfo.d.ts +15 -0
- package/build/types/listItem/AdditionalInfo/ListItemAdditionalInfo.d.ts.map +1 -0
- package/build/types/listItem/AdditionalInfo/index.d.ts +3 -0
- package/build/types/listItem/AdditionalInfo/index.d.ts.map +1 -0
- package/build/types/listItem/AvatarLayout/ListItemAvatarLayout.d.ts +18 -0
- package/build/types/listItem/AvatarLayout/ListItemAvatarLayout.d.ts.map +1 -0
- package/build/types/listItem/AvatarLayout/index.d.ts +3 -0
- package/build/types/listItem/AvatarLayout/index.d.ts.map +1 -0
- package/build/types/listItem/AvatarView/ListItemAvatarView.d.ts +16 -0
- package/build/types/listItem/AvatarView/ListItemAvatarView.d.ts.map +1 -0
- package/build/types/listItem/AvatarView/index.d.ts +3 -0
- package/build/types/listItem/AvatarView/index.d.ts.map +1 -0
- package/build/types/listItem/Button/ListItemButton.d.ts +20 -0
- package/build/types/listItem/Button/ListItemButton.d.ts.map +1 -0
- package/build/types/listItem/Button/index.d.ts +3 -0
- package/build/types/listItem/Button/index.d.ts.map +1 -0
- package/build/types/listItem/Checkbox/ListItemCheckbox.d.ts +14 -0
- package/build/types/listItem/Checkbox/ListItemCheckbox.d.ts.map +1 -0
- package/build/types/listItem/Checkbox/index.d.ts +3 -0
- package/build/types/listItem/Checkbox/index.d.ts.map +1 -0
- package/build/types/listItem/IconButton/ListItemIconButton.d.ts +18 -0
- package/build/types/listItem/IconButton/ListItemIconButton.d.ts.map +1 -0
- package/build/types/listItem/IconButton/index.d.ts +3 -0
- package/build/types/listItem/IconButton/index.d.ts.map +1 -0
- package/build/types/listItem/Image/ListItemImage.d.ts +25 -0
- package/build/types/listItem/Image/ListItemImage.d.ts.map +1 -0
- package/build/types/listItem/Image/index.d.ts +3 -0
- package/build/types/listItem/Image/index.d.ts.map +1 -0
- package/build/types/listItem/ListItem.d.ts +111 -0
- package/build/types/listItem/ListItem.d.ts.map +1 -0
- package/build/types/listItem/ListItemContext.d.ts +21 -0
- package/build/types/listItem/ListItemContext.d.ts.map +1 -0
- package/build/types/listItem/Navigation/ListItemNavigation.d.ts +15 -0
- package/build/types/listItem/Navigation/ListItemNavigation.d.ts.map +1 -0
- package/build/types/listItem/Navigation/index.d.ts +3 -0
- package/build/types/listItem/Navigation/index.d.ts.map +1 -0
- package/build/types/listItem/Prompt/ListItemPrompt.d.ts +16 -0
- package/build/types/listItem/Prompt/ListItemPrompt.d.ts.map +1 -0
- package/build/types/listItem/Prompt/index.d.ts +3 -0
- package/build/types/listItem/Prompt/index.d.ts.map +1 -0
- package/build/types/listItem/Radio/ListItemRadio.d.ts +14 -0
- package/build/types/listItem/Radio/ListItemRadio.d.ts.map +1 -0
- package/build/types/listItem/Radio/index.d.ts +3 -0
- package/build/types/listItem/Radio/index.d.ts.map +1 -0
- package/build/types/listItem/Switch/ListItemSwitch.d.ts +14 -0
- package/build/types/listItem/Switch/ListItemSwitch.d.ts.map +1 -0
- package/build/types/listItem/Switch/index.d.ts +3 -0
- package/build/types/listItem/Switch/index.d.ts.map +1 -0
- package/build/types/listItem/_stories/helpers.d.ts +27 -0
- package/build/types/listItem/_stories/helpers.d.ts.map +1 -0
- package/build/types/listItem/_stories/subcomponents.d.ts +18 -0
- package/build/types/listItem/_stories/subcomponents.d.ts.map +1 -0
- package/build/types/listItem/index.d.ts +14 -0
- package/build/types/listItem/index.d.ts.map +1 -0
- package/build/types/listItem/test-utils.d.ts +7 -0
- package/build/types/listItem/test-utils.d.ts.map +1 -0
- package/build/types/listItem/useListItemControl.d.ts +5 -0
- package/build/types/listItem/useListItemControl.d.ts.map +1 -0
- package/build/types/listItem/useListItemMedia.d.ts +6 -0
- package/build/types/listItem/useListItemMedia.d.ts.map +1 -0
- package/package.json +1 -1
- package/src/actionButton/ActionButton.story.tsx +1 -1
- package/src/avatar/Avatar.story.tsx +1 -1
- package/src/avatarWrapper/AvatarWrapper.story.tsx +1 -1
- package/src/badge/Badge.story.tsx +1 -1
- package/src/button/Button.css +1 -1
- package/src/button/Button.less +1 -1
- package/src/button/Button.story.tsx +95 -9
- package/src/button/LegacyButton.story.tsx +1 -1
- package/src/index.ts +15 -0
- package/src/legacylistItem/LegacyListItem.story.tsx +1 -1
- package/src/legacylistItem/LegacyListItem.tests.story.tsx +2 -1
- package/src/list/List.story.tsx +13 -3
- package/src/listItem/AdditionalInfo/ListItemAdditionalInfo.spec.tsx +56 -0
- package/src/listItem/AdditionalInfo/ListItemAdditionalInfo.story.tsx +198 -0
- package/src/listItem/AdditionalInfo/ListItemAdditionalInfo.tsx +36 -0
- package/src/listItem/AdditionalInfo/index.ts +2 -0
- package/src/listItem/AvatarLayout/ListItemAvatarLayout.spec.tsx +59 -0
- package/src/listItem/AvatarLayout/ListItemAvatarLayout.story.tsx +124 -0
- package/src/listItem/AvatarLayout/ListItemAvatarLayout.tsx +27 -0
- package/src/listItem/AvatarLayout/index.ts +2 -0
- package/src/listItem/AvatarView/ListItemAvatarView.spec.tsx +75 -0
- package/src/listItem/AvatarView/ListItemAvatarView.story.tsx +339 -0
- package/src/listItem/AvatarView/ListItemAvatarView.tsx +27 -0
- package/src/listItem/AvatarView/index.ts +2 -0
- package/src/listItem/Button/ListItemButton.spec.tsx +68 -0
- package/src/listItem/Button/ListItemButton.story.tsx +473 -0
- package/src/listItem/Button/ListItemButton.tsx +56 -0
- package/src/listItem/Button/index.ts +2 -0
- package/src/listItem/Checkbox/ListItemCheckbox.spec.tsx +82 -0
- package/src/listItem/Checkbox/ListItemCheckbox.story.tsx +128 -0
- package/src/listItem/Checkbox/ListItemCheckbox.tsx +33 -0
- package/src/listItem/Checkbox/index.ts +2 -0
- package/src/listItem/IconButton/ListItemIconButton.spec.tsx +119 -0
- package/src/listItem/IconButton/ListItemIconButton.story.tsx +284 -0
- package/src/listItem/IconButton/ListItemIconButton.tsx +73 -0
- package/src/listItem/IconButton/index.ts +2 -0
- package/src/listItem/Image/ListItemImage.spec.tsx +30 -0
- package/src/listItem/Image/ListItemImage.story.tsx +80 -0
- package/src/listItem/Image/ListItemImage.tsx +46 -0
- package/src/listItem/Image/index.ts +2 -0
- package/src/listItem/ListItem.css +770 -0
- package/src/listItem/ListItem.grid.css +370 -0
- package/src/listItem/ListItem.grid.less +622 -0
- package/src/listItem/ListItem.less +287 -0
- package/src/listItem/ListItem.spec.tsx +1511 -0
- package/src/listItem/ListItem.tsx +438 -0
- package/src/listItem/ListItemContext.tsx +26 -0
- package/src/listItem/Navigation/ListItemNavigation.spec.tsx +59 -0
- package/src/listItem/Navigation/ListItemNavigation.story.tsx +112 -0
- package/src/listItem/Navigation/ListItemNavigation.tsx +39 -0
- package/src/listItem/Navigation/index.ts +2 -0
- package/src/listItem/Prompt/ListItemPrompt.css +157 -0
- package/src/listItem/Prompt/ListItemPrompt.less +134 -0
- package/src/listItem/Prompt/ListItemPrompt.spec.tsx +36 -0
- package/src/listItem/Prompt/ListItemPrompt.story.tsx +204 -0
- package/src/listItem/Prompt/ListItemPrompt.tsx +32 -0
- package/src/listItem/Prompt/index.ts +2 -0
- package/src/listItem/Radio/ListItemRadio.spec.tsx +66 -0
- package/src/listItem/Radio/ListItemRadio.story.tsx +111 -0
- package/src/listItem/Radio/ListItemRadio.tsx +33 -0
- package/src/listItem/Radio/index.ts +2 -0
- package/src/listItem/Switch/ListItemSwitch.spec.tsx +47 -0
- package/src/listItem/Switch/ListItemSwitch.story.tsx +79 -0
- package/src/listItem/Switch/ListItemSwitch.tsx +33 -0
- package/src/listItem/Switch/index.ts +2 -0
- package/src/listItem/_stories/ListItem.focus.test.story.tsx +265 -0
- package/src/listItem/_stories/ListItem.layout.test.story.tsx +354 -0
- package/src/listItem/_stories/ListItem.scenarios.story.tsx +228 -0
- package/src/listItem/_stories/ListItem.story.tsx +774 -0
- package/src/listItem/_stories/ListItem.variants.test.story.tsx +271 -0
- package/src/listItem/_stories/helpers.tsx +53 -0
- package/src/listItem/_stories/subcomponents.tsx +139 -0
- package/src/listItem/index.ts +14 -0
- package/src/listItem/test-utils.tsx +33 -0
- package/src/listItem/useListItemControl.tsx +18 -0
- package/src/listItem/useListItemMedia.tsx +16 -0
- package/src/main.css +771 -1
- package/src/main.less +1 -0
- package/src/select/Select.story.tsx +1 -1
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { mockMatchMedia, render, screen } from '../../test-utils';
|
|
2
|
+
import { ListItem, type ListItemProps } from '../ListItem';
|
|
3
|
+
|
|
4
|
+
mockMatchMedia();
|
|
5
|
+
|
|
6
|
+
const avatars = [
|
|
7
|
+
{ imgSrc: 'avatar1.jpg', profileName: 'User 1' },
|
|
8
|
+
{ imgSrc: 'avatar2.jpg', profileName: 'User 2' },
|
|
9
|
+
];
|
|
10
|
+
|
|
11
|
+
const renderWithMedia = (media: ListItemProps['media']) =>
|
|
12
|
+
render(<ListItem title="Test Title" media={media} />);
|
|
13
|
+
|
|
14
|
+
describe('ListItem.AvatarLayout', () => {
|
|
15
|
+
it('applies custom className alongside default class', () => {
|
|
16
|
+
renderWithMedia(
|
|
17
|
+
<ListItem.AvatarLayout
|
|
18
|
+
className="custom-class"
|
|
19
|
+
role="group"
|
|
20
|
+
aria-label="Custom avatar group"
|
|
21
|
+
avatars={avatars}
|
|
22
|
+
/>,
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
expect(screen.getByRole('group', { name: 'Custom avatar group' })).toHaveClass('custom-class');
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('renders both avatars', () => {
|
|
29
|
+
renderWithMedia(<ListItem.AvatarLayout avatars={avatars} />);
|
|
30
|
+
|
|
31
|
+
const images = screen.getAllByRole('presentation');
|
|
32
|
+
expect(images).toHaveLength(avatars.length);
|
|
33
|
+
expect(images[0]).toHaveAttribute('src', avatars[0].imgSrc);
|
|
34
|
+
expect(images[1]).toHaveAttribute('src', avatars[1].imgSrc);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('renders with single avatar', () => {
|
|
38
|
+
renderWithMedia(<ListItem.AvatarLayout avatars={[avatars[0]]} />);
|
|
39
|
+
expect(screen.getByRole('presentation')).toHaveAttribute('src', avatars[0].imgSrc);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('handles empty avatars array', () => {
|
|
43
|
+
renderWithMedia(<ListItem.AvatarLayout avatars={[]} />);
|
|
44
|
+
expect(screen.queryByRole('img')).not.toBeInTheDocument();
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('supports accessibility props', () => {
|
|
48
|
+
renderWithMedia(<ListItem.AvatarLayout aria-label="User avatars" avatars={avatars} />);
|
|
49
|
+
expect(screen.getByLabelText('User avatars')).toBeInTheDocument();
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('passes through role', () => {
|
|
53
|
+
renderWithMedia(
|
|
54
|
+
<ListItem.AvatarLayout role="group" aria-label="Avatar group" avatars={avatars} />,
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
expect(screen.getByRole('group', { name: 'Avatar group' })).toBeInTheDocument();
|
|
58
|
+
});
|
|
59
|
+
});
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { Meta, StoryObj } from '@storybook/react-webpack5';
|
|
2
|
+
import { Flag } from '@wise/art';
|
|
3
|
+
import { lorem10 } from '../../test-utils';
|
|
4
|
+
import { ListItem } from '../ListItem';
|
|
5
|
+
import List from '../../list';
|
|
6
|
+
import {
|
|
7
|
+
SB_LIST_ITEM_ADDITIONAL_INFO as INFO,
|
|
8
|
+
SB_LIST_ITEM_CONTROLS as CONTROLS,
|
|
9
|
+
} from '../_stories/subcomponents';
|
|
10
|
+
import { disableControls } from '../_stories/helpers';
|
|
11
|
+
import type { ListItemAvatarLayoutProps } from './ListItemAvatarLayout';
|
|
12
|
+
|
|
13
|
+
export default {
|
|
14
|
+
component: ListItem.AvatarLayout,
|
|
15
|
+
title: 'Content/ListItem/ListItem.AvatarLayout',
|
|
16
|
+
parameters: {
|
|
17
|
+
docs: {
|
|
18
|
+
toc: true,
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
args: {
|
|
22
|
+
avatars: [{ asset: <Flag code="gb" /> }, { asset: <Flag code="eur" /> }],
|
|
23
|
+
orientation: 'horizontal',
|
|
24
|
+
size: 48,
|
|
25
|
+
},
|
|
26
|
+
argTypes: {
|
|
27
|
+
avatars: {
|
|
28
|
+
description: 'Array of avatar objects with asset and optional styling properties',
|
|
29
|
+
table: {
|
|
30
|
+
type: { summary: 'SingleAvatarType[]' },
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
orientation: {
|
|
34
|
+
control: 'inline-radio',
|
|
35
|
+
options: ['horizontal', 'diagonal'],
|
|
36
|
+
description: 'Layout orientation for multiple avatars',
|
|
37
|
+
},
|
|
38
|
+
size: {
|
|
39
|
+
control: 'select',
|
|
40
|
+
options: [32, 40, 48, 56, 72],
|
|
41
|
+
description: 'Size of the avatar layout',
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
} satisfies Meta<ListItemAvatarLayoutProps>;
|
|
45
|
+
|
|
46
|
+
type Story = StoryObj<ListItemAvatarLayoutProps>;
|
|
47
|
+
|
|
48
|
+
export const Playground: Story = {
|
|
49
|
+
tags: ['!autodocs'],
|
|
50
|
+
render: (args: ListItemAvatarLayoutProps) => {
|
|
51
|
+
return (
|
|
52
|
+
<List>
|
|
53
|
+
<ListItem
|
|
54
|
+
title="Transfer from GBP to EUR"
|
|
55
|
+
subtitle="Currency exchange"
|
|
56
|
+
media={<ListItem.AvatarLayout {...args} />}
|
|
57
|
+
control={CONTROLS.iconButton}
|
|
58
|
+
additionalInfo={INFO.nonInteractive}
|
|
59
|
+
/>
|
|
60
|
+
</List>
|
|
61
|
+
);
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* AvatarLayout supports two orientations: horizontal (default) and diagonal. <br />
|
|
67
|
+
* Please refer to the [design documentation](https://wise.design/components/avatar#double) for more details.
|
|
68
|
+
*/
|
|
69
|
+
export const Orientations: Story = {
|
|
70
|
+
argTypes: disableControls()(['orientation']),
|
|
71
|
+
render: (args) => {
|
|
72
|
+
return (
|
|
73
|
+
<List>
|
|
74
|
+
<ListItem
|
|
75
|
+
title="Horizontal orientation"
|
|
76
|
+
subtitle={lorem10}
|
|
77
|
+
media={<ListItem.AvatarLayout {...args} orientation="horizontal" />}
|
|
78
|
+
control={CONTROLS.iconButton}
|
|
79
|
+
/>
|
|
80
|
+
|
|
81
|
+
<ListItem
|
|
82
|
+
title="Diagonal orientation"
|
|
83
|
+
subtitle={lorem10}
|
|
84
|
+
media={<ListItem.AvatarLayout {...args} orientation="diagonal" />}
|
|
85
|
+
control={CONTROLS.iconButton}
|
|
86
|
+
/>
|
|
87
|
+
</List>
|
|
88
|
+
);
|
|
89
|
+
},
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* AvatarLayout supports various sizes to fit different list item contexts.
|
|
94
|
+
*/
|
|
95
|
+
export const Sizes: Story = {
|
|
96
|
+
parameters: {
|
|
97
|
+
docs: {
|
|
98
|
+
canvas: {
|
|
99
|
+
sourceState: 'hidden',
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
argTypes: disableControls()(['size', 'orientation']),
|
|
104
|
+
render: (args) => {
|
|
105
|
+
const sizes = [32, 40, 48, 56, 72] as const;
|
|
106
|
+
const orientations = ['horizontal', 'diagonal'] as const;
|
|
107
|
+
|
|
108
|
+
return (
|
|
109
|
+
<List>
|
|
110
|
+
{orientations.map((orientation) =>
|
|
111
|
+
sizes.map((size) => (
|
|
112
|
+
<ListItem
|
|
113
|
+
key={`${orientation}-${size}`}
|
|
114
|
+
title={`Size ${size}`}
|
|
115
|
+
subtitle={lorem10}
|
|
116
|
+
media={<ListItem.AvatarLayout {...args} size={size} orientation={orientation} />}
|
|
117
|
+
control={CONTROLS.iconButton}
|
|
118
|
+
/>
|
|
119
|
+
)),
|
|
120
|
+
)}
|
|
121
|
+
</List>
|
|
122
|
+
);
|
|
123
|
+
},
|
|
124
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import AvatarLayoutComp, { type AvatarLayoutProps } from '../../avatarLayout';
|
|
2
|
+
import { clsx } from 'clsx';
|
|
3
|
+
import { useListItemMedia } from '../useListItemMedia';
|
|
4
|
+
|
|
5
|
+
type SizeProp = { size?: 32 | 40 | 48 | 56 | 72 };
|
|
6
|
+
|
|
7
|
+
export type ListItemAvatarLayoutProps = Omit<AvatarLayoutProps, 'size' | 'interactive'> & SizeProp;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* This component allows for rendering 2 avatars. It's a thin wrapper around the
|
|
11
|
+
* [AvatarLayout component](https://storybook.wise.design/?path=/docs/content-avatarlayout--docs), but offers only
|
|
12
|
+
* a subset of its sizes, and disallows interactive mode, in line with the ListItem's constraints. <br />
|
|
13
|
+
* <br />
|
|
14
|
+
* Please refer to the [Design documentation](https://wise.design/components/list-item#avatar) for details.
|
|
15
|
+
*/
|
|
16
|
+
export const AvatarLayout = ({ className, size = 48, ...props }: ListItemAvatarLayoutProps) => {
|
|
17
|
+
useListItemMedia(size);
|
|
18
|
+
|
|
19
|
+
return (
|
|
20
|
+
<AvatarLayoutComp
|
|
21
|
+
{...props}
|
|
22
|
+
size={size}
|
|
23
|
+
className={clsx('wds-list-item-media-avatar-layout', className)}
|
|
24
|
+
/>
|
|
25
|
+
);
|
|
26
|
+
};
|
|
27
|
+
AvatarLayout.displayName = 'ListItem.AvatarLayout';
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { mockMatchMedia, render, screen } from '../../test-utils';
|
|
2
|
+
import { ListItem, type ListItemProps } from '../ListItem';
|
|
3
|
+
|
|
4
|
+
mockMatchMedia();
|
|
5
|
+
|
|
6
|
+
const baseProps = {
|
|
7
|
+
title: 'Test Title',
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
const avatarProps = {
|
|
11
|
+
imgSrc: 'avatar.jpg',
|
|
12
|
+
profileName: 'User Avatar',
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const renderWithMedia = (media: ListItemProps['media']) =>
|
|
16
|
+
render(<ListItem title={baseProps.title} media={media} />);
|
|
17
|
+
|
|
18
|
+
describe('ListItem.AvatarView', () => {
|
|
19
|
+
it('applies custom className alongside default class', () => {
|
|
20
|
+
const { container } = renderWithMedia(
|
|
21
|
+
<ListItem.AvatarView
|
|
22
|
+
className="custom-class"
|
|
23
|
+
imgSrc={avatarProps.imgSrc}
|
|
24
|
+
profileName={avatarProps.profileName}
|
|
25
|
+
/>,
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
expect(container.querySelector('.custom-class')).toBeInTheDocument();
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('renders avatar with correct image src', () => {
|
|
32
|
+
renderWithMedia(
|
|
33
|
+
<ListItem.AvatarView imgSrc={avatarProps.imgSrc} profileName={avatarProps.profileName} />,
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
expect(screen.getByRole('presentation')).toHaveAttribute('src', avatarProps.imgSrc);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('renders without image when no imgSrc provided', () => {
|
|
40
|
+
renderWithMedia(<ListItem.AvatarView profileName={avatarProps.profileName} />);
|
|
41
|
+
expect(screen.queryByRole('img')).not.toBeInTheDocument();
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('supports accessibility props', () => {
|
|
45
|
+
renderWithMedia(
|
|
46
|
+
<ListItem.AvatarView
|
|
47
|
+
imgSrc={avatarProps.imgSrc}
|
|
48
|
+
profileName={avatarProps.profileName}
|
|
49
|
+
aria-label="Profile picture"
|
|
50
|
+
/>,
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
expect(screen.getByLabelText('Profile picture')).toBeInTheDocument();
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('passes through role attribute', () => {
|
|
57
|
+
renderWithMedia(
|
|
58
|
+
<ListItem.AvatarView
|
|
59
|
+
imgSrc={avatarProps.imgSrc}
|
|
60
|
+
profileName={avatarProps.profileName}
|
|
61
|
+
role="button"
|
|
62
|
+
aria-label="Profile button"
|
|
63
|
+
/>,
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
expect(screen.getByRole('button', { name: 'Profile button' })).toBeInTheDocument();
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('renders initials when no image provided', () => {
|
|
70
|
+
const johnDoeProfile = 'John Doe';
|
|
71
|
+
renderWithMedia(<ListItem.AvatarView profileName={johnDoeProfile} />);
|
|
72
|
+
|
|
73
|
+
expect(screen.getByText('JD')).toBeInTheDocument();
|
|
74
|
+
});
|
|
75
|
+
});
|
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
import { Meta, StoryObj } from '@storybook/react-webpack5';
|
|
2
|
+
import { Leaf, Taxi } from '@transferwise/icons';
|
|
3
|
+
import { lorem10 } from '../../test-utils';
|
|
4
|
+
import { ProfileType } from '../../common';
|
|
5
|
+
import List from '../../list';
|
|
6
|
+
import { ListItem } from '../ListItem';
|
|
7
|
+
import {
|
|
8
|
+
SB_LIST_ITEM_ADDITIONAL_INFO as INFO,
|
|
9
|
+
SB_LIST_ITEM_CONTROLS as CONTROLS,
|
|
10
|
+
} from '../_stories/subcomponents';
|
|
11
|
+
import { disableControls } from '../_stories/helpers';
|
|
12
|
+
import type { ListItemAvatarViewProps } from './ListItemAvatarView';
|
|
13
|
+
|
|
14
|
+
const hideControls = disableControls(['badge', 'children']);
|
|
15
|
+
|
|
16
|
+
const SIZES = [32, 40, 48, 56, 72] as const;
|
|
17
|
+
const BADGES = {
|
|
18
|
+
'Country flag badge': { flagCode: 'GBP' },
|
|
19
|
+
'StatusIcon badge': { status: 'warning' },
|
|
20
|
+
'Icon badge': { icon: <Taxi /> },
|
|
21
|
+
'Default action badge': {
|
|
22
|
+
type: 'action',
|
|
23
|
+
},
|
|
24
|
+
'Default reference badge': {
|
|
25
|
+
type: 'reference',
|
|
26
|
+
},
|
|
27
|
+
'Action badge with custom icon': {
|
|
28
|
+
type: 'action',
|
|
29
|
+
icon: <Taxi />,
|
|
30
|
+
},
|
|
31
|
+
'Reference badge with custom icon': {
|
|
32
|
+
type: 'reference',
|
|
33
|
+
icon: <Taxi />,
|
|
34
|
+
},
|
|
35
|
+
'Custom badge': {
|
|
36
|
+
asset: (
|
|
37
|
+
<div
|
|
38
|
+
className="d-flex align-items-center justify-content-center"
|
|
39
|
+
style={{
|
|
40
|
+
backgroundColor: 'var(--color-bright-pink)',
|
|
41
|
+
color: 'var(--color-interactive-primary)',
|
|
42
|
+
width: '100%',
|
|
43
|
+
height: '100%',
|
|
44
|
+
}}
|
|
45
|
+
>
|
|
46
|
+
<Leaf />
|
|
47
|
+
</div>
|
|
48
|
+
),
|
|
49
|
+
},
|
|
50
|
+
} as const;
|
|
51
|
+
|
|
52
|
+
export default {
|
|
53
|
+
component: ListItem.AvatarView,
|
|
54
|
+
title: 'Content/ListItem/ListItem.AvatarView',
|
|
55
|
+
parameters: {
|
|
56
|
+
docs: {
|
|
57
|
+
toc: true,
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
args: {
|
|
61
|
+
size: 48,
|
|
62
|
+
selected: false,
|
|
63
|
+
badge: { type: 'action' },
|
|
64
|
+
notification: false,
|
|
65
|
+
profileName: undefined,
|
|
66
|
+
profileType: undefined,
|
|
67
|
+
imgSrc: undefined,
|
|
68
|
+
},
|
|
69
|
+
argTypes: {
|
|
70
|
+
size: {
|
|
71
|
+
control: 'select',
|
|
72
|
+
options: SIZES,
|
|
73
|
+
description: 'Size of the avatar',
|
|
74
|
+
},
|
|
75
|
+
profileName: {
|
|
76
|
+
control: 'text',
|
|
77
|
+
description: 'Name used to generate initials when no image or icon is provided',
|
|
78
|
+
},
|
|
79
|
+
imgSrc: {
|
|
80
|
+
control: 'text',
|
|
81
|
+
description: 'URL of the profile image',
|
|
82
|
+
},
|
|
83
|
+
profileType: {
|
|
84
|
+
control: 'select',
|
|
85
|
+
options: [ProfileType.PERSONAL, ProfileType.BUSINESS],
|
|
86
|
+
description: 'Type of profile for default icons',
|
|
87
|
+
table: {
|
|
88
|
+
type: { summary: 'ProfileType' },
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
notification: {
|
|
92
|
+
control: 'boolean',
|
|
93
|
+
description: 'Shows notification dot',
|
|
94
|
+
},
|
|
95
|
+
selected: {
|
|
96
|
+
control: 'boolean',
|
|
97
|
+
description: 'Toggles selected state',
|
|
98
|
+
},
|
|
99
|
+
badge: {
|
|
100
|
+
description: 'Badge configuration object',
|
|
101
|
+
table: {
|
|
102
|
+
type: { summary: 'AvatarViewBadgeProps' },
|
|
103
|
+
},
|
|
104
|
+
},
|
|
105
|
+
children: {
|
|
106
|
+
table: {
|
|
107
|
+
type: { summary: 'ReactNode' },
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
style: {
|
|
111
|
+
table: {
|
|
112
|
+
disable: true,
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
} satisfies Meta<ListItemAvatarViewProps>;
|
|
117
|
+
|
|
118
|
+
type Story = StoryObj<ListItemAvatarViewProps>;
|
|
119
|
+
|
|
120
|
+
export const Playground: Story = {
|
|
121
|
+
tags: ['!autodocs'],
|
|
122
|
+
render: (args: ListItemAvatarViewProps) => {
|
|
123
|
+
return (
|
|
124
|
+
<List>
|
|
125
|
+
<ListItem
|
|
126
|
+
title="John Smith"
|
|
127
|
+
subtitle="Personal account"
|
|
128
|
+
media={<ListItem.AvatarView {...args} />}
|
|
129
|
+
control={CONTROLS.iconButton}
|
|
130
|
+
additionalInfo={INFO.nonInteractive}
|
|
131
|
+
/>
|
|
132
|
+
</List>
|
|
133
|
+
);
|
|
134
|
+
},
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* AvatarView can display different types of content including icons, profile images, and initials. <br />
|
|
139
|
+
* Refer to the [design documentation](https://wise.design/components/avatar#:~:text=56%2C%20%E2%80%A8and%2072.-,Media,-There%20are%204) for details.
|
|
140
|
+
*/
|
|
141
|
+
export const ContentTypes: Story = {
|
|
142
|
+
args: {
|
|
143
|
+
badge: undefined,
|
|
144
|
+
},
|
|
145
|
+
argTypes: hideControls(['profileName', 'imgSrc', 'profileType']),
|
|
146
|
+
parameters: {
|
|
147
|
+
docs: {
|
|
148
|
+
canvas: {
|
|
149
|
+
sourceState: 'hidden',
|
|
150
|
+
},
|
|
151
|
+
},
|
|
152
|
+
},
|
|
153
|
+
render: (args) => {
|
|
154
|
+
return (
|
|
155
|
+
<List>
|
|
156
|
+
<ListItem
|
|
157
|
+
title="With icon"
|
|
158
|
+
subtitle="Default icon content"
|
|
159
|
+
media={
|
|
160
|
+
<ListItem.AvatarView {...args}>
|
|
161
|
+
<Taxi />
|
|
162
|
+
</ListItem.AvatarView>
|
|
163
|
+
}
|
|
164
|
+
control={CONTROLS.iconButton}
|
|
165
|
+
/>
|
|
166
|
+
|
|
167
|
+
<ListItem
|
|
168
|
+
title="With profile image"
|
|
169
|
+
subtitle="User profile picture."
|
|
170
|
+
media={
|
|
171
|
+
<ListItem.AvatarView
|
|
172
|
+
{...args}
|
|
173
|
+
imgSrc="../avatar-square-dude.webp"
|
|
174
|
+
profileName="John Smith"
|
|
175
|
+
/>
|
|
176
|
+
}
|
|
177
|
+
control={CONTROLS.iconButton}
|
|
178
|
+
/>
|
|
179
|
+
|
|
180
|
+
<ListItem
|
|
181
|
+
title="With initials"
|
|
182
|
+
subtitle="Fallback for when no profile image is available."
|
|
183
|
+
media={<ListItem.AvatarView {...args} profileName="Sarah Johnson" />}
|
|
184
|
+
control={CONTROLS.iconButton}
|
|
185
|
+
/>
|
|
186
|
+
|
|
187
|
+
<ListItem
|
|
188
|
+
title="Business profile"
|
|
189
|
+
subtitle="Fallback for when no logo is available"
|
|
190
|
+
media={<ListItem.AvatarView {...args} profileType={ProfileType.BUSINESS} />}
|
|
191
|
+
control={CONTROLS.iconButton}
|
|
192
|
+
/>
|
|
193
|
+
|
|
194
|
+
<ListItem
|
|
195
|
+
title="Personal profile"
|
|
196
|
+
subtitle="Fallback for when no personal data is available or when image fails to load."
|
|
197
|
+
media={
|
|
198
|
+
<ListItem.AvatarView
|
|
199
|
+
{...args}
|
|
200
|
+
profileType={ProfileType.PERSONAL}
|
|
201
|
+
profileName="Alex Chen"
|
|
202
|
+
/>
|
|
203
|
+
}
|
|
204
|
+
control={CONTROLS.iconButton}
|
|
205
|
+
/>
|
|
206
|
+
</List>
|
|
207
|
+
);
|
|
208
|
+
},
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* AvatarView supports 5 sizes to fit different list item contexts: `32`, `40`, `48`, `56`, `72`. If decorated with a Badge, those will be sized accordingly as well. <br />
|
|
213
|
+
* Please refer to the [design documentation](https://wise.design/components/list-item#avatar:~:text=of%20the%20avatar.-,Avatar%20sizes,-List%20item%20supports) for details on when to use which one.
|
|
214
|
+
*/
|
|
215
|
+
export const Sizes: Story = {
|
|
216
|
+
parameters: {
|
|
217
|
+
docs: {
|
|
218
|
+
canvas: {
|
|
219
|
+
sourceState: 'hidden',
|
|
220
|
+
},
|
|
221
|
+
},
|
|
222
|
+
},
|
|
223
|
+
argTypes: hideControls(['profileName', 'imgSrc', 'profileType', 'size']),
|
|
224
|
+
render: (args) => {
|
|
225
|
+
return (
|
|
226
|
+
<List>
|
|
227
|
+
{SIZES.map((size) => (
|
|
228
|
+
<ListItem
|
|
229
|
+
key={size}
|
|
230
|
+
title={`Size ${size}`}
|
|
231
|
+
subtitle={lorem10}
|
|
232
|
+
media={
|
|
233
|
+
<ListItem.AvatarView {...args} size={size}>
|
|
234
|
+
<Taxi />
|
|
235
|
+
</ListItem.AvatarView>
|
|
236
|
+
}
|
|
237
|
+
control={CONTROLS.iconButton}
|
|
238
|
+
/>
|
|
239
|
+
))}
|
|
240
|
+
</List>
|
|
241
|
+
);
|
|
242
|
+
},
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Similarly, AvatarView also support a notification dot, which also adjusts to the Avatar's size. <br />
|
|
247
|
+
* **NB:** You cannot use notification and badge at the same time – badge will always take precedence over notification.
|
|
248
|
+
*/
|
|
249
|
+
export const Notification: Story = {
|
|
250
|
+
parameters: {
|
|
251
|
+
docs: {
|
|
252
|
+
canvas: {
|
|
253
|
+
sourceState: 'hidden',
|
|
254
|
+
},
|
|
255
|
+
},
|
|
256
|
+
},
|
|
257
|
+
args: {
|
|
258
|
+
badge: undefined,
|
|
259
|
+
},
|
|
260
|
+
argTypes: hideControls([
|
|
261
|
+
'profileName',
|
|
262
|
+
'imgSrc',
|
|
263
|
+
'profileType',
|
|
264
|
+
'size',
|
|
265
|
+
'notification',
|
|
266
|
+
'selected',
|
|
267
|
+
]),
|
|
268
|
+
render: (args) => {
|
|
269
|
+
return (
|
|
270
|
+
<List>
|
|
271
|
+
{SIZES.map((size) => (
|
|
272
|
+
<ListItem
|
|
273
|
+
key={size}
|
|
274
|
+
title={`Size ${size}`}
|
|
275
|
+
subtitle={lorem10}
|
|
276
|
+
media={
|
|
277
|
+
<ListItem.AvatarView {...args} size={size} notification>
|
|
278
|
+
<Taxi />
|
|
279
|
+
</ListItem.AvatarView>
|
|
280
|
+
}
|
|
281
|
+
control={CONTROLS.iconButton}
|
|
282
|
+
/>
|
|
283
|
+
))}
|
|
284
|
+
</List>
|
|
285
|
+
);
|
|
286
|
+
},
|
|
287
|
+
};
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* AvatarView supports different type of badges for additional context and information. <br />
|
|
291
|
+
* Refer to the [design documentation](https://wise.design/components/avatar#:~:text=support%20the%20information.-,With%20badge,-Badges%20contain%20additional) for details.
|
|
292
|
+
*/
|
|
293
|
+
export const Badges: Story = {
|
|
294
|
+
args: {
|
|
295
|
+
imgSrc: '../avatar-square-dude.webp',
|
|
296
|
+
},
|
|
297
|
+
argTypes: hideControls(['profileName', 'imgSrc', 'profileType', 'notification', 'selected']),
|
|
298
|
+
parameters: {
|
|
299
|
+
docs: {
|
|
300
|
+
canvas: {
|
|
301
|
+
sourceState: 'hidden',
|
|
302
|
+
},
|
|
303
|
+
},
|
|
304
|
+
},
|
|
305
|
+
render: (args) => {
|
|
306
|
+
return (
|
|
307
|
+
<List>
|
|
308
|
+
{Object.entries(BADGES).map(([title, badge]) => (
|
|
309
|
+
<ListItem
|
|
310
|
+
key={title}
|
|
311
|
+
title={title}
|
|
312
|
+
subtitle={lorem10}
|
|
313
|
+
media={<ListItem.AvatarView {...args} badge={badge} />}
|
|
314
|
+
control={CONTROLS.iconButton}
|
|
315
|
+
/>
|
|
316
|
+
))}
|
|
317
|
+
</List>
|
|
318
|
+
);
|
|
319
|
+
},
|
|
320
|
+
};
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* AvatarView supports selected state for interactive contexts.
|
|
324
|
+
*/
|
|
325
|
+
export const Selected: Story = {
|
|
326
|
+
argTypes: hideControls(['profileName', 'imgSrc', 'profileType', 'notification', 'selected']),
|
|
327
|
+
render: (args) => {
|
|
328
|
+
return (
|
|
329
|
+
<List>
|
|
330
|
+
<ListItem
|
|
331
|
+
title="Selected state"
|
|
332
|
+
subtitle="Currently selected item"
|
|
333
|
+
media={<ListItem.AvatarView profileName="Alex Chen" selected />}
|
|
334
|
+
control={CONTROLS.iconButton}
|
|
335
|
+
/>
|
|
336
|
+
</List>
|
|
337
|
+
);
|
|
338
|
+
},
|
|
339
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { clsx } from 'clsx';
|
|
2
|
+
import AvatarViewComp, { type AvatarViewProps } from '../../avatarView';
|
|
3
|
+
import { useListItemMedia } from '../useListItemMedia';
|
|
4
|
+
|
|
5
|
+
export type ListItemAvatarViewProps = Omit<AvatarViewProps, 'size' | 'interactive'> & {
|
|
6
|
+
size?: 32 | 40 | 48 | 56 | 72;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* This component renders a single avatar. It's a thin wrapper around the
|
|
11
|
+
* [AvatarView component](https://storybook.wise.design/?path=/docs/content-avatarview--docs), but offers only
|
|
12
|
+
* a subset of its sizes, in line with the ListItem's constraints. <br />
|
|
13
|
+
* <br />
|
|
14
|
+
* Please refer to the [Design documentation](https://wise.design/components/list-item#avatar) for details.
|
|
15
|
+
*/
|
|
16
|
+
export const AvatarView = ({ className, size = 48, ...props }: ListItemAvatarViewProps) => {
|
|
17
|
+
useListItemMedia(size);
|
|
18
|
+
|
|
19
|
+
return (
|
|
20
|
+
<AvatarViewComp
|
|
21
|
+
{...props}
|
|
22
|
+
size={size}
|
|
23
|
+
className={clsx('wds-list-item-media-avatar-view', className)}
|
|
24
|
+
/>
|
|
25
|
+
);
|
|
26
|
+
};
|
|
27
|
+
AvatarView.displayName = 'ListItem.AvatarView';
|