Files
website/src/lib/components/Carousel.svelte
Jesse Winton 6a9fb9fea3 fix
2025-04-02 08:45:22 -04:00

155 lines
3.9 KiB
Svelte

<script lang="ts">
import type { Snippet } from 'svelte';
let carousel: HTMLElement;
interface Props {
size?: 'default' | 'medium' | 'big';
gap?: number;
header: Snippet;
children: Snippet;
}
let { size = 'default', gap = 32, header, children }: Props = $props();
let scroll = 0;
function calculateScrollAmount(prev = false) {
const direction = prev ? -1 : 1;
const carouselSize = carousel?.clientWidth;
const childSize = (carousel.childNodes[0] as HTMLUListElement)?.clientWidth + gap;
scroll = scroll || carouselSize;
const numberOfItems = Math.floor(carouselSize / childSize);
const overflow = scroll % childSize;
const amount = numberOfItems * childSize - overflow * direction;
scroll += amount * direction;
return amount * direction;
}
function next() {
carousel.scrollBy({
left: calculateScrollAmount(),
behavior: 'smooth'
});
}
function prev() {
carousel.scrollBy({
left: calculateScrollAmount(true),
behavior: 'smooth'
});
}
let isEnd = $state(false);
let isStart = $state(true);
function handleScroll() {
isStart = carousel.scrollLeft <= 0;
isEnd = Math.ceil(carousel.scrollLeft + carousel.offsetWidth) >= carousel.scrollWidth;
}
</script>
<div>
<div class="mt-2 flex flex-wrap items-center">
{@render header()}
<div class="nav ml-auto flex items-end gap-3">
<button
class="web-icon-button"
aria-label="Move carousel backward"
disabled={isStart}
onclick={prev}
>
<span class="web-icon-arrow-left" aria-hidden="true"></span>
</button>
<button
class="web-icon-button"
aria-label="Move carousel forward"
disabled={isEnd}
onclick={next}
>
<span class="web-icon-arrow-right" aria-hidden="true"></span>
</button>
</div>
</div>
<div class="carousel-wrapper" data-state={isStart ? 'start' : isEnd ? 'end' : 'middle'}>
<ul
class="web-grid-articles carousel mt-8"
class:is-medium={size === 'medium'}
class:is-big={size === 'big'}
style:gap="{gap}px"
bind:this={carousel}
onscroll={handleScroll}
>
{@render children()}
</ul>
</div>
</div>
<style lang="scss">
.nav {
button {
@media screen and (max-width: 1023.9px) {
display: none !important;
}
}
}
.carousel-wrapper {
position: relative;
&::before,
&::after {
content: '';
position: absolute;
top: 0;
width: 60px;
height: 100%;
transition: ease 250ms;
z-index: 100;
}
&::before {
left: 0;
background: linear-gradient(
to right,
hsl(var(--web-color-background-docs)),
transparent
);
}
&[data-state='start']::before {
opacity: 0;
}
&::after {
right: 0;
background: linear-gradient(
to left,
hsl(var(--web-color-background-docs)),
transparent
);
}
&[data-state='end']::after {
opacity: 0;
}
}
.carousel {
grid-auto-flow: column;
overflow-x: scroll;
scroll-snap-type: x proximity;
scrollbar-width: none;
-ms-overflow-style: none;
&::-webkit-scrollbar {
display: none;
}
}
.carousel :global(li) {
scroll-margin: 48px;
}
</style>