From f105f71e2276a82cc062eb50a8ffdc8dab5357c4 Mon Sep 17 00:00:00 2001 From: sebastienlorber Date: Thu, 13 Apr 2023 16:03:33 +0200 Subject: [PATCH] extract ThemedComponent --- .../src/theme/ThemedImage/index.tsx | 39 ++-------- .../src/components/ThemedComponent/index.tsx | 77 +++++++++++++++++++ .../ThemedComponent/styles.module.css | 18 +++++ packages/docusaurus-theme-common/src/index.ts | 2 + 4 files changed, 105 insertions(+), 31 deletions(-) create mode 100644 packages/docusaurus-theme-common/src/components/ThemedComponent/index.tsx create mode 100644 packages/docusaurus-theme-common/src/components/ThemedComponent/styles.module.css diff --git a/packages/docusaurus-theme-classic/src/theme/ThemedImage/index.tsx b/packages/docusaurus-theme-classic/src/theme/ThemedImage/index.tsx index b0b938350278..2090d14e09f7 100644 --- a/packages/docusaurus-theme-classic/src/theme/ThemedImage/index.tsx +++ b/packages/docusaurus-theme-classic/src/theme/ThemedImage/index.tsx @@ -6,44 +6,21 @@ */ import React from 'react'; -import clsx from 'clsx'; -import useIsBrowser from '@docusaurus/useIsBrowser'; -import {useColorMode} from '@docusaurus/theme-common'; +import {ThemedComponent} from '@docusaurus/theme-common'; import type {Props} from '@theme/ThemedImage'; -import styles from './styles.module.css'; - export default function ThemedImage(props: Props): JSX.Element { - const isBrowser = useIsBrowser(); - const {colorMode} = useColorMode(); - const {sources, className, alt, ...propsRest} = props; - - type SourceName = keyof Props['sources']; - - const clientThemes: SourceName[] = - colorMode === 'dark' ? ['dark'] : ['light']; - - const renderedSourceNames: SourceName[] = isBrowser - ? clientThemes - : // We need to render both images on the server to avoid flash - // See https://github.com/facebook/docusaurus/pull/3730 - ['light', 'dark']; - + const {sources, className: parentClassName, alt, ...propsRest} = props; return ( - <> - {renderedSourceNames.map((sourceName) => ( + + {({theme, className}) => ( {alt} - ))} - + )} + ); } diff --git a/packages/docusaurus-theme-common/src/components/ThemedComponent/index.tsx b/packages/docusaurus-theme-common/src/components/ThemedComponent/index.tsx new file mode 100644 index 000000000000..dcc631e04578 --- /dev/null +++ b/packages/docusaurus-theme-common/src/components/ThemedComponent/index.tsx @@ -0,0 +1,77 @@ +/** + * 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 React from 'react'; +import clsx from 'clsx'; +import useIsBrowser from '@docusaurus/useIsBrowser'; +import {useColorMode} from '../../contexts/colorMode'; + +import styles from './styles.module.css'; + +const AllThemes = ['light', 'dark'] as const; + +type Theme = (typeof AllThemes)[number]; + +type RenderFn = ({ + theme, + className, +}: { + theme: Theme; + className: string; +}) => React.ReactNode; + +type Props = { + children: RenderFn; + className?: string; +}; + +/** + * Generic component to render anything themed in light/dark + * Note: it's preferable to use CSS for theming because this component + * will need to render all the variants during SSR to avoid a theme flash. + * + * Use this only when CSS customizations are not convenient or impossible. + * For example, rendering themed images or SVGs... + * + * @param className applied to all the variants + * @param children function to render a theme variant + * @constructor + */ +export default function ThemedComponent({ + className, + children, +}: Props): JSX.Element { + const isBrowser = useIsBrowser(); + const {colorMode} = useColorMode(); + + function getThemesToRender(): Theme[] { + if (isBrowser) { + return colorMode === 'dark' ? ['dark'] : ['light']; + } + // We need to render both components on the server / hydration to avoid: + // - a flash of wrong theme before hydration + // - React hydration mismatches + // See https://github.com/facebook/docusaurus/pull/3730 + return ['light', 'dark']; + } + + return ( + <> + {getThemesToRender().map((theme) => { + const themedElement = children({ + theme, + className: clsx( + className, + styles.themedComponent, + styles[`themedComponent--${theme}`], + ), + }); + return {themedElement}; + })} + + ); +} diff --git a/packages/docusaurus-theme-common/src/components/ThemedComponent/styles.module.css b/packages/docusaurus-theme-common/src/components/ThemedComponent/styles.module.css new file mode 100644 index 000000000000..ebf047bd6c4c --- /dev/null +++ b/packages/docusaurus-theme-common/src/components/ThemedComponent/styles.module.css @@ -0,0 +1,18 @@ +/** + * 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. + */ + +.themedComponent { + display: none; +} + +[data-theme='light'] .themedComponent--light { + display: initial; +} + +[data-theme='dark'] .themedComponent--dark { + display: initial; +} diff --git a/packages/docusaurus-theme-common/src/index.ts b/packages/docusaurus-theme-common/src/index.ts index 0fdda7f3aa4f..0d69d161aeaf 100644 --- a/packages/docusaurus-theme-common/src/index.ts +++ b/packages/docusaurus-theme-common/src/index.ts @@ -24,6 +24,8 @@ export { type ColorModeConfig, } from './utils/useThemeConfig'; +export {default as ThemedComponent} from './components/ThemedComponent'; + export { createStorageSlot, useStorageSlot,