From eb331802b57f307aea4fa4595cc084ee82aedfc1 Mon Sep 17 00:00:00 2001 From: Ruben Sibon Date: Thu, 14 Mar 2024 14:13:38 +0100 Subject: [PATCH 01/27] WIP: scaffold avatar component --- packages/css/src/components/avatar/README.md | 3 ++ .../css/src/components/avatar/avatar.scss | 8 ++++ packages/css/src/components/index.scss | 1 + packages/react/src/Avatar/Avatar.test.tsx | 41 +++++++++++++++++++ packages/react/src/Avatar/Avatar.tsx | 20 +++++++++ packages/react/src/Avatar/README.md | 5 +++ packages/react/src/Avatar/index.ts | 2 + packages/react/src/index.ts | 1 + .../src/components/ams/avatar.tokens.json | 5 +++ .../src/components/Avatar/Avatar.docs.mdx | 11 +++++ .../src/components/Avatar/Avatar.stories.tsx | 21 ++++++++++ 11 files changed, 118 insertions(+) create mode 100644 packages/css/src/components/avatar/README.md create mode 100644 packages/css/src/components/avatar/avatar.scss create mode 100644 packages/react/src/Avatar/Avatar.test.tsx create mode 100644 packages/react/src/Avatar/Avatar.tsx create mode 100644 packages/react/src/Avatar/README.md create mode 100644 packages/react/src/Avatar/index.ts create mode 100644 proprietary/tokens/src/components/ams/avatar.tokens.json create mode 100644 storybook/src/components/Avatar/Avatar.docs.mdx create mode 100644 storybook/src/components/Avatar/Avatar.stories.tsx diff --git a/packages/css/src/components/avatar/README.md b/packages/css/src/components/avatar/README.md new file mode 100644 index 0000000000..e639f03b1c --- /dev/null +++ b/packages/css/src/components/avatar/README.md @@ -0,0 +1,3 @@ + + +# Avatar diff --git a/packages/css/src/components/avatar/avatar.scss b/packages/css/src/components/avatar/avatar.scss new file mode 100644 index 0000000000..96ba4a6609 --- /dev/null +++ b/packages/css/src/components/avatar/avatar.scss @@ -0,0 +1,8 @@ +/** + * @license EUPL-1.2+ + * Copyright Gemeente Amsterdam + */ + +.ams-avatar { + /* Add styles here */ +} diff --git a/packages/css/src/components/index.scss b/packages/css/src/components/index.scss index 348b39865f..ef67d4a70d 100644 --- a/packages/css/src/components/index.scss +++ b/packages/css/src/components/index.scss @@ -4,6 +4,7 @@ */ /* Append here */ +@import "./avatar/avatar"; @import "./row/row"; @import "./radio/radio"; @import "./tabs/tabs"; diff --git a/packages/react/src/Avatar/Avatar.test.tsx b/packages/react/src/Avatar/Avatar.test.tsx new file mode 100644 index 0000000000..ee1096641d --- /dev/null +++ b/packages/react/src/Avatar/Avatar.test.tsx @@ -0,0 +1,41 @@ +import { render } from '@testing-library/react' +import { createRef } from 'react' +import { Avatar } from './Avatar' +import '@testing-library/jest-dom' + +describe('Avatar', () => { + it('renders', () => { + const { container } = render() + + const component = container.querySelector(':only-child') + + expect(component).toBeInTheDocument() + expect(component).toBeVisible() + }) + + it('renders a design system BEM class name', () => { + const { container } = render() + + const component = container.querySelector(':only-child') + + expect(component).toHaveClass('ams-avatar') + }) + + it('renders an additional class name', () => { + const { container } = render() + + const component = container.querySelector(':only-child') + + expect(component).toHaveClass('ams-avatar extra') + }) + + it('supports ForwardRef in React', () => { + const ref = createRef() + + const { container } = render() + + const component = container.querySelector(':only-child') + + expect(ref.current).toBe(component) + }) +}) diff --git a/packages/react/src/Avatar/Avatar.tsx b/packages/react/src/Avatar/Avatar.tsx new file mode 100644 index 0000000000..c073682362 --- /dev/null +++ b/packages/react/src/Avatar/Avatar.tsx @@ -0,0 +1,20 @@ +/** + * @license EUPL-1.2+ + * Copyright Gemeente Amsterdam + */ + +import clsx from 'clsx' +import { forwardRef } from 'react' +import type { ForwardedRef, HTMLAttributes, PropsWithChildren } from 'react' + +export type AvatarProps = PropsWithChildren> + +export const Avatar = forwardRef( + ({ children, className, ...restProps }: AvatarProps, ref: ForwardedRef) => ( + + {children} + + ), +) + +Avatar.displayName = 'Avatar' diff --git a/packages/react/src/Avatar/README.md b/packages/react/src/Avatar/README.md new file mode 100644 index 0000000000..562c57ae54 --- /dev/null +++ b/packages/react/src/Avatar/README.md @@ -0,0 +1,5 @@ + + +# React Avatar component + +[Avatar documentation](../../../css/src/components/avatar/README.md) diff --git a/packages/react/src/Avatar/index.ts b/packages/react/src/Avatar/index.ts new file mode 100644 index 0000000000..11f8055882 --- /dev/null +++ b/packages/react/src/Avatar/index.ts @@ -0,0 +1,2 @@ +export { Avatar } from './Avatar' +export type { AvatarProps } from './Avatar' diff --git a/packages/react/src/index.ts b/packages/react/src/index.ts index a4c2b4c65c..bd96807b10 100644 --- a/packages/react/src/index.ts +++ b/packages/react/src/index.ts @@ -4,6 +4,7 @@ */ /* Append here */ +export * from './Avatar' export * from './Row' export * from './Radio' export * from './Tabs' diff --git a/proprietary/tokens/src/components/ams/avatar.tokens.json b/proprietary/tokens/src/components/ams/avatar.tokens.json new file mode 100644 index 0000000000..7353babf50 --- /dev/null +++ b/proprietary/tokens/src/components/ams/avatar.tokens.json @@ -0,0 +1,5 @@ +{ + "ams": { + "avatar": {} + } +} diff --git a/storybook/src/components/Avatar/Avatar.docs.mdx b/storybook/src/components/Avatar/Avatar.docs.mdx new file mode 100644 index 0000000000..2eb232661e --- /dev/null +++ b/storybook/src/components/Avatar/Avatar.docs.mdx @@ -0,0 +1,11 @@ +import { Controls, Markdown, Meta, Primary } from "@storybook/blocks"; +import * as AvatarStories from "./Avatar.stories.tsx"; +import README from "../../../../packages/css/src/components/avatar/README.md?raw"; + + + +{README} + + + + diff --git a/storybook/src/components/Avatar/Avatar.stories.tsx b/storybook/src/components/Avatar/Avatar.stories.tsx new file mode 100644 index 0000000000..6208a94a99 --- /dev/null +++ b/storybook/src/components/Avatar/Avatar.stories.tsx @@ -0,0 +1,21 @@ +/** + * @license EUPL-1.2+ + * Copyright Gemeente Amsterdam + */ + +import { Avatar } from '@amsterdam/design-system-react' +import { Meta, StoryObj } from '@storybook/react' + +const meta = { + title: 'Avatar', + component: Avatar, + args: { + children: 'Nieuw component', + }, +} satisfies Meta + +export default meta + +type Story = StoryObj + +export const Default: Story = {} From 56c5caf921363e8125f1e29b205c367b93cb3b87 Mon Sep 17 00:00:00 2001 From: Ruben Sibon Date: Thu, 14 Mar 2024 16:52:35 +0100 Subject: [PATCH 02/27] feat(component): :sparkles: new avatar component --- packages/css/src/components/avatar/README.md | 10 +++ .../css/src/components/avatar/avatar.scss | 64 ++++++++++++++++++- packages/react/src/Avatar/Avatar.test.tsx | 36 +++++++++-- packages/react/src/Avatar/Avatar.tsx | 57 ++++++++++++++--- .../src/components/ams/avatar.tokens.json | 45 ++++++++++++- .../src/components/Avatar/Avatar.docs.mdx | 4 ++ .../src/components/Avatar/Avatar.stories.tsx | 13 +++- 7 files changed, 212 insertions(+), 17 deletions(-) diff --git a/packages/css/src/components/avatar/README.md b/packages/css/src/components/avatar/README.md index e639f03b1c..5c7797a619 100644 --- a/packages/css/src/components/avatar/README.md +++ b/packages/css/src/components/avatar/README.md @@ -1,3 +1,13 @@ # Avatar + +A prominently coloured circular button containing two initials of a currently logged-in user. + +Clicking or tapping the button would typically open a context menu or view with information and actions related to the user. + +## Design + +The avatar contains two initial letters from the user's full name. +The default background colour is dark blue. +Suggestions on when to use the other colours will follow soon. diff --git a/packages/css/src/components/avatar/avatar.scss b/packages/css/src/components/avatar/avatar.scss index 96ba4a6609..2450ca716f 100644 --- a/packages/css/src/components/avatar/avatar.scss +++ b/packages/css/src/components/avatar/avatar.scss @@ -4,5 +4,67 @@ */ .ams-avatar { - /* Add styles here */ + border-radius: 50%; + border-style: var(--ams-avatar-border-style); + border-width: var(--ams-avatar-border-width); + display: inline-block; + font-family: var(--ams-avatar-font-family); + font-size: var(--ams-avatar-font-size); + line-height: var(--ams-avatar-line-height); + min-width: 1lh; + text-align: center; +} + +.ams-avatar--blue { + background-color: var(--ams-avatar-blue-background-color); + border-color: var(--ams-avatar-blue-background-color); + color: var(--ams-avatar-blue-color); +} + +.ams-avatar--dark-blue { + background-color: var(--ams-avatar-dark-blue-background-color); + border-color: var(--ams-avatar-dark-blue-background-color); + color: var(--ams-avatar-dark-blue-color); +} + +.ams-avatar--dark-green { + background-color: var(--ams-avatar-dark-green-background-color); + border-color: var(--ams-avatar-dark-green-background-color); + color: var(--ams-avatar-dark-green-color); +} + +.ams-avatar--green { + background-color: var(--ams-avatar-green-background-color); + border-color: var(--ams-avatar-green-background-color); + color: var(--ams-avatar-green-color); +} + +.ams-avatar--magenta { + background-color: var(--ams-avatar-magenta-background-color); + border-color: var(--ams-avatar-magenta-background-color); + color: var(--ams-avatar-magenta-color); +} + +.ams-avatar--orange { + background-color: var(--ams-avatar-orange-background-color); + border-color: var(--ams-avatar-orange-background-color); + color: var(--ams-avatar-orange-color); +} + +.ams-avatar--purple { + background-color: var(--ams-avatar-purple-background-color); + border-color: var(--ams-avatar-purple-background-color); + color: var(--ams-avatar-purple-color); +} + +.ams-avatar--red { + background-color: var(--ams-avatar-red-background-color); + border-color: var(--ams-avatar-red-background-color); + color: var(--ams-avatar-red-color); +} + +.ams-avatar--yellow { + background-color: var(--ams-avatar-yellow-background-color); + border-color: var(--ams-avatar-yellow-background-color); + color: var(--ams-avatar-yellow-color); } diff --git a/packages/react/src/Avatar/Avatar.test.tsx b/packages/react/src/Avatar/Avatar.test.tsx index ee1096641d..7fc28f4f1b 100644 --- a/packages/react/src/Avatar/Avatar.test.tsx +++ b/packages/react/src/Avatar/Avatar.test.tsx @@ -1,11 +1,11 @@ import { render } from '@testing-library/react' import { createRef } from 'react' -import { Avatar } from './Avatar' +import { Avatar, avatarColors } from './Avatar' import '@testing-library/jest-dom' describe('Avatar', () => { it('renders', () => { - const { container } = render() + const { container } = render() const component = container.querySelector(':only-child') @@ -14,7 +14,7 @@ describe('Avatar', () => { }) it('renders a design system BEM class name', () => { - const { container } = render() + const { container } = render() const component = container.querySelector(':only-child') @@ -22,7 +22,7 @@ describe('Avatar', () => { }) it('renders an additional class name', () => { - const { container } = render() + const { container } = render() const component = container.querySelector(':only-child') @@ -32,10 +32,36 @@ describe('Avatar', () => { it('supports ForwardRef in React', () => { const ref = createRef() - const { container } = render() + const { container } = render() const component = container.querySelector(':only-child') expect(ref.current).toBe(component) }) + + it('renders a label consisting of two letters', () => { + const { container } = render() + + const component = container.querySelector(':only-child') + + expect(component?.textContent).toHaveLength(2) + }) + + it('renders with default color', () => { + const { container } = render() + + const component = container.querySelector(':only-child') + + expect(component).toHaveClass('ams-avatar--dark-blue') + }) + + avatarColors.map((color) => + it(`renders with ${color} color`, () => { + const { container } = render() + + const component = container.querySelector(':only-child') + + expect(component).toHaveClass(`ams-avatar--${color}`) + }), + ) }) diff --git a/packages/react/src/Avatar/Avatar.tsx b/packages/react/src/Avatar/Avatar.tsx index c073682362..6a168f04fd 100644 --- a/packages/react/src/Avatar/Avatar.tsx +++ b/packages/react/src/Avatar/Avatar.tsx @@ -4,17 +4,58 @@ */ import clsx from 'clsx' -import { forwardRef } from 'react' -import type { ForwardedRef, HTMLAttributes, PropsWithChildren } from 'react' +import { forwardRef, useMemo } from 'react' +import type { ForwardedRef, HTMLAttributes } from 'react' -export type AvatarProps = PropsWithChildren> +export const avatarColors = [ + 'blue', + 'dark-blue', + 'dark-green', + 'green', + 'magenta', + 'orange', + 'purple', + 'red', + 'yellow', +] as const + +type AvatarColor = (typeof avatarColors)[number] + +export type AvatarProps = { + color?: AvatarColor + label: string +} & HTMLAttributes export const Avatar = forwardRef( - ({ children, className, ...restProps }: AvatarProps, ref: ForwardedRef) => ( - - {children} - - ), + ({ label, className, color = 'dark-blue', ...restProps }: AvatarProps, ref: ForwardedRef) => { + if (label.length !== 2) { + // TODO: should we log this somewhere or throw an error to the consumer? + console.warn(`Avatar label should be no more and no less than two characters. Got: "${label}".`) + } + + const initials = useMemo(() => { + if (label.length === 0) { + return 'n.b.' + } else if (label.length > 2) { + return label.slice(0, 2).toUpperCase() + } else { + return label.toUpperCase() + } + }, [label]) + + const initialsDotted = useMemo(() => `${initials.split('').join('.')}.`, [initials]) + + return ( + + {initials} + + ) + }, ) Avatar.displayName = 'Avatar' diff --git a/proprietary/tokens/src/components/ams/avatar.tokens.json b/proprietary/tokens/src/components/ams/avatar.tokens.json index 7353babf50..4409cda7a2 100644 --- a/proprietary/tokens/src/components/ams/avatar.tokens.json +++ b/proprietary/tokens/src/components/ams/avatar.tokens.json @@ -1,5 +1,48 @@ { "ams": { - "avatar": {} + "avatar": { + "border-width": { "value": "{ams.border-width.md}" }, + "border-style": { "value": "solid" }, + "font-family": { "value": "{ams.text.font-family}" }, + "font-size": { "value": "{ams.text.level.6.font-size}" }, + "font-weight": { "value": "{ams.text.font-weight.normal}" }, + "line-height": { "value": "{ams.text.level.6.line-height}" }, + "blue": { + "background-color": { "value": "{ams.color.blue}" }, + "color": { "value": "{ams.color.primary-black}" } + }, + "dark-blue": { + "background-color": { "value": "{ams.color.primary-blue}" }, + "color": { "value": "{ams.color.primary-white}" } + }, + "dark-green": { + "background-color": { "value": "{ams.color.dark-green}" }, + "color": { "value": "{ams.color.primary-white}" } + }, + "green": { + "background-color": { "value": "{ams.color.green}" }, + "color": { "value": "{ams.color.primary-black}" } + }, + "magenta": { + "background-color": { "value": "{ams.color.magenta}" }, + "color": { "value": "{ams.color.primary-white}" } + }, + "orange": { + "background-color": { "value": "{ams.color.orange}" }, + "color": { "value": "{ams.color.primary-black}" } + }, + "purple": { + "background-color": { "value": "{ams.color.purple}" }, + "color": { "value": "{ams.color.primary-white}" } + }, + "red": { + "background-color": { "value": "{ams.color.primary-red}" }, + "color": { "value": "{ams.color.primary-white}" } + }, + "yellow": { + "background-color": { "value": "{ams.color.yellow}" }, + "color": { "value": "{ams.color.primary-black}" } + } + } } } diff --git a/storybook/src/components/Avatar/Avatar.docs.mdx b/storybook/src/components/Avatar/Avatar.docs.mdx index 2eb232661e..49247b76d0 100644 --- a/storybook/src/components/Avatar/Avatar.docs.mdx +++ b/storybook/src/components/Avatar/Avatar.docs.mdx @@ -6,6 +6,10 @@ import README from "../../../../packages/css/src/components/avatar/README.md?raw {README} +## Stories + +### Default + diff --git a/storybook/src/components/Avatar/Avatar.stories.tsx b/storybook/src/components/Avatar/Avatar.stories.tsx index 6208a94a99..a1987629e6 100644 --- a/storybook/src/components/Avatar/Avatar.stories.tsx +++ b/storybook/src/components/Avatar/Avatar.stories.tsx @@ -7,10 +7,19 @@ import { Avatar } from '@amsterdam/design-system-react' import { Meta, StoryObj } from '@storybook/react' const meta = { - title: 'Avatar', + title: 'Components/Feedback/Avatar', component: Avatar, args: { - children: 'Nieuw component', + label: 'DS', + }, + argTypes: { + color: { + control: { + type: 'select', + }, + options: ['blue', 'dark-blue', 'dark-green', 'green', 'magenta', 'orange', 'purple', 'red', 'yellow'], + selected: 'dark-blue', + }, }, } satisfies Meta From 44d71016fa2430a82a70ce42ac992635fe924808 Mon Sep 17 00:00:00 2001 From: Ruben Sibon Date: Fri, 15 Mar 2024 14:43:11 +0100 Subject: [PATCH 03/27] feat(component): :sparkles: picture support & default user icon --- packages/css/src/components/avatar/README.md | 10 +++-- .../css/src/components/avatar/avatar.scss | 32 ++++++++------- packages/react/src/Avatar/Avatar.test.tsx | 33 ++++++++++++++-- packages/react/src/Avatar/Avatar.tsx | 39 ++++++++++--------- .../src/components/ams/avatar.tokens.json | 30 +++++++++++++- .../src/components/Avatar/Avatar.docs.mdx | 12 +++++- .../src/components/Avatar/Avatar.stories.tsx | 14 +++++++ 7 files changed, 128 insertions(+), 42 deletions(-) diff --git a/packages/css/src/components/avatar/README.md b/packages/css/src/components/avatar/README.md index 5c7797a619..300015a350 100644 --- a/packages/css/src/components/avatar/README.md +++ b/packages/css/src/components/avatar/README.md @@ -2,12 +2,14 @@ # Avatar -A prominently coloured circular button containing two initials of a currently logged-in user. +A circular badge containing either up to two initials or a profile picture of the currently logged-in user. -Clicking or tapping the button would typically open a context menu or view with information and actions related to the user. +The badge contains the personal login icon by default when no user is logged-in or no label is provided. + +Interaction can be added by wrapping the avatar in a link. ## Design -The avatar contains two initial letters from the user's full name. +The avatar contains either one or two initial letters from the user's full name, a picture or a generic user icon. + The default background colour is dark blue. -Suggestions on when to use the other colours will follow soon. diff --git a/packages/css/src/components/avatar/avatar.scss b/packages/css/src/components/avatar/avatar.scss index 2450ca716f..935c9de2d2 100644 --- a/packages/css/src/components/avatar/avatar.scss +++ b/packages/css/src/components/avatar/avatar.scss @@ -4,67 +4,71 @@ */ .ams-avatar { + aspect-ratio: 1/1; + background-position: center; + background-repeat: no-repeat; + background-size: cover; border-radius: 50%; - border-style: var(--ams-avatar-border-style); - border-width: var(--ams-avatar-border-width); - display: inline-block; + display: inline-flex; font-family: var(--ams-avatar-font-family); font-size: var(--ams-avatar-font-size); + justify-content: center; line-height: var(--ams-avatar-line-height); - min-width: 1lh; - text-align: center; + padding-block: var(--ams-avatar-padding); + padding-inline: var(--ams-avatar-padding); + width: calc(var(--ams-avatar-line-height) * var(--ams-avatar-font-size)); } .ams-avatar--blue { background-color: var(--ams-avatar-blue-background-color); - border-color: var(--ams-avatar-blue-background-color); + background-image: var(--ams-avatar-blue-background-image); color: var(--ams-avatar-blue-color); } .ams-avatar--dark-blue { background-color: var(--ams-avatar-dark-blue-background-color); - border-color: var(--ams-avatar-dark-blue-background-color); + background-image: var(--ams-avatar-dark-blue-background-image); color: var(--ams-avatar-dark-blue-color); } .ams-avatar--dark-green { background-color: var(--ams-avatar-dark-green-background-color); - border-color: var(--ams-avatar-dark-green-background-color); + background-image: var(--ams-avatar-dark-green-background-image); color: var(--ams-avatar-dark-green-color); } .ams-avatar--green { background-color: var(--ams-avatar-green-background-color); - border-color: var(--ams-avatar-green-background-color); + background-image: var(--ams-avatar-green-background-image); color: var(--ams-avatar-green-color); } .ams-avatar--magenta { background-color: var(--ams-avatar-magenta-background-color); - border-color: var(--ams-avatar-magenta-background-color); + background-image: var(--ams-avatar-magenta-background-image); color: var(--ams-avatar-magenta-color); } .ams-avatar--orange { background-color: var(--ams-avatar-orange-background-color); - border-color: var(--ams-avatar-orange-background-color); + background-image: var(--ams-avatar-orange-background-image); color: var(--ams-avatar-orange-color); } .ams-avatar--purple { background-color: var(--ams-avatar-purple-background-color); - border-color: var(--ams-avatar-purple-background-color); + background-image: var(--ams-avatar-purple-background-image); color: var(--ams-avatar-purple-color); } .ams-avatar--red { background-color: var(--ams-avatar-red-background-color); - border-color: var(--ams-avatar-red-background-color); + background-image: var(--ams-avatar-red-background-image); color: var(--ams-avatar-red-color); } .ams-avatar--yellow { background-color: var(--ams-avatar-yellow-background-color); - border-color: var(--ams-avatar-yellow-background-color); + background-image: var(--ams-avatar-yellow-background-image); color: var(--ams-avatar-yellow-color); } diff --git a/packages/react/src/Avatar/Avatar.test.tsx b/packages/react/src/Avatar/Avatar.test.tsx index 7fc28f4f1b..b1c571ff82 100644 --- a/packages/react/src/Avatar/Avatar.test.tsx +++ b/packages/react/src/Avatar/Avatar.test.tsx @@ -39,12 +39,22 @@ describe('Avatar', () => { expect(ref.current).toBe(component) }) - it('renders a label consisting of two letters', () => { - const { container } = render() + it('renders with a label consisting of no more than two, uppercase letters', () => { + const { container } = render() + + const component = container.querySelector(':only-child') + + expect(component).toHaveTextContent('DE') + expect(component).toHaveAttribute('style', 'background-image: none;') + }) + + it('renders with default content and title', () => { + const { container } = render() const component = container.querySelector(':only-child') - expect(component?.textContent).toHaveLength(2) + expect(component).toHaveTextContent('‏‏‎ ‎') + expect(component).toHaveAttribute('title', 'Niet-ingelogde gebruiker') }) it('renders with default color', () => { @@ -64,4 +74,21 @@ describe('Avatar', () => { expect(component).toHaveClass(`ams-avatar--${color}`) }), ) + + it('renders with a profile picture', () => { + const { container } = render( + , + ) + + const component = container.querySelector(':only-child') + + expect(component).toHaveTextContent('‏‏‎ ‎') + expect(component).toHaveAttribute( + 'style', + 'background-image: url(https://web.archive.org/web/20230610011324im_/https://avatars.githubusercontent.com/u/7290629?v=4);', + ) + }) }) diff --git a/packages/react/src/Avatar/Avatar.tsx b/packages/react/src/Avatar/Avatar.tsx index 6a168f04fd..5ba69bfc1a 100644 --- a/packages/react/src/Avatar/Avatar.tsx +++ b/packages/react/src/Avatar/Avatar.tsx @@ -5,7 +5,7 @@ import clsx from 'clsx' import { forwardRef, useMemo } from 'react' -import type { ForwardedRef, HTMLAttributes } from 'react' +import type { ForwardedRef, HTMLAttributes, ReactElement } from 'react' export const avatarColors = [ 'blue', @@ -23,36 +23,39 @@ type AvatarColor = (typeof avatarColors)[number] export type AvatarProps = { color?: AvatarColor + imageUrl?: string label: string } & HTMLAttributes export const Avatar = forwardRef( - ({ label, className, color = 'dark-blue', ...restProps }: AvatarProps, ref: ForwardedRef) => { - if (label.length !== 2) { - // TODO: should we log this somewhere or throw an error to the consumer? - console.warn(`Avatar label should be no more and no less than two characters. Got: "${label}".`) - } - - const initials = useMemo(() => { - if (label.length === 0) { - return 'n.b.' - } else if (label.length > 2) { - return label.slice(0, 2).toUpperCase() - } else { - return label.toUpperCase() - } + ({ label, imageUrl, className, color = 'dark-blue', ...restProps }: AvatarProps, ref: ForwardedRef) => { + const initials: string | ReactElement = useMemo(() => { + return (label.length > 2 ? label.slice(0, 2) : label).toUpperCase() }, [label]) - const initialsDotted = useMemo(() => `${initials.split('').join('.')}.`, [initials]) + const title = useMemo(() => { + return !initials.length ? 'Niet-ingelogde gebruiker' : `Initialen gebruiker: ${initials.split('').join('.')}.` + }, [initials]) + + const backgroundImageValue: string | undefined = useMemo(() => { + if (imageUrl) { + return `url(${imageUrl})` + } else if (label.length) { + return 'none' + } else { + return undefined + } + }, [imageUrl, label]) return ( - {initials} + {backgroundImageValue !== 'none' ? '‏‏‎ ‎' : initials} ) }, diff --git a/proprietary/tokens/src/components/ams/avatar.tokens.json b/proprietary/tokens/src/components/ams/avatar.tokens.json index 4409cda7a2..a56a18308b 100644 --- a/proprietary/tokens/src/components/ams/avatar.tokens.json +++ b/proprietary/tokens/src/components/ams/avatar.tokens.json @@ -1,46 +1,72 @@ { "ams": { "avatar": { - "border-width": { "value": "{ams.border-width.md}" }, - "border-style": { "value": "solid" }, "font-family": { "value": "{ams.text.font-family}" }, "font-size": { "value": "{ams.text.level.6.font-size}" }, "font-weight": { "value": "{ams.text.font-weight.normal}" }, "line-height": { "value": "{ams.text.level.6.line-height}" }, + "padding": { "value": "5px" }, "blue": { "background-color": { "value": "{ams.color.blue}" }, + "background-image": { + "value": "url(\"data:image/svg+xml;utf8,\")" + }, "color": { "value": "{ams.color.primary-black}" } }, "dark-blue": { "background-color": { "value": "{ams.color.primary-blue}" }, + "background-image": { + "value": "url(\"data:image/svg+xml;utf8,\")" + }, "color": { "value": "{ams.color.primary-white}" } }, "dark-green": { "background-color": { "value": "{ams.color.dark-green}" }, + "background-image": { + "value": "url(\"data:image/svg+xml;utf8,\")" + }, "color": { "value": "{ams.color.primary-white}" } }, "green": { "background-color": { "value": "{ams.color.green}" }, + "background-image": { + "value": "url(\"data:image/svg+xml;utf8,\")" + }, "color": { "value": "{ams.color.primary-black}" } }, "magenta": { "background-color": { "value": "{ams.color.magenta}" }, + "background-image": { + "value": "url(\"data:image/svg+xml;utf8,\")" + }, "color": { "value": "{ams.color.primary-white}" } }, "orange": { "background-color": { "value": "{ams.color.orange}" }, + "background-image": { + "value": "url(\"data:image/svg+xml;utf8,\")" + }, "color": { "value": "{ams.color.primary-black}" } }, "purple": { "background-color": { "value": "{ams.color.purple}" }, + "background-image": { + "value": "url(\"data:image/svg+xml;utf8,\")" + }, "color": { "value": "{ams.color.primary-white}" } }, "red": { "background-color": { "value": "{ams.color.primary-red}" }, + "background-image": { + "value": "url(\"data:image/svg+xml;utf8,\")" + }, "color": { "value": "{ams.color.primary-white}" } }, "yellow": { "background-color": { "value": "{ams.color.yellow}" }, + "background-image": { + "value": "url(\"data:image/svg+xml;utf8,\")" + }, "color": { "value": "{ams.color.primary-black}" } } } diff --git a/storybook/src/components/Avatar/Avatar.docs.mdx b/storybook/src/components/Avatar/Avatar.docs.mdx index 49247b76d0..be47b509cd 100644 --- a/storybook/src/components/Avatar/Avatar.docs.mdx +++ b/storybook/src/components/Avatar/Avatar.docs.mdx @@ -1,4 +1,4 @@ -import { Controls, Markdown, Meta, Primary } from "@storybook/blocks"; +import { Canvas, Controls, Markdown, Meta, Primary } from "@storybook/blocks"; import * as AvatarStories from "./Avatar.stories.tsx"; import README from "../../../../packages/css/src/components/avatar/README.md?raw"; @@ -13,3 +13,13 @@ import README from "../../../../packages/css/src/components/avatar/README.md?raw + +### Without Label + +Avatar without a label, i.e. no user is logged-in. + + + +### With Picture + + diff --git a/storybook/src/components/Avatar/Avatar.stories.tsx b/storybook/src/components/Avatar/Avatar.stories.tsx index a1987629e6..111c9e9755 100644 --- a/storybook/src/components/Avatar/Avatar.stories.tsx +++ b/storybook/src/components/Avatar/Avatar.stories.tsx @@ -11,6 +11,7 @@ const meta = { component: Avatar, args: { label: 'DS', + imageUrl: '', }, argTypes: { color: { @@ -28,3 +29,16 @@ export default meta type Story = StoryObj export const Default: Story = {} + +export const WithoutLabel: Story = { + args: { + label: '', + }, +} + +export const WithImageURL: Story = { + args: { + label: 'RS', + imageUrl: 'https://web.archive.org/web/20230610011324im_/https://avatars.githubusercontent.com/u/7290629?v=4', + }, +} From daf5ebdc4b5fc6479da7bbae6f7c6bdfc6a90fe7 Mon Sep 17 00:00:00 2001 From: Ruben Sibon Date: Fri, 15 Mar 2024 17:33:32 +0100 Subject: [PATCH 04/27] fix(component): :ambulance: replace bg images & address feedback Remove background images in favour of image and icon components; improve a11y; some simplifying and cleaning up of code. --- .../css/src/components/avatar/avatar.scss | 16 ++------ packages/react/src/Avatar/Avatar.test.tsx | 35 +++++++--------- packages/react/src/Avatar/Avatar.tsx | 41 +++++++++---------- .../src/components/ams/avatar.tokens.json | 27 ------------ .../src/components/Avatar/Avatar.docs.mdx | 2 +- .../src/components/Avatar/Avatar.stories.tsx | 8 ++-- 6 files changed, 42 insertions(+), 87 deletions(-) diff --git a/packages/css/src/components/avatar/avatar.scss b/packages/css/src/components/avatar/avatar.scss index 935c9de2d2..8c01d55fa1 100644 --- a/packages/css/src/components/avatar/avatar.scss +++ b/packages/css/src/components/avatar/avatar.scss @@ -5,9 +5,6 @@ .ams-avatar { aspect-ratio: 1/1; - background-position: center; - background-repeat: no-repeat; - background-size: cover; border-radius: 50%; display: inline-flex; font-family: var(--ams-avatar-font-family); @@ -17,58 +14,53 @@ padding-block: var(--ams-avatar-padding); padding-inline: var(--ams-avatar-padding); width: calc(var(--ams-avatar-line-height) * var(--ams-avatar-font-size)); + + svg { + fill: currentColor; + } } .ams-avatar--blue { background-color: var(--ams-avatar-blue-background-color); - background-image: var(--ams-avatar-blue-background-image); color: var(--ams-avatar-blue-color); } .ams-avatar--dark-blue { background-color: var(--ams-avatar-dark-blue-background-color); - background-image: var(--ams-avatar-dark-blue-background-image); color: var(--ams-avatar-dark-blue-color); } .ams-avatar--dark-green { background-color: var(--ams-avatar-dark-green-background-color); - background-image: var(--ams-avatar-dark-green-background-image); color: var(--ams-avatar-dark-green-color); } .ams-avatar--green { background-color: var(--ams-avatar-green-background-color); - background-image: var(--ams-avatar-green-background-image); color: var(--ams-avatar-green-color); } .ams-avatar--magenta { background-color: var(--ams-avatar-magenta-background-color); - background-image: var(--ams-avatar-magenta-background-image); color: var(--ams-avatar-magenta-color); } .ams-avatar--orange { background-color: var(--ams-avatar-orange-background-color); - background-image: var(--ams-avatar-orange-background-image); color: var(--ams-avatar-orange-color); } .ams-avatar--purple { background-color: var(--ams-avatar-purple-background-color); - background-image: var(--ams-avatar-purple-background-image); color: var(--ams-avatar-purple-color); } .ams-avatar--red { background-color: var(--ams-avatar-red-background-color); - background-image: var(--ams-avatar-red-background-image); color: var(--ams-avatar-red-color); } .ams-avatar--yellow { background-color: var(--ams-avatar-yellow-background-color); - background-image: var(--ams-avatar-yellow-background-image); color: var(--ams-avatar-yellow-color); } diff --git a/packages/react/src/Avatar/Avatar.test.tsx b/packages/react/src/Avatar/Avatar.test.tsx index b1c571ff82..4fa7f8b124 100644 --- a/packages/react/src/Avatar/Avatar.test.tsx +++ b/packages/react/src/Avatar/Avatar.test.tsx @@ -45,16 +45,26 @@ describe('Avatar', () => { const component = container.querySelector(':only-child') expect(component).toHaveTextContent('DE') - expect(component).toHaveAttribute('style', 'background-image: none;') }) it('renders with default content and title', () => { const { container } = render() - const component = container.querySelector(':only-child') + const a11yLabel = container.querySelector('.ams-visually-hidden') + const svg = container.querySelector('svg') + + expect(a11yLabel).toHaveTextContent('Gebruiker') + expect(svg).toBeVisible() + }) + + it('renders with a profile picture', () => { + const { container } = render() + + const a11yLabel = container.querySelector('.ams-visually-hidden') + const image = container.querySelector('[src="https://i.pravatar.cc/200"]') - expect(component).toHaveTextContent('‏‏‎ ‎') - expect(component).toHaveAttribute('title', 'Niet-ingelogde gebruiker') + expect(a11yLabel).toHaveTextContent('Initialen gebruiker: RS.') + expect(image).toBeVisible() }) it('renders with default color', () => { @@ -74,21 +84,4 @@ describe('Avatar', () => { expect(component).toHaveClass(`ams-avatar--${color}`) }), ) - - it('renders with a profile picture', () => { - const { container } = render( - , - ) - - const component = container.querySelector(':only-child') - - expect(component).toHaveTextContent('‏‏‎ ‎') - expect(component).toHaveAttribute( - 'style', - 'background-image: url(https://web.archive.org/web/20230610011324im_/https://avatars.githubusercontent.com/u/7290629?v=4);', - ) - }) }) diff --git a/packages/react/src/Avatar/Avatar.tsx b/packages/react/src/Avatar/Avatar.tsx index 5ba69bfc1a..9b8dfb1a6a 100644 --- a/packages/react/src/Avatar/Avatar.tsx +++ b/packages/react/src/Avatar/Avatar.tsx @@ -3,9 +3,12 @@ * Copyright Gemeente Amsterdam */ +import { PersonalLoginIcon } from '@amsterdam/design-system-react-icons' import clsx from 'clsx' import { forwardRef, useMemo } from 'react' import type { ForwardedRef, HTMLAttributes, ReactElement } from 'react' +import { Image } from '../Image' +import { VisuallyHidden } from '../VisuallyHidden' export const avatarColors = [ 'blue', @@ -23,39 +26,33 @@ type AvatarColor = (typeof avatarColors)[number] export type AvatarProps = { color?: AvatarColor - imageUrl?: string + imageSrc?: string label: string } & HTMLAttributes export const Avatar = forwardRef( - ({ label, imageUrl, className, color = 'dark-blue', ...restProps }: AvatarProps, ref: ForwardedRef) => { - const initials: string | ReactElement = useMemo(() => { - return (label.length > 2 ? label.slice(0, 2) : label).toUpperCase() - }, [label]) + ({ label, imageSrc, className, color = 'dark-blue', ...restProps }: AvatarProps, ref: ForwardedRef) => { + const initials: string = (label.length > 2 ? label.slice(0, 2) : label).toUpperCase() - const title = useMemo(() => { - return !initials.length ? 'Niet-ingelogde gebruiker' : `Initialen gebruiker: ${initials.split('').join('.')}.` - }, [initials]) + const a11yLabel = useMemo( + () => (initials.length === 0 ? 'Gebruiker' : `Initialen gebruiker: ${initials}.`), + [initials], + ) - const backgroundImageValue: string | undefined = useMemo(() => { - if (imageUrl) { - return `url(${imageUrl})` + const content: ReactElement | string = useMemo(() => { + if (imageSrc) { + return } else if (label.length) { - return 'none' + return initials } else { - return undefined + return } - }, [imageUrl, label]) + }, [imageSrc, label, initials]) return ( - - {backgroundImageValue !== 'none' ? '‏‏‎ ‎' : initials} + + {content} + {a11yLabel} ) }, diff --git a/proprietary/tokens/src/components/ams/avatar.tokens.json b/proprietary/tokens/src/components/ams/avatar.tokens.json index a56a18308b..e07d913cf6 100644 --- a/proprietary/tokens/src/components/ams/avatar.tokens.json +++ b/proprietary/tokens/src/components/ams/avatar.tokens.json @@ -8,65 +8,38 @@ "padding": { "value": "5px" }, "blue": { "background-color": { "value": "{ams.color.blue}" }, - "background-image": { - "value": "url(\"data:image/svg+xml;utf8,\")" - }, "color": { "value": "{ams.color.primary-black}" } }, "dark-blue": { "background-color": { "value": "{ams.color.primary-blue}" }, - "background-image": { - "value": "url(\"data:image/svg+xml;utf8,\")" - }, "color": { "value": "{ams.color.primary-white}" } }, "dark-green": { "background-color": { "value": "{ams.color.dark-green}" }, - "background-image": { - "value": "url(\"data:image/svg+xml;utf8,\")" - }, "color": { "value": "{ams.color.primary-white}" } }, "green": { "background-color": { "value": "{ams.color.green}" }, - "background-image": { - "value": "url(\"data:image/svg+xml;utf8,\")" - }, "color": { "value": "{ams.color.primary-black}" } }, "magenta": { "background-color": { "value": "{ams.color.magenta}" }, - "background-image": { - "value": "url(\"data:image/svg+xml;utf8,\")" - }, "color": { "value": "{ams.color.primary-white}" } }, "orange": { "background-color": { "value": "{ams.color.orange}" }, - "background-image": { - "value": "url(\"data:image/svg+xml;utf8,\")" - }, "color": { "value": "{ams.color.primary-black}" } }, "purple": { "background-color": { "value": "{ams.color.purple}" }, - "background-image": { - "value": "url(\"data:image/svg+xml;utf8,\")" - }, "color": { "value": "{ams.color.primary-white}" } }, "red": { "background-color": { "value": "{ams.color.primary-red}" }, - "background-image": { - "value": "url(\"data:image/svg+xml;utf8,\")" - }, "color": { "value": "{ams.color.primary-white}" } }, "yellow": { "background-color": { "value": "{ams.color.yellow}" }, - "background-image": { - "value": "url(\"data:image/svg+xml;utf8,\")" - }, "color": { "value": "{ams.color.primary-black}" } } } diff --git a/storybook/src/components/Avatar/Avatar.docs.mdx b/storybook/src/components/Avatar/Avatar.docs.mdx index be47b509cd..ccb7c66a2d 100644 --- a/storybook/src/components/Avatar/Avatar.docs.mdx +++ b/storybook/src/components/Avatar/Avatar.docs.mdx @@ -22,4 +22,4 @@ Avatar without a label, i.e. no user is logged-in. ### With Picture - + diff --git a/storybook/src/components/Avatar/Avatar.stories.tsx b/storybook/src/components/Avatar/Avatar.stories.tsx index 111c9e9755..401b4c9a26 100644 --- a/storybook/src/components/Avatar/Avatar.stories.tsx +++ b/storybook/src/components/Avatar/Avatar.stories.tsx @@ -11,7 +11,7 @@ const meta = { component: Avatar, args: { label: 'DS', - imageUrl: '', + imageSrc: '', }, argTypes: { color: { @@ -36,9 +36,9 @@ export const WithoutLabel: Story = { }, } -export const WithImageURL: Story = { +export const WithImageSrc: Story = { args: { - label: 'RS', - imageUrl: 'https://web.archive.org/web/20230610011324im_/https://avatars.githubusercontent.com/u/7290629?v=4', + label: 'PS', + imageSrc: 'https://i.pravatar.cc/200', }, } From c6d57b26ecf2e22ac6f2b3feae13b85380a29a5e Mon Sep 17 00:00:00 2001 From: Ruben Sibon Date: Fri, 15 Mar 2024 18:01:58 +0100 Subject: [PATCH 05/27] feat(story): :art: add example story with header --- .../src/components/Avatar/Avatar.docs.mdx | 4 +++ .../src/components/Avatar/Avatar.stories.tsx | 30 ++++++++++++++++++- 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/storybook/src/components/Avatar/Avatar.docs.mdx b/storybook/src/components/Avatar/Avatar.docs.mdx index ccb7c66a2d..e0b67d172d 100644 --- a/storybook/src/components/Avatar/Avatar.docs.mdx +++ b/storybook/src/components/Avatar/Avatar.docs.mdx @@ -23,3 +23,7 @@ Avatar without a label, i.e. no user is logged-in. ### With Picture + +### In a Header + + diff --git a/storybook/src/components/Avatar/Avatar.stories.tsx b/storybook/src/components/Avatar/Avatar.stories.tsx index 401b4c9a26..62f09e9e08 100644 --- a/storybook/src/components/Avatar/Avatar.stories.tsx +++ b/storybook/src/components/Avatar/Avatar.stories.tsx @@ -3,7 +3,8 @@ * Copyright Gemeente Amsterdam */ -import { Avatar } from '@amsterdam/design-system-react' +import { Avatar, Header, PageMenu } from '@amsterdam/design-system-react' +import { SearchIcon } from '@amsterdam/design-system-react-icons' import { Meta, StoryObj } from '@storybook/react' const meta = { @@ -42,3 +43,30 @@ export const WithImageSrc: Story = { imageSrc: 'https://i.pravatar.cc/200', }, } + +export const InAHeader: Story = { + decorators: [ + (Story) => ( +
+ +
+ ), + ], + render: () => { + return ( +
+ Contact + + Zoeken + + + + } + >
+ ) + }, +} From 5e1ebbc9580bfbc3213b2b3264982f6ee99f4f2a Mon Sep 17 00:00:00 2001 From: Ruben Sibon Date: Fri, 15 Mar 2024 18:08:41 +0100 Subject: [PATCH 06/27] refactor(ts): :label: remove already implied types --- packages/react/src/Avatar/Avatar.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/react/src/Avatar/Avatar.tsx b/packages/react/src/Avatar/Avatar.tsx index 9b8dfb1a6a..60a4538b80 100644 --- a/packages/react/src/Avatar/Avatar.tsx +++ b/packages/react/src/Avatar/Avatar.tsx @@ -6,7 +6,7 @@ import { PersonalLoginIcon } from '@amsterdam/design-system-react-icons' import clsx from 'clsx' import { forwardRef, useMemo } from 'react' -import type { ForwardedRef, HTMLAttributes, ReactElement } from 'react' +import type { ForwardedRef, HTMLAttributes } from 'react' import { Image } from '../Image' import { VisuallyHidden } from '../VisuallyHidden' @@ -32,14 +32,14 @@ export type AvatarProps = { export const Avatar = forwardRef( ({ label, imageSrc, className, color = 'dark-blue', ...restProps }: AvatarProps, ref: ForwardedRef) => { - const initials: string = (label.length > 2 ? label.slice(0, 2) : label).toUpperCase() + const initials = (label.length > 2 ? label.slice(0, 2) : label).toUpperCase() const a11yLabel = useMemo( () => (initials.length === 0 ? 'Gebruiker' : `Initialen gebruiker: ${initials}.`), [initials], ) - const content: ReactElement | string = useMemo(() => { + const content = useMemo(() => { if (imageSrc) { return } else if (label.length) { From 5ea676fa17aec643563b6b3f66aa36439e8b92f2 Mon Sep 17 00:00:00 2001 From: Ruben Sibon Date: Fri, 15 Mar 2024 18:29:58 +0100 Subject: [PATCH 07/27] fix(component): :lipstick: image position and clipping --- packages/css/src/components/avatar/avatar.scss | 14 ++++++++++++++ packages/react/src/Avatar/Avatar.tsx | 11 ++++++----- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/packages/css/src/components/avatar/avatar.scss b/packages/css/src/components/avatar/avatar.scss index 8c01d55fa1..758007630e 100644 --- a/packages/css/src/components/avatar/avatar.scss +++ b/packages/css/src/components/avatar/avatar.scss @@ -20,6 +20,20 @@ } } +.ams-avatar--image { + overflow: hidden; + position: relative; + + img { + height: calc(100% + var(--ams-avatar-padding) * 2); + left: calc(0px - var(--ams-avatar-padding)); + max-width: none; + position: absolute; + top: calc(0px - var(--ams-avatar-padding)); + width: calc(100% + var(--ams-avatar-padding) * 2); + } +} + .ams-avatar--blue { background-color: var(--ams-avatar-blue-background-color); color: var(--ams-avatar-blue-color); diff --git a/packages/react/src/Avatar/Avatar.tsx b/packages/react/src/Avatar/Avatar.tsx index 60a4538b80..6638546574 100644 --- a/packages/react/src/Avatar/Avatar.tsx +++ b/packages/react/src/Avatar/Avatar.tsx @@ -34,10 +34,7 @@ export const Avatar = forwardRef( ({ label, imageSrc, className, color = 'dark-blue', ...restProps }: AvatarProps, ref: ForwardedRef) => { const initials = (label.length > 2 ? label.slice(0, 2) : label).toUpperCase() - const a11yLabel = useMemo( - () => (initials.length === 0 ? 'Gebruiker' : `Initialen gebruiker: ${initials}.`), - [initials], - ) + const a11yLabel = initials.length === 0 ? 'Gebruiker' : `Initialen gebruiker: ${initials}.` const content = useMemo(() => { if (imageSrc) { @@ -50,7 +47,11 @@ export const Avatar = forwardRef( }, [imageSrc, label, initials]) return ( - + {content} {a11yLabel} From 49adfdedb156dd05ca85ba8d4bc8d680b4887a6f Mon Sep 17 00:00:00 2001 From: Vincent Smedinga Date: Mon, 18 Mar 2024 16:12:20 +0100 Subject: [PATCH 08/27] Use type for span element --- packages/react/src/Avatar/Avatar.test.tsx | 2 +- packages/react/src/Avatar/Avatar.tsx | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/react/src/Avatar/Avatar.test.tsx b/packages/react/src/Avatar/Avatar.test.tsx index 4fa7f8b124..bec646909b 100644 --- a/packages/react/src/Avatar/Avatar.test.tsx +++ b/packages/react/src/Avatar/Avatar.test.tsx @@ -30,7 +30,7 @@ describe('Avatar', () => { }) it('supports ForwardRef in React', () => { - const ref = createRef() + const ref = createRef() const { container } = render() diff --git a/packages/react/src/Avatar/Avatar.tsx b/packages/react/src/Avatar/Avatar.tsx index 6638546574..15afef7106 100644 --- a/packages/react/src/Avatar/Avatar.tsx +++ b/packages/react/src/Avatar/Avatar.tsx @@ -28,10 +28,13 @@ export type AvatarProps = { color?: AvatarColor imageSrc?: string label: string -} & HTMLAttributes +} & HTMLAttributes export const Avatar = forwardRef( - ({ label, imageSrc, className, color = 'dark-blue', ...restProps }: AvatarProps, ref: ForwardedRef) => { + ( + { label, imageSrc, className, color = 'dark-blue', ...restProps }: AvatarProps, + ref: ForwardedRef, + ) => { const initials = (label.length > 2 ? label.slice(0, 2) : label).toUpperCase() const a11yLabel = initials.length === 0 ? 'Gebruiker' : `Initialen gebruiker: ${initials}.` From a0b55d6aab60dce9bdbc5533a414eaac7fb0a6ed Mon Sep 17 00:00:00 2001 From: Vincent Smedinga Date: Mon, 18 Mar 2024 17:17:19 +0100 Subject: [PATCH 09/27] Tidy up Header story --- .../src/components/Avatar/Avatar.stories.tsx | 39 ++++++++----------- 1 file changed, 16 insertions(+), 23 deletions(-) diff --git a/storybook/src/components/Avatar/Avatar.stories.tsx b/storybook/src/components/Avatar/Avatar.stories.tsx index 62f09e9e08..a01d722ab2 100644 --- a/storybook/src/components/Avatar/Avatar.stories.tsx +++ b/storybook/src/components/Avatar/Avatar.stories.tsx @@ -45,28 +45,21 @@ export const WithImageSrc: Story = { } export const InAHeader: Story = { - decorators: [ - (Story) => ( -
- -
- ), - ], - render: () => { - return ( -
- Contact - - Zoeken - - - - } - >
- ) + args: { + label: 'DS', }, + render: (args) => ( +
+ Contact + + Zoeken + + + + } + title="Dashboard" + /> + ), } From f6b7f281380ffc933392642c008bfa765784420f Mon Sep 17 00:00:00 2001 From: Vincent Smedinga Date: Mon, 18 Mar 2024 17:17:35 +0100 Subject: [PATCH 10/27] Use design token for aspect ratio --- packages/css/src/components/avatar/avatar.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/css/src/components/avatar/avatar.scss b/packages/css/src/components/avatar/avatar.scss index 758007630e..2ac3d70d97 100644 --- a/packages/css/src/components/avatar/avatar.scss +++ b/packages/css/src/components/avatar/avatar.scss @@ -4,7 +4,7 @@ */ .ams-avatar { - aspect-ratio: 1/1; + aspect-ratio: var(--ams-aspect-ratio-square); border-radius: 50%; display: inline-flex; font-family: var(--ams-avatar-font-family); From 7131b44b984f8e13ced44cd1b099dc47a631ae6c Mon Sep 17 00:00:00 2001 From: Vincent Smedinga Date: Mon, 18 Mar 2024 17:21:22 +0100 Subject: [PATCH 11/27] Simplify slicing short labels --- packages/react/src/Avatar/Avatar.test.tsx | 28 +++++++++++++++-------- packages/react/src/Avatar/Avatar.tsx | 2 +- 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/packages/react/src/Avatar/Avatar.test.tsx b/packages/react/src/Avatar/Avatar.test.tsx index bec646909b..1ff6f44a07 100644 --- a/packages/react/src/Avatar/Avatar.test.tsx +++ b/packages/react/src/Avatar/Avatar.test.tsx @@ -29,16 +29,6 @@ describe('Avatar', () => { expect(component).toHaveClass('ams-avatar extra') }) - it('supports ForwardRef in React', () => { - const ref = createRef() - - const { container } = render() - - const component = container.querySelector(':only-child') - - expect(ref.current).toBe(component) - }) - it('renders with a label consisting of no more than two, uppercase letters', () => { const { container } = render() @@ -67,6 +57,14 @@ describe('Avatar', () => { expect(image).toBeVisible() }) + it('shortens a label that is too long', () => { + const { container } = render() + + const component = container.querySelector(':only-child') + + expect(component).toHaveTextContent('AB') + }) + it('renders with default color', () => { const { container } = render() @@ -84,4 +82,14 @@ describe('Avatar', () => { expect(component).toHaveClass(`ams-avatar--${color}`) }), ) + + it('supports ForwardRef in React', () => { + const ref = createRef() + + const { container } = render() + + const component = container.querySelector(':only-child') + + expect(ref.current).toBe(component) + }) }) diff --git a/packages/react/src/Avatar/Avatar.tsx b/packages/react/src/Avatar/Avatar.tsx index 15afef7106..718507167b 100644 --- a/packages/react/src/Avatar/Avatar.tsx +++ b/packages/react/src/Avatar/Avatar.tsx @@ -35,7 +35,7 @@ export const Avatar = forwardRef( { label, imageSrc, className, color = 'dark-blue', ...restProps }: AvatarProps, ref: ForwardedRef, ) => { - const initials = (label.length > 2 ? label.slice(0, 2) : label).toUpperCase() + const initials = label.slice(0, 2).toUpperCase() const a11yLabel = initials.length === 0 ? 'Gebruiker' : `Initialen gebruiker: ${initials}.` From 1eca2687a828bf2fde38512165757e6a645fd3d7 Mon Sep 17 00:00:00 2001 From: Vincent Smedinga Date: Mon, 18 Mar 2024 17:22:23 +0100 Subject: [PATCH 12/27] Decrease padding to match space tokens scale --- proprietary/tokens/src/components/ams/avatar.tokens.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proprietary/tokens/src/components/ams/avatar.tokens.json b/proprietary/tokens/src/components/ams/avatar.tokens.json index e07d913cf6..b073c0d45a 100644 --- a/proprietary/tokens/src/components/ams/avatar.tokens.json +++ b/proprietary/tokens/src/components/ams/avatar.tokens.json @@ -5,7 +5,7 @@ "font-size": { "value": "{ams.text.level.6.font-size}" }, "font-weight": { "value": "{ams.text.font-weight.normal}" }, "line-height": { "value": "{ams.text.level.6.line-height}" }, - "padding": { "value": "5px" }, + "padding": { "value": "4px" }, "blue": { "background-color": { "value": "{ams.color.blue}" }, "color": { "value": "{ams.color.primary-black}" } From f8331334695bddb99fa7077d2f1908364b4cec66 Mon Sep 17 00:00:00 2001 From: Vincent Smedinga Date: Mon, 18 Mar 2024 17:40:49 +0100 Subject: [PATCH 13/27] Display the fallback icon correctly --- packages/css/src/components/avatar/README.md | 14 +++++++------- packages/react/src/Avatar/Avatar.tsx | 9 ++++++--- storybook/src/components/Avatar/Avatar.docs.mdx | 10 +++++----- storybook/src/components/Avatar/Avatar.stories.tsx | 11 ++++++----- 4 files changed, 24 insertions(+), 20 deletions(-) diff --git a/packages/css/src/components/avatar/README.md b/packages/css/src/components/avatar/README.md index 300015a350..e27393b572 100644 --- a/packages/css/src/components/avatar/README.md +++ b/packages/css/src/components/avatar/README.md @@ -2,14 +2,14 @@ # Avatar -A circular badge containing either up to two initials or a profile picture of the currently logged-in user. - -The badge contains the personal login icon by default when no user is logged-in or no label is provided. - -Interaction can be added by wrapping the avatar in a link. +A circular badge representing a person. ## Design -The avatar contains either one or two initial letters from the user's full name, a picture or a generic user icon. - +The avatar contains 1 or 2 initial letters from the person's full name, a picture, or a generic icon. The default background colour is dark blue. + +## Usage + +Display an avatar for the person currently using the application, +or to associate a person with a content item. diff --git a/packages/react/src/Avatar/Avatar.tsx b/packages/react/src/Avatar/Avatar.tsx index 718507167b..0e81a1c82d 100644 --- a/packages/react/src/Avatar/Avatar.tsx +++ b/packages/react/src/Avatar/Avatar.tsx @@ -7,6 +7,7 @@ import { PersonalLoginIcon } from '@amsterdam/design-system-react-icons' import clsx from 'clsx' import { forwardRef, useMemo } from 'react' import type { ForwardedRef, HTMLAttributes } from 'react' +import { Icon } from '../Icon' import { Image } from '../Image' import { VisuallyHidden } from '../VisuallyHidden' @@ -42,11 +43,13 @@ export const Avatar = forwardRef( const content = useMemo(() => { if (imageSrc) { return - } else if (label.length) { + } + + if (label.length) { return initials - } else { - return } + + return }, [imageSrc, label, initials]) return ( diff --git a/storybook/src/components/Avatar/Avatar.docs.mdx b/storybook/src/components/Avatar/Avatar.docs.mdx index e0b67d172d..3643f19c03 100644 --- a/storybook/src/components/Avatar/Avatar.docs.mdx +++ b/storybook/src/components/Avatar/Avatar.docs.mdx @@ -14,15 +14,15 @@ import README from "../../../../packages/css/src/components/avatar/README.md?raw -### Without Label +### With Picture -Avatar without a label, i.e. no user is logged-in. + - +### Fallback Icon -### With Picture +A user icon displays if no label and image are provided. - + ### In a Header diff --git a/storybook/src/components/Avatar/Avatar.stories.tsx b/storybook/src/components/Avatar/Avatar.stories.tsx index a01d722ab2..4fdd950bd9 100644 --- a/storybook/src/components/Avatar/Avatar.stories.tsx +++ b/storybook/src/components/Avatar/Avatar.stories.tsx @@ -31,16 +31,17 @@ type Story = StoryObj export const Default: Story = {} -export const WithoutLabel: Story = { +export const WithPicture: Story = { args: { - label: '', + label: 'PS', + imageSrc: 'https://i.pravatar.cc/200', }, } -export const WithImageSrc: Story = { +export const FallbackIcon: Story = { args: { - label: 'PS', - imageSrc: 'https://i.pravatar.cc/200', + imageSrc: undefined, + label: '', }, } From ff50d827fce5af979498ee29b07da6086feafbfa Mon Sep 17 00:00:00 2001 From: Vincent Smedinga Date: Mon, 18 Mar 2024 17:41:51 +0100 Subject: [PATCH 14/27] Split padding tokens --- packages/css/src/components/avatar/avatar.scss | 4 ++-- proprietary/tokens/src/components/ams/avatar.tokens.json | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/css/src/components/avatar/avatar.scss b/packages/css/src/components/avatar/avatar.scss index 2ac3d70d97..76230629e2 100644 --- a/packages/css/src/components/avatar/avatar.scss +++ b/packages/css/src/components/avatar/avatar.scss @@ -11,8 +11,8 @@ font-size: var(--ams-avatar-font-size); justify-content: center; line-height: var(--ams-avatar-line-height); - padding-block: var(--ams-avatar-padding); - padding-inline: var(--ams-avatar-padding); + padding-block: var(--ams-avatar-padding-block); + padding-inline: var(--ams-avatar-padding-inline); width: calc(var(--ams-avatar-line-height) * var(--ams-avatar-font-size)); svg { diff --git a/proprietary/tokens/src/components/ams/avatar.tokens.json b/proprietary/tokens/src/components/ams/avatar.tokens.json index b073c0d45a..24cbd53af4 100644 --- a/proprietary/tokens/src/components/ams/avatar.tokens.json +++ b/proprietary/tokens/src/components/ams/avatar.tokens.json @@ -5,7 +5,8 @@ "font-size": { "value": "{ams.text.level.6.font-size}" }, "font-weight": { "value": "{ams.text.font-weight.normal}" }, "line-height": { "value": "{ams.text.level.6.line-height}" }, - "padding": { "value": "4px" }, + "padding-block": { "value": "4px" }, + "padding-inline": { "value": "4px" }, "blue": { "background-color": { "value": "{ams.color.blue}" }, "color": { "value": "{ams.color.primary-black}" } From a98b87e4592dd00e23623203c28d46afdeb55199 Mon Sep 17 00:00:00 2001 From: Vincent Smedinga Date: Mon, 18 Mar 2024 18:02:48 +0100 Subject: [PATCH 15/27] Simplify image sizing --- packages/css/src/components/avatar/avatar.scss | 18 ++++++------------ packages/react/src/Avatar/Avatar.tsx | 2 +- .../src/components/Avatar/Avatar.docs.mdx | 3 +++ .../src/components/Avatar/Avatar.stories.tsx | 2 +- 4 files changed, 11 insertions(+), 14 deletions(-) diff --git a/packages/css/src/components/avatar/avatar.scss b/packages/css/src/components/avatar/avatar.scss index 76230629e2..f936935d39 100644 --- a/packages/css/src/components/avatar/avatar.scss +++ b/packages/css/src/components/avatar/avatar.scss @@ -6,13 +6,14 @@ .ams-avatar { aspect-ratio: var(--ams-aspect-ratio-square); border-radius: 50%; + box-sizing: content-box; display: inline-flex; font-family: var(--ams-avatar-font-family); font-size: var(--ams-avatar-font-size); - justify-content: center; line-height: var(--ams-avatar-line-height); padding-block: var(--ams-avatar-padding-block); padding-inline: var(--ams-avatar-padding-inline); + place-content: center; width: calc(var(--ams-avatar-line-height) * var(--ams-avatar-font-size)); svg { @@ -20,18 +21,11 @@ } } -.ams-avatar--image { +.ams-avatar--has-image { overflow: hidden; - position: relative; - - img { - height: calc(100% + var(--ams-avatar-padding) * 2); - left: calc(0px - var(--ams-avatar-padding)); - max-width: none; - position: absolute; - top: calc(0px - var(--ams-avatar-padding)); - width: calc(100% + var(--ams-avatar-padding) * 2); - } + padding-block: 0; + padding-inline: 0; + width: calc(var(--ams-avatar-line-height) * var(--ams-avatar-font-size) + 2 * var(--ams-avatar-padding-inline)); } .ams-avatar--blue { diff --git a/packages/react/src/Avatar/Avatar.tsx b/packages/react/src/Avatar/Avatar.tsx index 0e81a1c82d..6a977d882b 100644 --- a/packages/react/src/Avatar/Avatar.tsx +++ b/packages/react/src/Avatar/Avatar.tsx @@ -56,7 +56,7 @@ export const Avatar = forwardRef( {content} {a11yLabel} diff --git a/storybook/src/components/Avatar/Avatar.docs.mdx b/storybook/src/components/Avatar/Avatar.docs.mdx index 3643f19c03..c76168cbeb 100644 --- a/storybook/src/components/Avatar/Avatar.docs.mdx +++ b/storybook/src/components/Avatar/Avatar.docs.mdx @@ -16,6 +16,9 @@ import README from "../../../../packages/css/src/components/avatar/README.md?raw ### With Picture +The Avatar can also display a photo or other image for the person. +Make sure to scale the image down to around 100 pixels to prevent unnecessary data transfers. + ### Fallback Icon diff --git a/storybook/src/components/Avatar/Avatar.stories.tsx b/storybook/src/components/Avatar/Avatar.stories.tsx index 4fdd950bd9..cabd08e522 100644 --- a/storybook/src/components/Avatar/Avatar.stories.tsx +++ b/storybook/src/components/Avatar/Avatar.stories.tsx @@ -34,7 +34,7 @@ export const Default: Story = {} export const WithPicture: Story = { args: { label: 'PS', - imageSrc: 'https://i.pravatar.cc/200', + imageSrc: 'https://i.pravatar.cc/128', }, } From 9d2af93adbd4d9bdad3f606ed369839c8dd49454 Mon Sep 17 00:00:00 2001 From: Aram Limpens Date: Tue, 19 Mar 2024 17:21:36 +0100 Subject: [PATCH 16/27] Remove useMemo, extract content rendering logic --- packages/react/src/Avatar/Avatar.tsx | 30 +++++++++++++++------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/packages/react/src/Avatar/Avatar.tsx b/packages/react/src/Avatar/Avatar.tsx index 6a977d882b..fe18e084a3 100644 --- a/packages/react/src/Avatar/Avatar.tsx +++ b/packages/react/src/Avatar/Avatar.tsx @@ -5,7 +5,7 @@ import { PersonalLoginIcon } from '@amsterdam/design-system-react-icons' import clsx from 'clsx' -import { forwardRef, useMemo } from 'react' +import { forwardRef } from 'react' import type { ForwardedRef, HTMLAttributes } from 'react' import { Icon } from '../Icon' import { Image } from '../Image' @@ -31,6 +31,20 @@ export type AvatarProps = { label: string } & HTMLAttributes +type ContentProps = { imageSrc?: string; initials: string } + +const Content = ({ imageSrc, initials }: ContentProps) => { + if (imageSrc) { + return + } + + if (initials.length) { + return initials + } + + return +} + export const Avatar = forwardRef( ( { label, imageSrc, className, color = 'dark-blue', ...restProps }: AvatarProps, @@ -40,25 +54,13 @@ export const Avatar = forwardRef( const a11yLabel = initials.length === 0 ? 'Gebruiker' : `Initialen gebruiker: ${initials}.` - const content = useMemo(() => { - if (imageSrc) { - return - } - - if (label.length) { - return initials - } - - return - }, [imageSrc, label, initials]) - return ( - {content} + {a11yLabel} ) From 187ce70094e33f876c716224b4695b0f1203fa13 Mon Sep 17 00:00:00 2001 From: Aram Limpens Date: Tue, 19 Mar 2024 17:28:58 +0100 Subject: [PATCH 17/27] Add alt, remove redundant initials --- packages/react/src/Avatar/Avatar.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/react/src/Avatar/Avatar.tsx b/packages/react/src/Avatar/Avatar.tsx index fe18e084a3..9e7ab22d45 100644 --- a/packages/react/src/Avatar/Avatar.tsx +++ b/packages/react/src/Avatar/Avatar.tsx @@ -35,7 +35,7 @@ type ContentProps = { imageSrc?: string; initials: string } const Content = ({ imageSrc, initials }: ContentProps) => { if (imageSrc) { - return + return } if (initials.length) { @@ -52,7 +52,7 @@ export const Avatar = forwardRef( ) => { const initials = label.slice(0, 2).toUpperCase() - const a11yLabel = initials.length === 0 ? 'Gebruiker' : `Initialen gebruiker: ${initials}.` + const a11yLabel = initials.length === 0 ? 'Gebruiker' : 'Initialen gebruiker:' return ( - {a11yLabel} + ) }, From 75de96bb6db07456b761877d86dea4f784374e58 Mon Sep 17 00:00:00 2001 From: Aram Limpens Date: Tue, 19 Mar 2024 17:36:37 +0100 Subject: [PATCH 18/27] Hide visible text from screenreaders --- packages/react/src/Avatar/Avatar.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/react/src/Avatar/Avatar.tsx b/packages/react/src/Avatar/Avatar.tsx index 9e7ab22d45..ded07e7c78 100644 --- a/packages/react/src/Avatar/Avatar.tsx +++ b/packages/react/src/Avatar/Avatar.tsx @@ -39,7 +39,7 @@ const Content = ({ imageSrc, initials }: ContentProps) => { } if (initials.length) { - return initials + return {initials} } return @@ -52,7 +52,7 @@ export const Avatar = forwardRef( ) => { const initials = label.slice(0, 2).toUpperCase() - const a11yLabel = initials.length === 0 ? 'Gebruiker' : 'Initialen gebruiker:' + const a11yLabel = initials.length === 0 ? 'Gebruiker' : `Initialen gebruiker: ${initials}` return ( Date: Tue, 19 Mar 2024 17:39:31 +0100 Subject: [PATCH 19/27] Fix invalid HTML --- storybook/src/components/Avatar/Avatar.stories.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/storybook/src/components/Avatar/Avatar.stories.tsx b/storybook/src/components/Avatar/Avatar.stories.tsx index cabd08e522..b60a6a00d4 100644 --- a/storybook/src/components/Avatar/Avatar.stories.tsx +++ b/storybook/src/components/Avatar/Avatar.stories.tsx @@ -57,7 +57,9 @@ export const InAHeader: Story = { Zoeken - +
  • + +
  • } title="Dashboard" From d25177e66b920aa7543938f611f22b5b93c5bcb8 Mon Sep 17 00:00:00 2001 From: Vincent Smedinga Date: Tue, 19 Mar 2024 18:56:18 +0100 Subject: [PATCH 20/27] Fix test --- packages/react/src/Avatar/Avatar.test.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react/src/Avatar/Avatar.test.tsx b/packages/react/src/Avatar/Avatar.test.tsx index 1ff6f44a07..34577dc8b0 100644 --- a/packages/react/src/Avatar/Avatar.test.tsx +++ b/packages/react/src/Avatar/Avatar.test.tsx @@ -53,7 +53,7 @@ describe('Avatar', () => { const a11yLabel = container.querySelector('.ams-visually-hidden') const image = container.querySelector('[src="https://i.pravatar.cc/200"]') - expect(a11yLabel).toHaveTextContent('Initialen gebruiker: RS.') + expect(a11yLabel).toHaveTextContent('Initialen gebruiker: RS') expect(image).toBeVisible() }) From ce52b1af5c7031a65fa25f4e8271b01b6dc52566 Mon Sep 17 00:00:00 2001 From: Vincent Smedinga Date: Tue, 19 Mar 2024 18:57:00 +0100 Subject: [PATCH 21/27] Remove property setting the initial value --- packages/css/src/components/avatar/avatar.scss | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/css/src/components/avatar/avatar.scss b/packages/css/src/components/avatar/avatar.scss index f936935d39..b7e1369ee7 100644 --- a/packages/css/src/components/avatar/avatar.scss +++ b/packages/css/src/components/avatar/avatar.scss @@ -6,7 +6,6 @@ .ams-avatar { aspect-ratio: var(--ams-aspect-ratio-square); border-radius: 50%; - box-sizing: content-box; display: inline-flex; font-family: var(--ams-avatar-font-family); font-size: var(--ams-avatar-font-size); From 5dd40fd716aac4dbe0064142d52709324c763520 Mon Sep 17 00:00:00 2001 From: Vincent Smedinga Date: Tue, 19 Mar 2024 19:01:46 +0100 Subject: [PATCH 22/27] Group components with their prop types --- packages/react/src/Avatar/Avatar.tsx | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/packages/react/src/Avatar/Avatar.tsx b/packages/react/src/Avatar/Avatar.tsx index ded07e7c78..8630b5e326 100644 --- a/packages/react/src/Avatar/Avatar.tsx +++ b/packages/react/src/Avatar/Avatar.tsx @@ -25,13 +25,10 @@ export const avatarColors = [ type AvatarColor = (typeof avatarColors)[number] -export type AvatarProps = { - color?: AvatarColor +type ContentProps = { imageSrc?: string - label: string -} & HTMLAttributes - -type ContentProps = { imageSrc?: string; initials: string } + initials: string +} const Content = ({ imageSrc, initials }: ContentProps) => { if (imageSrc) { @@ -45,6 +42,12 @@ const Content = ({ imageSrc, initials }: ContentProps) => { return } +export type AvatarProps = { + color?: AvatarColor + imageSrc?: string + label: string +} & HTMLAttributes + export const Avatar = forwardRef( ( { label, imageSrc, className, color = 'dark-blue', ...restProps }: AvatarProps, From 197f4e0fb40841aaf72c14526f6d2e43cade56eb Mon Sep 17 00:00:00 2001 From: Aram Limpens Date: Wed, 20 Mar 2024 09:34:00 +0100 Subject: [PATCH 23/27] Use getByText in tests where possible --- packages/react/src/Avatar/Avatar.test.tsx | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/react/src/Avatar/Avatar.test.tsx b/packages/react/src/Avatar/Avatar.test.tsx index 34577dc8b0..97bcde00e3 100644 --- a/packages/react/src/Avatar/Avatar.test.tsx +++ b/packages/react/src/Avatar/Avatar.test.tsx @@ -1,13 +1,13 @@ -import { render } from '@testing-library/react' +import { render, screen } from '@testing-library/react' import { createRef } from 'react' import { Avatar, avatarColors } from './Avatar' import '@testing-library/jest-dom' describe('Avatar', () => { it('renders', () => { - const { container } = render() + render() - const component = container.querySelector(':only-child') + const component = screen.getByText('Initialen gebruiker: NR') expect(component).toBeInTheDocument() expect(component).toBeVisible() @@ -40,20 +40,20 @@ describe('Avatar', () => { it('renders with default content and title', () => { const { container } = render() - const a11yLabel = container.querySelector('.ams-visually-hidden') + const component = screen.getByText('Gebruiker') const svg = container.querySelector('svg') - expect(a11yLabel).toHaveTextContent('Gebruiker') + expect(component).toBeVisible() expect(svg).toBeVisible() }) it('renders with a profile picture', () => { - const { container } = render() + const { container } = render() - const a11yLabel = container.querySelector('.ams-visually-hidden') - const image = container.querySelector('[src="https://i.pravatar.cc/200"]') + const component = screen.getByText('Initialen gebruiker: RS') + const image = container.querySelector('[src="image-source"]') - expect(a11yLabel).toHaveTextContent('Initialen gebruiker: RS') + expect(component).toBeVisible() expect(image).toBeVisible() }) From 7f792706d1e1b53a7fffbe20f2b4907f66c80a36 Mon Sep 17 00:00:00 2001 From: Aram Limpens Date: Wed, 20 Mar 2024 09:42:14 +0100 Subject: [PATCH 24/27] Don't reuse aspect ratio component token --- packages/css/src/components/avatar/avatar.scss | 2 +- proprietary/tokens/src/components/ams/avatar.tokens.json | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/css/src/components/avatar/avatar.scss b/packages/css/src/components/avatar/avatar.scss index b7e1369ee7..18847b40b8 100644 --- a/packages/css/src/components/avatar/avatar.scss +++ b/packages/css/src/components/avatar/avatar.scss @@ -4,7 +4,7 @@ */ .ams-avatar { - aspect-ratio: var(--ams-aspect-ratio-square); + aspect-ratio: var(--ams-avatar-aspect-ratio); border-radius: 50%; display: inline-flex; font-family: var(--ams-avatar-font-family); diff --git a/proprietary/tokens/src/components/ams/avatar.tokens.json b/proprietary/tokens/src/components/ams/avatar.tokens.json index 24cbd53af4..98d132f803 100644 --- a/proprietary/tokens/src/components/ams/avatar.tokens.json +++ b/proprietary/tokens/src/components/ams/avatar.tokens.json @@ -1,6 +1,7 @@ { "ams": { "avatar": { + "aspect-ratio": { "value": "{ams.proportion.square}" }, "font-family": { "value": "{ams.text.font-family}" }, "font-size": { "value": "{ams.text.level.6.font-size}" }, "font-weight": { "value": "{ams.text.font-weight.normal}" }, From b8fd59ea5f146b39ab45feab1f8f75c3e6e9e041 Mon Sep 17 00:00:00 2001 From: Aram Limpens Date: Wed, 20 Mar 2024 09:43:26 +0100 Subject: [PATCH 25/27] Use rem instead of px --- proprietary/tokens/src/components/ams/avatar.tokens.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/proprietary/tokens/src/components/ams/avatar.tokens.json b/proprietary/tokens/src/components/ams/avatar.tokens.json index 98d132f803..f62e6aef92 100644 --- a/proprietary/tokens/src/components/ams/avatar.tokens.json +++ b/proprietary/tokens/src/components/ams/avatar.tokens.json @@ -6,8 +6,8 @@ "font-size": { "value": "{ams.text.level.6.font-size}" }, "font-weight": { "value": "{ams.text.font-weight.normal}" }, "line-height": { "value": "{ams.text.level.6.line-height}" }, - "padding-block": { "value": "4px" }, - "padding-inline": { "value": "4px" }, + "padding-block": { "value": "0.25rem" }, + "padding-inline": { "value": "0.25rem" }, "blue": { "background-color": { "value": "{ams.color.blue}" }, "color": { "value": "{ams.color.primary-black}" } From 9f87473a2deb2875cd1d0bf6346e50b67a459d73 Mon Sep 17 00:00:00 2001 From: Aram Limpens Date: Wed, 20 Mar 2024 09:54:23 +0100 Subject: [PATCH 26/27] Remove phantom white space when image doesn't load --- packages/css/src/components/avatar/avatar.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/css/src/components/avatar/avatar.scss b/packages/css/src/components/avatar/avatar.scss index 18847b40b8..86b22dabbf 100644 --- a/packages/css/src/components/avatar/avatar.scss +++ b/packages/css/src/components/avatar/avatar.scss @@ -24,6 +24,7 @@ overflow: hidden; padding-block: 0; padding-inline: 0; + vertical-align: middle; width: calc(var(--ams-avatar-line-height) * var(--ams-avatar-font-size) + 2 * var(--ams-avatar-padding-inline)); } From c1f683defc878c2a43072f476f104368879154cd Mon Sep 17 00:00:00 2001 From: Aram Limpens Date: Wed, 20 Mar 2024 11:09:33 +0100 Subject: [PATCH 27/27] Add comment --- packages/css/src/components/avatar/avatar.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/css/src/components/avatar/avatar.scss b/packages/css/src/components/avatar/avatar.scss index 86b22dabbf..b267996dea 100644 --- a/packages/css/src/components/avatar/avatar.scss +++ b/packages/css/src/components/avatar/avatar.scss @@ -24,7 +24,7 @@ overflow: hidden; padding-block: 0; padding-inline: 0; - vertical-align: middle; + vertical-align: middle; /* Remove ‘phantom’ white space when image doesn’t load */ width: calc(var(--ams-avatar-line-height) * var(--ams-avatar-font-size) + 2 * var(--ams-avatar-padding-inline)); }