mirror of
https://github.com/woocommerce/woocommerce-paypal-payments.git
synced 2025-08-30 05:00:51 +08:00
Merge pull request #3561 from woocommerce/PCP-4483-add-a-button-to-copy-merchant-credentials-in-settings-tab
Add buttons to copy merchant credentials in the Settings tab (4483)
This commit is contained in:
commit
7c8532da62
5 changed files with 223 additions and 10 deletions
|
@ -1,9 +1,50 @@
|
|||
.ppcp--static-value {
|
||||
@include font(13, 26, 400);
|
||||
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
|
||||
&.ppcp--static-value-with-copy {
|
||||
display: inline-flex;
|
||||
align-items: flex-start;
|
||||
gap: 6px;
|
||||
overflow: visible;
|
||||
|
||||
.ppcp--static-value-text {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
white-space: normal;
|
||||
word-break: break-all;
|
||||
overflow-wrap: break-word;
|
||||
max-width: 37ch;
|
||||
}
|
||||
}
|
||||
|
||||
&:not(.ppcp--static-value-with-copy) {
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
.ppcp-copy-button {
|
||||
display: flex;
|
||||
border: none;
|
||||
background: transparent;
|
||||
color: $color-gray-700;
|
||||
cursor: pointer;
|
||||
transition: color 0.2s ease;
|
||||
flex-shrink: 0;
|
||||
|
||||
&:hover:not(:disabled) {
|
||||
color: $color-blueberry;
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
svg {
|
||||
fill: currentColor;
|
||||
}
|
||||
}
|
||||
|
||||
// Fix the checkbox layout (add gap between checkbox and label).
|
||||
|
|
|
@ -1,9 +1,31 @@
|
|||
import { Action } from '../Elements';
|
||||
import classNames from 'classnames';
|
||||
import CopyButton from '../Elements/CopyButton';
|
||||
|
||||
const ControlStaticValue = ( { value } ) => (
|
||||
<Action>
|
||||
<div className="ppcp--static-value">{ value }</div>
|
||||
</Action>
|
||||
);
|
||||
const ControlStaticValue = ( {
|
||||
value,
|
||||
showCopy = false,
|
||||
copyButtonProps = {},
|
||||
className,
|
||||
...props
|
||||
} ) => {
|
||||
const wrapperClass = classNames( 'ppcp--static-value', {
|
||||
'ppcp--static-value-with-copy': showCopy,
|
||||
'ppcp--has-copy': showCopy,
|
||||
} );
|
||||
|
||||
return (
|
||||
<Action className={ className } { ...props }>
|
||||
{ showCopy ? (
|
||||
<div className={ wrapperClass }>
|
||||
<div className="ppcp--static-value-text">{ value }</div>
|
||||
<CopyButton value={ value } { ...copyButtonProps } />
|
||||
</div>
|
||||
) : (
|
||||
<div className={ wrapperClass }>{ value }</div>
|
||||
) }
|
||||
</Action>
|
||||
);
|
||||
};
|
||||
|
||||
export default ControlStaticValue;
|
||||
|
|
|
@ -0,0 +1,106 @@
|
|||
import { __ } from '@wordpress/i18n';
|
||||
import { speak } from '@wordpress/a11y';
|
||||
import { Tooltip } from '@wordpress/components';
|
||||
import { SVG, Path } from '@wordpress/primitives';
|
||||
import classNames from 'classnames';
|
||||
import { useCopyToClipboard } from '../../../hooks/useCopyToClipboard';
|
||||
|
||||
const COPY_CONFIRMATION_DURATION = 1000;
|
||||
|
||||
/**
|
||||
* Copy button component with tooltip and icon transition
|
||||
* @param {Object} props - Component props
|
||||
* @param {string} props.value - The text value to copy to clipboard
|
||||
* @param {string} [props.className] - Additional CSS class names
|
||||
* @param {string} [props.ariaLabel] - Custom aria-label for the button
|
||||
*/
|
||||
const CopyButton = ( { value, className, ariaLabel, ...props } ) => {
|
||||
const { copy, copied, error } = useCopyToClipboard( {
|
||||
successDuration: COPY_CONFIRMATION_DURATION,
|
||||
} );
|
||||
|
||||
const buttonClass = classNames( 'ppcp-copy-button', className );
|
||||
|
||||
const getTooltipText = () => {
|
||||
if ( copied ) {
|
||||
return __( 'Copied!', 'woocommerce-paypal-payments' );
|
||||
}
|
||||
if ( error ) {
|
||||
return __( 'Failed to copy', 'woocommerce-paypal-payments' );
|
||||
}
|
||||
return __( 'Copy to clipboard', 'woocommerce-paypal-payments' );
|
||||
};
|
||||
|
||||
const handleCopy = async () => {
|
||||
if ( ! value ) {
|
||||
return;
|
||||
}
|
||||
await copy( value );
|
||||
|
||||
if ( copied ) {
|
||||
speak(
|
||||
__( 'Copied to clipboard', 'woocommerce-paypal-payments' ),
|
||||
'assertive'
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if ( error ) {
|
||||
speak(
|
||||
__(
|
||||
'Failed to copy to clipboard',
|
||||
'woocommerce-paypal-payments'
|
||||
),
|
||||
'assertive'
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
text={ getTooltipText() }
|
||||
placement="top"
|
||||
delay={ 100 }
|
||||
hideOnClick={ false }
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
onClick={ handleCopy }
|
||||
className={ buttonClass }
|
||||
disabled={ ! value }
|
||||
aria-label={ ariaLabel || getTooltipText() }
|
||||
{ ...props }
|
||||
>
|
||||
{ copied ? <CheckIcon /> : <CopyIcon /> }
|
||||
</button>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
|
||||
const CopyIcon = () => (
|
||||
<SVG
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<Path
|
||||
fillRule="evenodd"
|
||||
d="M16 16v3a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V9a1 1 0 0 1 1-1h3V5a1 1 0 0 1 1-1h10a1 1 0 0 1 1 1v10a1 1 0 0 1-1 1h-3zm2.5-10.5v9H16V9a1 1 0 0 0-1-1H9.5V5.5h9z"
|
||||
clipRule="evenodd"
|
||||
/>
|
||||
</SVG>
|
||||
);
|
||||
|
||||
const CheckIcon = () => (
|
||||
<SVG
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<Path d="M9 16.17L4.83 12L3.41 13.41L9 19L21 7L19.59 5.59L9 16.17Z" />
|
||||
</SVG>
|
||||
);
|
||||
|
||||
export default CopyButton;
|
|
@ -37,17 +37,23 @@ const ConnectionStatus = () => {
|
|||
title={ __( 'Merchant ID', 'woocommerce-paypal-payments' ) }
|
||||
className="ppcp--no-gap"
|
||||
>
|
||||
<ControlStaticValue value={ merchant.id } />
|
||||
<ControlStaticValue value={ merchant.id } showCopy={ true } />
|
||||
</SettingsBlock>
|
||||
<SettingsBlock
|
||||
title={ __( 'Email address', 'woocommerce-paypal-payments' ) }
|
||||
>
|
||||
<ControlStaticValue value={ merchant.email } />
|
||||
<ControlStaticValue
|
||||
value={ merchant.email }
|
||||
showCopy={ true }
|
||||
/>
|
||||
</SettingsBlock>
|
||||
<SettingsBlock
|
||||
title={ __( 'Client ID', 'woocommerce-paypal-payments' ) }
|
||||
>
|
||||
<ControlStaticValue value={ merchant.clientId } />
|
||||
<ControlStaticValue
|
||||
value={ merchant.clientId }
|
||||
showCopy={ true }
|
||||
/>
|
||||
</SettingsBlock>
|
||||
</SettingsCard>
|
||||
);
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
import { useState, useRef } from '@wordpress/element';
|
||||
|
||||
/**
|
||||
* Custom hook for handling copy to clipboard functionality
|
||||
*
|
||||
* @param {Object} options - Configuration options
|
||||
* @param {number} options.successDuration - How long to show success state (ms)
|
||||
* @return {Object} Copy functionality and state
|
||||
*/
|
||||
export const useCopyToClipboard = ( options = {} ) => {
|
||||
const { successDuration = 1000 } = options;
|
||||
const [ copied, setCopied ] = useState( false );
|
||||
const [ error, setError ] = useState( false );
|
||||
const timerRef = useRef( null );
|
||||
|
||||
const copy = async ( text ) => {
|
||||
try {
|
||||
await navigator.clipboard.writeText( text );
|
||||
|
||||
clearTimeout( timerRef.current );
|
||||
setCopied( true );
|
||||
setError( false );
|
||||
|
||||
timerRef.current = setTimeout(
|
||||
() => setCopied( false ),
|
||||
successDuration
|
||||
);
|
||||
} catch ( err ) {
|
||||
console.error( 'Copy failed:', err );
|
||||
setError( true );
|
||||
setCopied( false );
|
||||
}
|
||||
};
|
||||
|
||||
return { copy, copied, error };
|
||||
};
|
||||
|
||||
export default useCopyToClipboard;
|
Loading…
Add table
Add a link
Reference in a new issue