Formularios
Drop Target
Soltar archivos sobre cualquier contenido —una sección, una card— para abrir su formulario con el archivo, sin volverlo un recuadro de subida.
import { DropTarget } from "sebs7n-ui/drop-target"Ejemplos
Soltar sobre una sección
Arrastrá un PDF sobre la card de facturas: se pinta el anillo, y al soltar se «abre el formulario» con el archivo. El botón «Agregar factura» sigue andando, y es el camino sin arrastrar.
import { Button } from "sebs7n-ui/button"
import { Card, CardAction, CardContent, CardHeader, CardTitle } from "sebs7n-ui/card"
import { DropTarget } from "sebs7n-ui/drop-target"
import { FileTextIcon, PlusIcon } from "lucide-react"
import { List, ListRow } from "sebs7n-ui/list-row"
import { useState } from "react"
function Section() {
const [invoices, setInvoices] = useState(["factura-0012.pdf", "factura-0013.pdf"])
const [draft, setDraft] = useState<string | null>(null)
return (
<DropTarget accept=".pdf" className="w-full max-w-md" maxSize={5 * 1024 * 1024} onDrop={([file]) => setDraft(file!.name)}>
<Card>
<CardHeader>
<CardTitle>Facturas del cliente</CardTitle>
<CardAction>
<Button onClick={() => setDraft("")} size="sm" variant="secondary">
<PlusIcon aria-hidden="true" />
Agregar factura
</Button>
</CardAction>
</CardHeader>
<CardContent className="flex flex-col gap-3">
<List>
{invoices.map((name) => (
<ListRow icon={<FileTextIcon />} key={name} title={name} />
))}
</List>
{draft !== null && (
<div className="flex items-center justify-between gap-3 rounded-field bg-fill-1 px-3 py-2 text-callout text-label">
<span className="truncate">{draft ? `Nueva factura con ${draft}` : "Nueva factura, sin archivo"}</span>
<Button
onClick={() => {
if (draft) setInvoices([...invoices, draft])
setDraft(null)
}}
size="sm"
>
Guardar
</Button>
</div>
)}
</CardContent>
</Card>
</DropTarget>
)
}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».
DropTarget
Hereda las props de <div>.
| Prop | Tipo | Por defecto | Descripción |
|---|---|---|---|
onDrop* | (files: File[]) => void | — | Los archivos que pasaron accept, maxSize y validate (uno solo sin multiple). Lo usual: abrir el formulario con el archivo ya puesto. |
accept | string | — | Los tipos que acepta, como el accept de un <input type="file">: ".pdf,image/*". |
disabled | boolean | false | Apaga la interacción y lo marca con data-disabled, que es el atributo del que cuelgan los estilos de apagado. |
formatSize | (bytes: number) => string | — | Cómo se escribe el tamaño en el error de maxSize. Por defecto, en base 1024 con Intl. |
labels | Partial<DropTargetLabels> | — | Textos: drop, added, invalidType, tooLarge, tooMany y locale, los de labels.dropZone. |
maxSize | number | — | El tamaño máximo de cada archivo, en bytes. |
multiple | boolean | false | Más de un archivo por vez. Sin multiple, entra el primero y los demás se avisan. |
validate | (file: File) => string | Promise<string> | — | Una validación propia por archivo, como la de DropZone: el texto que devuelve es su error. Puede ser asíncrona; mientras tanto la zona lleva aria-busy, y sin multiple gana el último soltado. |
className | string | — | Clases del div que envuelve (es relative). |
Teclado
- —
- No agrega paradas de Tab: el contenido de adentro sigue con las suyas. El camino con teclado es el botón de la sección («Agregar factura»).
Accesibilidad
- No tiene rol ni foco: envuelve el contenido, que sigue siendo interactivo. Arrastrar es un atajo; el contenido tiene que traer su botón para el mismo resultado sin arrastrar (WCAG 2.5.7).
- El anillo y el tinte mientras se arrastra son decorativos (
aria-hidden) y no reciben el puntero. - Lo que entra se anuncia en una región viva («Archivos agregados: factura.pdf»), que se vacía antes de cada anuncio para que soltar el mismo archivo dos veces se lea dos veces; los rechazos aparecen abajo del contenido con
role="alert". Mientras corre unvalidateasíncrono llevaaria-busy.
Reglas de uso
- Para una sección que ya tiene su «Agregar»: soltar un archivo encima abre el mismo formulario con el archivo puesto (
onDrop={([file]) => openForm(file)}), y ahí elDropZonedel formulario lo muestra. Para elegir y listar archivos en un formulario,DropZone. - Valida como
DropZone:accept,maxSize(base 1024,formatSizepara escribirlo de otra forma) yvalidate(puede ser asíncrona). Sinmultiplellega uno solo, el resto se avisa, y si se suelta otro mientras valida gana el último. - Lo que toma algo de adentro es de eso: una
DropTargetanidada, unDropZoneo un<input type="file">nativo reciben su archivo y la de afuera solo se apaga. Con unDropZone scope="window"en la página, un soltar sobre laDropTargetes de ella. La marca es elpreventDefaultdel soltar, nostopPropagation: un soltar propio que hagapreventDefaulttambién la deja afuera. No guarda archivos ni tienename. - Los rechazos quedan abajo hasta el próximo arrastre o soltar: no hay botón para cerrarlos ni se van solos con el tiempo.
- Los textos son los de
DropZone(labels.dropZone:drop,added,invalidType,tooLarge,tooMany,locale). Solo por subpath (sebs7n-ui/drop-target).