Skip to content

Commit dba57ab

Browse files
authored
Merge 7821fec into e7d4a50
2 parents e7d4a50 + 7821fec commit dba57ab

File tree

14 files changed

+341
-25
lines changed

14 files changed

+341
-25
lines changed

package-lock.json

Lines changed: 10 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
"copy-to-clipboard": "^3.3.3",
3939
"crc-32": "^1.2.2",
4040
"history": "^4.10.1",
41+
"hotkeys-js": "^3.13.9",
4142
"lodash": "^4.17.21",
4243
"monaco-editor": "^0.52.2",
4344
"numeral": "^2.0.6",

src/containers/AsideNavigation/AsideNavigation.scss

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,9 @@
1919
width: 300px;
2020
padding: 10px;
2121
}
22+
23+
&__hotkeys-panel-title {
24+
display: flex;
25+
gap: var(--g-spacing-2);
26+
}
2227
}

src/containers/AsideNavigation/AsideNavigation.tsx

Lines changed: 83 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,18 @@ import React from 'react';
22

33
import {CircleQuestion, Gear, Person} from '@gravity-ui/icons';
44
import type {MenuItem} from '@gravity-ui/navigation';
5-
import {AsideHeader, FooterItem} from '@gravity-ui/navigation';
5+
import {AsideHeader, FooterItem, HotkeysPanel} from '@gravity-ui/navigation';
6+
import {Hotkey} from '@gravity-ui/uikit';
67
import type {IconData} from '@gravity-ui/uikit';
8+
import hotkeys from 'hotkeys-js';
79
import {useHistory} from 'react-router-dom';
810

9-
import {settingsManager} from '../../services/settings';
1011
import {cn} from '../../utils/cn';
11-
import {ASIDE_HEADER_COMPACT_KEY, LANGUAGE_KEY} from '../../utils/constants';
12+
import {ASIDE_HEADER_COMPACT_KEY} from '../../utils/constants';
1213
import {useSetting} from '../../utils/hooks';
1314

15+
import {InformationPopup} from './InformationPopup';
16+
import {HOTKEYS, SHORTCUTS_HOTKEY} from './constants';
1417
import i18n from './i18n';
1518

1619
import userSecret from '../../assets/icons/user-secret.svg';
@@ -62,26 +65,60 @@ export interface AsideNavigationProps {
6265

6366
enum Panel {
6467
UserSettings = 'UserSettings',
65-
}
66-
67-
function getDocumentationLink() {
68-
// Use saved language from settings if it's present, otherwise use browser language
69-
const lang = settingsManager.readUserSettingsValue(LANGUAGE_KEY, navigator.language);
70-
71-
if (lang === 'ru') {
72-
return 'https://ydb.tech/docs/ru/';
73-
}
74-
75-
return 'https://ydb.tech/docs/en/';
68+
Information = 'Information',
69+
Hotkeys = 'Hotkeys',
7670
}
7771

7872
export function AsideNavigation(props: AsideNavigationProps) {
7973
const history = useHistory();
8074

8175
const [visiblePanel, setVisiblePanel] = React.useState<Panel>();
82-
76+
const [informationPopupVisible, setInformationPopupVisible] = React.useState(false);
8377
const [compact, setIsCompact] = useSetting<boolean>(ASIDE_HEADER_COMPACT_KEY);
8478

79+
const toggleInformationPopup = React.useCallback(
80+
() => setInformationPopupVisible(!informationPopupVisible),
81+
[informationPopupVisible],
82+
);
83+
84+
const closeInformationPopup = React.useCallback(() => setInformationPopupVisible(false), []);
85+
86+
const openHotkeysPanel = React.useCallback(() => {
87+
closeInformationPopup();
88+
setVisiblePanel(Panel.Hotkeys);
89+
}, [closeInformationPopup]);
90+
91+
const closePanel = React.useCallback(() => {
92+
setVisiblePanel(undefined);
93+
}, []);
94+
95+
const renderInformationPopup = () => {
96+
return <InformationPopup onKeyboardShortcutsClick={openHotkeysPanel} />;
97+
};
98+
99+
React.useEffect(() => {
100+
// Register hotkey for keyboard shortcuts
101+
hotkeys(SHORTCUTS_HOTKEY, (event) => {
102+
event.preventDefault();
103+
setVisiblePanel(Panel.Hotkeys);
104+
});
105+
106+
// Add listener for custom event from Monaco editor
107+
const handleOpenKeyboardShortcutsPanel = () => {
108+
setVisiblePanel(Panel.Hotkeys);
109+
};
110+
111+
window.addEventListener('openKeyboardShortcutsPanel', handleOpenKeyboardShortcutsPanel);
112+
113+
return () => {
114+
hotkeys.unbind(SHORTCUTS_HOTKEY);
115+
window.removeEventListener(
116+
'openKeyboardShortcutsPanel',
117+
handleOpenKeyboardShortcutsPanel,
118+
);
119+
};
120+
}, []);
121+
85122
return (
86123
<React.Fragment>
87124
<AsideHeader
@@ -100,13 +137,16 @@ export function AsideNavigation(props: AsideNavigationProps) {
100137
<FooterItem
101138
compact={compact}
102139
item={{
103-
id: 'documentation',
104-
title: i18n('navigation-item.documentation'),
140+
id: 'information',
141+
title: i18n('navigation-item.information'),
105142
icon: CircleQuestion,
106-
onItemClick: () => {
107-
window.open(getDocumentationLink(), '_blank', 'noreferrer');
108-
},
143+
current: informationPopupVisible,
144+
onItemClick: toggleInformationPopup,
109145
}}
146+
enableTooltip={!informationPopupVisible}
147+
popupVisible={informationPopupVisible}
148+
onClosePopup={closeInformationPopup}
149+
renderPopupContent={renderInformationPopup}
110150
/>
111151

112152
<FooterItem
@@ -137,10 +177,30 @@ export function AsideNavigation(props: AsideNavigationProps) {
137177
visible: visiblePanel === Panel.UserSettings,
138178
content: props.settings,
139179
},
180+
{
181+
id: 'information',
182+
visible: visiblePanel === Panel.Information,
183+
},
184+
{
185+
id: 'hotkeys',
186+
visible: visiblePanel === Panel.Hotkeys,
187+
content: (
188+
<HotkeysPanel
189+
visible={visiblePanel === Panel.Hotkeys}
190+
hotkeys={HOTKEYS}
191+
className={b('hotkeys-panel')}
192+
title={
193+
<div className={b('hotkeys-panel-title')}>
194+
{i18n('help-center.footer.shortcuts')}
195+
<Hotkey value={SHORTCUTS_HOTKEY} />
196+
</div>
197+
}
198+
onClose={closePanel}
199+
/>
200+
),
201+
},
140202
]}
141-
onClosePanel={() => {
142-
setVisiblePanel(undefined);
143-
}}
203+
onClosePanel={closePanel}
144204
/>
145205
</React.Fragment>
146206
);
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
@import '../../../styles/mixins.scss';
2+
3+
:root {
4+
--information-popup-padding: 16px;
5+
--information-popup-header-padding: 16px;
6+
}
7+
8+
.information-popup {
9+
&__content {
10+
position: relative;
11+
display: flex;
12+
flex-direction: column;
13+
box-sizing: border-box;
14+
width: 280px;
15+
padding: var(--information-popup-header-padding) 0 0 0;
16+
}
17+
18+
&__docs,
19+
&__footer {
20+
display: flex;
21+
flex-direction: column;
22+
flex-shrink: 0;
23+
}
24+
25+
&__docs {
26+
padding-bottom: 8px;
27+
}
28+
29+
&__footer {
30+
position: relative;
31+
padding: 12px 0 8px;
32+
border-top: 1px solid var(--g-color-line-generic);
33+
background-color: var(--g-color-base-generic);
34+
}
35+
36+
&__title {
37+
flex-shrink: 0;
38+
margin-bottom: 4px;
39+
padding: 4px var(--information-popup-padding);
40+
}
41+
42+
&__docs-list-wrap {
43+
display: flex;
44+
flex-direction: column;
45+
flex-shrink: 0;
46+
margin-bottom: 12px;
47+
48+
&:last-child {
49+
margin-bottom: 0;
50+
}
51+
}
52+
53+
&__docs-link,
54+
&__shortcuts-item {
55+
display: flex;
56+
flex-grow: 1;
57+
align-items: center;
58+
box-sizing: border-box;
59+
width: 100%;
60+
height: 100%;
61+
cursor: pointer;
62+
padding: 8px var(--information-popup-padding);
63+
line-height: var(--g-text-body-1-line-height);
64+
65+
&:hover {
66+
background-color: var(--g-color-base-simple-hover);
67+
}
68+
}
69+
70+
&__shortcuts-item {
71+
justify-content: space-between;
72+
}
73+
74+
&__docs-link {
75+
&,
76+
&:hover,
77+
&:active,
78+
&:visited,
79+
&:focus {
80+
text-decoration: none;
81+
color: inherit;
82+
outline: none;
83+
}
84+
}
85+
86+
&__item-icon-wrap {
87+
width: 16px;
88+
height: 16px;
89+
margin-right: 10px;
90+
}
91+
92+
&__shortcuts-content {
93+
display: flex;
94+
align-items: center;
95+
}
96+
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import {Keyboard} from '@gravity-ui/icons';
2+
import {Flex, Hotkey, Icon, Link, List, Text} from '@gravity-ui/uikit';
3+
4+
import {settingsManager} from '../../../services/settings';
5+
import {cn} from '../../../utils/cn';
6+
import {LANGUAGE_KEY} from '../../../utils/constants';
7+
import {SHORTCUTS_HOTKEY} from '../constants';
8+
import i18n from '../i18n';
9+
10+
import './InformationPopup.scss';
11+
12+
const b = cn('information-popup');
13+
14+
export interface InformationPopupProps {
15+
/** The keyboard shortcuts action handler */
16+
onKeyboardShortcutsClick?: () => void;
17+
}
18+
19+
export function InformationPopup({onKeyboardShortcutsClick}: InformationPopupProps) {
20+
// Get documentation link based on language settings
21+
const getDocumentationLink = () => {
22+
const lang = settingsManager.readUserSettingsValue(LANGUAGE_KEY, navigator.language);
23+
return lang === 'ru' ? 'https://ydb.tech/docs/ru/' : 'https://ydb.tech/docs/en/';
24+
};
25+
26+
return (
27+
<div className={b('content', {})}>
28+
{/* Documentation section */}
29+
<div className={b('docs')}>
30+
<Text variant="subheader-3" color="primary" className={b('title')}>
31+
Documentation
32+
</Text>
33+
<div className={b('docs-list-wrap')}>
34+
<List
35+
items={[
36+
{
37+
text: i18n('help-center.item.documentation'),
38+
url: getDocumentationLink(),
39+
},
40+
]}
41+
filterable={false}
42+
virtualized={false}
43+
renderItem={({text, url}) => (
44+
<Link
45+
className={b('docs-link')}
46+
rel="noopener"
47+
target="_blank"
48+
href={url}
49+
title={typeof text === 'string' ? text : undefined}
50+
>
51+
{text}
52+
</Link>
53+
)}
54+
itemClassName={b('item')}
55+
/>
56+
</div>
57+
</div>
58+
59+
<div className={b('footer')}>
60+
<Flex
61+
justifyContent="space-between"
62+
className={b('shortcuts-item')}
63+
onClick={onKeyboardShortcutsClick}
64+
>
65+
<Flex alignItems="center">
66+
<div className={b('item-icon-wrap')}>
67+
<Icon data={Keyboard} />
68+
</div>
69+
{i18n('help-center.footer.shortcuts')}
70+
</Flex>
71+
<Hotkey value={SHORTCUTS_HOTKEY} />
72+
</Flex>
73+
</div>
74+
</div>
75+
);
76+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export {InformationPopup} from './InformationPopup';
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import type React from 'react';
2+
3+
export interface FooterItem {
4+
id: string;
5+
text: string;
6+
url?: string;
7+
rightContent?: React.ReactNode;
8+
onClick?: () => void;
9+
icon?: React.ReactNode;
10+
disableClickHandler?: boolean;
11+
}
12+
13+
export type FooterItemsArray = FooterItem[];

0 commit comments

Comments
 (0)