diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/button/GradientFloatingActionButton.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/button/GradientFloatingActionButton.kt new file mode 100644 index 00000000000..dcce83644c2 --- /dev/null +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/button/GradientFloatingActionButton.kt @@ -0,0 +1,147 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.designsystem.components.button + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.ripple.rememberRipple +import androidx.compose.material3.LocalContentColor +import androidx.compose.material3.minimumInteractiveComponentSize +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.draw.drawBehind +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.geometry.Size +import androidx.compose.ui.geometry.center +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.LinearGradientShader +import androidx.compose.ui.graphics.RadialGradientShader +import androidx.compose.ui.graphics.Shader +import androidx.compose.ui.graphics.ShaderBrush +import androidx.compose.ui.graphics.Shape +import androidx.compose.ui.graphics.graphicsLayer +import androidx.compose.ui.unit.dp +import io.element.android.compound.annotations.CoreColorToken +import io.element.android.compound.tokens.generated.CompoundIcons +import io.element.android.compound.tokens.generated.internal.LightColorTokens +import io.element.android.libraries.designsystem.preview.ElementPreview +import io.element.android.libraries.designsystem.preview.PreviewsDayNight +import io.element.android.libraries.designsystem.theme.components.Icon + +@OptIn(CoreColorToken::class) +@Composable +fun GradientFloatingActionButton( + onClick: () -> Unit, + modifier: Modifier = Modifier, + shape: Shape = RoundedCornerShape(25), + content: @Composable () -> Unit, +) { + val linearShaderBrush = remember { + object : ShaderBrush() { + override fun createShader(size: Size): Shader { + return LinearGradientShader( + from = Offset(size.width, size.height), + to = Offset(size.width, 0f), + colors = listOf( + LightColorTokens.colorBlue900, + LightColorTokens.colorGreen700, + ), + ) + } + } + } + val radialShaderBrush = remember { + object : ShaderBrush() { + override fun createShader(size: Size): Shader { + return RadialGradientShader( + center = size.center, + radius = size.width / 2, + colors = listOf( + LightColorTokens.colorGreen700, + LightColorTokens.colorBlue900, + ) + ) + } + } + } + + Box( + modifier = modifier + .minimumInteractiveComponentSize() + .graphicsLayer(shape = shape, clip = false) + .clip(shape) + .drawBehind { + drawRect(brush = radialShaderBrush, alpha = 0.4f) + drawRect(brush = linearShaderBrush) + } + .clickable( + enabled = true, + onClick = onClick, + interactionSource = remember { MutableInteractionSource() }, + indication = rememberRipple(color = Color.White) + ), + contentAlignment = Alignment.Center + ) { + CompositionLocalProvider(LocalContentColor provides Color.White) { + content() + } + } +} + +@PreviewsDayNight +@Composable +internal fun GradientFloatingActionButtonPreview() { + ElementPreview { + Box(modifier = Modifier.padding(20.dp)) { + GradientFloatingActionButton( + modifier = Modifier.size(48.dp), + onClick = {}, + ) { + Icon(imageVector = CompoundIcons.ChatNew(), contentDescription = null) + } + } + } +} + +@PreviewsDayNight +@Composable +internal fun GradientSendButtonPreview() { + ElementPreview { + Box(modifier = Modifier.padding(20.dp)) { + GradientFloatingActionButton( + shape = CircleShape, + modifier = Modifier.size(48.dp), + onClick = {}, + ) { + Icon( + modifier = Modifier.padding(start = 2.dp), + imageVector = CompoundIcons.SendSolid(), + contentDescription = null + ) + } + } + } +} diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/button/SuperButton.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/button/SuperButton.kt new file mode 100644 index 00000000000..56da5f5211e --- /dev/null +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/button/SuperButton.kt @@ -0,0 +1,193 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.designsystem.components.button + +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.border +import androidx.compose.foundation.clickable +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.padding +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.ripple.rememberRipple +import androidx.compose.material3.LocalContentColor +import androidx.compose.material3.LocalTextStyle +import androidx.compose.material3.Text +import androidx.compose.material3.minimumInteractiveComponentSize +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.draw.drawBehind +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.geometry.Size +import androidx.compose.ui.graphics.LinearGradientShader +import androidx.compose.ui.graphics.Shader +import androidx.compose.ui.graphics.ShaderBrush +import androidx.compose.ui.graphics.Shape +import androidx.compose.ui.graphics.graphicsLayer +import androidx.compose.ui.unit.dp +import io.element.android.compound.annotations.CoreColorToken +import io.element.android.compound.theme.ElementTheme +import io.element.android.compound.tokens.generated.internal.DarkColorTokens +import io.element.android.compound.tokens.generated.internal.LightColorTokens +import io.element.android.libraries.designsystem.preview.ElementPreview +import io.element.android.libraries.designsystem.preview.PreviewsDayNight +import io.element.android.libraries.designsystem.theme.components.ButtonSize +import io.element.android.libraries.designsystem.theme.components.HorizontalDivider + +@OptIn(CoreColorToken::class) +@Composable +fun SuperButton( + onClick: () -> Unit, + modifier: Modifier = Modifier, + shape: Shape = RoundedCornerShape(50), + buttonSize: ButtonSize = ButtonSize.Large, + enabled: Boolean = true, + content: @Composable () -> Unit, +) { + val contentPadding = remember(buttonSize) { + when (buttonSize) { + ButtonSize.Large -> PaddingValues(horizontal = 24.dp, vertical = 13.dp) + ButtonSize.Medium -> PaddingValues(horizontal = 20.dp, vertical = 9.dp) + ButtonSize.Small -> PaddingValues(horizontal = 16.dp, vertical = 5.dp) + } + } + val isLightTheme = ElementTheme.isLightTheme + val colors = remember(isLightTheme) { + if (isLightTheme) { + listOf( + LightColorTokens.colorBlue900, + LightColorTokens.colorGreen1100, + ) + } else { + listOf( + DarkColorTokens.colorBlue900, + DarkColorTokens.colorGreen1100, + ) + } + } + + val shaderBrush = remember(colors) { + object : ShaderBrush() { + override fun createShader(size: Size): Shader { + return LinearGradientShader( + from = Offset(0f, size.height), + to = Offset(size.width, 0f), + colors = colors, + ) + } + } + } + val border = if (enabled) { + BorderStroke(1.dp, shaderBrush) + } else { + BorderStroke(1.dp, ElementTheme.colors.borderDisabled) + } + val backgroundColor = ElementTheme.colors.bgCanvasDefault + Box( + modifier = modifier + .minimumInteractiveComponentSize() + .graphicsLayer(shape = shape, clip = false) + .clip(shape) + .border(border, shape) + .drawBehind { + drawRect(backgroundColor) + drawRect(brush = shaderBrush, alpha = 0.04f) + } + .clickable( + enabled = enabled, + onClick = onClick, + interactionSource = remember { MutableInteractionSource() }, + indication = rememberRipple() + ) + .padding(contentPadding), + contentAlignment = Alignment.Center + ) { + CompositionLocalProvider( + LocalContentColor provides if (enabled) ElementTheme.colors.textPrimary else ElementTheme.colors.textDisabled, + LocalTextStyle provides ElementTheme.typography.fontBodyLgMedium, + ) { + content() + } + } +} + +@PreviewsDayNight +@Composable +internal fun SuperButtonPreview() { + ElementPreview { + Column(horizontalAlignment = Alignment.CenterHorizontally) { + SuperButton( + modifier = Modifier.padding(10.dp), + buttonSize = ButtonSize.Large, + onClick = {}, + ) { + Text("Super button!") + } + + SuperButton( + modifier = Modifier.padding(10.dp), + buttonSize = ButtonSize.Medium, + onClick = {}, + ) { + Text("Super button!") + } + + SuperButton( + modifier = Modifier.padding(10.dp), + buttonSize = ButtonSize.Small, + onClick = {}, + ) { + Text("Super button!") + } + + HorizontalDivider() + + SuperButton( + modifier = Modifier.padding(10.dp), + buttonSize = ButtonSize.Large, + enabled = false, + onClick = {}, + ) { + Text("Super button!") + } + + SuperButton( + modifier = Modifier.padding(10.dp), + buttonSize = ButtonSize.Medium, + enabled = false, + onClick = {}, + ) { + Text("Super button!") + } + + SuperButton( + modifier = Modifier.padding(10.dp), + buttonSize = ButtonSize.Small, + enabled = false, + onClick = {}, + ) { + Text("Super button!") + } + } + } +} diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/Button.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/Button.kt index 6470cd0c8b5..c4e2f16fc2f 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/Button.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/Button.kt @@ -138,6 +138,7 @@ private fun ButtonInternal( leadingIcon: IconSource? = null, ) { val minHeight = when (size) { + ButtonSize.Small -> 32.dp ButtonSize.Medium -> 40.dp ButtonSize.Large -> 48.dp } @@ -145,6 +146,13 @@ private fun ButtonInternal( val hasStartDrawable = showProgress || leadingIcon != null val contentPadding = when (size) { + ButtonSize.Small -> { + if (hasStartDrawable) { + PaddingValues(start = 8.dp, top = 5.dp, end = 16.dp, bottom = 5.dp) + } else { + PaddingValues(start = 16.dp, top = 5.dp, end = 16.dp, bottom = 5.dp) + } + } ButtonSize.Medium -> when (style) { ButtonStyle.Filled, ButtonStyle.Outlined -> if (hasStartDrawable) { @@ -195,6 +203,7 @@ private fun ButtonInternal( } val textStyle = when (size) { + ButtonSize.Small, ButtonSize.Medium -> MaterialTheme.typography.labelLarge ButtonSize.Large -> ElementTheme.typography.fontBodyLgMedium } @@ -259,6 +268,7 @@ sealed interface IconSource { } enum class ButtonSize { + Small, Medium, Large } @@ -317,6 +327,15 @@ internal enum class ButtonStyle { } } +@Preview(group = PreviewGroup.Buttons) +@Composable +internal fun FilledButtonSmallPreview() { + ButtonCombinationPreview( + style = ButtonStyle.Filled, + size = ButtonSize.Small, + ) +} + @Preview(group = PreviewGroup.Buttons) @Composable internal fun FilledButtonMediumPreview() { @@ -335,6 +354,15 @@ internal fun FilledButtonLargePreview() { ) } +@Preview(group = PreviewGroup.Buttons) +@Composable +internal fun OutlinedButtonSmallPreview() { + ButtonCombinationPreview( + style = ButtonStyle.Outlined, + size = ButtonSize.Small, + ) +} + @Preview(group = PreviewGroup.Buttons) @Composable internal fun OutlinedButtonMediumPreview() { @@ -353,6 +381,15 @@ internal fun OutlinedButtonLargePreview() { ) } +@Preview(group = PreviewGroup.Buttons) +@Composable +internal fun TextButtonSmallPreview() { + ButtonCombinationPreview( + style = ButtonStyle.Text, + size = ButtonSize.Small, + ) +} + @Preview(group = PreviewGroup.Buttons) @Composable internal fun TextButtonMediumPreview() { diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.button_GradientFloatingActionButton_null_GradientFloatingActionButton-Day_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.button_GradientFloatingActionButton_null_GradientFloatingActionButton-Day_0_null,NEXUS_5,1.0,en].png new file mode 100644 index 00000000000..107d64cabda --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.button_GradientFloatingActionButton_null_GradientFloatingActionButton-Day_0_null,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cae4b9086f68755b300d2200d49755183df5e9e1e23cdab937aede8e4a473e18 +size 7886 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.button_GradientFloatingActionButton_null_GradientFloatingActionButton-Night_1_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.button_GradientFloatingActionButton_null_GradientFloatingActionButton-Night_1_null,NEXUS_5,1.0,en].png new file mode 100644 index 00000000000..0d901392daf --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.button_GradientFloatingActionButton_null_GradientFloatingActionButton-Night_1_null,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a3d11615261dcbefa4fd78cfc111c601a87c80060d19a5881ede28ee531b1dd4 +size 7815 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.button_GradientSendButton_null_GradientSendButton-Day_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.button_GradientSendButton_null_GradientSendButton-Day_0_null,NEXUS_5,1.0,en].png new file mode 100644 index 00000000000..27defedd12a --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.button_GradientSendButton_null_GradientSendButton-Day_0_null,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f59b4185d583aa3e42ab1449cb4dc9c46314cd4e00e8c7f8ea0e6db5a8560556 +size 8393 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.button_GradientSendButton_null_GradientSendButton-Night_1_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.button_GradientSendButton_null_GradientSendButton-Night_1_null,NEXUS_5,1.0,en].png new file mode 100644 index 00000000000..e015eab0910 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.button_GradientSendButton_null_GradientSendButton-Night_1_null,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c298a041d27639e42bc3e65d99d2c0865d499faf0f195f468ec32e1c372691a8 +size 8275 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.button_SuperButton_null_SuperButton-Day_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.button_SuperButton_null_SuperButton-Day_0_null,NEXUS_5,1.0,en].png new file mode 100644 index 00000000000..efd2eb0c963 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.button_SuperButton_null_SuperButton-Day_0_null,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:46bd7f805daa3ba70cecf8f75439415d0c37a9387118277ba034e5b14e2c08ed +size 57840 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.button_SuperButton_null_SuperButton-Night_1_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.button_SuperButton_null_SuperButton-Night_1_null,NEXUS_5,1.0,en].png new file mode 100644 index 00000000000..7b0bf201877 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.components.button_SuperButton_null_SuperButton-Night_1_null,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:989ee65e0c17961e226f5307f248b607b8741246d17d036bf7a6d345f8f3c93d +size 58331 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_FilledButtonSmall_null_Buttons_FilledButtonSmall_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_FilledButtonSmall_null_Buttons_FilledButtonSmall_0_null,NEXUS_5,1.0,en].png new file mode 100644 index 00000000000..3cfdde85365 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_FilledButtonSmall_null_Buttons_FilledButtonSmall_0_null,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fcab510e8f74975d5d4fccb627983752d607c768805e6a69ba5c606f78ec66c5 +size 62261 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_OutlinedButtonSmall_null_Buttons_OutlinedButtonSmall_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_OutlinedButtonSmall_null_Buttons_OutlinedButtonSmall_0_null,NEXUS_5,1.0,en].png new file mode 100644 index 00000000000..ed7700c98d8 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_OutlinedButtonSmall_null_Buttons_OutlinedButtonSmall_0_null,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c9aae210997acac9c248f01ad7879f00b9f94f4ba86bf91cf81f98c8c32c94d0 +size 70241 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_TextButtonSmall_null_Buttons_TextButtonSmall_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_TextButtonSmall_null_Buttons_TextButtonSmall_0_null,NEXUS_5,1.0,en].png new file mode 100644 index 00000000000..dae2dc2f3b2 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_TextButtonSmall_null_Buttons_TextButtonSmall_0_null,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9c1c1771b5d23e02261fdd38cb0995ef29b8d3d77f227da6bca35dff64c62256 +size 45971