83 lines
2.8 KiB
TypeScript
83 lines
2.8 KiB
TypeScript
'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>
|
|
);
|
|
}
|