# Carousel

> Un carrusel sobre Embla con la API de shadcn: flechas de 28 sobre el material translúcido, puntos y «2 de 5» para el lector.

```tsx
import { Carousel, CarouselContent, CarouselDots, CarouselItem, … } from "sebs7n-ui/carousel"
```

## Ejemplos

### Planes

Las flechas son botones de 28 sobre el material translúcido; los puntos saltan a cada uno. ←/→ con el foco adentro.

```tsx
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "sebs7n-ui"
import { Carousel, CarouselContent, CarouselDots, CarouselItem, CarouselNext, CarouselPrevious } from "sebs7n-ui/carousel"

function Plans() {
  return (
    <Carousel aria-label="Planes" className="w-full max-w-sm">
      <CarouselContent>
        {PLANS.map((plan) => (
          <CarouselItem key={plan.name}>
            <Card>
              <CardHeader>
                <CardTitle>{plan.name}</CardTitle>
                <CardDescription>{plan.detail}</CardDescription>
              </CardHeader>
              <CardContent className="text-title-1 text-label tabular-nums">{plan.price}</CardContent>
            </Card>
          </CarouselItem>
        ))}
      </CarouselContent>
      <CarouselPrevious />
      <CarouselNext />
      <CarouselDots />
    </Carousel>
  )
}
```

### Varios a la vista

`basis-1/2` en cada ítem y `opts={{ align: "start" }}`: dos por vez, de a uno.

```tsx
import { Card, CardDescription, CardHeader, CardTitle } from "sebs7n-ui"
import { Carousel, CarouselContent, CarouselDots, CarouselItem, CarouselNext, CarouselPrevious } from "sebs7n-ui/carousel"

function Several() {
  return (
    <Carousel aria-label="Planes, de a dos" className="w-full max-w-lg" opts={{ align: "start" }}>
      <CarouselContent>
        {PLANS.map((plan) => (
          <CarouselItem className="basis-1/2" key={plan.name}>
            <Card size="sm">
              <CardHeader>
                <CardTitle>{plan.name}</CardTitle>
                <CardDescription>{plan.price}</CardDescription>
              </CardHeader>
            </Card>
          </CarouselItem>
        ))}
      </CarouselContent>
      <CarouselPrevious />
      <CarouselNext />
      <CarouselDots />
    </Carousel>
  )
}
```

### Sobre la foto

`bleed` y `controls="overlay"`: la imagen de borde a borde de la card y las flechas y los puntos encima, sobre el gris del tooltip. Con el puntero, las flechas aparecen al pasar; con el dedo, se desliza.

```tsx
import { Card, CardContent } from "sebs7n-ui"
import { Carousel, CarouselContent, CarouselDots, CarouselItem, CarouselNext, CarouselPrevious } from "sebs7n-ui/carousel"

function Photos() {
  return (
    <Card className="w-full max-w-xs gap-0 overflow-hidden py-0">
      <Carousel aria-label="Factura 0012 escaneada" bleed controls="overlay" opts={{ loop: true }}>
        <CarouselContent>
          {PAGES.map((page) => (
            <CarouselItem key={page.name}>
              <div aria-label={page.name} className="aspect-4/3" role="img" style={{ background: page.background }} />
            </CarouselItem>
          ))}
        </CarouselContent>
        <CarouselPrevious />
        <CarouselNext />
        <CarouselDots />
      </Carousel>
      <CardContent className="p-4">
        <p className="text-headline text-label">Factura 0012</p>
        <p className="text-callout text-label-secondary">Acme S.A. · 3 páginas</p>
      </CardContent>
    </Card>
  )
}
```

## Props

### Carousel

Hereda las props de `<div>`.

| Prop | Tipo | Por defecto | Descripción |
|---|---|---|---|
| `aria-label` | `string` | — | Nombre accesible del elemento. |
| `aria-labelledby` | `string` | — | El `id` del título que nombra la región («Nuestros planes»). Le gana a `labels.label`. |
| `bleed` | `boolean` | `false` | A sangre: la vista sin el aire para la sombra ni la máscara de los costados, y las diapositivas sin separación. Para fotos de borde a borde, como la de una card de producto. |
| `controls` | `"outside" \| "overlay"` | `"outside"` | `overlay`: las flechas y los puntos van encima de la foto, sobre el gris oscuro del tooltip con desenfoque (contraste garantizado sobre cualquier imagen); las flechas aparecen con el puntero encima o con foco, y con el dedo se desliza. Por defecto (`outside`), los de 2.0. |
| `labels` | `Partial<CarouselLabels>` | — | Textos: `label` (el nombre de la región sin `aria-label`), `carousel`, `slide`, `previous`, `next`, `of` y `goTo`. Los que vienen por defecto son `carouselLabels`. |
| `onKeyDown` | `KeyboardEventHandler<T>` | — | Corre antes que ←/→: con `event.preventDefault()` el carrusel no se mueve. Un control de adentro que haga `preventDefault` también lo frena. |
| `opts` | `CarouselOptions` | — | Las opciones de Embla (`loop`, `align`, `slidesToScroll`…). |
| `orientation` | `"horizontal" \| "vertical"` | `"horizontal"` | `horizontal` (por defecto) o `vertical`. |
| `plugins` | `CarouselPlugin` | — | Los plugins de Embla (autoplay, ruedita…). |
| `setApi` | `(api: CarouselApi) => void` | — | Recibe la API de Embla, para manejarlo desde afuera. |
| `className` | `string` | — | Se fusiona con las clases del componente vía `cn()` (tailwind-merge): lo que pongas gana. |

### CarouselContent

Hereda las props de `<div>`.

| Prop | Tipo | Por defecto | Descripción |
|---|---|---|---|
| `className` | `string` | — | Se fusiona con las clases del componente vía `cn()` (tailwind-merge): lo que pongas gana. |

### CarouselDots

Hereda las props de `<div>`.

| Prop | Tipo | Por defecto | Descripción |
|---|---|---|---|
| `className` | `string` | — | Se fusiona con las clases del componente vía `cn()` (tailwind-merge): lo que pongas gana. |

### CarouselItem

Hereda las props de `<div>`.

| Prop | Tipo | Por defecto | Descripción |
|---|---|---|---|
| `className` | `string` | — | Se fusiona con las clases del componente vía `cn()` (tailwind-merge): lo que pongas gana. |

### CarouselNext

| Prop | Tipo | Por defecto | Descripción |
|---|---|---|---|
| `variant` | `"link" \| "default" \| "accent" \| "secondary" \| "plain" \| "ghost" \| "destructive" \| "destructive-plain"` | `"ghost"` | La variante del botón. Por defecto `ghost`, sobre el material translúcido. |
| `className` | `string` | — | Se fusiona con las clases del componente vía `cn()` (tailwind-merge): lo que pongas gana. |

### CarouselPrevious

| Prop | Tipo | Por defecto | Descripción |
|---|---|---|---|
| `variant` | `"link" \| "default" \| "accent" \| "secondary" \| "plain" \| "ghost" \| "destructive" \| "destructive-plain"` | `"ghost"` | La variante del botón. Por defecto `ghost`, sobre el material translúcido. |
| `className` | `string` | — | Se fusiona con las clases del componente vía `cn()` (tailwind-merge): lo que pongas gana. |

## Teclado

| Tecla | Qué hace |
|---|---|
| ← / → | Con el foco adentro, la parada anterior y la siguiente (↑/↓ si es vertical). En un control que ya usa las flechas (campo, slider, radios, listbox, combobox) son del control. |
| Tab | Recorre lo de adentro, las flechas y los puntos. |
| Enter · Espacio | En una flecha o un punto, mueven el carrusel. |

## Accesibilidad

- Es una región con `aria-roledescription` «carrusel»: nombrala con `aria-label` («Planes»); sin nombre se llama «Carrusel» (`labels.label`).
- Cada diapositiva es un `group` «diapositiva» llamado «2 de 5»; las flechas, «Diapositiva anterior» y «Diapositiva siguiente»; cada punto es una parada, «Ir a la página 2 de 3» (con `slidesToScroll` > 1 una página tiene varias diapositivas), y el actual lleva `aria-current`.
- En la punta, la flecha que no lleva a ningún lado se apaga y se esconde (salvo con `loop`).
- Con movimiento reducido salta en vez de deslizar.

## Reglas de uso

- Para fotos (la de una card de producto): `bleed` saca el aire para la sombra, la máscara de los costados y la separación entre diapositivas; `controls="overlay"` pone flechas y puntos encima de la imagen, en el gris opaco del tooltip con un filo blanco: 3:1 sobre una foto blanca y sobre una negra, y el foco de los puntos queda sobre la pastilla. Las flechas aparecen con el puntero encima o con foco de teclado (también en un iPad con teclado); con el dedo no se ven ni se tocan (se desliza).
- Para mirar de a uno algo que se compara poco: planes, fotos de un comprobante. Si hay que comparar, es una grilla.
- `opts` y `plugins` van derecho a Embla (`loop`, `align`, autoplay); `setApi` da su API. `basis-1/2` en `CarouselItem` para ver dos.
- No pasa solo: un carrusel que rota necesita un botón de pausa, y eso es de la app con el plugin de autoplay.
- **`embla-carousel-react` es un peer opcional:** `npm install embla-carousel-react` en la app que lo usa. Solo por subpath (`sebs7n-ui/carousel`).

## Relacionados

[card](/docs/components/card.md) · [widget-card](/docs/components/widget-card.md)
