@@ -6,29 +6,145 @@ import {
66 SidebarMenuButton ,
77 SidebarMenuItem
88} from "../sidebar" ;
9- import { NavbarConfiguration , NavigationItem } from "./utils" ;
9+ import { ChevronRight } from "lucide-react" ;
10+ import { match , P } from "ts-pattern" ;
11+ import { type NavbarConfiguration , type MenuItemType } from "./utils" ;
12+ import { When } from "../../lib/when" ;
1013
1114interface DynamicMenuSectionsProps {
1215 config : NavbarConfiguration ;
1316 activeArea : string ;
1417 activeItem : string | null ;
1518 isCollapsed : boolean ;
16- onItemClick : ( item : NavigationItem ) => void ;
19+ onItemClick : ( item : MenuItemType ) => void ;
20+ expandedItems : string [ ] ;
1721}
1822
1923export const DynamicMenuSections : React . FC < DynamicMenuSectionsProps > = ( {
2024 config,
2125 activeArea,
2226 activeItem,
2327 isCollapsed,
24- onItemClick
28+ onItemClick,
29+ expandedItems
2530} ) => {
2631 const currentAreaConfig = config . areas . find ( area => area . title === activeArea ) ;
2732
2833 if ( ! currentAreaConfig || currentAreaConfig . sections . length === 0 ) {
2934 return null ;
3035 }
3136
37+ const renderMenuItem = ( item : MenuItemType ) => {
38+ return match ( item )
39+ . with ( { children : P . array ( P . any ) } , ( expandableItem ) => {
40+ if ( isCollapsed ) {
41+ return null ;
42+ }
43+
44+ const isExpanded = expandedItems . includes ( expandableItem . title ) ;
45+
46+ return (
47+ < SidebarMenuItem key = { expandableItem . title } >
48+ < SidebarMenuButton
49+ isActive = { false }
50+ onClick = { ( ) => onItemClick ( expandableItem ) }
51+ tooltip = { undefined }
52+ size = "default"
53+ disabled = { expandableItem . disabled }
54+ className = { `
55+ text-brand-fgLight/90 hover:text-brand-fgLight hover:bg-brand-fgLight/15
56+ rounded-lg transition-all duration-200 mb-1.5
57+ focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-orange focus-visible:ring-offset-2 focus-visible:ring-offset-transparent
58+ ${ expandableItem . disabled ? 'opacity-50 cursor-not-allowed' : '' }
59+ justify-between
60+ ` }
61+ >
62+ < div className = "flex items-center gap-2 flex-1 min-w-0" >
63+ < When condition = { Boolean ( expandableItem . icon ) } >
64+ { expandableItem . icon && < expandableItem . icon className = "h-4 w-4 flex-shrink-0" /> }
65+ </ When >
66+ < span className = "text-sm font-medium truncate" >
67+ { expandableItem . title }
68+ </ span >
69+ </ div >
70+ < ChevronRight
71+ className = { `h-4 w-4 flex-shrink-0 transition-transform duration-200 ease-in-out ${ isExpanded ? 'transform rotate-90' : 'transform rotate-0'
72+ } `}
73+ />
74+ </ SidebarMenuButton >
75+
76+ < When condition = { isExpanded } >
77+ < SidebarMenu className = "ml-2 mt-1 pr-2 border-l !border-[#7c598f]" >
78+ { expandableItem . children . map ( ( child ) => (
79+ < SidebarMenuItem key = { child . title } className = "pl-1" >
80+ < SidebarMenuButton
81+ isActive = { activeItem === child . title }
82+ onClick = { ( ) => onItemClick ( child ) }
83+ tooltip = { undefined }
84+ size = "default"
85+ disabled = { child . disabled }
86+ className = { `
87+ text-brand-fgLight/90 hover:text-brand-fgLight hover:bg-brand-fgLight/15
88+ rounded-lg transition-all duration-200 mb-1.5
89+ focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-orange focus-visible:ring-offset-2 focus-visible:ring-offset-transparent
90+ ${ activeItem === child . title ? 'bg-brand-fgLight/20 text-brand-fgLight font-medium' : '' }
91+ ${ child . disabled ? 'opacity-50 cursor-not-allowed' : '' }
92+ ` }
93+ >
94+ < When condition = { Boolean ( child . icon ) } >
95+ { child . icon && < child . icon className = "h-4 w-4 flex-shrink-0" /> }
96+ </ When >
97+ < span className = "text-sm font-medium truncate" >
98+ { child . title }
99+ < When condition = { Boolean ( child . badge ) } >
100+ < span className = "ml-2 px-1.5 py-0.5 text-xs bg-brand-orange text-white rounded-full" >
101+ { child . badge }
102+ </ span >
103+ </ When >
104+ </ span >
105+ </ SidebarMenuButton >
106+ </ SidebarMenuItem >
107+ ) ) }
108+ </ SidebarMenu >
109+ </ When >
110+ </ SidebarMenuItem >
111+ ) ;
112+ } )
113+ . otherwise ( ( regularItem ) => (
114+ < SidebarMenuItem key = { regularItem . title } >
115+ < SidebarMenuButton
116+ isActive = { activeItem === regularItem . title }
117+ onClick = { ( ) => onItemClick ( regularItem ) }
118+ tooltip = { isCollapsed ? regularItem . title : undefined }
119+ size = "default"
120+ disabled = { regularItem . disabled }
121+ className = { `
122+ text-brand-fgLight/90 hover:text-brand-fgLight hover:bg-brand-fgLight/15
123+ rounded-lg transition-all duration-200 mb-1.5
124+ focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-orange focus-visible:ring-offset-2 focus-visible:ring-offset-transparent
125+ ${ activeItem === regularItem . title ? 'bg-brand-fgLight/20 text-brand-fgLight font-medium' : '' }
126+ ${ regularItem . disabled ? 'opacity-50 cursor-not-allowed' : '' }
127+ group-data-[collapsible=icon]:justify-center
128+ ` }
129+ >
130+ < When condition = { Boolean ( regularItem . icon ) } >
131+ { regularItem . icon && < regularItem . icon className = "h-5 w-5 flex-shrink-0" /> }
132+ </ When >
133+ < When condition = { ! isCollapsed } >
134+ < span className = "text-sm font-medium truncate" >
135+ { regularItem . title }
136+ < When condition = { Boolean ( regularItem . badge ) } >
137+ < span className = "ml-2 px-1.5 py-0.5 text-xs bg-brand-orange text-white rounded-full" >
138+ { regularItem . badge }
139+ </ span >
140+ </ When >
141+ </ span >
142+ </ When >
143+ </ SidebarMenuButton >
144+ </ SidebarMenuItem >
145+ ) ) ;
146+ } ;
147+
32148 return (
33149 < >
34150 { currentAreaConfig . sections . map ( ( section ) => (
@@ -38,37 +154,7 @@ export const DynamicMenuSections: React.FC<DynamicMenuSectionsProps> = ({
38154 </ SidebarGroupLabel >
39155 < SidebarGroupContent >
40156 < SidebarMenu >
41- { section . items . map ( ( item ) => (
42- < SidebarMenuItem key = { item . title } >
43- < SidebarMenuButton
44- isActive = { activeItem === item . title }
45- onClick = { ( ) => onItemClick ( item ) }
46- tooltip = { isCollapsed ? item . title : undefined }
47- size = "default"
48- disabled = { item . disabled }
49- className = { `
50- text-brand-fgLight/90 hover:text-brand-fgLight hover:bg-brand-fgLight/15
51- rounded-lg transition-all duration-200 mb-1.5
52- focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-orange focus-visible:ring-offset-2 focus-visible:ring-offset-transparent
53- ${ activeItem === item . title ? 'bg-brand-fgLight/20 text-brand-fgLight font-medium' : '' }
54- ${ item . disabled ? 'opacity-50 cursor-not-allowed' : '' }
55- group-data-[collapsible=icon]:justify-center
56- ` }
57- >
58- < item . icon className = "h-5 w-5 flex-shrink-0" />
59- { ! isCollapsed && (
60- < span className = "text-sm font-medium truncate" >
61- { item . title }
62- { item . badge && (
63- < span className = "ml-2 px-1.5 py-0.5 text-xs bg-brand-orange text-white rounded-full" >
64- { item . badge }
65- </ span >
66- ) }
67- </ span >
68- ) }
69- </ SidebarMenuButton >
70- </ SidebarMenuItem >
71- ) ) }
157+ { section . items . map ( renderMenuItem ) }
72158 </ SidebarMenu >
73159 </ SidebarGroupContent >
74160 </ SidebarGroup >
0 commit comments