Pantrix UI

Page Scaffold

A screen inside an app's shell: its title and what it can do at its top, and under them what it shows, kept clear of the shell's bars, of the keyboard and of the window's edges. An app lays its shell out once with a PantrixScaffold, its navigation along the bottom of a narrow window and down the side of a wide one, and each of its screens with this.

import com.pantrixui.components.container.page.PantrixPageScaffold

Examples

A page of an admin

The orders page of an admin panel. titlePlacement = PageTitlePlacement.Adaptive: choose "Wide" in the card's picker, and the title and the actions go down to the top of the content and the bar goes; in "Narrow" they are in the bar and the actions turn into icons. "Archive" has no icon: in a narrow page it is in the "…" menu. The key of the action pressed is written in the page.

var pressed by rememberSaveable { mutableStateOf<String?>(null) }
PageExampleFrame {
    PantrixPageScaffold(
        title = "Orders",
        subtitle = "12 open",
        actions = listOf(
            PageAction(key = "new", label = "New order", icon = PantrixIcons.Plus, primary = true),
            PageAction(key = "export", label = "Export", icon = PantrixIcons.Download),
            PageAction(key = "archive", label = "Archive")
        ),
        onAction = { pressed = it },
        header = {
            PantrixText("Orders of the last 30 days", color = PantrixTheme.colors.textSecondary)
        },
        titlePlacement = PageTitlePlacement.Adaptive,
        tagKey = "orders"
    ) {
        PantrixText(pressed?.let { "Last pressed: $it" } ?: "No action pressed yet")
        repeat(5) { index ->
            PantrixCard(modifier = Modifier.fillMaxWidth().padding(top = 8.dp)) {
                PantrixText("Order #${1040 + index}", modifier = Modifier.padding(16.dp))
            }
        }
    }
}

@Composable
private fun PageExampleFrame(content: @Composable () -> Unit) {
    PantrixSurface(
        modifier = Modifier.fillMaxWidth().height(PAGE_EXAMPLE_HEIGHT),
        border = PantrixTheme.colors.divider
    ) {
        Box { content() }
    }
}

private val PAGE_EXAMPLE_HEIGHT = 420.dp

Tabs under the bar, a list that scrolls itself

The tabs stand under the bar (stickyHeader), the list scrolls itself (scroll = PageScroll.None) and takes the room the page leaves with contentPadding: its last row inside the room, the margin inside the rows. Scroll the list: the line shows under the tabs. Press the back button and the subtitle counts.

var tab by rememberSaveable { mutableIntStateOf(0) }
var backs by rememberSaveable { mutableIntStateOf(0) }
val tabs = listOf(TabItem("All"), TabItem("On sale"), TabItem("Sold out"))
PageExampleFrame {
    PantrixPageScaffold(
        title = "Products",
        subtitle = if (backs == 0) null else "Back presses: $backs",
        onBack = { backs++ },
        stickyHeader = { PantrixTabs(tabs = tabs, selectedIndex = tab, onSelect = { tab = it }, tagKey = "stock") },
        scroll = PageScroll.None,
        tagKey = "products"
    ) {
        LazyColumn(contentPadding = contentPadding, verticalArrangement = Arrangement.spacedBy(12.dp)) {
            items(List(20) { "${tabs[tab].label}: product ${it + 1}" }) { product -> PantrixText(product) }
        }
    }
}

@Composable
private fun PageExampleFrame(content: @Composable () -> Unit) {
    PantrixSurface(
        modifier = Modifier.fillMaxWidth().height(PAGE_EXAMPLE_HEIGHT),
        border = PantrixTheme.colors.divider
    ) {
        Box { content() }
    }
}

private val PAGE_EXAMPLE_HEIGHT = 420.dp

A large title and a list

A list's page: PantrixLazyPageScaffold. Its rows are composed as they come into view; header is the list's first row. largeTitle = true: the title is written large under the bar, and goes up into the bar as the list scrolls. trailing puts the app's own button at the end of the bar. width = PageWidth.Content: a list that is read keeps to the theme's content width in a wide window.

PageExampleFrame {
    PantrixLazyPageScaffold(
        title = "Inbox",
        trailing = {
            PantrixIconButton(PantrixIcons.Search, contentDescription = "Search", onClick = {}, tagKey = "search")
        },
        header = { PantrixText("4 unread messages", color = PantrixTheme.colors.textSecondary) },
        largeTitle = true,
        width = PageWidth.Content,
        verticalArrangement = Arrangement.spacedBy(12.dp),
        tagKey = "inbox"
    ) {
        items(List(30) { "Message ${it + 1}" }) { message -> PantrixText(message) }
    }
}

@Composable
private fun PageExampleFrame(content: @Composable () -> Unit) {
    PantrixSurface(
        modifier = Modifier.fillMaxWidth().height(PAGE_EXAMPLE_HEIGHT),
        border = PantrixTheme.colors.divider
    ) {
        Box { content() }
    }
}

private val PAGE_EXAMPLE_HEIGHT = 420.dp

Inside a shell, with measures of its own

A page inside a shell: the PantrixScaffold has a bottom bar, the page reads its room itself and leaves its last row above the bar. padding is that even when it is not given (LocalPantrixScaffoldPadding.current); it is written out here. style changes the page's margin and the room between its sections.

PageExampleFrame {
    PantrixScaffold(
        bottomBar = { insets ->
            PantrixSurface(modifier = Modifier.fillMaxWidth().padding(insets).height(56.dp)) {
                PantrixText("Bottom bar", modifier = Modifier.align(Alignment.Center))
            }
        }
    ) {
        PantrixPageScaffold(
            title = "Settings",
            header = { PantrixText("A margin of 24 dp, 8 dp between the sections") },
            padding = LocalPantrixScaffoldPadding.current,
            style = PageScaffoldStyle(horizontalPadding = 24.dp, sectionGap = 8.dp),
            tagKey = "settings"
        ) {
            repeat(12) { index -> PantrixText("Setting ${index + 1}", modifier = Modifier.padding(vertical = 8.dp)) }
        }
    }
}

@Composable
private fun PageExampleFrame(content: @Composable () -> Unit) {
    PantrixSurface(
        modifier = Modifier.fillMaxWidth().height(PAGE_EXAMPLE_HEIGHT),
        border = PantrixTheme.colors.divider
    ) {
        Box { content() }
    }
}

private val PAGE_EXAMPLE_HEIGHT = 420.dp

The window's title

The window's title is given without a page too: PantrixWindowTitle says the screen's name, and LocalPantrixWindowTitleFormat how it is written as the window's. Turn the switch on: on the web the tab's title, on the desktop the window's, becomes "Reports — Admin"; turn it off and this page's comes back. Android and iOS show their user no window's title.

var named by rememberSaveable { mutableStateOf(false) }
PantrixSwitchButton(
    checked = named,
    onCheckedChange = { named = it },
    label = "Name the window \"Reports\"",
    tagKey = "window_title"
)
if (named) {
    CompositionLocalProvider(LocalPantrixWindowTitleFormat provides PantrixWindowTitleFormat { "$it — Admin" }) {
        PantrixWindowTitle("Reports")
    }
}

API

title

A screen inside an app's shell: its title and what it can do at its top, and under them what it shows, kept clear of the shell's bars, of the keyboard and of the window's edges. An app lays its shell out once with a PantrixScaffold, its navigation along the bottom of a narrow window and down the side of a wide one, and each of its screens with this.

ParameterTypeDefaultWhat it is
titleStringwhat the page is called: in its bar or its heading, its name for a screen reader, and the window's.
modifierModifierModifierthe page's modifier.
subtitleString?nulla line under the title, a count or a state, or null for none.
onBack(() -> Unit)?nullwhere the page goes back to, as its bar's back button; null where there is nowhere to go back to. The system's back, a gesture or a key, is the app's navigation's to hear.
actionsList<PageAction>emptyList()what the page can do, the most used first: laid out by the room, the rest in a menu. Each has a key of its own among them.
onAction(key: String) -> Unit{}told the key of the action pressed.
trailing(@Composable RowScope.() -> Unit)?nullwhat stands after the actions all the time, in a row: the app's own buttons.
header(@Composable () -> Unit)?nullwhat stands under the title and over the content, and scrolls with them.
stickyHeader(@Composable () -> Unit)?nullwhat stands under the bar and stays there as the page scrolls.
titlePlacementPageTitlePlacementPageTitlePlacement.Barwhether the title is in the bar at every width, or in the content of a wide page.
largeTitleBooleanfalsewhether the bar's title stands in a large type under it, and goes up into it as the page scrolls.
widthPageWidthPageWidth.Fullhow wide what the page shows is: the page's, or the theme's content width.
scrollPageScrollPageScroll.Pagewhether the page scrolls, or its content scrolls itself.
paddingPaddingValuesLocalPantrixScaffoldPadding.currentwhat the page keeps clear of at its edges: the enclosing scaffold's padding by default.
stylePageScaffoldStyle?nullthe page's geometry, over the theme's.
tagKeyString?nullthe page's automation key, the same in every language: what the page and its parts are tagged by, the TestScope of what it holds, and the page its show event is of.
content@Composable PageScaffoldScope.() -> Unitwhat the page shows, in a column; with PageScroll.None it is given the room to keep as PageScaffoldScope.contentPadding.

PageScaffoldStyle

PantrixPageScaffold's geometry: the room around what a page holds and between its parts.

ParameterTypeDefaultWhat it is
horizontalPaddingDpDp.UnspecifiedThe room between the page's sides and what it holds, past the scaffold's padding: unspecified, the theme's page margin, unless it is set.
verticalPaddingDpDp.UnspecifiedThe room above what the page holds and below it: 16dp on Android and iOS, 24dp on desktop and the web.
sectionGapDpDp.UnspecifiedThe room between the page's heading, its header and its content: 16dp on Android and iOS, 24dp on desktop and the web.
actionGapDpDp.UnspecifiedThe room between two of the page's actions, and between them and what stands beside them: 8dp.
topBarTopBarStyle?nullThe page's bar's own geometry, over the theme's. The bar's title starts at horizontalPadding unless this sets where.

Accessibility