@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.
@@ -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('[data-carousel-track]')
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-item')
210
- const carouselTrack = carouselSection.querySelector('[data-carousel-track]')
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 { KeyboardEvent, PropsWithChildren } from 'react'
2
- import React, { useMemo } from 'react'
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
- totalItems: childrenCount,
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={{ overflow: 'hidden', width: '100%' }}
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
- <div
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
- <div
193
- role="tabpanel"
194
- aria-roledescription="slide"
195
- key={idx}
196
- id={`carousel-item-${idx}`}
197
- data-carousel-item
198
- style={{ width: '100%' }}
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
- {shouldRenderItem(idx - Number(infiniteMode))
204
- ? currentSlide
205
- : null}
206
- </div>
331
+ {currentSlide}
332
+ </CarouselItem>
207
333
  ))}
208
- </div>
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
- aria-label="previous"
215
- data-arrow="left"
340
+ data-fs-carousel-control="left"
216
341
  aria-controls={id}
217
- onClick={slidePrevious}
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
- aria-label="next"
222
- data-arrow="right"
351
+ data-fs-carousel-control="right"
223
352
  aria-controls={id}
224
- onClick={slideNext}
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
- onClick={(_, idx) => {
237
- if (sliderState.sliding) {
238
- return
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
- slide(idx, sliderDispatch)
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
  )}