Skip to content

Commit 2a6c613

Browse files
authored
feat: get rid of ReduxTooltip (#3192)
1 parent 83e83fe commit 2a6c613

File tree

28 files changed

+959
-705
lines changed

28 files changed

+959
-705
lines changed
Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import React from 'react';
22

3-
import {showTooltip} from '../../../store/reducers/tooltip';
4-
import {useTypedDispatch} from '../../../utils/hooks';
3+
import {Popup} from '@gravity-ui/uikit';
4+
55
import {b} from '../QueryResultTable';
66

77
interface CellProps {
@@ -12,14 +12,31 @@ interface CellProps {
1212
export const Cell = React.memo(function Cell(props: CellProps) {
1313
const {className, value} = props;
1414

15-
const dispatch = useTypedDispatch();
15+
const [open, setOpen] = React.useState(false);
16+
const anchorRef = React.useRef<HTMLSpanElement | null>(null);
17+
18+
const handleToggle = React.useCallback(() => {
19+
setOpen((prevOpen) => !prevOpen);
20+
}, []);
21+
22+
const handleClose = React.useCallback(() => {
23+
setOpen(false);
24+
}, []);
1625

1726
return (
18-
<span
19-
className={b('cell', className)}
20-
onClick={(e) => dispatch(showTooltip(e.target, value, 'cell'))}
21-
>
22-
{value}
23-
</span>
27+
<React.Fragment>
28+
<Popup
29+
open={open}
30+
hasArrow
31+
placement={['top', 'bottom']}
32+
anchorRef={anchorRef}
33+
onOutsideClick={handleClose}
34+
>
35+
<div className={b('cell-popup')}>{value}</div>
36+
</Popup>
37+
<span ref={anchorRef} className={b('cell', className)} onClick={handleToggle}>
38+
{value}
39+
</span>
40+
</React.Fragment>
2441
);
2542
});

src/components/QueryResultTable/QueryResultTable.scss

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,13 @@
66
@include mixins.cell-container;
77
}
88

9+
&__cell-popup {
10+
max-width: 300px;
11+
padding: 10px;
12+
13+
word-break: break-word;
14+
}
15+
916
&__message {
1017
padding: 15px 10px;
1118
}

src/components/QueryResultTable/QueryResultTable.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ export const b = cn('ydb-query-result-table');
2828

2929
const WIDTH_PREDICTION_ROWS_COUNT = 100;
3030

31-
const prepareTypedColumns = (columns: ColumnType[], data?: KeyValueRow[]) => {
31+
const prepareTypedColumns = (columns: ColumnType[], data: KeyValueRow[] | undefined) => {
3232
if (!columns.length) {
3333
return [];
3434
}
@@ -49,7 +49,7 @@ const prepareTypedColumns = (columns: ColumnType[], data?: KeyValueRow[]) => {
4949
});
5050
};
5151

52-
const prepareGenericColumns = (data?: KeyValueRow[]) => {
52+
const prepareGenericColumns = (data: KeyValueRow[] | undefined) => {
5353
if (!data?.length) {
5454
return [];
5555
}
@@ -85,7 +85,7 @@ export const QueryResultTable = (props: QueryResultTableProps) => {
8585

8686
const preparedColumns = React.useMemo(() => {
8787
return columns ? prepareTypedColumns(columns, data) : prepareGenericColumns(data);
88-
}, [data, columns]);
88+
}, [columns, data]);
8989

9090
const settings = React.useMemo(() => {
9191
return {

src/containers/App/App.tsx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import {Helmet} from 'react-helmet-async';
77
import {componentsRegistry} from '../../components/ComponentsProvider/componentsRegistry';
88
import {FullscreenProvider} from '../../components/Fullscreen/FullscreenContext';
99
import {useTypedSelector} from '../../utils/hooks';
10-
import ReduxTooltip from '../ReduxTooltip/ReduxTooltip';
1110
import type {YDBEmbeddedUISettings} from '../UserSettings/settings';
1211

1312
import {useAppTitle} from './AppTitleContext';
@@ -34,7 +33,6 @@ function App({store, history, children, userSettings, appTitle = defaultAppTitle
3433
<Providers store={store} history={history} appTitle={appTitle}>
3534
<AppContent userSettings={userSettings}>{children}</AppContent>
3635
{ChatPanel && <ChatPanel />}
37-
<ReduxTooltip />
3836
</Providers>
3937
);
4038
}

src/containers/Heatmap/Heatmap.scss

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@
5151
}
5252

5353
&__items {
54+
position: relative;
55+
5456
overflow: auto;
5557
}
5658
&__canvas-container {
@@ -59,6 +61,19 @@
5961
cursor: pointer;
6062
}
6163

64+
&__tooltip-anchor {
65+
position: absolute;
66+
67+
width: 1px;
68+
height: 1px;
69+
70+
pointer-events: none;
71+
}
72+
73+
&__tooltip {
74+
padding: 10px;
75+
}
76+
6277
&__filters {
6378
display: flex;
6479
align-items: center;

src/containers/Heatmap/Heatmap.tsx

Lines changed: 71 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
import React from 'react';
22

3-
import {Checkbox, Select} from '@gravity-ui/uikit';
3+
import {Checkbox, Popup, Select} from '@gravity-ui/uikit';
44

55
import {ResponseError} from '../../components/Errors/ResponseError';
66
import {Loader} from '../../components/Loader';
7+
import {TabletTooltipContent} from '../../components/TooltipsContent';
78
import {heatmapApi, setHeatmapOptions} from '../../store/reducers/heatmap';
8-
import {hideTooltip, showTooltip} from '../../store/reducers/tooltip';
9-
import type {IHeatmapMetricValue} from '../../types/store/heatmap';
9+
import type {IHeatmapMetricValue, IHeatmapTabletData} from '../../types/store/heatmap';
1010
import {cn} from '../../utils/cn';
1111
import {EMPTY_DATA_PLACEHOLDER} from '../../utils/constants';
1212
import {formatNumber} from '../../utils/dataFormatters/dataFormatters';
@@ -32,6 +32,14 @@ export const Heatmap = ({path, database, databaseFullPath}: HeatmapProps) => {
3232

3333
const itemsContainer = React.useRef<HTMLDivElement | null>(null);
3434

35+
const [tabletTooltip, setTabletTooltip] = React.useState<{
36+
tablet: IHeatmapTabletData;
37+
position: {left: number; top: number};
38+
} | null>(null);
39+
const [tabletTooltipAnchorElement, setTabletTooltipAnchorElement] =
40+
React.useState<HTMLDivElement | null>(null);
41+
const isTabletTooltipHoveredRef = React.useRef(false);
42+
3543
const [autoRefreshInterval] = useAutoRefreshInterval();
3644

3745
const {currentData, isFetching, error} = heatmapApi.useGetHeatmapTabletsInfoQuery(
@@ -44,13 +52,35 @@ export const Heatmap = ({path, database, databaseFullPath}: HeatmapProps) => {
4452
const {tablets = [], metrics} = currentData || {};
4553
const {sort, heatmap, currentMetric} = useTypedSelector((state) => state.heatmap);
4654

47-
const onShowTooltip = (...args: Parameters<typeof showTooltip>) => {
48-
dispatch(showTooltip(...args));
49-
};
55+
const handleShowTabletTooltip = React.useCallback(
56+
(tablet: IHeatmapTabletData, position: {left: number; top: number}) => {
57+
setTabletTooltip({tablet, position});
58+
},
59+
[],
60+
);
5061

51-
const onHideTooltip = () => {
52-
dispatch(hideTooltip());
53-
};
62+
const handleHideTabletTooltip = React.useCallback(() => {
63+
setTabletTooltip(null);
64+
}, []);
65+
66+
const handleRequestHideTabletTooltip = React.useCallback(() => {
67+
setTabletTooltip((prev) => {
68+
if (!prev || isTabletTooltipHoveredRef.current) {
69+
return prev;
70+
}
71+
72+
return null;
73+
});
74+
}, []);
75+
76+
const handleTooltipMouseEnter = React.useCallback(() => {
77+
isTabletTooltipHoveredRef.current = true;
78+
}, []);
79+
80+
const handleTooltipMouseLeave = React.useCallback(() => {
81+
isTabletTooltipHoveredRef.current = false;
82+
handleHideTabletTooltip();
83+
}, [handleHideTabletTooltip]);
5484

5585
const handleMetricChange = (value: string[]) => {
5686
dispatch(
@@ -76,14 +106,7 @@ export const Heatmap = ({path, database, databaseFullPath}: HeatmapProps) => {
76106
};
77107

78108
const renderHistogram = () => {
79-
return (
80-
<Histogram
81-
tablets={tablets}
82-
currentMetric={currentMetric}
83-
showTooltip={onShowTooltip}
84-
hideTooltip={onHideTooltip}
85-
/>
86-
);
109+
return <Histogram tablets={tablets} currentMetric={currentMetric} />;
87110
};
88111

89112
const renderHeatmapCanvas = () => {
@@ -108,18 +131,30 @@ export const Heatmap = ({path, database, databaseFullPath}: HeatmapProps) => {
108131

109132
return (
110133
<div ref={itemsContainer} className={b('items')}>
134+
{tabletTooltip ? (
135+
<div
136+
key={`${tabletTooltip.position.left}-${tabletTooltip.position.top}`}
137+
ref={setTabletTooltipAnchorElement}
138+
className={b('tooltip-anchor')}
139+
style={{
140+
left: tabletTooltip.position.left,
141+
top: tabletTooltip.position.top,
142+
}}
143+
/>
144+
) : null}
111145
<HeatmapCanvas
112146
tablets={sortedTablets}
113147
parentRef={itemsContainer}
114-
showTooltip={onShowTooltip}
115-
hideTooltip={onHideTooltip}
148+
onShowTabletTooltip={handleShowTabletTooltip}
149+
onHideTabletTooltip={handleRequestHideTabletTooltip}
116150
/>
117151
</div>
118152
);
119153
};
120154

121155
const renderContent = () => {
122156
const {min, max} = getCurrentMetricLimits(currentMetric, tablets);
157+
const isTabletTooltipPopupOpen = Boolean(tabletTooltip && tabletTooltipAnchorElement);
123158

124159
let content;
125160
if (!error || currentData) {
@@ -128,6 +163,23 @@ export const Heatmap = ({path, database, databaseFullPath}: HeatmapProps) => {
128163

129164
return (
130165
<div className={b()}>
166+
{isTabletTooltipPopupOpen ? (
167+
<Popup
168+
open
169+
hasArrow
170+
placement={['top', 'bottom', 'left', 'right']}
171+
anchorElement={tabletTooltipAnchorElement}
172+
onOutsideClick={handleHideTabletTooltip}
173+
>
174+
<div
175+
className={b('tooltip')}
176+
onMouseEnter={handleTooltipMouseEnter}
177+
onMouseLeave={handleTooltipMouseLeave}
178+
>
179+
<TabletTooltipContent data={tabletTooltip?.tablet} />
180+
</div>
181+
</Popup>
182+
) : null}
131183
<div className={b('filters')}>
132184
<Select
133185
className={b('heatmap-select')}

0 commit comments

Comments
 (0)