mirror of
https://github.com/freedombox/FreedomBox.git
synced 2026-05-13 10:30:16 +00:00
ui: Implement a toggle menu for setting dark mode
- Add a toggle menu for selecting the color scheme. JS code largely taken from Bootstrap documentation and slightly customized. - Use local storage to store the setting for dark/light/auto. Default to auto which means browser level preference is picked up (which could be system level preference). Tests: - Appearance of the toggle menu is consistent. Check box is shown on the currently selected value. - Deleting the local storage value reverts the preference to browser set value. - Menu is collapsed at smaller screen sizes. Appearance and functionality as expected. Signed-off-by: Sunil Mohan Adapa <sunil@medhas.org> Reviewed-by: James Valleroy <jvalleroy@mailbox.org>
This commit is contained in:
parent
0419eb02cf
commit
00a69108dd
5
debian/copyright
vendored
5
debian/copyright
vendored
@ -38,6 +38,11 @@ Copyright: Marie Van den Broeck (https://thenounproject.com/marie49/)
|
|||||||
Comment: https://thenounproject.com/icon/162372/
|
Comment: https://thenounproject.com/icon/162372/
|
||||||
License: CC-BY-SA-3.0
|
License: CC-BY-SA-3.0
|
||||||
|
|
||||||
|
Files: static/themes/default/js/color-modes.js
|
||||||
|
Copyright: 2011-2025 The Bootstrap Authors
|
||||||
|
Comment: https://getbootstrap.com/docs/5.3/customize/color-modes/
|
||||||
|
License: CC-BY-3.0
|
||||||
|
|
||||||
Files: plinth/modules/bepasty/static/icons/bepasty.svg
|
Files: plinth/modules/bepasty/static/icons/bepasty.svg
|
||||||
Copyright: (c) 2014 by the Bepasty Team, see the AUTHORS file.
|
Copyright: (c) 2014 by the Bepasty Team, see the AUTHORS file.
|
||||||
Comment: https://github.com/bepasty/bepasty-server/blob/master/src/bepasty/static/app/bepasty.svg
|
Comment: https://github.com/bepasty/bepasty-server/blob/master/src/bepasty/static/app/bepasty.svg
|
||||||
|
|||||||
@ -58,6 +58,9 @@
|
|||||||
<link rel="stylesheet" href="{% static user_css %}"/>
|
<link rel="stylesheet" href="{% static user_css %}"/>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
<!-- This script is not loaded in defer mode because it needs to run before
|
||||||
|
page is rendered -->
|
||||||
|
<script type="text/javascript" src="{% static 'theme/js/color-modes.js' %}"></script>
|
||||||
<!-- Local link to system Bootstrap JS -->
|
<!-- Local link to system Bootstrap JS -->
|
||||||
<script type="text/javascript" src="{% static '/javascript/popperjs2/popper.min.js' %}" defer></script>
|
<script type="text/javascript" src="{% static '/javascript/popperjs2/popper.min.js' %}" defer></script>
|
||||||
<script type="text/javascript" src="{% static '/javascript/bootstrap5/js/bootstrap.bundle.min.js' %}" defer></script>
|
<script type="text/javascript" src="{% static '/javascript/bootstrap5/js/bootstrap.bundle.min.js' %}" defer></script>
|
||||||
@ -148,6 +151,8 @@
|
|||||||
|
|
||||||
{% include "notifications-dropdown.html" %}
|
{% include "notifications-dropdown.html" %}
|
||||||
|
|
||||||
|
{% include "theme-menu.html" %}
|
||||||
|
|
||||||
{% include "help-menu.html" %}
|
{% include "help-menu.html" %}
|
||||||
|
|
||||||
<li id="id_user_menu" class="nav-item dropdown">
|
<li id="id_user_menu" class="nav-item dropdown">
|
||||||
|
|||||||
46
plinth/templates/theme-menu.html
Normal file
46
plinth/templates/theme-menu.html
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
{% comment %}
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
{% endcomment %}
|
||||||
|
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
<li id="id_theme_menu" class="dropdown nav-item">
|
||||||
|
<a href="#" title="{% trans "Toggle theme (auto)" %}"
|
||||||
|
class="nav-link dropdown-toggle" data-bs-toggle="dropdown"
|
||||||
|
role="button" aria-expanded="false" aria-haspopup="true"
|
||||||
|
id="id_theme_menu_link">
|
||||||
|
<span class="fa fa-adjust nav-icon" id="id_active_theme_icon"></span>
|
||||||
|
<span class="nav-text d-md-none" id="id_toggle_theme_text">
|
||||||
|
{% trans "Toggle theme" %}
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
<ul class="dropdown-menu" role="menu" aria-labelledby="id_theme_menu_link">
|
||||||
|
<li>
|
||||||
|
<button type="button" class="dropdown-item d-flex align-items-center"
|
||||||
|
data-bs-theme-value="light" data-bs-icon-value="fa-sun"
|
||||||
|
aria-pressed="false">
|
||||||
|
<span class="fa fa-sun nav-icon me-2"></span>
|
||||||
|
{% trans "Light" %}
|
||||||
|
<span class="fa fa-check nav-icon ms-auto d-none"></span>
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<button type="button" class="dropdown-item d-flex align-items-center"
|
||||||
|
data-bs-theme-value="dark" data-bs-icon-value="fa-moon"
|
||||||
|
aria-pressed="false">
|
||||||
|
<span class="fa fa-moon nav-icon me-2"></span>
|
||||||
|
{% trans "Dark" %}
|
||||||
|
<span class="fa fa-check nav-icon ms-auto d-none"></span>
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<button type="button" class="dropdown-item d-flex align-items-center"
|
||||||
|
data-bs-theme-value="auto" data-bs-icon-value="fa-adjust"
|
||||||
|
aria-pressed="true">
|
||||||
|
<span class="fa fa-adjust nav-icon me-2"></span>
|
||||||
|
{% trans "Auto" %}
|
||||||
|
<span class="fa fa-check nav-icon ms-auto d-none"></span>
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
@ -522,6 +522,10 @@ footer {
|
|||||||
border-bottom: var(--freedombox-navbar-color) 3px solid;
|
border-bottom: var(--freedombox-navbar-color) 3px solid;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.main-header .dropdown-menu .active .fa-check {
|
||||||
|
display: block !important;
|
||||||
|
}
|
||||||
|
|
||||||
/* Breadcrumbs */
|
/* Breadcrumbs */
|
||||||
.breadcrumb-item {
|
.breadcrumb-item {
|
||||||
--bs-breadcrumb-divider: ">";
|
--bs-breadcrumb-divider: ">";
|
||||||
|
|||||||
96
static/themes/default/js/color-modes.js
Normal file
96
static/themes/default/js/color-modes.js
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
// SPDX-License-Identifier: CC-BY-3.0
|
||||||
|
/*
|
||||||
|
This file is part of FreedomBox. Color mode toggler for Bootstrap's docs
|
||||||
|
(https://getbootstrap.com/). Copyright 2011-2025 The Bootstrap Authors.
|
||||||
|
|
||||||
|
@licstart The following is the entire license notice for the
|
||||||
|
JavaScript code in this page.
|
||||||
|
|
||||||
|
Licensed under the Creative Commons Attribution 3.0 Unported License.
|
||||||
|
|
||||||
|
@licend The above is the entire license notice
|
||||||
|
for the JavaScript code in this page.
|
||||||
|
*/
|
||||||
|
|
||||||
|
(() => {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const getStoredTheme = () => localStorage.getItem('theme');
|
||||||
|
const setStoredTheme = theme => localStorage.setItem('theme', theme);
|
||||||
|
|
||||||
|
const getBrowserTheme = () => {
|
||||||
|
return window.matchMedia('(prefers-color-scheme: dark)')
|
||||||
|
.matches ? 'dark' : 'light';
|
||||||
|
};
|
||||||
|
|
||||||
|
const getPreferredTheme = () => {
|
||||||
|
const storedTheme = getStoredTheme();
|
||||||
|
if (storedTheme) {
|
||||||
|
return storedTheme;
|
||||||
|
}
|
||||||
|
|
||||||
|
return getBrowserTheme();
|
||||||
|
};
|
||||||
|
|
||||||
|
const setTheme = (theme) => {
|
||||||
|
if (theme === 'auto') {
|
||||||
|
theme = getBrowserTheme();
|
||||||
|
}
|
||||||
|
document.documentElement.setAttribute('data-bs-theme', theme);
|
||||||
|
};
|
||||||
|
|
||||||
|
setTheme(getPreferredTheme());
|
||||||
|
|
||||||
|
const showActiveTheme = (theme, focus = false) => {
|
||||||
|
const themeSwitcher = document.querySelector('#id_theme_menu_link');
|
||||||
|
|
||||||
|
if (!themeSwitcher) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const themeSwitcherText = document.querySelector('#id_toggle_theme_text');
|
||||||
|
const activeThemeIcon = document.querySelector('#id_active_theme_icon');
|
||||||
|
const btnToActive = document.querySelector(`[data-bs-theme-value="${theme}"]`);
|
||||||
|
const iconOfActiveBtn = btnToActive.dataset.bsIconValue;
|
||||||
|
|
||||||
|
document.querySelectorAll('[data-bs-theme-value]').forEach(element => {
|
||||||
|
element.classList.remove('active');
|
||||||
|
element.setAttribute('aria-pressed', 'false');
|
||||||
|
const iconOfBtn = element.dataset.bsIconValue;
|
||||||
|
if (activeThemeIcon.classList.contains(iconOfBtn)) {
|
||||||
|
activeThemeIcon.classList.remove(iconOfBtn);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
btnToActive.classList.add('active');
|
||||||
|
btnToActive.setAttribute('aria-pressed', 'true');
|
||||||
|
activeThemeIcon.classList.add(iconOfActiveBtn);
|
||||||
|
const themeSwitcherLabel = `${themeSwitcherText.textContent} (${btnToActive.dataset.bsThemeValue})`;
|
||||||
|
themeSwitcher.setAttribute('title', themeSwitcherLabel);
|
||||||
|
|
||||||
|
if (focus) {
|
||||||
|
themeSwitcher.focus();
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => {
|
||||||
|
const storedTheme = getStoredTheme();
|
||||||
|
if (storedTheme !== 'light' && storedTheme !== 'dark') {
|
||||||
|
setTheme(getPreferredTheme());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
window.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, true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
})();
|
||||||
Loading…
x
Reference in New Issue
Block a user