Skip to content

Commit c01b536

Browse files
authored
feat(Drawer): add utility to render header (#2235)
1 parent cc454d6 commit c01b536

File tree

6 files changed

+152
-0
lines changed

6 files changed

+152
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
.ydb-copy-link-button {
2+
&__icon {
3+
// prevent button icon from firing onMouseEnter/onFocus through parent button's handler
4+
pointer-events: none;
5+
}
6+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import React from 'react';
2+
3+
import {Link} from '@gravity-ui/icons';
4+
import type {ButtonProps, CopyToClipboardStatus} from '@gravity-ui/uikit';
5+
import {ActionTooltip, Button, CopyToClipboard, Icon} from '@gravity-ui/uikit';
6+
7+
import {cn} from '../../utils/cn';
8+
9+
import i18n from './i18n';
10+
11+
import './CopyLinkButton.scss';
12+
13+
const b = cn('ydb-copy-link-button');
14+
15+
interface LinkButtonComponentProps extends ButtonProps {
16+
size?: ButtonProps['size'];
17+
hasTooltip?: boolean;
18+
status: CopyToClipboardStatus;
19+
closeDelay?: number;
20+
title?: string;
21+
}
22+
23+
const DEFAULT_TIMEOUT = 1200;
24+
const TOOLTIP_ANIMATION = 200;
25+
26+
const LinkButtonComponent = (props: LinkButtonComponentProps) => {
27+
const {size = 'm', hasTooltip = true, status, closeDelay, title, ...rest} = props;
28+
29+
const baseTitle = title ?? i18n('description_copy');
30+
31+
return (
32+
<ActionTooltip
33+
title={status === 'success' ? i18n('description_copied') : baseTitle}
34+
disabled={!hasTooltip}
35+
closeDelay={closeDelay}
36+
>
37+
<Button view="flat" size={size} {...rest}>
38+
<Button.Icon className={b('icon')}>
39+
<Icon data={Link} size={16} />
40+
</Button.Icon>
41+
</Button>
42+
</ActionTooltip>
43+
);
44+
};
45+
46+
export interface CopyLinkButtonProps extends ButtonProps {
47+
text: string;
48+
}
49+
50+
export function CopyLinkButton(props: CopyLinkButtonProps) {
51+
const {text, ...buttonProps} = props;
52+
53+
const timerIdRef = React.useRef<number>();
54+
const [tooltipCloseDelay, setTooltipCloseDelay] = React.useState<number | undefined>(undefined);
55+
const [tooltipDisabled, setTooltipDisabled] = React.useState(false);
56+
const timeout = DEFAULT_TIMEOUT;
57+
58+
React.useEffect(() => window.clearTimeout(timerIdRef.current), []);
59+
60+
const handleCopy = React.useCallback(() => {
61+
setTooltipDisabled(false);
62+
setTooltipCloseDelay(timeout);
63+
64+
window.clearTimeout(timerIdRef.current);
65+
66+
timerIdRef.current = window.setTimeout(() => {
67+
setTooltipDisabled(true);
68+
}, timeout - TOOLTIP_ANIMATION);
69+
}, [timeout]);
70+
71+
return (
72+
<CopyToClipboard text={text} timeout={timeout} onCopy={handleCopy}>
73+
{(status) => (
74+
<LinkButtonComponent
75+
{...buttonProps}
76+
closeDelay={tooltipCloseDelay}
77+
hasTooltip={!tooltipDisabled}
78+
status={status}
79+
/>
80+
)}
81+
</CopyToClipboard>
82+
);
83+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"description_copy": "Copy link to clipboard",
3+
"description_copied": "Copied to clipboard"
4+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import {registerKeysets} from '../../../utils/i18n';
2+
3+
import en from './en.json';
4+
5+
const COMPONENT = 'ydb-copy-link-button';
6+
7+
export default registerKeysets(COMPONENT, {en});

src/components/Drawer/Drawer.scss

+4
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,8 @@
1212

1313
height: 100%;
1414
}
15+
16+
&__controls {
17+
margin-left: auto;
18+
}
1519
}

src/components/Drawer/Drawer.tsx

+48
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
import React from 'react';
22

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

57
import {cn} from '../../utils/cn';
68
import {isNumeric} from '../../utils/utils';
9+
import {CopyLinkButton} from '../CopyLinkButton/CopyLinkButton';
710

811
import {useDrawerContext} from './DrawerContext';
912

@@ -112,6 +115,11 @@ const DrawerPaneContentWrapper = ({
112115
);
113116
};
114117

118+
type DrawerControl =
119+
| {type: 'close'}
120+
| {type: 'copyLink'; link: string}
121+
| {type: 'custom'; node: React.ReactNode; key: string};
122+
115123
interface DrawerPaneProps {
116124
children: React.ReactNode;
117125
renderDrawerContent: () => React.ReactNode;
@@ -124,6 +132,9 @@ interface DrawerPaneProps {
124132
className?: string;
125133
detectClickOutside?: boolean;
126134
isPercentageWidth?: boolean;
135+
drawerControls?: DrawerControl[];
136+
title?: React.ReactNode;
137+
headerClassName?: string;
127138
}
128139

129140
export const DrawerWrapper = ({
@@ -138,12 +149,48 @@ export const DrawerWrapper = ({
138149
className,
139150
detectClickOutside,
140151
isPercentageWidth,
152+
drawerControls = [],
153+
title,
154+
headerClassName,
141155
}: DrawerPaneProps) => {
142156
React.useEffect(() => {
143157
return () => {
144158
onCloseDrawer();
145159
};
146160
}, [onCloseDrawer]);
161+
162+
const renderDrawerHeader = () => {
163+
const controls = [];
164+
for (const control of drawerControls) {
165+
switch (control.type) {
166+
case 'close':
167+
controls.push(
168+
<ActionTooltip title="Close" key="close">
169+
<Button view="flat" onClick={onCloseDrawer}>
170+
<Icon data={Xmark} size={16} />
171+
</Button>
172+
</ActionTooltip>,
173+
);
174+
break;
175+
case 'copyLink':
176+
controls.push(<CopyLinkButton text={control.link} key="copyLink" />);
177+
break;
178+
case 'custom':
179+
controls.push(
180+
<React.Fragment key={control.key}>{control.node}</React.Fragment>,
181+
);
182+
break;
183+
}
184+
}
185+
186+
return (
187+
<Flex justifyContent="space-between" className={headerClassName}>
188+
{title}
189+
<Flex className={b('controls')}>{controls}</Flex>
190+
</Flex>
191+
);
192+
};
193+
147194
return (
148195
<React.Fragment>
149196
{children}
@@ -158,6 +205,7 @@ export const DrawerWrapper = ({
158205
detectClickOutside={detectClickOutside}
159206
isPercentageWidth={isPercentageWidth}
160207
>
208+
{renderDrawerHeader()}
161209
{renderDrawerContent()}
162210
</DrawerPaneContentWrapper>
163211
</React.Fragment>

0 commit comments

Comments
 (0)