From 98a4b3a65cf7f331b6b5cf92775a86df1577ab8d Mon Sep 17 00:00:00 2001 From: tokarchyn Date: Fri, 5 Mar 2021 14:49:17 +0100 Subject: [PATCH] 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 --- .../package.json | 1 + .../src/__tests__/validateThemeConfig.test.js | 91 +++++++++++++++++++ .../src/index.js | 9 +- .../src/theme/Playground/index.js | 87 ++++++++++++------ .../src/theme/Playground/styles.module.css | 16 ++-- .../src/validateThemeConfig.js | 28 ++++++ .../docs/api/themes/theme-live-codeblock.md | 17 ++++ .../markdown-features-code-blocks.mdx | 2 + website/docusaurus.config.js | 3 + 9 files changed, 215 insertions(+), 39 deletions(-) create mode 100644 packages/docusaurus-theme-live-codeblock/src/__tests__/validateThemeConfig.test.js create mode 100644 packages/docusaurus-theme-live-codeblock/src/validateThemeConfig.js diff --git a/packages/docusaurus-theme-live-codeblock/package.json b/packages/docusaurus-theme-live-codeblock/package.json index ca0447d68d..797512dc7e 100644 --- a/packages/docusaurus-theme-live-codeblock/package.json +++ b/packages/docusaurus-theme-live-codeblock/package.json @@ -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" diff --git a/packages/docusaurus-theme-live-codeblock/src/__tests__/validateThemeConfig.test.js b/packages/docusaurus-theme-live-codeblock/src/__tests__/validateThemeConfig.test.js new file mode 100644 index 0000000000..ce0fdf0400 --- /dev/null +++ b/packages/docusaurus-theme-live-codeblock/src/__tests__/validateThemeConfig.test.js @@ -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]"`, + ); + }); +}); diff --git a/packages/docusaurus-theme-live-codeblock/src/index.js b/packages/docusaurus-theme-live-codeblock/src/index.js index d222af3d25..052e75d0a0 100644 --- a/packages/docusaurus-theme-live-codeblock/src/index.js +++ b/packages/docusaurus-theme-live-codeblock/src/index.js @@ -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; diff --git a/packages/docusaurus-theme-live-codeblock/src/theme/Playground/index.js b/packages/docusaurus-theme-live-codeblock/src/theme/Playground/index.js index 84891e1fce..882b07ff0e 100644 --- a/packages/docusaurus-theme-live-codeblock/src/theme/Playground/index.js +++ b/packages/docusaurus-theme-live-codeblock/src/theme/Playground/index.js @@ -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 ( +
+ + {text} + +
+ ); +} + +function ResultWithHeader() { + return ( + <> +
+
+ + +
+ + ); +} + +function EditorWithHeader() { + return ( + <> +
+ + + ); +} + 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}> -
- - Live Editor - -
- -
- - Result - -
-
- - -
+ {playgroundPosition === 'top' ? ( + <> + + + + ) : ( + <> + + + + )} ); diff --git a/packages/docusaurus-theme-live-codeblock/src/theme/Playground/styles.module.css b/packages/docusaurus-theme-live-codeblock/src/theme/Playground/styles.module.css index 6f5cf0805c..6750142321 100644 --- a/packages/docusaurus-theme-live-codeblock/src/theme/Playground/styles.module.css +++ b/packages/docusaurus-theme-live-codeblock/src/theme/Playground/styles.module.css @@ -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); +} diff --git a/packages/docusaurus-theme-live-codeblock/src/validateThemeConfig.js b/packages/docusaurus-theme-live-codeblock/src/validateThemeConfig.js new file mode 100644 index 0000000000..7b294874d6 --- /dev/null +++ b/packages/docusaurus-theme-live-codeblock/src/validateThemeConfig.js @@ -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); +}; diff --git a/website/docs/api/themes/theme-live-codeblock.md b/website/docs/api/themes/theme-live-codeblock.md index 9ee615cec0..8a676646f4 100644 --- a/website/docs/api/themes/theme-live-codeblock.md +++ b/website/docs/api/themes/theme-live-codeblock.md @@ -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', + }, + }, +}; +``` diff --git a/website/docs/guides/markdown-features/markdown-features-code-blocks.mdx b/website/docs/guides/markdown-features/markdown-features-code-blocks.mdx index 724f9dc989..270f92d05a 100644 --- a/website/docs/guides/markdown-features/markdown-features-code-blocks.mdx +++ b/website/docs/guides/markdown-features/markdown-features-code-blocks.mdx @@ -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. diff --git a/website/docusaurus.config.js b/website/docusaurus.config.js index f83ea28561..cddd0d0a53 100644 --- a/website/docusaurus.config.js +++ b/website/docusaurus.config.js @@ -281,6 +281,9 @@ const LocaleConfigs = isI18nStaging ], ], themeConfig: { + liveCodeBlock: { + playgroundPosition: 'bottom', + }, hideableSidebar: true, colorMode: { defaultMode: 'light',