File tree

2 files changed

+258
-1
lines changed

2 files changed

+258
-1
lines changed
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,6 @@ const ExampleSnippet: FC<ExampleSnippetProps> = ({
201201
</CTooltip>
202202
</CNav>
203203
</div>
204-
205204
{(hasJS || hasTS) && (
206205
<div className="highlight">
207206
<Highlight
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,258 @@
1+
import React, { FC, lazy, ReactNode, Suspense, useEffect, useMemo, useRef, useState } from 'react'
2+
import { Highlight, Language } from 'prism-react-renderer'
3+
import CIcon from '@coreui/icons-react'
4+
import { cibCodesandbox, cilCheckAlt, cilCopy } from '@coreui/icons'
5+
import { CNav, CNavLink, CTooltip, useClipboard } from '@coreui/react'
6+
import { openStackBlitzProject } from '../utils/stackblitz'
7+
import { openCodeSandboxProject } from '../utils/codesandbox'
8+
import { isInViewport } from '@coreui/react/src/utils'
9+
10+
interface CodeSnippets {
11+
js?: string
12+
ts?: string
13+
}
14+
15+
export interface ExampleSnippetLazyProps {
16+
children: ReactNode
17+
className?: string
18+
code?: string | CodeSnippets
19+
codeSandbox?: boolean
20+
component?: string
21+
componentName?: string
22+
pro?: boolean
23+
stackBlitz?: boolean
24+
}
25+
26+
const ExampleSnippetLazy: FC<ExampleSnippetLazyProps> = ({
27+
children,
28+
className = '',
29+
code,
30+
codeSandbox = true,
31+
component,
32+
componentName,
33+
pro = false,
34+
stackBlitz = true,
35+
}) => {
36+
const exampleSnippetRef = useRef<HTMLDivElement>(null)
37+
const [codeJS, setCodeJS] = useState<string>()
38+
const [codeTS, setCodeTS] = useState<string>()
39+
const [language, setLanguage] = useState<'js' | 'ts'>('js')
40+
const [visible, setVisible] = useState(false)
41+
const { copy, isCopied } = useClipboard()
42+
43+
const Preview = useMemo(() => {
44+
if (!component) return null
45+
return lazy(() =>
46+
import(`@example/${component}.tsx`)
47+
.then((module) => ({ default: module[component] }))
48+
.catch((error) => {
49+
console.error(`Failed to load Preview component for ${component}:`, error)
50+
return { default: () => <div>Preview not available.</div> }
51+
}),
52+
)
53+
}, [component])
54+
55+
const handleScroll = () => {
56+
setVisible(true)
57+
}
58+
59+
useEffect(() => {
60+
if (exampleSnippetRef.current && isInViewport(exampleSnippetRef.current)) {
61+
setVisible(true)
62+
}
63+
}, [])
64+
65+
useEffect(() => {
66+
if (visible) {
67+
window.removeEventListener('scroll', handleScroll)
68+
} else {
69+
window.addEventListener('scroll', handleScroll)
70+
}
71+
72+
return () => {
73+
window.removeEventListener('scroll', handleScroll)
74+
}
75+
}, [])
76+
77+
useEffect(() => {
78+
const loadCode = async () => {
79+
if (code) {
80+
if (typeof code === 'string') {
81+
setCodeJS(code)
82+
} else {
83+
setCodeJS(code.js)
84+
setCodeTS(code.ts)
85+
}
86+
} else if (component) {
87+
try {
88+
const tsModule = await import(`!!raw-loader!@example/${component}.tsx`)
89+
setCodeTS(tsModule.default)
90+
setCodeJS(tsModule.default)
91+
} catch (error) {
92+
console.error(`Failed to load TypeScript code for component ${component}:`, error)
93+
}
94+
95+
try {
96+
const jsModule = await import(`!!raw-loader!@example/${component}.jsx`)
97+
setCodeJS(jsModule.default)
98+
} catch {
99+
// JSX version may not exist
100+
}
101+
}
102+
}
103+
104+
loadCode()
105+
}, [code, component])
106+
107+
const hasJS = codeJS !== undefined && codeJS !== ''
108+
const hasTS = codeTS !== undefined && codeTS !== ''
109+
110+
useEffect(() => {
111+
if (!hasJS && hasTS) {
112+
setLanguage('ts')
113+
} else {
114+
setLanguage('js')
115+
}
116+
}, [hasJS, hasTS])
117+
118+
const handleCopy = () => {
119+
const codeToCopy = language === 'js' ? codeJS : codeTS
120+
if (codeToCopy) copy(codeToCopy)
121+
}
122+
123+
const prismLanguage: Language = language === 'js' ? 'jsx' : 'tsx'
124+
const showJSTab = hasJS && !(typeof code === 'object' && code?.js === code?.ts)
125+
const showTSTab = hasTS
126+
127+
const getProjectName = (): string => {
128+
if (React.isValidElement(children)) {
129+
const childType = (children as React.ReactElement).type
130+
if (typeof childType === 'string') return childType
131+
if (typeof childType === 'function' && childType.name) return childType.name
132+
}
133+
return 'ExampleProject'
134+
}
135+
136+
return (
137+
<div className="docs-example-snippet" ref={exampleSnippetRef}>
138+
{visible && (
139+
<div className={`docs-example ${className}`}>
140+
{children ? (
141+
children
142+
) : Preview ? (
143+
<Suspense fallback={<div>Loading preview...</div>}>
144+
<Preview />
145+
</Suspense>
146+
) : (
147+
<div>No component specified.</div>
148+
)}
149+
</div>
150+
)}
151+
<div className="highlight-toolbar border-top">
152+
<CNav className="px-3" variant="underline-border">
153+
{showJSTab && (
154+
<CNavLink as="button" active={language === 'js'} onClick={() => setLanguage('js')}>
155+
JavaScript
156+
</CNavLink>
157+
)}
158+
{showTSTab && (
159+
<CNavLink as="button" active={language === 'ts'} onClick={() => setLanguage('ts')}>
160+
TypeScript
161+
</CNavLink>
162+
)}
163+
<span className="ms-auto"></span>
164+
{codeSandbox && (
165+
<CTooltip content="Try it on CodeSandbox">
166+
<button
167+
type="button"
168+
className="btn btn-transparent"
169+
aria-label="Try it on CodeSandbox"
170+
onClick={() =>
171+
openCodeSandboxProject({
172+
name: component || getProjectName(),
173+
language,
174+
code: language === 'js' ? codeJS || '' : codeTS || '',
175+
componentName,
176+
pro,
177+
})
178+
}
179+
disabled={language === 'ts' && !hasTS}
180+
>
181+
<CIcon icon={cibCodesandbox} />
182+
</button>
183+
</CTooltip>
184+
)}
185+
{stackBlitz && (
186+
<CTooltip content="Try it on StackBlitz">
187+
<button
188+
type="button"
189+
className="btn btn-transparent px-1"
190+
aria-label="Try it on StackBlitz"
191+
onClick={() =>
192+
openStackBlitzProject({
193+
name: component || getProjectName(),
194+
language,
195+
code: language === 'js' ? codeJS || '' : codeTS || '',
196+
componentName,
197+
pro,
198+
})
199+
}
200+
disabled={language === 'ts' && !hasTS}
201+
>
202+
<svg
203+
className="icon"
204+
width="56"
205+
height="78"
206+
viewBox="0 0 56 78"
207+
fill="none"
208+
xmlns="http://www.w3.org/2000/svg"
209+
>
210+
<path
211+
d="M23.4273 48.2853C23.7931 47.5845 23.0614 46.8837 22.3298 46.8837H1.11228C0.0148224 46.8837 -0.350997 45.8326 0.380642 45.1318L40.9866 0.282084C41.7182 -0.418693 43.1815 0.282084 42.8157 1.33325L32.9386 30.0651C32.5727 30.7659 32.9386 31.4666 33.6702 31.4666H54.8877C55.9852 31.4666 56.351 32.5178 55.6194 33.2186L15.0134 77.7179C14.2818 78.4187 12.8185 77.7179 13.1843 76.6667L23.4273 48.2853Z"
212+
fill="currentColor"
213+
/>
214+
</svg>
215+
</button>
216+
</CTooltip>
217+
)}
218+
<CTooltip content={isCopied ? 'Copied' : 'Copy to clipboard'}>
219+
<button
220+
type="button"
221+
className="btn btn-transparent px-1"
222+
aria-label="Copy to clipboard"
223+
onClick={handleCopy}
224+
disabled={(language === 'js' && !hasJS) || (language === 'ts' && !hasTS)}
225+
>
226+
<CIcon icon={isCopied ? cilCheckAlt : cilCopy} />
227+
</button>
228+
</CTooltip>
229+
</CNav>
230+
</div>
231+
{visible && (hasJS || hasTS) && (
232+
<div className="highlight">
233+
<Highlight
234+
code={language === 'js' ? codeJS || '' : codeTS || ''}
235+
language={prismLanguage}
236+
theme={{ plain: {}, styles: [] }}
237+
>
238+
{({ className: highlightClass, style, tokens, getLineProps, getTokenProps }) => (
239+
<pre className={highlightClass} style={style}>
240+
{tokens.map((line, i) => (
241+
<div {...getLineProps({ line, key: i })} key={i}>
242+
{line.map((token, key) => (
243+
<span {...getTokenProps({ token, key })} key={key} />
244+
))}
245+
</div>
246+
))}
247+
</pre>
248+
)}
249+
</Highlight>
250+
</div>
251+
)}
252+
</div>
253+
)
254+
}
255+
256+
ExampleSnippetLazy.displayName = 'ExampleSnippetLazy'
257+
258+
export default ExampleSnippetLazy

0 commit comments

Comments
 (0)