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:
Drew Alexander 2020-07-31 21:39:12 +08:00 committed by GitHub
parent e442ac95ee
commit 53b28d2bb2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 171 additions and 24 deletions

View file

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

View file

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

View file

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

View file

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

View file

@ -16,12 +16,6 @@
.toggle::before {
position: absolute;
}
.moon::before {
content: '\1F31C';
}
.sun::before {
content: '\1F31E';
}
/**
* styles for React Toggle

View file

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

View file

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