Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions packages/docusaurus-theme-live-codeblock/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,18 @@
"sideEffects": [
"lib/theme/Playground/*"
],
"exports": {
"./lib/*": "./lib/*",
"./src/*": "./src/*",
"./client": {
"type": "./lib/client/index.d.ts",
"default": "./lib/client/index.js"
},
".": {
"types": "./src/theme-live-codeblock.d.ts",
"default": "./lib/index.js"
}
},
"publishConfig": {
"access": "public"
},
Expand Down
36 changes: 36 additions & 0 deletions packages/docusaurus-theme-live-codeblock/src/client/context.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/**
* 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 {createContext, useContext, type ReactNode} from 'react';

type PlaygroundContextValue = {
reset: () => void;
};

const PlaygroundContext = createContext<PlaygroundContextValue | null>(null);

export function PlaygroundProvider({
value,
children,
}: {
value: PlaygroundContextValue;
children: ReactNode;
}): ReactNode {
return (
<PlaygroundContext.Provider value={value}>
{children}
</PlaygroundContext.Provider>
);
}

export function usePlayground(): PlaygroundContextValue {
const context = useContext(PlaygroundContext);
if (!context) {
throw new Error('usePlayground must be used within PlaygroundProvider');
}
return context;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/**
* 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.
*/

export {usePlayground, PlaygroundProvider} from './context';
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,24 @@
/// <reference types="@docusaurus/module-type-aliases" />

declare module '@docusaurus/theme-live-codeblock' {
import type {PlaygroundPosition} from '@theme/Playground';

export type ThemeConfig = {
liveCodeBlock: {
playgroundPosition: 'top' | 'bottom';
playgroundPosition: PlaygroundPosition;
};
};
}

declare module '@theme/LiveCodeBlock' {
import type {ReactNode} from 'react';
import type {Props as BaseProps} from '@theme/CodeBlock';

export interface Props extends BaseProps {}
type CodeBlockProps = Omit<BaseProps, 'children'>;

export interface Props extends CodeBlockProps {
children?: string;
}

export default function LiveCodeBlock(props: Props): ReactNode;
}
Expand All @@ -29,14 +36,21 @@ declare module '@theme/Playground' {
import type {Props as BaseProps} from '@theme/CodeBlock';
import type {LiveProvider} from 'react-live';

type CodeBlockProps = Omit<BaseProps, 'className' | 'language' | 'title'>;
type CodeBlockProps = Omit<
BaseProps,
'children' | 'className' | 'language' | 'title'
>;
type LiveProviderProps = React.ComponentProps<typeof LiveProvider>;

export type PlaygroundPosition = 'top' | 'bottom';

export interface Props extends CodeBlockProps, LiveProviderProps {
// Allow empty live playgrounds
children?: string;
position?: PlaygroundPosition;
}
export default function Playground(props: LiveProviderProps): ReactNode;

export default function Playground(props: Props): ReactNode;
}

declare module '@theme/Playground/Provider' {
Expand All @@ -48,6 +62,13 @@ declare module '@theme/Playground/Provider' {
children: ReactNode;
}

export interface ResetContextValue {
resetKey: number;
reset: () => void;
}

export const PlaygroundResetContext: React.Context<ResetContextValue | null>;
export function usePlaygroundReset(): ResetContextValue;
export default function PlaygroundProvider(props: Props): ReactNode;
}

Expand All @@ -63,9 +84,11 @@ declare module '@theme/Playground/Container' {

declare module '@theme/Playground/Layout' {
import type {ReactNode} from 'react';
import type {PlaygroundPosition} from '@theme/Playground';

// eslint-disable-next-line @typescript-eslint/no-empty-interface
export interface Props {}
export interface Props {
position?: PlaygroundPosition;
}

export default function PlaygroundLayout(props: Props): ReactNode;
}
Expand All @@ -91,12 +114,24 @@ declare module '@theme/Playground/Editor' {
declare module '@theme/Playground/Header' {
import type {ReactNode} from 'react';

// eslint-disable-next-line @typescript-eslint/no-empty-interface
export interface Props {}
export interface Props {
children: ReactNode;
buttons?: ReactNode;
}

export default function PlaygroundHeader(props: Props): ReactNode;
}

declare module '@theme/Playground/Buttons/ResetButton' {
import type {ReactNode} from 'react';

export interface Props {
className?: string;
}

export default function ResetButton(props: Props): ReactNode;
}

declare module '@theme/ReactLiveScope' {
type Scope = {
[key: string]: unknown;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,14 @@ declare module '@theme/CodeBlock' {
}
}

function isLiveCodeBlock(props: CodeBlockProps): boolean {
return !!props.live;
function isLiveCodeBlock(
props: CodeBlockProps,
): props is {live: true; children: string | undefined} {
return (
!!props.live &&
(typeof props.children === 'undefined' ||
typeof props.children === 'string')
);
}

export default function CodeBlockEnhancer(props: CodeBlockProps): ReactNode {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/**
* 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 type {ReactNode} from 'react';
import clsx from 'clsx';
import Translate from '@docusaurus/Translate';
import {usePlayground} from '@docusaurus/theme-live-codeblock/client';
import type {Props} from '@theme/Playground/Buttons/ResetButton';
import styles from './styles.module.css';

function Icon() {
return (
<svg
className={styles.resetButtonIcon}
viewBox="0 0 16 16"
fill="currentColor"
aria-hidden="true">
<path d="M8 3a5 5 0 1 0 4.546 2.914.5.5 0 0 1 .908-.417A6 6 0 1 1 8 2v1z" />
<path d="M8 4.466V.534a.25.25 0 0 1 .41-.192l2.36 1.966c.12.1.12.284 0 .384L8.41 4.658A.25.25 0 0 1 8 4.466z" />
</svg>
);
}

function Label() {
return (
<Translate
id="theme.Playground.buttons.reset"
description="The reset button label for live code blocks">
Reset
</Translate>
);
}

export default function ResetButton({className}: Props): ReactNode {
const {reset} = usePlayground();
return (
<button
type="button"
aria-label="Reset code to original"
title="Reset"
className={clsx('clean-btn', className, styles.resetButton)}
onClick={() => reset()}>
<Icon />
<Label />
</button>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/**
* 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.
*/

.resetButton {
display: inline-flex;
align-items: center;
gap: 0.25rem;
padding: 0.25rem 0.5rem;
font-size: 0.875rem;
line-height: 1.5;
border: 1px solid var(--ifm-color-emphasis-300);
border-radius: var(--ifm-global-radius);
background: var(--ifm-button-background-color);
color: var(--ifm-font-color-base);
cursor: pointer;
transition: all var(--ifm-transition-fast);
}

.resetButton:hover {
background: var(--ifm-color-emphasis-200);
border-color: var(--ifm-color-emphasis-400);
}

.resetButton:active {
background: var(--ifm-color-emphasis-300);
}

.resetButtonIcon {
width: 1rem;
height: 1rem;
flex-shrink: 0;
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,14 @@ import {LiveEditor} from 'react-live';
import useIsBrowser from '@docusaurus/useIsBrowser';
import Translate from '@docusaurus/Translate';
import PlaygroundHeader from '@theme/Playground/Header';

import ResetButton from '@theme/Playground/Buttons/ResetButton';
import styles from './styles.module.css';

export default function PlaygroundEditor(): ReactNode {
const isBrowser = useIsBrowser();
return (
<>
<PlaygroundHeader>
<PlaygroundHeader buttons={<ResetButton />}>
<Translate
id="theme.Playground.liveEditor"
description="The live editor label of the live codeblocks">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,20 @@

import React, {type ReactNode} from 'react';
import clsx from 'clsx';
import type {Props} from '@theme/Playground/Header';

import styles from './styles.module.css';

export default function PlaygroundHeader({
children,
}: {
children: ReactNode;
}): ReactNode {
return <div className={clsx(styles.playgroundHeader)}>{children}</div>;
buttons,
}: Props): ReactNode {
return (
<div className={clsx(styles.playgroundHeader)}>
<div className={styles.playgroundHeaderContent}>{children}</div>
{buttons && (
<div className={styles.playgroundHeaderButtons}>{buttons}</div>
)}
</div>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
*/

.playgroundHeader {
display: flex;
justify-content: space-between;
align-items: center;
letter-spacing: 0.08rem;
padding: 0.75rem;
text-transform: uppercase;
Expand All @@ -15,7 +18,12 @@
font-size: var(--ifm-code-font-size);
}

.playgroundHeader:first-of-type {
background: var(--ifm-color-emphasis-700);
color: var(--ifm-color-content-inverse);
.playgroundHeaderContent {
font-weight: var(--ifm-font-weight-bold);
font-size: 0.875rem;
}

.playgroundHeaderButtons {
display: flex;
gap: 0.5rem;
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ 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 {Props} from '@theme/Playground/Layout';

import type {ThemeConfig} from '@docusaurus/theme-live-codeblock';

Expand All @@ -17,11 +18,12 @@ function useLiveCodeBlockThemeConfig() {
return themeConfig.liveCodeBlock;
}

export default function PlaygroundLayout(): ReactNode {
const {playgroundPosition} = useLiveCodeBlockThemeConfig();
export default function PlaygroundLayout(props: Props): ReactNode {
const themeConfig = useLiveCodeBlockThemeConfig();
const position = props.position ?? themeConfig.playgroundPosition;
return (
<>
{playgroundPosition === 'top' ? (
{position === 'top' ? (
<>
<PlaygroundPreview />
<PlaygroundEditor />
Expand Down
Loading
Loading