Pantrix UI

Modal

A modal layer of the app's own: a scrim over the whole window, and over it content the app draws, with everything a modal of Pantrix does. It is what PantrixDialog and PantrixCommandPalette stand on, for a layer neither of them draws: a search with filters of its own, a picture shown large, an editor that fills a phone.

import com.pantrixui.components.overlay.modal.PantrixModal

Examples

A search at the top

A search of your own, at the top of the window: the field stays where it is, and the results grow and shrink under it. Escape, back or a press on the scrim closes it; so does a press on a result.

var searching by remember { mutableStateOf(false) }
var query by remember { mutableStateOf("") }
PantrixButton(
    text = "Open the search",
    onClick = { searching = true },
    variant = ButtonVariant.Outline,
    tagKey = "search"
)
PantrixModal(
    visible = searching,
    onDismissRequest = { searching = false },
    position = ModalPosition.Top,
    paneTitle = "Search",
    tagKey = "search"
) {
    PantrixSurface(Modifier.widthIn(max = 560.dp), shape = PantrixTheme.shapes.lg, elevation = 8.dp) {
        Column(Modifier.padding(PantrixTheme.spacing.md)) {
            PantrixTextField(
                value = query,
                onValueChange = { query = it },
                modifier = Modifier.fillMaxWidth(),
                placeholder = "Search for a page",
                // The name a screen reader gives a field that has no label.
                contentDescription = "Search pages",
                tagKey = "search_query"
            )
            // Each result's key is its place in the list, never its words.
            PAGES.withIndex().filter { it.value.contains(query, ignoreCase = true) }.forEach { (index, page) ->
                PantrixListItem(
                    headline = page,
                    leading = { PantrixIcon(PantrixIcons.File, contentDescription = null) },
                    onClick = { searching = false },
                    tagKey = "result_$index"
                )
            }
        }
    }
}

private val PAGES = listOf("Installation", "Themes", "Components", "Guides", "Changelog")

A page of its own

A page on a phone: a margin of none, a scrim in the theme's background and content that fills itself. Behind the window's bars stays the background's colour too.

var editing by remember { mutableStateOf(false) }
var note by remember { mutableStateOf("Monday: go over the design") }
PantrixButton(text = "Edit the note", onClick = { editing = true }, variant = ButtonVariant.Outline, tagKey = "edit")
PantrixModal(
    visible = editing,
    onDismissRequest = { editing = false },
    margin = PaddingValues(),
    paneTitle = "Note",
    scrimColor = PantrixTheme.colors.background,
    tagKey = "editor"
) {
    Column(
        Modifier.fillMaxSize().padding(PantrixTheme.spacing.lg),
        verticalArrangement = Arrangement.spacedBy(PantrixTheme.spacing.md)
    ) {
        Row(Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) {
            PantrixText(text = "Note", style = PantrixTheme.typography.title, modifier = Modifier.weight(1f))
            PantrixButton(text = "Done", onClick = { editing = false }, tagKey = "done")
        }
        PantrixTextArea(value = note, onValueChange = { note = it }, label = "Note", tagKey = "note")
    }
}

One that asks for a decision

A modal that does not close before a decision is made: a press on the scrim and back do nothing (dismissOnScrimClick and dismissOnBack are off); one of the two buttons in it is chosen.

var asking by remember { mutableStateOf(false) }
var answer by remember { mutableStateOf("Not chosen yet") }
PantrixButton(
    text = "Show the terms",
    onClick = { asking = true },
    variant = ButtonVariant.Outline,
    tagKey = "terms"
)
PantrixText(text = answer, style = PantrixTheme.typography.footnote, tagKey = "answer")
PantrixModal(
    visible = asking,
    onDismissRequest = { asking = false },
    paneTitle = "Terms of use",
    dismissOnScrimClick = false,
    dismissOnBack = false,
    tagKey = "decision"
) {
    PantrixSurface(Modifier.widthIn(max = 420.dp), shape = PantrixTheme.shapes.lg) {
        Column(
            Modifier.padding(PantrixTheme.spacing.lg),
            verticalArrangement = Arrangement.spacedBy(PantrixTheme.spacing.md)
        ) {
            PantrixText(text = "Accept the terms of use to go on.")
            Row(horizontalArrangement = Arrangement.spacedBy(PantrixTheme.spacing.sm)) {
                PantrixButton(
                    text = "Accept",
                    onClick = {
                        answer = "Accepted"
                        asking = false
                    },
                    tagKey = "accept"
                )
                PantrixButton(
                    text = "Decline",
                    onClick = {
                        answer = "Declined"
                        asking = false
                    },
                    variant = ButtonVariant.Outline,
                    tagKey = "decline"
                )
            }
        }
    }
}

API

visible

A modal layer of the app's own: a scrim over the whole window, and over it content the app draws, with everything a modal of Pantrix does. It is what PantrixDialog and PantrixCommandPalette stand on, for a layer neither of them draws: a search with filters of its own, a picture shown large, an editor that fills a phone.

ParameterTypeDefaultWhat it is
visibleBooleanwhether the modal is shown. Set to false, the content leaves with its motion, and then nothing is composed.
onDismissRequest() -> Unitcalled when the user asks to close the modal: back, Escape, or a press on the scrim.
modifierModifierModifierapplied to the content, inside margin: the node it tags is the content as it is drawn, and a press anywhere on it is the content's, never the scrim's.
positionModalPositionModalPosition.Centerwhere the content sits: in the middle of the window, or near its top.
marginPaddingValuesPaddingValues(PantrixTheme.spacing.xl)the room kept between the content and the window's safe area, side by side. It is part of the scrim: a press in it, right beside the content, dismisses as one anywhere else on the scrim does. None, with content that fills what it is given, makes the content the whole safe area, as an editor on a phone is.
paneTitleString?nullwhat a screen reader announces as the modal appears: what it is, in a word or two.
dismissOnScrimClickBooleantruewhether a press on the scrim asks to dismiss. Where it does not, the scrim is a fill a screen reader passes over.
dismissOnBackBooleantruewhether back and Escape ask to dismiss.
scrimColorColorPantrixTheme.colors.scrimwhat is drawn over the app while the modal is shown, fading with the content: the theme's scrim unless given. The theme's background makes the modal a page of its own, the window's bars and all.
tagKeyString?nullthe modal's automation key: the content is Modal_<tagKey>. A dismissal is reported as a Modal event, dismiss, which says whether it came by the scrim or by back.
content@Composable () -> Unitwhat the modal shows, drawn by the app.

Accessibility