@@ -73,6 +73,95 @@ import {
7373} from '~/components/Collapsible'
7474import { Card } from '~/components/Card'
7575
76+ type LogoProps = {
77+ showMenu : boolean
78+ setShowMenu : React . Dispatch < React . SetStateAction < boolean > >
79+ menuButtonRef : React . RefObject < HTMLButtonElement | null >
80+ title ?: React . ComponentType < any > | null
81+ }
82+
83+ const LogoSection = ( {
84+ showMenu,
85+ setShowMenu,
86+ menuButtonRef,
87+ title,
88+ } : LogoProps ) => {
89+ const pointerInsideButtonRef = React . useRef ( false )
90+ const toggleMenu = ( ) => {
91+ setShowMenu ( ( prev ) => ! prev )
92+ }
93+ return (
94+ < >
95+ < button
96+ aria-label = "Open Menu"
97+ className = { twMerge (
98+ 'flex items-center justify-center' ,
99+ 'transition-all duration-300 h-8 px-2 py-1 lg:px-0' ,
100+ // At lg: only visible when Title exists (flyout mode)
101+ // Below lg: always visible
102+ title
103+ ? 'lg:w-9 lg:opacity-100 lg:translate-x-0'
104+ : 'lg:w-0 lg:opacity-0 lg:-translate-x-full' ,
105+ ) }
106+ ref = { menuButtonRef }
107+ onClick = { toggleMenu }
108+ onPointerEnter = { ( e ) => {
109+ // Enable hover to open flyout at md+ (but not touch)
110+ if ( window . innerWidth < 768 || e . pointerType === 'touch' ) return
111+ if ( pointerInsideButtonRef . current ) return
112+ pointerInsideButtonRef . current = true
113+ setShowMenu ( true )
114+ } }
115+ onPointerLeave = { ( ) => {
116+ pointerInsideButtonRef . current = false
117+ } }
118+ >
119+ { showMenu ? < X /> : < Menu /> }
120+ </ button >
121+ < Link
122+ to = "/"
123+ className = { twMerge ( `inline-flex items-center gap-1.5 cursor-pointer` ) }
124+ >
125+ < div className = "w-[30px] inline-grid items-center grid-cols-1 grid-rows-1 [&>*]:transition-opacity [&>*]:duration-1000" >
126+ < img
127+ src = { '/images/logos/logo-color-100.png' }
128+ alt = ""
129+ className = "row-start-1 col-start-1 w-full group-hover:opacity-0"
130+ />
131+ < img
132+ src = { '/images/logos/logo-black.svg' }
133+ alt = ""
134+ className = "row-start-1 col-start-1 w-full dark:opacity-0 opacity-0 group-hover:opacity-100"
135+ />
136+ < img
137+ src = { '/images/logos/logo-white.svg' }
138+ alt = ""
139+ className = "row-start-1 col-start-1 w-full light:opacity-0 dark:block opacity-0 group-hover:opacity-100"
140+ />
141+ </ div >
142+ < div > TanStack</ div >
143+ </ Link >
144+ </ >
145+ )
146+ }
147+
148+ const MobileCard = ( {
149+ children,
150+ isActive,
151+ } : {
152+ children : React . ReactNode
153+ isActive ?: boolean
154+ } ) => (
155+ < Card
156+ className = { twMerge (
157+ 'md:contents border-gray-200/50 dark:border-gray-700/50 shadow-sm' ,
158+ isActive && 'ring-2 ring-gray-400/30 dark:ring-gray-500/30' ,
159+ ) }
160+ >
161+ { children }
162+ </ Card >
163+ )
164+
76165export function Navbar ( { children } : { children : React . ReactNode } ) {
77166 const matches = useMatches ( )
78167 const capabilities = useCapabilities ( )
@@ -131,8 +220,6 @@ export function Navbar({ children }: { children: React.ReactNode }) {
131220 } , [ ] )
132221
133222 const [ showMenu , setShowMenu ] = React . useState ( false )
134- const pointerInsideButtonRef = React . useRef ( false )
135-
136223 const largeMenuRef = React . useRef < HTMLDivElement > ( null )
137224 const menuButtonRef = React . useRef < HTMLButtonElement > ( null )
138225
@@ -143,64 +230,6 @@ export function Navbar({ children }: { children: React.ReactNode }) {
143230 additionalRefs : [ largeMenuRef , menuButtonRef ] ,
144231 } )
145232
146- const toggleMenu = ( ) => {
147- setShowMenu ( ( prev ) => ! prev )
148- }
149-
150- const LogoSection = ( ) => (
151- < >
152- < button
153- aria-label = "Open Menu"
154- className = { twMerge (
155- 'flex items-center justify-center' ,
156- 'transition-all duration-300 h-8 px-2 py-1 lg:px-0' ,
157- // At lg: only visible when Title exists (flyout mode)
158- // Below lg: always visible
159- Title
160- ? 'lg:w-9 lg:opacity-100 lg:translate-x-0'
161- : 'lg:w-0 lg:opacity-0 lg:-translate-x-full' ,
162- ) }
163- ref = { menuButtonRef }
164- onClick = { toggleMenu }
165- onPointerEnter = { ( e ) => {
166- // Enable hover to open flyout at md+ (but not touch)
167- if ( window . innerWidth < 768 || e . pointerType === 'touch' ) return
168- if ( pointerInsideButtonRef . current ) return
169- pointerInsideButtonRef . current = true
170- setShowMenu ( true )
171- } }
172- onPointerLeave = { ( ) => {
173- pointerInsideButtonRef . current = false
174- } }
175- >
176- { showMenu ? < X /> : < Menu /> }
177- </ button >
178- < Link
179- to = "/"
180- className = { twMerge ( `inline-flex items-center gap-1.5 cursor-pointer` ) }
181- >
182- < div className = "w-[30px] inline-grid items-center grid-cols-1 grid-rows-1 [&>*]:transition-opacity [&>*]:duration-1000" >
183- < img
184- src = { '/images/logos/logo-color-100.png' }
185- alt = ""
186- className = "row-start-1 col-start-1 w-full group-hover:opacity-0"
187- />
188- < img
189- src = { '/images/logos/logo-black.svg' }
190- alt = ""
191- className = "row-start-1 col-start-1 w-full dark:opacity-0 opacity-0 group-hover:opacity-100"
192- />
193- < img
194- src = { '/images/logos/logo-white.svg' }
195- alt = ""
196- className = "row-start-1 col-start-1 w-full light:opacity-0 dark:block opacity-0 group-hover:opacity-100"
197- />
198- </ div >
199- < div > TanStack</ div >
200- </ Link >
201- </ >
202- )
203-
204233 const loginButton = (
205234 < >
206235 { ( ( ) => {
@@ -278,11 +307,25 @@ export function Navbar({ children }: { children: React.ReactNode }) {
278307 >
279308 < div className = "flex items-center min-w-0" >
280309 < div className = "flex items-center gap-2 font-black text-xl uppercase min-w-0" >
281- < React . Suspense fallback = { < LogoSection /> } >
310+ < React . Suspense
311+ fallback = {
312+ < LogoSection
313+ menuButtonRef = { menuButtonRef }
314+ setShowMenu = { setShowMenu }
315+ showMenu = { showMenu }
316+ title = { Title }
317+ />
318+ }
319+ >
282320 < LazyBrandContextMenu
283321 className = { twMerge ( `flex items-center group flex-shrink-0` ) }
284322 >
285- < LogoSection />
323+ < LogoSection
324+ menuButtonRef = { menuButtonRef }
325+ setShowMenu = { setShowMenu }
326+ showMenu = { showMenu }
327+ title = { Title }
328+ />
286329 </ LazyBrandContextMenu >
287330 </ React . Suspense >
288331 { Title ? (
@@ -318,23 +361,6 @@ export function Navbar({ children }: { children: React.ReactNode }) {
318361
319362 const linkClasses = `flex items-center justify-between gap-2 group px-3 py-3 md:px-2 md:py-1 rounded-lg hover:bg-gray-500/10 font-bold text-base md:text-sm`
320363
321- const MobileCard = ( {
322- children,
323- isActive,
324- } : {
325- children : React . ReactNode
326- isActive ?: boolean
327- } ) => (
328- < Card
329- className = { twMerge (
330- 'md:contents border-gray-200/50 dark:border-gray-700/50 shadow-sm' ,
331- isActive && 'ring-2 ring-gray-400/30 dark:ring-gray-500/30' ,
332- ) }
333- >
334- { children }
335- </ Card >
336- )
337-
338364 const items = (
339365 < div className = "contents md:block" >
340366 < div className = "contents md:block" >
0 commit comments