# Carousel

A carousel with motion and swipe.

- Install: `php artisan blatui:add carousel`
- Source: https://blatui.remix-it.com/r/carousel.json
- Composer: `composer require mallardduck/blade-lucide-icons gehrisandro/tailwind-merge-laravel`

## resources/views/components/ui/carousel.blade.php

```blade
{{--
    Carousel root — holds the active index and exposes prev/next.
      orientation  horizontal (default) | vertical. Vertical needs a height on
                   <x-ui.carousel-content> (e.g. class="h-[240px]").
      swipe        enable touch/pen swipe to change slides (default true; mouse uses the arrows).
--}}
@props(['orientation' => 'horizontal', 'swipe' => true])

<div
    data-slot="carousel"
    role="region"
    aria-roledescription="carousel"
    x-data="{
        index: 0,
        count: 0,
        orientation: @js($orientation),
        swipe: @js((bool) $swipe),
        drag: { active: false, start: 0 },
        init() { this.count = this.$refs.track ? this.$refs.track.children.length : 0 },
        get canPrev() { return this.index > 0 },
        get canNext() { return this.index < this.count - 1 },
        prev() { if (this.canPrev) this.index-- },
        next() { if (this.canNext) this.index++ },
        onPointerDown(e) {
            if (!this.swipe || e.pointerType === 'mouse') return;
            this.drag.active = true;
            this.drag.start = this.orientation === 'vertical' ? e.clientY : e.clientX;
        },
        onPointerUp(e) {
            if (!this.drag.active) return;
            this.drag.active = false;
            const end = this.orientation === 'vertical' ? e.clientY : e.clientX;
            const d = end - this.drag.start;
            const threshold = 40;
            if (d <= -threshold) this.next();
            else if (d >= threshold) this.prev();
        }
    }"
    @keydown.left.prevent="prev()"
    @keydown.right.prevent="next()"
    tabindex="0"
    {{ $attributes->twMerge('relative') }}
>
    {{ $slot }}
</div>
```

## resources/views/components/ui/carousel-content.blade.php

```blade
<div
    data-slot="carousel-content"
    @pointerdown="onPointerDown($event)"
    @pointerup.window="onPointerUp($event)"
    @pointercancel.window="drag.active = false"
    :class="orientation === 'vertical' ? 'touch-pan-x' : 'touch-pan-y'"
    {{ $attributes->twMerge('overflow-hidden') }}
>
    <div
        x-ref="track"
        class="flex"
        :class="orientation === 'vertical' ? '-mt-4 h-full flex-col' : '-ml-4'"
        :style="orientation === 'vertical'
            ? `transform: translateY(-${index * 100}%); transition: transform .3s ease`
            : `transform: translateX(-${index * 100}%); transition: transform .3s ease`"
    >
        {{ $slot }}
    </div>
</div>
```

## resources/views/components/ui/carousel-item.blade.php

```blade
<div
    role="group"
    aria-roledescription="slide"
    data-slot="carousel-item"
    :class="orientation === 'vertical' ? 'pt-4' : 'pl-4'"
    {{ $attributes->twMerge('min-w-0 shrink-0 grow-0 basis-full') }}
>
    {{ $slot }}
</div>
```

## resources/views/components/ui/carousel-next.blade.php

```blade
<button
    type="button"
    @click="next()"
    :disabled="!canNext"
    data-slot="carousel-next"
    :class="orientation === 'horizontal' ? 'top-1/2 -right-12 -translate-y-1/2' : '-bottom-12 left-1/2 -translate-x-1/2 rotate-90'"
    {{ $attributes->twMerge('border-input hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input absolute inline-flex size-8 items-center justify-center rounded-full border bg-background shadow-xs transition-all outline-none focus-visible:ring-ring/50 focus-visible:ring-[3px] disabled:pointer-events-none disabled:opacity-50') }}
>
    <x-lucide-arrow-right class="size-4" />
    <span class="sr-only">Next slide</span>
</button>
```

## resources/views/components/ui/carousel-previous.blade.php

```blade
<button
    type="button"
    @click="prev()"
    :disabled="!canPrev"
    data-slot="carousel-previous"
    :class="orientation === 'horizontal' ? 'top-1/2 -left-12 -translate-y-1/2' : '-top-12 left-1/2 -translate-x-1/2 rotate-90'"
    {{ $attributes->twMerge('border-input hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input absolute inline-flex size-8 items-center justify-center rounded-full border bg-background shadow-xs transition-all outline-none focus-visible:ring-ring/50 focus-visible:ring-[3px] disabled:pointer-events-none disabled:opacity-50') }}
>
    <x-lucide-arrow-left class="size-4" />
    <span class="sr-only">Previous slide</span>
</button>
```
