Skip to content

Commit 5b63854

Browse files
committed
DSM Update
1 parent 8a0e54c commit 5b63854

20 files changed

+581
-47
lines changed

front-packages/akeneo-design-system/example/src/__snapshots__/App.test.js.snap

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ exports[`renders the expected elements 1`] = `
55
<div>
66
<div>
77
<span
8-
class="sc-dlnjPT jLeOJM"
8+
class="sc-jSFkmK jjMYxm"
99
>
1010
Success
1111
</span>

front-packages/akeneo-design-system/jest-puppeteer.config.js

+1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ module.exports = {
22
launch: {
33
dumpio: true,
44
headless: true,
5+
args: ['--no-sandbox', '--disable-setuid-sandbox']
56
},
67
server: {
78
command: 'yarn http-server storybook-static -p 6006',
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
import {Meta, Story, ArgsTable, Canvas} from '@storybook/addon-docs';
2+
import {Avatar} from './Avatar.tsx';
3+
import {Avatars} from './Avatars.tsx';
4+
5+
<Meta
6+
title="Components/Avatar"
7+
component={Avatar}
8+
argTypes={{
9+
firstName: {control: {type: 'text'}},
10+
lastName: {control: {type: 'text'}},
11+
username: {control: {type: 'text'}},
12+
avatarUrl: {control: {type: 'text'}},
13+
size: {control: {type: 'select', options: ['default', 'big']}},
14+
}}
15+
args={{
16+
firstName: 'John',
17+
lastName: 'Doe',
18+
username: 'admin',
19+
avatarUrl: undefined,
20+
size: 'default',
21+
}}
22+
/>
23+
24+
# Avatar
25+
26+
## Usage
27+
28+
This component is used to display users avatars. If no avatar is available, first letters of the first and last name are
29+
displayed with a dedicated color.
30+
31+
## Playground
32+
33+
<Canvas>
34+
<Story name="Standard">
35+
{args => {
36+
return <Avatar firstName={'John'} lastName={'Doe'} username={'admin'} {...args} />;
37+
}}
38+
</Story>
39+
</Canvas>
40+
41+
<ArgsTable story="Standard" />
42+
43+
## Variation on background colors
44+
45+
The background color is based from the username.
46+
47+
<Canvas>
48+
<Story name="Background colors">
49+
{args => {
50+
return (
51+
<>
52+
<Avatar {...args} firstName={'Albert'} lastName={'Doe'} username={'a'} />
53+
<Avatar {...args} firstName={'Bertrand'} lastName={'Doe'} username={'b'} />
54+
<Avatar {...args} firstName={'Chris'} lastName={'Doe'} username={'c'} />
55+
<Avatar {...args} firstName={'Danny'} lastName={'Doe'} username={'d'} />
56+
<Avatar {...args} firstName={'Elon'} lastName={'Doe'} username={'e'} />
57+
<Avatar {...args} firstName={'Fred'} lastName={'Doe'} username={'f'} />
58+
<Avatar {...args} firstName={'Gus'} lastName={'Doe'} username={'g'} />
59+
<Avatar {...args} firstName={'Helen'} lastName={'Doe'} username={'h'} />
60+
<Avatar {...args} firstName={'Isabel'} lastName={'Doe'} username={'i'} />
61+
<Avatar {...args} firstName={'John'} lastName={'Doe'} username={'j'} />
62+
<Avatar {...args} firstName={'Kurt'} lastName={'Doe'} username={'k'} />
63+
<Avatar {...args} firstName={'Leonard'} lastName={'Doe'} username={'l'} />
64+
</>
65+
);
66+
}}
67+
</Story>
68+
</Canvas>
69+
70+
## Variation with image
71+
72+
<Canvas>
73+
<Story name="With image">
74+
{args => {
75+
return (
76+
<>
77+
<Avatar {...args} avatarUrl={'https://picsum.photos/seed/akeneo/32/32'} />
78+
</>
79+
);
80+
}}
81+
</Story>
82+
</Canvas>
83+
84+
## Variation on List
85+
86+
You can use a dedicated component to display avatar list. After a defined maximum, other avatars are not displayed.
87+
88+
<Canvas>
89+
<Story name="Avatar list">
90+
{args => {
91+
return (
92+
<>
93+
<Avatars
94+
max={5}
95+
title="Helen Doe&#10;Isabel Doe&#10;John Doe&#10;Kurt Doe&#10;Leonard Doe"
96+
>
97+
<Avatar
98+
{...args}
99+
firstName={'Albert'}
100+
lastName={'Doe'}
101+
username={'a'}
102+
avatarUrl={'https://picsum.photos/seed/akeneo/32/32'}
103+
/>
104+
<Avatar {...args} firstName={'Bertrand'} lastName={'Doe'} username={'b'} />
105+
<Avatar
106+
{...args}
107+
firstName={'Chris'}
108+
lastName={'Doe'}
109+
username={'c'}
110+
avatarUrl={'https://picsum.photos/seed/bkeneo/32/32'}
111+
/>
112+
<Avatar {...args} firstName={'Danny'} lastName={'Doe'} username={'d'} />
113+
<Avatar
114+
{...args}
115+
firstName={'Elon'}
116+
lastName={'Doe'}
117+
username={'e'}
118+
avatarUrl={'https://picsum.photos/seed/ckeneo/32/32'}
119+
/>
120+
<Avatar {...args} firstName={'Fred'} lastName={'Doe'} username={'f'} />
121+
<Avatar {...args} firstName={'Gus'} lastName={'Doe'} username={'g'} />
122+
<Avatar {...args} firstName={'Helen'} lastName={'Doe'} username={'h'} />
123+
<Avatar {...args} firstName={'Isabel'} lastName={'Doe'} username={'i'} />
124+
<Avatar {...args} firstName={'John'} lastName={'Doe'} username={'j'} />
125+
<Avatar {...args} firstName={'Kurt'} lastName={'Doe'} username={'k'} />
126+
<Avatar {...args} firstName={'Leonard'} lastName={'Doe'} username={'l'} />
127+
</Avatars>
128+
</>
129+
);
130+
}}
131+
</Story>
132+
</Canvas>
133+
134+
## Variation on Size
135+
136+
<Canvas>
137+
<Story name="Avatar size">
138+
{args => {
139+
return (
140+
<>
141+
<Avatar {...args} size={'default'} />
142+
<Avatar {...args} size={'big'} />
143+
</>
144+
);
145+
}}
146+
</Story>
147+
</Canvas>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
import React, {useMemo} from 'react';
2+
import styled, {css} from 'styled-components';
3+
import {useTheme} from '../../hooks';
4+
import {Override} from '../../shared';
5+
import {AkeneoThemedProps, getColor} from '../../theme';
6+
7+
const AvatarContainer = styled.span<AvatarProps & AkeneoThemedProps>`
8+
${({size}) =>
9+
size === 'default'
10+
? css`
11+
height: 32px;
12+
width: 32px;
13+
line-height: 32px;
14+
font-size: 15px;
15+
border-radius: 32px;
16+
`
17+
: css`
18+
height: 140px;
19+
width: 140px;
20+
line-height: 140px;
21+
font-size: 66px;
22+
border-radius: 140px;
23+
`}
24+
display: inline-block;
25+
color: ${getColor('white')};
26+
text-align: center;
27+
background-position: center;
28+
background-repeat: no-repeat;
29+
background-size: cover;
30+
text-transform: uppercase;
31+
cursor: ${({onClick}) => (onClick ? 'pointer' : 'default')};
32+
`;
33+
34+
type AvatarProps = Override<
35+
React.HTMLAttributes<HTMLSpanElement>,
36+
{
37+
/**
38+
* Username to use as fallback if the avatar is not provided and the Firstname and Lastname are empty.
39+
*/
40+
username: string;
41+
42+
/**
43+
* Firstname to use as fallback with the Lastname if the avatar is not provided.
44+
*/
45+
firstName: string;
46+
47+
/**
48+
* Lastname to use as fallback with the Firstname if the avatar is not provided.
49+
*/
50+
lastName: string;
51+
52+
/**
53+
* Url of the avatar image.
54+
*/
55+
avatarUrl?: string;
56+
57+
/**
58+
* Size of the avatar.
59+
*/
60+
size?: 'default' | 'big';
61+
}
62+
>;
63+
64+
const Avatar = ({username, firstName, lastName, avatarUrl, size = 'default', ...rest}: AvatarProps) => {
65+
const theme = useTheme();
66+
67+
const fallback = (
68+
firstName.trim().charAt(0) + lastName.trim().charAt(0) || username.substring(0, 2)
69+
).toLocaleUpperCase();
70+
const title = `${firstName} ${lastName}`.trim() || username;
71+
72+
const backgroundColor = useMemo(() => {
73+
const colorId = username.split('').reduce<number>((s, l) => s + l.charCodeAt(0), 0);
74+
const colors = [
75+
theme.colorAlternative.green120,
76+
theme.colorAlternative.darkCyan120,
77+
theme.colorAlternative.forestGreen120,
78+
theme.colorAlternative.oliveGreen120,
79+
theme.colorAlternative.blue120,
80+
theme.colorAlternative.darkBlue120,
81+
theme.colorAlternative.hotPink120,
82+
theme.colorAlternative.red120,
83+
theme.colorAlternative.coralRed120,
84+
theme.colorAlternative.yellow120,
85+
theme.colorAlternative.orange120,
86+
theme.colorAlternative.chocolate120,
87+
];
88+
89+
return colors[colorId % colors.length];
90+
}, [theme, username]);
91+
92+
const style = avatarUrl ? {backgroundImage: `url(${avatarUrl})`} : {backgroundColor};
93+
94+
return (
95+
<AvatarContainer size={size} {...rest} style={style} title={title}>
96+
{avatarUrl ? '' : fallback}
97+
</AvatarContainer>
98+
);
99+
};
100+
101+
export {Avatar};
102+
export type {AvatarProps};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import React from 'react';
2+
import {render, screen} from '../../storybook/test-util';
3+
import {Avatar} from './Avatar';
4+
5+
test('renders', () => {
6+
render(<Avatar username="john" firstName="John" lastName="Doe" />);
7+
8+
const avatar = screen.getByTitle('John Doe');
9+
expect(avatar).toBeInTheDocument();
10+
});
11+
12+
test('avatar image', () => {
13+
render(<Avatar username="john" firstName="John" lastName="Doe" avatarUrl="path/to/image" />);
14+
15+
const avatar = screen.getByTitle('John Doe');
16+
expect(avatar).toHaveStyle('background-image: url(path/to/image)');
17+
});
18+
19+
test('deterministic fallback color', () => {
20+
render(<Avatar username="john" firstName="John" lastName="Doe" />);
21+
22+
const avatar = screen.getByTitle('John Doe');
23+
expect(avatar).toHaveStyle('background-color: rgb(68, 31, 0)');
24+
});
25+
26+
test('fallback to firstname + lastname', () => {
27+
render(<Avatar username="" firstName="John" lastName="Doe" />);
28+
29+
const avatar = screen.getByTitle('John Doe');
30+
expect(avatar).toHaveTextContent('JD');
31+
});
32+
33+
test('fallback to firstname only', () => {
34+
render(<Avatar username="" firstName="John" lastName="" />);
35+
36+
const avatar = screen.getByTitle('John');
37+
expect(avatar).toHaveTextContent('J');
38+
});
39+
40+
test('fallback to lastname only', () => {
41+
render(<Avatar username="" firstName="" lastName="Doe" />);
42+
43+
const avatar = screen.getByTitle('Doe');
44+
expect(avatar).toHaveTextContent('D');
45+
});
46+
47+
test('fallback to username', () => {
48+
render(<Avatar username="john" firstName="" lastName="" />);
49+
50+
const avatar = screen.getByTitle('john');
51+
expect(avatar).toHaveTextContent('JO');
52+
});
53+
54+
test('initial are converted to uppercase', () => {
55+
render(<Avatar username="" firstName="john" lastName="doe" />);
56+
57+
const avatar = screen.getByTitle('john doe');
58+
expect(avatar).toHaveTextContent('JD');
59+
});
60+
61+
test('size default', () => {
62+
render(<Avatar username="john" firstName="John" lastName="Doe" />);
63+
64+
const avatar = screen.getByTitle('John Doe');
65+
expect(avatar).toHaveStyle('width: 32px');
66+
});
67+
68+
test('size big', () => {
69+
render(<Avatar username="john" firstName="John" lastName="Doe" size="big" />);
70+
71+
const avatar = screen.getByTitle('John Doe');
72+
expect(avatar).toHaveStyle('width: 140px');
73+
});
74+
75+
test('supports ...rest props', () => {
76+
render(<Avatar username="john" firstName="John" lastName="Doe" data-testid="my_value" />);
77+
78+
expect(screen.getByTestId('my_value')).toBeInTheDocument();
79+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import React, {Children} from 'react';
2+
import styled from 'styled-components';
3+
import {Override} from '../../shared';
4+
import {AkeneoThemedProps, getColor} from '../../theme';
5+
6+
const AvatarListContainer = styled.div<AvatarsProps & AkeneoThemedProps>`
7+
display: flex;
8+
flex-direction: row-reverse;
9+
justify-content: flex-end;
10+
& > * {
11+
margin-right: -4px;
12+
position: relative;
13+
}
14+
`;
15+
16+
const RemainingAvatar = styled.span`
17+
height: 32px;
18+
width: 32px;
19+
display: inline-block;
20+
border: 1px solid ${getColor('grey', 10)};
21+
line-height: 32px;
22+
text-align: center;
23+
font-size: 15px;
24+
border-radius: 32px;
25+
background-color: ${getColor('white')};
26+
`;
27+
28+
type AvatarsProps = Override<
29+
React.HTMLAttributes<HTMLDivElement>,
30+
{
31+
max: number;
32+
}
33+
>;
34+
35+
const Avatars = ({max, children, ...rest}: AvatarsProps) => {
36+
const childrenArray = Children.toArray(children);
37+
const displayedChildren = childrenArray.slice(0, max);
38+
const remainingChildrenCount = childrenArray.length - max;
39+
const reverseChildren = displayedChildren.reverse();
40+
41+
return (
42+
<AvatarListContainer {...rest}>
43+
{remainingChildrenCount > 0 && <RemainingAvatar>+{remainingChildrenCount}</RemainingAvatar>}
44+
{reverseChildren}
45+
</AvatarListContainer>
46+
);
47+
};
48+
49+
export {Avatars};
50+
export type {AvatarsProps};

0 commit comments

Comments
 (0)