update tab-row default spacing

Refactor the demos application

Test: NA

Relnote: NA

Change-Id: I83429a816cefa21aec877c15df42f47a49376034
diff --git a/tv/integration-tests/demos/src/main/java/androidx/tv/integration/demos/App.kt b/tv/integration-tests/demos/src/main/java/androidx/tv/integration/demos/App.kt
index 00ea81e..d966d00 100644
--- a/tv/integration-tests/demos/src/main/java/androidx/tv/integration/demos/App.kt
+++ b/tv/integration-tests/demos/src/main/java/androidx/tv/integration/demos/App.kt
@@ -16,8 +16,10 @@
 
 package androidx.tv.integration.demos
 
+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
@@ -25,22 +27,26 @@
 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.unit.dp
 import androidx.tv.material3.ExperimentalTvMaterial3Api
 import androidx.tv.material3.MaterialTheme
 import androidx.tv.material3.darkColorScheme
 
+val pageColor = Color.Black
+
 @OptIn(ExperimentalTvMaterial3Api::class)
 @Composable
 fun App() {
     var selectedTab by remember { mutableStateOf(Navigation.FeaturedCarousel) }
 
-    MaterialTheme(
-        colorScheme = darkColorScheme()
-    ) {
+    MaterialTheme(colorScheme = darkColorScheme()) {
         Column(
-            modifier = Modifier.padding(20.dp),
-            verticalArrangement = Arrangement.spacedBy(20.dp)
+            modifier = Modifier
+                .background(pageColor)
+                .fillMaxSize()
+                .padding(20.dp),
+            verticalArrangement = Arrangement.spacedBy(20.dp),
         ) {
             TopNavigation(updateSelectedTab = { selectedTab = it })
             selectedTab.action.invoke()
diff --git a/tv/integration-tests/demos/src/main/java/androidx/tv/integration/demos/Card.kt b/tv/integration-tests/demos/src/main/java/androidx/tv/integration/demos/Card.kt
new file mode 100644
index 0000000..db5bbb1
--- /dev/null
+++ b/tv/integration-tests/demos/src/main/java/androidx/tv/integration/demos/Card.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * 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 androidx.tv.integration.demos
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.focusable
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.width
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.unit.dp
+
+@Composable
+fun Card(
+    modifier: Modifier = Modifier,
+    backgroundColor: Color = Color.Transparent,
+) {
+    Box(
+        modifier = modifier
+            .background(backgroundColor.copy(alpha = 0.3f))
+            .width(200.dp)
+            .height(150.dp)
+            .drawBorderOnFocus()
+            .focusable()
+    )
+}
\ No newline at end of file
diff --git a/tv/integration-tests/demos/src/main/java/androidx/tv/integration/demos/FeaturedCarousel.kt b/tv/integration-tests/demos/src/main/java/androidx/tv/integration/demos/FeaturedCarousel.kt
index 6a5007f..bc21280 100644
--- a/tv/integration-tests/demos/src/main/java/androidx/tv/integration/demos/FeaturedCarousel.kt
+++ b/tv/integration-tests/demos/src/main/java/androidx/tv/integration/demos/FeaturedCarousel.kt
@@ -42,8 +42,8 @@
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.focus.onFocusChanged
 import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.dp
+import androidx.tv.foundation.ExperimentalTvFoundationApi
 import androidx.tv.material3.Carousel
 import androidx.tv.material3.CarouselDefaults
 import androidx.tv.material3.CarouselState
@@ -88,15 +88,9 @@
     }
 }
 
-@Composable
-fun Modifier.drawBorderOnFocus(borderColor: Color = Color.White, width: Dp = 5.dp): Modifier {
-    var isFocused by remember { mutableStateOf(false) }
-    return this
-        .border(width, borderColor.copy(alpha = if (isFocused) 1f else 0.2f))
-        .onFocusChanged { isFocused = it.isFocused }
-}
-
-@OptIn(ExperimentalTvMaterial3Api::class, ExperimentalAnimationApi::class)
+@OptIn(ExperimentalTvMaterial3Api::class, ExperimentalAnimationApi::class,
+    ExperimentalTvFoundationApi::class
+)
 @Composable
 internal fun FeaturedCarousel(modifier: Modifier = Modifier) {
     val backgrounds = listOf(
@@ -111,35 +105,42 @@
     )
 
     val carouselState = remember { CarouselState() }
-    Carousel(
-        itemCount = backgrounds.size,
-        carouselState = carouselState,
-        modifier = modifier
-            .height(300.dp)
-            .fillMaxWidth(),
-        carouselIndicator = {
-            CarouselDefaults.IndicatorRow(
-                itemCount = backgrounds.size,
-                activeItemIndex = carouselState.activeItemIndex,
-                modifier = Modifier
-                    .align(Alignment.BottomEnd)
-                    .padding(16.dp),
-            )
-        }
-    ) { itemIndex ->
-        CarouselItem(
-            background = {
-                Box(
+    FocusGroup {
+        Carousel(
+            itemCount = backgrounds.size,
+            carouselState = carouselState,
+            modifier = modifier
+                .height(300.dp)
+                .fillMaxWidth(),
+            carouselIndicator = {
+                CarouselDefaults.IndicatorRow(
+                    itemCount = backgrounds.size,
+                    activeItemIndex = carouselState.activeItemIndex,
                     modifier = Modifier
-                        .background(backgrounds[itemIndex])
-                        .fillMaxSize()
+                        .align(Alignment.BottomEnd)
+                        .padding(16.dp),
                 )
             }
-        ) {
-            Box(modifier = Modifier) {
-                OverlayButton(
-                    modifier = Modifier
-                )
+        ) { itemIndex ->
+            CarouselItem(
+                background = {
+                    Box(
+                        modifier = Modifier
+                            .background(backgrounds[itemIndex])
+                            .fillMaxSize()
+                    )
+                },
+                modifier =
+                if (itemIndex == 0)
+                    Modifier.initiallyFocused()
+                else
+                    Modifier.restorableFocus()
+            ) {
+                Box(modifier = Modifier) {
+                    OverlayButton(
+                        modifier = Modifier
+                    )
+                }
             }
         }
     }
diff --git a/tv/integration-tests/demos/src/main/java/androidx/tv/integration/demos/FocusGroup.kt b/tv/integration-tests/demos/src/main/java/androidx/tv/integration/demos/FocusGroup.kt
new file mode 100644
index 0000000..6d19cbd
--- /dev/null
+++ b/tv/integration-tests/demos/src/main/java/androidx/tv/integration/demos/FocusGroup.kt
@@ -0,0 +1,183 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * 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 androidx.tv.integration.demos
+
+import android.annotation.SuppressLint
+import android.util.Log
+import androidx.compose.foundation.focusable
+import androidx.compose.foundation.layout.Box
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.DisposableEffect
+import androidx.compose.runtime.MutableState
+import androidx.compose.runtime.Stable
+import androidx.compose.runtime.currentCompositeKeyHash
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberUpdatedState
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.ui.ExperimentalComposeUiApi
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.focus.FocusDirection
+import androidx.compose.ui.focus.FocusRequester
+import androidx.compose.ui.focus.focusRequester
+import androidx.compose.ui.focus.onFocusChanged
+import androidx.compose.ui.platform.LocalFocusManager
+import androidx.tv.foundation.ExperimentalTvFoundationApi
+
+/**
+ * Composable container that provides modifier extensions to allow focus to be restored to the
+ * element that was previously focused within the TvFocusGroup.
+ *
+ * @param modifier the modifier to apply to this group.
+ * @param content the content that is present within the group and can use focus-group modifier
+ * extensions.
+ */
+@OptIn(ExperimentalComposeUiApi::class)
+@ExperimentalTvFoundationApi
+@Composable
+fun FocusGroup(
+    modifier: Modifier = Modifier,
+    content: @Composable FocusGroupScope.() -> Unit
+) {
+    val focusManager = LocalFocusManager.current
+    val focusGroupKeyHash = currentCompositeKeyHash
+
+    // TODO: Is this the intended way to call rememberSaveable
+    //  with key set to parentHash?
+    val previousFocusedItemHash: MutableState<Int?> = rememberSaveable(
+        key = focusGroupKeyHash.toString()
+    ) {
+        mutableStateOf(null)
+    }
+
+    val state = FocusGroupState(previousFocusedItemHash = previousFocusedItemHash)
+
+    Box(
+        modifier = modifier
+            .onFocusChanged {
+                if (it.isFocused) {
+                    if (state.noRecordedState()) {
+                        focusManager.moveFocus(FocusDirection.Enter)
+                    } else {
+                        if (state.focusRequester != FocusRequester.Default) {
+                            try {
+                                state.focusRequester.requestFocus()
+                            } catch (e: Exception) {
+                                Log.w("TvFocusGroup", "TvFocusGroup: Failed to request focus", e)
+                            }
+                        } else {
+                            focusManager.moveFocus(FocusDirection.Enter)
+                        }
+                    }
+                }
+            }
+            .focusable(),
+        content = { FocusGroupScope(state).content() }
+    )
+}
+
+/**
+ * Scope containing the modifier extensions to be used within [FocusGroup].
+ */
+@ExperimentalTvFoundationApi
+class FocusGroupScope internal constructor(private val state: FocusGroupState) {
+    private var currentFocusableIdIndex = 0
+
+    private fun generateUniqueFocusableId(): Int = currentFocusableIdIndex++
+
+    /**
+     * Modifier that records if the item was in focus before it moved out of the group. When focus
+     * enters the [FocusGroup], the item will be returned focus.
+     */
+    @SuppressLint("ComposableModifierFactory")
+    @Composable
+    fun Modifier.restorableFocus(): Modifier =
+        this.restorableFocus(focusId = rememberSaveable { generateUniqueFocusableId() })
+
+    /**
+     * Modifier that marks the current composable as the item to gain focus initially when focus
+     * enters the [FocusGroup]. When focus enters the [FocusGroup], the item will be returned focus.
+     */
+    @SuppressLint("ComposableModifierFactory")
+    @Composable
+    fun Modifier.initiallyFocused(): Modifier {
+        val focusId = rememberSaveable { generateUniqueFocusableId() }
+        if (state.noRecordedState()) {
+            state.recordFocusedItemHash(focusId)
+        }
+        return this.restorableFocus(focusId)
+    }
+
+    @SuppressLint("ComposableModifierFactory")
+    @OptIn(ExperimentalComposeUiApi::class)
+    @Composable
+    private fun Modifier.restorableFocus(focusId: Int): Modifier {
+        val focusRequester = remember { FocusRequester() }
+        var isFocused = remember { false }
+        val isCurrentlyFocused by rememberUpdatedState(isFocused)
+        val focusManager = LocalFocusManager.current
+        state.associatedWith(focusId, focusRequester)
+        DisposableEffect(Unit) {
+            onDispose {
+                state.clearDisposedFocusRequester(focusId)
+                if (isCurrentlyFocused) {
+                    focusManager.moveFocus(FocusDirection.Exit)
+                    focusManager.moveFocus(FocusDirection.Enter)
+                }
+            }
+        }
+
+        return this
+            .focusRequester(focusRequester)
+            .onFocusChanged {
+                isFocused = it.isFocused || it.hasFocus
+                if (isFocused) {
+                    state.recordFocusedItemHash(focusId)
+                    state.associatedWith(focusId, focusRequester)
+                }
+            }
+    }
+}
+
+@Stable
+@ExperimentalTvFoundationApi
+internal class FocusGroupState(
+    private var previousFocusedItemHash: MutableState<Int?>
+) {
+    internal var focusRequester: FocusRequester = FocusRequester.Default
+        private set
+
+    internal fun recordFocusedItemHash(itemHash: Int) {
+        previousFocusedItemHash.value = itemHash
+    }
+
+    internal fun clearDisposedFocusRequester(itemHash: Int) {
+        if (previousFocusedItemHash.value == itemHash) {
+            focusRequester = FocusRequester.Default
+        }
+    }
+
+    internal fun associatedWith(itemHash: Int, focusRequester: FocusRequester) {
+        if (previousFocusedItemHash.value == itemHash) {
+            this.focusRequester = focusRequester
+        }
+    }
+
+    internal fun noRecordedState(): Boolean =
+        previousFocusedItemHash.value == null && focusRequester == FocusRequester.Default
+}
diff --git a/tv/integration-tests/demos/src/main/java/androidx/tv/integration/demos/ImmersiveList.kt b/tv/integration-tests/demos/src/main/java/androidx/tv/integration/demos/ImmersiveList.kt
index a690908..ac636f2 100644
--- a/tv/integration-tests/demos/src/main/java/androidx/tv/integration/demos/ImmersiveList.kt
+++ b/tv/integration-tests/demos/src/main/java/androidx/tv/integration/demos/ImmersiveList.kt
@@ -35,6 +35,7 @@
 import androidx.compose.ui.focus.onFocusChanged
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.unit.dp
+import androidx.tv.foundation.ExperimentalTvFoundationApi
 import androidx.tv.foundation.lazy.list.TvLazyColumn
 import androidx.tv.material3.ExperimentalTvMaterial3Api
 import androidx.tv.material3.ImmersiveList
@@ -48,7 +49,7 @@
     }
 }
 
-@OptIn(ExperimentalTvMaterial3Api::class)
+@OptIn(ExperimentalTvMaterial3Api::class, ExperimentalTvFoundationApi::class)
 @Composable
 private fun SampleImmersiveList() {
     val immersiveListHeight = 300.dp
@@ -61,36 +62,44 @@
         Color.Magenta,
     )
 
-    ImmersiveList(
-        modifier = Modifier
-            .height(immersiveListHeight + cardHeight / 2)
-            .fillMaxWidth(),
-        background = { index, _ ->
-            Box(
-                modifier = Modifier
-                    .background(backgrounds[index].copy(alpha = 0.3f))
-                    .height(immersiveListHeight)
-                    .fillMaxWidth()
-            )
-        }
-    ) {
-        Row(horizontalArrangement = Arrangement.spacedBy(cardSpacing)) {
-            backgrounds.forEachIndexed { index, backgroundColor ->
-                var isFocused by remember { mutableStateOf(false) }
-
+    FocusGroup {
+        ImmersiveList(
+            modifier = Modifier
+                .height(immersiveListHeight + cardHeight / 2)
+                .fillMaxWidth(),
+            background = { index, _ ->
                 Box(
                     modifier = Modifier
-                        .background(backgroundColor)
-                        .width(cardWidth)
-                        .height(cardHeight)
-                        .border(5.dp, Color.White.copy(alpha = if (isFocused) 1f else 0.3f))
-                        .onFocusChanged { isFocused = it.isFocused }
-                        .immersiveListItem(index)
-                        .clickable {
-                            Log.d("ImmersiveList", "Item $index was clicked")
-                        }
+                        .background(backgrounds[index].copy(alpha = 0.3f))
+                        .height(immersiveListHeight)
+                        .fillMaxWidth()
                 )
             }
+        ) {
+            Row(horizontalArrangement = Arrangement.spacedBy(cardSpacing)) {
+                backgrounds.forEachIndexed { index, backgroundColor ->
+                    var isFocused by remember { mutableStateOf(false) }
+
+                    Box(
+                        modifier = Modifier
+                            .background(backgroundColor)
+                            .width(cardWidth)
+                            .height(cardHeight)
+                            .border(5.dp, Color.White.copy(alpha = if (isFocused) 1f else 0.3f))
+                            .then(
+                                if (index == 0)
+                                    Modifier.initiallyFocused()
+                                else
+                                    Modifier.restorableFocus()
+                            )
+                            .onFocusChanged { isFocused = it.isFocused }
+                            .immersiveListItem(index)
+                            .clickable {
+                                Log.d("ImmersiveList", "Item $index was clicked")
+                            }
+                    )
+                }
+            }
         }
     }
 }
diff --git a/tv/integration-tests/demos/src/main/java/androidx/tv/integration/demos/LazyRowsAndColumns.kt b/tv/integration-tests/demos/src/main/java/androidx/tv/integration/demos/LazyRowsAndColumns.kt
index 72f62aa..7abb30e 100644
--- a/tv/integration-tests/demos/src/main/java/androidx/tv/integration/demos/LazyRowsAndColumns.kt
+++ b/tv/integration-tests/demos/src/main/java/androidx/tv/integration/demos/LazyRowsAndColumns.kt
@@ -16,16 +16,12 @@
 
 package androidx.tv.integration.demos
 
-import androidx.compose.foundation.background
-import androidx.compose.foundation.focusable
 import androidx.compose.foundation.layout.Arrangement
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.height
-import androidx.compose.foundation.layout.width
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.unit.dp
+import androidx.tv.foundation.ExperimentalTvFoundationApi
 import androidx.tv.foundation.lazy.list.TvLazyColumn
 import androidx.tv.foundation.lazy.list.TvLazyRow
 
@@ -39,22 +35,25 @@
     }
 }
 
+@OptIn(ExperimentalTvFoundationApi::class)
 @Composable
 fun SampleLazyRow() {
     val colors = listOf(Color.Red, Color.Magenta, Color.Green, Color.Yellow, Color.Blue, Color.Cyan)
     val backgroundColors = List(columnsCount) { colors.random() }
 
-    TvLazyRow(horizontalArrangement = Arrangement.spacedBy(10.dp)) {
-        backgroundColors.forEach { backgroundColor ->
-            item {
-                Box(
-                    modifier = Modifier
-                        .background(backgroundColor.copy(alpha = 0.3f))
-                        .width(200.dp)
-                        .height(150.dp)
-                        .drawBorderOnFocus()
-                        .focusable()
-                )
+    FocusGroup {
+        TvLazyRow(horizontalArrangement = Arrangement.spacedBy(10.dp)) {
+            backgroundColors.forEachIndexed { index, backgroundColor ->
+                item {
+                    Card(
+                        backgroundColor = backgroundColor,
+                        modifier =
+                        if (index == 0)
+                            Modifier.initiallyFocused()
+                        else
+                            Modifier.restorableFocus()
+                    )
+                }
             }
         }
     }
diff --git a/tv/integration-tests/demos/src/main/java/androidx/tv/integration/demos/NavigationDrawer.kt b/tv/integration-tests/demos/src/main/java/androidx/tv/integration/demos/NavigationDrawer.kt
new file mode 100644
index 0000000..95de519
--- /dev/null
+++ b/tv/integration-tests/demos/src/main/java/androidx/tv/integration/demos/NavigationDrawer.kt
@@ -0,0 +1,209 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * 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 androidx.tv.integration.demos
+
+import androidx.compose.animation.AnimatedVisibility
+import androidx.compose.foundation.background
+import androidx.compose.foundation.focusable
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxHeight
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.offset
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.width
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.KeyboardArrowLeft
+import androidx.compose.material.icons.filled.KeyboardArrowRight
+import androidx.compose.material3.Button
+import androidx.compose.material3.ButtonDefaults
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.MutableState
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.focus.onFocusChanged
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.vector.ImageVector
+import androidx.compose.ui.platform.LocalLayoutDirection
+import androidx.compose.ui.unit.LayoutDirection
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.zIndex
+import androidx.tv.foundation.ExperimentalTvFoundationApi
+import androidx.tv.material3.DrawerValue
+import androidx.tv.material3.ExperimentalTvMaterial3Api
+import androidx.tv.material3.Icon
+import androidx.tv.material3.NavigationDrawer
+import androidx.tv.material3.Text
+
+@OptIn(ExperimentalTvMaterial3Api::class)
+@Composable
+fun StandardNavigationDrawer() {
+    val direction = remember { mutableStateOf(LayoutDirection.Ltr) }
+
+    CompositionLocalProvider(LocalLayoutDirection provides direction.value) {
+        Row(Modifier.fillMaxSize()) {
+            Box(modifier = Modifier.height(400.dp)) {
+                NavigationDrawer(
+                    drawerContent = { drawerValue ->
+                        Sidebar(
+                            drawerValue = drawerValue,
+                            direction = direction,
+                        )
+                    }
+                ) {
+                    CommonBackground()
+                }
+            }
+        }
+    }
+}
+
+@OptIn(ExperimentalTvMaterial3Api::class)
+@Composable
+fun ModalNavigationDrawer() {
+    val direction = remember { mutableStateOf(LayoutDirection.Ltr) }
+
+    CompositionLocalProvider(LocalLayoutDirection provides direction.value) {
+        Row(Modifier.fillMaxSize()) {
+            Box(modifier = Modifier.height(400.dp)) {
+                androidx.tv.material3.ModalNavigationDrawer(
+                    drawerContent = { drawerValue ->
+                        Sidebar(
+                            drawerValue = drawerValue,
+                            direction = direction,
+                        )
+                    }
+                ) {
+                    CommonBackground()
+                }
+            }
+        }
+    }
+}
+
+@Composable
+private fun CommonBackground() {
+    Row(modifier = Modifier.padding(start = 10.dp)) {
+        Card(backgroundColor = Color.Red)
+    }
+}
+
+@OptIn(ExperimentalTvMaterial3Api::class, ExperimentalTvFoundationApi::class)
+@Composable
+private fun Sidebar(
+    drawerValue: DrawerValue,
+    direction: MutableState<LayoutDirection>,
+) {
+    val selectedIndex = remember { mutableStateOf(0) }
+
+    LaunchedEffect(selectedIndex.value) {
+        direction.value = when (selectedIndex.value) {
+            0 -> LayoutDirection.Ltr
+            else -> LayoutDirection.Rtl
+        }
+    }
+
+    FocusGroup {
+        Column(
+            modifier = Modifier
+                .fillMaxHeight()
+                .background(pageColor)
+                .focusable(false),
+            horizontalAlignment = Alignment.CenterHorizontally,
+        ) {
+            NavigationItem(
+                imageVector = Icons.Default.KeyboardArrowRight,
+                text = "LTR",
+                drawerValue = drawerValue,
+                selectedIndex = selectedIndex,
+                index = 0,
+                modifier = Modifier.initiallyFocused(),
+            )
+            NavigationItem(
+                imageVector = Icons.Default.KeyboardArrowLeft,
+                text = "RTL",
+                drawerValue = drawerValue,
+                selectedIndex = selectedIndex,
+                index = 1,
+                modifier = Modifier.restorableFocus(),
+            )
+        }
+    }
+}
+
+@OptIn(ExperimentalTvMaterial3Api::class)
+@Composable
+private fun NavigationItem(
+    imageVector: ImageVector,
+    text: String,
+    drawerValue: DrawerValue,
+    selectedIndex: MutableState<Int>,
+    index: Int,
+    modifier: Modifier = Modifier,
+) {
+    var isFocused by remember { mutableStateOf(false) }
+
+    Button(
+        onClick = { selectedIndex.value = index },
+        modifier = modifier
+            .onFocusChanged { isFocused = it.isFocused },
+        colors = ButtonDefaults.filledTonalButtonColors(
+            containerColor = if (isFocused) Color.White else Color.Transparent,
+        )
+    ) {
+        Box(modifier = Modifier) {
+            Row(
+                verticalAlignment = Alignment.CenterVertically,
+                horizontalArrangement = Arrangement.spacedBy(5.dp),
+            ) {
+                Icon(
+                    imageVector = imageVector,
+                    tint = if (isFocused) pageColor else Color.White,
+                    contentDescription = null,
+                )
+                AnimatedVisibility(visible = drawerValue == DrawerValue.Open) {
+                    Text(
+                        text = text,
+                        modifier = Modifier,
+                        softWrap = false,
+                        color = if (isFocused) pageColor else Color.White,
+                    )
+                }
+            }
+            if (selectedIndex.value == index) {
+                Box(
+                    modifier = Modifier
+                        .width(10.dp)
+                        .height(3.dp)
+                        .offset(y = 5.dp)
+                        .align(Alignment.BottomCenter)
+                        .background(Color.Red)
+                        .zIndex(10f)
+                )
+            }
+        }
+    }
+}
diff --git a/tv/integration-tests/demos/src/main/java/androidx/tv/integration/demos/SampleModalNavDrawer.kt b/tv/integration-tests/demos/src/main/java/androidx/tv/integration/demos/SampleModalNavDrawer.kt
deleted file mode 100644
index e36b1df..0000000
--- a/tv/integration-tests/demos/src/main/java/androidx/tv/integration/demos/SampleModalNavDrawer.kt
+++ /dev/null
@@ -1,114 +0,0 @@
-/*
- * Copyright 2022 The Android Open Source Project
- *
- * 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 androidx.tv.integration.demos
-
-import androidx.compose.animation.AnimatedVisibility
-import androidx.compose.animation.core.tween
-import androidx.compose.animation.shrinkHorizontally
-import androidx.compose.foundation.background
-import androidx.compose.foundation.border
-import androidx.compose.foundation.focusable
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.fillMaxHeight
-import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.height
-import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.size
-import androidx.compose.foundation.layout.width
-import androidx.compose.material3.Button
-import androidx.compose.material3.Text
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.CompositionLocalProvider
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.platform.LocalLayoutDirection
-import androidx.compose.ui.text.style.TextAlign
-import androidx.compose.ui.unit.LayoutDirection
-import androidx.compose.ui.unit.dp
-import androidx.tv.material3.DrawerValue
-import androidx.tv.material3.ExperimentalTvMaterial3Api
-import androidx.tv.material3.ModalNavigationDrawer
-
-@OptIn(ExperimentalTvMaterial3Api::class)
-@Composable
-fun SampleModalDrawer() {
-    Row(Modifier.fillMaxSize()) {
-        Box(modifier = Modifier
-            .height(400.dp)
-            .width(400.dp)
-            .border(2.dp, Color.Magenta)) {
-            ModalNavigationDrawer(drawerContent = drawerContent()) {
-                Button(modifier = Modifier
-                    .height(100.dp)
-                    .fillMaxWidth(), onClick = {}) {
-                    Text("BUTTON")
-                }
-            }
-        }
-
-        CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Rtl) {
-            Box(
-                modifier = Modifier
-                    .height(400.dp)
-                    .width(400.dp)
-                    .border(2.dp, Color.Magenta)
-            ) {
-                ModalNavigationDrawer(drawerContent = drawerContent()) {
-                    Button(modifier = Modifier
-                        .height(100.dp)
-                        .fillMaxWidth(), onClick = {}) {
-                        Text("BUTTON")
-                    }
-                }
-            }
-        }
-    }
-}
-
-@Composable
-@OptIn(ExperimentalTvMaterial3Api::class)
-internal fun drawerContent(): @Composable (DrawerValue) -> Unit =
-    {
-        Column(Modifier.background(Color.Gray).fillMaxHeight()) {
-            NavigationRow(it, Color.Red, "Red")
-            NavigationRow(it, Color.Blue, "Blue")
-            NavigationRow(it, Color.Yellow, "Yellow")
-        }
-    }
-
-@OptIn(ExperimentalTvMaterial3Api::class)
-@Composable
-private fun NavigationRow(drawerValue: DrawerValue, color: Color, text: String) {
-    Row(Modifier.padding(10.dp).drawBorderOnFocus(width = 2.dp).focusable()) {
-        Box(Modifier.size(50.dp).background(color).padding(end = 20.dp))
-        AnimatedVisibility(
-            drawerValue == DrawerValue.Open,
-            // intentionally slow to test animation
-            exit = shrinkHorizontally(tween(2000))
-        ) {
-            Text(
-                text = text,
-                softWrap = false,
-                modifier = Modifier.padding(15.dp).width(50.dp),
-                textAlign = TextAlign.Center
-            )
-        }
-    }
-}
diff --git a/tv/integration-tests/demos/src/main/java/androidx/tv/integration/demos/SampleNavDrawer.kt b/tv/integration-tests/demos/src/main/java/androidx/tv/integration/demos/SampleNavDrawer.kt
deleted file mode 100644
index cf1ed6a..0000000
--- a/tv/integration-tests/demos/src/main/java/androidx/tv/integration/demos/SampleNavDrawer.kt
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright 2022 The Android Open Source Project
- *
- * 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 androidx.tv.integration.demos
-
-import androidx.compose.foundation.border
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.height
-import androidx.compose.foundation.layout.width
-import androidx.compose.material3.Button
-import androidx.compose.material3.Text
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.CompositionLocalProvider
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.platform.LocalLayoutDirection
-import androidx.compose.ui.unit.LayoutDirection
-import androidx.compose.ui.unit.dp
-import androidx.tv.material3.ExperimentalTvMaterial3Api
-import androidx.tv.material3.NavigationDrawer
-
-@OptIn(ExperimentalTvMaterial3Api::class)
-@Composable
-fun SampleDrawer() {
-    Row(Modifier.fillMaxSize()) {
-        Box(modifier = Modifier
-            .height(400.dp)
-            .width(400.dp)
-            .border(2.dp, Color.Magenta)) {
-            NavigationDrawer(drawerContent = drawerContent()) {
-                Button(modifier = Modifier
-                    .height(100.dp)
-                    .fillMaxWidth(), onClick = {}) {
-                    Text("BUTTON")
-                }
-            }
-        }
-
-        CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Rtl) {
-            Box(
-                modifier = Modifier
-                    .height(400.dp)
-                    .width(400.dp)
-                    .border(2.dp, Color.Magenta)
-            ) {
-                NavigationDrawer(drawerContent = drawerContent()) {
-                    Button(modifier = Modifier
-                        .height(100.dp)
-                        .fillMaxWidth(), onClick = {}) {
-                        Text("BUTTON")
-                    }
-                }
-            }
-        }
-    }
-}
\ No newline at end of file
diff --git a/tv/integration-tests/demos/src/main/java/androidx/tv/integration/demos/TopNavigation.kt b/tv/integration-tests/demos/src/main/java/androidx/tv/integration/demos/TopNavigation.kt
index 95a6f7e..a41b4e5 100644
--- a/tv/integration-tests/demos/src/main/java/androidx/tv/integration/demos/TopNavigation.kt
+++ b/tv/integration-tests/demos/src/main/java/androidx/tv/integration/demos/TopNavigation.kt
@@ -16,9 +16,7 @@
 
 package androidx.tv.integration.demos
 
-import androidx.compose.foundation.layout.Spacer
 import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.width
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.getValue
@@ -28,6 +26,7 @@
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.unit.sp
+import androidx.tv.foundation.ExperimentalTvFoundationApi
 import androidx.tv.material3.ExperimentalTvMaterial3Api
 import androidx.tv.material3.Tab
 import androidx.tv.material3.TabRow
@@ -35,13 +34,13 @@
 import kotlinx.coroutines.delay
 
 enum class Navigation(val displayName: String, val action: @Composable () -> Unit) {
-  Drawer("Drawer", { SampleDrawer() }),
-  ModalDrawer("Modal Drawer", { SampleModalDrawer() }),
+  StandardNavigationDrawer("Standard Navigation Drawer", { StandardNavigationDrawer() }),
+  ModalNavigationDrawer("Modal Navigation Drawer", { ModalNavigationDrawer() }),
   LazyRowsAndColumns("Lazy Rows and Columns", { LazyRowsAndColumns() }),
   FeaturedCarousel("Featured Carousel", { FeaturedCarouselContent() }),
   ImmersiveList("Immersive List", { ImmersiveListContent() }),
-  StickyHeader("Sticky Header", { StickyHeaderContent() }),
   TextField("Text Field", { TextFieldContent() }),
+  StickyHeader("Sticky Header", { StickyHeaderContent() }),
 }
 
 @Composable
@@ -69,27 +68,31 @@
 /**
  * Pill indicator tab row for reference
  */
-@OptIn(ExperimentalTvMaterial3Api::class)
+@OptIn(ExperimentalTvMaterial3Api::class, ExperimentalTvFoundationApi::class)
 @Composable
 fun PillIndicatorTabRow(
   tabs: List<String>,
   selectedTabIndex: Int,
   updateSelectedTab: (Int) -> Unit
 ) {
-  TabRow(
-    selectedTabIndex = selectedTabIndex,
-    separator = { Spacer(modifier = Modifier.width(12.dp)) },
-  ) {
-    tabs.forEachIndexed { index, tab ->
-      Tab(
-        selected = index == selectedTabIndex,
-        onFocus = { updateSelectedTab(index) },
-      ) {
-        Text(
-          text = tab,
-          fontSize = 12.sp,
-          modifier = Modifier.padding(horizontal = 16.dp, vertical = 6.dp)
-        )
+  FocusGroup {
+    TabRow(selectedTabIndex = selectedTabIndex) {
+      tabs.forEachIndexed { index, tab ->
+        Tab(
+          selected = index == selectedTabIndex,
+          onFocus = { updateSelectedTab(index) },
+          modifier =
+          if (tab == Navigation.StandardNavigationDrawer.displayName)
+            Modifier.initiallyFocused()
+          else
+            Modifier.restorableFocus()
+        ) {
+          Text(
+            text = tab,
+            fontSize = 12.sp,
+            modifier = Modifier.padding(horizontal = 16.dp, vertical = 6.dp)
+          )
+        }
       }
     }
   }
diff --git a/tv/integration-tests/demos/src/main/java/androidx/tv/integration/demos/drawBorderOnFocus.kt b/tv/integration-tests/demos/src/main/java/androidx/tv/integration/demos/drawBorderOnFocus.kt
new file mode 100644
index 0000000..633887f
--- /dev/null
+++ b/tv/integration-tests/demos/src/main/java/androidx/tv/integration/demos/drawBorderOnFocus.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * 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 androidx.tv.integration.demos
+
+import androidx.compose.foundation.border
+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.focus.onFocusChanged
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+
+@Composable
+fun Modifier.drawBorderOnFocus(borderColor: Color = Color.White, width: Dp = 5.dp): Modifier {
+    var isFocused by remember { mutableStateOf(false) }
+    return this
+        .border(width, borderColor.copy(alpha = if (isFocused) 1f else 0.2f))
+        .onFocusChanged { isFocused = it.isFocused }
+}
diff --git a/tv/tv-material/src/main/java/androidx/tv/material3/TabRow.kt b/tv/tv-material/src/main/java/androidx/tv/material3/TabRow.kt
index a1af7e7..1b49193 100644
--- a/tv/tv-material/src/main/java/androidx/tv/material3/TabRow.kt
+++ b/tv/tv-material/src/main/java/androidx/tv/material3/TabRow.kt
@@ -170,7 +170,7 @@
   /** Space between tabs in the tab row */
   @Composable
   fun TabSeparator() {
-    Spacer(modifier = Modifier.width(20.dp))
+    Spacer(modifier = Modifier.width(8.dp))
   }
 
   /** Default accent color for the TabRow */