a post-component library for building user-interfaces on the web.
1import { html } from 'dhtml'
2import { createRoot, invalidate } from 'dhtml/client'
3import { computed, effect, signal } from './signals.js'
4
5function getSystemTheme() {
6 const media = window.matchMedia('(prefers-color-scheme: dark)')
7 const matches = signal(media.matches)
8 media.addEventListener('change', e => {
9 matches(e.matches)
10 })
11 return computed(() => (matches() ? 'dark' : 'light'))
12}
13
14function createThemeToggle(preference, systemTheme) {
15 return computed(
16 () => html`
17 <select
18 value=${preference()}
19 onchange=${e => {
20 preference(e.target.value)
21 }}
22 >
23 ${['System', 'Light', 'Dark'].map(
24 t => html` <option value=${t.toLowerCase()}>${t === 'System' ? `System (${systemTheme()})` : t}</option> `,
25 )}
26 </select>
27 `,
28 )
29}
30
31class App {
32 preference = signal('system')
33 systemTheme = getSystemTheme()
34 theme = computed(() => (this.preference() === 'system' ? this.systemTheme() : this.preference))
35
36 #themeToggle1 = createThemeToggle(this.preference, this.systemTheme)
37 #themeToggle2 = createThemeToggle(this.preference, this.systemTheme)
38
39 render() {
40 return html`
41 <main>
42 <h1>Hello World</h1>
43 ${this.#themeToggle1} ${this.#themeToggle2}
44 </main>
45 `
46 }
47}
48
49const app = new App()
50globalThis.app = app
51document.body.addEventListener('keypress', e => {
52 if (e.ctrlKey && e.key === 'i') invalidate(app)
53})
54
55effect(() => {
56 if (app.preference() === 'system') {
57 delete document.documentElement.dataset.theme
58 } else {
59 document.documentElement.dataset.theme = app.preference()
60 }
61})
62
63createRoot(document.body).render(app)