Files
onspring-api-docs/src/app/components/CodeSnippet.tsx
T

199 lines
4.9 KiB
TypeScript
Raw Normal View History

2023-04-09 23:13:02 -05:00
'use client';
2023-04-11 23:57:24 -05:00
import React, {
Children,
2023-04-26 19:31:15 -05:00
ReactElement,
2023-04-11 23:57:24 -05:00
ReactNode,
useState,
} from 'react';
import styles from './CodeSnippet.module.css';
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
};
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 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-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>
);
}
export default function CodeSnippet({
2023-04-09 14:50:50 -05:00
children,
2023-04-14 21:45:41 -05:00
method,
heading,
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;
heading: string;
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,
};
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
}
);
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 (
<div className={styles.snippetContainer}>
<div className={styles.snippetHeader}>
<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>
</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>
</div>
2023-04-09 14:50:50 -05:00
);
}