diff --git a/public/assets/ark-library/header-logo.svg b/public/assets/ark-library/header-logo.svg new file mode 100644 index 0000000..2282c23 --- /dev/null +++ b/public/assets/ark-library/header-logo.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/public/assets/ark-library/header-menu.svg b/public/assets/ark-library/header-menu.svg new file mode 100644 index 0000000..d8dca42 --- /dev/null +++ b/public/assets/ark-library/header-menu.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/assets/ark-library/header-search.svg b/public/assets/ark-library/header-search.svg new file mode 100644 index 0000000..43aaacc --- /dev/null +++ b/public/assets/ark-library/header-search.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/layouts/PublicLayout.tsx b/src/layouts/PublicLayout.tsx index 05d76d1..406e324 100644 --- a/src/layouts/PublicLayout.tsx +++ b/src/layouts/PublicLayout.tsx @@ -66,6 +66,11 @@ type LanguageDropdownProps = { className?: string; }; +function languageButtonLabel(lang: Lang): string { + if (lang === "zh-CN") return "中"; + return lang.toUpperCase(); +} + function LanguageDropdown({ lang, setLang, @@ -158,10 +163,102 @@ function LanguageDropdown({ ); } +type MobileLanguageButtonProps = { + lang: Lang; + setLang: (lang: Lang) => void; + ariaLabel: string; + onOpen?: () => void; +}; + +function MobileLanguageButton({ + lang, + setLang, + ariaLabel, + onOpen, +}: MobileLanguageButtonProps) { + const [open, setOpen] = useState(false); + const rootRef = useRef(null); + + useEffect(() => { + if (!open) return; + + const closeOnOutside = (event: MouseEvent | TouchEvent) => { + if (!rootRef.current?.contains(event.target as Node)) setOpen(false); + }; + const closeOnEscape = (event: KeyboardEvent) => { + if (event.key === "Escape") setOpen(false); + }; + + document.addEventListener("mousedown", closeOnOutside); + document.addEventListener("touchstart", closeOnOutside); + window.addEventListener("keydown", closeOnEscape); + return () => { + document.removeEventListener("mousedown", closeOnOutside); + document.removeEventListener("touchstart", closeOnOutside); + window.removeEventListener("keydown", closeOnEscape); + }; + }, [open]); + + return ( +
+ + + {open ? ( +
+ {LANG_OPTIONS.map((option) => { + const active = option.code === lang; + return ( + + ); + })} +
+ ) : null} +
+ ); +} + export function PublicLayout() { const { t, lang, setLang } = useI18n(); const { pathname, search, hash } = useLocation(); const [open, setOpen] = useState(false); + const [mobileSearchOpen, setMobileSearchOpen] = useState(false); const [q, setQ] = useState(""); const nav = useNavigate(); @@ -173,12 +270,102 @@ export function PublicLayout() { if (!s) return; nav(`/search?q=${encodeURIComponent(s)}`); setOpen(false); + setMobileSearchOpen(false); }; return (
-
-
+
+
+ + ARK LIBRARY + + +
+ + { + setOpen(false); + setMobileSearchOpen(false); + }} + /> + +
+
+ + {mobileSearchOpen ? ( +
+
+ + setQ(e.target.value)} + onKeyDown={(e) => e.key === "Enter" && goSearch()} + placeholder={t("searchPlaceholder")} + className="min-w-0 flex-1 bg-transparent text-sm text-neutral-100 outline-none placeholder:text-[#777985]" + /> +
+
+ ) : null} + +
{/* Single row (md+): logo | scrollable nav (左對齊,可橫向滑動) | 搜尋 + 語言 */}
-
+