import { useEffect, memo } from "react";
import { useMedia } from "react-use";
import { useSpring, animated, Globals } from "@react-spring/web";
import { useTheme } from "../../hooks/use-theme";
import { useHasMounted } from "../../hooks/use-has-mounted";
import { styled } from "../../lib/styles/stitches.config";
const Button = styled("button", {
border: 0,
padding: "0.6em",
marginRight: "-0.6em",
background: "none",
cursor: "pointer",
color: "$mediumDark",
"&:hover": {
color: "$warning",
},
});
export type ThemeToggleProps = {
id?: string;
className?: string;
};
const ThemeToggle = ({ id = "nav", className }: ThemeToggleProps) => {
const hasMounted = useHasMounted();
const prefersReducedMotion = useMedia("(prefers-reduced-motion: reduce)", false);
const { resolvedTheme, setTheme } = useTheme();
// default to light since `resolvedTheme` might be undefined
const safeTheme = resolvedTheme === "dark" ? "dark" : "light";
// accessibility: skip animation if user prefers reduced motion
useEffect(() => {
Globals.assign({
skipAnimation: !!prefersReducedMotion,
});
}, [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 (
);
}
return (
);
};
export default memo(ThemeToggle);