Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useEffect, useState, createContext } from 'react';
import React, { useEffect, useState, createContext, useCallback } from 'react';
import {
Button,
Page,
Expand Down Expand Up @@ -232,6 +232,26 @@ export function attachDocSearch(algolia, inputSelector, timeout) {
}
}

const DARK_MODE_CLASS = "pf-v6-theme-dark";
const DARK_MODE_STORAGE_KEY = "dark-mode";

/**
* Determines if dark mode is enabled based on the stored value or the system preference.
* @returns {boolean} true if dark mode is enabled, false otherwise
*/
function isDarkModeEnabled() {
// When running in prerender mode we can't access the browser APIs.
if (process.env.PRERENDER) {
return false;
}

const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
const storedValue = localStorage.getItem(DARK_MODE_STORAGE_KEY);
const isEnabled = storedValue === null ? mediaQuery.matches : storedValue === "true";

return isEnabled;
}

export const SideNavLayout = ({ children, groupedRoutes, navOpen: navOpenProp }) => {
const algolia = process.env.algolia;
const hasGdprBanner = process.env.hasGdprBanner;
Expand All @@ -245,7 +265,63 @@ export const SideNavLayout = ({ children, groupedRoutes, navOpen: navOpenProp })

const [versions, setVersions] = useState({ ...staticVersions });
const [isRTL, setIsRTL] = useState(false);
const [isDarkTheme, setIsDarkTheme] = React.useState(false);
const [isDarkTheme, setIsDarkThemeInternal] = useState(() => isDarkModeEnabled());

/**
* Stores the dark mode preference in local storage and updates the dark mode class.
*/
const setIsDarkTheme = useCallback((value) => {
localStorage.setItem(DARK_MODE_STORAGE_KEY, value.toString());
updateDarkMode();
}, []);

/**
* Updates the dark mode class to the root element depending on whether dark mode is enabled.
*/
const updateDarkMode = useCallback(() => {
const isEnabled = isDarkModeEnabled();
const root = document.documentElement;

if (isEnabled) {
root.classList.add(DARK_MODE_CLASS);
} else {
root.classList.remove(DARK_MODE_CLASS);
}

setIsDarkThemeInternal(isEnabled);
}, []);

useEffect(() => {
// When running in prerender mode we can't access the browser APIs.
if (process.env.PRERENDER) {
return;
}

// Update the dark mode when the the user changes their system/browser preference.
const onQueryChange = () => {
// Remove the stored value to defer to the system preference.
localStorage.removeItem(DARK_MODE_STORAGE_KEY);
updateDarkMode();
};

// Update the dark mode when the user changes the preference in another context (e.g. tab or window).
/** @type {(event: StorageEvent) => void} */
const onStorageChange = (event) => {
if (event.key === DARK_MODE_STORAGE_KEY) {
updateDarkMode();
}
};

const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");

mediaQuery.addEventListener("change", onQueryChange);
window.addEventListener("storage", onStorageChange);

return () => {
mediaQuery.removeEventListener("change", onQueryChange);
window.removeEventListener("storage", onStorageChange);
};
}, []);

useEffect(() => {
if (typeof window === 'undefined') {
Expand Down
36 changes: 36 additions & 0 deletions packages/documentation-framework/templates/html.ejs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1,shrink-to-fit=no">
<meta name="color-scheme" content="light dark">
<meta name="description" content="PatternFly is Red Hat's open source design system. It consists of components, documentation, and code for building enterprise applications at scale.">
<title><%= title %></title>
<link rel="shortcut icon" href="/assets/Favicon-Light.png">
Expand All @@ -13,6 +14,41 @@
<meta name="mobile-web-app-capable" content="yes">
<meta name="theme-color" content="#151515">
<meta name="application-name" content="PatternFly docs">
<script>
(() => {
"use strict";

const DARK_MODE_CLASS = "pf-v6-theme-dark";
const DARK_MODE_STORAGE_KEY = "dark-mode";
const darkModeQuery = window.matchMedia("(prefers-color-scheme: dark)");

// Ensure that the dark mode is correctly set before the page starts rendering.
updateDarkMode();

/**
* Applies the dark mode class to the root element if dark mode is enabled.
*/
function updateDarkMode() {
const isEnabled = isDarkModeEnabled();
const root = document.documentElement;

if (isEnabled) {
root.classList.add(DARK_MODE_CLASS);
}
}

/**
* Determines if dark mode is enabled based on the stored value or the system preference.
* @returns {boolean} true if dark mode is enabled, false otherwise
*/
function isDarkModeEnabled() {
const storedValue = localStorage.getItem(DARK_MODE_STORAGE_KEY);
const isEnabled = storedValue === null ? darkModeQuery.matches : storedValue === "true";

return isEnabled;
}
})();
</script>
<%= htmlWebpackPlugin.tags.headTags %>
</head>
<body>
Expand Down
Loading