feat: dark and light mdoe
This commit is contained in:
@@ -0,0 +1,82 @@
|
||||
'use client';
|
||||
|
||||
import { useEffect, useState } from 'react';
|
||||
import { SunIcon, MoonIcon } from '@heroicons/react/24/outline';
|
||||
|
||||
type Theme = 'light' | 'dark';
|
||||
|
||||
interface ThemeToggleProps {
|
||||
/** When true, renders as a fixed floating pill (used on the app shell). */
|
||||
floating?: boolean;
|
||||
/** When true, shows a Persian text label next to the icon. */
|
||||
withLabel?: boolean;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export default function ThemeToggle({ floating = false, withLabel, className = '' }: ThemeToggleProps) {
|
||||
const [theme, setTheme] = useState<Theme>('light');
|
||||
const [mounted, setMounted] = useState(false);
|
||||
|
||||
// Floating variant shows a label by default; inline variant is icon-only unless asked.
|
||||
const showLabel = withLabel ?? floating;
|
||||
|
||||
useEffect(() => {
|
||||
setMounted(true);
|
||||
setTheme(document.documentElement.classList.contains('dark') ? 'dark' : 'light');
|
||||
}, []);
|
||||
|
||||
const toggle = () => {
|
||||
const next: Theme = theme === 'dark' ? 'light' : 'dark';
|
||||
const root = document.documentElement;
|
||||
if (next === 'dark') {
|
||||
root.classList.add('dark');
|
||||
} else {
|
||||
root.classList.remove('dark');
|
||||
}
|
||||
try {
|
||||
localStorage.setItem('theme', next);
|
||||
} catch {
|
||||
// ignore storage errors
|
||||
}
|
||||
setTheme(next);
|
||||
};
|
||||
|
||||
const base =
|
||||
'inline-flex items-center gap-2 border border-gray-200 bg-white text-gray-700 shadow-sm hover:bg-gray-50 hover:text-gray-900 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-200 dark:hover:bg-gray-700 dark:hover:text-white dark:focus:ring-offset-gray-900 transition-colors';
|
||||
|
||||
const shape = showLabel
|
||||
? 'rounded-full px-4 py-2 text-sm font-medium'
|
||||
: 'rounded-full justify-center h-10 w-10';
|
||||
|
||||
const floatingCls = floating ? 'fixed bottom-5 left-5 z-50' : '';
|
||||
|
||||
const isDark = theme === 'dark';
|
||||
const label = isDark ? 'حالت روشن' : 'حالت تیره';
|
||||
|
||||
// Avoid hydration mismatch: render a neutral placeholder until mounted
|
||||
if (!mounted) {
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
aria-label="تغییر تم"
|
||||
className={`${base} ${shape} ${floatingCls} ${className}`}
|
||||
>
|
||||
<SunIcon className="h-5 w-5" />
|
||||
{showLabel && <span>تم</span>}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
onClick={toggle}
|
||||
aria-label={label}
|
||||
title={label}
|
||||
className={`${base} ${shape} ${floatingCls} ${className}`}
|
||||
>
|
||||
{isDark ? <SunIcon className="h-5 w-5" /> : <MoonIcon className="h-5 w-5" />}
|
||||
{showLabel && <span>{label}</span>}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user