@faststore/ui 1.12.19 → 1.12.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +18 -0
- package/dist/hooks/useSlider/useSlider.d.ts +3 -1
- package/dist/hooks/useSlider/useSlider.js +5 -10
- package/dist/hooks/useSlider/useSlider.js.map +1 -1
- package/dist/molecules/Bullets/Bullets.js +1 -1
- package/dist/molecules/Bullets/Bullets.js.map +1 -1
- package/dist/molecules/Carousel/Carousel.d.ts +40 -2
- package/dist/molecules/Carousel/Carousel.js +115 -56
- package/dist/molecules/Carousel/Carousel.js.map +1 -1
- package/dist/molecules/Carousel/CarouselItem.d.ts +11 -0
- package/dist/molecules/Carousel/CarouselItem.js +18 -0
- package/dist/molecules/Carousel/CarouselItem.js.map +1 -0
- package/package.json +5 -5
- package/src/hooks/useSlider/useSlider.ts +8 -9
- package/src/molecules/Bullets/Bullets.test.tsx +5 -5
- package/src/molecules/Bullets/Bullets.tsx +3 -3
- package/src/molecules/Bullets/stories/Bullets.mdx +2 -2
- package/src/molecules/Carousel/Carousel.test.tsx +55 -52
- package/src/molecules/Carousel/Carousel.tsx +217 -85
- package/src/molecules/Carousel/CarouselItem.tsx +54 -0
- package/src/molecules/Carousel/stories/Carousel.mdx +7 -7
- package/src/molecules/Carousel/stories/Carousel.stories.tsx +6 -6
|
@@ -24,7 +24,7 @@ describe('Carousel component', () => {
|
|
|
24
24
|
expect(carouselSection).toHaveAttribute('data-fs-carousel')
|
|
25
25
|
})
|
|
26
26
|
|
|
27
|
-
it('should have `data-carousel-track-container` and `data-carousel-track` attributes', () => {
|
|
27
|
+
it('should have `data-fs-carousel-track-container` and `data-fs-carousel-track` attributes', () => {
|
|
28
28
|
const { getByTestId } = render(
|
|
29
29
|
<Carousel>
|
|
30
30
|
<div>Slide 1</div>
|
|
@@ -38,10 +38,10 @@ describe('Carousel component', () => {
|
|
|
38
38
|
const carouselSection = getByTestId('store-carousel')
|
|
39
39
|
|
|
40
40
|
const trackContainer = carouselSection.querySelectorAll(
|
|
41
|
-
'[data-carousel-track-container]'
|
|
41
|
+
'[data-fs-carousel-track-container]'
|
|
42
42
|
)
|
|
43
43
|
|
|
44
|
-
const track = carouselSection.querySelectorAll('[data-carousel-track]')
|
|
44
|
+
const track = carouselSection.querySelectorAll('[data-fs-carousel-track]')
|
|
45
45
|
|
|
46
46
|
expect(trackContainer).toHaveLength(1)
|
|
47
47
|
expect(trackContainer[0]).toBeInTheDocument()
|
|
@@ -50,7 +50,7 @@ describe('Carousel component', () => {
|
|
|
50
50
|
expect(track[0]).toBeInTheDocument()
|
|
51
51
|
})
|
|
52
52
|
|
|
53
|
-
it('should have `data-carousel-controls` and `data-carousel-bullets` attributes', () => {
|
|
53
|
+
it('should have `data-fs-carousel-controls` and `data-fs-carousel-bullets` attributes', () => {
|
|
54
54
|
const { getByTestId } = render(
|
|
55
55
|
<Carousel>
|
|
56
56
|
<div>Slide 1</div>
|
|
@@ -62,11 +62,11 @@ describe('Carousel component', () => {
|
|
|
62
62
|
const carouselSection = getByTestId('store-carousel')
|
|
63
63
|
|
|
64
64
|
const controls = carouselSection.querySelectorAll(
|
|
65
|
-
'[data-carousel-controls]'
|
|
65
|
+
'[data-fs-carousel-controls]'
|
|
66
66
|
)
|
|
67
67
|
|
|
68
68
|
const bulletsContainer = carouselSection.querySelectorAll(
|
|
69
|
-
'[data-carousel-bullets]'
|
|
69
|
+
'[data-fs-carousel-bullets]'
|
|
70
70
|
)
|
|
71
71
|
|
|
72
72
|
expect(controls).toHaveLength(1)
|
|
@@ -76,7 +76,7 @@ describe('Carousel component', () => {
|
|
|
76
76
|
expect(bulletsContainer[0]).toBeInTheDocument()
|
|
77
77
|
})
|
|
78
78
|
|
|
79
|
-
it('should render 5 slides with `data-carousel-item` attributes', () => {
|
|
79
|
+
it('should render 5 slides with `data-fs-carousel-item` attributes', () => {
|
|
80
80
|
const { getByTestId } = render(
|
|
81
81
|
<Carousel infiniteMode={false}>
|
|
82
82
|
<div>Slide 1</div>
|
|
@@ -88,12 +88,12 @@ describe('Carousel component', () => {
|
|
|
88
88
|
)
|
|
89
89
|
|
|
90
90
|
const carouselSection = getByTestId('store-carousel')
|
|
91
|
-
const items = carouselSection.querySelectorAll('[data-carousel-item]')
|
|
91
|
+
const items = carouselSection.querySelectorAll('[data-fs-carousel-item]')
|
|
92
92
|
|
|
93
93
|
expect(items).toHaveLength(5)
|
|
94
94
|
})
|
|
95
95
|
|
|
96
|
-
it('should add `data-visible` attribute to currently visible carousel items', () => {
|
|
96
|
+
it('should add `data-fs-carousel-item-visible` attribute to currently visible carousel items', () => {
|
|
97
97
|
const { getByTestId } = render(
|
|
98
98
|
<Carousel>
|
|
99
99
|
<div>Slide 1</div>
|
|
@@ -106,18 +106,18 @@ describe('Carousel component', () => {
|
|
|
106
106
|
|
|
107
107
|
const carouselSection = getByTestId('store-carousel')
|
|
108
108
|
|
|
109
|
-
const items = carouselSection.querySelectorAll('[data-carousel-item]')
|
|
109
|
+
const items = carouselSection.querySelectorAll('[data-fs-carousel-item]')
|
|
110
110
|
|
|
111
111
|
expect(items).toHaveLength(7)
|
|
112
112
|
|
|
113
|
-
// Only the first item should have `data-visible` attributes
|
|
114
|
-
expect(items[0]).not.toHaveAttribute('data-visible')
|
|
115
|
-
expect(items[1]).toHaveAttribute('data-visible')
|
|
116
|
-
expect(items[2]).not.toHaveAttribute('data-visible')
|
|
117
|
-
expect(items[3]).not.toHaveAttribute('data-visible')
|
|
118
|
-
expect(items[4]).not.toHaveAttribute('data-visible')
|
|
119
|
-
expect(items[5]).not.toHaveAttribute('data-visible')
|
|
120
|
-
expect(items[6]).not.toHaveAttribute('data-visible')
|
|
113
|
+
// Only the first item should have `data-fs-carousel-item-visible` attributes
|
|
114
|
+
expect(items[0]).not.toHaveAttribute('data-fs-carousel-item-visible')
|
|
115
|
+
expect(items[1]).toHaveAttribute('data-fs-carousel-item-visible')
|
|
116
|
+
expect(items[2]).not.toHaveAttribute('data-fs-carousel-item-visible')
|
|
117
|
+
expect(items[3]).not.toHaveAttribute('data-fs-carousel-item-visible')
|
|
118
|
+
expect(items[4]).not.toHaveAttribute('data-fs-carousel-item-visible')
|
|
119
|
+
expect(items[5]).not.toHaveAttribute('data-fs-carousel-item-visible')
|
|
120
|
+
expect(items[6]).not.toHaveAttribute('data-fs-carousel-item-visible')
|
|
121
121
|
})
|
|
122
122
|
|
|
123
123
|
it('should allow users to navigate through pages using the `Arrows` controls', async () => {
|
|
@@ -140,7 +140,9 @@ describe('Carousel component', () => {
|
|
|
140
140
|
const carouselSection = getByTestId('store-carousel')
|
|
141
141
|
const goToNextPageButton = getByLabelText('next')
|
|
142
142
|
const goToPreviousPageButton = getByLabelText('previous')
|
|
143
|
-
const carouselTrack = carouselSection.querySelector(
|
|
143
|
+
const carouselTrack = carouselSection.querySelector(
|
|
144
|
+
'[data-fs-carousel-track]'
|
|
145
|
+
)
|
|
144
146
|
|
|
145
147
|
expect(goToNextPageButton).toBeInTheDocument()
|
|
146
148
|
expect(goToPreviousPageButton).toBeInTheDocument()
|
|
@@ -150,14 +152,14 @@ describe('Carousel component', () => {
|
|
|
150
152
|
fireEvent.click(goToNextPageButton)
|
|
151
153
|
})
|
|
152
154
|
|
|
153
|
-
let items = carouselSection.querySelectorAll('[data-carousel-item]')
|
|
155
|
+
let items = carouselSection.querySelectorAll('[data-fs-carousel-item]')
|
|
154
156
|
|
|
155
157
|
// Only the second item should be visible
|
|
156
|
-
expect(items[0]).not.toHaveAttribute('data-visible')
|
|
157
|
-
expect(items[1]).toHaveAttribute('data-visible')
|
|
158
|
-
expect(items[2]).not.toHaveAttribute('data-visible')
|
|
159
|
-
expect(items[3]).not.toHaveAttribute('data-visible')
|
|
160
|
-
expect(items[4]).not.toHaveAttribute('data-visible')
|
|
158
|
+
expect(items[0]).not.toHaveAttribute('data-fs-carousel-item-visible')
|
|
159
|
+
expect(items[1]).toHaveAttribute('data-fs-carousel-item-visible')
|
|
160
|
+
expect(items[2]).not.toHaveAttribute('data-fs-carousel-item-visible')
|
|
161
|
+
expect(items[3]).not.toHaveAttribute('data-fs-carousel-item-visible')
|
|
162
|
+
expect(items[4]).not.toHaveAttribute('data-fs-carousel-item-visible')
|
|
161
163
|
|
|
162
164
|
// Go from page 1 back to 0
|
|
163
165
|
await act(async () => {
|
|
@@ -178,14 +180,14 @@ describe('Carousel component', () => {
|
|
|
178
180
|
fireEvent.click(goToPreviousPageButton)
|
|
179
181
|
})
|
|
180
182
|
|
|
181
|
-
items = carouselSection.querySelectorAll('[data-carousel-item]')
|
|
183
|
+
items = carouselSection.querySelectorAll('[data-fs-carousel-item]')
|
|
182
184
|
|
|
183
185
|
// Only the first item should be visible
|
|
184
|
-
expect(items[0]).toHaveAttribute('data-visible')
|
|
185
|
-
expect(items[1]).not.toHaveAttribute('data-visible')
|
|
186
|
-
expect(items[2]).not.toHaveAttribute('data-visible')
|
|
187
|
-
expect(items[3]).not.toHaveAttribute('data-visible')
|
|
188
|
-
expect(items[4]).not.toHaveAttribute('data-visible')
|
|
186
|
+
expect(items[0]).toHaveAttribute('data-fs-carousel-item-visible')
|
|
187
|
+
expect(items[1]).not.toHaveAttribute('data-fs-carousel-item-visible')
|
|
188
|
+
expect(items[2]).not.toHaveAttribute('data-fs-carousel-item-visible')
|
|
189
|
+
expect(items[3]).not.toHaveAttribute('data-fs-carousel-item-visible')
|
|
190
|
+
expect(items[4]).not.toHaveAttribute('data-fs-carousel-item-visible')
|
|
189
191
|
})
|
|
190
192
|
|
|
191
193
|
it('should allow users to navigate through pages by clicking on a pagination bullet', async () => {
|
|
@@ -206,8 +208,10 @@ describe('Carousel component', () => {
|
|
|
206
208
|
)
|
|
207
209
|
|
|
208
210
|
const carouselSection = getByTestId('store-carousel')
|
|
209
|
-
const bullets = queryAllByTestId('store-bullets-
|
|
210
|
-
const carouselTrack = carouselSection.querySelector(
|
|
211
|
+
const bullets = queryAllByTestId('store-bullets-bullet')
|
|
212
|
+
const carouselTrack = carouselSection.querySelector(
|
|
213
|
+
'[data-fs-carousel-track]'
|
|
214
|
+
)
|
|
211
215
|
|
|
212
216
|
expect(bullets).toHaveLength(5)
|
|
213
217
|
|
|
@@ -217,14 +221,14 @@ describe('Carousel component', () => {
|
|
|
217
221
|
fireEvent.click(secondPageBullet)
|
|
218
222
|
})
|
|
219
223
|
|
|
220
|
-
let items = carouselSection.querySelectorAll('[data-carousel-item]')
|
|
224
|
+
let items = carouselSection.querySelectorAll('[data-fs-carousel-item]')
|
|
221
225
|
|
|
222
226
|
// Only the second item should be visible
|
|
223
|
-
expect(items[0]).not.toHaveAttribute('data-visible')
|
|
224
|
-
expect(items[1]).toHaveAttribute('data-visible')
|
|
225
|
-
expect(items[2]).not.toHaveAttribute('data-visible')
|
|
226
|
-
expect(items[3]).not.toHaveAttribute('data-visible')
|
|
227
|
-
expect(items[4]).not.toHaveAttribute('data-visible')
|
|
227
|
+
expect(items[0]).not.toHaveAttribute('data-fs-carousel-item-visible')
|
|
228
|
+
expect(items[1]).toHaveAttribute('data-fs-carousel-item-visible')
|
|
229
|
+
expect(items[2]).not.toHaveAttribute('data-fs-carousel-item-visible')
|
|
230
|
+
expect(items[3]).not.toHaveAttribute('data-fs-carousel-item-visible')
|
|
231
|
+
expect(items[4]).not.toHaveAttribute('data-fs-carousel-item-visible')
|
|
228
232
|
|
|
229
233
|
const thirdPageBullet = getByLabelText('Go to page 3')
|
|
230
234
|
|
|
@@ -246,14 +250,14 @@ describe('Carousel component', () => {
|
|
|
246
250
|
fireEvent.click(thirdPageBullet)
|
|
247
251
|
})
|
|
248
252
|
|
|
249
|
-
items = carouselSection.querySelectorAll('[data-carousel-item]')
|
|
253
|
+
items = carouselSection.querySelectorAll('[data-fs-carousel-item]')
|
|
250
254
|
|
|
251
255
|
// Only the 3rd item should be visible
|
|
252
|
-
expect(items[0]).not.toHaveAttribute('data-visible')
|
|
253
|
-
expect(items[1]).not.toHaveAttribute('data-visible')
|
|
254
|
-
expect(items[2]).toHaveAttribute('data-visible')
|
|
255
|
-
expect(items[3]).not.toHaveAttribute('data-visible')
|
|
256
|
-
expect(items[4]).not.toHaveAttribute('data-visible')
|
|
256
|
+
expect(items[0]).not.toHaveAttribute('data-fs-carousel-item-visible')
|
|
257
|
+
expect(items[1]).not.toHaveAttribute('data-fs-carousel-item-visible')
|
|
258
|
+
expect(items[2]).toHaveAttribute('data-fs-carousel-item-visible')
|
|
259
|
+
expect(items[3]).not.toHaveAttribute('data-fs-carousel-item-visible')
|
|
260
|
+
expect(items[4]).not.toHaveAttribute('data-fs-carousel-item-visible')
|
|
257
261
|
})
|
|
258
262
|
|
|
259
263
|
describe('Accessibility', () => {
|
|
@@ -305,7 +309,6 @@ describe('Carousel component', () => {
|
|
|
305
309
|
expect(getAllByRole('tablist')).toHaveLength(1)
|
|
306
310
|
expect(getAllByRole('tab')).toHaveLength(3)
|
|
307
311
|
expect(getAllByRole('tab', { selected: true })).toHaveLength(1)
|
|
308
|
-
expect(getAllByRole('tabpanel')).toHaveLength(3)
|
|
309
312
|
|
|
310
313
|
// Check bullets aria-controls
|
|
311
314
|
expect(
|
|
@@ -351,7 +354,7 @@ describe('Carousel component', () => {
|
|
|
351
354
|
)
|
|
352
355
|
|
|
353
356
|
const carouselTrack = getByTestId('store-carousel').querySelector(
|
|
354
|
-
'[data-carousel-track]'
|
|
357
|
+
'[data-fs-carousel-track]'
|
|
355
358
|
) as Element
|
|
356
359
|
|
|
357
360
|
const tabs = getAllByRole('tab')
|
|
@@ -391,7 +394,7 @@ describe('Carousel component', () => {
|
|
|
391
394
|
)
|
|
392
395
|
|
|
393
396
|
const carouselTrack = getByTestId('store-carousel').querySelector(
|
|
394
|
-
'[data-carousel-track]'
|
|
397
|
+
'[data-fs-carousel-track]'
|
|
395
398
|
) as Element
|
|
396
399
|
|
|
397
400
|
const tabs = getAllByRole('tab')
|
|
@@ -430,7 +433,7 @@ describe('Carousel component', () => {
|
|
|
430
433
|
)
|
|
431
434
|
|
|
432
435
|
const carouselTrack = getByTestId('store-carousel').querySelector(
|
|
433
|
-
'[data-carousel-track]'
|
|
436
|
+
'[data-fs-carousel-track]'
|
|
434
437
|
) as Element
|
|
435
438
|
|
|
436
439
|
const tabs = getAllByRole('tab')
|
|
@@ -466,7 +469,7 @@ describe('Carousel component', () => {
|
|
|
466
469
|
)
|
|
467
470
|
|
|
468
471
|
const carouselTrack = getByTestId('store-carousel').querySelector(
|
|
469
|
-
'[data-carousel-track]'
|
|
472
|
+
'[data-fs-carousel-track]'
|
|
470
473
|
) as Element
|
|
471
474
|
|
|
472
475
|
const tabs = getAllByRole('tab')
|
|
@@ -501,7 +504,7 @@ describe('Carousel component', () => {
|
|
|
501
504
|
)
|
|
502
505
|
|
|
503
506
|
const carouselTrack = getByTestId('store-carousel').querySelector(
|
|
504
|
-
'[data-carousel-track]'
|
|
507
|
+
'[data-fs-carousel-track]'
|
|
505
508
|
) as Element
|
|
506
509
|
|
|
507
510
|
const tabs = getAllByRole('tab')
|
|
@@ -539,7 +542,7 @@ describe('Carousel component', () => {
|
|
|
539
542
|
)
|
|
540
543
|
|
|
541
544
|
const carouselTrack = getByTestId('store-carousel').querySelector(
|
|
542
|
-
'[data-carousel-track]'
|
|
545
|
+
'[data-fs-carousel-track]'
|
|
543
546
|
) as Element
|
|
544
547
|
|
|
545
548
|
const tabs = getAllByRole('tab')
|
|
@@ -1,10 +1,16 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
|
|
1
|
+
import type {
|
|
2
|
+
UIEvent,
|
|
3
|
+
ReactNode,
|
|
4
|
+
CSSProperties,
|
|
5
|
+
KeyboardEvent,
|
|
6
|
+
PropsWithChildren,
|
|
7
|
+
} from 'react'
|
|
8
|
+
import React, { useMemo, useRef } from 'react'
|
|
3
9
|
import type { SwipeableProps } from 'react-swipeable'
|
|
4
10
|
|
|
5
11
|
import { RightArrowIcon, LeftArrowIcon } from './Arrows'
|
|
12
|
+
import CarouselItem from './CarouselItem'
|
|
6
13
|
import useSlider from '../../hooks/useSlider/useSlider'
|
|
7
|
-
import useSlideVisibility from './hooks/useSlideVisibility'
|
|
8
14
|
import Bullets from '../Bullets'
|
|
9
15
|
import IconButton from '../IconButton'
|
|
10
16
|
|
|
@@ -23,16 +29,54 @@ const createTransformValues = (infinite: boolean, totalItems: number) => {
|
|
|
23
29
|
}
|
|
24
30
|
|
|
25
31
|
export interface CarouselProps extends SwipeableProps {
|
|
32
|
+
/**
|
|
33
|
+
* ID of the current instance of the component.
|
|
34
|
+
*/
|
|
26
35
|
id?: string
|
|
36
|
+
/**
|
|
37
|
+
* ID to find this component in testing tools (e.g.: cypress, testing library, and jest).
|
|
38
|
+
*/
|
|
27
39
|
testId?: string
|
|
40
|
+
/**
|
|
41
|
+
* Returns the value of element's class content attribute.
|
|
42
|
+
*/
|
|
43
|
+
className?: string
|
|
44
|
+
/**
|
|
45
|
+
* Whether or not the Carousel is infinite slide/scroll. Only for the `slide` variant.
|
|
46
|
+
* @default true
|
|
47
|
+
*/
|
|
28
48
|
infiniteMode?: boolean
|
|
49
|
+
/**
|
|
50
|
+
* Specifies which navigation elements should be visible.
|
|
51
|
+
* @default complete
|
|
52
|
+
*/
|
|
29
53
|
controls?: 'complete' | 'navigationArrows' | 'paginationBullets'
|
|
54
|
+
/**
|
|
55
|
+
* Specifies the slide transition. Only for the `slide` variant
|
|
56
|
+
*/
|
|
30
57
|
transition?: {
|
|
31
58
|
duration: number
|
|
32
59
|
property: string
|
|
33
60
|
delay?: number
|
|
34
61
|
timing?: string
|
|
35
62
|
}
|
|
63
|
+
/**
|
|
64
|
+
* Specifies the number of items per page.
|
|
65
|
+
* @default 1
|
|
66
|
+
*/
|
|
67
|
+
itemsPerPage?: number
|
|
68
|
+
/**
|
|
69
|
+
* Specifies the Carousel track variant.
|
|
70
|
+
* @default slide
|
|
71
|
+
*/
|
|
72
|
+
variant?: 'slide' | 'scroll'
|
|
73
|
+
/**
|
|
74
|
+
* Specifies the navigation icons.
|
|
75
|
+
*/
|
|
76
|
+
navigationIcons?: {
|
|
77
|
+
left?: ReactNode
|
|
78
|
+
right?: ReactNode
|
|
79
|
+
}
|
|
36
80
|
}
|
|
37
81
|
|
|
38
82
|
function Carousel({
|
|
@@ -44,9 +88,16 @@ function Carousel({
|
|
|
44
88
|
property: 'transform',
|
|
45
89
|
},
|
|
46
90
|
children,
|
|
91
|
+
className,
|
|
47
92
|
id = 'store-carousel',
|
|
93
|
+
variant = 'slide',
|
|
94
|
+
itemsPerPage = 1,
|
|
95
|
+
navigationIcons = undefined,
|
|
48
96
|
...swipeableConfigOverrides
|
|
49
97
|
}: PropsWithChildren<CarouselProps>) {
|
|
98
|
+
const carouselTrackRef = useRef<HTMLUListElement>(null)
|
|
99
|
+
const isSlideCarousel = variant === 'slide'
|
|
100
|
+
const isScrollCarousel = variant === 'scroll'
|
|
50
101
|
const childrenArray = React.Children.toArray(children)
|
|
51
102
|
const childrenCount = childrenArray.length
|
|
52
103
|
const numberOfSlides = infiniteMode ? childrenCount + 2 : childrenCount
|
|
@@ -66,16 +117,11 @@ function Carousel({
|
|
|
66
117
|
)
|
|
67
118
|
|
|
68
119
|
const { handlers, slide, sliderState, sliderDispatch } = useSlider({
|
|
69
|
-
|
|
70
|
-
itemsPerPage: 1,
|
|
120
|
+
itemsPerPage,
|
|
71
121
|
infiniteMode,
|
|
72
|
-
...swipeableConfigOverrides,
|
|
73
|
-
})
|
|
74
|
-
|
|
75
|
-
const { isItemVisible, shouldRenderItem } = useSlideVisibility({
|
|
76
|
-
itemsPerPage: sliderState.itemsPerPage,
|
|
77
|
-
currentSlide: sliderState.currentItem,
|
|
78
122
|
totalItems: childrenCount,
|
|
123
|
+
shouldSlideOnSwipe: isSlideCarousel,
|
|
124
|
+
...swipeableConfigOverrides,
|
|
79
125
|
})
|
|
80
126
|
|
|
81
127
|
const postRenderedSlides =
|
|
@@ -89,6 +135,38 @@ function Carousel({
|
|
|
89
135
|
postRenderedSlides
|
|
90
136
|
)
|
|
91
137
|
|
|
138
|
+
const slideCarouselTrackStyle: CSSProperties = useMemo(
|
|
139
|
+
() => ({
|
|
140
|
+
display: 'flex',
|
|
141
|
+
width: `${numberOfSlides * 100}%`,
|
|
142
|
+
transition: sliderState.sliding ? slidingTransition : undefined,
|
|
143
|
+
transform: `translate3d(${
|
|
144
|
+
transformValues[sliderState.currentPage]
|
|
145
|
+
}%, 0, 0)`,
|
|
146
|
+
}),
|
|
147
|
+
[
|
|
148
|
+
numberOfSlides,
|
|
149
|
+
transformValues,
|
|
150
|
+
slidingTransition,
|
|
151
|
+
sliderState.sliding,
|
|
152
|
+
sliderState.currentPage,
|
|
153
|
+
]
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
const scrollCarouselTrackStyle: CSSProperties = useMemo(
|
|
157
|
+
() => ({
|
|
158
|
+
width: '100%',
|
|
159
|
+
display: 'block',
|
|
160
|
+
overflowX: 'scroll',
|
|
161
|
+
whiteSpace: 'nowrap',
|
|
162
|
+
}),
|
|
163
|
+
[]
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
const carouselTrackStyle =
|
|
167
|
+
((isSlideCarousel && slideCarouselTrackStyle) as CSSProperties) ||
|
|
168
|
+
((isScrollCarousel && scrollCarouselTrackStyle) as CSSProperties)
|
|
169
|
+
|
|
92
170
|
const slidePrevious = () => {
|
|
93
171
|
if (
|
|
94
172
|
sliderState.sliding ||
|
|
@@ -111,16 +189,93 @@ function Carousel({
|
|
|
111
189
|
slide('next', sliderDispatch)
|
|
112
190
|
}
|
|
113
191
|
|
|
192
|
+
const onScrollTrack = (event: UIEvent) => {
|
|
193
|
+
if (isSlideCarousel || itemsPerPage > 1) {
|
|
194
|
+
return
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const itemWidth = Number(event.currentTarget.firstElementChild?.scrollWidth)
|
|
198
|
+
const scrollOffset = event.currentTarget?.scrollLeft
|
|
199
|
+
const formatter = scrollOffset > itemWidth / 2 ? Math.round : Math.floor
|
|
200
|
+
const page = formatter(scrollOffset / itemWidth)
|
|
201
|
+
|
|
202
|
+
slide(page, sliderDispatch)
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const onTransitionTrackEnd = () => {
|
|
206
|
+
sliderDispatch({
|
|
207
|
+
type: 'STOP_SLIDE',
|
|
208
|
+
})
|
|
209
|
+
|
|
210
|
+
if (infiniteMode && sliderState.currentItem >= childrenCount) {
|
|
211
|
+
sliderDispatch({
|
|
212
|
+
type: 'GO_TO_PAGE',
|
|
213
|
+
payload: {
|
|
214
|
+
pageIndex: 0,
|
|
215
|
+
shouldSlide: false,
|
|
216
|
+
},
|
|
217
|
+
})
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
if (infiniteMode && sliderState.currentItem < 0) {
|
|
221
|
+
sliderDispatch({
|
|
222
|
+
type: 'GO_TO_PAGE',
|
|
223
|
+
payload: {
|
|
224
|
+
pageIndex: sliderState.totalPages - 1,
|
|
225
|
+
shouldSlide: false,
|
|
226
|
+
},
|
|
227
|
+
})
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const onScrollPagination = async (
|
|
232
|
+
index: number,
|
|
233
|
+
slideDirection?: 'previous' | 'next'
|
|
234
|
+
) => {
|
|
235
|
+
if (slideDirection === 'previous' && sliderState.currentPage === 0) {
|
|
236
|
+
return
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
if (
|
|
240
|
+
slideDirection === 'next' &&
|
|
241
|
+
sliderState.currentPage === sliderState.totalPages - 1
|
|
242
|
+
) {
|
|
243
|
+
return
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
let scrollOffset
|
|
247
|
+
const carouselItemsWidth = Number(
|
|
248
|
+
carouselTrackRef.current?.firstElementChild?.clientWidth
|
|
249
|
+
)
|
|
250
|
+
|
|
251
|
+
if (itemsPerPage > 1) {
|
|
252
|
+
scrollOffset = index * carouselItemsWidth * itemsPerPage
|
|
253
|
+
} else {
|
|
254
|
+
scrollOffset = index * carouselItemsWidth - carouselItemsWidth * 0.125
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
carouselTrackRef.current?.scrollTo({
|
|
258
|
+
left: scrollOffset,
|
|
259
|
+
behavior: 'smooth',
|
|
260
|
+
})
|
|
261
|
+
|
|
262
|
+
slide(index, sliderDispatch)
|
|
263
|
+
}
|
|
264
|
+
|
|
114
265
|
// accessible behavior for tablist
|
|
115
266
|
const handleBulletsKeyDown = (event: KeyboardEvent) => {
|
|
116
267
|
switch (event.key) {
|
|
117
268
|
case 'ArrowLeft': {
|
|
118
|
-
slidePrevious()
|
|
269
|
+
isSlideCarousel && slidePrevious()
|
|
270
|
+
isScrollCarousel &&
|
|
271
|
+
onScrollPagination(sliderState.currentPage - 1, 'previous')
|
|
119
272
|
break
|
|
120
273
|
}
|
|
121
274
|
|
|
122
275
|
case 'ArrowRight': {
|
|
123
|
-
slideNext()
|
|
276
|
+
isSlideCarousel && slideNext()
|
|
277
|
+
isScrollCarousel &&
|
|
278
|
+
onScrollPagination(sliderState.currentPage + 1, 'next')
|
|
124
279
|
break
|
|
125
280
|
}
|
|
126
281
|
|
|
@@ -142,109 +297,86 @@ function Carousel({
|
|
|
142
297
|
<section
|
|
143
298
|
id={id}
|
|
144
299
|
data-fs-carousel
|
|
300
|
+
className={className}
|
|
145
301
|
data-testid={testId}
|
|
146
302
|
aria-label="carousel"
|
|
147
303
|
aria-roledescription="carousel"
|
|
148
304
|
>
|
|
149
305
|
<div
|
|
150
|
-
data-carousel-track-container
|
|
151
|
-
style={{
|
|
306
|
+
data-fs-carousel-track-container
|
|
307
|
+
style={{
|
|
308
|
+
width: '100%',
|
|
309
|
+
overflow: 'hidden',
|
|
310
|
+
display: isScrollCarousel ? 'block' : undefined,
|
|
311
|
+
}}
|
|
152
312
|
{...handlers}
|
|
153
313
|
>
|
|
154
|
-
<
|
|
155
|
-
data-carousel-track
|
|
156
|
-
style={{
|
|
157
|
-
display: 'flex',
|
|
158
|
-
transition: sliderState.sliding ? slidingTransition : undefined,
|
|
159
|
-
width: `${numberOfSlides * 100}%`,
|
|
160
|
-
transform: `translate3d(${
|
|
161
|
-
transformValues[sliderState.currentPage]
|
|
162
|
-
}%, 0, 0)`,
|
|
163
|
-
}}
|
|
164
|
-
onTransitionEnd={() => {
|
|
165
|
-
sliderDispatch({
|
|
166
|
-
type: 'STOP_SLIDE',
|
|
167
|
-
})
|
|
168
|
-
|
|
169
|
-
if (sliderState.currentItem >= childrenCount) {
|
|
170
|
-
sliderDispatch({
|
|
171
|
-
type: 'GO_TO_PAGE',
|
|
172
|
-
payload: {
|
|
173
|
-
pageIndex: 0,
|
|
174
|
-
shouldSlide: false,
|
|
175
|
-
},
|
|
176
|
-
})
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
if (sliderState.currentItem < 0) {
|
|
180
|
-
sliderDispatch({
|
|
181
|
-
type: 'GO_TO_PAGE',
|
|
182
|
-
payload: {
|
|
183
|
-
pageIndex: sliderState.totalPages - 1,
|
|
184
|
-
shouldSlide: false,
|
|
185
|
-
},
|
|
186
|
-
})
|
|
187
|
-
}
|
|
188
|
-
}}
|
|
314
|
+
<ul
|
|
189
315
|
aria-live="polite"
|
|
316
|
+
ref={carouselTrackRef}
|
|
317
|
+
style={carouselTrackStyle}
|
|
318
|
+
data-fs-carousel-track
|
|
319
|
+
onScroll={onScrollTrack}
|
|
320
|
+
onTransitionEnd={onTransitionTrackEnd}
|
|
190
321
|
>
|
|
191
322
|
{slides.map((currentSlide, idx) => (
|
|
192
|
-
<
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
data-visible={
|
|
200
|
-
isItemVisible(idx - Number(infiniteMode)) || undefined
|
|
201
|
-
}
|
|
323
|
+
<CarouselItem
|
|
324
|
+
index={idx}
|
|
325
|
+
key={String(idx)}
|
|
326
|
+
state={sliderState}
|
|
327
|
+
totalItems={childrenCount}
|
|
328
|
+
infiniteMode={infiniteMode}
|
|
329
|
+
isScrollCarousel={isScrollCarousel}
|
|
202
330
|
>
|
|
203
|
-
{
|
|
204
|
-
|
|
205
|
-
: null}
|
|
206
|
-
</div>
|
|
331
|
+
{currentSlide}
|
|
332
|
+
</CarouselItem>
|
|
207
333
|
))}
|
|
208
|
-
</
|
|
334
|
+
</ul>
|
|
209
335
|
</div>
|
|
210
336
|
|
|
211
337
|
{showNavigationArrows && (
|
|
212
|
-
<div data-carousel-controls>
|
|
338
|
+
<div data-fs-carousel-controls>
|
|
213
339
|
<IconButton
|
|
214
|
-
|
|
215
|
-
data-arrow="left"
|
|
340
|
+
data-fs-carousel-control="left"
|
|
216
341
|
aria-controls={id}
|
|
217
|
-
|
|
218
|
-
icon={<LeftArrowIcon />}
|
|
342
|
+
aria-label="previous"
|
|
343
|
+
icon={navigationIcons?.left ?? <LeftArrowIcon />}
|
|
344
|
+
onClick={() => {
|
|
345
|
+
isSlideCarousel && slidePrevious()
|
|
346
|
+
isScrollCarousel &&
|
|
347
|
+
onScrollPagination(sliderState.currentPage - 1, 'previous')
|
|
348
|
+
}}
|
|
219
349
|
/>
|
|
220
350
|
<IconButton
|
|
221
|
-
|
|
222
|
-
data-arrow="right"
|
|
351
|
+
data-fs-carousel-control="right"
|
|
223
352
|
aria-controls={id}
|
|
224
|
-
|
|
225
|
-
icon={<RightArrowIcon />}
|
|
353
|
+
aria-label="next"
|
|
354
|
+
icon={navigationIcons?.right ?? <RightArrowIcon />}
|
|
355
|
+
onClick={() => {
|
|
356
|
+
isSlideCarousel && slideNext()
|
|
357
|
+
isScrollCarousel &&
|
|
358
|
+
onScrollPagination(sliderState.currentPage + 1, 'next')
|
|
359
|
+
}}
|
|
226
360
|
/>
|
|
227
361
|
</div>
|
|
228
362
|
)}
|
|
229
363
|
|
|
230
364
|
{showPaginationBullets && (
|
|
231
|
-
<div data-carousel-bullets>
|
|
365
|
+
<div data-fs-carousel-bullets>
|
|
232
366
|
<Bullets
|
|
233
367
|
tabIndex={0}
|
|
234
|
-
totalQuantity={childrenCount}
|
|
235
368
|
activeBullet={sliderState.currentPage}
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
369
|
+
totalQuantity={Math.ceil(childrenCount / sliderState.itemsPerPage)}
|
|
370
|
+
onKeyDown={handleBulletsKeyDown}
|
|
371
|
+
onClick={async (_, idx) => {
|
|
372
|
+
isSlideCarousel &&
|
|
373
|
+
!sliderState.sliding &&
|
|
374
|
+
slide(idx, sliderDispatch)
|
|
240
375
|
|
|
241
|
-
|
|
376
|
+
isScrollCarousel && onScrollPagination(idx)
|
|
242
377
|
}}
|
|
378
|
+
onFocus={(event) => event.currentTarget.focus()}
|
|
243
379
|
ariaControlsGenerator={(idx) => `carousel-item-${idx}`}
|
|
244
|
-
onKeyDown={handleBulletsKeyDown}
|
|
245
|
-
onFocus={(event) => {
|
|
246
|
-
event.currentTarget.focus()
|
|
247
|
-
}}
|
|
248
380
|
/>
|
|
249
381
|
</div>
|
|
250
382
|
)}
|