Skip to content

Commit 5a3e685

Browse files
committed
fix(sidebar): persist disclosure group state across hide/show cycles
Disclosure groups were losing their expanded/collapsed state when the sidebar was hidden and shown again, because AnimatedVisibility removes them from the composition tree, destroying local remember() state. Now, Scaffold remembers a shared SnapshotStateMap above AnimatedVisibility and provides it via LocalSidebarDisclosureStates. DisclosureItem reads/writes from this map when available, falling back to local state when used standalone.
1 parent fb659f9 commit 5a3e685

3 files changed

Lines changed: 29 additions & 3 deletions

File tree

macosui/src/commonMain/kotlin/io/github/kdroidfilter/nucleus/ui/apple/macos/components/Scaffold.kt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import androidx.compose.ui.draw.drawWithContent
2424
import androidx.compose.runtime.Composable
2525
import androidx.compose.runtime.CompositionLocalProvider
2626
import androidx.compose.runtime.getValue
27+
import androidx.compose.runtime.mutableStateMapOf
2728
import androidx.compose.runtime.mutableStateOf
2829
import androidx.compose.runtime.remember
2930
import androidx.compose.runtime.setValue
@@ -47,6 +48,7 @@ import io.github.kdroidfilter.nucleus.ui.apple.macos.theme.LocalSidebarWidth
4748
import io.github.kdroidfilter.nucleus.ui.apple.macos.theme.LocalTitleBarHeight
4849
import io.github.kdroidfilter.nucleus.ui.apple.macos.theme.SidebarResizeCallbacks
4950
import io.github.kdroidfilter.nucleus.ui.apple.macos.theme.LocalToolbarGlassState
51+
import io.github.kdroidfilter.nucleus.ui.apple.macos.theme.LocalSidebarDisclosureStates
5052
import io.github.kdroidfilter.nucleus.ui.apple.macos.theme.LocalSidebarHide
5153
import io.github.kdroidfilter.nucleus.ui.apple.macos.theme.LocalSidebarVisible
5254
import io.github.kdroidfilter.nucleus.ui.apple.macos.theme.macosSpring
@@ -171,6 +173,10 @@ fun Scaffold(
171173
Color.Transparent
172174
}
173175

176+
// Disclosure expanded states — remembered above AnimatedVisibility so they
177+
// survive sidebar hide/show cycles.
178+
val sidebarDisclosureStates = remember { mutableStateMapOf<String, Boolean>() }
179+
174180
CompositionLocalProvider(LocalTitleBarHeight provides topPadding) {
175181

176182
@Composable
@@ -214,6 +220,7 @@ fun Scaffold(
214220
LocalSidebarResize provides sidebarResizeCallbacks,
215221
LocalSidebarHide provides hideCallback,
216222
LocalSidebarVisible provides showSidebar,
223+
LocalSidebarDisclosureStates provides sidebarDisclosureStates,
217224
) {
218225
sidebar()
219226
}

macosui/src/commonMain/kotlin/io/github/kdroidfilter/nucleus/ui/apple/macos/components/Sidebar.kt

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ import io.github.kdroidfilter.nucleus.ui.apple.macos.components.VerticalScrollba
4848
import io.github.kdroidfilter.nucleus.ui.apple.macos.components.TrackClickBehavior
4949
import io.github.kdroidfilter.nucleus.ui.apple.macos.components.rememberScrollbarState
5050
import io.github.kdroidfilter.nucleus.ui.apple.macos.components.Text
51+
import io.github.kdroidfilter.nucleus.ui.apple.macos.theme.LocalSidebarDisclosureStates
5152
import io.github.kdroidfilter.nucleus.ui.apple.macos.icons.Icons
5253
import io.github.kdroidfilter.nucleus.ui.apple.macos.icons.LucideChevronsLeft
5354
import io.github.kdroidfilter.nucleus.ui.apple.macos.icons.Icon
@@ -721,7 +722,16 @@ private fun DisclosureItem(
721722
itemColors: SidebarItemColors,
722723
indentLevel: Int = 0,
723724
) {
724-
var expanded by remember { mutableStateOf(item.initiallyExpanded) }
725+
// When inside a Scaffold, use the shared map that survives AnimatedVisibility.
726+
// Otherwise, fall back to local state.
727+
val sharedStates = LocalSidebarDisclosureStates.current
728+
var localExpanded by remember { mutableStateOf(item.initiallyExpanded) }
729+
val expanded = sharedStates?.getOrPut(item.id) { item.initiallyExpanded } ?: localExpanded
730+
val toggleExpanded = {
731+
val newValue = !expanded
732+
if (sharedStates != null) sharedStates[item.id] = newValue
733+
localExpanded = newValue
734+
}
725735
val chevronRotation by animateFloatAsState(
726736
targetValue = if (expanded) 90f else 0f,
727737
animationSpec = sidebarSpring(),
@@ -761,7 +771,7 @@ private fun DisclosureItem(
761771
.clickable(
762772
interactionSource = remember { MutableInteractionSource() },
763773
indication = null,
764-
) { expanded = !expanded },
774+
) { toggleExpanded() },
765775
contentAlignment = Alignment.Center,
766776
) {
767777
Icon(
@@ -777,7 +787,7 @@ private fun DisclosureItem(
777787
SidebarItemRow(
778788
label = item.label,
779789
onClick = {
780-
expanded = !expanded
790+
toggleExpanded()
781791
item.onClick()
782792
},
783793
active = activeItem == item.id,

macosui/src/commonMain/kotlin/io/github/kdroidfilter/nucleus/ui/apple/macos/theme/Glass.kt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import androidx.compose.foundation.background
44
import androidx.compose.runtime.Composable
55
import androidx.compose.runtime.Immutable
66
import androidx.compose.runtime.compositionLocalOf
7+
import androidx.compose.runtime.snapshots.SnapshotStateMap
78
import androidx.compose.ui.Modifier
89
import androidx.compose.ui.draw.clip
910
import androidx.compose.ui.draw.drawWithContent
@@ -74,6 +75,14 @@ val LocalSidebarHide = compositionLocalOf<(() -> Unit)?> { null }
7475
*/
7576
val LocalSidebarVisible = compositionLocalOf { true }
7677

78+
/**
79+
* Stores the expanded/collapsed state of sidebar disclosure items by their ID.
80+
* Provided by [Scaffold][io.github.kdroidfilter.nucleus.ui.apple.macos.components.Scaffold]
81+
* above [AnimatedVisibility] so the state survives sidebar hide/show cycles.
82+
* Null when no scaffold is providing state (disclosure items fall back to local remember).
83+
*/
84+
val LocalSidebarDisclosureStates = compositionLocalOf<SnapshotStateMap<String, Boolean>?> { null }
85+
7786
/**
7887
* The two glass appearance variants matching macOS 26.
7988
*

0 commit comments

Comments
 (0)