Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat!: update avatar and user components #1221

Merged
merged 1 commit into from
Dec 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,8 @@
"playwright:install": "playwright install --with-deps",
"playwright": "playwright test --config=playwright/playwright.config.ts",
"playwright:update": "npm run playwright -- -u",
"playwright:docker": "docker run --rm --network host -v $(pwd):/work/ -w /work/ -it mcr.microsoft.com/playwright:v1.39.0-jammy /bin/bash -c 'npm ci && npx playwright install && npm run playwright'",
"playwright:docker:update": " docker run --rm --network host -v $(pwd):/work/ -w /work/ -it mcr.microsoft.com/playwright:v1.39.0-jammy /bin/bash -c 'npm ci && npx playwright install && npm run playwright:update'"
"playwright:docker": "docker run --rm --network host -v $(pwd):/work/ -w /work/ -it mcr.microsoft.com/playwright:v1.38.1-jammy /bin/bash -c 'npm ci && npm run playwright'",
"playwright:docker:update": "docker run --rm --network host -v $(pwd):/work/ -w /work/ -it mcr.microsoft.com/playwright:v1.38.1-jammy /bin/bash -c 'npm ci && npm run playwright:update'"
},
"dependencies": {
"@bem-react/classname": "^1.6.0",
Expand Down
6 changes: 3 additions & 3 deletions playwright/core/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,17 @@ import React from 'react';
import type {JsonObject} from '@playwright/experimental-ct-core/types/component';
import {ComponentFixtures, MountOptions, test as base} from '@playwright/experimental-ct-react';

type MountFixtures = {
type CustomFixtures = {
mount: ComponentFixtures['mount'];
};

export const test = base.extend<MountFixtures>({
export const test = base.extend<CustomFixtures>({
mount: async ({mount: baseMount}, use) => {
const mount = async (
component: JSX.Element,
options?: MountOptions<JsonObject> | undefined,
) => {
return await baseMount(
return baseMount(
<div style={{padding: 20, width: 'fit-content', height: 'fit-content'}}>
{component}
</div>,
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,
amje marked this conversation as resolved.
Show resolved Hide resolved
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;
}
Loading
Loading