🚧 Switch to monorepo with Turbo
This commit is contained in:
129
apps/website/src/components/organisms/Carousel.svelte
Normal file
129
apps/website/src/components/organisms/Carousel.svelte
Normal file
@@ -0,0 +1,129 @@
|
||||
<style lang="scss">
|
||||
@import "../../style/organisms/carousel";
|
||||
</style>
|
||||
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte'
|
||||
import { writable } from 'svelte/store'
|
||||
import EmblaCarousel, { type EmblaCarouselType } from 'embla-carousel'
|
||||
// Components
|
||||
import Image from '$components/atoms/Image.svelte'
|
||||
|
||||
export let slides: any
|
||||
|
||||
let carouselEl: HTMLElement
|
||||
let carousel: EmblaCarouselType
|
||||
let currentSlide = 0
|
||||
let arrowDirection: string = null
|
||||
$: isFirstSlide = currentSlide === 0
|
||||
$: isLastSlide = currentSlide === slides.length - 1
|
||||
|
||||
|
||||
/** Navigate to specific slide */
|
||||
const goToSlide = (index: number = 0) => carousel.scrollTo(index)
|
||||
|
||||
/** Move and change arrow direction when moving */
|
||||
const arrowPosition = writable({ x: 0, y: 0 })
|
||||
|
||||
/** Move arrow and define direction on mousemove */
|
||||
const handleArrowMove = (event: MouseEvent) => {
|
||||
const { left, top, width } = carouselEl.getBoundingClientRect()
|
||||
const offsetX = event.clientX - left
|
||||
const offsetY = event.clientY - top
|
||||
|
||||
// Define direction
|
||||
if (isFirstSlide) {
|
||||
arrowDirection = 'next'
|
||||
} else if (isLastSlide) {
|
||||
arrowDirection = 'prev'
|
||||
} else {
|
||||
arrowDirection = offsetX < Math.round(width / 2) ? 'prev' : 'next'
|
||||
}
|
||||
|
||||
// Move arrow
|
||||
arrowPosition.set({
|
||||
x: offsetX - 12,
|
||||
y: offsetY - 56,
|
||||
})
|
||||
}
|
||||
|
||||
/** Go to prev or next slide depending on direction */
|
||||
const handleArrowClick = () => {
|
||||
if (!carousel.clickAllowed()) return
|
||||
|
||||
// Define direction
|
||||
if (isFirstSlide) {
|
||||
arrowDirection = 'next'
|
||||
} else if (isLastSlide) {
|
||||
arrowDirection = 'prev'
|
||||
}
|
||||
|
||||
// Click only if carousel if being dragged
|
||||
if (arrowDirection === 'prev') {
|
||||
carousel.scrollPrev()
|
||||
} else {
|
||||
carousel.scrollNext()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
onMount(() => {
|
||||
// Init carousel
|
||||
carousel = EmblaCarousel(carouselEl, {
|
||||
loop: false
|
||||
})
|
||||
carousel.on('select', () => {
|
||||
currentSlide = carousel.selectedScrollSnap()
|
||||
})
|
||||
|
||||
|
||||
// Destroy
|
||||
return () => {
|
||||
carousel.destroy()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<div class="carousel {$$props.class ?? ''}">
|
||||
{#if slides.length}
|
||||
<div class="carousel__viewport" bind:this={carouselEl}
|
||||
on:mousemove={handleArrowMove}
|
||||
on:click={handleArrowClick}
|
||||
>
|
||||
<div class="carousel__slides">
|
||||
{#each slides as { id, alt }}
|
||||
<Image
|
||||
class="carousel__slide"
|
||||
id={id}
|
||||
sizeKey="product"
|
||||
sizes={{
|
||||
small: { width: 300 },
|
||||
medium: { width: 550 },
|
||||
large: { width: 800 },
|
||||
}}
|
||||
ratio={1.5}
|
||||
alt={alt}
|
||||
/>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ul class="carousel__dots">
|
||||
{#each slides as _, index}
|
||||
<li class:is-active={index === currentSlide}>
|
||||
<button on:click={() => goToSlide(index)} aria-label="Go to slide #{index + 1}" />
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
|
||||
<span class="carousel__arrow"
|
||||
style:--x="{$arrowPosition.x}px"
|
||||
style:--y="{$arrowPosition.y}px"
|
||||
class:is-flipped={arrowDirection === 'prev' && !isFirstSlide || isLastSlide}
|
||||
>
|
||||
<svg width="29" height="32">
|
||||
<use xlink:href="#arrow" />
|
||||
</svg>
|
||||
</span>
|
||||
{/if}
|
||||
</div>
|
||||
Reference in New Issue
Block a user