Skip to content

Commit 1286ea7

Browse files
authored
Merge pull request #2696 from element-hq/misc/add-super-button-component
Compound: add SuperButton and GradientFAB components
2 parents ff6c8df + 88b3c33 commit 1286ea7

12 files changed

+404
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
/*
2+
* Copyright (c) 2024 New Vector Ltd
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.element.android.libraries.designsystem.components.button
18+
19+
import androidx.compose.foundation.clickable
20+
import androidx.compose.foundation.interaction.MutableInteractionSource
21+
import androidx.compose.foundation.layout.Box
22+
import androidx.compose.foundation.layout.padding
23+
import androidx.compose.foundation.layout.size
24+
import androidx.compose.foundation.shape.CircleShape
25+
import androidx.compose.foundation.shape.RoundedCornerShape
26+
import androidx.compose.material.ripple.rememberRipple
27+
import androidx.compose.material3.LocalContentColor
28+
import androidx.compose.material3.minimumInteractiveComponentSize
29+
import androidx.compose.runtime.Composable
30+
import androidx.compose.runtime.CompositionLocalProvider
31+
import androidx.compose.runtime.remember
32+
import androidx.compose.ui.Alignment
33+
import androidx.compose.ui.Modifier
34+
import androidx.compose.ui.draw.clip
35+
import androidx.compose.ui.draw.drawBehind
36+
import androidx.compose.ui.geometry.Offset
37+
import androidx.compose.ui.geometry.Size
38+
import androidx.compose.ui.geometry.center
39+
import androidx.compose.ui.graphics.Color
40+
import androidx.compose.ui.graphics.LinearGradientShader
41+
import androidx.compose.ui.graphics.RadialGradientShader
42+
import androidx.compose.ui.graphics.Shader
43+
import androidx.compose.ui.graphics.ShaderBrush
44+
import androidx.compose.ui.graphics.Shape
45+
import androidx.compose.ui.graphics.graphicsLayer
46+
import androidx.compose.ui.unit.dp
47+
import io.element.android.compound.annotations.CoreColorToken
48+
import io.element.android.compound.tokens.generated.CompoundIcons
49+
import io.element.android.compound.tokens.generated.internal.LightColorTokens
50+
import io.element.android.libraries.designsystem.preview.ElementPreview
51+
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
52+
import io.element.android.libraries.designsystem.theme.components.Icon
53+
54+
@OptIn(CoreColorToken::class)
55+
@Composable
56+
fun GradientFloatingActionButton(
57+
onClick: () -> Unit,
58+
modifier: Modifier = Modifier,
59+
shape: Shape = RoundedCornerShape(25),
60+
content: @Composable () -> Unit,
61+
) {
62+
val linearShaderBrush = remember {
63+
object : ShaderBrush() {
64+
override fun createShader(size: Size): Shader {
65+
return LinearGradientShader(
66+
from = Offset(size.width, size.height),
67+
to = Offset(size.width, 0f),
68+
colors = listOf(
69+
LightColorTokens.colorBlue900,
70+
LightColorTokens.colorGreen700,
71+
),
72+
)
73+
}
74+
}
75+
}
76+
val radialShaderBrush = remember {
77+
object : ShaderBrush() {
78+
override fun createShader(size: Size): Shader {
79+
return RadialGradientShader(
80+
center = size.center,
81+
radius = size.width / 2,
82+
colors = listOf(
83+
LightColorTokens.colorGreen700,
84+
LightColorTokens.colorBlue900,
85+
)
86+
)
87+
}
88+
}
89+
}
90+
91+
Box(
92+
modifier = modifier
93+
.minimumInteractiveComponentSize()
94+
.graphicsLayer(shape = shape, clip = false)
95+
.clip(shape)
96+
.drawBehind {
97+
drawRect(brush = radialShaderBrush, alpha = 0.4f)
98+
drawRect(brush = linearShaderBrush)
99+
}
100+
.clickable(
101+
enabled = true,
102+
onClick = onClick,
103+
interactionSource = remember { MutableInteractionSource() },
104+
indication = rememberRipple(color = Color.White)
105+
),
106+
contentAlignment = Alignment.Center
107+
) {
108+
CompositionLocalProvider(LocalContentColor provides Color.White) {
109+
content()
110+
}
111+
}
112+
}
113+
114+
@PreviewsDayNight
115+
@Composable
116+
internal fun GradientFloatingActionButtonPreview() {
117+
ElementPreview {
118+
Box(modifier = Modifier.padding(20.dp)) {
119+
GradientFloatingActionButton(
120+
modifier = Modifier.size(48.dp),
121+
onClick = {},
122+
) {
123+
Icon(imageVector = CompoundIcons.ChatNew(), contentDescription = null)
124+
}
125+
}
126+
}
127+
}
128+
129+
@PreviewsDayNight
130+
@Composable
131+
internal fun GradientSendButtonPreview() {
132+
ElementPreview {
133+
Box(modifier = Modifier.padding(20.dp)) {
134+
GradientFloatingActionButton(
135+
shape = CircleShape,
136+
modifier = Modifier.size(48.dp),
137+
onClick = {},
138+
) {
139+
Icon(
140+
modifier = Modifier.padding(start = 2.dp),
141+
imageVector = CompoundIcons.SendSolid(),
142+
contentDescription = null
143+
)
144+
}
145+
}
146+
}
147+
}
Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
/*
2+
* Copyright (c) 2024 New Vector Ltd
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.element.android.libraries.designsystem.components.button
18+
19+
import androidx.compose.foundation.BorderStroke
20+
import androidx.compose.foundation.border
21+
import androidx.compose.foundation.clickable
22+
import androidx.compose.foundation.interaction.MutableInteractionSource
23+
import androidx.compose.foundation.layout.Box
24+
import androidx.compose.foundation.layout.Column
25+
import androidx.compose.foundation.layout.PaddingValues
26+
import androidx.compose.foundation.layout.padding
27+
import androidx.compose.foundation.shape.RoundedCornerShape
28+
import androidx.compose.material.ripple.rememberRipple
29+
import androidx.compose.material3.LocalContentColor
30+
import androidx.compose.material3.LocalTextStyle
31+
import androidx.compose.material3.Text
32+
import androidx.compose.material3.minimumInteractiveComponentSize
33+
import androidx.compose.runtime.Composable
34+
import androidx.compose.runtime.CompositionLocalProvider
35+
import androidx.compose.runtime.remember
36+
import androidx.compose.ui.Alignment
37+
import androidx.compose.ui.Modifier
38+
import androidx.compose.ui.draw.clip
39+
import androidx.compose.ui.draw.drawBehind
40+
import androidx.compose.ui.geometry.Offset
41+
import androidx.compose.ui.geometry.Size
42+
import androidx.compose.ui.graphics.LinearGradientShader
43+
import androidx.compose.ui.graphics.Shader
44+
import androidx.compose.ui.graphics.ShaderBrush
45+
import androidx.compose.ui.graphics.Shape
46+
import androidx.compose.ui.graphics.graphicsLayer
47+
import androidx.compose.ui.unit.dp
48+
import io.element.android.compound.annotations.CoreColorToken
49+
import io.element.android.compound.theme.ElementTheme
50+
import io.element.android.compound.tokens.generated.internal.DarkColorTokens
51+
import io.element.android.compound.tokens.generated.internal.LightColorTokens
52+
import io.element.android.libraries.designsystem.preview.ElementPreview
53+
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
54+
import io.element.android.libraries.designsystem.theme.components.ButtonSize
55+
import io.element.android.libraries.designsystem.theme.components.HorizontalDivider
56+
57+
@OptIn(CoreColorToken::class)
58+
@Composable
59+
fun SuperButton(
60+
onClick: () -> Unit,
61+
modifier: Modifier = Modifier,
62+
shape: Shape = RoundedCornerShape(50),
63+
buttonSize: ButtonSize = ButtonSize.Large,
64+
enabled: Boolean = true,
65+
content: @Composable () -> Unit,
66+
) {
67+
val contentPadding = remember(buttonSize) {
68+
when (buttonSize) {
69+
ButtonSize.Large -> PaddingValues(horizontal = 24.dp, vertical = 13.dp)
70+
ButtonSize.Medium -> PaddingValues(horizontal = 20.dp, vertical = 9.dp)
71+
ButtonSize.Small -> PaddingValues(horizontal = 16.dp, vertical = 5.dp)
72+
}
73+
}
74+
val isLightTheme = ElementTheme.isLightTheme
75+
val colors = remember(isLightTheme) {
76+
if (isLightTheme) {
77+
listOf(
78+
LightColorTokens.colorBlue900,
79+
LightColorTokens.colorGreen1100,
80+
)
81+
} else {
82+
listOf(
83+
DarkColorTokens.colorBlue900,
84+
DarkColorTokens.colorGreen1100,
85+
)
86+
}
87+
}
88+
89+
val shaderBrush = remember(colors) {
90+
object : ShaderBrush() {
91+
override fun createShader(size: Size): Shader {
92+
return LinearGradientShader(
93+
from = Offset(0f, size.height),
94+
to = Offset(size.width, 0f),
95+
colors = colors,
96+
)
97+
}
98+
}
99+
}
100+
val border = if (enabled) {
101+
BorderStroke(1.dp, shaderBrush)
102+
} else {
103+
BorderStroke(1.dp, ElementTheme.colors.borderDisabled)
104+
}
105+
val backgroundColor = ElementTheme.colors.bgCanvasDefault
106+
Box(
107+
modifier = modifier
108+
.minimumInteractiveComponentSize()
109+
.graphicsLayer(shape = shape, clip = false)
110+
.clip(shape)
111+
.border(border, shape)
112+
.drawBehind {
113+
drawRect(backgroundColor)
114+
drawRect(brush = shaderBrush, alpha = 0.04f)
115+
}
116+
.clickable(
117+
enabled = enabled,
118+
onClick = onClick,
119+
interactionSource = remember { MutableInteractionSource() },
120+
indication = rememberRipple()
121+
)
122+
.padding(contentPadding),
123+
contentAlignment = Alignment.Center
124+
) {
125+
CompositionLocalProvider(
126+
LocalContentColor provides if (enabled) ElementTheme.colors.textPrimary else ElementTheme.colors.textDisabled,
127+
LocalTextStyle provides ElementTheme.typography.fontBodyLgMedium,
128+
) {
129+
content()
130+
}
131+
}
132+
}
133+
134+
@PreviewsDayNight
135+
@Composable
136+
internal fun SuperButtonPreview() {
137+
ElementPreview {
138+
Column(horizontalAlignment = Alignment.CenterHorizontally) {
139+
SuperButton(
140+
modifier = Modifier.padding(10.dp),
141+
buttonSize = ButtonSize.Large,
142+
onClick = {},
143+
) {
144+
Text("Super button!")
145+
}
146+
147+
SuperButton(
148+
modifier = Modifier.padding(10.dp),
149+
buttonSize = ButtonSize.Medium,
150+
onClick = {},
151+
) {
152+
Text("Super button!")
153+
}
154+
155+
SuperButton(
156+
modifier = Modifier.padding(10.dp),
157+
buttonSize = ButtonSize.Small,
158+
onClick = {},
159+
) {
160+
Text("Super button!")
161+
}
162+
163+
HorizontalDivider()
164+
165+
SuperButton(
166+
modifier = Modifier.padding(10.dp),
167+
buttonSize = ButtonSize.Large,
168+
enabled = false,
169+
onClick = {},
170+
) {
171+
Text("Super button!")
172+
}
173+
174+
SuperButton(
175+
modifier = Modifier.padding(10.dp),
176+
buttonSize = ButtonSize.Medium,
177+
enabled = false,
178+
onClick = {},
179+
) {
180+
Text("Super button!")
181+
}
182+
183+
SuperButton(
184+
modifier = Modifier.padding(10.dp),
185+
buttonSize = ButtonSize.Small,
186+
enabled = false,
187+
onClick = {},
188+
) {
189+
Text("Super button!")
190+
}
191+
}
192+
}
193+
}

0 commit comments

Comments
 (0)