Multi Select
Several choices out of a list the control opens: the projects a page is narrowed to, the days a shop is open.
import com.pantrixui.components.overlay.multiselect.PantrixMultiSelect
- The text in the box. The box shows the labels of what is ticked, in the options' order and with a comma between them, and cuts what does not fit at one line. With nothing ticked,
placeholdershows. - Summary. Given
valueText, the box shows it in place of the labels while something is ticked: a count, or one word for all of them. - Search.
searchableputs a search field in the list that narrows the options by their labels. The focus stays in that field: the arrows move among the rows that match, and the keyboard's action key ticks or clears the row the arrows are on. - Options. The options are a select's
SelectOption: a value, a label and, if wanted, an icon. An option withenabled = falsestays in the list and is read, but cannot be ticked. - Form. A
PantrixMultiSelectthat takesfieldgets its choices, its error and its name from the form's field: the field's value is the set of what is ticked. - Error and disabled.
errorandenabledwork as in a select: an error turns the box's border, its label and the text under it to thedangerrole; a disabled box takes no focus and its list does not open. - Colours and style. It shares a select's style and colours:
SelectStyleandSelectColors, andTextFieldStyleandTextFieldColorsfor its box. A product that restyles its selects restyles these with them. The check boxes stay in the theme's colour. - Automation.
tagKeytags the boxMultiSelect_<tagKey>, its listMultiSelectList_<tagKey>and the search fieldMultiSelectSearch_<tagKey>. The options are named inside the box:MultiSelect_projects.MultiSelectOption_compose.
Examples
Projects
A filter that narrows a list by project. Every row is a check box: a press ticks it or clears it, and the list stays open. The box shows the labels of what is ticked, in the options' order, and the placeholder while nothing is ticked. Here all the projects are shown while none is ticked, and the box says so in its placeholder.
It has no label; contentDescription tells a screen reader its name. The card's "Last event" line says what onSelectionChange last asked for; most of the other examples have the same line.
var chosen by rememberSaveable { mutableStateOf(emptySet<String>()) }
val report = LocalExampleEvents.current
PantrixMultiSelect(
options = PROJECTS,
selected = chosen,
onSelectionChange = {
chosen = it
report(if (it.isEmpty()) "Showing all projects" else "Projects selected: ${it.size}")
},
modifier = Modifier.widthIn(max = 360.dp).fillMaxWidth(),
placeholder = "All projects (${PROJECTS.size})",
contentDescription = "Projects",
tagKey = "projects"
)
private val PROJECTS = listOf(
SelectOption(value = "compose", label = "Rorty Compose Android", icon = PantrixIcons.Folder, tagKey = "compose"),
SelectOption(value = "android", label = "Rorty Android", icon = PantrixIcons.Folder, tagKey = "android"),
SelectOption(value = "swiftui", label = "Rorty SwiftUI iOS", icon = PantrixIcons.Folder, tagKey = "swiftui"),
SelectOption(value = "uikit", label = "Rorty UIKIT iOS", icon = PantrixIcons.Folder, tagKey = "uikit")
)
Summary text
Given valueText, the box shows it in place of the labels while something is ticked: here "5 days". With many options ticked the box does not overflow.
With nothing ticked the placeholder still shows: clear every day and the box says "Closed".
var chosen by rememberSaveable { mutableStateOf(setOf("mon", "tue", "wed", "thu", "fri")) }
val report = LocalExampleEvents.current
PantrixMultiSelect(
options = DAYS,
selected = chosen,
onSelectionChange = {
chosen = it
report("Days selected: ${it.size}")
},
modifier = Modifier.widthIn(max = 360.dp).fillMaxWidth(),
label = "Open days",
placeholder = "Closed",
valueText = "${chosen.size} days",
tagKey = "open_days"
)
private val DAYS = listOf(
SelectOption(value = "mon", label = "Monday", tagKey = "mon"),
SelectOption(value = "tue", label = "Tuesday", tagKey = "tue"),
SelectOption(value = "wed", label = "Wednesday", tagKey = "wed"),
SelectOption(value = "thu", label = "Thursday", tagKey = "thu"),
SelectOption(value = "fri", label = "Friday", tagKey = "fri"),
SelectOption(value = "sat", label = "Saturday", tagKey = "sat"),
SelectOption(value = "sun", label = "Sunday", tagKey = "sun")
)
Searchable
searchable = true puts a search field in the list: the options narrow as you type. The focus stays in the search field: the arrows move among the rows that match, the keyboard's action key ticks or clears the row the arrows are on, and the search stays as it is.
Options the search does not show keep their ticks: tick a few languages, then search for something else. The labels in the box and the count in the card's "Last event" line do not change.
var chosen by rememberSaveable { mutableStateOf(emptySet<String>()) }
val report = LocalExampleEvents.current
PantrixMultiSelect(
options = SKILLS,
selected = chosen,
onSelectionChange = {
chosen = it
report("Languages selected: ${it.size}")
},
modifier = Modifier.widthIn(max = 360.dp).fillMaxWidth(),
label = "Languages you know",
placeholder = "Choose languages",
searchable = true,
searchPlaceholder = "Search languages…",
tagKey = "skills"
)
private val SKILLS = listOf(
SelectOption(value = "Kotlin", label = "Kotlin", tagKey = "kotlin"),
SelectOption(value = "Java", label = "Java", tagKey = "java"),
SelectOption(value = "Swift", label = "Swift", tagKey = "swift"),
SelectOption(value = "Dart", label = "Dart", tagKey = "dart"),
SelectOption(value = "TypeScript", label = "TypeScript", tagKey = "typescript"),
SelectOption(value = "JavaScript", label = "JavaScript", tagKey = "javascript"),
SelectOption(value = "Python", label = "Python", tagKey = "python"),
SelectOption(value = "Go", label = "Go", tagKey = "go"),
SelectOption(value = "Rust", label = "Rust", tagKey = "rust"),
SelectOption(value = "C#", label = "C#", tagKey = "csharp"),
SelectOption(value = "Ruby", label = "Ruby", tagKey = "ruby")
)
Partly disabled
SelectOption(enabled = false) disables a single option: the option stays in the list and is read, but it takes no press and the arrow keys pass over it. Its check box is drawn in the disabled colours too.
var chosen by rememberSaveable { mutableStateOf(setOf("mail")) }
val report = LocalExampleEvents.current
PantrixMultiSelect(
options = CHANNELS,
selected = chosen,
onSelectionChange = {
chosen = it
report("Notification channels selected: ${it.size}")
},
modifier = Modifier.widthIn(max = 360.dp).fillMaxWidth(),
label = "Notification channels",
placeholder = "No notifications",
supportingText = "SMS and calls are not in this plan.",
tagKey = "channels"
)
private val CHANNELS = listOf(
SelectOption(value = "mail", label = "Email", tagKey = "mail"),
SelectOption(value = "sms", label = "SMS", enabled = false, tagKey = "sms"),
SelectOption(value = "push", label = "Push notification", tagKey = "push"),
SelectOption(value = "call", label = "Phone call", enabled = false, tagKey = "call")
)
Form field
A PantrixMultiSelect that takes field is bound to a form's field: the field's value is the set of what is ticked, its error takes the place of the supporting text, and its id is the automation key (MultiSelect_roles). Rules.satisfies asks for at least one to be ticked.
Press "Invite" with nothing ticked: the rule's message takes the place of the supporting text and the focus comes here. Then tick a role and clear it: the error shows as soon as the last tick is cleared, because by then the field has been touched.
val form = rememberFormState()
val scope = rememberCoroutineScope()
val roles = form.field(
"roles",
initial = emptySet<String>(),
Rules.satisfies("Choose at least one role") { it.isNotEmpty() }
)
val report = LocalExampleEvents.current
PantrixMultiSelect(
field = roles,
options = ROLES,
modifier = Modifier.widthIn(max = 360.dp).fillMaxWidth(),
label = "Roles",
placeholder = "Choose roles",
supportingText = "A person can have more than one role."
)
Row(horizontalArrangement = Arrangement.spacedBy(PantrixTheme.spacing.sm)) {
PantrixButton(
text = "Invite",
onClick = { scope.launch { form.submit { report("Invitation sent. Roles: ${roles.value.size}") } } },
form = form,
tagKey = "submit_roles"
)
PantrixButton(
text = "Reset",
onClick = {
form.reset()
report("Form reset")
},
variant = ButtonVariant.Text,
tagKey = "reset_roles"
)
}
private val ROLES = listOf(
SelectOption(value = "admin", label = "Admin", tagKey = "admin"),
SelectOption(value = "editor", label = "Editor", tagKey = "editor"),
SelectOption(value = "viewer", label = "Viewer", tagKey = "viewer")
)
Error and disabled
error = true and enabled = false work as in a select. The border, the label and the text under a box in error are in the theme's danger role, and a screen reader announces that text as the error. A disabled box is drawn in the disabled colours, takes no focus and does not open its list; it still shows what is ticked.
PantrixMultiSelect(
options = ROLES,
selected = emptySet(),
onSelectionChange = {},
modifier = Modifier.widthIn(max = 360.dp).fillMaxWidth(),
label = "Roles",
placeholder = "Choose roles",
supportingText = "Choose at least one role",
error = true,
tagKey = "roles_in_error"
)
PantrixMultiSelect(
options = ROLES,
selected = setOf("editor", "viewer"),
onSelectionChange = {},
modifier = Modifier.widthIn(max = 360.dp).fillMaxWidth(),
enabled = false,
label = "Roles",
supportingText = "Only an admin can change the roles.",
tagKey = "roles_disabled"
)
private val ROLES = listOf(
SelectOption(value = "admin", label = "Admin", tagKey = "admin"),
SelectOption(value = "editor", label = "Editor", tagKey = "editor"),
SelectOption(value = "viewer", label = "Viewer", tagKey = "viewer")
)
Its own colours
colors = SelectColors(…) changes the list's ground, its text and its border; fieldColors = TextFieldColors(…) changes the box's focused border. Here the list is in the soft fill of the app's own purple.
The check boxes stay in the theme's colour. The selected row's fill (selectedContainer) is not used here: a list of several choices shows a choice by its check box and fills no row.
val own = ownColors()
var chosen by rememberSaveable { mutableStateOf(setOf("compose")) }
val report = LocalExampleEvents.current
PantrixMultiSelect(
options = PROJECTS,
selected = chosen,
onSelectionChange = {
chosen = it
report("Projects selected: ${it.size}")
},
modifier = Modifier.widthIn(max = 360.dp).fillMaxWidth(),
label = "Projects",
placeholder = "All projects (${PROJECTS.size})",
colors = SelectColors(container = own.container, content = own.onContainer, border = own.color),
fieldColors = TextFieldColors(focusedBorder = own.color),
tagKey = "own_projects"
)
private val PROJECTS = listOf(
SelectOption(value = "compose", label = "Rorty Compose Android", icon = PantrixIcons.Folder, tagKey = "compose"),
SelectOption(value = "android", label = "Rorty Android", icon = PantrixIcons.Folder, tagKey = "android"),
SelectOption(value = "swiftui", label = "Rorty SwiftUI iOS", icon = PantrixIcons.Folder, tagKey = "swiftui"),
SelectOption(value = "uikit", label = "Rorty UIKIT iOS", icon = PantrixIcons.Folder, tagKey = "uikit")
)
Style
The style is a select's too: style is the geometry of the list (SelectStyle), fieldStyle that of the box (TextFieldStyle). Here the list's rows are 56dp tall (itemHeight), and the list is 240dp at the most, with the options that do not fit scrolling inside it (maxHeight). The box's corners are fully round: TextFieldStyle(shape = ShapeRole.Full).
Only the fields that are given change; the rest comes from the theme's and the platform's style.
var chosen by rememberSaveable { mutableStateOf(setOf("sat", "sun")) }
PantrixMultiSelect(
options = DAYS,
selected = chosen,
onSelectionChange = { chosen = it },
modifier = Modifier.widthIn(max = 360.dp).fillMaxWidth(),
label = "Delivery days",
placeholder = "Choose days",
style = SelectStyle(maxHeight = 240.dp, itemHeight = 56.dp),
fieldStyle = TextFieldStyle(shape = ShapeRole.Full),
tagKey = "styled_days"
)
private val DAYS = listOf(
SelectOption(value = "mon", label = "Monday", tagKey = "mon"),
SelectOption(value = "tue", label = "Tuesday", tagKey = "tue"),
SelectOption(value = "wed", label = "Wednesday", tagKey = "wed"),
SelectOption(value = "thu", label = "Thursday", tagKey = "thu"),
SelectOption(value = "fri", label = "Friday", tagKey = "fri"),
SelectOption(value = "sat", label = "Saturday", tagKey = "sat"),
SelectOption(value = "sun", label = "Sunday", tagKey = "sun")
)
API
options
Several choices out of a list the control opens: the projects a page is narrowed to, the days a shop is open.
| Parameter | Type | Default | What it is |
|---|---|---|---|
options | List<SelectOption<T>> | the choices, in the order they are shown. With none, the list says so. | |
selected | Set<T> | the values that are chosen now: every option whose value is among them is ticked. | |
onSelectionChange | (Set<T>) -> Unit | asked for another set, with the whole of it: the one before with the pressed option's value put in or taken out. | |
modifier | Modifier | Modifier | the modifier of the whole field: its label, its box and what is under it. |
enabled | Boolean | true | whether the list can be opened. One that cannot be takes no focus, is drawn in the disabled colours and says so. |
label | String? | null | what the choice is, written above the box: the field's name for a screen reader, and what its list is announced as. |
placeholder | String? | null | shown in the box while nothing is chosen. |
supportingText | String? | null | help under the box. With error it is the message that says what to put right, which a screen reader announces as the error. |
error | Boolean | false | whether the choice is wrong: the border, the label and the supporting text turn to the danger role, and a screen reader is told. |
valueText | String? | null | what the box shows while something is chosen, in place of the chosen labels. |
searchable | Boolean | false | whether the list has a field to search its options by. |
searchPlaceholder | String? | null | what the search field shows while it is empty, and what it is called. The theme's word for a search where it is null. |
style | SelectStyle? | null | this call's own SelectStyle, the one its list is drawn with: over every enclosing one and under the density. Only the fields it sets change, on every platform. |
fieldStyle | TextFieldStyle? | null | this call's own TextFieldStyle, the one its box is drawn with: over every enclosing one and under the density. Only the fields it sets change, on every platform. |
colors | SelectColors? | null | its list's own colours, in place of the theme's roles: the ones it sets are used, and the rest stay the theme's. The check boxes are the theme's. |
fieldColors | TextFieldColors? | null | its box's own colours, as a text field takes them. A disabled one is drawn in the theme's disabled colours whatever is given. |
contentDescription | String? | null | what a screen reader says for the field: its name where it has no label. |
tagKey | String? | null | the field's automation key: the field is MultiSelect_<tagKey>, its list MultiSelectList_<tagKey>, the list's search field MultiSelectSearch_<tagKey>, and its options are named inside it, MultiSelect_<tagKey>.MultiSelectOption_<option's key>. It reports that its list opened and that an option was ticked or cleared, by the option's key and never by its label. |
interactionSource | MutableInteractionSource? | null | the field's interactions, hoisted: its focus, its hover and its presses. It keeps a source of its own when this is null. |
presentation | OverlayPresentation | OverlayPresentation.Popover | how the list is shown: under the field, by default; as a sheet from the bottom of the window, its title the field's label; or the one in a narrow window and the other in a wider one. Chosen as the list opens, it stays until the list has closed. |
field
A multi select bound to a form's field: its choices, its error and its name come from field.
| Parameter | Type | Default | What it is |
|---|---|---|---|
field | FormField<Set<T>> | the field the control shows and changes: a set of the options' values. | |
options | List<SelectOption<T>> | the choices, in the order they are shown. | |
modifier | Modifier | Modifier | the modifier of the whole field: its label, its box and what is under it. |
enabled | Boolean | true | whether the list can be opened. |
label | String? | null | what the choice is, written above the box: the field's name for a screen reader. |
placeholder | String? | null | shown in the box while nothing is chosen. |
supportingText | String? | null | help under the box, while the field has no error to show there. |
valueText | String? | null | what the box shows while something is chosen, in place of the chosen labels. |
searchable | Boolean | false | whether the list has a field to search its options by. |
searchPlaceholder | String? | null | what the search field shows while it is empty, and what it is called. |
style | SelectStyle? | null | this call's own SelectStyle, the one its list is drawn with. |
fieldStyle | TextFieldStyle? | null | this call's own TextFieldStyle, the one its box is drawn with. |
colors | SelectColors? | null | its list's own colours: the ones it sets, with the theme's for the rest. |
fieldColors | TextFieldColors? | null | its box's own colours, as a text field takes them. |
contentDescription | String? | null | what a screen reader says for the field where it has no label. |
interactionSource | MutableInteractionSource? | null | the field's interactions, hoisted. It keeps a source of its own when this is null. |
presentation | OverlayPresentation | OverlayPresentation.Popover | how the list is shown: under the field, as a sheet, or by the window's width. |
SelectStyle
The list a PantrixSelect opens: the panel it is drawn in, how far that sits from the box it belongs to and from the edges of the window, and its rows.
| Parameter | Type | Default | What it is |
|---|---|---|---|
shape | ShapeSpec | ShapeSpec.Unspecified | The panel's corners: the medium step of the shape scale on every platform, as the box's are. |
maxHeight | Dp | Dp.Unspecified | The tallest the panel gets: more options than fit scroll. |
elevation | Dp | Dp.Unspecified | How high the panel floats over the app, which is how large its shadow is. |
gap | Dp | Dp.Unspecified | Between the box and its list. |
margin | Dp | Dp.Unspecified | Kept clear between the panel and the edges of the window, and of what covers the window there: a system bar, a cutout, the keyboard. |
verticalPadding | Dp | Dp.Unspecified | Above the first row and below the last. |
itemHeight | Dp | Dp.Unspecified | The least a row is tall, where the platform's touch target is not taller. |
horizontalPadding | Dp | Dp.Unspecified | From the panel's edge to what a row starts with, and from what it ends with to the other edge. Each preset has the text field's own, so that an option's label starts where the box's text does. |
iconSize | Dp | Dp.Unspecified | An option's icon, and the tick beside the chosen one. |
iconGap | Dp | Dp.Unspecified | Between a row's parts: its check box, its icon, its label and its tick. |
SelectColors
What the list of a PantrixSelect is painted with, for a call that does not take the theme's colours: a colour that is set is used, and one that is left out stays the theme's. The box's colours are TextFieldColors.
| Parameter | Type | Default | What it is |
|---|---|---|---|
container | Color | Color.Unspecified | The panel's fill. The theme's surface. |
content | Color | Color.Unspecified | An option's label and icon. The theme's textPrimary. |
selectedContainer | Color | Color.Unspecified | The fill of the chosen option's row, in a select of one choice. The primary role's container. A select of several marks its choices with check boxes, and fills no row. |
selectedContent | Color | Color.Unspecified | The chosen option's label, its icon and the tick beside it, in a select of one choice. The primary role's onContainer. |
border | Color | Color.Unspecified | The hairline around the panel, which tells it from a surface of its own colour under it, as in the dark theme, where a shadow shows little, and the line between the rows and the field a list is searched by. The theme's divider on the theme's own grounds. On a fill, where that line would not read, a share of content: the edge a subtle button has. A transparent colour draws none. |
Accessibility
- Keyboard. It is a select's: Enter or the space bar opens the list, the arrows move among the rows and go round from the last to the first, and Home and End go to the first and the last row. Enter and the space bar tick or clear.