aller au contenu principal

1. Installation avec le CLI de shadcn/ui

Copiez la commande ci-dessous dans votre terminal afin d'installer le composant

pnpm dlx shadcn@latest add @envindavsorg/theme-switcher

2. Installation manuelle

(2.1) installez les dépendances :

pnpm add motion clsx tailwind-merge

(2.2) ajoutez le helper cn :

TypeScriptlib/utils.ts
import { type ClassValue, clsx } from 'clsx';
import { twMerge } from 'tailwind-merge';
 
export const cn = (...inputClasses: ClassValue[]): string => twMerge(clsx(inputClasses));

(2.3) ajoutez le code suivant :

Reactcomponents/ThemeSwitcher.tsx
'use client';
 
import { MonitorIcon, MoonIcon, SunIcon } from '@phosphor-icons/react';
import { motion } from 'motion/react';
import { useTheme } from 'next-themes';
import type React from 'react';
import { useEffect, useState } from 'react';
import { cn } from '@/lib/utils';
 
const THEME_OPTIONS = [
	{
		icon: <SunIcon weight="duotone" />,
		value: 'light',
	},
	{
		icon: <MoonIcon weight="duotone" />,
		value: 'dark',
	},
	{
		icon: <MonitorIcon weight="duotone" />,
		value: 'system',
	},
];
 
const ThemeOption = ({
	icon,
	value,
	isActive,
	onClick,
}: {
	icon: React.ReactNode;
	value: string;
	isActive?: boolean;
	onClick: (value: string) => void;
}) => (
	<button
		aria-label={value}
		className={cn(
			'relative flex size-10 items-center justify-center rounded-full transition-all',
			'[&_svg]:size-5 [&_svg]:cursor-pointer',
			isActive
				? 'text-zinc-950 dark:text-zinc-50'
				: 'text-zinc-400 hover:text-zinc-950 dark:text-zinc-500 dark:hover:text-zinc-50'
		)}
		onClick={() => onClick(value)}
		type="button"
	>
		{icon}
 
		{isActive && (
			<motion.div
				className="absolute inset-0 rounded-full border border-zinc-200 dark:border-zinc-700"
				layoutId="theme-option"
				transition={{
					type: 'spring',
					bounce: 0.3,
					duration: 0.6,
				}}
			/>
		)}
	</button>
);
 
export const ThemeSwitcher = () => {
	const { theme, setTheme } = useTheme();
 
	const [isMounted, setIsMounted] = useState(false);
 
	useEffect(() => {
		setIsMounted(true);
	}, []);
 
	if (!isMounted) {
		return <div className="h-8 w-2 sm:flex" />;
	}
 
	return (
		<motion.div
			animate={{ opacity: 1 }}
			className="relative items-center overflow-hidden rounded-full bg-white ring-1 ring-zinc-200 ring-inset dark:bg-zinc-950 dark:ring-zinc-700"
			initial={{ opacity: 0 }}
			key={String(isMounted)}
			role="radiogroup"
			transition={{ duration: 0.3 }}
		>
			{THEME_OPTIONS.map((option) => (
				<ThemeOption
					icon={option.icon}
					isActive={theme === option.value}
					key={option.value}
					onClick={setTheme}
					value={option.value}
				/>
			))}
		</motion.div>
	);
};

(2.4) petite astuce :

Mettez à jour les chemins d'import de vos dépendances pour correspondre à la structure de votre projet et ainsi le faire fonctionner correctement.

3. Utilisation

'use client';
 
import { ThemeSwitcher } from "@/components/theme-switcher";
 
<ThemeSwitcher />

4. Références