Skip to content

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

kotlin
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.PopupPositionProvider

Basic 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.

kotlin
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

kotlin
OverlayCascadingListPopup(
    show = showPopup,
    entries = entries,
    onDismissRequest = { showPopup = false },
    alignment = PopupPositionProvider.Align.Start,
)

Disable Window Dimming

kotlin
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).

kotlin
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.

kotlin
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 NameTypeDescriptionDefault Value
showBooleanWhether to show the popup.-
entriesList<DropdownEntry>Grouped dropdown entries; top-level items with non-empty children become submenu triggers.-
onDismissRequest() -> UnitCalled when the user requests dismissal (e.g., clicking outside, tapping the back button).-
popupModifierModifierModifier applied to the popup body.Modifier
onDismissFinished(() -> Unit)?Called after the exit animation finishes.null
popupPositionProviderPopupPositionProviderPosition strategy for the primary popup relative to its anchor.ListPopupDefaults.DropdownPositionProvider
alignmentPopupPositionProvider.AlignAlignment of the primary popup.PopupPositionProvider.Align.End
enableWindowDimBooleanWhether to dim the rest of the window while the popup is shown.true
maxHeightDp?Maximum height of either side. Null bounds it by the safe area.null
minWidthDpMinimum width of the popup.200.dp
renderInRootScaffoldBooleanWhether 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
dropdownColorsDropdownColorsColors used by every row.DropdownDefaults.dropdownColors()
collapseOnSelectionBooleanWhen true, selecting any leaf dismisses the popup.true
Property NameTypeDescriptionDefault ValueRequired
itemsList<DropdownItem>Items shown in this dropdown group-Yes
enabledBooleanWhether this group is enabled. False disables all items; true still respects each item's enabled statetrueNo
titleString?Optional non-clickable group header rendered above the items (12sp medium, secondary label, max 2 lines)nullNo
Property NameTypeDescriptionDefault ValueRequired
textStringText shown for the item-Yes
enabledBooleanWhether the item can be clicked. Disabled items are graytrueNo
selectedBooleanWhether the item is selectedfalseNo
onClick(() -> Unit)?Callback invoked when the item is clicked. Ignored when children is non-empty (the click expands the submenu instead)nullNo
icon@Composable ((Modifier) -> Unit)?Icon shown before the item textnullNo
summaryString?Summary text shown below the item textnullNo
childrenList<DropdownItem>?Optional submenu items; only the cascading variants render these as a submenu (depth limited to 2)nullNo
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 disablednullNo
alertBooleanWhether this is an alert (destructive) item; its title uses the error colorfalseNo
Property NameTypeDescription
contentColorColorColor of the option title
summaryColorColorColor of the option summary
containerColorColorBackground color of the option
selectedContentColorColorTitle color of the selected option
selectedSummaryColorColorSummary color of the selected option
selectedContainerColorColorBackground color of the selected option
selectedIndicatorColorColorColor of the selected indicator icon
disabledContentColorColorTitle color of a disabled option
alertContentColorColorTitle color of an alert option
headerColorColorTitle color of a group header row

PopupPositionProvider.Align

ValueDescription
StartAligns the popup to the start of the anchor.
EndAligns the popup to the end of the anchor.
TopStartAligns the popup to the top-start of the anchor.
TopEndAligns the popup to the top-end of the anchor.
BottomStartAligns the popup to the bottom-start of the anchor.
BottomEndAligns the popup to the bottom-end of the anchor.

Changelog

Released under the Apache-2.0 License