componente · @solu30/ui-kit/feedback
Sheet
Panel que entra deslizándose desde un borde de la pantalla, en seis anchos.
Seguir leyendo — 1 párrafo más
Sheet component for side panel layouts. Wraps modal state management and CSS transition animation logic.
127 sitios de import en 9 de 15 verticales — un archivo que la importa cuenta una vez, monte una instancia o veinte.
- TheCharterPanel
- carlosmartinpavon
- cmp-packages
- dietaly
- easycharter
- elliot
- gestorsalon
- luigi
- raquetea
El componente, montado
Cargando el espécimen…
Éste es el conjunto completo. Abajo, los mismos casos por separado, cada uno con su código.
Cómo se usa
import { Sheet } from '@solu30/ui-kit/feedback'<Sheet isOpen={abierto} onClose={() => setAbierto(false)} side="right" title="Filtros de la agenda">
<p className="text-sm text-content/70">Rango de fechas, estado y salón.</p>
</Sheet>Props
| Prop | Requerida | Tipo | Qué hace |
|---|---|---|---|
| isOpen | sí | boolean | — |
| onClose | sí | () => void | — |
| title | opcional | string | — |
| side | opcional | "right" | "left" | "bottom" | — |
| size | opcional | SheetSize | — |
| children | sí | ReactNode | — |
| header | opcional | ReactNode | — |
| footer | opcional | ReactNode | — |
| className | opcional | string | — |
| keepMounted | opcional | boolean | When true, the sheet content stays mounted in the DOM even when closed. Use this when children hold imperative handles (useImperativeHandle / forwardRef) that must remain live while the sheet is hidden — e.g. an editor whose ref is called before the sheet opens. @default false |
| manageHistory | opcional | boolean | Si este Sheet participa en la integración de historial del kit (pushState al abrir + el botón "atrás" lo cierra). Ver ADR-262. Default `true` — sin cambios de comportamiento para consumidores existentes. Poné `false` cuando `isOpen`/`onClose` YA están sincronizados con la URL de la app (Next.js App Router: `?param=` + `router.push`/`searchParams`) — en ese caso el router del framework ya hace "atrás cierra esto" nativamente, y el pushState propio del kit compite por `window.history` con la navegación del framework y puede abortarla (Navigation API: cualquier `history.pushState` aborta una navegación async en vuelo, sin importar el orden). @default true |
Lados
Cuando el panel lateral necesita cambiar de lado según el contexto — el mismo panel controlado, con el lado decidido por quien lo abre.
Cargando el espécimen…
export function SheetLados() {
const [open, setOpen] = useState(false)
const [side, setSide] = useState<'right' | 'left' | 'bottom'>('right')
const focus = useFocusReturnCheck()
const close = () => {
setOpen(false)
focus.notifyClosed()
}
return (
<>
{SHEET_SIDES.map((s) => (
<TriggerButton
key={s.side}
onClick={(e) =>
focus.openFrom(e, () => {
setSide(s.side)
setOpen(true)
})
}
>
{s.label}
</TriggerButton>
))}
<FocusReturnNote result={focus.result} />
<Sheet isOpen={open} onClose={close} side={side} title="Filtros de reservas" size="md">
<div className="flex flex-col gap-2 text-sm text-content/70">
<p>Rango de fechas, estado y salón — el mismo contenido en los tres lados.</p>
</div>
</Sheet>
</>
)
}De specimens/feedback.tsx · SheetLados: el espécimen del Muestrario, no un ejemplo oficial del paquete. Muestra cómo se usa la pieza de verdad, montada contra los tipos instalados —eso es lo que vale— pero no es documentación del kit.
Composición
Cuando el panel necesita un header y un footer propios, o abrirse por los cuatro lados —incluido el lado superior, que el panel de una pieza no ofrece—: la API de composición con la raíz, el disparador y el contenido separados.
Cargando el espécimen…
export function SheetCompositionMatrix({ abiertoAlMontar = false }: { abiertoAlMontar?: boolean }) {
const [open, setOpen] = useState(abiertoAlMontar)
const [lado, setLado] = useState<(typeof SHEET_LADOS)[number]>('right')
return (
<Stage label="Sheet composable · SheetRoot + Trigger + Content + Close, y el panel replicado adentro" tall wide>
<div className="flex flex-col items-center gap-3">
<div className="flex flex-col items-center gap-4 sm:flex-row sm:items-start sm:justify-center">
<SheetRoot open={open} onOpenChange={setOpen}>
<SheetTrigger
type="button"
className="rounded-md border border-content/10 bg-container px-3 py-1.5 text-xs font-medium text-content transition-colors hover:bg-upper"
>
Abrir el panel de filtros
</SheetTrigger>
<SheetContent side={lado} manageHistory={false}>
<SheetHeader>
<SheetTitle>Filtros de reservas</SheetTitle>
<SheetDescription>Se aplican sobre la agenda del mes en curso.</SheetDescription>
</SheetHeader>
<div className="flex flex-col gap-3 px-4 py-3">
<p className="text-sm text-content/70">
Los cuatro lados que acepta SheetContent. El control vive adentro del panel porque el
backdrop tapa todo lo de afuera.
</p>
<div className="flex flex-wrap gap-2">
{SHEET_LADOS.map((l) => (
<button
key={l}
type="button"
onClick={() => setLado(l)}
aria-pressed={l === lado}
className={
l === lado
? 'rounded-md border border-content/30 bg-upper px-3 py-1.5 font-mono text-[11px] text-content'
: 'rounded-md border border-content/10 bg-container px-3 py-1.5 font-mono text-[11px] text-content/60 transition-colors hover:bg-upper'
}
>
{l}
</button>
))}
</div>
</div>
<SheetFooter>
<SheetClose
type="button"
className="rounded-md border border-content/10 bg-container px-3 py-1.5 text-xs font-medium text-content transition-colors hover:bg-upper"
>
Cerrar sin aplicar
</SheetClose>
</SheetFooter>
</SheetContent>
</SheetRoot>
<ReplicaContenida nota={`réplica del panel · side="${lado}"`}>
<div className="flex flex-col gap-2 p-4">
<p className="text-sm font-medium text-content">Filtros de reservas</p>
<p className="text-xs text-content/60">Se aplican sobre la agenda del mes en curso.</p>
<p className="border-t border-content/10 pt-2 text-xs text-content/70">
Los cuatro lados que acepta SheetContent. El control vive adentro del panel porque el
backdrop tapa todo lo de afuera.
</p>
<div className="flex flex-wrap gap-1.5">
{SHEET_LADOS.map((l) => (
<span
key={l}
className={
l === lado
? 'rounded-md border border-content/30 bg-upper px-2.5 py-1 font-mono text-[10px] text-content'
: 'rounded-md border border-content/10 bg-container px-2.5 py-1 font-mono text-[10px] text-content/60'
}
>
{l}
</span>
))}
</div>
<div className="flex justify-end border-t border-content/10 pt-2">
<span className="rounded-md border border-content/10 bg-container px-3 py-1.5 text-xs text-content">
Cerrar sin aplicar
</span>
</div>
</div>
</ReplicaContenida>
</div>
<p className="max-w-[520px] text-center text-[11px] text-content/50">
El componente REAL es el de la izquierda y abre de verdad: su panel sale por un portal a
document.body y se dibuja fijo sobre la página, no adentro de este recuadro — por eso acá al
lado va la misma forma replicada, que sí se puede mirar sin que tape la ficha. El botón es
SheetTrigger; el del pie del panel, SheetClose. Los cuatro lados se prueban abriéndolo.
</p>
</div>
</Stage>
)
}De specimens/feedback.tsx · SheetCompositionMatrix: el espécimen del Muestrario, no un ejemplo oficial del paquete. Muestra cómo se usa la pieza de verdad, montada contra los tipos instalados —eso es lo que vale— pero no es documentación del kit.
Ejemplo del paquete
// El panel controlado de una sola pieza: trae overlay, título y cierre.
<Sheet isOpen={abierto} onClose={cerrar} title="Filtros" side="right" size="md">
<Filtros />
</Sheet>Del @example del JSDoc de @solu30/ui-kit, sin editar: son 65 de los 541 símbolos. La sangría y los renglones en blanco son los del paquete.