"use client"; import { Check, ChevronDown, Search } from "lucide-react"; import Image from "next/image"; import { useRouter } from "next/navigation"; import { useLocale } from "next-intl"; import { useEffect, useLayoutEffect, useMemo, useRef, useState, useTransition, } from "react"; import { createPortal } from "react-dom"; import { setLocaleCookie } from "@/actions/set-locale"; const LOCALES: { code: string; label: string; lang: string }[] = [ { code: "nl", label: "Nederlands", lang: "nl" }, { code: "en", label: "English", lang: "en" }, { code: "de", label: "Deutsch", lang: "de" }, { code: "fr", label: "Français", lang: "fr" }, { code: "es", label: "Español", lang: "es" }, { code: "it", label: "Italiano", lang: "it" }, { code: "pt", label: "Português", lang: "pt" }, { code: "pl", label: "Polski", lang: "pl" }, { code: "sv", label: "Svenska", lang: "sv" }, { code: "tr", label: "Türkçe", lang: "tr" }, { code: "ro", label: "Română", lang: "ro" }, { code: "hu", label: "Magyar", lang: "hu" }, { code: "cs", label: "Čeština", lang: "cs" }, { code: "sk", label: "Slovenčina", lang: "sk" }, { code: "da", label: "Dansk", lang: "da" }, { code: "no", label: "Norsk", lang: "no" }, { code: "el", label: "Ελληνικά", lang: "el" }, { code: "bg", label: "Български", lang: "bg" }, { code: "hr", label: "Hrvatski", lang: "hr" }, { code: "sr", label: "Српски", lang: "sr" }, { code: "uk", label: "Українська", lang: "uk" }, { code: "ru", label: "Русский", lang: "ru" }, ]; const MENU_WIDTH = 224; // w-56 const MENU_GAP = 6; const VIEWPORT_PAD = 8; export function LanguageSwitcher({ variant = "nav", }: { variant?: "nav" | "admin"; }) { const locale = useLocale(); const router = useRouter(); const [pending, startTransition] = useTransition(); const [open, setOpen] = useState(false); const [search, setSearch] = useState(""); const [menuPos, setMenuPos] = useState<{ top: number; left: number } | null>( null, ); const [mounted, setMounted] = useState(false); const triggerRef = useRef(null); const menuRef = useRef(null); const searchRef = useRef(null); const current = LOCALES.find((l) => l.code === locale) ?? LOCALES[0]; const isAdmin = variant === "admin"; const filtered = useMemo(() => { if (!search) return LOCALES; const q = search.toLowerCase(); return LOCALES.filter( (l) => l.label.toLowerCase().includes(q) || l.code.toLowerCase().includes(q) || l.lang.toLowerCase().includes(q), ); }, [search]); useEffect(() => { setMounted(true); }, []); useLayoutEffect(() => { if (!open) { setMenuPos(null); return; } function place() { const trigger = triggerRef.current; if (!trigger) return; const rect = trigger.getBoundingClientRect(); const maxLeft = window.innerWidth - MENU_WIDTH - VIEWPORT_PAD; const left = Math.min( Math.max(VIEWPORT_PAD, rect.right - MENU_WIDTH), Math.max(VIEWPORT_PAD, maxLeft), ); setMenuPos({ top: rect.bottom + MENU_GAP, left }); } place(); window.addEventListener("resize", place); window.addEventListener("scroll", place, true); return () => { window.removeEventListener("resize", place); window.removeEventListener("scroll", place, true); }; }, [open]); useEffect(() => { if (!open) return; function handleClick(e: MouseEvent) { const t = e.target as Node; if (triggerRef.current?.contains(t) || menuRef.current?.contains(t)) return; setOpen(false); } function handleKey(e: KeyboardEvent) { if (e.key === "Escape") setOpen(false); } document.addEventListener("mousedown", handleClick); document.addEventListener("keydown", handleKey); return () => { document.removeEventListener("mousedown", handleClick); document.removeEventListener("keydown", handleKey); }; }, [open]); useEffect(() => { if (!open) { setSearch(""); return; } const id = window.setTimeout(() => searchRef.current?.focus(), 0); return () => window.clearTimeout(id); }, [open]); function switchLocale(code: string) { if (code === locale) { setOpen(false); return; } startTransition(async () => { await setLocaleCookie(code); router.refresh(); }); setOpen(false); } const menu = open && menuPos && mounted && (
{filtered.length === 0 && (

No languages found

)} {filtered.map((l) => { const selected = l.code === locale; return ( ); })}
); return (
{mounted && menu ? createPortal(menu, document.body) : null}
); }