Skip to content

WindowSpinnerPreference

WindowSpinnerPreference is a dropdown selector component in COUI that provides titles, summaries, and a list of options with icons and text. It renders at the window level without needing a Scaffold host, making it suitable for use cases where Scaffold is not available or desired.

Note

This component does not rely on Scaffold and can be used in any Composable scope.

Import

kotlin
import io.github.suqi8.coui.kmp.preference.WindowSpinnerPreference
import io.github.suqi8.coui.kmp.basic.DropdownEntry
import io.github.suqi8.coui.kmp.basic.DropdownItem

Basic Usage

The WindowSpinnerPreference component provides basic dropdown selector functionality:

kotlin
var selectedIndex by remember { mutableStateOf(0) }
val options = listOf(
    DropdownItem(text = "Option 1"),
    DropdownItem(text = "Option 2"),
    DropdownItem(text = "Option 3"),
)

WindowSpinnerPreference(
    title = "Dropdown Selector",
    items = options,
    selectedIndex = selectedIndex,
    onSelectedIndexChange = { selectedIndex = it }
)

Options with Icons and Summaries

kotlin
// Create a rounded rectangle Painter
private class RoundedRectanglePainter(
    private val cornerRadius: Dp = 6.dp
) : Painter() {
    override val intrinsicSize = Size.Unspecified
    override fun DrawScope.onDraw() {
        drawRoundRect(
            color = Color.White,
            size = Size(size.width, size.height),
            cornerRadius = CornerRadius(cornerRadius.toPx(), cornerRadius.toPx())
        )
    }
}

var selectedIndex by remember { mutableStateOf(0) }
val options = listOf(
    DropdownItem(
        icon = { modifier -> Icon(RoundedRectanglePainter(), "Icon", modifier, Color(0xFFFF5B29)) },
        text = "Red Theme",
        summary = "Energetic Red"
    ),
    DropdownItem(
        icon = { modifier -> Icon(RoundedRectanglePainter(), "Icon", modifier, Color(0xFF3482FF)) },
        text = "Blue Theme",
        summary = "Calm Blue"
    ),
)

WindowSpinnerPreference(
    title = "Menu",
    items = options,
    selectedIndex = selectedIndex,
    onSelectedIndexChange = { selectedIndex = it }
)

Component State

Disabled State

kotlin
WindowSpinnerPreference(
    title = "Disabled Spinner",
    summary = "This spinner is currently unavailable",
    items = listOf(DropdownItem(text = "Option 1")),
    selectedIndex = 0,
    onSelectedIndexChange = {},
    enabled = false
)

Dialog Mode

WindowSpinnerPreference also supports a dialog mode, which is useful for displaying a larger list of options or when a more prominent selection interface is needed. This mode is activated by providing a dialogButtonString.

kotlin
var selectedIndex by remember { mutableStateOf(0) }
val options = listOf(
    DropdownItem(text = "Option A"),
    DropdownItem(text = "Option B"),
    DropdownItem(text = "Option C")
)

WindowSpinnerPreference(
    title = "Dialog Selector",
    dialogButtonString = "Cancel",
    items = options,
    selectedIndex = selectedIndex,
    onSelectedIndexChange = { selectedIndex = it },
)

Grouped Options

Use DropdownEntry when you need a custom item list with selected states, disabled items, item callbacks, icons, or summaries. Use entries to show multiple option groups separated by dividers.

kotlin
var firstSelectedIndex by remember { mutableStateOf(0) }
var secondSelectedIndex by remember { mutableStateOf(0) }
val entries = listOf(
    DropdownEntry(
        items = listOf("Small", "Medium").mapIndexed { index, text ->
            DropdownItem(text = text, selected = firstSelectedIndex == index, onClick = { firstSelectedIndex = index })
        }
    ),
    DropdownEntry(
        items = listOf("Red", "Green", "Blue").mapIndexed { index, text ->
            DropdownItem(text = text, selected = secondSelectedIndex == index, onClick = { secondSelectedIndex = index })
        }
    )
)

WindowSpinnerPreference(
    title = "Grouped Selector",
    entries = entries,
    collapseOnSelection = false
)

For the entries overload, collapseOnSelection controls whether the popup closes after an item is selected. It defaults to entries.size <= 1, so a single group closes after selection while multiple groups stay open for consecutive changes. The same entry and entries overloads are also available in dialog mode by providing dialogButtonString.

Multi Select

Because selection state lives on DropdownItem, multiple items can be selected by keeping a set of selected values and toggling each item from onClick.

kotlin
var selectedItems by remember { mutableStateOf(setOf("A1", "B2")) }
val entries = listOf(
    DropdownEntry(
        items = listOf("A1", "A2").map { text ->
            DropdownItem(
                text = text,
                selected = text in selectedItems,
                onClick = {
                    selectedItems = if (text in selectedItems) selectedItems - text else selectedItems + text
                }
            )
        }
    ),
    DropdownEntry(
        items = listOf("B1", "B2", "B3").map { text ->
            DropdownItem(
                text = text,
                selected = text in selectedItems,
                onClick = {
                    selectedItems = if (text in selectedItems) selectedItems - text else selectedItems + text
                }
            )
        }
    )
)

WindowSpinnerPreference(
    title = "Multi Select Selector",
    entries = entries,
    collapseOnSelection = false
)

Observe Expanded State

kotlin
var selectedIndex by remember { mutableStateOf(0) }
var expanded by remember { mutableStateOf(false) }
val options = listOf(
    DropdownItem(text = "Option A"),
    DropdownItem(text = "Option B"),
    DropdownItem(text = "Option C")
)

WindowSpinnerPreference(
    title = "Dropdown Selector",
    summary = if (expanded) "Expanded" else "Collapsed",
    items = options,
    selectedIndex = selectedIndex,
    onExpandedChange = { expanded = it },
    onSelectedIndexChange = { selectedIndex = it }
)

Properties

WindowSpinnerPreference Properties (Popup Mode)

Property NameTypeDescriptionDefault ValueRequired
itemsList<DropdownItem>List of dropdown items-Yes
selectedIndexIntIndex of currently selected item-Yes
titleStringTitle of the spinner-Yes
modifierModifierModifier applied to the componentModifierNo
titleColorBasicComponentColorsTitle text color configurationBasicComponentDefaults.titleColor()No
summaryString?Summary descriptionnullNo
summaryColorBasicComponentColorsSummary text color configurationBasicComponentDefaults.summaryColor()No
spinnerColorsDropdownColorsColor configuration for spinnerDropdownDefaults.dropdownColors()No
startAction@Composable (() -> Unit)?Custom start side contentnullNo
bottomAction@Composable (() -> Unit)?Custom bottom side contentnullNo
insideMarginPaddingValuesInternal content paddingBasicComponentDefaults.InsideMarginNo
cardListPositionCardListPositionRow position inside its card group; rounded outer edges gain extra paddingCardListPosition.NoneNo
maxHeightDp?Maximum height of popupnullNo
enabledBooleanWhether component is interactivetrueNo
showValueBooleanWhether to show the selected valuetrueNo
onExpandedChange((Boolean) -> Unit)?Callback when expanded state changesnullNo
onSelectedIndexChange((Int) -> Unit)?Selection change callbacknullNo

Entry Overload Properties

Property NameTypeDescriptionDefault ValueRequired
entryDropdownEntrySingle dropdown entry group-Yes
collapseOnSelectionBooleanWhether to close the popup after selectiontrueNo

Grouped Entries Overload Properties

Property NameTypeDescriptionDefault ValueRequired
entriesList<DropdownEntry>Dropdown entry groups separated by dividers-Yes
collapseOnSelectionBooleanWhether to close the popup after each selectionentries.size <= 1No

All other parameters are identical to the items overload above. The entry and entries overloads do not take onSelectedIndexChange — use each DropdownItem.onClick instead.

WindowSpinnerPreference Properties (Dialog Mode)

Property NameTypeDescriptionDefault ValueRequired
itemsList<DropdownItem>List of dropdown items-Yes
selectedIndexIntIndex of currently selected item-Yes
titleStringTitle of the spinner-Yes
dialogButtonStringStringText for the dialog button-Yes
modifierModifierModifier applied to the componentModifierNo
popupModifierModifierModifier for the popup dialogModifierNo
titleColorBasicComponentColorsTitle text color configurationBasicComponentDefaults.titleColor()No
summaryString?Summary descriptionnullNo
summaryColorBasicComponentColorsSummary text color configurationBasicComponentDefaults.summaryColor()No
spinnerColorsDropdownColorsColor configuration for spinnerDropdownDefaults.dialogDropdownColors()No
startAction@Composable (() -> Unit)?Custom start side contentnullNo
bottomAction@Composable (() -> Unit)?Custom bottom side contentnullNo
insideMarginPaddingValuesInternal content paddingBasicComponentDefaults.InsideMarginNo
cardListPositionCardListPositionRow position inside its card group; rounded outer edges gain extra paddingCardListPosition.NoneNo
enabledBooleanWhether component is interactivetrueNo
showValueBooleanWhether to show the selected valuetrueNo
collapseOnSelectionBooleanWhether to close the dialog after selectiontrueNo
onExpandedChange((Boolean) -> Unit)?Callback when expanded state changesnullNo
onSelectedIndexChange((Int) -> Unit)?Selection change callbacknullNo

Dialog Entry Overload Properties

Property NameTypeDescriptionDefault ValueRequired
entryDropdownEntrySingle dropdown entry group-Yes
dialogButtonStringStringText for the dialog button-Yes
collapseOnSelectionBooleanWhether to close the dialog after selectiontrueNo

Dialog Grouped Entries Overload Properties

Property NameTypeDescriptionDefault ValueRequired
entriesList<DropdownEntry>Dropdown entry groups separated by dividers-Yes
dialogButtonStringStringText for the dialog button-Yes
collapseOnSelectionBooleanWhether to close the dialog after each selectionentries.size <= 1No

All other parameters are identical to the dialog-mode items overload above. The entry and entries overloads do not take onSelectedIndexChange — use each DropdownItem.onClick instead.

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

Group titles are reserved for future use. The original COUI dropdown style currently has no matching group-title presentation, so the title field is not exposed yet.

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 clickednullNo
icon@Composable ((Modifier) -> Unit)?Icon shown before the item textnullNo
summaryString?Summary text shown below the item textnullNo
childrenList<DropdownItem>?Optional submenu items; cascading variants onlynullNo
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

Changelog

Released under the Apache-2.0 License