forked from 77media/video-flow
210 lines
7.6 KiB
TypeScript
210 lines
7.6 KiB
TypeScript
"use client";
|
|
|
|
import '../pages/style/top-bar.css';
|
|
import { Button } from '@/components/ui/button';
|
|
import { GradientText } from '@/components/ui/gradient-text';
|
|
import { useTheme } from 'next-themes';
|
|
import {
|
|
Sun,
|
|
Moon,
|
|
User,
|
|
Sparkles,
|
|
LogOut,
|
|
Bell,
|
|
PanelsLeftBottom,
|
|
Library
|
|
} from 'lucide-react';
|
|
import { motion, AnimatePresence } from 'framer-motion';
|
|
import { useRouter } from 'next/navigation';
|
|
import React, { useRef, useEffect } from 'react';
|
|
|
|
export function TopBar({ collapsed, onToggleSidebar }: { collapsed: boolean; onToggleSidebar: () => void }) {
|
|
const { theme, setTheme } = useTheme();
|
|
const router = useRouter();
|
|
const [isOpen, setIsOpen] = React.useState(false);
|
|
const menuRef = useRef<HTMLDivElement>(null);
|
|
const buttonRef = useRef<HTMLButtonElement>(null);
|
|
const currentUser = localStorage.getItem('currentUser');
|
|
const [openModal, setOpenModal] = React.useState(false);
|
|
|
|
useEffect(() => {
|
|
const handleClickOutside = (event: MouseEvent) => {
|
|
if (
|
|
menuRef.current &&
|
|
!menuRef.current.contains(event.target as Node) &&
|
|
buttonRef.current &&
|
|
!buttonRef.current.contains(event.target as Node)
|
|
) {
|
|
setIsOpen(false);
|
|
}
|
|
};
|
|
|
|
document.addEventListener('mousedown', handleClickOutside);
|
|
return () => {
|
|
document.removeEventListener('mousedown', handleClickOutside);
|
|
};
|
|
}, []);
|
|
|
|
const handleAnimationEnd = (event: React.AnimationEvent<HTMLDivElement>) => {
|
|
const element = event.currentTarget;
|
|
element.classList.remove('on');
|
|
};
|
|
|
|
const handleMouseEnter = (event: React.MouseEvent<HTMLDivElement>) => {
|
|
const element = event.currentTarget;
|
|
element.classList.add('on');
|
|
};
|
|
|
|
return (
|
|
<div className="fixed right-0 top-0 left-0 h-16 header z-50">
|
|
<div className="h-full flex items-center justify-between pr-6 pl-2">
|
|
<div className="flex items-center space-x-4">
|
|
<Button className='button-NxtqWZ' variant="ghost" size="sm" onClick={onToggleSidebar}>
|
|
<PanelsLeftBottom className="h-4 w-4" />
|
|
</Button>
|
|
<div
|
|
className={`flex items-center cursor-pointer space-x-4 link-logo roll event-on`}
|
|
onClick={() => router.push('/')}
|
|
onMouseEnter={handleMouseEnter}
|
|
onAnimationEnd={handleAnimationEnd}
|
|
>
|
|
<span className="translate">
|
|
<span>
|
|
<h1 className="logo text-2xl font-bold">
|
|
<GradientText
|
|
text="MovieFlow"
|
|
startPercentage={30}
|
|
endPercentage={70}
|
|
/>
|
|
</h1>
|
|
</span>
|
|
<span>
|
|
<h1 className="logo text-2xl font-bold">
|
|
<GradientText
|
|
text="MovieFlow"
|
|
startPercentage={30}
|
|
endPercentage={70}
|
|
/>
|
|
</h1>
|
|
</span>
|
|
</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="flex items-center space-x-4">
|
|
{/* Pricing Link */}
|
|
<Button
|
|
variant="ghost"
|
|
size="sm"
|
|
onClick={() => router.push('/pricing')}
|
|
className="text-gray-300 hover:text-white"
|
|
>
|
|
Pricing
|
|
</Button>
|
|
|
|
{/* Notifications */}
|
|
{/* <Button variant="ghost" size="sm" onClick={() => setOpenModal(true)}>
|
|
<Bell className="h-4 w-4" />
|
|
</Button> */}
|
|
|
|
{/* Theme Toggle */}
|
|
{/* <Button
|
|
variant="ghost"
|
|
size="sm"
|
|
onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')}
|
|
>
|
|
<Sun className="h-4 w-4 rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
|
|
<Moon className="absolute h-4 w-4 rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
|
|
</Button> */}
|
|
|
|
{/* User Menu */}
|
|
<div className="relative">
|
|
<Button
|
|
ref={buttonRef}
|
|
variant="ghost"
|
|
size="sm"
|
|
onClick={() => setIsOpen(!isOpen)}
|
|
data-alt="user-menu-trigger"
|
|
>
|
|
<User className="h-4 w-4" />
|
|
</Button>
|
|
|
|
<AnimatePresence>
|
|
{isOpen && (
|
|
<motion.div
|
|
ref={menuRef}
|
|
initial={{ opacity: 0, scale: 0.95, y: -20 }}
|
|
animate={{ opacity: 1, scale: 1, y: 0 }}
|
|
exit={{ opacity: 0, scale: 0.95, y: -20 }}
|
|
transition={{ duration: 0.2 }}
|
|
className="absolute right-0 mt-2 w-56 bg-[#1E1E1E] rounded-lg shadow-lg overflow-hidden z-50"
|
|
data-alt="user-menu-dropdown"
|
|
>
|
|
{/* User Info */}
|
|
<div className="p-4">
|
|
<div className="flex items-center space-x-3">
|
|
<div className="h-10 w-10 rounded-full bg-[#1E4D3E] flex items-center justify-center text-white font-semibold">
|
|
A
|
|
</div>
|
|
<div>
|
|
<p className="text-sm font-medium">admin-live</p>
|
|
<p className="text-xs text-gray-500">admin-live.com</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* AI Points */}
|
|
<div className="px-4 py-3 flex items-center justify-between">
|
|
<div className="flex items-center space-x-2">
|
|
<Sparkles className="h-4 w-4" />
|
|
<span className="text-white underline text-sm">100 点数</span>
|
|
</div>
|
|
<Button
|
|
variant="outline"
|
|
size="sm"
|
|
className="text-white border-white hover:bg-white/10 rounded-full px-8"
|
|
>
|
|
升级
|
|
</Button>
|
|
</div>
|
|
|
|
{/* Menu Items */}
|
|
<div className="p-2">
|
|
<motion.button
|
|
whileHover={{ backgroundColor: 'rgba(255,255,255,0.1)' }}
|
|
className="w-full flex items-center space-x-2 px-3 py-2 rounded-md text-sm text-white"
|
|
onClick={() => router.push('/my-library')}
|
|
data-alt="my-library-button"
|
|
>
|
|
<Library className="h-4 w-4" />
|
|
<span>我的库</span>
|
|
</motion.button>
|
|
|
|
<motion.button
|
|
whileHover={{ backgroundColor: 'rgba(255,255,255,0.1)' }}
|
|
className="w-full flex items-center space-x-2 px-3 py-2 rounded-md text-sm text-white"
|
|
onClick={() => {
|
|
// 处理退出登录
|
|
setIsOpen(false);
|
|
}}
|
|
data-alt="logout-button"
|
|
>
|
|
<LogOut className="h-4 w-4" />
|
|
<span>退出账号</span>
|
|
</motion.button>
|
|
|
|
{/* Footer */}
|
|
<div className="mt-4 px-3 py-2 text-xs text-gray-400 text-center">
|
|
<div>隐私权和条款 · 许可</div>
|
|
<div>250819215404 | 2025/8/20 06:00:50</div>
|
|
</div>
|
|
</div>
|
|
</motion.div>
|
|
)}
|
|
</AnimatePresence>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
} |