Bläddra i källkod

feat: add color-mode toggle dropdown to default topbar (#6010)

The color-mode toggle was documented at docs/color-mode.html but not
visible by default in the topbar, so multiple users missed it and
opened bugs ('light/dark toggle is missing').

Adds a visible Light / Dark / Auto dropdown to the default topbar next
to the fullscreen toggle, with the wiring JavaScript shipped globally
in _scripts.astro so it works out of the box on every demo page.

Behaviour:

- Three options: Light, Dark, Auto
- Auto follows prefers-color-scheme and live-updates if the OS theme
  changes mid-session
- Persists the user's choice in localStorage under the 'lte-theme' key
- Active option is highlighted in the dropdown with a check mark
- The trigger icon swaps to reflect the current theme: sun (light) /
  moon (dark) / circle-half (auto)
Aigars Silkalns 20 timmar sedan
förälder
incheckning
f9844f7e32
2 ändrade filer med 130 tillägg och 0 borttagningar
  1. 71 0
      src/html/components/_scripts.astro
  2. 59 0
      src/html/components/dashboard/_topbar.astro

+ 71 - 0
src/html/components/_scripts.astro

@@ -48,3 +48,74 @@ document.addEventListener("DOMContentLoaded", function () {
 </script>
 <!--end::OverlayScrollbars Configure-->
 
+<!--begin::Color Mode Toggle (#6010)-->
+<script is:inline>
+;(() => {
+  "use strict"
+
+  const STORAGE_KEY = "lte-theme"
+
+  const getStoredTheme = () => localStorage.getItem(STORAGE_KEY)
+  const setStoredTheme = (theme) => localStorage.setItem(STORAGE_KEY, theme)
+
+  const prefersDark = () =>
+    globalThis.matchMedia("(prefers-color-scheme: dark)").matches
+
+  const getPreferredTheme = () => {
+    const stored = getStoredTheme()
+    if (stored) return stored
+    return prefersDark() ? "dark" : "light"
+  }
+
+  const setTheme = (theme) => {
+    const resolved = theme === "auto" ?
+      (prefersDark() ? "dark" : "light") :
+      theme
+    document.documentElement.setAttribute("data-bs-theme", resolved)
+  }
+
+  setTheme(getPreferredTheme())
+
+  const showActiveTheme = (theme) => {
+    // Highlight the active dropdown option
+    document.querySelectorAll("[data-bs-theme-value]").forEach((el) => {
+      el.classList.remove("active")
+      el.setAttribute("aria-pressed", "false")
+      const check = el.querySelector(".bi-check-lg")
+      if (check) check.classList.add("d-none")
+    })
+    const active = document.querySelector(`[data-bs-theme-value="${theme}"]`)
+    if (active) {
+      active.classList.add("active")
+      active.setAttribute("aria-pressed", "true")
+      const check = active.querySelector(".bi-check-lg")
+      if (check) check.classList.remove("d-none")
+    }
+    // Sync the topbar trigger icon
+    document.querySelectorAll("[data-lte-theme-icon]").forEach((icon) => {
+      icon.classList.toggle("d-none", icon.dataset.lteThemeIcon !== theme)
+    })
+  }
+
+  globalThis
+    .matchMedia("(prefers-color-scheme: dark)")
+    .addEventListener("change", () => {
+      const stored = getStoredTheme()
+      if (!stored || stored === "auto") setTheme(getPreferredTheme())
+    })
+
+  document.addEventListener("DOMContentLoaded", () => {
+    showActiveTheme(getPreferredTheme())
+    document.querySelectorAll("[data-bs-theme-value]").forEach((toggle) => {
+      toggle.addEventListener("click", () => {
+        const theme = toggle.getAttribute("data-bs-theme-value")
+        setStoredTheme(theme)
+        setTheme(theme)
+        showActiveTheme(theme)
+      })
+    })
+  })
+})()
+</script>
+<!--end::Color Mode Toggle-->
+

+ 59 - 0
src/html/components/dashboard/_topbar.astro

@@ -166,6 +166,65 @@ const deploymentPath = depth === 0 ? './' : '../'.repeat(depth);
       </li>
       <!--end::Fullscreen Toggle-->
 
+      <!--begin::Color Mode Toggle (#6010)-->
+      <li class="nav-item dropdown">
+        <a
+          class="nav-link"
+          href="#"
+          id="bd-theme"
+          aria-label="Toggle color scheme"
+          data-bs-toggle="dropdown"
+          aria-expanded="false"
+        >
+          <i class="bi bi-sun-fill" data-lte-theme-icon="light"></i>
+          <i class="bi bi-moon-fill d-none" data-lte-theme-icon="dark"></i>
+          <i class="bi bi-circle-half d-none" data-lte-theme-icon="auto"></i>
+        </a>
+        <ul
+          class="dropdown-menu dropdown-menu-end"
+          aria-labelledby="bd-theme"
+          style="--bs-dropdown-min-width: 8rem;"
+        >
+          <li>
+            <button
+              type="button"
+              class="dropdown-item d-flex align-items-center"
+              data-bs-theme-value="light"
+              aria-pressed="false"
+            >
+              <i class="bi bi-sun-fill me-2"></i>
+              Light
+              <i class="bi bi-check-lg ms-auto d-none"></i>
+            </button>
+          </li>
+          <li>
+            <button
+              type="button"
+              class="dropdown-item d-flex align-items-center"
+              data-bs-theme-value="dark"
+              aria-pressed="false"
+            >
+              <i class="bi bi-moon-fill me-2"></i>
+              Dark
+              <i class="bi bi-check-lg ms-auto d-none"></i>
+            </button>
+          </li>
+          <li>
+            <button
+              type="button"
+              class="dropdown-item d-flex align-items-center active"
+              data-bs-theme-value="auto"
+              aria-pressed="true"
+            >
+              <i class="bi bi-circle-half me-2"></i>
+              Auto
+              <i class="bi bi-check-lg ms-auto d-none"></i>
+            </button>
+          </li>
+        </ul>
+      </li>
+      <!--end::Color Mode Toggle-->
+
       <!--begin::User Menu Dropdown-->
       <li class="nav-item dropdown user-menu">
         <a href="#" class="nav-link dropdown-toggle" data-bs-toggle="dropdown">