Skip to content

Commit

Permalink
feat!: update avatar and user components (#1221)
Browse files Browse the repository at this point in the history
  • Loading branch information
DakEnviy authored and amje committed Feb 1, 2024
1 parent 9d7867f commit cc5337e
Show file tree
Hide file tree
Showing 71 changed files with 1,179 additions and 356 deletions.
4 changes: 2 additions & 2 deletions CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
/src/components/ActionTooltip @amje
/src/components/Alert @IsaevAlexandr
/src/components/ArrowToggle @Marginy605
/src/components/Avatar @DakEnviy
#/src/components/Breadcrumbs
/src/components/Button @amje
/src/components/Card @Lunory
Expand Down Expand Up @@ -30,8 +31,7 @@
/src/components/Radio @zamkovskaya
/src/components/RadioButton @zamkovskaya
/src/components/RadioGroup @zamkovskaya
/src/components/User @DaffPunks
/src/components/UserAvatar @DaffPunks
/src/components/User @DakEnviy
/src/components/Select @korvin89
/src/components/Sheet @mournfulCoroner
/src/components/Skeleton @SeqviriouM
Expand Down
136 changes: 136 additions & 0 deletions src/components/Avatar/Avatar.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
@use '../variables';

$block: '.#{variables.$ns-new}avatar';

#{$block} {
--_--size: 32px;
--_--background-color: var(--g-color-base-misc-light);
--_--border-color: currentColor;
--_--color: var(--g-color-text-misc);
--_--font-size: var(--g-text-body-1-font-size);
--_--line-height: var(--g-text-body-1-line-height);

overflow: hidden;
display: inline-flex;
justify-content: center;
align-items: center;
width: var(--g-avatar-size, var(--_--size));
height: var(--g-avatar-size, var(--_--size));
border-radius: 50%;
background-color: var(--g-avatar-background-color, var(--_--background-color));

&__image {
display: block;
width: 100%;
height: 100%;
object-fit: cover;
}

&__icon {
color: var(--g-avatar-color, var(--_--color));

& > svg {
display: block;
}
}

&__text {
color: var(--g-avatar-color, var(--_--color));
font-size: var(--g-avatar-font-size, var(--_--font-size));
line-height: var(--g-avatar-line-height, var(--_--line-height));
font-weight: 500;
}

&_with-border,
&_view_outlined {
position: relative;

&::before,
&::after {
content: '';
z-index: 1;
position: absolute;
inset: 0;
border-radius: 50%;
}

&::before {
border: 3px solid var(--g-color-base-background);
}

&::after {
border: 2px solid var(--g-avatar-border-color, var(--_--border-color));
}
}

&_size {
&_xs {
--_--size: 24px;
}

&_s {
--_--size: 28px;
}

&_m {
--_--size: 32px;
}

&_l {
--_--size: 42px;
}

&_xl {
--_--size: 50px;
}

&_xs,
&_s {
--_--font-size: var(--g-text-caption-1-font-size);
--_--line-height: var(--g-text-caption-1-line-height);
}

&_m,
&_l {
--_--font-size: var(--g-text-body-1-font-size);
--_--line-height: var(--g-text-body-1-line-height);
}

&_xl {
--_--font-size: var(--g-text-body-2-font-size);
--_--line-height: var(--g-text-body-2-line-height);
}
}

&_theme {
&_normal {
&#{$block}_view {
&_filled {
--_--background-color: var(--g-color-base-misc-light);
--_--color: var(--g-color-text-misc);
}

&_outlined {
--_--background-color: var(--g-color-base-background);
--_--border-color: var(--g-color-text-misc);
--_--color: var(--g-color-text-misc);
}
}
}

&_brand {
&#{$block}_view {
&_filled {
--_--background-color: var(--g-color-base-brand);
--_--color: var(--g-color-text-brand-contrast);
}

&_outlined {
--_--background-color: var(--g-color-base-background);
--_--border-color: var(--g-color-text-brand);
--_--color: var(--g-color-text-brand);
}
}
}
}
}
91 changes: 91 additions & 0 deletions src/components/Avatar/Avatar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import React from 'react';

import {blockNew} from '../utils/cn';

import {AvatarIcon} from './AvatarIcon';
import {AvatarImage} from './AvatarImage';
import {AvatarText} from './AvatarText';
import type {AvatarProps} from './types/main';

import './Avatar.scss';

const b = blockNew('avatar');

export const Avatar = React.forwardRef<HTMLDivElement, AvatarProps>((props, ref) => {
const {
size = 'm',
theme = 'normal',
view = 'filled',
backgroundColor,
borderColor,
title,
'aria-label': ariaLabel,
'aria-labelledby': ariaLabelledby,
className,
style: styleProp,
qa,
} = props;

const style = React.useMemo(
() => ({backgroundColor, color: borderColor, ...styleProp}),
[backgroundColor, borderColor, styleProp],
);

const renderContent = () => {
if ('imgUrl' in props && props.imgUrl) {
return (
<AvatarImage
className={b('image')}
imgUrl={props.imgUrl}
fallbackImgUrl={props.fallbackImgUrl}
sizes={props.sizes}
srcSet={props.srcSet}
alt={props.alt || title}
loading={props.loading}
size={size}
/>
);
}

if ('icon' in props && props.icon) {
return (
<AvatarIcon
className={b('icon')}
icon={props.icon}
color={props.color}
size={size}
/>
);
}

if ('text' in props && props.text) {
return (
<AvatarText
className={b('text')}
text={props.text}
color={props.color}
size={size}
/>
);
}

return null;
};

return (
<div
className={b({size, theme, view, 'with-border': Boolean(borderColor)}, className)}
title={title}
role="img"
aria-label={ariaLabel}
aria-labelledby={ariaLabelledby}
style={style}
data-qa={qa}
ref={ref}
>
{renderContent()}
</div>
);
});

Avatar.displayName = 'Avatar';
24 changes: 24 additions & 0 deletions src/components/Avatar/AvatarIcon/AvatarIcon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import React from 'react';

import {Icon} from '../../Icon';
import type {AvatarSize} from '../types/common';

import type {AvatarIconProps} from './types';

const avatarSizeToIconSize: Record<AvatarSize, number> = {
xs: 14,
s: 16,
m: 16,
l: 20,
xl: 24,
};

export const AvatarIcon = ({icon, color, size, className}: AvatarIconProps) => {
const style = React.useMemo(() => ({color}), [color]);

return (
<div style={style} className={className}>
<Icon data={icon} size={avatarSizeToIconSize[size]} />
</div>
);
};
2 changes: 2 additions & 0 deletions src/components/Avatar/AvatarIcon/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export type {AvatarIconProps} from './types';
export {AvatarIcon} from './AvatarIcon';
7 changes: 7 additions & 0 deletions src/components/Avatar/AvatarIcon/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import type {IconData} from '../../Icon';
import type {AvatarCommonProps} from '../types/common';

export interface AvatarIconProps extends AvatarCommonProps {
icon: IconData;
color?: string;
}
41 changes: 41 additions & 0 deletions src/components/Avatar/AvatarImage/AvatarImage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import React from 'react';

import {AVATAR_SIZES} from '../constants';

import type {AvatarImageProps} from './types';

export const AvatarImage = ({
imgUrl,
fallbackImgUrl,
sizes,
srcSet,
alt,
loading,
size,
className,
}: AvatarImageProps) => {
const [isErrored, setIsErrored] = React.useState(false);

const handleError = React.useCallback(() => {
setIsErrored(true);
}, []);

// Reset error if `imgUrl` was changed to check it again
React.useEffect(() => {
setIsErrored(false);
}, [imgUrl]);

return (
<img
className={className}
loading={loading}
width={AVATAR_SIZES[size]}
height={AVATAR_SIZES[size]}
src={fallbackImgUrl && isErrored ? fallbackImgUrl : imgUrl}
sizes={sizes}
srcSet={srcSet}
alt={alt}
onError={handleError}
/>
);
};
2 changes: 2 additions & 0 deletions src/components/Avatar/AvatarImage/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export type {AvatarImageProps} from './types';
export {AvatarImage} from './AvatarImage';
10 changes: 10 additions & 0 deletions src/components/Avatar/AvatarImage/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import type {AvatarCommonProps} from '../types/common';

export interface AvatarImageProps extends AvatarCommonProps {
imgUrl: string;
fallbackImgUrl?: string;
sizes?: string;
srcSet?: string;
alt?: string;
loading?: 'eager' | 'lazy';
}
15 changes: 15 additions & 0 deletions src/components/Avatar/AvatarText/AvatarText.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import React from 'react';

import type {AvatarTextProps} from './types';
import {getAvatarDisplayText} from './utils';

export const AvatarText = ({text, color, className}: AvatarTextProps) => {
const style = React.useMemo(() => ({color}), [color]);
const displayText = React.useMemo(() => getAvatarDisplayText(text), [text]);

return (
<div style={style} className={className}>
{displayText}
</div>
);
};
3 changes: 3 additions & 0 deletions src/components/Avatar/AvatarText/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export type {AvatarTextProps} from './types';
export {getAvatarDisplayText} from './utils';
export {AvatarText} from './AvatarText';
6 changes: 6 additions & 0 deletions src/components/Avatar/AvatarText/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import type {AvatarCommonProps} from '../types/common';

export interface AvatarTextProps extends AvatarCommonProps {
text: string;
color?: string;
}
7 changes: 7 additions & 0 deletions src/components/Avatar/AvatarText/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export const getAvatarDisplayText = (text: string) => {
const words = text.split(' ');
const result =
words.length > 1 ? [words[0][0], words[1][0]].filter(Boolean).join('') : text.slice(0, 2);

return result.toUpperCase();
};
Loading

0 comments on commit cc5337e

Please sign in to comment.