| 43 | |
| 44 | /** Dropdown button for the Panel header — renders channel list inline */ |
| 45 | export function LiveChannelDropdown({ |
| 46 | activeStream, |
| 47 | onSelect, |
| 48 | }: { |
| 49 | activeStream: StreamSource | null; |
| 50 | onSelect: (stream: StreamSource | null) => void; |
| 51 | }) { |
| 52 | const { t } = useI18n(); |
| 53 | const [open, setOpen] = useState(false); |
| 54 | const [tab, setTab] = useState<CategoryTab>("news"); |
| 55 | const ref = useRef<HTMLDivElement>(null); |
| 56 | |
| 57 | const filteredStreams = useMemo( |
| 58 | () => STREAMS.filter((s) => s.category === tab), |
| 59 | [tab], |
| 60 | ); |
| 61 | |
| 62 | useEffect(() => { |
| 63 | if (!open) return; |
| 64 | const handler = (e: MouseEvent) => { |
| 65 | if (ref.current && !ref.current.contains(e.target as Node)) |
| 66 | setOpen(false); |
| 67 | }; |
| 68 | document.addEventListener("mousedown", handler); |
| 69 | return () => document.removeEventListener("mousedown", handler); |
| 70 | }, [open]); |
| 71 | |
| 72 | return ( |
| 73 | <div ref={ref} className="relative font-mono"> |
| 74 | <button |
| 75 | onClick={() => setOpen((o) => !o)} |
| 76 | className={`flex items-center gap-1.5 text-[10px] px-1.5 py-0.5 border transition-colors ${ |
| 77 | open || activeStream |
| 78 | ? "border-[var(--text-secondary)]/30 text-[var(--text-secondary)]" |
| 79 | : "border-[var(--border-subtle)] text-[var(--text-faint)] hover:text-[var(--text-secondary)]" |
| 80 | }`} |
| 81 | > |
| 82 | {activeStream ? ( |
| 83 | <> |
| 84 | <span className="w-1 h-1 rounded-full bg-[#22c55e] animate-pulse shrink-0" /> |
| 85 | <span className="max-w-[90px] truncate">{activeStream.name}</span> |
| 86 | </> |
| 87 | ) : ( |
| 88 | <>{t("livePanel.channelMenu")}</> |
| 89 | )} |
| 90 | </button> |
| 91 | |
| 92 | {open && ( |
| 93 | <div className="absolute right-0 top-full mt-0.5 z-[9999] bg-[var(--bg)] border border-[var(--border)] shadow-[0_8px_32px_rgba(0,0,0,0.4)] min-w-[180px]"> |
| 94 | {/* Category tabs */} |
| 95 | <div className="flex border-b border-[var(--border-subtle)]"> |
| 96 | {(["news", "sports"] as const).map((tabKey) => ( |
| 97 | <button |
| 98 | key={tabKey} |
| 99 | onClick={() => setTab(tabKey)} |
| 100 | className={`flex-1 text-[10px] uppercase tracking-wide px-2 py-1.5 font-mono transition-colors ${ |
| 101 | tab === tabKey |
| 102 | ? "text-[var(--text-secondary)] bg-[var(--surface)]" |