a post-component library for building user-interfaces on the web.
at main 63 lines 1.6 kB view raw
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)