Skip to content

Commit

Permalink
feat(ClipboardButton): delay tooltip's closing animation after copying (
Browse files Browse the repository at this point in the history
#1735)

Co-authored-by: Aleksandr Burobin <[email protected]>
  • Loading branch information
lxndr and Aleksandr Burobin authored Oct 1, 2024
1 parent b46f536 commit 20f19dd
Show file tree
Hide file tree
Showing 4 changed files with 100 additions and 10 deletions.
10 changes: 10 additions & 0 deletions src/components/ClipboardButton/ClipboardButton.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
@use '../variables';

$block: '.#{variables.$ns}clipboard-button';

#{$block} {
&__icon {
// prevent button icon from firing onMouseEnter/onFocus through parent button's handler
pointer-events: none;
}
}
96 changes: 88 additions & 8 deletions src/components/ClipboardButton/ClipboardButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,27 @@ import {Button} from '../Button';
import type {ButtonProps, ButtonSize} from '../Button';
import {ClipboardIcon} from '../ClipboardIcon';
import {CopyToClipboard} from '../CopyToClipboard';
import type {CopyToClipboardProps, CopyToClipboardStatus} from '../CopyToClipboard/types';
import type {
CopyToClipboardProps,
CopyToClipboardStatus,
OnCopyHandler,
} from '../CopyToClipboard/types';
import {block} from '../utils/cn';

import i18n from './i18n';

import './ClipboardButton.scss';

const b = block('clipboard-button');

export interface ClipboardButtonProps
extends Omit<CopyToClipboardProps, 'children'>,
Omit<ClipboardButtonComponentProps, 'status' | 'onClick'> {}
Omit<ClipboardButtonComponentProps, 'status' | 'closeDelay' | 'onClick'> {}

interface ClipboardButtonComponentProps
extends Omit<ButtonProps, 'href' | 'component' | 'target' | 'rel' | 'loading'> {
status: CopyToClipboardStatus;
closeDelay: number | undefined;
/** Disable tooltip. Tooltip won't be shown */
hasTooltip?: boolean;
/** Text shown before copy */
Expand All @@ -28,7 +38,8 @@ interface ClipboardButtonComponentProps
iconPosition?: 'start' | 'end';
}

const DEFAULT_TIMEOUT = 1000;
const DEFAULT_TIMEOUT = 1200;
const TOOLTIP_ANIMATION = 200;

const ButtonSizeToIconSize: Record<ButtonSize, number> = {
xs: 12,
Expand All @@ -49,19 +60,23 @@ const ClipboardButtonComponent = (props: ClipboardButtonComponentProps) => {
extraProps = {},
children,
iconPosition = 'start',
closeDelay,
onMouseEnter,
onFocus,
...rest
} = props;

const buttonIcon = (
<Button.Icon>
<Button.Icon className={b('icon')}>
<ClipboardIcon size={ButtonSizeToIconSize[size]} status={status} />
</Button.Icon>
);

return (
<ActionTooltip
disabled={!hasTooltip}
title={status === 'success' ? tooltipSuccessText : tooltipInitialText}
disabled={!hasTooltip}
closeDelay={closeDelay}
>
<Button
view={view}
Expand All @@ -70,6 +85,8 @@ const ClipboardButtonComponent = (props: ClipboardButtonComponentProps) => {
'aria-label': tooltipInitialText,
...extraProps,
}}
onMouseEnter={onMouseEnter}
onFocus={onFocus}
{...rest}
>
{iconPosition === 'start' ? buttonIcon : null}
Expand All @@ -81,10 +98,73 @@ const ClipboardButtonComponent = (props: ClipboardButtonComponentProps) => {
};

export function ClipboardButton(props: ClipboardButtonProps) {
const {text, timeout = DEFAULT_TIMEOUT, onCopy, options, ...buttonProps} = props;
const {
text,
timeout = DEFAULT_TIMEOUT,
onCopy,
options,
hasTooltip = true,
onMouseEnter,
onFocus,
...buttonProps
} = props;

const timerIdRef = React.useRef<number>();
const [tooltipCloseDelay, setTooltipCloseDelay] = React.useState<number | undefined>(undefined);
const [tooltipDisabled, setTooltipDisabled] = React.useState(false);

React.useEffect(() => window.clearTimeout(timerIdRef.current), []);

const handleCopy: OnCopyHandler = React.useCallback(
(text, result) => {

Check warning on line 119 in src/components/ClipboardButton/ClipboardButton.tsx

View workflow job for this annotation

GitHub Actions / Verify Files

'text' is already declared in the upper scope on line 102 column 9
onCopy?.(text, result);
setTooltipDisabled(false);
setTooltipCloseDelay(timeout);

window.clearTimeout(timerIdRef.current);

timerIdRef.current = window.setTimeout(() => {
setTooltipDisabled(true);
}, timeout - TOOLTIP_ANIMATION);
},
[onCopy, timeout],
);

const resetTooltip = React.useCallback(() => {
if (tooltipDisabled) {
setTooltipDisabled(false);
setTooltipCloseDelay(undefined);
}
}, [tooltipDisabled]);

const handleMouseEnter: React.MouseEventHandler<HTMLButtonElement> = React.useCallback(
(event) => {
onMouseEnter?.(event);
resetTooltip();
},
[onMouseEnter, resetTooltip],
);

const handleFocus: React.FocusEventHandler<HTMLButtonElement> = React.useCallback(
(event) => {
onFocus?.(event);
resetTooltip();
},
[onFocus, resetTooltip],
);

return (
<CopyToClipboard text={text} timeout={timeout} onCopy={onCopy} options={options}>
{(status) => <ClipboardButtonComponent {...buttonProps} status={status} />}
<CopyToClipboard text={text} timeout={timeout} onCopy={handleCopy} options={options}>
{(status) => (
<ClipboardButtonComponent
{...buttonProps}
closeDelay={tooltipCloseDelay}
hasTooltip={hasTooltip && !tooltipDisabled}
status={status}
onMouseEnter={handleMouseEnter}
onFocus={handleFocus}
/>
)}
</CopyToClipboard>
);
}
2 changes: 1 addition & 1 deletion src/components/ClipboardButton/i18n/en.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{
"startCopy": "Copy",
"endCopy": "Copied!"
"endCopy": "Copied"
}
2 changes: 1 addition & 1 deletion src/components/ClipboardButton/i18n/ru.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{
"startCopy": "Копировать",
"endCopy": "Скопировано!"
"endCopy": "Скопировано"
}

0 comments on commit 20f19dd

Please sign in to comment.