Lompat ke konten
dokumentasi.id
Commit 6ea47ab
v2
Star3,1 rb

Kamu membaca versi lama. Halaman ini hanya bisa dibaca.

8

Mode Gelap dan Persistensi Tema

Berkas app-dark.scss tidak menimpa tema terang secara global. Seluruh impor Bootstrap di berkas itu dibungkus selector html[data-bs-theme="dark"] (src/assets/scss/themes/dark/app-dark.scss:11), jadi perpindahan tema cukup mengubah nilai atribut data-bs-theme pada elemen <html>. Nilai atribut itu diurus oleh dua berkas JavaScript dengan pembagian tugas berbeda, dan disimpan di localStorage dengan key theme.

Dua skrip dengan peran berbeda

initTheme.js hanya lima baris dan tidak mendefinisikan fungsi apa pun. Skrip ini mengambil localStorage.getItem('theme'), lalu — bila nilainya ada — memanggil document.documentElement.setAttribute('data-bs-theme', theme) (src/assets/static/js/initTheme.js:1-5). Skrip itu dimuat di dalam <body>, sebelum #app (src/layouts/master.html:17), sehingga atribut sudah terpasang sebelum konten dirender. Skrip ini tidak menyentuh document.body.classList sama sekali.

components/dark.js memegang mesin keadaan sebenarnya. Berkas itu mendefinisikan THEME_KEY = "theme" dan toggleDarkTheme() (src/assets/static/js/components/dark.js:2-10), fungsi setTheme(theme, persist = false) yang menambahkan class ke body, menyetel atribut data-bs-theme, dan menulis localStorage hanya bila persist benar (src/assets/static/js/components/dark.js:17-24), serta initTheme() (src/assets/static/js/components/dark.js:29-48). Bagian akhir berkas memasang listener DOMContentLoaded untuk toggler, lalu memanggil initTheme() pada baris terakhirnya (src/assets/static/js/components/dark.js:50-64). dark.js dimuat di akhir body (src/layouts/master.html:32).

Mesin keadaan tema

stateDiagram-v2
    [*] --> TanpaTema
    TanpaTema --> Terang: initTheme.js memasang atribut dari localStorage
    TanpaTema --> Gelap: matchMedia prefers-color-scheme dark
    Terang --> Gelap: setTheme dark dengan persist true
    Gelap --> Terang: setTheme light dengan persist true
    Gelap --> Gelap: input toggler checked
    Terang --> Terang: input toggler unchecked
    style TanpaTema fill:#dc2626,color:#fff
    style Terang fill:#f59e0b,color:#000
    style Gelap fill:#2d7d46,color:#fff
%% fig: Perpindahan keadaan tema light dan dark
%% desc: Halaman mulai tanpa tema, lalu memilih light atau dark dari localStorage atau preferensi sistem, dan toggler memindahkan keadaan di antara keduanya.
Fig. 1: Perpindahan keadaan tema light dan darkHalaman mulai tanpa tema, lalu memilih light atau dark dari localStorage atau preferensi sistem, dan toggler memindahkan keadaan di antara keduanya.

initTheme() memakai urutan prioritas yang tegas (src/assets/static/js/components/dark.js:29-48). Nilai tersimpan di localStorage menang: return setTheme(storedTheme) dipanggil tanpa argumen persist, jadi nilai tersimpan tidak ditulis ulang. Bila tidak ada nilai tersimpan dan window.matchMedia tidak tersedia, fungsi berhenti. Bila tersedia, ia mendaftarkan listener change pada query (prefers-color-scheme: dark) yang memanggil setTheme(..., true), lalu menyetel tema awal dari mediaQuery.matches, juga dengan persist benar.

⚠️ Cabang terakhir memakai persist = true. Kunjungan pertama tanpa nilai tersimpan langsung menulis theme ke localStorage, sehingga sejak itu nilai tersimpan selalu menang atas preferensi sistem — meski listener change masih bisa memperbaruinya ketika preferensi sistem berubah (src/assets/static/js/components/dark.js:29-48).

Aliran nilainya berjalan dari tiga sumber masukan ke satu atribut, lalu berhenti di CSS.

graph LR
    LS["localStorage theme"] --> INIT["initTheme.js"]
    LS --> SET["setTheme di dark.js"]
    TGL["input#toggle-dark"] --> SET
    INITD["initTheme di dark.js"] --> SET
    MQ["matchMedia prefers-color-scheme dark"] --> INITD
    INIT --> ATTR["atribut data-bs-theme di html"]
    SET --> ATTR
    SET --> CLS["class body dark atau light"]
    ATTR --> CSS["blok selector dark di app-dark.scss"]
    style LS fill:#336791,color:#fff
    style INITD fill:#2d7d46,color:#fff
    style SET fill:#2d7d46,color:#fff
    style ATTR fill:#f59e0b,color:#000
    style CSS fill:#8b5cf6,color:#fff
%% fig: Aliran nilai tema sampai ke CSS
%% desc: localStorage, toggler, dan preferensi sistem masuk ke setTheme atau initTheme, lalu atribut pada html memicu blok selector gelap di app-dark.scss.
Fig. 2: Aliran nilai tema sampai ke CSSlocalStorage, toggler, dan preferensi sistem masuk ke setTheme atau initTheme, lalu atribut pada html memicu blok selector gelap di app-dark.scss.

Penanda yang dipakai bersama

PenandaNilaiDitulis olehDibaca oleh
localStoragekey "theme", nilai "dark" atau "light"setTheme(theme, true) (src/assets/static/js/components/dark.js:17-24)initTheme.js (src/assets/static/js/initTheme.js:1-5) dan initTheme() (src/assets/static/js/components/dark.js:29-48)
atribut <html>data-bs-theme="dark" atau "light"setTheme (src/assets/static/js/components/dark.js:17-24) dan initTheme.js (src/assets/static/js/initTheme.js:1-5)selector SCSS (src/assets/scss/themes/dark/app-dark.scss:11)
class <body>dark atau lightsetTheme lewat classList.add(theme) (src/assets/static/js/components/dark.js:17-24)—
status togglerchecked bila nilai tersimpan "dark"listener DOMContentLoaded (src/assets/static/js/components/dark.js:50-64)—

⚠️ setTheme hanya memanggil classList.add(theme) dan tidak pernah remove. Setelah pengguna berpindah dari gelap ke terang, body menyimpan class dark dan light sekaligus, sehingga urutan cascade di body yang menentukan hasil akhirnya (src/assets/static/js/components/dark.js:17-24).

Toggler di sidebar

Sakelar tema berada di header sidebar: satu div.theme-toggle berisi ikon matahari, sakelar form-check, dan ikon bulan (src/layouts/sidebar.html:7). Elemen yang diikat oleh JavaScript adalah input bertipe checkbox dengan id="toggle-dark" (src/layouts/sidebar.html:23-26).

Listener di dark.js mengeset toggler.checked dari nilai localStorage — bukan dari atribut data-bs-theme — lalu menambahkan listener input yang memanggil setTheme(e.target.checked ? "dark" : "light", true) (src/assets/static/js/components/dark.js:50-64). Jadi setiap interaksi pengguna selalu dipersistensi. Perlu dicatat: toggler tidak memanggil toggleDarkTheme(), fungsi itu hanya tersedia sebagai API. Berkas _mazer-dark.scss mengambil bagian kecil pada tampilan sakelar, yaitu warna .theme-toggle menjadi #9899ac (src/assets/scss/themes/dark/_mazer-dark.scss:31-34).

Lapisan SCSS khusus gelap

Urutan impor app-dark.scss menentukan bahwa variabel gelap dipasang setelah variabel Bootstrap: ./variables, bootstrap/scss/functions, variables, variables-dark, mixins, lalu ./variables-dark dan ./root (src/assets/scss/themes/dark/app-dark.scss:3-9). Setelah blok Bootstrap di dalam selector gelap selesai, berkas itu menambahkan ./../../fonts, ./../../mazer, ./mazer-dark, dan sekali lagi bootstrap/scss/utilities (src/assets/scss/themes/dark/app-dark.scss:52-58).

_variables-dark.scss menyalakan flag $dark-mode: true (src/assets/scss/themes/dark/_variables-dark.scss:3), lalu menimpa peta $theme-colors-light dengan tint-color() dari warna tema — misalnya primary menjadi tint-color($primary, 25%) dan dark menjadi #666 (src/assets/scss/themes/dark/_variables-dark.scss:5-13). Di bagian akhir, berkas itu mengganti variabel Mazer secara langsung: $sidebar-bg: #1e1e2d, $body-bg: #151521, dan $body-color: #c2c2d9 (src/assets/scss/themes/dark/_variables-dark.scss:113-124). _root.scss melengkapi lapisan ini dengan mendeklarasikan ulang CSS custom property di dalam html[data-bs-theme="dark"] (src/assets/scss/themes/dark/_root.scss:1). Rincian fungsi $dark-mode dan peta warna tema dibahas di Arsitektur SCSS dan Variabel Tema.

Titik pemasangan script per layout

LayoutStylesheet temainitTheme.jsdark.js
master.htmlapp.scss lalu app-dark.scss (src/layouts/master.html:9-10)di dalam <body> (src/layouts/master.html:17)akhir <body> (src/layouts/master.html:32)
rtl.htmlapp.rtl.css lalu app-dark.rtl.css (src/layouts/rtl.html:9-10)di dalam <body> (src/layouts/rtl.html:17)akhir <body> (src/layouts/rtl.html:32)
error.htmlhanya app.scss dan pages/error.scss (src/layouts/error.html:8-15)di dalam <body> (src/layouts/error.html:8-15)tidak dimuat

⚠️ error.html memasang initTheme.js tetapi tidak memuat app-dark.scss. Atribut data-bs-theme tetap terpasang, namun tidak ada blok selector gelap yang membacanya, jadi halaman error tidak ikut berubah gelap (src/layouts/error.html:8-15). Versi RTL memakai berkas hasil compile app-dark.rtl.css; proses build-nya dijelaskan di Layout RTL dan Build rtlcss.

Pemakaian lewat CDN mengikuti urutan yang sama: dua stylesheet dimuat berurutan (README.md:71-72), initTheme.js di awal <body>, dan dark.js di akhir <body> (README.md:76-81).