2023-04-09 23:13:02 -05:00
|
|
|
'use client';
|
|
|
|
|
|
2023-04-11 23:57:24 -05:00
|
|
|
import React, {
|
2023-04-11 22:45:48 -05:00
|
|
|
Children,
|
2023-04-26 19:31:15 -05:00
|
|
|
ReactElement,
|
2023-04-11 23:57:24 -05:00
|
|
|
ReactNode,
|
2023-04-11 22:45:48 -05:00
|
|
|
useState,
|
|
|
|
|
} from 'react';
|
|
|
|
|
import styles from './CodeSnippet.module.css';
|
2023-04-11 17:00:54 -05:00
|
|
|
|
2026-03-26 15:22:04 -05:00
|
|
|
interface MarkdocFenceProps {
|
|
|
|
|
language?: string;
|
|
|
|
|
content?: string;
|
|
|
|
|
className?: string;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function isMarkdocElement(
|
|
|
|
|
child: ReactNode
|
|
|
|
|
): child is ReactElement<MarkdocFenceProps> {
|
|
|
|
|
return React.isValidElement(child);
|
|
|
|
|
}
|
|
|
|
|
|
2023-04-26 19:31:15 -05:00
|
|
|
function CodeSnippetCopyButton({
|
|
|
|
|
copyText,
|
|
|
|
|
}: {
|
|
|
|
|
copyText: string;
|
|
|
|
|
}) {
|
2023-04-09 23:13:02 -05:00
|
|
|
const [copyButtonTooltip, setCopyButtonTooltip] =
|
|
|
|
|
useState<boolean>(false);
|
|
|
|
|
|
|
|
|
|
const [showTooltip, setShowTooltip] =
|
|
|
|
|
useState<boolean>(false);
|
|
|
|
|
|
2026-03-26 15:22:04 -05:00
|
|
|
const handleCopy = () => {
|
2023-04-09 23:13:02 -05:00
|
|
|
setCopyButtonTooltip(true);
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
setCopyButtonTooltip(false);
|
|
|
|
|
}, 1000);
|
|
|
|
|
|
2023-04-26 19:31:15 -05:00
|
|
|
navigator.clipboard.writeText(copyText ?? '');
|
2023-04-09 23:13:02 -05:00
|
|
|
};
|
|
|
|
|
|
2023-04-09 15:23:13 -05:00
|
|
|
return (
|
2023-04-09 23:13:02 -05:00
|
|
|
<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)}
|
2023-04-09 15:23:13 -05:00
|
|
|
>
|
2023-04-09 23:13:02 -05:00
|
|
|
<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>
|
2023-04-09 15:23:13 -05:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2023-04-11 23:57:24 -05:00
|
|
|
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>
|
|
|
|
|
);
|
|
|
|
|
}
|
2023-04-11 22:45:48 -05:00
|
|
|
|
|
|
|
|
export default function CodeSnippet({
|
2023-04-09 14:50:50 -05:00
|
|
|
children,
|
2023-04-14 21:45:41 -05:00
|
|
|
method,
|
2023-04-09 15:23:13 -05:00
|
|
|
heading,
|
2023-04-11 22:45:48 -05:00
|
|
|
defaultLanguage,
|
2023-04-09 14:50:50 -05:00
|
|
|
}: {
|
2023-04-11 23:57:24 -05:00
|
|
|
children: ReactNode;
|
2023-04-14 21:45:41 -05:00
|
|
|
method?: string;
|
2023-04-09 15:23:13 -05:00
|
|
|
heading: string;
|
2023-04-11 22:45:48 -05:00
|
|
|
defaultLanguage: string;
|
2023-04-09 14:50:50 -05:00
|
|
|
}) {
|
2023-04-14 21:45:41 -05:00
|
|
|
const methodStyles: { [key: string]: string } = {
|
|
|
|
|
GET: styles.get,
|
|
|
|
|
POST: styles.post,
|
|
|
|
|
PUT: styles.put,
|
|
|
|
|
DELETE: styles.delete,
|
|
|
|
|
};
|
|
|
|
|
|
2023-04-11 22:45:48 -05:00
|
|
|
const [selectedLanguage, setSelectedLanguage] =
|
|
|
|
|
useState<string>(defaultLanguage);
|
|
|
|
|
|
2023-04-11 23:57:24 -05:00
|
|
|
const handleSelectChange = (
|
|
|
|
|
e: React.ChangeEvent<HTMLSelectElement>
|
|
|
|
|
) => {
|
|
|
|
|
setSelectedLanguage(e.target.value);
|
|
|
|
|
};
|
|
|
|
|
|
2026-03-26 15:22:04 -05:00
|
|
|
const childrenArray = Children.toArray(children);
|
2023-04-26 19:31:15 -05:00
|
|
|
|
2026-03-26 15:22:04 -05:00
|
|
|
const code = childrenArray.find(
|
|
|
|
|
(child): child is ReactElement<MarkdocFenceProps> => {
|
|
|
|
|
if (!isMarkdocElement(child)) return false;
|
|
|
|
|
const { language, className } = child.props;
|
|
|
|
|
return (
|
|
|
|
|
language === selectedLanguage ||
|
|
|
|
|
className === `language-${selectedLanguage}`
|
|
|
|
|
);
|
2023-04-11 23:57:24 -05:00
|
|
|
}
|
2023-04-11 22:45:48 -05:00
|
|
|
);
|
2023-04-11 17:00:54 -05:00
|
|
|
|
2026-03-26 15:22:04 -05:00
|
|
|
const snippetLanguages = childrenArray
|
|
|
|
|
.map(child => {
|
|
|
|
|
if (isMarkdocElement(child)) {
|
|
|
|
|
const { language, className } = child.props;
|
|
|
|
|
return language || className?.split('-')[1];
|
|
|
|
|
}
|
|
|
|
|
return undefined;
|
|
|
|
|
})
|
|
|
|
|
.filter((l): l is string => typeof l === 'string');
|
|
|
|
|
|
2023-04-09 14:50:50 -05:00
|
|
|
return (
|
2023-04-09 15:23:13 -05:00
|
|
|
<div className={styles.snippetContainer}>
|
|
|
|
|
<div className={styles.snippetHeader}>
|
2023-04-24 16:34:30 -05:00
|
|
|
<div className={styles.snippetHeading}>
|
2023-04-14 21:45:41 -05:00
|
|
|
{method ? (
|
|
|
|
|
<span className={methodStyles[method]}>
|
|
|
|
|
{method}
|
|
|
|
|
</span>
|
|
|
|
|
) : null}{' '}
|
|
|
|
|
{heading}
|
|
|
|
|
</div>
|
2023-04-11 23:57:24 -05:00
|
|
|
<div className={styles.controlsContainer}>
|
|
|
|
|
{snippetLanguages.length > 1 ? (
|
|
|
|
|
<>
|
|
|
|
|
<SnippetLanguageDropdown
|
|
|
|
|
selectedLanguage={selectedLanguage}
|
|
|
|
|
snippetLanguages={snippetLanguages}
|
|
|
|
|
handleSelectChange={handleSelectChange}
|
|
|
|
|
/>
|
|
|
|
|
<div className={styles.spacer} />
|
|
|
|
|
</>
|
|
|
|
|
) : null}
|
2023-04-26 19:31:15 -05:00
|
|
|
<CodeSnippetCopyButton
|
2026-03-26 15:22:04 -05:00
|
|
|
copyText={code?.props.content ?? ''}
|
2023-04-26 19:31:15 -05:00
|
|
|
/>
|
2023-04-11 23:57:24 -05:00
|
|
|
</div>
|
2023-04-09 15:23:13 -05:00
|
|
|
</div>
|
2023-04-09 23:13:02 -05:00
|
|
|
<div className={styles.snippetContent}>
|
2023-04-26 19:31:15 -05:00
|
|
|
<pre>{code}</pre>
|
2023-04-09 23:13:02 -05:00
|
|
|
</div>
|
2023-04-09 15:23:13 -05:00
|
|
|
</div>
|
2023-04-09 14:50:50 -05:00
|
|
|
);
|
|
|
|
|
}
|