mirror of
https://github.com/facebook/docusaurus.git
synced 2025-05-03 12:17:20 +02:00
feat(theme): create more generic ThemedComponent util from ThemedImage (#8890)
This commit is contained in:
parent
14586895ae
commit
f76fc1bfac
4 changed files with 105 additions and 31 deletions
|
@ -6,44 +6,21 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import clsx from 'clsx';
|
import {ThemedComponent} from '@docusaurus/theme-common';
|
||||||
import useIsBrowser from '@docusaurus/useIsBrowser';
|
|
||||||
import {useColorMode} from '@docusaurus/theme-common';
|
|
||||||
import type {Props} from '@theme/ThemedImage';
|
import type {Props} from '@theme/ThemedImage';
|
||||||
|
|
||||||
import styles from './styles.module.css';
|
|
||||||
|
|
||||||
export default function ThemedImage(props: Props): JSX.Element {
|
export default function ThemedImage(props: Props): JSX.Element {
|
||||||
const isBrowser = useIsBrowser();
|
const {sources, className: parentClassName, alt, ...propsRest} = props;
|
||||||
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'];
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<ThemedComponent className={parentClassName}>
|
||||||
{renderedSourceNames.map((sourceName) => (
|
{({theme, className}) => (
|
||||||
<img
|
<img
|
||||||
key={sourceName}
|
src={sources[theme]}
|
||||||
src={sources[sourceName]}
|
|
||||||
alt={alt}
|
alt={alt}
|
||||||
className={clsx(
|
className={className}
|
||||||
styles.themedImage,
|
|
||||||
styles[`themedImage--${sourceName}`],
|
|
||||||
className,
|
|
||||||
)}
|
|
||||||
{...propsRest}
|
{...propsRest}
|
||||||
/>
|
/>
|
||||||
))}
|
)}
|
||||||
</>
|
</ThemedComponent>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 <React.Fragment key={theme}>{themedElement}</React.Fragment>;
|
||||||
|
})}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
|
@ -24,6 +24,8 @@ export {
|
||||||
type ColorModeConfig,
|
type ColorModeConfig,
|
||||||
} from './utils/useThemeConfig';
|
} from './utils/useThemeConfig';
|
||||||
|
|
||||||
|
export {default as ThemedComponent} from './components/ThemedComponent';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
createStorageSlot,
|
createStorageSlot,
|
||||||
useStorageSlot,
|
useStorageSlot,
|
||||||
|
|
Loading…
Add table
Reference in a new issue