1414 * limitations under the License.
1515 */
1616
17- import { useEffect , useMemo , useRef , useState } from 'react'
17+ import { MouseEvent , useEffect , useMemo , useRef , useState } from 'react'
1818import { useLocation } from 'react-router-dom'
1919
2020import {
@@ -51,7 +51,7 @@ export const Navigation = ({
5151 pageState,
5252} : NavigationProps ) => {
5353 // STATES
54- const [ clickedNavGroup , setClickedNavGroup ] = useState < NavigationGroupType | null > ( null )
54+ const [ hoveredNavGroup , setHoveredNavGroup ] = useState < NavigationGroupType | null > ( null )
5555 const [ searchText , setSearchText ] = useState ( '' )
5656 const [ showCommandBar , setShowCommandBar ] = useState ( false )
5757
@@ -61,6 +61,7 @@ export const Navigation = ({
6161 // REFS
6262 const securityTrivyModuleTimeout = useRef < NodeJS . Timeout > ( null )
6363 const securityClairModuleTimeout = useRef < NodeJS . Timeout > ( null )
64+ const timeoutRef = useRef < NodeJS . Timeout > ( null )
6465
6566 useEffect (
6667 ( ) => ( ) => {
@@ -139,8 +140,10 @@ export const Navigation = ({
139140 [ pathname ] ,
140141 )
141142
142- const currentNavGroup = clickedNavGroup || selectedNavGroup
143- const isExpanded = ! ! clickedNavGroup
143+ // The current navigation group is the one that is hovered or the one that is active, \
144+ // this is used to determine which nav group items are to be shown in expanded state.
145+ const currentNavGroup = hoveredNavGroup || selectedNavGroup
146+ const isExpanded = ! ! hoveredNavGroup
144147
145148 const navItems = useMemo < NavigationGroupType [ 'items' ] > (
146149 ( ) => ( currentNavGroup ? filterNavigationItems ( currentNavGroup . items , searchText ) : [ ] ) ,
@@ -158,7 +161,7 @@ export const Navigation = ({
158161 ) {
159162 e . preventDefault ( )
160163 }
161- setClickedNavGroup ( navItem )
164+ setHoveredNavGroup ( navItem )
162165 setSearchText ( '' )
163166 }
164167
@@ -168,10 +171,32 @@ export const Navigation = ({
168171 if ( searchText && ! forceClose ) {
169172 return
170173 }
171- setClickedNavGroup ( null )
174+ setHoveredNavGroup ( null )
172175 setSearchText ( '' )
173176 }
174177
178+ const handleNavGroupHover = ( navGroup : typeof hoveredNavGroup ) => ( isHovered : boolean ) => {
179+ clearTimeout ( timeoutRef . current )
180+
181+ if ( isHovered ) {
182+ if ( ! hoveredNavGroup ) {
183+ setHoveredNavGroup ( navGroup )
184+ return
185+ }
186+
187+ timeoutRef . current = setTimeout ( ( ) => {
188+ setHoveredNavGroup ( navGroup )
189+ setSearchText ( '' )
190+ } , 50 )
191+ }
192+ }
193+
194+ const handleOpenExpandedNavigation = ( e : MouseEvent < HTMLDivElement > ) => {
195+ if ( ! hoveredNavGroup && e . target === e . currentTarget ) {
196+ setHoveredNavGroup ( selectedNavGroup )
197+ }
198+ }
199+
175200 const handleOpenCommandBar = ( ) => {
176201 setShowCommandBar ( true )
177202 handleCloseExpandedNavigation ( true ) ( )
@@ -182,32 +207,44 @@ export const Navigation = ({
182207 < div className = "navigation dc__position-rel" >
183208 < nav
184209 className = { `navigation__default dc__position-rel dc__grid dc__overflow-hidden h-100 ${ isExpanded ? 'is-expanded' : '' } ` }
210+ onMouseEnter = { handleOpenExpandedNavigation }
185211 >
186212 < NavigationLogo />
187213 < NavGroup
188214 title = "Search"
189215 icon = "ic-magnifying-glass"
190216 isExpanded = { isExpanded }
191217 onClick = { handleOpenCommandBar }
218+ showTooltip
192219 tooltip = {
193220 < span className = "flex dc__gap-2" >
194221 Search
195222 < KeyboardShortcut keyboardKey = "Meta" />
196223 < KeyboardShortcut keyboardKey = "K" />
197224 </ span >
198225 }
226+ onHover = { handleCloseExpandedNavigation ( true ) }
227+ />
228+ < NavGroup
229+ title = "Overview"
230+ icon = "ic-speedometer"
231+ to = "/dummy-url"
232+ isExpanded = { isExpanded }
233+ onHover = { handleCloseExpandedNavigation ( true ) }
234+ disabled
235+ showTooltip
199236 />
200- < NavGroup title = "Overview" icon = "ic-speedometer" to = "/dummy-url" isExpanded = { isExpanded } />
201237 { NAVIGATION_LIST . map ( ( item ) => (
202238 < NavGroup
203239 key = { item . id }
204240 title = { item . title }
205241 icon = { item . icon }
206242 disabled = { item . disabled }
207243 isExpanded = { isExpanded }
208- isSelected = { clickedNavGroup ?. id === item . id || selectedNavGroup ?. id === item . id }
244+ isSelected = { hoveredNavGroup ?. id === item . id || selectedNavGroup ?. id === item . id }
209245 onClick = { handleNavGroupClick ( item ) }
210246 to = { findActiveNavigationItemOfNavGroup ( item . items ) ?. href }
247+ onHover = { handleNavGroupHover ( item ) }
211248 />
212249 ) ) }
213250 { ! window . _env_ . K8S_CLIENT && ! isAirgapped && showStackManager && (
@@ -216,6 +253,8 @@ export const Navigation = ({
216253 icon = "ic-stack"
217254 to = { URLS . STACK_MANAGER_ABOUT }
218255 isExpanded = { isExpanded }
256+ onHover = { handleCloseExpandedNavigation ( true ) }
257+ showTooltip
219258 />
220259 ) }
221260 </ nav >
@@ -249,13 +288,21 @@ export const Navigation = ({
249288 inputProps = { { autoFocus : true } }
250289 />
251290 < div className = "flex-grow-1 dc__overflow-auto" >
252- { navItems
253- . filter ( ( { forceHideEnvKey, hideNav } ) =>
254- forceHideEnvKey ? window . _env_ [ forceHideEnvKey ] : ! hideNav ,
255- )
256- . map ( ( item ) => (
257- < NavItem key = { item . title } { ...item } hasSearchText = { ! ! searchText } />
258- ) ) }
291+ { navItems . length ? (
292+ navItems
293+ . filter ( ( { forceHideEnvKey, hideNav } ) =>
294+ forceHideEnvKey ? window . _env_ [ forceHideEnvKey ] : ! hideNav ,
295+ )
296+ . map ( ( item ) => (
297+ < NavItem
298+ key = { item . title }
299+ { ...item }
300+ hasSearchText = { ! ! searchText }
301+ />
302+ ) )
303+ ) : (
304+ < span className = "fs-13 lh-20 text__sidenav" > No matching results</ span >
305+ ) }
259306 </ div >
260307 </ div >
261308 ) }
0 commit comments