feat(v2): Add playgroundPosition config for live codeblock (#4328)

* docs(v2): Add configuration parameter to allow putting Result before Editor in @docusaurus/theme-live-codeblock

* update doc

* refactor as playgroundPosition

Co-authored-by: slorber <lorber.sebastien@gmail.com>
This commit is contained in:
tokarchyn 2021-03-05 14:49:17 +01:00 committed by GitHub
parent f772c17bfc
commit 98a4b3a65c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 215 additions and 39 deletions

View file

@ -16,6 +16,7 @@
"@docusaurus/core": "2.0.0-alpha.70",
"@philpl/buble": "^0.19.7",
"clsx": "^1.1.1",
"joi": "^17.4.0",
"parse-numeric-range": "^1.2.0",
"prism-react-renderer": "^1.1.1",
"react-live": "^2.2.3"

View file

@ -0,0 +1,91 @@
/**
* 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.
*/
const {validateThemeConfig, DEFAULT_CONFIG} = require('../validateThemeConfig');
function testValidateThemeConfig(themeConfig) {
function validate(schema, cfg) {
const {value, error} = schema.validate(cfg, {
convert: false,
});
if (error) {
throw error;
}
return value;
}
return validateThemeConfig({validate, themeConfig});
}
describe('validateThemeConfig', () => {
test('undefined config', () => {
const liveCodeBlock = undefined;
expect(testValidateThemeConfig({liveCodeBlock})).toEqual({
liveCodeBlock: {
...DEFAULT_CONFIG,
},
});
});
test('unexist config', () => {
expect(testValidateThemeConfig({})).toEqual({
liveCodeBlock: {
...DEFAULT_CONFIG,
},
});
});
test('empty config', () => {
const liveCodeBlock = {};
expect(testValidateThemeConfig({liveCodeBlock})).toEqual({
liveCodeBlock: {
...DEFAULT_CONFIG,
},
});
});
test('playgroundPosition top', () => {
const liveCodeBlock = {
playgroundPosition: 'top',
};
expect(testValidateThemeConfig({liveCodeBlock})).toEqual({
liveCodeBlock: {
...DEFAULT_CONFIG,
...liveCodeBlock,
},
});
});
test('playgroundPosition bottom', () => {
const liveCodeBlock = {
playgroundPosition: 'bottom',
};
expect(testValidateThemeConfig({liveCodeBlock})).toEqual({
liveCodeBlock: {
...DEFAULT_CONFIG,
...liveCodeBlock,
},
});
});
test('playgroundPosition invalid string', () => {
const liveCodeBlock = {playgroundPosition: 'invalid'};
expect(() =>
testValidateThemeConfig({liveCodeBlock}),
).toThrowErrorMatchingInlineSnapshot(
`"\\"liveCodeBlock.playgroundPosition\\" must be one of [top, bottom]"`,
);
});
test('playgroundPosition invalid boolean', () => {
const liveCodeBlock = {playgroundPosition: true};
expect(() =>
testValidateThemeConfig({liveCodeBlock}),
).toThrowErrorMatchingInlineSnapshot(
`"\\"liveCodeBlock.playgroundPosition\\" must be one of [top, bottom]"`,
);
});
});

View file

@ -6,8 +6,9 @@
*/
const path = require('path');
const {validateThemeConfig} = require('./validateThemeConfig');
module.exports = function () {
function theme() {
return {
name: 'docusaurus-theme-live-codeblock',
@ -25,4 +26,8 @@ module.exports = function () {
};
},
};
};
}
module.exports = theme;
theme.validateThemeConfig = validateThemeConfig;

View file

@ -11,11 +11,56 @@ import clsx from 'clsx';
import Translate from '@docusaurus/Translate';
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
import usePrismTheme from '@theme/hooks/usePrismTheme';
import styles from './styles.module.css';
function Header({translateId, description, text}) {
return (
<div className={clsx(styles.playgroundHeader)}>
<Translate id={translateId} description={description}>
{text}
</Translate>
</div>
);
}
function ResultWithHeader() {
return (
<>
<Header
translateId="theme.Playground.result"
description="The result label of the live codeblocks"
text="Result"
/>
<div className={styles.playgroundPreview}>
<LivePreview />
<LiveError />
</div>
</>
);
}
function EditorWithHeader() {
return (
<>
<Header
translateId="theme.Playground.liveEditor"
description="The live editor label of the live codeblocks"
text="Live Editor"
/>
<LiveEditor className={styles.playgroundEditor} />
</>
);
}
export default function Playground({children, transformCode, ...props}) {
const {isClient} = useDocusaurusContext();
const {
isClient,
siteConfig: {
themeConfig: {
liveCodeBlock: {playgroundPosition},
},
},
} = useDocusaurusContext();
const prismTheme = usePrismTheme();
return (
@ -26,33 +71,17 @@ export default function Playground({children, transformCode, ...props}) {
transformCode={transformCode || ((code) => `${code};`)}
theme={prismTheme}
{...props}>
<div
className={clsx(
styles.playgroundHeader,
styles.playgroundEditorHeader,
)}>
<Translate
id="theme.Playground.liveEditor"
description="The live editor label of the live codeblocks">
Live Editor
</Translate>
</div>
<LiveEditor className={styles.playgroundEditor} />
<div
className={clsx(
styles.playgroundHeader,
styles.playgroundPreviewHeader,
)}>
<Translate
id="theme.Playground.result"
description="The result label of the live codeblocks">
Result
</Translate>
</div>
<div className={styles.playgroundPreview}>
<LivePreview />
<LiveError />
</div>
{playgroundPosition === 'top' ? (
<>
<ResultWithHeader />
<EditorWithHeader />
</>
) : (
<>
<EditorWithHeader />
<ResultWithHeader />
</>
)}
</LiveProvider>
</div>
);

View file

@ -14,21 +14,18 @@
padding: 0.75rem;
text-transform: uppercase;
font-weight: bold;
background: var(--ifm-color-emphasis-200);
color: var(--ifm-color-content);
font-size: var(--ifm-code-font-size);
}
.playgroundEditorHeader {
.playgroundHeader:first-of-type {
background: var(--ifm-color-emphasis-600);
color: var(--ifm-color-content-inverse);
border-top-left-radius: var(--ifm-global-radius);
border-top-right-radius: var(--ifm-global-radius);
}
.playgroundPreviewHeader {
background: var(--ifm-color-emphasis-200);
color: var(--ifm-color-content);
}
.playgroundEditor {
font: var(--ifm-code-font-size) / var(--ifm-pre-line-height)
var(--ifm-font-family-monospace) !important;
@ -38,7 +35,10 @@
.playgroundPreview {
border: 1px solid var(--ifm-color-emphasis-200);
border-bottom-left-radius: var(--ifm-global-radius);
border-bottom-right-radius: var(--ifm-global-radius);
padding: 1rem;
}
.playgroundPreview:last-of-type, .playgroundEditor:last-of-type {
border-bottom-left-radius: var(--ifm-global-radius);
border-bottom-right-radius: var(--ifm-global-radius);
}

View file

@ -0,0 +1,28 @@
/**
* 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.
*/
const Joi = require('joi');
const DEFAULT_CONFIG = {
playgroundPosition: 'bottom',
};
exports.DEFAULT_CONFIG = DEFAULT_CONFIG;
const Schema = Joi.object({
liveCodeBlock: Joi.object({
playgroundPosition: Joi.string()
.equal('top', 'bottom')
.default(DEFAULT_CONFIG.playgroundPosition),
})
.label('themeConfig.liveCodeBlock')
.default(DEFAULT_CONFIG),
});
exports.Schema = Schema;
exports.validateThemeConfig = function ({validate, themeConfig}) {
return validate(Schema, themeConfig);
};

View file

@ -9,3 +9,20 @@ This theme provides a `@theme/CodeBlock` component that is powered by react-live
```bash npm2yarn
npm install --save @docusaurus/theme-live-codeblock
```
### Configuration
```jsx title="docusaurus.config.js"
module.exports = {
plugins: ['@docusaurus/theme-live-codeblock'],
themeConfig: {
liveCodeBlock: {
/**
* The position of the live playground, above or under the editor
* Possible values: "top" | "bottom"
*/
playgroundPosition: 'bottom',
},
},
};
```

View file

@ -292,6 +292,8 @@ function Clock(props) {
}
```
### Imports
:::caution react-live and imports
It is not possible to import components directly from the react-live code editor, you have to define available imports upfront.

View file

@ -281,6 +281,9 @@ const LocaleConfigs = isI18nStaging
],
],
themeConfig: {
liveCodeBlock: {
playgroundPosition: 'bottom',
},
hideableSidebar: true,
colorMode: {
defaultMode: 'light',