diff --git a/packages/docusaurus-theme-bootstrap/src/theme/ThemeContext.ts b/packages/docusaurus-theme-bootstrap/src/theme/ThemeContext.ts new file mode 100644 index 000000000000..f5e0f05bc165 --- /dev/null +++ b/packages/docusaurus-theme-bootstrap/src/theme/ThemeContext.ts @@ -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( + undefined, +); + +export default ThemeContext; diff --git a/packages/docusaurus-theme-bootstrap/src/theme/ThemedImage/index.tsx b/packages/docusaurus-theme-bootstrap/src/theme/ThemedImage/index.tsx new file mode 100644 index 000000000000..d9e5961a189d --- /dev/null +++ b/packages/docusaurus-theme-bootstrap/src/theme/ThemedImage/index.tsx @@ -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 ( + {alt} + ); + })} + + ); +}; + +export default ThemedImage; diff --git a/packages/docusaurus-theme-bootstrap/src/theme/ThemedImage/styles.module.css b/packages/docusaurus-theme-bootstrap/src/theme/ThemedImage/styles.module.css new file mode 100644 index 000000000000..1066a48905a9 --- /dev/null +++ b/packages/docusaurus-theme-bootstrap/src/theme/ThemedImage/styles.module.css @@ -0,0 +1,11 @@ +.themedImage { + display: none; +} + +html[data-theme='light'] .themedImage--light { + display: block; +} + +html[data-theme='dark'] .themedImage--dark { + display: block; +} diff --git a/packages/docusaurus-theme-bootstrap/src/theme/hooks/useThemeContext.ts b/packages/docusaurus-theme-bootstrap/src/theme/hooks/useThemeContext.ts new file mode 100644 index 000000000000..5a9affb73ed7 --- /dev/null +++ b/packages/docusaurus-theme-bootstrap/src/theme/hooks/useThemeContext.ts @@ -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(ThemeContext); + return context == null ? {isDarkTheme: false} : context; +} + +export default useThemeContext; diff --git a/packages/docusaurus-theme-bootstrap/src/types.d.ts b/packages/docusaurus-theme-bootstrap/src/types.d.ts index b820dcb18393..0a5764eda0be 100644 --- a/packages/docusaurus-theme-bootstrap/src/types.d.ts +++ b/packages/docusaurus-theme-bootstrap/src/types.d.ts @@ -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, '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'; diff --git a/packages/docusaurus-theme-classic/src/theme/ThemedImage/index.tsx b/packages/docusaurus-theme-classic/src/theme/ThemedImage/index.tsx new file mode 100644 index 000000000000..d9e5961a189d --- /dev/null +++ b/packages/docusaurus-theme-classic/src/theme/ThemedImage/index.tsx @@ -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 ( + {alt} + ); + })} + + ); +}; + +export default ThemedImage; diff --git a/packages/docusaurus-theme-classic/src/theme/ThemedImage/styles.module.css b/packages/docusaurus-theme-classic/src/theme/ThemedImage/styles.module.css new file mode 100644 index 000000000000..1066a48905a9 --- /dev/null +++ b/packages/docusaurus-theme-classic/src/theme/ThemedImage/styles.module.css @@ -0,0 +1,11 @@ +.themedImage { + display: none; +} + +html[data-theme='light'] .themedImage--light { + display: block; +} + +html[data-theme='dark'] .themedImage--dark { + display: block; +} diff --git a/packages/docusaurus-theme-classic/src/types.d.ts b/packages/docusaurus-theme-classic/src/types.d.ts index 122b65cdc5d1..3087612c349f 100644 --- a/packages/docusaurus-theme-classic/src/types.d.ts +++ b/packages/docusaurus-theme-classic/src/types.d.ts @@ -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, 'src'>; + + const ThemedImage: (props: Props) => JSX.Element; + export default ThemedImage; +} + declare module '@theme/ThemeProvider' { import type {ReactNode} from 'react'; diff --git a/website/docs/markdown-features.mdx b/website/docs/markdown-features.mdx index 701a91dc2306..96fb3eeff959 100644 --- a/website/docs/markdown-features.mdx +++ b/website/docs/markdown-features.mdx @@ -1086,3 +1086,30 @@ html[data-theme='dark'] .themedDocusaurus [fill='#FFFF50'] { ``` + +### 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'; + +; +``` + +import useBaseUrl from '@docusaurus/useBaseUrl'; +import ThemedImage from '@theme/ThemedImage'; + +