mirror of
https://github.com/facebook/docusaurus.git
synced 2025-08-03 08:49:51 +02:00
fix(v2): refactor color mode system (#3012)
* refactor color mode system to enable all possibilities * fix destructuring bug * colorMode validation + deprecation + minor name changes + doc * rename method noFlashColorMode * fix doc wording * docs wording * docs wording * re-enable theme config merging/normalization + colorMode fixes * document theme normalization * code review changes
This commit is contained in:
parent
cf97662eef
commit
c2bb03ab00
8 changed files with 127 additions and 71 deletions
|
@ -20,41 +20,49 @@ const ContextReplacementPlugin = requireFromDocusaurusCore(
|
|||
// Need to be inlined to prevent dark mode FOUC
|
||||
// Make sure that the 'storageKey' is the same as the one in `/theme/hooks/useTheme.js`
|
||||
const storageKey = 'theme';
|
||||
const noFlash = (defaultDarkMode) => `(function() {
|
||||
var defaultDarkMode = ${defaultDarkMode};
|
||||
const noFlashColorMode = ({defaultMode, respectPrefersColorScheme}) => {
|
||||
return `(function() {
|
||||
var defaultMode = '${defaultMode}';
|
||||
var respectPrefersColorScheme = ${respectPrefersColorScheme};
|
||||
|
||||
function setDataThemeAttribute(theme) {
|
||||
document.documentElement.setAttribute('data-theme', theme);
|
||||
}
|
||||
|
||||
function getPreferredTheme() {
|
||||
function getStoredTheme() {
|
||||
var theme = null;
|
||||
try {
|
||||
theme = localStorage.getItem('${storageKey}');
|
||||
} catch (err) {}
|
||||
|
||||
return theme;
|
||||
}
|
||||
|
||||
var darkQuery = window.matchMedia('(prefers-color-scheme: dark)');
|
||||
|
||||
var preferredTheme = getPreferredTheme();
|
||||
if (preferredTheme !== null) {
|
||||
setDataThemeAttribute(preferredTheme);
|
||||
} else if (darkQuery.matches || defaultDarkMode) {
|
||||
setDataThemeAttribute('dark');
|
||||
var storedTheme = getStoredTheme();
|
||||
if (storedTheme !== null) {
|
||||
setDataThemeAttribute(storedTheme);
|
||||
} else {
|
||||
if (
|
||||
respectPrefersColorScheme &&
|
||||
window.matchMedia('(prefers-color-scheme: dark)').matches
|
||||
) {
|
||||
setDataThemeAttribute('dark');
|
||||
} else if (
|
||||
respectPrefersColorScheme &&
|
||||
window.matchMedia('(prefers-color-scheme: light)').matches
|
||||
) {
|
||||
setDataThemeAttribute('light');
|
||||
} else {
|
||||
setDataThemeAttribute(defaultMode === 'dark' ? 'dark' : 'light');
|
||||
}
|
||||
}
|
||||
})();`;
|
||||
};
|
||||
|
||||
module.exports = function (context, options) {
|
||||
const {
|
||||
siteConfig: {themeConfig},
|
||||
} = context;
|
||||
const {
|
||||
disableDarkMode = false,
|
||||
defaultDarkMode = false,
|
||||
prism: {additionalLanguages = []} = {},
|
||||
} = themeConfig || {};
|
||||
const {colorMode, prism: {additionalLanguages = []} = {}} = themeConfig || {};
|
||||
const {customCss} = options || {};
|
||||
|
||||
return {
|
||||
|
@ -97,9 +105,6 @@ module.exports = function (context, options) {
|
|||
},
|
||||
|
||||
injectHtmlTags() {
|
||||
if (disableDarkMode) {
|
||||
return {};
|
||||
}
|
||||
return {
|
||||
preBodyTags: [
|
||||
{
|
||||
|
@ -107,7 +112,7 @@ module.exports = function (context, options) {
|
|||
attributes: {
|
||||
type: 'text/javascript',
|
||||
},
|
||||
innerHTML: noFlash(defaultDarkMode),
|
||||
innerHTML: noFlashColorMode(colorMode),
|
||||
},
|
||||
],
|
||||
};
|
||||
|
@ -130,8 +135,26 @@ const NavbarLinkSchema = Joi.object({
|
|||
.xor('href', 'to')
|
||||
.id('navbarLinkSchema');
|
||||
|
||||
const ColorModeSchema = Joi.object({
|
||||
defaultMode: Joi.string().equal('dark', 'light').default('light'),
|
||||
disableSwitch: Joi.bool().default(false),
|
||||
respectPrefersColorScheme: Joi.bool().default(false),
|
||||
}).default({
|
||||
defaultMode: 'light',
|
||||
disableSwitch: false,
|
||||
respectPrefersColorScheme: false,
|
||||
});
|
||||
|
||||
const ThemeConfigSchema = Joi.object({
|
||||
disableDarkMode: Joi.bool().default(false),
|
||||
disableDarkMode: Joi.any().forbidden(false).messages({
|
||||
'any.unknown':
|
||||
'disableDarkMode theme config is deprecated. Please use the new colorMode attribute. You likely want: config.themeConfig.colorMode.disableSwitch = true',
|
||||
}),
|
||||
defaultDarkMode: Joi.any().forbidden(false).messages({
|
||||
'any.unknown':
|
||||
'defaultDarkMode theme config is deprecated. Please use the new colorMode attribute. You likely want: config.themeConfig.colorMode.defaultMode = "dark"',
|
||||
}),
|
||||
colorMode: ColorModeSchema,
|
||||
image: Joi.string(),
|
||||
announcementBar: Joi.object({
|
||||
id: Joi.string(),
|
||||
|
|
|
@ -185,7 +185,7 @@ function Navbar(): JSX.Element {
|
|||
siteConfig: {
|
||||
themeConfig: {
|
||||
navbar: {title = '', links = [], hideOnScroll = false} = {},
|
||||
disableDarkMode = false,
|
||||
colorMode: {disableSwitch: disableColorModeSwitch = false} = {},
|
||||
},
|
||||
},
|
||||
isClient,
|
||||
|
@ -283,7 +283,7 @@ function Navbar(): JSX.Element {
|
|||
{rightLinks.map((linkItem, i) => (
|
||||
<NavItem {...linkItem} key={i} />
|
||||
))}
|
||||
{!disableDarkMode && (
|
||||
{!disableColorModeSwitch && (
|
||||
<Toggle
|
||||
className={styles.displayOnlyInLargeViewport}
|
||||
aria-label="Dark mode toggle"
|
||||
|
@ -321,7 +321,7 @@ function Navbar(): JSX.Element {
|
|||
<strong className="navbar__title">{title}</strong>
|
||||
)}
|
||||
</Link>
|
||||
{!disableDarkMode && sidebarShown && (
|
||||
{!disableColorModeSwitch && sidebarShown && (
|
||||
<Toggle
|
||||
aria-label="Dark mode toggle in sidebar"
|
||||
checked={isDarkTheme}
|
||||
|
|
|
@ -8,58 +8,67 @@
|
|||
import {useState, useCallback, useEffect} from 'react';
|
||||
|
||||
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
|
||||
import ExecutionEnvironment from '@docusaurus/ExecutionEnvironment';
|
||||
|
||||
const themes = {
|
||||
light: '',
|
||||
light: 'light',
|
||||
dark: 'dark',
|
||||
};
|
||||
|
||||
// Ensure to always return a valid theme even if input is invalid
|
||||
const coerceToTheme = (theme) => {
|
||||
return theme === themes.dark ? themes.dark : themes.light;
|
||||
};
|
||||
|
||||
const getInitialTheme = () => {
|
||||
if (!ExecutionEnvironment.canUseDOM) {
|
||||
return themes.light; // SSR: we don't care
|
||||
}
|
||||
return coerceToTheme(document.documentElement.getAttribute('data-theme'));
|
||||
};
|
||||
|
||||
const storeTheme = (newTheme) => {
|
||||
try {
|
||||
localStorage.setItem('theme', coerceToTheme(newTheme));
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
};
|
||||
|
||||
const useTheme = (): {
|
||||
isDarkTheme: boolean;
|
||||
setLightTheme: () => void;
|
||||
setDarkTheme: () => void;
|
||||
} => {
|
||||
const {
|
||||
siteConfig: {themeConfig: {disableDarkMode = false} = {}} = {},
|
||||
siteConfig: {
|
||||
themeConfig: {colorMode: {disableSwitch = false} = {}} = {},
|
||||
} = {},
|
||||
} = useDocusaurusContext();
|
||||
const [theme, setTheme] = useState(
|
||||
typeof document !== 'undefined'
|
||||
? document.documentElement.getAttribute('data-theme')
|
||||
: themes.light,
|
||||
);
|
||||
const setThemeSyncWithLocalStorage = useCallback(
|
||||
(newTheme) => {
|
||||
try {
|
||||
localStorage.setItem('theme', newTheme);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
},
|
||||
[setTheme],
|
||||
);
|
||||
const [theme, setTheme] = useState(getInitialTheme);
|
||||
|
||||
const setLightTheme = useCallback(() => {
|
||||
setTheme(themes.light);
|
||||
setThemeSyncWithLocalStorage(themes.light);
|
||||
storeTheme(themes.light);
|
||||
}, []);
|
||||
const setDarkTheme = useCallback(() => {
|
||||
setTheme(themes.dark);
|
||||
setThemeSyncWithLocalStorage(themes.dark);
|
||||
storeTheme(themes.dark);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
// @ts-expect-error: safe to set null as attribute
|
||||
document.documentElement.setAttribute('data-theme', theme);
|
||||
document.documentElement.setAttribute('data-theme', coerceToTheme(theme));
|
||||
}, [theme]);
|
||||
|
||||
useEffect(() => {
|
||||
if (disableDarkMode) {
|
||||
if (disableSwitch) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const localStorageTheme = localStorage.getItem('theme');
|
||||
if (localStorageTheme !== null) {
|
||||
setTheme(localStorageTheme);
|
||||
setTheme(coerceToTheme(localStorageTheme));
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
|
@ -67,7 +76,7 @@ const useTheme = (): {
|
|||
}, [setTheme]);
|
||||
|
||||
useEffect(() => {
|
||||
if (disableDarkMode) {
|
||||
if (disableSwitch) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue