| import { useEffect, useState } from "react"; | |
| import { motion, AnimatePresence } from "framer-motion"; | |
| import { Home } from "lucide-react"; | |
| import { useWindowManager } from "./WindowContainer"; | |
| interface TaskbarProps { | |
| onLaunchApp?: (appId: string) => void; | |
| apps?: Array<{ id: string; name: string; icon: React.ReactNode; action: () => void }>; | |
| } | |
| export function Taskbar({ onLaunchApp, apps = [] }: TaskbarProps) { | |
| const { windows, activeWindowId, bringToFront, restoreWindow } = useWindowManager(); | |
| const [currentTime, setCurrentTime] = useState(new Date()); | |
| const [showStartMenu, setShowStartMenu] = useState(false); | |
| useEffect(() => { | |
| const timer = setInterval(() => setCurrentTime(new Date()), 1000); | |
| return () => clearInterval(timer); | |
| }, []); | |
| const formatTime = (date: Date) => date.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" }); | |
| return ( | |
| <> | |
| <AnimatePresence> | |
| {showStartMenu && ( | |
| <motion.div | |
| initial={{ opacity: 0, y: 16 }} | |
| animate={{ opacity: 1, y: 0 }} | |
| exit={{ opacity: 0, y: 16 }} | |
| className="fixed bottom-12 left-0 z-50 w-64 rounded-2xl bg-white p-2 shadow-2xl dark:bg-gray-800" | |
| > | |
| <div className="space-y-1"> | |
| {apps.map((app) => ( | |
| <button | |
| key={app.id} | |
| onClick={() => { | |
| app.action(); | |
| onLaunchApp?.(app.id); | |
| setShowStartMenu(false); | |
| }} | |
| className="flex w-full items-center gap-3 rounded-lg px-3 py-2 text-left transition hover:bg-gray-100 dark:hover:bg-gray-700" | |
| > | |
| {app.icon} | |
| <span>{app.name}</span> | |
| </button> | |
| ))} | |
| </div> | |
| </motion.div> | |
| )} | |
| </AnimatePresence> | |
| <div className="fixed bottom-0 left-0 right-0 z-50 flex items-center justify-between rounded-t-2xl bg-white/80 px-3 py-2 backdrop-blur dark:bg-gray-900/80"> | |
| <div className="flex items-center gap-2"> | |
| <button | |
| onClick={() => setShowStartMenu((prev) => !prev)} | |
| className="rounded-full bg-white p-2 transition hover:bg-white/80 dark:bg-gray-800" | |
| > | |
| <Home className="h-5 w-5 text-primary" /> | |
| </button> | |
| <div className="ml-2 flex gap-1"> | |
| {apps.slice(0, 3).map((app) => ( | |
| <button | |
| key={app.id} | |
| onClick={app.action} | |
| className="rounded-lg bg-white p-1.5 transition hover:bg-white/80 dark:bg-gray-800" | |
| title={app.name} | |
| > | |
| {app.icon} | |
| </button> | |
| ))} | |
| </div> | |
| </div> | |
| <div className="flex max-w-[60%] gap-1 overflow-x-auto px-2"> | |
| {windows | |
| .filter((w) => w.isOpen) | |
| .map((window) => ( | |
| <button | |
| key={window.id} | |
| onClick={() => (window.isMinimized ? restoreWindow(window.id) : bringToFront(window.id))} | |
| className={`rounded-full px-3 py-1 text-xs transition whitespace-nowrap ${ | |
| activeWindowId === window.id ? "bg-primary/20 text-primary" : "bg-white/70 dark:bg-gray-800" | |
| }`} | |
| > | |
| {window.title} | |
| </button> | |
| ))} | |
| </div> | |
| <span className="rounded-full bg-white px-3 py-1 text-xs dark:bg-gray-800">{formatTime(currentTime)}</span> | |
| </div> | |
| </> | |
| ); | |
| } | |
Xet Storage Details
- Size:
- 3.57 kB
- Xet hash:
- 6622b61bf33b787ef6f61ba40f83956f18de84194d1b6d1baa4ddd078f789130
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.