OrangeTV/src/components/ThemeToggle.tsx

90 lines
2.4 KiB
TypeScript

/* eslint-disable @typescript-eslint/no-explicit-any */
'use client';
import { Moon, Sun } from 'lucide-react';
import { usePathname } from '@/client/router';
import { useTheme } from '@/client/theme-provider';
import { useEffect, useState } from 'react';
import { AppIconButton } from './ui/HeroPrimitives';
export function ThemeToggle() {
const [mounted, setMounted] = useState(false);
const [isMobile, setIsMobile] = useState(false);
const { setTheme, resolvedTheme } = useTheme();
const pathname = usePathname();
const setThemeColor = (theme?: string) => {
const meta = document.querySelector('meta[name="theme-color"]');
if (!meta) {
const meta = document.createElement('meta');
meta.name = 'theme-color';
meta.content = theme === 'dark' ? '#080707' : '#151212';
document.head.appendChild(meta);
} else {
meta.setAttribute('content', theme === 'dark' ? '#080707' : '#151212');
}
};
useEffect(() => {
setMounted(true);
const checkMobile = () => {
setIsMobile(window.innerWidth < 768);
};
checkMobile();
window.addEventListener('resize', checkMobile);
return () => {
window.removeEventListener('resize', checkMobile);
};
}, []);
// 监听主题变化和路由变化,确保主题色始终同步
useEffect(() => {
if (mounted) {
setThemeColor(resolvedTheme);
}
}, [mounted, resolvedTheme, pathname]);
if (!mounted) {
// 渲染一个占位符以避免布局偏移
return <div className='w-10 h-10' />;
}
const toggleTheme = () => {
// 检查浏览器是否支持 View Transitions API
const targetTheme = resolvedTheme === 'dark' ? 'light' : 'dark';
setThemeColor(targetTheme);
if (!(document as any).startViewTransition) {
setTheme(targetTheme);
return;
}
(document as any).startViewTransition(() => {
setTheme(targetTheme);
});
};
return (
<>
<div className={`flex items-center ${isMobile ? 'space-x-1' : 'space-x-2'}`}>
{/* 主题切换按钮 */}
<AppIconButton
onPress={toggleTheme}
size={isMobile ? 'sm' : 'md'}
aria-label='Toggle theme'
>
{resolvedTheme === 'dark' ? (
<Sun className='h-5 w-5' />
) : (
<Moon className='h-5 w-5' />
)}
</AppIconButton>
</div>
</>
);
}