“vinit5112”
Upgrade UI
6f1f94e
raw
history blame
12.3 kB
import React from 'react';
import { motion, AnimatePresence } from 'framer-motion';
import {
PlusIcon,
TrashIcon,
XMarkIcon,
ChatBubbleLeftIcon,
HomeIcon,
Bars3Icon
} 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 - 1} days ago`;
return messageDate.toLocaleDateString();
};
const truncateTitle = (title, maxLength = 25) => {
if (title.length <= maxLength) return title;
return title.substring(0, maxLength) + '...';
};
return (
<AnimatePresence>
{open && (
<>
{/* Mobile: Full-screen overlay background */}
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
className="fixed inset-0 bg-black bg-opacity-50 z-40 md:hidden"
onClick={onClose}
/>
{/* Sidebar Content */}
<motion.div
initial={{
x: '-100%',
opacity: 0
}}
animate={{
x: 0,
opacity: 1
}}
exit={{
x: '-100%',
opacity: 0
}}
transition={{
type: "spring",
damping: 25,
stiffness: 200
}}
className={`fixed top-0 left-0 h-full w-full md:w-80 z-50 md:z-30 ${
darkMode
? 'bg-gray-900 border-gray-700'
: 'bg-white border-gray-200'
} border-r shadow-2xl md:shadow-xl flex flex-col`}
>
{/* Header - Mobile optimized */}
<div className={`p-4 md:p-6 border-b ${
darkMode ? 'border-gray-700/50' : 'border-gray-200/50'
} flex-shrink-0`}>
{/* Top row: Close button and title */}
<div className="flex items-center justify-between mb-4 md:mb-3">
<h2 className={`text-lg md:text-xl font-bold ${
darkMode ? 'text-white' : 'text-gray-900'
}`}>
Conversations
</h2>
<button
onClick={onClose}
className={`p-2 md:p-1.5 rounded-lg transition-colors touch-manipulation ${
darkMode
? 'hover:bg-gray-800 active:bg-gray-700 text-gray-400'
: 'hover:bg-gray-100 active:bg-gray-200 text-gray-500'
}`}
title="Close sidebar"
>
<XMarkIcon className="w-6 h-6 md:w-5 md:h-5" />
</button>
</div>
{/* Action buttons - Mobile optimized */}
<div className="flex flex-col sm:flex-row gap-2 md:gap-3">
<motion.button
whileHover={{ scale: 1.02 }}
whileTap={{ scale: 0.98 }}
onClick={() => {
onNewChat();
onClose();
}}
className={`flex items-center justify-center gap-2 md:gap-3 px-4 py-3 md:py-2.5 rounded-xl md:rounded-lg font-medium transition-all touch-manipulation ${
darkMode
? 'bg-primary-600 hover:bg-primary-700 active:bg-primary-800 text-white shadow-lg'
: 'bg-primary-500 hover:bg-primary-600 active:bg-primary-700 text-white shadow-lg'
} hover:shadow-xl active:shadow-2xl flex-1 sm:flex-none`}
>
<PlusIcon className="w-5 h-5 md:w-4 md:h-4" />
<span className="text-base md:text-sm">New Chat</span>
</motion.button>
<motion.button
whileHover={{ scale: 1.02 }}
whileTap={{ scale: 0.98 }}
onClick={() => {
onBackToHome();
onClose();
}}
className={`flex items-center justify-center gap-2 md:gap-3 px-4 py-3 md:py-2.5 rounded-xl md:rounded-lg font-medium transition-all touch-manipulation ${
darkMode
? 'bg-gray-700 hover:bg-gray-600 active:bg-gray-500 text-gray-200 shadow-lg'
: 'bg-gray-200 hover:bg-gray-300 active:bg-gray-400 text-gray-700 shadow-lg'
} hover:shadow-xl active:shadow-2xl flex-1 sm:flex-none`}
>
<HomeIcon className="w-5 h-5 md:w-4 md:h-4" />
<span className="text-base md:text-sm">Home</span>
</motion.button>
</div>
</div>
{/* Conversations List - Mobile optimized */}
<div className="flex-1 overflow-y-auto p-3 md:p-4">
{conversations.length === 0 ? (
<div className="flex flex-col items-center justify-center h-full text-center px-4">
<ChatBubbleLeftIcon className={`w-12 h-12 md:w-16 md:h-16 mb-4 ${
darkMode ? 'text-gray-600' : 'text-gray-400'
}`} />
<p className={`text-base md:text-lg font-medium mb-2 ${
darkMode ? 'text-gray-400' : 'text-gray-500'
}`}>
No conversations yet
</p>
<p className={`text-sm md:text-base ${
darkMode ? 'text-gray-500' : 'text-gray-400'
}`}>
Start a new chat to begin your CA study session
</p>
</div>
) : (
<div className="space-y-2 md:space-y-1">
{conversations.map((conv) => (
<motion.div
key={conv.id}
initial={{ opacity: 0, x: -20 }}
animate={{ opacity: 1, x: 0 }}
whileHover={{ x: 4 }}
className={`group relative p-3 md:p-3 rounded-xl md:rounded-lg cursor-pointer transition-all touch-manipulation ${
activeConversationId === conv.id
? darkMode
? 'bg-primary-600/20 border-primary-500/30 shadow-lg'
: 'bg-primary-50 border-primary-200 shadow-lg'
: darkMode
? 'hover:bg-gray-800 active:bg-gray-700'
: 'hover:bg-gray-50 active:bg-gray-100'
} border ${
activeConversationId === conv.id
? ''
: darkMode
? 'border-transparent'
: 'border-transparent'
}`}
onClick={() => {
onConversationSelect(conv.id);
onClose();
}}
>
{/* Conversation Content */}
<div className="flex items-start justify-between">
<div className="flex-1 min-w-0 mr-2">
{/* Title */}
<h3 className={`font-medium text-sm md:text-sm mb-1 truncate ${
activeConversationId === conv.id
? darkMode
? 'text-primary-300'
: 'text-primary-700'
: darkMode
? 'text-gray-200'
: 'text-gray-900'
}`}>
{truncateTitle(conv.title || 'New Conversation')}
</h3>
{/* Message count and date */}
<div className="flex items-center space-x-2">
<span className={`text-xs ${
activeConversationId === conv.id
? darkMode
? 'text-primary-400/80'
: 'text-primary-600/80'
: darkMode
? 'text-gray-500'
: 'text-gray-500'
}`}>
{conv.messages?.length || 0} messages
</span>
<span className={`text-xs ${
darkMode ? 'text-gray-600' : 'text-gray-400'
}`}>
</span>
<span className={`text-xs ${
darkMode ? 'text-gray-500' : 'text-gray-400'
}`}>
{formatDate(conv.createdAt)}
</span>
</div>
{/* Last message preview - Mobile optimized */}
{conv.messages && conv.messages.length > 0 && (
<p className={`text-xs mt-1 truncate ${
darkMode ? 'text-gray-500' : 'text-gray-500'
}`}>
{conv.messages[conv.messages.length - 1].content.substring(0, 40)}...
</p>
)}
</div>
{/* Delete Button - Larger touch target for mobile */}
<motion.button
whileHover={{ scale: 1.1 }}
whileTap={{ scale: 0.9 }}
onClick={(e) => {
e.stopPropagation();
onDeleteConversation(conv.id);
}}
className={`p-2 md:p-1.5 rounded-lg opacity-0 md:group-hover:opacity-100 transition-all touch-manipulation ${
darkMode
? 'hover:bg-red-600/20 active:bg-red-600/30 text-red-400'
: 'hover:bg-red-50 active:bg-red-100 text-red-500'
} flex-shrink-0 md:opacity-100 sm:opacity-100`}
title="Delete conversation"
>
<TrashIcon className="w-4 h-4 md:w-4 md:h-4" />
</motion.button>
</div>
{/* Active indicator */}
{activeConversationId === conv.id && (
<motion.div
layoutId="activeConversation"
className={`absolute left-0 top-0 bottom-0 w-1 rounded-r ${
darkMode ? 'bg-primary-500' : 'bg-primary-500'
}`}
/>
)}
</motion.div>
))}
</div>
)}
</div>
{/* Footer - Mobile optimized */}
<div className={`p-4 md:p-6 border-t ${
darkMode ? 'border-gray-700/50' : 'border-gray-200/50'
} flex-shrink-0`}>
<div className={`text-center text-xs ${
darkMode ? 'text-gray-500' : 'text-gray-400'
}`}>
<p className="mb-1">📚 CA Study Assistant</p>
<p>{conversations.length} conversation{conversations.length !== 1 ? 's' : ''}</p>
</div>
</div>
</motion.div>
</>
)}
</AnimatePresence>
);
};
export default Sidebar;