Pantrix UI

Surface

A container that paints color in shape and clips its content to it, with an optional one-dp border and a shadow of elevation.

import com.pantrixui.components.container.surface.PantrixSurface

Examples

At its plainest

The least there is to write: a PantrixSurface with its content. Its colour is the theme's surface and its shape is the theme's md shape; it has no border and no shadow. The surface takes as much room as its content and has no padding of its own: the padding comes from the text inside.

PantrixSurface {
    PantrixText(text = "Default surface", modifier = Modifier.padding(PantrixTheme.spacing.lg))
}

On a role's fill

Where color is a role's fill, the surface gives its content the colour paired with that fill: onColor on the role's color, onContainer on its container. The icon and the text in the rows are given no colour; both take what the surface gives.

val primary = PantrixTheme.colors.primary
val success = PantrixTheme.colors.success
PantrixSurface(modifier = Modifier.fillMaxWidth(), color = primary.color) {
    SurfaceLine(text = "primary.color: what is inside is drawn in onColor")
}
PantrixSurface(modifier = Modifier.fillMaxWidth(), color = primary.container) {
    SurfaceLine(text = "primary.container: what is inside is drawn in onContainer")
}
PantrixSurface(modifier = Modifier.fillMaxWidth(), color = success.container) {
    SurfaceLine(text = "success.container: the rule is the same for every role")
}

@Composable
private fun SurfaceLine(text: String) {
    Row(
        modifier = Modifier.padding(PantrixTheme.spacing.lg),
        horizontalArrangement = Arrangement.spacedBy(PantrixTheme.spacing.sm),
        verticalAlignment = Alignment.CenterVertically
    ) {
        PantrixIcon(imageVector = PantrixIcons.Info, contentDescription = null)
        PantrixText(text = text)
    }
}

On the grounds and on a colour the theme does not know

On the theme's own grounds, surface and background, the surface gives no content colour: each component draws in its own default, a text in textPrimary and an icon in onSurface.

It is the same on a fill no role has: the theme cannot know what reads on that colour. The third surface is painted in the app's own purple, and its content is again drawn in its own defaults. The fourth is given contentColor: on a fill like this it is the caller who says what colour reads on it.

val colors = PantrixTheme.colors
val own = ownColors()
PantrixSurface(modifier = Modifier.fillMaxWidth(), color = colors.surface, border = colors.divider) {
    SurfaceLine(text = "surface: no content colour")
}
PantrixSurface(modifier = Modifier.fillMaxWidth(), color = colors.background, border = colors.divider) {
    SurfaceLine(text = "background: no content colour")
}
PantrixSurface(modifier = Modifier.fillMaxWidth(), color = own.color) {
    SurfaceLine(text = "A colour the theme does not know: no content colour")
}
PantrixSurface(modifier = Modifier.fillMaxWidth(), color = own.color, contentColor = own.onColor) {
    SurfaceLine(text = "The same colour: contentColor given")
}

@Composable
private fun SurfaceLine(text: String) {
    Row(
        modifier = Modifier.padding(PantrixTheme.spacing.lg),
        horizontalArrangement = Arrangement.spacedBy(PantrixTheme.spacing.sm),
        verticalAlignment = Alignment.CenterVertically
    ) {
        PantrixIcon(imageVector = PantrixIcons.Info, contentDescription = null)
        PantrixText(text = text)
    }
}

A ground inside a fill

A ground-coloured surface on a fill is a ground again. The outer surface gives its content onColor; the inner one is the theme's surface and does not pass that colour on: the text inside it is drawn in textPrimary again, and the icon in onSurface. The colour the fill gave would not read on that ground.

PantrixSurface(modifier = Modifier.fillMaxWidth(), color = PantrixTheme.colors.primary.color) {
    Column(modifier = Modifier.padding(PantrixTheme.spacing.md)) {
        SurfaceLine(text = "On the fill")
        PantrixSurface(modifier = Modifier.fillMaxWidth()) {
            SurfaceLine(text = "On the inner surface: a ground again")
        }
    }
}

@Composable
private fun SurfaceLine(text: String) {
    Row(
        modifier = Modifier.padding(PantrixTheme.spacing.lg),
        horizontalArrangement = Arrangement.spacedBy(PantrixTheme.spacing.sm),
        verticalAlignment = Alignment.CenterVertically
    ) {
        PantrixIcon(imageVector = PantrixIcons.Info, contentDescription = null)
        PantrixText(text = text)
    }
}

Reading the content colour

The content colour is carried by LocalPantrixContentColor; something you draw yourself can read it too. The dot here is a Box, and takes its colour from there. On the grounds the value is Color.Unspecified: there the dot picks its own default.

val danger = PantrixTheme.colors.danger
Row(horizontalArrangement = Arrangement.spacedBy(PantrixTheme.spacing.md)) {
    PantrixSurface(color = danger.color) { SurfaceDot() }
    PantrixSurface(color = danger.container) { SurfaceDot() }
    PantrixSurface(border = PantrixTheme.colors.divider) { SurfaceDot() }
}

@Composable
private fun SurfaceDot() {
    val color = LocalPantrixContentColor.current.takeOrElse { PantrixTheme.colors.onSurface }
    Box(modifier = Modifier.padding(PantrixTheme.spacing.lg).size(16.dp).background(color, CircleShape))
}

Shape and clipping

shape is the surface's corners: one of the theme's shapes, or any Shape. The square surfaces show five of the theme's shapes; the default is md.

The content is clipped to the surface's shape. The band inside the bottom surface is a rectangle from edge to edge; its corners were cut by the surface's xl shape.

val shapes = PantrixTheme.shapes
val info = PantrixTheme.colors.info
FlowRow(
    horizontalArrangement = Arrangement.spacedBy(PantrixTheme.spacing.md),
    verticalArrangement = Arrangement.spacedBy(PantrixTheme.spacing.md)
) {
    listOf("none" to shapes.none, "sm" to shapes.sm, "md" to shapes.md, "xl" to shapes.xl, "full" to shapes.full)
        .forEach { (name, shape) ->
            PantrixSurface(modifier = Modifier.size(72.dp), color = info.container, shape = shape) {
                PantrixText(text = name, modifier = Modifier.align(Alignment.Center))
            }
        }
}
PantrixSurface(
    modifier = Modifier.widthIn(max = 360.dp),
    shape = shapes.xl,
    border = PantrixTheme.colors.divider
) {
    Column {
        Box(modifier = Modifier.fillMaxWidth().height(64.dp).background(info.color))
        PantrixText(
            text = "The surface's shape cut the band's corners",
            modifier = Modifier.padding(PantrixTheme.spacing.lg)
        )
    }
}

Border

When border is given a colour, a 1dp line that follows the surface's shape is drawn around it; without one there is no border. A panel's border is usually the theme's divider colour. The thickness does not change: only the colour is given.

val colors = PantrixTheme.colors
PantrixSurface(modifier = Modifier.fillMaxWidth(), shape = PantrixTheme.shapes.lg, border = colors.divider) {
    PantrixText(text = "A border in the divider colour", modifier = Modifier.padding(PantrixTheme.spacing.lg))
}
PantrixSurface(
    modifier = Modifier.fillMaxWidth(),
    color = colors.primary.container,
    shape = PantrixTheme.shapes.lg,
    border = colors.primary.color
) {
    PantrixText(text = "A fill, and a border in the role's colour", modifier = Modifier.padding(PantrixTheme.spacing.lg))
}

Shadow

elevation puts a shadow under the surface, following its shape. Here are the five steps of the theme's elevation scale: PantrixTheme.elevation, from 0dp to 8dp. The shadow is drawn outside the surface's bounds: it needs room around the surface.

val elevation = PantrixTheme.elevation
FlowRow(
    horizontalArrangement = Arrangement.spacedBy(PantrixTheme.spacing.xl),
    verticalArrangement = Arrangement.spacedBy(PantrixTheme.spacing.xl)
) {
    listOf(
        "none" to elevation.none,
        "sm" to elevation.sm,
        "md" to elevation.md,
        "lg" to elevation.lg,
        "xl" to elevation.xl
    ).forEach { (name, depth) ->
        PantrixSurface(modifier = Modifier.size(72.dp), elevation = depth) {
            PantrixText(text = name, modifier = Modifier.align(Alignment.Center))
        }
    }
}

The content is in a box

content is a BoxScope: the children are not laid out one under another, they stand one on top of another, and each picks its place with Modifier.align. To lay them out one under another, put a Column inside.

val spacing = PantrixTheme.spacing
PantrixSurface(
    modifier = Modifier.fillMaxWidth().height(120.dp),
    color = PantrixTheme.colors.neutral.container
) {
    PantrixText(text = "TopStart", modifier = Modifier.align(Alignment.TopStart).padding(spacing.md))
    PantrixText(text = "Center", modifier = Modifier.align(Alignment.Center))
    PantrixText(text = "BottomEnd", modifier = Modifier.align(Alignment.BottomEnd).padding(spacing.md))
}

API

modifier

A container that paints color in shape and clips its content to it, with an optional one-dp border and a shadow of elevation.

ParameterTypeDefaultWhat it is
modifierModifierModifierthe surface's modifier: its size and its place.
colorColorPantrixTheme.colors.surface
contentColorColorColor.Unspecified
shapeShapePantrixTheme.shapes.md
borderColorColor.Unspecified
elevationDp0.dp
content@Composable BoxScope.() -> Unitwhat the surface holds, drawn in its content colour and clipped to shape.