feat: implement docked mode for DocumentsSidebar with toggle functionality; enhance LayoutDataProvider and LayoutShell to support new state management

This commit is contained in:
Anish Sarkar 2026-03-10 12:26:45 +05:30
parent 74c95ee61f
commit 2608870ae6
4 changed files with 79 additions and 5 deletions

View file

@ -119,6 +119,19 @@ export function LayoutDataProvider({ searchSpaceId, children }: LayoutDataProvid
// Documents sidebar state (shared atom so Composer can toggle it)
const [isDocumentsSidebarOpen, setIsDocumentsSidebarOpen] = useAtom(documentsSidebarOpenAtom);
const [isDocumentsDocked, setIsDocumentsDocked] = useState(true);
// Open documents sidebar by default on desktop (docked mode)
const documentsInitialized = useRef(false);
useEffect(() => {
if (!documentsInitialized.current) {
documentsInitialized.current = true;
const isDesktop = typeof window !== "undefined" && window.innerWidth >= 768;
if (isDesktop) {
setIsDocumentsSidebarOpen(true);
}
}
}, [setIsDocumentsSidebarOpen]);
// Announcements sidebar state
const [isAnnouncementsSidebarOpen, setIsAnnouncementsSidebarOpen] = useState(false);
@ -678,6 +691,8 @@ export function LayoutDataProvider({ searchSpaceId, children }: LayoutDataProvid
documentsPanel={{
open: isDocumentsSidebarOpen,
onOpenChange: setIsDocumentsSidebarOpen,
isDocked: isDocumentsDocked,
onDockedChange: setIsDocumentsDocked,
}}
>
<Fragment key={chatResetKey}>{children}</Fragment>

View file

@ -97,6 +97,8 @@ interface LayoutShellProps {
documentsPanel?: {
open: boolean;
onOpenChange: (open: boolean) => void;
isDocked?: boolean;
onDockedChange?: (docked: boolean) => void;
};
}
@ -319,6 +321,16 @@ export function LayoutShell({
/>
)}
{/* Docked Documents Sidebar - renders as flex sibling between sidebar and content */}
{documentsPanel?.isDocked && (
<DocumentsSidebar
open={documentsPanel.open}
onOpenChange={documentsPanel.onOpenChange}
isDocked={documentsPanel.isDocked}
onDockedChange={documentsPanel.onDockedChange}
/>
)}
<main className="flex-1 flex flex-col min-w-0">
<Header />
@ -340,11 +352,13 @@ export function LayoutShell({
/>
)}
{/* Documents Sidebar - slide-out panel */}
{documentsPanel && (
{/* Documents Sidebar - floating slide-out panel (non-docked mode) */}
{documentsPanel && !documentsPanel.isDocked && (
<DocumentsSidebar
open={documentsPanel.open}
onOpenChange={documentsPanel.onOpenChange}
isDocked={false}
onDockedChange={documentsPanel.onDockedChange}
/>
)}

View file

@ -1,7 +1,7 @@
"use client";
import { useAtom, useAtomValue } from "jotai";
import { ChevronLeft } from "lucide-react";
import { ChevronLeft, ChevronRight } from "lucide-react";
import { useParams } from "next/navigation";
import { useTranslations } from "next-intl";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
@ -14,6 +14,7 @@ import {
import { sidebarSelectedDocumentsAtom } from "@/atoms/chat/mentioned-documents.atom";
import { deleteDocumentMutationAtom } from "@/atoms/documents/document-mutation.atoms";
import { Button } from "@/components/ui/button";
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
import type { DocumentTypeEnum } from "@/contracts/types/document.types";
import { useDebouncedValue } from "@/hooks/use-debounced-value";
import { useDocumentSearch } from "@/hooks/use-document-search";
@ -24,9 +25,11 @@ import { SidebarSlideOutPanel } from "./SidebarSlideOutPanel";
interface DocumentsSidebarProps {
open: boolean;
onOpenChange: (open: boolean) => void;
isDocked?: boolean;
onDockedChange?: (docked: boolean) => void;
}
export function DocumentsSidebar({ open, onOpenChange }: DocumentsSidebarProps) {
export function DocumentsSidebar({ open, onOpenChange, isDocked = false, onDockedChange }: DocumentsSidebarProps) {
const t = useTranslations("documents");
const tSidebar = useTranslations("sidebar");
const params = useParams();
@ -164,6 +167,37 @@ export function DocumentsSidebar({ open, onOpenChange }: DocumentsSidebarProps)
)}
<h2 className="text-lg font-semibold">{t("title") || "Documents"}</h2>
</div>
<div className="flex items-center gap-1">
{!isMobile && onDockedChange && (
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="ghost"
size="icon"
className="h-8 w-8 rounded-full"
onClick={() => {
if (isDocked) {
onDockedChange(false);
onOpenChange(false);
} else {
onDockedChange(true);
}
}}
>
{isDocked ? (
<ChevronLeft className="h-4 w-4 text-muted-foreground" />
) : (
<ChevronRight className="h-4 w-4 text-muted-foreground" />
)}
<span className="sr-only">{isDocked ? "Collapse panel" : "Expand panel"}</span>
</Button>
</TooltipTrigger>
<TooltipContent className="z-80">
{isDocked ? "Collapse panel" : "Expand panel"}
</TooltipContent>
</Tooltip>
)}
</div>
</div>
</div>
@ -199,6 +233,17 @@ export function DocumentsSidebar({ open, onOpenChange }: DocumentsSidebarProps)
</>
);
if (isDocked && open && !isMobile) {
return (
<aside
className="h-full w-[480px] shrink-0 bg-sidebar text-sidebar-foreground flex flex-col border-r"
aria-label={t("title") || "Documents"}
>
{documentsContent}
</aside>
);
}
return (
<SidebarSlideOutPanel
open={open}

View file

@ -149,7 +149,7 @@ function DropdownMenuSeparator({
return (
<DropdownMenuPrimitive.Separator
data-slot="dropdown-menu-separator"
className={cn("bg-border dark:bg-neutral-700 -mx-1 my-1 h-px", className)}
className={cn("bg-border mx-2 my-1 h-px", className)}
{...props}
/>
);