Skip to content
This repository has been archived by the owner on Jul 28, 2024. It is now read-only.

Commit

Permalink
feat: implement avatar component
Browse files Browse the repository at this point in the history
  • Loading branch information
Roman Chubarkin committed Jun 17, 2021
1 parent 29ba491 commit e1393ab
Show file tree
Hide file tree
Showing 6 changed files with 179 additions and 0 deletions.
1 change: 1 addition & 0 deletions src/static/icons/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ export { default as IconEyeOpened } from './eye-opened.svg';
export { default as IconPlus } from './plus.svg';
export { default as IconSearch } from './search.svg';
export { default as IconFilledUnchecked } from './check-filled-unchecked.svg';
export { default as IconProfile } from './profile.svg';
4 changes: 4 additions & 0 deletions src/static/icons/profile.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
99 changes: 99 additions & 0 deletions src/ui/atoms/avatar/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import React from 'react';
import styled, { StyledComponent, css } from 'styled-components';
import { IconProfile } from 'static/icons';
import { Variant } from 'lib/types';

import { useImageLoad } from './use-image-load';

interface Size {
size?: 'default' | 'small';
}

const AvatarContainer = styled.div.attrs(({ size }: Size) => ({
'data-size': size ?? 'default',
}))`
--local-size: 68px;
&[data-size='small'] {
--local-size: 30px;
}
display: flex;
align-items: center;
justify-content: center;
width: var(--local-size);
height: var(--local-size);
` as StyledComponent<'div', Record<string, unknown>, Size>;

const imgStyles = css`
--local-border-radius: 50%;
width: 100%;
height: 100%;
border-radius: var(--local-border-radius);
`;

const AvatarImage = styled.img`
${imgStyles}
`;

const FallbackContainer = styled.div.attrs(({ size, variant }: Size & Variant) => ({
'data-size': size ?? 'default',
'data-variant': variant ?? 'default',
}))`
--local-shape-color: var(--woly-shape-text-disabled);
--local-svg-size: 36px;
&[data-size='small'] {
--local-svg-size: 24px;
}
${imgStyles}
display: flex;
align-items: center;
justify-content: center;
background-color: var(--local-shape-color);
& > svg {
width: var(--local-svg-size);
height: var(--local-svg-size);
}
` as StyledComponent<'div', Record<string, unknown>, Size & Variant>;

interface AvatarProps {
alt?: string;
src?: string;
srcSet?: string;
children?: React.ReactNode;
}

export const Avatar: React.FC<AvatarProps & Size & Variant> = ({
alt,
src,
srcSet,
size = 'default',
variant = 'default',
children: childrenProp,
}) => {
const loadFailed = useImageLoad({ src, srcSet });
const hasImg = src || srcSet;
let children = null;

if (hasImg && !loadFailed) {
children = <AvatarImage alt={alt} src={src} srcSet={srcSet} />;
} else if (childrenProp) {
children = childrenProp;
} else {
// render fallback if image loading failed or no src attributes / children provided
children = (
<FallbackContainer variant={variant} size={size}>
<IconProfile />
</FallbackContainer>
);
}

return <AvatarContainer size={size}>{children}</AvatarContainer>;
};
35 changes: 35 additions & 0 deletions src/ui/atoms/avatar/usage.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
---
name: avatar
category: atoms
package: woly
---

import {Avatar} from 'ui'
import {Playground} from 'lib/playground'

`Avatar` shows user avatar

### Example

<Playground>
<Avatar src="https://image.flaticon.com/icons/png/512/168/168726.png" />
<Avatar size="small" src="https://image.flaticon.com/icons/png/512/168/168726.png" />
</Playground>

### Fallback

<Playground>
<Avatar src="http://broken.url/image" />
<Avatar size="small" src="http://broken.url/image" />
</Playground>

### Props

| Name | Type | Default | Description |
| ---------- | ------------------------ | ----------- | ---------------------------------------- |
| `size` | `'default'` or `'small'` | `'default'` | avatar size |
| `src` | `string` | `''` | avatar src |
| `srcSet` | `string` | `''` | avatar src set for multiple screen sizes |
| `alt` | `string` | `''` | text description of the image |
| `variant` | `string` | `'default'` | affetcs only avatar fallback color |
| `children` | `React.ReactNode` | `undefined` | use if no src attributes provided |
39 changes: 39 additions & 0 deletions src/ui/atoms/avatar/use-image-load.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { useEffect, useState } from 'react';

export function useImageLoad({ src, srcSet }: { src?: string; srcSet?: string }) {
const [failed, setFailed] = useState(false);

useEffect(() => {
if (!src && !srcSet) {
return;
}

setFailed(false);

let active = true;

const image = new Image();
image.src = src ?? '';
image.srcset = srcSet ?? '';

image.addEventListener('load', () => {
if (!active) {
return;
}
setFailed(false);
});

image.addEventListener('error', () => {
if (!active) {
return;
}
setFailed(true);
});

return () => {
active = false;
};
}, [src, srcSet]);

return failed;
}
1 change: 1 addition & 0 deletions src/ui/atoms/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,4 @@ export { Text } from './text';
export { TextArea } from './text-area';
export { Tooltip } from './tooltip';
export { UploadArea } from './upload-area';
export { Avatar } from './avatar';

0 comments on commit e1393ab

Please sign in to comment.