● Zion Boggan
repos/Docket/components/display-toggle.tsx
zionboggan.com ↗
101 lines · tsx
History for this file →
1
"use client";
2
 
3
import { useEffect, useState } from "react";
4
 
5
 
6
export type DisplayMode = "dark" | "light";
7
 
8
export function DisplayToggle() {
9
  const [mode, setMode] = useState<DisplayMode>("dark");
10
  const [mounted, setMounted] = useState(false);
11
 
12
  useEffect(() => {
13
    setMounted(true);
14
    const stored = localStorage.getItem("docket-display");
15
    const valid: DisplayMode = stored === "light" ? "light" : "dark";
16
    setMode(valid);
17
  }, []);
18
 
19
  function apply(next: DisplayMode) {
20
    setMode(next);
21
    localStorage.setItem("docket-display", next);
22
    const el = document.documentElement;
23
    el.classList.remove("dark", "light", "plain");
24
    if (next !== "dark") el.classList.add(next);
25
    el.dataset.display = next;
26
  }
27
 
28
  const isDark = mode === "dark";
29
  const ariaLabel = isDark ? "Switch to light mode" : "Switch to dark mode";
30
 
31
  return (
32
    <button
33
      type="button"
34
      onClick={() => apply(isDark ? "light" : "dark")}
35
      aria-label={ariaLabel}
36
      title={ariaLabel}
37
      className="focus-ring"
38
      style={{
39
        display: "inline-flex",
40
        alignItems: "center",
41
        justifyContent: "center",
42
        width: "44px",
43
        height: "44px",
44
        border: "1px solid var(--dk-line)",
45
        borderRadius: "8px",
46
        background: "transparent",
47
        color: "var(--dk-ash)",
48
        cursor: "pointer",
49
        transition: "color 0.2s, border-color 0.2s",
50
        flexShrink: 0,
51
        opacity: mounted ? 1 : 0,
52
      }}
53
    >
54
      {isDark ? <MoonIcon /> : <SunIcon />}
55
    </button>
56
  );
57
}
58
 
59
function MoonIcon() {
60
  return (
61
    <svg
62
      aria-hidden="true"
63
      width="18"
64
      height="18"
65
      viewBox="0 0 24 24"
66
      fill="none"
67
      stroke="currentColor"
68
      strokeWidth="1.75"
69
      strokeLinecap="round"
70
      strokeLinejoin="round"
71
    >
72
      <path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z" />
73
    </svg>
74
  );
75
}
76
 
77
function SunIcon() {
78
  return (
79
    <svg
80
      aria-hidden="true"
81
      width="18"
82
      height="18"
83
      viewBox="0 0 24 24"
84
      fill="none"
85
      stroke="currentColor"
86
      strokeWidth="1.75"
87
      strokeLinecap="round"
88
      strokeLinejoin="round"
89
    >
90
      <circle cx="12" cy="12" r="5" />
91
      <line x1="12" y1="1" x2="12" y2="3" />
92
      <line x1="12" y1="21" x2="12" y2="23" />
93
      <line x1="4.22" y1="4.22" x2="5.64" y2="5.64" />
94
      <line x1="18.36" y1="18.36" x2="19.78" y2="19.78" />
95
      <line x1="1" y1="12" x2="3" y2="12" />
96
      <line x1="21" y1="12" x2="23" y2="12" />
97
      <line x1="4.22" y1="19.78" x2="5.64" y2="18.36" />
98
      <line x1="18.36" y1="5.64" x2="19.78" y2="4.22" />
99
    </svg>
100
  );
101
}