Spaces:
Sleeping
Sleeping
| import React, { useState } from "react"; | |
| import { useNavigate } from "react-router-dom"; | |
| import { Button } from "@/components/ui/button"; | |
| import { | |
| CheckSquare, | |
| Grid3x3, | |
| X, | |
| Plus, | |
| FileText, | |
| LayoutDashboard, | |
| Settings, | |
| Bell, | |
| ChevronLeft, | |
| Bug | |
| } from "lucide-react"; | |
| import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip"; | |
| import { motion, AnimatePresence } from "framer-motion"; | |
| import { useAuth } from "@/lib/auth-context"; | |
| const QuickAccess: React.FC = () => { | |
| const [isOpen, setIsOpen] = useState(false); | |
| const [activeIndex, setActiveIndex] = useState<number | null>(null); | |
| const navigate = useNavigate(); | |
| const { userData } = useAuth(); | |
| // Don't render if userData is null or user is client role | |
| if (!userData || userData?.roleName?.toLowerCase() === "client") { | |
| return null; | |
| } | |
| const handleNavigate = (path: string, index: number) => { | |
| setActiveIndex(index); | |
| setTimeout(() => { | |
| navigate(path); | |
| setIsOpen(false); | |
| setActiveIndex(null); | |
| }, 300); | |
| }; | |
| const tools = [ | |
| { | |
| name: "Tasks", | |
| icon: <CheckSquare className="h-5 w-5" />, | |
| path: "/tasks", | |
| shortcut: "Alt+T", | |
| color: "from-blue-400 to-indigo-500" | |
| }, | |
| { | |
| name: "Features", | |
| icon: <Grid3x3 className="h-5 w-5" />, | |
| path: "/issues", | |
| shortcut: "Alt+F", | |
| color: "from-emerald-400 to-teal-500" | |
| }, | |
| { | |
| name: "Bugs", | |
| icon: <Bug className="h-5 w-5" />, | |
| path: "/bugs", | |
| shortcut: "Alt+B", | |
| color: "from-red-400 to-rose-500" | |
| }, | |
| { | |
| name: "Dashboard", | |
| icon: <LayoutDashboard className="h-5 w-5" />, | |
| path: "/", | |
| shortcut: "Alt+D", | |
| color: "from-purple-400 to-violet-500" | |
| }, | |
| // { | |
| // name: "Files", | |
| // icon: <FileText className="h-5 w-5" />, | |
| // path: "/files", | |
| // shortcut: "Alt+L", | |
| // color: "from-amber-400 to-orange-500" | |
| // }, | |
| // { | |
| // name: "Notifications", | |
| // icon: <Bell className="h-5 w-5" />, | |
| // path: "/notifications", | |
| // shortcut: "Alt+N", | |
| // color: "from-pink-400 to-rose-500" | |
| // }, | |
| { | |
| name: "Settings", | |
| icon: <Settings className="h-5 w-5" />, | |
| path: "/settings", | |
| shortcut: "Alt+S", | |
| color: "from-gray-400 to-gray-500" | |
| } | |
| ]; | |
| const slideVariants = { | |
| hidden: { opacity: 0, x: 100 }, | |
| visible: { opacity: 1, x: 0 }, | |
| exit: { opacity: 0, x: 100 } | |
| }; | |
| return ( | |
| <div className="fixed inset-y-0 right-0 z-50 flex items-center justify-end overflow-hidden"> | |
| <AnimatePresence mode="wait"> | |
| {isOpen && ( | |
| <motion.div | |
| variants={slideVariants} | |
| initial="hidden" | |
| animate="visible" | |
| exit="exit" | |
| transition={{ duration: 0.3, ease: [0.22, 1, 0.36, 1] }} | |
| className="bg-white/50 dark:bg-gray-800/50 backdrop-blur-md rounded-l-2xl shadow-[0_0_25px_rgba(0,0,0,0.1)] dark:shadow-[0_0_25px_rgba(0,0,0,0.3)] py-4 px-6 flex flex-col gap-3 border-y border-l border-gray-200/60 dark:border-gray-700/60 overflow-hidden" | |
| style={{ maxHeight: '90vh', width: '280px' }} | |
| > | |
| <div className="flex items-center justify-between"> | |
| <div className="text-lg font-medium text-gray-800 dark:text-gray-200"> | |
| Quick Access | |
| </div> | |
| <Button | |
| size="sm" | |
| variant="ghost" | |
| className="rounded-full w-8 h-8 flex items-center justify-center hover:bg-gray-100 dark:hover:bg-gray-700" | |
| onClick={() => setIsOpen(false)} | |
| > | |
| <X className="h-4 w-4" /> | |
| </Button> | |
| </div> | |
| <div className="flex flex-col space-y-2 overflow-y-auto overflow-x-hidden py-2 px-1 custom-scrollbar" | |
| style={{ maxHeight: 'calc(90vh - 80px)' }}> | |
| {tools.map((tool, index) => ( | |
| <motion.div | |
| key={tool.name} | |
| initial={{ opacity: 0, x: 30 }} | |
| animate={{ opacity: 1, x: 0 }} | |
| transition={{ delay: index * 0.05, duration: 0.2 }} | |
| whileHover={{ scale: 1.03, x: -5, transition: { duration: 0.2 } }} | |
| whileTap={{ scale: 0.97 }} | |
| className={`relative flex items-center p-3 bg-gray-50/80 dark:bg-gray-800/80 rounded-xl cursor-pointer transition-all overflow-hidden ${ | |
| activeIndex === index ? "pointer-events-none" : "" | |
| }`} | |
| onClick={() => handleNavigate(tool.path, index)} | |
| > | |
| {activeIndex === index && ( | |
| <motion.div | |
| className={`absolute inset-0 bg-gradient-to-r ${tool.color} opacity-10`} | |
| initial={{ opacity: 0 }} | |
| animate={{ opacity: 1 }} | |
| transition={{ duration: 0.2 }} | |
| /> | |
| )} | |
| <div className={`size-10 flex-shrink-0 flex items-center justify-center rounded-full bg-gradient-to-br ${tool.color} text-white shadow-sm mr-3`}> | |
| {tool.icon} | |
| </div> | |
| <div className="flex flex-col min-w-0"> | |
| <span className="text-sm font-medium text-gray-700 dark:text-gray-300 truncate"> | |
| {tool.name} | |
| </span> | |
| <kbd className="mt-1 w-fit px-1.5 py-0.5 text-xs font-mono text-gray-500 bg-gray-100 dark:bg-gray-700 dark:text-gray-300 border border-gray-200 dark:border-gray-600 rounded-md shadow-sm"> | |
| {tool.shortcut} | |
| </kbd> | |
| </div> | |
| {activeIndex === index && ( | |
| <motion.div | |
| className="absolute inset-0 bg-white/70 dark:bg-gray-800/70 flex items-center justify-center" | |
| initial={{ scale: 0, opacity: 0 }} | |
| animate={{ scale: 1, opacity: 1 }} | |
| transition={{ duration: 0.3, delay: 0.1 }} | |
| > | |
| <div className={`size-10 flex items-center justify-center rounded-full bg-gradient-to-br ${tool.color} animate-pulse`}> | |
| {tool.icon} | |
| </div> | |
| </motion.div> | |
| )} | |
| </motion.div> | |
| ))} | |
| </div> | |
| </motion.div> | |
| )} | |
| </AnimatePresence> | |
| {!isOpen && ( | |
| <TooltipProvider> | |
| <Tooltip> | |
| <TooltipTrigger asChild> | |
| <motion.div | |
| whileTap={{ scale: 0.95 }} | |
| whileHover={{ x: -5 }} | |
| className="group" | |
| > | |
| <Button | |
| size="icon" | |
| className="h-12 w-12 rounded-l-xl rounded-r-none shadow-lg border-y border-l border-gray-300 dark:border-gray-600 bg-indigo-500 hover:bg-indigo-600 dark:bg-indigo-600 dark:hover:bg-indigo-700 text-white" | |
| onClick={() => setIsOpen(true)} | |
| > | |
| <ChevronLeft className="h-6 w-6" /> | |
| </Button> | |
| </motion.div> | |
| </TooltipTrigger> | |
| <TooltipContent side="left" className="bg-gray-800 text-white border-gray-700"> | |
| <p>Open Quick Access</p> | |
| </TooltipContent> | |
| </Tooltip> | |
| </TooltipProvider> | |
| )} | |
| </div> | |
| ); | |
| }; | |
| export default QuickAccess; | |
| // Add this CSS to your global styles | |
| /* | |
| .custom-scrollbar::-webkit-scrollbar { | |
| width: 5px; | |
| } | |
| .custom-scrollbar::-webkit-scrollbar-track { | |
| background: transparent; | |
| } | |
| .custom-scrollbar::-webkit-scrollbar-thumb { | |
| background-color: rgba(156, 163, 175, 0.3); | |
| border-radius: 20px; | |
| } | |
| .custom-scrollbar { | |
| scrollbar-width: thin; | |
| scrollbar-color: rgba(156, 163, 175, 0.3) transparent; | |
| } | |
| */ |