// eslint-disable-next-line simple-import-sort/imports import 'prismjs/themes/prism.css'; import {useEffect, useRef, useState} from 'react'; import {useTheme} from '@emotion/react'; import styled from '@emotion/styled'; import copy from 'copy-text-to-clipboard'; import Prism from 'prismjs'; /** * JSX syntax for Prism. This file uses Prism * internally, so it must be imported after Prism. */ import 'prismjs/components/prism-jsx.min'; import {IconCode} from 'sentry/icons'; import space from 'sentry/styles/space'; import {Theme} from 'sentry/utils/theme'; type Props = { /** * Main code content gets passed as the children prop */ children: string; /** * Auto-generated class name for
and element,
* with a 'language-' prefix, e.g. language-css
*/
className?: string;
/**
* Meta props from the markdown syntax,
* for example, in
*
* ```jsx label=hello
* [some code]
* ```
*
* the label prop is set to 'hello'
*/
label?: string;
theme?: Theme;
};
const Code = ({children, className, label}: Props) => {
const theme = useTheme();
const codeRef = useRef(null);
const copyTimeoutRef = useRef(undefined);
const [copied, setCopied] = useState(false);
function handleCopyCode() {
// Remove comments from code
const copiableContent = children.replace(/\/\*[\s\S]*?\*\/|\/\/.*/g, '');
copy(copiableContent);
setCopied(true);
copyTimeoutRef.current = window.setTimeout(() => {
setCopied(false);
}, 500);
}
// Cleanup timeout on component unmount
useEffect(() => {
return () => {
if (copyTimeoutRef.current !== null) {
window.clearTimeout(copyTimeoutRef.current);
}
};
}, []);
useEffect(() => {
Prism.highlightElement(codeRef.current, false);
}, [children]);
return (
{label && }
{children}
{copied ? 'Copied' : 'Copy'}
);
};
export default Code;
const Wrap = styled('pre')`
/* Increase specificity to override default styles */
&& {
position: relative;
padding: ${space(2)};
padding-top: ${space(4)};
margin-top: ${space(4)};
margin-bottom: ${space(2)};
background: ${p => p.theme.bodyBackground};
border: solid 1px ${p => p.theme.border};
overflow: visible;
text-shadow: none;
}
& code {
text-shadow: none;
}
/* Overwrite default Prism behavior to allow for code wrapping */
pre[class*='language-'],
code[class*='language-'] {
white-space: normal;
word-break: break-word;
}
`;
const LabelWrap = styled('div')`
display: flex;
align-items: center;
position: absolute;
top: 0;
left: calc(${space(2)} - ${space(1)});
transform: translateY(-50%);
padding: ${space(0.25)} ${space(1)};
background: ${p => p.theme.docsBackground};
border: solid 1px ${p => p.theme.border};
border-radius: ${p => p.theme.borderRadius};
`;
const Label = styled('p')`
font-size: 0.875rem;
font-weight: 600;
color: ${p => p.theme.subText};
text-transform: uppercase;
margin-bottom: 0;
margin-left: ${space(1)};
`;
const HighlightedCode = styled('code')`
/** Increase specificity to override default styles */
${Wrap} > & {
font-family: ${p => p.theme.text.familyMono};
font-size: 0.875rem;
line-height: 1.6;
}
`;
const CopyButton = styled('button')`
position: absolute;
top: ${space(0.5)};
right: ${space(0.5)};
background: transparent;
border: none;
border-radius: ${p => p.theme.borderRadius};
padding: ${space(0.5)} ${space(1)};
font-size: 0.875rem;
font-weight: 600;
color: ${p => p.theme.subText};
&:hover:not(:disabled) {
color: ${p => p.theme.textColor};
}
`;