From 0951eef2d7ae190d09b4cd8257d1cbebd7126d88 Mon Sep 17 00:00:00 2001 From: Shubham Kumar Date: Fri, 2 Oct 2020 22:53:34 +0530 Subject: [PATCH] refactor(v2): add useThemeConfig hook + cleanup useless theme default values (#3394) * refactor(theme-classic): clean default or fallback values * refactor(theme-classic): fix announcementbar undefined error * refactor(theme-classic): fixed react hook warning error * refactor(theme-classic): revert prism destruct * create useThemeConfig and use it whenever possible * validateThemeConfig => add [] as default value for almost all arrays * fix tests Co-authored-by: slorber --- .../src/__tests__/validateThemeConfig.test.js | 22 +++-- .../src/theme/AnnouncementBar/index.tsx | 12 ++- .../src/theme/CodeBlock/index.tsx | 8 +- .../src/theme/DocItem/index.tsx | 2 +- .../src/theme/DocSidebar/index.tsx | 9 +- .../src/theme/Footer/index.tsx | 7 +- .../src/theme/Heading/index.tsx | 8 +- .../src/theme/Navbar/index.tsx | 20 ++-- .../src/theme/Toggle/index.tsx | 21 ++-- .../src/theme/hooks/useAnnouncementBar.ts | 4 +- .../src/theme/hooks/useLogo.ts | 6 +- .../src/theme/hooks/usePrismTheme.ts | 8 +- .../src/theme/hooks/useTheme.ts | 8 +- .../src/utils/useThemeConfig.ts | 24 +++++ .../src/validateThemeConfig.js | 39 +++++--- .../version-2.0.0-alpha.64/theme-bootstrap.md | 98 +++++++++---------- 16 files changed, 154 insertions(+), 142 deletions(-) create mode 100644 packages/docusaurus-theme-classic/src/utils/useThemeConfig.ts diff --git a/packages/docusaurus-theme-classic/src/__tests__/validateThemeConfig.test.js b/packages/docusaurus-theme-classic/src/__tests__/validateThemeConfig.test.js index 2cb8b91ef92b..706aff7bfdbd 100644 --- a/packages/docusaurus-theme-classic/src/__tests__/validateThemeConfig.test.js +++ b/packages/docusaurus-theme-classic/src/__tests__/validateThemeConfig.test.js @@ -11,14 +11,19 @@ const {ThemeConfigSchema, DEFAULT_CONFIG} = require('../validateThemeConfig'); const {normalizeThemeConfig} = require('@docusaurus/utils-validation'); -function testValidateThemeConfig(themeConfig) { - return normalizeThemeConfig(ThemeConfigSchema, themeConfig); +function testValidateThemeConfig(partialThemeConfig) { + return normalizeThemeConfig(ThemeConfigSchema, { + ...DEFAULT_CONFIG, + ...partialThemeConfig, + }); } -function testOk(partialConfig) { - expect(testValidateThemeConfig(partialConfig)).toEqual({ +function testOk(partialThemeConfig) { + expect( + testValidateThemeConfig({...DEFAULT_CONFIG, ...partialThemeConfig}), + ).toEqual({ ...DEFAULT_CONFIG, - ...partialConfig, + ...partialThemeConfig, }); } @@ -101,7 +106,10 @@ describe('themeConfig', () => { }; expect(testValidateThemeConfig(altTagConfig)).toEqual({ ...DEFAULT_CONFIG, - ...altTagConfig, + navbar: { + ...DEFAULT_CONFIG.navbar, + ...altTagConfig.navbar, + }, }); }); @@ -117,7 +125,7 @@ describe('themeConfig', () => { }); }); - describe.only('customCss config', () => { + describe('customCss config', () => { test('should accept customCss undefined', () => { testOk({ customCss: undefined, diff --git a/packages/docusaurus-theme-classic/src/theme/AnnouncementBar/index.tsx b/packages/docusaurus-theme-classic/src/theme/AnnouncementBar/index.tsx index de339ea493a9..95b147b94e20 100644 --- a/packages/docusaurus-theme-classic/src/theme/AnnouncementBar/index.tsx +++ b/packages/docusaurus-theme-classic/src/theme/AnnouncementBar/index.tsx @@ -7,21 +7,23 @@ import React from 'react'; import clsx from 'clsx'; -import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; +import useThemeConfig from '../../utils/useThemeConfig'; import useUserPreferencesContext from '@theme/hooks/useUserPreferencesContext'; import styles from './styles.module.css'; function AnnouncementBar(): JSX.Element | null { - const { - siteConfig: {themeConfig: {announcementBar = {}} = {}} = {}, - } = useDocusaurusContext(); - const {content, backgroundColor, textColor, isCloseable} = announcementBar; const { isAnnouncementBarClosed, closeAnnouncementBar, } = useUserPreferencesContext(); + const {announcementBar} = useThemeConfig(); + + if (!announcementBar) { + return null; + } + const {content, backgroundColor, textColor, isCloseable} = announcementBar; if (!content || (isCloseable && isAnnouncementBarClosed)) { return null; } diff --git a/packages/docusaurus-theme-classic/src/theme/CodeBlock/index.tsx b/packages/docusaurus-theme-classic/src/theme/CodeBlock/index.tsx index 458dd2c1d061..416cb4030782 100644 --- a/packages/docusaurus-theme-classic/src/theme/CodeBlock/index.tsx +++ b/packages/docusaurus-theme-classic/src/theme/CodeBlock/index.tsx @@ -12,11 +12,11 @@ import clsx from 'clsx'; import Highlight, {defaultProps} from 'prism-react-renderer'; import copy from 'copy-text-to-clipboard'; import rangeParser from 'parse-numeric-range'; -import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; import usePrismTheme from '@theme/hooks/usePrismTheme'; import type {Props} from '@theme/CodeBlock'; import styles from './styles.module.css'; +import useThemeConfig from '../../utils/useThemeConfig'; const highlightLinesRangeRegex = /{([\d,-]+)}/; const getHighlightDirectiveRegex = ( @@ -93,11 +93,7 @@ export default ({ className: languageClassName, metastring, }: Props): JSX.Element => { - const { - siteConfig: { - themeConfig: {prism = {}}, - }, - } = useDocusaurusContext(); + const {prism} = useThemeConfig(); const [showCopied, setShowCopied] = useState(false); const [mounted, setMounted] = useState(false); diff --git a/packages/docusaurus-theme-classic/src/theme/DocItem/index.tsx b/packages/docusaurus-theme-classic/src/theme/DocItem/index.tsx index 3a6b2eef7c3f..eab7e8988070 100644 --- a/packages/docusaurus-theme-classic/src/theme/DocItem/index.tsx +++ b/packages/docusaurus-theme-classic/src/theme/DocItem/index.tsx @@ -24,7 +24,7 @@ import { } from '@theme/hooks/useDocs'; function DocItem(props: Props): JSX.Element { - const {siteConfig = {}} = useDocusaurusContext(); + const {siteConfig} = useDocusaurusContext(); const {url: siteUrl, title: siteTitle, titleDelimiter} = siteConfig; const {content: DocContent} = props; const {metadata} = DocContent; diff --git a/packages/docusaurus-theme-classic/src/theme/DocSidebar/index.tsx b/packages/docusaurus-theme-classic/src/theme/DocSidebar/index.tsx index e1f4e42d072e..1a76709f4ffb 100644 --- a/packages/docusaurus-theme-classic/src/theme/DocSidebar/index.tsx +++ b/packages/docusaurus-theme-classic/src/theme/DocSidebar/index.tsx @@ -8,6 +8,7 @@ import React, {useState, useCallback, useEffect, useRef} from 'react'; import clsx from 'clsx'; import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; +import useThemeConfig from '../../utils/useThemeConfig'; import useUserPreferencesContext from '@theme/hooks/useUserPreferencesContext'; import useLockBodyScroll from '@theme/hooks/useLockBodyScroll'; import useWindowSize, {windowSizes} from '@theme/hooks/useWindowSize'; @@ -171,11 +172,9 @@ function DocSidebar({ }: Props): JSX.Element | null { const [showResponsiveSidebar, setShowResponsiveSidebar] = useState(false); const { - siteConfig: { - themeConfig: {navbar: {title = '', hideOnScroll = false} = {}} = {}, - } = {}, - isClient, - } = useDocusaurusContext(); + navbar: {title, hideOnScroll}, + } = useThemeConfig(); + const {isClient} = useDocusaurusContext(); const {logoLink, logoLinkProps, logoImageUrl, logoAlt} = useLogo(); const {isAnnouncementBarClosed} = useUserPreferencesContext(); const {scrollY} = useScrollPosition(); diff --git a/packages/docusaurus-theme-classic/src/theme/Footer/index.tsx b/packages/docusaurus-theme-classic/src/theme/Footer/index.tsx index a942520f1b9f..5cb11111cdfb 100644 --- a/packages/docusaurus-theme-classic/src/theme/Footer/index.tsx +++ b/packages/docusaurus-theme-classic/src/theme/Footer/index.tsx @@ -9,7 +9,7 @@ import React from 'react'; import clsx from 'clsx'; import Link from '@docusaurus/Link'; -import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; +import useThemeConfig from '../../utils/useThemeConfig'; import useBaseUrl from '@docusaurus/useBaseUrl'; import styles from './styles.module.css'; @@ -40,10 +40,7 @@ const FooterLogo = ({url, alt}) => ( ); function Footer(): JSX.Element | null { - const context = useDocusaurusContext(); - const {siteConfig = {}} = context; - const {themeConfig = {}} = siteConfig; - const {footer} = themeConfig; + const {footer} = useThemeConfig(); const {copyright, links = [], logo = {}} = footer || {}; const logoUrl = useBaseUrl(logo.src); diff --git a/packages/docusaurus-theme-classic/src/theme/Heading/index.tsx b/packages/docusaurus-theme-classic/src/theme/Heading/index.tsx index 8724028afa92..9d4b1d4cb4ca 100644 --- a/packages/docusaurus-theme-classic/src/theme/Heading/index.tsx +++ b/packages/docusaurus-theme-classic/src/theme/Heading/index.tsx @@ -9,8 +9,8 @@ import React from 'react'; import clsx from 'clsx'; -import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; import type {HeadingType, Props} from '@theme/Heading'; +import useThemeConfig from '../../utils/useThemeConfig'; import './styles.css'; import styles from './styles.module.css'; @@ -18,10 +18,8 @@ import styles from './styles.module.css'; const Heading = (Tag: HeadingType): ((props: Props) => JSX.Element) => function TargetComponent({id, ...props}) { const { - siteConfig: { - themeConfig: {navbar: {hideOnScroll = false} = {}} = {}, - } = {}, - } = useDocusaurusContext(); + navbar: {hideOnScroll}, + } = useThemeConfig(); if (!id) { return ; diff --git a/packages/docusaurus-theme-classic/src/theme/Navbar/index.tsx b/packages/docusaurus-theme-classic/src/theme/Navbar/index.tsx index fe606ba63e92..89addc41ebe7 100644 --- a/packages/docusaurus-theme-classic/src/theme/Navbar/index.tsx +++ b/packages/docusaurus-theme-classic/src/theme/Navbar/index.tsx @@ -13,6 +13,7 @@ import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; import SearchBar from '@theme/SearchBar'; import Toggle from '@theme/Toggle'; import useThemeContext from '@theme/hooks/useThemeContext'; +import useThemeConfig from '../../utils/useThemeConfig'; import useHideableNavbar from '@theme/hooks/useHideableNavbar'; import useLockBodyScroll from '@theme/hooks/useLockBodyScroll'; import useWindowSize, {windowSizes} from '@theme/hooks/useWindowSize'; @@ -40,20 +41,13 @@ function splitNavItemsByPosition(items) { } function Navbar(): JSX.Element { + const {isClient} = useDocusaurusContext(); + const { - siteConfig: { - themeConfig: { - navbar: { - title = '', - items = [], - hideOnScroll = false, - style = undefined, - } = {}, - colorMode: {disableSwitch: disableColorModeSwitch = false} = {}, - }, - }, - isClient, - } = useDocusaurusContext(); + navbar: {title, items, hideOnScroll, style}, + colorMode: {disableSwitch: disableColorModeSwitch}, + } = useThemeConfig(); + const [sidebarShown, setSidebarShown] = useState(false); const [isSearchBarExpanded, setIsSearchBarExpanded] = useState(false); diff --git a/packages/docusaurus-theme-classic/src/theme/Toggle/index.tsx b/packages/docusaurus-theme-classic/src/theme/Toggle/index.tsx index 3ab914e4a242..07eea1e64fa5 100644 --- a/packages/docusaurus-theme-classic/src/theme/Toggle/index.tsx +++ b/packages/docusaurus-theme-classic/src/theme/Toggle/index.tsx @@ -7,7 +7,7 @@ import React, {ComponentProps} from 'react'; import Toggle from 'react-toggle'; - +import useThemeConfig from '../../utils/useThemeConfig'; import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; import clsx from 'clsx'; @@ -26,20 +26,11 @@ const Light = ({icon, style}) => ( export default function (props: ComponentProps): JSX.Element { const { - siteConfig: { - themeConfig: { - colorMode: { - switchConfig: { - darkIcon, - darkIconStyle, - lightIcon, - lightIconStyle, - }, - }, - }, - }, - isClient - } = useDocusaurusContext(); + colorMode: { + switchConfig: {darkIcon, darkIconStyle, lightIcon, lightIconStyle}, + }, + } = useThemeConfig(); + const {isClient} = useDocusaurusContext(); return ( { - const {announcementBar} = useDocusaurusContext().siteConfig.themeConfig; + const {announcementBar} = useThemeConfig(); const [isClosed, setClosed] = useState(true); diff --git a/packages/docusaurus-theme-classic/src/theme/hooks/useLogo.ts b/packages/docusaurus-theme-classic/src/theme/hooks/useLogo.ts index 198a33a3f7d7..39377ada6906 100644 --- a/packages/docusaurus-theme-classic/src/theme/hooks/useLogo.ts +++ b/packages/docusaurus-theme-classic/src/theme/hooks/useLogo.ts @@ -5,16 +5,16 @@ * LICENSE file in the root directory of this source tree. */ -import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; import useThemeContext from '@theme/hooks/useThemeContext'; import useBaseUrl from '@docusaurus/useBaseUrl'; import isInternalUrl from '@docusaurus/isInternalUrl'; import type {LogoLinkProps, useLogoReturns} from '@theme/hooks/useLogo'; +import useThemeConfig from '../../utils/useThemeConfig'; const useLogo = (): useLogoReturns => { const { - siteConfig: {themeConfig: {navbar: {logo = {}} = {}} = {}} = {}, - } = useDocusaurusContext(); + navbar: {logo}, + } = useThemeConfig(); const {isDarkTheme} = useThemeContext(); const logoLink = useBaseUrl(logo.href || '/'); let logoLinkProps: LogoLinkProps = {}; diff --git a/packages/docusaurus-theme-classic/src/theme/hooks/usePrismTheme.ts b/packages/docusaurus-theme-classic/src/theme/hooks/usePrismTheme.ts index 90ac0a4673f4..e8287d3cd9e3 100644 --- a/packages/docusaurus-theme-classic/src/theme/hooks/usePrismTheme.ts +++ b/packages/docusaurus-theme-classic/src/theme/hooks/usePrismTheme.ts @@ -6,15 +6,11 @@ */ import defaultTheme from 'prism-react-renderer/themes/palenight'; -import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; import useThemeContext from '@theme/hooks/useThemeContext'; +import useThemeConfig from '../../utils/useThemeConfig'; const usePrismTheme = (): typeof defaultTheme => { - const { - siteConfig: { - themeConfig: {prism = {}}, - }, - } = useDocusaurusContext(); + const {prism} = useThemeConfig(); const {isDarkTheme} = useThemeContext(); const lightModeTheme = prism.theme || defaultTheme; const darkModeTheme = prism.darkTheme || lightModeTheme; diff --git a/packages/docusaurus-theme-classic/src/theme/hooks/useTheme.ts b/packages/docusaurus-theme-classic/src/theme/hooks/useTheme.ts index 24d3c770cde6..74265b0eac31 100644 --- a/packages/docusaurus-theme-classic/src/theme/hooks/useTheme.ts +++ b/packages/docusaurus-theme-classic/src/theme/hooks/useTheme.ts @@ -7,9 +7,9 @@ import {useState, useCallback, useEffect} from 'react'; -import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; import ExecutionEnvironment from '@docusaurus/ExecutionEnvironment'; import type {useThemeReturns} from '@theme/hooks/useTheme'; +import useThemeConfig from '../../utils/useThemeConfig'; const themes = { light: 'light', @@ -38,10 +38,8 @@ const storeTheme = (newTheme) => { const useTheme = (): useThemeReturns => { const { - siteConfig: { - themeConfig: {colorMode: {disableSwitch = false} = {}} = {}, - } = {}, - } = useDocusaurusContext(); + colorMode: {disableSwitch = false}, + } = useThemeConfig(); const [theme, setTheme] = useState(getInitialTheme); const setLightTheme = useCallback(() => { diff --git a/packages/docusaurus-theme-classic/src/utils/useThemeConfig.ts b/packages/docusaurus-theme-classic/src/utils/useThemeConfig.ts new file mode 100644 index 000000000000..db8b66a6b6d3 --- /dev/null +++ b/packages/docusaurus-theme-classic/src/utils/useThemeConfig.ts @@ -0,0 +1,24 @@ +/** + * 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 useDocusaurusContext from '@docusaurus/useDocusaurusContext'; + +type ThemeConfig = { + // TODO we should complete this theme config type over time + // and share it across all themes + // and use it in the Joi validation schema? + + // TODO temporary types + navbar: any; + colorMode: any; + announcementBar: any; + prism: any; + footer: any; +}; + +export default function useThemeConfig(): ThemeConfig { + return useDocusaurusContext().siteConfig.themeConfig as ThemeConfig; +} diff --git a/packages/docusaurus-theme-classic/src/validateThemeConfig.js b/packages/docusaurus-theme-classic/src/validateThemeConfig.js index f365bfd05d0f..6b86e99b4098 100644 --- a/packages/docusaurus-theme-classic/src/validateThemeConfig.js +++ b/packages/docusaurus-theme-classic/src/validateThemeConfig.js @@ -23,6 +23,13 @@ const DEFAULT_COLOR_MODE_CONFIG = { const DEFAULT_CONFIG = { colorMode: DEFAULT_COLOR_MODE_CONFIG, metadatas: [], + prism: { + additionalLanguages: [], + }, + navbar: { + hideOnScroll: false, + items: [], + }, }; exports.DEFAULT_CONFIG = DEFAULT_CONFIG; @@ -195,13 +202,15 @@ const ThemeConfigSchema = Joi.object({ }).optional(), navbar: Joi.object({ style: Joi.string().equal('dark', 'primary'), - hideOnScroll: Joi.bool().default(false), + hideOnScroll: Joi.bool().default(DEFAULT_CONFIG.navbar.hideOnScroll), // TODO temporary (@alpha-58) links: Joi.any().forbidden().messages({ 'any.unknown': 'themeConfig.navbar.links has been renamed as themeConfig.navbar.items', }), - items: Joi.array().items(NavbarItemSchema), + items: Joi.array() + .items(NavbarItemSchema) + .default(DEFAULT_CONFIG.navbar.items), title: Joi.string().allow('', null), logo: Joi.object({ alt: Joi.string().allow(''), @@ -210,7 +219,7 @@ const ThemeConfigSchema = Joi.object({ href: Joi.string(), target: Joi.string(), }), - }), + }).default(DEFAULT_CONFIG.navbar), footer: Joi.object({ style: Joi.string().equal('dark', 'light').default('light'), logo: Joi.object({ @@ -219,13 +228,15 @@ const ThemeConfigSchema = Joi.object({ href: Joi.string(), }), copyright: Joi.string(), - links: Joi.array().items( - Joi.object({ - title: Joi.string().required(), - items: Joi.array().items(FooterLinkItemSchema).default([]), - }), - ), - }), + links: Joi.array() + .items( + Joi.object({ + title: Joi.string().required(), + items: Joi.array().items(FooterLinkItemSchema).default([]), + }), + ) + .default([]), + }).optional(), prism: Joi.object({ theme: Joi.object({ plain: Joi.alternatives().try(Joi.array(), Joi.object()).required(), @@ -236,8 +247,12 @@ const ThemeConfigSchema = Joi.object({ styles: Joi.alternatives().try(Joi.array(), Joi.object()).required(), }), defaultLanguage: Joi.string(), - additionalLanguages: Joi.array().items(Joi.string()), - }).unknown(), + additionalLanguages: Joi.array() + .items(Joi.string()) + .default(DEFAULT_CONFIG.prism.additionalLanguages), + }) + .default(DEFAULT_CONFIG.prism) + .unknown(), }); exports.ThemeConfigSchema = ThemeConfigSchema; diff --git a/website/versioned_docs/version-2.0.0-alpha.64/theme-bootstrap.md b/website/versioned_docs/version-2.0.0-alpha.64/theme-bootstrap.md index c5abde38f948..7331da5a6fd1 100644 --- a/website/versioned_docs/version-2.0.0-alpha.64/theme-bootstrap.md +++ b/website/versioned_docs/version-2.0.0-alpha.64/theme-bootstrap.md @@ -24,18 +24,13 @@ import useLogo from '@theme/hooks/useLogo'; const Example = () => { // highlight-next-line - const {logoLink, logoLinkProps, logoImageUrl, logoAlt} = useLogo(); + const {logoLink, logoLinkProps, logoImageUrl, logoAlt} = useLogo(); return ( - {logoImageUrl != null && ( - {logoAlt} - )} + {logoImageUrl != null && {logoAlt}} - ) + ); }; ``` @@ -107,7 +102,6 @@ React Router should automatically apply active link styling to links, but you ca Outbound (external) links automatically get `target="_blank" rel="noopener noreferrer"` attributes. - ## Footer You can add logo and a copyright to the footer via `themeConfig.footer`. Logo can be placed in [static folder](static-assets.md). Logo URL works in the same way of the navbar logo. @@ -125,58 +119,58 @@ You can add logo and a copyright to the footer via `themeConfig.footer`. Logo ca ``` ## Footer Links -You can add links to the navbar via `themeConfig.footer.links`: +You can add links to the navbar via `themeConfig.footer.links`: ```js {5-15} title="docusaurus.config.js" module.exports = { // ... footer: { - links: [ - { - // Label of the section of these links - title: 'Docs', - items: [ - { - // Label of the link - label: 'Style Guide', - // Client-side routing, used for navigating within the website. - // The baseUrl will be automatically prepended to this value. - to: 'docs/', - }, - { - label: 'Second Doc', - to: 'docs/doc2/', - }, - ], - }, - { - title: 'Community', - items: [ - { - label: 'Stack Overflow', - // A full-page navigation, used for navigating outside of the website. - href: 'https://stackoverflow.com/questions/tagged/docusaurus', - }, - { - label: 'Discord', - href: 'https://discordapp.com/invite/docusaurus', - }, - { - label: 'Twitter', - href: 'https://twitter.com/docusaurus', - }, - { - //Renders the html pass-through instead of a simple link - html: ` + links: [ + { + // Label of the section of these links + title: 'Docs', + items: [ + { + // Label of the link + label: 'Style Guide', + // Client-side routing, used for navigating within the website. + // The baseUrl will be automatically prepended to this value. + to: 'docs/', + }, + { + label: 'Second Doc', + to: 'docs/doc2/', + }, + ], + }, + { + title: 'Community', + items: [ + { + label: 'Stack Overflow', + // A full-page navigation, used for navigating outside of the website. + href: 'https://stackoverflow.com/questions/tagged/docusaurus', + }, + { + label: 'Discord', + href: 'https://discordapp.com/invite/docusaurus', + }, + { + label: 'Twitter', + href: 'https://twitter.com/docusaurus', + }, + { + //Renders the html pass-through instead of a simple link + html: ` Deploys by Netlify `, - }, - ], - }, - ], - }, + }, + ], + }, + ], + }, }; ```