Skip to content

Instantly share code, notes, and snippets.

@ElianCodes
Created May 5, 2025 09:32
Show Gist options
  • Select an option

  • Save ElianCodes/9e5c7fddb2c807b924e39fa02898652a to your computer and use it in GitHub Desktop.

Select an option

Save ElianCodes/9e5c7fddb2c807b924e39fa02898652a to your computer and use it in GitHub Desktop.
import { types, useVisualEdit } from 'react-bricks/astro'
import Editor from 'react-simple-code-editor'
import { useEffect, useState } from 'react'
import { codeToHtml, bundledLanguagesInfo as bundledLanguages, type BundledLanguage } from 'shiki'
interface CodeBlockProps {
lang: BundledLanguage
code: string
lines?: string
addLineNumbers: boolean
themeLight: string
themeDark: string
}
const supportedLanguages = bundledLanguages.map((lang) => ({
value: lang.id,
label: lang.name
}))
const parseHighlightLines = (input: string | undefined): number[] => {
if (!input) return []
const lines = input.split(',').flatMap((entry) => {
if (entry.includes('-')) {
const [start, end] = entry.split('-').map(Number)
return Array.from({ length: end - start + 1 }, (_, i) => start + i)
}
return [Number(entry)]
})
return lines
}
function extractTextFromHTML(html: string) {
const tempDiv = document.createElement('div')
tempDiv.innerHTML = html
return tempDiv.textContent || tempDiv.innerText || ''
}
const CodeBlock: types.Brick<CodeBlockProps> = ({
lang,
code,
lines,
addLineNumbers,
themeLight,
themeDark,
}: CodeBlockProps) => {
const highlightedLines = parseHighlightLines(lines)
const [changeValue, readonly] = useVisualEdit('code')
const [highlightedCode, setHighlightedCode] = useState(code)
const [isFocus, setIsFocused] = useState(false)
useEffect(() => {
const loadShiki = async () => {
const config = getShikiConfig(code, lang, { light: themeLight, dark: themeDark }, highlightedLines, addLineNumbers)
const html = await codeToHtml(code, config)
setHighlightedCode(html)
}
if (!isFocus) {
loadShiki()
} else {
setHighlightedCode(extractTextFromHTML(code))
}
}, [readonly, code, lang, lines, isFocus, addLineNumbers, themeLight, themeDark])
return readonly ? (
<ReadOnlyCodeBlock highlightedCode={highlightedCode} />
) : (
<EditableCodeBlock
code={code}
highlightedCode={highlightedCode}
onFocus={() => setIsFocused(true)}
onBlur={() => setIsFocused(false)}
onValueChange={(val) => changeValue(val)}
/>
)
}
CodeBlock.schema = {
name: 'code-block',
label: 'Code Block',
astroInteractivity: 'load',
getDefaultProps: () => ({
lang: 'tsx',
code: `const a = 'hello'`,
lines: '1',
themeDark: 'nord',
themeLight: 'one-light',
}),
sideEditProps: [
{
name: 'lang',
label: 'Language',
type: types.SideEditPropType.Select,
selectOptions: {
display: types.OptionsDisplay.Select,
options: supportedLanguages,
},
},
{
name: 'lines',
label: 'Lines to highlight',
type: types.SideEditPropType.Text,
},
{
name: 'addLineNumbers',
label: 'Add line numbers',
type: types.SideEditPropType.Boolean,
},
{
name: 'themeLight',
label: 'Light theme',
type: types.SideEditPropType.Select,
selectOptions: {
display: types.OptionsDisplay.Select,
options: [
{ value: 'github-light-default', label: 'Github light default' },
{ value: 'github-light', label: 'Github light' },
{ value: 'one-light', label: 'One light' },
],
},
},
{
name: 'themeDark',
label: 'Dark theme',
type: types.SideEditPropType.Select,
selectOptions: {
display: types.OptionsDisplay.Select,
options: [
{ value: 'github-dark-default', label: 'Github dark default' },
{ value: 'github-dark', label: 'Github dark' },
{ value: 'nord', label: 'Nord' },
],
},
},
],
}
export default CodeBlock
const getShikiConfig = (code: string, lang: BundledLanguage, themes: { light: string; dark: string }, highlightedLines: number[], addLineNumbers: boolean) => ({
lang,
themes,
transformers: [
{
pre(node) {
node.properties.style =
(node.properties.style || '') +
'; padding: 20px; border-radius:6px; overflow:visible;'
},
line(node, lineNumber: number) {
if (highlightedLines.includes(lineNumber)) {
node.properties.style =
(node.properties.style || '') +
'background-color:#fff085; --shiki-dark-bg:#894b00;'
}
},
},
addLineNumbers
? {
pre(node) {
node.properties.class =
(node.properties.class || '') + ' shiki-code'
},
line(node, lineNumber: number) {
const style = node.properties.style || ''
node.children.unshift({
type: 'element',
tagName: 'span',
properties: {
className: ['line-number'],
style:
'display:inline-block; width:2em; user-select:none; opacity:0.5;',
},
children: [
{
type: 'text',
value: lineNumber.toString().padStart(2) + ' ',
},
],
})
node.properties.style = style
},
}
: {},
],
})
const ReadOnlyCodeBlock = ({ highlightedCode }: { highlightedCode: string }) => (
<div
className="max-w-[692px] mx-auto bg-[#fafafa] dark:bg-[#2e3440] rounded-lg mt-8 mb-6 text-sm"
dangerouslySetInnerHTML={{ __html: highlightedCode }}
/>
)
const EditableCodeBlock = ({
code,
highlightedCode,
onFocus,
onBlur,
onValueChange,
}: {
code: string
highlightedCode: string
onFocus: () => void
onBlur: () => void
onValueChange: (val: string) => void
}) => (
<Editor
value={code}
onFocus={onFocus}
onBlur={onBlur}
onValueChange={onValueChange}
highlight={() => highlightedCode}
padding={10}
style={{
fontFamily: '"Fira Code", "Fira Mono", monospace',
fontSize: 12,
}}
preClassName="overflow-auto"
className="max-w-[692px] mx-auto rounded-lg mt-8 mb-6 text-sm overflow-auto dark:text-white"
/>
)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment