-
Notifications
You must be signed in to change notification settings - Fork 16
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Create a UserAvatar component (#2898)
* Create a UserAvatar component, style it in scss, add it to stirybook, write tests for it * added tests * fixed sonar issues, made changes according to comments to PR
- Loading branch information
1 parent
b2b6410
commit bd0a2e1
Showing
4 changed files
with
409 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
@use '~scss/utilities' as *; | ||
|
||
.#{$prefix}user-avatar { | ||
--#{$prefix}avatar-size-sm: #{get-var('line-height-3xl')}; | ||
--#{$prefix}avatar-size-md: #{get-var('line-height-5xl')}; | ||
--#{$prefix}avatar-size-lg: #{get-var('line-height-6xl')}; | ||
|
||
display: flex; | ||
align-items: center; | ||
justify-content: center; | ||
position: relative; | ||
|
||
.#{$prefix}avatar { | ||
font-family: get-var('font-family-rubik'); | ||
font-weight: get-var('font-weight-semibold'); | ||
background-color: get-var('blue-gray-200'); | ||
color: get-var('blue-gray-800'); | ||
|
||
&-sm { | ||
width: var(--#{$prefix}avatar-size-sm); | ||
height: var(--#{$prefix}avatar-size-sm); | ||
font-size: get-var('font-size-md'); | ||
} | ||
|
||
&-md { | ||
width: var(--#{$prefix}avatar-size-md); | ||
height: var(--#{$prefix}avatar-size-md); | ||
font-size: get-var('font-size-md'); | ||
} | ||
|
||
&-lg { | ||
width: var(--#{$prefix}avatar-size-lg); | ||
height: var(--#{$prefix}avatar-size-lg); | ||
font-size: get-var('font-size-lg'); | ||
} | ||
} | ||
|
||
.#{$prefix}user-avatar-status { | ||
position: absolute; | ||
border-radius: 50%; | ||
background-color: get-var('green-500'); | ||
right: 0; | ||
bottom: 0; | ||
|
||
&-sm { | ||
border: get-var('border-width-md') solid get-var('neutral-0'); | ||
width: calc(var(--#{$prefix}avatar-size-sm) * 0.2); | ||
height: calc(var(--#{$prefix}avatar-size-sm) * 0.2); | ||
} | ||
|
||
&-md { | ||
border: get-var('border-width-lg') solid get-var('neutral-0'); | ||
width: calc(var(--#{$prefix}avatar-size-md) * 0.2); | ||
height: calc(var(--#{$prefix}avatar-size-md) * 0.2); | ||
} | ||
|
||
&-lg { | ||
border: get-var('border-width-lg') solid get-var('neutral-0'); | ||
width: calc(var(--#{$prefix}avatar-size-lg) * 0.2); | ||
height: calc(var(--#{$prefix}avatar-size-lg) * 0.2); | ||
} | ||
} | ||
} |
106 changes: 106 additions & 0 deletions
106
src/design-system/components/user-avatar/UserAvatar.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
import '~scss-components/user-avatar/UserAvatar.scss' | ||
import { forwardRef } from 'react' | ||
import { | ||
Avatar as MuiAvatar, | ||
AvatarProps as MuiAvatarProps | ||
} from '@mui/material' | ||
import CheckIcon from '@mui/icons-material/Check' | ||
import { cn } from '~/utils/cn' | ||
|
||
const variants = ['check', 'avatar', 'monogram', 'photo'] as const | ||
const sizes = ['sm', 'md', 'lg'] as const | ||
|
||
interface BaseUserAvatarProps { | ||
variant?: (typeof variants)[number] | ||
src?: string | ||
size?: (typeof sizes)[number] | ||
firstName: string | ||
lastName: string | ||
isOnline?: boolean | ||
} | ||
|
||
export type UserAvatarProps = BaseUserAvatarProps & | ||
Omit<MuiAvatarProps, keyof BaseUserAvatarProps> | ||
|
||
type Ref = MuiAvatarProps['ref'] | ||
const UserAvatar = forwardRef( | ||
( | ||
{ | ||
variant = 'avatar', | ||
src, | ||
size = 'sm', | ||
firstName, | ||
lastName, | ||
isOnline, | ||
onClick, | ||
...props | ||
}: UserAvatarProps, | ||
forwardedRef: Ref | ||
) => { | ||
const monogram = firstName.charAt(0) + lastName.charAt(0) | ||
|
||
const avatarClass = cn('s2s-avatar', `s2s-avatar-${size}`) | ||
|
||
let avatarContent | ||
if (variant === 'photo' && src) { | ||
avatarContent = ( | ||
<MuiAvatar | ||
alt={monogram} | ||
className={avatarClass} | ||
onClick={onClick} | ||
ref={forwardedRef} | ||
src={src} | ||
{...props} | ||
/> | ||
) | ||
} else if (variant === 'monogram') { | ||
avatarContent = ( | ||
<MuiAvatar | ||
onClick={onClick} | ||
ref={forwardedRef} | ||
{...props} | ||
className={avatarClass} | ||
> | ||
{monogram} | ||
</MuiAvatar> | ||
) | ||
} else if (variant === 'check') { | ||
avatarContent = ( | ||
<MuiAvatar | ||
onClick={onClick} | ||
ref={forwardedRef} | ||
{...props} | ||
className={avatarClass} | ||
> | ||
<CheckIcon /> | ||
</MuiAvatar> | ||
) | ||
} else { | ||
avatarContent = ( | ||
<MuiAvatar | ||
onClick={onClick} | ||
ref={forwardedRef} | ||
{...props} | ||
className={avatarClass} | ||
/> | ||
) | ||
} | ||
|
||
return ( | ||
<div className='s2s-user-avatar'> | ||
{avatarContent} | ||
{isOnline && ( | ||
<span | ||
className={cn( | ||
's2s-user-avatar-status', | ||
`s2s-user-avatar-status-${size}` | ||
)} | ||
/> | ||
)} | ||
</div> | ||
) | ||
} | ||
) | ||
|
||
UserAvatar.displayName = 'UserAvatar' | ||
export default UserAvatar |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,168 @@ | ||
import type { Meta, StoryObj } from '@storybook/react' | ||
import { fn } from '@storybook/test' | ||
import UserAvatar from '~scss-components/user-avatar/UserAvatar' | ||
|
||
const meta: Meta<typeof UserAvatar> = { | ||
title: 'Components/UserAvatar', | ||
component: UserAvatar, | ||
parameters: { | ||
layout: 'centered', | ||
docs: { | ||
description: { | ||
component: ` | ||
The \`UserAvatar\` component is a flexible and customizable avatar element that can be used to display user profile images, initials, and online status. It supports different variants, sizes, and states, making it a versatile component for showcasing user information across your application. | ||
#### Key Features: | ||
- **Variants**: Choose from several types of avatars: | ||
- Photo: Displays a user’s profile image (requires src prop). | ||
- Monogram: Shows the initials of the user based on the firstName and lastName props. | ||
- Check: Displays a check icon inside the avatar. | ||
- Avatar: Displays the user’s initials by default, making it ideal for generic avatars when no photo is available. | ||
- **Sizes:** Adjust the avatar's size to fit your design needs, with options for sm (small), md (medium), or lg (large). | ||
- **Online Status:** Show the user's online status with a small color-coded status dot that appears when isOnline is set to true. | ||
- **Customizable:** Use the sx prop to apply custom styles or override the default styling to match your design requirements. | ||
- **Monogram Generation:** Automatically generates a monogram based on the user’s first and last name, perfect when no image is available. | ||
This component is ideal for user profile representations, whether in a user list, comments section, or anywhere you need to display user information. | ||
` | ||
} | ||
} | ||
}, | ||
tags: ['autodocs'], | ||
argTypes: { | ||
variant: { | ||
control: { type: 'select' }, | ||
options: ['check', 'avatar', 'monogram', 'photo'], | ||
description: 'The type of avatar to display.', | ||
table: { | ||
type: { summary: 'check | avatar | monogram | photo' } | ||
} | ||
}, | ||
size: { | ||
control: { type: 'text' }, | ||
description: 'The size of the avatar.', | ||
table: { | ||
type: { summary: 'sm | md | lg' } | ||
} | ||
}, | ||
isOnline: { | ||
control: { type: 'boolean' }, | ||
description: 'Displays online status if true.', | ||
table: { | ||
type: { summary: 'boolean' } | ||
} | ||
}, | ||
src: { | ||
control: { type: 'text' }, | ||
description: 'The URL of the image for the photo variant.', | ||
table: { | ||
type: { summary: 'string' } | ||
} | ||
}, | ||
firstName: { | ||
control: { type: 'text' }, | ||
description: 'The first name of the user.', | ||
table: { | ||
type: { summary: 'string' } | ||
} | ||
}, | ||
lastName: { | ||
control: { type: 'text' }, | ||
description: 'The last name of the user.', | ||
table: { | ||
type: { summary: 'string' } | ||
} | ||
} | ||
}, | ||
args: { | ||
variant: 'avatar', | ||
size: 'sm', | ||
firstName: 'Test', | ||
lastName: 'User', | ||
isOnline: false, | ||
onClick: fn() | ||
} | ||
} | ||
|
||
export default meta | ||
type Story = StoryObj<typeof meta> | ||
|
||
export const All: Story = { | ||
render: (args) => ( | ||
<div style={{ display: 'flex', gap: '30px' }}> | ||
<UserAvatar | ||
{...args} | ||
src='/src/assets/img/user-profile-page/avatar.png' | ||
variant='photo' | ||
/> | ||
<UserAvatar {...args} variant='monogram' /> | ||
<UserAvatar {...args} variant='check' /> | ||
<UserAvatar {...args} variant='avatar' /> | ||
</div> | ||
), | ||
parameters: { | ||
docs: { | ||
description: { | ||
story: | ||
'This story showcases all avatar variants in a single row for easy comparison.' | ||
} | ||
} | ||
} | ||
} | ||
|
||
export const PhotoVariant: Story = { | ||
parameters: { | ||
docs: { | ||
description: { | ||
story: | ||
'Displays the photo variant of the UserAvatar component. Use this variant when displaying a user profile picture.' | ||
} | ||
} | ||
}, | ||
args: { | ||
variant: 'photo', | ||
src: '/src/assets/img/user-profile-page/avatar.png' | ||
} | ||
} | ||
|
||
export const MonogramVariant: Story = { | ||
parameters: { | ||
docs: { | ||
description: { | ||
story: | ||
'Displays the monogram variant of the UserAvatar component. The user’s initials are shown as a fallback when no photo is provided.' | ||
} | ||
} | ||
}, | ||
args: { | ||
variant: 'monogram' | ||
} | ||
} | ||
|
||
export const CheckVariant: Story = { | ||
parameters: { | ||
docs: { | ||
description: { | ||
story: | ||
'Displays the check variant of the UserAvatar component. This variant includes a check icon, typically used for selection or confirmation purposes.' | ||
} | ||
} | ||
}, | ||
args: { | ||
variant: 'check' | ||
} | ||
} | ||
|
||
export const AvatarVariant: Story = { | ||
parameters: { | ||
docs: { | ||
description: { | ||
story: | ||
'Displays the default avatar variant of the UserAvatar component. The initials of the user are displayed by default, with optional online status.' | ||
} | ||
} | ||
}, | ||
args: { | ||
variant: 'avatar' | ||
} | ||
} |
Oops, something went wrong.