Segment Button
A segment button: a small set of choices side by side in one control, one of them chosen.
import com.pantrixui.components.elements.segmentbutton.PantrixSegmentButton
- The sliding pill. The pill slides under the segment that is selected, and that segment's label is written in the
primarycolour. Pressing the selected segment again does nothing. - Width.
fullWidth(the default) spans the control across the width it is given and gives the segments equal shares: the segments stay where they are whatever their labels say in another language.fullWidth = falsemakes each segment as wide as its label. - Icons.
SegmentOption(icon = …)puts an icon before the label, in the label's colour; a screen reader passes over it. - A segment that cannot be selected.
SegmentOption(enabled = false)disables a single segment: a press on it does nothing and the arrow keys skip it. The control'senabled = falsedisables them all; the pill stays where it is and the selection can still be read. - With none selected. If
selectedis not the value of any option, and null is such a value, no segment is selected and no pill is drawn. - Platform. On Android it is a fully round pill 40dp tall. On iOS it is 32dp tall, on desktop 30dp and on the web 34dp: a rectangle with medium rounded corners.
- Its own colours and style.
SegmentButtonColorsgives the colour of the track, of the pill, and of the selected and the unselected labels;SegmentButtonStylegives the track's shape, its height and its paddings.labelStylechanges the labels' type; their colours come from the segment's state. - Font scale. The track is as tall as the style says, and the platform's touch target is reserved around it. When the system's font size is made larger, a label taller than the track makes the track taller too.
Examples
Segments that share the width equally
At its plainest: the options, the selected value and onSelect. The control spans the width it is given and the three segments share it equally (fullWidth, the default). The choice is applied at once: the line under the control changes with what is chosen.
The pill slides under the segment that is selected, and that segment's label is written in the primary colour. With a keyboard the control is one Tab stop; the arrow keys move the selection to the next segment or to the previous one.
var sort by rememberSaveable { mutableStateOf("newest") }
PantrixSegmentButton(
options = SORTS,
selected = sort,
onSelect = { sort = it },
modifier = Modifier.widthIn(max = 480.dp),
tagKey = "sort"
)
PantrixText(
text = "Sort order: ${SORTS.first { it.value == sort }.label}",
style = PantrixTheme.typography.footnote,
color = PantrixTheme.colors.textSecondary,
tagKey = "sort_result"
)
private val SORTS = listOf(
SegmentOption("newest", "Newest", tagKey = "newest"),
SegmentOption("cheapest", "Cheapest", tagKey = "cheapest"),
SegmentOption("nearest", "Nearest", tagKey = "nearest")
)
As wide as its labels
fullWidth = false makes each segment as wide as its label: in a toolbar or beside a heading the control takes only the room it needs. If there is not room for them all, the short labels stay whole and the long ones share what is left.
Both controls are like this: a time span with four options and a choice of platform with three.
var span by rememberSaveable { mutableStateOf("week") }
var platform by rememberSaveable { mutableStateOf("web") }
PantrixSegmentButton(
options = SPANS,
selected = span,
onSelect = { span = it },
fullWidth = false,
tagKey = "span"
)
PantrixText(
text = "Showing: the last ${SPANS.first { it.value == span }.label.lowercase()}",
style = PantrixTheme.typography.footnote,
color = PantrixTheme.colors.textSecondary,
tagKey = "span_result"
)
PantrixSegmentButton(
options = PLATFORMS,
selected = platform,
onSelect = { platform = it },
fullWidth = false,
tagKey = "platform"
)
private val SPANS = listOf(
SegmentOption("day", "Day", tagKey = "day"),
SegmentOption("week", "Week", tagKey = "week"),
SegmentOption("month", "Month", tagKey = "month"),
SegmentOption("year", "Year", tagKey = "year")
)
private val PLATFORMS = listOf(
SegmentOption("android", "Android", tagKey = "android"),
SegmentOption("ios", "iOS", tagKey = "ios"),
SegmentOption("web", "Web", tagKey = "web")
)
Icons and a segment that cannot be selected
SegmentOption(icon = …) puts an icon before the label: the icon is in the label's colour, and a screen reader passes over it.
"Folders" is not open yet (enabled = false): it is written in the disabled colour, a press on it does nothing and the arrow keys skip it.
var view by rememberSaveable { mutableStateOf("list") }
PantrixSegmentButton(
options = listOf(
SegmentOption("list", "List", icon = PantrixIcons.Menu, tagKey = "list"),
SegmentOption("search", "Search", icon = PantrixIcons.Search, tagKey = "search"),
SegmentOption("folders", "Folders", icon = PantrixIcons.Folder, enabled = false, tagKey = "folders")
),
selected = view,
onSelect = { view = it },
modifier = Modifier.widthIn(max = 480.dp),
tagKey = "view"
)
With none selected
If selected is not the value of any option, no segment is selected and no pill is drawn; null is such a value too. It is used where an answer must not be chosen in advance.
Press one: the pill appears there. From then on the selection slides between the two segments; the control does not clear the selection by itself.
var helpful by rememberSaveable { mutableStateOf<String?>(null) }
PantrixText(text = if (helpful == null) "Was this page helpful?" else "Your answer was saved")
PantrixSegmentButton(
options = listOf(
SegmentOption("yes", "Yes", tagKey = "yes"),
SegmentOption("no", "No", tagKey = "no")
),
selected = helpful,
onSelect = { helpful = it },
fullWidth = false,
tagKey = "helpful"
)
Its own colours
colors changes only the fields that are given: here the pill is in the app's own purple (pill), and the label on it in the colour that reads on that purple (selectedContent); the track and the unselected labels stay in the theme's colour.
The app's purple is in none of the theme's roles: the colour that reads on such a pill is the caller's to give too. A disabled control's track and a segment that cannot be selected are drawn in the theme's colours, even when colours are given.
val own = ownColors()
var span by rememberSaveable { mutableStateOf("week") }
PantrixSegmentButton(
options = SPANS,
selected = span,
onSelect = { span = it },
modifier = Modifier.widthIn(max = 480.dp),
colors = SegmentButtonColors(pill = own.color, selectedContent = own.onColor),
tagKey = "span_own"
)
private val SPANS = listOf(
SegmentOption("day", "Day", tagKey = "day"),
SegmentOption("week", "Week", tagKey = "week"),
SegmentOption("month", "Month", tagKey = "month"),
SegmentOption("year", "Year", tagKey = "year")
)
Disabled
enabled = false disables every segment: the track and the labels are drawn in the disabled colours, and the control takes no press and no focus. The pill stays where it is: which option is selected can still be read.
This control's options are the same as those of the first example. The segments are named inside the control's test tag, so they do not clash: the one there is SegmentButton_sort.Segment_newest, and the one here SegmentButton_locked.Segment_newest.
PantrixSegmentButton(
options = SORTS,
selected = "newest",
onSelect = {},
modifier = Modifier.widthIn(max = 480.dp),
enabled = false,
tagKey = "locked"
)
private val SORTS = listOf(
SegmentOption("newest", "Newest", tagKey = "newest"),
SegmentOption("cheapest", "Cheapest", tagKey = "cheapest"),
SegmentOption("nearest", "Nearest", tagKey = "nearest")
)
Style
style changes the geometry of a single control: the track's shape (shape) and its height (height), the room between the track's edge and the pill (trackPadding), and the room on each side of a label (segmentPadding). Only the fields that are given change; the rest comes from the theme's and the platform's style.
The pill has no shape of its own: it is the track's shape, with each corner made smaller by trackPadding. That way the corners of the two curve around the same centre.
var size by rememberSaveable { mutableStateOf("m") }
PantrixSegmentButton(
options = listOf(
SegmentOption("s", "Small", tagKey = "s"),
SegmentOption("m", "Medium", tagKey = "m"),
SegmentOption("l", "Large", tagKey = "l")
),
selected = size,
onSelect = { size = it },
fullWidth = false,
style = SegmentButtonStyle(
shape = ShapeRole.Small,
height = 48.dp,
trackPadding = 6.dp,
segmentPadding = 20.dp
),
tagKey = "size"
)
In a type of its own
In a narrow column, in a type of its own: three options fit in the theme's footnote type (labelStyle). The colour is the segment's: the chosen one's the primary colour, the others' quieter.
var mode by rememberSaveable { mutableStateOf("auto") }
PantrixSegmentButton(
options = listOf(
SegmentOption("light", "Light", tagKey = "light"),
SegmentOption("dark", "Dark", tagKey = "dark"),
SegmentOption("auto", "System", tagKey = "auto")
),
selected = mode,
onSelect = { mode = it },
modifier = Modifier.widthIn(max = 220.dp),
tagKey = "mode",
labelStyle = PantrixTheme.typography.footnote
)
API
options
A segment button: a small set of choices side by side in one control, one of them chosen.
| Parameter | Type | Default | What it is |
|---|---|---|---|
options | List<SegmentOption<T>> | the choices, in order. An empty list draws nothing. | |
selected | T? | the value of the chosen segment, compared with each option's by ==. A value no option has, null among them, chooses none: no segment is selected and no pill is drawn. | |
onSelect | (T) -> Unit | called with the value of the segment the user chose, by a press or by a key, when it is not the chosen one already. The control chooses nothing by itself: a segment is chosen when selected says so. | |
modifier | Modifier | Modifier | the control's modifier, outside the touch target it reserves. |
enabled | Boolean | true | false disables every segment. The pill stays where it is, so that the choice can still be read. |
fullWidth | Boolean | true | true spans the control across the width it is given, each segment taking an equal share, so that the segments stay where they are whatever their labels say in another language. False makes each segment as wide as its label; in a room too narrow for that, the short labels stay whole and the long ones share what is left. |
style | SegmentButtonStyle? | null | this call's own style: over every enclosing one and under the density. Only the fields it sets change, on every platform. |
colors | SegmentButtonColors? | null | this control's own colours: the ones it sets, with the theme's for the rest. A disabled control's track and a segment that cannot be chosen keep the theme's. |
tagKey | String? | null | the control's automation key: the control is SegmentButton_<tagKey>, and its segments are named inside it, SegmentButton_<tagKey>.Segment_<key>, so that two controls with the same options are told apart. A choice is reported as a SegmentButton event, select, which carries the segment's key, or its place without one, and never its label. |
labelStyle | TextStyle | PantrixTheme.typography.label | the type the labels are set in: the theme's label unless given. A label set larger than the track makes it taller, as a large font scale does. |
SegmentButtonStyle
PantrixSegmentButton's geometry: the track, the pill that slides inside it, and the room each segment gives its label.
| Parameter | Type | Default | What it is |
|---|---|---|---|
shape | ShapeSpec | ShapeSpec.Unspecified | The track's shape: a step of the brand's shape scale, or a fixed shape. |
height | Dp | Dp.Unspecified | The track's height. A label set larger than it has room for, at a large font scale, makes the track taller. |
trackPadding | Dp | Dp.Unspecified | The room between the track's edge and the pill. |
segmentPadding | Dp | Dp.Unspecified | The room on each side of a segment's label. |
iconGap | Dp | Dp.Unspecified | Between a segment's icon and its label. |
iconSize | Dp | Dp.Unspecified | The width and height of a segment's icon. |
SegmentButtonColors
What PantrixSegmentButton is painted with, for a control that does not take the theme's: a colour that is set is used, and one that is left out stays the theme's role.
| Parameter | Type | Default | What it is |
|---|---|---|---|
track | Color | Color.Unspecified | The track the segments stand on. The neutral role's container. |
pill | Color | Color.Unspecified | The pill under the chosen segment. The theme's surface. |
selectedContent | Color | Color.Unspecified | The label and the icon of the chosen segment: the colour that reads on pill. The theme's primary. |
unselectedContent | Color | Color.Unspecified | The label and the icon of the other segments: the colour that reads on track. The theme's textSecondary. |
Accessibility
- Keyboard. The control is one stop for Tab, on the selected segment. The arrow keys select the next or the previous segment, go round from the last to the first and skip one that cannot be selected; in a right-to-left layout the directions swap.
- Screen reader. It is read as a group of radio buttons: each segment is named by its label, and one of them is selected.