"use client"; import React, { useEffect, useRef, useState } from "react"; import { useAuth } from "@/context/auth-context"; const SIGNIN_BADGE_URL = "https://huggingface.co/datasets/huggingface/badges/resolve/main/sign-in-with-huggingface-md-dark.svg"; // `badge` — the official HF brand badge. Use as a strong invitation when the // auth path is itself the page's headline action. // `ghost` — a quiet inline cyan link, sized to the surrounding body copy. // Use when auth is a secondary affordance next to a primary CTA // (e.g. the home page's search bar). // `tab` — uppercase tracked text styled to match a tab strip; pairs with // the episode viewer's tab bar so the auth control reads as part // of the same register. type Variant = "badge" | "ghost" | "tab"; // Slot height per variant. Matches the variant's rendered button so the // pre-config placeholder (when isAuthAvailable hasn't resolved yet) and the // signed-in/signed-out states all occupy exactly the same vertical space — // no layout shift on auth state changes. `tab` is taller because it lives // in the episode tab bar and needs to align with the `text-xs px-5 py-3` // tab buttons (~40px implicit height). const SLOT_HEIGHT: Record = { badge: "h-8", ghost: "h-7", tab: "h-10", }; interface HfAuthButtonProps { variant?: Variant; } export default function HfAuthButton({ variant = "badge" }: HfAuthButtonProps) { const { oauth, isAuthAvailable, signIn, signOut } = useAuth(); // Stable slot — auth state resolves async on mount (config fetch, then // localStorage rehydrate), so the rendered control changes from // null → signed-out → signed-in. Reserve the height so the surrounding // layout doesn't reflow each time. if (!isAuthAvailable) { return ( ); } if (oauth) { const name = oauth.userInfo?.preferred_username ?? oauth.userInfo?.name ?? "signed in"; const avatar = oauth.userInfo?.picture; return ( ); } if (variant === "ghost") { return ( ); } if (variant === "tab") { return ( ); } return ( ); } function SignedInMenu({ name, avatar, onSignOut, variant, }: { name: string; avatar?: string; onSignOut: () => void; variant: Variant; }) { const [open, setOpen] = useState(false); const wrapperRef = useRef(null); useEffect(() => { if (!open) return; const onDown = (e: MouseEvent) => { if ( wrapperRef.current && !wrapperRef.current.contains(e.target as Node) ) { setOpen(false); } }; const onKey = (e: KeyboardEvent) => { if (e.key === "Escape") setOpen(false); }; document.addEventListener("mousedown", onDown); document.addEventListener("keydown", onKey); return () => { document.removeEventListener("mousedown", onDown); document.removeEventListener("keydown", onKey); }; }, [open]); return (
{open && (
)}
); }