diff --git a/graph/ui-code-block/src/lib/json-code-block.tsx b/graph/ui-code-block/src/lib/json-code-block.tsx
index 1142025c50..4f7c7c5077 100644
--- a/graph/ui-code-block/src/lib/json-code-block.tsx
+++ b/graph/ui-code-block/src/lib/json-code-block.tsx
@@ -1,13 +1,8 @@
-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 { JSX, ReactNode, useMemo } from 'react';
import { twMerge } from 'tailwind-merge';
+import { CopyToClipboardButton } from '@nx/graph/ui-components';
export function JsonCodeBlockPreTag({
children,
@@ -30,45 +25,27 @@ export function JsonCodeBlockPreTag({
export interface JsonCodeBlockProps {
data: any;
renderSource: (propertyName: string) => ReactNode;
+ copyTooltipText: string;
}
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 (
- {
- setCopied(true);
- }}
- >
-
-
+ tooltipAlignment="right"
+ tooltipText={props.copyTooltipText}
+ 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'
+ )}
+ />
= {
+ component: CopyToClipboardButton,
+ title: 'CopyToClipboardButton',
+};
+export default meta;
+
+type Story = StoryObj;
+
+export const Simple: Story = {
+ args: {
+ text: 'Hello, world!',
+ tooltipAlignment: 'left',
+ } as CopyToClipboardButtonProps,
+};
diff --git a/graph/ui-components/src/lib/copy-to-clipboard-button.tsx b/graph/ui-components/src/lib/copy-to-clipboard-button.tsx
new file mode 100644
index 0000000000..0f7b667508
--- /dev/null
+++ b/graph/ui-components/src/lib/copy-to-clipboard-button.tsx
@@ -0,0 +1,61 @@
+// @ts-ignore
+import { CopyToClipboard } from 'react-copy-to-clipboard';
+import { JSX, ReactNode, useEffect, useState } from 'react';
+import {
+ ClipboardDocumentCheckIcon,
+ ClipboardDocumentIcon,
+} from '@heroicons/react/24/outline';
+
+export interface CopyToClipboardButtonProps {
+ text: string;
+ tooltipText?: string;
+ tooltipAlignment?: 'left' | 'right';
+ className?: string;
+ children?: ReactNode;
+}
+
+export function CopyToClipboardButton({
+ text,
+ tooltipAlignment,
+ tooltipText,
+ className,
+ children,
+}: CopyToClipboardButtonProps) {
+ const [copied, setCopied] = useState(false);
+
+ useEffect(() => {
+ if (!copied) return;
+ const t = setTimeout(() => {
+ setCopied(false);
+ }, 3000);
+ return () => clearTimeout(t);
+ }, [copied]);
+
+ return (
+ {
+ setCopied(true);
+ }}
+ >
+
+
+ );
+}
diff --git a/graph/ui-project-details/src/lib/copy-to-clipboard/copy-to-clipboard.stories.tsx b/graph/ui-project-details/src/lib/copy-to-clipboard/copy-to-clipboard.stories.tsx
deleted file mode 100644
index 8101be70cf..0000000000
--- a/graph/ui-project-details/src/lib/copy-to-clipboard/copy-to-clipboard.stories.tsx
+++ /dev/null
@@ -1,17 +0,0 @@
-import type { Meta, StoryObj } from '@storybook/react';
-import { CopyToClipboard } from './copy-to-clipboard';
-
-const meta: Meta = {
- component: CopyToClipboard,
- title: 'CopyToClipboard',
-};
-export default meta;
-
-type Story = StoryObj;
-
-export const Simple: Story = {
- args: {
- onCopy: () => {},
- tooltipAlignment: 'left',
- },
-};
diff --git a/graph/ui-project-details/src/lib/copy-to-clipboard/copy-to-clipboard.tsx b/graph/ui-project-details/src/lib/copy-to-clipboard/copy-to-clipboard.tsx
deleted file mode 100644
index e7cbfdd7ff..0000000000
--- a/graph/ui-project-details/src/lib/copy-to-clipboard/copy-to-clipboard.tsx
+++ /dev/null
@@ -1,36 +0,0 @@
-import { ClipboardIcon } from '@heroicons/react/24/outline';
-import { JSX, useEffect, useState } from 'react';
-
-interface CopyToClipboardProps {
- onCopy: () => void;
- tooltipAlignment?: 'left' | 'right';
-}
-
-export function CopyToClipboard(props: CopyToClipboardProps): JSX.Element {
- const [copied, setCopied] = useState(false);
- useEffect(() => {
- if (copied) {
- const timeout = setTimeout(() => {
- setCopied(false);
- }, 3000);
- return () => clearTimeout(timeout);
- }
- });
- return (
-
- {
- e.stopPropagation();
- setCopied(true);
- props.onCopy();
- }}
- >
-
- );
-}
diff --git a/graph/ui-project-details/src/lib/project-details/project-details.tsx b/graph/ui-project-details/src/lib/project-details/project-details.tsx
index 39b9508c6c..bcb6894a14 100644
--- a/graph/ui-project-details/src/lib/project-details/project-details.tsx
+++ b/graph/ui-project-details/src/lib/project-details/project-details.tsx
@@ -1,5 +1,3 @@
-// eslint-disable-next-line @typescript-eslint/no-unused-vars
-
/* eslint-disable @nx/enforce-module-boundaries */
// nx-ignore-next-line
import type { ProjectGraphProjectNode } from '@nx/devkit';
@@ -71,7 +69,7 @@ export const ProjectDetails = ({
>
@@ -90,17 +88,15 @@ export const ProjectDetails = ({
className="h-6 w-6"
/>
-
- {onViewInProjectGraph && viewInProjectGraphPosition === 'top' && (
-
- onViewInProjectGraph({ projectName: project.name })
- }
- />
- )}{' '}
-
+ {onViewInProjectGraph && viewInProjectGraphPosition === 'top' && (
+
+ onViewInProjectGraph({ projectName: project.name })
+ }
+ />
+ )}
-
+
{projectData.metadata?.description ? (
@@ -133,16 +129,14 @@ export const ProjectDetails = ({
) : null}
-
- {onViewInProjectGraph &&
- viewInProjectGraphPosition === 'bottom' && (
-
- onViewInProjectGraph({ projectName: project.name })
- }
- />
- )}{' '}
-
+ {onViewInProjectGraph &&
+ viewInProjectGraphPosition === 'bottom' && (
+
+ onViewInProjectGraph({ projectName: project.name })
+ }
+ />
+ )}
diff --git a/graph/ui-project-details/src/lib/target-configuration-details-header/target-configuration-details-header.tsx b/graph/ui-project-details/src/lib/target-configuration-details-header/target-configuration-details-header.tsx
index 849fe99200..6fcd9073b6 100644
--- a/graph/ui-project-details/src/lib/target-configuration-details-header/target-configuration-details-header.tsx
+++ b/graph/ui-project-details/src/lib/target-configuration-details-header/target-configuration-details-header.tsx
@@ -1,6 +1,7 @@
/* eslint-disable @nx/enforce-module-boundaries */
// nx-ignore-next-line
import type { TargetConfiguration } from '@nx/devkit';
+import { CopyToClipboardButton } from '@nx/graph/ui-components';
import {
ChevronDownIcon,
ChevronUpIcon,
@@ -17,7 +18,6 @@ import { twMerge } from 'tailwind-merge';
import { Pill } from '../pill';
import { TargetTechnologies } from '../target-technologies/target-technologies';
import { SourceInfo } from '../source-info/source-info';
-import { CopyToClipboard } from '../copy-to-clipboard/copy-to-clipboard';
import { getDisplayHeaderFromTargetConfiguration } from '../utils/get-display-header-from-target-configuration';
import { TargetExecutor } from '../target-executor/target-executor';
@@ -53,10 +53,6 @@ export const TargetConfigurationDetailsHeader = ({
onViewInTaskGraph,
onNxConnect,
}: TargetConfigurationDetailsHeaderProps) => {
- const handleCopyClick = async (copyText: string) => {
- await window.navigator.clipboard.writeText(copyText);
- };
-
if (!collapsable) {
// when collapsable is false, isCollasped should be false
isCollasped = false;
@@ -156,6 +152,12 @@ export const TargetConfigurationDetailsHeader = ({
+
{onViewInTaskGraph && (