feat(sidebar): enhance document filtering with Drawer component

- Integrated Drawer component in EmbeddedDocumentsMenu for mobile-friendly document type filtering.
- Updated UI to improve accessibility and user experience with new button interactions and layout adjustments.
- Refactored document type display logic to streamline filtering options and enhance visual clarity.
This commit is contained in:
Anish Sarkar 2026-07-07 21:58:47 +05:30
parent 2461353312
commit b911a36bcf
2 changed files with 105 additions and 44 deletions

View file

@ -3,6 +3,7 @@
import { useQuery } from "@rocicorp/zero/react"; import { useQuery } from "@rocicorp/zero/react";
import { useAtom, useAtomValue, useSetAtom } from "jotai"; import { useAtom, useAtomValue, useSetAtom } from "jotai";
import { import {
Check,
FolderInput, FolderInput,
FolderPlus, FolderPlus,
FolderSync, FolderSync,
@ -50,6 +51,7 @@ import {
AlertDialogTitle, AlertDialogTitle,
} from "@/components/ui/alert-dialog"; } from "@/components/ui/alert-dialog";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Drawer, DrawerContent, DrawerHandle, DrawerTitle } from "@/components/ui/drawer";
import { import {
DropdownMenu, DropdownMenu,
DropdownMenuCheckboxItem, DropdownMenuCheckboxItem,
@ -69,6 +71,7 @@ import { EnumConnectorName } from "@/contracts/enums/connector";
import { getConnectorIcon } from "@/contracts/enums/connectorIcons"; import { getConnectorIcon } from "@/contracts/enums/connectorIcons";
import type { SearchSourceConnector } from "@/contracts/types/connector.types"; import type { SearchSourceConnector } from "@/contracts/types/connector.types";
import type { DocumentTypeEnum } from "@/contracts/types/document.types"; import type { DocumentTypeEnum } from "@/contracts/types/document.types";
import { useIsMobile } from "@/hooks/use-mobile";
import { useElectronAPI, usePlatform } from "@/hooks/use-platform"; import { useElectronAPI, usePlatform } from "@/hooks/use-platform";
import { documentsApiService } from "@/lib/apis/documents-api.service"; import { documentsApiService } from "@/lib/apis/documents-api.service";
import { foldersApiService } from "@/lib/apis/folders-api.service"; import { foldersApiService } from "@/lib/apis/folders-api.service";
@ -127,60 +130,111 @@ export function EmbeddedDocumentsMenu({
onToggleType: (type: DocumentTypeEnum, checked: boolean) => void; onToggleType: (type: DocumentTypeEnum, checked: boolean) => void;
onCreateFolder: () => void; onCreateFolder: () => void;
}) { }) {
const isMobile = useIsMobile();
const [filterDrawerOpen, setFilterDrawerOpen] = useState(false);
const documentTypes = useMemo( const documentTypes = useMemo(
() => Object.keys(typeCounts).sort() as DocumentTypeEnum[], () => Object.keys(typeCounts).sort() as DocumentTypeEnum[],
[typeCounts] [typeCounts]
); );
return ( return (
<DropdownMenu> <>
<DropdownMenuTrigger asChild> <DropdownMenu>
<Button <DropdownMenuTrigger asChild>
type="button" <Button
variant="ghost" type="button"
size="icon" variant="ghost"
className="relative h-7 w-7 text-muted-foreground hover:bg-accent hover:text-accent-foreground" size="icon"
aria-label="Document actions" className="relative h-7 w-7 text-muted-foreground hover:bg-accent hover:text-accent-foreground"
aria-label="Document actions"
>
<SlidersVertical className="h-3.5 w-3.5" />
{activeTypes.length > 0 ? (
<span className="absolute right-0.5 top-0.5 h-1.5 w-1.5 rounded-full bg-primary" />
) : null}
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-44">
<DropdownMenuItem onSelect={onCreateFolder}>
<FolderPlus className="h-4 w-4" />
New folder
</DropdownMenuItem>
{isMobile ? (
<DropdownMenuItem onSelect={() => setFilterDrawerOpen(true)}>
<ListFilter className="h-4 w-4" />
<span className="flex-1">Filter by type</span>
</DropdownMenuItem>
) : (
<DropdownMenuSub>
<DropdownMenuSubTrigger>
<ListFilter className="h-4 w-4" />
Filter by type
</DropdownMenuSubTrigger>
<DropdownMenuSubContent className="w-52 max-h-72 overflow-y-auto">
{documentTypes.length > 0 ? (
documentTypes.map((type) => (
<DropdownMenuCheckboxItem
key={type}
checked={activeTypes.includes(type)}
onCheckedChange={(checked) => onToggleType(type, checked === true)}
onSelect={(event) => event.preventDefault()}
>
{getDocumentTypeIcon(type, "h-4 w-4")}
<span className="min-w-0 flex-1 truncate">{getDocumentTypeLabel(type)}</span>
<span className="ml-auto text-xs text-muted-foreground">
{typeCounts[type] ?? 0}
</span>
</DropdownMenuCheckboxItem>
))
) : (
<DropdownMenuItem disabled>No document types</DropdownMenuItem>
)}
</DropdownMenuSubContent>
</DropdownMenuSub>
)}
</DropdownMenuContent>
</DropdownMenu>
<Drawer
open={filterDrawerOpen}
onOpenChange={setFilterDrawerOpen}
shouldScaleBackground={false}
>
<DrawerContent
className="z-80 max-h-[75vh] rounded-t-2xl border bg-popover text-popover-foreground"
overlayClassName="z-80"
> >
<SlidersVertical className="h-3.5 w-3.5" /> <DrawerHandle className="mt-3 h-1.5 w-10" />
{activeTypes.length > 0 ? ( <DrawerTitle className="px-4 pb-2 pt-3 text-center text-base font-semibold">
<span className="absolute right-0.5 top-0.5 h-1.5 w-1.5 rounded-full bg-primary" />
) : null}
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-44">
<DropdownMenuItem onSelect={onCreateFolder}>
<FolderPlus className="h-4 w-4" />
New folder
</DropdownMenuItem>
<DropdownMenuSub>
<DropdownMenuSubTrigger>
<ListFilter className="h-4 w-4" />
Filter by type Filter by type
</DropdownMenuSubTrigger> </DrawerTitle>
<DropdownMenuSubContent className="w-52 max-h-72 overflow-y-auto"> <div className="px-4 pb-6 pt-1">
{documentTypes.length > 0 ? ( {documentTypes.length > 0 ? (
documentTypes.map((type) => ( documentTypes.map((type, index) => {
<DropdownMenuCheckboxItem const isActive = activeTypes.includes(type);
key={type} return (
checked={activeTypes.includes(type)} <div key={type}>
onCheckedChange={(checked) => onToggleType(type, checked === true)} {index > 0 && <div className="mx-3 h-px bg-popover-border" />}
onSelect={(event) => event.preventDefault()} <button
> type="button"
{getDocumentTypeIcon(type, "h-4 w-4")} className="flex h-12 w-full items-center gap-3 rounded-lg px-3 text-left text-sm transition-colors hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
<span className="min-w-0 flex-1 truncate">{getDocumentTypeLabel(type)}</span> onClick={() => onToggleType(type, !isActive)}
<span className="ml-auto text-xs text-muted-foreground"> >
{typeCounts[type] ?? 0} {getDocumentTypeIcon(type, "h-4 w-4")}
</span> <span className="min-w-0 flex-1 truncate">{getDocumentTypeLabel(type)}</span>
</DropdownMenuCheckboxItem> <span className="text-xs text-muted-foreground">{typeCounts[type] ?? 0}</span>
)) {isActive && <Check className="h-4 w-4 shrink-0 text-primary" />}
</button>
</div>
);
})
) : ( ) : (
<DropdownMenuItem disabled>No document types</DropdownMenuItem> <p className="px-3 py-4 text-sm text-muted-foreground">No document types</p>
)} )}
</DropdownMenuSubContent> </div>
</DropdownMenuSub> </DrawerContent>
</DropdownMenuContent> </Drawer>
</DropdownMenu> </>
); );
} }

View file

@ -5,6 +5,7 @@ import Link from "next/link";
import { useParams } from "next/navigation"; import { useParams } from "next/navigation";
import { useTranslations } from "next-intl"; import { useTranslations } from "next-intl";
import { type ReactNode, useMemo, useState } from "react"; import { type ReactNode, useMemo, useState } from "react";
import { ConnectAgentDialog } from "@/components/mcp/connect-agent-dialog";
import { Badge } from "@/components/ui/badge"; import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Progress } from "@/components/ui/progress"; import { Progress } from "@/components/ui/progress";
@ -322,10 +323,16 @@ export function Sidebar({
/> />
)} )}
{!isCollapsed && (
<div className="shrink-0 py-1.5">
<ConnectAgentDialog className="w-[calc(100%-1rem)]" />
</div>
)}
<SidebarUsageFooter <SidebarUsageFooter
pageUsage={pageUsage} pageUsage={pageUsage}
isCollapsed={isCollapsed} isCollapsed={isCollapsed}
hasNavSectionAbove={footerNavItems.length > 0} hasNavSectionAbove={footerNavItems.length > 0 || !isCollapsed}
onNavigate={onNavigate} onNavigate={onNavigate}
/> />