@@ -2,15 +2,18 @@ import React from 'react';
2
2
3
3
import { CircleQuestion , Gear , Person } from '@gravity-ui/icons' ;
4
4
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' ;
6
7
import type { IconData } from '@gravity-ui/uikit' ;
8
+ import hotkeys from 'hotkeys-js' ;
7
9
import { useHistory } from 'react-router-dom' ;
8
10
9
- import { settingsManager } from '../../services/settings' ;
10
11
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' ;
12
13
import { useSetting } from '../../utils/hooks' ;
13
14
15
+ import { InformationPopup } from './InformationPopup' ;
16
+ import { HOTKEYS , SHORTCUTS_HOTKEY } from './constants' ;
14
17
import i18n from './i18n' ;
15
18
16
19
import userSecret from '../../assets/icons/user-secret.svg' ;
@@ -62,26 +65,86 @@ export interface AsideNavigationProps {
62
65
63
66
enum Panel {
64
67
UserSettings = 'UserSettings' ,
68
+ Information = 'Information' ,
69
+ Hotkeys = 'Hotkeys' ,
65
70
}
66
71
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 ) ;
72
+ /**
73
+ * HotkeysPanelWrapper creates a render cycle separation between mounting and visibility change.
74
+ * This is necessary for smooth animations as HotkeysPanel uses CSSTransition internally.
75
+ *
76
+ * When a component is both mounted and set to visible at once, CSSTransition can't
77
+ * properly sequence its transition classes (panel → panel-active) because it's already active when mounted
78
+ * and counts transition as it has already happened.
79
+ * This wrapper ensures the component mounts first, then sets visible=true in a subsequent render cycle
80
+ * to make transition actually happen.
81
+ */
82
+ function HotkeysPanelWrapper ( {
83
+ visiblePanel,
84
+ closePanel,
85
+ } : {
86
+ visiblePanel ?: Panel ;
87
+ closePanel : ( ) => void ;
88
+ } ) {
89
+ const [ visible , setVisible ] = React . useState ( false ) ;
90
+
91
+ React . useEffect ( ( ) => {
92
+ setVisible ( visiblePanel === Panel . Hotkeys ) ;
93
+ } , [ visiblePanel ] ) ;
70
94
71
- if ( lang === 'ru' ) {
72
- return 'https://ydb.tech/docs/ru/' ;
73
- }
74
-
75
- return 'https://ydb.tech/docs/en/' ;
95
+ return (
96
+ < HotkeysPanel
97
+ visible = { visible }
98
+ hotkeys = { HOTKEYS }
99
+ className = { b ( 'hotkeys-panel' ) }
100
+ title = {
101
+ < div className = { b ( 'hotkeys-panel-title' ) } >
102
+ { i18n ( 'help-center.footer.shortcuts' ) }
103
+ < Hotkey value = { SHORTCUTS_HOTKEY } />
104
+ </ div >
105
+ }
106
+ onClose = { closePanel }
107
+ />
108
+ ) ;
76
109
}
77
110
78
111
export function AsideNavigation ( props : AsideNavigationProps ) {
79
112
const history = useHistory ( ) ;
80
113
81
114
const [ visiblePanel , setVisiblePanel ] = React . useState < Panel > ( ) ;
82
-
115
+ const [ informationPopupVisible , setInformationPopupVisible ] = React . useState ( false ) ;
83
116
const [ compact , setIsCompact ] = useSetting < boolean > ( ASIDE_HEADER_COMPACT_KEY ) ;
84
117
118
+ const toggleInformationPopup = ( ) => setInformationPopupVisible ( ( prev ) => ! prev ) ;
119
+
120
+ const closeInformationPopup = React . useCallback ( ( ) => setInformationPopupVisible ( false ) , [ ] ) ;
121
+
122
+ const openHotkeysPanel = React . useCallback ( ( ) => {
123
+ closeInformationPopup ( ) ;
124
+ setVisiblePanel ( Panel . Hotkeys ) ;
125
+ } , [ closeInformationPopup ] ) ;
126
+
127
+ const closePanel = React . useCallback ( ( ) => {
128
+ setVisiblePanel ( undefined ) ;
129
+ } , [ ] ) ;
130
+
131
+ const renderInformationPopup = ( ) => {
132
+ return < InformationPopup onKeyboardShortcutsClick = { openHotkeysPanel } /> ;
133
+ } ;
134
+
135
+ React . useEffect ( ( ) => {
136
+ // Register hotkey for keyboard shortcuts
137
+ hotkeys ( SHORTCUTS_HOTKEY , openHotkeysPanel ) ;
138
+
139
+ // Add listener for custom event from Monaco editor
140
+ window . addEventListener ( 'openKeyboardShortcutsPanel' , openHotkeysPanel ) ;
141
+
142
+ return ( ) => {
143
+ hotkeys . unbind ( SHORTCUTS_HOTKEY ) ;
144
+ window . removeEventListener ( 'openKeyboardShortcutsPanel' , openHotkeysPanel ) ;
145
+ } ;
146
+ } , [ openHotkeysPanel ] ) ;
147
+
85
148
return (
86
149
< React . Fragment >
87
150
< AsideHeader
@@ -100,13 +163,16 @@ export function AsideNavigation(props: AsideNavigationProps) {
100
163
< FooterItem
101
164
compact = { compact }
102
165
item = { {
103
- id : 'documentation ' ,
104
- title : i18n ( 'navigation-item.documentation ' ) ,
166
+ id : 'information ' ,
167
+ title : i18n ( 'navigation-item.information ' ) ,
105
168
icon : CircleQuestion ,
106
- onItemClick : ( ) => {
107
- window . open ( getDocumentationLink ( ) , '_blank' , 'noreferrer' ) ;
108
- } ,
169
+ current : informationPopupVisible ,
170
+ onItemClick : toggleInformationPopup ,
109
171
} }
172
+ enableTooltip = { ! informationPopupVisible }
173
+ popupVisible = { informationPopupVisible }
174
+ onClosePopup = { closeInformationPopup }
175
+ renderPopupContent = { renderInformationPopup }
110
176
/>
111
177
112
178
< FooterItem
@@ -137,10 +203,23 @@ export function AsideNavigation(props: AsideNavigationProps) {
137
203
visible : visiblePanel === Panel . UserSettings ,
138
204
content : props . settings ,
139
205
} ,
206
+ {
207
+ id : 'information' ,
208
+ visible : visiblePanel === Panel . Information ,
209
+ } ,
210
+ {
211
+ id : 'hotkeys' ,
212
+ visible : visiblePanel === Panel . Hotkeys ,
213
+ keepMounted : true ,
214
+ content : (
215
+ < HotkeysPanelWrapper
216
+ visiblePanel = { visiblePanel }
217
+ closePanel = { closePanel }
218
+ />
219
+ ) ,
220
+ } ,
140
221
] }
141
- onClosePanel = { ( ) => {
142
- setVisiblePanel ( undefined ) ;
143
- } }
222
+ onClosePanel = { closePanel }
144
223
/>
145
224
</ React . Fragment >
146
225
) ;
0 commit comments