diff --git a/packages/docusaurus-mdx-loader/src/remark/mdx1Compat/codeCompatPlugin.ts b/packages/docusaurus-mdx-loader/src/remark/mdx1Compat/codeCompatPlugin.ts index f71dca66ee..6078ee6987 100644 --- a/packages/docusaurus-mdx-loader/src/remark/mdx1Compat/codeCompatPlugin.ts +++ b/packages/docusaurus-mdx-loader/src/remark/mdx1Compat/codeCompatPlugin.ts @@ -25,6 +25,7 @@ const plugin: Plugin = function plugin(): Transformer { node.data.hProperties = node.data.hProperties || {}; node.data.hProperties.metastring = node.meta; + // TODO Docusaurus v4: remove special case // Retrocompatible support for live codeblock metastring // Not really the appropriate place to handle that :s node.data.hProperties.live = node.meta?.split(' ').includes('live'); diff --git a/packages/docusaurus-theme-common/src/utils/useThemeConfig.ts b/packages/docusaurus-theme-common/src/utils/useThemeConfig.ts index 4d0b08fa94..a1d095387e 100644 --- a/packages/docusaurus-theme-common/src/utils/useThemeConfig.ts +++ b/packages/docusaurus-theme-common/src/utils/useThemeConfig.ts @@ -104,6 +104,7 @@ export type TableOfContents = { maxHeadingLevel: number; }; +// TODO Docusaurus v4: use interface + declaration merging to enhance // Theme config after validation/normalization export type ThemeConfig = { docs: { diff --git a/packages/docusaurus-theme-live-codeblock/src/theme-live-codeblock.d.ts b/packages/docusaurus-theme-live-codeblock/src/theme-live-codeblock.d.ts index e3854a0681..3e6c8e971c 100644 --- a/packages/docusaurus-theme-live-codeblock/src/theme-live-codeblock.d.ts +++ b/packages/docusaurus-theme-live-codeblock/src/theme-live-codeblock.d.ts @@ -16,6 +16,14 @@ declare module '@docusaurus/theme-live-codeblock' { }; } +declare module '@theme/LiveCodeBlock' { + import type {Props as BaseProps} from '@theme/CodeBlock'; + + export interface Props extends BaseProps {} + + export default function LiveCodeBlock(props: Props): ReactNode; +} + declare module '@theme/Playground' { import type {ReactNode} from 'react'; import type {Props as BaseProps} from '@theme/CodeBlock'; @@ -31,6 +39,64 @@ declare module '@theme/Playground' { export default function Playground(props: LiveProviderProps): ReactNode; } +declare module '@theme/Playground/Provider' { + import type {ReactNode} from 'react'; + import type {Props as PlaygroundProps} from '@theme/Playground'; + + export interface Props extends Omit { + code: string | undefined; + children: ReactNode; + } + + export default function PlaygroundProvider(props: Props): ReactNode; +} + +declare module '@theme/Playground/Container' { + import type {ReactNode} from 'react'; + + export interface Props { + children: ReactNode; + } + + export default function PlaygroundContainer(props: Props): ReactNode; +} + +declare module '@theme/Playground/Layout' { + import type {ReactNode} from 'react'; + + // eslint-disable-next-line @typescript-eslint/no-empty-interface + export interface Props {} + + export default function PlaygroundLayout(props: Props): ReactNode; +} + +declare module '@theme/Playground/Preview' { + import type {ReactNode} from 'react'; + + // eslint-disable-next-line @typescript-eslint/no-empty-interface + export interface Props {} + + export default function PlaygroundPreview(props: Props): ReactNode; +} + +declare module '@theme/Playground/Editor' { + import type {ReactNode} from 'react'; + + // eslint-disable-next-line @typescript-eslint/no-empty-interface + export interface Props {} + + export default function PlaygroundEditor(props: Props): ReactNode; +} + +declare module '@theme/Playground/Header' { + import type {ReactNode} from 'react'; + + // eslint-disable-next-line @typescript-eslint/no-empty-interface + export interface Props {} + + export default function PlaygroundHeader(props: Props): ReactNode; +} + declare module '@theme/ReactLiveScope' { type Scope = { [key: string]: unknown; diff --git a/packages/docusaurus-theme-live-codeblock/src/theme/CodeBlock/index.tsx b/packages/docusaurus-theme-live-codeblock/src/theme/CodeBlock/index.tsx index 225073c0ee..a7290ad768 100644 --- a/packages/docusaurus-theme-live-codeblock/src/theme/CodeBlock/index.tsx +++ b/packages/docusaurus-theme-live-codeblock/src/theme/CodeBlock/index.tsx @@ -5,21 +5,28 @@ * LICENSE file in the root directory of this source tree. */ -import React from 'react'; -import Playground from '@theme/Playground'; -import ReactLiveScope from '@theme/ReactLiveScope'; -import CodeBlock, {type Props} from '@theme-init/CodeBlock'; +import React, {type ReactNode} from 'react'; +import type {Props as CodeBlockProps} from '@theme/CodeBlock'; +import OriginalCodeBlock from '@theme-init/CodeBlock'; +import LiveCodeBlock from '@theme/LiveCodeBlock'; -const withLiveEditor = (Component: typeof CodeBlock) => { - function WrappedComponent(props: Props) { - if (props.live) { - return ; - } - - return ; +// TODO Docusaurus v4: remove special case +// see packages/docusaurus-mdx-loader/src/remark/mdx1Compat/codeCompatPlugin.ts +// we can just use the metastring instead +declare module '@theme/CodeBlock' { + interface Props { + live?: boolean; } +} - return WrappedComponent; -}; +function isLiveCodeBlock(props: CodeBlockProps): boolean { + return !!props.live; +} -export default withLiveEditor(CodeBlock); +export default function CodeBlockEnhancer(props: CodeBlockProps): ReactNode { + return isLiveCodeBlock(props) ? ( + + ) : ( + + ); +} diff --git a/packages/docusaurus-theme-live-codeblock/src/theme/LiveCodeBlock/index.tsx b/packages/docusaurus-theme-live-codeblock/src/theme/LiveCodeBlock/index.tsx new file mode 100644 index 0000000000..5beb7ca277 --- /dev/null +++ b/packages/docusaurus-theme-live-codeblock/src/theme/LiveCodeBlock/index.tsx @@ -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, {type ReactNode} from 'react'; +import Playground from '@theme/Playground'; +import ReactLiveScope from '@theme/ReactLiveScope'; +import type {Props} from '@theme/LiveCodeBlock'; + +export default function LiveCodeBlock(props: Props): ReactNode { + return ; +} diff --git a/packages/docusaurus-theme-live-codeblock/src/theme/Playground/Container/index.tsx b/packages/docusaurus-theme-live-codeblock/src/theme/Playground/Container/index.tsx new file mode 100644 index 0000000000..4dd77725bb --- /dev/null +++ b/packages/docusaurus-theme-live-codeblock/src/theme/Playground/Container/index.tsx @@ -0,0 +1,16 @@ +/** + * 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, {type ReactNode} from 'react'; + +import type {Props} from '@theme/Playground/Container'; + +import styles from './styles.module.css'; + +export default function PlaygroundContainer({children}: Props): ReactNode { + return
{children}
; +} diff --git a/packages/docusaurus-theme-live-codeblock/src/theme/Playground/Container/styles.module.css b/packages/docusaurus-theme-live-codeblock/src/theme/Playground/Container/styles.module.css new file mode 100644 index 0000000000..e5690b0fe6 --- /dev/null +++ b/packages/docusaurus-theme-live-codeblock/src/theme/Playground/Container/styles.module.css @@ -0,0 +1,13 @@ +/** + * 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. + */ + +.playgroundContainer { + margin-bottom: var(--ifm-leading); + border-radius: var(--ifm-global-radius); + box-shadow: var(--ifm-global-shadow-lw); + overflow: hidden; +} diff --git a/packages/docusaurus-theme-live-codeblock/src/theme/Playground/Editor/index.tsx b/packages/docusaurus-theme-live-codeblock/src/theme/Playground/Editor/index.tsx new file mode 100644 index 0000000000..e807f85706 --- /dev/null +++ b/packages/docusaurus-theme-live-codeblock/src/theme/Playground/Editor/index.tsx @@ -0,0 +1,35 @@ +/** + * 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, {type ReactNode} from 'react'; +import {LiveEditor} from 'react-live'; +import useIsBrowser from '@docusaurus/useIsBrowser'; +import Translate from '@docusaurus/Translate'; +import PlaygroundHeader from '@theme/Playground/Header'; + +import styles from './styles.module.css'; + +export default function PlaygroundEditor(): ReactNode { + const isBrowser = useIsBrowser(); + return ( + <> + + + Live Editor + + + + + ); +} diff --git a/packages/docusaurus-theme-live-codeblock/src/theme/Playground/Editor/styles.module.css b/packages/docusaurus-theme-live-codeblock/src/theme/Playground/Editor/styles.module.css new file mode 100644 index 0000000000..3b5165baf9 --- /dev/null +++ b/packages/docusaurus-theme-live-codeblock/src/theme/Playground/Editor/styles.module.css @@ -0,0 +1,17 @@ +/** + * 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. + */ + +.playgroundEditor { + font: var(--ifm-code-font-size) / var(--ifm-pre-line-height) + var(--ifm-font-family-monospace) !important; + /* rtl:ignore */ + direction: ltr; +} + +.playgroundEditor pre { + border-radius: 0; +} diff --git a/packages/docusaurus-theme-live-codeblock/src/theme/Playground/Header/index.tsx b/packages/docusaurus-theme-live-codeblock/src/theme/Playground/Header/index.tsx new file mode 100644 index 0000000000..5d1e4d2499 --- /dev/null +++ b/packages/docusaurus-theme-live-codeblock/src/theme/Playground/Header/index.tsx @@ -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 React, {type ReactNode} from 'react'; +import clsx from 'clsx'; + +import styles from './styles.module.css'; + +export default function PlaygroundHeader({ + children, +}: { + children: ReactNode; +}): ReactNode { + return
{children}
; +} diff --git a/packages/docusaurus-theme-live-codeblock/src/theme/Playground/styles.module.css b/packages/docusaurus-theme-live-codeblock/src/theme/Playground/Header/styles.module.css similarity index 53% rename from packages/docusaurus-theme-live-codeblock/src/theme/Playground/styles.module.css rename to packages/docusaurus-theme-live-codeblock/src/theme/Playground/Header/styles.module.css index 6696d31335..092af7825d 100644 --- a/packages/docusaurus-theme-live-codeblock/src/theme/Playground/styles.module.css +++ b/packages/docusaurus-theme-live-codeblock/src/theme/Playground/Header/styles.module.css @@ -5,13 +5,6 @@ * LICENSE file in the root directory of this source tree. */ -.playgroundContainer { - margin-bottom: var(--ifm-leading); - border-radius: var(--ifm-global-radius); - box-shadow: var(--ifm-global-shadow-lw); - overflow: hidden; -} - .playgroundHeader { letter-spacing: 0.08rem; padding: 0.75rem; @@ -26,19 +19,3 @@ background: var(--ifm-color-emphasis-600); color: var(--ifm-color-content-inverse); } - -.playgroundEditor { - font: var(--ifm-code-font-size) / var(--ifm-pre-line-height) - var(--ifm-font-family-monospace) !important; - /* rtl:ignore */ - direction: ltr; -} - -.playgroundEditor pre { - border-radius: 0; -} - -.playgroundPreview { - padding: 1rem; - background-color: var(--ifm-pre-background); -} diff --git a/packages/docusaurus-theme-live-codeblock/src/theme/Playground/Layout/index.tsx b/packages/docusaurus-theme-live-codeblock/src/theme/Playground/Layout/index.tsx new file mode 100644 index 0000000000..63b209ace8 --- /dev/null +++ b/packages/docusaurus-theme-live-codeblock/src/theme/Playground/Layout/index.tsx @@ -0,0 +1,37 @@ +/** + * 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, {type ReactNode} from 'react'; +import {useThemeConfig} from '@docusaurus/theme-common'; +import PlaygroundPreview from '@theme/Playground/Preview'; +import PlaygroundEditor from '@theme/Playground/Editor'; + +import type {ThemeConfig} from '@docusaurus/theme-live-codeblock'; + +function useLiveCodeBlockThemeConfig() { + const themeConfig = useThemeConfig() as unknown as ThemeConfig; + return themeConfig.liveCodeBlock; +} + +export default function PlaygroundLayout(): ReactNode { + const {playgroundPosition} = useLiveCodeBlockThemeConfig(); + return ( + <> + {playgroundPosition === 'top' ? ( + <> + + + + ) : ( + <> + + + + )} + + ); +} diff --git a/packages/docusaurus-theme-live-codeblock/src/theme/Playground/Preview/index.tsx b/packages/docusaurus-theme-live-codeblock/src/theme/Playground/Preview/index.tsx new file mode 100644 index 0000000000..b4f6e1ef88 --- /dev/null +++ b/packages/docusaurus-theme-live-codeblock/src/theme/Playground/Preview/index.tsx @@ -0,0 +1,59 @@ +/** + * 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, {type ReactNode} from 'react'; +import {LiveError, LivePreview} from 'react-live'; +import BrowserOnly from '@docusaurus/BrowserOnly'; +import {ErrorBoundaryErrorMessageFallback} from '@docusaurus/theme-common'; +import ErrorBoundary from '@docusaurus/ErrorBoundary'; +import Translate from '@docusaurus/Translate'; +import PlaygroundHeader from '@theme/Playground/Header'; + +import styles from './styles.module.css'; + +function Loader() { + // Is it worth improving/translating? + // eslint-disable-next-line @docusaurus/no-untranslated-text + return
Loading...
; +} + +function PlaygroundLivePreview(): ReactNode { + // No SSR for the live preview + // See https://github.com/facebook/docusaurus/issues/5747 + return ( + }> + {() => ( + <> + ( + + )}> + + + + + )} + + ); +} + +export default function PlaygroundPreview(): ReactNode { + return ( + <> + + + Result + + +
+ +
+ + ); +} diff --git a/packages/docusaurus-theme-live-codeblock/src/theme/Playground/Preview/styles.module.css b/packages/docusaurus-theme-live-codeblock/src/theme/Playground/Preview/styles.module.css new file mode 100644 index 0000000000..b22c113f10 --- /dev/null +++ b/packages/docusaurus-theme-live-codeblock/src/theme/Playground/Preview/styles.module.css @@ -0,0 +1,11 @@ +/** + * 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. + */ + +.playgroundPreview { + padding: 1rem; + background-color: var(--ifm-pre-background); +} diff --git a/packages/docusaurus-theme-live-codeblock/src/theme/Playground/Provider/index.tsx b/packages/docusaurus-theme-live-codeblock/src/theme/Playground/Provider/index.tsx new file mode 100644 index 0000000000..6664b59d23 --- /dev/null +++ b/packages/docusaurus-theme-live-codeblock/src/theme/Playground/Provider/index.tsx @@ -0,0 +1,35 @@ +/** + * 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, {type ReactNode} from 'react'; +import {LiveProvider} from 'react-live'; +import {usePrismTheme} from '@docusaurus/theme-common'; + +import type {Props} from '@theme/Playground/Provider'; + +// this should rather be a stable function +// see https://github.com/facebook/docusaurus/issues/9630#issuecomment-1855682643 +const DEFAULT_TRANSFORM_CODE = (code: string) => `${code};`; + +export default function PlaygroundProvider({ + code, + children, + ...props +}: Props): ReactNode { + const prismTheme = usePrismTheme(); + const noInline = props.metastring?.includes('noInline') ?? false; + return ( + + {children} + + ); +} diff --git a/packages/docusaurus-theme-live-codeblock/src/theme/Playground/index.tsx b/packages/docusaurus-theme-live-codeblock/src/theme/Playground/index.tsx index 37cda3ee02..61463314d4 100644 --- a/packages/docusaurus-theme-live-codeblock/src/theme/Playground/index.tsx +++ b/packages/docusaurus-theme-live-codeblock/src/theme/Playground/index.tsx @@ -6,137 +6,22 @@ */ import React, {type ReactNode} from 'react'; -import clsx from 'clsx'; -import useIsBrowser from '@docusaurus/useIsBrowser'; -import {LiveProvider, LiveEditor, LiveError, LivePreview} from 'react-live'; -import Translate from '@docusaurus/Translate'; -import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; -import BrowserOnly from '@docusaurus/BrowserOnly'; -import { - ErrorBoundaryErrorMessageFallback, - usePrismTheme, -} from '@docusaurus/theme-common'; -import ErrorBoundary from '@docusaurus/ErrorBoundary'; +import PlaygroundProvider from '@theme/Playground/Provider'; +import PlaygroundContainer from '@theme/Playground/Container'; +import PlaygroundLayout from '@theme/Playground/Layout'; import type {Props} from '@theme/Playground'; -import type {ThemeConfig} from '@docusaurus/theme-live-codeblock'; - -import styles from './styles.module.css'; - -function Header({children}: {children: ReactNode}) { - return
{children}
; -} - -function LivePreviewLoader() { - // Is it worth improving/translating? - // eslint-disable-next-line @docusaurus/no-untranslated-text - return
Loading...
; -} - -function Preview() { - // No SSR for the live preview - // See https://github.com/facebook/docusaurus/issues/5747 - return ( - }> - {() => ( - <> - ( - - )}> - - - - - )} - - ); -} - -function ResultWithHeader() { - return ( - <> -
- - Result - -
- {/* https://github.com/facebook/docusaurus/issues/5747 */} -
- -
- - ); -} - -function ThemedLiveEditor() { - const isBrowser = useIsBrowser(); - return ( - - ); -} - -function EditorWithHeader() { - return ( - <> -
- - Live Editor - -
- - - ); -} - -// this should rather be a stable function -// see https://github.com/facebook/docusaurus/issues/9630#issuecomment-1855682643 -const DEFAULT_TRANSFORM_CODE = (code: string) => `${code};`; export default function Playground({ children, transformCode, ...props }: Props): ReactNode { - const { - siteConfig: {themeConfig}, - } = useDocusaurusContext(); - const { - liveCodeBlock: {playgroundPosition}, - } = themeConfig as ThemeConfig; - const prismTheme = usePrismTheme(); - - const noInline = props.metastring?.includes('noInline') ?? false; - return ( -
- - {playgroundPosition === 'top' ? ( - <> - - - - ) : ( - <> - - - - )} - -
+ + + + + ); } diff --git a/packages/docusaurus-theme-live-codeblock/src/types.d.ts b/packages/docusaurus-theme-live-codeblock/src/types.d.ts deleted file mode 100644 index 47200c50ab..0000000000 --- a/packages/docusaurus-theme-live-codeblock/src/types.d.ts +++ /dev/null @@ -1,20 +0,0 @@ -/** - * 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. - */ - -/// -/// - -declare module '@theme-init/CodeBlock' { - import type CodeBlock from '@theme/CodeBlock'; - import type {Props as BaseProps} from '@theme/CodeBlock'; - - export interface Props extends BaseProps { - live?: boolean; - } - const CodeBlockComp: typeof CodeBlock; - export default CodeBlockComp; -}