Spaces:
Sleeping
Sleeping
| 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; |