diff --git a/packages/docusaurus-theme-classic/package.json b/packages/docusaurus-theme-classic/package.json index c0167d8f9073..5255b67606bc 100644 --- a/packages/docusaurus-theme-classic/package.json +++ b/packages/docusaurus-theme-classic/package.json @@ -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", diff --git a/packages/docusaurus-theme-classic/src/__tests__/validateThemeConfig.test.js b/packages/docusaurus-theme-classic/src/__tests__/validateThemeConfig.test.js new file mode 100644 index 000000000000..44531fe179c9 --- /dev/null +++ b/packages/docusaurus-theme-classic/src/__tests__/validateThemeConfig.test.js @@ -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), + }); + }); +}); diff --git a/packages/docusaurus-theme-classic/src/index.js b/packages/docusaurus-theme-classic/src/index.js index cff72869155f..8d0653830b34 100644 --- a/packages/docusaurus-theme-classic/src/index.js +++ b/packages/docusaurus-theme-classic/src/index.js @@ -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; diff --git a/packages/docusaurus-theme-classic/src/theme/Toggle/index.tsx b/packages/docusaurus-theme-classic/src/theme/Toggle/index.tsx index a73e95e05551..3ab914e4a242 100644 --- a/packages/docusaurus-theme-classic/src/theme/Toggle/index.tsx +++ b/packages/docusaurus-theme-classic/src/theme/Toggle/index.tsx @@ -13,17 +13,40 @@ import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; import clsx from 'clsx'; import styles from './styles.module.css'; -const Moon = () => ; -const Sun = () => ; +const Dark = ({icon, style}) => ( + + {icon} + +); +const Light = ({icon, style}) => ( + + {icon} + +); export default function (props: ComponentProps): JSX.Element { - const {isClient} = useDocusaurusContext(); + const { + siteConfig: { + themeConfig: { + colorMode: { + switchConfig: { + darkIcon, + darkIconStyle, + lightIcon, + lightIconStyle, + }, + }, + }, + }, + isClient + } = useDocusaurusContext(); + return ( , - unchecked: , + checked: , + unchecked: , }} {...props} /> diff --git a/packages/docusaurus-theme-classic/src/theme/Toggle/styles.module.css b/packages/docusaurus-theme-classic/src/theme/Toggle/styles.module.css index 42619f0251f6..1ac128ce8cf1 100644 --- a/packages/docusaurus-theme-classic/src/theme/Toggle/styles.module.css +++ b/packages/docusaurus-theme-classic/src/theme/Toggle/styles.module.css @@ -16,12 +16,6 @@ .toggle::before { position: absolute; } -.moon::before { - content: '\1F31C'; -} -.sun::before { - content: '\1F31E'; -} /** * styles for React Toggle diff --git a/packages/docusaurus-theme-classic/src/themeConfigSchema.js b/packages/docusaurus-theme-classic/src/validateThemeConfig.js similarity index 81% rename from packages/docusaurus-theme-classic/src/themeConfigSchema.js rename to packages/docusaurus-theme-classic/src/validateThemeConfig.js index 7f847f4d6ca1..9cfe86602c87 100644 --- a/packages/docusaurus-theme-classic/src/themeConfigSchema.js +++ b/packages/docusaurus-theme-classic/src/validateThemeConfig.js @@ -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); +}; diff --git a/website/docs/docusaurus.config.js.md b/website/docs/docusaurus.config.js.md index e15c83d4d124..20a0914c822a 100644 --- a/website/docs/docusaurus.config.js.md +++ b/website/docs/docusaurus.config.js.md @@ -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: {