refactor(sidebar): enhance layout and styling in FolderTreeView and AllChatsSidebar

- Removed unnecessary min-height and overflow properties in FolderTreeView and DocumentsSidebar for cleaner layout.
- Updated AllChatsSidebar to improve header styling and adjust padding for better visual consistency.
- Enhanced input and button sizes for improved usability across chat components.
This commit is contained in:
Anish Sarkar 2026-07-06 13:05:11 +05:30
parent e24abd7eac
commit 192b9840f2
4 changed files with 137 additions and 132 deletions

View file

@ -336,7 +336,7 @@ export function FolderTreeView({
return (
<DndProvider backend={HTML5Backend}>
<div className="flex-1 min-h-0 overflow-y-auto px-2 py-1">{treeNodes}</div>
<div className="px-2 py-1">{treeNodes}</div>
</DndProvider>
);
}

View file

@ -11,7 +11,6 @@ import {
RotateCcwIcon,
Search,
Trash2,
User,
Users,
X,
} from "lucide-react";
@ -236,8 +235,12 @@ function AllChatsContent({ searchSpaceId, className }: AllChatsContentProps) {
return (
<div className={cn("flex h-full min-h-0 w-full flex-1 flex-col", className)}>
<div className="shrink-0 space-y-4 px-3 pb-3 pt-3">
<div className="flex items-center justify-between gap-4">
<h2 className="text-lg font-semibold">{t("chats") || "Chats"}</h2>
<div className="flex items-center justify-between gap-4 flex-wrap">
<div className="flex items-baseline gap-3">
<h1 className="text-xl md:text-2xl font-semibold text-foreground">
{t("chats") || "Chats"}
</h1>
</div>
{!isSearchMode && (
<DropdownMenu>
<DropdownMenuTrigger asChild>
@ -276,22 +279,22 @@ function AllChatsContent({ searchSpaceId, className }: AllChatsContentProps) {
</div>
<div className="relative">
<Search className="absolute left-2.5 top-1/2 -translate-y-1/2 h-3.5 w-3.5 text-muted-foreground" />
<Search className="absolute left-3.5 top-1/2 -translate-y-1/2 h-4.5 w-4.5 text-muted-foreground" />
<Input
type="text"
placeholder={t("search_chats") || "Search chats..."}
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="h-8 border-0 bg-muted pl-8 pr-7 text-sm shadow-none"
className="h-12 border-0 bg-muted pl-10 pr-9 text-base shadow-none"
/>
{searchQuery && (
<Button
variant="ghost"
size="icon"
className="absolute right-1 top-1/2 h-5 w-5 -translate-y-1/2 rounded-sm text-muted-foreground transition-colors hover:bg-accent hover:text-accent-foreground"
className="absolute right-2 top-1/2 h-7 w-7 -translate-y-1/2 rounded-sm text-muted-foreground transition-colors hover:bg-accent hover:text-accent-foreground"
onClick={handleClearSearch}
>
<X className="h-3.5 w-3.5" />
<X className="h-4.5 w-4.5" />
<span className="sr-only">{t("clear_search") || "Clear search"}</span>
</Button>
)}
@ -487,17 +490,11 @@ function AllChatsContent({ searchSpaceId, className }: AllChatsContentProps) {
</div>
) : (
<div className="text-center py-8">
<User className="mx-auto mb-2.5 h-10 w-10 text-muted-foreground" />
<p className="text-xs text-muted-foreground">
<p className="text-base font-medium text-muted-foreground">
{showArchived
? t("no_archived_chats") || "No archived chats"
: t("no_chats") || "No chats"}
</p>
{!showArchived && (
<p className="mt-1 text-[11px] text-muted-foreground/70">
{t("start_new_chat_hint") || "Start a new chat from the chat page"}
</p>
)}
</div>
)}
</div>

View file

@ -176,7 +176,7 @@ function CloudDocumentsSkeleton() {
];
return (
<div className="flex-1 min-h-0 overflow-y-auto px-2 py-1">
<div className="px-2 py-1">
<div className="space-y-1">
{rows.map((row) => (
<div key={row.id} className="flex h-8 items-center gap-2 px-2">
@ -971,7 +971,7 @@ function AuthenticatedDocumentsSidebarBase({
activeTypesForTree?: DocumentTypeEnum[];
searchQuery?: string;
} = {}) => (
<div className="relative flex-1 min-h-0 overflow-auto">
<div className="relative">
{deletableSelectedIds.length > 0 && (
<div className="absolute inset-x-0 top-0 z-10 flex items-center justify-center px-4 py-1.5 animate-in fade-in duration-150 pointer-events-none">
<Button
@ -1043,7 +1043,6 @@ function AuthenticatedDocumentsSidebarBase({
<SidebarSection
title={t("title") || "Documents"}
defaultOpen={true}
fillHeight
contentClassName="px-0"
persistentAction={
<EmbeddedDocumentsMenu
@ -1217,7 +1216,6 @@ function AnonymousDocumentsSidebar({ embedded = false }: DocumentsSidebarProps)
<SidebarSection
title={t("title") || "Documents"}
defaultOpen={true}
fillHeight
contentClassName="px-0"
persistentAction={
<EmbeddedDocumentsMenu

View file

@ -141,6 +141,7 @@ export function Sidebar({
}: SidebarProps) {
const t = useTranslations("sidebar");
const [openDropdownChatId, setOpenDropdownChatId] = useState<number | null>(null);
const [isSidebarNavScrolled, setIsSidebarNavScrolled] = useState(false);
// Inbox, Automations, and Artifacts are rendered explicitly right below
// New Chat. Pull them out of the nav items list so they don't also appear
@ -220,128 +221,137 @@ export function Sidebar({
) : null}
</div>
<div className="flex flex-col gap-0.5 py-1.5">
<div
className={cn(
"relative flex flex-col gap-0.5 pt-1.5 pb-0 after:absolute after:inset-x-3 after:bottom-0 after:h-px after:bg-border after:transition-opacity",
isSidebarNavScrolled ? "after:opacity-100" : "after:opacity-0"
)}
>
<SidebarButton
icon={SquarePen}
label={t("new_chat")}
onClick={onNewChat}
isCollapsed={isCollapsed}
/>
{inboxItem && (
<SidebarButton
icon={inboxItem.icon}
label={inboxItem.title}
onClick={() => onNavItemClick?.(inboxItem)}
isCollapsed={isCollapsed}
isActive={inboxItem.isActive}
badge={inboxItem.badge}
collapsedIconNode={<CollapsedInboxIcon item={inboxItem} />}
tooltipContent={isCollapsed ? inboxItem.title : undefined}
buttonProps={
{
"data-joyride": "inbox-sidebar",
} as React.ButtonHTMLAttributes<HTMLButtonElement>
}
/>
)}
{automationsItem && (
<SidebarButton
icon={automationsItem.icon}
label={automationsItem.title}
onClick={() => onNavItemClick?.(automationsItem)}
isCollapsed={isCollapsed}
isActive={automationsItem.isActive}
tooltipContent={isCollapsed ? automationsItem.title : undefined}
/>
)}
{artifactsItem && (
<SidebarButton
icon={artifactsItem.icon}
label={artifactsItem.title}
onClick={() => onNavItemClick?.(artifactsItem)}
isCollapsed={isCollapsed}
isActive={artifactsItem.isActive}
tooltipContent={isCollapsed ? artifactsItem.title : undefined}
/>
)}
{documentsItem && (
<SidebarButton
icon={documentsItem.icon}
label={documentsItem.title}
onClick={() => onNavItemClick?.(documentsItem)}
isCollapsed={isCollapsed}
isActive={documentsItem.isActive}
tooltipContent={isCollapsed ? documentsItem.title : undefined}
/>
)}
</div>
{/* Chat sections - fills available space */}
{isCollapsed ? (
<div className="flex-1 w-full" />
) : (
<div className="flex-1 flex flex-col gap-1 pt-2 w-full min-h-0 overflow-hidden">
<SidebarSection
title={t("recents")}
defaultOpen={true}
fillHeight={!documentsPanel?.open}
className={
documentsPanel?.open ? "flex shrink-0 flex-col min-h-0 max-h-[35%]" : undefined
}
alwaysShowAction={!disableTooltips && isAllChatsActive}
action={
onViewAllChats ? (
<Button
type="button"
variant="ghost"
onClick={onViewAllChats}
className="h-auto cursor-pointer whitespace-nowrap bg-transparent p-0 text-xs font-medium text-muted-foreground/60 transition-colors hover:bg-transparent hover:text-muted-foreground"
>
{!disableTooltips && isAllChatsActive ? t("hide") : t("show_all")}
</Button>
) : undefined
}
>
{isLoadingChats ? (
<ChatListSkeletonRows />
) : chats.length > 0 ? (
<div className="relative flex-1 min-h-0">
<div
className={`flex flex-col gap-0.5 h-full overflow-y-auto scrollbar-thin scrollbar-thumb-muted-foreground/20 scrollbar-track-transparent ${chats.length > 4 ? "pb-2" : ""}`}
>
{chats.slice(0, 20).map((chat) => (
<ChatListItem
key={chat.id}
name={chat.name}
isActive={chat.id === activeChatId}
isShared={chat.visibility === "SEARCH_SPACE"}
archived={chat.archived}
dropdownOpen={openDropdownChatId === chat.id}
onDropdownOpenChange={(open) => setOpenDropdownChatId(open ? chat.id : null)}
onClick={() => onChatSelect(chat)}
onPrefetch={() => onChatPrefetch?.(chat)}
onRename={() => onChatRename?.(chat)}
onArchive={() => onChatArchive?.(chat)}
onDelete={() => onChatDelete?.(chat)}
/>
))}
</div>
{/* Gradient fade indicator when more than 4 items */}
{chats.length > 4 && (
<div className="pointer-events-none absolute bottom-0 left-0 right-0 h-5 bg-gradient-to-t from-sidebar/80 to-transparent" />
)}
</div>
) : (
<p className="px-2 py-1 text-sm text-muted-foreground/60">{t("no_chats")}</p>
)}
</SidebarSection>
{documentsPanel?.open ? (
<div className="min-h-0 flex flex-1 flex-col">
<DocumentsSidebar embedded />
</div>
) : null}
<div
className="flex-1 w-full min-h-0 overflow-y-auto overflow-x-hidden scrollbar-thin scrollbar-thumb-muted-foreground/20 scrollbar-track-transparent"
onScroll={(event) => setIsSidebarNavScrolled(event.currentTarget.scrollTop > 0)}
>
<div className="flex flex-col gap-0.5 pt-0.5 pb-1.5">
{inboxItem && (
<SidebarButton
icon={inboxItem.icon}
label={inboxItem.title}
onClick={() => onNavItemClick?.(inboxItem)}
isCollapsed={isCollapsed}
isActive={inboxItem.isActive}
badge={inboxItem.badge}
collapsedIconNode={<CollapsedInboxIcon item={inboxItem} />}
tooltipContent={isCollapsed ? inboxItem.title : undefined}
buttonProps={
{
"data-joyride": "inbox-sidebar",
} as React.ButtonHTMLAttributes<HTMLButtonElement>
}
/>
)}
{automationsItem && (
<SidebarButton
icon={automationsItem.icon}
label={automationsItem.title}
onClick={() => onNavItemClick?.(automationsItem)}
isCollapsed={isCollapsed}
isActive={automationsItem.isActive}
tooltipContent={isCollapsed ? automationsItem.title : undefined}
/>
)}
{artifactsItem && (
<SidebarButton
icon={artifactsItem.icon}
label={artifactsItem.title}
onClick={() => onNavItemClick?.(artifactsItem)}
isCollapsed={isCollapsed}
isActive={artifactsItem.isActive}
tooltipContent={isCollapsed ? artifactsItem.title : undefined}
/>
)}
{documentsItem && (
<SidebarButton
icon={documentsItem.icon}
label={documentsItem.title}
onClick={() => onNavItemClick?.(documentsItem)}
isCollapsed={isCollapsed}
isActive={documentsItem.isActive}
tooltipContent={isCollapsed ? documentsItem.title : undefined}
/>
)}
</div>
)}
{/* Chat sections - fills available space */}
{isCollapsed ? (
<div className="w-full" />
) : (
<div className="flex flex-col gap-1 pt-2 w-full min-h-0">
<SidebarSection
title={t("recents")}
defaultOpen={true}
alwaysShowAction={!disableTooltips && isAllChatsActive}
action={
onViewAllChats ? (
<Button
type="button"
variant="ghost"
onClick={onViewAllChats}
className="h-auto cursor-pointer whitespace-nowrap bg-transparent p-0 text-xs font-medium text-muted-foreground/60 transition-colors hover:bg-transparent hover:text-muted-foreground"
>
{!disableTooltips && isAllChatsActive ? t("hide") : t("show_all")}
</Button>
) : undefined
}
>
{isLoadingChats ? (
<ChatListSkeletonRows />
) : chats.length > 0 ? (
<div className="relative">
<div className={`flex flex-col gap-0.5 ${chats.length > 4 ? "pb-2" : ""}`}>
{chats.slice(0, 20).map((chat) => (
<ChatListItem
key={chat.id}
name={chat.name}
isActive={chat.id === activeChatId}
isShared={chat.visibility === "SEARCH_SPACE"}
archived={chat.archived}
dropdownOpen={openDropdownChatId === chat.id}
onDropdownOpenChange={(open) =>
setOpenDropdownChatId(open ? chat.id : null)
}
onClick={() => onChatSelect(chat)}
onPrefetch={() => onChatPrefetch?.(chat)}
onRename={() => onChatRename?.(chat)}
onArchive={() => onChatArchive?.(chat)}
onDelete={() => onChatDelete?.(chat)}
/>
))}
</div>
{/* Gradient fade indicator when more than 4 items */}
{chats.length > 4 && (
<div className="pointer-events-none absolute bottom-0 left-0 right-0 h-5 bg-gradient-to-t from-sidebar/80 to-transparent" />
)}
</div>
) : (
<p className="px-2 py-1 text-sm text-muted-foreground/60">{t("no_chats")}</p>
)}
</SidebarSection>
{documentsPanel?.open ? (
<div className="min-h-0 flex flex-1 flex-col">
<DocumentsSidebar embedded />
</div>
) : null}
</div>
)}
</div>
{/* Footer */}
<div className="mt-auto border-t">