- add prototype upload, list, view, and delete api routes - add prototype manager ui with search, modal, and theme toggle - add filesystem-backed zip extraction and serving logic - switch project to pnpm with updated lockfile and tooling - add multi-stage dockerfile with standalone next.js output - add github actions to lint, build, and publish docker images
52 lines
1.3 KiB
TypeScript
52 lines
1.3 KiB
TypeScript
"use client";
|
|
|
|
import { useEffect, useState } from "react";
|
|
import { THEME_STORAGE_KEY } from "@/lib/prototype";
|
|
|
|
function getInitialTheme() {
|
|
if (typeof window === "undefined") {
|
|
return false;
|
|
}
|
|
|
|
const saved = localStorage.getItem(THEME_STORAGE_KEY);
|
|
|
|
if (saved === "true") {
|
|
return true;
|
|
}
|
|
|
|
if (saved === "false") {
|
|
return false;
|
|
}
|
|
|
|
return window.matchMedia("(prefers-color-scheme: dark)").matches;
|
|
}
|
|
|
|
function applyTheme(isDark: boolean) {
|
|
document.documentElement.classList.toggle("dark", isDark);
|
|
}
|
|
|
|
export function ThemeToggle() {
|
|
const [darkMode, setDarkMode] = useState(getInitialTheme);
|
|
|
|
useEffect(() => {
|
|
applyTheme(darkMode);
|
|
localStorage.setItem(THEME_STORAGE_KEY, String(darkMode));
|
|
}, [darkMode]);
|
|
|
|
const toggle = () => {
|
|
setDarkMode((prev) => !prev);
|
|
};
|
|
|
|
return (
|
|
<button
|
|
type="button"
|
|
onClick={toggle}
|
|
className="fixed right-8 bottom-8 z-50 flex h-12 w-12 items-center justify-center rounded-full border border-slate-200 bg-white text-xl shadow-md transition hover:-translate-y-0.5 dark:border-slate-700 dark:bg-slate-900"
|
|
aria-label={darkMode ? "切换到浅色模式" : "切换到深色模式"}
|
|
title={darkMode ? "浅色模式" : "深色模式"}
|
|
>
|
|
{darkMode ? "☀️" : "🌙"}
|
|
</button>
|
|
);
|
|
}
|