feat: introduce wide-button composables in tv material

Test: added instrumentation and snapshot tests

Relnote: "Introduce 2 WideButton composables in 'Compose for TV' material package"

Change-Id: I4cecf3405f62f7798bcc00a48e48165c1d8a5d66
diff --git a/tv/tv-material/api/public_plus_experimental_current.txt b/tv/tv-material/api/public_plus_experimental_current.txt
index 72025d1..23e7a539 100644
--- a/tv/tv-material/api/public_plus_experimental_current.txt
+++ b/tv/tv-material/api/public_plus_experimental_current.txt
@@ -529,5 +529,23 @@
     property public final androidx.compose.ui.text.TextStyle titleSmall;
   }
 
+  @androidx.compose.runtime.Immutable @androidx.tv.material3.ExperimentalTvMaterial3Api public final class WideButtonContentColor {
+  }
+
+  @androidx.tv.material3.ExperimentalTvMaterial3Api public final class WideButtonDefaults {
+    method @androidx.compose.runtime.Composable public void Background(boolean enabled, androidx.compose.foundation.interaction.MutableInteractionSource interactionSource);
+    method @androidx.compose.runtime.Composable @androidx.compose.runtime.ReadOnlyComposable public androidx.tv.material3.ButtonBorder border(optional androidx.tv.material3.Border border, optional androidx.tv.material3.Border focusedBorder, optional androidx.tv.material3.Border pressedBorder, optional androidx.tv.material3.Border disabledBorder, optional androidx.tv.material3.Border focusedDisabledBorder);
+    method @androidx.compose.runtime.Composable @androidx.compose.runtime.ReadOnlyComposable public androidx.tv.material3.WideButtonContentColor contentColor(optional long color, optional long focusedColor, optional long pressedColor, optional long disabledColor);
+    method public androidx.tv.material3.ButtonGlow glow(optional androidx.tv.material3.Glow glow, optional androidx.tv.material3.Glow focusedGlow, optional androidx.tv.material3.Glow pressedGlow);
+    method public androidx.tv.material3.ButtonScale scale(optional @FloatRange(from=0.0) float scale, optional @FloatRange(from=0.0) float focusedScale, optional @FloatRange(from=0.0) float pressedScale, optional @FloatRange(from=0.0) float disabledScale, optional @FloatRange(from=0.0) float focusedDisabledScale);
+    method public androidx.tv.material3.ButtonShape shape(optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.ui.graphics.Shape focusedShape, optional androidx.compose.ui.graphics.Shape pressedShape, optional androidx.compose.ui.graphics.Shape disabledShape, optional androidx.compose.ui.graphics.Shape focusedDisabledShape);
+    field public static final androidx.tv.material3.WideButtonDefaults INSTANCE;
+  }
+
+  public final class WideButtonKt {
+    method @androidx.compose.runtime.Composable @androidx.compose.runtime.NonRestartableComposable @androidx.tv.material3.ExperimentalTvMaterial3Api public static void WideButton(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, optional kotlin.jvm.functions.Function0<kotlin.Unit> background, optional androidx.tv.material3.ButtonScale scale, optional androidx.tv.material3.ButtonGlow glow, optional androidx.tv.material3.ButtonShape shape, optional androidx.tv.material3.WideButtonContentColor contentColor, optional float tonalElevation, optional androidx.tv.material3.ButtonBorder border, optional androidx.compose.foundation.layout.PaddingValues contentPadding, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> content);
+    method @androidx.compose.runtime.Composable @androidx.compose.runtime.NonRestartableComposable @androidx.tv.material3.ExperimentalTvMaterial3Api public static void WideButton(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, kotlin.jvm.functions.Function0<kotlin.Unit> title, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional kotlin.jvm.functions.Function0<kotlin.Unit>? icon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? subtitle, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, optional kotlin.jvm.functions.Function0<kotlin.Unit> background, optional androidx.tv.material3.ButtonScale scale, optional androidx.tv.material3.ButtonGlow glow, optional androidx.tv.material3.ButtonShape shape, optional androidx.tv.material3.WideButtonContentColor contentColor, optional float tonalElevation, optional androidx.tv.material3.ButtonBorder border, optional androidx.compose.foundation.layout.PaddingValues contentPadding);
+  }
+
 }
 
diff --git a/tv/tv-material/src/androidTest/java/androidx/tv/material3/WideButtonScreenshotTest.kt b/tv/tv-material/src/androidTest/java/androidx/tv/material3/WideButtonScreenshotTest.kt
new file mode 100644
index 0000000..1ee53ca
--- /dev/null
+++ b/tv/tv-material/src/androidTest/java/androidx/tv/material3/WideButtonScreenshotTest.kt
@@ -0,0 +1,365 @@
+/*
+ * 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.material3
+
+import android.os.Build
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Settings
+import androidx.compose.testutils.assertAgainstGolden
+import androidx.compose.ui.test.captureToImage
+import androidx.compose.ui.test.hasClickAction
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import androidx.test.filters.SdkSuppress
+import androidx.test.screenshot.AndroidXScreenshotTestRule
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalTvMaterial3Api::class)
+@MediumTest
+@RunWith(AndroidJUnit4::class)
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+class WideButtonScreenshotTest {
+
+    @get:Rule
+    val rule = createComposeRule()
+
+    @get:Rule
+    val screenshotRule = AndroidXScreenshotTestRule(TV_GOLDEN_MATERIAL3)
+
+    @Test
+    fun defaultWideButton_lightTheme() {
+        rule.setContent {
+            LightMaterialTheme {
+                WideButton(onClick = { }) {
+                    Text("Settings")
+                }
+            }
+        }
+
+        rule.onNode(hasClickAction())
+            .captureToImage()
+            .assertAgainstGolden(screenshotRule, "wide_button_light_theme")
+    }
+
+    @Test
+    fun defaultWideButton_darkTheme() {
+        rule.setContent {
+            DarkMaterialTheme {
+                WideButton(onClick = { }) {
+                    Text("Settings")
+                }
+            }
+        }
+
+        rule.onNode(hasClickAction())
+            .captureToImage()
+            .assertAgainstGolden(screenshotRule, "wide_button_dark_theme")
+    }
+
+    @Test
+    fun disabled_wideButton_lightTheme() {
+        rule.setContent {
+            LightMaterialTheme {
+                WideButton(onClick = { }, enabled = false) {
+                    Text("Settings")
+                }
+            }
+        }
+
+        rule.onNode(hasClickAction())
+            .captureToImage()
+            .assertAgainstGolden(screenshotRule, "wide_button_disabled_light_theme")
+    }
+
+    @Test
+    fun disabled_wideButton_darkTheme() {
+        rule.setContent {
+            DarkMaterialTheme {
+                WideButton(onClick = { }, enabled = false) {
+                    Text("Settings")
+                }
+            }
+        }
+
+        rule.onNode(hasClickAction())
+            .captureToImage()
+            .assertAgainstGolden(screenshotRule, "wide_button_disabled_dark_theme")
+    }
+
+    @Test
+    fun wideButton_withSubtitle_lightTheme() {
+        rule.setContent {
+            LightMaterialTheme {
+                WideButton(
+                    onClick = { },
+                    title = { Text("Settings") },
+                    subtitle = { Text(text = "Update device preferences") },
+                )
+            }
+        }
+
+        rule.onNode(hasClickAction())
+            .captureToImage()
+            .assertAgainstGolden(screenshotRule, "wide_button_with_subtitle_light_theme")
+    }
+
+    @Test
+    fun wideButton_withSubtitle_darkTheme() {
+        rule.setContent {
+            DarkMaterialTheme {
+                WideButton(
+                    onClick = { },
+                    title = { Text("Settings") },
+                    subtitle = { Text(text = "Update device preferences") },
+                )
+            }
+        }
+
+        rule.onNode(hasClickAction())
+            .captureToImage()
+            .assertAgainstGolden(screenshotRule, "wide_button_with_subtitle_dark_theme")
+    }
+
+    @Test
+    fun disabled_wideButton_withSubtitle_lightTheme() {
+        rule.setContent {
+            LightMaterialTheme {
+                WideButton(
+                    onClick = { },
+                    enabled = false,
+                    title = { Text("Settings") },
+                    subtitle = { Text(text = "Update device preferences") },
+                )
+            }
+        }
+
+        rule.onNode(hasClickAction())
+            .captureToImage()
+            .assertAgainstGolden(screenshotRule, "disabled_wide_button_with_subtitle_light_theme")
+    }
+
+    @Test
+    fun disabled_wideButton_withSubtitle_darkTheme() {
+        rule.setContent {
+            DarkMaterialTheme {
+                WideButton(
+                    onClick = { },
+                    enabled = false,
+                    title = { Text("Settings") },
+                    subtitle = { Text(text = "Update device preferences") },
+                )
+            }
+        }
+
+        rule.onNode(hasClickAction())
+            .captureToImage()
+            .assertAgainstGolden(screenshotRule, "disabled_wide_button_with_subtitle_dark_theme")
+    }
+
+    @Test
+    fun wideButton_withIcon_lightTheme() {
+        rule.setContent {
+            LightMaterialTheme {
+                WideButton(
+                    onClick = { },
+                    title = { Text("Settings") },
+                    icon = {
+                        Icon(
+                            imageVector = Icons.Default.Settings,
+                            contentDescription = "Settings"
+                        )
+                    }
+                )
+            }
+        }
+
+        rule.onNode(hasClickAction())
+            .captureToImage()
+            .assertAgainstGolden(screenshotRule, "wide_button_with_icon_light_theme")
+    }
+
+    @Test
+    fun wideButton_withIcon_darkTheme() {
+        rule.setContent {
+            DarkMaterialTheme {
+                WideButton(
+                    onClick = { },
+                    title = { Text("Settings") },
+                    icon = {
+                        Icon(
+                            imageVector = Icons.Default.Settings,
+                            contentDescription = "Settings"
+                        )
+                    }
+                )
+            }
+        }
+
+        rule.onNode(hasClickAction())
+            .captureToImage()
+            .assertAgainstGolden(screenshotRule, "wide_button_with_icon_dark_theme")
+    }
+
+    @Test
+    fun disabled_wideButton_withIcon_lightTheme() {
+        rule.setContent {
+            LightMaterialTheme {
+                WideButton(
+                    onClick = { },
+                    enabled = false,
+                    title = { Text("Settings") },
+                    icon = {
+                        Icon(
+                            imageVector = Icons.Default.Settings,
+                            contentDescription = "Settings"
+                        )
+                    }
+                )
+            }
+        }
+
+        rule.onNode(hasClickAction())
+            .captureToImage()
+            .assertAgainstGolden(screenshotRule, "disabled_wide_button_with_icon_light_theme")
+    }
+
+    @Test
+    fun disabled_wideButton_withIcon_darkTheme() {
+        rule.setContent {
+            DarkMaterialTheme {
+                WideButton(
+                    onClick = { },
+                    enabled = false,
+                    title = { Text("Settings") },
+                    icon = {
+                        Icon(
+                            imageVector = Icons.Default.Settings,
+                            contentDescription = "Settings"
+                        )
+                    }
+                )
+            }
+        }
+
+        rule.onNode(hasClickAction())
+            .captureToImage()
+            .assertAgainstGolden(screenshotRule, "disabled_wide_button_with_icon_dark_theme")
+    }
+
+    @Test
+    fun wideButton_withSubtitleAndIcon_lightTheme() {
+        rule.setContent {
+            LightMaterialTheme {
+                WideButton(
+                    onClick = { },
+                    title = { Text("Settings") },
+                    subtitle = { Text(text = "Update device preferences") },
+                    icon = {
+                        Icon(
+                            imageVector = Icons.Default.Settings,
+                            contentDescription = "Settings"
+                        )
+                    }
+                )
+            }
+        }
+
+        rule.onNode(hasClickAction())
+            .captureToImage()
+            .assertAgainstGolden(screenshotRule, "wide_button_with_subtitle_and_icon_light_theme")
+    }
+
+    @Test
+    fun wideButton_withSubtitleAndIcon_darkTheme() {
+        rule.setContent {
+            DarkMaterialTheme {
+                WideButton(
+                    onClick = { },
+                    title = { Text("Settings") },
+                    subtitle = { Text(text = "Update device preferences") },
+                    icon = {
+                        Icon(
+                            imageVector = Icons.Default.Settings,
+                            contentDescription = "Settings"
+                        )
+                    }
+                )
+            }
+        }
+
+        rule.onNode(hasClickAction())
+            .captureToImage()
+            .assertAgainstGolden(screenshotRule, "wide_button_with_subtitle_and_icon_dark_theme")
+    }
+
+    @Test
+    fun disabled_wideButton_withSubtitleAndIcon_lightTheme() {
+        rule.setContent {
+            LightMaterialTheme {
+                WideButton(
+                    onClick = { },
+                    enabled = false,
+                    title = { Text("Settings") },
+                    subtitle = { Text(text = "Update device preferences") },
+                    icon = {
+                        Icon(
+                            imageVector = Icons.Default.Settings,
+                            contentDescription = "Settings"
+                        )
+                    }
+                )
+            }
+        }
+
+        rule.onNode(hasClickAction())
+            .captureToImage()
+            .assertAgainstGolden(
+                screenshotRule,
+                "disabled_wide_button_with_subtitle_and_icon_light_theme"
+            )
+    }
+
+    @Test
+    fun disabled_wideButton_withSubtitleAndIcon_darkTheme() {
+        rule.setContent {
+            DarkMaterialTheme {
+                WideButton(
+                    onClick = { },
+                    enabled = false,
+                    title = { Text("Settings") },
+                    subtitle = { Text(text = "Update device preferences") },
+                    icon = {
+                        Icon(
+                            imageVector = Icons.Default.Settings,
+                            contentDescription = "Settings"
+                        )
+                    }
+                )
+            }
+        }
+
+        rule.onNode(hasClickAction())
+            .captureToImage()
+            .assertAgainstGolden(
+                screenshotRule,
+                "disabled_wide_button_with_subtitle_and_icon_dark_theme"
+            )
+    }
+}
diff --git a/tv/tv-material/src/androidTest/java/androidx/tv/material3/WideButtonTest.kt b/tv/tv-material/src/androidTest/java/androidx/tv/material3/WideButtonTest.kt
new file mode 100644
index 0000000..d7d9625
--- /dev/null
+++ b/tv/tv-material/src/androidTest/java/androidx/tv/material3/WideButtonTest.kt
@@ -0,0 +1,260 @@
+/*
+ * 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.material3
+
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.size
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Settings
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.ExperimentalComposeUiApi
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.input.key.Key
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.semantics.Role
+import androidx.compose.ui.semantics.SemanticsActions
+import androidx.compose.ui.semantics.SemanticsProperties
+import androidx.compose.ui.semantics.semantics
+import androidx.compose.ui.test.ExperimentalTestApi
+import androidx.compose.ui.test.SemanticsMatcher
+import androidx.compose.ui.test.assert
+import androidx.compose.ui.test.assertHasClickAction
+import androidx.compose.ui.test.assertIsEnabled
+import androidx.compose.ui.test.assertIsEqualTo
+import androidx.compose.ui.test.assertIsNotEnabled
+import androidx.compose.ui.test.getUnclippedBoundsInRoot
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.test.performKeyInput
+import androidx.compose.ui.test.performSemanticsAction
+import androidx.compose.ui.test.pressKey
+import androidx.compose.ui.unit.dp
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import com.google.common.truth.Truth
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(
+    ExperimentalComposeUiApi::class,
+    ExperimentalTestApi::class,
+    ExperimentalTvMaterial3Api::class
+)
+@MediumTest
+@RunWith(AndroidJUnit4::class)
+class WideButtonTest {
+
+    @get:Rule
+    val rule = createComposeRule()
+
+    @Test
+    fun wideButton_defaultSemantics() {
+        rule.setContent {
+            Box {
+                WideButton(
+                    onClick = { },
+                    modifier = Modifier
+                        .testTag(WideButtonTag),
+                    title = { Text(text = "Settings") },
+                    icon = {
+                        Icon(imageVector = Icons.Default.Settings, contentDescription = "")
+                    },
+                    subtitle = { Text(text = "Update device preferences") }
+                )
+            }
+        }
+
+        rule.onNodeWithTag(WideButtonTag)
+            .assert(SemanticsMatcher.expectValue(SemanticsProperties.Role, Role.Button))
+            .assertIsEnabled()
+    }
+
+    @Test
+    fun wideButton_disabledSemantics() {
+        rule.setContent {
+            Box {
+                WideButton(
+                    onClick = { },
+                    modifier = Modifier
+                        .testTag(WideButtonTag),
+                    enabled = false,
+                    title = { Text(text = "Settings") },
+                    icon = {
+                        Icon(imageVector = Icons.Default.Settings, contentDescription = "")
+                    },
+                    subtitle = { Text(text = "Update device preferences") }
+                )
+            }
+        }
+
+        rule.onNodeWithTag(WideButtonTag)
+            .assert(SemanticsMatcher.expectValue(SemanticsProperties.Role, Role.Button))
+            .assertIsNotEnabled()
+    }
+
+    @Test
+    fun wideButton_findByTagAndClick() {
+        var counter = 0
+        val onClick: () -> Unit = { ++counter }
+
+        rule.setContent {
+            Box {
+                WideButton(
+                    onClick = onClick,
+                    modifier = Modifier
+                        .testTag(WideButtonTag),
+                    title = { Text(text = "Settings") },
+                    icon = {
+                        Icon(imageVector = Icons.Default.Settings, contentDescription = "")
+                    },
+                    subtitle = { Text(text = "Update device preferences") }
+                )
+            }
+        }
+        rule.onNodeWithTag(WideButtonTag)
+            .performSemanticsAction(SemanticsActions.RequestFocus)
+            .performKeyInput { pressKey(Key.DirectionCenter) }
+        rule.runOnIdle {
+            Truth.assertThat(counter).isEqualTo(1)
+        }
+    }
+
+    @Test
+    fun wideButton_canBeDisabled() {
+        rule.setContent {
+            var enabled by remember { mutableStateOf(true) }
+            Box {
+                WideButton(
+                    onClick = { enabled = false },
+                    modifier = Modifier
+                        .testTag(WideButtonTag),
+                    enabled = enabled,
+                    title = { Text(text = "Settings") },
+                    icon = {
+                        Icon(imageVector = Icons.Default.Settings, contentDescription = "")
+                    },
+                    subtitle = { Text(text = "Update device preferences") }
+                )
+            }
+        }
+        rule.onNodeWithTag(WideButtonTag)
+            // Confirm the button starts off enabled, with a click action
+            .assertHasClickAction()
+            .assertIsEnabled()
+            .performSemanticsAction(SemanticsActions.RequestFocus)
+            .performKeyInput { pressKey(Key.DirectionCenter) }
+            // Then confirm it's disabled with click action after clicking it
+            .assertHasClickAction()
+            .assertIsNotEnabled()
+    }
+
+    @Test
+    fun wideButton_clickIsIndependentBetweenButtons() {
+        var watchButtonCounter = 0
+        val watchButtonOnClick: () -> Unit = { ++watchButtonCounter }
+        val watchButtonTag = "WatchButton"
+
+        var playButtonCounter = 0
+        val playButtonOnClick: () -> Unit = { ++playButtonCounter }
+        val playButtonTag = "PlayButton"
+
+        rule.setContent {
+            Column {
+                WideButton(
+                    onClick = watchButtonOnClick,
+                    modifier = Modifier.testTag(watchButtonTag),
+                ) {
+                    Text(text = "Watch")
+                }
+                WideButton(
+                    onClick = playButtonOnClick,
+                    modifier = Modifier.testTag(playButtonTag),
+                ) {
+                    Text(text = "Play")
+                }
+            }
+        }
+
+        rule.onNodeWithTag(watchButtonTag)
+            .performSemanticsAction(SemanticsActions.RequestFocus)
+            .performKeyInput { pressKey(Key.DirectionCenter) }
+
+        rule.runOnIdle {
+            Truth.assertThat(watchButtonCounter).isEqualTo(1)
+            Truth.assertThat(playButtonCounter).isEqualTo(0)
+        }
+
+        rule.onNodeWithTag(playButtonTag)
+            .performSemanticsAction(SemanticsActions.RequestFocus)
+            .performKeyInput { pressKey(Key.DirectionCenter) }
+
+        rule.runOnIdle {
+            Truth.assertThat(watchButtonCounter).isEqualTo(1)
+            Truth.assertThat(playButtonCounter).isEqualTo(1)
+        }
+    }
+
+    @Test
+    fun wideButton_buttonPositioning() {
+        rule.setContent {
+            Box {
+                WideButton(
+                    onClick = { },
+                    modifier = Modifier
+                        .testTag(WideButtonTag),
+                    contentPadding = WideButtonDefaults.ContentPadding,
+                    title = {
+                        Text(
+                            text = "Email",
+                            modifier = Modifier
+                                .testTag(WideButtonTextTag)
+                                .semantics(mergeDescendants = true) {}
+                        )
+                            },
+                    icon = {
+                        Icon(
+                            imageVector = Icons.Default.Settings,
+                            contentDescription = "",
+                            modifier = Modifier
+                                .size(WideButtonIconSize)
+                                .testTag(WideButtonIconTag)
+                                .semantics(mergeDescendants = true) {}
+                        )
+                    }
+                )
+            }
+        }
+
+        val buttonBounds = rule.onNodeWithTag(WideButtonTag).getUnclippedBoundsInRoot()
+        val leadingIconBounds = rule.onNodeWithTag(WideButtonIconTag).getUnclippedBoundsInRoot()
+
+        (leadingIconBounds.left - buttonBounds.left).assertIsEqualTo(
+            16.dp,
+            "padding between the start of the button and the start of the leading icon."
+        )
+    }
+}
+
+private val WideButtonIconSize = 18.0.dp
+private const val WideButtonTag = "WideButtonTag"
+private const val WideButtonTextTag = "WideButtonText"
+private const val WideButtonIconTag = "WideButtonIcon"
diff --git a/tv/tv-material/src/main/java/androidx/tv/material3/ButtonStyles.kt b/tv/tv-material/src/main/java/androidx/tv/material3/ButtonStyles.kt
index 75e56dd..560ad85 100644
--- a/tv/tv-material/src/main/java/androidx/tv/material3/ButtonStyles.kt
+++ b/tv/tv-material/src/main/java/androidx/tv/material3/ButtonStyles.kt
@@ -122,6 +122,47 @@
 }
 
 /**
+ * Defines [Color]s for all TV [Interaction] states of a WideButton
+ */
+@ExperimentalTvMaterial3Api
+@Immutable
+class WideButtonContentColor internal constructor(
+    internal val contentColor: Color,
+    internal val focusedContentColor: Color,
+    internal val pressedContentColor: Color,
+    internal val disabledContentColor: Color,
+) {
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (other == null || this::class != other::class) return false
+
+        other as WideButtonContentColor
+
+        if (contentColor != other.contentColor) return false
+        if (focusedContentColor != other.focusedContentColor) return false
+        if (pressedContentColor != other.pressedContentColor) return false
+        if (disabledContentColor != other.disabledContentColor) return false
+
+        return true
+    }
+
+    override fun hashCode(): Int {
+        var result = contentColor.hashCode()
+        result = 31 * result + focusedContentColor.hashCode()
+        result = 31 * result + pressedContentColor.hashCode()
+        result = 31 * result + disabledContentColor.hashCode()
+        return result
+    }
+
+    override fun toString(): String {
+        return "WideButtonContentColor(contentColor=$contentColor, " +
+            "focusedContentColor=$focusedContentColor, " +
+            "pressedContentColor=$pressedContentColor, " +
+            "disabledContentColor=$disabledContentColor)"
+    }
+}
+
+/**
  * Defines the scale for all TV [Interaction] states of Button.
  */
 @ExperimentalTvMaterial3Api
@@ -272,6 +313,15 @@
     )
 
 @OptIn(ExperimentalTvMaterial3Api::class)
+internal fun WideButtonContentColor.toClickableSurfaceContentColor(): ClickableSurfaceColor =
+    ClickableSurfaceColor(
+        color = contentColor,
+        focusedColor = focusedContentColor,
+        pressedColor = pressedContentColor,
+        disabledColor = disabledContentColor,
+    )
+
+@OptIn(ExperimentalTvMaterial3Api::class)
 internal fun ButtonScale.toClickableSurfaceScale() = ClickableSurfaceScale(
     scale = scale,
     focusedScale = focusedScale,
diff --git a/tv/tv-material/src/main/java/androidx/tv/material3/WideButton.kt b/tv/tv-material/src/main/java/androidx/tv/material3/WideButton.kt
new file mode 100644
index 0000000..30f4d67
--- /dev/null
+++ b/tv/tv-material/src/main/java/androidx/tv/material3/WideButton.kt
@@ -0,0 +1,284 @@
+/*
+ * 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.material3
+
+import androidx.compose.foundation.interaction.Interaction
+import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.RowScope
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.defaultMinSize
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.NonRestartableComposable
+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.graphics.Color
+import androidx.compose.ui.layout.onPlaced
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.semantics.Role
+import androidx.compose.ui.semantics.role
+import androidx.compose.ui.semantics.semantics
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+import androidx.tv.material3.tokens.Elevation
+
+/**
+ * Material Design wide button for TV.
+ *
+ * @param onClick called when this button is clicked
+ * @param modifier the [Modifier] to be applied to this button
+ * @param enabled controls the enabled state of this button. When `false`, this component will not
+ * respond to user input, and it will appear visually disabled and disabled to accessibility
+ * services.
+ * @param interactionSource the [MutableInteractionSource] representing the stream of [Interaction]s
+ * for this button. You can create and pass in your own `remember`ed instance to observe
+ * [Interaction]s and customize the appearance / behavior of this button in different states.
+ * @param background the background to be applied to the [WideButton]
+ * @param scale Defines size of the Button relative to its original size.
+ * @param glow Shadow to be shown behind the Button.
+ * @param shape Defines the Button's shape.
+ * @param contentColor Color to be used for the text content of the Button
+ * @param tonalElevation tonal elevation used to apply a color shift to the button to give the it
+ * higher emphasis
+ * @param border Defines a border around the Button.
+ * @param contentPadding the spacing values to apply internally between the container and the
+ * content
+ * @param content the content of the button
+ */
+@ExperimentalTvMaterial3Api
+@NonRestartableComposable
+@Composable
+fun WideButton(
+    onClick: () -> Unit,
+    modifier: Modifier = Modifier,
+    enabled: Boolean = true,
+    interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
+    background: @Composable () -> Unit = {
+        WideButtonDefaults.Background(
+            enabled = enabled,
+            interactionSource = interactionSource,
+        )
+    },
+    scale: ButtonScale = WideButtonDefaults.scale(),
+    glow: ButtonGlow = WideButtonDefaults.glow(),
+    shape: ButtonShape = WideButtonDefaults.shape(),
+    contentColor: WideButtonContentColor = WideButtonDefaults.contentColor(),
+    tonalElevation: Dp = Elevation.Level0,
+    border: ButtonBorder = WideButtonDefaults.border(),
+    contentPadding: PaddingValues = WideButtonDefaults.ContentPadding,
+    content: @Composable RowScope.() -> Unit
+) {
+    WideButtonImpl(
+        onClick = onClick,
+        enabled = enabled,
+        scale = scale,
+        glow = glow,
+        shape = shape,
+        contentColor = contentColor,
+        tonalElevation = tonalElevation,
+        border = border,
+        contentPadding = contentPadding,
+        interactionSource = interactionSource,
+        modifier = modifier,
+        background = background,
+        content = content
+    )
+}
+
+/**
+ * Material Design wide button for TV.
+ *
+ * @param onClick called when this button is clicked
+ * @param title the title content of the button, typically a [Text]
+ * @param modifier the [Modifier] to be applied to this button
+ * @param enabled controls the enabled state of this button. When `false`, this component will not
+ * respond to user input, and it will appear visually disabled and disabled to accessibility
+ * services.
+ * @param icon the leading icon content of the button, typically an [Icon]
+ * @param subtitle the subtitle content of the button, typically a [Text]
+ * @param interactionSource the [MutableInteractionSource] representing the stream of [Interaction]s
+ * for this button. You can create and pass in your own `remember`ed instance to observe
+ * [Interaction]s and customize the appearance / behavior of this button in different states.
+ * @param background the background to be applied to the [WideButton]
+ * @param scale Defines size of the Button relative to its original size.
+ * @param glow Shadow to be shown behind the Button.
+ * @param shape Defines the Button's shape.
+ * @param contentColor Color to be used for the text content of the Button
+ * @param tonalElevation tonal elevation used to apply a color shift to the button to give the it
+ * higher emphasis
+ * @param border Defines a border around the Button.
+ * @param contentPadding the spacing values to apply internally between the container and the
+ * content
+ */
+@ExperimentalTvMaterial3Api
+@NonRestartableComposable
+@Composable
+fun WideButton(
+    onClick: () -> Unit,
+    title: @Composable () -> Unit,
+    modifier: Modifier = Modifier,
+    enabled: Boolean = true,
+    icon: (@Composable () -> Unit)? = null,
+    subtitle: (@Composable () -> Unit)? = null,
+    interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
+    background: @Composable () -> Unit = {
+        WideButtonDefaults.Background(
+            enabled = enabled,
+            interactionSource = interactionSource
+        )
+    },
+    scale: ButtonScale = WideButtonDefaults.scale(),
+    glow: ButtonGlow = WideButtonDefaults.glow(),
+    shape: ButtonShape = WideButtonDefaults.shape(),
+    contentColor: WideButtonContentColor = WideButtonDefaults.contentColor(),
+    tonalElevation: Dp = Elevation.Level0,
+    border: ButtonBorder = WideButtonDefaults.border(),
+    contentPadding: PaddingValues = WideButtonDefaults.ContentPadding,
+) {
+
+    WideButtonImpl(
+        onClick = onClick,
+        enabled = enabled,
+        scale = scale,
+        glow = glow,
+        shape = shape,
+        contentColor = contentColor,
+        tonalElevation = tonalElevation,
+        border = border,
+        contentPadding = contentPadding,
+        interactionSource = interactionSource,
+        modifier = modifier,
+        minHeight = if (subtitle == null)
+            BaseWideButtonDefaults.MinHeight
+        else
+            BaseWideButtonDefaults.MinHeightWithSubtitle,
+        background = background
+    ) {
+        if (icon != null) {
+            icon()
+            Spacer(
+                modifier = Modifier.padding(end = BaseWideButtonDefaults.HorizontalContentGap)
+            )
+        }
+        Column {
+            ProvideTextStyle(
+                value = MaterialTheme.typography.titleMedium,
+                content = {
+                    Box(
+                        modifier = Modifier
+                            .padding(vertical = BaseWideButtonDefaults.VerticalContentGap)
+                    ) {
+                        title()
+                    }
+                }
+            )
+            if (subtitle != null) {
+                ProvideTextStyle(
+                    value = MaterialTheme.typography.bodySmall.copy(
+                        color = LocalContentColor.current.copy(
+                            alpha = BaseWideButtonDefaults.SubtitleAlpha
+                        )
+                    ),
+                    content = subtitle
+                )
+            }
+        }
+    }
+}
+
+@ExperimentalTvMaterial3Api
+@Composable
+private fun WideButtonImpl(
+    onClick: () -> Unit,
+    enabled: Boolean,
+    scale: ButtonScale,
+    glow: ButtonGlow,
+    shape: ButtonShape,
+    contentColor: WideButtonContentColor,
+    tonalElevation: Dp,
+    border: ButtonBorder,
+    contentPadding: PaddingValues,
+    interactionSource: MutableInteractionSource,
+    background: @Composable () -> Unit,
+    modifier: Modifier = Modifier,
+    minHeight: Dp = BaseWideButtonDefaults.MinHeight,
+    content: @Composable RowScope.() -> Unit
+) {
+    val density = LocalDensity.current
+    var buttonWidth by remember { mutableStateOf(0.dp) }
+    var buttonHeight by remember { mutableStateOf(0.dp) }
+
+    Surface(
+        modifier = modifier.semantics { role = Role.Button },
+        onClick = onClick,
+        enabled = enabled,
+        scale = scale.toClickableSurfaceScale(),
+        glow = glow.toClickableSurfaceGlow(),
+        shape = shape.toClickableSurfaceShape(),
+        color = wideButtonContainerColor(),
+        contentColor = contentColor.toClickableSurfaceContentColor(),
+        tonalElevation = tonalElevation,
+        border = border.toClickableSurfaceBorder(),
+        interactionSource = interactionSource
+    ) {
+        ProvideTextStyle(value = MaterialTheme.typography.labelLarge) {
+            Box(
+                modifier = Modifier
+                    .defaultMinSize(
+                        minWidth = BaseWideButtonDefaults.MinWidth,
+                        minHeight = minHeight,
+                    )
+                    .onPlaced {
+                        with(density) {
+                            buttonWidth = it.size.width.toDp()
+                            buttonHeight = it.size.height.toDp()
+                        }
+                    }
+            ) {
+                Box(modifier = Modifier.size(buttonWidth, buttonHeight)) {
+                    background()
+                }
+
+                Row(
+                    modifier = Modifier
+                        .size(buttonWidth, buttonHeight)
+                        .padding(contentPadding),
+                    verticalAlignment = Alignment.CenterVertically,
+                    content = content
+                )
+            }
+        }
+    }
+}
+
+@OptIn(ExperimentalTvMaterial3Api::class)
+@Composable
+private fun wideButtonContainerColor() = ClickableSurfaceDefaults.color(
+    color = Color.Transparent,
+    focusedColor = Color.Transparent,
+    pressedColor = Color.Transparent,
+    disabledColor = Color.Transparent,
+)
diff --git a/tv/tv-material/src/main/java/androidx/tv/material3/WideButtonDefaults.kt b/tv/tv-material/src/main/java/androidx/tv/material3/WideButtonDefaults.kt
new file mode 100644
index 0000000..07402ff
--- /dev/null
+++ b/tv/tv-material/src/main/java/androidx/tv/material3/WideButtonDefaults.kt
@@ -0,0 +1,207 @@
+/*
+ * 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.material3
+
+import androidx.annotation.FloatRange
+import androidx.compose.foundation.BorderStroke
+import androidx.compose.foundation.background
+import androidx.compose.foundation.interaction.Interaction
+import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.foundation.interaction.collectIsFocusedAsState
+import androidx.compose.foundation.interaction.collectIsPressedAsState
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.ReadOnlyComposable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.Shape
+import androidx.compose.ui.unit.dp
+
+internal object BaseWideButtonDefaults {
+    const val SubtitleAlpha = 0.8f
+    val MinWidth = 240.dp
+    val MinHeight = 48.dp
+    val MinHeightWithSubtitle = 64.dp
+    val HorizontalContentGap = 12.dp
+    val VerticalContentGap = 4.dp
+}
+
+@ExperimentalTvMaterial3Api
+object WideButtonDefaults {
+    private val HorizontalPadding = 16.dp
+    private val VerticalPadding = 10.dp
+
+    /** The default content padding used by [WideButton] */
+    internal val ContentPadding = PaddingValues(
+        start = HorizontalPadding,
+        top = VerticalPadding,
+        end = HorizontalPadding,
+        bottom = VerticalPadding
+    )
+
+    private val ContainerShape = RoundedCornerShape(12.dp)
+
+    /**
+     * Default background for a [WideButton]
+     */
+    @Composable
+    fun Background(
+        enabled: Boolean,
+        interactionSource: MutableInteractionSource,
+    ) {
+        val isFocused = interactionSource.collectIsFocusedAsState().value
+        val isPressed = interactionSource.collectIsPressedAsState().value
+
+        val backgroundColor = when {
+            !enabled -> MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.4f)
+            isPressed -> MaterialTheme.colorScheme.onSurface
+            isFocused -> MaterialTheme.colorScheme.onSurface
+            else -> MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.4f)
+        }
+
+        Box(modifier = Modifier
+            .fillMaxSize()
+            .background(backgroundColor))
+    }
+
+    /**
+     * Creates a [ButtonShape] that represents the default container shapes used in a [WideButton]
+     *
+     * @param shape the shape used when the Button is enabled, and has no other [Interaction]s
+     * @param focusedShape the shape used when the Button is enabled and focused
+     * @param pressedShape the shape used when the Button is enabled pressed
+     * @param disabledShape the shape used when the Button is not enabled
+     * @param focusedDisabledShape the shape used when the Button is not enabled and focused
+     */
+    fun shape(
+        shape: Shape = ContainerShape,
+        focusedShape: Shape = shape,
+        pressedShape: Shape = shape,
+        disabledShape: Shape = shape,
+        focusedDisabledShape: Shape = disabledShape
+    ) = ButtonShape(
+        shape = shape,
+        focusedShape = focusedShape,
+        pressedShape = pressedShape,
+        disabledShape = disabledShape,
+        focusedDisabledShape = focusedDisabledShape
+    )
+
+    /**
+     * Creates a [WideButtonContentColor] that represents the default content colors used in a
+     * [WideButton]
+     *
+     * @param color the content color of this Button when enabled
+     * @param focusedColor the content color of this Button when enabled and focused
+     * @param pressedColor the content color of this Button when enabled and pressed
+     * @param disabledColor the content color of this Button when not enabled
+     */
+    @ReadOnlyComposable
+    @Composable
+    fun contentColor(
+        color: Color = MaterialTheme.colorScheme.onSurface,
+        focusedColor: Color = MaterialTheme.colorScheme.inverseOnSurface,
+        pressedColor: Color = focusedColor,
+        disabledColor: Color = color
+    ) = WideButtonContentColor(
+        contentColor = color,
+        focusedContentColor = focusedColor,
+        pressedContentColor = pressedColor,
+        disabledContentColor = disabledColor
+    )
+
+    /**
+     * Creates a [ButtonScale] that represents the default scales used in a [WideButton].
+     * Scale is used to modify the size of a composable in different [Interaction]
+     * states e.g. 1f (original) in default state, 1.2f (scaled up) in focused state,
+     * 0.8f (scaled down) in pressed state, etc.
+     *
+     * @param scale the scale to be used for this Button when enabled
+     * @param focusedScale the scale to be used for this Button when focused
+     * @param pressedScale the scale to be used for this Button when pressed
+     * @param disabledScale the scale to be used for this Button when disabled
+     * @param focusedDisabledScale the scale to be used for this Button when disabled and
+     * focused
+     */
+    fun scale(
+        @FloatRange(from = 0.0) scale: Float = 1f,
+        @FloatRange(from = 0.0) focusedScale: Float = 1.1f,
+        @FloatRange(from = 0.0) pressedScale: Float = scale,
+        @FloatRange(from = 0.0) disabledScale: Float = scale,
+        @FloatRange(from = 0.0) focusedDisabledScale: Float = disabledScale
+    ) = ButtonScale(
+        scale = scale,
+        focusedScale = focusedScale,
+        pressedScale = pressedScale,
+        disabledScale = disabledScale,
+        focusedDisabledScale = focusedDisabledScale
+    )
+
+    /**
+     * Creates a [ButtonBorder] that represents the default [Border]s applied on a
+     * [WideButton] in different [Interaction] states
+     *
+     * @param border the [Border] to be used for this Button when enabled
+     * @param focusedBorder the [Border] to be used for this Button when focused
+     * @param pressedBorder the [Border] to be used for this Button when pressed
+     * @param disabledBorder the [Border] to be used for this Button when disabled
+     * @param focusedDisabledBorder the [Border] to be used for this Button when disabled and
+     * focused
+     */
+    @ReadOnlyComposable
+    @Composable
+    fun border(
+        border: Border = Border.None,
+        focusedBorder: Border = border,
+        pressedBorder: Border = focusedBorder,
+        disabledBorder: Border = border,
+        focusedDisabledBorder: Border = Border(
+            border = BorderStroke(
+                width = 2.dp,
+                color = MaterialTheme.colorScheme.border
+            ),
+            inset = 0.dp,
+            shape = ContainerShape
+        )
+    ) = ButtonBorder(
+        border = border,
+        focusedBorder = focusedBorder,
+        pressedBorder = pressedBorder,
+        disabledBorder = disabledBorder,
+        focusedDisabledBorder = focusedDisabledBorder
+    )
+
+    /**
+     * Creates a [ButtonGlow] that represents the default [Glow]s used in a [WideButton]
+     *
+     * @param glow the Glow behind this Button when enabled
+     * @param focusedGlow the Glow behind this Button when focused
+     * @param pressedGlow the Glow behind this Button when pressed
+     */
+    fun glow(
+        glow: Glow = Glow.None,
+        focusedGlow: Glow = glow,
+        pressedGlow: Glow = glow
+    ) = ButtonGlow(
+        glow = glow,
+        focusedGlow = focusedGlow,
+        pressedGlow = pressedGlow
+    )
+}