From 036bdf1ac8daa845028c9d98748468908ab7697d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oddbj=C3=B8rn=20=C3=98vernes?= Date: Tue, 16 Dec 2025 16:19:01 +0100 Subject: [PATCH 01/29] wip avatar-stack --- packages/css/src/avatar-stack.css | 44 ++++++++++++++++++ packages/css/src/index.css | 1 + .../components/avatar-stack/avatar-stack.mdx | 17 +++++++ .../avatar-stack/avatar-stack.stories.tsx | 41 +++++++++++++++++ .../components/avatar-stack/avatar-stack.tsx | 46 +++++++++++++++++++ packages/react/src/components/index.ts | 3 ++ 6 files changed, 152 insertions(+) create mode 100644 packages/css/src/avatar-stack.css create mode 100644 packages/react/src/components/avatar-stack/avatar-stack.mdx create mode 100644 packages/react/src/components/avatar-stack/avatar-stack.stories.tsx create mode 100644 packages/react/src/components/avatar-stack/avatar-stack.tsx diff --git a/packages/css/src/avatar-stack.css b/packages/css/src/avatar-stack.css new file mode 100644 index 0000000000..100c70f49f --- /dev/null +++ b/packages/css/src/avatar-stack.css @@ -0,0 +1,44 @@ +@property --captured-length { + syntax: ''; + inherits: true; + initial-value: 0px; +} + +.ds-avatar-stack { + --dsc-avatar-stack-size: round(up, 2.625em, 1px); + --dsc-avatar-stack-gap: 4px; + --dsc-avatar-stack-overlap: -12px; + --dsc-avatar-stack-outline-color: light-dark(transparent, white); + + --captured-length: var(--dsc-avatar-stack-overlap); /*overlap*/ + display: flex; + padding-block: 1px; + transition: --captured-length 0.2s ease; + width: calc(var(--dsc-avatar-stack-size) * var(--n) + var(--dsc-avatar-stack-overlap) * (var(--n) - 1)); + filter: drop-shadow(0 0 0.5px var(--dsc-avatar-stack-outline-color)) drop-shadow(0 0 0.5px var(--dsc-avatar-stack-outline-color)) + drop-shadow(0 0 0.5px var(--dsc-avatar-stack-outline-color)); + &:hover { + --captured-length: 0px; + } + &:empty { + display: none; + } + .ds-avatar { + --dsc-avatar-size: var(--dsc-avatar-stack-size); + aspect-ratio: 1; + border-radius: 50%; + display: grid; + place-items: center; + mask: radial-gradient( + 50% 50% at calc(150% + var(--captured-length)), + transparent calc(100% - 1px + var(--dsc-avatar-stack-gap)), + #000 calc(100% + var(--dsc-avatar-stack-gap)) + ); + &:not(:first-child) { + margin-left: var(--captured-length); + } + &:last-child { + mask: none; + } + } +} diff --git a/packages/css/src/index.css b/packages/css/src/index.css index ce5b8887ba..23fb4ab15d 100644 --- a/packages/css/src/index.css +++ b/packages/css/src/index.css @@ -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); diff --git a/packages/react/src/components/avatar-stack/avatar-stack.mdx b/packages/react/src/components/avatar-stack/avatar-stack.mdx new file mode 100644 index 0000000000..3da7c695b8 --- /dev/null +++ b/packages/react/src/components/avatar-stack/avatar-stack.mdx @@ -0,0 +1,17 @@ +import { Meta, Canvas, Controls, Primary } from '@storybook/addon-docs/blocks'; +import { CssVariables } from '@doc-components'; +/* import css from '@digdir/designsystemet-css/avatar-stack.css?inline'; */ +import * as AvatarStackStories from './avatar-stack.stories'; + + + +# AvatarStack + +`AvatarStack` er en komponent som stabler `Avatar`. + + + + +## CSS Variabler + +{/* */} diff --git a/packages/react/src/components/avatar-stack/avatar-stack.stories.tsx b/packages/react/src/components/avatar-stack/avatar-stack.stories.tsx new file mode 100644 index 0000000000..61d7351a83 --- /dev/null +++ b/packages/react/src/components/avatar-stack/avatar-stack.stories.tsx @@ -0,0 +1,41 @@ +import cat1 from '@assets/img/cats/Cat 3.jpg'; +import { BriefcaseIcon } from '@navikt/aksel-icons'; +import type { Meta, StoryFn } from '@storybook/react-vite'; +import { Avatar, AvatarStack } from '../'; + +type Story = StoryFn; + +const meta: Meta = { + title: 'Komponenter/AvatarStack', + component: AvatarStack, + parameters: { + layout: 'padded', + customStyles: { + display: 'flex', + gap: 'var(--ds-size-2)', + justifyContent: 'center', + alignItems: 'center', + flexWrap: 'wrap', + }, + }, +}; + +export default meta; + +export const Preview: Story = (args) => ( + + + + + + + + + + + +); + +Preview.args = { + 'aria-label': 'Ola Nordmann', +}; diff --git a/packages/react/src/components/avatar-stack/avatar-stack.tsx b/packages/react/src/components/avatar-stack/avatar-stack.tsx new file mode 100644 index 0000000000..b5142da52a --- /dev/null +++ b/packages/react/src/components/avatar-stack/avatar-stack.tsx @@ -0,0 +1,46 @@ +import cl from 'clsx/lite'; +import type { HTMLAttributes } from 'react'; +import { forwardRef } from 'react'; + +export type AvatarStackProps = { + /** + * Adjusts gap between avatars in the stack in px. + * @default '4' + */ + gap?: number; + avatarSize?: number; + overlap?: number; +} & HTMLAttributes; + +/** + * Use `AvatarStack` to constrain Avatars into a stack. + * + * @example + * + * + * + * + * + * + * + * + * + */ +export const AvatarStack = forwardRef( + function AvatarStack({ className, ...rest }, ref) { + const style = { + ...(rest.style || {}), + '--avatar-stack-gap': rest.gap ? `${rest.gap}px` : undefined, + '--avatar-size': rest.avatarSize ? `${rest.avatarSize}px` : undefined, + '--avatar-overlap': rest.overlap ? `${rest.overlap}px` : undefined, + } as React.CSSProperties; + return ( +
+ ); + }, +); diff --git a/packages/react/src/components/index.ts b/packages/react/src/components/index.ts index 58fbcfa7ce..20541d27a1 100644 --- a/packages/react/src/components/index.ts +++ b/packages/react/src/components/index.ts @@ -4,6 +4,9 @@ export { Alert } from './alert/alert'; export type { AvatarProps } from './avatar/avatar'; export { Avatar } from './avatar/avatar'; +export type { AvatarStackProps } from './avatar-stack/avatar-stack'; +export { AvatarStack } from './avatar-stack/avatar-stack'; + export type { BadgePositionProps, BadgeProps } from './badge'; export { Badge, BadgePosition } from './badge'; From caa11afa743b1616da4ff9bb08bf200eaf306cfa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oddbj=C3=B8rn=20=C3=98vernes?= Date: Tue, 16 Dec 2025 16:28:31 +0100 Subject: [PATCH 02/29] hook up vars --- packages/css/src/avatar-stack.css | 6 +++--- packages/react/src/components/avatar-stack/avatar-stack.tsx | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/css/src/avatar-stack.css b/packages/css/src/avatar-stack.css index 100c70f49f..1b93862aec 100644 --- a/packages/css/src/avatar-stack.css +++ b/packages/css/src/avatar-stack.css @@ -5,9 +5,9 @@ } .ds-avatar-stack { - --dsc-avatar-stack-size: round(up, 2.625em, 1px); - --dsc-avatar-stack-gap: 4px; - --dsc-avatar-stack-overlap: -12px; + --dsc-avatar-stack-size: var(--size, round(up, 2.625em, 1px)); + --dsc-avatar-stack-gap: var(--gap, 4px); + --dsc-avatar-stack-overlap: var(--overlap, -12px); --dsc-avatar-stack-outline-color: light-dark(transparent, white); --captured-length: var(--dsc-avatar-stack-overlap); /*overlap*/ diff --git a/packages/react/src/components/avatar-stack/avatar-stack.tsx b/packages/react/src/components/avatar-stack/avatar-stack.tsx index b5142da52a..dc0348a37b 100644 --- a/packages/react/src/components/avatar-stack/avatar-stack.tsx +++ b/packages/react/src/components/avatar-stack/avatar-stack.tsx @@ -30,9 +30,9 @@ export const AvatarStack = forwardRef( function AvatarStack({ className, ...rest }, ref) { const style = { ...(rest.style || {}), - '--avatar-stack-gap': rest.gap ? `${rest.gap}px` : undefined, - '--avatar-size': rest.avatarSize ? `${rest.avatarSize}px` : undefined, - '--avatar-overlap': rest.overlap ? `${rest.overlap}px` : undefined, + '--gap': rest.gap ? `${rest.gap}px` : undefined, + '--size': rest.avatarSize ? `${rest.avatarSize}px` : undefined, + '--overlap': rest.overlap ? `${rest.overlap}px` : undefined, } as React.CSSProperties; return (
Date: Wed, 17 Dec 2025 09:54:31 +0100 Subject: [PATCH 03/29] make overlap input a percentage --- packages/css/src/avatar-stack.css | 4 ++-- .../components/avatar-stack/avatar-stack.tsx | 17 ++++++++++++----- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/packages/css/src/avatar-stack.css b/packages/css/src/avatar-stack.css index 1b93862aec..995842cc5b 100644 --- a/packages/css/src/avatar-stack.css +++ b/packages/css/src/avatar-stack.css @@ -7,8 +7,8 @@ .ds-avatar-stack { --dsc-avatar-stack-size: var(--size, round(up, 2.625em, 1px)); --dsc-avatar-stack-gap: var(--gap, 4px); - --dsc-avatar-stack-overlap: var(--overlap, -12px); - --dsc-avatar-stack-outline-color: light-dark(transparent, white); + --dsc-avatar-stack-overlap: calc((var(--dsc-avatar-stack-size) / 100 * var(--overlap, 50)) * -1); + --dsc-avatar-stack-outline-color: light-dark(transparent, transparent); --captured-length: var(--dsc-avatar-stack-overlap); /*overlap*/ display: flex; diff --git a/packages/react/src/components/avatar-stack/avatar-stack.tsx b/packages/react/src/components/avatar-stack/avatar-stack.tsx index dc0348a37b..a47b217eae 100644 --- a/packages/react/src/components/avatar-stack/avatar-stack.tsx +++ b/packages/react/src/components/avatar-stack/avatar-stack.tsx @@ -5,10 +5,14 @@ import { forwardRef } from 'react'; export type AvatarStackProps = { /** * Adjusts gap between avatars in the stack in px. - * @default '4' + * @default 4 */ gap?: number; avatarSize?: number; + /** + * This is a percentage value of how much avatars should overlap. + * @default 50 + */ overlap?: number; } & HTMLAttributes; @@ -27,12 +31,15 @@ export type AvatarStackProps = { * */ export const AvatarStack = forwardRef( - function AvatarStack({ className, ...rest }, ref) { + function AvatarStack( + { className, gap, avatarSize, overlap = 50, ...rest }, + ref, + ) { const style = { ...(rest.style || {}), - '--gap': rest.gap ? `${rest.gap}px` : undefined, - '--size': rest.avatarSize ? `${rest.avatarSize}px` : undefined, - '--overlap': rest.overlap ? `${rest.overlap}px` : undefined, + '--gap': gap ? `${gap}px` : undefined, + '--size': avatarSize ? `${avatarSize}px` : undefined, + '--overlap': overlap ? `${overlap}` : undefined, } as React.CSSProperties; return (
Date: Wed, 17 Dec 2025 17:32:09 +0100 Subject: [PATCH 04/29] more wip --- packages/css/src/avatar-stack.css | 42 ++++++++++++---- .../avatar-stack/avatar-stack.stories.tsx | 2 +- .../components/avatar-stack/avatar-stack.tsx | 50 ++++++++++++++++--- 3 files changed, 74 insertions(+), 20 deletions(-) diff --git a/packages/css/src/avatar-stack.css b/packages/css/src/avatar-stack.css index 995842cc5b..029b21face 100644 --- a/packages/css/src/avatar-stack.css +++ b/packages/css/src/avatar-stack.css @@ -5,40 +5,60 @@ } .ds-avatar-stack { - --dsc-avatar-stack-size: var(--size, round(up, 2.625em, 1px)); + /*The following variables are defined in the style attribute on .ds-avatar-stack: + * --size, --gap, --overlap, --n + */ + --dsc-avatar-stack-size: var(--size, 2.625em); --dsc-avatar-stack-gap: var(--gap, 4px); - --dsc-avatar-stack-overlap: calc((var(--dsc-avatar-stack-size) / 100 * var(--overlap, 50)) * -1); - --dsc-avatar-stack-outline-color: light-dark(transparent, transparent); + --dsc-avatar-stack-overlap: calc(((var(--dsc-avatar-stack-size) / 100) * var(--overlap, 50)) * -1); --captured-length: var(--dsc-avatar-stack-overlap); /*overlap*/ + container-type: inline-size; display: flex; padding-block: 1px; transition: --captured-length 0.2s ease; width: calc(var(--dsc-avatar-stack-size) * var(--n) + var(--dsc-avatar-stack-overlap) * (var(--n) - 1)); - filter: drop-shadow(0 0 0.5px var(--dsc-avatar-stack-outline-color)) drop-shadow(0 0 0.5px var(--dsc-avatar-stack-outline-color)) - drop-shadow(0 0 0.5px var(--dsc-avatar-stack-outline-color)); - &:hover { - --captured-length: 0px; + + &[data-expandable='true'] { + &:hover { + --captured-length: 0px; + } } + &:empty { display: none; } + .ds-avatar { --dsc-avatar-size: var(--dsc-avatar-stack-size); - aspect-ratio: 1; - border-radius: 50%; - display: grid; - place-items: center; + --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(--dsc-avatar-stack-gap)), #000 calc(100% + var(--dsc-avatar-stack-gap)) ); + &:not(:first-child) { margin-left: var(--captured-length); } + &:last-child { mask: none; } + + &[data-initials]:empty::before { + font-size: var(--font-size); + } + + > span { + font-size: var(--font-size); + } + } + + &[data-additionals]::after { + content: '+' attr(data-additionals); + place-self: center; + margin-left: 1ch; + font-size: max(1rem, calc(var(--dsc-avatar-stack-size) * 0.4)); } } diff --git a/packages/react/src/components/avatar-stack/avatar-stack.stories.tsx b/packages/react/src/components/avatar-stack/avatar-stack.stories.tsx index 61d7351a83..0606e23792 100644 --- a/packages/react/src/components/avatar-stack/avatar-stack.stories.tsx +++ b/packages/react/src/components/avatar-stack/avatar-stack.stories.tsx @@ -31,7 +31,7 @@ export const Preview: Story = (args) => ( - + md ); diff --git a/packages/react/src/components/avatar-stack/avatar-stack.tsx b/packages/react/src/components/avatar-stack/avatar-stack.tsx index a47b217eae..cc7e3d4d12 100644 --- a/packages/react/src/components/avatar-stack/avatar-stack.tsx +++ b/packages/react/src/components/avatar-stack/avatar-stack.tsx @@ -1,19 +1,33 @@ import cl from 'clsx/lite'; import type { HTMLAttributes } from 'react'; -import { forwardRef } from 'react'; +import { Children, forwardRef } from 'react'; export type AvatarStackProps = { /** - * Adjusts gap between avatars in the stack in px. + * Adjusts gap-mask between avatars in the stack in px. * @default 4 */ gap?: number; + /** + * Control the size of the avatars. Must be a valid css length value (px, em, rem, var(--ds-size-12) etc.) + * @default 'var(--ds-size-12)' + */ avatarSize?: number; /** - * This is a percentage value of how much avatars should overlap. + * A number between 0 and 100 which represents the percentage value of how much avatars should overlap. * @default 50 */ overlap?: number; + /** + * The maximum number of avatars to show, additional avatars will show as "+ (n - max)". + * @default 10 + */ + max?: number; + /** + * Expand on hover to show full avatars. + * @default false + */ + expandable?: boolean; } & HTMLAttributes; /** @@ -32,22 +46,42 @@ export type AvatarStackProps = { */ export const AvatarStack = forwardRef( function AvatarStack( - { className, gap, avatarSize, overlap = 50, ...rest }, + { + className, + gap, + avatarSize = 'var(--ds-size-12)', + overlap = 50, + max = 4, + expandable = false, + children, + ...rest + }, ref, ) { + const overflow = Math.max(Children.count(children) - max, 0); + const safeOverlap = Math.min(Math.max(overlap, 0.01), 100); + const childrenToShow = + overflow > 0 + ? Children.toArray(children).slice(0, max) + : Children.toArray(children); const style = { ...(rest.style || {}), '--gap': gap ? `${gap}px` : undefined, - '--size': avatarSize ? `${avatarSize}px` : undefined, - '--overlap': overlap ? `${overlap}` : undefined, + '--size': avatarSize ? `${avatarSize}` : undefined, + '--overlap': safeOverlap ? `${safeOverlap}` : undefined, + '--n': childrenToShow.length || 0, } as React.CSSProperties; return ( -
0 ? overflow : undefined} {...rest} - /> + > + {childrenToShow} + ); }, ); From 782e1de4d79d56a49ffc4bccd3ef52007fdcc968 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oddbj=C3=B8rn=20=C3=98vernes?= Date: Thu, 18 Dec 2025 14:11:44 +0100 Subject: [PATCH 05/29] more wip, support square --- packages/css/src/avatar-stack.css | 9 +- .../components/avatar-stack/avatar-stack.mdx | 9 +- .../avatar-stack/avatar-stack.stories.tsx | 139 +++++++++++++++++- .../components/avatar-stack/avatar-stack.tsx | 6 +- 4 files changed, 151 insertions(+), 12 deletions(-) diff --git a/packages/css/src/avatar-stack.css b/packages/css/src/avatar-stack.css index 029b21face..9bfcb8b71c 100644 --- a/packages/css/src/avatar-stack.css +++ b/packages/css/src/avatar-stack.css @@ -21,7 +21,7 @@ &[data-expandable='true'] { &:hover { - --captured-length: 0px; + --captured-length: var(--dsc-avatar-stack-gap); } } @@ -37,6 +37,13 @@ transparent calc(100% - 1px + var(--dsc-avatar-stack-gap)), #000 calc(100% + var(--dsc-avatar-stack-gap)) ); + &[data-variant='square'] { + mask: linear-gradient( + to right, + #000 calc(100% + var(--captured-length) - var(--dsc-avatar-stack-gap)), + transparent calc(100% + var(--captured-length) - var(--dsc-avatar-stack-gap)) + ); + } &:not(:first-child) { margin-left: var(--captured-length); diff --git a/packages/react/src/components/avatar-stack/avatar-stack.mdx b/packages/react/src/components/avatar-stack/avatar-stack.mdx index 3da7c695b8..39a048a867 100644 --- a/packages/react/src/components/avatar-stack/avatar-stack.mdx +++ b/packages/react/src/components/avatar-stack/avatar-stack.mdx @@ -1,6 +1,4 @@ import { Meta, Canvas, Controls, Primary } from '@storybook/addon-docs/blocks'; -import { CssVariables } from '@doc-components'; -/* import css from '@digdir/designsystemet-css/avatar-stack.css?inline'; */ import * as AvatarStackStories from './avatar-stack.stories'; @@ -11,7 +9,8 @@ import * as AvatarStackStories from './avatar-stack.stories'; +## data-size + +## Square + -## CSS Variabler - -{/* */} diff --git a/packages/react/src/components/avatar-stack/avatar-stack.stories.tsx b/packages/react/src/components/avatar-stack/avatar-stack.stories.tsx index 0606e23792..6d69ea3286 100644 --- a/packages/react/src/components/avatar-stack/avatar-stack.stories.tsx +++ b/packages/react/src/components/avatar-stack/avatar-stack.stories.tsx @@ -1,4 +1,3 @@ -import cat1 from '@assets/img/cats/Cat 3.jpg'; import { BriefcaseIcon } from '@navikt/aksel-icons'; import type { Meta, StoryFn } from '@storybook/react-vite'; import { Avatar, AvatarStack } from '../'; @@ -9,14 +8,14 @@ const meta: Meta = { title: 'Komponenter/AvatarStack', component: AvatarStack, parameters: { - layout: 'padded', - customStyles: { + layout: 'fullscreen', + /* customStyles: { display: 'flex', gap: 'var(--ds-size-2)', justifyContent: 'center', alignItems: 'center', flexWrap: 'wrap', - }, + }, */ }, }; @@ -25,7 +24,7 @@ export default meta; export const Preview: Story = (args) => ( - + @@ -39,3 +38,133 @@ export const Preview: Story = (args) => ( Preview.args = { 'aria-label': 'Ola Nordmann', }; + +export const DataSize: Story = (args) => ( + <> +
+ avatarSize='var(--ds-size-12)' + + + + + + + + sm + + + + + + + + + + md + + + + + + + + + + lg + + +
+
+ avatarSize='3em' + + + + + + + + sm + + + + + + + + + + md + + + + + + + + + + lg + + +
+
+ avatarSize='3rem' + + + + + + + + sm + + + + + + + + + + md + + + + + + + + + + lg + + +
+ +); +DataSize.args = { + expandable: true, + gap: 3, +}; + +export const ShapeVariants: Story = (args) => ( + + + + + + + + + +); +ShapeVariants.args = { + overlap: 50, + expandable: true, +}; diff --git a/packages/react/src/components/avatar-stack/avatar-stack.tsx b/packages/react/src/components/avatar-stack/avatar-stack.tsx index cc7e3d4d12..395e759766 100644 --- a/packages/react/src/components/avatar-stack/avatar-stack.tsx +++ b/packages/react/src/components/avatar-stack/avatar-stack.tsx @@ -2,6 +2,10 @@ import cl from 'clsx/lite'; import type { HTMLAttributes } from 'react'; import { Children, forwardRef } from 'react'; +/* @TODO +- support tooltips around avatar? +- support link avatars? + */ export type AvatarStackProps = { /** * Adjusts gap-mask between avatars in the stack in px. @@ -12,7 +16,7 @@ export type AvatarStackProps = { * Control the size of the avatars. Must be a valid css length value (px, em, rem, var(--ds-size-12) etc.) * @default 'var(--ds-size-12)' */ - avatarSize?: number; + avatarSize?: string; /** * A number between 0 and 100 which represents the percentage value of how much avatars should overlap. * @default 50 From a029c0df58cf58183c919352625fb2b6d0b0f693 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oddbj=C3=B8rn=20=C3=98vernes?= Date: Thu, 18 Dec 2025 15:02:36 +0100 Subject: [PATCH 06/29] test tooltip --- packages/css/src/avatar-stack.css | 4 +- .../components/avatar-stack/avatar-stack.mdx | 5 ++ .../avatar-stack/avatar-stack.stories.tsx | 74 ++++++++++++++++++- .../components/avatar-stack/avatar-stack.tsx | 2 +- 4 files changed, 81 insertions(+), 4 deletions(-) diff --git a/packages/css/src/avatar-stack.css b/packages/css/src/avatar-stack.css index 9bfcb8b71c..77a3ed758c 100644 --- a/packages/css/src/avatar-stack.css +++ b/packages/css/src/avatar-stack.css @@ -15,6 +15,7 @@ --captured-length: var(--dsc-avatar-stack-overlap); /*overlap*/ container-type: inline-size; display: flex; + isolation: isolate; padding-block: 1px; transition: --captured-length 0.2s ease; width: calc(var(--dsc-avatar-stack-size) * var(--n) + var(--dsc-avatar-stack-overlap) * (var(--n) - 1)); @@ -48,8 +49,7 @@ &:not(:first-child) { margin-left: var(--captured-length); } - - &:last-child { + &:nth-last-child(1 of .ds-avatar) { mask: none; } diff --git a/packages/react/src/components/avatar-stack/avatar-stack.mdx b/packages/react/src/components/avatar-stack/avatar-stack.mdx index 39a048a867..b3580d2db6 100644 --- a/packages/react/src/components/avatar-stack/avatar-stack.mdx +++ b/packages/react/src/components/avatar-stack/avatar-stack.mdx @@ -9,8 +9,13 @@ import * as AvatarStackStories from './avatar-stack.stories'; + ## data-size + ## Square +## Tooltip + + diff --git a/packages/react/src/components/avatar-stack/avatar-stack.stories.tsx b/packages/react/src/components/avatar-stack/avatar-stack.stories.tsx index 6d69ea3286..4919632a07 100644 --- a/packages/react/src/components/avatar-stack/avatar-stack.stories.tsx +++ b/packages/react/src/components/avatar-stack/avatar-stack.stories.tsx @@ -1,6 +1,6 @@ import { BriefcaseIcon } from '@navikt/aksel-icons'; import type { Meta, StoryFn } from '@storybook/react-vite'; -import { Avatar, AvatarStack } from '../'; +import { Avatar, AvatarStack, Tooltip } from '../'; type Story = StoryFn; @@ -151,6 +151,7 @@ export const DataSize: Story = (args) => ( DataSize.args = { expandable: true, gap: 3, + max: 3, }; export const ShapeVariants: Story = (args) => ( @@ -162,9 +163,80 @@ export const ShapeVariants: Story = (args) => ( + + + + + +
); ShapeVariants.args = { overlap: 50, expandable: true, + max: 4, +}; + +export const WithTooltip: Story = (args) => ( +
+
+ expandable + + + + + + + + + + + + + + + + + + + + + + +
+
+ not expandable + + + + + + + + + + + + + + + + + + + + + + +
+
+); +WithTooltip.args = { + max: 4, }; diff --git a/packages/react/src/components/avatar-stack/avatar-stack.tsx b/packages/react/src/components/avatar-stack/avatar-stack.tsx index 395e759766..3c7f43f580 100644 --- a/packages/react/src/components/avatar-stack/avatar-stack.tsx +++ b/packages/react/src/components/avatar-stack/avatar-stack.tsx @@ -23,7 +23,7 @@ export type AvatarStackProps = { */ overlap?: number; /** - * The maximum number of avatars to show, additional avatars will show as "+ (n - max)". + * The maximum number of avatars to render, additional avatars will show as "+ (n - max)". * @default 10 */ max?: number; From 766df7667689510e9623172db9c23df03a305040 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oddbj=C3=B8rn=20=C3=98vernes?= Date: Thu, 18 Dec 2025 15:59:27 +0100 Subject: [PATCH 07/29] make it work with link inside avatar --- packages/css/src/avatar-stack.css | 8 +++ .../components/avatar-stack/avatar-stack.mdx | 3 + .../avatar-stack/avatar-stack.stories.tsx | 72 +++++++++++++++++++ .../components/avatar-stack/avatar-stack.tsx | 4 -- 4 files changed, 83 insertions(+), 4 deletions(-) diff --git a/packages/css/src/avatar-stack.css b/packages/css/src/avatar-stack.css index 77a3ed758c..8b2427fd72 100644 --- a/packages/css/src/avatar-stack.css +++ b/packages/css/src/avatar-stack.css @@ -60,6 +60,14 @@ > 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; + } } &[data-additionals]::after { diff --git a/packages/react/src/components/avatar-stack/avatar-stack.mdx b/packages/react/src/components/avatar-stack/avatar-stack.mdx index b3580d2db6..487312273b 100644 --- a/packages/react/src/components/avatar-stack/avatar-stack.mdx +++ b/packages/react/src/components/avatar-stack/avatar-stack.mdx @@ -19,3 +19,6 @@ import * as AvatarStackStories from './avatar-stack.stories'; ## Tooltip +## Link + + diff --git a/packages/react/src/components/avatar-stack/avatar-stack.stories.tsx b/packages/react/src/components/avatar-stack/avatar-stack.stories.tsx index 4919632a07..f61c51e296 100644 --- a/packages/react/src/components/avatar-stack/avatar-stack.stories.tsx +++ b/packages/react/src/components/avatar-stack/avatar-stack.stories.tsx @@ -240,3 +240,75 @@ export const WithTooltip: Story = (args) => ( WithTooltip.args = { max: 4, }; + +export const WithTooltipAndLink: Story = (args) => ( +
+
+ Link expandable + + + + + + + + + + + + + + + + + + + + + + +
+
+ Link + Tooltip + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+); +WithTooltipAndLink.args = { + max: 4, +}; diff --git a/packages/react/src/components/avatar-stack/avatar-stack.tsx b/packages/react/src/components/avatar-stack/avatar-stack.tsx index 3c7f43f580..a8db5752f8 100644 --- a/packages/react/src/components/avatar-stack/avatar-stack.tsx +++ b/packages/react/src/components/avatar-stack/avatar-stack.tsx @@ -2,10 +2,6 @@ import cl from 'clsx/lite'; import type { HTMLAttributes } from 'react'; import { Children, forwardRef } from 'react'; -/* @TODO -- support tooltips around avatar? -- support link avatars? - */ export type AvatarStackProps = { /** * Adjusts gap-mask between avatars in the stack in px. From 0b67e5a53fca235f23229622b6d974553cafbc00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oddbj=C3=B8rn=20=C3=98vernes?= Date: Thu, 18 Dec 2025 16:12:46 +0100 Subject: [PATCH 08/29] overlap story --- .../components/avatar-stack/avatar-stack.mdx | 3 + .../avatar-stack/avatar-stack.stories.tsx | 111 ++++++++++++++++++ 2 files changed, 114 insertions(+) diff --git a/packages/react/src/components/avatar-stack/avatar-stack.mdx b/packages/react/src/components/avatar-stack/avatar-stack.mdx index 487312273b..c762f532fb 100644 --- a/packages/react/src/components/avatar-stack/avatar-stack.mdx +++ b/packages/react/src/components/avatar-stack/avatar-stack.mdx @@ -22,3 +22,6 @@ import * as AvatarStackStories from './avatar-stack.stories'; ## Link +## Overlaps (gap: 3) + + diff --git a/packages/react/src/components/avatar-stack/avatar-stack.stories.tsx b/packages/react/src/components/avatar-stack/avatar-stack.stories.tsx index f61c51e296..129d431ca7 100644 --- a/packages/react/src/components/avatar-stack/avatar-stack.stories.tsx +++ b/packages/react/src/components/avatar-stack/avatar-stack.stories.tsx @@ -312,3 +312,114 @@ export const WithTooltipAndLink: Story = (args) => ( WithTooltipAndLink.args = { max: 4, }; + +export const Overlaps: Story = (args) => ( +
+
+ overlap: 100 + + + + + + + + + + + + + + +
+
+ overlap: 70 + + + + + + + + + + + + + + +
+
+ overlap: 50 + + + + + + + + + + + + + + +
+
+ overlap: 30 + + + + + + + + + + + + + + +
+
+ overlap: 0 + + + + + + + + + + + + + + +
+
+); +Overlaps.args = { + max: 4, + gap: 3, +}; From ca47a5e7aa406e07706eae1192d1ea883cb472c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oddbj=C3=B8rn=20=C3=98vernes?= Date: Fri, 19 Dec 2025 11:21:51 +0100 Subject: [PATCH 09/29] refactor/rename variables --- packages/css/src/avatar-stack.css | 46 ++++++++++--------- .../components/avatar-stack/avatar-stack.tsx | 2 +- 2 files changed, 25 insertions(+), 23 deletions(-) diff --git a/packages/css/src/avatar-stack.css b/packages/css/src/avatar-stack.css index 8b2427fd72..6098ba2905 100644 --- a/packages/css/src/avatar-stack.css +++ b/packages/css/src/avatar-stack.css @@ -5,24 +5,34 @@ } .ds-avatar-stack { - /*The following variables are defined in the style attribute on .ds-avatar-stack: - * --size, --gap, --overlap, --n + /*The following variables can be defined in a parent of .ds-avatar-stack: + * --dsc-avatar-stack-size + * --dsc-avatar-stack-gap + * --dsc-avatar-stack-overlap + * --dsc-avatar-count + * + * --n or --dsc-avatar-count must be set with javascript, but is optionally only needed + * when the stack is expandable, to calculate a fixed width. */ - --dsc-avatar-stack-size: var(--size, 2.625em); - --dsc-avatar-stack-gap: var(--gap, 4px); - --dsc-avatar-stack-overlap: calc(((var(--dsc-avatar-stack-size) / 100) * var(--overlap, 50)) * -1); - --captured-length: var(--dsc-avatar-stack-overlap); /*overlap*/ - container-type: inline-size; + --size: var(--dsc-avatar-stack-size, 2.625em); + --gap: var(--dsc-avatar-stack-gap, 4); + --overlap: var(--dsc-avatar-stack-overlap, 50); + --n: var(--dsc-avatar-count, initial); + + --_gap: calc(var(--gap) * 1px); + --_overlap: calc(((var(--size) / 100) * var(--overlap)) * -1); + + --captured-length: var(--_overlap); display: flex; isolation: isolate; padding-block: 1px; transition: --captured-length 0.2s ease; - width: calc(var(--dsc-avatar-stack-size) * var(--n) + var(--dsc-avatar-stack-overlap) * (var(--n) - 1)); + width: calc(var(--size) * var(--n) + var(--_overlap) * (var(--n) - 1)); &[data-expandable='true'] { &:hover { - --captured-length: var(--dsc-avatar-stack-gap); + --captured-length: var(--_gap); } } @@ -31,19 +41,11 @@ } .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(--dsc-avatar-stack-gap)), - #000 calc(100% + var(--dsc-avatar-stack-gap)) - ); + --dsc-avatar-size: var(--size); + --font-size: max(0.75rem, calc(var(--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(--dsc-avatar-stack-gap)), - transparent calc(100% + var(--captured-length) - var(--dsc-avatar-stack-gap)) - ); + mask: linear-gradient(to right, #000 calc(100% + var(--captured-length) - var(--_gap)), transparent calc(100% + var(--captured-length) - var(--_gap))); } &:not(:first-child) { @@ -74,6 +76,6 @@ content: '+' attr(data-additionals); place-self: center; margin-left: 1ch; - font-size: max(1rem, calc(var(--dsc-avatar-stack-size) * 0.4)); + font-size: max(1rem, calc(var(--size) * 0.4)); } } diff --git a/packages/react/src/components/avatar-stack/avatar-stack.tsx b/packages/react/src/components/avatar-stack/avatar-stack.tsx index a8db5752f8..a98879e66a 100644 --- a/packages/react/src/components/avatar-stack/avatar-stack.tsx +++ b/packages/react/src/components/avatar-stack/avatar-stack.tsx @@ -66,7 +66,7 @@ export const AvatarStack = forwardRef( : Children.toArray(children); const style = { ...(rest.style || {}), - '--gap': gap ? `${gap}px` : undefined, + '--gap': gap ? `${gap}` : undefined, '--size': avatarSize ? `${avatarSize}` : undefined, '--overlap': safeOverlap ? `${safeOverlap}` : undefined, '--n': childrenToShow.length || 0, From 0df37024163afff4571798f46f4578d8dfdf8188 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oddbj=C3=B8rn=20=C3=98vernes?= Date: Fri, 19 Dec 2025 12:04:11 +0100 Subject: [PATCH 10/29] fixed width expand optional --- packages/css/src/avatar-stack.css | 7 +- .../components/avatar-stack/avatar-stack.mdx | 2 + .../avatar-stack/avatar-stack.stories.tsx | 77 ++++++++++++++++++- .../components/avatar-stack/avatar-stack.tsx | 5 +- 4 files changed, 83 insertions(+), 8 deletions(-) diff --git a/packages/css/src/avatar-stack.css b/packages/css/src/avatar-stack.css index 6098ba2905..abcc5c2469 100644 --- a/packages/css/src/avatar-stack.css +++ b/packages/css/src/avatar-stack.css @@ -28,14 +28,17 @@ isolation: isolate; padding-block: 1px; transition: --captured-length 0.2s ease; - width: calc(var(--size) * var(--n) + var(--_overlap) * (var(--n) - 1)); - &[data-expandable='true'] { + &[data-expandable] { &:hover { --captured-length: var(--_gap); } } + &[data-expandable='fixed'] { + width: calc(var(--size) * var(--n) + var(--_overlap) * (var(--n) - 1)); + } + &:empty { display: none; } diff --git a/packages/react/src/components/avatar-stack/avatar-stack.mdx b/packages/react/src/components/avatar-stack/avatar-stack.mdx index c762f532fb..41c70a5503 100644 --- a/packages/react/src/components/avatar-stack/avatar-stack.mdx +++ b/packages/react/src/components/avatar-stack/avatar-stack.mdx @@ -9,6 +9,8 @@ import * as AvatarStackStories from './avatar-stack.stories'; +## expandable + ## data-size diff --git a/packages/react/src/components/avatar-stack/avatar-stack.stories.tsx b/packages/react/src/components/avatar-stack/avatar-stack.stories.tsx index 129d431ca7..d6ba45cdc9 100644 --- a/packages/react/src/components/avatar-stack/avatar-stack.stories.tsx +++ b/packages/react/src/components/avatar-stack/avatar-stack.stories.tsx @@ -39,6 +39,75 @@ Preview.args = { 'aria-label': 'Ola Nordmann', }; +export const Expandable: Story = (args) => ( +
+
+ expandable + + + + + + + + + + + + + + +
+
+ expandable="fixed" + + + + + + + + + + + + + + +
+
+ not expandable + + + + + + + + + + + +
+
+); +Expandable.args = { + gap: 4, +}; + export const DataSize: Story = (args) => ( <>
( ); DataSize.args = { - expandable: true, + expandable: 'fixed', gap: 3, max: 3, }; @@ -173,7 +242,7 @@ export const ShapeVariants: Story = (args) => ( ); ShapeVariants.args = { overlap: 50, - expandable: true, + expandable: 'fixed', max: 4, }; @@ -185,7 +254,7 @@ export const WithTooltip: Story = (args) => ( style={{ display: 'flex', gap: 'var(--ds-size-4)', alignItems: 'center' }} > expandable - + @@ -249,7 +318,7 @@ export const WithTooltipAndLink: Story = (args) => ( style={{ display: 'flex', gap: 'var(--ds-size-4)', alignItems: 'center' }} > Link expandable - + diff --git a/packages/react/src/components/avatar-stack/avatar-stack.tsx b/packages/react/src/components/avatar-stack/avatar-stack.tsx index a98879e66a..ed0418a3cd 100644 --- a/packages/react/src/components/avatar-stack/avatar-stack.tsx +++ b/packages/react/src/components/avatar-stack/avatar-stack.tsx @@ -25,9 +25,10 @@ export type AvatarStackProps = { max?: number; /** * Expand on hover to show full avatars. + * 'fixed': AvatarStack physical width does not change when avatars are expanded. * @default false */ - expandable?: boolean; + expandable?: 'fixed' | boolean; } & HTMLAttributes; /** @@ -52,7 +53,7 @@ export const AvatarStack = forwardRef( avatarSize = 'var(--ds-size-12)', overlap = 50, max = 4, - expandable = false, + expandable, children, ...rest }, From 8e878a4eec227ca5d2af379b7b8a0ae0b65d826b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oddbj=C3=B8rn=20=C3=98vernes?= Date: Fri, 19 Dec 2025 15:11:52 +0100 Subject: [PATCH 11/29] focusable if expandable --- packages/css/src/avatar-stack.css | 8 +++++++- .../avatar-stack/avatar-stack.stories.tsx | 14 +++----------- .../src/components/avatar-stack/avatar-stack.tsx | 9 +++++++++ 3 files changed, 19 insertions(+), 12 deletions(-) diff --git a/packages/css/src/avatar-stack.css b/packages/css/src/avatar-stack.css index abcc5c2469..9928cf9a59 100644 --- a/packages/css/src/avatar-stack.css +++ b/packages/css/src/avatar-stack.css @@ -27,10 +27,16 @@ 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 { + &:hover, + &:focus-visible { --captured-length: var(--_gap); } } diff --git a/packages/react/src/components/avatar-stack/avatar-stack.stories.tsx b/packages/react/src/components/avatar-stack/avatar-stack.stories.tsx index d6ba45cdc9..2cd97608ff 100644 --- a/packages/react/src/components/avatar-stack/avatar-stack.stories.tsx +++ b/packages/react/src/components/avatar-stack/avatar-stack.stories.tsx @@ -9,13 +9,9 @@ const meta: Meta = { component: AvatarStack, parameters: { layout: 'fullscreen', - /* customStyles: { - display: 'flex', - gap: 'var(--ds-size-2)', - justifyContent: 'center', - alignItems: 'center', - flexWrap: 'wrap', - }, */ + }, + args: { + 'aria-label': 'Test av aria label', }, }; @@ -35,10 +31,6 @@ export const Preview: Story = (args) => ( ); -Preview.args = { - 'aria-label': 'Ola Nordmann', -}; - export const Expandable: Story = (args) => (
( } as React.CSSProperties; return (
Date: Fri, 19 Dec 2025 15:15:42 +0100 Subject: [PATCH 12/29] changesets --- .changeset/neat-eggs-clap.md | 5 +++++ .changeset/sour-beers-knock.md | 5 +++++ 2 files changed, 10 insertions(+) create mode 100644 .changeset/neat-eggs-clap.md create mode 100644 .changeset/sour-beers-knock.md diff --git a/.changeset/neat-eggs-clap.md b/.changeset/neat-eggs-clap.md new file mode 100644 index 0000000000..f9aae7863b --- /dev/null +++ b/.changeset/neat-eggs-clap.md @@ -0,0 +1,5 @@ +--- +"@digdir/designsystemet-react": minor +--- + +**AvatarStack**: New component diff --git a/.changeset/sour-beers-knock.md b/.changeset/sour-beers-knock.md new file mode 100644 index 0000000000..09956e54d7 --- /dev/null +++ b/.changeset/sour-beers-knock.md @@ -0,0 +1,5 @@ +--- +"@digdir/designsystemet-css": minor +--- + +**avatar-stack**: New component From 7f3a7ac27f3211394af455826f790cc2a3aa73e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oddbj=C3=B8rn=20=C3=98vernes?= Date: Sat, 20 Dec 2025 18:16:58 +0100 Subject: [PATCH 13/29] remove defaults from react --- packages/css/src/avatar-stack.css | 8 ++++---- .../react/src/components/avatar-stack/avatar-stack.tsx | 10 +++++++--- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/packages/css/src/avatar-stack.css b/packages/css/src/avatar-stack.css index 9928cf9a59..554613113a 100644 --- a/packages/css/src/avatar-stack.css +++ b/packages/css/src/avatar-stack.css @@ -15,7 +15,7 @@ * when the stack is expandable, to calculate a fixed width. */ - --size: var(--dsc-avatar-stack-size, 2.625em); + --size: var(--dsc-avatar-stack-size, var(--ds-size-12)); --gap: var(--dsc-avatar-stack-gap, 4); --overlap: var(--dsc-avatar-stack-overlap, 50); --n: var(--dsc-avatar-count, initial); @@ -51,7 +51,7 @@ .ds-avatar { --dsc-avatar-size: var(--size); - --font-size: max(0.75rem, calc(var(--size) * 0.5)); + --_font-size: max(0.75rem, calc(var(--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))); @@ -65,11 +65,11 @@ } &[data-initials]:empty::before { - font-size: var(--font-size); + font-size: var(--_font-size); } > span { - font-size: var(--font-size); + font-size: var(--_font-size); } > a { line-height: 0; diff --git a/packages/react/src/components/avatar-stack/avatar-stack.tsx b/packages/react/src/components/avatar-stack/avatar-stack.tsx index ca7d015135..b4051c45ca 100644 --- a/packages/react/src/components/avatar-stack/avatar-stack.tsx +++ b/packages/react/src/components/avatar-stack/avatar-stack.tsx @@ -58,8 +58,8 @@ export const AvatarStack = forwardRef( { className, gap, - avatarSize = 'var(--ds-size-12)', - overlap = 50, + avatarSize, + overlap, max = 4, expandable, children, @@ -68,7 +68,11 @@ export const AvatarStack = forwardRef( ref, ) { const overflow = Math.max(Children.count(children) - max, 0); - const safeOverlap = Math.min(Math.max(overlap, 0.01), 100); + /*overlap can not be exactly 0. @Todo: can it be solved in css?*/ + const safeOverlap = + overlap !== undefined + ? Math.min(Math.max(overlap, 0.01), 100) + : undefined; const childrenToShow = overflow > 0 ? Children.toArray(children).slice(0, max) From cff237a07f49ce5f1df7f1548e8f9d24a55b99ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oddbj=C3=B8rn=20=C3=98vernes?= Date: Sat, 20 Dec 2025 19:04:45 +0100 Subject: [PATCH 14/29] handle gap 0 --- packages/react/src/components/avatar-stack/avatar-stack.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react/src/components/avatar-stack/avatar-stack.tsx b/packages/react/src/components/avatar-stack/avatar-stack.tsx index b4051c45ca..231917ef6d 100644 --- a/packages/react/src/components/avatar-stack/avatar-stack.tsx +++ b/packages/react/src/components/avatar-stack/avatar-stack.tsx @@ -79,7 +79,7 @@ export const AvatarStack = forwardRef( : Children.toArray(children); const style = { ...(rest.style || {}), - '--gap': gap ? `${gap}` : undefined, + '--gap': gap !== undefined ? `${gap}` : undefined, '--size': avatarSize ? `${avatarSize}` : undefined, '--overlap': safeOverlap ? `${safeOverlap}` : undefined, '--n': childrenToShow.length || 0, From 61ca64cbb00d2bc26bd98f2186b8ad2747c73e66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oddbj=C3=B8rn=20=C3=98vernes?= Date: Sat, 20 Dec 2025 19:05:22 +0100 Subject: [PATCH 15/29] interactive story for size/gap/overlap --- .../components/avatar-stack/avatar-stack.mdx | 8 +- .../avatar-stack/avatar-stack.stories.tsx | 215 +++++++++--------- 2 files changed, 111 insertions(+), 112 deletions(-) diff --git a/packages/react/src/components/avatar-stack/avatar-stack.mdx b/packages/react/src/components/avatar-stack/avatar-stack.mdx index 41c70a5503..db984ccac5 100644 --- a/packages/react/src/components/avatar-stack/avatar-stack.mdx +++ b/packages/react/src/components/avatar-stack/avatar-stack.mdx @@ -9,10 +9,14 @@ import * as AvatarStackStories from './avatar-stack.stories'; + +## Interactive playground + + ## expandable -## data-size +## data-size with differen units ## Square @@ -24,6 +28,4 @@ import * as AvatarStackStories from './avatar-stack.stories'; ## Link -## Overlaps (gap: 3) - diff --git a/packages/react/src/components/avatar-stack/avatar-stack.stories.tsx b/packages/react/src/components/avatar-stack/avatar-stack.stories.tsx index 2cd97608ff..21ab593910 100644 --- a/packages/react/src/components/avatar-stack/avatar-stack.stories.tsx +++ b/packages/react/src/components/avatar-stack/avatar-stack.stories.tsx @@ -1,6 +1,7 @@ import { BriefcaseIcon } from '@navikt/aksel-icons'; import type { Meta, StoryFn } from '@storybook/react-vite'; -import { Avatar, AvatarStack, Tooltip } from '../'; +import { useState } from 'react'; +import { Avatar, AvatarStack, Label, Tooltip } from '../'; type Story = StoryFn; @@ -374,113 +375,109 @@ WithTooltipAndLink.args = { max: 4, }; -export const Overlaps: Story = (args) => ( -
-
- overlap: 100 - - - - - - - - - - - - - - -
-
- overlap: 70 - - - - - - - - - - - - - - -
-
- overlap: 50 - - - - - - - - - - - - - - -
-
- overlap: 30 - - - - - - - - - - - - - - -
-
{ + const [size, setSize] = useState(64); + const [overlap, setOverlap] = useState(50); + const [gap, setGap] = useState(4); + const labelStyle = { + display: 'flex', + flexDirection: 'column', + gap: 'var(--ds-size-2)', + accentColor: 'var(--ds-color-base-default)', + } as const; + + return ( +
- overlap: 0 - - - - - - - - - - - - - - -
-
-); -Overlaps.args = { - max: 4, - gap: 3, +
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + +
+
+ ); }; From d0a97b3bf7d2e5c1615f0a2c6514c3070f33c0f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oddbj=C3=B8rn=20=C3=98vernes?= Date: Sat, 20 Dec 2025 21:39:03 +0100 Subject: [PATCH 16/29] add more to playground story --- .../avatar-stack/avatar-stack.stories.tsx | 59 +++++++++++++++---- 1 file changed, 46 insertions(+), 13 deletions(-) diff --git a/packages/react/src/components/avatar-stack/avatar-stack.stories.tsx b/packages/react/src/components/avatar-stack/avatar-stack.stories.tsx index 21ab593910..fe172b1e30 100644 --- a/packages/react/src/components/avatar-stack/avatar-stack.stories.tsx +++ b/packages/react/src/components/avatar-stack/avatar-stack.stories.tsx @@ -1,7 +1,7 @@ import { BriefcaseIcon } from '@navikt/aksel-icons'; import type { Meta, StoryFn } from '@storybook/react-vite'; import { useState } from 'react'; -import { Avatar, AvatarStack, Label, Tooltip } from '../'; +import { Avatar, AvatarStack, Checkbox, Label, Tooltip } from '../'; type Story = StoryFn; @@ -376,7 +376,10 @@ WithTooltipAndLink.args = { }; export const Playground: Story = () => { + const [expandable, setExpandable] = useState(undefined); + const [square, setSquare] = useState(false); const [size, setSize] = useState(64); + const [max, setMax] = useState(4); const [overlap, setOverlap] = useState(50); const [gap, setGap] = useState(4); const labelStyle = { @@ -392,6 +395,7 @@ export const Playground: Story = () => { display: 'flex', flexDirection: 'column', gap: 'var(--ds-size-8)', + minHeight: '395px', }} >
{ gap: 'var(--ds-size-4)', }} > +
+ setExpandable((prev) => (prev ? undefined : true))} + /> + setSquare((prev) => !prev)} + /> + +
-
- - - - - - - - - md - - - - - - - - - - - -
+ + + + + + + + md + + + + + + + + + + +
); }; From 9e17550b9d7bf27a375c00256aefdabe29d5df8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oddbj=C3=B8rn=20=C3=98vernes?= Date: Mon, 5 Jan 2026 10:55:12 +0100 Subject: [PATCH 18/29] default gap 2px --- packages/css/src/avatar-stack.css | 2 +- packages/react/src/components/avatar-stack/avatar-stack.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/css/src/avatar-stack.css b/packages/css/src/avatar-stack.css index 554613113a..90ff05ba9f 100644 --- a/packages/css/src/avatar-stack.css +++ b/packages/css/src/avatar-stack.css @@ -16,7 +16,7 @@ */ --size: var(--dsc-avatar-stack-size, var(--ds-size-12)); - --gap: var(--dsc-avatar-stack-gap, 4); + --gap: var(--dsc-avatar-stack-gap, 2); --overlap: var(--dsc-avatar-stack-overlap, 50); --n: var(--dsc-avatar-count, initial); diff --git a/packages/react/src/components/avatar-stack/avatar-stack.tsx b/packages/react/src/components/avatar-stack/avatar-stack.tsx index 231917ef6d..912d5269f3 100644 --- a/packages/react/src/components/avatar-stack/avatar-stack.tsx +++ b/packages/react/src/components/avatar-stack/avatar-stack.tsx @@ -13,7 +13,7 @@ import { Children, forwardRef } from 'react'; export type AvatarStackProps = { /** * Adjusts gap-mask between avatars in the stack in px. - * @default 4 + * @default 2 */ gap?: number; /** From f50448e365a32ca375faf6d46f881ecfc1e4d76b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oddbj=C3=B8rn=20=C3=98vernes?= Date: Tue, 6 Jan 2026 15:45:38 +0100 Subject: [PATCH 19/29] fix overlap 0 = falsy --- .../src/components/avatar-stack/avatar-stack.stories.tsx | 4 ++-- .../react/src/components/avatar-stack/avatar-stack.tsx | 9 ++------- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/packages/react/src/components/avatar-stack/avatar-stack.stories.tsx b/packages/react/src/components/avatar-stack/avatar-stack.stories.tsx index 2ecc337091..0178e3638c 100644 --- a/packages/react/src/components/avatar-stack/avatar-stack.stories.tsx +++ b/packages/react/src/components/avatar-stack/avatar-stack.stories.tsx @@ -381,7 +381,7 @@ export const Playground: Story = () => { const [size, setSize] = useState(64); const [max, setMax] = useState(4); const [overlap, setOverlap] = useState(50); - const [gap, setGap] = useState(4); + const [gap, setGap] = useState(2); const labelStyle = { display: 'flex', flexDirection: 'column', @@ -453,7 +453,7 @@ export const Playground: Story = () => {
); -WithTooltip.args = { - max: 4, -}; export const WithTooltipAndLink: Story = (args) => (
(
); -WithTooltipAndLink.args = { - max: 4, -}; export const Playground: Story = () => { const [expandable, setExpandable] = useState(undefined); const [square, setSquare] = useState(false); const [size, setSize] = useState(64); - const [max, setMax] = useState(4); const [overlap, setOverlap] = useState(50); const [gap, setGap] = useState(2); const labelStyle = { @@ -423,19 +410,6 @@ export const Playground: Story = () => { checked={square} onChange={() => setSquare((prev) => !prev)} /> -
); diff --git a/packages/react/src/components/avatar-stack/avatar-stack.tsx b/packages/react/src/components/avatar-stack/avatar-stack.tsx index e2fab584ea..aeb4b003ce 100644 --- a/packages/react/src/components/avatar-stack/avatar-stack.tsx +++ b/packages/react/src/components/avatar-stack/avatar-stack.tsx @@ -27,10 +27,9 @@ export type AvatarStackProps = { */ overlap?: number; /** - * The maximum number of avatars to render, additional avatars will show as "+ (n - max)". - * @default 10 + * Text to the right of the avatars to show a number representing additional avatars not shown such as '+5'". */ - max?: number; + suffix?: string; /** * Expand on hover to show full avatars. * 'fixed': AvatarStack physical width does not change when avatars are expanded. @@ -58,26 +57,23 @@ export const AvatarStack = forwardRef( { className, gap, + suffix, avatarSize, overlap, - max = 4, expandable, children, ...rest }, ref, ) { - const overflow = Math.max(Children.count(children) - max, 0); - const childrenToShow = - overflow > 0 - ? Children.toArray(children).slice(0, max) - : Children.toArray(children); const style = { ...(rest.style || {}), - '--gap': gap !== undefined ? `${gap}` : undefined, - '--size': avatarSize ? `${avatarSize}` : undefined, - '--overlap': overlap !== undefined ? `${overlap}` : undefined, - '--n': childrenToShow.length || 0, + '--dsc-avatar-stack-gap': gap !== undefined ? `${gap}` : undefined, + '--dsc-avatar-stack-size': avatarSize ? `${avatarSize}` : undefined, + '--dsc-avatar-stack-overlap': + overlap !== undefined ? `${overlap}` : undefined, + '--dsc-avatar-count': + expandable === 'fixed' ? Children.count(children) : undefined, } as React.CSSProperties; return (
( className={cl(`ds-avatar-stack`, className)} style={style} data-expandable={expandable} - data-additionals={overflow > 0 ? overflow : undefined} + data-suffix={suffix} {...rest} > - {childrenToShow} + {children}
); }, From b718ad6a400e74b61a6d7db7148695edbee201bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oddbj=C3=B8rn=20=C3=98vernes?= Date: Thu, 8 Jan 2026 08:49:59 +0100 Subject: [PATCH 22/29] align center default --- packages/css/src/avatar-stack.css | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/css/src/avatar-stack.css b/packages/css/src/avatar-stack.css index 5a56e6d7b7..ae27359dd8 100644 --- a/packages/css/src/avatar-stack.css +++ b/packages/css/src/avatar-stack.css @@ -16,6 +16,7 @@ --captured-length: var(--_overlap); display: flex; + align-items: center; isolation: isolate; padding-block: 1px; width: max-content; From 234fa834c19ae11fb8e0fe2320f4a01069fc6a87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oddbj=C3=B8rn=20=C3=98vernes?= Date: Thu, 8 Jan 2026 14:03:36 +0100 Subject: [PATCH 23/29] Avatar: add asChild support --- .../avatar-stack/avatar-stack.stories.tsx | 20 +++++++++---------- .../react/src/components/avatar/avatar.tsx | 11 ++++++++-- 2 files changed, 18 insertions(+), 13 deletions(-) diff --git a/packages/react/src/components/avatar-stack/avatar-stack.stories.tsx b/packages/react/src/components/avatar-stack/avatar-stack.stories.tsx index c0bc57c451..27d3ab6032 100644 --- a/packages/react/src/components/avatar-stack/avatar-stack.stories.tsx +++ b/packages/react/src/components/avatar-stack/avatar-stack.stories.tsx @@ -303,22 +303,22 @@ export const WithTooltipAndLink: Story = (args) => ( > Link expandable - +
- + - + - + @@ -331,31 +331,29 @@ export const WithTooltipAndLink: Story = (args) => ( Link + Tooltip - + - + - + - - - - + + BR diff --git a/packages/react/src/components/avatar/avatar.tsx b/packages/react/src/components/avatar/avatar.tsx index 8a25177e9d..df2bb3d748 100644 --- a/packages/react/src/components/avatar/avatar.tsx +++ b/packages/react/src/components/avatar/avatar.tsx @@ -31,6 +31,11 @@ export type AvatarProps = MergeRight< * Initials to display inside the avatar. */ initials?: string; + /** + * Change the default rendered element for the one passed as a child, merging their props and behavior. + * @default false + */ + asChild?: boolean; /** * Image, icon or initials to display inside the avatar. * @@ -63,16 +68,18 @@ export const Avatar = forwardRef(function Avatar( className, children, initials, + asChild, ...rest }, ref, ) { + const OuterComponent = asChild ? Slot : 'span'; const useSlot = children && typeof children !== 'string'; const textChild = children && typeof children === 'string'; const Component = useSlot ? Slot : Fragment; return ( - (function Avatar( {textChild ? {children} : children} - + ); }); From aabf8c45165f1290a85589fef23cb5464cc667c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oddbj=C3=B8rn=20=C3=98vernes?= Date: Thu, 8 Jan 2026 14:14:40 +0100 Subject: [PATCH 24/29] :has fallback in @supports to not break tooltip --- packages/css/src/avatar-stack.css | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/packages/css/src/avatar-stack.css b/packages/css/src/avatar-stack.css index ae27359dd8..4472801d70 100644 --- a/packages/css/src/avatar-stack.css +++ b/packages/css/src/avatar-stack.css @@ -59,10 +59,19 @@ &:not(:first-child) { margin-left: var(--captured-length); } + &:nth-last-child(1 of .ds-avatar) { mask: none; } + @supports not selector(:nth-last-child(1 of .ds-avatar)) { + /*Note: This selector will remove all masks if there are tooltips on avatars but will only apply to chromium browsers older than 2023*/ + &:not(:has(+ .ds-avatar)) { + mask: none; + } + } + + /*we have to override the default font-size for the two ways you can add initials to avatar */ &[data-initials]:empty::before { font-size: var(--_font-size); } @@ -70,9 +79,6 @@ > span { font-size: var(--_font-size); } - > a { - line-height: 0; - } &:has(:focus-visible) { mask: none; @composes ds-focus--visible from './base.css'; From 7cf5f8be204ffd860f0caa653fe399a65fd2d0ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oddbj=C3=B8rn=20=C3=98vernes?= Date: Thu, 8 Jan 2026 14:25:47 +0100 Subject: [PATCH 25/29] fix focus when asChild a --- packages/css/src/avatar-stack.css | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/css/src/avatar-stack.css b/packages/css/src/avatar-stack.css index 4472801d70..c4355f9f9b 100644 --- a/packages/css/src/avatar-stack.css +++ b/packages/css/src/avatar-stack.css @@ -79,7 +79,8 @@ > span { font-size: var(--_font-size); } - &:has(:focus-visible) { + &:has(:focus-visible), + &:focus-visible { mask: none; @composes ds-focus--visible from './base.css'; z-index: 1; From cc87fbb40b651a003dc64c5be6270fd5cd24e18e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oddbj=C3=B8rn=20=C3=98vernes?= Date: Thu, 8 Jan 2026 14:29:28 +0100 Subject: [PATCH 26/29] changeset for avatar asChild --- .changeset/floppy-bananas-wonder.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/floppy-bananas-wonder.md diff --git a/.changeset/floppy-bananas-wonder.md b/.changeset/floppy-bananas-wonder.md new file mode 100644 index 0000000000..2b977e646a --- /dev/null +++ b/.changeset/floppy-bananas-wonder.md @@ -0,0 +1,5 @@ +--- +"@digdir/designsystemet-react": minor +--- + +**Avatar**: added `asChild` prop From bd833e35dd2cc2cb05e0f229d17c39d5df26976b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oddbj=C3=B8rn=20=C3=98vernes?= Date: Thu, 8 Jan 2026 14:38:03 +0100 Subject: [PATCH 27/29] gap input requires unit --- packages/css/src/avatar-stack.css | 4 ++-- .../src/components/avatar-stack/avatar-stack.stories.tsx | 4 ++-- packages/react/src/components/avatar-stack/avatar-stack.tsx | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/css/src/avatar-stack.css b/packages/css/src/avatar-stack.css index c4355f9f9b..bdec2d7f0e 100644 --- a/packages/css/src/avatar-stack.css +++ b/packages/css/src/avatar-stack.css @@ -6,12 +6,12 @@ .ds-avatar-stack { --dsc-avatar-stack-size: var(--ds-size-12); - --dsc-avatar-stack-gap: 2; + --dsc-avatar-stack-gap: 2px; --dsc-avatar-stack-overlap: 50; /*--dsc-avatar-count 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); + --_gap: var(--dsc-avatar-stack-gap); --_overlap: calc(((var(--dsc-avatar-stack-size) / 100) * var(--dsc-avatar-stack-overlap)) * -1); --captured-length: var(--_overlap); diff --git a/packages/react/src/components/avatar-stack/avatar-stack.stories.tsx b/packages/react/src/components/avatar-stack/avatar-stack.stories.tsx index 27d3ab6032..1834e789af 100644 --- a/packages/react/src/components/avatar-stack/avatar-stack.stories.tsx +++ b/packages/react/src/components/avatar-stack/avatar-stack.stories.tsx @@ -98,7 +98,7 @@ export const Expandable: Story = (args) => ( ); Expandable.args = { - gap: 4, + gap: '4px', }; export const DataSize: Story = (args) => ( @@ -453,7 +453,7 @@ export const Playground: Story = () => { diff --git a/packages/react/src/components/avatar-stack/avatar-stack.tsx b/packages/react/src/components/avatar-stack/avatar-stack.tsx index aeb4b003ce..4ccf90a4a7 100644 --- a/packages/react/src/components/avatar-stack/avatar-stack.tsx +++ b/packages/react/src/components/avatar-stack/avatar-stack.tsx @@ -12,10 +12,10 @@ import { Children, forwardRef } from 'react'; export type AvatarStackProps = { /** - * Adjusts gap-mask between avatars in the stack in px. - * @default 2 + * Adjusts gap-mask between avatars in the stack. Must be a valid css length value (px, em, rem, var(--ds-size-1) etc.) + * @default 2px */ - gap?: number; + gap?: string; /** * Control the size of the avatars. Must be a valid css length value (px, em, rem, var(--ds-size-12) etc.) * @default 'var(--ds-size-12)' From 90e215ba9d2d92351b27d1c30b6edb33139fe1da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oddbj=C3=B8rn=20=C3=98vernes?= Date: Fri, 9 Jan 2026 15:19:17 +0100 Subject: [PATCH 28/29] www: couple of deprecated biome warnings --- apps/www/app/app.css | 2 -- 1 file changed, 2 deletions(-) diff --git a/apps/www/app/app.css b/apps/www/app/app.css index dcde17b6d5..619c1b1f8d 100644 --- a/apps/www/app/app.css +++ b/apps/www/app/app.css @@ -11,11 +11,9 @@ --circle-wipe-progress: 150vmax; } } - /* biome-ignore lint/correctness/noUnknownTypeSelector: new feature */ ::view-transition-old(root) { animation: none; } - /* biome-ignore lint/correctness/noUnknownTypeSelector: new feature */ &::view-transition-new(root) { mask-image: radial-gradient(circle var(--circle-wipe-progress) at var(--_theme-x, 50%) var(--_theme-y, 50%), black 70%, transparent 100%); animation: 0.6s ease-in both --circle-wipe; From 4c72b533022cf56093049ba6d235d538ed8ae61b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oddbj=C3=B8rn=20=C3=98vernes?= Date: Fri, 9 Jan 2026 15:47:59 +0100 Subject: [PATCH 29/29] scaffold docs --- .../avatar-stack/avatar.stories.tsx | 22 ++++++++ .../avatar-stack/en/accessibility.mdx | 5 ++ .../components/avatar-stack/en/code.mdx | 55 +++++++++++++++++++ .../components/avatar-stack/en/overview.mdx | 16 ++++++ .../components/avatar-stack/metadata.json | 12 ++++ .../avatar-stack/no/accessibility.mdx | 6 ++ .../components/avatar-stack/no/code.mdx | 55 +++++++++++++++++++ .../components/avatar-stack/no/overview.mdx | 16 ++++++ 8 files changed, 187 insertions(+) create mode 100644 apps/www/app/content/components/avatar-stack/avatar.stories.tsx create mode 100644 apps/www/app/content/components/avatar-stack/en/accessibility.mdx create mode 100644 apps/www/app/content/components/avatar-stack/en/code.mdx create mode 100644 apps/www/app/content/components/avatar-stack/en/overview.mdx create mode 100644 apps/www/app/content/components/avatar-stack/metadata.json create mode 100644 apps/www/app/content/components/avatar-stack/no/accessibility.mdx create mode 100644 apps/www/app/content/components/avatar-stack/no/code.mdx create mode 100644 apps/www/app/content/components/avatar-stack/no/overview.mdx diff --git a/apps/www/app/content/components/avatar-stack/avatar.stories.tsx b/apps/www/app/content/components/avatar-stack/avatar.stories.tsx new file mode 100644 index 0000000000..5cd43a310f --- /dev/null +++ b/apps/www/app/content/components/avatar-stack/avatar.stories.tsx @@ -0,0 +1,22 @@ +import { + Avatar, + AvatarStack, + Checkbox, + Label, + Tooltip, +} from '@digdir/designsystemet-react'; +import { BriefcaseIcon } from '@navikt/aksel-icons'; + +export const Preview = () => ( + + + + + + + + + md + + +); diff --git a/apps/www/app/content/components/avatar-stack/en/accessibility.mdx b/apps/www/app/content/components/avatar-stack/en/accessibility.mdx new file mode 100644 index 0000000000..ac2c20cd8d --- /dev/null +++ b/apps/www/app/content/components/avatar-stack/en/accessibility.mdx @@ -0,0 +1,5 @@ + +We are working to improve the accessibility documentation for this component. If you have questions or see something that should be prioritised, please contact us on [Github](https://github.com/digdir/designsystemet/issues/new?template=1bug_report.yml) or [Slack](https://designsystemet.no/slack). + + +## How to describe `AvatarStack` for screen readers diff --git a/apps/www/app/content/components/avatar-stack/en/code.mdx b/apps/www/app/content/components/avatar-stack/en/code.mdx new file mode 100644 index 0000000000..7296011766 --- /dev/null +++ b/apps/www/app/content/components/avatar-stack/en/code.mdx @@ -0,0 +1,55 @@ + + + + +## Usage + +```tsx +import { AvatarStack, Avatar } from '@digdir/designsystemet-react'; + + + ON + + + + + +``` + +## Examples + +### Customizability + +### "Additional avatars" + +### Sizing + +### Tooltip + +### Link + + +## HTML + +AvatarStack uses the class name `ds-avatar-stack` on a `
`. + +```html + + + + +ON + + + + + + + + + + +Ola Nordmann +``` + +## CSS variables and data attributes diff --git a/apps/www/app/content/components/avatar-stack/en/overview.mdx b/apps/www/app/content/components/avatar-stack/en/overview.mdx new file mode 100644 index 0000000000..95016e57da --- /dev/null +++ b/apps/www/app/content/components/avatar-stack/en/overview.mdx @@ -0,0 +1,16 @@ + + +**Use `AvatarStack` when** + +**Avoid `AvatarStack` when** + + +## Examples + +### Sizing + +### Variants + +## Guidelines + +## Text diff --git a/apps/www/app/content/components/avatar-stack/metadata.json b/apps/www/app/content/components/avatar-stack/metadata.json new file mode 100644 index 0000000000..113445462a --- /dev/null +++ b/apps/www/app/content/components/avatar-stack/metadata.json @@ -0,0 +1,12 @@ +{ + "no": { + "title": "AvatarStack", + "subtitle": "`AvatarStack` stabler en samling `Avatar` elementer" + }, + "en": { + "title": "AvatarStack", + "subtitle": "`AvatarStack` stacks a collection of `Avatar` elements" + }, + "image": "Avatar.svg", + "cssFile": "avatar-stack.css" +} diff --git a/apps/www/app/content/components/avatar-stack/no/accessibility.mdx b/apps/www/app/content/components/avatar-stack/no/accessibility.mdx new file mode 100644 index 0000000000..28e84fb52a --- /dev/null +++ b/apps/www/app/content/components/avatar-stack/no/accessibility.mdx @@ -0,0 +1,6 @@ + +Vi jobber med å forbedre dokumentasjonen for tilgjengelighet på denne komponenten. Har du spørsmål eller ser noe som bør prioriteres, ta gjerne kontakt med oss på [Github](https://github.com/digdir/designsystemet/issues/new?template=1bug_report.yml) eller [Slack](https://designsystemet.no/slack). + + +## Hvordan beskrive `Avatar` for skjermlesere + diff --git a/apps/www/app/content/components/avatar-stack/no/code.mdx b/apps/www/app/content/components/avatar-stack/no/code.mdx new file mode 100644 index 0000000000..eb9be1ccf1 --- /dev/null +++ b/apps/www/app/content/components/avatar-stack/no/code.mdx @@ -0,0 +1,55 @@ + + + + +## Bruk + +```tsx +import { AvatarStack, Avatar } from '@digdir/designsystemet-react'; + + + ON + + + + + +``` + +## Eksempel + +### Customizability + +### "Additional avatars" + +### Størrelser + +### Tooltip + +### Link + + +## HTML + +AvatarStack bruker klassen `ds-avatar-stack` på en `
`. + +```html + + + + +ON + + + + + + + + + + +Ola Nordmann +``` + +## CSS variabler og data atributter \ No newline at end of file diff --git a/apps/www/app/content/components/avatar-stack/no/overview.mdx b/apps/www/app/content/components/avatar-stack/no/overview.mdx new file mode 100644 index 0000000000..8b6cdea58f --- /dev/null +++ b/apps/www/app/content/components/avatar-stack/no/overview.mdx @@ -0,0 +1,16 @@ + + +**Bruk `AvatarStack` når** + +**Unngå `AvatarStack` når** + + +## Eksempel + +### Størrelser + +### Varianter + +## Retningslinjer + +## tekst