@@ -7,11 +7,12 @@ import React, {
7
7
useRef ,
8
8
useEffect ,
9
9
useState ,
10
+ useCallback ,
10
11
} from 'react'
11
12
import { Slot } from '@radix-ui/react-slot'
12
13
import { Icon } from '../Icon'
13
14
import { useAppLayout } from '@/hooks/useAppLayout'
14
- import { AnimatePresence , motion } from 'framer-motion'
15
+ import { motion } from 'framer-motion'
15
16
import { Logo } from '../Logo'
16
17
import {
17
18
Tooltip ,
@@ -249,7 +250,7 @@ const AppLayoutSidebar = ({
249
250
ref = { sidebarRef }
250
251
transition = { { duration : 0.25 , type : 'spring' , bounce : 0 } }
251
252
>
252
- < div className = "flex flex-col gap-4 px-2" >
253
+ < div className = "flex flex-col gap-3 px-2" >
253
254
< Logo
254
255
variant = { collapsed ? 'icon' : 'wordmark' }
255
256
className = { cn ( 'cursor-pointer' , ! collapsed && 'min-w-[140px]' ) }
@@ -468,10 +469,7 @@ interface AppLayoutNavProps extends HTMLAttributes<HTMLDivElement> {
468
469
469
470
const AppLayoutNav = ( { children, className, ...props } : AppLayoutNavProps ) => {
470
471
return (
471
- < nav
472
- className = { cn ( 'mt-3 flex flex-col items-start gap-3' , className ) }
473
- { ...props }
474
- >
472
+ < nav className = { cn ( 'mt-3 flex flex-col items-start' , className ) } { ...props } >
475
473
{ children }
476
474
</ nav >
477
475
)
@@ -497,6 +495,7 @@ export interface AppLayoutNavItemProps
497
495
active ?: boolean
498
496
disabled ?: boolean
499
497
asChild ?: boolean
498
+ defaultSubNavItemsOpen ?: boolean
500
499
}
501
500
502
501
const AppLayoutNavItem = React . forwardRef <
@@ -513,6 +512,7 @@ const AppLayoutNavItem = React.forwardRef<
513
512
active,
514
513
disabled,
515
514
children,
515
+ defaultSubNavItemsOpen = false ,
516
516
...rest
517
517
} ,
518
518
ref
@@ -544,46 +544,91 @@ const AppLayoutNavItem = React.forwardRef<
544
544
return type . displayName === 'AppLayout.SubNavItem'
545
545
} )
546
546
547
- const [ isSubNavItemsOpen , setIsSubNavItemsOpen ] = useState ( false )
547
+ const [ isSubNavItemsOpen , setIsSubNavItemsOpen ] = useState (
548
+ defaultSubNavItemsOpen
549
+ )
550
+
551
+ const { _setActiveNavItem, _activeNavItem } = useAppLayout ( )
552
+
553
+ // Close this nav item when another nav item becomes active
554
+ useEffect ( ( ) => {
555
+ if (
556
+ _activeNavItem !== null &&
557
+ _activeNavItem !== title &&
558
+ isSubNavItemsOpen
559
+ ) {
560
+ setIsSubNavItemsOpen ( false )
561
+ }
562
+ } , [ _activeNavItem , title , isSubNavItemsOpen ] )
563
+
564
+ const toggleSubNavItems = useCallback ( ( ) => {
565
+ const newOpenState = ! isSubNavItemsOpen
566
+ setIsSubNavItemsOpen ( newOpenState )
567
+
568
+ // Only set as active if we're opening, not closing
569
+ if ( newOpenState ) {
570
+ _setActiveNavItem ( title )
571
+ } else {
572
+ _setActiveNavItem ( null )
573
+ }
574
+ } , [ isSubNavItemsOpen , title , _setActiveNavItem ] )
548
575
549
576
const content = asChild ? (
550
577
children
551
578
) : (
552
- < div className = "flex w-full flex-col items-start gap-2" >
553
- < div className = "flex w-full items-center gap-2" >
579
+ < motion . div className = "flex w-full flex-col items-start gap-1" >
580
+ < div
581
+ className = { cn (
582
+ 'flex w-full items-center gap-2' ,
583
+ subNavItems . length > 0 && ! collapsed && 'cursor-pointer'
584
+ ) }
585
+ onClick = {
586
+ subNavItems . length > 0 && ! collapsed ? toggleSubNavItems : undefined
587
+ }
588
+ >
554
589
{ iconElement }
555
590
{ titleElement }
556
591
557
- { subNavItems . length > 0 && (
558
- < button
559
- className = "text-muted-foreground hover:text-foreground ml-auto"
560
- onClick = { ( ) => setIsSubNavItemsOpen ( ! isSubNavItemsOpen ) }
561
- >
562
- < Icon
563
- name = { isSubNavItemsOpen ? 'chevron-up' : 'chevron-down' }
564
- className = "size-4 text-current"
565
- />
566
- </ button >
592
+ { subNavItems . length > 0 && ! collapsed && (
593
+ < Icon
594
+ name = { isSubNavItemsOpen ? 'chevron-up' : 'chevron-down' }
595
+ className = "text-muted-foreground ml-auto size-4"
596
+ />
567
597
) }
568
598
</ div >
569
- < AnimatePresence >
570
- { subNavItems . length > 0 && isSubNavItemsOpen && (
571
- < div className = "mb-2 ml-8 flex flex-col gap-1" >
572
- { subNavItems . map ( ( child , index ) => (
573
- < motion . div
574
- key = { index }
575
- initial = { { opacity : 0 } }
576
- animate = { { opacity : 1 } }
577
- exit = { { opacity : 0 } }
578
- transition = { { duration : 0.2 } }
579
- >
580
- { child }
581
- </ motion . div >
582
- ) ) }
583
- </ div >
584
- ) }
585
- </ AnimatePresence >
586
- </ div >
599
+ < motion . div
600
+ className = "ml-8 overflow-hidden"
601
+ initial = { false }
602
+ animate = { {
603
+ height : subNavItems . length > 0 && isSubNavItemsOpen ? 'auto' : 0 ,
604
+ marginBottom : subNavItems . length > 0 && isSubNavItemsOpen ? 8 : 0 ,
605
+ } }
606
+ transition = { {
607
+ duration : 0.2 ,
608
+ ease : [ 0.25 , 0.46 , 0.45 , 0.94 ] ,
609
+ } }
610
+ >
611
+ < div className = "flex flex-col gap-1" >
612
+ { subNavItems . map ( ( child , index ) => (
613
+ < motion . div
614
+ key = { index }
615
+ initial = { false }
616
+ animate = { {
617
+ opacity : isSubNavItemsOpen ? 1 : 0 ,
618
+ y : isSubNavItemsOpen ? 0 : - 4 ,
619
+ } }
620
+ transition = { {
621
+ duration : 0.15 ,
622
+ delay : isSubNavItemsOpen ? index * 0.03 : 0 ,
623
+ ease : [ 0.25 , 0.46 , 0.45 , 0.94 ] ,
624
+ } }
625
+ >
626
+ { child }
627
+ </ motion . div >
628
+ ) ) }
629
+ </ div >
630
+ </ motion . div >
631
+ </ motion . div >
587
632
)
588
633
589
634
return (
@@ -649,9 +694,11 @@ const AppLayoutNavItemGroup = ({
649
694
} : AppLayoutNavItemGroupProps ) => {
650
695
const { collapsed } = useAppLayout ( )
651
696
return (
652
- < div className = { cn ( 'mb-4 flex flex-col' , className ) } { ...props } >
653
- { ! collapsed && < div className = "text-codeline-sm uppercase" > { name } </ div > }
654
- { children }
697
+ < div className = { cn ( 'mb-3 flex w-full flex-col' , className ) } { ...props } >
698
+ { ! collapsed && (
699
+ < div className = "text-codeline-sm mb-2 uppercase" > { name } </ div >
700
+ ) }
701
+ < div className = "flex flex-col gap-1" > { children } </ div >
655
702
</ div >
656
703
)
657
704
}
0 commit comments