mirror of
https://github.com/veeso/termscp.git
synced 2026-09-25 13:31:26 -07:00
feat(site): nav, footer, theme toggle, language picker
This commit is contained in:
@@ -0,0 +1,17 @@
|
|||||||
|
---
|
||||||
|
import { useTranslations, type Locale } from "../i18n/ui";
|
||||||
|
interface Props { locale: Locale; }
|
||||||
|
const { locale } = Astro.props;
|
||||||
|
const t = useTranslations(locale);
|
||||||
|
const year = new Date().getFullYear();
|
||||||
|
---
|
||||||
|
<footer class="mt-auto border-t border-line bg-mantle">
|
||||||
|
<div class="mx-auto flex max-w-5xl flex-col items-center gap-2 px-4 py-8 text-sm text-overlay">
|
||||||
|
<div class="flex gap-5">
|
||||||
|
<a href="https://github.com/veeso/termscp" class="hover:text-text">GitHub</a>
|
||||||
|
<a href="https://crates.io/crates/termscp" class="hover:text-text">crates.io</a>
|
||||||
|
<a href="https://ko-fi.com/veeso" class="hover:text-text">{t("footer.support")}</a>
|
||||||
|
</div>
|
||||||
|
<p>© {year} Christian Visintin · {t("footer.rights")}</p>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
---
|
||||||
|
import { locales, defaultLocale, type Locale } from "../i18n/ui";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
locale: Locale;
|
||||||
|
path: string; // unprefixed path, e.g. "/install" or "/"
|
||||||
|
}
|
||||||
|
const { locale, path } = Astro.props;
|
||||||
|
const labels: Record<Locale, string> = {
|
||||||
|
en: "EN",
|
||||||
|
"zh-CN": "中文",
|
||||||
|
it: "IT",
|
||||||
|
fr: "FR",
|
||||||
|
es: "ES",
|
||||||
|
};
|
||||||
|
const href = (l: Locale) => (l === defaultLocale ? path : `/${l}${path}`);
|
||||||
|
---
|
||||||
|
<div class="flex items-center gap-2 text-sm">
|
||||||
|
{locales.map((l) => (
|
||||||
|
<a
|
||||||
|
href={href(l)}
|
||||||
|
class={l === locale ? "text-green" : "text-overlay hover:text-text"}
|
||||||
|
>{labels[l]}</a>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
---
|
||||||
|
import { defaultLocale, useTranslations, type Locale } from "../i18n/ui";
|
||||||
|
import ThemeToggle from "./ThemeToggle.astro";
|
||||||
|
import LangPicker from "./LangPicker.astro";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
locale: Locale;
|
||||||
|
path: string;
|
||||||
|
}
|
||||||
|
const { locale, path } = Astro.props;
|
||||||
|
const t = useTranslations(locale);
|
||||||
|
const p = (sub: string) => (locale === defaultLocale ? sub : `/${locale}${sub}`);
|
||||||
|
---
|
||||||
|
<header class="sticky top-0 z-50 border-b border-line bg-mantle/90 backdrop-blur">
|
||||||
|
<nav class="mx-auto flex max-w-5xl items-center justify-between px-4 py-3">
|
||||||
|
<a href={p("/")} class="flex items-center gap-2 text-text">
|
||||||
|
<img src="/assets/images/termscp.webp" alt="termscp" class="h-7 w-7" />
|
||||||
|
<span class="font-bold">termscp</span>
|
||||||
|
</a>
|
||||||
|
<div class="flex items-center gap-5 text-sm">
|
||||||
|
<a href={p("/install")} class="text-overlay hover:text-text">{t("nav.install")}</a>
|
||||||
|
<a href={p("/user-manual")} class="text-overlay hover:text-text">{t("nav.manual")}</a>
|
||||||
|
<a href="https://github.com/veeso/termscp" class="text-overlay hover:text-text">{t("nav.github")}</a>
|
||||||
|
<LangPicker locale={locale} path={path} />
|
||||||
|
<ThemeToggle />
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
</header>
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
---
|
||||||
|
// Tiny client island: flips data-theme + persists. Contract: writes "frappe"|"latte".
|
||||||
|
---
|
||||||
|
<button
|
||||||
|
id="theme-toggle"
|
||||||
|
type="button"
|
||||||
|
aria-label="Toggle theme"
|
||||||
|
class="rounded p-2 text-text hover:bg-mantle"
|
||||||
|
>
|
||||||
|
<span data-icon="frappe">☾</span>
|
||||||
|
<span data-icon="latte" hidden>☀</span>
|
||||||
|
</button>
|
||||||
|
<script is:inline>
|
||||||
|
(() => {
|
||||||
|
const btn = document.getElementById("theme-toggle");
|
||||||
|
const sync = () => {
|
||||||
|
const t = document.documentElement.getAttribute("data-theme");
|
||||||
|
btn.querySelector('[data-icon="frappe"]').hidden = t !== "frappe";
|
||||||
|
btn.querySelector('[data-icon="latte"]').hidden = t !== "latte";
|
||||||
|
};
|
||||||
|
btn.addEventListener("click", () => {
|
||||||
|
const next =
|
||||||
|
document.documentElement.getAttribute("data-theme") === "frappe"
|
||||||
|
? "latte"
|
||||||
|
: "frappe";
|
||||||
|
document.documentElement.setAttribute("data-theme", next);
|
||||||
|
localStorage.setItem("theme", next);
|
||||||
|
sync();
|
||||||
|
});
|
||||||
|
sync();
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
Reference in New Issue
Block a user