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';