Skip to content

Commit

Permalink
feat: Image slider (#1595)
Browse files Browse the repository at this point in the history
Co-authored-by: Ruben Sibon <[email protected]>
Co-authored-by: Vincent Smedinga <[email protected]>
  • Loading branch information
3 people authored Oct 3, 2024
1 parent 20bc950 commit 58e7766
Show file tree
Hide file tree
Showing 24 changed files with 946 additions and 5 deletions.
23 changes: 23 additions & 0 deletions packages/css/src/components/image-slider/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<!-- @license CC0-1.0 -->

# Image Slider

Displays a small set of images in a limited space.

## Design

The first or selected image shows at its maximum size.
Every image displays a thumbnail at about 20% of its width.
Users can use buttons, thumbnails or swiping to navigate between the images.
The buttons re not displayed on a narrow touch device.
The images do not slide automatically.

## How to use

- Use this for a series of images that belong together.
- Feature the most essential image first.
- Display the Image Slider at the entire width of the [Screen](/docs/components-layout-screen--docs); do not position it on the [Grid](/docs/components-layout-grid--docs).
- Provide at least 2 images but at most 5.
- Assume that some or many users will not use the Slider and only see the first image.
Display all images separately if you want each of them to receive attention.
- Consult the [Image](/docs/components-media-image--docs) docs for guidelines on the individual images.
89 changes: 89 additions & 0 deletions packages/css/src/components/image-slider/image-slider.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/**
* @license EUPL-1.2+
* Copyright Gemeente Amsterdam
*/
@import "../../common/breakpoint";

.ams-image-slider {
display: grid;
gap: var(--ams-image-slider-gap);
grid-template-rows: 1fr auto;
}

.ams-image-slider__item {
scroll-snap-align: center;
scroll-snap-stop: always;

/** temporary fix for covering the entire gallery */
.ams-image {
inline-size: 100%;
}
}

.ams-image-slider__scroller {
align-items: center;
display: grid;
gap: var(--ams-image-slider-scroller-gap);
grid-auto-columns: 100%;
grid-auto-flow: column;
grid-column: 1/-1;
grid-row: 1;
outline-offset: var(--ams-image-slider-scroller-outline-offset);
overflow-x: auto;
overscroll-behavior-x: contain;
scroll-snap-type: x mandatory;
scrollbar-width: none;

&::-webkit-scrollbar {
display: none;
}

@media not (prefers-reduced-motion) {
scroll-behavior: smooth;
}
}

.ams-image-slider__controls {
display: flex;
grid-column: 1/-1;
grid-row: 1;
justify-content: space-between;

@media (pointer: coarse) and (max-width: $ams-breakpoint-medium) {
display: none;
}
}

.ams-image-slider__control {
place-self: center;
z-index: 1;
}

.ams-image-slider__thumbnails {
display: grid;
gap: var(--ams-image-slider-thumbnails-gap);
grid-template-columns: repeat(5, 1fr);
max-inline-size: 100%;
}

.ams-image-slider__thumbnail {
background-color: var(--ams-image-slider-thumbnails-thumbnail-background-color);
background-position: center;
background-size: cover;
border: none;
cursor: var(--ams-image-slider-thumbnails-thumbnail-cursor);
opacity: var(--ams-image-slider-thumbnails-thumbnail-opacity);
outline-offset: var(--ams-button-outline-offset);
padding-block: 0;
padding-inline: 0;
position: relative;
scroll-snap-align: start;

&:hover {
opacity: var(--ams-image-slider-thumbnails-thumbnail-hover-opacity);
}
}

.ams-image-slider__thumbnail--in-view {
opacity: var(--ams-image-slider-thumbnails-thumbnail-in-view-opacity);
}
1 change: 1 addition & 0 deletions packages/css/src/components/index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
@import "./hint/hint";
@import "./password-input/password-input";
@import "./form-error-list/form-error-list";
@import "./image-slider/image-slider";
@import "./table-of-contents/table-of-contents";
@import "./error-message/error-message";
@import "./file-input/file-input";
Expand Down
1 change: 1 addition & 0 deletions packages/css/src/components/screen/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ Manages the maximum width and alignment of the entire website or application.
[Header](/docs/components-containers-header--docs),
[Footer](/docs/components-containers-footer--docs),
[Spotlight](/docs/components-containers-spotlight--docs),
[Image Slider](/docs/components-containers-spotlight--docs),
and Figure.

## Design
Expand Down
1 change: 1 addition & 0 deletions packages/css/src/components/spotlight/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ Emphasizes a section on a page through a distinctive background colour.

## Guidelines

- Display the Spotlight at the entire width of the [Screen](/docs/components-layout-screen--docs); do not position it on the [Grid](/docs/components-layout-grid--docs).
- Refer to [this overview on Stijlweb](https://amsterdam.nl/stijlweb/basiselementen/kleuren/#PagCls_15671872) to determine whether you can use black or white text on the background colour of your choice.

## Relevant WCAG requirements
Expand Down
89 changes: 89 additions & 0 deletions packages/react/src/ImageSlider/ImageSlider.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import { render } from '@testing-library/react'
import { createRef } from 'react'
import { ImageSlider, ImageSliderImageProps } from './ImageSlider'
import '@testing-library/jest-dom'

const observe = jest.fn()
const unobserve = jest.fn()
const disconnect = jest.fn()
const takeRecords = jest.fn()

// Mock implementation of IntersectionObserver
window.IntersectionObserver = jest.fn(() => ({
observe,
unobserve,
disconnect,
takeRecords,
root: null,
rootMargin: '',
thresholds: [],
}))

describe('Image Slider', () => {
const images: ImageSliderImageProps[] = [
{
alt: 'Bridge',
aspectRatio: 'x-wide',
src: 'https://picsum.photos/id/122/320/180',
},
{
alt: 'Bunker',
aspectRatio: 'x-wide',
src: 'https://picsum.photos/id/101/320/180',
},
{
alt: 'Chairs',
aspectRatio: 'x-wide',
src: 'https://picsum.photos/id/153/320/180',
},
]

it('renders', () => {
const { container } = render(<ImageSlider images={images} />)

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

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

it('renders slides', () => {
const { container } = render(<ImageSlider images={images} />)

const slides = Array.from(container.querySelectorAll('.ams-image-slider__item'))

expect(slides).toHaveLength(3)
})

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

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

expect(component).toHaveClass('ams-image-slider')
})

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

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

expect(component).toHaveClass('ams-image-slider extra')
})

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

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

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

expect(ref.current).toBe(component)
})

it('renders thumbnails', () => {
const { container } = render(<ImageSlider images={images} />)

expect(container.querySelector('.ams-image-slider__thumbnails')).toBeInTheDocument()
})
})
Loading

0 comments on commit 58e7766

Please sign in to comment.