Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add Figure component #1795

Merged
merged 12 commits into from
Dec 20, 2024
Merged
5 changes: 5 additions & 0 deletions packages/css/src/components/figure/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<!-- @license CC0-1.0 -->

# Figure

Groups media content with a caption that describes it.
33 changes: 33 additions & 0 deletions packages/css/src/components/figure/figure.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/**
* @license EUPL-1.2+
* Copyright Gemeente Amsterdam
*/

@use "../../common/text-rendering" as *;

@mixin reset-figure {
margin-block: 0;
margin-inline: 0;
}

.ams-figure {
display: flex;
flex-direction: column;
gap: var(--ams-figure-gap);
alimpens marked this conversation as resolved.
Show resolved Hide resolved

@include reset-figure;
}

.ams-figure__caption {
color: var(--ams-figure-caption-color);
font-family: var(--ams-figure-caption-font-family);
font-size: var(--ams-figure-caption-font-size);
font-weight: var(--ams-figure-caption-font-weight);
line-height: var(--ams-figure-caption-line-height);

@include text-rendering;
}

.ams-figure__caption--inverse-color {
color: var(--ams-figure-caption-inverse-color);
}
1 change: 1 addition & 0 deletions packages/css/src/components/index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
@use "error-message/error-message";
@use "field-set/field-set";
@use "field/field";
@use "figure/figure";
@use "file-input/file-input";
@use "file-list/file-list";
@use "footer/footer";
Expand Down
41 changes: 41 additions & 0 deletions packages/react/src/Figure/Figure.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { render, screen } from '@testing-library/react'
import { createRef } from 'react'
import { Figure } from './Figure'
import '@testing-library/jest-dom'

describe('Figure', () => {
it('renders', () => {
render(<Figure />)

const component = screen.getByRole('figure')

expect(component).toBeInTheDocument()
expect(component).toBeVisible()
})

it('renders a design system BEM class name', () => {
render(<Figure />)

const component = screen.getByRole('figure')

expect(component).toHaveClass('ams-figure')
})

it('renders an additional class name', () => {
render(<Figure className="extra" />)

const component = screen.getByRole('figure')

expect(component).toHaveClass('ams-figure extra')
})

it('supports ForwardRef in React', () => {
const ref = createRef<HTMLElement>()

render(<Figure ref={ref} />)

const component = screen.getByRole('figure')

expect(ref.current).toBe(component)
})
})
alimpens marked this conversation as resolved.
Show resolved Hide resolved
21 changes: 21 additions & 0 deletions packages/react/src/Figure/Figure.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/**
* @license EUPL-1.2+
* Copyright Gemeente Amsterdam
*/

import clsx from 'clsx'
import { forwardRef } from 'react'
import type { ForwardedRef, HTMLAttributes, PropsWithChildren } from 'react'
import { FigureCaption } from './FigureCaption'

export type FigureProps = PropsWithChildren<HTMLAttributes<HTMLElement>>

const FigureRoot = forwardRef(({ children, className, ...restProps }: FigureProps, ref: ForwardedRef<HTMLElement>) => (
<figure {...restProps} ref={ref} className={clsx('ams-figure', className)}>
{children}
</figure>
))

FigureRoot.displayName = 'Figure'

export const Figure = Object.assign(FigureRoot, { Caption: FigureCaption })
49 changes: 49 additions & 0 deletions packages/react/src/Figure/FigureCaption.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { render } from '@testing-library/react'
import { createRef } from 'react'
import { FigureCaption } from './FigureCaption'
import '@testing-library/jest-dom'

describe('Figure Caption', () => {
it('renders', () => {
const { container } = render(<FigureCaption />)

const component = container.querySelector(':only-child')

expect(component).toBeInTheDocument()
expect(component).toBeVisible()
})

it('renders a design system BEM class name', () => {
const { container } = render(<FigureCaption />)

const component = container.querySelector(':only-child')

expect(component).toHaveClass('ams-figure__caption')
})

it('renders the right inverse color class', () => {
const { container } = render(<FigureCaption inverseColor>Caption</FigureCaption>)

const component = container.querySelector(':only-child')

expect(component).toHaveClass('ams-figure__caption--inverse-color')
})

it('renders an additional class name', () => {
const { container } = render(<FigureCaption className="extra" />)

const component = container.querySelector(':only-child')

expect(component).toHaveClass('ams-figure__caption extra')
})

it('supports ForwardRef in React', () => {
const ref = createRef<HTMLElement>()

const { container } = render(<FigureCaption ref={ref} />)

const component = container.querySelector(':only-child')

expect(ref.current).toBe(component)
})
})
27 changes: 27 additions & 0 deletions packages/react/src/Figure/FigureCaption.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/**
* @license EUPL-1.2+
* Copyright Gemeente Amsterdam
*/

import clsx from 'clsx'
import { forwardRef } from 'react'
import type { ForwardedRef, HTMLAttributes, PropsWithChildren } from 'react'

export type FigureCaptionProps = {
/** Changes the text colour for readability on a dark background. */
inverseColor?: boolean
} & PropsWithChildren<HTMLAttributes<HTMLElement>>

export const FigureCaption = forwardRef(
({ children, className, inverseColor, ...restProps }: FigureCaptionProps, ref: ForwardedRef<HTMLElement>) => (
<figcaption
{...restProps}
ref={ref}
className={clsx('ams-figure__caption', inverseColor && 'ams-figure__caption--inverse-color', className)}
>
{children}
</figcaption>
),
)

FigureCaption.displayName = 'Figure.Caption'
5 changes: 5 additions & 0 deletions packages/react/src/Figure/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<!-- @license CC0-1.0 -->

# React Figure component

[Figure documentation](../../../css/src/components/figure/README.md)
2 changes: 2 additions & 0 deletions packages/react/src/Figure/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { Figure } from './Figure'
export type { FigureProps } from './Figure'
1 change: 1 addition & 0 deletions packages/react/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export * from './Dialog'
export * from './ErrorMessage'
export * from './Field'
export * from './FieldSet'
export * from './Figure'
export * from './FileInput'
export * from './FileList'
export * from './Footer'
Expand Down
15 changes: 15 additions & 0 deletions proprietary/tokens/src/components/ams/figure.tokens.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"ams": {
"figure": {
"gap": { "value": "{ams.space.sm}" },
"caption": {
"color": { "value": "{ams.brand.color.neutral.100}" },
"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}" },
"inverse-color": { "value": "{ams.brand.color.neutral.0}" }
}
}
}
}
20 changes: 20 additions & 0 deletions storybook/src/components/Figure/Figure.docs.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{/* @license CC0-1.0 */}

import { Canvas, Markdown, Meta, Primary } from "@storybook/blocks";
import * as FigureStories from "./Figure.stories.tsx";
import README from "../../../../packages/css/src/components/figure/README.md?raw";

<Meta of={FigureStories} />

<Markdown>{README}</Markdown>

<Primary />

## Examples

### Inverse colour

Set the `inverseColor` prop if the Figure Caption sits on a dark background.
This ensures the colour of the text provides enough contrast.

<Canvas of={FigureStories.InverseColour} />
45 changes: 45 additions & 0 deletions storybook/src/components/Figure/Figure.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/**
* @license EUPL-1.2+
* Copyright Gemeente Amsterdam
*/

import { Image } from '@amsterdam/design-system-react'
import { Figure } from '@amsterdam/design-system-react/src'
import { Meta, StoryObj } from '@storybook/react'
import { exampleCaption } from '../shared/exampleContent'

const caption = exampleCaption()

const meta = {
title: 'Components/Media/Figure',
component: Figure,
args: {
children: caption,
inverseColor: false,
},
render: ({ children, ...args }) => (
<Figure>
<Image
alt=""
aspectRatio="2x-wide"
sizes="(max-width: 36rem) 640px, (max-width: 68rem) 1280px, 1600px"
src="https://picsum.photos/1600/500"
srcSet="https://picsum.photos/640/200 640w, https://picsum.photos/1280/400 1280w, https://picsum.photos/1600/500 1600w"
/>
<Figure.Caption {...args}>{children}</Figure.Caption>
</Figure>
),
} satisfies Meta<typeof Figure.Caption>
// We use the Caption type here to allow inverseColor. This works as long as Figure has no props of its own.

export default meta

type Story = StoryObj<typeof meta>

export const Default: Story = {}

export const InverseColour: Story = {
args: {
inverseColor: true,
},
}
13 changes: 13 additions & 0 deletions storybook/src/components/shared/exampleContent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,19 @@ export const exampleAccordionHeading = () =>
'Voorgaande versies van ramingen',
])

export const exampleCaption = () =>
pickRandomContent<string>([
'Een rustige Amsterdamse gracht met eeuwenoude gevels die weerspiegelen in het water, terwijl fietsen nonchalant tegen de brugleuning rusten – een alledaags tafereel vol historie en charme. Foto: Liam Dekker.',
'Een rij geparkeerde fietsen langs een smalle gracht met klassieke Amsterdamse gevels op de achtergrond.',
'Een klein houten bootje dobbert rustig op het water, omringd door bomen en bakstenen panden met grote ramen. Foto: Sophie van der Brugge.',
'Een typische Amsterdamse brug met smeedijzeren leuningen, vol met fietsen en uitzicht op een grachtenpand met een klokgevel.',
'Een stille gracht met weerspiegelende gevels, terwijl een tram in de verte over een brug rijdt. Foto: Isabel Groeneveld.',
'Een zonovergoten terras aan de gracht, met stoelen op de kade en uitzicht op een sierlijke ophaalbrug.',
'Een grachtenpand met vrolijke bloemenbakken op de vensterbanken en een smalle trap naar de voordeur. Foto: Joris Zandvoort.',
'Een schuin geplaatste fiets tegen een lantaarnpaal, met op de achtergrond een karakteristiek houten bruggetje.',
'Een groep Ajax-supporters in rood-witte sjaals verzamelt zich op een plein, klaar voor een wedstrijd in de Johan Cruijff ArenA. Foto: Louis Flitskamp.',
])

export const exampleHeading = () =>
pickRandomContent<string>([
'Meer plekken voor kunst en cultuur, verspreid over de stad',
Expand Down
4 changes: 2 additions & 2 deletions storybook/src/styles/overrides.css
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
.sbdocs-content.sbdocs-content > div:not(.sb-unstyled) > :is(ol, ul) li,
.sbdocs-content.sbdocs-content > table:not(.sb-unstyled) :is(td, th),
.sbdocs-content.sbdocs-content > div:not(.sb-unstyled) > table:not(.sb-unstyled) :is(td, th),
.sbdocs-content.sbdocs-content > div:not(.sb-unstyled) figcaption {
.sbdocs-content.sbdocs-content > div:not(.sb-unstyled) > figure > figcaption {
color: #000;
font-family: "Amsterdam Sans", "Arial", sans-serif;
}
Expand Down Expand Up @@ -110,7 +110,7 @@
font-size: 1rem;
}

.sbdocs-content.sbdocs-content > div:not(.sb-unstyled) figcaption {
.sbdocs-content.sbdocs-content > div:not(.sb-unstyled) > figure > figcaption {
font-size: 0.875rem;
}

Expand Down
Loading