feat(web): implement automatic theme switch for light/dark (#2046)

* Implement an automatic theme

The "auto" theme will automatically switch between "dark" and "light"
depending on user preference. This allows for automatic dark mode.

* fix(configuration): allow the "auto" theme when validating

The new theme "auto" was not allowed to be used in a configuration file.

* docs: clarify what critera controls the automatic theme

How the "auto" theme functioned was unclear.

* docs: typeset themes as code

* fix(web): apply useEffector to media query watch

* docs: add technical details

* fix(configuration): resolve merge conflicts
pull/2090/head^2
Alex Gustafsson 2021-06-17 08:42:03 +02:00 committed by GitHub
parent 78a9faacfe
commit 150116a172
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 23 additions and 7 deletions

View File

@ -31,3 +31,5 @@ There are currently 3 available themes for Authelia:
* light (default)
* dark
* grey
To enable automatic switching between themes, you can set `theme` to `auto`. The theme will be set to either `dark` or `light` depending on the user's system preference which is determined using media queries. To read more technical details about the media queries used, read the [MDN](https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-color-scheme).

View File

@ -17,7 +17,7 @@ port: 9091
## They should be in base64 format, and have one of the following extensions: *.cer, *.crt, *.pem.
# certificates_directory: /config/certificates
## The theme to display: light, dark, grey.
## The theme to display: light, dark, grey, auto.
theme: light
##

View File

@ -13,8 +13,9 @@ func ValidateTheme(configuration *schema.Configuration, validator *schema.Struct
configuration.Theme = "light"
}
validThemes := regexp.MustCompile("light|dark|grey")
validThemes := regexp.MustCompile("light|dark|grey|auto")
if !validThemes.MatchString(configuration.Theme) {
validator.Push(fmt.Errorf("Theme: %s is not valid, valid themes are: \"light\", \"dark\" or \"grey\"", configuration.Theme))
validator.Push(fmt.Errorf("Theme: %s is not valid, valid themes are: \"light\", \"dark\", \"grey\" or \"auto\"", configuration.Theme))
}
}

View File

@ -36,7 +36,7 @@ func (suite *Theme) TestShouldRaiseErrorWhenInvalidThemeProvided() {
suite.Assert().False(suite.validator.HasWarnings())
suite.Require().Len(suite.validator.Errors(), 1)
suite.Assert().EqualError(suite.validator.Errors()[0], "Theme: invalid is not valid, valid themes are: \"light\", \"dark\" or \"grey\"")
suite.Assert().EqualError(suite.validator.Errors()[0], "Theme: invalid is not valid, valid themes are: \"light\", \"dark\", \"grey\" or \"auto\"")
}
func TestThemes(t *testing.T) {

View File

@ -1,4 +1,4 @@
import React, { useState } from "react";
import React, { useState, useEffect } from "react";
import { config as faConfig } from "@fortawesome/fontawesome-svg-core";
import { CssBaseline, ThemeProvider } from "@material-ui/core";
@ -37,6 +37,8 @@ function Theme() {
return themes.Dark;
case "grey":
return themes.Grey;
case "auto":
return window.matchMedia("(prefers-color-scheme: dark)").matches ? themes.Dark : themes.Light;
default:
return themes.Light;
}
@ -44,9 +46,20 @@ function Theme() {
const App: React.FC = () => {
const [notification, setNotification] = useState(null as Notification | null);
const [theme, setTheme] = useState(Theme());
useEffect(() => {
if (getTheme() === "auto") {
const query = window.matchMedia("(prefers-color-scheme: dark)");
// MediaQueryLists does not inherit from EventTarget in Internet Explorer
if (query.addEventListener) {
query.addEventListener("change", (e) => {
setTheme(e.matches ? themes.Dark : themes.Light);
});
}
}
}, []);
return (
<ThemeProvider theme={Theme()}>
<ThemeProvider theme={theme}>
<CssBaseline />
<NotificationsContext.Provider value={{ notification, setNotification }}>
<Router basename={getBasePath()}>