Add prop to fence component called content which will hold the content contained within the fence component. Update code snippet component so that it filters the fence components passed to it as children to only display the fence component that is same language as the selected language and pass the content prop of the fence component to the copy code button component on each render of the snippet component.
189 lines
4.6 KiB
TypeScript
189 lines
4.6 KiB
TypeScript
'use client';
|
|
|
|
import React, {
|
|
Children,
|
|
MouseEvent,
|
|
ReactElement,
|
|
ReactNode,
|
|
useState,
|
|
} from 'react';
|
|
import styles from './CodeSnippet.module.css';
|
|
|
|
function CodeSnippetCopyButton({
|
|
copyText,
|
|
}: {
|
|
copyText: string;
|
|
}) {
|
|
const [copyButtonTooltip, setCopyButtonTooltip] =
|
|
useState<boolean>(false);
|
|
|
|
const [showTooltip, setShowTooltip] =
|
|
useState<boolean>(false);
|
|
|
|
const handleCopy = (e: MouseEvent<HTMLButtonElement>) => {
|
|
setCopyButtonTooltip(true);
|
|
setTimeout(() => {
|
|
setCopyButtonTooltip(false);
|
|
}, 1000);
|
|
|
|
navigator.clipboard.writeText(copyText ?? '');
|
|
};
|
|
|
|
return (
|
|
<div className={styles.copyButtonContainer}>
|
|
<div>
|
|
<div
|
|
className={
|
|
showTooltip
|
|
? styles.tooltip
|
|
: styles.tooltipHidden
|
|
}
|
|
>
|
|
{copyButtonTooltip ? 'Copied!' : 'Click to Copy'}
|
|
</div>
|
|
</div>
|
|
<button
|
|
className={styles.copyButton}
|
|
onClick={handleCopy}
|
|
onMouseEnter={() => setShowTooltip(true)}
|
|
onMouseLeave={() => setShowTooltip(false)}
|
|
>
|
|
<svg
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
width="16"
|
|
height="16"
|
|
fill="currentColor"
|
|
viewBox="0 0 16 16"
|
|
>
|
|
<path
|
|
fillRule="evenodd"
|
|
d="M10 1.5a.5.5 0 0 0-.5-.5h-3a.5.5 0 0 0-.5.5v1a.5.5 0 0 0 .5.5h3a.5.5 0 0 0 .5-.5v-1Zm-5 0A1.5 1.5 0 0 1 6.5 0h3A1.5 1.5 0 0 1 11 1.5v1A1.5 1.5 0 0 1 9.5 4h-3A1.5 1.5 0 0 1 5 2.5v-1Zm-2 0h1v1A2.5 2.5 0 0 0 6.5 5h3A2.5 2.5 0 0 0 12 2.5v-1h1a2 2 0 0 1 2 2V14a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V3.5a2 2 0 0 1 2-2Z"
|
|
/>
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function SnippetLanguageDropdown({
|
|
selectedLanguage,
|
|
snippetLanguages,
|
|
handleSelectChange,
|
|
}: {
|
|
selectedLanguage: string;
|
|
snippetLanguages: string[];
|
|
handleSelectChange: (
|
|
e: React.ChangeEvent<HTMLSelectElement>
|
|
) => void;
|
|
}) {
|
|
return (
|
|
<div className={styles.dropdownContainer}>
|
|
<div className={styles.dropdown}>
|
|
<select
|
|
title="Select a language"
|
|
className={styles.dropdownSelect}
|
|
value={selectedLanguage}
|
|
onChange={handleSelectChange}
|
|
>
|
|
{snippetLanguages.map(language => {
|
|
return (
|
|
<option
|
|
key={language}
|
|
value={language}
|
|
className={styles.selectItem}
|
|
>
|
|
{language}
|
|
</option>
|
|
);
|
|
})}
|
|
</select>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export default function CodeSnippet({
|
|
children,
|
|
method,
|
|
heading,
|
|
defaultLanguage,
|
|
}: {
|
|
children: ReactNode;
|
|
method?: string;
|
|
heading: string;
|
|
defaultLanguage: string;
|
|
}) {
|
|
const methodStyles: { [key: string]: string } = {
|
|
GET: styles.get,
|
|
POST: styles.post,
|
|
PUT: styles.put,
|
|
DELETE: styles.delete,
|
|
};
|
|
|
|
const [selectedLanguage, setSelectedLanguage] =
|
|
useState<string>(defaultLanguage);
|
|
|
|
const handleSelectChange = (
|
|
e: React.ChangeEvent<HTMLSelectElement>
|
|
) => {
|
|
setSelectedLanguage(e.target.value);
|
|
};
|
|
|
|
const code = Children.toArray(children)
|
|
.filter(
|
|
child =>
|
|
React.isValidElement(child) &&
|
|
child.props.className ===
|
|
`language-${selectedLanguage}`
|
|
)
|
|
.map(child => {
|
|
return child as ReactElement;
|
|
})[0];
|
|
|
|
const snippetLanguages = Children.toArray(children).map(
|
|
child => {
|
|
if (React.isValidElement(child)) {
|
|
const languageClassParts =
|
|
child.props.className.split('-');
|
|
if (languageClassParts.length > 1) {
|
|
const language = languageClassParts[1];
|
|
return language;
|
|
}
|
|
}
|
|
}
|
|
);
|
|
|
|
return (
|
|
<div className={styles.snippetContainer}>
|
|
<div className={styles.snippetHeader}>
|
|
<div className={styles.snippetHeading}>
|
|
{method ? (
|
|
<span className={methodStyles[method]}>
|
|
{method}
|
|
</span>
|
|
) : null}{' '}
|
|
{heading}
|
|
</div>
|
|
<div className={styles.controlsContainer}>
|
|
{snippetLanguages.length > 1 ? (
|
|
<>
|
|
<SnippetLanguageDropdown
|
|
selectedLanguage={selectedLanguage}
|
|
snippetLanguages={snippetLanguages}
|
|
handleSelectChange={handleSelectChange}
|
|
/>
|
|
<div className={styles.spacer} />
|
|
</>
|
|
) : null}
|
|
<CodeSnippetCopyButton
|
|
copyText={code.props.content}
|
|
/>
|
|
</div>
|
|
</div>
|
|
<div className={styles.snippetContent}>
|
|
<pre>{code}</pre>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|