/**
* ThemeToggle - Theme switcher component
*
* Switches between light and dark themes.
* Must be used within ThemeProvider.
*
* @example
* ```tsx
* import { ThemeToggle } from '@djangocfg/ui-core/theme';
*
* // Default
*
*
* // Compact (for mobile/tight spaces)
*
*
* // Custom className
*
* ```
*/
'use client';
import { Moon, Sun } from 'lucide-react';
import { useEffect, useState } from 'react';
import { Button } from '../components/forms/button';
import { useIsMobile } from '../hooks/media/useMobile';
import { cn } from '../lib/utils';
import { useForcedTheme } from './ForcedThemeContext';
import { useThemeContext } from './ThemeProvider';
export type ThemeLockBehavior = 'disable' | 'hide';
export interface ThemeToggleProps {
/** Custom className */
className?: string;
/** Size variant (auto-detects mobile if not specified) */
size?: 'default' | 'compact' | 'auto';
/**
* What to do when the current route forces a theme via `ThemeOverride`.
* - `disable` (default): render the toggle disabled with a tooltip.
* - `hide`: render nothing.
*/
lockedBehavior?: ThemeLockBehavior;
/** Title shown when the control is locked. */
lockedTitle?: string;
}
export function ThemeToggle({
className,
size = 'auto',
lockedBehavior = 'disable',
lockedTitle = 'Theme is set for this page',
}: ThemeToggleProps) {
const { resolvedTheme, toggleTheme } = useThemeContext();
const forcedTheme = useForcedTheme();
const [mounted, setMounted] = useState(false);
const isMobile = useIsMobile();
// Prevent hydration mismatch
useEffect(() => {
setMounted(true);
}, []);
const isLocked = Boolean(forcedTheme);
// Determine actual size based on prop and screen size
const actualSize = size === 'auto' ? (isMobile ? 'compact' : 'default') : size;
const buttonSize = actualSize === 'compact' ? 'h-8 w-8' : 'h-9 w-9';
const iconSize = actualSize === 'compact' ? 'h-3.5 w-3.5' : 'h-4 w-4';
// The icon always reflects what's on screen (forced or not) — that is
// `resolvedTheme`. When locked, it shows the forced theme's icon.
const showSun = mounted ? resolvedTheme === 'light' : true;
if (isLocked && lockedBehavior === 'hide') {
return null;
}
if (!mounted) {
return (
);
}
return (
);
}