Skip to content

Instantly share code, notes, and snippets.

@tzynwang
Last active October 6, 2022 23:51
Show Gist options
  • Save tzynwang/2ba1ebc33cea47d511be59cbb9ae057d to your computer and use it in GitHub Desktop.
Save tzynwang/2ba1ebc33cea47d511be59cbb9ae057d to your computer and use it in GitHub Desktop.
import React, { memo, useCallback, useMemo, useState } from 'react';
import { css } from '@emotion/css';
import cn from 'classnames';
import { PersonIcon } from '@Assets/icons';
import ImageBase from '@Components/Base/ImageBase';
import type { AvatarProps } from './types';
const figureStyle = css({
border: '4px solid #f9f4ef',
backgroundColor: '#f9f4ef',
});
const imgStyle = css({
height: '48px',
width: '48px',
boxSizing: 'content-box',
display: 'inline-flex',
justifyContent: 'center',
alignItems: 'center',
borderRadius: '50%',
});
const onErrorStyle = css({
width: '48px',
height: '48px',
display: 'inline-block',
'&::after': {
content: '""',
width: '100%',
height: '100%',
position: 'absolute',
top: 0,
left: 0,
},
});
const withChildrenStyle = css({
backgroundColor: '#4e342e',
color: '#fff',
fontSize: '24px',
});
const withChildrenBorderStyle = css({
border: '4px solid #f9f4ef',
});
function Avatar(props: AvatarProps): React.ReactElement {
/* States */
const { src, children = null, withBorder = false } = props;
const [onError, setOnError] = useState<boolean>(false);
/* Functions */
const fallBackToText = useCallback(() => {
setOnError(true);
}, []);
/* Views */
const finalRender = useMemo(() => {
if (onError) {
return (
<div
className={cn(imgStyle, withBorder && figureStyle, withChildrenStyle)}
>
<PersonIcon fill="#fff" />
</div>
);
}
if (children) {
return (
<div
className={cn(
imgStyle,
withBorder && figureStyle,
withChildrenStyle,
withBorder && withChildrenBorderStyle
)}
>
{children}
</div>
);
}
if (src) {
return (
<ImageBase
src={src}
classes={{
figure: cn(withBorder && figureStyle, imgStyle),
img: imgStyle,
onError: onErrorStyle,
}}
onError={fallBackToText}
/>
);
}
return (
<div
className={cn(imgStyle, withBorder && figureStyle, withChildrenStyle)}
>
<PersonIcon fill="#fff" />
</div>
);
}, [children, src, onError, withBorder, fallBackToText]);
/* Main */
return finalRender;
}
export default memo(Avatar);
import React from 'react';
export interface AvatarProps {
children?: React.ReactNode;
src?: string;
withBorder?: boolean;
}
import React, { memo } from 'react';
import Avatar from '@Components/Common/Avatar';
import AvatarGroup from '@Components/Common/AvatarGroup';
import Stack from '@Components/Layout/Stack';
import SpaceWrapper from '@Components/Layout/SpaceWrapper';
import foxSrc from '@Assets/alexander-andrews-mEdKuPYJe1I-unsplash.jpg';
import { PhotoCameraIcon } from '@Assets/icons';
function AvatarDemo(): React.ReactElement {
/* Main */
return (
<Stack>
<SpaceWrapper padding={8}>
<Avatar src={foxSrc} />
</SpaceWrapper>
<SpaceWrapper padding={8}>
<Avatar>C</Avatar>
</SpaceWrapper>
<SpaceWrapper padding={8}>
<Avatar>
<PhotoCameraIcon fill="#fff" />
</Avatar>
</SpaceWrapper>
<SpaceWrapper padding={8}>
<AvatarGroup>
<Avatar src={foxSrc} />
<Avatar>C</Avatar>
<Avatar />
<Avatar>
<PhotoCameraIcon fill="#fff" />
</Avatar>
<Avatar>F</Avatar>
</AvatarGroup>
</SpaceWrapper>
<SpaceWrapper padding={8}>
<AvatarGroup max={4}>
<Avatar src={foxSrc} />
<Avatar>C</Avatar>
<Avatar src="..." />
<Avatar>
<PhotoCameraIcon fill="#fff" />
</Avatar>
<Avatar>F</Avatar>
</AvatarGroup>
</SpaceWrapper>
</Stack>
);
}
export default memo(AvatarDemo);
import React, { memo, useEffect, useState } from 'react';
import { css } from '@emotion/css';
import cn from 'classnames';
import Avatar from '@Components/Common/Avatar';
import type { AvatarGroupProps } from './types';
const baseStyle = css({
display: 'flex',
flexDirection: 'row-reverse',
justifyContent: 'flex-end',
paddingLeft: 'calc(12px - 2px - 2px)',
'& > div': {
marginLeft: '-12px',
},
});
function AvatarGroup(props: AvatarGroupProps): React.ReactElement {
/* States */
const { children, max, className, ...rest } = props;
const [childrenArr, setChildrenArr] = useState<JSX.Element[]>([]);
/* Hooks */
useEffect(() => {
let result: JSX.Element[] = [];
React.Children.forEach(children, (child) => {
const c = child as JSX.Element;
result.push(React.cloneElement(c, { withBorder: true }));
});
const childrenLength = React.Children.count(children);
if (typeof max === 'number' && max > 1 && max < childrenLength) {
result = result.slice(0, max - 1);
result.push(<Avatar withBorder>+{childrenLength - (max - 1)}</Avatar>);
}
setChildrenArr(result.reverse());
}, [children, max]);
/* Main */
return (
<div className={cn(baseStyle, className)} {...rest}>
{childrenArr.map((child, index) => (
<React.Fragment key={index}>{child}</React.Fragment>
))}
</div>
);
}
export default memo(AvatarGroup);
import type React from 'react';
export interface AvatarGroupProps extends React.HTMLAttributes<HTMLDivElement> {
children: React.ReactNode;
max?: number;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment