Chat
Las piezas de una conversación con un asistente: cabecera, mensajes, sugerencias, el campo para escribir y sus estados. No sabe de modelos ni de streaming: eso es de la app.
import { Chat, ChatActions, ChatDisclaimer, ChatEmpty, … } from "sebs7n-ui/chat"Ejemplos
El asistente entero
El lanzador abre un panel de vidrio con la conversación. Apretá una sugerencia: mientras responde, el borde del panel se enciende y el canto del lanzador gira.
import { AiLauncher } from "sebs7n-ui/ai-button"
import { Popover, PopoverContent, PopoverTrigger } from "sebs7n-ui/popover"
import { XIcon } from "lucide-react"
import { useState } from "react"
function Asistente() {
const [abierto, setAbierto] = useState(false)
const [respondiendo, setRespondiendo] = useState(false)
return (
<div className="flex h-24 w-full items-center justify-end pr-4">
<Popover onOpenChange={setAbierto} open={abierto}>
<PopoverTrigger render={<AiLauncher active={respondiendo} label={abierto ? "Cerrar asistente" : "Asistente"} />}>
{abierto ? <XIcon /> : undefined}
</PopoverTrigger>
<PopoverContent
align="end"
aria-label="Asistente"
// Material grueso: es de lo más grande que flota. `p-0` y `gap-0` porque el aire lo
// ponen las piezas del chat, que llegan hasta el borde con sus líneas divisorias.
className="h-[min(560px,var(--available-height))] w-[min(400px,var(--available-width))] gap-0 overflow-hidden rounded-panel p-0"
side="top"
sideOffset={12}
>
<Conversacion onBusyChange={setRespondiendo} onClose={() => setAbierto(false)} />
</PopoverContent>
</Popover>
</div>
)
}En una tarjeta
Las mismas piezas, fijas en la página. El `Chat` no dibuja superficie: acá la pone la `Card`.
import { Card } from "sebs7n-ui/card"
function EnTarjeta() {
return (
<Card className="h-[480px] w-full max-w-md gap-0 overflow-hidden py-0">
<Conversacion />
</Card>
)
}Estados
Una respuesta en curso: el borde encendido, «escribiendo» y el botón de detener. Y el error con su reintento.
import { Card } from "sebs7n-ui/card"
import { Chat, ChatError, ChatFooter, ChatInput, ChatMessage, ChatMessages, ChatTyping } from "sebs7n-ui/chat"
function Estados() {
return (
<Card className="w-full max-w-md gap-0 overflow-hidden py-0">
<Chat busy>
<ChatMessages className="max-h-80">
<ChatMessage from="user">¿Cuál fue el cliente que más compró?</ChatMessage>
<ChatTyping />
<ChatMessage from="user">¿Y el mes pasado?</ChatMessage>
<ChatError onRetry={() => {}}>No pude responder ahora. Probá de nuevo en un rato.</ChatError>
</ChatMessages>
<ChatFooter>
<ChatInput onStop={() => {}} />
</ChatFooter>
</Chat>
</Card>
)
}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».
Chat
Hereda las props de <div>.
| Prop | Tipo | Por defecto | Descripción |
|---|---|---|---|
busy | boolean | false | Hay una respuesta en curso. Enciende el borde de la IA alrededor de la conversación, y le avisa a ChatMessages y a ChatInput, que ya no necesitan su propio busy. |
className | string | — | Se fusiona con las clases del componente vía cn() (tailwind-merge): lo que pongas gana. |
ChatActions
Hereda las props de <div>.
| Prop | Tipo | Por defecto | Descripción |
|---|---|---|---|
className | string | — | Se fusiona con las clases del componente vía cn() (tailwind-merge): lo que pongas gana. |
ChatDisclaimer
Hereda las props de <p>.
| Prop | Tipo | Por defecto | Descripción |
|---|---|---|---|
className | string | — | Se fusiona con las clases del componente vía cn() (tailwind-merge): lo que pongas gana. |
ChatEmpty
Hereda las props de <div>.
| Prop | Tipo | Por defecto | Descripción |
|---|---|---|---|
className | string | — | Se fusiona con las clases del componente vía cn() (tailwind-merge): lo que pongas gana. |
ChatError
Hereda las props de <div>.
| Prop | Tipo | Por defecto | Descripción |
|---|---|---|---|
labels | Partial<ChatLabels> | — | Textos de la interfaz, para traducir o ajustar el tono. |
onRetry | () => void | — | Con esto aparece el botón de reintentar. |
className | string | — | Se fusiona con las clases del componente vía cn() (tailwind-merge): lo que pongas gana. |
ChatFooter
Hereda las props de <div>.
| Prop | Tipo | Por defecto | Descripción |
|---|---|---|---|
className | string | — | Se fusiona con las clases del componente vía cn() (tailwind-merge): lo que pongas gana. |
ChatHeader
Hereda las props de <div>.
| Prop | Tipo | Por defecto | Descripción |
|---|---|---|---|
className | string | — | Se fusiona con las clases del componente vía cn() (tailwind-merge): lo que pongas gana. |
ChatInput
Hereda las props de <form>.
| Prop | Tipo | Por defecto | Descripción |
|---|---|---|---|
busy | boolean | — | Hay una respuesta en curso: el botón de enviar pasa a ser el de detener. Si no se pasa, toma el del Chat. |
defaultValue | string | (readonly string[] & string) | "" | El valor inicial. Es la versión no controlada de value. |
disabled | boolean | false | Apaga el campo y el botón: sin conexión, límite de mensajes alcanzado. |
enterKey | "auto" | "send" | "newline" | "auto" | Qué hace Enter. auto (el default) envía con mouse o trackpad y hace un salto de línea en una pantalla táctil, donde el teclado en pantalla no tiene otra forma de bajar de renglón. |
inputRef | React.Ref<HTMLTextAreaElement> | — | El <textarea>, para enfocarlo desde afuera: al abrir el panel, después de una respuesta. |
labels | Partial<ChatLabels> | — | Textos de la interfaz, para traducir o ajustar el tono. |
maxLength | number | — | El largo máximo del mensaje. |
onSend | (message: string) => void | — | Manda el mensaje, ya sin espacios a los costados. Sin controlar, el campo se vacía solo. |
onStop | () => void | — | Corta la respuesta en curso. Sin esto, mientras busy el botón queda apagado. |
onValueChange | (value: string) => void | — | Avisa cada cambio del texto. |
value | string | — | El texto, controlado. |
className | string | — | Se fusiona con las clases del componente vía cn() (tailwind-merge): lo que pongas gana. |
ChatMessage
Hereda las props de <div>.
| Prop | Tipo | Por defecto | Descripción |
|---|---|---|---|
from* | "user" | "assistant" | — | Quién habla. El usuario va a la derecha, en un globo; el asistente a la izquierda, sin globo. |
className | string | — | Se fusiona con las clases del componente vía cn() (tailwind-merge): lo que pongas gana. |
ChatMessageActions
Hereda las props de <div>.
| Prop | Tipo | Por defecto | Descripción |
|---|---|---|---|
className | string | — | Se fusiona con las clases del componente vía cn() (tailwind-merge): lo que pongas gana. |
ChatMessages
Hereda las props de <div>.
| Prop | Tipo | Por defecto | Descripción |
|---|---|---|---|
busy | boolean | — | Hay una respuesta en curso: lo anuncia con aria-busy. Si no se pasa, toma el del Chat. |
labels | Partial<ChatLabels> | — | Textos de la interfaz, para traducir o ajustar el tono. |
onScroll | UIEventHandler<T> | — | Se llama después de que la lista anota si el usuario sigue abajo. Sirve para mostrar un botón de «ir al final». |
className | string | — | Se fusiona con las clases del componente vía cn() (tailwind-merge): lo que pongas gana. |
ChatSuggestion
Hereda las props de <button>.
| Prop | Tipo | Por defecto | Descripción |
|---|---|---|---|
type | "button" | "submit" | "reset" | "button" | Por defecto button, no submit: una sugerencia adentro de un <form> no tiene que enviarlo. |
className | string | — | Se fusiona con las clases del componente vía cn() (tailwind-merge): lo que pongas gana. |
disabled | boolean | — | Heredada de Base UI. Apaga la interacción y lo marca con data-disabled, que es el atributo del que cuelgan los estilos de apagado. |
ChatSuggestions
Hereda las props de <div>.
| Prop | Tipo | Por defecto | Descripción |
|---|---|---|---|
className | string | — | Se fusiona con las clases del componente vía cn() (tailwind-merge): lo que pongas gana. |
ChatTitle
Hereda las props de <div>.
| Prop | Tipo | Por defecto | Descripción |
|---|---|---|---|
className | string | — | Se fusiona con las clases del componente vía cn() (tailwind-merge): lo que pongas gana. |
ChatTyping
Hereda las props de <div>.
| Prop | Tipo | Por defecto | Descripción |
|---|---|---|---|
labels | Partial<ChatLabels> | — | Textos de la interfaz, para traducir o ajustar el tono. |
className | string | — | Se fusiona con las clases del componente vía cn() (tailwind-merge): lo que pongas gana. |
Teclado
- Enter
- Envía el mensaje. En una pantalla táctil, baja de renglón.
- Shift + Enter
- Baja de renglón.
- Tab
- Recorre la lista de mensajes, el campo y el botón.
- ↑ ↓ · Re Pág · Av Pág
- Con el foco en la lista, la scrollean.
Accesibilidad
- La lista es un
role="log"conaria-live="polite": lo que se agrega se anuncia sin mover el foco.busylo marca conaria-busy. - La lista es tabulable: una zona que scrollea sin nada enfocable adentro no se puede recorrer con el teclado.
ChatTypinges unrole="status"con nombre;ChatError, unrole="alert".- El campo tiene su
<label>, oculto a la vista. El botón de enviar y el de detener, suaria-label. - En táctil el campo mide 44px y la letra 16px: con menos, iOS hace zoom al enfocar.
- El borde de la IA es decoración: lo que anuncia la espera es el
aria-busyde la lista. Conprefers-reduced-motionse enciende pero no gira. - Mientras se arma una palabra con un IME (japonés, coreano), Enter la confirma y no envía.
Reglas de uso
- No dibuja superficie: va adentro de un
Popover, unSheeto unaCard. - El texto de un mensaje va como
children. Si la respuesta trae formato, convertila a elementos de React. Nunca la inyectes como HTML: lo que escribe un modelo es texto de un tercero. ChatMessagessigue al último mensaje mientras el usuario esté abajo. Si subió a leer, no lo arrastra.busyva en elChat: enciende el borde de la IA alrededor de la conversación y les avisa a la lista y al campo. Es el borde que recorre la pantalla del iPhone cuando se activa Siri, y acá hace de spinner.- Con una respuesta en curso, el botón de enviar pasa a ser el de detener, en el mismo lugar.
- Sin controlar,
ChatInputse vacía solo al enviar. Controlado (value), vaciarlo es de la app, que es quien sabe si el envío salió. - La letra chica (
ChatDisclaimer) va debajo del campo, no en la cabecera: ahí es donde se mira antes de enviar.