diff --git a/docs/03-components/02-structure/tabs.mdx b/docs/03-components/02-structure/tabs.mdx index e6ee7088b..ce74111fc 100644 --- a/docs/03-components/02-structure/tabs.mdx +++ b/docs/03-components/02-structure/tabs.mdx @@ -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 @@ -65,4 +67,4 @@ composeTestRule.onNodeWithTag("variantB").performClick().assertIsSelected() ## Customization -`TabRow`/`Tab` appearance is not customizable. +`TabRow`/`ScrollableTabRow`/`Tab` appearance is not customizable. diff --git a/lint/src/main/kotlin/kiwi/orbit/compose/lint/detectors/MaterialDesignInsteadOrbitDesignDetector.kt b/lint/src/main/kotlin/kiwi/orbit/compose/lint/detectors/MaterialDesignInsteadOrbitDesignDetector.kt index d078a7cc2..b8c30a610 100644 --- a/lint/src/main/kotlin/kiwi/orbit/compose/lint/detectors/MaterialDesignInsteadOrbitDesignDetector.kt +++ b/lint/src/main/kotlin/kiwi/orbit/compose/lint/detectors/MaterialDesignInsteadOrbitDesignDetector.kt @@ -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", diff --git a/ui/src/androidMain/kotlin/kiwi/orbit/compose/ui/controls/Tabs.kt b/ui/src/androidMain/kotlin/kiwi/orbit/compose/ui/controls/Tabs.kt index d59a70a69..6e168305e 100644 --- a/ui/src/androidMain/kotlin/kiwi/orbit/compose/ui/controls/Tabs.kt +++ b/ui/src/androidMain/kotlin/kiwi/orbit/compose/ui/controls/Tabs.kt @@ -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 @@ -34,6 +35,9 @@ public fun TabRow( Modifier.tabIndicatorOffset(tabPositions[selectedTabIndex]), ) }, + divider: @Composable () -> Unit = @Composable { + Divider() + }, tabs: @Composable () -> Unit, ) { OrbitElevations { @@ -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) -> 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, ) } @@ -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() { @@ -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") } + } } } diff --git a/ui/src/androidUnitTest/kotlin/kiwi/orbit/compose/ui/controls/TabsTest.kt b/ui/src/androidUnitTest/kotlin/kiwi/orbit/compose/ui/controls/TabsTest.kt index 15b363d84..48323a777 100644 --- a/ui/src/androidUnitTest/kotlin/kiwi/orbit/compose/ui/controls/TabsTest.kt +++ b/ui/src/androidUnitTest/kotlin/kiwi/orbit/compose/ui/controls/TabsTest.kt @@ -21,7 +21,7 @@ internal class TabsTest { val composeTestRule = createComposeRule() @Test - fun testBasics() { + fun testTabRow() { composeTestRule.setContent { var selectedPage by remember { mutableStateOf(0) } TabRow(selectedTabIndex = selectedPage) { @@ -41,4 +41,47 @@ internal class TabsTest { } composeTestRule.onNodeWithTag("variantB").performClick().assertIsSelected() } + + @Test + fun testScrollableTabRow() { + composeTestRule.setContent { + var selectedPage by remember { mutableStateOf(0) } + ScrollableTabRow( + selectedTabIndex = selectedPage, + modifier = Modifier.testTag("tabs"), + ) { + 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("variantB").performClick().assertIsSelected() + } } diff --git a/ui/src/test/snapshots/images/kiwi.orbit.compose.ui_ScreenshotTest_tabs.png b/ui/src/test/snapshots/images/kiwi.orbit.compose.ui_ScreenshotTest_tabs.png index 033c1f83a..2f21e864d 100644 --- a/ui/src/test/snapshots/images/kiwi.orbit.compose.ui_ScreenshotTest_tabs.png +++ b/ui/src/test/snapshots/images/kiwi.orbit.compose.ui_ScreenshotTest_tabs.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:76dea795511e737f85568e9e915782b9445413847d0172d47de210e008c1a4fc -size 7125 +oid sha256:ad16b65f270f79355f685b43437effd38cbe647f59e8670aeaab8db8c5795156 +size 17409 diff --git a/ui/src/test/snapshots/images/kiwi.orbit.compose.ui_ScreenshotTest_tabs_big.png b/ui/src/test/snapshots/images/kiwi.orbit.compose.ui_ScreenshotTest_tabs_big.png index 22a32fa18..90271b366 100644 --- a/ui/src/test/snapshots/images/kiwi.orbit.compose.ui_ScreenshotTest_tabs_big.png +++ b/ui/src/test/snapshots/images/kiwi.orbit.compose.ui_ScreenshotTest_tabs_big.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3f9becada1c879d549553a425c4b6fa75bbfa9ba97c4a1207704b5408cf3c95d -size 12983 +oid sha256:83f48f3e3aaba2043ecb16eddc2fd1759e8e5b064e87c319c5ac5039303ca906 +size 25598 diff --git a/ui/src/test/snapshots/images/kiwi.orbit.compose.ui_ScreenshotTest_tabs_dark.png b/ui/src/test/snapshots/images/kiwi.orbit.compose.ui_ScreenshotTest_tabs_dark.png index 0f65bc4e6..cb11f47fe 100644 --- a/ui/src/test/snapshots/images/kiwi.orbit.compose.ui_ScreenshotTest_tabs_dark.png +++ b/ui/src/test/snapshots/images/kiwi.orbit.compose.ui_ScreenshotTest_tabs_dark.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:681e195af6da97a5c9c2f11ece274514ce7be9f5de8df93d9a1f22e8b45427bb -size 7291 +oid sha256:6c4324a2f346be0dba34c1b27d249371a81b4292e7ce7f291fffc5c637e65e29 +size 17794