Skip to content

Commit

Permalink
don-862 implement segmented control (#2139)
Browse files Browse the repository at this point in the history
* don-862 implement segmented control

* updates for shadow and styles

* update divider height

* address feedback

* up todate documentation screenshots

* Updated snapshots

* use BpkBorderRadius.Sm

* added ripple effect

---------

Co-authored-by: Lokmane Krizou <[email protected]>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
  • Loading branch information
3 people authored Nov 12, 2024
1 parent b76e02e commit a7d42fb
Show file tree
Hide file tree
Showing 58 changed files with 617 additions and 0 deletions.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/**
* Backpack for Android - Skyscanner's Design System
*
* Copyright 2018 Skyscanner Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package net.skyscanner.backpack.demo.components

import net.skyscanner.backpack.meta.ComponentMarker

@ComponentMarker("SegmentedControl")
annotation class SegmentedControlComponent
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
/**
* Backpack for Android - Skyscanner's Design System
*
* Copyright 2018 Skyscanner Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package net.skyscanner.backpack.demo.compose

import androidx.compose.animation.animateColorAsState
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
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.res.stringResource
import net.skyscanner.backpack.compose.divider.BpkDivider
import net.skyscanner.backpack.compose.segmentedcontrol.BpkSegmentedControl
import net.skyscanner.backpack.compose.segmentedcontrol.BpkSegmentedControlStyle
import net.skyscanner.backpack.compose.text.BpkText
import net.skyscanner.backpack.compose.theme.BpkTheme
import net.skyscanner.backpack.compose.tokens.BpkSpacing
import net.skyscanner.backpack.demo.R
import net.skyscanner.backpack.demo.components.SegmentedControlComponent
import net.skyscanner.backpack.demo.meta.ComposeStory

@Composable
@SegmentedControlComponent
@ComposeStory("Canvas Default")
fun SegmentedControlCanvasDefaultStory(modifier: Modifier = Modifier) {
SegmentedControlsSample(
type = BpkSegmentedControlStyle.CanvasDefault,
modifier = modifier,
)
}

@Composable
@SegmentedControlComponent
@ComposeStory("Canvas Contrast")
fun SegmentedControlCanvasContrastStory(modifier: Modifier = Modifier) {
SegmentedControlsSample(
type = BpkSegmentedControlStyle.CanvasContrast,
modifier = modifier,
)
}

@Composable
@SegmentedControlComponent
@ComposeStory("Surface Default")
fun SegmentedControlSurfaceDefaultStory(modifier: Modifier = Modifier) {
SegmentedControlsSample(
type = BpkSegmentedControlStyle.SurfaceDefault,
modifier = modifier,
)
}

@Composable
@SegmentedControlComponent
@ComposeStory("Surface Contrast")
fun SegmentedControlSurfaceContrastStory(modifier: Modifier = Modifier) {
SegmentedControlsSample(
type = BpkSegmentedControlStyle.SurfaceContrast,
modifier = modifier,
)
}

@Composable
private fun SegmentedControlsSample(
type: BpkSegmentedControlStyle,
modifier: Modifier = Modifier,
) {
Column(
modifier = modifier
.fillMaxSize()
.background(type.toColor())
.padding(BpkSpacing.Base),
verticalArrangement = Arrangement.spacedBy(BpkSpacing.Lg),
) {
SegmentedControlCase(
numberOfButtons = 2,
type = type,
)
BpkDivider()
SegmentedControlCase(
numberOfButtons = 3,
type = type,
)
BpkDivider()
SegmentedControlCase(
numberOfButtons = 4,
type = type,
)
}
}

@Composable
private fun SegmentedControlCase(
numberOfButtons: Int,
type: BpkSegmentedControlStyle,
modifier: Modifier = Modifier,
) {
Column(
modifier = modifier,
verticalArrangement = Arrangement.spacedBy(BpkSpacing.Base),
) {

SegmentedControlWithShadowStatus(
numberOfButtons = numberOfButtons,
type = type,
)
SegmentedControlWithShadowStatus(
shadow = true,
numberOfButtons = numberOfButtons,
type = type,
)
}
}

@Composable
private fun SegmentedControlWithShadowStatus(
numberOfButtons: Int,
type: BpkSegmentedControlStyle,
modifier: Modifier = Modifier,
shadow: Boolean = false,
) {
Column(
modifier = modifier,
verticalArrangement = Arrangement.spacedBy(BpkSpacing.Md),
) {
BpkText(
color = type.getTextColor(),
style = BpkTheme.typography.bodyDefault,
text = stringResource(
id = R.string.shadow_status,
stringResource(if (shadow) R.string.on else R.string.off),
),
)
SegmentedControlsSample(
numberOfButtons = numberOfButtons,
type = type,
shadow = shadow,
)
}
}

@Composable
internal fun SegmentedControlsSample(
numberOfButtons: Int,
type: BpkSegmentedControlStyle,
modifier: Modifier = Modifier,
selectedIndex: Int = 0,
content: String = stringResource(R.string.value),
shadow: Boolean = false,
) {
var currentIndex by remember { mutableIntStateOf(selectedIndex) }
val buttonContents = List(numberOfButtons) { content }
BpkSegmentedControl(
modifier = modifier,
type = type,
shadow = shadow,
buttonContents = buttonContents,
onItemClick = { currentIndex = it },
selectedIndex = selectedIndex,
)
}

@Composable
private fun BpkSegmentedControlStyle.getTextColor(): Color {
return animateColorAsState(
if (this == BpkSegmentedControlStyle.SurfaceContrast) BpkTheme.colors.textOnDark
else BpkTheme.colors.textPrimary,
).value
}

@Composable
internal fun BpkSegmentedControlStyle.toColor() = when (this) {
BpkSegmentedControlStyle.CanvasDefault -> BpkTheme.colors.canvas
BpkSegmentedControlStyle.CanvasContrast -> BpkTheme.colors.canvasContrast
BpkSegmentedControlStyle.SurfaceDefault -> BpkTheme.colors.surfaceDefault
BpkSegmentedControlStyle.SurfaceContrast -> BpkTheme.colors.surfaceContrast
}
4 changes: 4 additions & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -398,4 +398,8 @@
<string name="card_list_item_2_body_text">Watch the city come alive in bloom as the warmer climes approach.</string>
<string name="card_list_item_3_body_text">Immerse yourself in the rich history and culture of the Irish capital.</string>
<string name="card_list_item_9_body_text">Watch the city come alive in bloom as the warmer climes approach.</string>
<string name="shadow_status" translatable="false">Shadow: %1$s</string>
<string name="on" translatable="false">On</string>
<string name="off" translatable="false">Off</string>
<string name="value" translatable="false">Value</string>
</resources>
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/**
* Backpack for Android - Skyscanner's Design System
*
* Copyright 2018 Skyscanner Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package net.skyscanner.backpack.compose.segmentedcontrol

import androidx.compose.ui.res.stringResource
import net.skyscanner.backpack.compose.BpkSnapshotTest
import net.skyscanner.backpack.demo.R
import net.skyscanner.backpack.demo.compose.SegmentedControlsSample
import net.skyscanner.backpack.demo.compose.toColor
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.ParameterizedRobolectricTestRunner

@RunWith(ParameterizedRobolectricTestRunner::class)
class BpkSegmentedControlTest(private val permutation: Permutation) : BpkSnapshotTest(listOf(permutation)) {

@Test
fun regularValue() = snap({ permutation.type.toColor() }) {
SegmentedControlsSample(
type = permutation.type,
shadow = permutation.shadowEnabled,
selectedIndex = permutation.selectedInt,
numberOfButtons = permutation.numberOfButtons,
)
}

@Test
fun longValue() = snap({ permutation.type.toColor() }) {
SegmentedControlsSample(
type = permutation.type,
shadow = permutation.shadowEnabled,
selectedIndex = permutation.selectedInt,
numberOfButtons = permutation.numberOfButtons,
content = stringResource(R.string.button_long_text),
)
}

companion object {
enum class Permutation(
val type: BpkSegmentedControlStyle,
val shadowEnabled: Boolean = false,
val numberOfButtons: Int = 2,
val selectedInt: Int = 0,
) {
CanvasDefaultTwoButtons(type = BpkSegmentedControlStyle.CanvasDefault),
CanvasContrastTwoButtons(type = BpkSegmentedControlStyle.CanvasContrast),
SurfaceDefaultTwoButtons(type = BpkSegmentedControlStyle.SurfaceDefault),
SurfaceContrastTwoButtons(type = BpkSegmentedControlStyle.SurfaceContrast),
CanvasDefaultThreeButtons(type = BpkSegmentedControlStyle.CanvasDefault, numberOfButtons = 3),
CanvasDefaultFourButtons(type = BpkSegmentedControlStyle.CanvasDefault, numberOfButtons = 4),
CanvasDefaultTwoButtonsAndShadow(type = BpkSegmentedControlStyle.CanvasDefault, shadowEnabled = true),
}

@JvmStatic
@ParameterizedRobolectricTestRunner.Parameters(name = "{0} Screenshot")
fun data(): Collection<Array<Any>> {
return Permutation.entries.map { arrayOf(it) }
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/**
* Backpack for Android - Skyscanner's Design System
*
* Copyright 2018 Skyscanner Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package net.skyscanner.backpack.compose.segmentedcontrol

import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import net.skyscanner.backpack.compose.segmentedcontrol.internal.BpkSegmentedControlImpl

enum class BpkSegmentedControlStyle {
CanvasDefault,
CanvasContrast,
SurfaceDefault,
SurfaceContrast,
}

private const val maxNumberOfButtons = 4

@Composable
fun BpkSegmentedControl(
buttonContents: List<String>,
onItemClick: (Int) -> Unit,
selectedIndex: Int,
modifier: Modifier = Modifier,
shadow: Boolean = false,
type: BpkSegmentedControlStyle = BpkSegmentedControlStyle.CanvasDefault,
) {
val limitedButtonContents = buttonContents.take(maxNumberOfButtons)
BpkSegmentedControlImpl(
buttonContents = limitedButtonContents,
onItemClick = onItemClick,
selectedIndex = selectedIndex,
modifier = modifier,
shadow = shadow,
type = type,
)
}
Loading

0 comments on commit a7d42fb

Please sign in to comment.