design : introduce CounterAtom and ListItemContent.Counter
This commit is contained in:
parent
9e2ff83f73
commit
d6f95fd47d
2 changed files with 90 additions and 0 deletions
|
|
@ -0,0 +1,83 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2025 New Vector Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
* Please see LICENSE in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.libraries.designsystem.atomic.atoms
|
||||||
|
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.layout.Arrangement.spacedBy
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.text.rememberTextMeasurer
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import io.element.android.compound.theme.ElementTheme
|
||||||
|
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||||
|
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||||
|
import io.element.android.libraries.designsystem.text.toDp
|
||||||
|
import io.element.android.libraries.designsystem.theme.components.Text
|
||||||
|
|
||||||
|
private const val MAX_COUNT = 99
|
||||||
|
private const val MAX_COUNT_STRING = "+$MAX_COUNT"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A counter atom that displays a number in a circle.
|
||||||
|
* Figma link : https://www.figma.com/design/G1xy0HDZKJf5TCRFmKb5d5/Compound-Android-Components?node-id=2805-2649&m=dev
|
||||||
|
*
|
||||||
|
* @param count The number to display. If the number is greater than [MAX_COUNT], the counter will display [MAX_COUNT_STRING].
|
||||||
|
* If the number is less than 1, the counter will not be displayed.
|
||||||
|
* @param modifier The modifier to apply to this layout.
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
fun CounterAtom(
|
||||||
|
count: Int,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
) {
|
||||||
|
if (count < 1) return
|
||||||
|
val countAsText = when (count) {
|
||||||
|
in 0..MAX_COUNT -> count.toString()
|
||||||
|
else -> MAX_COUNT_STRING
|
||||||
|
}
|
||||||
|
val textStyle = ElementTheme.typography.fontBodyMdMedium
|
||||||
|
val textMeasurer = rememberTextMeasurer()
|
||||||
|
// Measure the maximum count string size
|
||||||
|
val textLayoutResult = textMeasurer.measure(
|
||||||
|
text = MAX_COUNT_STRING,
|
||||||
|
style = textStyle
|
||||||
|
)
|
||||||
|
val textSize = textLayoutResult.size
|
||||||
|
val squareSize = maxOf(textSize.width, textSize.height)
|
||||||
|
Box(
|
||||||
|
modifier = modifier
|
||||||
|
.size(squareSize.toDp() + 1.dp)
|
||||||
|
.clip(CircleShape)
|
||||||
|
.background(ElementTheme.colors.iconSuccessPrimary)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
modifier = Modifier.align(Alignment.Center),
|
||||||
|
text = countAsText,
|
||||||
|
style = textStyle,
|
||||||
|
color = Color.White,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreviewsDayNight
|
||||||
|
@Composable
|
||||||
|
internal fun CounterAtomPreview() = ElementPreview {
|
||||||
|
Column(verticalArrangement = spacedBy(2.dp)) {
|
||||||
|
CounterAtom(count = 0)
|
||||||
|
CounterAtom(count = 4)
|
||||||
|
CounterAtom(count = 99)
|
||||||
|
CounterAtom(count = 100)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -17,6 +17,7 @@ import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.DpSize
|
import androidx.compose.ui.unit.DpSize
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import io.element.android.libraries.designsystem.atomic.atoms.CounterAtom
|
||||||
import io.element.android.libraries.designsystem.atomic.atoms.RedIndicatorAtom
|
import io.element.android.libraries.designsystem.atomic.atoms.RedIndicatorAtom
|
||||||
import io.element.android.libraries.designsystem.theme.components.IconSource
|
import io.element.android.libraries.designsystem.theme.components.IconSource
|
||||||
import io.element.android.libraries.designsystem.theme.components.ListItem
|
import io.element.android.libraries.designsystem.theme.components.ListItem
|
||||||
|
|
@ -91,6 +92,9 @@ sealed interface ListItemContent {
|
||||||
/** Displays a badge. */
|
/** Displays a badge. */
|
||||||
data object Badge : ListItemContent
|
data object Badge : ListItemContent
|
||||||
|
|
||||||
|
/** Displays a counter. */
|
||||||
|
data class Counter(val count: Int) : ListItemContent
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun View() {
|
fun View() {
|
||||||
when (this) {
|
when (this) {
|
||||||
|
|
@ -125,6 +129,9 @@ sealed interface ListItemContent {
|
||||||
) {
|
) {
|
||||||
RedIndicatorAtom()
|
RedIndicatorAtom()
|
||||||
}
|
}
|
||||||
|
is Counter -> {
|
||||||
|
CounterAtom(count = count)
|
||||||
|
}
|
||||||
is Custom -> content()
|
is Custom -> content()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue