"use client"; import { useEffect, useId } from "react"; import { animated, Globals, useSpring, useReducedMotion } from "@react-spring/web"; import useFirstMountState from "../../hooks/useFirstMountState"; import useTheme from "../../hooks/useTheme"; import useHasMounted from "../../hooks/useHasMounted"; import styles from "./ThemeToggle.module.css"; export type ThemeToggleProps = { className?: string; }; const ThemeToggle = ({ className }: ThemeToggleProps) => { const hasMounted = useHasMounted(); const { theme, setTheme } = useTheme(); const isFirstMount = useFirstMountState(); const prefersReducedMotion = useReducedMotion() ?? false; const maskId = useId(); // SSR-safe ID to cross-reference areas of the SVG // default to light since `theme` might be undefined const safeTheme = theme === "dark" ? theme : "light"; // accessibility: disable animation if user prefers reduced motion useEffect(() => { Globals.assign({ skipAnimation: isFirstMount || prefersReducedMotion, }); }, [isFirstMount, prefersReducedMotion]); // modified from https://jfelix.info/blog/using-react-spring-to-animate-svg-icons-dark-mode-toggle const springProperties = { light: { svg: { transform: "rotate(90deg)", }, circle: { r: 5, }, mask: { cx: "100%", cy: "0%", }, lines: { opacity: 1, }, }, dark: { svg: { transform: "rotate(40deg)", }, circle: { r: 9, }, mask: { cx: "50%", cy: "23%", }, lines: { opacity: 0, }, }, springConfig: { mass: 4, tension: 250, friction: 35 }, }; const { svg, circle, mask, lines } = springProperties[safeTheme]; const svgContainerProps = useSpring({ ...svg, config: springProperties.springConfig, }); const centerCircleProps = useSpring({ ...circle, config: springProperties.springConfig, }); const maskedCircleProps = useSpring({ ...mask, config: springProperties.springConfig, }); const linesProps = useSpring({ ...lines, config: springProperties.springConfig, }); // render a blank div of the same size to avoid layout shifting until we're fully mounted and self-aware if (!hasMounted) { return (