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 conflictspull/2090/head^2
parent
78a9faacfe
commit
150116a172
|
@ -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).
|
||||
|
|
|
@ -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
|
||||
|
||||
##
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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()}>
|
||||
|
|
Loading…
Reference in New Issue