mirror of
https://github.com/facebook/docusaurus.git
synced 2025-05-12 16:47:26 +02:00
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:
parent
f772c17bfc
commit
98a4b3a65c
9 changed files with 215 additions and 39 deletions
|
@ -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"
|
||||
|
|
|
@ -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]"`,
|
||||
);
|
||||
});
|
||||
});
|
|
@ -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;
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
};
|
|
@ -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',
|
||||
},
|
||||
},
|
||||
};
|
||||
```
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -281,6 +281,9 @@ const LocaleConfigs = isI18nStaging
|
|||
],
|
||||
],
|
||||
themeConfig: {
|
||||
liveCodeBlock: {
|
||||
playgroundPosition: 'bottom',
|
||||
},
|
||||
hideableSidebar: true,
|
||||
colorMode: {
|
||||
defaultMode: 'light',
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue