Skip to content

Commit 0ec2f98

Browse files
authored
chore: refrigerator as component (#2225)
1 parent 5727c21 commit 0ec2f98

File tree

12 files changed

+310
-13
lines changed

12 files changed

+310
-13
lines changed

src/components/Drawer/Drawer.scss

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
.ydb-drawer {
2+
&__drawer-container {
3+
position: relative;
4+
5+
overflow: hidden;
6+
7+
height: 100%;
8+
}
9+
10+
&__item {
11+
z-index: 4;
12+
13+
height: 100%;
14+
}
15+
}

src/components/Drawer/Drawer.tsx

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
import React from 'react';
2+
3+
import {DrawerItem, Drawer as GravityDrawer} from '@gravity-ui/navigation';
4+
5+
import {cn} from '../../utils/cn';
6+
import {isNumeric} from '../../utils/utils';
7+
8+
import {useDrawerContext} from './DrawerContext';
9+
10+
const DEFAULT_DRAWER_WIDTH_PERCENTS = 60;
11+
const DEFAULT_DRAWER_WIDTH = 600;
12+
const DRAWER_WIDTH_KEY = 'drawer-width';
13+
const b = cn('ydb-drawer');
14+
15+
import './Drawer.scss';
16+
17+
interface DrawerPaneContentWrapperProps {
18+
isVisible: boolean;
19+
onClose: () => void;
20+
children: React.ReactNode;
21+
drawerId?: string;
22+
storageKey?: string;
23+
direction?: 'left' | 'right';
24+
className?: string;
25+
detectClickOutside?: boolean;
26+
defaultWidth?: number;
27+
isPercentageWidth?: boolean;
28+
}
29+
30+
const DrawerPaneContentWrapper = ({
31+
isVisible,
32+
onClose,
33+
children,
34+
drawerId = 'drawer',
35+
storageKey = DRAWER_WIDTH_KEY,
36+
defaultWidth,
37+
direction = 'right',
38+
className,
39+
detectClickOutside = false,
40+
isPercentageWidth,
41+
}: DrawerPaneContentWrapperProps) => {
42+
const [drawerWidth, setDrawerWidth] = React.useState(() => {
43+
const savedWidth = localStorage.getItem(storageKey);
44+
return isNumeric(savedWidth) ? Number(savedWidth) : defaultWidth;
45+
});
46+
47+
const drawerRef = React.useRef<HTMLDivElement>(null);
48+
const {containerWidth} = useDrawerContext();
49+
// Calculate drawer width based on container width percentage if specified
50+
const calculatedWidth = React.useMemo(() => {
51+
if (isPercentageWidth && containerWidth > 0) {
52+
return Math.round(
53+
(containerWidth * (drawerWidth || DEFAULT_DRAWER_WIDTH_PERCENTS)) / 100,
54+
);
55+
}
56+
return drawerWidth || DEFAULT_DRAWER_WIDTH;
57+
}, [containerWidth, isPercentageWidth, drawerWidth]);
58+
59+
React.useEffect(() => {
60+
if (!detectClickOutside) {
61+
return undefined;
62+
}
63+
64+
const handleClickOutside = (event: MouseEvent) => {
65+
if (
66+
isVisible &&
67+
drawerRef.current &&
68+
!drawerRef.current.contains(event.target as Node)
69+
) {
70+
onClose();
71+
}
72+
};
73+
74+
document.addEventListener('click', handleClickOutside);
75+
return () => {
76+
document.removeEventListener('click', handleClickOutside);
77+
};
78+
}, [isVisible, onClose, detectClickOutside]);
79+
80+
const handleResizeDrawer = (width: number) => {
81+
if (isPercentageWidth && containerWidth > 0) {
82+
const percentageWidth = Math.round((width / containerWidth) * 100);
83+
setDrawerWidth(percentageWidth);
84+
localStorage.setItem(storageKey, percentageWidth.toString());
85+
} else {
86+
setDrawerWidth(width);
87+
localStorage.setItem(storageKey, width.toString());
88+
}
89+
};
90+
91+
return (
92+
<GravityDrawer
93+
onEscape={onClose}
94+
onVeilClick={onClose}
95+
hideVeil
96+
className={b('container', className)}
97+
>
98+
<DrawerItem
99+
id={drawerId}
100+
visible={isVisible}
101+
resizable
102+
maxResizeWidth={containerWidth}
103+
width={isPercentageWidth ? calculatedWidth : drawerWidth}
104+
onResize={handleResizeDrawer}
105+
direction={direction}
106+
className={b('item')}
107+
ref={detectClickOutside ? drawerRef : undefined}
108+
>
109+
{children}
110+
</DrawerItem>
111+
</GravityDrawer>
112+
);
113+
};
114+
115+
interface DrawerPaneProps {
116+
children: React.ReactNode;
117+
renderDrawerContent: () => React.ReactNode;
118+
isDrawerVisible: boolean;
119+
onCloseDrawer: () => void;
120+
drawerId?: string;
121+
storageKey?: string;
122+
defaultWidth?: number;
123+
direction?: 'left' | 'right';
124+
className?: string;
125+
detectClickOutside?: boolean;
126+
isPercentageWidth?: boolean;
127+
}
128+
129+
export const DrawerWrapper = ({
130+
children,
131+
renderDrawerContent,
132+
isDrawerVisible,
133+
onCloseDrawer,
134+
drawerId,
135+
storageKey,
136+
defaultWidth,
137+
direction,
138+
className,
139+
detectClickOutside,
140+
isPercentageWidth,
141+
}: DrawerPaneProps) => {
142+
React.useEffect(() => {
143+
return () => {
144+
onCloseDrawer();
145+
};
146+
}, [onCloseDrawer]);
147+
return (
148+
<React.Fragment>
149+
{children}
150+
<DrawerPaneContentWrapper
151+
isVisible={isDrawerVisible}
152+
onClose={onCloseDrawer}
153+
drawerId={drawerId}
154+
storageKey={storageKey}
155+
defaultWidth={defaultWidth}
156+
direction={direction}
157+
className={className}
158+
detectClickOutside={detectClickOutside}
159+
isPercentageWidth={isPercentageWidth}
160+
>
161+
{renderDrawerContent()}
162+
</DrawerPaneContentWrapper>
163+
</React.Fragment>
164+
);
165+
};
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import React from 'react';
2+
3+
import {cn} from '../../utils/cn';
4+
5+
import './Drawer.scss';
6+
7+
const b = cn('ydb-drawer');
8+
9+
export interface DrawerContextType {
10+
containerWidth: number;
11+
setContainerWidth: React.Dispatch<React.SetStateAction<number>>;
12+
}
13+
14+
const DrawerContext = React.createContext<DrawerContextType>({
15+
containerWidth: 0,
16+
setContainerWidth: () => {},
17+
});
18+
19+
interface DrawerContextProviderProps {
20+
children: React.ReactNode;
21+
className?: string;
22+
}
23+
24+
export const DrawerContextProvider = ({children, className}: DrawerContextProviderProps) => {
25+
const [containerWidth, setContainerWidth] = React.useState(0);
26+
const containerRef = React.useRef<HTMLDivElement>(null);
27+
28+
React.useEffect(() => {
29+
if (!containerRef.current) {
30+
return undefined;
31+
}
32+
33+
const updateWidth = () => {
34+
if (containerRef.current) {
35+
setContainerWidth(containerRef.current.clientWidth);
36+
}
37+
};
38+
39+
// Set initial width
40+
updateWidth();
41+
42+
// Update width on resize
43+
const resizeObserver = new ResizeObserver(updateWidth);
44+
resizeObserver.observe(containerRef.current);
45+
46+
return () => {
47+
if (containerRef.current) {
48+
resizeObserver.disconnect();
49+
}
50+
};
51+
}, []);
52+
53+
// Memoize the context value to prevent unnecessary re-renders
54+
const value = React.useMemo(
55+
() => ({
56+
containerWidth,
57+
setContainerWidth,
58+
}),
59+
[containerWidth],
60+
);
61+
62+
return (
63+
<DrawerContext.Provider value={value}>
64+
<div ref={containerRef} className={b('drawer-container', className)}>
65+
{children}
66+
</div>
67+
</DrawerContext.Provider>
68+
);
69+
};
70+
71+
export const useDrawerContext = (): DrawerContextType => {
72+
const context = React.useContext(DrawerContext);
73+
74+
if (context === undefined) {
75+
throw Error('useDrawerContext must be used within a DrawerContextProvider');
76+
}
77+
78+
return context;
79+
};

src/components/Drawer/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export {DrawerWrapper} from './Drawer';
2+
export {useDrawerContext} from './DrawerContext';
3+
export type {DrawerContextType} from './DrawerContext';

src/components/Search/Search.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ interface SearchProps {
1515
className?: string;
1616
debounce?: number;
1717
placeholder?: string;
18+
inputRef?: React.RefObject<HTMLInputElement>;
1819
}
1920

2021
export const Search = ({
@@ -24,6 +25,7 @@ export const Search = ({
2425
className,
2526
debounce = 200,
2627
placeholder,
28+
inputRef,
2729
}: SearchProps) => {
2830
const [searchValue, setSearchValue] = React.useState<string>(value);
2931

@@ -52,6 +54,7 @@ export const Search = ({
5254
<TextInput
5355
hasClear
5456
autoFocus
57+
controlRef={inputRef}
5558
style={{width}}
5659
className={b(null, className)}
5760
placeholder={placeholder}

src/components/SyntaxHighlighter/YDBSyntaxHighlighter.tsx

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,20 @@ export function YDBSyntaxHighlighter({
8888
return null;
8989
};
9090

91+
let paddingStyles = {};
92+
93+
if (
94+
withClipboardButton &&
95+
typeof withClipboardButton === 'object' &&
96+
withClipboardButton.alwaysVisible
97+
) {
98+
if (withClipboardButton.withLabel) {
99+
paddingStyles = {paddingRight: 80};
100+
} else {
101+
paddingStyles = {paddingRight: 40};
102+
}
103+
}
104+
91105
return (
92106
<div className={b(null, className)}>
93107
{renderCopyButton()}
@@ -96,7 +110,7 @@ export function YDBSyntaxHighlighter({
96110
key={highlighterKey}
97111
language={language}
98112
style={style}
99-
customStyle={{height: '100%'}}
113+
customStyle={{height: '100%', ...paddingStyles}}
100114
>
101115
{text}
102116
</ReactSyntaxHighlighter>

src/containers/Tenant/Diagnostics/Diagnostics.scss

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
@use '../../../styles/mixins.scss';
22

33
.kv-tenant-diagnostics {
4+
--diagnostics-margin-top: var(--g-spacing-4);
45
display: flex;
56
overflow: hidden;
67
flex-direction: column;
78

89
height: 100%;
910

1011
&__header-wrapper {
11-
padding: 0 20px 16px;
12+
padding: 0 var(--g-spacing-5);
1213

1314
background-color: var(--g-color-base-background);
1415
}
@@ -39,7 +40,12 @@
3940
flex-grow: 1;
4041

4142
width: 100%;
42-
padding: 0 20px;
43+
44+
// Margin is not counted in height
45+
// thats why we need to subtract it.
46+
height: calc(100% - var(--diagnostics-margin-top));
47+
margin-top: var(--diagnostics-margin-top);
48+
padding: 0 var(--g-spacing-5);
4349

4450
.ydb-table-with-controls-layout {
4551
&__controls {

src/containers/Tenant/Diagnostics/Diagnostics.tsx

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {Link} from 'react-router-dom';
66
import {StringParam, useQueryParams} from 'use-query-params';
77

88
import {AutoRefreshControl} from '../../../components/AutoRefreshControl/AutoRefreshControl';
9+
import {DrawerContextProvider} from '../../../components/Drawer/DrawerContext';
910
import {useFeatureFlagsAvailable} from '../../../store/reducers/capabilities/hooks';
1011
import {TENANT_DIAGNOSTICS_TABS_IDS} from '../../../store/reducers/tenant/constants';
1112
import {setDiagnosticsTab} from '../../../store/reducers/tenant/tenant';
@@ -194,9 +195,11 @@ function Diagnostics(props: DiagnosticsProps) {
194195
</Helmet>
195196
) : null}
196197
{renderTabs()}
197-
<div className={b('page-wrapper')} ref={containerRef}>
198-
{renderTabContent()}
199-
</div>
198+
<DrawerContextProvider>
199+
<div className={b('page-wrapper')} ref={containerRef}>
200+
{renderTabContent()}
201+
</div>
202+
</DrawerContextProvider>
200203
</div>
201204
);
202205
}

src/containers/Tenant/Diagnostics/TopQueries/columns/columns.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,13 @@ export function getTenantOverviewTopQueriesColumns() {
152152
return [queryHashColumn, oneLineQueryTextColumn, cpuTimeUsColumn];
153153
}
154154
export function getRunningQueriesColumns() {
155-
const columns = [userSIDColumn, queryStartColumn, queryTextColumn, applicationColumn];
155+
const columns = [
156+
queryHashColumn,
157+
userSIDColumn,
158+
queryStartColumn,
159+
queryTextColumn,
160+
applicationColumn,
161+
];
156162

157163
return columns.map((column) => ({
158164
...column,

src/containers/Tenant/Diagnostics/TopQueries/columns/constants.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ export const DEFAULT_TOP_QUERIES_COLUMNS: QueriesColumnId[] = [
3737
export const REQUIRED_TOP_QUERIES_COLUMNS: QueriesColumnId[] = ['CPUTime', 'QueryText'];
3838

3939
export const DEFAULT_RUNNING_QUERIES_COLUMNS: QueriesColumnId[] = [
40+
'QueryHash',
4041
'UserSID',
4142
'QueryStartAt',
4243
'QueryText',

0 commit comments

Comments
 (0)