Skip to content

Commit 34daa08

Browse files
author
James Etienne
committed
Added avatar to product react
1 parent 665732e commit 34daa08

File tree

7 files changed

+478
-0
lines changed

7 files changed

+478
-0
lines changed
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
$avatar-background-color: #e6e6e6;
2+
$avatar-fill-color: #b3b3b3;
3+
$avatar-iconSize: (
4+
small: 32px,
5+
medium: 40px,
6+
large: 148px
7+
);
8+
9+
@mixin avatar-size($size) {
10+
height: map-get($avatar-iconSize, $size);
11+
width: map-get($avatar-iconSize, $size);
12+
}
13+
14+
.lucia-avatar {
15+
border-radius: 50%;
16+
display: inline-block;
17+
position: relative;
18+
19+
img {
20+
border-radius: 50%;
21+
max-height: 100%;
22+
max-width: 100%;
23+
}
24+
25+
span {
26+
float: left;
27+
left: 50%;
28+
position: relative;
29+
text-transform: uppercase;
30+
top: 50%;
31+
transform: translate(-50%, -50%);
32+
}
33+
34+
&--small {
35+
@include avatar-size(small);
36+
}
37+
38+
&--medium {
39+
@include avatar-size(medium);
40+
}
41+
42+
&--large {
43+
@include avatar-size(large);
44+
}
45+
}
46+
47+
.lucia-no-avatar {
48+
background-color: $avatar-background-color;
49+
}
50+
51+
.lucia-avatar--action {
52+
cursor: pointer;
53+
height: 100%;
54+
position: absolute;
55+
width: 100%;
56+
57+
svg {
58+
background-color: $avatar-fill-color;
59+
border-radius: 50%;
60+
bottom: 0;
61+
fill: $avatar-fill-color;
62+
height: 48px;
63+
padding: 8px;
64+
position: absolute;
65+
right: 0;
66+
width: 48px;
67+
}
68+
}
Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
import Enzyme, { configure, mount, ReactWrapper } from 'enzyme';
2+
import Adapter from 'enzyme-adapter-react-16';
3+
import React from 'react';
4+
import { Avatar } from './Avatar';
5+
6+
configure({ adapter: new Adapter() });
7+
8+
describe('Avatar', () => {
9+
let component: ReactWrapper<any>;
10+
const fauxCallback = jest.fn();
11+
12+
beforeEach(() => {
13+
component = mount(<Avatar />);
14+
});
15+
16+
test('should match snapshot and styles', () => {
17+
expect(mount(<Avatar />)).toMatchSnapshot();
18+
});
19+
20+
describe('Default Properties', () => {
21+
it('has default properties "add" that can be set', () => {
22+
expect(component.props().add).toEqual(false);
23+
});
24+
it('has default properties "edit" that can be set', () => {
25+
expect(component.props().edit).toEqual(false);
26+
});
27+
it('has default properties "size" that can be set', () => {
28+
expect(component.props().size).toEqual('medium');
29+
});
30+
it('has no defaulted image', () => {
31+
expect(component.props().image).toEqual(undefined);
32+
expect(
33+
component
34+
.find('img')
35+
.at(0)
36+
.exists(),
37+
).toEqual(false);
38+
});
39+
it('has no default "add/edit" icon', () => {
40+
expect(component.props().add).toEqual(false);
41+
expect(component.props().edit).toEqual(false);
42+
expect(
43+
component
44+
.find('.lucia-avatar--action')
45+
.at(0)
46+
.exists(),
47+
).toEqual(false);
48+
});
49+
it('has initial of name when specified', () => {
50+
component.setProps({
51+
name: 'Patrick Stewart',
52+
});
53+
expect(component.find('img').exists()).toEqual(false);
54+
expect(
55+
component
56+
.find('span')
57+
.at(0)
58+
.contains('P'),
59+
).toEqual(true);
60+
});
61+
it('defaults to display image avatar if both image and name is specified', () => {
62+
component.setProps({
63+
image:
64+
'https://pbs.twimg.com/profile_images/624249118114881536/qxn_I_oR_400x400.jpg',
65+
name: 'Patrick Stewart',
66+
});
67+
expect(
68+
component
69+
.find('span')
70+
.at(0)
71+
.contains('P'),
72+
).toEqual(false);
73+
expect(component.find('img').exists()).toEqual(true);
74+
});
75+
});
76+
77+
describe('Classes', () => {
78+
it('contains DEFAULT class', () => {
79+
expect(
80+
component
81+
.find('.lucia-avatar')
82+
.at(0)
83+
.exists(),
84+
).toEqual(true);
85+
});
86+
it('contains nested "add" icon (ONLY for size="large")', () => {
87+
expect(
88+
component
89+
.find('.lucia-avatar--action')
90+
.at(0)
91+
.exists(),
92+
).toEqual(false);
93+
component.setProps({ add: true });
94+
expect(
95+
component
96+
.find('.lucia-avatar--action')
97+
.at(0)
98+
.exists(),
99+
).toEqual(false);
100+
component.setProps({ add: true, size: 'small' });
101+
expect(
102+
component
103+
.find('.lucia-avatar--action')
104+
.at(0)
105+
.exists(),
106+
).toEqual(false);
107+
component.setProps({ add: true, size: 'large' });
108+
expect(
109+
component
110+
.find('.lucia-avatar--action')
111+
.at(0)
112+
.exists(),
113+
).toEqual(true);
114+
});
115+
it('contains nested image', () => {
116+
expect(
117+
component
118+
.find('.lucia-avatar--action')
119+
.at(0)
120+
.exists(),
121+
).toEqual(false);
122+
component.setProps({
123+
image:
124+
'https://pbs.twimg.com/profile_images/624249118114881536/qxn_I_oR_400x400.jpg',
125+
});
126+
expect(
127+
component
128+
.find('img')
129+
.at(0)
130+
.exists(),
131+
).toEqual(true);
132+
});
133+
it('contains correct class for "size" variant ', () => {
134+
expect(
135+
component
136+
.find('.lucia-avatar--small')
137+
.at(0)
138+
.exists(),
139+
).toEqual(false);
140+
component.setProps({ size: 'small' });
141+
expect(component.find('.lucia-avatar--small').exists()).toEqual(true);
142+
component.setProps({ size: 'medium' });
143+
expect(component.find('.lucia-avatar--medium').exists()).toEqual(true);
144+
component.setProps({ size: 'large' });
145+
expect(component.find('.lucia-avatar--large').exists()).toEqual(true);
146+
component.setProps({ size: 'gobblygook' });
147+
expect(component.find('.lucia-avatar--small').exists()).toEqual(false);
148+
expect(component.find('.lucia-avatar--medium').exists()).toEqual(false);
149+
expect(component.find('.lucia-avatar--large').exists()).toEqual(false);
150+
});
151+
});
152+
153+
describe('Functions', () => {
154+
it('clicks (ONLY for size="large" w/ "add" icon and passed action)', () => {
155+
component.setProps({
156+
onAddImageClick: fauxCallback,
157+
add: true,
158+
size: 'large',
159+
});
160+
component.find('.lucia-avatar--action').simulate('click');
161+
expect(fauxCallback.mock.calls.length).toEqual(1);
162+
});
163+
});
164+
});
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import clsx from 'clsx';
2+
import React, { FC } from 'react';
3+
import './Avatar.scss';
4+
import { AvatarAddIcon, AvatarEditIcon } from './AvatarIcons';
5+
6+
export enum SizeVariations {
7+
small = 'small',
8+
medium = 'medium',
9+
large = 'large'
10+
}
11+
12+
export interface AvatarProps {
13+
add?: boolean;
14+
alt?: string;
15+
edit?: boolean;
16+
name?: string;
17+
image?: string;
18+
size?: keyof typeof SizeVariations;
19+
className?: string;
20+
onAddImageClick?: (event?: React.SyntheticEvent<EventTarget>) => void;
21+
}
22+
23+
export const Avatar: FC<AvatarProps> = ({
24+
add,
25+
alt,
26+
edit,
27+
image,
28+
name,
29+
size,
30+
onAddImageClick,
31+
className,
32+
...rest
33+
}) => {
34+
const wrapperClass = clsx(
35+
'lucia-avatar',
36+
{
37+
'lucia-avatar--small': size === 'small',
38+
'lucia-avatar--medium': size === 'medium',
39+
'lucia-avatar--large': size === 'large',
40+
'lucia-no-avatar': image === undefined
41+
},
42+
className
43+
);
44+
45+
const getInitialFontSize = (size: any) => {
46+
switch (size) {
47+
case SizeVariations.small:
48+
return 'ray-text--body-x-small';
49+
case SizeVariations.large:
50+
return 'ray-text--body-large';
51+
default:
52+
return 'ray-text--body';
53+
}
54+
};
55+
56+
const onAddImageClickAction = (
57+
event: React.SyntheticEvent<EventTarget>
58+
): void => {
59+
onAddImageClick && onAddImageClick();
60+
};
61+
return (
62+
<div className={wrapperClass} {...rest}>
63+
{size === SizeVariations.large && (
64+
<div className="lucia-avatar--action" onClick={onAddImageClickAction}>
65+
{add && AvatarAddIcon}
66+
{edit && AvatarEditIcon}
67+
</div>
68+
)}
69+
{image ? (
70+
<img src={image} alt={alt} />
71+
) : (
72+
name && <span className={getInitialFontSize(size)}>{name[0]} </span>
73+
)}
74+
</div>
75+
);
76+
};
77+
78+
Avatar.defaultProps = {
79+
add: false,
80+
edit: false,
81+
size: SizeVariations.medium
82+
};
83+
84+
export default Avatar;
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import React from 'react';
2+
3+
const AvatarEditIcon = (
4+
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
5+
<g stroke="none" strokeWidth="1" fill="none" fillRule="evenodd">
6+
<g id="ico-bin" fill="#FFFFFF" fillOpacity="0">
7+
<rect id="bin" x="0" y="0" width="24" height="24" />
8+
</g>
9+
<rect
10+
id="Rectangle-path"
11+
fill="#FFFFFF"
12+
fillRule="nonzero"
13+
x="7"
14+
y="21"
15+
width="13"
16+
height="1"
17+
/>
18+
<rect
19+
id="Rectangle-path"
20+
fill="#FFFFFF"
21+
fillRule="nonzero"
22+
x="4"
23+
y="21"
24+
width="2"
25+
height="1"
26+
/>
27+
<path
28+
d="M7,20 L7.5,20 C7.56594566,19.9997888 7.63119813,19.9865344 7.692,19.961 C7.72834515,19.9422957 7.76198516,19.9187476 7.792,19.891 C7.81227353,19.8802064 7.83166667,19.867835 7.85,19.854 L19.85,7.854 C19.9440417,7.76019574 19.9968938,7.63282726 19.9968938,7.5 C19.9968938,7.36717274 19.9440417,7.23980426 19.85,7.146 L16.85,4.146 C16.7561957,4.0519583 16.6288273,3.99910622 16.496,3.99910622 C16.3631727,3.99910622 16.2358043,4.0519583 16.142,4.146 L4.142,16.146 C4.12940941,16.163384 4.11772711,16.1814082 4.107,16.2 C4.07848061,16.2327731 4.05428313,16.2690694 4.035,16.308 C4.01083484,16.3690797 3.99894165,16.4343223 3.99993509,16.5 L3.99993509,20 L7,20 Z M16.5,5.207 L18.793,7.5 L17.5,8.793 L15.207,6.5 L16.5,5.207 Z M5,18.5 L5,16.707 L14.5,7.207 L16.793,9.5 L7.293,19 L5,19 L5,18.5 Z"
29+
id="Shape"
30+
fill="#FFFFFF"
31+
fillRule="nonzero"
32+
/>
33+
</g>
34+
</svg>
35+
);
36+
37+
const AvatarAddIcon = (
38+
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
39+
<g stroke="none" strokeWidth="1" fill="none" fillRule="evenodd">
40+
<g id="ico-bin" fill="#FFFFFF" fillOpacity="0">
41+
<rect id="bin" x="0" y="0" width="24" height="24" />
42+
</g>
43+
<path
44+
d="M12.5,11.5 L20.5,11.5 C20.7761424,11.5 21,11.7238576 21,12 C21,12.2761424 20.7761424,12.5 20.5,12.5 L12.5,12.5 L12.5,20.5 C12.5,20.7761424 12.2761424,21 12,21 C11.7238576,21 11.5,20.7761424 11.5,20.5 L11.5,12.5 L3.5,12.5 C3.22385763,12.5 3,12.2761424 3,12 C3,11.7238576 3.22385763,11.5 3.5,11.5 L11.5,11.5 L11.5,3.5 C11.5,3.22385763 11.7238576,3 12,3 C12.2761424,3 12.5,3.22385763 12.5,3.5 L12.5,11.5 Z"
45+
id="Combined-Shape"
46+
fill="#FFFFFF"
47+
fillRule="nonzero"
48+
/>
49+
</g>
50+
</svg>
51+
);
52+
53+
export { AvatarAddIcon, AvatarEditIcon };
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`Avatar should match snapshot and styles 1`] = `
4+
<Avatar
5+
add={false}
6+
edit={false}
7+
size="medium"
8+
>
9+
<div
10+
className="lucia-avatar lucia-avatar--medium lucia-no-avatar"
11+
/>
12+
</Avatar>
13+
`;
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './Avatar';

0 commit comments

Comments
 (0)