Merge pull request #4940 from element-hq/feature/bma/navigationBar
Navigation bar component
This commit is contained in:
@@ -17,7 +17,7 @@ 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.TextStyle
|
||||
import androidx.compose.ui.text.rememberTextMeasurer
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.element.android.compound.theme.ElementTheme
|
||||
@@ -36,18 +36,21 @@ private const val MAX_COUNT_STRING = "+$MAX_COUNT"
|
||||
* @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.
|
||||
* @param textStyle The style to apply to the text inside the counter.
|
||||
* @param isCritical If true, the counter will use a critical color scheme, otherwise it will use an accent color scheme.
|
||||
*/
|
||||
@Composable
|
||||
fun CounterAtom(
|
||||
count: Int,
|
||||
modifier: Modifier = Modifier,
|
||||
textStyle: TextStyle = CounterAtomDefaults.textStyle,
|
||||
isCritical: Boolean = false,
|
||||
) {
|
||||
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(
|
||||
@@ -58,19 +61,30 @@ fun CounterAtom(
|
||||
val squareSize = maxOf(textSize.width, textSize.height)
|
||||
Box(
|
||||
modifier = modifier
|
||||
.size(squareSize.toDp() + 1.dp)
|
||||
.clip(CircleShape)
|
||||
.background(ElementTheme.colors.iconSuccessPrimary)
|
||||
.size(squareSize.toDp() + 1.dp)
|
||||
.clip(CircleShape)
|
||||
.background(
|
||||
if (isCritical) {
|
||||
ElementTheme.colors.iconCriticalPrimary
|
||||
} else {
|
||||
ElementTheme.colors.iconAccentPrimary
|
||||
}
|
||||
)
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier.align(Alignment.Center),
|
||||
text = countAsText,
|
||||
style = textStyle,
|
||||
color = Color.White,
|
||||
color = ElementTheme.colors.textOnSolidPrimary,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
object CounterAtomDefaults {
|
||||
val textStyle: TextStyle
|
||||
@Composable get() = ElementTheme.typography.fontBodyMdMedium
|
||||
}
|
||||
|
||||
@PreviewsDayNight
|
||||
@Composable
|
||||
internal fun CounterAtomPreview() = ElementPreview {
|
||||
@@ -79,5 +93,6 @@ internal fun CounterAtomPreview() = ElementPreview {
|
||||
CounterAtom(count = 4)
|
||||
CounterAtom(count = 99)
|
||||
CounterAtom(count = 100)
|
||||
CounterAtom(count = 4, isCritical = true)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
/*
|
||||
* Copyright 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.designsystem.theme.components
|
||||
|
||||
import androidx.compose.foundation.layout.RowScope
|
||||
import androidx.compose.foundation.layout.WindowInsets
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.NavigationBarDefaults
|
||||
import androidx.compose.material3.contentColorFor
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import io.element.android.compound.theme.ElementTheme
|
||||
import io.element.android.compound.tokens.generated.CompoundIcons
|
||||
import io.element.android.libraries.designsystem.preview.ElementThemedPreview
|
||||
import io.element.android.libraries.designsystem.preview.PreviewGroup
|
||||
|
||||
@Composable
|
||||
fun NavigationBar(
|
||||
modifier: Modifier = Modifier,
|
||||
containerColor: Color = ElementNavigationBarDefaults.containerColor,
|
||||
contentColor: Color = MaterialTheme.colorScheme.contentColorFor(containerColor),
|
||||
tonalElevation: Dp = ElementNavigationBarDefaults.tonalElevation,
|
||||
windowInsets: WindowInsets = ElementNavigationBarDefaults.windowInsets,
|
||||
content: @Composable RowScope.() -> Unit
|
||||
) {
|
||||
androidx.compose.material3.NavigationBar(
|
||||
modifier = modifier,
|
||||
containerColor = containerColor,
|
||||
contentColor = contentColor,
|
||||
tonalElevation = tonalElevation,
|
||||
windowInsets = windowInsets,
|
||||
content = content
|
||||
)
|
||||
}
|
||||
|
||||
object ElementNavigationBarDefaults {
|
||||
val containerColor: Color
|
||||
@Composable get() = if (ElementTheme.isLightTheme) {
|
||||
ElementTheme.colors.bgSubtlePrimary
|
||||
} else {
|
||||
ElementTheme.colors.textOnSolidPrimary
|
||||
}
|
||||
|
||||
val tonalElevation: Dp = NavigationBarDefaults.Elevation
|
||||
|
||||
val windowInsets: WindowInsets
|
||||
@Composable get() = NavigationBarDefaults.windowInsets
|
||||
}
|
||||
|
||||
@Preview(group = PreviewGroup.AppBars)
|
||||
@Composable
|
||||
internal fun NavigationBarPreview() = ElementThemedPreview {
|
||||
NavigationBar {
|
||||
NavigationBarItem(
|
||||
icon = {
|
||||
NavigationBarIcon(
|
||||
imageVector = CompoundIcons.ChatSolid(),
|
||||
count = 5,
|
||||
isCritical = false,
|
||||
)
|
||||
},
|
||||
label = {
|
||||
NavigationBarText(
|
||||
text = "Chats"
|
||||
)
|
||||
},
|
||||
selected = true,
|
||||
onClick = {},
|
||||
)
|
||||
NavigationBarItem(
|
||||
icon = {
|
||||
NavigationBarIcon(
|
||||
imageVector = CompoundIcons.ChatSolid(),
|
||||
count = 5,
|
||||
isCritical = true,
|
||||
)
|
||||
},
|
||||
label = {
|
||||
NavigationBarText(
|
||||
text = "Teams"
|
||||
)
|
||||
},
|
||||
selected = false,
|
||||
onClick = {},
|
||||
)
|
||||
NavigationBarItem(
|
||||
icon = {
|
||||
NavigationBarIcon(
|
||||
imageVector = CompoundIcons.ChatSolid(),
|
||||
count = 0,
|
||||
isCritical = false,
|
||||
)
|
||||
},
|
||||
label = {
|
||||
NavigationBarText(
|
||||
text = "Other"
|
||||
)
|
||||
},
|
||||
selected = false,
|
||||
onClick = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.designsystem.theme.components
|
||||
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.offset
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.element.android.compound.theme.ElementTheme
|
||||
import io.element.android.libraries.designsystem.atomic.atoms.CounterAtom
|
||||
|
||||
@Composable
|
||||
fun NavigationBarIcon(
|
||||
imageVector: ImageVector,
|
||||
modifier: Modifier = Modifier,
|
||||
count: Int = 0,
|
||||
isCritical: Boolean = false,
|
||||
) {
|
||||
Box(modifier) {
|
||||
Icon(
|
||||
imageVector = imageVector,
|
||||
contentDescription = null,
|
||||
)
|
||||
CounterAtom(
|
||||
modifier = Modifier.offset(11.dp, (-11).dp),
|
||||
textStyle = ElementTheme.typography.fontBodyXsMedium,
|
||||
count = count,
|
||||
isCritical = isCritical,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.designsystem.theme.components
|
||||
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.RowScope
|
||||
import androidx.compose.material3.NavigationBarItem
|
||||
import androidx.compose.material3.NavigationBarItemColors
|
||||
import androidx.compose.material3.NavigationBarItemDefaults
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import io.element.android.compound.theme.ElementTheme
|
||||
|
||||
@Composable
|
||||
fun RowScope.NavigationBarItem(
|
||||
selected: Boolean,
|
||||
onClick: () -> Unit,
|
||||
icon: @Composable () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
enabled: Boolean = true,
|
||||
label: @Composable (() -> Unit)? = null,
|
||||
alwaysShowLabel: Boolean = true,
|
||||
colors: NavigationBarItemColors = ElementNavigationBarItemDefaults.colors(),
|
||||
interactionSource: MutableInteractionSource? = null
|
||||
) {
|
||||
NavigationBarItem(
|
||||
selected = selected,
|
||||
onClick = onClick,
|
||||
icon = icon,
|
||||
modifier = modifier,
|
||||
enabled = enabled,
|
||||
label = label,
|
||||
alwaysShowLabel = alwaysShowLabel,
|
||||
colors = colors,
|
||||
interactionSource = interactionSource,
|
||||
)
|
||||
}
|
||||
|
||||
object ElementNavigationBarItemDefaults {
|
||||
@Composable
|
||||
fun colors() = NavigationBarItemDefaults.colors().copy(
|
||||
selectedIconColor = ElementTheme.colors.iconPrimary,
|
||||
selectedTextColor = ElementTheme.colors.textPrimary,
|
||||
unselectedIconColor = ElementTheme.colors.iconTertiary,
|
||||
unselectedTextColor = ElementTheme.colors.textDisabled,
|
||||
selectedIndicatorColor = Color.Companion.Transparent,
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Copyright 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.designsystem.theme.components
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import io.element.android.compound.theme.ElementTheme
|
||||
|
||||
@Composable
|
||||
fun NavigationBarText(
|
||||
text: String,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Text(
|
||||
modifier = modifier,
|
||||
text = text,
|
||||
style = ElementTheme.typography.fontBodySmMedium,
|
||||
)
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user