From 6f47a566535d9a4337925e088e2ff7a1ebf4b879 Mon Sep 17 00:00:00 2001 From: Vladimir Rubin Date: Sun, 1 Dec 2024 18:31:23 +0200 Subject: [PATCH] feat: add dark mode --- assets/js/app.js | 34 ++++++----- assets/tailwind.config.js | 1 + assets/vendor/dark_mode.js | 46 +++++++++++++++ lib/exmr_web/components/core_components.ex | 19 ++++--- lib/exmr_web/components/dark_mode.ex | 57 +++++++++++++++++++ .../components/layouts/root.html.heex | 20 +++++-- .../controllers/page_html/home.html.heex | 14 ++--- lib/exmr_web/live/exam_live/index.html.heex | 4 +- .../live/user_forgot_password_live.ex | 2 +- lib/exmr_web/live/user_login_live.ex | 2 +- lib/exmr_web/live/user_registration_live.ex | 6 +- 11 files changed, 165 insertions(+), 40 deletions(-) create mode 100644 assets/vendor/dark_mode.js create mode 100644 lib/exmr_web/components/dark_mode.ex diff --git a/assets/js/app.js b/assets/js/app.js index d5e278a..7e5779f 100644 --- a/assets/js/app.js +++ b/assets/js/app.js @@ -16,29 +16,35 @@ // // Include phoenix_html to handle method=PUT/DELETE in forms and buttons. -import "phoenix_html" +import "phoenix_html"; // Establish Phoenix Socket and LiveView configuration. -import {Socket} from "phoenix" -import {LiveSocket} from "phoenix_live_view" -import topbar from "../vendor/topbar" +import { Socket } from "phoenix"; +import { LiveSocket } from "phoenix_live_view"; +import topbar from "../vendor/topbar"; -let csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content") +import darkModeHook from "../vendor/dark_mode"; +let Hooks = {}; +Hooks.DarkThemeToggle = darkModeHook; + +let csrfToken = document + .querySelector("meta[name='csrf-token']") + .getAttribute("content"); let liveSocket = new LiveSocket("/live", Socket, { - longPollFallbackMs: 2500, - params: {_csrf_token: csrfToken} -}) + longPollFallbackMs: 2500, + params: { _csrf_token: csrfToken }, + hooks: Hooks, +}); // Show progress bar on live navigation and form submits -topbar.config({barColors: {0: "#29d"}, shadowColor: "rgba(0, 0, 0, .3)"}) -window.addEventListener("phx:page-loading-start", _info => topbar.show(300)) -window.addEventListener("phx:page-loading-stop", _info => topbar.hide()) +topbar.config({ barColors: { 0: "#29d" }, shadowColor: "rgba(0, 0, 0, .3)" }); +window.addEventListener("phx:page-loading-start", (_info) => topbar.show(300)); +window.addEventListener("phx:page-loading-stop", (_info) => topbar.hide()); // connect if there are any LiveViews on the page -liveSocket.connect() +liveSocket.connect(); // expose liveSocket on window for web console debug logs and latency simulation: // >> liveSocket.enableDebug() // >> liveSocket.enableLatencySim(1000) // enabled for duration of browser session // >> liveSocket.disableLatencySim() -window.liveSocket = liveSocket - +window.liveSocket = liveSocket; diff --git a/assets/tailwind.config.js b/assets/tailwind.config.js index 2e6b547..749f87c 100644 --- a/assets/tailwind.config.js +++ b/assets/tailwind.config.js @@ -7,6 +7,7 @@ const path = require("path"); module.exports = { content: ["./js/**/*.js", "../lib/exmr_web.ex", "../lib/exmr_web/**/*.*ex"], + darkMode: "class", theme: { extend: { colors: { diff --git a/assets/vendor/dark_mode.js b/assets/vendor/dark_mode.js new file mode 100644 index 0000000..da02a50 --- /dev/null +++ b/assets/vendor/dark_mode.js @@ -0,0 +1,46 @@ +const localStorageKey = "theme"; + +const isDark = () => { + if (localStorage.getItem(localStorageKey) === "dark") return true; + if (localStorage.getItem(localStorageKey) === "light") return false; + return window.matchMedia("(prefers-color-scheme: dark)").matches; +}; + +const setupThemeToggle = () => { + toggleVisibility = (dark) => { + const themeToggleDarkIcon = document.getElementById( + "theme-toggle-dark-icon", + ); + const themeToggleLightIcon = document.getElementById( + "theme-toggle-light-icon", + ); + if (themeToggleDarkIcon == null || themeToggleLightIcon == null) return; + const show = dark ? themeToggleDarkIcon : themeToggleLightIcon; + const hide = dark ? themeToggleLightIcon : themeToggleDarkIcon; + show.classList.remove("hidden", "text-transparent"); + hide.classList.add("hidden", "text-transparent"); + if (dark) { + document.documentElement.classList.add("dark"); + } else { + document.documentElement.classList.remove("dark"); + } + try { + localStorage.setItem(localStorageKey, dark ? "dark" : "light"); + } catch (_err) { } + }; + toggleVisibility(isDark()); + document + .getElementById("theme-toggle") + .addEventListener("click", function() { + toggleVisibility(!isDark()); + }); +}; + +const darkModeHook = { + mounted() { + setupThemeToggle(); + }, + updated() { }, +}; + +export default darkModeHook; diff --git a/lib/exmr_web/components/core_components.ex b/lib/exmr_web/components/core_components.ex index 113ccbf..63fb4a2 100644 --- a/lib/exmr_web/components/core_components.ex +++ b/lib/exmr_web/components/core_components.ex @@ -202,9 +202,12 @@ defmodule ExmrWeb.CoreComponents do def simple_form(assigns) do ~H""" <.form :let={f} for={@for} as={@as} {@rest}> -
+
<%= render_slot(@inner_block, f) %> -
+
<%= render_slot(action, f) %>
@@ -232,7 +235,7 @@ defmodule ExmrWeb.CoreComponents do type={@type} class={[ "phx-submit-loading:opacity-75 rounded-lg bg-zinc-900 hover:bg-zinc-700 py-2 px-3", - "text-sm font-semibold leading-6 text-white active:text-white/80", + "text-sm font-semibold leading-6", @class ]} {@rest} @@ -310,7 +313,7 @@ defmodule ExmrWeb.CoreComponents do ~H"""
-