another ui/ux polishing in chat sidebar

This commit is contained in:
abhi1992002
2026-02-03 12:07:12 +05:30
parent 432bda5c70
commit 6730293036
2 changed files with 85 additions and 28 deletions

View File

@@ -1,18 +1,26 @@
"use client";
import { Sidebar, SidebarHeader, SidebarContent, SidebarFooter, SidebarTrigger, useSidebar } from "@/components/ui/sidebar";
import { cn } from "@/lib/utils";
import { SparkleIcon, PlusIcon, SpinnerGapIcon } from "@phosphor-icons/react";
import { SparkleIcon, PlusIcon, SpinnerGapIcon, ChatCircleIcon } from "@phosphor-icons/react";
import { motion } from "framer-motion";
import { useState } from "react";
import { parseAsString, useQueryState } from "nuqs";
import { postV2CreateSession } from "@/app/api/__generated__/endpoints/chat/chat";
import { postV2CreateSession, useGetV2ListSessions, getGetV2ListSessionsQueryKey } from "@/app/api/__generated__/endpoints/chat/chat";
import { Button } from "@/components/atoms/Button/Button";
import { useQueryClient } from "@tanstack/react-query";
export function ChatSidebar() {
const { state } = useSidebar();
const isCollapsed = state === "collapsed";
const [isCreating, setIsCreating] = useState(false);
const [, setSessionId] = useQueryState("sessionId", parseAsString);
const [sessionId, setSessionId] = useQueryState("sessionId", parseAsString);
const queryClient = useQueryClient();
const { data: sessionsResponse, isLoading: isLoadingSessions } = useGetV2ListSessions(
{ limit: 50 },
);
const sessions = sessionsResponse?.status === 200 ? sessionsResponse.data.sessions : [];
async function handleNewChat() {
if (isCreating) return;
@@ -23,12 +31,29 @@ export function ChatSidebar() {
});
if (response.status === 200 && response.data?.id) {
setSessionId(response.data.id);
queryClient.invalidateQueries({ queryKey: getGetV2ListSessionsQueryKey() });
}
} finally {
setIsCreating(false);
}
}
function handleSelectSession(id: string) {
setSessionId(id);
}
function formatDate(dateString: string) {
const date = new Date(dateString);
const now = new Date();
const diffMs = now.getTime() - date.getTime();
const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24));
if (diffDays === 0) return "Today";
if (diffDays === 1) return "Yesterday";
if (diffDays < 7) return `${diffDays} days ago`;
return date.toLocaleDateString();
}
return (
<Sidebar
variant="inset"
@@ -59,32 +84,64 @@ export function ChatSidebar() {
</div>}
</motion.div>
</SidebarHeader>}
<SidebarContent className="gap-4 px-2 py-4">
<SidebarContent className="gap-4 px-2 py-4 overflow-y-auto [-ms-overflow-style:none] [scrollbar-width:none] [&::-webkit-scrollbar]:hidden">
<div className="flex items-center gap-2">
<Button
variant="primary"
size="icon"
onClick={handleNewChat}
disabled={isCreating}
className={cn(
"w-full gap-2 rounded-3xl flex items-center justify-start h-fit px-4 py-2",
isCollapsed && "justify-center px-1 rounded-3xl "
<Button
variant="outline"
size="icon"
onClick={handleNewChat}
disabled={isCreating}
className={cn(
"w-full gap-2 rounded-3xl flex items-center justify-center h-fit px-3 py-2 bg-purple-100 border-purple-400 text-purple-600 hover:bg-purple-200 hover:border-purple-500 hover:text-purple-700",
isCollapsed && "justify-center px-1 rounded-3xl"
)}
>
{isCreating ? (
<SpinnerGapIcon className="h-4 w-4 animate-spin" />
) : (
<PlusIcon className="h-4 w-4" weight="bold" />
)}
{!isCollapsed && <span>{isCreating ? "Creating..." : "New Chat"}</span>}
</Button>
{!isCollapsed && (
<div className="bg-secondary border border-neutral-400 h-fit p-1 rounded-3xl">
<SidebarTrigger />
</div>
)}
>
{isCreating ? (
<SpinnerGapIcon className="h-4 w-4 animate-spin" />
) : (
<PlusIcon className="h-4 w-4" />
)}
{!isCollapsed && <span>{isCreating ? "Creating..." : "New Chat"}</span>}
</Button>
{!isCollapsed && <div className="bg-secondary border border-neutral-400 h-fit p-1 rounded-3xl">
<SidebarTrigger />
</div>}
</div>
{!isCollapsed && (
<div className="flex flex-col gap-1 mt-4">
{isLoadingSessions ? (
<div className="flex items-center justify-center py-4">
<SpinnerGapIcon className="h-5 w-5 animate-spin text-neutral-400" />
</div>
) : sessions.length === 0 ? (
<p className="text-sm text-neutral-500 text-center py-4">No conversations yet</p>
) : (
sessions.map((session) => (
<button
key={session.id}
onClick={() => handleSelectSession(session.id)}
className={cn(
"flex items-center gap-3 rounded-lg px-3 py-2 text-left text-sm transition-colors hover:bg-neutral-100 dark:hover:bg-neutral-800",
sessionId === session.id && "bg-neutral-100 dark:bg-neutral-800"
)}
>
<ChatCircleIcon className="h-4 w-4 shrink-0 text-neutral-500" />
<div className="flex flex-col overflow-hidden">
<span className="truncate font-medium">
{session.title || `Untitled chat`}
</span>
<span className="text-xs text-neutral-500">
{formatDate(session.updated_at)}
</span>
</div>
</button>
))
)}
</div>
)}
</SidebarContent>
<SidebarFooter className="px-2">
</SidebarFooter>

View File

@@ -27,8 +27,8 @@ import {
const SIDEBAR_COOKIE_NAME = "sidebar_state"
const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7
const SIDEBAR_WIDTH = "16rem"
const SIDEBAR_WIDTH_MOBILE = "18rem"
const SIDEBAR_WIDTH = "20rem"
const SIDEBAR_WIDTH_MOBILE = "20rem"
const SIDEBAR_WIDTH_ICON = "3rem"
const SIDEBAR_KEYBOARD_SHORTCUT = "b"