This component is pretty light-weight. It only contains a button with an
SVG and conditionally rendered text. While simple, it still requires a
bit of tweaking to make it feel good. I started off by setting up the
state and the click handler.
I re-created the iOS theme toggle icon in Figma and then exported it as
an SVG. I made sure to structure the SVG into individual paths, so I
could animate each path separately.
The original iOS animation is animated in a more complex way.
I've tried that as well, but for this interaction I preferred a simpler
approach.
The text animation is done using AnimatePresence and animating the y position of each span. I used the popLayout mode to make the exiting
element pop out of the page layout, allowing the new element to move
into the layout immediately.
The words "Dark" and "Light" aren't the same width. This caused
instant changes in the layout. I wanted these changes to be animated. I
added the layout prop to the SVG
and the "Mode" span.
<motion.span layout>Mode</motion.span>
For the SVG, I created a new motion.div and added the layout prop there, instead of
adding the prop directly to the SVG component, as that would interfere
with the rotation animation.
Finally, after setting everything up, I added a timeout to the button.
Without this, clicking the button quickly can trigger the animation
again before it's completed, causing it to start from the wrong
direction.