diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/atoms/CounterAtom.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/atoms/CounterAtom.kt index 32b96b6260..483c9aa346 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/atoms/CounterAtom.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/atoms/CounterAtom.kt @@ -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) } } diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/NavigationBar.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/NavigationBar.kt new file mode 100644 index 0000000000..9d67f4ec05 --- /dev/null +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/NavigationBar.kt @@ -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 = {}, + ) + } +} diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/NavigationBarIcon.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/NavigationBarIcon.kt new file mode 100644 index 0000000000..2c2890f854 --- /dev/null +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/NavigationBarIcon.kt @@ -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, + ) + } +} diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/NavigationBarItem.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/NavigationBarItem.kt new file mode 100644 index 0000000000..8c42b932dd --- /dev/null +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/NavigationBarItem.kt @@ -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, + ) +} diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/NavigationBarText.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/NavigationBarText.kt new file mode 100644 index 0000000000..826c99f743 --- /dev/null +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/NavigationBarText.kt @@ -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, + ) +} diff --git a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_15_en.png b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_15_en.png index 3818826788..d21d1e40f0 100644 --- a/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_15_en.png +++ b/tests/uitests/src/test/snapshots/images/features.roomdetails.impl_RoomDetailsDark_15_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8201ab8637db30a33b16a1336705f1777fff946832e9f10ac45987eaa44a20b4 -size 42884 +oid sha256:3f40ee24252a856c4680dc0b62865e71108d54b326c81b651dcf9031c8d7ae95 +size 42835 diff --git a/tests/uitests/src/test/snapshots/images/libraries.designsystem.atomic.atoms_CounterAtom_Day_0_en.png b/tests/uitests/src/test/snapshots/images/libraries.designsystem.atomic.atoms_CounterAtom_Day_0_en.png index f12cb438d2..aa6c71936b 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.designsystem.atomic.atoms_CounterAtom_Day_0_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.designsystem.atomic.atoms_CounterAtom_Day_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7a2a171ac0edac9cb734d418b7baf68dd294d9feb93f005cf445420a7538a8ed -size 7687 +oid sha256:49f6848dbff23f8c906894ee852253c051807c942f968ba3856e361aeae5c2ca +size 8683 diff --git a/tests/uitests/src/test/snapshots/images/libraries.designsystem.atomic.atoms_CounterAtom_Night_0_en.png b/tests/uitests/src/test/snapshots/images/libraries.designsystem.atomic.atoms_CounterAtom_Night_0_en.png index cd66fcf3c8..4661a04e60 100644 --- a/tests/uitests/src/test/snapshots/images/libraries.designsystem.atomic.atoms_CounterAtom_Night_0_en.png +++ b/tests/uitests/src/test/snapshots/images/libraries.designsystem.atomic.atoms_CounterAtom_Night_0_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:56508c5827cfda4c672f924e3e9280659b729b772455c65dbae76ba7815e18dd -size 7502 +oid sha256:d341142ffd67a41fee3b2f41146011caeee07b9ec3085d9c47131d1a5d68683a +size 8002 diff --git a/tests/uitests/src/test/snapshots/images/libraries.designsystem.theme.components_NavigationBar_App_Bars_en.png b/tests/uitests/src/test/snapshots/images/libraries.designsystem.theme.components_NavigationBar_App_Bars_en.png new file mode 100644 index 0000000000..f25072200f --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/libraries.designsystem.theme.components_NavigationBar_App_Bars_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0554fae92f638dcb6f0701c36f67df8126402a74a8864daf8f821cd5d3ffd29c +size 16367