Ir al contenido
Contenido y datos

WidgetBoard

Un panel de widgets que el usuario edita, como la pantalla de inicio de iCloud: una grilla estática de cards que, con «Editar», se reordena arrastrando o con el teclado, saca widgets con su «−», los vuelve a agregar desde un catálogo y se restablece. Se guarda en localStorage.

import { WidgetBoard, WidgetBoardEditButton } from "sebs7n-ui/widget-board"

Ejemplos

Panel editable

«Editar» pone los widgets en modo edición: tiemblan, se reordenan arrastrando o con Espacio y las flechas, se sacan con su «−» y vuelven desde «Agregar widget». «Restablecer» deja el panel original. El orden se guarda en este navegador.

import { WidgetBoard, WidgetBoardEditButton } from "sebs7n-ui/widget-board"
import { useWidgetLayout } from "sebs7n-ui/lib/widget-layout"

function Editable() {
  const layout = useWidgetLayout({ storageKey: "demo:widget-board", widgets: WIDGETS })
  return (
    <div className="@container flex w-full flex-col gap-4">
      <div className="flex justify-end">
        <WidgetBoardEditButton layout={layout} size="sm" />
      </div>
      <WidgetBoard layout={layout} />
    </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».

WidgetBoard

Hereda las props de <div>.

PropTipoPor defectoDescripción
layout*WidgetLayout—El estado: lo que devuelve useWidgetLayout.
gridClassNamestring—Clases de la grilla, por ejemplo otro gap.
classNamestring—Se fusiona con las clases del componente vía cn() (tailwind-merge): lo que pongas gana.

WidgetBoardEditButton

PropTipoPor defectoDescripción
layout*WidgetLayout—El estado: lo que devuelve useWidgetLayout.
loadingboolean—Como en Button: deshabilita y marca el botón como ocupado.
size"sm" | "md" | "lg" | "icon-sm" | "icon-md" | "icon-lg"—El tamaño del botón, como en Button. En la cabecera de una pantalla, md (por defecto).
classNamestring—Se fusiona con las clases del componente vía cn() (tailwind-merge): lo que pongas gana.

Teclado

Tab
Recorre los botones de la barra de edición, los «−» y, en cada card, lo interactivo de adentro.
Espacio
En un widget en edición lo toma; con otro Espacio lo suelta en su nuevo lugar.
← → ↑ ↓
Con un widget tomado, lo mueven por la grilla.
Escape
Cancela el movimiento; sin un widget tomado, sale de la edición.
Enter · Espacio
En un «−» saca el widget; en una fila del catálogo lo agrega.

Accesibilidad

  • La grilla es una lista con nombre («Widgets»). Entrar en edición, sacar, agregar, mover y restablecer se anuncian en español en regiones role="status".
  • Cada «−» y cada fila del catálogo se llaman con el widget («Sacar Clientes», «Agregar Clientes»). Al sacar uno el foco pasa al «−» que queda (o a «Agregar widget» si no queda ninguno); al agregar, al «−» del nuevo.
  • Con movimiento reducido el temblor se apaga y un contorno punteado dice que está en edición. La vista previa del catálogo es decorativa (aria-hidden).
  • Sin widgets en pantalla hay un EmptyState con «Agregar widget» y «Restablecer»: el panel nunca queda mudo.

Reglas de uso

  • Tres piezas: useWidgetLayout({ storageKey, widgets }) (el estado), <WidgetBoard layout /> (la grilla) y <WidgetBoardEditButton layout /> (el «Editar»/«Listo» de la cabecera, secundario: la acción primaria sigue siendo la de la pantalla).
  • Cada widget es { id, title, size?, description?, preview?, icon?, render }. size: sm (1 columna de 4), md (2) o lg (4); por debajo de 36 rem de ancho del contenedor son 2 columnas y por debajo de 56 rem, 1. Las cards de una fila quedan alineadas: la card llena el alto de su celda.
  • Carga diferida: sin editar es una grilla estática (sin @dnd-kit); el arrastre, la barra y el catálogo se piden la primera vez que se aprieta «Editar» (React.lazy), con la misma grilla de fondo mientras llegan. Los peers opcionales @dnd-kit/* hacen falta solo si se usa.
  • El HTML del servidor muestra el orden original; lo guardado se adopta después de montar. Si el panel depende de un proyecto o una cuenta, una clave por cada uno y key en la pantalla para que el estado no se arrastre.
  • Solo por subpath (sebs7n-ui/widget-board); el modelo puro (moveWidget, removeWidget, addWidget, reorderWidgets, serializeLayout) sale de sebs7n-ui/lib/widget-layout.

Relacionados