feat: add responsive mobile-friendly sidebar for chat sessions

This commit is contained in:
poyrazavsever
2026-06-09 16:03:49 +03:00
parent 3d899c07fe
commit a2ac043aed
+84 -53
View File
@@ -42,6 +42,7 @@ export default function AIChatPage() {
const [sessions, setSessions] = useState<ChatSession[]>([]); const [sessions, setSessions] = useState<ChatSession[]>([]);
const [activeSessionId, setActiveSessionId] = useState<string | null>(null); const [activeSessionId, setActiveSessionId] = useState<string | null>(null);
const [input, setInput] = useState(""); const [input, setInput] = useState("");
const [isMobileSessionsOpen, setIsMobileSessionsOpen] = useState(false);
const messagesEndRef = useRef<HTMLDivElement>(null); const messagesEndRef = useRef<HTMLDivElement>(null);
const { messages, sendMessage, setMessages, status, stop } = useChat({ const { messages, sendMessage, setMessages, status, stop } = useChat({
@@ -158,55 +159,85 @@ export default function AIChatPage() {
await sendMessage({ text: currentInput }, { body: { sessionId } }); await sendMessage({ text: currentInput }, { body: { sessionId } });
} }
return ( const SessionsSidebarContent = (
<div className="flex h-[calc(100dvh-6rem)] w-full overflow-hidden rounded-sm border border-border bg-background"> <>
<aside className="hidden w-80 flex-col border-r border-border bg-muted/20 md:flex"> <div className="flex items-center justify-between border-b border-border p-4 shrink-0">
<div className="flex items-center justify-between border-b border-border p-4"> <h2 className="flex items-center gap-2 font-semibold text-foreground">
<h2 className="flex items-center gap-2 font-semibold text-foreground"> <MessageSquare className="h-4 w-4" />
<MessageSquare className="h-4 w-4" /> Sohbetler
Sohbetler </h2>
</h2> <Button variant="outline" size="icon" className="h-8 w-8" onClick={() => {
<Button variant="outline" size="icon" className="h-8 w-8" onClick={handleNewChat}> handleNewChat();
<Plus className="h-4 w-4" /> setIsMobileSessionsOpen(false);
</Button> }}>
</div> <Plus className="h-4 w-4" />
</Button>
</div>
<div className="tiny-scrollbar flex-1 space-y-2 overflow-y-auto p-3"> <div className="tiny-scrollbar flex-1 space-y-2 overflow-y-auto p-3">
{sessions.length === 0 ? ( {sessions.length === 0 ? (
<div className="mt-10 text-center text-sm text-muted-foreground"> <div className="mt-10 text-center text-sm text-muted-foreground">
Henüz sohbet yok. Henüz sohbet yok.
</div> </div>
) : ( ) : (
sessions.map((session) => ( sessions.map((session) => (
<button <button
key={session.id} key={session.id}
type="button" type="button"
onClick={() => setActiveSessionId(session.id)} onClick={() => {
className={`group flex w-full items-center justify-between rounded-sm p-3 text-left transition-colors ${ setActiveSessionId(session.id);
activeSessionId === session.id setIsMobileSessionsOpen(false);
? "bg-primary/10 text-primary" }}
: "text-foreground hover:bg-muted/50" className={`group flex w-full items-center justify-between rounded-sm p-3 text-left transition-colors ${
}`} activeSessionId === session.id
? "bg-primary/10 text-primary"
: "text-foreground hover:bg-muted/50"
}`}
>
<span className="truncate pr-2 text-sm font-medium">
{session.title || "İsimsiz sohbet"}
</span>
<span
role="button"
tabIndex={0}
onClick={(event) => void handleDeleteSession(session.id, event)}
className="rounded-sm p-1 opacity-0 transition-all hover:bg-rose-50 hover:text-rose-600 lg:group-hover:opacity-100"
> >
<span className="truncate pr-2 text-sm font-medium"> <Trash2 className="h-3.5 w-3.5" />
{session.title || "İsimsiz sohbet"} </span>
</span> </button>
<span ))
role="button" )}
tabIndex={0} </div>
onClick={(event) => void handleDeleteSession(session.id, event)} </>
className="rounded-sm p-1 opacity-0 transition-all hover:bg-rose-50 hover:text-rose-600 group-hover:opacity-100" );
>
<Trash2 className="h-3.5 w-3.5" /> return (
</span> <div className="flex flex-col md:flex-row h-[calc(100dvh-3.5rem)] md:h-[calc(100dvh-6rem)] w-[calc(100%+2rem)] md:w-full -mx-4 -my-4 md:mx-0 md:my-0 overflow-hidden md:rounded-sm border-0 md:border md:border-border bg-background">
</button>
)) {/* Desktop Sidebar */}
)} <aside className="hidden w-80 flex-col border-r border-border bg-muted/20 md:flex">
</div> {SessionsSidebarContent}
</aside> </aside>
<section className="flex min-w-0 flex-1 flex-col"> {/* Mobile Sidebar Overlay */}
<header className="flex h-14 items-center justify-between border-b border-border px-6"> {isMobileSessionsOpen && (
<div
className="fixed inset-0 z-40 bg-black/50 backdrop-blur-sm md:hidden transition-opacity"
onClick={() => setIsMobileSessionsOpen(false)}
/>
)}
{/* Mobile Sidebar Drawer */}
<aside
className={`fixed inset-y-0 left-0 z-50 w-72 transform border-r border-border bg-background transition-transform duration-300 ease-in-out md:hidden flex flex-col ${
isMobileSessionsOpen ? "translate-x-0" : "-translate-x-full"
}`}
>
{SessionsSidebarContent}
</aside>
<section className="flex min-w-0 flex-1 flex-col h-full">
<header className="flex h-14 items-center justify-between border-b border-border px-4 md:px-6 shrink-0">
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<div className="flex h-8 w-8 items-center justify-center rounded-sm bg-primary/10 text-primary"> <div className="flex h-8 w-8 items-center justify-center rounded-sm bg-primary/10 text-primary">
<Brain className="h-4 w-4" /> <Brain className="h-4 w-4" />
@@ -216,8 +247,8 @@ export default function AIChatPage() {
<p className="text-xs text-muted-foreground">Kayıtlı verilerin hakkında soru sor.</p> <p className="text-xs text-muted-foreground">Kayıtlı verilerin hakkında soru sor.</p>
</div> </div>
</div> </div>
<Button variant="outline" size="icon" className="h-8 w-8 md:hidden" onClick={handleNewChat}> <Button variant="outline" size="sm" className="h-8 md:hidden text-xs px-3" onClick={() => setIsMobileSessionsOpen(true)}>
<Plus className="h-4 w-4" /> <MessageSquare className="h-3.5 w-3.5 mr-1.5" /> Sohbetler
</Button> </Button>
</header> </header>
@@ -264,8 +295,8 @@ export default function AIChatPage() {
<div ref={messagesEndRef} /> <div ref={messagesEndRef} />
</div> </div>
<form onSubmit={handleSubmit} className="border-t border-border p-4 shrink-0"> <form onSubmit={handleSubmit} className="border-t border-border p-3 md:p-4 shrink-0 bg-background">
<div className="mx-auto flex max-w-4xl items-end gap-2 rounded-sm border border-border bg-background p-2 focus-within:border-primary"> <div className="mx-auto flex max-w-4xl items-end gap-2 rounded-sm border border-border bg-background p-1.5 focus-within:border-primary">
<textarea <textarea
value={input} value={input}
onChange={(event) => setInput(event.target.value)} onChange={(event) => setInput(event.target.value)}
@@ -275,17 +306,17 @@ export default function AIChatPage() {
void handleSubmit(event); void handleSubmit(event);
} }
}} }}
placeholder="Örn. Bu hafta geciken işlerim neler?" placeholder="Mesaj gönder..."
className="min-h-10 max-h-40 flex-1 resize-none bg-transparent px-2 py-2 text-sm outline-none placeholder:text-muted-foreground" className="min-h-9 max-h-40 flex-1 resize-none bg-transparent px-2 py-2 text-sm outline-none placeholder:text-muted-foreground placeholder:truncate"
rows={1} rows={1}
disabled={isLoading} disabled={isLoading}
/> />
{isLoading ? ( {isLoading ? (
<Button type="button" variant="outline" size="icon" onClick={() => void stop()}> <Button type="button" variant="outline" size="icon" className="shrink-0 h-9 w-9" onClick={() => void stop()}>
<span className="h-3 w-3 bg-current" /> <span className="h-3 w-3 bg-current" />
</Button> </Button>
) : ( ) : (
<Button type="submit" size="icon" disabled={!input.trim()}> <Button type="submit" size="icon" className="shrink-0 h-9 w-9" disabled={!input.trim()}>
<Send className="h-4 w-4" /> <Send className="h-4 w-4" />
</Button> </Button>
)} )}