Theme Toggle Circular

PreviousNext

Circular ripple animation for theme toggle using View Transitions API.

Docs
animbitscomponent

Preview

Loading preview…
registry/new-york/animations/transitions/theme-toggle-circular.tsx
"use client";
import * as React from "react";
export interface ThemeToggleCircularProps {
  children: React.ReactNode;
  onToggle?: () => void;
  theme?: "light" | "dark";
  className?: string;
  speed?: number;
  blur?: number;
}
export function ThemeToggleCircular({
  children,
  onToggle,
  theme,
  className,
  speed = 0.5,
  blur = 0,
}: ThemeToggleCircularProps) {
  const [isTransitioning, setIsTransitioning] = React.useState(false);
  const handleClick = async (e: React.MouseEvent<HTMLDivElement>) => {
    if (isTransitioning) return;
    if (!document.startViewTransition) {
      onToggle?.();
      return;
    }
    setIsTransitioning(true);
    const x = e.clientX;
    const y = e.clientY;
    const isDark = document.documentElement.classList.contains("dark");
    const targetTheme = isDark ? "to-light" : "to-dark";
    document.documentElement.style.setProperty("--x", `${x}px`);
    document.documentElement.style.setProperty("--y", `${y}px`);
    document.documentElement.style.setProperty(
      "--transition-speed",
      `${speed}s`,
    );
    document.documentElement.style.setProperty(
      "--transition-blur",
      `${blur}px`,
    );
    document.documentElement.setAttribute("data-theme-transition", targetTheme);
    try {
      const transition = document.startViewTransition(() => {
        onToggle?.();
      });
      await transition.finished;
    } catch (error) {
      console.error("Theme transition error:", error);
    } finally {
      document.documentElement.removeAttribute("data-theme-transition");
      setIsTransitioning(false);
    }
  };
  return (
    <div
      onClick={handleClick}
      className={className}
      style={{ pointerEvents: isTransitioning ? "none" : "auto" }}
    >
      {children}
    </div>
  );
}

Installation

npx shadcn@latest add @animbits/transitions-theme-toggle-circular

Usage

import { TransitionsThemeToggleCircular } from "@/components/transitions-theme-toggle-circular"
<TransitionsThemeToggleCircular />