src/components/ShellCommand/index.tsx (58 lines of code) (raw):
import React, { useState } from 'react';
import clsx from 'clsx';
import styles from './styles.module.css';
import CopyIcon from '@site/static/icons/copy.svg';
import CheckedIcon from '@site/static/icons/checked.svg';
interface Props {
className?: string;
style?: React.CSSProperties;
command: string;
}
export default function ShellCommand({ className, style, command }: Props) {
const [copied, setCopied] = useState(false);
function copyCommand() {
if (copied) return;
window.navigator.clipboard.writeText(command);
setCopied(true);
setTimeout(() => setCopied(false), 2000);
}
return (
<div
className={clsx(styles['shell-command-block'], className)}
style={style}
>
{Highlighter(command)}
<button className={styles['copy-icon']} onClick={copyCommand}>
{copied ? <CheckedIcon /> : <CopyIcon />}
</button>
</div>
);
}
// Implement simpy command highlighting
function Highlighter(command: string): React.ReactNode {
const tokens = command.split(' ');
const KNOWN_COMMAND = ['curl', 'sh'];
const rules: Record<string, (token: string, index: number) => boolean> = {
'code-cmd': (token, index) => index === 0 || KNOWN_COMMAND.includes(token),
'code-opt': (token, index) => token.startsWith('-'),
'code-opr': (token, index) => /\|+|&+|>+/.test(token),
};
function getCodeStyleName(token, index): string {
for (const key in rules) {
const matched = rules[key](token, index);
if (matched) return key;
}
}
return (
<pre>
<code key={'$'} className="select-none mr-1">
$
</code>
{tokens.map((token, index) => {
return (
<code key={token} className={styles[getCodeStyleName(token, index)]}>
{token}
{index < tokens.length && ' '}
</code>
);
})}
</pre>
);
}