@@ -20,7 +20,7 @@ interface INavItemProps {
2020
2121
2222export const SidebarDatamodelView = ( { } : ISidebarDatamodelViewProps ) => {
23- const { currentSection, currentGroup, scrollToSection, loadingSection } = useDatamodelView ( ) ;
23+ const { currentSection, currentGroup, scrollToSection, scrollToGroup , loadingSection } = useDatamodelView ( ) ;
2424 const { close : closeSidebar } = useSidebar ( ) ;
2525 const theme = useTheme ( ) ;
2626 const isMobile = useIsMobile ( ) ;
@@ -31,6 +31,7 @@ export const SidebarDatamodelView = ({ }: ISidebarDatamodelViewProps) => {
3131
3232 const [ searchTerm , setSearchTerm ] = useState ( "" ) ;
3333 const [ displaySearchTerm , setDisplaySearchTerm ] = useState ( "" ) ;
34+ const [ expandedGroups , setExpandedGroups ] = useState < Set < string > > ( new Set ( ) ) ;
3435
3536 // Memoize search results to prevent recalculation on every render
3637 const filteredGroups = useMemo ( ( ) => {
@@ -67,6 +68,7 @@ export const SidebarDatamodelView = ({ }: ISidebarDatamodelViewProps) => {
6768 newExpandedGroups . add ( group . Name ) ;
6869 }
6970 } ) ;
71+ setExpandedGroups ( newExpandedGroups ) ;
7072 }
7173 } , [ groups ] ) ;
7274
@@ -93,8 +95,42 @@ export const SidebarDatamodelView = ({ }: ISidebarDatamodelViewProps) => {
9395 } , [ ] ) ;
9496
9597 const handleGroupClick = useCallback ( ( groupName : string ) => {
96- dataModelDispatch ( { type : "SET_CURRENT_GROUP" , payload : groupName } ) ;
97- } , [ dataModelDispatch ] ) ;
98+ setExpandedGroups ( prev => {
99+ const newExpanded = new Set ( prev ) ;
100+ if ( newExpanded . has ( groupName ) ) {
101+ newExpanded . delete ( groupName ) ;
102+ } else {
103+ if ( currentGroup ?. toLowerCase ( ) === groupName . toLowerCase ( ) ) return newExpanded ;
104+ newExpanded . add ( groupName ) ;
105+ }
106+ return newExpanded ;
107+ } ) ;
108+ } , [ dataModelDispatch , currentGroup ] ) ;
109+
110+ const handleScrollToGroup = useCallback ( ( group : GroupType ) => {
111+
112+ // Set current group and scroll to group header
113+ dataModelDispatch ( { type : "SET_CURRENT_GROUP" , payload : group . Name } ) ;
114+ if ( group . Entities . length > 0 )
115+ dataModelDispatch ( { type : "SET_CURRENT_SECTION" , payload : group . Entities [ 0 ] . SchemaName } ) ;
116+
117+ setExpandedGroups ( prev => {
118+ const newExpanded = new Set ( prev ) ;
119+ if ( newExpanded . has ( group . Name ) ) {
120+ newExpanded . delete ( group . Name ) ;
121+ }
122+ return newExpanded ;
123+ } ) ;
124+
125+ if ( scrollToGroup ) {
126+ scrollToGroup ( group . Name ) ;
127+ }
128+
129+ // On phone - close sidebar
130+ if ( ! ! isMobile ) {
131+ closeSidebar ( ) ;
132+ }
133+ } , [ dataModelDispatch , scrollToGroup , isMobile , closeSidebar ] ) ;
98134
99135 const handleSectionClick = useCallback ( ( sectionId : string , groupName : string ) => {
100136 // Use requestAnimationFrame to defer heavy operations
@@ -115,27 +151,31 @@ export const SidebarDatamodelView = ({ }: ISidebarDatamodelViewProps) => {
115151 scrollToSection ( sectionId ) ;
116152 }
117153 clearSearch ( ) ;
118-
119- // Clear loading section after a short delay to show the loading state
120- setTimeout ( ( ) => {
121- dataModelDispatch ( { type : 'SET_LOADING_SECTION' , payload : null } ) ;
122- } , 500 ) ;
123154 } ) ;
124155 } ) ;
125156 } , [ dataModelDispatch , scrollToSection , clearSearch ] ) ;
126157
127158 const NavItem = useCallback ( ( { group } : INavItemProps ) => {
128159 const isCurrentGroup = currentGroup ?. toLowerCase ( ) === group . Name . toLowerCase ( ) ;
129-
160+ const isExpanded = expandedGroups . has ( group . Name ) || isCurrentGroup ;
161+
130162 return (
131163 < Accordion
132164 disableGutters
133- expanded = { isCurrentGroup }
134- onClick = { ( ) => handleGroupClick ( group . Name ) }
135- className = { `group/accordion transition-all duration-300 w-full first:rounded-t-lg last:rounded-b-lg shadow-none p-1` }
165+ expanded = { isExpanded }
166+ onChange = { ( ) => handleGroupClick ( group . Name ) }
167+ className = { `group/accordion w-full first:rounded-t-lg last:rounded-b-lg shadow-none p-1` }
168+ slotProps = { {
169+ transition : {
170+ timeout : 300 ,
171+ }
172+ } }
136173 sx = { {
137174 backgroundColor : "background.paper" ,
138175 borderColor : 'border.main' ,
176+ '& .MuiCollapse-root' : {
177+ transition : 'height 0.3s cubic-bezier(0.4, 0, 0.2, 1)' ,
178+ }
139179 } }
140180 >
141181 < AccordionSummary
@@ -145,7 +185,7 @@ export const SidebarDatamodelView = ({ }: ISidebarDatamodelViewProps) => {
145185 isCurrentGroup ? "font-semibold" : "hover:bg-sidebar-accent hover:text-sidebar-primary"
146186 ) }
147187 sx = { {
148- backgroundColor : isCurrentGroup ? alpha ( theme . palette . primary . main , 0.1 ) : 'transparent' ,
188+ backgroundColor : isExpanded ? alpha ( theme . palette . primary . main , 0.1 ) : 'transparent' ,
149189 padding : '4px' ,
150190 minHeight : '32px !important' ,
151191 '& .MuiAccordionSummary-content' : {
@@ -157,24 +197,24 @@ export const SidebarDatamodelView = ({ }: ISidebarDatamodelViewProps) => {
157197 } }
158198 >
159199 < Typography
160- className = { `flex-1 text-sm text-left truncate min-w-0 ${ isCurrentGroup ? 'font-semibold' : '' } ` }
200+ className = { `flex-1 text-sm text-left truncate min-w-0 ${ isExpanded ? 'font-semibold' : '' } ` }
161201 sx = { {
162- color : isCurrentGroup ? 'primary.main' : 'text.primary'
202+ color : isExpanded ? 'primary.main' : 'text.primary'
163203 } }
164204 >
165205 { group . Name }
166206 </ Typography >
167- < Typography className = { `flex-shrink-0 text-xs mr-2 ${ isCurrentGroup ? 'font-semibold' : '' } ` } sx = { { opacity : 0.7 , color : isCurrentGroup ? 'primary.main' : 'text.primary' } } > { group . Entities . length } </ Typography >
207+ < Typography className = { `flex-shrink-0 text-xs mr-2 ${ isExpanded ? 'font-semibold' : '' } ` } sx = { { opacity : 0.7 , color : isExpanded ? 'primary.main' : 'text.primary' } } > { group . Entities . length } </ Typography >
168208
169209 < OpenInNewRounded
170210 onClick = { ( e ) => {
171211 e . stopPropagation ( ) ;
172- if ( group . Entities . length > 0 ) handleSectionClick ( group . Entities [ 0 ] . SchemaName , group . Name ) ;
212+ handleScrollToGroup ( group ) ;
173213 } }
174214 aria-label = { `Link to first entity in ${ group . Name } ` }
175215 className = "w-4 h-4 flex-shrink-0"
176216 sx = { {
177- color : isCurrentGroup ? "primary.main" : "default"
217+ color : isExpanded ? "primary.main" : "default"
178218 } }
179219 />
180220 </ AccordionSummary >
@@ -257,7 +297,7 @@ export const SidebarDatamodelView = ({ }: ISidebarDatamodelViewProps) => {
257297 </ AccordionDetails >
258298 </ Accordion >
259299 )
260- } , [ currentGroup , currentSection , theme , handleGroupClick , handleSectionClick , isEntityMatch , searchTerm , highlightText ] ) ;
300+ } , [ currentGroup , currentSection , theme , handleGroupClick , handleSectionClick , isEntityMatch , searchTerm , highlightText , expandedGroups , loadingSection ] ) ;
261301
262302 return (
263303 < Box className = "flex flex-col w-full p-2" >
0 commit comments