-
Notifications
You must be signed in to change notification settings - Fork 114
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Unify code blocks into a single component #409
Merged
Merged
Changes from 45 commits
Commits
Show all changes
47 commits
Select commit
Hold shift + click to select a range
5de9cf8
chore: Install react-simple-code-editor
jerelmiller 605c6f3
chore: Install babel-plugin-prismjs
jerelmiller 867127a
chore: Add custom babelrc with prismjs babel plugin
jerelmiller cdef05b
chore: Use Prism from prismjs instead of prism-react-renderer
jerelmiller 66cc42f
chore: Dont wrap pre in pre tag
jerelmiller b61401a
chore: Add code block component
jerelmiller e0b00d7
chore: Add css variables for nord theme
jerelmiller 553af7a
chore: Add language as a class name for the code block
jerelmiller cede4ff
chore: Extract CodeBlock to CodeHighlight. Conditionally apply langua…
jerelmiller 9650393
feat: Add a small button type
jerelmiller 6390d3a
chore: Add copy button to CodeBlock
jerelmiller 7bd13c6
chore: Add line numbers
jerelmiller eb9730c
chore: Add code formatting for code block
jerelmiller 526718e
refactor: Extract CodeHighlight to own file
jerelmiller 5f3dd27
feat: Add support for text wrapping in code highlight
jerelmiller ea895b4
chore: Add live editing support
jerelmiller c62c614
refactor: Extract some array helper methods
jerelmiller dddc708
feat: Add support for line highlighting
jerelmiller e62b60f
chore: Rename i to idx
jerelmiller 32524b2
chore: Pass along highlighted lines from code block
jerelmiller a27260a
chore: Use CodeBlock instead of CodeSnippet in MDX
jerelmiller 8745abf
chore: Remove CodeSnippet
jerelmiller c40dd76
chore: Default line numbers to false
jerelmiller 0efe62e
chore: Fix eslint error
jerelmiller 8ea9dd8
chore: Fix edit mode when line numbers are not displayed
jerelmiller 9d32cc5
refactor: Lift state up to CodeBlock
jerelmiller 15c97fc
chore: WIP live preview
jerelmiller ad7f59c
chore: Fix prop type error with button size
jerelmiller 2f2a9b8
feat: add preview styles for code block
jerelmiller 0f96781
chore: Use className from props instead of preview style
jerelmiller 321acbf
feat: add a shallowEqual helper
jerelmiller 0c6ca59
chore: Update useFormattedCode to useShallowMemo
jerelmiller fc688ad
feat: Pass format options in CodeBlock
jerelmiller 551cb4b
chore: Check deps length when comparing in shallow equal
jerelmiller f1a8db7
chore: Ensure CodeBlock renders correctly in light mode
jerelmiller 1bceb66
refactor: fully migrate to CodeBlock in ReferenceExample
jerelmiller 45e319a
feat: update api and component reference templates to use the code bl…
jerelmiller fb464c6
chore: Disable previews in guides
jerelmiller 8b47d85
docs: update component guide with changes to code blocks
jerelmiller 2fb389d
refactor: Use CSS module name for line highlight instead of global name
jerelmiller ffed14c
fix: dont use window for component scope
jerelmiller c06efa2
chore: Add max height to code blocks
jerelmiller 6872ffb
refactor: rename copy prop to copyable
jerelmiller 0d192de
chore: Remove scope from mdx container
jerelmiller 20712d3
feat: Add support for hcl syntax
jerelmiller c0cbc1c
chore: Add tests for the range and partition functions
jerelmiller a0c0ef3
refactor: Extract mdx code block into own component
jerelmiller File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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" | ||
] | ||
} | ||
] | ||
] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -46,3 +46,7 @@ | |
color: var(--color-brand-400); | ||
} | ||
} | ||
|
||
.small { | ||
font-size: 0.75rem; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we have any instances where we are adding components that aren't the
Preview
? I'm a little unclear about the purpose of this prop.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No.
<Preview />
is the only component right now that is customizable. To understand how I got here, let me describe a few of my goals for this refactor and some ways I considered designing this API.Goals
Regardless of where it is rendered, it should be able to handle all use cases demanded of the code block. This includes:
While this is explicitly disabled currently, its something we can enable when we have some demand for this particular use case.
The API is designed to be used everywhere. You'll notice that there is no developer website specific code in here. We want to be able to use this component in the open source site as well, which means we have to try and be as generic as possible.
API Design
Goal 3 presents an interesting challenge. Because we want to enable live previews on any code on any site, not just the NR1 SDK in the developer site, we can't use something like the code in
<ReferencePreview />
which uses shadow DOM, loads the NR1 SDK's CSS, etc. On the flip side, because we want to be generic about this, this also presents a problem for our use case of needing style isolation for the NR1 SDK component previews. This means that the live preview MUST be customizable.Specifically, the NR1 SDK component previews require the following:
<Grid />
which show boxes)ToastManager
when theToast
component is used)<Spinner />
which renders absolutely positioned)There are a couple of APIs I considered to allow this to be customizable with the previous requirements in mind:
While these may not have been the final names of the props, you can see that the surface area of
<CodeBlock />
now has grown a lot, specifically with a LOT of props dedicated to configuring the preview. This approach also means we need to add new props any time we have new uses cases for the preview that weren't already considered..
delimited approach to give back control of the structure to the user of the componentWhile this approach is a bit better because I no longer have tons of
preview
props dedicated to configuring the component preview, I've now lost control of the structure of<CodeBlock />
and can't enforce it. For example, what if someone uses the component this way?Do we honor this structure and render the preview underneath the code? And what about the status bar that renders the file name and copy button? Are those lost? Should we provide a component for that to make it obvious it's being rendered?
As you can see, while I gain the ability to easily customize the structure of the preview, I've now lost an enforced structure for the entire thing and put the burden back into the hands of the user of the component to always make sure these are in the proper order. I didn't like this unnecessary burden since we want an enforced structure for the code block.
This is popular with libraries like React Select and even the
MDXProvider
for rendering mdx with customized components.This approach allows us to be able to enforce the proper structure internally to the code block, but also provides the flexibility for the user to customize the structure of the preview component. This means users of the API can design the preview for any and all uses cases that may pop up over time without needing to touch the
<CodeBlock />
to do so. This is the approach I opted for as I felt it provided the greatest flexibility without compromising the enforced structure we want in this component.Final notes
Right now
Preview
is the only component that is allowed to be customized because it is the only one that needs it. I'm not sure it makes sense to provide a customized status bar, or customized code highlighting as of right now. If that use case ever exists, then we have a way to extend this component to allow for those customizations.Hope that explains the reasoning behind this API design choice and why I chose to go the way I did.