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:
Jon Koops 2024-10-04 14:27:37 +02:00 committed by GitHub
parent 95c529104e
commit 05e8b932c3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 106 additions and 44 deletions

View 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
----

View file

@ -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>

View file

@ -1,2 +1,5 @@
parent=base parent=base
deprecatedMode=false deprecatedMode=false
darkMode=true
kcDarkModeClass=pf-v5-theme-dark

View file

@ -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();

View file

@ -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>

View file

@ -1 +1,4 @@
parent=base parent=base
darkMode=true
kcDarkModeClass=pf-v5-theme-dark

View file

@ -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();

View file

@ -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";

View file

@ -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),
);
}

View file

@ -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>

View file

@ -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

View file

@ -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}">

View file

@ -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