Skip to content
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

Add line highlighting for live code blocks #453

Merged
merged 1 commit into from
Jul 18, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 30 additions & 1 deletion docs/content/usage/live-code.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,35 @@ Every property on the object exported by `live-code-scope.js` will be available
```
````

## Line highlighting

If you want to emphasize a particular range of lines, use the `highlight` attribute on the code block. The expected format is `higlight=start-end`. The line highlighting will disappear when the live example is edited.


````markdown
``` jsx live highlight=2-4
<div>
<p>
This paragraph element should be higlighted.
</p>
<p>
This paragraph element should not be higlighted.
</p>
</div>
```
````

``` jsx live highlight=2-4
<div>
<p>
This paragraph element should be higlighted.
</p>
<p>
This paragraph element should not be higlighted.
</p>
</div>
```

## Global styles

Live previews are completely isolated from the rest of the page because they are rendered inside [iframes](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe). This means that you can apply global styles inside live previews without affecting the rest of the page:
Expand Down Expand Up @@ -120,4 +149,4 @@ function DemoApp() {
}

render(<DemoApp />)
```
```
6 changes: 4 additions & 2 deletions theme/src/components/code.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,14 @@ import Prism from '../prism'
import ClipboardCopy from './clipboard-copy'
import LiveCode from './live-code'

function Code({className, children, live, noinline, metastring}) {
function Code({className, children, live, highlight, noinline, metastring}) {
const language = className ? className.replace(/language-/, '') : ''
const code = children.trim()

if (live) {
return <LiveCode code={code} language={language} noinline={noinline} metastring={metastring} />
return (
<LiveCode code={code} highlight={highlight} language={language} noinline={noinline} metastring={metastring} />
)
}

return (
Expand Down
71 changes: 54 additions & 17 deletions theme/src/components/live-code.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import {ThemeContext} from 'styled-components'
import scope from '../live-code-scope'
import ClipboardCopy from './clipboard-copy'
import LivePreviewWrapper from './live-preview-wrapper'
import styled from 'styled-components'
import themeGet from '@styled-system/theme-get'

const languageTransformers = {
html: html => htmlToJsx(html),
Expand Down Expand Up @@ -39,10 +41,43 @@ const getResolvedScope = metastring => {
return scope
}

function LiveCode({code, language, noinline, metastring}) {
function parseHighlightRange(highlight) {
// Captures numbers separated by a dash: 2-3, 34-5, 2-101
const numbersWithDash = new RegExp('([0-9]+)-([0-9]+)')

const match = numbersWithDash.exec(highlight)
if (!match) return null

return {firstLine: match[1], lastLine: match[2]}
}

const LineWrapper = styled.div`
// Using negative and positive nth-child values to select the children.
pre .token-line:nth-child(n + ${props => props.range.firstLine}):nth-child(-n + ${props => props.range.lastLine}) {
// 16px is the padding of the react-live <pre> element that wraps the .token-line elements.
// The margin/padding combo extends the token-line elements so the background color reaches the border.
margin: 0px -16px;
padding: 0px 16px;
background-color: ${themeGet('colors.accent.subtle')};
// We use box-shadow instead of a border to avoid flickering when toggling the highlighting on/off.
box-shadow: inset 3px 0px 0px 0px ${themeGet('colors.accent.fg')};
}
`

function LineHighlighter({enabled, range, children}) {
if (!enabled || !range) return children

return <LineWrapper range={range}>{children}</LineWrapper>
}

function LiveCode({code, language, highlight, noinline, metastring}) {
const theme = React.useContext(ThemeContext)
const [liveCode, setLiveCode] = useState(code)
const handleChange = updatedLiveCode => setLiveCode(updatedLiveCode)
const [pristine, setPristine] = useState(true)
const handleChange = updatedLiveCode => {
setLiveCode(updatedLiveCode)
setPristine(false)
}

return (
<Box sx={{flexDirection: 'column', mb: 3, display: 'flex'}}>
Expand All @@ -66,21 +101,23 @@ function LiveCode({code, language, noinline, metastring}) {
</LivePreviewWrapper>
</Box>
<Box sx={{position: 'relative'}}>
<LiveEditor
onChange={handleChange}
theme={githubTheme}
ignoreTabKey={true}
padding={theme.space[3]}
style={{
fontFamily: theme.fonts.mono,
fontSize: '85%',
borderBottomLeftRadius: theme.radii[2],
borderBottomRightRadius: theme.radii[2],
border: '1px solid',
borderTop: 0,
borderColor: theme.colors.border.default
}}
/>
<LineHighlighter range={parseHighlightRange(highlight)} enabled={pristine}>
<LiveEditor
onChange={handleChange}
theme={githubTheme}
ignoreTabKey={true}
padding={theme.space[3]}
style={{
fontFamily: theme.fonts.mono,
fontSize: '85%',
borderBottomLeftRadius: theme.radii[2],
borderBottomRightRadius: theme.radii[2],
border: '1px solid',
borderTop: 0,
borderColor: theme.colors.border.default
}}
/>
</LineHighlighter>
<Box sx={{top: 0, right: 0, p: 2, position: 'absolute'}}>
<ClipboardCopy value={liveCode} />
</Box>
Expand Down