From 3d93145971ac96935722810f27b27caa3902c19f Mon Sep 17 00:00:00 2001 From: Niels Roozemond Date: Tue, 16 Jul 2024 20:27:01 +0200 Subject: [PATCH 01/62] Scaffold pre-observers --- .../css/src/components/image-slider/README.md | 3 + .../components/image-slider/image-slider.scss | 43 +++++++++++++ packages/css/src/components/index.scss | 1 + .../src/ImageSlider/ImageSlider.test.tsx | 41 +++++++++++++ .../react/src/ImageSlider/ImageSlider.tsx | 60 +++++++++++++++++++ .../react/src/ImageSlider/ImageSliderItem.tsx | 20 +++++++ packages/react/src/ImageSlider/README.md | 5 ++ packages/react/src/ImageSlider/index.ts | 2 + packages/react/src/index.ts | 1 + .../components/ams/image-slider.tokens.json | 7 +++ .../ImageSlider/ImageSlider.docs.mdx | 13 ++++ .../ImageSlider/ImageSlider.stories.tsx | 41 +++++++++++++ 12 files changed, 237 insertions(+) create mode 100644 packages/css/src/components/image-slider/README.md create mode 100644 packages/css/src/components/image-slider/image-slider.scss create mode 100644 packages/react/src/ImageSlider/ImageSlider.test.tsx create mode 100644 packages/react/src/ImageSlider/ImageSlider.tsx create mode 100644 packages/react/src/ImageSlider/ImageSliderItem.tsx create mode 100644 packages/react/src/ImageSlider/README.md create mode 100644 packages/react/src/ImageSlider/index.ts create mode 100644 proprietary/tokens/src/components/ams/image-slider.tokens.json create mode 100644 storybook/src/components/ImageSlider/ImageSlider.docs.mdx create mode 100644 storybook/src/components/ImageSlider/ImageSlider.stories.tsx diff --git a/packages/css/src/components/image-slider/README.md b/packages/css/src/components/image-slider/README.md new file mode 100644 index 0000000000..f0e70409b2 --- /dev/null +++ b/packages/css/src/components/image-slider/README.md @@ -0,0 +1,3 @@ + + +# Image Slider diff --git a/packages/css/src/components/image-slider/image-slider.scss b/packages/css/src/components/image-slider/image-slider.scss new file mode 100644 index 0000000000..431541af0b --- /dev/null +++ b/packages/css/src/components/image-slider/image-slider.scss @@ -0,0 +1,43 @@ +/** + * @license EUPL-1.2+ + * Copyright Gemeente Amsterdam + */ + +.ams-image-slider { + display: grid; + gap: var(--ams-image-slider-gap); + grid-template-rows: 1fr auto; +} + +.ams-image-slider--snapstop .ams-image-slider__item { + scroll-snap-align: center; +} + +.ams-image-slider__scroller { + align-items: center; + display: grid; + gap: var(--ams-image-slider-gap); + grid-auto-columns: 100%; + grid-auto-flow: column; + grid-column: 1/-1; + grid-row: 1; + overflow-x: auto; + overscroll-behavior-x: contain; + scroll-snap-type: x mandatory; + + @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; +} + +.ams-image-slider__control { + place-self: center; + z-index: 1; +} diff --git a/packages/css/src/components/index.scss b/packages/css/src/components/index.scss index 595ea6302a..60cc57b89e 100644 --- a/packages/css/src/components/index.scss +++ b/packages/css/src/components/index.scss @@ -4,6 +4,7 @@ */ /* Append here */ +@import "./image-slider/image-slider"; @import "./table-of-contents/table-of-contents"; @import "./error-message/error-message"; @import "./file-input/file-input"; diff --git a/packages/react/src/ImageSlider/ImageSlider.test.tsx b/packages/react/src/ImageSlider/ImageSlider.test.tsx new file mode 100644 index 0000000000..4dea965c7f --- /dev/null +++ b/packages/react/src/ImageSlider/ImageSlider.test.tsx @@ -0,0 +1,41 @@ +import { render } from '@testing-library/react' +import { createRef } from 'react' +import { ImageSlider } from './ImageSlider' +import '@testing-library/jest-dom' + +describe('Image slider', () => { + 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-image-slider') + }) + + it('renders an additional class name', () => { + const { container } = render() + + const component = container.querySelector(':only-child') + + expect(component).toHaveClass('ams-image-slider 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/ImageSlider/ImageSlider.tsx b/packages/react/src/ImageSlider/ImageSlider.tsx new file mode 100644 index 0000000000..09098183b1 --- /dev/null +++ b/packages/react/src/ImageSlider/ImageSlider.tsx @@ -0,0 +1,60 @@ +/** + * @license EUPL-1.2+ + * Copyright Gemeente Amsterdam + */ + +import { ChevronLeftIcon, ChevronRightIcon } from '@amsterdam/design-system-react-icons' +import clsx from 'clsx' +import { forwardRef } from 'react' +import type { ForwardedRef, HTMLAttributes, PropsWithChildren } from 'react' +import { ImageSliderItem } from './ImageSliderItem' +import { IconButton } from '../IconButton' + +export type ImageSliderProps = { + controls?: boolean + scrollbar?: boolean + snapstop?: boolean + thumbnails?: boolean +} & PropsWithChildren> + +export const ImageSliderRoot = forwardRef( + ( + { children, className, controls, scrollbar, snapstop, thumbnails, ...restProps }: ImageSliderProps, + ref: ForwardedRef, + ) => ( +
+ {controls && ( +
+ + +
+ )} +
{children}
+
+ ), +) + +ImageSliderRoot.displayName = 'ImageSlider' + +export const ImageSlider = Object.assign(ImageSliderRoot, { Item: ImageSliderItem }) diff --git a/packages/react/src/ImageSlider/ImageSliderItem.tsx b/packages/react/src/ImageSlider/ImageSliderItem.tsx new file mode 100644 index 0000000000..98b72260b2 --- /dev/null +++ b/packages/react/src/ImageSlider/ImageSliderItem.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 ImageSliderItemProps = PropsWithChildren> + +export const ImageSliderItem = forwardRef( + ({ children, className, ...restProps }: ImageSliderItemProps, ref: ForwardedRef) => ( +
+ {children} +
+ ), +) + +ImageSliderItem.displayName = 'ImageSlider.Item' diff --git a/packages/react/src/ImageSlider/README.md b/packages/react/src/ImageSlider/README.md new file mode 100644 index 0000000000..54ae76ec9c --- /dev/null +++ b/packages/react/src/ImageSlider/README.md @@ -0,0 +1,5 @@ + + +# React Image Slider component + +[Image Slider documentation](../../../css/src/components/image-slider/README.md) diff --git a/packages/react/src/ImageSlider/index.ts b/packages/react/src/ImageSlider/index.ts new file mode 100644 index 0000000000..f31cdcf48b --- /dev/null +++ b/packages/react/src/ImageSlider/index.ts @@ -0,0 +1,2 @@ +export { ImageSlider } from './ImageSlider' +export type { ImageSliderProps } from './ImageSlider' diff --git a/packages/react/src/index.ts b/packages/react/src/index.ts index 1aef581892..8970ac8880 100644 --- a/packages/react/src/index.ts +++ b/packages/react/src/index.ts @@ -4,6 +4,7 @@ */ /* Append here */ +export * from './ImageSlider' export * from './FormErrorList' export * from './TableOfContents' export * from './ErrorMessage' diff --git a/proprietary/tokens/src/components/ams/image-slider.tokens.json b/proprietary/tokens/src/components/ams/image-slider.tokens.json new file mode 100644 index 0000000000..b7f00951b9 --- /dev/null +++ b/proprietary/tokens/src/components/ams/image-slider.tokens.json @@ -0,0 +1,7 @@ +{ + "ams": { + "image-slider": { + "gap": { "value": "{ams.space.xs}" } + } + } +} diff --git a/storybook/src/components/ImageSlider/ImageSlider.docs.mdx b/storybook/src/components/ImageSlider/ImageSlider.docs.mdx new file mode 100644 index 0000000000..98a001533c --- /dev/null +++ b/storybook/src/components/ImageSlider/ImageSlider.docs.mdx @@ -0,0 +1,13 @@ +{/* @license CC0-1.0 */} + +import { Controls, Markdown, Meta, Primary } from "@storybook/blocks"; +import * as ImageSliderStories from "./ImageSlider.stories.tsx"; +import README from "../../../../packages/css/src/components/image-slider/README.md?raw"; + + + +{README} + + + + diff --git a/storybook/src/components/ImageSlider/ImageSlider.stories.tsx b/storybook/src/components/ImageSlider/ImageSlider.stories.tsx new file mode 100644 index 0000000000..0c16c125cd --- /dev/null +++ b/storybook/src/components/ImageSlider/ImageSlider.stories.tsx @@ -0,0 +1,41 @@ +/** + * @license EUPL-1.2+ + * Copyright Gemeente Amsterdam + */ + +import { AspectRatio, Image, ImageSlider } from '@amsterdam/design-system-react/src' +import { Meta, StoryObj } from '@storybook/react' + +const meta = { + title: 'Components/Media/Image Slider', + component: ImageSlider, + args: { + children: [ + + + + + , + + + + + , + + + + + , + ], + controls: true, + scrollbar: true, + snapstop: true, + thumbnails: true, + }, +} satisfies Meta + +export default meta + +type Story = StoryObj + +export const Default: Story = {} From f986c7ce16840f031b28c0b93221520bbb41923b Mon Sep 17 00:00:00 2001 From: Niels Roozemond Date: Thu, 18 Jul 2024 11:15:29 +0200 Subject: [PATCH 02/62] Testing intersection observers --- .../react/src/ImageSlider/ImageSlider.tsx | 116 +++++++++++++----- .../src/ImageSlider/ImageSliderScroller.tsx | 20 +++ packages/react/src/ImageSlider/index.ts | 1 + 3 files changed, 104 insertions(+), 33 deletions(-) create mode 100644 packages/react/src/ImageSlider/ImageSliderScroller.tsx diff --git a/packages/react/src/ImageSlider/ImageSlider.tsx b/packages/react/src/ImageSlider/ImageSlider.tsx index 09098183b1..de97145bb4 100644 --- a/packages/react/src/ImageSlider/ImageSlider.tsx +++ b/packages/react/src/ImageSlider/ImageSlider.tsx @@ -5,9 +5,11 @@ import { ChevronLeftIcon, ChevronRightIcon } from '@amsterdam/design-system-react-icons' import clsx from 'clsx' -import { forwardRef } from 'react' +import { forwardRef, useEffect, useRef, useState } from 'react' import type { ForwardedRef, HTMLAttributes, PropsWithChildren } from 'react' import { ImageSliderItem } from './ImageSliderItem' +// import useInViewPort from './useInViewPort' +import { ImageSliderScroller } from './ImageSliderScroller' import { IconButton } from '../IconButton' export type ImageSliderProps = { @@ -21,38 +23,86 @@ export const ImageSliderRoot = forwardRef( ( { children, className, controls, scrollbar, snapstop, thumbnails, ...restProps }: ImageSliderProps, ref: ForwardedRef, - ) => ( -
- {controls && ( -
- - -
- )} -
{children}
-
- ), + ) => { + const [currentSlideIndex, setCurrentSlideIndex] = useState(0) + const targetRef = useRef(null) + // const inViewport = useInViewPort(targetRef, { threshold: 0.5 }) + // console.log(currentSlideIndex) + + useEffect(() => { + const sliderScroller = targetRef.current + + if (!sliderScroller) { + return + } + + const slides = sliderScroller.querySelectorAll('.ams-image-slider__item') + const slidesArray = Array.from(slides) + // const hasIntersected = new Set() + + console.log(slidesArray) + const observer = new IntersectionObserver( + // (observations) => { + // for (let observation of observations) { + // hasIntersected.add(observation) + + // console.log(observation) + // // toggle --in-view class if intersecting or not + // observation.target.classList.toggle('--in-view', observation.isIntersecting) + // } + (entries) => { + console.log(entries) + entries.forEach((entry) => { + if (entry.isIntersecting) { + const index = slidesArray.indexOf(entry.target) + setCurrentSlideIndex(index) + console.log(entry, currentSlideIndex) + } + }) + }, + { + root: sliderScroller, + threshold: 0.6, + }, + ) + console.log(observer) + + slides.forEach((slide) => observer.unobserve(slide)) + }, []) + + return ( +
+ {controls && ( +
+ + +
+ )} + {children} +
+ ) + }, ) ImageSliderRoot.displayName = 'ImageSlider' diff --git a/packages/react/src/ImageSlider/ImageSliderScroller.tsx b/packages/react/src/ImageSlider/ImageSliderScroller.tsx new file mode 100644 index 0000000000..719d4d2848 --- /dev/null +++ b/packages/react/src/ImageSlider/ImageSliderScroller.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 ImageSliderScrollerProps = PropsWithChildren> + +export const ImageSliderScroller = forwardRef( + ({ children, className, ...restProps }: ImageSliderScrollerProps, ref: ForwardedRef) => ( +
+ {children} +
+ ), +) + +ImageSliderScroller.displayName = 'ImageSlider.Scroller' diff --git a/packages/react/src/ImageSlider/index.ts b/packages/react/src/ImageSlider/index.ts index f31cdcf48b..12f2fd1acf 100644 --- a/packages/react/src/ImageSlider/index.ts +++ b/packages/react/src/ImageSlider/index.ts @@ -1,2 +1,3 @@ export { ImageSlider } from './ImageSlider' export type { ImageSliderProps } from './ImageSlider' +export type { ImageSliderItemProps } from './ImageSliderItem' From 05b315545feababe53c84a024bd697e985363bd6 Mon Sep 17 00:00:00 2001 From: Niels Roozemond Date: Fri, 19 Jul 2024 09:39:56 +0200 Subject: [PATCH 03/62] Working intersectionObserver --- .../react/src/ImageSlider/ImageSlider.tsx | 33 +++++-------------- 1 file changed, 8 insertions(+), 25 deletions(-) diff --git a/packages/react/src/ImageSlider/ImageSlider.tsx b/packages/react/src/ImageSlider/ImageSlider.tsx index de97145bb4..292092b68a 100644 --- a/packages/react/src/ImageSlider/ImageSlider.tsx +++ b/packages/react/src/ImageSlider/ImageSlider.tsx @@ -5,7 +5,7 @@ import { ChevronLeftIcon, ChevronRightIcon } from '@amsterdam/design-system-react-icons' import clsx from 'clsx' -import { forwardRef, useEffect, useRef, useState } from 'react' +import { forwardRef, useEffect, useRef } from 'react' import type { ForwardedRef, HTMLAttributes, PropsWithChildren } from 'react' import { ImageSliderItem } from './ImageSliderItem' // import useInViewPort from './useInViewPort' @@ -24,10 +24,7 @@ export const ImageSliderRoot = forwardRef( { children, className, controls, scrollbar, snapstop, thumbnails, ...restProps }: ImageSliderProps, ref: ForwardedRef, ) => { - const [currentSlideIndex, setCurrentSlideIndex] = useState(0) const targetRef = useRef(null) - // const inViewport = useInViewPort(targetRef, { threshold: 0.5 }) - // console.log(currentSlideIndex) useEffect(() => { const sliderScroller = targetRef.current @@ -38,36 +35,22 @@ export const ImageSliderRoot = forwardRef( const slides = sliderScroller.querySelectorAll('.ams-image-slider__item') const slidesArray = Array.from(slides) - // const hasIntersected = new Set() + const hasIntersected = new Set() - console.log(slidesArray) const observer = new IntersectionObserver( - // (observations) => { - // for (let observation of observations) { - // hasIntersected.add(observation) - - // console.log(observation) - // // toggle --in-view class if intersecting or not - // observation.target.classList.toggle('--in-view', observation.isIntersecting) - // } - (entries) => { - console.log(entries) - entries.forEach((entry) => { - if (entry.isIntersecting) { - const index = slidesArray.indexOf(entry.target) - setCurrentSlideIndex(index) - console.log(entry, currentSlideIndex) - } - }) + (observations) => { + for (let observation of observations) { + hasIntersected.add(observation) + observation.target.classList.toggle('ams-image-slider__item--in-view', observation.isIntersecting) + } }, { root: sliderScroller, threshold: 0.6, }, ) - console.log(observer) - slides.forEach((slide) => observer.unobserve(slide)) + for (let slide of slidesArray) observer.observe(slide) }, []) return ( From dd01ba1f618318b9ab22069b9da993e10dbf20b9 Mon Sep 17 00:00:00 2001 From: Niels Roozemond Date: Fri, 19 Jul 2024 11:27:41 +0200 Subject: [PATCH 04/62] Test fix --- packages/react/src/ImageSlider/ImageSlider.test.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react/src/ImageSlider/ImageSlider.test.tsx b/packages/react/src/ImageSlider/ImageSlider.test.tsx index 4dea965c7f..0c0668c975 100644 --- a/packages/react/src/ImageSlider/ImageSlider.test.tsx +++ b/packages/react/src/ImageSlider/ImageSlider.test.tsx @@ -30,7 +30,7 @@ describe('Image slider', () => { }) it('supports ForwardRef in React', () => { - const ref = createRef() + const ref = createRef() const { container } = render() From 175af142717b7c05aaa587bc3d7ba14c97d4438f Mon Sep 17 00:00:00 2001 From: Niels Roozemond Date: Mon, 22 Jul 2024 10:02:35 +0200 Subject: [PATCH 05/62] Scrollbar and snapstop options --- .../components/image-slider/image-slider.scss | 19 ++++++++++++++++++- .../react/src/ImageSlider/ImageSlider.tsx | 6 +++++- .../ImageSlider/ImageSlider.stories.tsx | 2 +- 3 files changed, 24 insertions(+), 3 deletions(-) diff --git a/packages/css/src/components/image-slider/image-slider.scss b/packages/css/src/components/image-slider/image-slider.scss index 431541af0b..8c1927d092 100644 --- a/packages/css/src/components/image-slider/image-slider.scss +++ b/packages/css/src/components/image-slider/image-slider.scss @@ -9,8 +9,17 @@ grid-template-rows: 1fr auto; } -.ams-image-slider--snapstop .ams-image-slider__item { +.ams-image-slider__item { scroll-snap-align: center; + + /** temporary fix for covering the entire gallery */ + .ams-image { + inline-size: 100%; + } +} + +.ams-image-slider--snapstop .ams-image-slider__item { + scroll-snap-stop: always; } .ams-image-slider__scroller { @@ -30,6 +39,14 @@ } } +.ams-image-slider:not(.ams-image-slider--scrollbar) .ams-image-slider__scroller { + scrollbar-width: none; + + &::-webkit-scrollbar { + display: none; + } +} + .ams-image-slider__controls { display: flex; grid-column: 1/-1; diff --git a/packages/react/src/ImageSlider/ImageSlider.tsx b/packages/react/src/ImageSlider/ImageSlider.tsx index 292092b68a..7788f1d6ab 100644 --- a/packages/react/src/ImageSlider/ImageSlider.tsx +++ b/packages/react/src/ImageSlider/ImageSlider.tsx @@ -8,14 +8,17 @@ import clsx from 'clsx' import { forwardRef, useEffect, useRef } from 'react' import type { ForwardedRef, HTMLAttributes, PropsWithChildren } from 'react' import { ImageSliderItem } from './ImageSliderItem' -// import useInViewPort from './useInViewPort' import { ImageSliderScroller } from './ImageSliderScroller' import { IconButton } from '../IconButton' export type ImageSliderProps = { + /** Show navigation controls */ controls?: boolean + /** Show native scrollbar inside gallery */ scrollbar?: boolean + /** Prevent passing over possible snap elements */ snapstop?: boolean + /** Show thumbnails */ thumbnails?: boolean } & PropsWithChildren> @@ -24,6 +27,7 @@ export const ImageSliderRoot = forwardRef( { children, className, controls, scrollbar, snapstop, thumbnails, ...restProps }: ImageSliderProps, ref: ForwardedRef, ) => { + // const [ currentSlide, setCurrentSlide ] = useState(0) const targetRef = useRef(null) useEffect(() => { diff --git a/storybook/src/components/ImageSlider/ImageSlider.stories.tsx b/storybook/src/components/ImageSlider/ImageSlider.stories.tsx index 0c16c125cd..be2e7ef367 100644 --- a/storybook/src/components/ImageSlider/ImageSlider.stories.tsx +++ b/storybook/src/components/ImageSlider/ImageSlider.stories.tsx @@ -28,7 +28,7 @@ const meta = { , ], controls: true, - scrollbar: true, + scrollbar: false, snapstop: true, thumbnails: true, }, From c33e0f0ec44d735dc66b7fe12bf73e724954ebc9 Mon Sep 17 00:00:00 2001 From: Niels Roozemond Date: Mon, 22 Jul 2024 14:54:34 +0200 Subject: [PATCH 06/62] Previous and Next slide functions --- .../react/src/ImageSlider/ImageSlider.tsx | 54 +++++++++++++++++-- .../src/ImageSlider/ImageSliderScroller.tsx | 4 +- 2 files changed, 54 insertions(+), 4 deletions(-) diff --git a/packages/react/src/ImageSlider/ImageSlider.tsx b/packages/react/src/ImageSlider/ImageSlider.tsx index 7788f1d6ab..e8d1ad5c7e 100644 --- a/packages/react/src/ImageSlider/ImageSlider.tsx +++ b/packages/react/src/ImageSlider/ImageSlider.tsx @@ -5,7 +5,7 @@ import { ChevronLeftIcon, ChevronRightIcon } from '@amsterdam/design-system-react-icons' import clsx from 'clsx' -import { forwardRef, useEffect, useRef } from 'react' +import { forwardRef, useEffect, useRef, useState } from 'react' import type { ForwardedRef, HTMLAttributes, PropsWithChildren } from 'react' import { ImageSliderItem } from './ImageSliderItem' import { ImageSliderScroller } from './ImageSliderScroller' @@ -27,7 +27,7 @@ export const ImageSliderRoot = forwardRef( { children, className, controls, scrollbar, snapstop, thumbnails, ...restProps }: ImageSliderProps, ref: ForwardedRef, ) => { - // const [ currentSlide, setCurrentSlide ] = useState(0) + const [currentSlide, setCurrentSlide] = useState(0) const targetRef = useRef(null) useEffect(() => { @@ -46,6 +46,7 @@ export const ImageSliderRoot = forwardRef( for (let observation of observations) { hasIntersected.add(observation) observation.target.classList.toggle('ams-image-slider__item--in-view', observation.isIntersecting) + if (observation.isIntersecting) setCurrentSlide(slidesArray.indexOf(observation.target)) } }, { @@ -57,6 +58,47 @@ export const ImageSliderRoot = forwardRef( for (let slide of slidesArray) observer.observe(slide) }, []) + const goToSlide = (element: HTMLElement) => { + const sliderScroller = targetRef.current + + if (!sliderScroller || !element) { + return + } + + const delta = Math.abs(sliderScroller.offsetLeft - element.offsetLeft) + const scrollerPadding = parseInt(getComputedStyle(sliderScroller).getPropertyValue('padding-left'), 10) + + const pos = sliderScroller.clientWidth / 2 > delta ? delta - scrollerPadding : delta + scrollerPadding + + sliderScroller.scrollTo(pos, 0) + } + + const goToNextSlide = (element: HTMLElement) => { + const next = element.nextElementSibling as HTMLElement | null + + if (element === next) return + + if (next) { + goToSlide(next) + } else { + console.log('at the end') + // goToSlide(element.parentElement?.firstElementChild as HTMLElement) + } + } + + const goToPreviousSlide = (element: HTMLElement) => { + const next = element.previousElementSibling as HTMLElement | null + + if (element === next) return + + if (next) { + goToSlide(next) + } else { + console.log('at the start') + // goToSlide(element.parentElement?.lastElementChild as HTMLElement) + } + } + return (
+ goToPreviousSlide(document.querySelector('.ams-image-slider__item--in-view') as HTMLElement) + } /> goToNextSlide(document.querySelector('.ams-image-slider__item--in-view') as HTMLElement)} />
)} - {children} + + {children} + ) }, diff --git a/packages/react/src/ImageSlider/ImageSliderScroller.tsx b/packages/react/src/ImageSlider/ImageSliderScroller.tsx index 719d4d2848..37d7ba1ad9 100644 --- a/packages/react/src/ImageSlider/ImageSliderScroller.tsx +++ b/packages/react/src/ImageSlider/ImageSliderScroller.tsx @@ -7,7 +7,9 @@ import clsx from 'clsx' import { forwardRef } from 'react' import type { ForwardedRef, HTMLAttributes, PropsWithChildren } from 'react' -export type ImageSliderScrollerProps = PropsWithChildren> +export type ImageSliderScrollerProps = { + currentSlide: number +} & PropsWithChildren> export const ImageSliderScroller = forwardRef( ({ children, className, ...restProps }: ImageSliderScrollerProps, ref: ForwardedRef) => ( From 15f4794d1896cfca31aa01b8c6cb34477c4a3cb0 Mon Sep 17 00:00:00 2001 From: Niels Roozemond Date: Fri, 26 Jul 2024 12:09:06 +0200 Subject: [PATCH 07/62] Interactions and thumbnails testing --- .../components/image-slider/image-slider.scss | 20 ++ .../react/src/ImageSlider/ImageSlider.tsx | 196 +++++++++++------- .../src/ImageSlider/ImageSliderContext.tsx | 16 ++ .../react/src/ImageSlider/ImageSliderItem.tsx | 29 ++- .../src/ImageSlider/ImageSliderScroller.tsx | 4 +- .../ImageSlider/ImageSlider.stories.tsx | 6 +- 6 files changed, 178 insertions(+), 93 deletions(-) create mode 100644 packages/react/src/ImageSlider/ImageSliderContext.tsx diff --git a/packages/css/src/components/image-slider/image-slider.scss b/packages/css/src/components/image-slider/image-slider.scss index 8c1927d092..d394d957b4 100644 --- a/packages/css/src/components/image-slider/image-slider.scss +++ b/packages/css/src/components/image-slider/image-slider.scss @@ -30,6 +30,7 @@ grid-auto-flow: column; grid-column: 1/-1; grid-row: 1; + outline-offset: 2px; overflow-x: auto; overscroll-behavior-x: contain; scroll-snap-type: x mandatory; @@ -58,3 +59,22 @@ place-self: center; z-index: 1; } + +.ams-image-slider__thumbnails { + display: grid; + gap: 1rem; + grid-auto-flow: column; + grid-column: 1/-1; + max-inline-size: 100%; + overflow-x: auto; + overscroll-behavior-x: contain; + place-self: flex-start; + scrollbar-width: none; +} + +.ams-image-slider__thumbnail { + aspect-ratio: 1 / 1; + background-size: cover; + border: none; + min-block-size: 8rem; +} diff --git a/packages/react/src/ImageSlider/ImageSlider.tsx b/packages/react/src/ImageSlider/ImageSlider.tsx index e8d1ad5c7e..1fc5d458b7 100644 --- a/packages/react/src/ImageSlider/ImageSlider.tsx +++ b/packages/react/src/ImageSlider/ImageSlider.tsx @@ -6,7 +6,8 @@ import { ChevronLeftIcon, ChevronRightIcon } from '@amsterdam/design-system-react-icons' import clsx from 'clsx' import { forwardRef, useEffect, useRef, useState } from 'react' -import type { ForwardedRef, HTMLAttributes, PropsWithChildren } from 'react' +import type { ForwardedRef, HTMLAttributes, KeyboardEventHandler, PropsWithChildren } from 'react' +import { ImageSliderContext } from './ImageSliderContext' import { ImageSliderItem } from './ImageSliderItem' import { ImageSliderScroller } from './ImageSliderScroller' import { IconButton } from '../IconButton' @@ -27,36 +28,44 @@ export const ImageSliderRoot = forwardRef( { children, className, controls, scrollbar, snapstop, thumbnails, ...restProps }: ImageSliderProps, ref: ForwardedRef, ) => { - const [currentSlide, setCurrentSlide] = useState(0) + const [currentSlideId, setCurrentSlideId] = useState(0) + const [atStart, setAtStart] = useState(true) + const [atEnd, setAtEnd] = useState(false) const targetRef = useRef(null) + const hasIntersected = new Set() + + const inView = (observations: IntersectionObserverEntry[]) => { + const slides = targetRef.current?.children || [] + const slidesArray = Array.from(slides) + + for (let observation of observations) { + hasIntersected.add(observation) + if (observation.isIntersecting) { + setCurrentSlideId(slidesArray.indexOf(observation.target)) + } + } + } + + const observerOptions = { + root: targetRef.current, + threshold: 0.6, + } useEffect(() => { - const sliderScroller = targetRef.current + const observer = new IntersectionObserver(inView, observerOptions) - if (!sliderScroller) { - return + if (targetRef.current) { + const slides = targetRef.current.children + const slidesArray = Array.from(slides) + for (let slide of slidesArray) observer.observe(slide) + + targetRef.current.addEventListener('scrollend', synchronise.bind(targetRef.current)) } + }, [targetRef, observerOptions]) - const slides = sliderScroller.querySelectorAll('.ams-image-slider__item') - const slidesArray = Array.from(slides) - const hasIntersected = new Set() - - const observer = new IntersectionObserver( - (observations) => { - for (let observation of observations) { - hasIntersected.add(observation) - observation.target.classList.toggle('ams-image-slider__item--in-view', observation.isIntersecting) - if (observation.isIntersecting) setCurrentSlide(slidesArray.indexOf(observation.target)) - } - }, - { - root: sliderScroller, - threshold: 0.6, - }, - ) - - for (let slide of slidesArray) observer.observe(slide) - }, []) + const synchronise = () => { + updateControls() + } const goToSlide = (element: HTMLElement) => { const sliderScroller = targetRef.current @@ -66,76 +75,103 @@ export const ImageSliderRoot = forwardRef( } const delta = Math.abs(sliderScroller.offsetLeft - element.offsetLeft) - const scrollerPadding = parseInt(getComputedStyle(sliderScroller).getPropertyValue('padding-left'), 10) - const pos = sliderScroller.clientWidth / 2 > delta ? delta - scrollerPadding : delta + scrollerPadding - - sliderScroller.scrollTo(pos, 0) + sliderScroller.scrollTo(delta, 0) } - const goToNextSlide = (element: HTMLElement) => { - const next = element.nextElementSibling as HTMLElement | null + const goToNextSlide = () => { + const sliderScroller = targetRef.current + const element = sliderScroller?.children[currentSlideId] + const next = element?.nextElementSibling as HTMLElement | null if (element === next) return - if (next) { - goToSlide(next) - } else { - console.log('at the end') - // goToSlide(element.parentElement?.firstElementChild as HTMLElement) - } + if (next) goToSlide(next) } - const goToPreviousSlide = (element: HTMLElement) => { - const next = element.previousElementSibling as HTMLElement | null + const goToPreviousSlide = () => { + const sliderScroller = targetRef.current + const element = sliderScroller?.children[currentSlideId] + const next = element?.previousElementSibling as HTMLElement | null if (element === next) return - if (next) { - goToSlide(next) - } else { - console.log('at the start') - // goToSlide(element.parentElement?.lastElementChild as HTMLElement) + if (next) goToSlide(next) + } + + const updateControls = () => { + const sliderScroller = targetRef.current + const { lastElementChild: last, firstElementChild: first } = sliderScroller as HTMLDivElement + + setAtStart(first === sliderScroller?.children[currentSlideId]) + setAtEnd(last === sliderScroller?.children[currentSlideId]) + } + + const createThumbnailImage = (slide: Element) => { + return `url(${(slide as HTMLElement).querySelector('img')?.getAttribute('src')})` + } + + const handleKeyDown: KeyboardEventHandler = (e) => { + if (e.key === 'ArrowRight') { + goToNextSlide() + } else if (e.key === 'ArrowLeft') { + goToPreviousSlide() } } return ( -
- {controls && ( -
- - goToPreviousSlide(document.querySelector('.ams-image-slider__item--in-view') as HTMLElement) - } - /> - goToNextSlide(document.querySelector('.ams-image-slider__item--in-view') as HTMLElement)} - /> -
- )} - - {children} - -
+ +
+ {controls && ( +
+ goToPreviousSlide()} + disabled={atStart} + /> + goToNextSlide()} + disabled={atEnd} + /> +
+ )} + + {children} + + {thumbnails && ( +
+ {Array.from(targetRef.current?.children || []).map((child, index) => ( + + ))} +
+ )} +
+
) }, ) diff --git a/packages/react/src/ImageSlider/ImageSliderContext.tsx b/packages/react/src/ImageSlider/ImageSliderContext.tsx new file mode 100644 index 0000000000..e66409b4e1 --- /dev/null +++ b/packages/react/src/ImageSlider/ImageSliderContext.tsx @@ -0,0 +1,16 @@ +/** + * @license EUPL-1.2+ + * Copyright Gemeente Amsterdam + */ + +import { createContext } from 'react' + +export type ImageSliderContextValue = { + currentSlide: number +} + +const defaultValues: ImageSliderContextValue = { + currentSlide: 0, +} + +export const ImageSliderContext = createContext(defaultValues) diff --git a/packages/react/src/ImageSlider/ImageSliderItem.tsx b/packages/react/src/ImageSlider/ImageSliderItem.tsx index 98b72260b2..414ee52fc9 100644 --- a/packages/react/src/ImageSlider/ImageSliderItem.tsx +++ b/packages/react/src/ImageSlider/ImageSliderItem.tsx @@ -4,17 +4,32 @@ */ import clsx from 'clsx' -import { forwardRef } from 'react' +import { forwardRef, useContext } from 'react' import type { ForwardedRef, HTMLAttributes, PropsWithChildren } from 'react' +import { ImageSliderContext } from './ImageSliderContext' -export type ImageSliderItemProps = PropsWithChildren> +export type ImageSliderItemProps = { + slideId: number +} & PropsWithChildren> export const ImageSliderItem = forwardRef( - ({ children, className, ...restProps }: ImageSliderItemProps, ref: ForwardedRef) => ( -
- {children} -
- ), + ({ children, slideId, className, ...restProps }: ImageSliderItemProps, ref: ForwardedRef) => { + const { currentSlide } = useContext(ImageSliderContext) + const isInView = currentSlide === slideId + + // if (isInView) updateSlide(slideId) + + return ( +
+ {children} +
+ ) + }, ) ImageSliderItem.displayName = 'ImageSlider.Item' diff --git a/packages/react/src/ImageSlider/ImageSliderScroller.tsx b/packages/react/src/ImageSlider/ImageSliderScroller.tsx index 37d7ba1ad9..719d4d2848 100644 --- a/packages/react/src/ImageSlider/ImageSliderScroller.tsx +++ b/packages/react/src/ImageSlider/ImageSliderScroller.tsx @@ -7,9 +7,7 @@ import clsx from 'clsx' import { forwardRef } from 'react' import type { ForwardedRef, HTMLAttributes, PropsWithChildren } from 'react' -export type ImageSliderScrollerProps = { - currentSlide: number -} & PropsWithChildren> +export type ImageSliderScrollerProps = PropsWithChildren> export const ImageSliderScroller = forwardRef( ({ children, className, ...restProps }: ImageSliderScrollerProps, ref: ForwardedRef) => ( diff --git a/storybook/src/components/ImageSlider/ImageSlider.stories.tsx b/storybook/src/components/ImageSlider/ImageSlider.stories.tsx index be2e7ef367..6b1aaf72ff 100644 --- a/storybook/src/components/ImageSlider/ImageSlider.stories.tsx +++ b/storybook/src/components/ImageSlider/ImageSlider.stories.tsx @@ -11,17 +11,17 @@ const meta = { component: ImageSlider, args: { children: [ - + , - + , - + From cd3e6c31fc40332b2331f7b92087d040d3ea519f Mon Sep 17 00:00:00 2001 From: Niels Roozemond Date: Fri, 26 Jul 2024 13:58:53 +0200 Subject: [PATCH 08/62] Thumbnails --- .../components/image-slider/image-slider.scss | 11 +++-- .../react/src/ImageSlider/ImageSlider.tsx | 41 +++++++++++-------- .../src/ImageSlider/ImageSliderContext.tsx | 3 ++ .../react/src/ImageSlider/ImageSliderItem.tsx | 3 +- .../src/ImageSlider/ImageSliderThumbnails.tsx | 31 ++++++++++++++ .../components/ams/image-slider.tokens.json | 5 ++- .../ImageSlider/ImageSlider.stories.tsx | 21 ++++++++-- 7 files changed, 85 insertions(+), 30 deletions(-) create mode 100644 packages/react/src/ImageSlider/ImageSliderThumbnails.tsx diff --git a/packages/css/src/components/image-slider/image-slider.scss b/packages/css/src/components/image-slider/image-slider.scss index d394d957b4..f736a7ffd7 100644 --- a/packages/css/src/components/image-slider/image-slider.scss +++ b/packages/css/src/components/image-slider/image-slider.scss @@ -62,19 +62,18 @@ .ams-image-slider__thumbnails { display: grid; - gap: 1rem; + gap: var(--ams-image-slider-thumbnails-gap); + grid-auto-columns: 80px; grid-auto-flow: column; - grid-column: 1/-1; max-inline-size: 100%; overflow-x: auto; overscroll-behavior-x: contain; - place-self: flex-start; + scroll-snap-type: x mandatory; scrollbar-width: none; } .ams-image-slider__thumbnail { - aspect-ratio: 1 / 1; - background-size: cover; border: none; - min-block-size: 8rem; + padding-inline: 0; + scroll-snap-align: start; } diff --git a/packages/react/src/ImageSlider/ImageSlider.tsx b/packages/react/src/ImageSlider/ImageSlider.tsx index 1fc5d458b7..1490dfc963 100644 --- a/packages/react/src/ImageSlider/ImageSlider.tsx +++ b/packages/react/src/ImageSlider/ImageSlider.tsx @@ -5,11 +5,12 @@ import { ChevronLeftIcon, ChevronRightIcon } from '@amsterdam/design-system-react-icons' import clsx from 'clsx' -import { forwardRef, useEffect, useRef, useState } from 'react' +import { Children, forwardRef, useEffect, useRef, useState } from 'react' import type { ForwardedRef, HTMLAttributes, KeyboardEventHandler, PropsWithChildren } from 'react' import { ImageSliderContext } from './ImageSliderContext' import { ImageSliderItem } from './ImageSliderItem' import { ImageSliderScroller } from './ImageSliderScroller' +import { ImageSliderThumbnails } from './ImageSliderThumbnails' import { IconButton } from '../IconButton' export type ImageSliderProps = { @@ -79,6 +80,13 @@ export const ImageSliderRoot = forwardRef( sliderScroller.scrollTo(delta, 0) } + const goToSlideId = (id: number) => { + const sliderScroller = targetRef.current + const element = sliderScroller?.children[id] as HTMLElement + + goToSlide(element) + } + const goToNextSlide = () => { const sliderScroller = targetRef.current const element = sliderScroller?.children[currentSlideId] @@ -107,10 +115,6 @@ export const ImageSliderRoot = forwardRef( setAtEnd(last === sliderScroller?.children[currentSlideId]) } - const createThumbnailImage = (slide: Element) => { - return `url(${(slide as HTMLElement).querySelector('img')?.getAttribute('src')})` - } - const handleKeyDown: KeyboardEventHandler = (e) => { if (e.key === 'ArrowRight') { goToNextSlide() @@ -120,7 +124,7 @@ export const ImageSliderRoot = forwardRef( } return ( - +
{thumbnails && ( -
- {Array.from(targetRef.current?.children || []).map((child, index) => ( - - ))} -
+ + //
+ // {Array.from(targetRef.current?.children || []).map((child, index) => ( + // + // ))} + //
)}
diff --git a/packages/react/src/ImageSlider/ImageSliderContext.tsx b/packages/react/src/ImageSlider/ImageSliderContext.tsx index e66409b4e1..5a2926503c 100644 --- a/packages/react/src/ImageSlider/ImageSliderContext.tsx +++ b/packages/react/src/ImageSlider/ImageSliderContext.tsx @@ -7,10 +7,13 @@ import { createContext } from 'react' export type ImageSliderContextValue = { currentSlide: number + // eslint-disable-next-line no-unused-vars + goToSlideId: (id: number) => void } const defaultValues: ImageSliderContextValue = { currentSlide: 0, + goToSlideId: () => {}, } export const ImageSliderContext = createContext(defaultValues) diff --git a/packages/react/src/ImageSlider/ImageSliderItem.tsx b/packages/react/src/ImageSlider/ImageSliderItem.tsx index 414ee52fc9..52d69094b3 100644 --- a/packages/react/src/ImageSlider/ImageSliderItem.tsx +++ b/packages/react/src/ImageSlider/ImageSliderItem.tsx @@ -9,6 +9,7 @@ import type { ForwardedRef, HTMLAttributes, PropsWithChildren } from 'react' import { ImageSliderContext } from './ImageSliderContext' export type ImageSliderItemProps = { + /** this ID needs to match the key or order of the slides (starting with 0) */ slideId: number } & PropsWithChildren> @@ -17,8 +18,6 @@ export const ImageSliderItem = forwardRef( const { currentSlide } = useContext(ImageSliderContext) const isInView = currentSlide === slideId - // if (isInView) updateSlide(slideId) - return (
+ +export const ImageSliderThumbnails = forwardRef( + ({ thumbnails, className, ...restProps }: ImageSliderThumbnailsProps, ref: ForwardedRef) => { + const { goToSlideId } = useContext(ImageSliderContext) + return ( +
+ {thumbnails && + thumbnails.map((thumbnail, index) => ( + + ))} +
+ ) + }, +) + +ImageSliderThumbnails.displayName = 'ImageSlider.Thumbnails' diff --git a/proprietary/tokens/src/components/ams/image-slider.tokens.json b/proprietary/tokens/src/components/ams/image-slider.tokens.json index b7f00951b9..415e5b5401 100644 --- a/proprietary/tokens/src/components/ams/image-slider.tokens.json +++ b/proprietary/tokens/src/components/ams/image-slider.tokens.json @@ -1,7 +1,10 @@ { "ams": { "image-slider": { - "gap": { "value": "{ams.space.xs}" } + "gap": { "value": "{ams.space.xs}" }, + "thumbnails": { + "gap": { "value": "{ams.space.xs}" } + } } } } diff --git a/storybook/src/components/ImageSlider/ImageSlider.stories.tsx b/storybook/src/components/ImageSlider/ImageSlider.stories.tsx index 6b1aaf72ff..8d9a9be572 100644 --- a/storybook/src/components/ImageSlider/ImageSlider.stories.tsx +++ b/storybook/src/components/ImageSlider/ImageSlider.stories.tsx @@ -13,17 +13,32 @@ const meta = { children: [ - + , - + , - + + + , + + + + + , + + + + + , + + + , ], From d15603505382868da92fe320ac9574c39ac783b7 Mon Sep 17 00:00:00 2001 From: Niels Roozemond Date: Fri, 26 Jul 2024 14:23:08 +0200 Subject: [PATCH 09/62] Cleaning --- .../components/image-slider/image-slider.scss | 11 +++++--- .../react/src/ImageSlider/ImageSlider.tsx | 25 +++---------------- .../components/ams/image-slider.tokens.json | 13 +++++++++- .../ImageSlider/ImageSlider.stories.tsx | 12 ++++----- 4 files changed, 30 insertions(+), 31 deletions(-) diff --git a/packages/css/src/components/image-slider/image-slider.scss b/packages/css/src/components/image-slider/image-slider.scss index f736a7ffd7..ec795fb6ca 100644 --- a/packages/css/src/components/image-slider/image-slider.scss +++ b/packages/css/src/components/image-slider/image-slider.scss @@ -25,12 +25,12 @@ .ams-image-slider__scroller { align-items: center; display: grid; - gap: var(--ams-image-slider-gap); + gap: var(--ams-image-slider-scroller-gap); grid-auto-columns: 100%; grid-auto-flow: column; grid-column: 1/-1; grid-row: 1; - outline-offset: 2px; + outline-offset: var(--ams-image-slider-scroller-outline-offset); overflow-x: auto; overscroll-behavior-x: contain; scroll-snap-type: x mandatory; @@ -63,7 +63,7 @@ .ams-image-slider__thumbnails { display: grid; gap: var(--ams-image-slider-thumbnails-gap); - grid-auto-columns: 80px; + grid-auto-columns: minmax(58px, 188px); grid-auto-flow: column; max-inline-size: 100%; overflow-x: auto; @@ -74,6 +74,11 @@ .ams-image-slider__thumbnail { border: none; + opacity: var(--ams-image-slider-thumbnails-thumbnail-opacity); padding-inline: 0; scroll-snap-align: start; + + &:has([class*="--in-view"]) { + opacity: var(--ams-image-slider-thumbnails-thumbnail-in-view-opacity); + } } diff --git a/packages/react/src/ImageSlider/ImageSlider.tsx b/packages/react/src/ImageSlider/ImageSlider.tsx index 1490dfc963..a45de00189 100644 --- a/packages/react/src/ImageSlider/ImageSlider.tsx +++ b/packages/react/src/ImageSlider/ImageSlider.tsx @@ -81,15 +81,13 @@ export const ImageSliderRoot = forwardRef( } const goToSlideId = (id: number) => { - const sliderScroller = targetRef.current - const element = sliderScroller?.children[id] as HTMLElement + const element = targetRef.current?.children[id] as HTMLElement goToSlide(element) } const goToNextSlide = () => { - const sliderScroller = targetRef.current - const element = sliderScroller?.children[currentSlideId] + const element = targetRef.current?.children[currentSlideId] const next = element?.nextElementSibling as HTMLElement | null if (element === next) return @@ -98,8 +96,7 @@ export const ImageSliderRoot = forwardRef( } const goToPreviousSlide = () => { - const sliderScroller = targetRef.current - const element = sliderScroller?.children[currentSlideId] + const element = targetRef.current?.children[currentSlideId] const next = element?.previousElementSibling as HTMLElement | null if (element === next) return @@ -160,21 +157,7 @@ export const ImageSliderRoot = forwardRef( {children} - {thumbnails && ( - - //
- // {Array.from(targetRef.current?.children || []).map((child, index) => ( - // - // ))} - //
- )} + {thumbnails && }
) diff --git a/proprietary/tokens/src/components/ams/image-slider.tokens.json b/proprietary/tokens/src/components/ams/image-slider.tokens.json index 415e5b5401..5ee04965db 100644 --- a/proprietary/tokens/src/components/ams/image-slider.tokens.json +++ b/proprietary/tokens/src/components/ams/image-slider.tokens.json @@ -2,8 +2,19 @@ "ams": { "image-slider": { "gap": { "value": "{ams.space.xs}" }, + "scroller": { + "gap": { "value": "{ams.space.xs}" }, + "outline-offset": { "value": "{ams.focus.outline-offset}" } + }, "thumbnails": { - "gap": { "value": "{ams.space.xs}" } + "gap": { "value": "{ams.space.xs}" }, + "thumbnail": { + "opacity": { "value": "40%" }, + "outline-offset": { "value": "{ams.focus.outline-offset}" }, + "in-view": { + "opacity": { "value": "100%" } + } + } } } } diff --git a/storybook/src/components/ImageSlider/ImageSlider.stories.tsx b/storybook/src/components/ImageSlider/ImageSlider.stories.tsx index 8d9a9be572..f4b5c61b41 100644 --- a/storybook/src/components/ImageSlider/ImageSlider.stories.tsx +++ b/storybook/src/components/ImageSlider/ImageSlider.stories.tsx @@ -11,32 +11,32 @@ const meta = { component: ImageSlider, args: { children: [ - + , - + , - + , - + , - + , - + From 23b691089a5454edcfd0b0f2e65bdf7007da7343 Mon Sep 17 00:00:00 2001 From: Niels Roozemond Date: Fri, 26 Jul 2024 14:38:00 +0200 Subject: [PATCH 10/62] Some testing --- .../src/ImageSlider/ImageSlider.test.tsx | 42 +++++++++++++++++++ .../src/ImageSlider/ImageSliderThumbnails.tsx | 8 ++-- 2 files changed, 46 insertions(+), 4 deletions(-) diff --git a/packages/react/src/ImageSlider/ImageSlider.test.tsx b/packages/react/src/ImageSlider/ImageSlider.test.tsx index 0c0668c975..e09906c611 100644 --- a/packages/react/src/ImageSlider/ImageSlider.test.tsx +++ b/packages/react/src/ImageSlider/ImageSlider.test.tsx @@ -2,6 +2,24 @@ import { render } from '@testing-library/react' import { createRef } from 'react' import { ImageSlider } from './ImageSlider' import '@testing-library/jest-dom' +import { AspectRatio } from '../AspectRatio' +import { Image } from '../Image' + +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', () => { it('renders', () => { @@ -13,6 +31,16 @@ describe('Image slider', () => { expect(component).toBeVisible() }) + it('renders children', () => { + const { container } = render( + + child + , + ) + + expect(container).toHaveTextContent('child') + }) + it('renders a design system BEM class name', () => { const { container } = render() @@ -38,4 +66,18 @@ describe('Image slider', () => { expect(ref.current).toBe(component) }) + + it('renders thumbnails', () => { + const { container } = render( + + + + + + + , + ) + + expect(container.querySelector('.ams-image-slider__thumbnail')).toBeInTheDocument() + }) }) diff --git a/packages/react/src/ImageSlider/ImageSliderThumbnails.tsx b/packages/react/src/ImageSlider/ImageSliderThumbnails.tsx index bcb5196e2a..79fe6bd4b1 100644 --- a/packages/react/src/ImageSlider/ImageSliderThumbnails.tsx +++ b/packages/react/src/ImageSlider/ImageSliderThumbnails.tsx @@ -10,20 +10,20 @@ import { ImageSliderContext } from './ImageSliderContext' export type ImageSliderThumbnailsProps = { thumbnails: ReactNode[] -} & HTMLAttributes +} & HTMLAttributes export const ImageSliderThumbnails = forwardRef( - ({ thumbnails, className, ...restProps }: ImageSliderThumbnailsProps, ref: ForwardedRef) => { + ({ thumbnails, className, ...restProps }: ImageSliderThumbnailsProps, ref: ForwardedRef) => { const { goToSlideId } = useContext(ImageSliderContext) return ( -
+
+ ) }, ) From c26634ea246300b901786ee4e11e8d3a8818121d Mon Sep 17 00:00:00 2001 From: Niels Roozemond Date: Fri, 26 Jul 2024 15:09:02 +0200 Subject: [PATCH 11/62] Some documentation and stories --- .../css/src/components/image-slider/README.md | 2 ++ .../ImageSlider/ImageSlider.docs.mdx | 24 ++++++++++++++- .../ImageSlider/ImageSlider.stories.tsx | 30 ++++++++++++++++++- 3 files changed, 54 insertions(+), 2 deletions(-) diff --git a/packages/css/src/components/image-slider/README.md b/packages/css/src/components/image-slider/README.md index f0e70409b2..70635452ec 100644 --- a/packages/css/src/components/image-slider/README.md +++ b/packages/css/src/components/image-slider/README.md @@ -1,3 +1,5 @@ # Image Slider + +An image slider is a carousel of images that can be navigated by clicking on the navigation buttons or by swiping on touch devices. diff --git a/storybook/src/components/ImageSlider/ImageSlider.docs.mdx b/storybook/src/components/ImageSlider/ImageSlider.docs.mdx index 98a001533c..385cc190f9 100644 --- a/storybook/src/components/ImageSlider/ImageSlider.docs.mdx +++ b/storybook/src/components/ImageSlider/ImageSlider.docs.mdx @@ -1,6 +1,6 @@ {/* @license CC0-1.0 */} -import { Controls, Markdown, Meta, Primary } from "@storybook/blocks"; +import { Canvas, Controls, Markdown, Meta, Primary } from "@storybook/blocks"; import * as ImageSliderStories from "./ImageSlider.stories.tsx"; import README from "../../../../packages/css/src/components/image-slider/README.md?raw"; @@ -11,3 +11,25 @@ import README from "../../../../packages/css/src/components/image-slider/README. + +## Examples + +### No Controls + + + +### No Thumbnails + + + +### Snapstop + +Turning on `snapStop` will make the slider stop at each image. You might need to refresh the window to see the effect. + + + +### Native scrollbar + +Turning on `nativeScrollbar` will make the slider use the native scrollbar. You might need this if you disable to other controls. + + diff --git a/storybook/src/components/ImageSlider/ImageSlider.stories.tsx b/storybook/src/components/ImageSlider/ImageSlider.stories.tsx index f4b5c61b41..1b1a13c1a1 100644 --- a/storybook/src/components/ImageSlider/ImageSlider.stories.tsx +++ b/storybook/src/components/ImageSlider/ImageSlider.stories.tsx @@ -44,7 +44,7 @@ const meta = { ], controls: true, scrollbar: false, - snapstop: true, + snapstop: false, thumbnails: true, }, } satisfies Meta @@ -54,3 +54,31 @@ export default meta type Story = StoryObj export const Default: Story = {} + +export const NoControls: Story = { + args: { + controls: false, + }, +} + +export const NoThumbnails: Story = { + args: { + thumbnails: false, + }, +} + +export const Snapstop: Story = { + args: { + snapstop: true, + thumbnails: false, + controls: false, + }, +} + +export const NativeScrollbar: Story = { + args: { + scrollbar: true, + controls: false, + thumbnails: false, + }, +} From cf8b78fefacb42a9867d4b46d8abf586fe410834 Mon Sep 17 00:00:00 2001 From: Niels Roozemond Date: Fri, 26 Jul 2024 15:10:24 +0200 Subject: [PATCH 12/62] REfresh not needed --- storybook/src/components/ImageSlider/ImageSlider.docs.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/storybook/src/components/ImageSlider/ImageSlider.docs.mdx b/storybook/src/components/ImageSlider/ImageSlider.docs.mdx index 385cc190f9..6df27e26b2 100644 --- a/storybook/src/components/ImageSlider/ImageSlider.docs.mdx +++ b/storybook/src/components/ImageSlider/ImageSlider.docs.mdx @@ -24,7 +24,7 @@ import README from "../../../../packages/css/src/components/image-slider/README. ### Snapstop -Turning on `snapStop` will make the slider stop at each image. You might need to refresh the window to see the effect. +Turning on `snapStop` will make the slider stop at each image. From 8fb8b3b3b292682922d18c436d34ab563a4c9b8c Mon Sep 17 00:00:00 2001 From: Niels Roozemond Date: Fri, 26 Jul 2024 15:12:43 +0200 Subject: [PATCH 13/62] Aria roles for thumbnails --- packages/react/src/ImageSlider/ImageSliderThumbnails.tsx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/react/src/ImageSlider/ImageSliderThumbnails.tsx b/packages/react/src/ImageSlider/ImageSliderThumbnails.tsx index 79fe6bd4b1..f8df2245f4 100644 --- a/packages/react/src/ImageSlider/ImageSliderThumbnails.tsx +++ b/packages/react/src/ImageSlider/ImageSliderThumbnails.tsx @@ -19,7 +19,13 @@ export const ImageSliderThumbnails = forwardRef( diff --git a/storybook/src/components/ImageSlider/ImageSlider.stories.tsx b/storybook/src/components/ImageSlider/ImageSlider.stories.tsx index 87000efd3b..d92e7169d9 100644 --- a/storybook/src/components/ImageSlider/ImageSlider.stories.tsx +++ b/storybook/src/components/ImageSlider/ImageSlider.stories.tsx @@ -13,32 +13,32 @@ const meta = { children: [ - + This is a gallery image , - + This is a gallery image , - + This is a gallery image , - + This is a gallery image , - + This is a gallery image , - + This is a gallery image , ], @@ -88,17 +88,17 @@ export const VariousSizes: Story = { children: [ - + This is a gallery image , - + This is a gallery image , - + This is a gallery image , ], From 6844fdfe19d518703527f94d38ef565c40dab4fd Mon Sep 17 00:00:00 2001 From: Niels Roozemond Date: Fri, 26 Jul 2024 15:42:34 +0200 Subject: [PATCH 18/62] Alt tekst (testing screen reader) --- .../ImageSlider/ImageSlider.stories.tsx | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/storybook/src/components/ImageSlider/ImageSlider.stories.tsx b/storybook/src/components/ImageSlider/ImageSlider.stories.tsx index d92e7169d9..99a5f76486 100644 --- a/storybook/src/components/ImageSlider/ImageSlider.stories.tsx +++ b/storybook/src/components/ImageSlider/ImageSlider.stories.tsx @@ -13,32 +13,32 @@ const meta = { children: [ - This is a gallery image + This is gallery image 1 , - This is a gallery image + This is gallery image 2 , - This is a gallery image + This is gallery image 3 , - This is a gallery image + This is gallery image 4 , - This is a gallery image + This is gallery image 5 , - This is a gallery image + This is gallery image 6 , ], @@ -88,17 +88,17 @@ export const VariousSizes: Story = { children: [ - This is a gallery image + This is gallery image 1 , - This is a gallery image + This is gallery image 2 , - This is a gallery image + This is gallery image 3 , ], From c178452bf922d3f41b086f231c0559417068af9f Mon Sep 17 00:00:00 2001 From: Niels Roozemond Date: Fri, 26 Jul 2024 15:46:39 +0200 Subject: [PATCH 19/62] Translatable labels --- .../react/src/ImageSlider/ImageSlider.tsx | 20 ++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/packages/react/src/ImageSlider/ImageSlider.tsx b/packages/react/src/ImageSlider/ImageSlider.tsx index a45de00189..a514e0467a 100644 --- a/packages/react/src/ImageSlider/ImageSlider.tsx +++ b/packages/react/src/ImageSlider/ImageSlider.tsx @@ -22,11 +22,25 @@ export type ImageSliderProps = { snapstop?: boolean /** Show thumbnails */ thumbnails?: boolean + /** Label for the previous button */ + previousLabel?: string + /** Label for the next button */ + nextLabel?: string } & PropsWithChildren> export const ImageSliderRoot = forwardRef( ( - { children, className, controls, scrollbar, snapstop, thumbnails, ...restProps }: ImageSliderProps, + { + children, + className, + controls, + scrollbar, + snapstop, + thumbnails, + previousLabel = 'Vorige', + nextLabel = 'Volgende', + ...restProps + }: ImageSliderProps, ref: ForwardedRef, ) => { const [currentSlideId, setCurrentSlideId] = useState(0) @@ -138,7 +152,7 @@ export const ImageSliderRoot = forwardRef(
goToPreviousSlide()} @@ -146,7 +160,7 @@ export const ImageSliderRoot = forwardRef( /> goToNextSlide()} From f66c42e38a46ef2e288dbd2598a031ce27e513a3 Mon Sep 17 00:00:00 2001 From: Niels Roozemond Date: Fri, 26 Jul 2024 16:04:10 +0200 Subject: [PATCH 20/62] ImageSliderItem test --- .../src/ImageSlider/ImageSliderItem.test.tsx | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 packages/react/src/ImageSlider/ImageSliderItem.test.tsx diff --git a/packages/react/src/ImageSlider/ImageSliderItem.test.tsx b/packages/react/src/ImageSlider/ImageSliderItem.test.tsx new file mode 100644 index 0000000000..1390aadc85 --- /dev/null +++ b/packages/react/src/ImageSlider/ImageSliderItem.test.tsx @@ -0,0 +1,47 @@ +import { render } from '@testing-library/react' +import { createRef } from 'react' +import { ImageSliderItem } from './ImageSliderItem' +import '@testing-library/jest-dom' + +describe('Image slider', () => { + it('renders', () => { + const { container } = render() + + const component = container.querySelector(':only-child') + + expect(component).toBeInTheDocument() + expect(component).toBeVisible() + }) + + it('renders children', () => { + const { container } = render(child) + + expect(container).toHaveTextContent('child') + }) + + it('renders a design system BEM class name', () => { + const { container } = render() + + const component = container.querySelector(':only-child') + + expect(component).toHaveClass('ams-image-slider__item') + }) + + it('renders an additional class name', () => { + const { container } = render() + + const component = container.querySelector(':only-child') + + expect(component).toHaveClass('ams-image-slider__item extra') + }) + + it('supports ForwardRef in React', () => { + const ref = createRef() + + const { container } = render() + + const component = container.querySelector(':only-child') + + expect(ref.current).toBe(component) + }) +}) From 39f5503aba0ac6990c1967a13912abbdf8f28b28 Mon Sep 17 00:00:00 2001 From: Niels Roozemond Date: Fri, 26 Jul 2024 16:05:10 +0200 Subject: [PATCH 21/62] ImageSliderScroller test --- .../ImageSlider/ImageSliderScroller.test.tsx | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 packages/react/src/ImageSlider/ImageSliderScroller.test.tsx diff --git a/packages/react/src/ImageSlider/ImageSliderScroller.test.tsx b/packages/react/src/ImageSlider/ImageSliderScroller.test.tsx new file mode 100644 index 0000000000..093b8a5966 --- /dev/null +++ b/packages/react/src/ImageSlider/ImageSliderScroller.test.tsx @@ -0,0 +1,47 @@ +import { render } from '@testing-library/react' +import { createRef } from 'react' +import { ImageSliderScroller } from './ImageSliderScroller' +import '@testing-library/jest-dom' + +describe('Image slider', () => { + it('renders', () => { + const { container } = render() + + const component = container.querySelector(':only-child') + + expect(component).toBeInTheDocument() + expect(component).toBeVisible() + }) + + it('renders children', () => { + const { container } = render(child) + + expect(container).toHaveTextContent('child') + }) + + it('renders a design system BEM class name', () => { + const { container } = render() + + const component = container.querySelector(':only-child') + + expect(component).toHaveClass('ams-image-slider__scroller') + }) + + it('renders an additional class name', () => { + const { container } = render() + + const component = container.querySelector(':only-child') + + expect(component).toHaveClass('ams-image-slider__scroller extra') + }) + + it('supports ForwardRef in React', () => { + const ref = createRef() + + const { container } = render() + + const component = container.querySelector(':only-child') + + expect(ref.current).toBe(component) + }) +}) From 869ee47ea6ae152b2e38988574c26f7b2eabdc13 Mon Sep 17 00:00:00 2001 From: Niels Roozemond Date: Fri, 26 Jul 2024 16:12:19 +0200 Subject: [PATCH 22/62] Thumbnails test --- .../src/ImageSlider/ImageSliderItem.test.tsx | 2 +- .../ImageSlider/ImageSliderScroller.test.tsx | 2 +- .../ImageSliderThumbnails.test.tsx | 64 +++++++++++++++++++ 3 files changed, 66 insertions(+), 2 deletions(-) create mode 100644 packages/react/src/ImageSlider/ImageSliderThumbnails.test.tsx diff --git a/packages/react/src/ImageSlider/ImageSliderItem.test.tsx b/packages/react/src/ImageSlider/ImageSliderItem.test.tsx index 1390aadc85..416a4cc4d1 100644 --- a/packages/react/src/ImageSlider/ImageSliderItem.test.tsx +++ b/packages/react/src/ImageSlider/ImageSliderItem.test.tsx @@ -3,7 +3,7 @@ import { createRef } from 'react' import { ImageSliderItem } from './ImageSliderItem' import '@testing-library/jest-dom' -describe('Image slider', () => { +describe('Image slider item', () => { it('renders', () => { const { container } = render() diff --git a/packages/react/src/ImageSlider/ImageSliderScroller.test.tsx b/packages/react/src/ImageSlider/ImageSliderScroller.test.tsx index 093b8a5966..3ead4bc366 100644 --- a/packages/react/src/ImageSlider/ImageSliderScroller.test.tsx +++ b/packages/react/src/ImageSlider/ImageSliderScroller.test.tsx @@ -3,7 +3,7 @@ import { createRef } from 'react' import { ImageSliderScroller } from './ImageSliderScroller' import '@testing-library/jest-dom' -describe('Image slider', () => { +describe('Image slider scroller', () => { it('renders', () => { const { container } = render() diff --git a/packages/react/src/ImageSlider/ImageSliderThumbnails.test.tsx b/packages/react/src/ImageSlider/ImageSliderThumbnails.test.tsx new file mode 100644 index 0000000000..dc641f64b5 --- /dev/null +++ b/packages/react/src/ImageSlider/ImageSliderThumbnails.test.tsx @@ -0,0 +1,64 @@ +import { render } from '@testing-library/react' +import { createRef } from 'react' +import { ImageSlider } from './ImageSlider' +import { ImageSliderThumbnails } from './ImageSliderThumbnails' +import '@testing-library/jest-dom' +import { AspectRatio } from '../AspectRatio' +import { Image } from '../Image' + +describe('Image slider thumbnails', () => { + const thumbnails = [ + + + This is gallery image 1 + + , + + + This is gallery image 2 + + , + ] + it('renders', () => { + const { container } = render() + + const component = container.querySelector(':only-child') + + expect(component).toBeInTheDocument() + expect(component).toBeVisible() + }) + + it('renders thumbnails', () => { + const { container } = render() + + const thumbs = container.querySelectorAll('.ams-image-slider__thumbnail') + + expect(thumbs).toHaveLength(thumbnails.length) + }) + + it('renders a design system BEM class name', () => { + const { container } = render() + + const component = container.querySelector(':only-child') + + expect(component).toHaveClass('ams-image-slider__thumbnails') + }) + + it('renders an additional class name', () => { + const { container } = render() + + const component = container.querySelector(':only-child') + + expect(component).toHaveClass('ams-image-slider__thumbnails extra') + }) + + it('supports ForwardRef in React', () => { + const ref = createRef() + + const { container } = render() + + const component = container.querySelector(':only-child') + + expect(ref.current).toBe(component) + }) +}) From 5c7290d4520b3cfd7f4ec775302e550eda2e956f Mon Sep 17 00:00:00 2001 From: Niels Roozemond Date: Mon, 12 Aug 2024 10:17:03 +0200 Subject: [PATCH 23/62] Contrast color prop change --- packages/react/src/ImageSlider/ImageSlider.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/react/src/ImageSlider/ImageSlider.tsx b/packages/react/src/ImageSlider/ImageSlider.tsx index a514e0467a..18fb7c38c6 100644 --- a/packages/react/src/ImageSlider/ImageSlider.tsx +++ b/packages/react/src/ImageSlider/ImageSlider.tsx @@ -153,7 +153,7 @@ export const ImageSliderRoot = forwardRef( goToPreviousSlide()} disabled={atStart} @@ -161,7 +161,7 @@ export const ImageSliderRoot = forwardRef( goToNextSlide()} disabled={atEnd} From 9cd66b52c8b35b01623ef6468c96a19fb394c1b6 Mon Sep 17 00:00:00 2001 From: Niels Roozemond Date: Mon, 12 Aug 2024 10:23:12 +0200 Subject: [PATCH 24/62] Ok now you need two props --- packages/react/src/ImageSlider/ImageSlider.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/react/src/ImageSlider/ImageSlider.tsx b/packages/react/src/ImageSlider/ImageSlider.tsx index 18fb7c38c6..8efe58a7d7 100644 --- a/packages/react/src/ImageSlider/ImageSlider.tsx +++ b/packages/react/src/ImageSlider/ImageSlider.tsx @@ -154,6 +154,7 @@ export const ImageSliderRoot = forwardRef( svg={ChevronLeftIcon} label={previousLabel} contrastColor={true} + inverseColor={true} className="ams-image-slider__control ams-image-slider__control--previous" onClick={() => goToPreviousSlide()} disabled={atStart} @@ -162,6 +163,7 @@ export const ImageSliderRoot = forwardRef( svg={ChevronRightIcon} label={nextLabel} contrastColor={true} + inverseColor={true} className="ams-image-slider__control ams-image-slider__control--next" onClick={() => goToNextSlide()} disabled={atEnd} From be167b01dde155534369dfe771753921c941d264 Mon Sep 17 00:00:00 2001 From: Niels Roozemond Date: Mon, 12 Aug 2024 11:25:27 +0200 Subject: [PATCH 25/62] Thumbnail hover effect --- packages/css/src/components/image-slider/image-slider.scss | 5 +++++ .../tokens/src/components/ams/image-slider.tokens.json | 4 ++++ 2 files changed, 9 insertions(+) diff --git a/packages/css/src/components/image-slider/image-slider.scss b/packages/css/src/components/image-slider/image-slider.scss index 81dfac9c49..d7617a1154 100644 --- a/packages/css/src/components/image-slider/image-slider.scss +++ b/packages/css/src/components/image-slider/image-slider.scss @@ -75,6 +75,7 @@ .ams-image-slider__thumbnail { background-color: var(--ams-image-slider-thumbnails-thumbnail-background-color); border: none; + cursor: var(--ams-image-slider-thumbnails-thumbnail-cursor); opacity: var(--ams-image-slider-thumbnails-thumbnail-opacity); padding-inline: 0; scroll-snap-align: start; @@ -82,4 +83,8 @@ &:has([class*="--in-view"]) { opacity: var(--ams-image-slider-thumbnails-thumbnail-in-view-opacity); } + + &:hover { + opacity: var(--ams-image-slider-thumbnails-thumbnail-hover-opacity); + } } diff --git a/proprietary/tokens/src/components/ams/image-slider.tokens.json b/proprietary/tokens/src/components/ams/image-slider.tokens.json index 8b0e9927b2..c04e70781e 100644 --- a/proprietary/tokens/src/components/ams/image-slider.tokens.json +++ b/proprietary/tokens/src/components/ams/image-slider.tokens.json @@ -10,10 +10,14 @@ "gap": { "value": "{ams.space.xs}" }, "thumbnail": { "background-color": { "value": "transparent" }, + "cursor": { "value": "{ams.action.activate.cursor}" }, "opacity": { "value": "40%" }, "outline-offset": { "value": "{ams.focus.outline-offset}" }, "in-view": { "opacity": { "value": "100%" } + }, + "hover": { + "opacity": { "value": "100%" } } } } From 1cab90a2cbef48ac4ba33feb4d5e75015395d700 Mon Sep 17 00:00:00 2001 From: Niels Roozemond Date: Mon, 12 Aug 2024 13:16:07 +0200 Subject: [PATCH 26/62] Getting used to new button props --- packages/react/src/ImageSlider/ImageSlider.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/react/src/ImageSlider/ImageSlider.tsx b/packages/react/src/ImageSlider/ImageSlider.tsx index 8efe58a7d7..dec2b7d80a 100644 --- a/packages/react/src/ImageSlider/ImageSlider.tsx +++ b/packages/react/src/ImageSlider/ImageSlider.tsx @@ -153,7 +153,6 @@ export const ImageSliderRoot = forwardRef( goToPreviousSlide()} @@ -162,7 +161,6 @@ export const ImageSliderRoot = forwardRef( goToNextSlide()} From 308a999d858321dc9367b15e46642e0f19c702b5 Mon Sep 17 00:00:00 2001 From: Niels Roozemond Date: Thu, 12 Sep 2024 16:48:48 +0200 Subject: [PATCH 27/62] Removed various sizes story --- .../ImageSlider/ImageSlider.docs.mdx | 6 --- .../ImageSlider/ImageSlider.stories.tsx | 48 +++++++++---------- 2 files changed, 22 insertions(+), 32 deletions(-) diff --git a/storybook/src/components/ImageSlider/ImageSlider.docs.mdx b/storybook/src/components/ImageSlider/ImageSlider.docs.mdx index 4ddfa5c3eb..adc3b9ad9a 100644 --- a/storybook/src/components/ImageSlider/ImageSlider.docs.mdx +++ b/storybook/src/components/ImageSlider/ImageSlider.docs.mdx @@ -33,9 +33,3 @@ Setting `snapStop` to false will make the slider not stop at each image. Turning on `nativeScrollbar` will make the slider use the native scrollbar. You might need this if you disable to other controls. - -### Various Sizes - -When you mix images of different sizes, the slider will adjust to the size of the largest image. - - diff --git a/storybook/src/components/ImageSlider/ImageSlider.stories.tsx b/storybook/src/components/ImageSlider/ImageSlider.stories.tsx index 99a5f76486..26d658b731 100644 --- a/storybook/src/components/ImageSlider/ImageSlider.stories.tsx +++ b/storybook/src/components/ImageSlider/ImageSlider.stories.tsx @@ -5,6 +5,7 @@ import { AspectRatio, Image, ImageSlider } from '@amsterdam/design-system-react/src' import { Meta, StoryObj } from '@storybook/react' +import React from 'react' const meta = { title: 'Components/Media/Image Slider', @@ -36,11 +37,6 @@ const meta = { This is gallery image 5 , - - - This is gallery image 6 - - , ], controls: true, scrollbar: false, @@ -83,24 +79,24 @@ export const NativeScrollbar: Story = { }, } -export const VariousSizes: Story = { - args: { - children: [ - - - This is gallery image 1 - - , - - - This is gallery image 2 - - , - - - This is gallery image 3 - - , - ], - }, -} +// export const VariousSizes: Story = { +// args: { +// children: [ +// +// +// This is gallery image 1 +// +// , +// +// +// This is gallery image 2 +// +// , +// +// +// This is gallery image 3 +// +// , +// ], +// }, +// } From f11504fcac31af32659597aa2cfb87fd9e5aa0ce Mon Sep 17 00:00:00 2001 From: Niels Roozemond Date: Fri, 13 Sep 2024 11:37:45 +0200 Subject: [PATCH 28/62] Screen decorator for max-width --- .../src/components/ImageSlider/ImageSlider.stories.tsx | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/storybook/src/components/ImageSlider/ImageSlider.stories.tsx b/storybook/src/components/ImageSlider/ImageSlider.stories.tsx index 26d658b731..5ac4bbd7bc 100644 --- a/storybook/src/components/ImageSlider/ImageSlider.stories.tsx +++ b/storybook/src/components/ImageSlider/ImageSlider.stories.tsx @@ -3,7 +3,7 @@ * Copyright Gemeente Amsterdam */ -import { AspectRatio, Image, ImageSlider } from '@amsterdam/design-system-react/src' +import { AspectRatio, Image, ImageSlider, Screen } from '@amsterdam/design-system-react/src' import { Meta, StoryObj } from '@storybook/react' import React from 'react' @@ -43,6 +43,13 @@ const meta = { snapstop: true, thumbnails: true, }, + decorators: [ + (Story) => ( + + + + ), + ], } satisfies Meta export default meta From d877ef1c85a0645bbab095922ef9211c32cb27b9 Mon Sep 17 00:00:00 2001 From: Niels Roozemond Date: Fri, 20 Sep 2024 09:22:21 +0200 Subject: [PATCH 29/62] Removed outline in favor of opacity effect --- packages/css/src/components/image-slider/image-slider.scss | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/css/src/components/image-slider/image-slider.scss b/packages/css/src/components/image-slider/image-slider.scss index d7617a1154..22910dd0e3 100644 --- a/packages/css/src/components/image-slider/image-slider.scss +++ b/packages/css/src/components/image-slider/image-slider.scss @@ -84,7 +84,9 @@ opacity: var(--ams-image-slider-thumbnails-thumbnail-in-view-opacity); } - &:hover { + &:hover, + &:focus { opacity: var(--ams-image-slider-thumbnails-thumbnail-hover-opacity); + outline: 0; } } From d2bd221b552a7e5c8d21c1592bb554522ba93e78 Mon Sep 17 00:00:00 2001 From: Niels Roozemond Date: Fri, 20 Sep 2024 11:46:05 +0200 Subject: [PATCH 30/62] Refactored to use an object --- .../components/image-slider/image-slider.scss | 34 +++++++----- .../react/src/ImageSlider/ImageSlider.tsx | 32 ++++++++--- .../src/ImageSlider/ImageSliderThumbnails.tsx | 27 ++++++---- .../ImageSlider/ImageSlider.stories.tsx | 54 +++++++++---------- 4 files changed, 92 insertions(+), 55 deletions(-) diff --git a/packages/css/src/components/image-slider/image-slider.scss b/packages/css/src/components/image-slider/image-slider.scss index 22910dd0e3..4009e4d08d 100644 --- a/packages/css/src/components/image-slider/image-slider.scss +++ b/packages/css/src/components/image-slider/image-slider.scss @@ -63,30 +63,38 @@ .ams-image-slider__thumbnails { display: grid; gap: var(--ams-image-slider-thumbnails-gap); - grid-auto-columns: minmax(58px, 188px); - grid-auto-flow: column; + grid-template-columns: repeat(5, 1fr); max-inline-size: 100%; - overflow-x: auto; - overscroll-behavior-x: contain; - scroll-snap-type: x mandatory; - scrollbar-width: none; + + // grid-auto-flow: column; + // grid-auto-columns: minmax(58px, 188px); + // overflow-x: auto; + // overscroll-behavior-x: contain; + // scroll-snap-type: x mandatory; + // scrollbar-width: none; } .ams-image-slider__thumbnail { background-color: var(--ams-image-slider-thumbnails-thumbnail-background-color); + 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; - &:has([class*="--in-view"]) { - opacity: var(--ams-image-slider-thumbnails-thumbnail-in-view-opacity); - } - - &:hover, - &:focus { + &:hover { opacity: var(--ams-image-slider-thumbnails-thumbnail-hover-opacity); - outline: 0; } + + // &:focus-visible { + // outline: 4px solid blue; + // } +} + +.ams-image-slider__thumbnail--in-view { + opacity: var(--ams-image-slider-thumbnails-thumbnail-in-view-opacity); } diff --git a/packages/react/src/ImageSlider/ImageSlider.tsx b/packages/react/src/ImageSlider/ImageSlider.tsx index dec2b7d80a..62ec7c8a0f 100644 --- a/packages/react/src/ImageSlider/ImageSlider.tsx +++ b/packages/react/src/ImageSlider/ImageSlider.tsx @@ -5,15 +5,26 @@ import { ChevronLeftIcon, ChevronRightIcon } from '@amsterdam/design-system-react-icons' import clsx from 'clsx' -import { Children, forwardRef, useEffect, useRef, useState } from 'react' -import type { ForwardedRef, HTMLAttributes, KeyboardEventHandler, PropsWithChildren } from 'react' +import { forwardRef, useEffect, useRef, useState } from 'react' +import type { ForwardedRef, HTMLAttributes, KeyboardEventHandler } from 'react' import { ImageSliderContext } from './ImageSliderContext' import { ImageSliderItem } from './ImageSliderItem' import { ImageSliderScroller } from './ImageSliderScroller' import { ImageSliderThumbnails } from './ImageSliderThumbnails' +import { Ratio } from '../AspectRatio' import { IconButton } from '../IconButton' +import { Image } from '../Image/Image' + +export type SlideProps = { + src: string + ratio: Ratio + srcSet?: Array + sizes?: string + alt?: string +} export type ImageSliderProps = { + slides: SlideProps[] /** Show navigation controls */ controls?: boolean /** Show native scrollbar inside gallery */ @@ -26,19 +37,22 @@ export type ImageSliderProps = { previousLabel?: string /** Label for the next button */ nextLabel?: string -} & PropsWithChildren> + /** Label for the image if you need to translate the alt text */ + imageLabel?: string +} & HTMLAttributes export const ImageSliderRoot = forwardRef( ( { - children, className, + slides, controls, scrollbar, snapstop, thumbnails, previousLabel = 'Vorige', nextLabel = 'Volgende', + imageLabel = 'Afbeelding', ...restProps }: ImageSliderProps, ref: ForwardedRef, @@ -169,9 +183,15 @@ export const ImageSliderRoot = forwardRef(
)} - {children} + {slides.map((slide, index) => ( + + {slide.alt} + + ))} - {thumbnails && } + {thumbnails && ( + + )} ) diff --git a/packages/react/src/ImageSlider/ImageSliderThumbnails.tsx b/packages/react/src/ImageSlider/ImageSliderThumbnails.tsx index b9fa1afe0e..fd267f0331 100644 --- a/packages/react/src/ImageSlider/ImageSliderThumbnails.tsx +++ b/packages/react/src/ImageSlider/ImageSliderThumbnails.tsx @@ -5,30 +5,39 @@ import clsx from 'clsx' import { forwardRef, useContext } from 'react' -import type { ForwardedRef, HTMLAttributes, ReactNode } from 'react' +import type { ForwardedRef, HTMLAttributes } from 'react' +import { SlideProps } from './ImageSlider' import { ImageSliderContext } from './ImageSliderContext' export type ImageSliderThumbnailsProps = { - thumbnails: ReactNode[] + thumbnails: SlideProps[] + imageLabel?: string + currentSlide?: number } & HTMLAttributes export const ImageSliderThumbnails = forwardRef( - ({ thumbnails, className, ...restProps }: ImageSliderThumbnailsProps, ref: ForwardedRef) => { + ( + { thumbnails, imageLabel, currentSlide, className, ...restProps }: ImageSliderThumbnailsProps, + ref: ForwardedRef, + ) => { const { goToSlideId } = useContext(ImageSliderContext) return ( - diff --git a/storybook/src/components/ImageSlider/ImageSlider.stories.tsx b/storybook/src/components/ImageSlider/ImageSlider.stories.tsx index ebad6462e1..e40107162a 100644 --- a/storybook/src/components/ImageSlider/ImageSlider.stories.tsx +++ b/storybook/src/components/ImageSlider/ImageSlider.stories.tsx @@ -14,33 +14,32 @@ const meta = { slides: [ { src: 'https://picsum.photos/id/122/1280/720', - alt: 'This is gallery image 1', + alt: 'Bridge', ratio: 'x-wide', }, { src: 'https://picsum.photos/id/101/1280/720', - alt: 'This is gallery image 2', + alt: 'Bunker', ratio: 'x-wide', }, { src: 'https://picsum.photos/id/153/1280/720', - alt: 'This is gallery image 3', + alt: 'Chairs', ratio: 'x-wide', }, { src: 'https://picsum.photos/id/159/1280/720', - alt: 'This is gallery image 4', + alt: 'Droplet', ratio: 'x-wide', }, { src: 'https://picsum.photos/id/123/1280/720', - alt: 'This is gallery image 5', + alt: 'Dew', ratio: 'x-wide', }, ], controls: true, scrollbar: false, - snapstop: true, thumbnails: true, }, decorators: [ @@ -72,7 +71,6 @@ export const NoThumbnails: Story = { export const NoSnapstop: Story = { args: { - snapstop: false, thumbnails: false, controls: false, }, From 6c26a17eccd1c9603172d5076132ffd8a4b271f3 Mon Sep 17 00:00:00 2001 From: Niels Roozemond Date: Mon, 23 Sep 2024 11:02:04 +0200 Subject: [PATCH 33/62] JSdoc prop description --- packages/react/src/ImageSlider/ImageSlider.tsx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/react/src/ImageSlider/ImageSlider.tsx b/packages/react/src/ImageSlider/ImageSlider.tsx index ffd4bb849d..106f383b74 100644 --- a/packages/react/src/ImageSlider/ImageSlider.tsx +++ b/packages/react/src/ImageSlider/ImageSlider.tsx @@ -17,13 +17,19 @@ import { Image } from '../Image/Image' export type SlideProps = { src: string + /** Prove a URL to the image */ ratio: Ratio + /** Define an aspect ratio to use on all images */ + alt: string + /** Describe to image */ srcSet?: Array + /** Provide a src-set array to use responsive images */ sizes?: string - alt?: string + /** Provide a sizes attribute to each image */ } export type ImageSliderProps = { + /** An array of images to display */ slides: SlideProps[] /** Show navigation controls */ controls?: boolean From 1c336c8bccb9600e608851abb46bcc308ce1a836 Mon Sep 17 00:00:00 2001 From: Niels Roozemond Date: Mon, 23 Sep 2024 11:27:22 +0200 Subject: [PATCH 34/62] Source set images story --- .../react/src/ImageSlider/ImageSlider.tsx | 4 +- .../ImageSlider/ImageSlider.docs.mdx | 12 ++-- .../ImageSlider/ImageSlider.stories.tsx | 55 +++++++++---------- 3 files changed, 35 insertions(+), 36 deletions(-) diff --git a/packages/react/src/ImageSlider/ImageSlider.tsx b/packages/react/src/ImageSlider/ImageSlider.tsx index 106f383b74..ff775593fa 100644 --- a/packages/react/src/ImageSlider/ImageSlider.tsx +++ b/packages/react/src/ImageSlider/ImageSlider.tsx @@ -22,7 +22,7 @@ export type SlideProps = { /** Define an aspect ratio to use on all images */ alt: string /** Describe to image */ - srcSet?: Array + srcSet?: string /** Provide a src-set array to use responsive images */ sizes?: string /** Provide a sizes attribute to each image */ @@ -226,7 +226,7 @@ export const ImageSliderRoot = forwardRef( {slides.map((slide, index) => ( - {slide.alt} + {slide.alt} ))} diff --git a/storybook/src/components/ImageSlider/ImageSlider.docs.mdx b/storybook/src/components/ImageSlider/ImageSlider.docs.mdx index adc3b9ad9a..376623c740 100644 --- a/storybook/src/components/ImageSlider/ImageSlider.docs.mdx +++ b/storybook/src/components/ImageSlider/ImageSlider.docs.mdx @@ -22,14 +22,14 @@ import README from "../../../../packages/css/src/components/image-slider/README. -### Snapstop - -Setting `snapStop` to false will make the slider not stop at each image. - - - ### Native scrollbar Turning on `nativeScrollbar` will make the slider use the native scrollbar. You might need this if you disable to other controls. + +### Source set images + +To use source set images, you need to pass an array of objects with `srcSet` and `src` properties. The `srcSet` property should be a string with the image URLs separated by commas. The `src` property should be the URL of the image to use as a fallback. + + diff --git a/storybook/src/components/ImageSlider/ImageSlider.stories.tsx b/storybook/src/components/ImageSlider/ImageSlider.stories.tsx index e40107162a..377e9939a8 100644 --- a/storybook/src/components/ImageSlider/ImageSlider.stories.tsx +++ b/storybook/src/components/ImageSlider/ImageSlider.stories.tsx @@ -69,13 +69,6 @@ export const NoThumbnails: Story = { }, } -export const NoSnapstop: Story = { - args: { - thumbnails: false, - controls: false, - }, -} - export const NativeScrollbar: Story = { args: { scrollbar: true, @@ -84,24 +77,30 @@ export const NativeScrollbar: Story = { }, } -// export const VariousSizes: Story = { -// args: { -// children: [ -// -// -// This is gallery image 1 -// -// , -// -// -// This is gallery image 2 -// -// , -// -// -// This is gallery image 3 -// -// , -// ], -// }, -// } +export const SourceSetImages: Story = { + args: { + slides: [ + { + src: 'https://picsum.photos/id/122/640/360', + srcSet: 'https://picsum.photos/id/122/640/360 640w, https://picsum.photos/id/122/1280/720 1280w', + sizes: '(max-width: 36rem) 640px, 50vw', + alt: 'Bridge', + ratio: 'x-wide', + }, + { + src: 'https://picsum.photos/id/101/640/360', + srcSet: 'https://picsum.photos/id/101/640/360 640w, https://picsum.photos/id/101/1280/720 1280w', + sizes: '(max-width: 36rem) 640px, 50vw', + alt: 'Bunker', + ratio: 'x-wide', + }, + { + src: 'https://picsum.photos/id/153/640/360', + srcSet: 'https://picsum.photos/id/153/640/360 640w, https://picsum.photos/id/153/1280/720 1280w', + sizes: '(max-width: 36rem) 640px, 50vw', + alt: 'Chairs', + ratio: 'x-wide', + }, + ], + }, +} From e1e272a28c768cbc0130548536b54bb753af8eb1 Mon Sep 17 00:00:00 2001 From: Niels Roozemond Date: Fri, 27 Sep 2024 16:19:27 +0200 Subject: [PATCH 35/62] Always use thumbnails and never show scroller --- .../components/image-slider/image-slider.scss | 24 ++------ .../react/src/ImageSlider/ImageSlider.tsx | 59 ++++++++----------- 2 files changed, 30 insertions(+), 53 deletions(-) diff --git a/packages/css/src/components/image-slider/image-slider.scss b/packages/css/src/components/image-slider/image-slider.scss index 8fbbbe7e1e..c1d5b7d6c7 100644 --- a/packages/css/src/components/image-slider/image-slider.scss +++ b/packages/css/src/components/image-slider/image-slider.scss @@ -31,18 +31,15 @@ overflow-x: auto; overscroll-behavior-x: contain; scroll-snap-type: x mandatory; - - @media not (prefers-reduced-motion) { - scroll-behavior: smooth; - } -} - -.ams-image-slider:not(.ams-image-slider--scrollbar) .ams-image-slider__scroller { scrollbar-width: none; &::-webkit-scrollbar { display: none; } + + @media not (prefers-reduced-motion) { + scroll-behavior: smooth; + } } .ams-image-slider__controls { @@ -61,19 +58,12 @@ display: grid; gap: var(--ams-image-slider-thumbnails-gap); grid-template-columns: repeat(5, 1fr); - list-style: none; max-inline-size: 100%; - - // grid-auto-flow: column; - // grid-auto-columns: minmax(58px, 188px); - // overflow-x: auto; - // overscroll-behavior-x: contain; - // scroll-snap-type: x mandatory; - // scrollbar-width: none; } .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); @@ -87,10 +77,6 @@ &:hover { opacity: var(--ams-image-slider-thumbnails-thumbnail-hover-opacity); } - - // &:focus-visible { - // outline: 4px solid blue; - // } } .ams-image-slider__thumbnail--in-view { diff --git a/packages/react/src/ImageSlider/ImageSlider.tsx b/packages/react/src/ImageSlider/ImageSlider.tsx index ff775593fa..ffda920340 100644 --- a/packages/react/src/ImageSlider/ImageSlider.tsx +++ b/packages/react/src/ImageSlider/ImageSlider.tsx @@ -29,33 +29,27 @@ export type SlideProps = { } export type ImageSliderProps = { - /** An array of images to display */ - slides: SlideProps[] /** Show navigation controls */ controls?: boolean - /** Show native scrollbar inside gallery */ - scrollbar?: boolean - /** Show thumbnails */ - thumbnails?: boolean - /** Label for the previous button */ - previousLabel?: string - /** Label for the next button */ - nextLabel?: string /** Label for the image if you need to translate the alt text */ imageLabel?: string + /** Label for the next button */ + nextLabel?: string + /** Label for the previous button */ + previousLabel?: string + /** An array of images to display */ + slides: SlideProps[] } & HTMLAttributes export const ImageSliderRoot = forwardRef( ( { className, - slides, controls, - scrollbar, - thumbnails, - previousLabel = 'Vorige', - nextLabel = 'Volgende', imageLabel = 'Afbeelding', + nextLabel = 'Volgende', + previousLabel = 'Vorige', + slides, ...restProps }: ImageSliderProps, ref: ForwardedRef, @@ -98,9 +92,8 @@ export const ImageSliderRoot = forwardRef( } if (targetRef.current) { - const slides = targetRef.current.children - const slidesArray = Array.from(slides) - for (let slide of slidesArray) observer.observe(slide) + const slides = Array.from(targetRef.current.children) + for (let slide of slides) observer.observe(slide) targetRef.current.addEventListener('scrollend', synchronise) targetRef.current.addEventListener('keydown', handleKeyDown) @@ -195,13 +188,7 @@ export const ImageSliderRoot = forwardRef( ref={ref} aria-roledescription="carousel" tabIndex={-1} - className={clsx( - 'ams-image-slider', - controls && 'ams-image-slider--controls', - scrollbar && 'ams-image-slider--scrollbar', - thumbnails && 'ams-image-slider--thumbnails', - className, - )} + className={clsx('ams-image-slider', controls && 'ams-image-slider--controls', className)} > {controls && (
@@ -226,18 +213,22 @@ export const ImageSliderRoot = forwardRef( {slides.map((slide, index) => ( - {slide.alt} + {slide.alt} ))} - {thumbnails && ( - - )} +
) From 4c8b2e1fe40dcc988d922580435950dbe8bc99e3 Mon Sep 17 00:00:00 2001 From: Niels Roozemond Date: Fri, 27 Sep 2024 16:26:29 +0200 Subject: [PATCH 36/62] Test fix --- packages/react/src/ImageSlider/ImageSlider.test.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react/src/ImageSlider/ImageSlider.test.tsx b/packages/react/src/ImageSlider/ImageSlider.test.tsx index 9d5efbad09..141c2b2c6b 100644 --- a/packages/react/src/ImageSlider/ImageSlider.test.tsx +++ b/packages/react/src/ImageSlider/ImageSlider.test.tsx @@ -83,7 +83,7 @@ describe('Image slider', () => { }) it('renders thumbnails', () => { - const { container } = render() + const { container } = render() expect(container.querySelector('.ams-image-slider__thumbnail')).toBeInTheDocument() }) From f27100583f5fb33e220056cba2d63734c775e6ca Mon Sep 17 00:00:00 2001 From: Niels Roozemond Date: Mon, 30 Sep 2024 09:32:12 +0200 Subject: [PATCH 37/62] Some comment resolutions --- .../src/ImageSlider/ImageSlider.test.tsx | 18 +++++------ .../react/src/ImageSlider/ImageSlider.tsx | 31 +++++++------------ .../ImageSlider/ImageSlider.docs.mdx | 10 ------ .../ImageSlider/ImageSlider.stories.tsx | 20 ++---------- 4 files changed, 23 insertions(+), 56 deletions(-) diff --git a/packages/react/src/ImageSlider/ImageSlider.test.tsx b/packages/react/src/ImageSlider/ImageSlider.test.tsx index 141c2b2c6b..37c89789ea 100644 --- a/packages/react/src/ImageSlider/ImageSlider.test.tsx +++ b/packages/react/src/ImageSlider/ImageSlider.test.tsx @@ -1,6 +1,6 @@ import { render } from '@testing-library/react' import { createRef } from 'react' -import { ImageSlider, SlideProps } from './ImageSlider' +import { ImageSlider, ImageSliderImageProps } from './ImageSlider' import '@testing-library/jest-dom' const observe = jest.fn() @@ -20,7 +20,7 @@ window.IntersectionObserver = jest.fn(() => ({ })) describe('Image slider', () => { - const slides: SlideProps[] = [ + const images: ImageSliderImageProps[] = [ { src: 'https://picsum.photos/id/122/320/180', alt: 'Bridge', @@ -39,7 +39,7 @@ describe('Image slider', () => { ] it('renders', () => { - const { container } = render() + const { container } = render() const component = container.querySelector(':only-child') @@ -47,8 +47,8 @@ describe('Image slider', () => { expect(component).toBeVisible() }) - it('renders slides', () => { - const { container } = render() + it('renders images', () => { + const { container } = render() const slideElements = container.querySelectorAll('.ams-image-slider__item') const slideArray = Array.from(slideElements) @@ -57,7 +57,7 @@ describe('Image slider', () => { }) it('renders a design system BEM class name', () => { - const { container } = render() + const { container } = render() const component = container.querySelector(':only-child') @@ -65,7 +65,7 @@ describe('Image slider', () => { }) it('renders an additional class name', () => { - const { container } = render() + const { container } = render() const component = container.querySelector(':only-child') @@ -75,7 +75,7 @@ describe('Image slider', () => { it('supports ForwardRef in React', () => { const ref = createRef() - const { container } = render() + const { container } = render() const component = container.querySelector(':only-child') @@ -83,7 +83,7 @@ describe('Image slider', () => { }) it('renders thumbnails', () => { - const { container } = render() + const { container } = render() expect(container.querySelector('.ams-image-slider__thumbnail')).toBeInTheDocument() }) diff --git a/packages/react/src/ImageSlider/ImageSlider.tsx b/packages/react/src/ImageSlider/ImageSlider.tsx index ffda920340..ffb77e0f20 100644 --- a/packages/react/src/ImageSlider/ImageSlider.tsx +++ b/packages/react/src/ImageSlider/ImageSlider.tsx @@ -13,19 +13,12 @@ import { ImageSliderScroller } from './ImageSliderScroller' import { ImageSliderThumbnails } from './ImageSliderThumbnails' import { Ratio } from '../AspectRatio' import { IconButton } from '../IconButton' -import { Image } from '../Image/Image' +import { Image, ImageProps } from '../Image/Image' -export type SlideProps = { - src: string - /** Prove a URL to the image */ - ratio: Ratio +export type ImageSliderImageProps = ImageProps & { /** Define an aspect ratio to use on all images */ - alt: string + ratio: Ratio /** Describe to image */ - srcSet?: string - /** Provide a src-set array to use responsive images */ - sizes?: string - /** Provide a sizes attribute to each image */ } export type ImageSliderProps = { @@ -38,7 +31,7 @@ export type ImageSliderProps = { /** Label for the previous button */ previousLabel?: string /** An array of images to display */ - slides: SlideProps[] + images: ImageSliderImageProps[] } & HTMLAttributes export const ImageSliderRoot = forwardRef( @@ -49,7 +42,7 @@ export const ImageSliderRoot = forwardRef( imageLabel = 'Afbeelding', nextLabel = 'Volgende', previousLabel = 'Vorige', - slides, + images, ...restProps }: ImageSliderProps, ref: ForwardedRef, @@ -61,13 +54,12 @@ export const ImageSliderRoot = forwardRef( const hasIntersected = new Set() const inView = (observations: IntersectionObserverEntry[]) => { - const slides = targetRef.current?.children || [] - const slidesArray = Array.from(slides) + const images = Array.from(targetRef.current?.children || []) for (let observation of observations) { hasIntersected.add(observation) if (observation.isIntersecting) { - setCurrentSlideId(slidesArray.indexOf(observation.target as HTMLElement)) + setCurrentSlideId(images.indexOf(observation.target as HTMLElement)) } } } @@ -92,8 +84,8 @@ export const ImageSliderRoot = forwardRef( } if (targetRef.current) { - const slides = Array.from(targetRef.current.children) - for (let slide of slides) observer.observe(slide) + const images = Array.from(targetRef.current.children) + for (let slide of images) observer.observe(slide) targetRef.current.addEventListener('scrollend', synchronise) targetRef.current.addEventListener('keydown', handleKeyDown) @@ -125,6 +117,7 @@ export const ImageSliderRoot = forwardRef( goToNextSlide() } } + if (event.key === 'ArrowLeft') { const previous = element?.previousElementSibling as HTMLElement | null @@ -211,7 +204,7 @@ export const ImageSliderRoot = forwardRef( )} - {slides.map((slide, index) => ( + {images.map((slide, index) => ( -### No Thumbnails - - - -### Native scrollbar - -Turning on `nativeScrollbar` will make the slider use the native scrollbar. You might need this if you disable to other controls. - - - ### Source set images To use source set images, you need to pass an array of objects with `srcSet` and `src` properties. The `srcSet` property should be a string with the image URLs separated by commas. The `src` property should be the URL of the image to use as a fallback. diff --git a/storybook/src/components/ImageSlider/ImageSlider.stories.tsx b/storybook/src/components/ImageSlider/ImageSlider.stories.tsx index 377e9939a8..d5ec3a70c2 100644 --- a/storybook/src/components/ImageSlider/ImageSlider.stories.tsx +++ b/storybook/src/components/ImageSlider/ImageSlider.stories.tsx @@ -11,7 +11,7 @@ const meta = { title: 'Components/Media/Image Slider', component: ImageSlider, args: { - slides: [ + images: [ { src: 'https://picsum.photos/id/122/1280/720', alt: 'Bridge', @@ -39,8 +39,6 @@ const meta = { }, ], controls: true, - scrollbar: false, - thumbnails: true, }, decorators: [ (Story) => ( @@ -63,23 +61,9 @@ export const NoControls: Story = { }, } -export const NoThumbnails: Story = { - args: { - thumbnails: false, - }, -} - -export const NativeScrollbar: Story = { - args: { - scrollbar: true, - controls: false, - thumbnails: false, - }, -} - export const SourceSetImages: Story = { args: { - slides: [ + images: [ { src: 'https://picsum.photos/id/122/640/360', srcSet: 'https://picsum.photos/id/122/640/360 640w, https://picsum.photos/id/122/1280/720 1280w', From 744327e7b186f0d58838e7764c948816f67e8a96 Mon Sep 17 00:00:00 2001 From: Niels Roozemond Date: Mon, 30 Sep 2024 09:33:51 +0200 Subject: [PATCH 38/62] Rename map --- packages/react/src/ImageSlider/ImageSlider.tsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/react/src/ImageSlider/ImageSlider.tsx b/packages/react/src/ImageSlider/ImageSlider.tsx index ffb77e0f20..3e228265e0 100644 --- a/packages/react/src/ImageSlider/ImageSlider.tsx +++ b/packages/react/src/ImageSlider/ImageSlider.tsx @@ -204,14 +204,14 @@ export const ImageSliderRoot = forwardRef( )} - {images.map((slide, index) => ( + {images.map((image, index) => ( {slide.alt} ))} From ce03bbbfc7879704769a231cc06cb4d3c3258abc Mon Sep 17 00:00:00 2001 From: Niels Roozemond Date: Mon, 30 Sep 2024 09:39:01 +0200 Subject: [PATCH 39/62] Hide controls when primary input is not a mouse --- packages/css/src/components/image-slider/image-slider.scss | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/css/src/components/image-slider/image-slider.scss b/packages/css/src/components/image-slider/image-slider.scss index c1d5b7d6c7..c5716606c9 100644 --- a/packages/css/src/components/image-slider/image-slider.scss +++ b/packages/css/src/components/image-slider/image-slider.scss @@ -47,6 +47,10 @@ grid-column: 1/-1; grid-row: 1; justify-content: space-between; + + @media (pointer: coarse) and (max-width: $ams-breakpoint-wide) { + display: none; + } } .ams-image-slider__control { From 4b9732a10e11338fcea66ca73078c0a9a2115d3e Mon Sep 17 00:00:00 2001 From: Niels Roozemond Date: Mon, 30 Sep 2024 10:05:33 +0200 Subject: [PATCH 40/62] Lower breakpoint, buttons are only too big for phones --- packages/css/src/components/image-slider/image-slider.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/css/src/components/image-slider/image-slider.scss b/packages/css/src/components/image-slider/image-slider.scss index c5716606c9..b7e2e0bbe0 100644 --- a/packages/css/src/components/image-slider/image-slider.scss +++ b/packages/css/src/components/image-slider/image-slider.scss @@ -48,7 +48,7 @@ grid-row: 1; justify-content: space-between; - @media (pointer: coarse) and (max-width: $ams-breakpoint-wide) { + @media (pointer: coarse) and (max-width: $ams-breakpoint-medium) { display: none; } } From a04208dad3efc3664ec3545192d8321f5d8d6f03 Mon Sep 17 00:00:00 2001 From: Niels Roozemond Date: Mon, 30 Sep 2024 15:11:03 +0200 Subject: [PATCH 41/62] Comments --- packages/react/src/ImageSlider/ImageSlider.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/react/src/ImageSlider/ImageSlider.tsx b/packages/react/src/ImageSlider/ImageSlider.tsx index 3e228265e0..896cd8d0e9 100644 --- a/packages/react/src/ImageSlider/ImageSlider.tsx +++ b/packages/react/src/ImageSlider/ImageSlider.tsx @@ -16,9 +16,8 @@ import { IconButton } from '../IconButton' import { Image, ImageProps } from '../Image/Image' export type ImageSliderImageProps = ImageProps & { - /** Define an aspect ratio to use on all images */ + /** Define an aspect ratio to use on the image */ ratio: Ratio - /** Describe to image */ } export type ImageSliderProps = { From 9aca20a657da700af1adaee726e4a88ad7529093 Mon Sep 17 00:00:00 2001 From: Niels Roozemond Date: Mon, 30 Sep 2024 15:13:15 +0200 Subject: [PATCH 42/62] Error fix --- packages/css/src/components/image-slider/image-slider.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/css/src/components/image-slider/image-slider.scss b/packages/css/src/components/image-slider/image-slider.scss index b7e2e0bbe0..36a87b3aa7 100644 --- a/packages/css/src/components/image-slider/image-slider.scss +++ b/packages/css/src/components/image-slider/image-slider.scss @@ -2,6 +2,7 @@ * @license EUPL-1.2+ * Copyright Gemeente Amsterdam */ +@import "../../common/breakpoint"; .ams-image-slider { display: grid; From 7f57b76ac2898b7a263e93c70924cc941aa510c4 Mon Sep 17 00:00:00 2001 From: Niels Roozemond Date: Tue, 1 Oct 2024 11:09:48 +0200 Subject: [PATCH 43/62] Thumbnail functions moved --- .../react/src/ImageSlider/ImageSlider.tsx | 53 +++++++++---------- .../src/ImageSlider/ImageSliderContext.tsx | 4 ++ .../src/ImageSlider/ImageSliderScroller.tsx | 12 +++-- .../src/ImageSlider/ImageSliderThumbnails.tsx | 49 +++++++++++++---- 4 files changed, 76 insertions(+), 42 deletions(-) diff --git a/packages/react/src/ImageSlider/ImageSlider.tsx b/packages/react/src/ImageSlider/ImageSlider.tsx index 896cd8d0e9..f690c3cc0c 100644 --- a/packages/react/src/ImageSlider/ImageSlider.tsx +++ b/packages/react/src/ImageSlider/ImageSlider.tsx @@ -5,7 +5,7 @@ import { ChevronLeftIcon, ChevronRightIcon } from '@amsterdam/design-system-react-icons' import clsx from 'clsx' -import { forwardRef, KeyboardEvent as ReactKeyboardEvent, useEffect, useRef, useState } from 'react' +import { forwardRef, useEffect, useRef, useState } from 'react' import type { ForwardedRef, HTMLAttributes } from 'react' import { ImageSliderContext } from './ImageSliderContext' import { ImageSliderItem } from './ImageSliderItem' @@ -102,32 +102,32 @@ export const ImageSliderRoot = forwardRef( updateControls() } - const handleThumbsKeyDown = (event: ReactKeyboardEvent) => { - const target = event.target as HTMLElement - const element = target.parentElement?.children[currentSlideId] + // const handleThumbsKeyDown = (event: ReactKeyboardEvent) => { + // const target = event.target as HTMLElement + // const element = target.parentElement?.children[currentSlideId] - if (event.key === 'ArrowRight') { - const next = element?.nextElementSibling as HTMLElement | null + // if (event.key === 'ArrowRight') { + // const next = element?.nextElementSibling as HTMLElement | null - if (next === element) return + // if (next === element) return - if (next) { - next.focus() - goToNextSlide() - } - } + // if (next) { + // next.focus() + // goToNextSlide() + // } + // } - if (event.key === 'ArrowLeft') { - const previous = element?.previousElementSibling as HTMLElement | null + // if (event.key === 'ArrowLeft') { + // const previous = element?.previousElementSibling as HTMLElement | null - if (previous === element) return + // if (previous === element) return - if (previous) { - previous.focus() - goToPreviousSlide() - } - } - } + // if (previous) { + // previous.focus() + // goToPreviousSlide() + // } + // } + // } const goToSlide = (element: HTMLElement) => { const sliderScroller = targetRef.current @@ -174,7 +174,9 @@ export const ImageSliderRoot = forwardRef( } return ( - +
))} - +
) diff --git a/packages/react/src/ImageSlider/ImageSliderContext.tsx b/packages/react/src/ImageSlider/ImageSliderContext.tsx index 5a2926503c..8110f6e222 100644 --- a/packages/react/src/ImageSlider/ImageSliderContext.tsx +++ b/packages/react/src/ImageSlider/ImageSliderContext.tsx @@ -7,12 +7,16 @@ import { createContext } from 'react' export type ImageSliderContextValue = { currentSlide: number + goToNextSlide: () => void + goToPreviousSlide: () => void // eslint-disable-next-line no-unused-vars goToSlideId: (id: number) => void } const defaultValues: ImageSliderContextValue = { currentSlide: 0, + goToNextSlide: () => {}, + goToPreviousSlide: () => {}, goToSlideId: () => {}, } diff --git a/packages/react/src/ImageSlider/ImageSliderScroller.tsx b/packages/react/src/ImageSlider/ImageSliderScroller.tsx index 719d4d2848..cbe50c20a1 100644 --- a/packages/react/src/ImageSlider/ImageSliderScroller.tsx +++ b/packages/react/src/ImageSlider/ImageSliderScroller.tsx @@ -10,11 +10,13 @@ import type { ForwardedRef, HTMLAttributes, PropsWithChildren } from 'react' export type ImageSliderScrollerProps = PropsWithChildren> export const ImageSliderScroller = forwardRef( - ({ children, className, ...restProps }: ImageSliderScrollerProps, ref: ForwardedRef) => ( -
- {children} -
- ), + ({ children, className, ...restProps }: ImageSliderScrollerProps, ref: ForwardedRef) => { + return ( +
+ {children} +
+ ) + }, ) ImageSliderScroller.displayName = 'ImageSlider.Scroller' diff --git a/packages/react/src/ImageSlider/ImageSliderThumbnails.tsx b/packages/react/src/ImageSlider/ImageSliderThumbnails.tsx index b17549bf53..45b0c48738 100644 --- a/packages/react/src/ImageSlider/ImageSliderThumbnails.tsx +++ b/packages/react/src/ImageSlider/ImageSliderThumbnails.tsx @@ -4,25 +4,56 @@ */ import clsx from 'clsx' -import { forwardRef, useContext } from 'react' +import { forwardRef, KeyboardEvent, useContext } from 'react' import type { ForwardedRef, HTMLAttributes } from 'react' -import { SlideProps } from './ImageSlider' +import { ImageSliderImageProps } from './ImageSlider' import { ImageSliderContext } from './ImageSliderContext' export type ImageSliderThumbnailsProps = { - thumbnails: SlideProps[] + thumbnails: ImageSliderImageProps[] imageLabel?: string currentSlide?: number } & HTMLAttributes export const ImageSliderThumbnails = forwardRef( - ( - { thumbnails, imageLabel, currentSlide, className, ...restProps }: ImageSliderThumbnailsProps, - ref: ForwardedRef, - ) => { - const { goToSlideId } = useContext(ImageSliderContext) + ({ thumbnails, imageLabel, className, ...restProps }: ImageSliderThumbnailsProps, ref: ForwardedRef) => { + const { currentSlide, goToNextSlide, goToPreviousSlide, goToSlideId } = useContext(ImageSliderContext) + + const handleThumbsKeyDown = (event: KeyboardEvent) => { + const target = event.target as HTMLElement + const element = target.parentElement?.children[currentSlide] + + if (event.key === 'ArrowRight') { + const next = element?.nextElementSibling as HTMLElement | null + + if (next === element) return + + if (next) { + next.focus() + goToNextSlide() + } + } + + if (event.key === 'ArrowLeft') { + const previous = element?.previousElementSibling as HTMLElement | null + + if (previous === element) return + + if (previous) { + previous.focus() + goToPreviousSlide() + } + } + } + return ( -