100 lines
3.5 KiB
TypeScript
100 lines
3.5 KiB
TypeScript
import { useCallback, useEffect, useState } from "react";
|
|
import { saveSession } from "../cookie";
|
|
import { ThemeTypes } from "../types/theme";
|
|
import { Moon, Sun } from "lucide-react";
|
|
|
|
export const SunMoonToggle = ({
|
|
theme: initialTheme,
|
|
}: {
|
|
theme: ThemeTypes;
|
|
}) => {
|
|
const [theme, setTheme] = useState<ThemeTypes>(initialTheme);
|
|
|
|
useEffect(() => {
|
|
// Apply theme class to document body to match root.tsx
|
|
const body = document.body;
|
|
if (theme === "dark") {
|
|
body.classList.add("dark");
|
|
} else {
|
|
body.classList.remove("dark");
|
|
}
|
|
}, [theme]);
|
|
|
|
const changeTheme = useCallback(async () => {
|
|
const newTheme = theme === "light" ? "dark" : "light";
|
|
setTheme(newTheme);
|
|
|
|
// Update document body class immediately to match root.tsx
|
|
const body = document.body;
|
|
if (newTheme === "dark") {
|
|
body.classList.add("dark");
|
|
} else {
|
|
body.classList.remove("dark");
|
|
}
|
|
|
|
await saveSession("theme", newTheme);
|
|
|
|
// Refresh the page to ensure all components update with new theme
|
|
window.location.reload();
|
|
}, [theme]);
|
|
|
|
return (
|
|
<div className="relative">
|
|
<input
|
|
type="checkbox"
|
|
checked={theme === "dark"}
|
|
onChange={changeTheme}
|
|
className="sr-only"
|
|
aria-label={`Switch to ${theme === "light" ? "dark" : "light"} theme`}
|
|
/>
|
|
<div
|
|
onClick={changeTheme}
|
|
role="button"
|
|
tabIndex={0}
|
|
onKeyDown={(e) => {
|
|
if (e.key === "Enter") {
|
|
changeTheme();
|
|
}
|
|
}}
|
|
className="relative w-14 h-7 bg-gradient-to-r from-blue-400 to-blue-500 dark:from-indigo-600 dark:to-purple-600 rounded-full cursor-pointer transition-all duration-300 ease-in-out shadow-lg hover:shadow-xl"
|
|
>
|
|
{/* Track background with gradient */}
|
|
<div className="absolute inset-0 rounded-full bg-gradient-to-r from-sky-300 via-blue-400 to-blue-500 dark:from-slate-700 dark:via-slate-600 dark:to-slate-800 transition-all duration-300"></div>
|
|
|
|
{/* Sliding circle */}
|
|
<div
|
|
className={`absolute top-0.5 w-6 h-6 bg-WHITE rounded-full shadow-lg transform transition-all duration-300 ease-in-out flex items-center justify-center ${
|
|
theme === "dark" ? "-translate-x-8" : ""
|
|
}`}
|
|
>
|
|
{/* Icon inside the circle */}
|
|
<div className="transition-all duration-300">
|
|
{theme === "light" ? (
|
|
<Sun className="w-4 h-4 text-yellow-500" />
|
|
) : (
|
|
<Moon className="w-4 h-4 text-indigo-600" />
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Stars for dark mode */}
|
|
{theme === "dark" && (
|
|
<div className="absolute inset-0 rounded-full overflow-hidden">
|
|
<div className="absolute top-1 left-1 w-1 h-1 bg-WHITE rounded-full opacity-60 animate-pulse"></div>
|
|
<div className="absolute top-2 left-4 w-0.5 h-0.5 bg-WHITE rounded-full opacity-40 animate-pulse delay-75"></div>
|
|
<div className="absolute top-1.5 right-2 w-0.5 h-0.5 bg-WHITE rounded-full opacity-50 animate-pulse delay-150"></div>
|
|
</div>
|
|
)}
|
|
|
|
{/* Clouds for light mode */}
|
|
{theme === "light" && (
|
|
<div className="absolute inset-0 rounded-full overflow-hidden">
|
|
<div className="absolute top-1 right-1 w-2 h-1 bg-WHITE/30 rounded-full"></div>
|
|
<div className="absolute top-2 right-2.5 w-1.5 h-0.5 bg-WHITE/20 rounded-full"></div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|