Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
036bdf1
wip avatar-stack
oddvernes Dec 16, 2025
c50f815
Merge branch 'main' into feat/avatar-stack
oddvernes Dec 16, 2025
caa11af
hook up vars
oddvernes Dec 16, 2025
bbfd917
make overlap input a percentage
oddvernes Dec 17, 2025
cc394ce
more wip
oddvernes Dec 17, 2025
dd5892f
Merge branch 'main' into feat/avatar-stack
oddvernes Dec 18, 2025
782e1de
more wip, support square
oddvernes Dec 18, 2025
a029c0d
test tooltip
oddvernes Dec 18, 2025
766df76
make it work with link inside avatar
oddvernes Dec 18, 2025
0b67e5a
overlap story
oddvernes Dec 18, 2025
ca47a5e
refactor/rename variables
oddvernes Dec 19, 2025
fe47b74
Merge branch 'main' into feat/avatar-stack
oddvernes Dec 19, 2025
0df3702
fixed width expand optional
oddvernes Dec 19, 2025
ea6a70e
Merge branch 'main' into feat/avatar-stack
oddvernes Dec 19, 2025
8e878a4
focusable if expandable
oddvernes Dec 19, 2025
85bbc35
changesets
oddvernes Dec 19, 2025
7f3a7ac
remove defaults from react
oddvernes Dec 20, 2025
cff237a
handle gap 0
oddvernes Dec 20, 2025
61ca64c
interactive story for size/gap/overlap
oddvernes Dec 20, 2025
d0a97b3
add more to playground story
oddvernes Dec 20, 2025
d37bbd9
remove a fieldset
oddvernes Dec 20, 2025
a9534d0
Merge branch 'main' into feat/avatar-stack
oddvernes Jan 5, 2026
9e17550
default gap 2px
oddvernes Jan 5, 2026
868d606
Merge branch 'main' into feat/avatar-stack
oddvernes Jan 6, 2026
f50448e
fix overlap 0 = falsy
oddvernes Jan 6, 2026
3f55678
Merge branch 'main' into feat/avatar-stack
oddvernes Jan 6, 2026
0818a70
Merge branch 'main' into feat/avatar-stack
oddvernes Jan 7, 2026
9b5546b
biome
oddvernes Jan 7, 2026
f5affc2
remove logic and rename variables
oddvernes Jan 7, 2026
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
5 changes: 5 additions & 0 deletions .changeset/neat-eggs-clap.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@digdir/designsystemet-react": minor
---

**AvatarStack**: New component
5 changes: 5 additions & 0 deletions .changeset/sour-beers-knock.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@digdir/designsystemet-css": minor
---

**avatar-stack**: New component
81 changes: 81 additions & 0 deletions packages/css/src/avatar-stack.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
@property --captured-length {
syntax: '<length>';
inherits: true;
initial-value: 0px;
}

.ds-avatar-stack {
--dsc-avatar-stack-size: var(--ds-size-12);
--dsc-avatar-stack-gap: 2;
--dsc-avatar-stack-overlap: 50;
/*--dsc-avatar-count<number> is only needed when the stack is expandable="fixed", to calculate a fixed width.*/
--dsc-avatar-count: initial;

--_gap: calc(var(--dsc-avatar-stack-gap) * 1px);
--_overlap: calc(((var(--dsc-avatar-stack-size) / 100) * var(--dsc-avatar-stack-overlap)) * -1);

--captured-length: var(--_overlap);
display: flex;
isolation: isolate;
padding-block: 1px;
width: max-content;
transition: --captured-length 0.2s ease;

&:focus-visible {
@composes ds-focus--visible from './base.css';
}

&[data-expandable] {
&:hover,
&:focus-visible {
--captured-length: var(--_gap);
}
}

&[data-expandable='fixed'] {
width: calc(var(--dsc-avatar-stack-size) * var(--dsc-avatar-count) + var(--_overlap) * (var(--dsc-avatar-count) - 1));
}

&:empty {
display: none;
}

&[data-suffix]::after {
content: attr(data-suffix);
place-self: center;
margin-left: 1ch;
font-size: max(1rem, calc(var(--dsc-avatar-stack-size) * 0.4));
}

.ds-avatar {
--dsc-avatar-size: var(--dsc-avatar-stack-size);
--_font-size: max(0.75rem, calc(var(--dsc-avatar-stack-size) * 0.5));
mask: radial-gradient(50% 50% at calc(150% + var(--captured-length)), transparent calc(100% - 1px + var(--_gap)), #000 calc(100% + var(--_gap)));
&[data-variant='square'] {
mask: linear-gradient(to right, #000 calc(100% + var(--captured-length) - var(--_gap)), transparent calc(100% + var(--captured-length) - var(--_gap)));
}

&:not(:first-child) {
margin-left: var(--captured-length);
}
&:nth-last-child(1 of .ds-avatar) {
mask: none;
}

&[data-initials]:empty::before {
font-size: var(--_font-size);
}

> span {
font-size: var(--_font-size);
}
> a {
line-height: 0;
}
&:has(:focus-visible) {
mask: none;
@composes ds-focus--visible from './base.css';
z-index: 1;
}
}
}
1 change: 1 addition & 0 deletions packages/css/src/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -37,5 +37,6 @@
@import url('./breadcrumbs.css') layer(ds.components);
@import url('./badge.css') layer(ds.components);
@import url('./avatar.css') layer(ds.components);
@import url('./avatar-stack.css') layer(ds.components);
@import url('./suggestion.css') layer(ds.components);
@import url('./combobox.css') layer(ds.components);
31 changes: 31 additions & 0 deletions packages/react/src/components/avatar-stack/avatar-stack.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { Meta, Canvas, Controls, Primary } from '@storybook/addon-docs/blocks';
import * as AvatarStackStories from './avatar-stack.stories';

<Meta of={AvatarStackStories} />

# AvatarStack

`AvatarStack` er en komponent som stabler `Avatar`.

<Primary />
<Controls />

## Interactive playground
<Canvas of={AvatarStackStories.Playground} />

## expandable
<Canvas of={AvatarStackStories.Expandable} />

## data-size with differen units
<Canvas of={AvatarStackStories.DataSize} />

## Square
<Canvas of={AvatarStackStories.ShapeVariants} />

## Tooltip
<Canvas of={AvatarStackStories.WithTooltip} />

## Link
<Canvas of={AvatarStackStories.WithTooltipAndLink} />


Loading