diff --git a/src/components/ToggleDarkMode/ToggleDarkMode.tsx b/src/components/ToggleDarkMode/ToggleDarkMode.tsx
index e86bcc6..448ae42 100644
--- a/src/components/ToggleDarkMode/ToggleDarkMode.tsx
+++ b/src/components/ToggleDarkMode/ToggleDarkMode.tsx
@@ -1,7 +1,9 @@
import { useState, useEffect, useCallback } from 'react'
-import { Moon, Sun } from 'lucide-react'
+import { Moon, Sun, Monitor } from 'lucide-react'
import { Button, type ButtonProps } from '../Button'
+export type ThemePreference = 'light' | 'dark' | 'system'
+
export interface ToggleDarkModeProps {
/**
* Color mode for background context (passed to Button)
@@ -35,15 +37,20 @@ export interface ToggleDarkModeProps {
*/
darkLabel?: string
+ /**
+ * Custom label for system mode
+ */
+ systemLabel?: string
+
/**
* localStorage key for persisting preference
*/
storageKey?: string
/**
- * Callback when dark mode changes
+ * Callback when theme preference changes
*/
- onChange?: (isDark: boolean) => void
+ onChange?: (theme: ThemePreference) => void
/**
* Additional CSS classes
@@ -51,8 +58,14 @@ export interface ToggleDarkModeProps {
className?: string
}
+const CYCLE: ThemePreference[] = ['light', 'dark', 'system']
+
+function getSystemDark() {
+ return window.matchMedia('(prefers-color-scheme: dark)').matches
+}
+
/**
- * Toggle button for switching between light and dark modes.
+ * Toggle button for cycling between light, dark, and system color modes.
* Automatically persists preference to localStorage and applies
* the 'dark' class to the document element.
*
@@ -64,11 +77,8 @@ export interface ToggleDarkModeProps {
* // With label
*
*
- * // Custom labels
- *
- *
* // With change callback
- * console.log('Dark mode:', isDark)} />
+ * console.log('Theme:', theme)} />
* ```
*/
export function ToggleDarkMode({
@@ -78,45 +88,68 @@ export function ToggleDarkMode({
showLabel = false,
lightLabel = 'Light',
darkLabel = 'Dark',
+ systemLabel = 'System',
storageKey = 'darkMode',
onChange,
className,
}: ToggleDarkModeProps) {
- const [isDarkMode, setIsDarkMode] = useState(() => {
- // Check for saved preference or system preference
+ const [theme, setTheme] = useState(() => {
if (typeof window !== 'undefined') {
const saved = localStorage.getItem(storageKey)
- if (saved !== null) return saved === 'true'
- return window.matchMedia('(prefers-color-scheme: dark)').matches
+ // Migrate legacy boolean values
+ if (saved === 'true') return 'dark'
+ if (saved === 'false') return 'light'
+ if (saved === 'light' || saved === 'dark' || saved === 'system') return saved
}
- return false
+ return 'system'
})
- // Apply dark mode class to html element
+ // Apply dark mode class based on theme preference
useEffect(() => {
- document.documentElement.classList.toggle('dark', isDarkMode)
- localStorage.setItem(storageKey, String(isDarkMode))
- }, [isDarkMode, storageKey])
+ const apply = (isDark: boolean) => {
+ document.documentElement.classList.toggle('dark', isDark)
+ }
+
+ if (theme === 'system') {
+ apply(getSystemDark())
+ const mq = window.matchMedia('(prefers-color-scheme: dark)')
+ const handler = (e: MediaQueryListEvent) => apply(e.matches)
+ mq.addEventListener('change', handler)
+ return () => mq.removeEventListener('change', handler)
+ }
+
+ apply(theme === 'dark')
+ }, [theme])
+
+ // Persist preference
+ useEffect(() => {
+ localStorage.setItem(storageKey, theme)
+ }, [theme, storageKey])
const toggle = useCallback(() => {
- setIsDarkMode((prev) => {
- const next = !prev
+ setTheme((prev) => {
+ const next = CYCLE[(CYCLE.indexOf(prev) + 1) % CYCLE.length]
onChange?.(next)
return next
})
}, [onChange])
+ const icon = { light: , dark: , system: }[theme]
+ const label = { light: lightLabel, dark: darkLabel, system: systemLabel }[theme]
+ const nextTheme = CYCLE[(CYCLE.indexOf(theme) + 1) % CYCLE.length]
+ const ariaLabel = `Switch to ${nextTheme} mode`
+
return (
: }
+ icon={icon}
onClick={toggle}
className={className}
- aria-label={isDarkMode ? 'Switch to light mode' : 'Switch to dark mode'}
+ aria-label={ariaLabel}
>
- {showLabel && (isDarkMode ? lightLabel : darkLabel)}
+ {showLabel && label}
)
}
diff --git a/src/components/ToggleDarkMode/index.ts b/src/components/ToggleDarkMode/index.ts
index 04212f3..615774f 100644
--- a/src/components/ToggleDarkMode/index.ts
+++ b/src/components/ToggleDarkMode/index.ts
@@ -1,2 +1,2 @@
export { ToggleDarkMode } from './ToggleDarkMode'
-export type { ToggleDarkModeProps } from './ToggleDarkMode'
+export type { ToggleDarkModeProps, ThemePreference } from './ToggleDarkMode'