Chat Sidebar

La barra lateral de una app de chat. Logo, búsqueda y Nuevo chat arriba, tus propios enlaces debajo, chats fijados, proyectos que se expanden para mostrar sus chats, recientes agrupados por día y filas con menús al pasar el cursor y con clic derecho, renombrado en línea, eliminación con deshacer y estados de respuesta en vivo.

ChatGPT, Claude, Perplexity y Grok han acabado en la misma barra lateral: empezar un chat o buscar desde arriba, mantener unos pocos chats fijados, agrupar el trabajo en proyectos y recorrer todo lo demás por día. Chat Sidebar es esa barra lateral, construida sobre la variante inset de Sidebar de HextaUI, de modo que la conversación queda en su propio panel redondeado dentro del marco. Se colapsa a un riel de iconos en escritorio y, en móviles, se desliza para llenar la pantalla y se retira en cuanto eliges un chat.

El encabezado contiene tu logo, un botón de búsqueda y el botón de colapsar. Debajo está New chat con su atajo siempre visible, seguido de los enlaces que pases, como Images, Library o Plugins. Los proyectos son carpetas colapsables, cada una con su propio icono, y sus chats quedan con sangría dentro de ellas en lugar de en el historial. Los recientes se agrupan en Today, Yesterday, Previous 7 days, Previous 30 days y luego por mes, y se renderizan por páginas al desplazarte, así que miles de chats se abren al instante.

Cada fila tiene un menú al pasar el cursor y el mismo menú con clic derecho: fijar, renombrar, mover a un proyecto y eliminar. Renombrar se hace en el sitio con doble clic o F2. Delete oculta el chat al instante y ofrece Undo durante cinco segundos antes de llamar a onDelete. Un chat que está respondiendo muestra un spinner y uno que terminó en otro lugar muestra un punto; ambos dejan sitio al botón de menú al pasar el cursor. El menú de cuenta se abre hacia arriba con tu nombre y correo encima de los elementos que le pasas.

  1. Añade el registro Pro a components.json

    components.json
    {
      "registries": {
        "@hextaui-pro": {
          "url": "https://hextaui.com/r/pro/{name}.json",
          "headers": {
            "Authorization": "Bearer ${HEXTAUI_PRO_TOKEN}"
          }
        }
      }
    }
  2. Añade tu token

    Crea un token en tu página de cuenta y colócalo en .env.local como HEXTAUI_PRO_TOKEN.

  3. Añade el bloque

    pnpm dlx shadcn@latest add @hextaui-pro/chat-sidebar

Conéctalo a tu estado

Pasa los chats y los handlers que admites. Cada elemento de menú aparece solo cuando su handler está definido, así que un historial de solo lectura necesita únicamente onSelect y onNewChat.

"use client"

import * as React from "react"

import { SidebarInset, SidebarProvider } from "@/components/ui/sidebar"

import { ChatSidebar } from "@/components/blocks/chat-sidebar/chat-sidebar"
import type { SidebarChat } from "@/components/blocks/chat-sidebar/chats"

export function App({ initialChats }: { initialChats: SidebarChat[] }) {
  const [chats, setChats] = React.useState(initialChats)
  const [activeId, setActiveId] = React.useState<string | null>(null)

  const update = (id: string, change: Partial<SidebarChat>) =>
    setChats((current) => current.map((chat) => (chat.id === id ? { ...chat, ...change } : chat)))

  return (
    <SidebarProvider>
      <ChatSidebar
        chats={chats}
        activeId={activeId}
        onSelect={setActiveId}
        onNewChat={() => setActiveId(null)}
        onRename={(id, title) => update(id, { title })}
        onPinnedChange={(id, pinned) => update(id, { pinned })}
        onDelete={(id) => setChats((current) => current.filter((chat) => chat.id !== id))}
        user={{ name: "Mia Chen", plan: "Pro plan" }}
      />
      <SidebarInset />
    </SidebarProvider>
  )
}

Proyectos y enrutamiento

Cada proyecto es una carpeta colapsable con su propio icono. Sus chats quedan con sangría dentro de ella en lugar de en el historial por fechas. onMove añade Move to project a cada fila.

"use client"

import { useRouter } from "next/navigation"

import { SidebarInset, SidebarProvider } from "@/components/ui/sidebar"

import { ChatSidebar } from "@/components/blocks/chat-sidebar/chat-sidebar"
import type { SidebarChat, SidebarProject } from "@/components/blocks/chat-sidebar/chats"

export function Workspace({
  chats,
  projects,
  activeId,
  moveChat,
  children,
}: {
  chats: SidebarChat[]
  projects: SidebarProject[]
  activeId: string | null
  moveChat: (id: string, projectId: string | null) => void
  children: React.ReactNode
}) {
  const router = useRouter()

  return (
    <SidebarProvider>
      <ChatSidebar
        chats={chats}
        projects={projects}
        activeId={activeId}
        onSelect={(id) => router.push(`/c/${id}`)}
        onNewChat={() => router.push("/")}
        defaultOpenProjects={projects.slice(0, 1).map((project) => project.id)}
        onMove={moveChat}
      />
      <SidebarInset>{children}</SidebarInset>
    </SidebarProvider>
  )
}

Anatomía

Las partes que compones, de fuera hacia dentro.

ParteDescripción
ChatSidebarLa barra lateral completa: encabezado, New chat y enlaces, chats fijados, proyectos, historial por fechas y el menú de cuenta.
groupChatsOrdena los chats del más reciente al más antiguo y los divide en los grupos por fecha que muestra la barra lateral.
searchChatsBúsqueda de títulos por palabras usada por el diálogo de búsqueda. Devuelve los 8 chats más recientes para una consulta vacía.
PropTipoPredeterminado
chatsTodos los chats, en cualquier orden. Cada uno tiene id, title, updatedAt (ms) y, opcionalmente, projectId, pinned y status ("streaming" o "unread").
SidebarChat[]–
projectsProyectos con id, name y un icon opcional. Cada uno se renderiza como una carpeta colapsable que contiene sus chats. La sección se oculta cuando está vacía.
SidebarProject[][]
activeIdEl chat abierto, marcado como la página actual.
string | null–
onSelectSe llama cuando se elige un chat de la lista, de la búsqueda o de la tarjeta de recientes del riel de iconos.
(id: string) => void–
onNewChatSe llama desde el botón New chat y ⌘⇧O.
() => void–
linksFilas adicionales bajo New chat, como Images, Library o Plugins. En el riel permanecen como iconos con tooltips.
{ label, icon, href?, active?, onSelect? }[][]
defaultOpenProjectsProyectos que empiezan expandidos.
string[][]
onProjectOpenChangeSe llama cuando un proyecto se expande o se colapsa, para que puedas recordarlo.
(id: string, open: boolean) => void–
onRenameHabilita Rename, el doble clic y F2. Se llama solo cuando el título realmente cambió.
(id: string, title: string) => void–
onPinnedChangeHabilita Pin y Unpin.
(id: string, pinned: boolean) => void–
onMoveHabilita Move to project. null saca el chat de su proyecto.
(id: string, projectId: string | null) => void–
onDeleteHabilita Delete. Se llama cuando se cierra la ventana de cinco segundos de Undo.
(id: string) => void–
brandTu logo, arriba junto a los botones de búsqueda y colapsar.
ReactNode–
userMuestra el botón de cuenta en el pie.
{ name, email?, image?, plan? }–
accountMenuElementos de DropdownMenu bajo el nombre y el correo, como Settings, un submenú Help y Log out.
ReactNode–
pageSizeCuántas filas del historial se renderizan a la vez. Se cargan más al acercarte al final de la lista.
number60
classNameClases para la raíz de Sidebar.
string–
KeyAcción
⌘KAbre o cierra la búsqueda de chats. El botón de búsqueda del encabezado hace lo mismo.
⌘⇧OInicia un chat nuevo.
⌘BColapsa al riel de iconos o expande. En móviles, abre o cierra la barra lateral a pantalla completa.
F2Renombra el chat enfocado. El doble clic hace lo mismo.
EnterExpande o colapsa el proyecto enfocado. Al renombrar, guarda el nuevo título; salir del campo también guarda.
EscCancela un renombrado, o cierra un menú o la búsqueda.
  • El chat abierto está marcado con aria-current="page", y el botón de menú de cada fila se nombra con su chat, como «Options for Pricing page copy». Los estados de respuesta se anuncian como «Replying» y «Unread reply» en lugar de mostrarse solo con un spinner o un punto.
  • Los botones de menú aparecen al pasar el cursor para los usuarios de ratón pero siguen siendo accesibles con Tab, y cada acción de fila está en ese menú, así que nada depende del clic derecho ni del doble clic. Los proyectos informan de aria-expanded, y sus chats se pueden encontrar con la búsqueda del navegador una vez que se ha abierto un proyecto.
  • El título que se escribe y el deslizamiento a pantalla completa en móviles respetan el movimiento reducido. Undo permanece en el toast hasta que caduca, y el foco se queda donde estaba cuando se elimina un chat.

Construido con

Los componentes gratuitos de HextaUI con los que está hecho Chat Sidebar. Cada uno se instala por separado.

Código

3 archivos, añadidos a components/blocks/chat-sidebar.