Skip to content

Commit 49619fd

Browse files
NPX2218slorber
andauthored
feat(theme-live-codeblock): reset button + wire position prop (facebook#11675)
* feat(theme-live-codeblock): add reset button to live code playground Adds a reset button to live code playgrounds that restores edited code to its original state. The button appears in the playground header alongside the Live Editor label. Closes facebook#10711 * chore: update theme translations for reset button * add mising LiveCodeBlockThemeConfig type import to website * some fixes * change type order * remove useless dogfood page * rename i18n key * extract context to a client api export * fix prop types import * restore former comment * refactor a bit, extract playground position prop * expose position prop + dogfood * wire position prop * fix type issues * subcomponents * restore some former CSS * fix React playground examples * restore comment --------- Co-authored-by: sebastien <lorber.sebastien@gmail.com> Co-authored-by: Sébastien Lorber <slorber@users.noreply.github.com>
1 parent a8881ad commit 49619fd

55 files changed

Lines changed: 340 additions & 63 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

packages/docusaurus-theme-live-codeblock/package.json

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,18 @@
77
"sideEffects": [
88
"lib/theme/Playground/*"
99
],
10+
"exports": {
11+
"./lib/*": "./lib/*",
12+
"./src/*": "./src/*",
13+
"./client": {
14+
"type": "./lib/client/index.d.ts",
15+
"default": "./lib/client/index.js"
16+
},
17+
".": {
18+
"types": "./src/theme-live-codeblock.d.ts",
19+
"default": "./lib/index.js"
20+
}
21+
},
1022
"publishConfig": {
1123
"access": "public"
1224
},
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/**
2+
* Copyright (c) Facebook, Inc. and its affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
import {createContext, useContext, type ReactNode} from 'react';
9+
10+
type PlaygroundContextValue = {
11+
reset: () => void;
12+
};
13+
14+
const PlaygroundContext = createContext<PlaygroundContextValue | null>(null);
15+
16+
export function PlaygroundProvider({
17+
value,
18+
children,
19+
}: {
20+
value: PlaygroundContextValue;
21+
children: ReactNode;
22+
}): ReactNode {
23+
return (
24+
<PlaygroundContext.Provider value={value}>
25+
{children}
26+
</PlaygroundContext.Provider>
27+
);
28+
}
29+
30+
export function usePlayground(): PlaygroundContextValue {
31+
const context = useContext(PlaygroundContext);
32+
if (!context) {
33+
throw new Error('usePlayground must be used within PlaygroundProvider');
34+
}
35+
return context;
36+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
/**
2+
* Copyright (c) Facebook, Inc. and its affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
export {usePlayground, PlaygroundProvider} from './context';

packages/docusaurus-theme-live-codeblock/src/theme-live-codeblock.d.ts

Lines changed: 43 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,24 @@
99
/// <reference types="@docusaurus/module-type-aliases" />
1010

1111
declare module '@docusaurus/theme-live-codeblock' {
12+
import type {PlaygroundPosition} from '@theme/Playground';
13+
1214
export type ThemeConfig = {
1315
liveCodeBlock: {
14-
playgroundPosition: 'top' | 'bottom';
16+
playgroundPosition: PlaygroundPosition;
1517
};
1618
};
1719
}
1820

1921
declare module '@theme/LiveCodeBlock' {
22+
import type {ReactNode} from 'react';
2023
import type {Props as BaseProps} from '@theme/CodeBlock';
2124

22-
export interface Props extends BaseProps {}
25+
type CodeBlockProps = Omit<BaseProps, 'children'>;
26+
27+
export interface Props extends CodeBlockProps {
28+
children?: string;
29+
}
2330

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

32-
type CodeBlockProps = Omit<BaseProps, 'className' | 'language' | 'title'>;
39+
type CodeBlockProps = Omit<
40+
BaseProps,
41+
'children' | 'className' | 'language' | 'title'
42+
>;
3343
type LiveProviderProps = React.ComponentProps<typeof LiveProvider>;
3444

45+
export type PlaygroundPosition = 'top' | 'bottom';
46+
3547
export interface Props extends CodeBlockProps, LiveProviderProps {
3648
// Allow empty live playgrounds
3749
children?: string;
50+
position?: PlaygroundPosition;
3851
}
39-
export default function Playground(props: LiveProviderProps): ReactNode;
52+
53+
export default function Playground(props: Props): ReactNode;
4054
}
4155

4256
declare module '@theme/Playground/Provider' {
@@ -48,6 +62,13 @@ declare module '@theme/Playground/Provider' {
4862
children: ReactNode;
4963
}
5064

65+
export interface ResetContextValue {
66+
resetKey: number;
67+
reset: () => void;
68+
}
69+
70+
export const PlaygroundResetContext: React.Context<ResetContextValue | null>;
71+
export function usePlaygroundReset(): ResetContextValue;
5172
export default function PlaygroundProvider(props: Props): ReactNode;
5273
}
5374

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

6485
declare module '@theme/Playground/Layout' {
6586
import type {ReactNode} from 'react';
87+
import type {PlaygroundPosition} from '@theme/Playground';
6688

67-
// eslint-disable-next-line @typescript-eslint/no-empty-interface
68-
export interface Props {}
89+
export interface Props {
90+
position?: PlaygroundPosition;
91+
}
6992

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

94-
// eslint-disable-next-line @typescript-eslint/no-empty-interface
95-
export interface Props {}
117+
export interface Props {
118+
children: ReactNode;
119+
buttons?: ReactNode;
120+
}
96121

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

125+
declare module '@theme/Playground/Buttons/ResetButton' {
126+
import type {ReactNode} from 'react';
127+
128+
export interface Props {
129+
className?: string;
130+
}
131+
132+
export default function ResetButton(props: Props): ReactNode;
133+
}
134+
100135
declare module '@theme/ReactLiveScope' {
101136
type Scope = {
102137
[key: string]: unknown;

packages/docusaurus-theme-live-codeblock/src/theme/CodeBlock/index.tsx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,14 @@ declare module '@theme/CodeBlock' {
1919
}
2020
}
2121

22-
function isLiveCodeBlock(props: CodeBlockProps): boolean {
23-
return !!props.live;
22+
function isLiveCodeBlock(
23+
props: CodeBlockProps,
24+
): props is {live: true; children: string | undefined} {
25+
return (
26+
!!props.live &&
27+
(typeof props.children === 'undefined' ||
28+
typeof props.children === 'string')
29+
);
2430
}
2531

2632
export default function CodeBlockEnhancer(props: CodeBlockProps): ReactNode {
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/**
2+
* Copyright (c) Facebook, Inc. and its affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
import type {ReactNode} from 'react';
9+
import clsx from 'clsx';
10+
import Translate from '@docusaurus/Translate';
11+
import {usePlayground} from '@docusaurus/theme-live-codeblock/client';
12+
import type {Props} from '@theme/Playground/Buttons/ResetButton';
13+
import styles from './styles.module.css';
14+
15+
function Icon() {
16+
return (
17+
<svg
18+
className={styles.resetButtonIcon}
19+
viewBox="0 0 16 16"
20+
fill="currentColor"
21+
aria-hidden="true">
22+
<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" />
23+
<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" />
24+
</svg>
25+
);
26+
}
27+
28+
function Label() {
29+
return (
30+
<Translate
31+
id="theme.Playground.buttons.reset"
32+
description="The reset button label for live code blocks">
33+
Reset
34+
</Translate>
35+
);
36+
}
37+
38+
export default function ResetButton({className}: Props): ReactNode {
39+
const {reset} = usePlayground();
40+
return (
41+
<button
42+
type="button"
43+
aria-label="Reset code to original"
44+
title="Reset"
45+
className={clsx('clean-btn', className, styles.resetButton)}
46+
onClick={() => reset()}>
47+
<Icon />
48+
<Label />
49+
</button>
50+
);
51+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/**
2+
* Copyright (c) Facebook, Inc. and its affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
.resetButton {
9+
display: inline-flex;
10+
align-items: center;
11+
gap: 0.25rem;
12+
padding: 0.25rem 0.5rem;
13+
font-size: 0.875rem;
14+
line-height: 1.5;
15+
border: 1px solid var(--ifm-color-emphasis-300);
16+
border-radius: var(--ifm-global-radius);
17+
background: var(--ifm-button-background-color);
18+
color: var(--ifm-font-color-base);
19+
cursor: pointer;
20+
transition: all var(--ifm-transition-fast);
21+
}
22+
23+
.resetButton:hover {
24+
background: var(--ifm-color-emphasis-200);
25+
border-color: var(--ifm-color-emphasis-400);
26+
}
27+
28+
.resetButton:active {
29+
background: var(--ifm-color-emphasis-300);
30+
}
31+
32+
.resetButtonIcon {
33+
width: 1rem;
34+
height: 1rem;
35+
flex-shrink: 0;
36+
}

packages/docusaurus-theme-live-codeblock/src/theme/Playground/Editor/index.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,14 @@ import {LiveEditor} from 'react-live';
1010
import useIsBrowser from '@docusaurus/useIsBrowser';
1111
import Translate from '@docusaurus/Translate';
1212
import PlaygroundHeader from '@theme/Playground/Header';
13-
13+
import ResetButton from '@theme/Playground/Buttons/ResetButton';
1414
import styles from './styles.module.css';
1515

1616
export default function PlaygroundEditor(): ReactNode {
1717
const isBrowser = useIsBrowser();
1818
return (
1919
<>
20-
<PlaygroundHeader>
20+
<PlaygroundHeader buttons={<ResetButton />}>
2121
<Translate
2222
id="theme.Playground.liveEditor"
2323
description="The live editor label of the live codeblocks">

packages/docusaurus-theme-live-codeblock/src/theme/Playground/Header/index.tsx

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,20 @@
77

88
import React, {type ReactNode} from 'react';
99
import clsx from 'clsx';
10+
import type {Props} from '@theme/Playground/Header';
1011

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

1314
export default function PlaygroundHeader({
1415
children,
15-
}: {
16-
children: ReactNode;
17-
}): ReactNode {
18-
return <div className={clsx(styles.playgroundHeader)}>{children}</div>;
16+
buttons,
17+
}: Props): ReactNode {
18+
return (
19+
<div className={clsx(styles.playgroundHeader)}>
20+
<div className={styles.playgroundHeaderContent}>{children}</div>
21+
{buttons && (
22+
<div className={styles.playgroundHeaderButtons}>{buttons}</div>
23+
)}
24+
</div>
25+
);
1926
}

packages/docusaurus-theme-live-codeblock/src/theme/Playground/Header/styles.module.css

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@
66
*/
77

88
.playgroundHeader {
9+
display: flex;
10+
justify-content: space-between;
11+
align-items: center;
912
letter-spacing: 0.08rem;
1013
padding: 0.75rem;
1114
text-transform: uppercase;
@@ -15,7 +18,12 @@
1518
font-size: var(--ifm-code-font-size);
1619
}
1720

18-
.playgroundHeader:first-of-type {
19-
background: var(--ifm-color-emphasis-700);
20-
color: var(--ifm-color-content-inverse);
21+
.playgroundHeaderContent {
22+
font-weight: var(--ifm-font-weight-bold);
23+
font-size: 0.875rem;
24+
}
25+
26+
.playgroundHeaderButtons {
27+
display: flex;
28+
gap: 0.5rem;
2129
}

0 commit comments

Comments
 (0)