({
children,
})
| 3 | import { Button } from "performative-ui"; |
| 4 | |
| 5 | type Theme = "light" | "dark" | "system"; |
| 6 | |
| 7 | /** |
| 8 | * Two consumers, one decision: Tailwind switches on the `.dark` class, |
| 9 | * performative-ui switches on `[data-theme]`. Everything below writes both, |
| 10 | * always, so a component can never render in the opposite palette to the page |
| 11 | * it sits on. |
| 12 | */ |
| 13 | const paint = (dark: boolean) => { |
| 14 | const root = document.documentElement; |
| 15 | root.classList.toggle("dark", dark); |
| 16 | root.setAttribute("data-theme", dark ? "dark" : "light"); |
| 17 | }; |
| 18 | |
| 19 | const systemPrefersDark = () => |
| 20 | window.matchMedia("(prefers-color-scheme: dark)").matches; |
| 21 | |
| 22 | const SunIcon = () => ( |
| 23 | <svg |
| 24 | className="stroke-current w-4 h-4 inline align-middle" |
| 25 | xmlns="http://www.w3.org/2000/svg" |
| 26 | viewBox="0 0 24 24" |
| 27 | fill="none" |
| 28 | stroke-width="2" |
| 29 | stroke-linecap="round" |
| 30 | aria-hidden="true" |
| 31 | > |
| 32 | <circle cx="12" cy="12" r="4" /> |
| 33 | <path d="M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M4.93 19.07l1.41-1.41M17.66 6.34l1.41-1.41" /> |
| 34 | </svg> |
| 35 | ); |
| 36 | |
| 37 | const MoonIcon = () => ( |
| 38 | <svg |
| 39 | className="stroke-current w-4 h-4 inline align-middle" |
| 40 | xmlns="http://www.w3.org/2000/svg" |
| 41 | viewBox="0 0 24 24" |
| 42 | fill="none" |
| 43 | stroke-width="2" |
| 44 | stroke-linecap="round" |
| 45 | stroke-linejoin="round" |
| 46 | aria-hidden="true" |
| 47 | > |
| 48 | <path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z" /> |
| 49 | </svg> |
| 50 | ); |
| 51 | |
| 52 | export const DarkModeSwitcher: React.FC<{ children?: any }> = ({ |
| 53 | children, |
| 54 | }) => { |
| 55 | const [activeTheme, setActiveTheme] = useState<Theme>("system"); |
| 56 | |
| 57 | useEffect(() => { |
| 58 | const savedTheme = (localStorage.getItem("theme") as Theme) || "system"; |
| 59 | setActiveTheme(savedTheme); |
| 60 | paint(savedTheme === "system" ? systemPrefersDark() : savedTheme === "dark"); |
| 61 | |
| 62 | const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)"); |
nothing calls this directly
no test coverage detected