PM-24942: Move Segmented control to UI module (#5727)

This commit is contained in:
David Perez
2025-08-18 15:51:31 -05:00
committed by GitHub
parent a8546bb4eb
commit 25a78f60ab
3 changed files with 6 additions and 6 deletions

View File

@@ -0,0 +1,151 @@
package com.bitwarden.ui.platform.components.segment
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.IntrinsicSize
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.WindowInsetsSides
import androidx.compose.foundation.layout.displayCutout
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.navigationBars
import androidx.compose.foundation.layout.only
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.union
import androidx.compose.foundation.layout.windowInsetsPadding
import androidx.compose.material3.SegmentedButton
import androidx.compose.material3.SingleChoiceSegmentedButtonRow
import androidx.compose.material3.SingleChoiceSegmentedButtonRowScope
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.text.style.Hyphens
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import com.bitwarden.ui.platform.base.util.nullableTestTag
import com.bitwarden.ui.platform.base.util.toDp
import com.bitwarden.ui.platform.components.segment.color.bitwardenSegmentedButtonColors
import com.bitwarden.ui.platform.theme.BitwardenTheme
import kotlinx.collections.immutable.ImmutableList
private const val FONT_SCALE_THRESHOLD = 1.5f
/**
* Displays a Bitwarden styled row of segmented buttons.
*
* @param options List of options to display.
* @param modifier Modifier.
* @param windowInsets The insets to be applied to this composable.
* @param optionContent For outer context the content lambda passes back the index of the option,
* the weighted width (in [Dp]) per option (total width / # number of options), and the
* corresponding [SegmentedButtonState].
*/
@Composable
fun BitwardenSegmentedButton(
options: ImmutableList<SegmentedButtonState>,
modifier: Modifier = Modifier,
windowInsets: WindowInsets = WindowInsets.displayCutout
.union(WindowInsets.navigationBars)
.only(WindowInsetsSides.Horizontal),
density: Density = LocalDensity.current,
optionContent: @Composable SingleChoiceSegmentedButtonRowScope.(
Int,
Dp,
SegmentedButtonState,
) -> Unit = { _, _, optionState ->
this.SegmentedButtonOptionContent(
option = optionState,
)
},
) {
if (options.isEmpty()) return
Box(
modifier = modifier
.background(color = BitwardenTheme.colorScheme.background.secondary)
.padding(top = 4.dp, bottom = 8.dp, start = 16.dp, end = 16.dp)
.windowInsetsPadding(insets = windowInsets),
) {
var weightedWidth by remember {
mutableStateOf(0.dp)
}
SingleChoiceSegmentedButtonRow(
modifier = Modifier
.fillMaxWidth()
.background(
color = BitwardenTheme.colorScheme.background.primary,
shape = BitwardenTheme.shapes.segmentedControl,
)
.onGloballyPositioned {
weightedWidth = (it.size.width / options.size).toDp(density)
}
.padding(horizontal = 2.dp, vertical = 2.dp)
.height(IntrinsicSize.Min),
space = 0.dp,
) {
options.forEachIndexed { index, option ->
optionContent(index, weightedWidth, option)
}
}
}
}
/**
* Default content definition for each option in a [BitwardenSegmentedButton].
*/
@Composable
fun SingleChoiceSegmentedButtonRowScope.SegmentedButtonOptionContent(
option: SegmentedButtonState,
modifier: Modifier = Modifier,
) {
val fontScale = LocalConfiguration.current.fontScale
val labelVerticalPadding = if (fontScale > FONT_SCALE_THRESHOLD) {
8.dp
} else {
0.dp
}
SegmentedButton(
enabled = option.isEnabled,
selected = option.isChecked,
onClick = option.onClick,
colors = bitwardenSegmentedButtonColors(),
shape = BitwardenTheme.shapes.segmentedControl,
border = BorderStroke(width = 0.dp, color = Color.Transparent),
label = {
Text(
text = option.text,
style = BitwardenTheme.typography.labelLarge.copy(
hyphens = Hyphens.Auto,
),
modifier = Modifier.padding(
vertical = labelVerticalPadding,
horizontal = 4.dp,
),
)
},
icon = {
// No icon required
},
modifier = modifier.nullableTestTag(tag = option.testTag),
)
}
/**
* Models state for an individual button in a [BitwardenSegmentedButton].
*/
data class SegmentedButtonState(
val text: String,
val onClick: () -> Unit,
val isChecked: Boolean,
val isEnabled: Boolean = true,
val testTag: String? = null,
)

View File

@@ -0,0 +1,25 @@
package com.bitwarden.ui.platform.components.segment.color
import androidx.compose.material3.SegmentedButtonColors
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.Color
import com.bitwarden.ui.platform.theme.BitwardenTheme
/**
* Provides a default set of Bitwarden-styled colors for segmented buttons.
*/
@Composable
fun bitwardenSegmentedButtonColors(): SegmentedButtonColors = SegmentedButtonColors(
activeContainerColor = BitwardenTheme.colorScheme.filledButton.backgroundReversed,
activeContentColor = BitwardenTheme.colorScheme.filledButton.foregroundReversed,
activeBorderColor = Color.Transparent,
inactiveContainerColor = BitwardenTheme.colorScheme.background.primary,
inactiveContentColor = BitwardenTheme.colorScheme.text.secondary,
inactiveBorderColor = Color.Transparent,
disabledActiveContainerColor = BitwardenTheme.colorScheme.background.primary,
disabledActiveContentColor = BitwardenTheme.colorScheme.filledButton.foregroundDisabled,
disabledActiveBorderColor = Color.Transparent,
disabledInactiveContainerColor = BitwardenTheme.colorScheme.background.primary,
disabledInactiveContentColor = BitwardenTheme.colorScheme.stroke.divider,
disabledInactiveBorderColor = Color.Transparent,
)