Pantrix UI

Button Group

A group of buttons: a few actions of the same rank, drawn as one control or set apart at one distance.

import com.pantrixui.components.elements.buttongroup.PantrixButtonGroup

Examples

An attached toolbar

Its shortest form: a list of buttons. The group is attached and its variant is Outline: there is one edge around it and a line between each two buttons, and the corners are only at the group's two ends.

The first button has an icon before its label; the last is an icon alone and says what it does with contentDescription. Each button is a button in its own right: Tab stops at each of them, and a screen reader says the name of each. Press a button: the line below names the last action.

var last by rememberSaveable { mutableStateOf<String?>(null) }
PantrixButtonGroup(
    buttons = listOf(
        GroupButton(text = "Copy", onClick = { last = "Copy" }, icon = PantrixIcons.Copy, tagKey = "copy"),
        GroupButton(text = "Paste", onClick = { last = "Paste" }, tagKey = "paste"),
        GroupButton(
            icon = PantrixIcons.MoreHorizontal,
            contentDescription = "More actions",
            onClick = { last = "More actions" },
            tagKey = "more"
        )
    ),
    tagKey = "clipboard"
)
PantrixText(
    text = last?.let { "Last action: $it" } ?: "No action pressed yet",
    style = PantrixTheme.typography.footnote,
    color = PantrixTheme.colors.textSecondary,
    tagKey = "clipboard_result"
)

A stepper of icons alone

A stepper made of icons alone, in the Soft variant. A button that is an icon alone is written with the second constructor of GroupButton, and there contentDescription is required: a button is never decoration.

enabled is given to each button separately: at the lower limit the minus button cannot be pressed, and the plus button can.

var count by rememberSaveable { mutableIntStateOf(1) }
Row(
    horizontalArrangement = Arrangement.spacedBy(PantrixTheme.spacing.md),
    verticalAlignment = Alignment.CenterVertically
) {
    PantrixButtonGroup(
        buttons = listOf(
            GroupButton(
                icon = PantrixIcons.Minus,
                contentDescription = "Decrease",
                onClick = { count-- },
                enabled = count > 1,
                tagKey = "minus"
            ),
            GroupButton(
                icon = PantrixIcons.Plus,
                contentDescription = "Increase",
                onClick = { count++ },
                tagKey = "plus"
            )
        ),
        variant = ButtonVariant.Soft,
        tagKey = "stepper"
    )
    PantrixText(text = "Quantity: $count", tagKey = "stepper_count")
}

Labels read by their description

Two buttons that change the text size. Their labels are "A-" and "A+": enough for someone who sees them, but they mean nothing in a screen reader. So each has a contentDescription, and the screen reader reads the description alone, not the label: "Make text smaller", "Make text larger".

var percent by rememberSaveable { mutableIntStateOf(100) }
Row(
    horizontalArrangement = Arrangement.spacedBy(PantrixTheme.spacing.md),
    verticalAlignment = Alignment.CenterVertically
) {
    PantrixButtonGroup(
        buttons = listOf(
            GroupButton(
                text = "A-",
                onClick = { percent -= 10 },
                enabled = percent > 80,
                contentDescription = "Make text smaller",
                tagKey = "smaller"
            ),
            GroupButton(
                text = "A+",
                onClick = { percent += 10 },
                enabled = percent < 150,
                contentDescription = "Make text larger",
                tagKey = "larger"
            )
        ),
        tagKey = "text_size"
    )
    PantrixText(text = "Text size: $percent%", tagKey = "text_size_percent")
}

Variants

variant is the variant of every button that has none of its own; when it is not given, it is Outline. In the variants that have an edge, Outline and Subtle, there is one edge around the group and the lines between the buttons are in its colour. In those without an edge, only the lines between the buttons are drawn.

listOf(
    ButtonVariant.Outline,
    ButtonVariant.Subtle,
    ButtonVariant.Soft,
    ButtonVariant.Solid,
    ButtonVariant.Text
).forEach { variant ->
    PantrixButtonGroup(
        buttons = listOf(
            GroupButton(text = "Previous", onClick = {}, tagKey = "previous"),
            GroupButton(text = "Next", onClick = {}, tagKey = "next")
        ),
        variant = variant,
        tagKey = "variant_${variant.name}"
    )
}

Sizes

size is the size of every button in the group, so that they all line up: here Small, Medium and Large. When it is not given, it is Medium.

listOf(ButtonSize.Small, ButtonSize.Medium, ButtonSize.Large).forEach { size ->
    PantrixButtonGroup(
        buttons = listOf(
            GroupButton(text = "Cut", onClick = {}, tagKey = "cut"),
            GroupButton(text = "Copy", onClick = {}, tagKey = "copy"),
            GroupButton(text = "Paste", onClick = {}, tagKey = "paste")
        ),
        size = size,
        tagKey = "size_${size.name}"
    )
}

A column of actions

orientation = Orientation.Vertical lays the buttons out one under another: they are all as wide as the widest. In an attached column the corners are at the top and the bottom of the group, and the lines between the buttons are horizontal.

The action that deletes carries its own variant: on that button the variant of GroupButton takes the place of the group's.

PantrixButtonGroup(
    buttons = listOf(
        GroupButton(text = "Rename", onClick = {}, tagKey = "rename"),
        GroupButton(text = "Duplicate", onClick = {}, tagKey = "duplicate"),
        GroupButton(text = "Delete", onClick = {}, variant = ButtonVariant.Destructive, tagKey = "delete")
    ),
    orientation = Orientation.Vertical,
    tagKey = "record"
)

Apart and of equal width

The two actions under a dialog. With attached = false they stand apart, each with its own shape and edge, with the style's gap between them. With equalWidth = true they share the group's width equally; here the group is 360dp wide at most.

loading is the button's own state: when "Delete" is pressed it shows as loading for a short while and cannot be pressed in the meantime. "Cancel" stops the loading.

var deleting by remember { mutableStateOf(false) }
LaunchedEffect(deleting) {
    if (deleting) {
        delay(1500)
        deleting = false
    }
}
PantrixButtonGroup(
    buttons = listOf(
        GroupButton(text = "Cancel", onClick = { deleting = false }, tagKey = "cancel"),
        GroupButton(
            text = "Delete",
            onClick = { deleting = true },
            loading = deleting,
            variant = ButtonVariant.Destructive,
            tagKey = "delete"
        )
    ),
    modifier = Modifier.widthIn(max = 360.dp),
    attached = false,
    equalWidth = true,
    tagKey = "footer"
)

In a narrow space

The same three buttons, twice, in a space 280dp wide. In a row each button is as wide as what it says; where there is not enough room the short ones stay whole and the long one takes what is left, its label cut off with an ellipsis.

The second group has equalWidth = true: the three buttons share the width equally, and the long label is cut off sooner.

PantrixButtonGroup(
    buttons = listOf(
        GroupButton(text = "Save", onClick = {}, tagKey = "save"),
        GroupButton(text = "Exit without saving and delete the draft", onClick = {}, tagKey = "discard"),
        GroupButton(text = "Cancel", onClick = {}, tagKey = "cancel")
    ),
    modifier = Modifier.width(280.dp),
    tagKey = "narrow"
)
PantrixButtonGroup(
    buttons = listOf(
        GroupButton(text = "Save", onClick = {}, tagKey = "save"),
        GroupButton(text = "Exit without saving and delete the draft", onClick = {}, tagKey = "discard"),
        GroupButton(text = "Cancel", onClick = {}, tagKey = "cancel")
    ),
    modifier = Modifier.width(280.dp),
    equalWidth = true,
    tagKey = "narrow_equal"
)

In a role's colours

colors draws the group's buttons in the given colours instead of a variant's primary colours. Here PantrixColor.buttonColors(ButtonVariant.Outline) gives the outlined button colours of the success role. In an attached group the colour given for the edge is the colour of the group's one edge and of the lines between the buttons.

PantrixButtonGroup(
    buttons = listOf(
        GroupButton(text = "Approve", onClick = {}, icon = PantrixIcons.Check, tagKey = "approve"),
        GroupButton(text = "Approve all", onClick = {}, tagKey = "approve_all")
    ),
    colors = PantrixTheme.colors.success.buttonColors(ButtonVariant.Outline),
    tagKey = "review"
)

In its own colour

The first group's colour is in none of the theme's roles: ButtonColors takes the fill, the content's colour and the edge directly.

In the second group only one button has a colour of its own. On that button the colors of GroupButton take the place of the group's colours and of its own variant; the button still stands inside the group's edge.

val own = ownColors()
PantrixButtonGroup(
    buttons = listOf(
        GroupButton(text = "Add to collection", onClick = {}, icon = PantrixIcons.Star, tagKey = "collect"),
        GroupButton(text = "Share", onClick = {}, tagKey = "share")
    ),
    colors = ButtonColors(container = own.container, content = own.onContainer, border = own.color),
    tagKey = "own_colors"
)
PantrixButtonGroup(
    buttons = listOf(
        GroupButton(text = "Edit", onClick = {}, tagKey = "edit"),
        GroupButton(
            text = "Feature",
            onClick = {},
            colors = ButtonColors(container = own.color, content = own.onColor),
            tagKey = "feature"
        )
    ),
    tagKey = "one_own_button"
)

Shape and style

In an attached group shape is the shape of the whole group: each button carries only the corners at its own end. In a group whose buttons stand apart it is the shape of each button. The first group was drawn in the theme's slightly rounded shape (shapes.sm).

style changes the group's geometry: in the second group ButtonGroupStyle(gap = 16.dp) widens the gap between the buttons that stand apart. The buttons' sizes come from the button's own style, not from the group's.

PantrixButtonGroup(
    buttons = listOf(
        GroupButton(text = "Print", onClick = {}, tagKey = "print"),
        GroupButton(text = "Export", onClick = {}, tagKey = "export"),
        GroupButton(text = "Share", onClick = {}, tagKey = "share")
    ),
    shape = PantrixTheme.shapes.sm,
    tagKey = "less_round"
)
PantrixButtonGroup(
    buttons = listOf(
        GroupButton(text = "Back", onClick = {}, tagKey = "back"),
        GroupButton(text = "Next", onClick = {}, tagKey = "forward")
    ),
    attached = false,
    style = ButtonGroupStyle(gap = 16.dp),
    tagKey = "wide_gap"
)

A disabled group

The group's enabled = false disables every button: none of them takes a press, and their labels and the group's edge are drawn faded.

PantrixButtonGroup(
    buttons = listOf(
        GroupButton(text = "Undo", onClick = {}, tagKey = "undo"),
        GroupButton(text = "Redo", onClick = {}, tagKey = "redo")
    ),
    enabled = false,
    tagKey = "locked"
)

API

buttons

A group of buttons: a few actions of the same rank, drawn as one control or set apart at one distance.

ParameterTypeDefaultWhat it is
buttonsList<GroupButton>the buttons, in order. An empty list draws nothing.
modifierModifierModifierthe group's modifier, outside the touch target an attached group reserves.
variantButtonVariantButtonVariant.Outlinethe variant of every button that has none of its own.
sizeButtonSizeButtonSize.Mediumthe size of every button, so that they line up.
orientationOrientationOrientation.HorizontalOrientation.Horizontal for a row, Orientation.Vertical for a column of actions.
attachedBooleantruetrue draws the buttons as one control. False sets them the style's gap apart.
equalWidthBooleanfalsetrue gives every button of a row the same share of the width the group is given; in a room without an end, the width of the widest. A column's buttons are as wide as each other whatever this says.
enabledBooleantruefalse disables every button.
styleButtonGroupStyle?nullthis call's own style: over every enclosing one and under the density. Only the fields it sets change, on every platform. The buttons keep the button's own style for their sizes.
shapeShapePantrixTheme.style(ButtonGroupStyle, style).shape.toShape()the group's shape: attached, the shape of the whole group; apart, of each button.
colorsButtonColorsvariant.colors()what the buttons are painted with, each one that has neither colours nor a variant of its own. Attached, the border's colour is the group's one edge, and the colour of the lines between the buttons.
tagKeyString?nullthe group's automation key: the group is ButtonGroup_<tagKey>, and its buttons are named inside it, ButtonGroup_<tagKey>.Button_<key>, so that two groups with the same actions are told apart.

ButtonGroupStyle

PantrixButtonGroup's geometry: the shape of the group, the line between two buttons that are attached, and the room between two that are not.

ParameterTypeDefaultWhat it is
shapeShapeSpecShapeSpec.UnspecifiedThe group's shape: a step of the brand's shape scale, or a fixed shape.
dividerWidthDpDp.UnspecifiedThe width of the line between two attached buttons.
gapDpDp.UnspecifiedThe room between two buttons that are not attached.

ButtonColors

What a button is painted with. ButtonVariant.colors gives a variant's in primary, and PantrixColor.buttonColors the same prominences in any role.

ParameterTypeDefaultWhat it is
containerColorThe fill; transparent for an outline or a text button.
contentColorThe label's and the icon's colour.
borderColorColor.UnspecifiedThe edge's colour, or Color.Unspecified for a button without an edge.

Accessibility