"use client"; import { ArrowLeft, Clock, Link2, Menu, Search, X } from "lucide-react"; import { useEffect, useRef, useState } from "react"; import { Logo } from "@/components/ui"; import AccountMenu from "@/components/layout/AccountMenu"; import { isMusicUrl } from "@/lib/url"; import { RECENT_EVENT, RECENT_MAX, readRecent, writeRecent } from "@/lib/recent"; /** 다른 화면(서버 홈의 "노래 검색" 버튼 등)에서 검색창을 열 때 쓰는 이벤트 이름 */ export const FOCUS_SEARCH_EVENT = "musicbot:focus-search"; interface TopNavProps { /** 지금 화면에 걸린 검색어(뒤로가기 등으로 바뀌면 입력칸도 맞춘다) */ query: string; onSearch: (query: string) => void; onHome: () => void; onToggleGuide: () => void; } /** * 유튜브 뮤직 상단 바: [☰ 로고] [검색창] ......... [프로필] * - 검색창을 누르면 최근 검색어가 펼쳐진다(유튜브 뮤직과 같은 방식). * - 좁은 화면은 돋보기 버튼 → 바 전체가 검색창으로 바뀐다. */ export default function TopNav({ query, onSearch, onHome, onToggleGuide }: TopNavProps) { const [term, setTerm] = useState(query); const [mobileSearch, setMobileSearch] = useState(false); const [focused, setFocused] = useState(false); const [recent, setRecent] = useState([]); const inputRef = useRef(null); const boxRef = useRef(null); // 화면의 검색어가 바뀌면(뒤로가기·홈) 입력칸도 맞춘다. useEffect(() => { setTerm(query); }, [query]); useEffect(() => { const sync = () => setRecent(readRecent()); sync(); window.addEventListener(RECENT_EVENT, sync); return () => window.removeEventListener(RECENT_EVENT, sync); }, []); // 서버 홈의 "노래 검색" 버튼 → 검색창 포커스(좁은 화면이면 검색 모드로 전환) useEffect(() => { const focus = () => { setMobileSearch(true); requestAnimationFrame(() => inputRef.current?.focus()); }; window.addEventListener(FOCUS_SEARCH_EVENT, focus); return () => window.removeEventListener(FOCUS_SEARCH_EVENT, focus); }, []); // 검색창 바깥을 누르면 최근 검색어 목록을 닫는다. useEffect(() => { if (!focused) return; const onDown = (e: MouseEvent) => { if (!boxRef.current?.contains(e.target as Node)) setFocused(false); }; document.addEventListener("mousedown", onDown); return () => document.removeEventListener("mousedown", onDown); }, [focused]); const run = (value: string) => { const q = value.trim(); if (!q) return; setTerm(q); // 주소는 최근 검색어에 남기지 않는다(길고 다시 쓸 일이 적다). if (!isMusicUrl(q)) { const next = [q, ...recent.filter((r) => r !== q)].slice(0, RECENT_MAX); setRecent(next); writeRecent(next); } onSearch(q); setFocused(false); inputRef.current?.blur(); setMobileSearch(false); }; const removeRecent = (r: string) => { const next = recent.filter((x) => x !== r); setRecent(next); writeRecent(next); }; const showDropdown = focused && (recent.length > 0 || isMusicUrl(term)); return (
); }