-
Notifications
You must be signed in to change notification settings - Fork 16
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
Create a UserAvatar component #2898
Merged
PavloButynets
merged 3 commits into
develop
from
feature/2857/create-UserAvatar-component
Dec 4, 2024
Merged
Changes from 1 commit
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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); | ||
} | ||
} | ||
} |
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,92 @@ | ||
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) | ||
|
||
return ( | ||
<div className='s2s-user-avatar'> | ||
{variant === 'photo' && src ? ( | ||
<MuiAvatar | ||
alt={monogram} | ||
className={cn('s2s-avatar', `s2s-avatar-${size}`)} | ||
onClick={onClick} | ||
ref={forwardedRef} | ||
src={src} | ||
{...props} | ||
/> | ||
) : variant === 'monogram' ? ( | ||
<MuiAvatar | ||
onClick={onClick} | ||
ref={forwardedRef} | ||
{...props} | ||
className={cn('s2s-avatar', `s2s-avatar-${size}`)} | ||
> | ||
{monogram} | ||
</MuiAvatar> | ||
) : variant === 'check' ? ( | ||
<MuiAvatar | ||
onClick={onClick} | ||
ref={forwardedRef} | ||
{...props} | ||
className={cn('s2s-avatar', `s2s-avatar-${size}`)} | ||
> | ||
<CheckIcon /> | ||
</MuiAvatar> | ||
) : ( | ||
<MuiAvatar | ||
onClick={onClick} | ||
ref={forwardedRef} | ||
{...props} | ||
className={cn('s2s-avatar', `s2s-avatar-${size}`)} | ||
/> | ||
)} | ||
{isOnline && ( | ||
<span | ||
className={`s2s-user-avatar-status s2s-user-avatar-status-${size}`} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. use cn for this className |
||
/> | ||
)} | ||
</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' | ||
} | ||
} |
54 changes: 54 additions & 0 deletions
54
tests/unit/design-system/components/user-avatar/UserAvatar.spec.jsx
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. please fix Sonar Issue and add more coverage on tests |
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,54 @@ | ||
import { render, screen } from '@testing-library/react'; | ||
import UserAvatar from '~scss-components/user-avatar/UserAvatar'; | ||
|
||
const firstName = 'John'; | ||
const lastName = 'Doe'; | ||
const avatarSrc = '/src/assets/img/user-profile-page/avatar.png'; | ||
const isOnline = true; | ||
|
||
describe('UserAvatar Component', () => { | ||
it('should render with monogram variant', () => { | ||
render( | ||
<UserAvatar | ||
variant="monogram" | ||
firstName={firstName} | ||
lastName={lastName} | ||
isOnline={isOnline} | ||
/> | ||
); | ||
|
||
const monogramElement = screen.getByText(firstName.charAt(0) + lastName.charAt(0)); | ||
expect(monogramElement).toBeInTheDocument(); | ||
}); | ||
|
||
it('should show online status indicator when isOnline is true', () => { | ||
render( | ||
<UserAvatar | ||
variant="photo" | ||
firstName={firstName} | ||
lastName={lastName} | ||
isOnline={true} | ||
src={avatarSrc} | ||
/> | ||
); | ||
|
||
const onlineStatus = document.querySelector('.s2s-user-avatar-status'); | ||
expect(onlineStatus).toBeInTheDocument(); | ||
}); | ||
|
||
it('should not show online status indicator when isOnline is false', () => { | ||
render( | ||
<UserAvatar | ||
variant="photo" | ||
firstName={firstName} | ||
lastName={lastName} | ||
isOnline={false} | ||
src={avatarSrc} | ||
/> | ||
); | ||
|
||
const onlineStatus = document.querySelector('.s2s-user-avatar-status'); | ||
expect(onlineStatus).not.toBeInTheDocument(); | ||
}); | ||
|
||
}); |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you can replace
cn('s2s-avatar',
s2s-avatar-${size})
to variable