mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-07-08 22:22:17 +02:00
feat(sidebar): introduce SidebarListItem component for consistent sidebar item styling
- Added SidebarListItem component to standardize the appearance of sidebar items across DocumentNode and FolderNode. - Refactored DocumentNode and FolderNode to utilize SidebarListItem for improved layout and interaction. - Updated ChatListItem to leverage sidebarListItemClassName for consistent styling. - Enhanced SidebarSection to improve content rendering and visibility transitions.
This commit is contained in:
parent
312ae6f753
commit
53259b6105
6 changed files with 104 additions and 40 deletions
|
|
@ -40,6 +40,7 @@ import { Spinner } from "@/components/ui/spinner";
|
|||
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
|
||||
import type { DocumentTypeEnum } from "@/contracts/types/document.types";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { SidebarListItem } from "../layout/ui/sidebar/SidebarListItem";
|
||||
import { DND_TYPES } from "./FolderNode";
|
||||
import { isVersionableType } from "./version-history";
|
||||
|
||||
|
|
@ -165,13 +166,11 @@ export const DocumentNode = React.memo(function DocumentNode({
|
|||
return (
|
||||
<ContextMenu onOpenChange={onContextMenuOpenChange}>
|
||||
<ContextMenuTrigger asChild>
|
||||
<div
|
||||
<SidebarListItem
|
||||
ref={attachRef}
|
||||
className={cn(
|
||||
"group flex h-8 w-full items-center gap-2.5 rounded-md px-1 text-sm hover:bg-accent hover:text-accent-foreground cursor-pointer select-none text-left",
|
||||
isMentioned && "bg-accent text-accent-foreground",
|
||||
isDragging && "opacity-40"
|
||||
)}
|
||||
active={isMentioned}
|
||||
dragging={isDragging}
|
||||
className="gap-2.5 px-1"
|
||||
style={{ paddingLeft: `${depth * 16 + 4}px` }}
|
||||
>
|
||||
{(() => {
|
||||
|
|
@ -358,7 +357,7 @@ export const DocumentNode = React.memo(function DocumentNode({
|
|||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</span>
|
||||
</div>
|
||||
</SidebarListItem>
|
||||
</ContextMenuTrigger>
|
||||
|
||||
{contextMenuOpen && (
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ import {
|
|||
ChevronDown,
|
||||
ChevronRight,
|
||||
Download,
|
||||
Eye,
|
||||
EyeOff,
|
||||
Folder,
|
||||
FolderOpen,
|
||||
|
|
@ -18,6 +17,7 @@ import {
|
|||
} from "lucide-react";
|
||||
import React, { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { useDrag, useDrop } from "react-dnd";
|
||||
import { SidebarListItem } from "@/components/layout/ui/sidebar/SidebarListItem";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Checkbox } from "@/components/ui/checkbox";
|
||||
import {
|
||||
|
|
@ -254,15 +254,14 @@ export const FolderNode = React.memo(function FolderNode({
|
|||
return (
|
||||
<ContextMenu onOpenChange={onContextMenuOpenChange}>
|
||||
<ContextMenuTrigger asChild disabled={isRenaming}>
|
||||
{/* biome-ignore lint/a11y/useSemanticElements: div required for drag/drop refs */}
|
||||
<div
|
||||
<SidebarListItem
|
||||
ref={attachRef}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
dragging={isDragging}
|
||||
className={cn(
|
||||
"group relative flex h-8 items-center gap-1 rounded-md px-1 text-sm hover:bg-accent hover:text-accent-foreground cursor-pointer select-none",
|
||||
"relative gap-1 px-1",
|
||||
isExpanded && "font-medium",
|
||||
isDragging && "opacity-40",
|
||||
isOver && canDrop && dropZone === "middle" && "bg-accent ring-1 ring-primary/40",
|
||||
isOver && canDrop && dropZone === "top" && "border-t-2 border-primary",
|
||||
isOver && canDrop && dropZone === "bottom" && "border-b-2 border-primary",
|
||||
|
|
@ -434,7 +433,7 @@ export const FolderNode = React.memo(function FolderNode({
|
|||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
)}
|
||||
</div>
|
||||
</SidebarListItem>
|
||||
</ContextMenuTrigger>
|
||||
|
||||
{!isRenaming && contextMenuOpen && (
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import { useLongPress } from "@/hooks/use-long-press";
|
|||
import { useIsMobile } from "@/hooks/use-mobile";
|
||||
import { useTypewriter } from "@/hooks/use-typewriter";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { sidebarListItemClassName } from "./SidebarListItem";
|
||||
|
||||
interface ChatListItemProps {
|
||||
name: string;
|
||||
|
|
@ -66,12 +67,11 @@ export function ChatListItem({
|
|||
onMouseEnter={onPrefetch}
|
||||
onFocus={onPrefetch}
|
||||
{...(isMobile ? longPressHandlers : {})}
|
||||
className={cn(
|
||||
"h-auto w-full justify-start gap-2 overflow-hidden px-2 py-1.5 text-left font-normal",
|
||||
"group-hover/item:bg-accent group-hover/item:text-accent-foreground",
|
||||
"focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring",
|
||||
isActive && "bg-accent text-accent-foreground"
|
||||
)}
|
||||
className={sidebarListItemClassName({
|
||||
active: isActive,
|
||||
className:
|
||||
"justify-start gap-2 overflow-hidden px-2 py-1.5 font-normal focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring",
|
||||
})}
|
||||
>
|
||||
<span className="min-w-0 flex-1 truncate">{animatedName}</span>
|
||||
</Button>
|
||||
|
|
|
|||
|
|
@ -1556,22 +1556,31 @@ function AuthenticatedDocumentsSidebarBase({
|
|||
|
||||
if (embedded) {
|
||||
return (
|
||||
<SidebarSection
|
||||
title={t("title") || "Documents"}
|
||||
defaultOpen={true}
|
||||
fillHeight
|
||||
contentClassName="px-0"
|
||||
persistentAction={
|
||||
<EmbeddedDocumentsMenu
|
||||
typeCounts={typeCounts}
|
||||
activeTypes={activeTypes}
|
||||
onToggleType={onToggleType}
|
||||
onCreateFolder={() => handleCreateFolder(null)}
|
||||
/>
|
||||
}
|
||||
>
|
||||
{renderDocumentTree()}
|
||||
</SidebarSection>
|
||||
<>
|
||||
<SidebarSection
|
||||
title={t("title") || "Documents"}
|
||||
defaultOpen={true}
|
||||
fillHeight
|
||||
contentClassName="px-0"
|
||||
persistentAction={
|
||||
<EmbeddedDocumentsMenu
|
||||
typeCounts={typeCounts}
|
||||
activeTypes={activeTypes}
|
||||
onToggleType={onToggleType}
|
||||
onCreateFolder={() => handleCreateFolder(null)}
|
||||
/>
|
||||
}
|
||||
>
|
||||
{renderDocumentTree()}
|
||||
</SidebarSection>
|
||||
|
||||
<CreateFolderDialog
|
||||
open={createFolderOpen}
|
||||
onOpenChange={setCreateFolderOpen}
|
||||
parentFolderName={createFolderParentName}
|
||||
onConfirm={handleCreateFolderConfirm}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,47 @@
|
|||
"use client";
|
||||
|
||||
import type React from "react";
|
||||
import { forwardRef } from "react";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
type SidebarListItemElementProps = React.HTMLAttributes<HTMLDivElement>;
|
||||
|
||||
interface SidebarListItemProps extends SidebarListItemElementProps {
|
||||
active?: boolean;
|
||||
dragging?: boolean;
|
||||
interactive?: boolean;
|
||||
}
|
||||
|
||||
export const SidebarListItem = forwardRef<HTMLDivElement, SidebarListItemProps>(
|
||||
({ active, dragging, interactive = true, className, children, ...props }, ref) => (
|
||||
<div
|
||||
ref={ref}
|
||||
className={sidebarListItemClassName({ active, dragging, interactive, className })}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
);
|
||||
|
||||
SidebarListItem.displayName = "SidebarListItem";
|
||||
|
||||
export function sidebarListItemClassName({
|
||||
active,
|
||||
dragging,
|
||||
interactive = true,
|
||||
className,
|
||||
}: {
|
||||
active?: boolean;
|
||||
dragging?: boolean;
|
||||
interactive?: boolean;
|
||||
className?: string;
|
||||
}) {
|
||||
return cn(
|
||||
"group group/sidebar-list-item flex h-8 w-full items-center rounded-md text-left text-sm select-none",
|
||||
interactive && "cursor-pointer hover:bg-accent hover:text-accent-foreground",
|
||||
active && "bg-accent text-accent-foreground",
|
||||
dragging && "opacity-40",
|
||||
className
|
||||
);
|
||||
}
|
||||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
import { ChevronRight } from "lucide-react";
|
||||
import { useState } from "react";
|
||||
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible";
|
||||
import { Collapsible, CollapsibleTrigger } from "@/components/ui/collapsible";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
interface SidebarSectionProps {
|
||||
|
|
@ -78,11 +78,21 @@ export function SidebarSection({
|
|||
)}
|
||||
</div>
|
||||
|
||||
<CollapsibleContent className={cn("overflow-hidden flex-1 flex flex-col min-h-0")}>
|
||||
<div className={cn("px-2 flex-1 flex flex-col min-h-0 overflow-hidden", contentClassName)}>
|
||||
{children}
|
||||
<div
|
||||
className={cn(
|
||||
"grid flex-1 overflow-hidden transition-[grid-template-rows,opacity] duration-200 ease-out",
|
||||
isOpen ? "grid-rows-[1fr] opacity-100" : "pointer-events-none grid-rows-[0fr] opacity-0"
|
||||
)}
|
||||
aria-hidden={!isOpen}
|
||||
>
|
||||
<div className="min-h-0 overflow-hidden">
|
||||
<div
|
||||
className={cn("px-2 flex h-full min-h-0 flex-col overflow-hidden", contentClassName)}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
</CollapsibleContent>
|
||||
</div>
|
||||
</Collapsible>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue