mirror of
https://github.com/facebook/docusaurus.git
synced 2025-06-20 11:37:52 +02:00
feat(v2): dark mode toggle customization (#3127)
* Add dark/light style fields to config * Update yarn.lock * Remove css content * Add documentation * Add icon fields to toggle component * Add config validation fields * Remove changes from docusaurus.config * Add unicode documentation example * Fix default values * Add color mode config default * Add lodash to theme-classic * Change themeConfigSchema name to match other packages * Add themeConfig color-mode tests * Add default config merge function * Remove unneeded object merging * Add more documentation
This commit is contained in:
parent
e442ac95ee
commit
53b28d2bb2
7 changed files with 171 additions and 24 deletions
|
@ -20,6 +20,7 @@
|
|||
"clsx": "^1.1.1",
|
||||
"copy-text-to-clipboard": "^2.2.0",
|
||||
"infima": "0.2.0-alpha.12",
|
||||
"lodash": "^4.17.19",
|
||||
"parse-numeric-range": "^0.0.2",
|
||||
"prism-react-renderer": "^1.1.0",
|
||||
"prismjs": "^1.20.0",
|
||||
|
|
|
@ -0,0 +1,86 @@
|
|||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
import merge from 'lodash/merge';
|
||||
|
||||
const {
|
||||
validateThemeConfig,
|
||||
DEFAULT_COLOR_MODE_CONFIG,
|
||||
} = require('../validateThemeConfig');
|
||||
|
||||
const mergeDefault = (config) => merge({}, DEFAULT_COLOR_MODE_CONFIG, config);
|
||||
|
||||
function testValidateThemeConfig(themeConfig) {
|
||||
function validate(schema, cfg) {
|
||||
const {value, error} = schema.validate(cfg, {
|
||||
convert: false,
|
||||
});
|
||||
if (error) {
|
||||
throw error;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
return validateThemeConfig({themeConfig, validate});
|
||||
}
|
||||
|
||||
describe('color mode config', () => {
|
||||
test('minimal config', () => {
|
||||
const colorMode = {
|
||||
switchConfig: {
|
||||
darkIcon: '🌙',
|
||||
},
|
||||
};
|
||||
expect(testValidateThemeConfig({colorMode})).toEqual({
|
||||
colorMode: mergeDefault(colorMode),
|
||||
});
|
||||
});
|
||||
|
||||
test('max config', () => {
|
||||
const colorMode = {
|
||||
defaultMode: 'dark',
|
||||
disableSwitch: false,
|
||||
respectPrefersColorScheme: true,
|
||||
switchConfig: {
|
||||
darkIcon: '🌙',
|
||||
darkIconStyle: {
|
||||
marginTop: '1px',
|
||||
marginLeft: '2px',
|
||||
},
|
||||
lightIcon: '☀️',
|
||||
lightIconStyle: {
|
||||
marginLeft: '1px',
|
||||
},
|
||||
},
|
||||
};
|
||||
expect(testValidateThemeConfig({colorMode})).toEqual({
|
||||
colorMode: mergeDefault(colorMode),
|
||||
});
|
||||
});
|
||||
|
||||
test('undefined config', () => {
|
||||
const colorMode = undefined;
|
||||
expect(testValidateThemeConfig({colorMode})).toEqual({
|
||||
colorMode: mergeDefault(colorMode),
|
||||
});
|
||||
});
|
||||
|
||||
test('empty config', () => {
|
||||
const colorMode = {};
|
||||
expect(testValidateThemeConfig({colorMode})).toEqual({
|
||||
colorMode: mergeDefault(colorMode),
|
||||
});
|
||||
});
|
||||
|
||||
test('empty switch config', () => {
|
||||
const colorMode = {
|
||||
switchConfig: {},
|
||||
};
|
||||
expect(testValidateThemeConfig({colorMode})).toEqual({
|
||||
colorMode: mergeDefault(colorMode),
|
||||
});
|
||||
});
|
||||
});
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
const path = require('path');
|
||||
const Module = require('module');
|
||||
const ThemeConfigSchema = require('./themeConfigSchema');
|
||||
const {validateThemeConfig} = require('./validateThemeConfig');
|
||||
|
||||
const createRequire = Module.createRequire || Module.createRequireFromPath;
|
||||
const requireFromDocusaurusCore = createRequire(
|
||||
|
@ -120,6 +120,4 @@ module.exports = function (context, options) {
|
|||
};
|
||||
};
|
||||
|
||||
module.exports.validateThemeConfig = ({validate, themeConfig}) => {
|
||||
return validate(ThemeConfigSchema, themeConfig);
|
||||
};
|
||||
module.exports.validateThemeConfig = validateThemeConfig;
|
||||
|
|
|
@ -13,17 +13,40 @@ import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
|
|||
import clsx from 'clsx';
|
||||
import styles from './styles.module.css';
|
||||
|
||||
const Moon = () => <span className={clsx(styles.toggle, styles.moon)} />;
|
||||
const Sun = () => <span className={clsx(styles.toggle, styles.sun)} />;
|
||||
const Dark = ({icon, style}) => (
|
||||
<span className={clsx(styles.toggle, styles.dark)} style={style}>
|
||||
{icon}
|
||||
</span>
|
||||
);
|
||||
const Light = ({icon, style}) => (
|
||||
<span className={clsx(styles.toggle, styles.light)} style={style}>
|
||||
{icon}
|
||||
</span>
|
||||
);
|
||||
|
||||
export default function (props: ComponentProps<typeof Toggle>): JSX.Element {
|
||||
const {isClient} = useDocusaurusContext();
|
||||
const {
|
||||
siteConfig: {
|
||||
themeConfig: {
|
||||
colorMode: {
|
||||
switchConfig: {
|
||||
darkIcon,
|
||||
darkIconStyle,
|
||||
lightIcon,
|
||||
lightIconStyle,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
isClient
|
||||
} = useDocusaurusContext();
|
||||
|
||||
return (
|
||||
<Toggle
|
||||
disabled={!isClient}
|
||||
icons={{
|
||||
checked: <Moon />,
|
||||
unchecked: <Sun />,
|
||||
checked: <Dark icon={darkIcon} style={darkIconStyle} />,
|
||||
unchecked: <Light icon={lightIcon} style={lightIconStyle} />,
|
||||
}}
|
||||
{...props}
|
||||
/>
|
||||
|
|
|
@ -16,12 +16,6 @@
|
|||
.toggle::before {
|
||||
position: absolute;
|
||||
}
|
||||
.moon::before {
|
||||
content: '\1F31C';
|
||||
}
|
||||
.sun::before {
|
||||
content: '\1F31E';
|
||||
}
|
||||
|
||||
/**
|
||||
* styles for React Toggle
|
||||
|
|
|
@ -7,6 +7,19 @@
|
|||
|
||||
const Joi = require('@hapi/joi');
|
||||
|
||||
const DEFAULT_COLOR_MODE_CONFIG = {
|
||||
defaultMode: 'light',
|
||||
disableSwitch: false,
|
||||
respectPrefersColorScheme: false,
|
||||
switchConfig: {
|
||||
darkIcon: '🌜',
|
||||
darkIconStyle: {},
|
||||
lightIcon: '🌞',
|
||||
lightIconStyle: {},
|
||||
},
|
||||
};
|
||||
exports.DEFAULT_COLOR_MODE_CONFIG = DEFAULT_COLOR_MODE_CONFIG;
|
||||
|
||||
const NavbarItemPosition = Joi.string().equal('left', 'right').default('left');
|
||||
|
||||
// TODO we should probably create a custom navbar item type "dropdown"
|
||||
|
@ -102,14 +115,28 @@ const NavbarItemSchema = Joi.object().when('type', {
|
|||
*/
|
||||
|
||||
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,
|
||||
});
|
||||
defaultMode: Joi.string()
|
||||
.equal('dark', 'light')
|
||||
.default(DEFAULT_COLOR_MODE_CONFIG.defaultMode),
|
||||
disableSwitch: Joi.bool().default(DEFAULT_COLOR_MODE_CONFIG.disableSwitch),
|
||||
respectPrefersColorScheme: Joi.bool().default(
|
||||
DEFAULT_COLOR_MODE_CONFIG.respectPrefersColorScheme,
|
||||
),
|
||||
switchConfig: Joi.object({
|
||||
darkIcon: Joi.string().default(
|
||||
DEFAULT_COLOR_MODE_CONFIG.switchConfig.darkIcon,
|
||||
),
|
||||
darkIconStyle: Joi.object().default(
|
||||
DEFAULT_COLOR_MODE_CONFIG.switchConfig.darkIconStyle,
|
||||
),
|
||||
lightIcon: Joi.string().default(
|
||||
DEFAULT_COLOR_MODE_CONFIG.switchConfig.lightIcon,
|
||||
),
|
||||
lightIconStyle: Joi.object().default(
|
||||
DEFAULT_COLOR_MODE_CONFIG.switchConfig.lightIconStyle,
|
||||
),
|
||||
}).default(DEFAULT_COLOR_MODE_CONFIG.switchConfig),
|
||||
}).default(DEFAULT_COLOR_MODE_CONFIG);
|
||||
|
||||
const FooterLinkItemSchema = Joi.object({
|
||||
to: Joi.string(),
|
||||
|
@ -178,4 +205,6 @@ const ThemeConfigSchema = Joi.object({
|
|||
}),
|
||||
});
|
||||
|
||||
module.exports = ThemeConfigSchema;
|
||||
exports.validateThemeConfig = ({validate, themeConfig}) => {
|
||||
return validate(ThemeConfigSchema, themeConfig);
|
||||
};
|
|
@ -158,6 +158,22 @@ Example:
|
|||
```js title="docusaurus.config.js"
|
||||
module.exports = {
|
||||
themeConfig: {
|
||||
colorMode: {
|
||||
defaultMode: 'light',
|
||||
disableSwitch: false,
|
||||
respectPrefersColorScheme: true,
|
||||
switchConfig: {
|
||||
darkIcon: '🌙',
|
||||
darkIconStyle: { // Style object passed to inline CSS
|
||||
// For more information about styling options visit: https://reactjs.org/docs/dom-elements.html#style
|
||||
marginLeft: '2px',
|
||||
},
|
||||
lightIcon: '\u2600',
|
||||
lightIconStyle: {
|
||||
marginLeft: '1px',
|
||||
},
|
||||
},
|
||||
},
|
||||
navbar: {
|
||||
title: 'Site Title',
|
||||
logo: {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue