Add dark mode support to welcome theme and unify approach (#32495)
Closes #26178 Signed-off-by: Jon Koops <jonkoops@gmail.com> Signed-off-by: Erik Jan de Wit <erikjan.dewit@gmail.com> Co-authored-by: Erik Jan de Wit <erikjan.dewit@gmail.com>
This commit is contained in:
parent
95c529104e
commit
05e8b932c3
13 changed files with 106 additions and 44 deletions
10
docs/documentation/release_notes/topics/26_1_0.adoc
Normal file
10
docs/documentation/release_notes/topics/26_1_0.adoc
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
= Dark mode enabled for the welcome theme
|
||||||
|
|
||||||
|
We've now enabled dark mode support for all the `keycloak` themes. This feature was previously present in the admin console, account console and login, and is now also available in the welcome page. If a user indicates their preference through an operating system setting (e.g. light or dark mode) or a user agent setting, the theme will automatically follow these preferences.
|
||||||
|
|
||||||
|
If you are using a custom theme that extends any of the `keycloak` themes and are not yet ready to support dark mode, or have styling conflicts that prevent you from implementing dark mode, you can disable support by adding the following property to your theme:
|
||||||
|
|
||||||
|
[source,properties]
|
||||||
|
----
|
||||||
|
darkMode=false
|
||||||
|
----
|
|
@ -5,6 +5,7 @@
|
||||||
<base href="${resourceUrl}/">
|
<base href="${resourceUrl}/">
|
||||||
<link rel="icon" type="${properties.favIconType!'image/svg+xml'}" href="${resourceUrl}${properties.favIcon!'/favicon.svg'}">
|
<link rel="icon" type="${properties.favIconType!'image/svg+xml'}" href="${resourceUrl}${properties.favIcon!'/favicon.svg'}">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<meta name="color-scheme" content="light${(properties.darkMode)?boolean?then(' dark', '')}">
|
||||||
<meta name="description" content="${properties.description!'The Account Console is a web-based interface for managing your account.'}">
|
<meta name="description" content="${properties.description!'The Account Console is a web-based interface for managing your account.'}">
|
||||||
<title>${properties.title!'Account Management'}</title>
|
<title>${properties.title!'Account Management'}</title>
|
||||||
<style>
|
<style>
|
||||||
|
@ -57,6 +58,25 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
<#if properties.darkMode?boolean>
|
||||||
|
<script type="module" async blocking="render">
|
||||||
|
const DARK_MODE_CLASS = "${properties.kcDarkModeClass}";
|
||||||
|
const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
|
||||||
|
|
||||||
|
updateDarkMode(mediaQuery.matches);
|
||||||
|
mediaQuery.addEventListener("change", (event) => updateDarkMode(event.matches));
|
||||||
|
|
||||||
|
function updateDarkMode(isEnabled) {
|
||||||
|
const { classList } = document.documentElement;
|
||||||
|
|
||||||
|
if (isEnabled) {
|
||||||
|
classList.add(DARK_MODE_CLASS);
|
||||||
|
} else {
|
||||||
|
classList.remove(DARK_MODE_CLASS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</#if>
|
||||||
<#if !isSecureContext>
|
<#if !isSecureContext>
|
||||||
<script type="module" src="${resourceCommonUrl}/vendor/web-crypto-shim/web-crypto-shim.js"></script>
|
<script type="module" src="${resourceCommonUrl}/vendor/web-crypto-shim/web-crypto-shim.js"></script>
|
||||||
</#if>
|
</#if>
|
||||||
|
|
|
@ -1,2 +1,5 @@
|
||||||
parent=base
|
parent=base
|
||||||
deprecatedMode=false
|
deprecatedMode=false
|
||||||
|
darkMode=true
|
||||||
|
|
||||||
|
kcDarkModeClass=pf-v5-theme-dark
|
||||||
|
|
|
@ -1,15 +1,12 @@
|
||||||
import "@patternfly/react-core/dist/styles/base.css";
|
import "@patternfly/react-core/dist/styles/base.css";
|
||||||
import "@patternfly/patternfly/patternfly-addons.css";
|
import "@patternfly/patternfly/patternfly-addons.css";
|
||||||
|
|
||||||
import { initializeDarkMode } from "@keycloak/keycloak-ui-shared";
|
|
||||||
import { StrictMode } from "react";
|
import { StrictMode } from "react";
|
||||||
import { createRoot } from "react-dom/client";
|
import { createRoot } from "react-dom/client";
|
||||||
import { createBrowserRouter, RouterProvider } from "react-router-dom";
|
import { createBrowserRouter, RouterProvider } from "react-router-dom";
|
||||||
import { i18n } from "./i18n";
|
import { i18n } from "./i18n";
|
||||||
import { routes } from "./routes";
|
import { routes } from "./routes";
|
||||||
|
|
||||||
initializeDarkMode();
|
|
||||||
|
|
||||||
// Initialize required components before rendering app.
|
// Initialize required components before rendering app.
|
||||||
await i18n.init();
|
await i18n.init();
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
<base href="${resourceUrl}/">
|
<base href="${resourceUrl}/">
|
||||||
<link rel="icon" type="${properties.favIconType!'image/svg+xml'}" href="${resourceUrl}${properties.favIcon!'/favicon.svg'}">
|
<link rel="icon" type="${properties.favIconType!'image/svg+xml'}" href="${resourceUrl}${properties.favIcon!'/favicon.svg'}">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<meta name="color-scheme" content="light${(properties.darkMode)?boolean?then(' dark', '')}">
|
||||||
<meta name="description" content="${properties.description!'The Keycloak Administration Console is a web-based interface for managing Keycloak.'}">
|
<meta name="description" content="${properties.description!'The Keycloak Administration Console is a web-based interface for managing Keycloak.'}">
|
||||||
<title>${properties.title!'Keycloak Administration Console'}</title>
|
<title>${properties.title!'Keycloak Administration Console'}</title>
|
||||||
<style>
|
<style>
|
||||||
|
@ -57,6 +58,25 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
<#if properties.darkMode?boolean>
|
||||||
|
<script type="module" async blocking="render">
|
||||||
|
const DARK_MODE_CLASS = "${properties.kcDarkModeClass}";
|
||||||
|
const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
|
||||||
|
|
||||||
|
updateDarkMode(mediaQuery.matches);
|
||||||
|
mediaQuery.addEventListener("change", (event) => updateDarkMode(event.matches));
|
||||||
|
|
||||||
|
function updateDarkMode(isEnabled) {
|
||||||
|
const { classList } = document.documentElement;
|
||||||
|
|
||||||
|
if (isEnabled) {
|
||||||
|
classList.add(DARK_MODE_CLASS);
|
||||||
|
} else {
|
||||||
|
classList.remove(DARK_MODE_CLASS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</#if>
|
||||||
<#if !isSecureContext>
|
<#if !isSecureContext>
|
||||||
<script type="module" src="${resourceCommonUrl}/vendor/web-crypto-shim/web-crypto-shim.js"></script>
|
<script type="module" src="${resourceCommonUrl}/vendor/web-crypto-shim/web-crypto-shim.js"></script>
|
||||||
</#if>
|
</#if>
|
||||||
|
|
|
@ -1 +1,4 @@
|
||||||
parent=base
|
parent=base
|
||||||
|
darkMode=true
|
||||||
|
|
||||||
|
kcDarkModeClass=pf-v5-theme-dark
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import "@patternfly/patternfly/patternfly-addons.css";
|
import "@patternfly/patternfly/patternfly-addons.css";
|
||||||
import "@patternfly/react-core/dist/styles/base.css";
|
import "@patternfly/react-core/dist/styles/base.css";
|
||||||
|
|
||||||
import { initializeDarkMode } from "@keycloak/keycloak-ui-shared";
|
|
||||||
import { StrictMode } from "react";
|
import { StrictMode } from "react";
|
||||||
import { createRoot } from "react-dom/client";
|
import { createRoot } from "react-dom/client";
|
||||||
import { createHashRouter, RouterProvider } from "react-router-dom";
|
import { createHashRouter, RouterProvider } from "react-router-dom";
|
||||||
|
@ -10,8 +9,6 @@ import { RootRoute } from "./routes";
|
||||||
|
|
||||||
import "./index.css";
|
import "./index.css";
|
||||||
|
|
||||||
initializeDarkMode();
|
|
||||||
|
|
||||||
// Initialize required components before rendering app.
|
// Initialize required components before rendering app.
|
||||||
await i18n.init();
|
await i18n.init();
|
||||||
|
|
||||||
|
|
|
@ -93,4 +93,3 @@ export {
|
||||||
} from "./utils/ErrorBoundary";
|
} from "./utils/ErrorBoundary";
|
||||||
export type { FallbackProps } from "./utils/ErrorBoundary";
|
export type { FallbackProps } from "./utils/ErrorBoundary";
|
||||||
export { OrganizationTable } from "./controls/OrganizationTable";
|
export { OrganizationTable } from "./controls/OrganizationTable";
|
||||||
export { initializeDarkMode } from "./utils/darkMode";
|
|
||||||
|
|
|
@ -1,19 +0,0 @@
|
||||||
const DARK_MODE_CLASS = "pf-v5-theme-dark";
|
|
||||||
const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
|
|
||||||
|
|
||||||
function updateDarkMode(isEnabled: boolean) {
|
|
||||||
const { classList } = document.documentElement;
|
|
||||||
|
|
||||||
if (isEnabled) {
|
|
||||||
classList.add(DARK_MODE_CLASS);
|
|
||||||
} else {
|
|
||||||
classList.remove(DARK_MODE_CLASS);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function initializeDarkMode() {
|
|
||||||
updateDarkMode(mediaQuery.matches);
|
|
||||||
mediaQuery.addEventListener("change", (event) =>
|
|
||||||
updateDarkMode(event.matches),
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -28,6 +28,7 @@
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||||
<meta name="robots" content="noindex, nofollow">
|
<meta name="robots" content="noindex, nofollow">
|
||||||
|
<meta name="color-scheme" content="light${(properties.darkMode)?boolean?then(' dark', '')}">
|
||||||
|
|
||||||
<#if properties.meta?has_content>
|
<#if properties.meta?has_content>
|
||||||
<#list properties.meta?split(' ') as meta>
|
<#list properties.meta?split(' ') as meta>
|
||||||
|
@ -53,6 +54,25 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
<#if properties.darkMode?boolean>
|
||||||
|
<script type="module" async blocking="render">
|
||||||
|
const DARK_MODE_CLASS = "${properties.kcDarkModeClass}";
|
||||||
|
const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
|
||||||
|
|
||||||
|
updateDarkMode(mediaQuery.matches);
|
||||||
|
mediaQuery.addEventListener("change", (event) => updateDarkMode(event.matches));
|
||||||
|
|
||||||
|
function updateDarkMode(isEnabled) {
|
||||||
|
const { classList } = document.documentElement;
|
||||||
|
|
||||||
|
if (isEnabled) {
|
||||||
|
classList.add(DARK_MODE_CLASS);
|
||||||
|
} else {
|
||||||
|
classList.remove(DARK_MODE_CLASS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</#if>
|
||||||
<#if properties.scripts?has_content>
|
<#if properties.scripts?has_content>
|
||||||
<#list properties.scripts?split(' ') as script>
|
<#list properties.scripts?split(' ') as script>
|
||||||
<script src="${url.resourcesPath}/${script}" type="text/javascript"></script>
|
<script src="${url.resourcesPath}/${script}" type="text/javascript"></script>
|
||||||
|
@ -70,21 +90,6 @@
|
||||||
checkCookiesAndSetTimer(
|
checkCookiesAndSetTimer(
|
||||||
"${url.ssoLoginInOtherTabsUrl?no_esc}"
|
"${url.ssoLoginInOtherTabsUrl?no_esc}"
|
||||||
);
|
);
|
||||||
|
|
||||||
const DARK_MODE_CLASS = "pf-v5-theme-dark";
|
|
||||||
const mediaQuery =window.matchMedia("(prefers-color-scheme: dark)");
|
|
||||||
updateDarkMode(mediaQuery.matches);
|
|
||||||
mediaQuery.addEventListener("change", (event) =>
|
|
||||||
updateDarkMode(event.matches),
|
|
||||||
);
|
|
||||||
function updateDarkMode(isEnabled) {
|
|
||||||
const { classList } = document.documentElement;
|
|
||||||
if (isEnabled) {
|
|
||||||
classList.add(DARK_MODE_CLASS);
|
|
||||||
} else {
|
|
||||||
classList.remove(DARK_MODE_CLASS);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,8 @@ import=common/keycloak
|
||||||
styles=css/styles.css
|
styles=css/styles.css
|
||||||
stylesCommon=vendor/patternfly-v5/patternfly.min.css vendor/patternfly-v5/patternfly-addons.css
|
stylesCommon=vendor/patternfly-v5/patternfly.min.css vendor/patternfly-v5/patternfly-addons.css
|
||||||
|
|
||||||
|
darkMode=true
|
||||||
|
|
||||||
kcFormGroupClass=pf-v5-c-form__group
|
kcFormGroupClass=pf-v5-c-form__group
|
||||||
kcFormGroupLabelClass=pf-v5-c-form__group-label
|
kcFormGroupLabelClass=pf-v5-c-form__group-label
|
||||||
kcLabelClass=pf-v5-c-form__label
|
kcLabelClass=pf-v5-c-form__label
|
||||||
|
@ -91,3 +93,5 @@ kcLoginOTPListItemIconBodyClass=pf-v5-c-tile__icon
|
||||||
kcLoginOTPListItemIconClass=fa fa-mobile
|
kcLoginOTPListItemIconClass=fa fa-mobile
|
||||||
kcLoginOTPListItemTitleClass=pf-v5-c-tile__title
|
kcLoginOTPListItemTitleClass=pf-v5-c-tile__title
|
||||||
kcLoginOTPListSelectedClass=pf-m-selected
|
kcLoginOTPListSelectedClass=pf-m-selected
|
||||||
|
|
||||||
|
kcDarkModeClass=pf-v5-theme-dark
|
||||||
|
|
|
@ -4,8 +4,28 @@
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="robots" content="noindex, nofollow">
|
<meta name="robots" content="noindex, nofollow">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<meta name="color-scheme" content="light${(properties.darkMode)?boolean?then(' dark', '')}">
|
||||||
<title>Welcome to ${productName}</title>
|
<title>Welcome to ${productName}</title>
|
||||||
<link rel="shortcut icon" href="${resourcesCommonPath}/img/favicon.ico">
|
<link rel="shortcut icon" href="${resourcesCommonPath}/img/favicon.ico">
|
||||||
|
<#if properties.darkMode?boolean>
|
||||||
|
<script type="module" async blocking="render">
|
||||||
|
const DARK_MODE_CLASS = "${properties.kcDarkModeClass}";
|
||||||
|
const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
|
||||||
|
|
||||||
|
updateDarkMode(mediaQuery.matches);
|
||||||
|
mediaQuery.addEventListener("change", (event) => updateDarkMode(event.matches));
|
||||||
|
|
||||||
|
function updateDarkMode(isEnabled) {
|
||||||
|
const { classList } = document.documentElement;
|
||||||
|
|
||||||
|
if (isEnabled) {
|
||||||
|
classList.add(DARK_MODE_CLASS);
|
||||||
|
} else {
|
||||||
|
classList.remove(DARK_MODE_CLASS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</#if>
|
||||||
<#if properties.stylesCommon?has_content>
|
<#if properties.stylesCommon?has_content>
|
||||||
<#list properties.stylesCommon?split(' ') as style>
|
<#list properties.stylesCommon?split(' ') as style>
|
||||||
<link rel="stylesheet" href="${resourcesCommonPath}/${style}">
|
<link rel="stylesheet" href="${resourcesCommonPath}/${style}">
|
||||||
|
|
|
@ -5,3 +5,6 @@ styles=css/welcome.css
|
||||||
|
|
||||||
# When set to true, the user will be redirected to the Administration Console if an administrative users already exists.
|
# When set to true, the user will be redirected to the Administration Console if an administrative users already exists.
|
||||||
redirectToAdmin=true
|
redirectToAdmin=true
|
||||||
|
darkMode=true
|
||||||
|
|
||||||
|
kcDarkModeClass=pf-v5-theme-dark
|
||||||
|
|
Loading…
Reference in a new issue