feat(ui): Dark Mode (#2493)

This commit is contained in:
Vithorio Polten
2024-05-24 22:28:39 -03:00
committed by GitHub
parent 2b18e4c73d
commit 4a9130126c
16 changed files with 246 additions and 62 deletions

View File

@@ -1,5 +1,5 @@
<template> <template>
<nav class="navbar navbar-expand-lg navbar-light" style="background-color: #ffc400"> <nav class="navbar navbar-light navbar-expand-lg navbar-background header">
<div class="container-fluid"> <div class="container-fluid">
<a class="navbar-brand" href="/" title="Sunshine"> <a class="navbar-brand" href="/" title="Sunshine">
<img src="/images/logo-sunshine-45.png" height="45" alt="Sunshine"> <img src="/images/logo-sunshine-45.png" height="45" alt="Sunshine">
@@ -28,6 +28,9 @@
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" href="/troubleshooting"><i class="fas fa-fw fa-info"></i> {{ $t('navbar.troubleshoot') }}</a> <a class="nav-link" href="/troubleshooting"><i class="fas fa-fw fa-info"></i> {{ $t('navbar.troubleshoot') }}</a>
</li> </li>
<li class="nav-item">
<ThemeToggle/>
</li>
</ul> </ul>
</div> </div>
</div> </div>
@@ -35,7 +38,10 @@
</template> </template>
<script> <script>
import ThemeToggle from './ThemeToggle.vue'
export default { export default {
components: { ThemeToggle },
created() { created() {
console.log("Header mounted!") console.log("Header mounted!")
}, },
@@ -50,10 +56,33 @@ export default {
</script> </script>
<style> <style>
.nav-link.active { .navbar-background {
background-color: #ffc400
}
.header .nav-link {
color: rgba(0, 0, 0, .65) !important;
}
.header .nav-link.active {
color: rgb(0, 0, 0) !important;
font-weight: 500; font-weight: 500;
} }
.header .nav-link:hover {
color: rgb(0, 0, 0) !important;
font-weight: 500;
}
.header .navbar-toggler {
color: rgba(var(--bs-dark-rgb), .65) !important;
border: var(--bs-border-width) solid rgba(var(--bs-dark-rgb), 0.15) !important;
}
.header .navbar-toggler-icon {
--bs-navbar-toggler-icon-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%2833, 37, 41, 0.75%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e") !important;
}
.form-control::placeholder { .form-control::placeholder {
opacity: 0.5; opacity: 0.5;
} }

View File

@@ -0,0 +1,46 @@
<script setup>
import { loadAutoTheme, setupThemeToggleListener } from './theme'
import { onMounted } from 'vue'
onMounted(() => {
loadAutoTheme()
setupThemeToggleListener()
})
</script>
<template>
<div class="dropdown bd-mode-toggle">
<a class="nav-link dropdown-toggle align-items-center"
id="bd-theme"
type="button"
aria-expanded="false"
data-bs-toggle="dropdown"
aria-label="{{ $t('navbar.toggle_theme') }} ({{ $t('navbar.theme_auto') }})">
<span class="bi my-1 theme-icon-active"><i class="fa-solid fa-circle-half-stroke"></i></span>
<span id="bd-theme-text">{{ $t('navbar.toggle_theme') }}</span>
</a>
<ul class="dropdown-menu dropdown-menu-end" aria-labelledby="bd-theme-text">
<li>
<button type="button" class="dropdown-item d-flex align-items-center" data-bs-theme-value="light" aria-pressed="false">
<i class="bi me-2 theme-icon fas fa-fw fa-solid fa-sun"></i>
{{ $t('navbar.theme_light') }}
</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 me-2 theme-icon fas fa-fw fa-solid fa-moon"></i>
{{ $t('navbar.theme_dark') }}
</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 me-2 theme-icon fas fa-fw fa-solid fa-circle-half-stroke"></i>
{{ $t('navbar.theme_auto') }}
</button>
</li>
</ul>
</div>
</template>
<style scoped>
</style>

View File

@@ -1,5 +1,5 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en" data-bs-theme="auto">
<head> <head>
<%- header %> <%- header %>
@@ -355,10 +355,10 @@
</div> </div>
</body> </body>
<script type="module"> <script type="module">
import { createApp } from 'vue'; import { createApp } from 'vue'
import { initApp } from './init' import { initApp } from './init'
import Navbar from './Navbar.vue' import Navbar from './Navbar.vue'
import {Dropdown} from 'bootstrap' import { Dropdown } from 'bootstrap/dist/js/bootstrap'
const app = createApp({ const app = createApp({
components: { components: {

View File

@@ -1,5 +1,5 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en" data-bs-theme="auto">
<head> <head>
<%- header %> <%- header %>
@@ -13,12 +13,6 @@
.buttons { .buttons {
padding: 1em 0; padding: 1em 0;
} }
.ms-item {
background-color: #ccc;
font-size: 12px;
font-weight: bold;
}
</style> </style>
</head> </head>

View File

@@ -87,3 +87,6 @@ const config = ref(props.config)
</div> </div>
</template> </template>
<style scoped>
</style>

View File

@@ -65,3 +65,11 @@ const fpsIn = ref("")
<div class="form-text">{{ $t('config.res_fps_desc') }}</div> <div class="form-text">{{ $t('config.res_fps_desc') }}</div>
</div> </div>
</template> </template>
<style scoped>
.ms-item {
background-color: var(--bs-dark-bg-subtle);
font-size: 12px;
font-weight: bold;
}
</style>

View File

@@ -1,5 +1,5 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en" data-bs-theme="auto">
<head> <head>
<%- header %> <%- header %>

View File

@@ -1,5 +1,10 @@
import i18n from './locale' import i18n from './locale'
// must import even if not implicitly using here
// https://github.com/aurelia/skeleton-navigation/issues/894
// https://discourse.aurelia.io/t/bootstrap-import-bootstrap-breaks-dropdown-menu-in-navbar/641/9
import 'bootstrap/dist/js/bootstrap'
export function initApp(app, config) { export function initApp(app, config) {
//Wait for locale initialization, then render //Wait for locale initialization, then render
i18n().then(i18n => { i18n().then(i18n => {

View File

@@ -1,5 +1,5 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en" data-bs-theme="auto">
<head> <head>
<%- header %> <%- header %>

View File

@@ -1,5 +1,5 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en" data-bs-theme="auto">
<head> <head>
<%- header %> <%- header %>

View File

@@ -2,3 +2,15 @@
[v-cloak] { [v-cloak] {
display: none; display: none;
} }
[data-bs-theme=dark] .element {
color: var(--bs-primary-text-emphasis);
background-color: var(--bs-primary-bg-subtle);
}
@media (prefers-color-scheme: dark) {
.element {
color: var(--bs-primary-text-emphasis);
background-color: var(--bs-primary-bg-subtle);
}
}

View File

@@ -337,6 +337,10 @@
"home": "Home", "home": "Home",
"password": "Change Password", "password": "Change Password",
"pin": "Pin", "pin": "Pin",
"theme_auto": "Auto",
"theme_dark": "Dark",
"theme_light": "Light",
"toggle_theme": "Theme",
"troubleshoot": "Troubleshooting" "troubleshoot": "Troubleshooting"
}, },
"password": { "password": {

View File

@@ -7,4 +7,3 @@
<link href="@fortawesome/fontawesome-free/css/all.min.css" rel="stylesheet"> <link href="@fortawesome/fontawesome-free/css/all.min.css" rel="stylesheet">
<link href="bootstrap/dist/css/bootstrap.min.css" rel="stylesheet" /> <link href="bootstrap/dist/css/bootstrap.min.css" rel="stylesheet" />
<link href="/assets/css/sunshine.css" rel="stylesheet" /> <link href="/assets/css/sunshine.css" rel="stylesheet" />
<script type="module" src="bootstrap/dist/js/bootstrap.bundle.min.js"></script>

View File

@@ -0,0 +1,84 @@
const getStoredTheme = () => localStorage.getItem('theme')
const setStoredTheme = theme => localStorage.setItem('theme', theme)
export const getPreferredTheme = () => {
const storedTheme = getStoredTheme()
if (storedTheme) {
return storedTheme
}
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'
}
const setTheme = theme => {
if (theme === 'auto') {
document.documentElement.setAttribute(
'data-bs-theme',
(window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light')
)
} else {
document.documentElement.setAttribute('data-bs-theme', theme)
}
}
export const showActiveTheme = (theme, focus = false) => {
const themeSwitcher = document.querySelector('#bd-theme')
if (!themeSwitcher) {
return
}
const themeSwitcherText = document.querySelector('#bd-theme-text')
const activeThemeIcon = document.querySelector('.theme-icon-active i')
const btnToActive = document.querySelector(`[data-bs-theme-value="${theme}"]`)
const classListOfActiveBtn = btnToActive.querySelector('i').classList
document.querySelectorAll('[data-bs-theme-value]').forEach(element => {
element.classList.remove('active')
element.setAttribute('aria-pressed', 'false')
})
btnToActive.classList.add('active')
btnToActive.setAttribute('aria-pressed', 'true')
activeThemeIcon.classList.remove(...activeThemeIcon.classList.values())
activeThemeIcon.classList.add(...classListOfActiveBtn)
const themeSwitcherLabel = `${themeSwitcherText.textContent} (${btnToActive.textContent.trim()})`
themeSwitcher.setAttribute('aria-label', themeSwitcherLabel)
if (focus) {
themeSwitcher.focus()
}
}
export function setupThemeToggleListener() {
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)
})
})
showActiveTheme(getPreferredTheme(), false)
}
export function loadAutoTheme() {
(() => {
'use strict'
setTheme(getPreferredTheme())
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => {
const storedTheme = getStoredTheme()
if (storedTheme !== 'light' && storedTheme !== 'dark') {
setTheme(getPreferredTheme())
}
})
window.addEventListener('DOMContentLoaded', () => {
showActiveTheme(getPreferredTheme())
})
})()
}

View File

@@ -1,5 +1,5 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en" data-bs-theme="auto">
<head> <head>
<%- header %> <%- header %>

View File

@@ -1,5 +1,5 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en" data-bs-theme="auto">
<head> <head>
<%- header %> <%- header %>