-
Notifications
You must be signed in to change notification settings - Fork 40
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(Avatar): new component #2312
Merged
Merged
Changes from all commits
Commits
Show all changes
55 commits
Select commit
Hold shift + click to select a range
9a841bb
feat(Avatar): new component
Barsnes 66f5a2f
add stories, misc work
Barsnes 7f44431
add subtle and strong colors
Barsnes 79b584a
defaults, neutral color
Barsnes f9ce508
no padding in code, stories
Barsnes fffdf53
padding only on icon
Barsnes 41ea9a5
circle and square variant
Barsnes 79080ef
add image
Barsnes 50303a4
fix initials getter
Barsnes a62dd7a
typography map
Barsnes 22d3650
size on sm
Barsnes 15181ed
trim and easier children check
Barsnes 3eeb7cd
better icon handling
Barsnes 1e6ad3f
padding in sm
Barsnes a4819ed
fix initials supporting one
Barsnes cede7b8
dropdown example
Barsnes 232e49e
misc before weeeekend
Barsnes 123ce5a
css changes
Barsnes 6cfb76a
add tests, fix some css and misc
Barsnes 6bcbcbe
remove unused class, better calc
Barsnes cefd2be
test for aria hidden
Barsnes 1b8e793
use sizing, and change xs to 7
Barsnes 27888c5
update avatar colors
Barsnes 15948e7
better initials handling
Barsnes 6102409
cleaning
Barsnes e635d27
Merge branch 'next' into feat/avatar
Barsnes 109f251
use data attrs, nest css
Barsnes d085037
support asChild, add story about it
Barsnes 29212f3
remove `asChild`
Barsnes f131c7f
changes from review
Barsnes af29ad7
update jsdoc and bruk
Barsnes db83e80
width and height svg
Barsnes 686ffa3
use max instead
Barsnes 8022599
new initials func
Barsnes bb13a1b
default no lang tag
Barsnes d523160
one initial support, add test for this
Barsnes 9164f6f
fix typo on const
Barsnes 4e384bc
spread rest at bottom
Barsnes 9f474b3
check children
Barsnes efce414
req., aria-label, changes accordion to this
Barsnes ea0e5f6
set empty children
Barsnes da36f98
Create spotty-oranges-guess.md
Barsnes 42e4e8f
Update packages/react/src/components/Avatar/Avatar.tsx
mimarz de7bca3
Update packages/react/src/components/Avatar/Avatar.tsx
mimarz 4be892f
Update packages/react/src/components/Avatar/Avatar.tsx
mimarz 19fef5a
Update packages/react/src/components/Avatar/Avatar.tsx
mimarz 80466f9
Update packages/react/src/components/Avatar/Avatar.tsx
mimarz 333a040
Update packages/react/src/components/Avatar/Avatar.test.tsx
mimarz af8160b
fix empty labels
mimarz f2016de
cleaned some code
mimarz 443b70f
Update packages/react/src/components/Avatar/Avatar.mdx
mimarz 98c351b
fix withImage a11y
mimarz 3d15e65
omit aria-label to avoid confusion
mimarz 18076bb
fix css var prefix
mimarz 574dbbd
Merge branch 'next' into feat/avatar
mimarz 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,6 @@ | ||
--- | ||
"@digdir/designsystemet-css": patch | ||
"@digdir/designsystemet-react": patch | ||
--- | ||
|
||
Avatar: new component |
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,98 @@ | ||
.ds-avatar { | ||
--dsc-avatar-background: var(--ds-color-accent-base-default); | ||
--dsc-avatar-color: var(--ds-color-accent-contrast-default); | ||
--dsc-avatar-size: var(--ds-sizing-12); | ||
--dsc-avatar-padding: var(--ds-spacing-2); | ||
--dsc-avatar-border-radius: 50%; | ||
|
||
background: var(--dsc-avatar-background); | ||
height: var(--dsc-avatar-size); | ||
aspect-ratio: 1 / 1; | ||
color: var(--dsc-avatar-color); | ||
border-radius: var(--dsc-avatar-border-radius); | ||
overflow: hidden; | ||
display: inline-flex; | ||
justify-content: center; | ||
align-items: center; | ||
user-select: none; | ||
text-transform: uppercase; | ||
text-decoration: none; | ||
|
||
&:not(:has(> img)) { | ||
padding: var(--dsc-avatar-padding); | ||
} | ||
|
||
& img { | ||
object-fit: cover; | ||
width: 100%; | ||
height: 100%; | ||
} | ||
|
||
& svg { | ||
max-width: 100%; | ||
max-height: 100%; | ||
} | ||
|
||
&:empty::before { | ||
content: ''; | ||
width: 100%; | ||
height: 100%; | ||
mask-repeat: no-repeat; | ||
mask-size: contain; | ||
background-color: currentcolor; | ||
mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='1em' height='1em' fill='none' viewBox='0 0 24 24' focusable='false' role='img'%3E%3Cpath fill='currentColor' fill-rule='evenodd' d='M8.25 7.5a3.75 3.75 0 1 1 7.5 0 3.75 3.75 0 0 1-7.5 0M12 2.25a5.25 5.25 0 1 0 0 10.5 5.25 5.25 0 0 0 0-10.5M8.288 17.288A5.25 5.25 0 0 1 17.25 21a.75.75 0 0 0 1.5 0 6.75 6.75 0 0 0-13.5 0 .75.75 0 0 0 1.5 0 5.25 5.25 0 0 1 1.538-3.712' clip-rule='evenodd'%3E%3C/path%3E%3C/svg%3E"); | ||
} | ||
|
||
&[data-ds-variant='circle'] { | ||
--dsc-avatar-border-radius: var(--ds-border-radius-full); | ||
} | ||
|
||
&[data-ds-variant='square'] { | ||
--dsc-avatar-border-radius: var(--ds-border-radius-sm); | ||
} | ||
|
||
&[data-ds-color='accent'] { | ||
--dsc-avatar-background: var(--ds-color-accent-base-default); | ||
--dsc-avatar-color: var(--ds-color-accent-contrast-default); | ||
} | ||
|
||
&[data-ds-color='neutral'] { | ||
--dsc-avatar-background: var(--ds-color-neutral-base-default); | ||
--dsc-avatar-color: var(--ds-color-neutral-contrast-default); | ||
} | ||
|
||
&[data-ds-color='brand1'] { | ||
--dsc-avatar-background: var(--ds-color-brand1-base-default); | ||
--dsc-avatar-color: var(--ds-color-brand1-contrast-default); | ||
} | ||
|
||
&[data-ds-color='brand2'] { | ||
--dsc-avatar-background: var(--ds-color-brand2-base-default); | ||
--dsc-avatar-color: var(--ds-color-brand2-contrast-default); | ||
} | ||
|
||
&[data-ds-color='brand3'] { | ||
--dsc-avatar-background: var(--ds-color-brand3-base-default); | ||
--dsc-avatar-color: var(--ds-color-brand3-contrast-default); | ||
} | ||
|
||
&[data-ds-size='xs'] { | ||
--dsc-avatar-size: var(--ds-sizing-7); | ||
--dsc-avatar-padding: var(--ds-spacing-1); | ||
} | ||
|
||
&[data-ds-size='sm'] { | ||
--dsc-avatar-size: var(--ds-sizing-9); | ||
--dsc-avatar-padding: var(--ds-spacing-1); | ||
} | ||
|
||
&[data-ds-size='md'] { | ||
--dsc-avatar-size: var(--ds-sizing-12); | ||
--dsc-avatar-padding: var(--ds-spacing-2); | ||
} | ||
|
||
&[data-ds-size='lg'] { | ||
--dsc-avatar-size: var(--ds-sizing-15); | ||
--dsc-avatar-padding: var(--ds-spacing-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
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,75 @@ | ||
import { Meta, Canvas, Controls, Primary } from '@storybook/blocks'; | ||
|
||
import * as AvatarStories from './Avatar.stories'; | ||
|
||
<Meta of={AvatarStories} /> | ||
|
||
# Avatar | ||
|
||
`Avatar` er en komponent som viser et bilde, initialer eller ikon for en bruker eller profil. | ||
|
||
<Primary /> | ||
<Controls /> | ||
|
||
## Bruk | ||
|
||
```tsx | ||
import { Avatar } from '@digdir/designsystemet-react'; | ||
|
||
/* Med standard brukerikon */ | ||
<Avatar aria-label="Ola Nordmann" /> | ||
|
||
/* Med initialer */ | ||
<Avatar aria-label="Ola Nordmann">ON</Avatar> | ||
|
||
/* Med bilde */ | ||
<Avatar aria-label="Ola Nordmann"> | ||
<img src="..." /> | ||
</Avatar> | ||
``` | ||
|
||
## Standard ikon | ||
|
||
Sender du ikke inn `children` viser vi et standard brukerikon. | ||
|
||
<Canvas of={AvatarStories.NoName} /> | ||
|
||
## Størrelser | ||
|
||
`Avatar` kommer i flere størrelser. Tekst og ikon vil skalere, men det kan hende ikonet ditt må tilpasses. | ||
|
||
<Canvas of={AvatarStories.Sizes} /> | ||
|
||
## Fargevarianter | ||
|
||
Du kan bruke alle tema fargene dine på `Avatar`. | ||
|
||
<Canvas of={AvatarStories.ColorVariants} /> | ||
|
||
## Varianter | ||
|
||
Du kan endre mellom sirkel og firkantet form. Standard er sirkel. | ||
|
||
<Canvas of={AvatarStories.ShapeVariants} /> | ||
|
||
## Med bilde | ||
|
||
Skal du ha bilde, legger du dette som direkte barn av `Avatar`. | ||
|
||
Bilde (og andre child elements) får automatisk `aria-hidden="true"` for å unngå dobbel informasjon. | ||
|
||
<Canvas of={AvatarStories.WithImage} /> | ||
|
||
## Komponering | ||
|
||
Du kan komponere `Avatar` inn i andre komponenter, som `DropdownMenu`, samt bruke komponenter som | ||
`Badge` rundt for å vise status. | ||
|
||
<Canvas of={AvatarStories.InDropdownMenu} /> | ||
|
||
## Som lenke | ||
|
||
Legg lenker eller knapper rundt `Avatar` for å gjøre det klikkbart. | ||
Du må selv sette styling dersom du skal ha en interaktiv `Avatar`. | ||
|
||
<Canvas of={AvatarStories.AsLink} /> |
129 changes: 129 additions & 0 deletions
129
packages/react/src/components/Avatar/Avatar.stories.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,129 @@ | ||
import cat1 from '@assets/img/cats/Cat 3.jpg'; | ||
import type { Meta, StoryFn } from '@storybook/react'; | ||
|
||
import { BriefcaseIcon } from '@navikt/aksel-icons'; | ||
import { Avatar } from '.'; | ||
import { Badge, DropdownMenu } from '../'; | ||
|
||
type Story = StoryFn<typeof Avatar>; | ||
|
||
const meta: Meta = { | ||
title: 'Komponenter/Avatar', | ||
component: Avatar, | ||
parameters: { | ||
layout: 'padded', | ||
}, | ||
decorators: [ | ||
(Story) => ( | ||
<div | ||
style={{ | ||
display: 'flex', | ||
gap: 'var(--ds-spacing-2)', | ||
justifyContent: 'center', | ||
alignItems: 'center', | ||
}} | ||
> | ||
<Story /> | ||
</div> | ||
), | ||
], | ||
}; | ||
|
||
export default meta; | ||
|
||
export const Preview: Story = (args) => <Avatar {...args} />; | ||
|
||
Preview.args = { | ||
'aria-label': 'Ola Nordmann', | ||
color: 'accent', | ||
size: 'md', | ||
variant: 'circle', | ||
children: '', | ||
}; | ||
|
||
export const NoName: Story = () => <Avatar aria-label='Ola' />; | ||
|
||
export const Sizes: Story = () => ( | ||
<> | ||
<Avatar size='xs' aria-label='extra small'> | ||
xs | ||
</Avatar> | ||
<Avatar size='xs' aria-label='extra small' /> | ||
<Avatar size='sm' aria-label='small'> | ||
sm | ||
</Avatar> | ||
<Avatar size='sm' aria-label='small' /> | ||
<Avatar size='md' aria-label='medium'> | ||
md | ||
</Avatar> | ||
<Avatar size='md' aria-label='medium' /> | ||
<Avatar size='lg' aria-label='large'> | ||
lg | ||
</Avatar> | ||
<Avatar size='lg' aria-label='large' /> | ||
</> | ||
); | ||
|
||
export const ColorVariants: Story = () => ( | ||
<> | ||
<Avatar color='accent' aria-label='color accent' /> | ||
<Avatar color='neutral' aria-label='color neutral' /> | ||
<Avatar color='brand1' aria-label='color brand1' /> | ||
<Avatar color='brand2' aria-label='color brand2' /> | ||
<Avatar color='brand3' aria-label='color brand3' /> | ||
</> | ||
); | ||
|
||
export const ShapeVariants: Story = () => ( | ||
<> | ||
<Avatar variant='circle' aria-label='variant circle' /> | ||
<Avatar variant='square' aria-label='variant square' /> | ||
<Avatar variant='circle' aria-label='Ola Nordman'> | ||
ON | ||
</Avatar> | ||
<Avatar variant='square' aria-label='Ola Nordman'> | ||
ON | ||
</Avatar> | ||
</> | ||
); | ||
|
||
export const WithImage: Story = () => ( | ||
<Avatar aria-label='Ola Nordman'> | ||
<img src={cat1} alt='' /> | ||
</Avatar> | ||
); | ||
|
||
export const InDropdownMenu: Story = () => ( | ||
<DropdownMenu.Root placement='bottom-end' size='md' portal> | ||
<DropdownMenu.Trigger variant='tertiary'> | ||
<Avatar aria-label='Ola Nordmann' size='sm'> | ||
ON | ||
</Avatar> | ||
Velg Profil | ||
</DropdownMenu.Trigger> | ||
<DropdownMenu.Content> | ||
<DropdownMenu.Group heading='Alle kontoer'> | ||
<DropdownMenu.Item> | ||
<Badge overlap='circle' color='danger' size='sm'> | ||
<Avatar aria-label='Ola Nordmann' size='xs'> | ||
ON | ||
</Avatar> | ||
</Badge> | ||
Ola Nordmann | ||
</DropdownMenu.Item> | ||
<DropdownMenu.Item> | ||
<Avatar size='xs' color='brand1' aria-label='Sogndal Kommune'> | ||
<BriefcaseIcon fontSize='5em' /> | ||
</Avatar> | ||
Sogndal kommune | ||
</DropdownMenu.Item> | ||
</DropdownMenu.Group> | ||
</DropdownMenu.Content> | ||
</DropdownMenu.Root> | ||
); | ||
|
||
export const AsLink: Story = () => ( | ||
<a href='#'> | ||
<Avatar aria-label='Ola Nordmann' /> | ||
</a> | ||
); |
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,52 @@ | ||
import { render, screen } from '@testing-library/react'; | ||
import { Avatar } from './'; | ||
|
||
describe('Avatar', () => { | ||
it('should render correctly with default props', () => { | ||
render(<Avatar aria-label='ola' />); | ||
expect(screen.getByRole('img')).toBeInTheDocument(); | ||
expect(screen.getByRole('img')).toHaveAttribute('data-ds-size', 'md'); | ||
expect(screen.getByRole('img')).toHaveAttribute( | ||
'data-ds-variant', | ||
'circle', | ||
); | ||
expect(screen.getByRole('img')).toHaveAttribute('data-ds-color', 'accent'); | ||
}); | ||
|
||
it('should render correctly with custom props', () => { | ||
render(<Avatar size='lg' variant='square' aria-label='ola' />); | ||
expect(screen.getByRole('img')).toBeInTheDocument(); | ||
expect(screen.getByRole('img')).toHaveAttribute('data-ds-size', 'lg'); | ||
expect(screen.getByRole('img')).toHaveAttribute( | ||
'data-ds-variant', | ||
'square', | ||
); | ||
}); | ||
|
||
it('should render initials when aria-label is set', () => { | ||
render(<Avatar aria-label='Ola Nordmann'>ON</Avatar>); | ||
expect(screen.getByText('ON')).toBeInTheDocument(); | ||
}); | ||
|
||
it('should render children', () => { | ||
render( | ||
<Avatar aria-label='Ola Nordmann'> | ||
<img src='' alt='ola nordmann' data-testid='child-image' /> | ||
</Avatar>, | ||
); | ||
/* look for image with correct id */ | ||
expect(screen.getByTestId('child-image')).toBeInTheDocument(); | ||
}); | ||
|
||
it('children should have aria-hidden', () => { | ||
render( | ||
<Avatar aria-label='Ola Nordmann'> | ||
<img src='' alt='ola nordmann' data-testid='child-image' /> | ||
</Avatar>, | ||
); | ||
expect(screen.getByTestId('child-image')).toHaveAttribute( | ||
'aria-hidden', | ||
'true', | ||
); | ||
}); | ||
}); |
Oops, something went wrong.
Oops, something went wrong.
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.
Were we supposed to add info about recommendation for initals only being 2 characters?