src\utils\context-utils.ts

import type { Context } from 'react';
import { useContext } from 'react';

export function useContextOrThrow<T>(context: Context<T | undefined>): T {
  const theContext = useContext(context);

  if (!theContext) {
    throw new Error(`The hook is not used within the context.`);
  }

  return theContext;
}

src\utils\darkmode-utils.ts

import { createContext } from 'react';
import { useContextOrThrow } from './context-utils.ts';

export const Darkmode = {
  Light: 'light',
  Dark: 'dark',
} as const;

export type Darkmode = (typeof Darkmode)[keyof typeof Darkmode];

type DarkmodeContextType = {
  theme: Darkmode;
  toggleTheme(): void;
};

export const DarkmodeContext = createContext<DarkmodeContextType | undefined>(
  undefined
);

export const useDarkmodeContext = (): DarkmodeContextType =>
  useContextOrThrow(DarkmodeContext);

src\utils\DarkmodeProvider.tsx

import type { FC, PropsWithChildren } from 'react';
import { useEffect, useState } from 'react';
import { Darkmode, DarkmodeContext } from './darkmode-utils.ts';

export const DarkmodeProvider: FC<PropsWithChildren> = ({ children }) => {
  const localStorageTheme = localStorage.getItem('theme') as Darkmode;
  const defaultTheme = window.matchMedia('(prefers-color-scheme: dark)').matches
    ? Darkmode.Dark
    : Darkmode.Light;
  const [theme, setTheme] = useState(localStorageTheme || defaultTheme);

  useEffect(() => {
    if (defaultTheme) {
      setTheme(theme);
      document.documentElement.setAttribute('data-theme', theme);
    }
  }, [defaultTheme, theme]);

  const toggleTheme = () => {
    const newTheme = theme === Darkmode.Light ? Darkmode.Dark : Darkmode.Light;
    setTheme(newTheme);
    localStorage.setItem('theme', newTheme);
    document.documentElement.setAttribute('data-theme', newTheme);
  };

  return (
    <DarkmodeContext.Provider value={{ theme, toggleTheme }}>
      {children}
    </DarkmodeContext.Provider>
  );
};

src\components\DarkmodeToggle.tsx

import type { FC } from 'react';
import { useState } from 'react';
import MoonIcon from '../../assets/MoonIcon';
import SunIcon from '../../assets/SunIcon';
import { Darkmode, useDarkmodeContext } from '../utils/darkmode-utils.ts';
import styles from './DarkmodeToggle.module.less';

interface DarkmodeToggleProps {
  className?: string;
}

const DarkmodeToggle: FC<DarkmodeToggleProps> = ({ className }) => {
  const { theme, toggleTheme } = useDarkmodeContext();
  const [isToggled, setIsToggled] = useState(false);

  const handleClick = () => {
    toggleTheme();
    setIsToggled(!isToggled);
  };

  return (
    <button
      onClick={handleClick}
      aria-label="Toggle dark mode"
      className={`${styles.darkmodeToggle} ${className}`}
    >
      {theme === Darkmode.Dark ? <SunIcon /> : <MoonIcon />}
    </button>
  );
};

export default DarkmodeToggle;

src\components\DarkmodeToggle.module.less

.darkmodeToggle {
  background-color: unset;
  min-width: 40px;
  height: 40px;
  display: flex;

  & svg {
    width: 20px;
    height: auto;
    margin: auto;
  }
}

src\App.tsx

import MainPage from './page/MainPage.tsx';
import { DarkmodeProvider } from './utils/DarkmodeProvider';

import './App.less';

function App() {
  return (
    <DarkmodeProvider>
      <MainPage />;
    </DarkmodeProvider>
  );
}

export default App;

src\pages\MainPage.tsx

import { useState } from 'react';
import List from '../components/List.tsx';
import ToggleView from '../components/ToggleView.tsx';
import DarkmodeToggle from '../components/DarkmodeToggle';
import styles from './MainPage.module.less';

const MainPage = () => {
  const [view, setView] = useState<'characters' | 'episodes' | 'locations'>(
    'characters'
  );

  return (
    <main>
      <div className={styles.buttonContainer}>
        <DarkmodeToggle />
      </div>
      <div className={styles.starBackground}></div>
      <div className={styles.pageContainer}>
        <h1>WUBBA LUBBA FETCH FETCH</h1>
        <p>Morty, we’re fetching data, not feelings</p>
        <ToggleView view={view} onChange={setView} />
        <List view={view} />
      </div>
    </main>
  );
};

export default MainPage;