docuflow / src /components /document /DocumentSidebar.tsx
Joedroid's picture
feat: add close buttons to Folder Sidebar, Sync Logs, AI Panel, and Live Preview
3720e61
Raw
History Blame Contribute Delete
6.88 kB
import React from "react";
import {
FileText,
Plus,
Search,
X,
Trash2,
Wifi,
WifiOff,
RefreshCw
} from "lucide-react";
import { Document } from "../../types";
interface DocumentSidebarProps {
documents: Document[];
activeDocId: string;
setActiveDocId: (id: string) => void;
setShowDocModal: (show: boolean) => void;
handleDeleteDocument: (id: string, e: React.MouseEvent) => void;
documentSearchTerm: string;
setDocumentSearchTerm: (term: string) => void;
syncStatus: "synced" | "syncing" | "offline" | "error";
teamChangesEnabled: boolean;
activeCollaborators: number;
onClose?: () => void;
}
export default function DocumentSidebar({
documents,
activeDocId,
setActiveDocId,
setShowDocModal,
handleDeleteDocument,
documentSearchTerm,
setDocumentSearchTerm,
syncStatus,
teamChangesEnabled,
activeCollaborators,
onClose
}: DocumentSidebarProps) {
const filtered = documents.filter((doc) =>
doc.title.toLowerCase().includes(documentSearchTerm.toLowerCase())
);
return (
<div className="bg-[#15181E] border border-slate-800 rounded-xl p-4 space-y-3.5 shadow-md">
<div className="flex items-center justify-between">
<h2 className="text-xs font-mono uppercase tracking-wider text-slate-505 flex items-center gap-1.5 font-bold">
<FileText className="h-4 w-4 text-blue-500" /> Folder Documents
</h2>
<div className="flex items-center gap-1.5">
<button
onClick={() => setShowDocModal(true)}
className="p-1 px-2.5 bg-[#0F1115] hover:bg-slate-800 text-slate-300 hover:text-white border border-slate-800 rounded-md text-xs font-semibold flex items-center gap-1 transition cursor-pointer"
>
<Plus className="h-3 w-3" /> New
</button>
{onClose && (
<button
onClick={onClose}
className="p-1 bg-[#0F1115] hover:bg-slate-800 text-slate-400 hover:text-white border border-slate-800 rounded-md transition cursor-pointer"
title="Hide Sidebar"
>
<X className="h-3 w-3" />
</button>
)}
</div>
</div>
{/* Highly intuitive search input layout */}
<div className="relative">
<span className="absolute inset-y-0 left-0 flex items-center pl-2.5 pointer-events-none">
<Search className="h-3.5 w-3.5 text-slate-500" />
</span>
<input
type="text"
placeholder="Search by title..."
value={documentSearchTerm}
onChange={(e) => setDocumentSearchTerm(e.target.value)}
className="w-full text-xs bg-[#0F1115] border border-slate-800 rounded-lg pl-8 pr-7 py-2 text-slate-205 placeholder:text-slate-650 focus:outline-none focus:border-slate-700 tracking-tight transition-colors"
/>
{documentSearchTerm && (
<button
type="button"
onClick={() => setDocumentSearchTerm("")}
className="absolute inset-y-0 right-0 flex items-center pr-2.5 text-slate-500 hover:text-white transition-colors cursor-pointer"
title="Clear search query"
>
<X className="h-3.5 w-3.5" />
</button>
)}
</div>
{/* Documents List */}
<div className="space-y-1 max-h-[180px] overflow-y-auto pr-1">
{filtered.length === 0 ? (
<div className="text-center py-6 text-slate-500 text-xs font-mono">
No matching segments
</div>
) : (
filtered.map((doc) => {
const isSelected = doc.id === activeDocId;
return (
<div
key={doc.id}
onClick={() => setActiveDocId(doc.id)}
className={`w-full text-left px-3 py-2 rounded-lg transition cursor-pointer flex items-center justify-between gap-2 group ${
isSelected
? "bg-blue-600/10 border border-blue-500/20 text-white"
: "text-slate-400 hover:text-slate-200 hover:bg-[#0F1115]/50 border border-transparent"
}`}
>
<div className="font-mono text-xs truncate max-w-[80%]">
{doc.title}
</div>
<button
onClick={(e) => handleDeleteDocument(doc.id, e)}
className="opacity-0 group-hover:opacity-100 hover:text-red-400 transition p-1 cursor-pointer"
>
<Trash2 className="h-3 w-3" />
</button>
</div>
);
})
)}
</div>
{/* Secure Offline Persistent Sync Status Banner */}
<div className="pt-3 border-t border-slate-800 space-y-2.5">
<div className="flex items-center justify-between text-xs">
<span className="font-mono text-[10px] text-slate-500">Sync Pipeline</span>
{syncStatus === "synced" && (
<span className="flex items-center gap-1 font-mono text-[10px] text-emerald-400 bg-emerald-500/10 px-2 py-0.5 rounded-full border border-emerald-500/20 text-[9px] font-bold">
<Wifi className="h-3 w-3" /> Online Synced
</span>
)}
{syncStatus === "syncing" && (
<span className="flex items-center gap-1 font-mono text-[10px] text-amber-400 bg-amber-500/10 px-2 py-0.5 rounded-full border border-amber-500/20 text-[9px] font-bold">
<RefreshCw className="h-3 w-3 animate-spin" /> Uploading Segment
</span>
)}
{syncStatus === "offline" && (
<span className="flex items-center gap-1 font-mono text-[10px] text-slate-400 bg-slate-800/10 px-2 py-0.5 rounded-full border border-slate-750 text-[9px] font-bold">
<WifiOff className="h-3 w-3" /> Offline Buffer
</span>
)}
{syncStatus === "error" && (
<span className="flex items-center gap-1 font-mono text-[10px] text-red-405 bg-red-500/10 px-2 py-0.5 rounded-full border border-red-500/20 text-[9px] font-bold">
<WifiOff className="h-3 w-3" /> Sync Failed
</span>
)}
</div>
<div className="bg-[#0F1115]/60 border border-slate-800 p-2.5 rounded-lg space-y-1">
<div className="flex justify-between items-center text-[9px] text-slate-500 font-mono">
<span>Collaborators</span>
<span>{teamChangesEnabled ? activeCollaborators : "1"} Editor Online</span>
</div>
<div className="flex items-center gap-1 py-1">
<div className="h-2 w-2 rounded-full bg-emerald-500 animate-pulse" />
<span className="font-mono text-[9px] text-slate-400">Sarah and Liam joined review panel</span>
</div>
</div>
</div>
</div>
);
}