Ir al contenido
Navegación

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.

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.

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 ↑ ↓.

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

Generadas del TypeScript del paquete. Las propias del componente, más las heredadas del primitivo que tienen algo que explicar —marcadas «heredada de Base UI»—. El resto está en la línea «hereda de».

ResizableHandle

Hereda las props de <div>.

PropTipoPor defectoDescripción
aria-labelstring—Nombre accesible del elemento.
onKeyDownKeyboardEventHandler<T>—Se llama antes de mover. Con event.preventDefault(), la tecla no mueve.
onPointerDownPointerEventHandler<T>—Se llama antes de empezar a arrastrar. Con event.preventDefault(), no arrastra.
withHandlebooleanfalseUna manija visible en el medio de la línea.
classNamestring—Se fusiona con las clases del componente vía cn() (tailwind-merge): lo que pongas gana.

ResizablePanel

Hereda las props de <div>.

PropTipoPor defectoDescripción
defaultSizenumber—El tamaño al arrancar, en %. Sin él, reparte el resto.
idstring—El id del panel (el aria-controls del separador). Por defecto, uno generado.
maxSizenumber100El máximo, en %. Por defecto, 100.
minSizenumber0El mínimo, en %. Por defecto, 0.
styleCSSProperties—Se suma al flex que pone el reparto; no pises flex-grow.
classNamestring—Se fusiona con las clases del componente vía cn() (tailwind-merge): lo que pongas gana.

ResizablePanelGroup

Hereda las props de <div>.

PropTipoPor defectoDescripción
defaultLayoutnumber[]—Los tamaños al arrancar (lo guardado con onLayout). Le gana al defaultSize de cada panel.
keyboardStepnumber5Cuá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).
classNamestring—Se fusiona con las clases del componente vía cn() (tailwind-merge): lo que pongas gana.

Teclado

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