mirror of
https://github.com/veeso/termscp.git
synced 2026-06-13 03:59:37 +02:00
feat(site): nav, footer, theme toggle, language picker
This commit is contained in:
17
site/src/components/Footer.astro
Normal file
17
site/src/components/Footer.astro
Normal file
@@ -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>
|
||||
25
site/src/components/LangPicker.astro
Normal file
25
site/src/components/LangPicker.astro
Normal file
@@ -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>
|
||||
28
site/src/components/Nav.astro
Normal file
28
site/src/components/Nav.astro
Normal file
@@ -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>
|
||||
32
site/src/components/ThemeToggle.astro
Normal file
32
site/src/components/ThemeToggle.astro
Normal file
@@ -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