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(Avatar): new component #2312

Merged
merged 55 commits into from
Sep 2, 2024
Merged
Show file tree
Hide file tree
Changes from 52 commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
9a841bb
feat(Avatar): new component
Barsnes Aug 22, 2024
66f5a2f
add stories, misc work
Barsnes Aug 22, 2024
7f44431
add subtle and strong colors
Barsnes Aug 22, 2024
79b584a
defaults, neutral color
Barsnes Aug 22, 2024
f9ce508
no padding in code, stories
Barsnes Aug 22, 2024
fffdf53
padding only on icon
Barsnes Aug 22, 2024
41ea9a5
circle and square variant
Barsnes Aug 22, 2024
79080ef
add image
Barsnes Aug 22, 2024
50303a4
fix initials getter
Barsnes Aug 22, 2024
a62dd7a
typography map
Barsnes Aug 22, 2024
22d3650
size on sm
Barsnes Aug 22, 2024
15181ed
trim and easier children check
Barsnes Aug 22, 2024
3eeb7cd
better icon handling
Barsnes Aug 23, 2024
1e6ad3f
padding in sm
Barsnes Aug 23, 2024
a4819ed
fix initials supporting one
Barsnes Aug 23, 2024
cede7b8
dropdown example
Barsnes Aug 23, 2024
232e49e
misc before weeeekend
Barsnes Aug 23, 2024
123ce5a
css changes
Barsnes Aug 26, 2024
6cfb76a
add tests, fix some css and misc
Barsnes Aug 26, 2024
6bcbcbe
remove unused class, better calc
Barsnes Aug 26, 2024
cefd2be
test for aria hidden
Barsnes Aug 26, 2024
1b8e793
use sizing, and change xs to 7
Barsnes Aug 27, 2024
27888c5
update avatar colors
Barsnes Aug 27, 2024
15948e7
better initials handling
Barsnes Aug 27, 2024
6102409
cleaning
Barsnes Aug 27, 2024
e635d27
Merge branch 'next' into feat/avatar
Barsnes Aug 27, 2024
109f251
use data attrs, nest css
Barsnes Aug 27, 2024
d085037
support asChild, add story about it
Barsnes Aug 27, 2024
29212f3
remove `asChild`
Barsnes Aug 27, 2024
f131c7f
changes from review
Barsnes Aug 27, 2024
af29ad7
update jsdoc and bruk
Barsnes Aug 28, 2024
db83e80
width and height svg
Barsnes Aug 28, 2024
686ffa3
use max instead
Barsnes Aug 28, 2024
8022599
new initials func
Barsnes Aug 28, 2024
bb13a1b
default no lang tag
Barsnes Aug 28, 2024
d523160
one initial support, add test for this
Barsnes Aug 28, 2024
9164f6f
fix typo on const
Barsnes Aug 29, 2024
4e384bc
spread rest at bottom
Barsnes Aug 29, 2024
9f474b3
check children
Barsnes Aug 29, 2024
efce414
req., aria-label, changes accordion to this
Barsnes Aug 30, 2024
ea0e5f6
set empty children
Barsnes Aug 30, 2024
da36f98
Create spotty-oranges-guess.md
Barsnes Aug 30, 2024
42e4e8f
Update packages/react/src/components/Avatar/Avatar.tsx
mimarz Sep 2, 2024
de7bca3
Update packages/react/src/components/Avatar/Avatar.tsx
mimarz Sep 2, 2024
4be892f
Update packages/react/src/components/Avatar/Avatar.tsx
mimarz Sep 2, 2024
19fef5a
Update packages/react/src/components/Avatar/Avatar.tsx
mimarz Sep 2, 2024
80466f9
Update packages/react/src/components/Avatar/Avatar.tsx
mimarz Sep 2, 2024
333a040
Update packages/react/src/components/Avatar/Avatar.test.tsx
mimarz Sep 2, 2024
af8160b
fix empty labels
mimarz Sep 2, 2024
f2016de
cleaned some code
mimarz Sep 2, 2024
443b70f
Update packages/react/src/components/Avatar/Avatar.mdx
mimarz Sep 2, 2024
98c351b
fix withImage a11y
mimarz Sep 2, 2024
3d15e65
omit aria-label to avoid confusion
mimarz Sep 2, 2024
18076bb
fix css var prefix
mimarz Sep 2, 2024
574dbbd
Merge branch 'next' into feat/avatar
mimarz Sep 2, 2024
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
6 changes: 6 additions & 0 deletions .changeset/spotty-oranges-guess.md
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
98 changes: 98 additions & 0 deletions packages/css/avatar.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
.ds-avatar {
--ds-avatar-background: var(--ds-color-accent-base-default);
--ds-avatar-color: var(--ds-color-accent-contrast-default);
--ds-avatar-size: var(--ds-sizing-12);
--ds-avatar-padding: var(--ds-spacing-2);
--ds-avatar-border-radius: 50%;

background: var(--ds-avatar-background);
height: var(--ds-avatar-size);
aspect-ratio: 1 / 1;
color: var(--ds-avatar-color);
border-radius: var(--ds-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(--ds-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'] {
--ds-avatar-border-radius: var(--ds-border-radius-full);
}

&[data-ds-variant='square'] {
--ds-avatar-border-radius: var(--ds-border-radius-sm);
}

&[data-ds-color='accent'] {
--ds-avatar-background: var(--ds-color-accent-base-default);
--ds-avatar-color: var(--ds-color-accent-contrast-default);
}

&[data-ds-color='neutral'] {
--ds-avatar-background: var(--ds-color-neutral-base-default);
--ds-avatar-color: var(--ds-color-neutral-contrast-default);
}

&[data-ds-color='brand1'] {
--ds-avatar-background: var(--ds-color-brand1-base-default);
--ds-avatar-color: var(--ds-color-brand1-contrast-default);
}

&[data-ds-color='brand2'] {
--ds-avatar-background: var(--ds-color-brand2-base-default);
--ds-avatar-color: var(--ds-color-brand2-contrast-default);
}

&[data-ds-color='brand3'] {
--ds-avatar-background: var(--ds-color-brand3-base-default);
--ds-avatar-color: var(--ds-color-brand3-contrast-default);
}

&[data-ds-size='xs'] {
--ds-avatar-size: var(--ds-sizing-7);
--ds-avatar-padding: var(--ds-spacing-1);
}

&[data-ds-size='sm'] {
--ds-avatar-size: var(--ds-sizing-9);
--ds-avatar-padding: var(--ds-spacing-1);
}

&[data-ds-size='md'] {
--ds-avatar-size: var(--ds-sizing-12);
--ds-avatar-padding: var(--ds-spacing-2);
}

&[data-ds-size='lg'] {
--ds-avatar-size: var(--ds-sizing-15);
--ds-avatar-padding: var(--ds-spacing-2);
}
}
1 change: 1 addition & 0 deletions packages/css/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,4 @@
@import url('./combobox.css') layer(ds.components);
@import url('./breadcrumbs.css') layer(ds.components);
@import url('./badge.css') layer(ds.components);
@import url('./avatar.css') layer(ds.components);
75 changes: 75 additions & 0 deletions packages/react/src/components/Avatar/Avatar.mdx
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.
Copy link
Collaborator

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?


<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 packages/react/src/components/Avatar/Avatar.stories.tsx
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>
);
52 changes: 52 additions & 0 deletions packages/react/src/components/Avatar/Avatar.test.tsx
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',
);
});
});
Loading
Loading