diff --git a/lib/static/new-ui/features/suites/components/TestStepArgs/index.module.css b/lib/static/new-ui/features/suites/components/TestStepArgs/index.module.css index a57a2a356..10ef189d0 100644 --- a/lib/static/new-ui/features/suites/components/TestStepArgs/index.module.css +++ b/lib/static/new-ui/features/suites/components/TestStepArgs/index.module.css @@ -4,7 +4,19 @@ overflow: hidden; } +.copy { + opacity: 1; + pointer-events: auto; + background: none; + margin-left: 2px; + + position: absolute; + top: 0; + right: 2px; +} + .item { + position: relative; white-space: nowrap; min-width: 0; overflow: hidden; @@ -17,9 +29,42 @@ background-color: var(--color-tag-bg, var(--g-color-base-generic)); margin-left: 8px; padding: 0 4px; + + .copy { + opacity: 0; + position: absolute; + top: 2px; + right: 2px; + + color: var(--color-tag-title, var(--g-color-private-black-400)); + } + + &:hover { + background-color: var(--color-tag-bg, var(--g-color-private-black-150-solid)); + + .copy { + --background-color: var(--color-tag-bg, var(--g-color-private-black-150-solid)); + opacity: 1; + background-color: var(--background-color); + box-shadow: 0 0 8px 5px var(--background-color); + } + } } .item--failed { background: var(--g-color-private-red-50); color: var(--g-color-private-red-500-solid); + + .copy { + color: var(--g-color-private-red-500-solid); + } + + &:hover { + background-color: var(--g-color-private-red-150-solid); + + .copy { + --background-color: var(--g-color-private-red-150-solid); + background-color: var(--background-color); + } + } } diff --git a/lib/static/new-ui/features/suites/components/TestStepArgs/index.tsx b/lib/static/new-ui/features/suites/components/TestStepArgs/index.tsx index 149d63282..d2243c063 100644 --- a/lib/static/new-ui/features/suites/components/TestStepArgs/index.tsx +++ b/lib/static/new-ui/features/suites/components/TestStepArgs/index.tsx @@ -1,5 +1,7 @@ import classNames from 'classnames'; -import React, {ReactNode} from 'react'; +import React, {ReactNode, useState, useEffect} from 'react'; +import {Icon} from '@gravity-ui/uikit'; +import {Copy, CopyCheck} from '@gravity-ui/icons'; import styles from './index.module.css'; import {stringify} from '@/static/new-ui/utils'; @@ -10,19 +12,67 @@ interface TestStepArgsProps { isActive?: boolean; } -export function TestStepArgs(props: TestStepArgsProps): ReactNode { - const renderItems = (index: number): ReactNode => { - if (index >= props.args.length) { - return null; +interface ArgItemProps { + className: string; + value: string; +} + +function truncate(value: string, maxLen: number): string { + if (value.length <= maxLen) { + return value; + } + + return `${value.slice(0, maxLen - 3)}...`; +} + +const ArgItem = ({className, value}: ArgItemProps): ReactNode => { + const [copied, setCopied] = useState(false); + + useEffect(() => { + let timeoutId: ReturnType | null = null; + + if (copied) { + timeoutId = setTimeout(() => setCopied(false), 500); } - return
- {stringify(props.args[index])} - {renderItems(index + 1)} -
; - }; + return () => { + if (timeoutId) { + clearTimeout(timeoutId); + } + }; + }, [copied]); + + return ( + => { + e.stopPropagation(); + await navigator.clipboard.writeText(value); + setCopied(true); + }} + > + {truncate(value, 50)} + + + + + ); +}; + +export function TestStepArgs(props: TestStepArgsProps): ReactNode { + if (props.args.length === 0) { + return null; + } + + const itemClassName = classNames([styles.item, { + [styles['item--failed']]: !props.isActive && props.isFailed + }]); - return renderItems(0); + return ( +
+ {props.args.map((arg, index) => ( + + ))} +
+ ); }