All files / Bandstand/src/components ThemeToggle.tsx

100% Statements 56/56
100% Branches 7/7
100% Functions 3/3
100% Lines 56/56

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    1x 1x   1x 1x 1x 1x 1x 1x 1x 1x 1x 1x           7x 7x 7x 7x 7x 7x 3x 3x 3x 7x 7x 7x   7x 7x   7x   7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 1x 1x 1x 1x   6x 6x 1x 1x 1x     5x 5x 5x 5x 5x   5x  
'use client'
 
import {useStoredState} from '@/components/useStoredState'
import {THEME_KEY, applyTheme, type ThemePref} from '@/lib/theme'
 
const NEXT: Record<ThemePref, ThemePref> = {
  system: 'light',
  light: 'dark',
  dark: 'system',
}
const NAME: Record<ThemePref, string> = {
  system: 'Auto (matches device)',
  light: 'Light',
  dark: 'Dark',
}
 
/**
 * One tap cycles Auto → Light → Dark. The icon shows the current choice.
 * Remembered on this device only.
 */
export default function ThemeToggle() {
  const [pref, setPref] = useStoredState<ThemePref>(THEME_KEY, 'system')
  return (
    <button
      type="button"
      onClick={() => {
        setPref(NEXT[pref])
        applyTheme(NEXT[pref])
      }}
      aria-label={`Appearance: ${NAME[pref]}. Switch to ${NAME[NEXT[pref]]}`}
      title={`Appearance: ${NAME[pref]}`}
      className="flex h-11 w-11 items-center justify-center rounded-full text-muted hover:text-text"
    >
      <ThemeIcon pref={pref} />
    </button>
  )
}
 
function ThemeIcon({pref}: {pref: ThemePref}) {
  const common = {
    width: 22,
    height: 22,
    viewBox: '0 0 24 24',
    fill: 'none',
    stroke: 'currentColor',
    strokeWidth: 2,
    strokeLinecap: 'round' as const,
    strokeLinejoin: 'round' as const,
    'aria-hidden': true,
  }
  if (pref === 'light')
    return (
      <svg {...common}>
        <circle cx="12" cy="12" r="4" />
        <path d="M12 2v2M12 20v2M4.9 4.9l1.4 1.4M17.7 17.7l1.4 1.4M2 12h2M20 12h2M4.9 19.1l1.4-1.4M17.7 6.3l1.4-1.4" />
      </svg>
    )
  if (pref === 'dark')
    return (
      <svg {...common}>
        <path d="M20 14.5A8 8 0 1 1 9.5 4a6.5 6.5 0 0 0 10.5 10.5z" />
      </svg>
    )
  // Auto: a circle half light, half dark
  return (
    <svg {...common}>
      <circle cx="12" cy="12" r="8.5" />
      <path d="M12 3.5a8.5 8.5 0 0 1 0 17z" fill="currentColor" />
    </svg>
  )
}