nx/graph/ui-code-block/src/lib/json-code-block.tsx
Emily Xiong e9b7439ce2
feat(graph): add description and tags to details page (#26252)
<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->

<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->

## Current Behavior
<!-- This is the behavior we have today -->

## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->
<img width="1155" alt="Screenshot 2024-05-29 at 5 47 20 PM"
src="https://github.com/nrwl/nx/assets/16211801/025f08d5-52cf-4087-94a5-e3319c89f8b1">
<img width="1127" alt="Screenshot 2024-05-29 at 5 47 04 PM"
src="https://github.com/nrwl/nx/assets/16211801/ff19514d-2513-4b13-ac9c-4b124ac0ce4a">
<img width="387" alt="Screenshot 2024-06-04 at 11 56 54 PM"
src="https://github.com/nrwl/nx/assets/16211801/ea2f0c47-a444-4be8-9ccd-60fd2b534e12">



## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->

Fixes #
2024-06-14 14:27:14 -04:00

118 lines
3.3 KiB
TypeScript

import {
ClipboardDocumentCheckIcon,
ClipboardDocumentIcon,
} from '@heroicons/react/24/outline';
// @ts-ignore
import { CopyToClipboard } from 'react-copy-to-clipboard';
// @ts-ignore
import SyntaxHighlighter, { createElement } from 'react-syntax-highlighter';
import { JSX, ReactNode, useEffect, useMemo, useState } from 'react';
import { twMerge } from 'tailwind-merge';
export function JsonCodeBlockPreTag({
children,
}: {
children: ReactNode;
}): JSX.Element {
return (
<div
className={twMerge(
'hljs not-prose w-full overflow-hidden rounded-md',
'font-mono text-sm',
'border border-slate-200 bg-slate-50/50 dark:border-slate-700 dark:bg-slate-800/60'
)}
>
<div className="p-4">{children}</div>
</div>
);
}
export interface JsonCodeBlockProps {
data: any;
renderSource: (propertyName: string) => ReactNode;
}
export function JsonCodeBlock(props: JsonCodeBlockProps): JSX.Element {
const [copied, setCopied] = useState(false);
const jsonString = useMemo(
() => JSON.stringify(props.data, null, 2),
[props.data]
);
useEffect(() => {
if (!copied) return;
const t = setTimeout(() => {
setCopied(false);
}, 3000);
return () => clearTimeout(t);
}, [copied]);
return (
<div className="code-block group relative w-full">
<div className="absolute right-0 top-0 z-10 flex">
<CopyToClipboard
text={jsonString}
onCopy={() => {
setCopied(true);
}}
>
<button
type="button"
className={twMerge(
'not-prose flex',
'border border-slate-200 bg-slate-50/50 p-2 dark:border-slate-700 dark:bg-slate-800/60',
'opacity-0 transition-opacity group-hover:opacity-100'
)}
>
{copied ? (
<ClipboardDocumentCheckIcon className="h-5 w-5 text-blue-500 dark:text-sky-500" />
) : (
<ClipboardDocumentIcon className="h-5 w-5" />
)}
</button>
</CopyToClipboard>
</div>
<SyntaxHighlighter
language="json"
children={jsonString}
PreTag={JsonCodeBlockPreTag}
renderer={sourcesRenderer(props.renderSource)}
/>
</div>
);
}
export function sourcesRenderer(
renderSource: (propertyName: string) => ReactNode
) {
return ({ rows, stylesheet }: any) => {
return rows.map((node: any, idx: number) => {
const element = createElement({
node,
stylesheet,
useInlineStyles: false,
key: `code-line-${idx}`,
});
let sourceElement: ReactNode;
const attrNode = node.children.find(
(c: any) =>
c.type === 'element' && c.properties?.className?.includes('hljs-attr')
);
if (attrNode?.children?.length) {
for (const child of attrNode.children) {
sourceElement = renderSource(child.value); // e.g. command
if (sourceElement) break;
}
}
return (
<span className="group/line flex" key={`code-group${idx}`}>
<span>{element}</span>
{sourceElement && (
<span className="min-w-0 flex-1 pl-2 opacity-0 transition-opacity duration-150 ease-in-out group-hover/line:opacity-100">
{sourceElement}
</span>
)}
</span>
);
});
};
}