Coverage for src / lexigram / ui / atoms / theme_toggle.py: 100%
6 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-08-10 04:11 +0800
« prev ^ index » next coverage.py v7.13.5, created at 2026-08-10 04:11 +0800
1"""Dark mode theme toggle component."""
3from __future__ import annotations
5from typing import Any
7from lexigram.ui.core.base import Component, el
10class ThemeToggle(Component):
11 """Theme toggle with light/dark/system modes.
13 Persists preference in localStorage. Falls back to system
14 ``prefers-color-scheme`` when no preference is stored.
15 Renders as an icon button that cycles through modes.
16 """
18 def render(self) -> Any:
19 return el(
20 "button",
21 {"x-on:click": "cycleTheme()"},
22 el(
23 "svg",
24 el(
25 "path",
26 d="M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z",
27 ),
28 aria_hidden="true",
29 fill="none",
30 viewBox="0 0 24 24",
31 stroke="currentColor",
32 stroke_width="2",
33 x_show="theme === 'light'",
34 class_="h-5 w-5",
35 ),
36 el(
37 "svg",
38 el("path", d="M21 12.79A9 9 0 1111.21 3 7 7 0 0021 12.79z"),
39 aria_hidden="true",
40 fill="currentColor",
41 viewBox="0 0 24 24",
42 x_show="theme === 'dark'",
43 class_="h-5 w-5",
44 ),
45 el(
46 "svg",
47 el(
48 "path",
49 d="M3 5a2 2 0 012-2h14a2 2 0 012 2v10a2 2 0 01-2 2h-3l-1 3H9l-1-3H5a2 2 0 01-2-2V5z",
50 ),
51 aria_hidden="true",
52 fill="none",
53 viewBox="0 0 24 24",
54 stroke="currentColor",
55 stroke_width="2",
56 x_show="theme === 'system'",
57 class_="h-5 w-5 opacity-50",
58 ),
59 type="button",
60 class_="inline-flex items-center justify-center rounded-md p-2 text-muted-foreground hover:bg-accent hover:text-accent-foreground transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
61 aria_label="Toggle theme",
62 x_data="themeToggle()",
63 x_init="$store.theme = localStorage.getItem('theme')",
64 )