Merge pull request #2696 from element-hq/misc/add-super-button-component
Compound: add SuperButton and GradientFAB components
This commit is contained in:
commit
1286ea7e77
12 changed files with 404 additions and 0 deletions
|
|
@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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!")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:cae4b9086f68755b300d2200d49755183df5e9e1e23cdab937aede8e4a473e18
|
||||
size 7886
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:a3d11615261dcbefa4fd78cfc111c601a87c80060d19a5881ede28ee531b1dd4
|
||||
size 7815
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:f59b4185d583aa3e42ab1449cb4dc9c46314cd4e00e8c7f8ea0e6db5a8560556
|
||||
size 8393
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:c298a041d27639e42bc3e65d99d2c0865d499faf0f195f468ec32e1c372691a8
|
||||
size 8275
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:46bd7f805daa3ba70cecf8f75439415d0c37a9387118277ba034e5b14e2c08ed
|
||||
size 57840
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:989ee65e0c17961e226f5307f248b607b8741246d17d036bf7a6d345f8f3c93d
|
||||
size 58331
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:fcab510e8f74975d5d4fccb627983752d607c768805e6a69ba5c606f78ec66c5
|
||||
size 62261
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:c9aae210997acac9c248f01ad7879f00b9f94f4ba86bf91cf81f98c8c32c94d0
|
||||
size 70241
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:9c1c1771b5d23e02261fdd38cb0995ef29b8d3d77f227da6bca35dff64c62256
|
||||
size 45971
|
||||
Loading…
Add table
Add a link
Reference in a new issue