Chat Sidebar

Die Seitenleiste für eine Chat-App. Logo, Suche und Neuer Chat oben, deine eigenen Links darunter, angeheftete Chats, Projekte, die sich aufklappen und ihre Chats zeigen, zuletzt verwendete nach Tag gruppiert und Zeilen mit Hover- und Rechtsklick-Menüs, direktem Umbenennen, Löschen mit Rückgängig und Live-Antwortzuständen.

ChatGPT, Claude, Perplexity und Grok haben sich alle auf dieselbe Seitenleiste geeinigt: oben einen Chat starten oder suchen, ein paar Chats angeheftet halten, Arbeit in Projekten gruppieren und alles andere nach Tag zurückscrollen. Chat Sidebar ist diese Seitenleiste, gebaut auf der Inset-Variante der HextaUI-Sidebar, sodass die Unterhaltung in einem eigenen abgerundeten Panel im Rahmen sitzt. Auf dem Desktop klappt sie zu einer Icon-Leiste ein, auf Smartphones gleitet sie bildschirmfüllend herein und sofort wieder weg, sobald du einen Chat wählst.

Der Header enthält dein Logo, einen Such-Button und den Einklapp-Button. Darunter steht New chat mit stets sichtbarem Tastenkürzel, gefolgt von allen Links, die du übergibst, etwa Images, Library oder Plugins. Projekte sind einklappbare Ordner, jeweils mit eigenem Icon, und ihre Chats stehen eingerückt darin statt im Verlauf. Recents gruppieren sich in Heute, Gestern, Letzte 7 Tage, Letzte 30 Tage und danach nach Monat und werden beim Scrollen seitenweise gerendert, sodass Tausende Chats sofort öffnen.

Jede Zeile hat beim Hover ein Menü und dasselbe Menü per Rechtsklick: anheften, umbenennen, in ein Projekt verschieben und löschen. Umbenannt wird direkt per Doppelklick oder F2. Delete blendet den Chat sofort aus und bietet fünf Sekunden Undo an, bevor onDelete aufgerufen wird. Ein Chat, der gerade antwortet, zeigt einen Spinner, einer, der anderswo fertig wurde, einen Punkt; beide weichen beim Hover dem Menü-Button. Das Kontomenü öffnet sich nach oben, mit Name und E-Mail über den Items, die du übergibst.

  1. Die Pro-Registry zu components.json hinzufügen

    components.json
    {
      "registries": {
        "@hextaui-pro": {
          "url": "https://hextaui.com/r/pro/{name}.json",
          "headers": {
            "Authorization": "Bearer ${HEXTAUI_PRO_TOKEN}"
          }
        }
      }
    }
  2. Token hinzufügen

    Erstelle auf deiner Kontoseite einen Token und trage ihn in .env.local als HEXTAUI_PRO_TOKEN ein.

  3. Den Block hinzufügen

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

An deinen State anbinden

Übergib die Chats und die Handler, die du unterstützt. Jeder Menüpunkt erscheint nur, wenn sein Handler gesetzt ist, ein schreibgeschützter Verlauf braucht also nur onSelect und 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>
  )
}

Projekte und Routing

Jedes Projekt ist ein einklappbarer Ordner mit eigenem Icon. Seine Chats stehen eingerückt darin statt im datierten Verlauf. onMove fügt jeder Zeile Move to project hinzu.

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

Aufbau

Die Teile, die du zusammensetzt, von außen nach innen.

PartBeschreibung
ChatSidebarDie ganze Seitenleiste: Header, New chat und Links, angeheftete Chats, Projekte, datierter Verlauf und das Kontomenü.
groupChatsSortiert Chats mit den neuesten zuerst und teilt sie in die datierten Gruppen ein, die die Seitenleiste zeigt.
searchChatsWortbasierte Titelsuche, die der Suchdialog verwendet. Gibt bei leerer Anfrage die 8 neuesten Chats zurück.
PropTypStandard
chatsJeder Chat, in beliebiger Reihenfolge. Jeder hat id, title, updatedAt (ms) und optional projectId, pinned und status ("streaming" oder "unread").
SidebarChat[]–
projectsProjekte mit id, name und einem optionalen icon. Jedes wird als einklappbarer Ordner mit seinen Chats gerendert. Der Bereich verschwindet, wenn er leer ist.
SidebarProject[][]
activeIdDer geöffnete Chat, als aktuelle Seite markiert.
string | null–
onSelectWird aufgerufen, wenn ein Chat aus der Liste, der Suche oder der Recents-Karte in der Icon-Leiste gewählt wird.
(id: string) => void–
onNewChatWird vom New-chat-Button und ⌘⇧O aufgerufen.
() => void–
linksZusätzliche Zeilen unter New chat, etwa Images, Library oder Plugins. In der Leiste bleiben sie Icons mit Tooltips.
{ label, icon, href?, active?, onSelect? }[][]
defaultOpenProjectsProjekte, die aufgeklappt starten.
string[][]
onProjectOpenChangeWird aufgerufen, wenn ein Projekt auf- oder zugeklappt wird, damit du es dir merken kannst.
(id: string, open: boolean) => void–
onRenameAktiviert Rename, Doppelklick und F2. Wird nur aufgerufen, wenn sich der Titel tatsächlich geändert hat.
(id: string, title: string) => void–
onPinnedChangeAktiviert Pin und Unpin.
(id: string, pinned: boolean) => void–
onMoveAktiviert Move to project. null entfernt den Chat aus seinem Projekt.
(id: string, projectId: string | null) => void–
onDeleteAktiviert Delete. Wird aufgerufen, nachdem das fünfsekündige Undo-Fenster schließt.
(id: string) => void–
brandDein Logo, oben neben den Buttons für Suche und Einklappen.
ReactNode–
userZeigt den Konto-Button in der Fußzeile.
{ name, email?, image?, plan? }–
accountMenuDropdownMenu-Items unter Name und E-Mail, etwa Settings, ein Help-Untermenü und Log out.
ReactNode–
pageSizeWie viele Verlaufszeilen auf einmal gerendert werden. Weitere laden, wenn du dich dem Listenende näherst.
number60
classNameKlassen für das Sidebar-Root.
string–
TasteAktion
⌘KÖffnet oder schließt die Chat-Suche. Der Such-Button im Header tut dasselbe.
⌘⇧OStartet einen neuen Chat.
⌘BKlappt zur Icon-Leiste ein oder aus. Öffnet oder schließt auf Smartphones die Vollbild-Seitenleiste.
F2Benennt den fokussierten Chat um. Ein Doppelklick tut dasselbe.
EnterKlappt das fokussierte Projekt auf oder zu. Speichert beim Umbenennen den neuen Titel; auch das Verlassen des Felds speichert.
EscBricht ein Umbenennen ab oder schließt ein Menü oder die Suche.
  • Der geöffnete Chat ist mit aria-current="page" markiert, und der Menü-Button jeder Zeile trägt den Namen ihres Chats, etwa „Optionen für Pricing page copy“. Antwortzustände werden als „Antwortet“ und „Ungelesene Antwort“ angesagt, statt nur als Spinner oder Punkt gezeigt zu werden.
  • Menü-Buttons erscheinen für Mausnutzer beim Hover, bleiben aber per Tab erreichbar, und jede Zeilenaktion steckt in diesem Menü, sodass nichts von Rechtsklick oder Doppelklick abhängt. Projekte melden aria-expanded, und ihre Chats bleiben mit der Browsersuche auffindbar, sobald ein Projekt geöffnet wurde.
  • Der getippte Titel und das Vollbild-Gleiten auf Smartphones respektieren reduzierte Bewegung. Undo bleibt im Toast, bis er abläuft, und der Fokus bleibt, wo er war, wenn ein Chat gelöscht wird.

Gebaut mit

Die kostenlosen HextaUI-Komponenten, aus denen Chat Sidebar besteht. Jede lässt sich einzeln installieren.

Code

3 Dateien, hinzugefügt zu components/blocks/chat-sidebar.