11"use client" ;
22
3- import { Link , useLocation } from 'react-router-dom' ;
3+ import { Link , useLocation , useNavigate } from 'react-router-dom' ;
44import { useAuth } from '../../contexts/AuthContext' ;
55import { useState , useEffect , useMemo } from 'react' ;
66import { cn } from '../../lib/utils' ;
7- import { Type , Combine , Terminal , Users , Upload , User , Shield , Plus , Sun , Moon , Coffee , Sparkles , Cherry , Gem , Leaf , Flame , Cloud , SunDim , Waves , Anchor , Building , Joystick , ShoppingBag , Droplets } from 'lucide-react' ;
8- import { motion } from 'framer-motion' ;
7+ import { Type , Combine , Terminal , Users , User , Shield , Plus , Sun , Moon , Coffee , Sparkles , Cherry , Gem , Leaf , Flame , Cloud , SunDim , Waves , Anchor , Building , Joystick , ShoppingBag , Droplets } from 'lucide-react' ;
8+ import { motion , AnimatePresence } from 'framer-motion' ;
99import { useTheme } from '../../contexts/ThemeContext' ;
1010import { Haptics , ImpactStyle } from '@capacitor/haptics' ;
1111import { useUpdate } from '../../contexts/UpdateContext' ;
1212import { Download } from 'lucide-react' ;
1313import Logo from '/logo/logo.png' ; // Assuming white version exists or we filter it
14+ import { CustomSerifA , CustomTerminal , CustomUpload , CustomUsers } from '../ui/CustomIcons' ;
1415
1516export default function Navbar ( ) {
1617 const { user, profile } = useAuth ( ) ;
1718 const { theme, toggleTheme } = useTheme ( ) ;
1819 const { hasUpdate, isDownloading, progress, openModal, closeModal, isModalOpen } = useUpdate ( ) ;
1920 const [ isScrolled , setIsScrolled ] = useState ( false ) ;
2021 const location = useLocation ( ) ;
22+ const navigate = useNavigate ( ) ;
23+
24+ // Local state for instantaneous UI updates so animations don't lag
25+ const [ activeTab , setActiveTab ] = useState ( location . pathname ) ;
26+
27+ useEffect ( ( ) => {
28+ setActiveTab ( location . pathname ) ;
29+ } , [ location . pathname ] ) ;
2130
2231 useEffect ( ( ) => {
2332 const handleScroll = ( ) => {
@@ -37,13 +46,11 @@ export default function Navbar() {
3746 ] , [ profile ?. role ] ) ;
3847
3948 const mobileNavLinks = useMemo ( ( ) => [
40- { name : 'Fonts' , path : '/fonts' , icon : Type } ,
49+ { name : 'Fonts' , path : '/fonts' , icon : CustomSerifA } ,
4150 { name : 'Pairing' , path : '/pairing' , icon : Combine } ,
42- { name : 'CLI' , path : '/cli' , icon : Terminal , badge : 'NEW' } ,
43- ...( profile ?. role === 'member' || profile ?. role === 'admin'
44- ? [ { name : 'Members' , path : '/members' , icon : Users } ,
45- { name : 'Upload' , path : '/upload' , icon : Upload } , ]
46- : [ ] ) ,
51+ { name : 'CLI' , path : '/cli' , icon : CustomTerminal , badge : 'NEW' } ,
52+ { name : 'Members' , path : '/members' , icon : CustomUsers } ,
53+ { name : 'Upload' , path : '/upload' , icon : CustomUpload } ,
4754 ] , [ profile ?. role ] ) ;
4855
4956 return (
@@ -234,57 +241,65 @@ export default function Navbar() {
234241
235242 { /* Mobile Bottom Dock */ }
236243 < div className = "md:hidden fixed bottom-0 left-1/2 -translate-x-1/2 z-100" >
237- < nav className = "flex items-center justify-evenly w-screen px-2 py-2.5 bg-[rgb(var(--color-background)/0.6)] backdrop-blur-3xl border-t border-[rgb(var(--color-border)/0.3)] shadow-2xl " >
244+ < nav className = "flex items-center justify-evenly w-screen px-2 py-2.5 bg-[rgb(var(--color-background)/0.6)] backdrop-blur-3xl border-t border-[rgb(var(--color-border)/0.3)]" >
238245 { mobileNavLinks . map ( ( link ) => {
239246 const Icon = link . icon ;
240- const isActive = location . pathname === link . path ;
247+ const isActive = activeTab === link . path ;
248+
241249 return (
242- < Link
250+ < motion . button
243251 key = { link . path }
244- to = { link . path }
245- onClick = { async ( ) => {
246- await Haptics . selectionChanged ( )
252+ layout // Smoothly adjusts the surrounding buttons when width changes
253+ onClick = { async ( e ) => {
254+ e . preventDefault ( ) ;
255+ if ( activeTab === link . path ) return ;
256+
257+ setActiveTab ( link . path ) ; // Instant UI feedback
258+ await Haptics . selectionChanged ( ) ;
259+
260+ // Let the button layout animation finish (300ms) before hitting the React Router tree
261+ setTimeout ( ( ) => {
262+ navigate ( link . path ) ;
263+ } , 300 ) ;
247264 } }
248265 className = { cn (
249- "relative flex flex-col items-center justify-center h-12 px-4 rounded-full transition-all duration-300" ,
266+ "relative flex items-center justify-center max- h-12 py-2 rounded-full transition-all duration-300 ease-in-out " ,
250267 isActive
251- ? "text -[rgb(var(--color-foreground))] "
252- : "text-[rgb(var(--color-muted- foreground))] hover:text-[rgb(var(--color-foreground))] hover:bg-[rgb(var(--color-foreground)/0.1)]"
268+ ? "bg -[rgb(var(--color-foreground)/0.8)] text-[rgb(var(--color-background))] px-4 shadow-sm "
269+ : "bg-transparent text-[rgb(var(--color-foreground))] hover:text-[rgb(var(--color-foreground))] hover:bg-[rgb(var(--color-foreground)/0.1)] px-3 "
253270 ) }
254271 style = { { WebkitTapHighlightColor : 'transparent' } }
255272 >
256- { /* Animated Background Pill */ }
257- { isActive && (
258- < motion . div
259- layoutId = "active-nav-pill"
260- className = "absolute inset-0 bg-white/10 rounded-full"
261- transition = { { type : 'spring' , stiffness : 300 , damping : 25 } }
262- />
263- ) }
264-
265- { /* Icon & Label */ }
266- < span className = "relative z-10 flex items-center gap-2" >
267- < Icon size = { 20 } strokeWidth = { isActive ? 3.5 : 2 } />
273+ { /* Icon */ }
274+ < Icon className = { cn (
275+ "shrink-0 w-4.5 h-4.5" ,
276+ isActive
277+ ? "fill-[rgb(var(--color-background))]"
278+ : "text-[rgb(var(--color-foreground))]"
279+ ) } />
268280
281+ { /* Animating Text */ }
282+ < AnimatePresence initial = { false } >
269283 { isActive && (
270284 < motion . span
271- initial = { { opacity : 0 , width : 0 } }
272- animate = { { opacity : 1 , width : 'auto' } }
273- exit = { { opacity : 0 , width : 0 } }
274- className = "font-black text-md overflow-hidden whitespace-nowrap"
285+ initial = { { width : 0 , opacity : 0 , marginLeft : 0 } }
286+ animate = { { width : 'auto' , opacity : 1 , marginLeft : 8 } }
287+ exit = { { width : 0 , opacity : 0 , marginLeft : 0 } }
288+ transition = { { type : 'spring' , bounce : 0 , duration : 0.35 } }
289+ className = "font-boldx font-rayton-brink text-[28px] mt-1 overflow-hidden whitespace-nowrap"
275290 >
276291 { link . name }
277292 </ motion . span >
278293 ) }
279- </ span >
294+ </ AnimatePresence >
280295
281296 { link . badge && (
282- < span className = "absolute top-1 right-1 flex h-2 w-2" >
297+ < span className = "absolute top-0 right-0 flex h-2.5 w-2.5 transform translate-x-1/2 -translate-y-1/ 2" >
283298 < span className = "animate-ping absolute inline-flex h-full w-full rounded-full bg-[rgb(var(--color-success)/0.75)]" > </ span >
284- < span className = "relative inline-flex rounded-full h-2 w-2 bg-[rgb(var(--color-success))]" > </ span >
299+ < span className = "relative inline-flex rounded-full h-2.5 w-2.5 bg-[rgb(var(--color-success))]" > </ span >
285300 </ span >
286301 ) }
287- </ Link >
302+ </ motion . button >
288303 ) ;
289304 } ) }
290305 </ nav >
0 commit comments