Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
6 changes: 6 additions & 0 deletions src/components/CopyLinkButton/CopyLinkButton.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
.ydb-copy-link-button {
&__icon {
// prevent button icon from firing onMouseEnter/onFocus through parent button's handler
pointer-events: none;
}
}
83 changes: 83 additions & 0 deletions src/components/CopyLinkButton/CopyLinkButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import React from 'react';

import {Link} from '@gravity-ui/icons';
import type {ButtonProps, CopyToClipboardStatus} from '@gravity-ui/uikit';
import {ActionTooltip, Button, CopyToClipboard, Icon} from '@gravity-ui/uikit';

import {cn} from '../../utils/cn';

import i18n from './i18n';

import './CopyLinkButton.scss';

const b = cn('ydb-copy-link-button');

interface LinkButtonComponentProps extends ButtonProps {
size?: ButtonProps['size'];
hasTooltip?: boolean;
status: CopyToClipboardStatus;
closeDelay?: number;
title?: string;
}

const DEFAULT_TIMEOUT = 1200;
const TOOLTIP_ANIMATION = 200;

const LinkButtonComponent = (props: LinkButtonComponentProps) => {
const {size = 'm', hasTooltip = true, status, closeDelay, title, ...rest} = props;

const baseTitle = title ?? i18n('description_copy');

return (
<ActionTooltip
title={status === 'success' ? i18n('description_copied') : baseTitle}
disabled={!hasTooltip}
closeDelay={closeDelay}
>
<Button view="flat" size={size} {...rest}>
<Button.Icon className={b('icon')}>
<Icon data={Link} size={16} />
</Button.Icon>
</Button>
</ActionTooltip>
);
};

export interface CopyLinkButtonProps extends ButtonProps {
text: string;
}

export function CopyLinkButton(props: CopyLinkButtonProps) {
const {text, ...buttonProps} = props;

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

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

const handleCopy = React.useCallback(() => {
setTooltipDisabled(false);
setTooltipCloseDelay(timeout);

window.clearTimeout(timerIdRef.current);

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

return (
<CopyToClipboard text={text} timeout={timeout} onCopy={handleCopy}>
{(status) => (
<LinkButtonComponent
{...buttonProps}
closeDelay={tooltipCloseDelay}
hasTooltip={!tooltipDisabled}
status={status}
/>
)}
</CopyToClipboard>
);
}
4 changes: 4 additions & 0 deletions src/components/CopyLinkButton/i18n/en.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"description_copy": "Copy link to clipboard",
"description_copied": "Copied to clipboard"
}
7 changes: 7 additions & 0 deletions src/components/CopyLinkButton/i18n/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import {registerKeysets} from '../../../utils/i18n';

import en from './en.json';

const COMPONENT = 'ydb-copy-link-button';

export default registerKeysets(COMPONENT, {en});
4 changes: 4 additions & 0 deletions src/components/Drawer/Drawer.scss
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,8 @@

height: 100%;
}

&__controls {
margin-left: auto;
}
}
48 changes: 48 additions & 0 deletions src/components/Drawer/Drawer.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import React from 'react';

import {Xmark} from '@gravity-ui/icons';
import {DrawerItem, Drawer as GravityDrawer} from '@gravity-ui/navigation';
import {ActionTooltip, Button, Flex, Icon} from '@gravity-ui/uikit';

import {cn} from '../../utils/cn';
import {isNumeric} from '../../utils/utils';
import {CopyLinkButton} from '../CopyLinkButton/CopyLinkButton';

import {useDrawerContext} from './DrawerContext';

Expand Down Expand Up @@ -112,6 +115,11 @@ const DrawerPaneContentWrapper = ({
);
};

type DrawerControl =
| {type: 'close'}
| {type: 'copyLink'; link: string}
| {type: 'custom'; node: React.ReactNode; key: string};

interface DrawerPaneProps {
children: React.ReactNode;
renderDrawerContent: () => React.ReactNode;
Expand All @@ -124,6 +132,9 @@ interface DrawerPaneProps {
className?: string;
detectClickOutside?: boolean;
isPercentageWidth?: boolean;
drawerControls?: DrawerControl[];
title?: React.ReactNode;
headerClassName?: string;
}

export const DrawerWrapper = ({
Expand All @@ -138,12 +149,48 @@ export const DrawerWrapper = ({
className,
detectClickOutside,
isPercentageWidth,
drawerControls = [],
title,
headerClassName,
}: DrawerPaneProps) => {
React.useEffect(() => {
return () => {
onCloseDrawer();
};
}, [onCloseDrawer]);

const renderDrawerHeader = () => {
const controls = [];
for (const control of drawerControls) {
switch (control.type) {
case 'close':
controls.push(
<ActionTooltip title="Close" key="close">
<Button view="flat" onClick={onCloseDrawer}>
<Icon data={Xmark} size={16} />
</Button>
</ActionTooltip>,
);
break;
case 'copyLink':
controls.push(<CopyLinkButton text={control.link} key="copyLink" />);
break;
case 'custom':
controls.push(
<React.Fragment key={control.key}>{control.node}</React.Fragment>,
);
break;
}
}

return (
<Flex justifyContent="space-between" className={headerClassName}>
{title}
<Flex className={b('controls')}>{controls}</Flex>
</Flex>
);
};

return (
<React.Fragment>
{children}
Expand All @@ -158,6 +205,7 @@ export const DrawerWrapper = ({
detectClickOutside={detectClickOutside}
isPercentageWidth={isPercentageWidth}
>
{renderDrawerHeader()}
{renderDrawerContent()}
</DrawerPaneContentWrapper>
</React.Fragment>
Expand Down
Loading