11import { Tag } from '@rspress/core/theme'
22import {
33 isExternalUrl ,
4- withoutBase ,
4+ matchNavbar ,
55 type NavItem ,
66 type NavItemWithChildren ,
77 type NavItemWithLink ,
88 type NavItemWithLinkAndChildren ,
99} from '@rspress/shared'
10- import { useCallback , useState , type ReactNode } from 'react'
10+ import { useRef , useState , type ReactNode } from 'react'
1111
12- import { SvgDown } from './Down.js'
12+ import { Down } from './Down.js'
1313import {
1414 NavMenuSingleItem ,
1515 type NavMenuSingleItemProps ,
@@ -23,7 +23,7 @@ export interface NavMenuGroupItem {
2323 tag ?: string
2424 // Design for i18n highlight.
2525 activeValue ?: string
26- // Currrnt pathname.
26+ // Current pathname.
2727 pathname ?: string
2828 // Base path.
2929 base ?: string
@@ -35,32 +35,32 @@ function ActiveGroupItem({ item }: { item: NavItemWithLink }) {
3535 return (
3636 < div
3737 key = { item . link }
38- className = "rounded-2xl my-1 flex"
38+ className = "rp- rounded-2xl rp- my-1 rp- flex"
3939 style = { {
4040 padding : '0.4rem 1.5rem 0.4rem 0.75rem' ,
4141 } }
4242 >
4343 { item . tag && < Tag tag = { item . tag } /> }
44- < span className = "text-brand" > { item . text } </ span >
44+ < span className = "rp- text-brand" > { item . text } </ span >
4545 </ div >
4646 )
4747}
4848
4949function NormalGroupItem ( { item } : { item : NavItemWithLink } ) {
5050 return (
51- < div key = { item . link } className = "font-medium my-1" >
51+ < div key = { item . link } className = "rp- font-medium rp- my-1" >
5252 < a
5353 href = { item . link }
5454 target = { isExternalUrl ( item . link ) ? '_blank' : undefined }
5555 rel = "noopener noreferrer"
5656 >
5757 < div
58- className = "rounded-2xl hover:bg-mute"
58+ className = "rp- rounded-2xl hover:rp- bg-mute"
5959 style = { {
6060 padding : '0.4rem 1.5rem 0.4rem 0.75rem' ,
6161 } }
6262 >
63- < div className = "flex" >
63+ < div className = "rp- flex" >
6464 { item . tag && < Tag tag = { item . tag } /> }
6565 < span > { item . text } </ span >
6666 </ div >
@@ -71,21 +71,41 @@ function NormalGroupItem({ item }: { item: NavItemWithLink }) {
7171}
7272
7373export function NavMenuGroup ( item : NavMenuGroupItem ) {
74- const { activeValue, items : groupItems , base = '' , pathname = '' } = item
74+ const {
75+ activeValue,
76+ items : groupItems ,
77+ base = '' ,
78+ link = '' ,
79+ pathname = '' ,
80+ } = item
7581 const [ isOpen , setIsOpen ] = useState ( false )
82+ const closeTimerRef = useRef < number > ( null )
7683
77- const onOpen = useCallback ( ( ) => {
78- setIsOpen ( true )
79- } , [ ] )
84+ const clearCloseTimer = ( ) => {
85+ if ( closeTimerRef . current ) {
86+ clearTimeout ( closeTimerRef . current )
87+ closeTimerRef . current = null
88+ }
89+ }
90+
91+ /**
92+ * Handle mouse leave event for the dropdown menu
93+ * Closes the menu after a 150ms delay to allow diagonal mouse movement
94+ * to the dropdown content area
95+ */
96+ const handleMouseLeave = ( ) => {
97+ closeTimerRef . current = window . setTimeout ( ( ) => {
98+ setIsOpen ( false )
99+ } , 150 )
100+ }
80101
81- const onClose = useCallback ( ( ) => {
82- setIsOpen ( false )
83- } , [ ] )
102+ const handleMouseEnter = ( ) => {
103+ clearCloseTimer ( )
104+ setIsOpen ( true )
105+ }
84106
85107 const renderLinkItem = ( item : NavItemWithLink ) => {
86- const isLinkActive = new RegExp ( item . activeMatch || item . link ) . test (
87- withoutBase ( pathname , base ) ,
88- )
108+ const isLinkActive = matchNavbar ( item , pathname , base )
89109 if ( activeValue === item . text || ( ! activeValue && isLinkActive ) ) {
90110 return < ActiveGroupItem key = { item . link } item = { item } />
91111 }
@@ -100,7 +120,7 @@ export function NavMenuGroup(item: NavMenuGroupItem) {
100120 { 'link' in item ? (
101121 renderLinkItem ( item )
102122 ) : (
103- < p className = "font-bold text-gray-400 my-1 not:first:border" >
123+ < p className = "rp- font-bold rp- text-gray-400 rp- my-1 not:first:rp- border" >
104124 { item . text }
105125 </ p >
106126 ) }
@@ -109,49 +129,47 @@ export function NavMenuGroup(item: NavMenuGroupItem) {
109129 )
110130 }
111131
112- const hasMultiItems = groupItems . length > 1
113-
114- const Content = hasMultiItems || item . link ? 'button' : 'span'
115-
116- const content = (
117- < Content
118- onMouseEnter = { hasMultiItems ? onOpen : undefined }
119- className = { `${ Content === 'button' ? 'rspress-nav-menu-group-button ' : '' } flex-center items-center font-medium text-sm text-text-1${ hasMultiItems ? ' hover:text-text-2 transition-colors duration-200' : '' } ` }
132+ return (
133+ < div
134+ className = "rp-relative rp-flex rp-items-center rp-justify-center rp-h-14"
135+ onMouseLeave = { handleMouseLeave }
120136 >
121- { item . link ? (
122- < NavMenuSingleItem
123- { ...( item as NavMenuSingleItemProps ) }
124- rightIcon = { < SvgWrapper icon = { SvgDown } /> }
125- />
126- ) : (
127- < >
128- < span
129- className = "text-sm font-medium flex"
130- style = { hasMultiItems ? { marginRight : '2px' } : undefined }
131- >
132- < Tag tag = { item . tag } />
133- { item . text }
134- </ span >
135- { hasMultiItems && < SvgWrapper icon = { SvgDown } /> }
136- </ >
137- ) }
138- </ Content >
139- )
140-
141- return hasMultiItems ? (
142- < div className = "relative flex-center h-14" onMouseLeave = { onClose } >
143- { content }
144137 < div
145- className = "rspress-nav-menu-group-content absolute mx-0.8 transition-opacity duration-300"
138+ onMouseEnter = { handleMouseEnter }
139+ className = "rspress-nav-menu-group-button rp-flex rp-justify-center rp-items-center rp-font-medium rp-text-sm rp-text-text-1 hover:rp-text-text-2 rp-transition-colors rp-duration-200 rp-cursor-pointer"
140+ >
141+ { link ? (
142+ < NavMenuSingleItem
143+ { ...( item as NavMenuSingleItemProps ) }
144+ rightIcon = { < SvgWrapper icon = { Down } /> }
145+ />
146+ ) : (
147+ < >
148+ < span
149+ className = "rp-text-sm rp-font-medium rp-flex"
150+ style = { {
151+ marginRight : '2px' ,
152+ } }
153+ >
154+ < Tag tag = { item . tag } />
155+ { item . text }
156+ </ span >
157+ < SvgWrapper icon = { Down } />
158+ </ >
159+ ) }
160+ </ div >
161+ < div
162+ className = "rspress-nav-menu-group-content rp-absolute rp-mx-0.8 rp-transition-opacity rp-duration-300"
146163 style = { {
147164 opacity : isOpen ? 1 : 0 ,
148165 visibility : isOpen ? 'visible' : 'hidden' ,
149166 right : 0 ,
150167 top : '52px' ,
151168 } }
169+ onMouseEnter = { clearCloseTimer }
152170 >
153171 < div
154- className = "p-3 pr-2 w-full h-full max-h-100vh whitespace-nowrap"
172+ className = "rp- p-3 rp- pr-2 rp- w-full rp- h-full rp- max-h-100vh rp- whitespace-nowrap"
155173 style = { {
156174 boxShadow : 'var(--rp-shadow-3)' ,
157175 zIndex : 100 ,
@@ -169,7 +187,5 @@ export function NavMenuGroup(item: NavMenuGroupItem) {
169187 </ div >
170188 </ div >
171189 </ div >
172- ) : (
173- content
174190 )
175191}
0 commit comments