Spaces:
Running
Running
| 'use client' | |
| import React, { useState, useEffect, useRef } from 'react' | |
| import { | |
| SpeakerHigh, | |
| WifiHigh, | |
| BatteryFull, | |
| MagnifyingGlass, | |
| Desktop, | |
| CirclesFour, | |
| Check, | |
| Warning | |
| } from '@phosphor-icons/react' | |
| import { motion, AnimatePresence } from 'framer-motion' | |
| import { CalendarWidget } from './CalendarWidget' | |
| interface TopBarProps { | |
| activeAppName?: string | |
| onAboutClick?: () => void | |
| onSleep?: () => void | |
| onRestart?: () => void | |
| onShutdown?: () => void | |
| } | |
| export function TopBar({ activeAppName = 'Finder', onAboutClick, onSleep, onRestart, onShutdown }: TopBarProps) { | |
| const [activeMenu, setActiveMenu] = useState<'apple' | 'battery' | 'wifi' | 'clock' | null>(null) | |
| const [dateDisplay, setDateDisplay] = useState('') | |
| const [timeDisplay, setTimeDisplay] = useState('') | |
| const menuRef = useRef<HTMLDivElement>(null) | |
| useEffect(() => { | |
| const updateTime = () => { | |
| const now = new Date() | |
| const days = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'] | |
| const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'] | |
| const timeString = now.toLocaleTimeString('en-US', { | |
| hour: 'numeric', | |
| minute: '2-digit', | |
| hour12: true | |
| }) | |
| const dateString = `${days[now.getDay()]} ${now.getDate()} ${months[now.getMonth()]}` | |
| setDateDisplay(dateString) | |
| setTimeDisplay(timeString) | |
| } | |
| updateTime() | |
| const interval = setInterval(updateTime, 1000) | |
| const handleClickOutside = (event: MouseEvent) => { | |
| if (menuRef.current && !menuRef.current.contains(event.target as Node)) { | |
| setActiveMenu(null) | |
| } | |
| } | |
| document.addEventListener('mousedown', handleClickOutside) | |
| return () => { | |
| clearInterval(interval) | |
| document.removeEventListener('mousedown', handleClickOutside) | |
| } | |
| }, []) | |
| const toggleMenu = (menu: 'apple' | 'battery' | 'wifi' | 'clock') => { | |
| setActiveMenu(activeMenu === menu ? null : menu) | |
| } | |
| return ( | |
| <div className="fixed top-0 left-0 right-0 h-10 sm:h-8 glass flex items-center justify-between px-2 sm:px-4 text-xs sm:text-sm z-50 shadow-sm backdrop-blur-xl bg-white/40 border-b border-white/20 select-none"> | |
| <div className="flex items-center space-x-2 sm:space-x-4" ref={menuRef}> | |
| <div className="relative"> | |
| <button | |
| onClick={() => toggleMenu('apple')} | |
| className={`font-bold text-lg hover:text-blue-600 transition-colors relative ${activeMenu === 'apple' ? 'text-blue-600' : ''}`} | |
| > | |
| <CirclesFour size={20} weight="fill" /> | |
| </button> | |
| <AnimatePresence> | |
| {activeMenu === 'apple' && ( | |
| <motion.div | |
| initial={{ opacity: 0, y: 5, scale: 0.95 }} | |
| animate={{ opacity: 1, y: 0, scale: 1 }} | |
| exit={{ opacity: 0, y: 5, scale: 0.95 }} | |
| transition={{ duration: 0.1 }} | |
| className="absolute top-8 left-0 w-56 glass rounded-lg shadow-2xl flex flex-col py-1.5 z-[60] text-sm border border-white/40 backdrop-blur-2xl bg-white/80" | |
| > | |
| <button | |
| onClick={() => { | |
| if (onAboutClick) onAboutClick() | |
| setActiveMenu(null) | |
| }} | |
| className="text-left px-4 py-1.5 hover:bg-blue-500 hover:text-white transition-colors text-gray-800 font-medium"> | |
| About Reuben OS | |
| </button> | |
| <div className="h-px bg-gray-300/50 my-1 mx-2" /> | |
| <button className="text-left px-4 py-1.5 hover:bg-blue-500 hover:text-white transition-colors text-gray-800"> | |
| System Settings... | |
| </button> | |
| <div className="h-px bg-gray-300/50 my-1 mx-2" /> | |
| <button | |
| onClick={() => { | |
| if (onSleep) onSleep() | |
| setActiveMenu(null) | |
| }} | |
| className="text-left px-4 py-1.5 hover:bg-blue-500 hover:text-white transition-colors text-gray-800" | |
| > | |
| Sleep | |
| </button> | |
| <button | |
| onClick={() => { | |
| if (onRestart) onRestart() | |
| setActiveMenu(null) | |
| }} | |
| className="text-left px-4 py-1.5 hover:bg-blue-500 hover:text-white transition-colors text-gray-800" | |
| > | |
| Restart... | |
| </button> | |
| <button | |
| onClick={() => { | |
| if (onShutdown) onShutdown() | |
| setActiveMenu(null) | |
| }} | |
| className="text-left px-4 py-1.5 hover:bg-blue-500 hover:text-white transition-colors text-gray-800" | |
| > | |
| Shut Down... | |
| </button> | |
| <div className="h-px bg-gray-300/50 my-1 mx-2" /> | |
| <button | |
| onClick={() => { | |
| setActiveMenu(null) | |
| }} | |
| className="text-left px-4 py-1.5 hover:bg-blue-500 hover:text-white transition-colors text-gray-800" | |
| > | |
| Log Out... | |
| </button> | |
| </motion.div> | |
| )} | |
| </AnimatePresence> | |
| </div> | |
| <span className="font-bold text-gray-800 hidden sm:inline text-xs sm:text-sm">Reuben OS</span> | |
| <span className="text-gray-600 text-[10px] sm:text-xs ml-1 sm:ml-2 font-medium hidden xs:inline">{activeAppName}</span> | |
| </div> | |
| <div className="flex items-center space-x-2 md:space-x-4" ref={menuRef}> | |
| <div className="flex items-center space-x-1 md:space-x-3 text-gray-700"> | |
| {/* Battery */} | |
| <div className="relative hidden sm:block"> | |
| <button | |
| onClick={() => toggleMenu('battery')} | |
| className={`hover:bg-black/5 rounded px-1 py-0.5 transition-colors ${activeMenu === 'battery' ? 'bg-black/10' : ''}`} | |
| > | |
| <BatteryFull size={18} weight="fill" /> | |
| </button> | |
| <AnimatePresence> | |
| {activeMenu === 'battery' && ( | |
| <motion.div | |
| initial={{ opacity: 0, y: 5, scale: 0.95 }} | |
| animate={{ opacity: 1, y: 0, scale: 1 }} | |
| exit={{ opacity: 0, y: 5, scale: 0.95 }} | |
| transition={{ duration: 0.1 }} | |
| className="absolute top-8 right-0 w-64 glass rounded-lg shadow-2xl flex flex-col py-2 z-[60] text-sm border border-white/40 backdrop-blur-2xl bg-white/80" | |
| > | |
| <div className="px-4 py-2 flex justify-between items-center border-b border-gray-200/50"> | |
| <span className="font-bold text-gray-700">Battery</span> | |
| <span className="text-gray-500">85%</span> | |
| </div> | |
| <div className="px-4 py-2 text-xs text-gray-500"> | |
| Power Source: Battery | |
| </div> | |
| <div className="px-4 py-2 text-xs text-gray-500"> | |
| Using Significant Energy: | |
| <div className="mt-1 flex items-center gap-2"> | |
| <div className="w-3 h-3 bg-gray-400 rounded-sm"></div> | |
| <span>Chrome</span> | |
| </div> | |
| </div> | |
| </motion.div> | |
| )} | |
| </AnimatePresence> | |
| </div> | |
| {/* WiFi */} | |
| <div className="relative hidden sm:block"> | |
| <button | |
| onClick={() => toggleMenu('wifi')} | |
| className={`hover:bg-black/5 rounded px-1 py-0.5 transition-colors ${activeMenu === 'wifi' ? 'bg-black/10' : ''}`} | |
| > | |
| <WifiHigh size={18} weight="bold" /> | |
| </button> | |
| <AnimatePresence> | |
| {activeMenu === 'wifi' && ( | |
| <motion.div | |
| initial={{ opacity: 0, y: 5, scale: 0.95 }} | |
| animate={{ opacity: 1, y: 0, scale: 1 }} | |
| exit={{ opacity: 0, y: 5, scale: 0.95 }} | |
| transition={{ duration: 0.1 }} | |
| className="absolute top-8 right-0 w-72 glass rounded-lg shadow-2xl flex flex-col py-2 z-[60] text-sm border border-white/40 backdrop-blur-2xl bg-white/80" | |
| > | |
| <div className="px-4 py-2 flex justify-between items-center border-b border-gray-200/50"> | |
| <span className="font-bold text-gray-700">Wi-Fi</span> | |
| <div className="w-8 h-4 bg-blue-500 rounded-full relative cursor-pointer"> | |
| <div className="absolute right-0.5 top-0.5 w-3 h-3 bg-white rounded-full shadow-sm"></div> | |
| </div> | |
| </div> | |
| <div className="py-1"> | |
| <div className="px-4 py-1.5 bg-blue-500/10 flex justify-between items-center"> | |
| <div className="flex items-center gap-2"> | |
| <Check size={14} weight="bold" className="text-blue-600" /> | |
| <span className="font-medium text-gray-800">Reuben's WiFi</span> | |
| </div> | |
| <WifiHigh size={14} weight="bold" className="text-gray-600" /> | |
| </div> | |
| <div className="h-px bg-gray-200/50 my-1 mx-4" /> | |
| <div className="px-4 py-1.5 text-gray-500 text-xs font-semibold">Personal Hotspots</div> | |
| <div className="px-4 py-1.5 hover:bg-blue-500 hover:text-white transition-colors flex justify-between items-center group cursor-pointer"> | |
| <span className="pl-6">iPhone 15 Pro</span> | |
| <WifiHigh size={14} weight="bold" className="text-gray-400 group-hover:text-white" /> | |
| </div> | |
| <div className="h-px bg-gray-200/50 my-1 mx-4" /> | |
| <div className="px-4 py-1.5 text-gray-500 text-xs font-semibold">Other Networks</div> | |
| <div className="px-4 py-1.5 hover:bg-blue-500 hover:text-white transition-colors flex justify-between items-center group cursor-pointer"> | |
| <span className="pl-6">Guest Network</span> | |
| <WifiHigh size={14} weight="bold" className="text-gray-400 group-hover:text-white" /> | |
| </div> | |
| <div className="px-4 py-1.5 hover:bg-blue-500 hover:text-white transition-colors flex justify-between items-center group cursor-pointer"> | |
| <span className="pl-6">Starbucks WiFi</span> | |
| <WifiHigh size={14} weight="bold" className="text-gray-400 group-hover:text-white" /> | |
| </div> | |
| </div> | |
| <div className="h-px bg-gray-200/50 my-1 mx-2" /> | |
| <button className="text-left px-4 py-1.5 hover:bg-blue-500 hover:text-white transition-colors text-gray-800"> | |
| Wi-Fi Settings... | |
| </button> | |
| </motion.div> | |
| )} | |
| </AnimatePresence> | |
| </div> | |
| <MagnifyingGlass size={18} weight="bold" className="hidden sm:block" /> | |
| </div> | |
| {/* Clock */} | |
| <div className="relative"> | |
| <button | |
| onClick={() => toggleMenu('clock')} | |
| className={`font-medium text-gray-800 min-w-[50px] sm:min-w-[60px] md:min-w-[140px] text-right hover:bg-black/5 rounded px-1 sm:px-2 py-0.5 transition-colors text-[11px] sm:text-xs ${activeMenu === 'clock' ? 'bg-black/10' : ''}`} | |
| > | |
| <span className="hidden md:inline mr-2">{dateDisplay}</span> | |
| <span>{timeDisplay}</span> | |
| </button> | |
| <AnimatePresence> | |
| {activeMenu === 'clock' && ( | |
| <motion.div | |
| initial={{ opacity: 0, y: 5, scale: 0.95 }} | |
| animate={{ opacity: 1, y: 0, scale: 1 }} | |
| exit={{ opacity: 0, y: 5, scale: 0.95 }} | |
| transition={{ duration: 0.1 }} | |
| className="absolute top-8 right-0 z-[60]" | |
| > | |
| <CalendarWidget /> | |
| </motion.div> | |
| )} | |
| </AnimatePresence> | |
| </div> | |
| </div> | |
| </div> | |
| ) | |
| } |