-
Notifications
You must be signed in to change notification settings - Fork 518
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
20 changed files
with
581 additions
and
47 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Binary file added
BIN
+9.58 KB
...ual-tsx-visual-tests-renders-components-avatar-avatar-list-correctly-1-snap.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+6.54 KB
...ual-tsx-visual-tests-renders-components-avatar-avatar-size-correctly-1-snap.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+11.3 KB
...x-visual-tests-renders-components-avatar-background-colors-correctly-1-snap.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+1.37 KB
...visual-tsx-visual-tests-renders-components-avatar-standard-correctly-1-snap.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+2.74 KB
...sual-tsx-visual-tests-renders-components-avatar-with-image-correctly-1-snap.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
147 changes: 147 additions & 0 deletions
147
front-packages/akeneo-design-system/src/components/Avatar/Avatar.stories.mdx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,147 @@ | ||
import {Meta, Story, ArgsTable, Canvas} from '@storybook/addon-docs'; | ||
import {Avatar} from './Avatar.tsx'; | ||
import {Avatars} from './Avatars.tsx'; | ||
|
||
<Meta | ||
title="Components/Avatar" | ||
component={Avatar} | ||
argTypes={{ | ||
firstName: {control: {type: 'text'}}, | ||
lastName: {control: {type: 'text'}}, | ||
username: {control: {type: 'text'}}, | ||
avatarUrl: {control: {type: 'text'}}, | ||
size: {control: {type: 'select', options: ['default', 'big']}}, | ||
}} | ||
args={{ | ||
firstName: 'John', | ||
lastName: 'Doe', | ||
username: 'admin', | ||
avatarUrl: undefined, | ||
size: 'default', | ||
}} | ||
/> | ||
|
||
# Avatar | ||
|
||
## Usage | ||
|
||
This component is used to display users avatars. If no avatar is available, first letters of the first and last name are | ||
displayed with a dedicated color. | ||
|
||
## Playground | ||
|
||
<Canvas> | ||
<Story name="Standard"> | ||
{args => { | ||
return <Avatar firstName={'John'} lastName={'Doe'} username={'admin'} {...args} />; | ||
}} | ||
</Story> | ||
</Canvas> | ||
|
||
<ArgsTable story="Standard" /> | ||
|
||
## Variation on background colors | ||
|
||
The background color is based from the username. | ||
|
||
<Canvas> | ||
<Story name="Background colors"> | ||
{args => { | ||
return ( | ||
<> | ||
<Avatar {...args} firstName={'Albert'} lastName={'Doe'} username={'a'} /> | ||
<Avatar {...args} firstName={'Bertrand'} lastName={'Doe'} username={'b'} /> | ||
<Avatar {...args} firstName={'Chris'} lastName={'Doe'} username={'c'} /> | ||
<Avatar {...args} firstName={'Danny'} lastName={'Doe'} username={'d'} /> | ||
<Avatar {...args} firstName={'Elon'} lastName={'Doe'} username={'e'} /> | ||
<Avatar {...args} firstName={'Fred'} lastName={'Doe'} username={'f'} /> | ||
<Avatar {...args} firstName={'Gus'} lastName={'Doe'} username={'g'} /> | ||
<Avatar {...args} firstName={'Helen'} lastName={'Doe'} username={'h'} /> | ||
<Avatar {...args} firstName={'Isabel'} lastName={'Doe'} username={'i'} /> | ||
<Avatar {...args} firstName={'John'} lastName={'Doe'} username={'j'} /> | ||
<Avatar {...args} firstName={'Kurt'} lastName={'Doe'} username={'k'} /> | ||
<Avatar {...args} firstName={'Leonard'} lastName={'Doe'} username={'l'} /> | ||
</> | ||
); | ||
}} | ||
</Story> | ||
</Canvas> | ||
|
||
## Variation with image | ||
|
||
<Canvas> | ||
<Story name="With image"> | ||
{args => { | ||
return ( | ||
<> | ||
<Avatar {...args} avatarUrl={'https://picsum.photos/seed/akeneo/32/32'} /> | ||
</> | ||
); | ||
}} | ||
</Story> | ||
</Canvas> | ||
|
||
## Variation on List | ||
|
||
You can use a dedicated component to display avatar list. After a defined maximum, other avatars are not displayed. | ||
|
||
<Canvas> | ||
<Story name="Avatar list"> | ||
{args => { | ||
return ( | ||
<> | ||
<Avatars | ||
max={5} | ||
title="Helen Doe Isabel Doe John Doe Kurt Doe Leonard Doe" | ||
> | ||
<Avatar | ||
{...args} | ||
firstName={'Albert'} | ||
lastName={'Doe'} | ||
username={'a'} | ||
avatarUrl={'https://picsum.photos/seed/akeneo/32/32'} | ||
/> | ||
<Avatar {...args} firstName={'Bertrand'} lastName={'Doe'} username={'b'} /> | ||
<Avatar | ||
{...args} | ||
firstName={'Chris'} | ||
lastName={'Doe'} | ||
username={'c'} | ||
avatarUrl={'https://picsum.photos/seed/bkeneo/32/32'} | ||
/> | ||
<Avatar {...args} firstName={'Danny'} lastName={'Doe'} username={'d'} /> | ||
<Avatar | ||
{...args} | ||
firstName={'Elon'} | ||
lastName={'Doe'} | ||
username={'e'} | ||
avatarUrl={'https://picsum.photos/seed/ckeneo/32/32'} | ||
/> | ||
<Avatar {...args} firstName={'Fred'} lastName={'Doe'} username={'f'} /> | ||
<Avatar {...args} firstName={'Gus'} lastName={'Doe'} username={'g'} /> | ||
<Avatar {...args} firstName={'Helen'} lastName={'Doe'} username={'h'} /> | ||
<Avatar {...args} firstName={'Isabel'} lastName={'Doe'} username={'i'} /> | ||
<Avatar {...args} firstName={'John'} lastName={'Doe'} username={'j'} /> | ||
<Avatar {...args} firstName={'Kurt'} lastName={'Doe'} username={'k'} /> | ||
<Avatar {...args} firstName={'Leonard'} lastName={'Doe'} username={'l'} /> | ||
</Avatars> | ||
</> | ||
); | ||
}} | ||
</Story> | ||
</Canvas> | ||
|
||
## Variation on Size | ||
|
||
<Canvas> | ||
<Story name="Avatar size"> | ||
{args => { | ||
return ( | ||
<> | ||
<Avatar {...args} size={'default'} /> | ||
<Avatar {...args} size={'big'} /> | ||
</> | ||
); | ||
}} | ||
</Story> | ||
</Canvas> |
102 changes: 102 additions & 0 deletions
102
front-packages/akeneo-design-system/src/components/Avatar/Avatar.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
import React, {useMemo} from 'react'; | ||
import styled, {css} from 'styled-components'; | ||
import {useTheme} from '../../hooks'; | ||
import {Override} from '../../shared'; | ||
import {AkeneoThemedProps, getColor} from '../../theme'; | ||
|
||
const AvatarContainer = styled.span<AvatarProps & AkeneoThemedProps>` | ||
${({size}) => | ||
size === 'default' | ||
? css` | ||
height: 32px; | ||
width: 32px; | ||
line-height: 32px; | ||
font-size: 15px; | ||
border-radius: 32px; | ||
` | ||
: css` | ||
height: 140px; | ||
width: 140px; | ||
line-height: 140px; | ||
font-size: 66px; | ||
border-radius: 140px; | ||
`} | ||
display: inline-block; | ||
color: ${getColor('white')}; | ||
text-align: center; | ||
background-position: center; | ||
background-repeat: no-repeat; | ||
background-size: cover; | ||
text-transform: uppercase; | ||
cursor: ${({onClick}) => (onClick ? 'pointer' : 'default')}; | ||
`; | ||
|
||
type AvatarProps = Override< | ||
React.HTMLAttributes<HTMLSpanElement>, | ||
{ | ||
/** | ||
* Username to use as fallback if the avatar is not provided and the Firstname and Lastname are empty. | ||
*/ | ||
username: string; | ||
|
||
/** | ||
* Firstname to use as fallback with the Lastname if the avatar is not provided. | ||
*/ | ||
firstName: string; | ||
|
||
/** | ||
* Lastname to use as fallback with the Firstname if the avatar is not provided. | ||
*/ | ||
lastName: string; | ||
|
||
/** | ||
* Url of the avatar image. | ||
*/ | ||
avatarUrl?: string; | ||
|
||
/** | ||
* Size of the avatar. | ||
*/ | ||
size?: 'default' | 'big'; | ||
} | ||
>; | ||
|
||
const Avatar = ({username, firstName, lastName, avatarUrl, size = 'default', ...rest}: AvatarProps) => { | ||
const theme = useTheme(); | ||
|
||
const fallback = ( | ||
firstName.trim().charAt(0) + lastName.trim().charAt(0) || username.substring(0, 2) | ||
).toLocaleUpperCase(); | ||
const title = `${firstName} ${lastName}`.trim() || username; | ||
|
||
const backgroundColor = useMemo(() => { | ||
const colorId = username.split('').reduce<number>((s, l) => s + l.charCodeAt(0), 0); | ||
const colors = [ | ||
theme.colorAlternative.green120, | ||
theme.colorAlternative.darkCyan120, | ||
theme.colorAlternative.forestGreen120, | ||
theme.colorAlternative.oliveGreen120, | ||
theme.colorAlternative.blue120, | ||
theme.colorAlternative.darkBlue120, | ||
theme.colorAlternative.hotPink120, | ||
theme.colorAlternative.red120, | ||
theme.colorAlternative.coralRed120, | ||
theme.colorAlternative.yellow120, | ||
theme.colorAlternative.orange120, | ||
theme.colorAlternative.chocolate120, | ||
]; | ||
|
||
return colors[colorId % colors.length]; | ||
}, [theme, username]); | ||
|
||
const style = avatarUrl ? {backgroundImage: `url(${avatarUrl})`} : {backgroundColor}; | ||
|
||
return ( | ||
<AvatarContainer size={size} {...rest} style={style} title={title}> | ||
{avatarUrl ? '' : fallback} | ||
</AvatarContainer> | ||
); | ||
}; | ||
|
||
export {Avatar}; | ||
export type {AvatarProps}; |
79 changes: 79 additions & 0 deletions
79
front-packages/akeneo-design-system/src/components/Avatar/Avatar.unit.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
import React from 'react'; | ||
import {render, screen} from '../../storybook/test-util'; | ||
import {Avatar} from './Avatar'; | ||
|
||
test('renders', () => { | ||
render(<Avatar username="john" firstName="John" lastName="Doe" />); | ||
|
||
const avatar = screen.getByTitle('John Doe'); | ||
expect(avatar).toBeInTheDocument(); | ||
}); | ||
|
||
test('avatar image', () => { | ||
render(<Avatar username="john" firstName="John" lastName="Doe" avatarUrl="path/to/image" />); | ||
|
||
const avatar = screen.getByTitle('John Doe'); | ||
expect(avatar).toHaveStyle('background-image: url(path/to/image)'); | ||
}); | ||
|
||
test('deterministic fallback color', () => { | ||
render(<Avatar username="john" firstName="John" lastName="Doe" />); | ||
|
||
const avatar = screen.getByTitle('John Doe'); | ||
expect(avatar).toHaveStyle('background-color: rgb(68, 31, 0)'); | ||
}); | ||
|
||
test('fallback to firstname + lastname', () => { | ||
render(<Avatar username="" firstName="John" lastName="Doe" />); | ||
|
||
const avatar = screen.getByTitle('John Doe'); | ||
expect(avatar).toHaveTextContent('JD'); | ||
}); | ||
|
||
test('fallback to firstname only', () => { | ||
render(<Avatar username="" firstName="John" lastName="" />); | ||
|
||
const avatar = screen.getByTitle('John'); | ||
expect(avatar).toHaveTextContent('J'); | ||
}); | ||
|
||
test('fallback to lastname only', () => { | ||
render(<Avatar username="" firstName="" lastName="Doe" />); | ||
|
||
const avatar = screen.getByTitle('Doe'); | ||
expect(avatar).toHaveTextContent('D'); | ||
}); | ||
|
||
test('fallback to username', () => { | ||
render(<Avatar username="john" firstName="" lastName="" />); | ||
|
||
const avatar = screen.getByTitle('john'); | ||
expect(avatar).toHaveTextContent('JO'); | ||
}); | ||
|
||
test('initial are converted to uppercase', () => { | ||
render(<Avatar username="" firstName="john" lastName="doe" />); | ||
|
||
const avatar = screen.getByTitle('john doe'); | ||
expect(avatar).toHaveTextContent('JD'); | ||
}); | ||
|
||
test('size default', () => { | ||
render(<Avatar username="john" firstName="John" lastName="Doe" />); | ||
|
||
const avatar = screen.getByTitle('John Doe'); | ||
expect(avatar).toHaveStyle('width: 32px'); | ||
}); | ||
|
||
test('size big', () => { | ||
render(<Avatar username="john" firstName="John" lastName="Doe" size="big" />); | ||
|
||
const avatar = screen.getByTitle('John Doe'); | ||
expect(avatar).toHaveStyle('width: 140px'); | ||
}); | ||
|
||
test('supports ...rest props', () => { | ||
render(<Avatar username="john" firstName="John" lastName="Doe" data-testid="my_value" />); | ||
|
||
expect(screen.getByTestId('my_value')).toBeInTheDocument(); | ||
}); |
50 changes: 50 additions & 0 deletions
50
front-packages/akeneo-design-system/src/components/Avatar/Avatars.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
import React, {Children} from 'react'; | ||
import styled from 'styled-components'; | ||
import {Override} from '../../shared'; | ||
import {AkeneoThemedProps, getColor} from '../../theme'; | ||
|
||
const AvatarListContainer = styled.div<AvatarsProps & AkeneoThemedProps>` | ||
display: flex; | ||
flex-direction: row-reverse; | ||
justify-content: flex-end; | ||
& > * { | ||
margin-right: -4px; | ||
position: relative; | ||
} | ||
`; | ||
|
||
const RemainingAvatar = styled.span` | ||
height: 32px; | ||
width: 32px; | ||
display: inline-block; | ||
border: 1px solid ${getColor('grey', 10)}; | ||
line-height: 32px; | ||
text-align: center; | ||
font-size: 15px; | ||
border-radius: 32px; | ||
background-color: ${getColor('white')}; | ||
`; | ||
|
||
type AvatarsProps = Override< | ||
React.HTMLAttributes<HTMLDivElement>, | ||
{ | ||
max: number; | ||
} | ||
>; | ||
|
||
const Avatars = ({max, children, ...rest}: AvatarsProps) => { | ||
const childrenArray = Children.toArray(children); | ||
const displayedChildren = childrenArray.slice(0, max); | ||
const remainingChildrenCount = childrenArray.length - max; | ||
const reverseChildren = displayedChildren.reverse(); | ||
|
||
return ( | ||
<AvatarListContainer {...rest}> | ||
{remainingChildrenCount > 0 && <RemainingAvatar>+{remainingChildrenCount}</RemainingAvatar>} | ||
{reverseChildren} | ||
</AvatarListContainer> | ||
); | ||
}; | ||
|
||
export {Avatars}; | ||
export type {AvatarsProps}; |
Oops, something went wrong.