mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-07-06 22:12:12 +02:00
feat(sidebar): enhance chat sections layout and functionality with improved height management
This commit is contained in:
parent
ba304be977
commit
b148731fda
3 changed files with 139 additions and 108 deletions
|
|
@ -87,10 +87,10 @@ export function LayoutDataProvider({
|
||||||
enabled: !!searchSpaceId,
|
enabled: !!searchSpaceId,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Fetch threads
|
// Fetch threads (40 total to allow up to 20 per section - shared/private)
|
||||||
const { data: threadsData } = useQuery({
|
const { data: threadsData } = useQuery({
|
||||||
queryKey: ["threads", searchSpaceId, { limit: 4 }],
|
queryKey: ["threads", searchSpaceId, { limit: 40 }],
|
||||||
queryFn: () => fetchThreads(Number(searchSpaceId), 4),
|
queryFn: () => fetchThreads(Number(searchSpaceId), 40),
|
||||||
enabled: !!searchSpaceId,
|
enabled: !!searchSpaceId,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@
|
||||||
import { FolderOpen, MessageSquare, PenSquare } from "lucide-react";
|
import { FolderOpen, MessageSquare, PenSquare } from "lucide-react";
|
||||||
import { useTranslations } from "next-intl";
|
import { useTranslations } from "next-intl";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { ScrollArea } from "@/components/ui/scroll-area";
|
|
||||||
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
|
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import type { ChatItem, NavItem, PageUsage, SearchSpace, User } from "../../types/layout.types";
|
import type { ChatItem, NavItem, PageUsage, SearchSpace, User } from "../../types/layout.types";
|
||||||
|
|
@ -121,101 +120,109 @@ export function Sidebar({
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Scrollable content */}
|
{/* Chat sections - fills available space */}
|
||||||
<ScrollArea className="flex-1">
|
{isCollapsed ? (
|
||||||
{isCollapsed ? (
|
<div className="flex-1 flex flex-col items-center gap-2 py-2 w-[60px]">
|
||||||
<div className="flex flex-col items-center gap-2 py-2 w-[60px]">
|
{(chats.length > 0 || sharedChats.length > 0) && (
|
||||||
{(chats.length > 0 || sharedChats.length > 0) && (
|
<Tooltip>
|
||||||
<Tooltip>
|
<TooltipTrigger asChild>
|
||||||
<TooltipTrigger asChild>
|
<Button
|
||||||
<Button
|
variant="ghost"
|
||||||
variant="ghost"
|
size="icon"
|
||||||
size="icon"
|
className="h-10 w-10"
|
||||||
className="h-10 w-10"
|
onClick={() => onToggleCollapse?.()}
|
||||||
onClick={() => onToggleCollapse?.()}
|
>
|
||||||
>
|
<MessageSquare className="h-4 w-4" />
|
||||||
<MessageSquare className="h-4 w-4" />
|
<span className="sr-only">{t("chats")}</span>
|
||||||
<span className="sr-only">{t("chats")}</span>
|
</Button>
|
||||||
</Button>
|
</TooltipTrigger>
|
||||||
</TooltipTrigger>
|
<TooltipContent side="right">
|
||||||
<TooltipContent side="right">
|
{t("chats")} ({chats.length + sharedChats.length})
|
||||||
{t("chats")} ({chats.length + sharedChats.length})
|
</TooltipContent>
|
||||||
</TooltipContent>
|
</Tooltip>
|
||||||
</Tooltip>
|
)}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="flex-1 flex flex-col gap-1 py-2 w-[240px] min-h-0 overflow-hidden">
|
||||||
|
{/* Shared Chats Section - takes half the space */}
|
||||||
|
<SidebarSection
|
||||||
|
title={t("shared_chats")}
|
||||||
|
defaultOpen={true}
|
||||||
|
fillHeight={true}
|
||||||
|
action={
|
||||||
|
onViewAllSharedChats ? (
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger asChild>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="icon"
|
||||||
|
className="h-8 w-8 shrink-0 hover:bg-transparent hover:text-current focus-visible:ring-0"
|
||||||
|
onClick={onViewAllSharedChats}
|
||||||
|
>
|
||||||
|
<FolderOpen className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent side="top">
|
||||||
|
{t("view_all_shared_chats") || "View all shared chats"}
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
) : undefined
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{sharedChats.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 ${sharedChats.length > 4 ? 'pb-8' : ''}`}>
|
||||||
|
{sharedChats.slice(0, 20).map((chat) => (
|
||||||
|
<ChatListItem
|
||||||
|
key={chat.id}
|
||||||
|
name={chat.name}
|
||||||
|
isActive={chat.id === activeChatId}
|
||||||
|
archived={chat.archived}
|
||||||
|
onClick={() => onChatSelect(chat)}
|
||||||
|
onArchive={() => onChatArchive?.(chat)}
|
||||||
|
onDelete={() => onChatDelete?.(chat)}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
{/* Gradient fade indicator when more than 4 items */}
|
||||||
|
{sharedChats.length > 4 && (
|
||||||
|
<div className="pointer-events-none absolute bottom-0 left-0 right-0 h-8 bg-gradient-to-t from-sidebar via-sidebar/90 to-transparent" />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<p className="px-2 py-1 text-xs text-muted-foreground">{t("no_shared_chats")}</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</SidebarSection>
|
||||||
) : (
|
|
||||||
<div className="flex flex-col gap-1 py-2 w-[240px]">
|
|
||||||
{/* Shared Chats Section */}
|
|
||||||
<SidebarSection
|
|
||||||
title={t("shared_chats")}
|
|
||||||
defaultOpen={true}
|
|
||||||
action={
|
|
||||||
onViewAllSharedChats ? (
|
|
||||||
<Tooltip>
|
|
||||||
<TooltipTrigger asChild>
|
|
||||||
<Button
|
|
||||||
variant="ghost"
|
|
||||||
size="icon"
|
|
||||||
className="h-8 w-8 shrink-0 hover:bg-transparent hover:text-current focus-visible:ring-0"
|
|
||||||
onClick={onViewAllSharedChats}
|
|
||||||
>
|
|
||||||
<FolderOpen className="h-4 w-4" />
|
|
||||||
</Button>
|
|
||||||
</TooltipTrigger>
|
|
||||||
<TooltipContent side="top">
|
|
||||||
{t("view_all_shared_chats") || "View all shared chats"}
|
|
||||||
</TooltipContent>
|
|
||||||
</Tooltip>
|
|
||||||
) : undefined
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{sharedChats.length > 0 ? (
|
|
||||||
<div className="flex flex-col gap-0.5">
|
|
||||||
{sharedChats.map((chat) => (
|
|
||||||
<ChatListItem
|
|
||||||
key={chat.id}
|
|
||||||
name={chat.name}
|
|
||||||
isActive={chat.id === activeChatId}
|
|
||||||
archived={chat.archived}
|
|
||||||
onClick={() => onChatSelect(chat)}
|
|
||||||
onArchive={() => onChatArchive?.(chat)}
|
|
||||||
onDelete={() => onChatDelete?.(chat)}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<p className="px-2 py-1 text-xs text-muted-foreground">{t("no_shared_chats")}</p>
|
|
||||||
)}
|
|
||||||
</SidebarSection>
|
|
||||||
|
|
||||||
{/* Private Chats Section */}
|
{/* Private Chats Section - takes half the space */}
|
||||||
<SidebarSection
|
<SidebarSection
|
||||||
title={t("chats")}
|
title={t("chats")}
|
||||||
defaultOpen={true}
|
defaultOpen={true}
|
||||||
action={
|
fillHeight={true}
|
||||||
onViewAllPrivateChats ? (
|
action={
|
||||||
<Tooltip>
|
onViewAllPrivateChats ? (
|
||||||
<TooltipTrigger asChild>
|
<Tooltip>
|
||||||
<Button
|
<TooltipTrigger asChild>
|
||||||
variant="ghost"
|
<Button
|
||||||
size="icon"
|
variant="ghost"
|
||||||
className="h-8 w-8 shrink-0 hover:bg-transparent hover:text-current focus-visible:ring-0"
|
size="icon"
|
||||||
onClick={onViewAllPrivateChats}
|
className="h-8 w-8 shrink-0 hover:bg-transparent hover:text-current focus-visible:ring-0"
|
||||||
>
|
onClick={onViewAllPrivateChats}
|
||||||
<FolderOpen className="h-4 w-4" />
|
>
|
||||||
</Button>
|
<FolderOpen className="h-4 w-4" />
|
||||||
</TooltipTrigger>
|
</Button>
|
||||||
<TooltipContent side="top">
|
</TooltipTrigger>
|
||||||
{t("view_all_private_chats") || "View all private chats"}
|
<TooltipContent side="top">
|
||||||
</TooltipContent>
|
{t("view_all_private_chats") || "View all private chats"}
|
||||||
</Tooltip>
|
</TooltipContent>
|
||||||
) : undefined
|
</Tooltip>
|
||||||
}
|
) : undefined
|
||||||
>
|
}
|
||||||
{chats.length > 0 ? (
|
>
|
||||||
<div className="flex flex-col gap-0.5">
|
{chats.length > 0 ? (
|
||||||
{chats.map((chat) => (
|
<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-8' : ''}`}>
|
||||||
|
{chats.slice(0, 20).map((chat) => (
|
||||||
<ChatListItem
|
<ChatListItem
|
||||||
key={chat.id}
|
key={chat.id}
|
||||||
name={chat.name}
|
name={chat.name}
|
||||||
|
|
@ -227,13 +234,17 @@ export function Sidebar({
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
{/* Gradient fade indicator when more than 4 items */}
|
||||||
<p className="px-2 py-1 text-xs text-muted-foreground">{t("no_chats")}</p>
|
{chats.length > 4 && (
|
||||||
)}
|
<div className="pointer-events-none absolute bottom-0 left-0 right-0 h-8 bg-gradient-to-t from-sidebar via-sidebar/90 to-transparent" />
|
||||||
</SidebarSection>
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
) : (
|
||||||
</ScrollArea>
|
<p className="px-2 py-1 text-xs text-muted-foreground">{t("no_chats")}</p>
|
||||||
|
)}
|
||||||
|
</SidebarSection>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Footer */}
|
{/* Footer */}
|
||||||
<div className="mt-auto border-t">
|
<div className="mt-auto border-t">
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,8 @@ interface SidebarSectionProps {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
action?: React.ReactNode;
|
action?: React.ReactNode;
|
||||||
persistentAction?: React.ReactNode;
|
persistentAction?: React.ReactNode;
|
||||||
|
className?: string;
|
||||||
|
fillHeight?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function SidebarSection({
|
export function SidebarSection({
|
||||||
|
|
@ -19,12 +21,22 @@ export function SidebarSection({
|
||||||
children,
|
children,
|
||||||
action,
|
action,
|
||||||
persistentAction,
|
persistentAction,
|
||||||
|
className,
|
||||||
|
fillHeight = false,
|
||||||
}: SidebarSectionProps) {
|
}: SidebarSectionProps) {
|
||||||
const [isOpen, setIsOpen] = useState(defaultOpen);
|
const [isOpen, setIsOpen] = useState(defaultOpen);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Collapsible open={isOpen} onOpenChange={setIsOpen} className="overflow-hidden">
|
<Collapsible
|
||||||
<div className="flex items-center group/section">
|
open={isOpen}
|
||||||
|
onOpenChange={setIsOpen}
|
||||||
|
className={cn(
|
||||||
|
"overflow-hidden",
|
||||||
|
fillHeight && "flex flex-col flex-1 min-h-0",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div className="flex items-center group/section shrink-0">
|
||||||
<CollapsibleTrigger className="flex flex-1 items-center gap-1.5 px-2 py-1.5 text-xs font-medium text-muted-foreground hover:text-foreground transition-colors min-w-0">
|
<CollapsibleTrigger className="flex flex-1 items-center gap-1.5 px-2 py-1.5 text-xs font-medium text-muted-foreground hover:text-foreground transition-colors min-w-0">
|
||||||
<ChevronRight
|
<ChevronRight
|
||||||
className={cn(
|
className={cn(
|
||||||
|
|
@ -48,8 +60,16 @@ export function SidebarSection({
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<CollapsibleContent className="overflow-hidden">
|
<CollapsibleContent className={cn(
|
||||||
<div className="px-2 pb-2">{children}</div>
|
"overflow-hidden",
|
||||||
|
fillHeight && "flex-1 flex flex-col min-h-0"
|
||||||
|
)}>
|
||||||
|
<div className={cn(
|
||||||
|
"px-2 pb-2",
|
||||||
|
fillHeight && "flex-1 flex flex-col min-h-0 overflow-hidden"
|
||||||
|
)}>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
</CollapsibleContent>
|
</CollapsibleContent>
|
||||||
</Collapsible>
|
</Collapsible>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue