nx/nx-dev/ui-fence/src/lib/fence.tsx
Nicholas Cunningham b9b89b2575
feat(nx-dev): Use app router for blogs (#23127)
The PR activates the app router for the Blog page at /blog. 

Its purpose is to test Next.js changes within nx-dev, allowing us to
identify and address any issues that users might encounter.
Integrating these changes into our environment, we can gain firsthand
experience and insights into potential problems, ensuring that the
updates are robust and reliable.

This approach helps us improve the overall quality and user experience
of our platform by proactively identifying and resolving any issues that
could affect consumers.
2024-06-11 09:28:29 -04:00

218 lines
5.8 KiB
TypeScript

'use client';
import {
ClipboardDocumentCheckIcon,
ClipboardDocumentIcon,
SparklesIcon,
} from '@heroicons/react/24/outline';
import { JSX, ReactNode, useEffect, useState } from 'react';
// @ts-ignore
import { CopyToClipboard } from 'react-copy-to-clipboard';
// @ts-ignore
import SyntaxHighlighter from 'react-syntax-highlighter';
import { CodeOutput } from './fences/code-output';
import { TerminalOutput } from './fences/terminal-output';
import { Selector } from './selector';
function resolveLanguage(lang: string) {
switch (lang) {
case 'ts':
return 'typescript';
case 'js':
return 'javascript';
default:
return lang;
}
}
function CodeWrapper(options: {
fileName: string;
command: string;
path: string;
language: string;
children: string; // intentionally typed as such
}): ({ children }: { children: ReactNode }) => JSX.Element {
return ({ children }: { children: ReactNode }) =>
options.language === 'shell' ? (
<TerminalOutput command={options.children} path="" content={null} />
) : options.command ? (
<TerminalOutput
content={children}
command={options.command}
path={options.path}
/>
) : (
<CodeOutput content={children} fileName={options.fileName} />
);
}
// pre-process the highlightLines to expand ranges like
// ["8-10", 19, 22] => [8,9,10,19,22]
function processHighlightLines(highlightLines: any): number[] {
const expandRange = (range: any) => {
const [start, end] = range.split('-').map(Number);
return Array.from({ length: end - start + 1 }, (_, i) => start + i);
};
// Process each item in the array
return (
highlightLines
.map((item: any) => {
if (typeof item === 'string' && item.includes('-')) {
return expandRange(item);
}
return Number(item);
})
.flat()
// remove duplicates
.filter(
(value: any, index: number, self: number[]) =>
self.indexOf(value) === index
)
);
}
export interface FenceProps {
children: string;
command: string;
path: string;
fileName: string;
highlightLines: number[];
lineGroups: Record<string, number[]>;
language: string;
enableCopy: boolean;
skipRescope?: boolean;
selectedLineGroup?: string;
onLineGroupSelectionChange?: (selection: string) => void;
}
export function Fence({
children,
command,
path,
fileName,
lineGroups,
highlightLines,
language,
enableCopy,
selectedLineGroup,
skipRescope,
onLineGroupSelectionChange,
}: FenceProps) {
if (highlightLines) {
highlightLines = processHighlightLines(highlightLines);
}
function lineNumberStyle(lineNumber: number) {
if (
(highlightLines && highlightLines.includes(lineNumber)) ||
(selectedLineGroup &&
lineGroups[selectedLineGroup] &&
lineGroups[selectedLineGroup].includes(lineNumber))
) {
return {
fontSize: 0,
display: 'inline-block',
position: 'absolute',
left: 0,
right: 0,
zIndex: -1,
borderLeftStyle: 'solid',
borderLeftWidth: 10,
lineHeight: '21px',
};
}
return {
fontSize: 0,
position: 'absolute',
};
}
const highlightOptions = Object.keys(lineGroups).map((lineNumberKey) => ({
label: lineNumberKey,
value: lineNumberKey,
}));
if (highlightOptions.length > 0) {
highlightOptions.unshift({
label: 'No highlighting',
value: '',
});
}
let selectedOption =
highlightOptions.find((option) => option.value === selectedLineGroup) ||
highlightOptions[0];
const [copied, setCopied] = useState(false);
useEffect(() => {
let t: NodeJS.Timeout;
if (copied) {
t = setTimeout(() => {
setCopied(false);
}, 3000);
}
return () => {
t && clearTimeout(t);
};
}, [copied]);
function highlightChange(item: { label: string; value: string }) {
onLineGroupSelectionChange?.(item.value);
}
return (
<div className="code-block group relative w-full">
<div>
<div className="absolute right-0 top-0 z-10 flex">
{enableCopy && enableCopy === true && (
<CopyToClipboard
text={command && command !== '' ? command : children}
onCopy={() => {
setCopied(true);
}}
>
<button
type="button"
className={
'not-prose flex border border-slate-200 bg-slate-50/50 p-2 opacity-0 transition-opacity group-hover:opacity-100 dark:border-slate-700 dark:bg-slate-800/60' +
(highlightOptions && highlightOptions[0]
? ''
: ' rounded-tr-lg')
}
>
{copied ? (
<ClipboardDocumentCheckIcon className="h-5 w-5 text-blue-500 dark:text-sky-500" />
) : (
<ClipboardDocumentIcon className="h-5 w-5" />
)}
</button>
</CopyToClipboard>
)}
{highlightOptions && highlightOptions[0] && (
<Selector
className="rounded-tr-lg"
items={highlightOptions}
selected={selectedOption}
onChange={highlightChange}
>
<SparklesIcon className="mr-1 h-5 w-5"></SparklesIcon>
</Selector>
)}
</div>
<SyntaxHighlighter
useInlineStyles={false}
showLineNumbers={true}
lineNumberStyle={lineNumberStyle}
language={resolveLanguage(language)}
children={children}
PreTag={CodeWrapper({
fileName,
command,
path,
language,
children,
})}
/>
</div>
</div>
);
}