Unverified Commit 7b458daf authored by Lemon-miaow's avatar Lemon-miaow
Browse files

feat(panel): light/dark theme

parent dd2fc6fa
Loading
Loading
Loading
Loading
+12 −1
Changes for panel/index.html: 12 added lines, 1 removed line.
Original line number Diff line number Diff line
<!doctype html>
<html lang="en" class="dark">
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Felis · Control Panel</title>
    <script>
      try {
        const theme = localStorage.getItem("felis-theme") || "system";
        const dark = theme === "dark" || (theme === "system" && window.matchMedia("(prefers-color-scheme: dark)").matches);
        if (dark) {
          document.documentElement.classList.add("dark");
        } else {
          document.documentElement.classList.remove("dark");
        }
      } catch (_) {}
    </script>
  </head>
  <body>
    <div id="root"></div>
+3 −0
Changes for panel/src/App.tsx: 3 added lines, 0 removed lines.
Original line number Diff line number Diff line
import { BrowserRouter, Routes, Route, Navigate } from "react-router-dom";
import { ThemeProvider } from "@/lib/theme";
import { TierProvider } from "@/lib/tier";
import { AppShell } from "@/components/AppShell";
import { RequireAdmin } from "@/components/RequireAdmin";
@@ -23,6 +24,7 @@ import { FleetTable } from "@/pages/ops/FleetTable";
// the split is by concern, not by tier. TierProvider fetches /me once at boot.
export default function App() {
  return (
    <ThemeProvider storageKey="felis-theme">
      <TierProvider>
        <BrowserRouter>
        <Routes>
@@ -62,5 +64,6 @@ export default function App() {
        </Routes>
      </BrowserRouter>
    </TierProvider>
  </ThemeProvider>
  );
}
+34 −4
Changes for panel/src/components/AppShell.tsx: 34 added lines, 4 removed lines.
Original line number Diff line number Diff line
import { useEffect } from "react";
import { NavLink, Outlet } from "react-router-dom";
import { Cat, Globe } from "lucide-react";
import { Cat, Globe, Sun, Moon } from "lucide-react";
import { useTranslation } from "react-i18next";
import * as SelectPrimitive from "@radix-ui/react-select";
import { cn } from "@/lib/utils";
import { useTier } from "@/lib/tier";
import { visibleSections } from "@/lib/nav";
import { Select, SelectContent, SelectItem } from "@/components/ui/select";
import { useTheme } from "@/lib/theme";

function LangToggle() {
  const { i18n } = useTranslation();

  return (
    <Select value={i18n.language} onValueChange={(v) => i18n.changeLanguage(v)}>
      <SelectPrimitive.Trigger className="h-7 w-7 p-0 flex items-center justify-center border border-border rounded-md bg-zinc-900/50 hover:bg-accent text-muted-foreground hover:text-foreground focus:outline-none transition-colors shrink-0">
      <SelectPrimitive.Trigger className="h-7 w-7 p-0 flex items-center justify-center border border-border rounded-md bg-muted/40 hover:bg-accent text-muted-foreground hover:text-foreground focus:outline-none transition-colors shrink-0">
        <Globe className="h-4 w-4" />
      </SelectPrimitive.Trigger>
      <SelectContent align="start" className="min-w-[6rem]">
@@ -24,6 +25,26 @@ function LangToggle() {
  );
}

function ThemeToggle() {
  const { theme, toggleTheme } = useTheme();
  const { t } = useTranslation("common");

  return (
    <button
      onClick={toggleTheme}
      className="h-7 w-7 p-0 flex items-center justify-center border border-border rounded-md bg-muted/40 hover:bg-accent text-muted-foreground hover:text-foreground focus:outline-none transition-all duration-200 active:scale-95 shrink-0"
      aria-label={t("toggle_theme")}
      title={t("toggle_theme")}
    >
      {theme === "light" ? (
        <Sun className="h-4 w-4" />
      ) : (
        <Moon className="h-4 w-4" />
      )}
    </button>
  );
}

export function AppShell() {
  const { isAdmin } = useTier();
  const { t, i18n } = useTranslation("navigation");
@@ -77,8 +98,11 @@ export function AppShell() {
        </nav>

        <div className="mt-auto px-3 pt-4 border-t border-border/60">
          <div className="flex items-start gap-2.5">
          <div className="flex items-start gap-2">
            <div className="flex items-center gap-1.5 shrink-0">
              <LangToggle />
              <ThemeToggle />
            </div>
            <span className="text-[10px] text-muted-foreground/60 leading-tight pt-1">
              {t("common:brand_tagline")}
            </span>
@@ -87,9 +111,15 @@ export function AppShell() {
      </aside>

      <div className="flex min-w-0 flex-1 flex-col">
        <header className="flex h-14 items-center gap-2 border-b border-border px-4 md:hidden">
        <header className="flex h-14 items-center justify-between border-b border-border px-4 md:hidden">
          <div className="flex items-center gap-2">
            <Cat className="h-5 w-5 text-primary" />
            <span className="font-semibold">{t("common:brand_name")}</span>
          </div>
          <div className="flex items-center gap-2">
            <LangToggle />
            <ThemeToggle />
          </div>
        </header>
        <main className="flex-1 p-6">
          <Outlet />
+2 −1
Changes for panel/src/i18n/resources/en-US/common.json: 2 added lines, 1 removed line.
Original line number Diff line number Diff line
@@ -18,5 +18,6 @@
  "page_title": "Felis · Control Panel",
  "lang_en": "EN",
  "lang_zh": "中文",
  "lang_toggle_hint": "Switch to {{lang}}"
  "lang_toggle_hint": "Switch to {{lang}}",
  "toggle_theme": "Toggle theme"
}
+2 −1
Changes for panel/src/i18n/resources/zh-CN/common.json: 2 added lines, 1 removed line.
Original line number Diff line number Diff line
@@ -18,5 +18,6 @@
  "page_title": "Felis · 控制面板",
  "lang_en": "EN",
  "lang_zh": "中文",
  "lang_toggle_hint": "切换至 {{lang}}"
  "lang_toggle_hint": "切换至 {{lang}}",
  "toggle_theme": "切换主题"
}
Loading