From 5b7ccf0b671e2999b62befc729a3e517a0433728 Mon Sep 17 00:00:00 2001 From: Bertrand Yuan Date: Mon, 15 Dec 2025 23:48:10 +0800 Subject: initial commit -- the front-end prototype The initial code is base on Anirudh's work. More to see at: https://github.com/techwithanirudh/shadcn-blog Therefore, the code in this commit is under MIT license. --- src/components/theme-toggle.tsx | 126 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 126 insertions(+) create mode 100644 src/components/theme-toggle.tsx (limited to 'src/components/theme-toggle.tsx') diff --git a/src/components/theme-toggle.tsx b/src/components/theme-toggle.tsx new file mode 100644 index 0000000..cca9d03 --- /dev/null +++ b/src/components/theme-toggle.tsx @@ -0,0 +1,126 @@ +'use client'; + +import { cn } from '@/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 & { + mode?: 'light-dark' | 'light-dark-system'; +}) { + const { setTheme, theme: currentTheme, resolvedTheme } = useTheme(); + const [mounted, setMounted] = useState(false); + + const container = cn( + 'relative 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) { + document.documentElement.style.viewTransitionName = 'theme-transition'; + await document.startViewTransition(update).finished; + document.documentElement.style.viewTransitionName = ''; + } else { + update(); + } + }; + + const value = mounted + ? mode === 'light-dark' + ? resolvedTheme + : currentTheme + : null; + + return ( +
{ + 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 ( + + ); + })} +
+ ); +} -- cgit v1.2.3