{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "code-editor", "type": "registry:ui", "title": "Code Editor", "description": "A code editor component featuring syntax highlighting and animation.", "dependencies": [ "motion", "shiki" ], "registryDependencies": [ "https://animate-ui.com/r/copy-button" ], "files": [ { "path": "registry/components/code-editor/index.tsx", "content": "'use client';\n\nimport * as React from 'react';\nimport { useInView, type UseInViewOptions } from 'motion/react';\nimport { useTheme } from 'next-themes';\n\nimport { cn } from '@/lib/utils';\nimport { CopyButton } from '@/components/animate-ui/buttons/copy';\n\ntype CodeEditorProps = Omit, 'onCopy'> & {\n children: string;\n lang: string;\n themes?: {\n light: string;\n dark: string;\n };\n duration?: number;\n delay?: number;\n header?: boolean;\n dots?: boolean;\n icon?: React.ReactNode;\n cursor?: boolean;\n inView?: boolean;\n inViewMargin?: UseInViewOptions['margin'];\n inViewOnce?: boolean;\n copyButton?: boolean;\n writing?: boolean;\n title?: string;\n onDone?: () => void;\n onCopy?: (content: string) => void;\n};\n\nfunction CodeEditor({\n children: code,\n lang,\n themes = {\n light: 'github-light',\n dark: 'github-dark',\n },\n duration = 5,\n delay = 0,\n className,\n header = true,\n dots = true,\n icon,\n cursor = false,\n inView = false,\n inViewMargin = '0px',\n inViewOnce = true,\n copyButton = false,\n writing = true,\n title,\n onDone,\n onCopy,\n ...props\n}: CodeEditorProps) {\n const { resolvedTheme } = useTheme();\n\n const editorRef = React.useRef(null);\n const [visibleCode, setVisibleCode] = React.useState('');\n const [highlightedCode, setHighlightedCode] = React.useState('');\n const [isDone, setIsDone] = React.useState(false);\n\n const inViewResult = useInView(editorRef, {\n once: inViewOnce,\n margin: inViewMargin,\n });\n const isInView = !inView || inViewResult;\n\n React.useEffect(() => {\n if (!visibleCode.length || !isInView) return;\n\n const loadHighlightedCode = async () => {\n try {\n const { codeToHtml } = await import('shiki');\n\n const highlighted = await codeToHtml(visibleCode, {\n lang,\n themes: {\n light: themes.light,\n dark: themes.dark,\n },\n defaultColor: resolvedTheme === 'dark' ? 'dark' : 'light',\n });\n\n setHighlightedCode(highlighted);\n } catch (e) {\n console.error(`Language \"${lang}\" could not be loaded.`, e);\n }\n };\n\n loadHighlightedCode();\n }, [\n lang,\n themes,\n writing,\n isInView,\n duration,\n delay,\n visibleCode,\n resolvedTheme,\n ]);\n\n React.useEffect(() => {\n if (!writing) {\n setVisibleCode(code);\n onDone?.();\n return;\n }\n\n if (!code.length || !isInView) return;\n\n const characters = Array.from(code);\n let index = 0;\n const totalDuration = duration * 1000;\n const interval = totalDuration / characters.length;\n let intervalId: NodeJS.Timeout;\n\n const timeout = setTimeout(() => {\n intervalId = setInterval(() => {\n if (index < characters.length) {\n setVisibleCode((prev) => {\n const currentIndex = index;\n index += 1;\n return prev + characters[currentIndex];\n });\n editorRef.current?.scrollTo({\n top: editorRef.current?.scrollHeight,\n behavior: 'smooth',\n });\n } else {\n clearInterval(intervalId);\n setIsDone(true);\n onDone?.();\n }\n }, interval);\n }, delay * 1000);\n\n return () => {\n clearTimeout(timeout);\n clearInterval(intervalId);\n };\n }, [code, duration, delay, isInView, writing, onDone]);\n\n return (\n \n {header ? (\n
\n {dots && (\n
\n
\n
\n
\n
\n )}\n\n {title && (\n \n {icon ? (\n \n {typeof icon !== 'string' ? icon : null}\n
\n ) : null}\n
\n {title}\n
\n \n )}\n\n {copyButton ? (\n \n ) : null}\n \n ) : (\n copyButton && (\n \n )\n )}\n \n pre,_&_code]:!bg-transparent [&>pre,_&_code]:[background:transparent_!important] [&>pre,_&_code]:border-none [&_code]:!text-[13px]',\n cursor &&\n !isDone &&\n \"[&_.line:last-of-type::after]:content-['|'] [&_.line:last-of-type::after]:animate-pulse [&_.line:last-of-type::after]:inline-block [&_.line:last-of-type::after]:w-[1ch] [&_.line:last-of-type::after]:-translate-px\",\n )}\n dangerouslySetInnerHTML={{ __html: highlightedCode }}\n />\n \n \n );\n}\n\nexport { CodeEditor, type CodeEditorProps };\n", "type": "registry:ui", "target": "components/animate-ui/components/code-editor.tsx" } ] }