Skip to content

Commit e201a83

Browse files
committed
feat: side panel aka refrigerator for query text in top queries
1 parent 5727c21 commit e201a83

24 files changed

+1066
-140
lines changed

src/assets/icons/cry-cat.svg

Lines changed: 14 additions & 0 deletions
Loading

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/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/containers/Drawer/Drawer.tsx

Lines changed: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
1+
import React from 'react';
2+
3+
import {DrawerItem, Drawer as GravityDrawer} from '@gravity-ui/navigation';
4+
5+
import {cn} from '../../utils/cn';
6+
7+
const DEFAULT_DRAWER_WIDTH_PERCENTS = 60;
8+
const DEFAULT_DRAWER_WIDTH = 600;
9+
const DRAWER_WIDTH_KEY = 'drawer-width';
10+
const b = cn('ydb-drawer');
11+
12+
import './Drawer.scss';
13+
14+
// Create a context for sharing container dimensions
15+
interface DrawerContextType {
16+
containerWidth: number;
17+
setContainerWidth: React.Dispatch<React.SetStateAction<number>>;
18+
}
19+
20+
const DrawerContext = React.createContext<DrawerContextType | undefined>(undefined);
21+
22+
// Custom hook to use the drawer context
23+
const useDrawerContext = () => {
24+
const context = React.useContext(DrawerContext);
25+
if (context === undefined) {
26+
return {containerWidth: 0, setContainerWidth: () => {}};
27+
}
28+
return context;
29+
};
30+
31+
interface ContentWrapperProps {
32+
isVisible: boolean;
33+
onClose: () => void;
34+
children: React.ReactNode;
35+
drawerId?: string;
36+
storageKey?: string;
37+
direction?: 'left' | 'right';
38+
className?: string;
39+
detectClickOutside?: boolean;
40+
defaultWidth?: number;
41+
isPercentageWidth?: boolean;
42+
}
43+
44+
const ContentWrapper = ({
45+
isVisible,
46+
onClose,
47+
children,
48+
drawerId = 'drawer',
49+
storageKey = DRAWER_WIDTH_KEY,
50+
defaultWidth,
51+
direction = 'right',
52+
className,
53+
detectClickOutside = false,
54+
isPercentageWidth,
55+
}: ContentWrapperProps) => {
56+
const [drawerWidth, setDrawerWidth] = React.useState(() => {
57+
const savedWidth = localStorage.getItem(storageKey);
58+
return savedWidth ? Number(savedWidth) : defaultWidth;
59+
});
60+
61+
const drawerRef = React.useRef<HTMLDivElement>(null);
62+
const {containerWidth} = useDrawerContext();
63+
// Calculate drawer width based on container width percentage if specified
64+
const calculatedWidth = React.useMemo(() => {
65+
if (isPercentageWidth && containerWidth > 0) {
66+
return Math.round(
67+
(containerWidth * (drawerWidth || DEFAULT_DRAWER_WIDTH_PERCENTS)) / 100,
68+
);
69+
}
70+
return drawerWidth || DEFAULT_DRAWER_WIDTH;
71+
}, [containerWidth, isPercentageWidth, drawerWidth]);
72+
73+
React.useEffect(() => {
74+
if (!detectClickOutside) {
75+
return undefined;
76+
}
77+
78+
const handleClickOutside = (event: MouseEvent) => {
79+
if (
80+
isVisible &&
81+
drawerRef.current &&
82+
!drawerRef.current.contains(event.target as Node)
83+
) {
84+
onClose();
85+
}
86+
};
87+
88+
document.addEventListener('click', handleClickOutside);
89+
return () => {
90+
document.removeEventListener('click', handleClickOutside);
91+
};
92+
}, [isVisible, onClose, detectClickOutside]);
93+
94+
const handleResizeDrawer = (width: number) => {
95+
if (isPercentageWidth && containerWidth > 0) {
96+
const percentageWidth = Math.round((width / containerWidth) * 100);
97+
setDrawerWidth(percentageWidth);
98+
localStorage.setItem(storageKey, percentageWidth.toString());
99+
} else {
100+
setDrawerWidth(width);
101+
localStorage.setItem(storageKey, width.toString());
102+
}
103+
};
104+
105+
return (
106+
<GravityDrawer
107+
onEscape={onClose}
108+
onVeilClick={onClose}
109+
hideVeil
110+
className={b('container', className)}
111+
>
112+
<DrawerItem
113+
id={drawerId}
114+
visible={isVisible}
115+
resizable
116+
maxResizeWidth={containerWidth}
117+
width={isPercentageWidth ? calculatedWidth : drawerWidth}
118+
onResize={handleResizeDrawer}
119+
direction={direction}
120+
className={b('item')}
121+
ref={detectClickOutside ? drawerRef : undefined}
122+
>
123+
{children}
124+
</DrawerItem>
125+
</GravityDrawer>
126+
);
127+
};
128+
129+
interface ContainerProps {
130+
children: React.ReactNode;
131+
className?: string;
132+
}
133+
134+
interface ItemWrapperProps {
135+
children: React.ReactNode;
136+
renderDrawerContent: () => React.ReactNode;
137+
isDrawerVisible: boolean;
138+
onCloseDrawer: () => void;
139+
drawerId?: string;
140+
storageKey?: string;
141+
defaultWidth?: number;
142+
direction?: 'left' | 'right';
143+
className?: string;
144+
detectClickOutside?: boolean;
145+
isPercentageWidth?: boolean;
146+
}
147+
148+
export const Drawer = {
149+
Container: ({children, className}: ContainerProps) => {
150+
const [containerWidth, setContainerWidth] = React.useState(0);
151+
const containerRef = React.useRef<HTMLDivElement>(null);
152+
153+
React.useEffect(() => {
154+
if (!containerRef.current) {
155+
return undefined;
156+
}
157+
158+
const updateWidth = () => {
159+
if (containerRef.current) {
160+
setContainerWidth(containerRef.current.clientWidth);
161+
}
162+
};
163+
164+
// Set initial width
165+
updateWidth();
166+
167+
// Update width on resize
168+
const resizeObserver = new ResizeObserver(updateWidth);
169+
resizeObserver.observe(containerRef.current);
170+
171+
return () => {
172+
if (containerRef.current) {
173+
resizeObserver.disconnect();
174+
}
175+
};
176+
}, []);
177+
178+
return (
179+
<DrawerContext.Provider value={{containerWidth, setContainerWidth}}>
180+
<div ref={containerRef} className={b('drawer-container', className)}>
181+
{children}
182+
</div>
183+
</DrawerContext.Provider>
184+
);
185+
},
186+
187+
ItemWrapper: ({
188+
children,
189+
renderDrawerContent,
190+
isDrawerVisible,
191+
onCloseDrawer,
192+
drawerId,
193+
storageKey,
194+
defaultWidth,
195+
direction,
196+
className,
197+
detectClickOutside,
198+
isPercentageWidth,
199+
}: ItemWrapperProps) => {
200+
React.useEffect(() => {
201+
return () => {
202+
onCloseDrawer();
203+
};
204+
}, [onCloseDrawer]);
205+
return (
206+
<React.Fragment>
207+
{children}
208+
<ContentWrapper
209+
isVisible={isDrawerVisible}
210+
onClose={onCloseDrawer}
211+
drawerId={drawerId}
212+
storageKey={storageKey}
213+
defaultWidth={defaultWidth}
214+
direction={direction}
215+
className={className}
216+
detectClickOutside={detectClickOutside}
217+
isPercentageWidth={isPercentageWidth}
218+
>
219+
{renderDrawerContent()}
220+
</ContentWrapper>
221+
</React.Fragment>
222+
);
223+
},
224+
};

src/containers/Tenant/Diagnostics/Diagnostics.scss

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
height: 100%;
99

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

1313
background-color: var(--g-color-base-background);
1414
}
@@ -39,7 +39,8 @@
3939
flex-grow: 1;
4040

4141
width: 100%;
42-
padding: 0 20px;
42+
height: 100%;
43+
margin: var(--g-spacing-4) var(--g-spacing-5) 0 var(--g-spacing-5);
4344

4445
.ydb-table-with-controls-layout {
4546
&__controls {

src/containers/Tenant/Diagnostics/Diagnostics.tsx

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import type {AdditionalNodesProps, AdditionalTenantsProps} from '../../../types/
1313
import type {EPathType} from '../../../types/api/schema';
1414
import {cn} from '../../../utils/cn';
1515
import {useTypedDispatch, useTypedSelector} from '../../../utils/hooks';
16+
import {Drawer} from '../../Drawer/Drawer';
1617
import {Heatmap} from '../../Heatmap';
1718
import {Nodes} from '../../Nodes/Nodes';
1819
import {Operations} from '../../Operations';
@@ -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+
<Drawer.Container>
199+
<div className={b('page-wrapper')} ref={containerRef}>
200+
{renderTabContent()}
201+
</div>
202+
</Drawer.Container>
200203
</div>
201204
);
202205
}

0 commit comments

Comments
 (0)