Skip to content

Commit

Permalink
Merge pull request #409 from newrelic/jerel/unify-code-snippets
Browse files Browse the repository at this point in the history
Unify code blocks into a single component
  • Loading branch information
jerelmiller authored Jul 8, 2020
2 parents acd3549 + a0c0ef3 commit 4777dbf
Show file tree
Hide file tree
Showing 27 changed files with 791 additions and 298 deletions.
20 changes: 20 additions & 0 deletions .babelrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"presets": ["babel-preset-gatsby"],
"plugins": [
[
"babel-plugin-prismjs",
{
"languages": [
"css",
"hcl",
"javascript",
"json",
"jsx",
"ruby",
"shell",
"sql"
]
}
]
]
}
8 changes: 4 additions & 4 deletions COMPONENT_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -178,9 +178,9 @@ A step description

> Note: keep in mind that a new line is necesary after an `img` tag to ensure proper rendering of subsequent text/markdown.
## Code Snippet
## Code blocks

Code Snippets are automatically formatted by three backticks. This is our preferred method to delineate code snippets, but it's worth noting that markdown will also consider any text that is indented 4 spaces (or 1 tab) to be a code block.
Code blocks are automatically formatted by three backticks. This is our preferred method to delineate code snippets, but it's worth noting that markdown will also consider any text that is indented 4 spaces (or 1 tab) to be a code block.

### Usage

Expand All @@ -193,10 +193,10 @@ There are four props that can be supplied to a code snippet.
```
````

- `lineNumbers`: `true` or `false`. Will show line numbers of the left side of the code, defaults to `true`.
- `lineNumbers`: `true` or `false`. Will show line numbers of the left side of the code, defaults to `false`.

````md
```jsx lineNumbers=false
```jsx lineNumbers=true
```
````

Expand Down
17 changes: 14 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,14 @@
"react-markdown": "^4.3.1",
"react-middle-ellipsis": "^1.1.0",
"react-shadow": "^18.1.2",
"react-simple-code-editor": "^0.11.0",
"use-dark-mode": "^2.3.1"
},
"devDependencies": {
"@newrelic/eslint-plugin-newrelic": "^0.3.0",
"@testing-library/react": "^10.0.4",
"babel-jest": "^26.0.1",
"babel-plugin-prismjs": "^2.0.1",
"babel-preset-gatsby": "^0.4.2",
"eslint": "^6.8.0",
"eslint-plugin-jsx-a11y": "^6.2.3",
Expand Down
8 changes: 7 additions & 1 deletion src/components/Button.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,12 @@ const Button = ({
children,
className,
variant,
size,
...props
}) => (
<Component
{...props}
className={cx(className, styles.button, styles[variant])}
className={cx(className, styles.button, styles[variant], styles[size])}
>
{children}
</Component>
Expand All @@ -24,10 +25,15 @@ Button.VARIANT = {
NORMAL: 'normal',
};

Button.SIZE = {
SMALL: 'small',
};

Button.propTypes = {
as: PropTypes.elementType,
children: PropTypes.node,
className: PropTypes.string,
size: PropTypes.oneOf(Object.values(Button.SIZE)),
type: PropTypes.oneOf(['button', 'submit', 'reset']),
variant: PropTypes.oneOf(Object.values(Button.VARIANT)).isRequired,
};
Expand Down
4 changes: 4 additions & 0 deletions src/components/Button.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,7 @@
color: var(--color-brand-400);
}
}

.small {
font-size: 0.75rem;
}
113 changes: 113 additions & 0 deletions src/components/CodeBlock.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import React, { useState, useEffect } from 'react';
import PropTypes from 'prop-types';
import cx from 'classnames';
import Button from './Button';
import CodeEditor from './CodeEditor';
import CodeHighlight from './CodeHighlight';
import FeatherIcon from './FeatherIcon';
import MiddleEllipsis from 'react-middle-ellipsis';
import { LiveError, LivePreview, LiveProvider } from 'react-live';
import styles from './CodeBlock.module.scss';
import useClipboard from '../hooks/useClipboard';
import useFormattedCode from '../hooks/useFormattedCode';

const defaultComponents = {
Preview: LivePreview,
};

const CodeBlock = ({
children,
components: componentOverrides = {},
copyable,
live,
highlightedLines,
fileName,
language,
lineNumbers,
preview,
scope,
formatOptions,
}) => {
const components = { ...defaultComponents, ...componentOverrides };
const formattedCode = useFormattedCode(children.trim(), formatOptions);
const [copied, copy] = useClipboard();
const [code, setCode] = useState(formattedCode);

useEffect(() => {
setCode(formattedCode);
}, [formattedCode]);

return (
<LiveProvider code={code} scope={scope}>
{preview && <components.Preview className={styles.preview} />}
<div className={cx(styles.container, { [styles.withPreview]: preview })}>
<div className={styles.scrollContainer}>
{live ? (
<CodeEditor
value={code}
language={language}
lineNumbers={lineNumbers}
onChange={setCode}
/>
) : (
<CodeHighlight
highlightedLines={highlightedLines}
language={language}
lineNumbers={lineNumbers}
>
{code}
</CodeHighlight>
)}
</div>

{(copyable || fileName) && (
<div className={styles.statusBar}>
<div className={styles.fileName}>
{fileName && (
<MiddleEllipsis>
<span title={fileName}>{fileName}</span>
</MiddleEllipsis>
)}
</div>
<Button
type="button"
className={styles.copyButton}
variant={Button.VARIANT.PLAIN}
onClick={() => copy(code)}
size={Button.SIZE.SMALL}
>
<FeatherIcon name="copy" className={styles.copyButtonIcon} />
{copied ? 'Copied' : 'Copy output'}
</Button>
</div>
)}
</div>
{(live || preview) && <LiveError className={styles.liveError} />}
</LiveProvider>
);
};

CodeBlock.propTypes = {
fileName: PropTypes.string,
components: PropTypes.shape({
Preview: PropTypes.elementType,
}),
copyable: PropTypes.bool,
children: PropTypes.string.isRequired,
formatOptions: PropTypes.object,
highlightedLines: PropTypes.string,
language: PropTypes.string,
lineNumbers: PropTypes.bool,
live: PropTypes.bool,
preview: PropTypes.bool,
scope: PropTypes.object,
};

CodeBlock.defaultProps = {
copyable: true,
lineNumbers: false,
live: false,
preview: false,
};

export default CodeBlock;
69 changes: 69 additions & 0 deletions src/components/CodeBlock.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
.container {
background: var(--color-nord-0);
border-radius: 4px;

:global(.light-mode) & {
background: var(--color-nord-6);
}

&.withPreview {
border-top-left-radius: 0;
border-top-right-radius: 0;
}
}

.scrollContainer {
max-height: 26em;
overflow: auto;
}

.statusBar {
color: var(--color-nord-6);
display: flex;
justify-content: space-between;
align-items: center;
background: var(--color-nord-1);
border-bottom-left-radius: 4px;
border-bottom-right-radius: 4px;
padding: 0 1rem;
font-size: 0.75rem;

:global(.light-mode) & {
color: var(--color-nord-0);
background: var(--color-nord-4);
}
}

.fileName {
font-family: var(--code-font);
white-space: nowrap;
overflow: hidden;
padding-right: 0.5rem;
}

.copyButton {
white-space: nowrap;
}

.copyButtonIcon {
margin-right: 0.5rem;
}

.liveError {
color: white;
background: var(--color-red-400);
padding: 0.5rem 1rem;
font-size: 0.75rem;
overflow: auto;
margin-top: 0.5rem;
border-radius: 2px;
}

.preview {
padding: 2rem;
background: var(--color-white);
border: 1px solid var(--color-neutrals-100);
box-shadow: var(--boxshadow);
border-top-left-radius: 4px;
border-top-right-radius: 4px;
}
43 changes: 43 additions & 0 deletions src/components/CodeEditor.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import React from 'react';
import PropTypes from 'prop-types';
import cx from 'classnames';
import Editor from 'react-simple-code-editor';
import CodeHighlight from './CodeHighlight';
import styles from './CodeEditor.module.scss';

const CodeEditor = ({ value, language, lineNumbers, onChange }) => {
const lineNumberWidth = value.trim().split('\n').length.toString().length;

return (
<Editor
value={value}
padding={16}
onValueChange={onChange}
highlight={(code) => (
<CodeHighlight
wrap
className={styles.editor}
language={language}
lineNumbers={lineNumbers}
>
{code}
</CodeHighlight>
)}
textareaClassName={cx({ [styles.lineNumbers]: lineNumbers })}
style={{
fontFamily: 'var(--code-font)',
fontSize: '0.75rem',
'--line-number-width': `${lineNumberWidth}ch`,
}}
/>
);
};

CodeEditor.propTypes = {
language: PropTypes.string.isRequired,
lineNumbers: PropTypes.bool,
onChange: PropTypes.func,
value: PropTypes.string.isRequired,
};

export default CodeEditor;
7 changes: 7 additions & 0 deletions src/components/CodeEditor.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
.editor {
padding: 0 !important;
}

.lineNumbers {
padding-left: calc(2rem + var(--line-number-width)) !important;
}
Loading

0 comments on commit 4777dbf

Please sign in to comment.