import { SearchIcon, SearchClearIcon } from './SvgIcons' import { useState, useRef, useEffect, ChangeEvent } from 'react' import { useJellyfinContext } from '../context/JellyfinContext/JellyfinContext' import { MediaItem } from '../api/jellyfin' import { BaseItemKind } from '@jellyfin/sdk/lib/generated-client' import { useNavigate } from 'react-router-dom' import { AlbumIcon, ArtistsIcon, TrackIcon } from './SvgIcons' import { BookmarkFillIcon } from '@primer/octicons-react' export const HeaderSearchBar = () => { const api = useJellyfinContext() const searchInputRef = useRef(null) const navigate = useNavigate() const [isExpanded, setIsExpanded] = useState(false) const [searchQuery, setSearchQuery] = useState('') const [searchResults, setSearchResults] = useState([]) const [searchLoading, setSearchLoading] = useState(false) useEffect(() => { const debounceTimer = setTimeout(() => { const fetchSearchResults = async () => { if (!searchQuery || !api.auth.serverUrl || !api.auth.token || !api.auth.userId) { setSearchResults([]) return } setSearchLoading(true) try { // Fetch artists from /Artists endpoint const [artistResponse, itemsResponse] = await Promise.all([ api.searchArtists(searchQuery, 5), api.searchItems(searchQuery, 10), ]) // Combine results const artists = artistResponse const items = itemsResponse // Combine and limit results const combinedResults = [...artists, ...items].slice(0, 10) setSearchResults(combinedResults) } catch (err) { console.error('Search error:', err) setSearchResults([]) } finally { setSearchLoading(false) } } fetchSearchResults() }, 300) return () => clearTimeout(debounceTimer) }, [searchQuery, api]) const handleInputChange = (e: ChangeEvent) => { const value = e.target.value setSearchQuery(value) setIsExpanded(value.length > 0) } const handleClear = () => { setSearchQuery('') setIsExpanded(false) if (searchInputRef.current) { searchInputRef.current.focus() } } const handleSearchIconClick = () => { setIsExpanded(true) if (searchInputRef.current) { searchInputRef.current.focus() } } const handleResultClick = (item: MediaItem) => { setSearchQuery('') setIsExpanded(false) // Navigate to the appropriate page based on item type if (item.Type === BaseItemKind.Audio) { navigate(`/search/${encodeURIComponent(searchQuery)}`) } else if (item.Type === BaseItemKind.MusicAlbum) { navigate(`/album/${item.Id}`) } else if (item.Type === BaseItemKind.MusicArtist) { navigate(`/artist/${item.Id}`) } else { navigate(`/search/${encodeURIComponent(searchQuery)}`) } } const handleSearchSubmit = (e: React.FormEvent) => { e.preventDefault() if (searchQuery.trim()) { setIsExpanded(false) navigate(`/search/${encodeURIComponent(searchQuery)}`) } } return (
setIsExpanded(true)} onBlur={() => { // Delay collapse to allow for click events on search results setTimeout(() => { if (!searchQuery) { setIsExpanded(false) } }, 150) }} placeholder="Search music..." className="search-input" /> {searchQuery && (
)} {searchLoading && (
)}
{isExpanded && searchResults.length > 0 && (
{searchResults.map((item, index) => { const isArtist = item.Type === BaseItemKind.MusicArtist const isAlbum = item.Type === BaseItemKind.MusicAlbum const isTrack = item.Type === BaseItemKind.Audio return (
handleResultClick(item)} >
{isArtist && } {isAlbum && } {isTrack && } {!isArtist && !isAlbum && !isTrack && ( )}
{item.Name}
{item.AlbumArtist && (
{item.AlbumArtist}
)} {item.ProductionYear && (
{item.ProductionYear}
)}
) })}
)}
) }