Spaces:
Running
Running
import React from 'react'; | |
import { motion, AnimatePresence } from 'framer-motion'; | |
import { | |
PlusIcon, | |
XMarkIcon, | |
ChatBubbleLeftIcon, | |
TrashIcon, | |
HomeIcon | |
} from '@heroicons/react/24/outline'; | |
const Sidebar = ({ | |
open, | |
onClose, | |
conversations, | |
activeConversationId, | |
onConversationSelect, | |
onNewChat, | |
onDeleteConversation, | |
onBackToHome, | |
darkMode | |
}) => { | |
const formatDate = (date) => { | |
const now = new Date(); | |
const messageDate = new Date(date); | |
const diffTime = Math.abs(now - messageDate); | |
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24)); | |
if (diffDays === 1) return 'Today'; | |
if (diffDays === 2) return 'Yesterday'; | |
if (diffDays <= 7) return `${diffDays} days ago`; | |
return messageDate.toLocaleDateString(); | |
}; | |
const sidebarVariants = { | |
open: { | |
x: 0, | |
transition: { | |
type: "spring", | |
stiffness: 300, | |
damping: 30 | |
} | |
}, | |
closed: { | |
x: -280, | |
transition: { | |
type: "spring", | |
stiffness: 300, | |
damping: 30 | |
} | |
} | |
}; | |
const overlayVariants = { | |
open: { opacity: 1 }, | |
closed: { opacity: 0 } | |
}; | |
return ( | |
<> | |
{/* Mobile Overlay */} | |
<AnimatePresence> | |
{open && ( | |
<motion.div | |
variants={overlayVariants} | |
initial="closed" | |
animate="open" | |
exit="closed" | |
className="fixed inset-0 bg-black bg-opacity-50 z-40 md:hidden" | |
onClick={onClose} | |
/> | |
)} | |
</AnimatePresence> | |
{/* Sidebar */} | |
<motion.aside | |
variants={sidebarVariants} | |
initial="closed" | |
animate={open ? "open" : "closed"} | |
className={`fixed left-0 top-16 h-[calc(100vh-4rem)] w-64 z-50 ${ | |
darkMode | |
? 'bg-gray-900 border-gray-700' | |
: 'bg-white border-gray-200' | |
} border-r shadow-lg flex flex-col`} | |
> | |
{/* Header */} | |
<div className="p-4 border-b border-gray-200 dark:border-gray-700"> | |
<div className="flex items-center justify-between mb-4"> | |
<h2 className="font-semibold text-lg">Conversations</h2> | |
<button | |
onClick={onClose} | |
className={`p-1 rounded-lg transition-colors md:hidden ${ | |
darkMode | |
? 'hover:bg-gray-800 text-gray-400' | |
: 'hover:bg-gray-100 text-gray-500' | |
}`} | |
> | |
<XMarkIcon className="w-5 h-5" /> | |
</button> | |
</div> | |
<div className="space-y-2"> | |
<motion.button | |
whileHover={{ scale: 1.02 }} | |
whileTap={{ scale: 0.98 }} | |
onClick={onNewChat} | |
className={`w-full flex items-center gap-3 px-3 py-2 rounded-lg transition-colors ${ | |
darkMode | |
? 'bg-gray-800 hover:bg-gray-700 text-white border-gray-600' | |
: 'bg-gray-50 hover:bg-gray-100 text-gray-900 border-gray-300' | |
} border`} | |
> | |
<PlusIcon className="w-4 h-4" /> | |
<span className="font-medium">New Chat</span> | |
</motion.button> | |
<motion.button | |
whileHover={{ scale: 1.02 }} | |
whileTap={{ scale: 0.98 }} | |
onClick={onBackToHome} | |
className={`w-full flex items-center gap-3 px-3 py-2 rounded-lg transition-colors ${ | |
darkMode | |
? 'bg-primary-600 hover:bg-primary-700 text-white' | |
: 'bg-primary-50 hover:bg-primary-100 text-primary-900 border-primary-300' | |
} border`} | |
> | |
<HomeIcon className="w-4 h-4" /> | |
<span className="font-medium">Back to Home</span> | |
</motion.button> | |
</div> | |
</div> | |
{/* Conversations List */} | |
<div className="flex-1 overflow-y-auto p-2"> | |
{conversations.length === 0 ? ( | |
<div className={`text-center py-8 ${ | |
darkMode ? 'text-gray-500' : 'text-gray-400' | |
}`}> | |
<ChatBubbleLeftIcon className="w-12 h-12 mx-auto mb-3 opacity-50" /> | |
<p className="text-sm">No conversations yet</p> | |
<p className="text-xs mt-1">Start a new chat to begin</p> | |
</div> | |
) : ( | |
<div className="space-y-1"> | |
{conversations.map((conversation) => ( | |
<motion.button | |
key={conversation.id} | |
whileHover={{ x: 4 }} | |
onClick={() => { | |
onConversationSelect(conversation.id); | |
onClose(); | |
}} | |
className={`w-full text-left p-3 rounded-lg transition-all group ${ | |
activeConversationId === conversation.id | |
? darkMode | |
? 'bg-primary-600 text-white' | |
: 'bg-primary-50 text-primary-900 border-primary-200' | |
: darkMode | |
? 'hover:bg-gray-800 text-gray-300' | |
: 'hover:bg-gray-50 text-gray-700' | |
}`} | |
> | |
<div className="flex items-start justify-between"> | |
<div className="flex-1 min-w-0"> | |
<p className="font-medium truncate text-sm"> | |
{conversation.title} | |
</p> | |
<p className={`text-xs mt-1 ${ | |
activeConversationId === conversation.id | |
? 'text-primary-200' | |
: darkMode | |
? 'text-gray-500' | |
: 'text-gray-500' | |
}`}> | |
{formatDate(conversation.createdAt)} | |
</p> | |
</div> | |
<button | |
onClick={(e) => { | |
e.stopPropagation(); | |
if (window.confirm('Are you sure you want to delete this conversation?')) { | |
onDeleteConversation(conversation.id); | |
} | |
}} | |
className={`opacity-0 group-hover:opacity-100 p-1 rounded transition-opacity ${ | |
darkMode | |
? 'hover:bg-gray-700 text-gray-400' | |
: 'hover:bg-gray-200 text-gray-500' | |
}`} | |
> | |
<TrashIcon className="w-4 h-4" /> | |
</button> | |
</div> | |
</motion.button> | |
))} | |
</div> | |
)} | |
</div> | |
{/* Footer */} | |
<div className={`p-4 border-t ${ | |
darkMode ? 'border-gray-700' : 'border-gray-200' | |
}`}> | |
<div className={`text-xs ${ | |
darkMode ? 'text-gray-500' : 'text-gray-400' | |
}`}> | |
<p>CA Study Assistant v2.0</p> | |
<p className="mt-1">Powered by AI</p> | |
</div> | |
</div> | |
</motion.aside> | |
</> | |
); | |
}; | |
export default Sidebar; |