Bin
2025-12-17 1d710f844b65d9bfdf986a71a3b924cd70598a41
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
73
74
75
76
77
78
79
80
import { clsx } from "clsx";
import styles from "./ThemeToggle.module.scss";
import { useCallback, useEffect, useMemo, useState } from "react";
import { ReactComponent as Sun } from "./icons/sun.svg";
import { ReactComponent as Moon } from "./icons/moon.svg";
import { Badge } from "@humansignal/ui";
import { atom, useSetAtom } from "jotai";
 
interface ThemeToggleProps {
  className?: string;
}
 
const THEME_OPTIONS = ["Auto", "Light", "Dark"];
const PREFERRED_COLOR_SCHEME_KEY = "preferred-color-scheme";
 
export const getCurrentTheme = () => {
  const themeSelection = window.localStorage.getItem(PREFERRED_COLOR_SCHEME_KEY) ?? THEME_OPTIONS[0];
  return themeSelection === THEME_OPTIONS[0]
    ? window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches
      ? "Dark"
      : "Light"
    : themeSelection;
};
export const themeAtom = atom<string>(getCurrentTheme());
export const ThemeToggle: React.FC<ThemeToggleProps> = ({ className }) => {
  const presetTheme = window.localStorage.getItem(PREFERRED_COLOR_SCHEME_KEY) ?? THEME_OPTIONS[1];
  const [theme, setTheme] = useState(presetTheme);
  const systemMode = useMemo(
    () => (window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches ? "Dark" : "Light"),
    [],
  );
  const [appliedTheme, setAppliedTheme] = useState(presetTheme === "Auto" ? systemMode : presetTheme);
  const setThemeAtom = useSetAtom(themeAtom);
 
  useEffect(() => {
    if (!appliedTheme) return;
    document.documentElement.setAttribute("data-color-scheme", appliedTheme.toLowerCase());
  }, [appliedTheme]);
 
  const themeChanged = useCallback(() => {
    const length = THEME_OPTIONS.length;
    const index = (THEME_OPTIONS.indexOf(theme) + 1) % length;
    const nextTheme = THEME_OPTIONS[index];
 
    window.localStorage.setItem(PREFERRED_COLOR_SCHEME_KEY, nextTheme);
    setTheme(nextTheme);
    const newTheme = nextTheme === "Auto" ? systemMode : nextTheme;
    setAppliedTheme(newTheme);
    setThemeAtom(newTheme);
  }, [theme]);
 
  const themeLabel = useMemo(
    () => THEME_OPTIONS.find((option) => option.toLowerCase() === theme.toLowerCase()),
    [theme],
  );
 
  return (
    <button
      className={clsx(styles.themeToggle, className, {
        [styles.dark]: appliedTheme === "Dark",
        [styles.light]: appliedTheme === "Light",
      })}
      onClick={themeChanged}
      type="button"
    >
      <div className={clsx(styles.themeToggle__icon)}>
        <div className={clsx(styles.animationWrapper)}>
          <Moon className={clsx(styles.moon)} />
          <Sun className={clsx(styles.sun)} />
        </div>
      </div>
      <span className={clsx(styles.themeToggle__label)}>{themeLabel}</span>
      <Badge variant="beta" className={styles.betaBadge}>
        Beta
      </Badge>
    </button>
  );
};
 
export default ThemeToggle;