Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: components ClipboardButton and ClipboardIcon #1123

Merged
merged 6 commits into from
Nov 16, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@
"dependencies": {
"@bem-react/classname": "^1.6.0",
"@gravity-ui/i18n": "^1.1.0",
"@gravity-ui/icons": "^2.5.0",
"@gravity-ui/icons": "^2.8.1",
"@popperjs/core": "^2.11.8",
"blueimp-md5": "^2.19.0",
"focus-trap": "^7.5.2",
Expand Down
8 changes: 0 additions & 8 deletions src/components/ClipboardButton/ClipboardButton.scss

This file was deleted.

58 changes: 20 additions & 38 deletions src/components/ClipboardButton/ClipboardButton.tsx
Original file line number Diff line number Diff line change
@@ -1,34 +1,24 @@
import React from 'react';

import {Button} from '../Button';
import type {ButtonProps} from '../Button';
import type {ButtonProps, ButtonSize} from '../Button';
import {ClipboardIcon} from '../ClipboardIcon';
import {CopyToClipboard} from '../CopyToClipboard';
import {CopyToClipboardStatus} from '../CopyToClipboard/types';
import type {CopyToClipboardBaseProps} from '../CopyToClipboard/types';
import type {CopyToClipboardBaseProps, CopyToClipboardStatus} from '../CopyToClipboard/types';
import {Tooltip} from '../Tooltip';
import type {QAProps} from '../types';
import {block} from '../utils/cn';

import i18n from './i18n';

import './ClipboardButton.scss';

export interface ClipboardButtonProps
extends CopyToClipboardBaseProps,
Omit<ClipboardButtonComponentProps, 'status' | 'onClick'>,
QAProps {
Omit<ClipboardButtonComponentProps, 'status' | 'onClick'> {
/** Time to restore initial state, ms */
timeout?: number;
}

interface ClipboardButtonComponentProps extends QAProps {
/** Icon size in pixels */
size?: number;
/** Element CSS class */
className?: string;
interface ClipboardButtonComponentProps
extends Omit<ButtonProps, 'href' | 'component' | 'target' | 'rel' | 'loading'> {
status: CopyToClipboardStatus;
onClick?: ButtonProps['onClick'];
/** Disable tooltip. Tooltip won't be shown */
hasTooltip?: boolean;
/** Text shown before copy */
Expand All @@ -37,44 +27,36 @@ interface ClipboardButtonComponentProps extends QAProps {
tooltipSuccessText?: string;
}

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

const DEFAULT_ICON_SIZE = 24;
const DEFAULT_TIMEOUT = 1000;

const ButtonSizeToIconSize: Record<ButtonSize, number> = {
xs: 14,
s: 18,
m: 22,
l: 30,
xl: 38,
amje marked this conversation as resolved.
Show resolved Hide resolved
};

const ClipboardButtonComponent = (props: ClipboardButtonComponentProps) => {
const {
size = DEFAULT_ICON_SIZE,
className,
qa,
size = 'm',
hasTooltip = true,
tooltipInitialText = i18n('startCopy'),
tooltipSuccessText = i18n('endCopy'),
status,
onClick,
view = 'flat',
...rest
} = props;
const buttonRef = React.useRef<HTMLButtonElement | null>(null);

React.useEffect(() => {
buttonRef?.current?.style.setProperty('--g-button-height', `${size}px`);
}, [size]);
const buttonRef = React.useRef<HTMLButtonElement>(null);
amje marked this conversation as resolved.
Show resolved Hide resolved

return (
<Tooltip
disabled={!hasTooltip}
content={
status === CopyToClipboardStatus.Success ? tooltipSuccessText : tooltipInitialText
}
content={status === 'success' ? tooltipSuccessText : tooltipInitialText}
>
<Button
ref={buttonRef}
view="flat"
className={b(null, className)}
qa={qa}
onClick={onClick}
>
<Button ref={buttonRef} view={view} size={size} {...rest}>
<Button.Icon>
<ClipboardIcon status={status} size={size} className={b('icon')} />
<ClipboardIcon size={ButtonSizeToIconSize[size]} status={status} />
</Button.Icon>
</Button>
</Tooltip>
Expand Down
34 changes: 28 additions & 6 deletions src/components/ClipboardButton/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,18 +30,40 @@ LANDING_BLOCK-->

## Properties

##### Specific ClipboardButton properties

| Name | Description | Type | Default |
| :----------------- | :----------------------------------------------------------------------- | :-----------------------------------------------: | :---------: |
| text | Text to copy | `string` | |
| hasTooltip | Disable tooltip. Tooltip won't be shown | `boolean` | `true` |
| onCopy | Callback after copy `(text: string, result: boolean) => void` | `Function` | |
| options | Copy to clipboard options | [CopyToClipboardOptions](#copytoclipboardoptions) | |
| size | Icon size | `number` | `24` |
| className | CSS class name | `string` | |
| hasTooltip | Disable tooltip. Tooltip won't be shown | `boolean` | `true` |
| text | Text to copy | `string` | |
| timeout | Time before state bounces back to its normal after the button is clicked | `number` | `1000` |
| tooltipInitialText | Text shown before copy | `string` | `"Copy"` |
| tooltipSuccessText | Text shown after copy | `string` | `"Copied!"` |
| qa | HTML `data-qa` attribute, used in tests | `string` | |
| timeout | Time before state bounces back to its normal after the button is clicked | `number` | `1000` |

##### Properties inherited from Button
amje marked this conversation as resolved.
Show resolved Hide resolved

| Name | Description | Type | Default |
| :----------- | :-------------------------------------- | :-----------------------------: | :-------------: |
| className | HTML `class` attribute | `string` | |
| disabled | Toggles `disabled` state | `false` | `false` |
| extraProps | Any additional props | `Record` | |
| id | HTML `id` attribute | `string` | |
| onBlur | `blur` event handler | `Function` | |
| onFocus | `focus` event handler | `Function` | |
| onMouseEnter | `mouseenter` event handler | `Function` | |
| onMouseLeave | `mouseleave` event handler | `Function` | |
| pin | Sets button edges style | `string` | `"round-round"` |
| qa | HTML `data-qa` attribute, used in tests | `string` | |
| selected | Toggles `selected` state | | |
| size | Sets button size | `string` | `"m"` |
| style | HTML `style` attribute | `React.CSSProperties` | |
| tabIndex | HTML `tabIndex` attribute | `number` | |
| title | HTML `title` attribute | `string` | |
| type | HTML `type` attribute | `"button"` `"submit"` `"reset"` | `"button"` |
| view | Sets button appearance | `string` | `"normal"` |
| width | `"auto"` `"max"` | `"auto"` `"max"` | |

### CopyToClipboardOptions

Expand Down
23 changes: 0 additions & 23 deletions src/components/ClipboardIcon/ClipboardIcon.scss

This file was deleted.

49 changes: 11 additions & 38 deletions src/components/ClipboardIcon/ClipboardIcon.tsx
Original file line number Diff line number Diff line change
@@ -1,48 +1,21 @@
import React from 'react';

import {CopyToClipboardStatus} from '../CopyToClipboard/types';
import {block} from '../utils/cn';
import {a11yHiddenSvgProps} from '../utils/svg';

import './ClipboardIcon.scss';
import {Copy, CopyCheck, CopyXmark} from '@gravity-ui/icons';

import type {CopyToClipboardStatus} from '../CopyToClipboard/types';
import {Icon} from '../Icon';
export interface ClipboardIconProps {
size: number;
status: CopyToClipboardStatus;
className?: string;
}

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

const renderStatusPath = (path: string) => (
<path
stroke="currentColor"
fill="transparent"
className={b('state')}
strokeWidth="1.5"
d={path}
/>
);

const STATUS_PATH = {
[CopyToClipboardStatus.Success]: renderStatusPath('M9.5 13l3 3l5 -5'),
[CopyToClipboardStatus.Error]: renderStatusPath('M9.5 10l8 8m-8 0l8 -8'),
};

export function ClipboardIcon({size, status, className}: ClipboardIconProps) {
return (
<svg
width={size}
height={size}
viewBox="0 0 24 24"
className={b(null, className)}
{...a11yHiddenSvgProps}
>
<path
fill="currentColor"
d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"
/>
{status === CopyToClipboardStatus.Pending ? null : STATUS_PATH[status]}
</svg>
);
export function ClipboardIcon({status, ...rest}: ClipboardIconProps) {
if (status === 'error') {
return <Icon data={CopyXmark} {...rest} />;
}
if (status === 'success') {
return <Icon data={CopyCheck} {...rest} />;
}
return <Icon data={Copy} {...rest} />;
}
6 changes: 3 additions & 3 deletions src/components/ClipboardIcon/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@
import {ClipboardIcon} from '@gravity-ui/uikit';
```

ClipboardIcon is a svg copy-paste icon. This component is mainly used together with `CopyToClipboard` as wrap component.
This component is mainly used together with `CopyToClipboard` as wrap component.

### Status

Depends on `status` property additionally draws animated successful (✓ sign) or failed (X️ sign) operation indicators.
Depends on `status` property icon is changed.

<!--LANDING_BLOCK

Expand Down Expand Up @@ -43,6 +43,6 @@ LANDING_BLOCK-->

| Name | Description | Type | Default |
| :-------- | :---------------------------------------------- | :------- | :------ |
| classname | HTML `class` attribute | `string` | |
| className | HTML `class` attribute | `string` | |
| size | Sets icon size | `number` | |
| status | Available values: `pending`, `success`, `error` | `string` | |
11 changes: 7 additions & 4 deletions src/components/CopyToClipboard/CopyToClipboard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@ import React from 'react';

import ReactCopyToClipboard from 'react-copy-to-clipboard';

import {CopyToClipboardStatus} from './types';
import type {CopyToClipboardBaseProps, CopyToClipboardContent} from './types';
import type {
CopyToClipboardBaseProps,
CopyToClipboardContent,
CopyToClipboardStatus,
} from './types';

interface CopyToClipboardGeneralProps extends CopyToClipboardBaseProps {
children: CopyToClipboardContent;
Expand All @@ -29,7 +32,7 @@ export class CopyToClipboard extends React.Component<
CopyToClipboardInnerProps,
CopyToClipboardState
> {
static INITIAL_STATUS = CopyToClipboardStatus.Pending;
static INITIAL_STATUS: CopyToClipboardStatus = 'pending';

state: CopyToClipboardState = {
status: CopyToClipboard.INITIAL_STATUS,
Expand Down Expand Up @@ -61,7 +64,7 @@ export class CopyToClipboard extends React.Component<
const {timeout, onCopy} = this.props;

this.setState({
status: result ? CopyToClipboardStatus.Success : CopyToClipboardStatus.Error,
status: result ? 'success' : 'error',
});

clearTimeout(this.timerId);
Expand Down
6 changes: 1 addition & 5 deletions src/components/CopyToClipboard/types.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
import type ReactCopyToClipboard from 'react-copy-to-clipboard';

export enum CopyToClipboardStatus {
Pending = 'pending',
Success = 'success',
Error = 'error',
}
export type CopyToClipboardStatus = 'pending' | 'success' | 'error';

export type OnCopyHandler = (text: string, result: boolean) => void;

Expand Down
5 changes: 1 addition & 4 deletions src/components/Label/Label.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -149,10 +149,7 @@ export const Label = React.forwardRef<HTMLDivElement, LabelProps>(function Label
{...commonActionButtonProps}
>
<Button.Icon>
<ClipboardIcon
status={status || CopyToClipboardStatus.Pending}
size={copyIconSize}
/>
<ClipboardIcon status={status || 'pending'} size={copyIconSize} />
</Button.Icon>
</Button>
);
Expand Down
2 changes: 1 addition & 1 deletion src/components/Select/__stories__/SelectShowcase.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ const ExampleItem = (props: {
{codeItem}
<ClipboardButton
className={b('copy-button')}
size={16}
size="xs"
text={codeItem}
/>
</pre>
Expand Down
2 changes: 1 addition & 1 deletion src/components/Table/hoc/withTableCopy/withTableCopy.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ export function withTableCopy<I extends TableDataItem, E extends {} = {}>(
<div className={b('copy')}>
<div className={b('copy-content')}>{originContent}</div>
<div className={b('copy-button')}>
<ClipboardButton text={copyText} size={14} />
<ClipboardButton text={copyText} size="xs" />
</div>
</div>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ export function BrandingConfigurator({theme}: BrandingConfiguratorProps) {
<div className={b('result')}>
<Card view="filled" theme="normal" className={b('result-card')}>
<div className={b('result-text')}>{resultText}</div>
<ClipboardButton text={resultText} size={16} className={b('result-copy')} />
<ClipboardButton text={resultText} size="xs" className={b('result-copy')} />
</Card>
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ export function PaletteGenerator({theme}: BrandingConfiguratorProps) {
<div className={b('result')}>
<Card view="filled" theme="normal" className={b('result-card')}>
<div className={b('result-text')}>{resultText}</div>
<ClipboardButton text={resultText} size={16} className={b('result-copy')} />
<ClipboardButton text={resultText} size="xs" className={b('result-copy')} />
</Card>
</div>
</div>
Expand Down