Skip to content

Commit 5ad2630

Browse files
committed
feat: Implement default avatar based on user name
1 parent 1cd1d66 commit 5ad2630

File tree

6 files changed

+88
-43
lines changed

6 files changed

+88
-43
lines changed

doc/src/misc/DefaultAvatar.mdx

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
---
2+
name: DefaultAvatar
3+
route: /DefaultAvatar
4+
menu: Misc
5+
---
6+
7+
import { Playground, Props } from 'docz';
8+
import { DefaultAvatar } from '@cosmotech/ui';
9+
10+
# Help Menu
11+
12+
The aim of this component is to display an avatar for users that didn't define one, while offering a specific one, whose color is based on the user's name.
13+
14+
## Props
15+
16+
<Props of={DefaultAvatar} />
17+
18+
## Basic Usage
19+
20+
<Playground>
21+
<div>
22+
<DefaultAvatar userName="Emma Michelet" />
23+
<DefaultAvatar userName="Elena Sasova" />
24+
<DefaultAvatar userName="Tristan Huet" />
25+
<DefaultAvatar userName="Nicolas Borde" />
26+
<DefaultAvatar userName="Jérémy Reynard" />
27+
</div>
28+
</Playground>

src/menus/UserInfo/UserInfo.js

Lines changed: 5 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,8 @@
44
import React, { useState } from 'react';
55
import PropTypes from 'prop-types';
66
import { Box, Menu, MenuItem, ClickAwayListener, IconButton, Tooltip } from '@material-ui/core';
7-
import {
8-
ArrowRight as ArrowRightIcon,
9-
AccountCircle as AccountCircleIcon,
10-
Check as CheckIcon,
11-
} from '@material-ui/icons';
12-
7+
import { ArrowRight as ArrowRightIcon, Check as CheckIcon } from '@material-ui/icons';
8+
import { DefaultAvatar } from '../../misc';
139
import useStyles from './style';
1410

1511
export const UserInfo = (props) => {
@@ -44,19 +40,13 @@ export const UserInfo = (props) => {
4440
return (
4541
<ClickAwayListener onClickAway={handleClose}>
4642
<div>
47-
<Box
48-
data-cy="user-profile-menu"
49-
aria-controls="user-profile-button"
50-
aria-haspopup="true"
51-
onClick={handleClick}
52-
className={`${classes.menuTrigger} ${isMenuOpen ? 'active' : ''}`}
53-
>
43+
<Box data-cy="user-profile-menu" aria-controls="user-profile-button" aria-haspopup="true" onClick={handleClick}>
5444
<Tooltip key="user-name-tooltip" title={userName}>
5545
{profilePlaceholder ? (
5646
<img src={profilePlaceholder} />
5747
) : (
58-
<IconButton aria-label="account" color="inherit">
59-
<AccountCircleIcon />
48+
<IconButton aria-label="account">
49+
<DefaultAvatar userName={userName} />
6050
</IconButton>
6151
)}
6252
</Tooltip>

src/menus/UserInfo/style.js

Lines changed: 0 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,6 @@
11
import { makeStyles } from '@material-ui/core';
22

33
const useStyles = makeStyles((theme) => ({
4-
menuTrigger: {
5-
backgroundRepeat: 'no-repeat',
6-
minWidth: '32px',
7-
backgroundSize: '32px',
8-
borderRadius: '50%',
9-
flexShrink: 0,
10-
transition: 'box-shadow ease-in-out 0.2s',
11-
'&:hover': {
12-
cursor: 'pointer',
13-
},
14-
display: 'flex',
15-
flexDirection: 'row',
16-
flexWrap: 'nowrap',
17-
justifyContent: 'center',
18-
alignItems: 'stretch',
19-
},
20-
profilePic: {
21-
width: '32px',
22-
height: '32px',
23-
},
24-
userName: {
25-
fontSize: '16px',
26-
textAlign: 'center',
27-
lineHeight: '32px',
28-
height: '32px',
29-
marginLeft: '8px',
30-
color: theme.palette.text.primary,
31-
},
324
menu: {
335
transform: 'translate3d(0,30px,0) !important',
346
},
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import React from 'react';
2+
import PropTypes from 'prop-types';
3+
import { Avatar, makeStyles } from '@material-ui/core';
4+
5+
function stringToColor(str) {
6+
let hash = 0;
7+
8+
for (let i = 0; i < str.length; ++i) {
9+
hash = str.charCodeAt(i) + ((hash << 5) - hash);
10+
}
11+
12+
let color = '#';
13+
for (let i = 0; i < 3; ++i) {
14+
const value = (hash >> (i * 8)) & 0xff;
15+
color += `00${value.toString(16)}`.slice(-2);
16+
}
17+
18+
return color;
19+
}
20+
21+
const useStyles = makeStyles((theme) => ({
22+
avatar: {
23+
width: (props) => `${props.size}px`,
24+
height: (props) => `${props.size}px`,
25+
fontSize: (props) => theme.typography.pxToRem(props.size / 2),
26+
backgroundColor: (props) => props.avatarBackgroundColor,
27+
color: (props) => theme.palette.getContrastText(props.avatarBackgroundColor),
28+
},
29+
}));
30+
31+
export const DefaultAvatar = ({ userName, size }) => {
32+
const avatarBackgroundColor = stringToColor(userName);
33+
const letter = userName.charAt(0).toUpperCase();
34+
35+
const classes = useStyles({ avatarBackgroundColor, size });
36+
37+
return <Avatar className={classes.avatar}>{letter}</Avatar>;
38+
};
39+
40+
DefaultAvatar.propTypes = {
41+
/**
42+
* User name to get first letter and compute avatar's color
43+
*/
44+
userName: PropTypes.string.isRequired,
45+
/**
46+
* Icon size in pixels
47+
*/
48+
size: PropTypes.int,
49+
};
50+
51+
DefaultAvatar.defaultProps = {
52+
size: 24,
53+
};

src/misc/DefaultAvatar/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { DefaultAvatar } from './DefaultAvatar';

src/misc/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,4 @@ export { FixedRatioContainer } from './FixedRatioContainer';
66
export { ScenarioValidationStatusChip } from './ScenarioValidationStatusChip';
77
export { LoadingLine } from './LoadingLine';
88
export { ErrorBanner } from './ErrorBanner';
9+
export { DefaultAvatar } from './DefaultAvatar';

0 commit comments

Comments
 (0)