Pantrix UI

Back Button

A button that goes back to the screen before, for a PantrixTopBar's navigation icon: an icon button a screen reader calls "Back" in the theme's language.

import com.pantrixui.components.container.topbar.PantrixBackButton

Examples

Back button, actions and content scrolling under the bar

A conversation screen: a back button, two actions and a list that scrolls under the bar. navigationIcon puts a PantrixBackButton before the title; the subtitle says how many times the back button has been pressed. actions puts two icon buttons after the title: drawn as they are, in the brand's colour.

Scroll the list: as soon as it leaves its top, a line appears along the bar's bottom edge (scrolled), and it stays for as long as the list can scroll back. The line is inside the bar's height; nothing moves as it comes and goes.

val list = rememberLazyListState()
var back by rememberSaveable { mutableIntStateOf(0) }
PantrixSurface(modifier = Modifier.fillMaxWidth(), border = PantrixTheme.colors.divider) {
    Column(modifier = Modifier.height(220.dp)) {
        PantrixTopBar(
            title = "Conversation",
            subtitle = if (back == 0) null else "Back presses: $back",
            navigationIcon = { PantrixBackButton(onClick = { back++ }, tagKey = "back") },
            actions = {
                PantrixIconButton(
                    imageVector = PantrixIcons.Search,
                    contentDescription = "Search",
                    onClick = {},
                    tagKey = "search"
                )
                PantrixIconButton(
                    imageVector = PantrixIcons.MoreHorizontal,
                    contentDescription = "More",
                    onClick = {},
                    tagKey = "more"
                )
            },
            scrolled = list.canScrollBackward,
            tagKey = "conversation"
        )
        LazyColumn(
            // The list is as wide as the bar: left narrower, a finger on the space beside it would scroll
            // the page, not the list.
            modifier = Modifier.fillMaxWidth(),
            state = list,
            contentPadding = PaddingValues(PantrixTheme.spacing.lg),
            verticalArrangement = Arrangement.spacedBy(PantrixTheme.spacing.md)
        ) {
            items(12) { index -> PantrixText(text = "Message ${index + 1}") }
        }
    }
}

A title that does not fit is cut short

The title does not run under the navigation icon or the actions: a title too long for the room left between them is cut short. The bar here is at most 360dp wide; the title does not fit between the back button and the star, and its end is cut off.

PantrixSurface(modifier = Modifier.widthIn(max = 360.dp), border = PantrixTheme.colors.divider) {
    PantrixTopBar(
        title = "Third-quarter reports, as they were sent to the board of directors",
        navigationIcon = { PantrixBackButton(onClick = {}, tagKey = "long_back") },
        actions = {
            PantrixIconButton(
                imageVector = PantrixIcons.Star,
                contentDescription = "Add to favourites",
                onClick = {},
                tagKey = "favourite"
            )
        },
        tagKey = "long"
    )
}

Where the title stands: at the start and in the middle

Where the title stands follows the platform: at the start on Android, desktop and the web, in the middle of the bar on iOS. Here the same bar is drawn twice, and the place is given with style: TopBarStyle(titleAlignment = ...). Both show, whichever platform you are on.

A title in the middle is in the middle of the bar whatever stands beside it: the back button does not push it aside.

PantrixSurface(modifier = Modifier.fillMaxWidth(), border = PantrixTheme.colors.divider) {
    PantrixTopBar(
        title = "Settings",
        navigationIcon = { PantrixBackButton(onClick = {}, tagKey = "start_back") },
        style = TopBarStyle(titleAlignment = TopBarTitleAlignment.Start),
        tagKey = "start"
    )
}
PantrixSurface(modifier = Modifier.fillMaxWidth(), border = PantrixTheme.colors.divider) {
    PantrixTopBar(
        title = "Settings",
        navigationIcon = { PantrixBackButton(onClick = {}, tagKey = "center_back") },
        style = TopBarStyle(titleAlignment = TopBarTitleAlignment.Center),
        tagKey = "center"
    )
}

The back button's icon

PantrixBackButton takes the icon it draws from the bar's style: TopBarStyle.backIcon. The platform's own icon is an arrow on Android, desktop and the web, a chevron on iOS. Here each of the two bars is given an icon with style; a back button in a bar's navigationIcon uses the style that bar was given.

Both turn round in a right-to-left layout: flip the reading direction from the "Typeface and direction" menu in the app's top bar and look.

PantrixSurface(modifier = Modifier.fillMaxWidth(), border = PantrixTheme.colors.divider) {
    PantrixTopBar(
        title = "Arrow",
        navigationIcon = { PantrixBackButton(onClick = {}, tagKey = "arrow_back") },
        style = TopBarStyle(backIcon = BackButtonIcon.Arrow),
        tagKey = "arrow"
    )
}
PantrixSurface(modifier = Modifier.fillMaxWidth(), border = PantrixTheme.colors.divider) {
    PantrixTopBar(
        title = "Chevron",
        navigationIcon = { PantrixBackButton(onClick = {}, tagKey = "chevron_back") },
        style = TopBarStyle(backIcon = BackButtonIcon.Chevron),
        tagKey = "chevron"
    )
}

With nowhere to go back to

On the first screen of a flow there is nowhere to go back to: the enabled of PantrixBackButton is false and the button cannot be pressed. Move on with the button at the end: the back button is enabled from the second step on.

var step by rememberSaveable { mutableIntStateOf(0) }
PantrixSurface(modifier = Modifier.fillMaxWidth(), border = PantrixTheme.colors.divider) {
    PantrixTopBar(
        title = "Step ${step + 1} / 3",
        navigationIcon = { PantrixBackButton(onClick = { step -= 1 }, enabled = step > 0, tagKey = "step_back") },
        actions = {
            PantrixIconButton(
                imageVector = PantrixIcons.ArrowForward,
                contentDescription = "Next step",
                onClick = { step += 1 },
                enabled = step < 2,
                tagKey = "step_next"
            )
        },
        tagKey = "steps"
    )
}

A large title that collapses as the list scrolls

A collapsing title: the state is the app's and is kept with rememberPantrixTopBarState(); the list hands the bar its scroll with Modifier.topBarScroll(state). While the bar is open the title is written large, in the theme's headline style, in a band under the row. The large title stands at the bar's start on every platform; the title in the row stands where the platform has it, in the middle on iOS.

Scroll the list up: first the band closes and the title comes into the row, and only then does the list scroll. The bar opens again only once the list is back at its top. Left between its two heights by a finger, it goes on to the nearer; after a mouse wheel it goes on the way the wheel was last turned. A screen reader reads the title in the row: that one is there all the time, and the title is not read twice.

The bar and the list are inside a scaffold, as they would be in an app: the list fills the whole scaffold and is given the bar's height as its top inset. As the bar closes and opens the list itself stays where it is; only its inset changes. The button below takes the list back to its top and opens the bar by setting collapsedFraction to zero.

val topBar = rememberPantrixTopBarState()
val list = rememberLazyListState()
val scope = rememberCoroutineScope()
val spacing = PantrixTheme.spacing
PantrixSurface(modifier = Modifier.fillMaxWidth(), border = PantrixTheme.colors.divider) {
    PantrixScaffold(
        modifier = Modifier.height(320.dp),
        topBar = { insets ->
            PantrixTopBar(
                title = "Inbox",
                subtitle = "3 unread",
                navigationIcon = { PantrixBackButton(onClick = {}, tagKey = "collapsing_back") },
                actions = {
                    PantrixIconButton(
                        imageVector = PantrixIcons.Search,
                        contentDescription = "Search",
                        onClick = {},
                        tagKey = "collapsing_search"
                    )
                },
                scrolled = list.canScrollBackward,
                state = topBar,
                insets = insets,
                tagKey = "collapsing"
            )
        },
        containerColor = PantrixTheme.colors.surface,
        // A box inside the page: the system bars are in the page's scaffold, and this one has no insets.
        windowInsets = WindowInsets(0, 0, 0, 0)
    ) { padding ->
        LazyColumn(
            modifier = Modifier.fillMaxSize().topBarScroll(topBar),
            state = list,
            contentPadding = PaddingValues(
                start = spacing.lg,
                top = padding.calculateTopPadding() + spacing.lg,
                end = spacing.lg,
                bottom = spacing.lg
            ),
            verticalArrangement = Arrangement.spacedBy(spacing.md)
        ) {
            items(24) { index -> PantrixText(text = "Conversation ${index + 1}") }
        }
    }
}
PantrixButton(
    text = "Back to top and open the bar",
    onClick = {
        scope.launch {
            list.scrollToItem(0)
            topBar.collapsedFraction = 0f
        }
    },
    variant = ButtonVariant.Outline,
    size = ButtonSize.Small,
    tagKey = "collapsing_to_top"
)

The insets the bar keeps clear

insets is the room the bar keeps clear at its edges: room for the status bar and a cutout. A PantrixScaffold hands it to its top bar itself; here a top inset of 28dp is given by hand. The bar's fill runs behind the inset, and the title and the back button stay below it: the bar is as tall as its style's height plus the inset.

PantrixSurface(modifier = Modifier.fillMaxWidth(), border = PantrixTheme.colors.divider) {
    PantrixTopBar(
        title = "Settings",
        navigationIcon = { PantrixBackButton(onClick = {}, tagKey = "insets_back") },
        colors = TopBarColors(container = PantrixTheme.colors.primary.container),
        insets = PaddingValues(top = 28.dp),
        tagKey = "insets"
    )
}

In its own colours

colors gives the bar colours of its own in place of the theme's. The first bar's fill is the colour of one of the theme's roles, and that alone is given: the theme pairs the colour of the title, the subtitle and the back button with it.

The second bar is in a colour the theme does not have, the app's own purple: the colour that reads on it is given with content. The title, the subtitle and the back button take the content colour. The icon button has colours of its own: the colour that reads on the fill is given to it separately.

val own = ownColors()
PantrixSurface(modifier = Modifier.fillMaxWidth(), border = PantrixTheme.colors.divider) {
    PantrixTopBar(
        title = "Inbox",
        subtitle = "Only the fill is given",
        navigationIcon = { PantrixBackButton(onClick = {}, tagKey = "role_back") },
        colors = TopBarColors(container = PantrixTheme.colors.primary.color),
        tagKey = "role"
    )
}
PantrixSurface(modifier = Modifier.fillMaxWidth(), border = PantrixTheme.colors.divider) {
    PantrixTopBar(
        title = "Inbox",
        subtitle = "3 unread",
        navigationIcon = { PantrixBackButton(onClick = {}, tagKey = "own_back") },
        actions = {
            PantrixIconButton(
                imageVector = PantrixIcons.Search,
                contentDescription = "Search",
                onClick = {},
                colors = ButtonColors(container = Color.Transparent, content = own.onColor),
                tagKey = "own_search"
            )
        },
        colors = TopBarColors(container = own.color, content = own.onColor),
        tagKey = "own"
    )
}

API

onClick

A button that goes back to the screen before, for a PantrixTopBar's navigation icon: an icon button a screen reader calls "Back" in the theme's language.

ParameterTypeDefaultWhat it is
onClick() -> Unitcalled at a press: go back to the screen before.
modifierModifierModifierthe button's modifier.
enabledBooleantruewhether the button can be pressed.
tagKeyString?nullthe button's automation key, the same in every language.