tangled
alpha
login
or
join now
finxol.io
/
bookmarker
0
fork
atom
A very simple bookmarking webapp
bookmarker.finxol.deno.net/
0
fork
atom
overview
issues
pulls
pipelines
feat: add theme switcher
finxol.io
1 month ago
aafcd9dd
aa0f504e
verified
This commit was signed with the committer's
known signature
.
finxol.io
SSH Key Fingerprint:
SHA256:olFE3asYdoBMScuJOt60UxXdJ0RFdGv5kVKrdOtIcPI=
1/1
deploy.yaml
success
10s
+91
-19
3 changed files
expand all
collapse all
unified
split
src
components
ThemeSwitcher.tsx
routes
__root.tsx
root.css
+62
src/components/ThemeSwitcher.tsx
···
1
1
+
import { createEffect, createSignal, onCleanup, onMount } from "solid-js"
2
2
+
import { MoonIcon, SunIcon } from "lucide-solid"
3
3
+
4
4
+
type Theme = "light" | "dark"
5
5
+
6
6
+
const STORAGE_KEY = "theme"
7
7
+
8
8
+
function getSystemTheme(): Theme {
9
9
+
return window.matchMedia("(prefers-color-scheme: dark)").matches
10
10
+
? "dark"
11
11
+
: "light"
12
12
+
}
13
13
+
14
14
+
function getInitialTheme(): Theme {
15
15
+
const stored = localStorage.getItem(STORAGE_KEY)
16
16
+
if (stored === "light" || stored === "dark") {
17
17
+
return stored
18
18
+
}
19
19
+
return getSystemTheme()
20
20
+
}
21
21
+
22
22
+
export function ThemeSwitcher() {
23
23
+
const [theme, setTheme] = createSignal<Theme>(getInitialTheme())
24
24
+
25
25
+
// Reactively apply the theme to <html> and persist to localStorage
26
26
+
createEffect(() => {
27
27
+
const current = theme()
28
28
+
document.documentElement.dataset.theme = current
29
29
+
localStorage.setItem(STORAGE_KEY, current)
30
30
+
})
31
31
+
32
32
+
// Listen for system preference changes when no explicit choice is stored
33
33
+
onMount(() => {
34
34
+
const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)")
35
35
+
36
36
+
const handleChange = (e: MediaQueryListEvent) => {
37
37
+
if (!localStorage.getItem(STORAGE_KEY)) {
38
38
+
setTheme(e.matches ? "dark" : "light")
39
39
+
}
40
40
+
}
41
41
+
42
42
+
mediaQuery.addEventListener("change", handleChange)
43
43
+
onCleanup(() => mediaQuery.removeEventListener("change", handleChange))
44
44
+
})
45
45
+
46
46
+
const toggleTheme = () => {
47
47
+
setTheme((prev) => (prev === "light" ? "dark" : "light"))
48
48
+
}
49
49
+
50
50
+
return (
51
51
+
<button
52
52
+
type="button"
53
53
+
onClick={toggleTheme}
54
54
+
title={`Switch to ${theme() === "light" ? "dark" : "light"} mode`}
55
55
+
class="button-icon button-ghost"
56
56
+
>
57
57
+
{theme() === "light"
58
58
+
? <MoonIcon size={24} />
59
59
+
: <SunIcon size={24} />}
60
60
+
</button>
61
61
+
)
62
62
+
}
+15
-11
src/routes/__root.tsx
···
1
1
import { createRootRoute, Outlet } from "@tanstack/solid-router"
2
2
import { useQuery } from "@tanstack/solid-query"
3
3
+
import { Link } from "@tanstack/solid-router"
4
4
+
import { BookmarkIcon, LogInIcon } from "lucide-solid"
5
5
+
import { Show } from "solid-js"
3
6
import { client } from "../apiclient.ts"
4
4
-
import { Link } from "@tanstack/solid-router"
7
7
+
import { ThemeSwitcher } from "../components/ThemeSwitcher.tsx"
5
8
import "./main.css"
6
9
import "./root.css"
7
7
-
import { BookmarkIcon, LogInIcon } from "lucide-solid"
8
8
-
import { Show } from "solid-js"
9
10
10
11
export const Route = createRootRoute({
11
12
component: RootComponent,
···
35
36
</Link>
36
37
</h1>
37
38
</nav>
38
38
-
<Show when={!query.isPending && query.data}>
39
39
-
<Link to="/account">
40
40
-
<img
41
41
-
src={query.data?.avatar ?? undefined}
42
42
-
alt={query.data?.name}
43
43
-
/>
44
44
-
</Link>
45
45
-
</Show>
39
39
+
<div>
40
40
+
<ThemeSwitcher />
41
41
+
<Show when={!query.isPending && query.data}>
42
42
+
<Link to="/account">
43
43
+
<img
44
44
+
src={query.data?.avatar ?? undefined}
45
45
+
alt={query.data?.name}
46
46
+
/>
47
47
+
</Link>
48
48
+
</Show>
49
49
+
</div>
46
50
</header>
47
51
{!query.isPending &&
48
52
(query.data ? <Outlet /> : <LoggedOutPage />)}
+14
-8
src/routes/root.css
···
44
44
}
45
45
}
46
46
47
47
-
img {
48
48
-
--size: 2.25rem;
49
49
-
border-radius: 50%;
50
50
-
object-fit: cover;
51
51
-
border: 2px solid oklch(from var(--primary) l c h / 0.2);
52
52
-
transition: border-color 0.15s ease;
47
47
+
& > div {
48
48
+
display: flex;
49
49
+
align-items: center;
50
50
+
gap: var(--spacing);
53
51
54
54
-
&:hover {
55
55
-
border-color: var(--primary);
52
52
+
img {
53
53
+
--size: 2.25rem;
54
54
+
border-radius: 50%;
55
55
+
object-fit: cover;
56
56
+
border: 2px solid oklch(from var(--primary) l c h / 0.2);
57
57
+
transition: border-color 0.15s ease;
58
58
+
59
59
+
&:hover {
60
60
+
border-color: var(--primary);
61
61
+
}
56
62
}
57
63
}
58
64
}