mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-07-06 22:12:12 +02:00
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:
parent
e24abd7eac
commit
192b9840f2
4 changed files with 137 additions and 132 deletions
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue