Files
neo-blog/web/src/contexts/DeviceContext.tsx
Snowykami 9ca307f4d9 ️ feat: add main page layout with navigation and footer
feat: create random labels page

feat: implement login page with OpenID Connect support

feat: add Gravatar component for user avatars

feat: create Navbar component with navigation menu

chore: create Sidebar component placeholder

feat: implement login form with OIDC and email/password options

feat: add reusable button component

feat: create card component for structured content display

feat: implement input component for forms

feat: create label component for form labels

feat: add navigation menu component for site navigation

chore: add configuration file for site metadata

feat: implement device context for responsive design

feat: add utility functions for class name management

feat: define OIDC configuration model

feat: define base response model for API responses

feat: define user model for user data

feat: implement i18n for internationalization support

feat: add English and Chinese translations for login

chore: create index for locale resources

chore: add blog home view placeholder
2025-07-24 09:22:50 +08:00

143 lines
4.2 KiB
TypeScript

"use client";
import React, { createContext, useCallback, useContext, useEffect, useState } from "react";
import i18n, { getDefaultLang } from "@/utils/i18n";
type Mode = "light" | "dark";
type Lang = string;
interface DeviceContextProps {
isMobile: boolean;
mode: Mode;
setMode: (mode: Mode) => void;
toggleMode: () => void;
lang: Lang;
setLang: (lang: Lang) => void;
viewport: {
width: number;
height: number;
};
}
const DeviceContext = createContext<DeviceContextProps>({
isMobile: false,
mode: "light",
setMode: () => {},
toggleMode: () => {},
lang: "zh-cn",
setLang: () => {},
viewport: {
width: 0,
height: 0,
},
});
export const DeviceProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
const [isMobile, setIsMobile] = useState(false);
const [mode, setModeState] = useState<Mode>("light");
const [lang, setLangState] = useState<Lang>(getDefaultLang());
const [viewport, setViewport] = useState({
width: typeof window !== "undefined" ? window.innerWidth : 0,
height: typeof window !== "undefined" ? window.innerHeight : 0,
});
// 检查系统主题
const getSystemTheme = () =>
typeof window !== "undefined" && window.matchMedia("(prefers-color-scheme: dark)").matches
? "dark"
: "light";
useEffect(() => {
const checkMobile = () => setIsMobile(window.innerWidth <= 768);
checkMobile();
window.addEventListener("resize", checkMobile);
return () => window.removeEventListener("resize", checkMobile);
}, []);
useEffect(() => {
// 更新检测函数以同时更新视窗尺寸
const handleResize = () => {
const width = window.innerWidth;
const height = window.innerHeight;
setIsMobile(width <= 768);
setViewport({ width, height });
};
handleResize(); // 初始化
window.addEventListener("resize", handleResize);
return () => window.removeEventListener("resize", handleResize);
}, []);
// 初始化主题和系统主题变化监听
useEffect(() => {
if (typeof window !== "undefined") {
const savedTheme = localStorage.getItem("theme") as Mode | null;
const systemTheme = getSystemTheme();
const theme = savedTheme || systemTheme;
setModeState(theme);
document.documentElement.classList.toggle("dark", theme === "dark");
// 监听系统主题变动
const media = window.matchMedia("(prefers-color-scheme: dark)");
const handleChange = (e: MediaQueryListEvent) => {
if (!localStorage.getItem("theme")) {
const newTheme = e.matches ? "dark" : "light";
setModeState(newTheme);
document.documentElement.classList.toggle("dark", newTheme === "dark");
}
};
media.addEventListener("change", handleChange);
return () => media.removeEventListener("change", handleChange);
}
}, []);
// 初始化语言
useEffect(() => {
if (typeof window !== "undefined") {
const savedLang = localStorage.getItem("language") || getDefaultLang();
setLangState(savedLang);
i18n.changeLanguage(savedLang);
}
}, []);
const setMode = useCallback((newMode: Mode) => {
setModeState(newMode);
document.documentElement.classList.toggle("dark", newMode === "dark");
if (newMode === getSystemTheme()) {
localStorage.removeItem("theme");
} else {
localStorage.setItem("theme", newMode);
}
}, []);
const toggleMode = useCallback(() => {
setModeState((prev) => {
const newMode = prev === "dark" ? "light" : "dark";
document.documentElement.classList.toggle("dark", newMode === "dark");
if (newMode === getSystemTheme()) {
localStorage.removeItem("theme");
} else {
localStorage.setItem("theme", newMode);
}
return newMode;
});
}, []);
const setLang = useCallback((newLang: Lang) => {
setLangState(newLang);
i18n.changeLanguage(newLang);
localStorage.setItem("language", newLang);
}, []);
return (
<DeviceContext.Provider
value={{ isMobile, mode, setMode, toggleMode, lang, setLang, viewport }}
>
{children}
</DeviceContext.Provider>
);
};
export const useDevice = () => useContext(DeviceContext);