SimpleCodeBlock.tsx112 lines · main
1'use client'
2
3/**
4 * Copyright (c) 2017-present, Facebook, Inc.
5 *
6 * This source code is licensed under the MIT license found in the
7 * LICENSE file in the root directory of this source tree.
8 */
9import { useTheme } from 'next-themes'
10import { Highlight, Language, Prism, themes } from 'prism-react-renderer'
11import { createContext, PropsWithChildren, useContext, useEffect, useRef, useState } from 'react'
12import { Button, cn, copyToClipboard } from 'ui'
13
14import { dart } from './prism'
15
16// Context for copy callback - can be provided by parent components
17export const CopyCallbackContext = createContext<(() => void) | undefined>(undefined)
18
19dart(Prism)
20
21const prism = {
22 defaultLanguage: 'js',
23 plugins: ['line-numbers', 'show-language'],
24}
25
26interface SimpleCodeBlockProps {
27 parentClassName?: string
28 className?: string
29 showCopy?: boolean
30 onCopy?: () => void
31 handleCopy?: (value: string) => void
32}
33
34// [Joshen] Refactor: De-dupe with CodeBlock.tsx
35export const SimpleCodeBlock = ({
36 children,
37 parentClassName,
38 className: languageClassName,
39 showCopy = true,
40 onCopy,
41 handleCopy,
42}: PropsWithChildren<SimpleCodeBlockProps>) => {
43 const { resolvedTheme } = useTheme()
44 const [showCopied, setShowCopied] = useState(false)
45 const target = useRef(null)
46 const contextOnCopy = useContext(CopyCallbackContext)
47 let highlightLines: any = []
48
49 useEffect(() => {
50 if (!showCopied) return
51 const timer = setTimeout(() => setShowCopied(false), 2000)
52 return () => clearTimeout(timer)
53 }, [showCopied])
54
55 let language = languageClassName && languageClassName.replace(/language-/, '')
56
57 if (!language && prism.defaultLanguage) {
58 language = prism.defaultLanguage
59 }
60
61 const handleCopyCode = (code: any) => {
62 if (!!handleCopy) {
63 handleCopy(code)
64 } else {
65 copyToClipboard(code)
66 }
67 setShowCopied(true)
68 // Use prop onCopy if provided, otherwise fall back to context
69 const copyCallback = onCopy || contextOnCopy
70 copyCallback?.()
71 }
72
73 return (
74 <Highlight
75 theme={resolvedTheme === 'dark' ? themes.nightOwl : themes.nightOwlLight}
76 code={(children as string)?.trim() ?? ''}
77 language={language as Language}
78 >
79 {({ className, tokens, getLineProps, getTokenProps }) => {
80 return (
81 <div className="Code codeBlockWrapper group">
82 <pre ref={target} className={cn('codeBlock', className, parentClassName)}>
83 {tokens.map((line, i) => {
84 const { key: _key, ...lineProps } = getLineProps({ line, key: i })
85
86 if (highlightLines.includes(i + 1)) {
87 lineProps.className = `${lineProps.className} docusaurus-highlight-code-line`
88 }
89
90 return (
91 <div key={i} {...lineProps}>
92 {line.map((token, key) => {
93 const { key: _key, ...tokenProps } = getTokenProps({ token, key })
94 return <span key={key} {...tokenProps} />
95 })}
96 </div>
97 )
98 })}
99 </pre>
100 {showCopy && (
101 <div className="invisible absolute right-0 top-0 opacity-0 transition-opacity group-hover:visible group-hover:opacity-100">
102 <Button size="tiny" type="default" onClick={() => handleCopyCode(children)}>
103 {showCopied ? 'Copied' : 'Copy'}
104 </Button>
105 </div>
106 )}
107 </div>
108 )
109 }}
110 </Highlight>
111 )
112}