Skip to main content
Glama
theme-toggle.tsx3.94 kB
"use client"; import { cn } from "@repo/ui/lib/utils"; import { cva } from "class-variance-authority"; import { Airplay, Moon, Sun } from "lucide-react"; import { motion } from "motion/react"; import { useTheme } from "next-themes"; import { type HTMLAttributes, useLayoutEffect, useState } from "react"; const themes = [ { key: "light", icon: Sun, label: "Light theme", }, { key: "dark", icon: Moon, label: "Dark theme", }, { key: "system", icon: Airplay, label: "System theme", }, ]; const itemVariants = cva( "relative size-6.5 rounded-full p-1.5 text-fd-muted-foreground", { variants: { active: { true: "text-fd-accent-foreground", false: "text-fd-muted-foreground", }, }, } ); type Theme = "light" | "dark" | "system"; export function ThemeToggle({ className, mode = "light-dark", ...props }: HTMLAttributes<HTMLDivElement> & { mode?: "light-dark" | "light-dark-system"; }) { const { setTheme, theme: currentTheme, resolvedTheme } = useTheme(); const [mounted, setMounted] = useState(false); const container = cn( "relative inline-flex items-center rounded-full p-1 ring-1 ring-border", className ); useLayoutEffect(() => { setMounted(true); }, []); const handleChangeTheme = async (theme: Theme) => { function update() { setTheme(theme); } if (document.startViewTransition && theme !== resolvedTheme) { // Get the toggle button position for the ripple effect const toggleButton = document.querySelector("[data-theme-toggle]"); if (toggleButton) { const rect = toggleButton.getBoundingClientRect(); const x = rect.left + rect.width / 2; const y = rect.top + rect.height / 2; // Set the mask position for the ripple effect document.documentElement.style.setProperty( "--theme-toggle-x", `${x}px` ); document.documentElement.style.setProperty( "--theme-toggle-y", `${y}px` ); // Add a class to identify this is a theme toggle transition document.documentElement.classList.add("theme-toggle-transition"); document.documentElement.style.viewTransitionName = "root"; await document.startViewTransition(update).finished; document.documentElement.style.viewTransitionName = ""; document.documentElement.classList.remove("theme-toggle-transition"); } } else { update(); } }; const value = mounted ? mode === "light-dark" ? resolvedTheme : currentTheme : null; return ( <div className={container} onClick={() => { if (mode !== "light-dark") return; handleChangeTheme(resolvedTheme === "dark" ? "light" : "dark"); }} data-theme-toggle="" aria-label={mode === "light-dark" ? "Toggle Theme" : undefined} {...props} > {themes.map(({ key, icon: Icon, label }) => { const isActive = value === key; if (mode === "light-dark" && key === "system") return; return ( <button type="button" key={key} className={itemVariants({ active: isActive })} onClick={() => { if (mode === "light-dark") return; handleChangeTheme(key as Theme); }} aria-label={label} > {isActive && ( <motion.div layoutId="activeTheme" className="absolute inset-0 rounded-full bg-accent" transition={{ type: "spring", duration: mode === "light-dark" ? 1.5 : 1, }} /> )} <Icon className={"relative m-auto size-full"} fill={"currentColor"} /> </button> ); })} </div> ); }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/metacode0602/open-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server