components/code-block/code-block.tsx (129 lines of code) (raw):

import React, { FC, ReactNode, useState, useImperativeHandle, forwardRef, useEffect, createRef, } from 'react'; import dynamic from 'next/dynamic'; import { Button } from '@rescui/button'; import { ThemeProvider } from '@rescui/ui-contexts'; import styles from './code-block.module.css'; import { trackEvent } from '../../utils/event-logger'; const KotlinPlayground = dynamic<any>(() => import('react-kotlin-playground').then(), { loading: () => null, ssr: false, }); interface Props { children: ReactNode; ref: any; targetPlatform?: string; } declare global { interface Window { dataLayer?: any; } } export const CodeBlock: FC<Props> = forwardRef(({ children, targetPlatform }, ref) => { const [codeBlockInstance, setCodeBlockInstance] = useState(null); const [editorFocus, setEditorFocus] = useState<boolean>(false); const [isCodeSampleEdited, setIsCodeSampleEdited] = useState<boolean>(false); const editButtonRef = createRef<any>(); const handleGetInstance = (instance) => { setCodeBlockInstance(instance); }; useImperativeHandle(ref, () => ({ runInstance() { const event = { eventCategory: 'kotlin-playground', eventAction: 'Playground Run', eventLabel: !isCodeSampleEdited ? 'unchanged' : 'changed', }; trackEvent(event); codeBlockInstance?.execute(); }, scrollResultsToView() { if (codeBlockInstance?.nodes?.length) { const outputWrapper = codeBlockInstance.nodes[0].querySelector('.output-wrapper'); if ( typeof window !== 'undefined' && outputWrapper.getBoundingClientRect().bottom > window.innerHeight ) { outputWrapper.scrollIntoView({ behavior: 'smooth', block: 'end', inline: 'nearest', }); } } }, })); const handleEdit = () => { codeBlockInstance?.codemirror.focus(); }; const handleEditorFocus = () => { setEditorFocus(true); }; const handleEditorBlur = () => { setEditorFocus(false); }; const escHandler = (event) => { if (event.keyCode === 27) { if (editorFocus && editButtonRef.current) { editButtonRef.current.focus(); } } }; useEffect(() => { const cmInstance = codeBlockInstance?.codemirror; if (cmInstance) { cmInstance.setOption('tabindex', '-1'); cmInstance.on('focus', (codemirror, event) => { handleEditorFocus(); }); cmInstance.on('blur', (codemirror, event) => { handleEditorBlur(); }); cmInstance.on('change', (codemirror, event) => { setIsCodeSampleEdited(true); }) } }, [codeBlockInstance]); useEffect(() => { if (typeof document !== `undefined`) { document.addEventListener('keydown', escHandler); return () => { document.removeEventListener('keydown', escHandler); }; } }, [escHandler]); return ( <div className={styles.codeBlockContainer} tabIndex={-1}> <pre className={styles.code}>{children}</pre> <div className={styles.buttonWrapper}> <ThemeProvider theme={'light'}> <Button className={styles.editButton} mode={'rock'} onClick={handleEdit} ref={editButtonRef} size={'l'} > Edit code example </Button> </ThemeProvider> </div> <div> <KotlinPlayground theme={'darcula'} autoIndent={false} getInstance={handleGetInstance} {...(targetPlatform && { targetPlatform })} className={styles.codeBlockWrapper} > {children} </KotlinPlayground> </div> </div> ); }); CodeBlock.displayName = 'CodeBlock';