From f1a4276094d1e1d42c32a9ad33f4aaae97e6e646 Mon Sep 17 00:00:00 2001 From: Roman Chubarkin Date: Fri, 18 Jun 2021 01:01:25 +0300 Subject: [PATCH 1/2] feat: implement avatar component --- src/static/icons/index.ts | 1 + src/static/icons/profile.svg | 5 ++ src/woly/atoms/avatar/index.tsx | 43 ++++++++++++++++ src/woly/atoms/avatar/usage.mdx | 67 +++++++++++++++++++++++++ src/woly/atoms/avatar/use-image-load.ts | 35 +++++++++++++ src/woly/atoms/index.ts | 1 + 6 files changed, 152 insertions(+) create mode 100644 src/static/icons/profile.svg create mode 100644 src/woly/atoms/avatar/index.tsx create mode 100644 src/woly/atoms/avatar/usage.mdx create mode 100644 src/woly/atoms/avatar/use-image-load.ts diff --git a/src/static/icons/index.ts b/src/static/icons/index.ts index 5f1ac341..65413845 100644 --- a/src/static/icons/index.ts +++ b/src/static/icons/index.ts @@ -11,3 +11,4 @@ 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 IconSpinner } from './spinner.svg'; +export { default as IconProfile } from './profile.svg'; diff --git a/src/static/icons/profile.svg b/src/static/icons/profile.svg new file mode 100644 index 00000000..484b6645 --- /dev/null +++ b/src/static/icons/profile.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/woly/atoms/avatar/index.tsx b/src/woly/atoms/avatar/index.tsx new file mode 100644 index 00000000..49777d08 --- /dev/null +++ b/src/woly/atoms/avatar/index.tsx @@ -0,0 +1,43 @@ +import React from 'react'; +import styled from 'styled-components'; +import { IconProfile } from 'static/icons'; + +import { useImageLoad } from './use-image-load'; + +const AvatarContainer = styled.div` + --local-size: calc((var(--woly-component-level) + 2) * 2 * var(--woly-const-m)); + + width: var(--local-size); + height: var(--local-size); + + & > * { + width: 100%; + height: 100%; + + border-radius: 50%; + } +`; + +interface AvatarProps { + alt?: string; + src?: string; + srcSet?: string; + children?: React.ReactNode; +} + +export const Avatar: React.FC = ({ alt, src, srcSet, children: childrenProp }) => { + const loadFailed = useImageLoad({ src, srcSet }); + const hasImg = src || srcSet; + let children = null; + + if (hasImg && !loadFailed) { + children = {alt}; + } else if (childrenProp) { + children = childrenProp; + } else { + // render fallback if image loading failed or no src attributes / children provided + children = ; + } + + return {children}; +}; diff --git a/src/woly/atoms/avatar/usage.mdx b/src/woly/atoms/avatar/usage.mdx new file mode 100644 index 00000000..b5c08468 --- /dev/null +++ b/src/woly/atoms/avatar/usage.mdx @@ -0,0 +1,67 @@ +import {Avatar} from 'ui' +import {Playground, block} from 'lib/playground' + +`Avatar` shows user avatar + +### Example + + + + + +### Sizes + + + + + + + + + + + + + + + + + + + + + + +### Fallback + + + + + +### Custom child component + + + +
+ RC +
+
+
+ +### Props + +| Name | Type | Default | Description | +| ---------- | ----------------- | ----------- | ---------------------------------------- | +| `alt` | `string` | `''` | text description of the image | +| `children` | `React.ReactNode` | `undefined` | use if no src attributes provided | +| `src` | `string` | `''` | avatar src | +| `srcSet` | `string` | `''` | avatar src set for multiple screen sizes | diff --git a/src/woly/atoms/avatar/use-image-load.ts b/src/woly/atoms/avatar/use-image-load.ts new file mode 100644 index 00000000..fefbfe56 --- /dev/null +++ b/src/woly/atoms/avatar/use-image-load.ts @@ -0,0 +1,35 @@ +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); + + const image = new Image(); + image.src = src ?? ''; + image.srcset = srcSet ?? ''; + + const onLoad = () => { + setFailed(false); + }; + + const onError = () => { + setFailed(true); + }; + + image.addEventListener('load', onLoad); + image.addEventListener('error', onError); + + return () => { + image.removeEventListener('load', onLoad); + image.removeEventListener('error', onError); + }; + }, [src, srcSet]); + + return failed; +} diff --git a/src/woly/atoms/index.ts b/src/woly/atoms/index.ts index 7b484e4f..9f61c84a 100644 --- a/src/woly/atoms/index.ts +++ b/src/woly/atoms/index.ts @@ -16,3 +16,4 @@ export { Text } from './text'; export { TextArea } from './text-area'; export { Tooltip } from './tooltip'; export { UploadArea } from './upload-area'; +export { Avatar } from './avatar'; From ef7d358d283c2f2e6fe41bb11d84a0956e1463c9 Mon Sep 17 00:00:00 2001 From: Roman Chubarkin Date: Wed, 23 Jun 2021 15:27:25 +0300 Subject: [PATCH 2/2] feat: pass AvatarBase to styled instead of declare AvatarContainer --- src/woly/atoms/avatar/index.tsx | 40 +++++++++++++++++++-------------- 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/src/woly/atoms/avatar/index.tsx b/src/woly/atoms/avatar/index.tsx index 49777d08..5735e3a8 100644 --- a/src/woly/atoms/avatar/index.tsx +++ b/src/woly/atoms/avatar/index.tsx @@ -1,23 +1,9 @@ import React from 'react'; -import styled from 'styled-components'; +import styled, { StyledComponent } from 'styled-components'; import { IconProfile } from 'static/icons'; import { useImageLoad } from './use-image-load'; -const AvatarContainer = styled.div` - --local-size: calc((var(--woly-component-level) + 2) * 2 * var(--woly-const-m)); - - width: var(--local-size); - height: var(--local-size); - - & > * { - width: 100%; - height: 100%; - - border-radius: 50%; - } -`; - interface AvatarProps { alt?: string; src?: string; @@ -25,7 +11,13 @@ interface AvatarProps { children?: React.ReactNode; } -export const Avatar: React.FC = ({ alt, src, srcSet, children: childrenProp }) => { +const AvatarBase: React.FC = ({ + alt, + children: childrenProp, + src, + srcSet, + ...props +}) => { const loadFailed = useImageLoad({ src, srcSet }); const hasImg = src || srcSet; let children = null; @@ -39,5 +31,19 @@ export const Avatar: React.FC = ({ alt, src, srcSet, children: chil children = ; } - return {children}; + return
{children}
; }; + +export const Avatar = styled(AvatarBase)` + --local-size: calc((var(--woly-component-level) + 2) * 2 * var(--woly-const-m)); + + width: var(--local-size); + height: var(--local-size); + + & > * { + width: 100%; + height: 100%; + + border-radius: 50%; + } +` as StyledComponent<'div', Record, AvatarProps>;