feat(graph): add copy button for entire target configuration (#26284)
<!-- 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="540" alt="Screenshot 2024-07-03 at 5 29 12 PM" src="https://github.com/nrwl/nx/assets/16211801/bed98c56-3bd5-4170-893b-cefe5fe292f9"> <img width="1195" alt="Screenshot 2024-07-03 at 5 29 03 PM" src="https://github.com/nrwl/nx/assets/16211801/544a4c4e-299f-40fc-a767-215d9c758dd8"> ## Related Issue(s) <!-- Please link the issue being fixed so it gets closed when this is merged. --> Fixes #
This commit is contained in:
parent
e09bad9363
commit
311710e56c
@ -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 (
|
||||
<div className="code-block group relative w-full">
|
||||
<div className="absolute right-0 top-0 z-10 flex">
|
||||
<CopyToClipboard
|
||||
<CopyToClipboardButton
|
||||
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>
|
||||
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'
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<SyntaxHighlighter
|
||||
language="json"
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
export * from './lib/copy-to-clipboard-button';
|
||||
export * from './lib/debounced-text-input';
|
||||
export * from './lib/tag';
|
||||
export * from './lib/dropdown';
|
||||
|
||||
@ -0,0 +1,20 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
import {
|
||||
CopyToClipboardButton,
|
||||
CopyToClipboardButtonProps,
|
||||
} from './copy-to-clipboard-button';
|
||||
|
||||
const meta: Meta<typeof CopyToClipboardButton> = {
|
||||
component: CopyToClipboardButton,
|
||||
title: 'CopyToClipboardButton',
|
||||
};
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof CopyToClipboardButton>;
|
||||
|
||||
export const Simple: Story = {
|
||||
args: {
|
||||
text: 'Hello, world!',
|
||||
tooltipAlignment: 'left',
|
||||
} as CopyToClipboardButtonProps,
|
||||
};
|
||||
61
graph/ui-components/src/lib/copy-to-clipboard-button.tsx
Normal file
61
graph/ui-components/src/lib/copy-to-clipboard-button.tsx
Normal file
@ -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 (
|
||||
<CopyToClipboard
|
||||
text={text}
|
||||
onCopy={() => {
|
||||
setCopied(true);
|
||||
}}
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
data-tooltip={tooltipText ? tooltipText : false}
|
||||
data-tooltip-align-right={tooltipAlignment === 'right'}
|
||||
data-tooltip-align-left={tooltipAlignment === 'left'}
|
||||
className={className}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}}
|
||||
>
|
||||
{copied ? (
|
||||
<ClipboardDocumentCheckIcon className="inline h-5 w-5 text-blue-500 dark:text-sky-500" />
|
||||
) : (
|
||||
<ClipboardDocumentIcon className="inline h-5 w-5" />
|
||||
)}
|
||||
{children}
|
||||
</button>
|
||||
</CopyToClipboard>
|
||||
);
|
||||
}
|
||||
@ -1,17 +0,0 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
import { CopyToClipboard } from './copy-to-clipboard';
|
||||
|
||||
const meta: Meta<typeof CopyToClipboard> = {
|
||||
component: CopyToClipboard,
|
||||
title: 'CopyToClipboard',
|
||||
};
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof CopyToClipboard>;
|
||||
|
||||
export const Simple: Story = {
|
||||
args: {
|
||||
onCopy: () => {},
|
||||
tooltipAlignment: 'left',
|
||||
},
|
||||
};
|
||||
@ -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 (
|
||||
<span
|
||||
data-tooltip="Copy to clipboard"
|
||||
data-tooltip-align-right={props.tooltipAlignment === 'right'}
|
||||
>
|
||||
<ClipboardIcon
|
||||
className={`inline h-4 w-5 !cursor-pointer ${
|
||||
copied ? 'text-sky-500' : ''
|
||||
}`}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
setCopied(true);
|
||||
props.onCopy();
|
||||
}}
|
||||
></ClipboardIcon>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
@ -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 = ({
|
||||
>
|
||||
<div
|
||||
className={twMerge(
|
||||
`flex items-center justify-between`,
|
||||
`flex flex-wrap items-center justify-between`,
|
||||
isCompact ? `gap-1` : `mb-4 gap-2`
|
||||
)}
|
||||
>
|
||||
@ -90,17 +88,15 @@ export const ProjectDetails = ({
|
||||
className="h-6 w-6"
|
||||
/>
|
||||
</div>
|
||||
<span>
|
||||
{onViewInProjectGraph && viewInProjectGraphPosition === 'top' && (
|
||||
<ViewInProjectGraphButton
|
||||
callback={() =>
|
||||
onViewInProjectGraph({ projectName: project.name })
|
||||
}
|
||||
/>
|
||||
)}{' '}
|
||||
</span>
|
||||
{onViewInProjectGraph && viewInProjectGraphPosition === 'top' && (
|
||||
<ViewInProjectGraphButton
|
||||
callback={() =>
|
||||
onViewInProjectGraph({ projectName: project.name })
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex justify-between py-2">
|
||||
<div className="flex flex-wrap justify-between py-2">
|
||||
<div>
|
||||
{projectData.metadata?.description ? (
|
||||
<p className="mb-2 text-sm capitalize text-gray-500 dark:text-slate-400">
|
||||
@ -133,16 +129,14 @@ export const ProjectDetails = ({
|
||||
) : null}
|
||||
</div>
|
||||
<div className="self-end">
|
||||
<span>
|
||||
{onViewInProjectGraph &&
|
||||
viewInProjectGraphPosition === 'bottom' && (
|
||||
<ViewInProjectGraphButton
|
||||
callback={() =>
|
||||
onViewInProjectGraph({ projectName: project.name })
|
||||
}
|
||||
/>
|
||||
)}{' '}
|
||||
</span>
|
||||
{onViewInProjectGraph &&
|
||||
viewInProjectGraphPosition === 'bottom' && (
|
||||
<ViewInProjectGraphButton
|
||||
callback={() =>
|
||||
onViewInProjectGraph({ projectName: project.name })
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
@ -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 = ({
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<CopyToClipboardButton
|
||||
text={JSON.stringify(targetConfiguration, null, 2)}
|
||||
tooltipText={!isCollasped ? 'Copy Target' : undefined}
|
||||
tooltipAlignment="right"
|
||||
className="rounded-md bg-inherit p-1 text-sm text-slate-600 ring-1 ring-inset ring-slate-400/40 hover:bg-slate-200 dark:text-slate-300 dark:ring-slate-400/30 dark:hover:bg-slate-700/60"
|
||||
/>
|
||||
{onViewInTaskGraph && (
|
||||
<button
|
||||
className="rounded-md bg-inherit p-1 text-sm text-slate-600 ring-1 ring-inset ring-slate-400/40 hover:bg-slate-200 dark:text-slate-300 dark:ring-slate-400/30 dark:hover:bg-slate-700/60"
|
||||
@ -194,21 +196,22 @@ export const TargetConfigurationDetailsHeader = ({
|
||||
</div>
|
||||
{!isCollasped && (
|
||||
<div className="ml-5 mt-2 text-sm">
|
||||
<SourceInfo
|
||||
data={sourceMap[`targets.${targetName}`]}
|
||||
propertyKey={`targets.${targetName}`}
|
||||
color="text-gray-500 dark:text-slate-400"
|
||||
/>
|
||||
<div className="flex">
|
||||
<SourceInfo
|
||||
data={sourceMap[`targets.${targetName}`]}
|
||||
propertyKey={`targets.${targetName}`}
|
||||
color="text-gray-500 dark:text-slate-400"
|
||||
/>
|
||||
</div>
|
||||
{targetName !== 'nx-release-publish' && (
|
||||
<div className="mt-2 text-right">
|
||||
<code className="ml-4 rounded bg-gray-100 px-2 py-1 font-mono text-gray-800 dark:bg-gray-700 dark:text-gray-300">
|
||||
nx run {projectName}:{targetName}
|
||||
</code>
|
||||
<span>
|
||||
<CopyToClipboard
|
||||
onCopy={() =>
|
||||
handleCopyClick(`nx run ${projectName}:${targetName}`)
|
||||
}
|
||||
<CopyToClipboardButton
|
||||
text={`nx run ${projectName}:${targetName}`}
|
||||
tooltipText="Copy Command"
|
||||
tooltipAlignment="right"
|
||||
/>
|
||||
</span>
|
||||
|
||||
@ -1,12 +1,11 @@
|
||||
/* eslint-disable @nx/enforce-module-boundaries */
|
||||
// nx-ignore-next-line
|
||||
import type { TargetConfiguration } from '@nx/devkit';
|
||||
|
||||
import { JsonCodeBlock } from '@nx/graph/ui-code-block';
|
||||
import { CopyToClipboardButton } from '@nx/graph/ui-components';
|
||||
import { useCallback, useContext, useEffect, useState } from 'react';
|
||||
import { FadingCollapsible } from './fading-collapsible';
|
||||
import { TargetConfigurationProperty } from './target-configuration-property';
|
||||
import { CopyToClipboard } from '../copy-to-clipboard/copy-to-clipboard';
|
||||
import { PropertyInfoTooltip, Tooltip } from '@nx/graph/ui-tooltips';
|
||||
import { TooltipTriggerText } from './tooltip-trigger-text';
|
||||
import { Pill } from '../pill';
|
||||
@ -53,10 +52,6 @@ export default function TargetConfigurationDetails({
|
||||
const [collapsed, setCollapsed] = useState(true);
|
||||
const { expandedTargets, toggleTarget } = useContext(ExpandedTargetsContext);
|
||||
|
||||
const handleCopyClick = async (copyText: string) => {
|
||||
await window.navigator.clipboard.writeText(copyText);
|
||||
};
|
||||
|
||||
const handleCollapseToggle = useCallback(() => {
|
||||
if (toggleTarget) {
|
||||
toggleTarget(targetName);
|
||||
@ -110,10 +105,7 @@ export default function TargetConfigurationDetails({
|
||||
<div className="p-4 text-base">
|
||||
<div className="group mb-4">
|
||||
<h4 className="mb-4">
|
||||
<TargetExecutorTitle
|
||||
{...displayHeader}
|
||||
handleCopyClick={handleCopyClick}
|
||||
/>
|
||||
<TargetExecutorTitle {...displayHeader} />
|
||||
</h4>
|
||||
<p className="pl-5 font-mono">
|
||||
<TargetExecutor {...displayHeader} link={link}>
|
||||
@ -131,10 +123,7 @@ export default function TargetConfigurationDetails({
|
||||
{script && (
|
||||
<div className="group mb-4">
|
||||
<h4 className="mb-4">
|
||||
<TargetExecutorTitle
|
||||
script={script}
|
||||
handleCopyClick={handleCopyClick}
|
||||
/>
|
||||
<TargetExecutorTitle script={script} />
|
||||
</h4>
|
||||
<p className="pl-5 font-mono">
|
||||
<TargetExecutor script={script} link={link}>
|
||||
@ -164,6 +153,7 @@ export default function TargetConfigurationDetails({
|
||||
<FadingCollapsible>
|
||||
<JsonCodeBlock
|
||||
data={options}
|
||||
copyTooltipText="Copy Options"
|
||||
renderSource={(propertyName: string) => (
|
||||
<TargetSourceInfo
|
||||
className="flex min-w-0 pl-4"
|
||||
@ -198,14 +188,11 @@ export default function TargetConfigurationDetails({
|
||||
</span>
|
||||
</Tooltip>
|
||||
<span className="mb-1 ml-2 hidden group-hover:inline">
|
||||
<CopyToClipboard
|
||||
onCopy={() =>
|
||||
handleCopyClick(
|
||||
`"inputs": ${JSON.stringify(
|
||||
targetConfiguration.inputs
|
||||
)}`
|
||||
)
|
||||
}
|
||||
<CopyToClipboardButton
|
||||
text={`"inputs": ${JSON.stringify(
|
||||
targetConfiguration.inputs
|
||||
)}`}
|
||||
tooltipText="Copy Inputs"
|
||||
/>
|
||||
</span>
|
||||
</h4>
|
||||
@ -239,14 +226,11 @@ export default function TargetConfigurationDetails({
|
||||
</span>
|
||||
</Tooltip>
|
||||
<span className="mb-1 ml-2 hidden group-hover:inline">
|
||||
<CopyToClipboard
|
||||
onCopy={() =>
|
||||
handleCopyClick(
|
||||
`"outputs": ${JSON.stringify(
|
||||
targetConfiguration.outputs
|
||||
)}`
|
||||
)
|
||||
}
|
||||
<CopyToClipboardButton
|
||||
text={`"outputs": ${JSON.stringify(
|
||||
targetConfiguration.outputs
|
||||
)}`}
|
||||
tooltipText="Copy Outputs"
|
||||
/>
|
||||
</span>
|
||||
</h4>
|
||||
@ -279,15 +263,12 @@ export default function TargetConfigurationDetails({
|
||||
<TooltipTriggerText>Depends On</TooltipTriggerText>
|
||||
</span>
|
||||
</Tooltip>
|
||||
<span className="inline pl-4 opacity-0 transition-opacity duration-150 ease-in-out group-hover/line:opacity-100">
|
||||
<CopyToClipboard
|
||||
onCopy={() =>
|
||||
handleCopyClick(
|
||||
`"dependsOn": ${JSON.stringify(
|
||||
targetConfiguration.dependsOn
|
||||
)}`
|
||||
)
|
||||
}
|
||||
<span className="mb-1 ml-2 hidden group-hover:inline">
|
||||
<CopyToClipboardButton
|
||||
text={`"dependsOn": ${JSON.stringify(
|
||||
targetConfiguration.dependsOn
|
||||
)}`}
|
||||
tooltipText="Copy Depends On"
|
||||
/>
|
||||
</span>
|
||||
</h4>
|
||||
@ -336,6 +317,7 @@ export default function TargetConfigurationDetails({
|
||||
<FadingCollapsible>
|
||||
<JsonCodeBlock
|
||||
data={targetConfiguration.configurations}
|
||||
copyTooltipText="Copy Configurations"
|
||||
renderSource={(propertyName: string) => (
|
||||
<TargetSourceInfo
|
||||
className="flex min-w-0 pl-4"
|
||||
|
||||
@ -1,29 +1,26 @@
|
||||
import { PropertyInfoTooltip, Tooltip } from '@nx/graph/ui-tooltips';
|
||||
import { CopyToClipboard } from '../copy-to-clipboard/copy-to-clipboard';
|
||||
import { CopyToClipboardButton } from '@nx/graph/ui-components';
|
||||
import { TooltipTriggerText } from '../target-configuration-details/tooltip-trigger-text';
|
||||
|
||||
export function TargetExecutorTitle({
|
||||
commands,
|
||||
command,
|
||||
script,
|
||||
handleCopyClick,
|
||||
executor,
|
||||
}: {
|
||||
handleCopyClick: (copyText: string) => void;
|
||||
commands?: string[];
|
||||
command?: string;
|
||||
script?: string;
|
||||
executor?: string;
|
||||
}) {
|
||||
if (commands && commands.length) {
|
||||
return (
|
||||
<span className="font-medium">
|
||||
Commands
|
||||
<span className="mb-1 ml-2 hidden group-hover:inline">
|
||||
<CopyToClipboard
|
||||
onCopy={() =>
|
||||
handleCopyClick(
|
||||
`"commands": [${commands.map((c) => `"${c}"`).join(', ')}]`
|
||||
)
|
||||
}
|
||||
<CopyToClipboardButton
|
||||
text={`"commands": [${commands.map((c) => `"${c}"`).join(', ')}]`}
|
||||
tooltipText="Copy Commands"
|
||||
/>
|
||||
</span>
|
||||
</span>
|
||||
@ -34,8 +31,9 @@ export function TargetExecutorTitle({
|
||||
<span className="font-medium">
|
||||
Command
|
||||
<span className="mb-1 ml-2 hidden group-hover:inline">
|
||||
<CopyToClipboard
|
||||
onCopy={() => handleCopyClick(`"command": "${command}"`)}
|
||||
<CopyToClipboardButton
|
||||
text={`"command": "${command}"`}
|
||||
tooltipText="Copy Command"
|
||||
/>
|
||||
</span>
|
||||
</span>
|
||||
@ -46,7 +44,7 @@ export function TargetExecutorTitle({
|
||||
<span className="font-medium">
|
||||
Script
|
||||
<span className="mb-1 ml-2 hidden group-hover:inline">
|
||||
<CopyToClipboard onCopy={() => handleCopyClick(script)} />
|
||||
<CopyToClipboardButton text={script} tooltipText="Copy Script" />
|
||||
</span>
|
||||
</span>
|
||||
);
|
||||
@ -58,6 +56,12 @@ export function TargetExecutorTitle({
|
||||
>
|
||||
<span className="font-medium">
|
||||
<TooltipTriggerText>Executor</TooltipTriggerText>
|
||||
<span className="mb-1 ml-2 hidden group-hover:inline">
|
||||
<CopyToClipboardButton
|
||||
text={executor ?? ''}
|
||||
tooltipText="Copy Executor"
|
||||
/>
|
||||
</span>
|
||||
</span>
|
||||
</Tooltip>
|
||||
);
|
||||
|
||||
@ -338,7 +338,7 @@
|
||||
"npm-run-path": "^4.0.1",
|
||||
"preact": "10.6.4",
|
||||
"react": "18.3.1",
|
||||
"react-copy-to-clipboard": "^5.0.3",
|
||||
"react-copy-to-clipboard": "^5.1.0",
|
||||
"react-dom": "18.3.1",
|
||||
"react-syntax-highlighter": "^15.5.0",
|
||||
"regenerator-runtime": "0.13.7",
|
||||
|
||||
2
pnpm-lock.yaml
generated
2
pnpm-lock.yaml
generated
@ -109,7 +109,7 @@ dependencies:
|
||||
specifier: 18.3.1
|
||||
version: 18.3.1
|
||||
react-copy-to-clipboard:
|
||||
specifier: ^5.0.3
|
||||
specifier: ^5.1.0
|
||||
version: 5.1.0(react@18.3.1)
|
||||
react-dom:
|
||||
specifier: 18.3.1
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user