Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 | 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x | /**
* Theme Toggle Component
*
* A button component to toggle between light and dark themes.
*
* Features:
* - Animated sun/moon icon transition
* - Shows current theme state
* - Accessible with keyboard navigation
* - Customizable appearance
*
* @example
* ```tsx
* <ThemeToggle />
* <ThemeToggle variant="icon-only" />
* <ThemeToggle variant="text" />
* ```
*/
'use client';
import React from 'react';
import { useTheme } from '@/hooks/useTheme';
import { cn } from '@/lib/core';
import { Icon } from '@/components/ui/icons';
export interface ThemeToggleProps {
/** Visual variant of the toggle */
variant?: 'default' | 'icon-only' | 'text';
/** Size of the toggle button */
size?: 'sm' | 'md' | 'lg';
/** Custom className */
className?: string;
/** Show the label text */
showLabel?: boolean;
}
/**
* ThemeToggle Component
*/
const ThemeToggle = React.forwardRef<HTMLButtonElement, ThemeToggleProps>(
(
{
variant = 'icon-only',
size = 'md',
className,
showLabel = false},
ref
) => {
const { resolvedTheme, toggleTheme } = useTheme();
const isDark = resolvedTheme === 'dark';
const sizeClasses = {
sm: 'h-8 w-8 text-sm',
md: 'h-10 w-10 text-base',
lg: 'h-12 w-12 text-lg'};
return (
<button
ref={ref}
type="button"
onClick={toggleTheme}
className={cn(
'theme-toggle',
'relative inline-flex items-center justify-center',
'rounded-full',
'bg-gray-200 dark:bg-gray-700',
'text-gray-800 dark:text-gray-200',
'hover:bg-gray-300 dark:hover:bg-gray-600',
'transition-colors duration-200',
'focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-2',
'active:scale-95 transition-transform',
sizeClasses[size],
variant === 'text' && 'px-4 w-auto',
className
)}
aria-label={isDark ? 'Switch to light mode' : 'Switch to dark mode'}
title={isDark ? 'Switch to light mode' : 'Switch to dark mode'}
>
{/* Sun Icon (visible in dark mode) */}
<Icon
name="sun"
size={size === 'sm' ? 16 : size === 'md' ? 20 : 24}
className={cn(
'absolute transition-all duration-300',
isDark ? 'rotate-0 scale-100 opacity-100' : 'rotate-90 scale-0 opacity-0'
)}
aria-hidden="true"
/>
{/* Moon Icon (visible in light mode) */}
<Icon
name="moon"
size={size === 'sm' ? 16 : size === 'md' ? 20 : 24}
className={cn(
'absolute transition-all duration-300',
isDark ? 'rotate-90 scale-0 opacity-0' : 'rotate-0 scale-100 opacity-100'
)}
aria-hidden="true"
/>
{/* Text label (for text variant or when showLabel is true) */}
{(variant === 'text' || showLabel) && (
<span className="ml-8">
{isDark ? 'Light' : 'Dark'}
</span>
)}
</button>
);
}
);
ThemeToggle.displayName = 'ThemeToggle';
export default ThemeToggle;
|