# Resizable

> Paneles que se reparten el espacio y se redimensionan arrastrando la línea entre paneles de Mail, que se vuelve acento al agarrarla; con teclado, mínimos, máximos y el reparto para guardar.

```tsx
import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from "sebs7n-ui/resizable"
```

## Ejemplos

### Carpetas, lista y vista previa

Tres paneles lado a lado: la línea entre paneles de Mail se agarra (se vuelve acento) y se arrastra, o se enfoca con Tab y se mueve con las flechas. `onLayout` avisa el reparto para guardarlo.

```tsx
import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from "sebs7n-ui/resizable"
import { useState } from "react"

function Basico() {
  const [reparto, setReparto] = useState<number[]>([22, 33, 45])
  return (
    <div className="flex w-full flex-col gap-2">
      <div className="h-72 w-full overflow-hidden rounded-surface border border-separator-strong">
        <ResizablePanelGroup defaultLayout={reparto} onLayout={setReparto}>
          <ResizablePanel className="bg-surface-secondary p-4" maxSize={40} minSize={15}>
            <p className="text-callout font-semibold">Carpetas</p>
            <p className="text-callout text-label-secondary">Emitidas · Recibidas · Archivo</p>
          </ResizablePanel>
          <ResizableHandle aria-label="Ancho de las carpetas" />
          <ResizablePanel className="p-4" minSize={25}>
            <p className="text-callout font-semibold">Facturas</p>
            <p className="text-callout text-label-secondary">A-0012 · Acme S.A.</p>
          </ResizablePanel>
          <ResizableHandle aria-label="Ancho de la lista" withHandle />
          <ResizablePanel className="p-4" minSize={25}>
            <p className="text-callout font-semibold">Vista previa</p>
            <p className="text-callout text-label-secondary">Servicios de septiembre · $ 128.400</p>
          </ResizablePanel>
        </ResizablePanelGroup>
      </div>
      <p className="text-footnote text-label-secondary tabular-nums">Reparto: {reparto.map((size) => `${Math.round(size)} %`).join(" · ")}</p>
    </div>
  )
}
```

### Apilados

`orientation="vertical"`: el detalle arriba y las notas abajo; la línea se mueve con ↑ ↓.

```tsx
import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from "sebs7n-ui/resizable"

function Vertical() {
  return (
    <div className="h-72 w-full max-w-md overflow-hidden rounded-surface border border-separator-strong">
      <ResizablePanelGroup orientation="vertical">
        <ResizablePanel className="p-4" defaultSize={60} minSize={30}>
          <p className="text-callout font-semibold">Factura A-0012</p>
          <p className="text-callout text-label-secondary">Acme S.A. · Vence el 30/09</p>
        </ResizablePanel>
        <ResizableHandle aria-label="Alto de las notas" withHandle />
        <ResizablePanel className="bg-surface-secondary p-4" minSize={20}>
          <p className="text-callout font-semibold">Notas internas</p>
          <p className="text-callout text-label-secondary">Pedir la orden de compra antes de enviar.</p>
        </ResizablePanel>
      </ResizablePanelGroup>
    </div>
  )
}
```

## Props

### ResizableHandle

Hereda las props de `<div>`.

| Prop | Tipo | Por defecto | Descripción |
|---|---|---|---|
| `aria-label` | `string` | — | Nombre accesible del elemento. |
| `onKeyDown` | `KeyboardEventHandler<T>` | — | Se llama antes de mover. Con `event.preventDefault()`, la tecla no mueve. |
| `onPointerDown` | `PointerEventHandler<T>` | — | Se llama antes de empezar a arrastrar. Con `event.preventDefault()`, no arrastra. |
| `withHandle` | `boolean` | `false` | Una manija visible en el medio de la línea. |
| `className` | `string` | — | Se fusiona con las clases del componente vía `cn()` (tailwind-merge): lo que pongas gana. |

### ResizablePanel

Hereda las props de `<div>`.

| Prop | Tipo | Por defecto | Descripción |
|---|---|---|---|
| `defaultSize` | `number` | — | El tamaño al arrancar, en %. Sin él, reparte el resto. |
| `id` | `string` | — | El `id` del panel (el `aria-controls` del separador). Por defecto, uno generado. |
| `maxSize` | `number` | `100` | El máximo, en %. Por defecto, 100. |
| `minSize` | `number` | `0` | El mínimo, en %. Por defecto, 0. |
| `style` | `CSSProperties` | — | Se suma al `flex` que pone el reparto; no pises `flex-grow`. |
| `className` | `string` | — | Se fusiona con las clases del componente vía `cn()` (tailwind-merge): lo que pongas gana. |

### ResizablePanelGroup

Hereda las props de `<div>`.

| Prop | Tipo | Por defecto | Descripción |
|---|---|---|---|
| `defaultLayout` | `number[]` | — | Los tamaños al arrancar (lo guardado con `onLayout`). Le gana al `defaultSize` de cada panel. |
| `keyboardStep` | `number` | `5` | Cuánto mueve una flecha, en %. Shift, el doble. Por defecto, 5. |
| `onLayout` | `(sizes: number[]) => void` | — | Se llama con los tamaños (en %, en el orden de los paneles) al soltar o con cada tecla. |
| `orientation` | `"horizontal" \| "vertical"` | `"horizontal"` | `horizontal` (default, lado a lado) o `vertical` (apilados). |
| `className` | `string` | — | Se fusiona con las clases del componente vía `cn()` (tailwind-merge): lo que pongas gana. |

## Teclado

| Tecla | Qué hace |
|---|---|
| Tab | Enfoca el separador (la línea pasa a la banda de foco de 3 px). |
| ← → (lado a lado) · ↑ ↓ (apilados) | Mueven la línea de a `keyboardStep` (5 %); con Shift, el doble. En RTL, ← agranda el panel de antes. |
| Home / End | Llevan el panel de antes a su mínimo o a su máximo. |

## Accesibilidad

- Cada `ResizableHandle` es el «window splitter» de WAI-ARIA: `role="separator"` tabulable con `aria-orientation`, `aria-valuenow` (el tamaño del panel de antes, en %), `aria-valuemin`/`aria-valuemax` (los reales, sin romper al vecino) y `aria-controls` al panel que mueve.
- **Nombralo por lo que cambia**: «Ancho de la lista» (`aria-label`). Sin él dice «Cambiar el tamaño», que con dos separadores no alcanza.
- La manija (`withHandle`) es decorativa. El área para agarrar la línea mide 12 px (24 con el dedo) aunque la línea mida 1.

## Reglas de uso

- **Para que la persona decida cuánto lugar le da a cada parte**: una lista y su vista previa, un editor y su resultado. Si los anchos fijos alcanzan, `SplitView` sin redimensionar, como Mail.
- Los tamaños son **porcentajes del grupo**: `defaultSize`, `minSize`, `maxSize` por panel. Los que no traen `defaultSize` se reparten el resto.
- **Para recordar el reparto**: guardá lo que llega en `onLayout` (al soltar o con cada tecla) y devolvelo en `defaultLayout`.
- El grupo toma el alto de su contenedor (`h-full`): ponele un alto al de afuera. Cada panel scrollea por su cuenta.
- `SplitView resizable` usa la misma línea en px para el sidebar y la lista.
- Solo por subpath (`sebs7n-ui/resizable`): no está en el barrel, por peso.

## Relacionados

[split-view](/docs/components/split-view.md) · [app-shell](/docs/components/app-shell.md)
