feat: added product page
This commit is contained in:
98
src/lib/components/ui/image-carousel/ImageCarousel.svelte
Normal file
98
src/lib/components/ui/image-carousel/ImageCarousel.svelte
Normal file
@@ -0,0 +1,98 @@
|
||||
<script lang="ts">
|
||||
import type { ImageCarouselItem } from '.'
|
||||
import * as Carousel from '../carousel'
|
||||
import ImgCarouselItem from './ImageCarouselItem.svelte'
|
||||
import type { CarouselAPI } from '$lib/components/ui/carousel/context.js'
|
||||
import { Button } from '../button'
|
||||
import { ChevronLeftIcon, ChevronRightIcon } from '@lucide/svelte'
|
||||
|
||||
const {
|
||||
items,
|
||||
preview = false,
|
||||
buttonPosition = 'default',
|
||||
}: {
|
||||
items: ImageCarouselItem[]
|
||||
preview?: boolean
|
||||
buttonPosition?: 'contained' | 'default' | 'hidden'
|
||||
} = $props()
|
||||
|
||||
let api = $state<CarouselAPI>()
|
||||
let currentImageIndex = $state(0)
|
||||
|
||||
$effect(() => {
|
||||
if (!api) return
|
||||
|
||||
api.on('select', () => {
|
||||
if (!api) return
|
||||
currentImageIndex = api.selectedScrollSnap()
|
||||
})
|
||||
})
|
||||
|
||||
function onPreviewImageClick(toIndex: number) {
|
||||
if (!api) return
|
||||
|
||||
api.scrollTo(toIndex, false)
|
||||
}
|
||||
|
||||
function prevImage() {
|
||||
if (!api) return
|
||||
|
||||
api.scrollPrev()
|
||||
}
|
||||
function nextImage() {
|
||||
if (!api) return
|
||||
|
||||
api.scrollNext()
|
||||
}
|
||||
</script>
|
||||
|
||||
<div>
|
||||
<Carousel.Root setApi={(emblaApi) => (api = emblaApi)}>
|
||||
<Carousel.Content>
|
||||
{#each items as item}
|
||||
<ImgCarouselItem {item} />
|
||||
{/each}
|
||||
</Carousel.Content>
|
||||
{#if buttonPosition === 'default'}
|
||||
<Carousel.Next />
|
||||
{:else if buttonPosition === 'contained'}
|
||||
<Button
|
||||
onclick={prevImage}
|
||||
variant="outline"
|
||||
size="icon"
|
||||
class="absolute top-1/2 -translate-y-1/2 left-4 rounded-full"
|
||||
><ChevronLeftIcon /></Button
|
||||
>
|
||||
{/if}
|
||||
{#if buttonPosition === 'default'}
|
||||
<Carousel.Previous />
|
||||
{:else if buttonPosition === 'contained'}
|
||||
<Button
|
||||
onclick={nextImage}
|
||||
variant="outline"
|
||||
size="icon"
|
||||
class="absolute top-1/2 -translate-y-1/2 right-4 rounded-full"
|
||||
><ChevronRightIcon /></Button
|
||||
>
|
||||
{/if}
|
||||
</Carousel.Root>
|
||||
{#if preview}
|
||||
<Carousel.Root
|
||||
opts={{
|
||||
align: 'start',
|
||||
}}
|
||||
class="hidden md:block"
|
||||
>
|
||||
<Carousel.Content>
|
||||
{#each items as item, i}
|
||||
<ImgCarouselItem
|
||||
onclick={() => onPreviewImageClick(i)}
|
||||
{item}
|
||||
class="md:basis-1/3 lg:basis-1/4 mt-4"
|
||||
isSelected={i === currentImageIndex}
|
||||
/>
|
||||
{/each}
|
||||
</Carousel.Content>
|
||||
</Carousel.Root>
|
||||
{/if}
|
||||
</div>
|
||||
@@ -0,0 +1,34 @@
|
||||
<script lang="ts">
|
||||
import { cn } from '$lib/utils'
|
||||
import type { ImageCarouselItem } from '.'
|
||||
import * as Carousel from '../carousel'
|
||||
|
||||
const {
|
||||
item,
|
||||
class: className,
|
||||
isSelected,
|
||||
onclick,
|
||||
}: {
|
||||
item: ImageCarouselItem
|
||||
class?: string
|
||||
isSelected?: boolean
|
||||
onclick?: () => void
|
||||
} = $props()
|
||||
|
||||
const itemClass = $derived(
|
||||
cn(
|
||||
'object-cover rounded-lg',
|
||||
isSelected
|
||||
? 'border-2 transition-all border-blue-600 dark:border-blue-500'
|
||||
: 'border-2 transition-all border-gray-200 dark:border-gray-700 hover:border-gray-300 dark:hover:border-gray-600',
|
||||
!item.aspect || item.aspect === '1:1' ? 'aspect-square' : '',
|
||||
item.aspect === '16:9' ? 'aspect-video' : '',
|
||||
item.aspect === '9:16' ? 'aspect-9/16' : '',
|
||||
item.aspect === '4:3' ? 'aspect-4/3' : '',
|
||||
),
|
||||
)
|
||||
</script>
|
||||
|
||||
<Carousel.Item {onclick} class={className}>
|
||||
<img src={item.image} alt={item.alt} class={itemClass} />
|
||||
</Carousel.Item>
|
||||
9
src/lib/components/ui/image-carousel/index.ts
Normal file
9
src/lib/components/ui/image-carousel/index.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import ImageCarousel from './ImageCarousel.svelte'
|
||||
|
||||
type ImageCarouselItem = {
|
||||
image: string
|
||||
alt: string
|
||||
aspect?: '1:1' | '16:9' | '9:16' | '4:3'
|
||||
}
|
||||
|
||||
export { ImageCarousel, type ImageCarouselItem }
|
||||
Reference in New Issue
Block a user