Skip to content

Commit

Permalink
Refactor UserAvatar with hooks and improve background-image values
Browse files Browse the repository at this point in the history
  • Loading branch information
willemarcel committed Dec 1, 2020
1 parent 6d856c1 commit cade80c
Show file tree
Hide file tree
Showing 6 changed files with 334 additions and 91 deletions.
78 changes: 39 additions & 39 deletions frontend/src/components/user/avatar.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import { useSelector } from 'react-redux';

import { ProfilePictureIcon, CloseIcon } from '../svgIcons';
import { getRandomArrayItem } from '../../utils/random';
import { useAvatarStyle } from '../../hooks/UseAvatarStyle';
import { useAvatarText } from '../../hooks/UseAvatarText';

export const CurrentUserAvatar = (props) => {
const userPicture = useSelector((state) => state.auth.getIn(['userDetails', 'pictureUrl']));
Expand All @@ -30,44 +32,48 @@ export const UserAvatar = ({
editMode,
disableLink = false,
}: Object) => {
let sizeClasses = 'h2 w2 f5';
let textPadding = editMode ? { top: '-0.75rem' } : { paddingTop: '0.375rem' };
let sizeStyles = {};
let closeIconStyle = { left: '0.4rem' };
const avatarText = useAvatarText(name, username, number);

if (size === 'large') {
closeIconStyle = { marginLeft: '3rem' };
sizeClasses = 'h3 w3 f2';
textPadding = editMode ? { top: '-0.5rem' } : { paddingTop: '0.625rem' };
}

if (size === 'small') {
closeIconStyle = { marginLeft: '0' };
sizeClasses = 'f6';
sizeStyles = { height: '1.5rem', width: '1.5rem' };
textPadding = editMode ? { top: '-0.5rem' } : { paddingTop: '0.225rem' };
}

let letters;
if (name) {
letters = name
.split(' ')
.map((word) => word[0])
.join('');
} else if (number) {
letters = number;
if ((removeFn && editMode) || disableLink) {
return (
<Avatar
text={avatarText}
username={username}
picture={picture}
size={size}
colorClasses={colorClasses}
removeFn={removeFn}
editMode={editMode}
/>
);
} else {
letters = username
.split(' ')
.map((word) => word[0])
.join('');
return (
<Link to={`/users/${username}`}>
<Avatar
text={avatarText}
username={username}
picture={picture}
size={size}
colorClasses={colorClasses}
removeFn={removeFn}
editMode={editMode}
/>
</Link>
);
}
if (picture) sizeStyles.backgroundImage = `url(${picture})`;
};

const Avatar = ({ username, size, colorClasses, removeFn, picture, text, editMode }) => {
const { sizeClasses, textPadding, closeIconStyle, sizeStyle } = useAvatarStyle(
size,
editMode,
picture,
);

const avatar = (
return (
<div
title={username}
style={sizeStyles}
style={sizeStyle}
className={`dib mh1 br-100 tc v-mid cover ${colorClasses} ${sizeClasses}`}
>
{removeFn && editMode && (
Expand All @@ -81,17 +87,11 @@ export const UserAvatar = ({
)}
{!picture && (
<span className="relative tc w-100 dib ttu dib barlow-condensed v-mid" style={textPadding}>
{letters.substr(0, 3)}
{text}
</span>
)}
</div>
);

if ((removeFn && editMode) || disableLink) {
return avatar;
} else {
return <Link to={`/users/${username}`}>{avatar}</Link>;
}
};

export const UserAvatarList = ({
Expand Down
132 changes: 80 additions & 52 deletions frontend/src/components/user/tests/userAvatar.test.js
Original file line number Diff line number Diff line change
@@ -1,46 +1,55 @@
import React from 'react';
import TestRenderer from 'react-test-renderer';
import TestRenderer, { act } from 'react-test-renderer';

import { UserAvatar, UserAvatarList } from '../avatar';
import { CloseIcon } from '../../svgIcons';

describe('UserAvatar', () => {
it('with picture url and default size', () => {
const element = TestRenderer.create(
<UserAvatar username={'Mary'} picture={'http://image.xyz/photo.jpg'} colorClasses="red" />,
);
let element;
act(() => {
element = TestRenderer.create(
<UserAvatar username={'Mary'} picture={'http://image.xyz/photo.jpg'} colorClasses="red" />,
);
});
const elementInstance = element.root;
expect(elementInstance.findByProps({ title: 'Mary' }).type).toBe('div');
expect(elementInstance.findByProps({ title: 'Mary' }).props.style.backgroundImage).toBe(
'url(http://image.xyz/photo.jpg)',
'url("http://image.xyz/photo.jpg")',
);
expect(elementInstance.findByProps({ title: 'Mary' }).props.className).toBe(
'dib mh1 br-100 tc v-mid cover red h2 w2 f5',
);
});

it('with picture url and large size', () => {
const element = TestRenderer.create(
<UserAvatar
username={'Mary'}
colorClasses="orange"
size="large"
picture={'http://image.xyz/photo2.jpg'}
/>,
);
let element;
act(() => {
element = TestRenderer.create(
<UserAvatar
username={'Mary'}
colorClasses="orange"
size="large"
picture={'http://image.xyz/photo2.jpg'}
/>,
);
});
const elementInstance = element.root;
expect(elementInstance.findByProps({ title: 'Mary' }).props.style.backgroundImage).toBe(
'url(http://image.xyz/photo2.jpg)',
'url("http://image.xyz/photo2.jpg")',
);
expect(elementInstance.findByProps({ title: 'Mary' }).props.className).toBe(
'dib mh1 br-100 tc v-mid cover orange h3 w3 f2',
);
});

it('without picture url and with large size', () => {
const element = TestRenderer.create(
<UserAvatar username={'Mary'} size="large" colorClasses="white bg-red" />,
);
let element;
act(() => {
element = TestRenderer.create(
<UserAvatar username={'Mary'} size="large" colorClasses="white bg-red" />,
);
});
const elementInstance = element.root;
expect(elementInstance.findByType('div').props.className).toBe(
'dib mh1 br-100 tc v-mid cover white bg-red h3 w3 f2',
Expand All @@ -52,9 +61,12 @@ describe('UserAvatar', () => {
});

it('with name with default size', () => {
const element = TestRenderer.create(
<UserAvatar username={'Mary'} name={'Mary Poppins'} colorClasses="white bg-red" />,
);
let element;
act(() => {
element = TestRenderer.create(
<UserAvatar username={'Mary'} name={'Mary Poppins'} colorClasses="white bg-red" />,
);
});
const elementInstance = element.root;
expect(elementInstance.findByType('div').props.className).toBe(
'dib mh1 br-100 tc v-mid cover white bg-red h2 w2 f5',
Expand All @@ -66,17 +78,27 @@ describe('UserAvatar', () => {
});

it('with more than 3 words name', () => {
const element = TestRenderer.create(
<UserAvatar username={'Mary'} name={'Mary Poppins Long Name'} colorClasses="white bg-red" />,
);
let element;
act(() => {
element = TestRenderer.create(
<UserAvatar
username={'Mary'}
name={'Mary Poppins Long Name'}
colorClasses="white bg-red"
/>,
);
});
const elementInstance = element.root;
expect(elementInstance.findByType('span').props.children).toContain('MPL');
});

it('with username containing space', () => {
const element = TestRenderer.create(
<UserAvatar username={'Mary Poppins Long Name'} colorClasses="white bg-red" />,
);
let element;
act(() => {
element = TestRenderer.create(
<UserAvatar username={'Mary Poppins Long Name'} colorClasses="white bg-red" />,
);
});
const elementInstance = element.root;
expect(elementInstance.findByType('span').props.children).toContain('MPL');
expect(() => elementInstance.findByType(CloseIcon)).toThrow(
Expand All @@ -85,13 +107,16 @@ describe('UserAvatar', () => {
});

it('with editMode TRUE but without removeFn has NOT a CloseIcon', () => {
const element = TestRenderer.create(
<UserAvatar
username={'Mary Poppins Long Name'}
colorClasses="white bg-red"
editMode={true}
/>,
);
let element;
act(() => {
element = TestRenderer.create(
<UserAvatar
username={'Mary Poppins Long Name'}
colorClasses="white bg-red"
editMode={true}
/>,
);
});
const elementInstance = element.root;
expect(elementInstance.findByType('span').props.children).toContain('MPL');
expect(() => elementInstance.findByType(CloseIcon)).toThrow(
Expand All @@ -100,13 +125,16 @@ describe('UserAvatar', () => {
});

it('with removeFn, but with editMode FALSE has NOT a CloseIcon', () => {
const element = TestRenderer.create(
<UserAvatar
username={'Mary Poppins Long Name'}
colorClasses="white bg-red"
removeFn={() => console.log('no')}
/>,
);
let element;
act(() => {
element = TestRenderer.create(
<UserAvatar
username={'Mary Poppins Long Name'}
colorClasses="white bg-red"
removeFn={() => console.log('no')}
/>,
);
});
const elementInstance = element.root;
expect(elementInstance.findByType('span').props.children).toContain('MPL');
expect(() => elementInstance.findByType(CloseIcon)).toThrow(
Expand Down Expand Up @@ -188,11 +216,13 @@ describe('UserAvatarList', () => {
{ username: 'osmuser' },
];
it('large size, with a defined bgColor and without maxLength', () => {
const element = TestRenderer.create(
<UserAvatarList users={users} bgColor="bg-red" size="large" />,
);
let element;
act(() => {
element = TestRenderer.create(<UserAvatarList users={users} bgColor="bg-red" size="large" />);
});
const elementInstance = element.root;
expect(elementInstance.findAllByType(UserAvatar).length).toBe(users.length);
expect(elementInstance.findAllByType(UserAvatar)[0].props.colorClasses).toBe('white bg-red');
expect(elementInstance.findAllByProps({ className: 'dib' }).length).toBe(users.length);
expect(elementInstance.findAllByProps({ className: 'dib' })[0].props.style).toStrictEqual({
marginLeft: '',
Expand All @@ -201,23 +231,21 @@ describe('UserAvatarList', () => {
marginLeft: '-1.5rem',
});
expect(elementInstance.findAllByType(UserAvatar)[0].props.size).toBe('large');
expect(elementInstance.findAllByProps({ colorClasses: 'white bg-red' }).length).toBe(
users.length,
);
});
it('small size, with a defined bgColor and textColor', () => {
const element = TestRenderer.create(
<UserAvatarList users={users} bgColor="bg-white" textColor="black" size="small" />,
);
let element;
act(() => {
element = TestRenderer.create(
<UserAvatarList users={users} bgColor="bg-white" textColor="black" size="small" />,
);
});
const elementInstance = element.root;
expect(elementInstance.findAllByType(UserAvatar).length).toBe(users.length);
expect(elementInstance.findAllByType(UserAvatar)[0].props.size).toBe('small');
expect(elementInstance.findAllByType(UserAvatar)[0].props.colorClasses).toBe('black bg-white');
expect(elementInstance.findAllByProps({ className: 'dib' })[1].props.style).toStrictEqual({
marginLeft: '-0.875rem',
});
expect(elementInstance.findAllByProps({ colorClasses: 'black bg-white' }).length).toBe(
users.length,
);
});
it('default size, without bgColor and with maxLength = 5', () => {
const element = TestRenderer.create(<UserAvatarList users={users} maxLength={5} />);
Expand Down
37 changes: 37 additions & 0 deletions frontend/src/hooks/UseAvatarStyle.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { useState, useEffect } from 'react';

export const useAvatarStyle = (size, editMode, picture) => {
const [sizeClasses, setSizeClasses] = useState('h2 w2 f5');
const [textPadding, setTextPadding] = useState({});
const [sizeStyle, setSizeStyle] = useState({});
const [closeIconStyle, setCloseIconStyle] = useState({ left: '0.4rem' });

useEffect(() => {
if (size === 'large') setSizeClasses('h3 w3 f2');
if (size === 'small') setSizeClasses('f6');
if (size === 'medium' || !size) setSizeClasses('h2 w2 f5');
}, [size]);
useEffect(() => {
if (size === 'large')
setTextPadding(editMode ? { top: '-0.5rem' } : { paddingTop: '0.625rem' });
if (size === 'small')
setTextPadding(editMode ? { top: '-0.5rem' } : { paddingTop: '0.225rem' });
if (size === 'medium' || !size)
setTextPadding(editMode ? { top: '-0.75rem' } : { paddingTop: '0.375rem' });
}, [size, editMode]);
useEffect(() => {
if (size === 'large') setCloseIconStyle({ marginLeft: '3rem' });
if (size === 'small') setCloseIconStyle({ marginLeft: '0' });
if (size === 'medium' || !size) setCloseIconStyle({ left: '0.4rem' });
}, [size]);
useEffect(() => {
if (size === 'small') {
const smallStyle = { height: '1.5rem', width: '1.5rem' };
if (picture) smallStyle.backgroundImage = `url("${picture}")`;
setSizeStyle(smallStyle);
} else {
setSizeStyle(picture ? { backgroundImage: `url("${picture}")` } : {});
}
}, [picture, size]);
return { sizeClasses, textPadding, closeIconStyle, sizeStyle };
};
27 changes: 27 additions & 0 deletions frontend/src/hooks/UseAvatarText.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { useState, useEffect } from 'react';

export const useAvatarText = (name, username, number) => {
const [text, setText] = useState('');
useEffect(() => {
if (name) {
setText(
name
.split(' ')
.map((word) => word[0])
.join('')
.substr(0, 3),
);
} else if (number) {
setText(number);
} else {
setText(
username
.split(' ')
.map((word) => word[0])
.join('')
.substr(0, 3),
);
}
}, [name, number, username]);
return text;
};
Loading

0 comments on commit cade80c

Please sign in to comment.