Skip to content

Commit

Permalink
Added ScrollableTabRow component. Also exposed divider slot on TabRow.
Browse files Browse the repository at this point in the history
  • Loading branch information
Bořek Leikep committed Jul 18, 2023
1 parent 21f1df9 commit 4afccaf
Show file tree
Hide file tree
Showing 7 changed files with 112 additions and 12 deletions.
18 changes: 14 additions & 4 deletions docs/03-components/02-structure/tabs.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@ title: Android
`Tab` component is provided as a simple wrapping layout and tab item components:

- [`TabRow`](https://kiwicom.github.io/orbit-compose/ui/kiwi.orbit.compose.ui.controls/-tab-row.html)
- [`ScrollableTabRow`](https://kiwicom.github.io/orbit-compose/ui/kiwi.orbit.compose.ui.controls/-scrollable-tab-row.html)
- [`Tab`](https://kiwicom.github.io/orbit-compose/ui/kiwi.orbit.compose.ui.controls/-tab.html)

## Usage

`TabRow` layout takes a `content` slot with multiple `Tab`s. `TabRow` should utilize `TopAppBar`'s `extraContent` slot.
`ScrollableTabRow` is a scrollable variant of the component, allowing for more tabs to be displayed off screen.

```kotlin
@Composable
Expand Down Expand Up @@ -40,12 +42,14 @@ fun Example() {

## UI Testing

`Tab` composable provides a standard interface for UI testing, e.g. it exposes `isSelected` semantics property to check which tab is selected.
`Tab` composable provides a standard interface for UI testing, e.g. it exposes `isSelected` semantics property
to check which tab is selected. If your tabs are in a `ScrollableTabRow` you should first call
`performScrollTo()` before `performClick()` to make sure the tab is actually visible on screen.

```kotlin
composeTestRule.setContent {
var selectedPage by remember { mutableStateOf(0) }
TabRow(selectedTabIndex = selectedPage) {
ScrollableTabRow(selectedTabIndex = selectedPage) {
Tab(
modifier = Modifier.testTag("variantA"),
selected = selectedPage == 0,
Expand All @@ -58,11 +62,17 @@ composeTestRule.setContent {
onClick = { selectedPage = 1 },
text = { Text("Variant B") },
)
Tab(
modifier = Modifier.testTag("variantC"),
selected = selectedPage == 2,
onClick = { selectedPage = 2 },
text = { Text("Variant C") },
)
}
}
composeTestRule.onNodeWithTag("variantB").performClick().assertIsSelected()
composeTestRule.onNodeWithTag("variantC").performScrollTo().performClick().assertIsSelected()
```

## Customization

`TabRow`/`Tab` appearance is not customizable.
`TabRow`/`ScrollableTabRow`/`Tab` appearance is not customizable.
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,10 @@ class MaterialDesignInsteadOrbitDesignDetector : Detector(), Detector.UastScanne
"androidx.compose.material.TabRow",
"androidx.compose.material3.TabRow",
),
"kiwi.orbit.compose.ui.controls.ScrollableTabRow" to setOf(
"androidx.compose.material.ScrollableTabRow",
"androidx.compose.material3.ScrollableTabRow",
),
"kiwi.orbit.compose.ui.controls.Text" to setOf(
"androidx.compose.material.Text",
"androidx.compose.material3.Text",
Expand Down
47 changes: 46 additions & 1 deletion ui/src/androidMain/kotlin/kiwi/orbit/compose/ui/controls/Tabs.kt
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import kiwi.orbit.compose.ui.OrbitTheme
import kiwi.orbit.compose.ui.controls.internal.OrbitElevations
Expand All @@ -34,6 +35,9 @@ public fun TabRow(
Modifier.tabIndicatorOffset(tabPositions[selectedTabIndex]),
)
},
divider: @Composable () -> Unit = @Composable {
Divider()
},
tabs: @Composable () -> Unit,
) {
OrbitElevations {
Expand All @@ -43,7 +47,36 @@ public fun TabRow(
containerColor = OrbitTheme.colors.surface.main,
contentColor = contentColorFor(OrbitTheme.colors.surface.main),
indicator = indicator,
divider = { Divider() },
divider = divider,
tabs = tabs,
)
}
}

@Composable
public fun ScrollableTabRow(
selectedTabIndex: Int,
modifier: Modifier = Modifier,
edgePadding: Dp = ScrollableTabRowPadding,
indicator: @Composable (tabPositions: List<TabPosition>) -> Unit = @Composable { tabPositions ->
TabIndicator(
Modifier.tabIndicatorOffset(tabPositions[selectedTabIndex]),
)
},
divider: @Composable () -> Unit = @Composable {
Divider()
},
tabs: @Composable () -> Unit,
) {
OrbitElevations {
androidx.compose.material3.ScrollableTabRow(
selectedTabIndex = selectedTabIndex,
modifier = modifier,
containerColor = OrbitTheme.colors.surface.main,
contentColor = contentColorFor(OrbitTheme.colors.surface.main),
edgePadding = edgePadding,
indicator = indicator,
divider = divider,
tabs = tabs,
)
}
Expand Down Expand Up @@ -93,6 +126,11 @@ public fun TabIndicator(
)
}

/**
* The default padding from the starting edge before a tab in a [ScrollableTabRow].
*/
private val ScrollableTabRowPadding = 52.dp

@OrbitPreviews
@Composable
internal fun TabsPreview() {
Expand All @@ -103,5 +141,12 @@ internal fun TabsPreview() {
Tab(selected = i == 1, onClick = { i = 1 }) { Text("Tab B") }
Tab(selected = i == 2, onClick = { i = 2 }) { Text("Tab C") }
}
ScrollableTabRow(selectedTabIndex = i) {
Tab(selected = i == 0, onClick = { i = 0 }) { Text("Tab A") }
Tab(selected = i == 1, onClick = { i = 1 }) { Text("Tab B") }
Tab(selected = i == 2, onClick = { i = 2 }) { Text("Tab C") }
Tab(selected = i == 3, onClick = { i = 3 }) { Text("Tab D") }
Tab(selected = i == 4, onClick = { i = 4 }) { Text("Tab E") }
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import androidx.compose.ui.test.assertIsSelected
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.performClick
import androidx.compose.ui.test.performScrollTo
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
Expand All @@ -21,7 +22,7 @@ internal class TabsTest {
val composeTestRule = createComposeRule()

@Test
fun testBasics() {
fun testTabRow() {
composeTestRule.setContent {
var selectedPage by remember { mutableStateOf(0) }
TabRow(selectedTabIndex = selectedPage) {
Expand All @@ -41,4 +42,44 @@ internal class TabsTest {
}
composeTestRule.onNodeWithTag("variantB").performClick().assertIsSelected()
}

@Test
fun testScrollableTabRow() {
composeTestRule.setContent {
var selectedPage by remember { mutableStateOf(0) }
ScrollableTabRow(selectedTabIndex = selectedPage) {
Tab(
modifier = Modifier.testTag("variantA"),
selected = selectedPage == 0,
onClick = { selectedPage = 0 },
text = { Text("Variant A") },
)
Tab(
modifier = Modifier.testTag("variantB"),
selected = selectedPage == 1,
onClick = { selectedPage = 1 },
text = { Text("Variant B") },
)
Tab(
modifier = Modifier.testTag("variantC"),
selected = selectedPage == 2,
onClick = { selectedPage = 2 },
text = { Text("Variant C") },
)
Tab(
modifier = Modifier.testTag("variantD"),
selected = selectedPage == 3,
onClick = { selectedPage = 3 },
text = { Text("Variant D") },
)
Tab(
modifier = Modifier.testTag("variantE"),
selected = selectedPage == 4,
onClick = { selectedPage = 4 },
text = { Text("Variant E") },
)
}
}
composeTestRule.onNodeWithTag("variantE").performScrollTo().performClick().assertIsSelected()
}
}
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.

0 comments on commit 4afccaf

Please sign in to comment.