feat(v2): add ThemedImage component (#3730)

* feat(v2): add ThemedImage component

* add themed image problematic example

* refactor, SSR fix, openness about extending img tag, docs update

* refactor themed-image

* update themed image doc

Co-authored-by: slorber <lorber.sebastien@gmail.com>
Co-authored-by: Sébastien Lorber <slorber@users.noreply.github.com>
This commit is contained in:
Bartosz Kaszubowski 2020-11-13 14:29:45 +01:00 committed by GitHub
parent 73f151d04c
commit 9d90e896f0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 229 additions and 4 deletions

View file

@ -0,0 +1,15 @@
/**
* 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 type {ThemeContextProps} from '@theme/hooks/useThemeContext';
const ThemeContext = React.createContext<ThemeContextProps | undefined>(
undefined,
);
export default ThemeContext;

View file

@ -0,0 +1,53 @@
/**
* 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 useDocusaurusContext from '@docusaurus/useDocusaurusContext';
import useThemeContext from '@theme/hooks/useThemeContext';
import type {Props} from '@theme/ThemedImage';
import styles from './styles.module.css';
const ThemedImage = (props: Props): JSX.Element => {
const {isClient} = useDocusaurusContext();
const {isDarkTheme} = useThemeContext();
const {sources, className, alt = '', ...propsRest} = props;
type SourceName = keyof Props['sources'];
const renderedSourceNames: SourceName[] = isClient
? isDarkTheme
? ['dark']
: ['light']
: // We need to render both images on the server to avoid flash
// See https://github.com/facebook/docusaurus/pull/3730
['light', 'dark'];
return (
<>
{renderedSourceNames.map((sourceName) => {
return (
<img
key={sourceName}
src={sources[sourceName]}
alt={alt}
className={clsx(
styles.themedImage,
styles[`themedImage--${sourceName}`],
className,
)}
{...propsRest}
/>
);
})}
</>
);
};
export default ThemedImage;

View file

@ -0,0 +1,11 @@
.themedImage {
display: none;
}
html[data-theme='light'] .themedImage--light {
display: block;
}
html[data-theme='dark'] .themedImage--dark {
display: block;
}

View file

@ -0,0 +1,19 @@
/**
* 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 {useContext} from 'react';
import ThemeContext from '@theme/ThemeContext';
import type {ThemeContextProps} from '@theme/hooks/useThemeContext';
// TODO: Un-stub the theme context (#3730)
function useThemeContext(): ThemeContextProps {
const context = useContext<ThemeContextProps | undefined>(ThemeContext);
return context == null ? {isDarkTheme: false} : context;
}
export default useThemeContext;

View file

@ -78,7 +78,7 @@ declare module '@theme/DocSidebar' {
}
declare module '@theme/Tabs' {
import type {ReactElement, ReactNode} from 'react';
import type {ReactElement} from 'react';
export type Props = {
readonly block?: boolean;
@ -88,10 +88,24 @@ declare module '@theme/Tabs' {
readonly groupId?: string;
};
const Tabs: () => JSX.Element;
const Tabs: (props: Props) => JSX.Element;
export default Tabs;
}
declare module '@theme/ThemedImage' {
import type {ComponentProps} from 'react';
export type Props = {
readonly sources: {
readonly light: string;
readonly dark: string;
};
} & Omit<ComponentProps<'img'>, 'src'>;
const ThemedImage: (props: Props) => JSX.Element;
export default ThemedImage;
}
declare module '@theme/Footer' {
const Footer: () => JSX.Element | null;
export default Footer;
@ -125,6 +139,14 @@ declare module '@theme/hooks/useLogo' {
export default useLogo;
}
declare module '@theme/hooks/useThemeContext' {
export type ThemeContextProps = {
isDarkTheme: boolean;
};
export default function useThemeContext(): ThemeContextProps;
}
declare module '@theme/Layout' {
import type {ReactNode} from 'react';

View file

@ -0,0 +1,53 @@
/**
* 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 useDocusaurusContext from '@docusaurus/useDocusaurusContext';
import useThemeContext from '@theme/hooks/useThemeContext';
import type {Props} from '@theme/ThemedImage';
import styles from './styles.module.css';
const ThemedImage = (props: Props): JSX.Element => {
const {isClient} = useDocusaurusContext();
const {isDarkTheme} = useThemeContext();
const {sources, className, alt = '', ...propsRest} = props;
type SourceName = keyof Props['sources'];
const renderedSourceNames: SourceName[] = isClient
? isDarkTheme
? ['dark']
: ['light']
: // We need to render both images on the server to avoid flash
// See https://github.com/facebook/docusaurus/pull/3730
['light', 'dark'];
return (
<>
{renderedSourceNames.map((sourceName) => {
return (
<img
key={sourceName}
src={sources[sourceName]}
alt={alt}
className={clsx(
styles.themedImage,
styles[`themedImage--${sourceName}`],
className,
)}
{...propsRest}
/>
);
})}
</>
);
};
export default ThemedImage;

View file

@ -0,0 +1,11 @@
.themedImage {
display: none;
}
html[data-theme='light'] .themedImage--light {
display: block;
}
html[data-theme='dark'] .themedImage--dark {
display: block;
}

View file

@ -381,7 +381,7 @@ declare module '@theme/TabItem' {
readonly className: string;
};
const TabItem: () => JSX.Element;
const TabItem: (props: Props) => JSX.Element;
export default TabItem;
}
@ -399,10 +399,24 @@ declare module '@theme/Tabs' {
readonly className?: string;
};
const Tabs: () => JSX.Element;
const Tabs: (props: Props) => JSX.Element;
export default Tabs;
}
declare module '@theme/ThemedImage' {
import type {ComponentProps} from 'react';
export type Props = {
readonly sources: {
readonly light: string;
readonly dark: string;
};
} & Omit<ComponentProps<'img'>, 'src'>;
const ThemedImage: (props: Props) => JSX.Element;
export default ThemedImage;
}
declare module '@theme/ThemeProvider' {
import type {ReactNode} from 'react';

View file

@ -1086,3 +1086,30 @@ html[data-theme='dark'] .themedDocusaurus [fill='#FFFF50'] {
```
<DocusaurusSvg className="themedDocusaurus" />
### Themed Images
Docusaurus supports themed images: the `ThemedImage` component (included in the classic/bootstrap themes) allows you to switch the image source based on the current theme.
```jsx {5-8}
import ThemedImage from '@theme/ThemedImage';
<ThemedImage
alt="Docusaurus themed image"
sources={{
light: useBaseUrl('img/docusaurus_light.svg'),
dark: useBaseUrl('img/docusaurus_dark.svg'),
}}
/>;
```
import useBaseUrl from '@docusaurus/useBaseUrl';
import ThemedImage from '@theme/ThemedImage';
<ThemedImage
alt="Docusaurus themed image"
sources={{
light: useBaseUrl('img/docusaurus_keytar.svg'),
dark: useBaseUrl('img/docusaurus_speed.svg'),
}}
/>