# Circular Carousel

A ring of photos in 3D. The cards bend to follow the ring, the ring drifts on its own, and you can grab and throw it with a mouse, a finger or a horizontal trackpad scroll. When it comes to rest it snaps onto a card. Click a card to turn it to the front, or focus the ring and use the arrow keys. Four presets change the shape: cylinder, orbit, wheel and panorama.

<demo-circular-carousel>



</demo-circular-carousel>

## Installation

```bash
npx shadcn-vue@latest add "https://nxui.geoql.in/r/circular-carousel.json"
```

## Usage

```vue
<script setup lang="ts">
  import CircularCarousel from '~/components/ui/circular-carousel/CircularCarousel.vue';
  import type { CircularCarouselItem } from '~/components/ui/circular-carousel/types';

  const photos: CircularCarouselItem[] = [
    {
      src: '/photos/valley.jpg',
      alt: 'Mist in a valley',
      title: 'Valley',
      subtitle: 'Landscape',
    },
    {
      src: '/photos/towers.jpg',
      alt: 'Glass towers',
      title: 'Towers',
      subtitle: 'Architecture',
    },
    // …
  ];

  function onItemClick(item: CircularCarouselItem) {
    console.log(item.title);
  }
</script>

<template>
  <div class="relative h-140 w-full">
    <CircularCarousel
      :items="photos"
      preset="cylinder"
      intro="rise"
      fade-color="var(--background)"
      captions
      @item-click="onItemClick"
    />
  </div>
</template>
```

The carousel fills its container and scales the whole ring down when it does not fit, so give the parent a height. Passing a new `items` list with the same length crossfades each card to its new image, starting from the front.

## Props

<table>
<thead>
  <tr>
    <th>
      Prop
    </th>
    
    <th>
      Type
    </th>
    
    <th>
      Default
    </th>
    
    <th>
      Description
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        items
      </code>
    </td>
    
    <td>
      <code>
        CircularCarouselItem[]
      </code>
    </td>
    
    <td>
      10 sample photos
    </td>
    
    <td>
      Images on the ring: <code>
        { src, alt?, title?, subtitle? }
      </code>
      
      . <code>
        alt
      </code>
      
       is announced, <code>
        title
      </code>
      
       is captioned.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        preset
      </code>
    </td>
    
    <td>
      <code>
        'cylinder' | 'orbit' | 'wheel' | 'panorama'
      </code>
    </td>
    
    <td>
      <code>
        'cylinder'
      </code>
    </td>
    
    <td>
      Ring shape. Orbit keeps cards facing you, wheel spins top to bottom, panorama sits inside.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        intro
      </code>
    </td>
    
    <td>
      <code>
        'assemble' | 'rise' | 'spin' | 'none'
      </code>
    </td>
    
    <td>
      <code>
        'rise'
      </code>
    </td>
    
    <td>
      Entrance once the images load.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        card-width
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        220
      </code>
    </td>
    
    <td>
      Card width in px.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        aspect-ratio
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        1
      </code>
    </td>
    
    <td>
      Card width divided by its height. Images are cropped to fill.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        gap
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        25
      </code>
    </td>
    
    <td>
      Space between neighbouring cards, in px.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        curve
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      from preset
    </td>
    
    <td>
      Card bend, from flat (0) to fully wrapped (1). Ignored by orbit.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        tilt
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      from preset
    </td>
    
    <td>
      Camera angle in degrees. Negative values look down onto the ring.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        perspective
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      from preset
    </td>
    
    <td>
      Camera distance in px. Panorama sets its own.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        autoplay
      </code>
    </td>
    
    <td>
      <code>
        'drift' | 'step' | 'off'
      </code>
    </td>
    
    <td>
      <code>
        'drift'
      </code>
    </td>
    
    <td>
      Drift turns continuously, step moves one card at a time, off only moves when you do.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        speed
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        14
      </code>
    </td>
    
    <td>
      Drift speed in degrees per second.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        interval
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        3
      </code>
    </td>
    
    <td>
      Seconds between moves in step mode.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        direction
      </code>
    </td>
    
    <td>
      <code>
        'left' | 'right'
      </code>
    </td>
    
    <td>
      <code>
        'left'
      </code>
    </td>
    
    <td>
      Which way the front cards travel. A flick changes it. On the wheel, left means up.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        draggable
      </code>
    </td>
    
    <td>
      <code>
        boolean
      </code>
    </td>
    
    <td>
      <code>
        true
      </code>
    </td>
    
    <td>
      Grab and throw the ring with a pointer or horizontal scroll.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        momentum
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        0.6
      </code>
    </td>
    
    <td>
      How long a throw keeps gliding before it blends back into autoplay, from 0 to 1.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        snap
      </code>
    </td>
    
    <td>
      <code>
        boolean
      </code>
    </td>
    
    <td>
      <code>
        true
      </code>
    </td>
    
    <td>
      Settle on a card whenever the ring comes to rest.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        pause-on-hover
      </code>
    </td>
    
    <td>
      <code>
        boolean
      </code>
    </td>
    
    <td>
      <code>
        true
      </code>
    </td>
    
    <td>
      Ease the ring to a stop while the pointer is over it.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        focus-on-click
      </code>
    </td>
    
    <td>
      <code>
        boolean
      </code>
    </td>
    
    <td>
      <code>
        true
      </code>
    </td>
    
    <td>
      Clicking a card turns it to the front.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        parallax
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        0.3
      </code>
    </td>
    
    <td>
      How much the camera leans toward the pointer, from 0 to 1.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        stretch
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        0.5
      </code>
    </td>
    
    <td>
      How much the ring swells outward when it spins fast, from 0 to 1.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        depth-fade
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        0.55
      </code>
    </td>
    
    <td>
      How strongly cards fade into <code>
        fade-color
      </code>
      
       as they turn away, from 0 to 1.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        fade-color
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      <code>
        '#000000'
      </code>
    </td>
    
    <td>
      Color distant cards fade toward. Match the background; <code>
        var(--background)
      </code>
      
       works.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        inner-shade
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        0.6
      </code>
    </td>
    
    <td>
      Brightness of the inside faces seen through the back of the ring, from 0 to 1.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        corner-radius
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        12
      </code>
    </td>
    
    <td>
      Corner radius of the cards, in px.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        captions
      </code>
    </td>
    
    <td>
      <code>
        boolean
      </code>
    </td>
    
    <td>
      <code>
        false
      </code>
    </td>
    
    <td>
      Show the front card's title, subtitle and position under the ring.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        class
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      <code>
        ''
      </code>
    </td>
    
    <td>
      Extra classes on the root.
    </td>
  </tr>
</tbody>
</table>

## Events

<table>
<thead>
  <tr>
    <th>
      Event
    </th>
    
    <th>
      Payload
    </th>
    
    <th>
      Description
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        change
      </code>
    </td>
    
    <td>
      <code>
        (index: number)
      </code>
    </td>
    
    <td>
      A different card reached the front.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        item-click
      </code>
    </td>
    
    <td>
      <code>
        (item: CircularCarouselItem, index: number)
      </code>
    </td>
    
    <td>
      A card was clicked, or Enter / Space was pressed on the ring.
    </td>
  </tr>
</tbody>
</table>

## Accessibility

- The root is a focusable `region` with `aria-roledescription="carousel"`; each card is a `group` with `aria-roledescription="slide"` and a "title, n of total" label.
- Arrow keys step one card (Left / Right, or Up / Down on the wheel), Home and End jump to the first and last card, Enter and Space fire `item-click` for the front card.
- A polite live region announces the front card as it changes.
- With `prefers-reduced-motion: reduce`, the intro, autoplay, parallax and stretch are turned off; dragging and keys still work.
- Ported from [React Bits](https://reactbits.dev/components/circular-carousel) by David Haz.
