OverlayCascadingListPopup
OverlayCascadingListPopup is a popup list that supports a two-level cascading menu. Items whose DropdownItem.children is non-empty become submenu triggers; tapping such an item morphs the popup into a secondary list anchored at the trigger row, while the primary list scales down behind a translucent mask. Cascading depth is limited to 2.
Prerequisite
This component depends on Scaffold providing COUIPopupHost to render popup content. It must be used within Scaffold, otherwise popup content will not render correctly.
Import
import io.github.suqi8.coui.kmp.overlay.OverlayCascadingListPopup
import io.github.suqi8.coui.kmp.basic.DropdownEntry
import io.github.suqi8.coui.kmp.basic.DropdownItem
import io.github.suqi8.coui.kmp.basic.PopupPositionProviderBasic Usage
Build entries so that some DropdownItems carry a non-empty children list. The component renders a chevron next to such trigger rows; tapping the trigger expands the second level inline.
var showPopup by remember { mutableStateOf(false) }
var sortIndex by remember { mutableStateOf(0) }
var viewIndex by remember { mutableStateOf(0) }
val sortLabels = listOf("Sort by capture date", "Sort by date added")
val viewLabels = listOf("Group by date", "Compact")
val entries = listOf(
DropdownEntry(
items = sortLabels.mapIndexed { idx, label ->
DropdownItem(
text = label,
selected = sortIndex == idx,
onClick = { sortIndex = idx },
)
},
),
DropdownEntry(
items = listOf(
DropdownItem(
text = "View mode",
children = viewLabels.mapIndexed { idx, label ->
DropdownItem(
text = label,
selected = viewIndex == idx,
onClick = { viewIndex = idx },
)
},
),
),
),
)
Scaffold {
Box {
TextButton(
text = "Click to show menu",
onClick = { showPopup = true },
)
OverlayCascadingListPopup(
show = showPopup,
entries = entries,
onDismissRequest = { showPopup = false },
)
}
}Cascading Depth
Cascading depth is capped at 2. Items at the secondary level cannot have their own children; deeper trees are silently ignored.
Component States
Different Alignments
OverlayCascadingListPopup(
show = showPopup,
entries = entries,
onDismissRequest = { showPopup = false },
alignment = PopupPositionProvider.Align.Start,
)Disable Window Dimming
OverlayCascadingListPopup(
show = showPopup,
entries = entries,
onDismissRequest = { showPopup = false },
enableWindowDim = false,
)Keep Popup Open After Selection
By default the popup closes when the user picks any leaf item. Set collapseOnSelection = false to keep it open (e.g., for multi-select within a submenu).
OverlayCascadingListPopup(
show = showPopup,
entries = entries,
onDismissRequest = { showPopup = false },
collapseOnSelection = false,
)Selected Item Marking
Leaf items with selected = true show a trailing check icon tinted with DropdownColors.selectedIndicatorColor; submenu trigger rows always show a trailing chevron instead.
DropdownItem(
text = "Sort by date added",
selected = true, // Marks this leaf with a trailing check icon
onClick = { /* ... */ },
)Back Navigation and Morph Behavior
Back handling mirrors the outside-tap rule: while the secondary list is shown, a back gesture (or a tap outside the popup, or a tap on the cloned submenu header) collapses the secondary back into the primary list; at the primary level, back and outside taps dismiss the popup via onDismissRequest. Predictive back gestures preview the collapse/dismiss and restore the popup when cancelled — no extra configuration is needed.
Properties
OverlayCascadingListPopup
| Property Name | Type | Description | Default Value |
|---|---|---|---|
| show | Boolean | Whether to show the popup. | - |
| entries | List<DropdownEntry> | Grouped dropdown entries; top-level items with non-empty children become submenu triggers. | - |
| onDismissRequest | () -> Unit | Called when the user requests dismissal (e.g., clicking outside, tapping the back button). | - |
| popupModifier | Modifier | Modifier applied to the popup body. | Modifier |
| onDismissFinished | (() -> Unit)? | Called after the exit animation finishes. | null |
| popupPositionProvider | PopupPositionProvider | Position strategy for the primary popup relative to its anchor. | ListPopupDefaults.DropdownPositionProvider |
| alignment | PopupPositionProvider.Align | Alignment of the primary popup. | PopupPositionProvider.Align.End |
| enableWindowDim | Boolean | Whether to dim the rest of the window while the popup is shown. | true |
| maxHeight | Dp? | Maximum height of either side. Null bounds it by the safe area. | null |
| minWidth | Dp | Minimum width of the popup. | 200.dp |
| renderInRootScaffold | Boolean | Whether to render the popup in the root (outermost) Scaffold. When true, the popup covers the full screen. When false, it renders within the current Scaffold's bounds with position compensation. | true |
| dropdownColors | DropdownColors | Colors used by every row. | DropdownDefaults.dropdownColors() |
| collapseOnSelection | Boolean | When true, selecting any leaf dismisses the popup. | true |
DropdownEntry
| Property Name | Type | Description | Default Value | Required |
|---|---|---|---|---|
| items | List<DropdownItem> | Items shown in this dropdown group | - | Yes |
| enabled | Boolean | Whether this group is enabled. False disables all items; true still respects each item's enabled state | true | No |
| title | String? | Optional non-clickable group header rendered above the items (12sp medium, secondary label, max 2 lines) | null | No |
DropdownItem
| Property Name | Type | Description | Default Value | Required |
|---|---|---|---|---|
| text | String | Text shown for the item | - | Yes |
| enabled | Boolean | Whether the item can be clicked. Disabled items are gray | true | No |
| selected | Boolean | Whether the item is selected | false | No |
| onClick | (() -> Unit)? | Callback invoked when the item is clicked. Ignored when children is non-empty (the click expands the submenu instead) | null | No |
| icon | @Composable ((Modifier) -> Unit)? | Icon shown before the item text | null | No |
| summary | String? | Summary text shown below the item text | null | No |
| children | List<DropdownItem>? | Optional submenu items; only the cascading variants render these as a submenu (depth limited to 2) | null | No |
| hint | @Composable (() -> Unit)? | Optional trailing hint slot (badge, red dot, short count) shown before the selection indicator, width-capped at 40dp. Hidden entirely while the row is disabled | null | No |
| alert | Boolean | Whether this is an alert (destructive) item; its title uses the error color | false | No |
DropdownColors
| Property Name | Type | Description |
|---|---|---|
| contentColor | Color | Color of the option title |
| summaryColor | Color | Color of the option summary |
| containerColor | Color | Background color of the option |
| selectedContentColor | Color | Title color of the selected option |
| selectedSummaryColor | Color | Summary color of the selected option |
| selectedContainerColor | Color | Background color of the selected option |
| selectedIndicatorColor | Color | Color of the selected indicator icon |
| disabledContentColor | Color | Title color of a disabled option |
| alertContentColor | Color | Title color of an alert option |
| headerColor | Color | Title color of a group header row |
PopupPositionProvider.Align
| Value | Description |
|---|---|
| Start | Aligns the popup to the start of the anchor. |
| End | Aligns the popup to the end of the anchor. |
| TopStart | Aligns the popup to the top-start of the anchor. |
| TopEnd | Aligns the popup to the top-end of the anchor. |
| BottomStart | Aligns the popup to the bottom-start of the anchor. |
| BottomEnd | Aligns the popup to the bottom-end of the anchor. |
