Chat Sidebar

A barra lateral de um app de chat. Logo, busca e Novo chat no topo, seus próprios links abaixo, chats fixados, projetos que se expandem para mostrar seus chats, recentes agrupados por dia e linhas com menus de hover e de clique direito, renomeação inline, exclusão com desfazer e estados de resposta ao vivo.

ChatGPT, Claude, Perplexity e Grok adotaram a mesma barra lateral: iniciar um chat ou buscar pelo topo, manter alguns chats fixados, agrupar o trabalho em projetos e voltar a todo o resto por dia. O Chat Sidebar é essa barra lateral, construída sobre a variante inset do Sidebar do HextaUI, para que a conversa fique em seu próprio painel arredondado dentro da moldura. Ele se recolhe em uma barra de ícones no desktop e, no celular, desliza para preencher a tela e some assim que você escolhe um chat.

O cabeçalho contém seu logo, um botão de busca e o botão de recolher. New chat fica abaixo, com seu atalho sempre visível, seguido de quaisquer links que você passar, como Images, Library ou Plugins. Os projetos são pastas recolhíveis, cada uma com seu próprio ícone, e seus chats ficam recuados dentro delas, e não no histórico. Os recentes se agrupam em Today, Yesterday, Previous 7 days, Previous 30 days e depois por mês, e são renderizados em páginas conforme você rola, então milhares de chats abrem instantaneamente.

Toda linha tem um menu no hover e o mesmo menu no clique direito: fixar, renomear, mover para um projeto e excluir. A renomeação acontece no próprio lugar com duplo clique ou F2. Excluir oculta o chat de imediato e oferece Undo por cinco segundos antes de chamar onDelete. Um chat que está respondendo mostra um spinner e um que terminou em outro lugar mostra um ponto; ambos dão lugar ao botão de menu no hover. O menu da conta abre para cima com seu nome e e-mail acima dos itens que você passa.

  1. Adicione o registro Pro ao components.json

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

    Crie um token na sua página de conta e coloque-o em .env.local como HEXTAUI_PRO_TOKEN.

  3. Adicione o bloco

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

Conecte ao seu estado

Passe os chats e os handlers que você suporta. Cada item de menu só aparece quando seu handler está definido, então um histórico somente leitura precisa apenas de onSelect e 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>
  )
}

Projetos e roteamento

Cada projeto é uma pasta recolhível com seu próprio ícone. Seus chats ficam recuados dentro dela, em vez de no histórico por data. onMove adiciona Move to project a cada linha.

"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>
  )
}

Anatomia

As partes que você compõe, de fora para dentro.

ParteDescrição
ChatSidebarA barra lateral inteira: cabeçalho, New chat e links, chats fixados, projetos, histórico por data e o menu da conta.
groupChatsOrdena os chats do mais novo ao mais antigo e os divide nos grupos por data que a barra lateral exibe.
searchChatsBusca de títulos por palavras usada pelo diálogo de busca. Retorna os 8 chats mais recentes para uma consulta vazia.
PropTipoPadrão
chatsTodos os chats, em qualquer ordem. Cada um tem id, title, updatedAt (ms) e, opcionalmente, projectId, pinned e status ("streaming" ou "unread").
SidebarChat[]–
projectsProjetos com id, name e um icon opcional. Cada um é renderizado como uma pasta recolhível com seus chats. A seção some quando está vazia.
SidebarProject[][]
activeIdO chat aberto, marcado como a página atual.
string | null–
onSelectChamado quando um chat é escolhido na lista, na busca ou no card de recentes na barra de ícones.
(id: string) => void–
onNewChatChamado pelo botão New chat e por ⌘⇧O.
() => void–
linksLinhas extras sob New chat, como Images, Library ou Plugins. Na barra de ícones, continuam como ícones com tooltips.
{ label, icon, href?, active?, onSelect? }[][]
defaultOpenProjectsProjetos que começam expandidos.
string[][]
onProjectOpenChangeChamado quando um projeto é expandido ou recolhido, para que você possa lembrar disso.
(id: string, open: boolean) => void–
onRenameHabilita Rename, duplo clique e F2. Chamado somente quando o título realmente mudou.
(id: string, title: string) => void–
onPinnedChangeHabilita Pin e Unpin.
(id: string, pinned: boolean) => void–
onMoveHabilita Move to project. null remove o chat do seu projeto.
(id: string, projectId: string | null) => void–
onDeleteHabilita Delete. Chamado depois que a janela de cinco segundos do Undo se fecha.
(id: string) => void–
brandSeu logo, no topo, ao lado dos botões de busca e de recolher.
ReactNode–
userMostra o botão da conta no rodapé.
{ name, email?, image?, plan? }–
accountMenuItens do DropdownMenu sob o nome e o e-mail, como Settings, um submenu Help e Log out.
ReactNode–
pageSizeQuantas linhas do histórico são renderizadas por vez. Mais são carregadas ao se aproximar do fim da lista.
number60
classNameClasses para a raiz do Sidebar.
string–
TeclaAção
⌘KAbre ou fecha a busca de chats. O botão de busca no cabeçalho faz o mesmo.
⌘⇧OInicia um novo chat.
⌘BRecolhe para a barra de ícones ou expande. No celular, abre ou fecha a barra lateral em tela cheia.
F2Renomeia o chat em foco. O duplo clique faz o mesmo.
EnterExpande ou recolhe o projeto em foco. Ao renomear, salva o novo título; sair do campo também salva.
EscCancela uma renomeação, ou fecha um menu ou a busca.
  • O chat aberto é marcado com aria-current="page", e o botão de menu de cada linha é nomeado a partir do seu chat, como "Opções para Texto da página de preços". Os estados de resposta são anunciados como "Respondendo" e "Resposta não lida", em vez de aparecerem só como um spinner ou um ponto.
  • Os botões de menu aparecem no hover para quem usa mouse, mas continuam acessíveis com Tab, e toda ação de linha está nesse menu, então nada depende de clique direito ou duplo clique. Os projetos informam aria-expanded, e seus chats continuam encontráveis pela busca do navegador depois que um projeto foi aberto.
  • O título que é digitado e o deslize em tela cheia no celular respeitam o movimento reduzido. O Undo permanece no toast até o tempo se esgotar, e o foco continua onde estava quando um chat é excluído.

Construído com

Os componentes gratuitos do HextaUI de que Chat Sidebar é feito. Cada um é instalado separadamente.

Código

3 arquivos, adicionados a components/blocks/chat-sidebar.