Modo Oscuro
Agrega soporte de modo oscuro a tu aplicación con transiciones suaves y detección de preferencias del sistema.
La librería i7a-themes proporciona una forma simple y elegante de implementar modo oscuro en tus aplicaciones React, con soporte para preferencias del sistema, transiciones suaves y animaciones al hacer clic.
Instalación
Instala el paquete vía pnpm:
pnpm add i7a-themes
Inicio Rápido
1. Configura tu CSS
Primero, asegúrate de tener definidas las variables de tema tanto para modo claro como oscuro en tu CSS:
:root {--background: oklch(99.405% 0.00011 271.152);--foreground: oklch(0% 0 0);/* ... otras variables de modo claro */}.dark {--background: oklch(20% 0.02 230);--foreground: oklch(96% 0.008 230);/* ... otras variables de modo oscuro */}
2. Envuelve tu app con ThemeProvider
Crea un componente providers para envolver tu aplicación:
'use client';import { ThemeProvider } from 'i7a-themes';export function Providers({ children }: { children: React.ReactNode }) {return <ThemeProvider>{children}</ThemeProvider>;}
Luego úsalo en tu layout raíz:
// app/layout.tsximport { Providers } from '@/components/providers';export default function RootLayout({ children }: { children: React.ReactNode }) {return (<html lang="es" suppressHydrationWarning><body><Providers>{children}</Providers></body></html>);}
3. Crea un botón de cambio de tema
'use client';import { useTheme } from 'i7a-themes';import { MoonIcon, SunIcon } from '@radix-ui/react-icons';import { Button } from '@/components/ui/button';export function ThemeToggle() {const { theme, setTheme } = useTheme();const isDark = theme === 'dark';const Icon = isDark ? MoonIcon : SunIcon;return (<Buttonvariant="ghost"size="icon"onClick={(e) => setTheme(isDark ? 'light' : 'dark', e)}aria-label={`Cambiar a modo ${isDark ? 'claro' : 'oscuro'}`}><Icon className="h-5 w-5" /></Button>);}
Características
Detección de Tema del Sistema
Por defecto, el provider respeta la preferencia del sistema del usuario:
<ThemeProvider>{children}</ThemeProvider>
La librería detecta automáticamente cambios en las preferencias del sistema y actualiza el tema en consecuencia cuando está configurado en 'system'.
Temas Disponibles
El hook useTheme proporciona acceso a tres modos de tema:
'light'- Modo claro'dark'- Modo oscuro'system'- Sigue la preferencia del sistema
const { theme, setTheme, themes } = useTheme();// themes = ['light', 'dark', 'system']
Transiciones Suaves con View Transitions
La librería incluye soporte integrado para la View Transitions API, creando transiciones animadas suaves entre temas. Cuando pasas un evento de clic a setTheme, crea una animación circular de revelación desde la posición del clic:
<button onClick={(e) => setTheme('dark', e)}>Cambiar Tema</button>
Sin el evento de clic, recurre a una transición estándar de fundido cruzado:
<button onClick={() => setTheme('dark')}>Cambiar Tema</button>
Tema Resuelto
Obtén el tema real que se está aplicando, incluso cuando está configurado en 'system':
const { theme, resolvedTheme } = useTheme();// theme = 'system'// resolvedTheme = 'dark' (si el sistema prefiere oscuro)
Uso Avanzado
Toggle de Tema con Estado de Carga
Maneja el estado de hidratación para prevenir cambios en el layout:
'use client';import { useMounted } from '@/hooks/use-mounted';import { useTheme } from 'i7a-themes';import { MoonIcon, SunIcon } from '@radix-ui/react-icons';import { Button } from '@/components/ui/button';export function ThemeToggle() {const mounted = useMounted();const { theme, setTheme } = useTheme();if (!mounted) {return (<Buttonvariant="ghost"size="icon"disabledclassName="bg-muted/30 animate-pulse cursor-default"><div className="bg-foreground/20 h-5 w-5 rounded-full" /></Button>);}const isDark = theme === 'dark';const Icon = isDark ? MoonIcon : SunIcon;return (<Buttonvariant="ghost"size="icon"onClick={(e) => setTheme(isDark ? 'light' : 'dark', e)}aria-label={`Cambiar a modo ${isDark ? 'claro' : 'oscuro'}`}className="h-8 w-8"><Icon className="h-5 w-5" /></Button>);}
El hook useMounted puede implementarse como:
import { useEffect, useState } from 'react';export function useMounted() {const [mounted, setMounted] = useState(false);useEffect(() => {setMounted(true);}, []);return mounted;}
Menú Desplegable de Tema
Crea un selector de tema más sofisticado:
'use client';import { useTheme } from 'i7a-themes';import {DropdownMenu,DropdownMenuContent,DropdownMenuItem,DropdownMenuTrigger,} from '@/components/ui/dropdown-menu';import { Button } from '@/components/ui/button';import { MoonIcon, SunIcon, DesktopIcon } from '@radix-ui/react-icons';export function ThemeDropdown() {const { theme, setTheme } = useTheme();return (<DropdownMenu><DropdownMenuTrigger asChild><Button variant="ghost" size="icon"><SunIcon className="h-5 w-5 scale-100 rotate-0 transition-transform dark:scale-0 dark:-rotate-90" /><MoonIcon className="absolute h-5 w-5 scale-0 rotate-90 transition-transform dark:scale-100 dark:rotate-0" /><span className="sr-only">Cambiar tema</span></Button></DropdownMenuTrigger><DropdownMenuContent align="end"><DropdownMenuItem onClick={() => setTheme('light')}><SunIcon className="mr-2 h-4 w-4" />Claro</DropdownMenuItem><DropdownMenuItem onClick={() => setTheme('dark')}><MoonIcon className="mr-2 h-4 w-4" />Oscuro</DropdownMenuItem><DropdownMenuItem onClick={() => setTheme('system')}><DesktopIcon className="mr-2 h-4 w-4" />Sistema</DropdownMenuItem></DropdownMenuContent></DropdownMenu>);}
Cambios de Tema Programáticos
Accede a información del tema en cualquier parte de tu app:
'use client';import { useTheme } from 'i7a-themes';import { useEffect } from 'react';export function ContenidoDinamico() {const { resolvedTheme } = useTheme();useEffect(() => {// Actualiza librerías de terceros según el temaif (resolvedTheme === 'dark') {// Inicializa modo oscuro para servicios externos}}, [resolvedTheme]);return (<div><p>Tema actual: {resolvedTheme}</p></div>);}
Soporte de TypeScript
La librería está completamente tipada. Todos los hooks y componentes tienen definiciones completas de TypeScript:
import { Theme } from 'i7a-themes';const miTema: Theme = 'dark';