import { useEffect, useRef, useState } from 'react' import { Link, useNavigate } from 'react-router-dom' import { AnimatePresence, motion } from 'framer-motion' import { Film, LoaderCircle, Menu, Search, Star, X } from 'lucide-react' import { imageURL } from '../api/client' import { mediaAPI } from '../api/library' import type { Media, PlayProfile, User } from '../types' import { LayoutThemeToggle } from './LayoutThemeToggle' import { LayoutUserMenu } from './LayoutUserMenu' import type { useLayoutProfiles } from './useLayoutProfiles' import type { ThemeMode, useThemeMode } from './useThemeMode' type LayoutProfileState = ReturnType type LayoutThemeState = ReturnType type LayoutPermissionState = { can: (key: string) => boolean isAdmin: boolean } type LayoutHeaderProps = { permissions: LayoutPermissionState theme: LayoutThemeState onOpenMobileDrawer: () => void user: User | null | undefined activeProfileId: string | null profile: LayoutProfileState onLogout: () => void showSidebar?: boolean hideSearch?: boolean } export function LayoutHeader({ permissions, theme, onOpenMobileDrawer, user, activeProfileId, profile, onLogout, showSidebar, hideSearch, }: LayoutHeaderProps) { return (
{/* Left: Mobile Menu button or Brand Logo */}
MMTL MMTL
{/* Middle: Search Box */}
{!hideSearch && }
{/* Right: Actions (Theme Toggle & User Menu) */} profile.setIsProfileOpen((open) => !open)} onCloseProfile={() => profile.setIsProfileOpen(false)} onUseDefaultProfile={profile.useDefaultProfile} onSwitchProfile={profile.switchProfile} onLogout={onLogout} />
) } function LayoutHeaderSearch() { const [query, setQuery] = useState('') const [isOpen, setIsOpen] = useState(false) const [loading, setLoading] = useState(false) const [results, setResults] = useState([]) const containerRef = useRef(null) const navigate = useNavigate() useEffect(() => { const trimmed = query.trim() if (!trimmed) { setResults([]) setLoading(false) return } setLoading(true) const timer = setTimeout(async () => { try { const res = await mediaAPI.search(trimmed, 8) setResults(res.items || []) setIsOpen(true) } catch { setResults([]) } finally { setLoading(false) } }, 250) return () => clearTimeout(timer) }, [query]) useEffect(() => { function handleClickOutside(e: MouseEvent) { if (containerRef.current && !containerRef.current.contains(e.target as Node)) { setIsOpen(false) } } document.addEventListener('mousedown', handleClickOutside) return () => document.removeEventListener('mousedown', handleClickOutside) }, []) const handleSelect = (mediaId: string) => { setIsOpen(false) setQuery('') navigate(`/media/${mediaId}`) } const handleKeyDown = (e: React.KeyboardEvent) => { if (e.key === 'Escape') { setIsOpen(false) } else if (e.key === 'Enter' && results.length > 0) { handleSelect(results[0].id) } } return (
{ setQuery(e.target.value) if (e.target.value.trim()) setIsOpen(true) }} onFocus={() => { if (results.length > 0) setIsOpen(true) }} onKeyDown={handleKeyDown} placeholder="搜索媒体…" className="w-full h-9 sm:h-10 pl-8 sm:pl-10 pr-8 sm:pr-9 rounded-xl sm:rounded-2xl border border-[var(--app-border)] bg-[var(--app-panel)] text-xs sm:text-sm text-[var(--app-text)] placeholder:text-[var(--app-muted)] shadow-sm outline-none transition-all duration-200 focus:border-brand-500 focus:ring-2 focus:ring-brand-500/20 focus:bg-[var(--app-panel-elevated)]" /> {loading ? ( ) : query ? ( ) : null}
{/* Search Dropdown Results */} {isOpen && query.trim() && ( {results.length === 0 && !loading ? (
未搜索到与 “{query}” 相关的媒体内容
) : (
{results.map((item) => ( ))}
)}
)}
) } type LayoutHeaderActionsProps = { permissions: LayoutPermissionState themeMode: ThemeMode onThemeChange: (mode: ThemeMode) => void user: User | null | undefined isProfileOpen: boolean profiles: PlayProfile[] activeProfileId: string | null activeProfile: PlayProfile | null onToggleProfile: () => void onCloseProfile: () => void onUseDefaultProfile: () => void onSwitchProfile: (profile: PlayProfile) => void onLogout: () => void } function LayoutHeaderActions({ themeMode, onThemeChange, user, isProfileOpen, profiles, activeProfileId, activeProfile, onToggleProfile, onCloseProfile, onUseDefaultProfile, onSwitchProfile, onLogout, }: LayoutHeaderActionsProps) { return (
) }