Skip to content

Commit a12f0bc

Browse files
committed
✨ Add multiple items per slide support for Carousel
1 parent 4d960fd commit a12f0bc

File tree

12 files changed

+84
-27
lines changed

12 files changed

+84
-27
lines changed

src/components/Carousel/Carousel.astro

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ interface Props extends CarouselProps {}
1313
1414
const {
1515
items,
16-
visibleItems = 1,
16+
itemsPerSlide = 1,
1717
subText,
1818
scrollSnap = true,
1919
progress,
@@ -38,6 +38,7 @@ const containerClasses = [
3838
const wrapperClasses = [
3939
styles.wrapper,
4040
effect && styles[effect],
41+
itemsPerSlide > 1 && styles['no-snap'],
4142
wrapperClassName
4243
]
4344
@@ -51,10 +52,10 @@ const paginationClasses = classNames([
5152
!subText && paginationClassName
5253
])
5354
54-
const totalPages = Math.ceil(items / visibleItems)
55+
const totalPages = Math.ceil(items / itemsPerSlide)
5556
const subTextValue = subText?.match(/\{0\}|\{1\}/g) ? subText : undefined
56-
const style = visibleItems > 1
57-
? `--w-slide-width: ${100 / visibleItems}%;`
57+
const style = itemsPerSlide > 1
58+
? `--w-slide-width: calc(${100 / itemsPerSlide}% - 5px);`
5859
: null
5960
---
6061

@@ -67,7 +68,7 @@ const style = visibleItems > 1
6768
<ul
6869
class:list={wrapperClasses}
6970
style={style}
70-
data-visible-items={visibleItems > 1 ? visibleItems : null}
71+
data-visible-items={itemsPerSlide > 1 ? itemsPerSlide : null}
7172
>
7273
<slot />
7374
</ul>
@@ -159,8 +160,16 @@ const style = visibleItems > 1
159160

160161
const progress = target.closest('section').querySelector('.w-carousel-progress')
161162
const progressValue = (100 / (Number(target.dataset.totalPages) - 1))
162-
const visibleItems = Number(carousel.dataset.visibleItems) || 0
163-
const pageIndex = (event.page + visibleItems) - 1
163+
const itemsPerSlide = Number(carousel.dataset.visibleItems) || 1
164+
const totalItems = carousel.children.length
165+
const indexes = Array.from({ length: Math.ceil(totalItems / itemsPerSlide) }, (_, i) => {
166+
return Array.from({ length: itemsPerSlide }, (_, j) => (i * itemsPerSlide) + j)
167+
.filter(index => index < totalItems)
168+
})
169+
170+
const pageIndex = event.direction === 'prev'
171+
? indexes[event.page - 1][0]
172+
: indexes[event.page - 1][indexes[event.page - 1].length - 1]
164173

165174
const liElement = carousel.children[pageIndex]
166175
const subText = event.target.nextElementSibling

src/components/Carousel/Carousel.svelte

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
import type { PaginationEventType } from '../Pagination/pagination'
1515
1616
export let items: SvelteCarouselProps['items'] = 0
17-
export let visibleItems: SvelteCarouselProps['visibleItems'] = 1
17+
export let itemsPerSlide: SvelteCarouselProps['itemsPerSlide'] = 1
1818
export let subText: SvelteCarouselProps['subText'] = ''
1919
export let scrollSnap: SvelteCarouselProps['scrollSnap'] = true
2020
export let progress: SvelteCarouselProps['progress'] = false
@@ -46,6 +46,7 @@
4646
const wrapperClasses = classNames([
4747
styles.wrapper,
4848
effect && styles[effect],
49+
itemsPerSlide! > 1 && styles['no-snap'],
4950
wrapperClassName
5051
])
5152
@@ -59,10 +60,10 @@
5960
!subText && paginationClassName
6061
])
6162
62-
const totalPages = Math.ceil(items / visibleItems!)
63+
const totalPages = Math.ceil(items / itemsPerSlide!)
6364
const subTextValue = subText?.match(/\{0\}|\{1\}/g) ? subText : undefined
64-
const style = visibleItems! > 1
65-
? `--w-slide-width: ${100 / visibleItems!}%;`
65+
const style = itemsPerSlide! > 1
66+
? `--w-slide-width: calc(${100 / itemsPerSlide!}% - 5px);`
6667
: null
6768
6869
const updateValues = () => {
@@ -100,7 +101,16 @@
100101
}, debounce)
101102
102103
const paginate = (event: PaginationEventType) => {
103-
const liElement = carouselItems[event.page - 1] as HTMLLIElement
104+
const indexes = Array.from({ length: Math.ceil(items / itemsPerSlide!) }, (_, i) => {
105+
return Array.from({ length: itemsPerSlide! }, (_, j) => (i * itemsPerSlide!) + j)
106+
.filter(index => index < items)
107+
})
108+
109+
const pageIndex = event.direction === 'prev'
110+
? indexes[event.page - 1][0]
111+
: indexes[event.page - 1][indexes[event.page - 1].length - 1]
112+
113+
const liElement = carouselItems[pageIndex] as HTMLLIElement
104114
105115
liElement.scrollIntoView({ behavior: 'smooth', block: 'nearest' })
106116

src/components/Carousel/Carousel.tsx

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import type { PaginationEventType } from '../Pagination/pagination'
1414

1515
const Carousel = ({
1616
items,
17-
visibleItems = 1,
17+
itemsPerSlide = 1,
1818
subText,
1919
scrollSnap = true,
2020
progress,
@@ -49,6 +49,7 @@ const Carousel = ({
4949
const wrapperClasses = classNames([
5050
styles.wrapper,
5151
effect && styles[effect],
52+
itemsPerSlide! > 1 && styles['no-snap'],
5253
wrapperClassName
5354
])
5455

@@ -62,10 +63,10 @@ const Carousel = ({
6263
!subText && paginationClassName
6364
])
6465

65-
const totalPages = Math.ceil(items / visibleItems!)
66+
const totalPages = Math.ceil(items / itemsPerSlide!)
6667
const subTextValue = subText?.match(/\{0\}|\{1\}/g) ? subText : undefined
67-
const style = visibleItems > 1
68-
? { '--w-slide-width': `${100 / visibleItems}%;` } as React.CSSProperties
68+
const style = itemsPerSlide > 1
69+
? { '--w-slide-width': `calc(${100 / itemsPerSlide!}% - 5px);` } as React.CSSProperties
6970
: undefined
7071

7172
const updateValues = (page: number) => {
@@ -105,7 +106,16 @@ const Carousel = ({
105106
}, debounce)
106107

107108
const paginate = (event: PaginationEventType) => {
108-
const liElement = carouselItems.current[event.page - 1]
109+
const indexes = Array.from({ length: Math.ceil(items / itemsPerSlide!) }, (_, i) => {
110+
return Array.from({ length: itemsPerSlide! }, (_, j) => (i * itemsPerSlide!) + j)
111+
.filter(index => index < items)
112+
})
113+
114+
const pageIndex = event.direction === 'prev'
115+
? indexes[event.page - 1][0]
116+
: indexes[event.page - 1][indexes[event.page - 1].length - 1]
117+
118+
const liElement = carouselItems.current[pageIndex]
109119

110120
liElement.scrollIntoView({ behavior: 'smooth', block: 'nearest' })
111121

src/components/Carousel/carousel.module.scss

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,12 +34,15 @@ body {
3434
filter: saturate(0);
3535
}
3636

37+
&:not(.no-snap) li {
38+
scroll-snap-align: center;
39+
}
40+
3741
li {
3842
@include transition();
3943
@include spacing(m0);
4044
@include layout(flex, h-center);
4145

42-
scroll-snap-align: center;
4346
min-width: var(--w-slide-width);
4447
}
4548
}

src/components/Carousel/carousel.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import type { PaginationProps } from '../Pagination/pagination'
22

33
export type CarouselProps = {
44
items: number
5-
visibleItems?: number
5+
itemsPerSlide?: number
66
subText?: string
77
autoplay?: boolean
88
vertical?: boolean

src/components/Pagination/Pagination.astro

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,7 @@ const generatedPages = pages?.length
150150
const prevPageButton = element.querySelector('[data-page="prev"]') as HTMLButtonElement
151151
const nextPageButton = element.querySelector('[data-page="next"]') as HTMLButtonElement
152152
const currentPageButton = element.querySelector('[data-active]') as HTMLButtonElement
153+
const previousPage = currentPage
153154

154155
const pageElements = Array
155156
.from(pagination.children)
@@ -179,6 +180,7 @@ const generatedPages = pages?.length
179180

180181
dispatch('paginate', {
181182
page: currentPage,
183+
direction: previousPage > currentPage ? 'prev' : 'next',
182184
...(activeButton?.dataset.page && { label: activeButton?.dataset.page }),
183185
target: element,
184186
trusted: event.isTrusted

src/components/Pagination/Pagination.svelte

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@
4545
}))
4646
4747
const paginate = (to: string | number) => {
48+
const previousPage = calculatedCurrentPage
49+
4850
if (to === 'prev') {
4951
calculatedCurrentPage = calculatedCurrentPage - 1
5052
} else if (to === 'next') {
@@ -57,6 +59,7 @@
5759
5860
onChange?.({
5961
page: calculatedCurrentPage,
62+
direction: previousPage > calculatedCurrentPage ? 'prev' : 'next',
6063
...(label && { label })
6164
})
6265
}
@@ -87,7 +90,7 @@
8790
theme={theme}
8891
onClick={!(disablePrevious || (calculatedCurrentPage === 1 && !previousLink))
8992
? () => paginate('prev')
90-
: null
93+
: undefined
9194
}
9295
>
9396
{#if showChevrons || type === 'arrows'}
@@ -107,7 +110,7 @@
107110
theme={theme}
108111
onClick={calculatedCurrentPage !== index + 1
109112
? () => paginate(index + 1)
110-
: null
113+
: undefined
111114
}
112115
>
113116
{page.label}
@@ -129,7 +132,7 @@
129132
theme={theme}
130133
onClick={!disableNext
131134
? () => paginate('next')
132-
: null
135+
: undefined
133136
}
134137
>
135138
{#if type !== 'arrows'}

src/components/Pagination/Pagination.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ const Pagination = ({
5353
}))
5454

5555
const paginate = (to: string | number) => {
56+
const previousPage = calculatedCurrentPage
5657
let currentPage = calculatedCurrentPage
5758

5859
if (to === 'prev') {
@@ -69,6 +70,7 @@ const Pagination = ({
6970

7071
onChange?.({
7172
page: currentPage,
73+
direction: previousPage > currentPage ? 'prev' : 'next',
7274
...(label && { label })
7375
})
7476
}

src/components/Pagination/pagination.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import type { ButtonProps } from '../Button/button'
22

33
export type PaginationEventType = {
44
page: number
5+
direction?: 'prev' | 'next'
56
label?: string | number | undefined
67
}
78

src/pages/components/carousel.astro

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ const sections = getSections({
2424
<ComponentWrapper type="Astro">
2525
<AstroCarousel
2626
items={3}
27-
visibleItems={1}
2827
subText="Slide {0} of {1}"
2928
progress={true}
3029
pagination={{ type: 'dots' }}
@@ -40,7 +39,6 @@ const sections = getSections({
4039
<SvelteCarousel
4140
client:idle
4241
items={3}
43-
visibleItems={1}
4442
subText="Slide {0} of {1}"
4543
progress={true}
4644
pagination={{ type: 'dots' }}
@@ -56,7 +54,6 @@ const sections = getSections({
5654
<ReactCarousel
5755
client:idle
5856
items={3}
59-
visibleItems={1}
6057
subText="Slide {0} of {1}"
6158
progress={true}
6259
pagination={{ type: 'dots' }}
@@ -208,6 +205,28 @@ const sections = getSections({
208205
</li>
209206
</section.component>
210207
</ComponentWrapper>
208+
209+
<ComponentWrapper title="Multiple items per slide (2)">
210+
<section.component items={6} itemsPerSlide={2}>
211+
<li><Box fullWidth={true}>1</Box></li>
212+
<li><Box fullWidth={true}>2</Box></li>
213+
<li><Box fullWidth={true}>3</Box></li>
214+
<li><Box fullWidth={true}>4</Box></li>
215+
<li><Box fullWidth={true}>5</Box></li>
216+
<li><Box fullWidth={true}>6</Box></li>
217+
</section.component>
218+
</ComponentWrapper>
219+
220+
<ComponentWrapper title="Multiple items per slide (3)">
221+
<section.component items={6} itemsPerSlide={3}>
222+
<li><Box fullWidth={true}>1</Box></li>
223+
<li><Box fullWidth={true}>2</Box></li>
224+
<li><Box fullWidth={true}>3</Box></li>
225+
<li><Box fullWidth={true}>4</Box></li>
226+
<li><Box fullWidth={true}>5</Box></li>
227+
<li><Box fullWidth={true}>6</Box></li>
228+
</section.component>
229+
</ComponentWrapper>
211230
</div>
212231
))}
213232
</Layout>

0 commit comments

Comments
 (0)