From 6e50afea8e028f34360018265526b4412a708fbc Mon Sep 17 00:00:00 2001 From: DhirajTsx Date: Sat, 17 Jan 2026 16:06:07 +0530 Subject: [PATCH] fix: fallback for missing startViewTransition API --- apps/www/public/llms-full.txt | 81 ++++++++++++------- apps/www/public/r/animated-theme-toggler.json | 2 +- apps/www/public/r/pointer.json | 2 +- .../magicui/animated-theme-toggler.tsx | 76 ++++++++++------- 4 files changed, 97 insertions(+), 64 deletions(-) diff --git a/apps/www/public/llms-full.txt b/apps/www/public/llms-full.txt index 15d8dabca..ea2d693c5 100644 --- a/apps/www/public/llms-full.txt +++ b/apps/www/public/llms-full.txt @@ -2813,7 +2813,8 @@ import { flushSync } from "react-dom" import { cn } from "@/lib/utils" -interface AnimatedThemeTogglerProps extends React.ComponentPropsWithoutRef<"button"> { +interface AnimatedThemeTogglerProps + extends React.ComponentPropsWithoutRef<"button"> { duration?: number } @@ -2841,40 +2842,57 @@ export const AnimatedThemeToggler = ({ return () => observer.disconnect() }, []) - const toggleTheme = useCallback(async () => { + const toggleTheme = useCallback(() => { if (!buttonRef.current) return - await document.startViewTransition(() => { - flushSync(() => { - const newTheme = !isDark - setIsDark(newTheme) - document.documentElement.classList.toggle("dark") - localStorage.setItem("theme", newTheme ? "dark" : "light") + const applyTheme = () => { + const newTheme = !isDark + setIsDark(newTheme) + document.documentElement.classList.toggle("dark") + localStorage.setItem("theme", newTheme ? "dark" : "light") + } + + + if ( + typeof document === "undefined" || + !("startViewTransition" in document) + ) { + applyTheme() + return + } + + + + document + .startViewTransition(() => { + flushSync(applyTheme) }) - }).ready - - const { top, left, width, height } = - buttonRef.current.getBoundingClientRect() - const x = left + width / 2 - const y = top + height / 2 - const maxRadius = Math.hypot( - Math.max(left, window.innerWidth - left), - Math.max(top, window.innerHeight - top) - ) + .ready.then(() => { + const { top, left, width, height } = + buttonRef.current!.getBoundingClientRect() - document.documentElement.animate( - { - clipPath: [ - `circle(0px at ${x}px ${y}px)`, - `circle(${maxRadius}px at ${x}px ${y}px)`, - ], - }, - { - duration, - easing: "ease-in-out", - pseudoElement: "::view-transition-new(root)", - } - ) + const x = left + width / 2 + const y = top + height / 2 + + const maxRadius = Math.hypot( + Math.max(left, window.innerWidth - left), + Math.max(top, window.innerHeight - top) + ) + + document.documentElement.animate( + { + clipPath: [ + `circle(0px at ${x}px ${y}px)`, + `circle(${maxRadius}px at ${x}px ${y}px)`, + ], + }, + { + duration, + easing: "ease-in-out", + pseudoElement: "::view-transition-new(root)", + } + ) + }) }, [isDark, duration]) return ( @@ -11401,6 +11419,7 @@ export function Pointer({ const handleMouseMove = (e: MouseEvent) => { x.set(e.clientX) y.set(e.clientY) + setIsActive(true) } const handleMouseEnter = (e: MouseEvent) => { diff --git a/apps/www/public/r/animated-theme-toggler.json b/apps/www/public/r/animated-theme-toggler.json index 8c0bb7b15..65f04de35 100644 --- a/apps/www/public/r/animated-theme-toggler.json +++ b/apps/www/public/r/animated-theme-toggler.json @@ -10,7 +10,7 @@ "files": [ { "path": "registry/magicui/animated-theme-toggler.tsx", - "content": "\"use client\"\n\nimport { useCallback, useEffect, useRef, useState } from \"react\"\nimport { Moon, Sun } from \"lucide-react\"\nimport { flushSync } from \"react-dom\"\n\nimport { cn } from \"@/lib/utils\"\n\ninterface AnimatedThemeTogglerProps extends React.ComponentPropsWithoutRef<\"button\"> {\n duration?: number\n}\n\nexport const AnimatedThemeToggler = ({\n className,\n duration = 400,\n ...props\n}: AnimatedThemeTogglerProps) => {\n const [isDark, setIsDark] = useState(false)\n const buttonRef = useRef(null)\n\n useEffect(() => {\n const updateTheme = () => {\n setIsDark(document.documentElement.classList.contains(\"dark\"))\n }\n\n updateTheme()\n\n const observer = new MutationObserver(updateTheme)\n observer.observe(document.documentElement, {\n attributes: true,\n attributeFilter: [\"class\"],\n })\n\n return () => observer.disconnect()\n }, [])\n\n const toggleTheme = useCallback(async () => {\n if (!buttonRef.current) return\n\n await document.startViewTransition(() => {\n flushSync(() => {\n const newTheme = !isDark\n setIsDark(newTheme)\n document.documentElement.classList.toggle(\"dark\")\n localStorage.setItem(\"theme\", newTheme ? \"dark\" : \"light\")\n })\n }).ready\n\n const { top, left, width, height } =\n buttonRef.current.getBoundingClientRect()\n const x = left + width / 2\n const y = top + height / 2\n const maxRadius = Math.hypot(\n Math.max(left, window.innerWidth - left),\n Math.max(top, window.innerHeight - top)\n )\n\n document.documentElement.animate(\n {\n clipPath: [\n `circle(0px at ${x}px ${y}px)`,\n `circle(${maxRadius}px at ${x}px ${y}px)`,\n ],\n },\n {\n duration,\n easing: \"ease-in-out\",\n pseudoElement: \"::view-transition-new(root)\",\n }\n )\n }, [isDark, duration])\n\n return (\n \n {isDark ? : }\n Toggle theme\n \n )\n}\n", + "content": "\"use client\"\n\nimport { useCallback, useEffect, useRef, useState } from \"react\"\nimport { Moon, Sun } from \"lucide-react\"\nimport { flushSync } from \"react-dom\"\n\nimport { cn } from \"@/lib/utils\"\n\ninterface AnimatedThemeTogglerProps\n extends React.ComponentPropsWithoutRef<\"button\"> {\n duration?: number\n}\n\nexport const AnimatedThemeToggler = ({\n className,\n duration = 400,\n ...props\n}: AnimatedThemeTogglerProps) => {\n const [isDark, setIsDark] = useState(false)\n const buttonRef = useRef(null)\n\n useEffect(() => {\n const updateTheme = () => {\n setIsDark(document.documentElement.classList.contains(\"dark\"))\n }\n\n updateTheme()\n\n const observer = new MutationObserver(updateTheme)\n observer.observe(document.documentElement, {\n attributes: true,\n attributeFilter: [\"class\"],\n })\n\n return () => observer.disconnect()\n }, [])\n\n const toggleTheme = useCallback(() => {\n if (!buttonRef.current) return\n\n const applyTheme = () => {\n const newTheme = !isDark\n setIsDark(newTheme)\n document.documentElement.classList.toggle(\"dark\")\n localStorage.setItem(\"theme\", newTheme ? \"dark\" : \"light\")\n }\n\n \n if (\n typeof document === \"undefined\" ||\n !(\"startViewTransition\" in document)\n ) {\n applyTheme()\n return\n }\n\n\n \n document\n .startViewTransition(() => {\n flushSync(applyTheme)\n })\n .ready.then(() => {\n const { top, left, width, height } =\n buttonRef.current!.getBoundingClientRect()\n\n const x = left + width / 2\n const y = top + height / 2\n\n const maxRadius = Math.hypot(\n Math.max(left, window.innerWidth - left),\n Math.max(top, window.innerHeight - top)\n )\n\n document.documentElement.animate(\n {\n clipPath: [\n `circle(0px at ${x}px ${y}px)`,\n `circle(${maxRadius}px at ${x}px ${y}px)`,\n ],\n },\n {\n duration,\n easing: \"ease-in-out\",\n pseudoElement: \"::view-transition-new(root)\",\n }\n )\n })\n }, [isDark, duration])\n\n return (\n \n {isDark ? : }\n Toggle theme\n \n )\n}\n", "type": "registry:ui" } ], diff --git a/apps/www/public/r/pointer.json b/apps/www/public/r/pointer.json index 8ea7c56ba..7bee8f189 100644 --- a/apps/www/public/r/pointer.json +++ b/apps/www/public/r/pointer.json @@ -10,7 +10,7 @@ "files": [ { "path": "registry/magicui/pointer.tsx", - "content": "\"use client\"\n\nimport { useEffect, useRef, useState } from \"react\"\nimport {\n AnimatePresence,\n HTMLMotionProps,\n motion,\n useMotionValue,\n} from \"motion/react\"\n\nimport { cn } from \"@/lib/utils\"\n\n/**\n * A custom pointer component that displays an animated cursor.\n * Add this as a child to any component to enable a custom pointer when hovering.\n * You can pass custom children to render as the pointer.\n *\n * @component\n * @param {HTMLMotionProps<\"div\">} props - The component props\n */\nexport function Pointer({\n className,\n style,\n children,\n ...props\n}: HTMLMotionProps<\"div\">): React.ReactNode {\n const x = useMotionValue(0)\n const y = useMotionValue(0)\n const [isActive, setIsActive] = useState(false)\n const containerRef = useRef(null)\n\n useEffect(() => {\n if (typeof window !== \"undefined\" && containerRef.current) {\n // Get the parent element directly from the ref\n const parentElement = containerRef.current.parentElement\n\n if (parentElement) {\n // Add cursor-none to parent\n parentElement.style.cursor = \"none\"\n\n // Add event listeners to parent\n const handleMouseMove = (e: MouseEvent) => {\n x.set(e.clientX)\n y.set(e.clientY)\n }\n\n const handleMouseEnter = (e: MouseEvent) => {\n x.set(e.clientX)\n y.set(e.clientY)\n setIsActive(true)\n }\n\n const handleMouseLeave = () => {\n setIsActive(false)\n }\n\n parentElement.addEventListener(\"mousemove\", handleMouseMove)\n parentElement.addEventListener(\"mouseenter\", handleMouseEnter)\n parentElement.addEventListener(\"mouseleave\", handleMouseLeave)\n\n return () => {\n parentElement.style.cursor = \"\"\n parentElement.removeEventListener(\"mousemove\", handleMouseMove)\n parentElement.removeEventListener(\"mouseenter\", handleMouseEnter)\n parentElement.removeEventListener(\"mouseleave\", handleMouseLeave)\n }\n }\n }\n }, [x, y])\n\n return (\n <>\n
\n \n {isActive && (\n \n {children || (\n \n \n \n )}\n \n )}\n \n \n )\n}\n", + "content": "\"use client\"\n\nimport { useEffect, useRef, useState } from \"react\"\nimport {\n AnimatePresence,\n HTMLMotionProps,\n motion,\n useMotionValue,\n} from \"motion/react\"\n\nimport { cn } from \"@/lib/utils\"\n\n/**\n * A custom pointer component that displays an animated cursor.\n * Add this as a child to any component to enable a custom pointer when hovering.\n * You can pass custom children to render as the pointer.\n *\n * @component\n * @param {HTMLMotionProps<\"div\">} props - The component props\n */\nexport function Pointer({\n className,\n style,\n children,\n ...props\n}: HTMLMotionProps<\"div\">): React.ReactNode {\n const x = useMotionValue(0)\n const y = useMotionValue(0)\n const [isActive, setIsActive] = useState(false)\n const containerRef = useRef(null)\n\n useEffect(() => {\n if (typeof window !== \"undefined\" && containerRef.current) {\n // Get the parent element directly from the ref\n const parentElement = containerRef.current.parentElement\n\n if (parentElement) {\n // Add cursor-none to parent\n parentElement.style.cursor = \"none\"\n\n // Add event listeners to parent\n const handleMouseMove = (e: MouseEvent) => {\n x.set(e.clientX)\n y.set(e.clientY)\n setIsActive(true)\n }\n\n const handleMouseEnter = (e: MouseEvent) => {\n x.set(e.clientX)\n y.set(e.clientY)\n setIsActive(true)\n }\n\n const handleMouseLeave = () => {\n setIsActive(false)\n }\n\n parentElement.addEventListener(\"mousemove\", handleMouseMove)\n parentElement.addEventListener(\"mouseenter\", handleMouseEnter)\n parentElement.addEventListener(\"mouseleave\", handleMouseLeave)\n\n return () => {\n parentElement.style.cursor = \"\"\n parentElement.removeEventListener(\"mousemove\", handleMouseMove)\n parentElement.removeEventListener(\"mouseenter\", handleMouseEnter)\n parentElement.removeEventListener(\"mouseleave\", handleMouseLeave)\n }\n }\n }\n }, [x, y])\n\n return (\n <>\n
\n \n {isActive && (\n \n {children || (\n \n \n \n )}\n \n )}\n \n \n )\n}\n", "type": "registry:ui" } ] diff --git a/apps/www/registry/magicui/animated-theme-toggler.tsx b/apps/www/registry/magicui/animated-theme-toggler.tsx index f896fbcdf..64fb6889e 100644 --- a/apps/www/registry/magicui/animated-theme-toggler.tsx +++ b/apps/www/registry/magicui/animated-theme-toggler.tsx @@ -34,40 +34,54 @@ export const AnimatedThemeToggler = ({ return () => observer.disconnect() }, []) - const toggleTheme = useCallback(async () => { + const toggleTheme = useCallback(() => { if (!buttonRef.current) return - await document.startViewTransition(() => { - flushSync(() => { - const newTheme = !isDark - setIsDark(newTheme) - document.documentElement.classList.toggle("dark") - localStorage.setItem("theme", newTheme ? "dark" : "light") + const applyTheme = () => { + const newTheme = !isDark + setIsDark(newTheme) + document.documentElement.classList.toggle("dark") + localStorage.setItem("theme", newTheme ? "dark" : "light") + } + + if ( + typeof document === "undefined" || + !("startViewTransition" in document) + ) { + applyTheme() + return + } + + document + .startViewTransition(() => { + flushSync(applyTheme) + }) + .ready.then(() => { + const { top, left, width, height } = + buttonRef.current!.getBoundingClientRect() + + const x = left + width / 2 + const y = top + height / 2 + + const maxRadius = Math.hypot( + Math.max(left, window.innerWidth - left), + Math.max(top, window.innerHeight - top) + ) + + document.documentElement.animate( + { + clipPath: [ + `circle(0px at ${x}px ${y}px)`, + `circle(${maxRadius}px at ${x}px ${y}px)`, + ], + }, + { + duration, + easing: "ease-in-out", + pseudoElement: "::view-transition-new(root)", + } + ) }) - }).ready - - const { top, left, width, height } = - buttonRef.current.getBoundingClientRect() - const x = left + width / 2 - const y = top + height / 2 - const maxRadius = Math.hypot( - Math.max(left, window.innerWidth - left), - Math.max(top, window.innerHeight - top) - ) - - document.documentElement.animate( - { - clipPath: [ - `circle(0px at ${x}px ${y}px)`, - `circle(${maxRadius}px at ${x}px ${y}px)`, - ], - }, - { - duration, - easing: "ease-in-out", - pseudoElement: "::view-transition-new(root)", - } - ) }, [isDark, duration]) return (