[Compound] Implement components (Button) (#1021)

* Create `CompoundButton`

* Some fixes

* Lint fixes

* Start replacing existing `Button` usages

* Replace button usages

* Remove previous Button composable

* Rename `CompoundButton` to `Button`

* Fix emphasized button being displayed as Text

* Fix cancel button in `WaitListView`

* Update screenshots

* Add shorthand functions for `OutlinedButton` and `TextButton`

* Add changelog

* Fix wrong size used for emphasized button in dialog

* Create a private `ButtonInternal` implementation with the shared logic.

- Make `ButtonStyle` private.
- Rename `title` to `text`.
- Rename `buttonStyle` and `buttonSize` to just `style` and `size`.

* Fix several warnings and lint issues.

* Update screenshots

---------

Co-authored-by: ElementBot <benoitm+elementbot@element.io>
This commit is contained in:
Jorge Martin Espinosa 2023-08-08 18:11:37 +02:00 committed by GitHub
parent fdee5d8a76
commit 23982dde47
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
228 changed files with 805 additions and 950 deletions

View file

@ -28,7 +28,7 @@ import androidx.compose.ui.unit.dp
import io.element.android.libraries.designsystem.preview.ElementPreviewDark
import io.element.android.libraries.designsystem.preview.ElementPreviewLight
import io.element.android.libraries.designsystem.theme.components.Button
import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.designsystem.theme.components.OutlinedButton
import io.element.android.libraries.designsystem.theme.components.TextButton
@Composable
@ -59,11 +59,8 @@ internal fun ButtonColumnMoleculeDarkPreview() =
@Composable
private fun ContentToPreview() {
ButtonColumnMolecule {
Button(onClick = {}, modifier = Modifier.fillMaxWidth()) {
Text(text = "Button")
}
TextButton(onClick = {}, modifier = Modifier.fillMaxWidth()) {
Text(text = "TextButton")
}
Button(text = "Button", onClick = {}, modifier = Modifier.fillMaxWidth())
OutlinedButton(text = "OutlinedButton", onClick = {}, modifier = Modifier.fillMaxWidth())
TextButton(text = "TextButton", onClick = {}, modifier = Modifier.fillMaxWidth())
}
}

View file

@ -25,7 +25,6 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import io.element.android.libraries.designsystem.preview.ElementPreviewDark
import io.element.android.libraries.designsystem.preview.ElementPreviewLight
import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.designsystem.theme.components.TextButton
@Composable
@ -54,11 +53,7 @@ internal fun ButtonRowMoleculeDarkPreview() =
@Composable
private fun ContentToPreview() {
ButtonRowMolecule {
TextButton(onClick = { }) {
Text("Button 1")
}
TextButton(onClick = { }) {
Text("Button 2")
}
TextButton(text = "Button 1", onClick = {})
TextButton(text = "Button 2", onClick = {})
}
}

View file

@ -129,9 +129,10 @@ private fun ProgressDialogContent(
modifier = Modifier.fillMaxWidth(),
contentAlignment = Alignment.BottomEnd
) {
TextButton(onClick = onCancelClicked) {
Text(stringResource(id = CommonStrings.action_cancel))
}
TextButton(
text = stringResource(id = CommonStrings.action_cancel),
onClick = onCancelClicked,
)
}
}
}

View file

@ -48,9 +48,10 @@ fun AsyncFailure(
Text(text = throwable.message ?: stringResource(id = CommonStrings.error_unknown))
if (onRetry != null) {
Spacer(modifier = Modifier.height(24.dp))
Button(onClick = onRetry) {
Text(text = stringResource(id = CommonStrings.action_retry))
}
Button(
text = stringResource(id = CommonStrings.action_retry),
onClick = onRetry
)
}
}
}

View file

@ -1,107 +0,0 @@
/*
* Copyright (c) 2023 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.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.progressSemantics
import androidx.compose.material3.ButtonColors
import androidx.compose.material3.ButtonElevation
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import io.element.android.libraries.designsystem.preview.ElementThemedPreview
import io.element.android.libraries.designsystem.preview.PreviewGroup
import io.element.android.libraries.designsystem.theme.aliasButtonText
import io.element.android.libraries.designsystem.theme.components.Button
import io.element.android.libraries.designsystem.theme.components.CircularProgressIndicator
import io.element.android.libraries.designsystem.theme.components.ElementButtonDefaults
import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.theme.ElementTheme
/**
* A component that will display a button with an indeterminate circular progressbar.
* When [showProgress] is true:
* - A circular progressbar is displayed.
* - [text] is replaced by [progressText], if defined.
* - [onClick] gets disabled.
*/
@Composable
fun ButtonWithProgress(
text: String?,
onClick: () -> Unit,
modifier: Modifier = Modifier,
showProgress: Boolean = false,
progressText: String? = text,
enabled: Boolean = true,
shape: Shape = ElementButtonDefaults.shape,
colors: ButtonColors = ElementButtonDefaults.buttonColors(),
elevation: ButtonElevation? = ElementButtonDefaults.buttonElevation(),
border: BorderStroke? = null,
contentPadding: PaddingValues = ElementButtonDefaults.ContentPadding,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
) {
Button(
onClick = {
if (!showProgress) {
onClick()
}
},
modifier = modifier,
enabled = enabled,
shape = shape,
colors = colors,
elevation = elevation,
border = border,
contentPadding = contentPadding,
interactionSource = interactionSource,
) {
if (showProgress) {
CircularProgressIndicator(
modifier = Modifier
.progressSemantics()
.size(18.dp),
color = MaterialTheme.colorScheme.onPrimary,
strokeWidth = 2.dp,
)
if (progressText != null) {
Spacer(Modifier.width(10.dp))
Text(progressText, style = ElementTheme.typography.aliasButtonText)
}
} else if (text != null) {
Text(text, style = ElementTheme.typography.aliasButtonText)
}
}
}
@Preview(group = PreviewGroup.Buttons)
@Composable
internal fun ButtonWithProgressPreview() = ElementThemedPreview {
ButtonWithProgress(
text = "Button with progress",
onClick = {},
showProgress = true,
)
}

View file

@ -27,7 +27,6 @@ import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.ProvideTextStyle
import androidx.compose.material3.Surface
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.ui.Alignment
@ -38,7 +37,10 @@ import androidx.compose.ui.layout.Layout
import androidx.compose.ui.layout.Placeable
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import io.element.android.libraries.designsystem.theme.components.ButtonSize
import io.element.android.libraries.designsystem.theme.components.Button
import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.designsystem.theme.components.TextButton
import io.element.android.libraries.theme.ElementTheme
import kotlin.math.max
@ -53,7 +55,6 @@ internal fun SimpleAlertDialogContent(
onSubmitClicked: () -> Unit = {},
thirdButtonText: String? = null,
onThirdButtonClicked: () -> Unit = {},
emphasizeSubmitButton: Boolean = false,
shape: Shape = AlertDialogDefaults.shape,
containerColor: Color = AlertDialogDefaults.containerColor,
iconContentColor: Color = AlertDialogDefaults.iconContentColor,
@ -71,30 +72,23 @@ internal fun SimpleAlertDialogContent(
if (thirdButtonText != null) {
// If there is a 3rd item it should be at the end of the dialog
// Having this 3rd action is discouraged, see https://m3.material.io/components/dialogs/guidelines#e13b68f5-e367-4275-ad6f-c552ee8e358f
TextButton(onClick = onThirdButtonClicked) {
Text(
text = thirdButtonText,
style = ElementTheme.typography.fontBodyMdRegular,
)
}
}
TextButton(onClick = onCancelClicked) {
Text(
text = cancelText,
style = ElementTheme.typography.fontBodyMdRegular,
TextButton(
text = thirdButtonText,
buttonSize = ButtonSize.Medium,
onClick = onThirdButtonClicked,
)
}
TextButton(
text = cancelText,
buttonSize = ButtonSize.Medium,
onClick = onCancelClicked,
)
if (submitText != null) {
TextButton(onClick = onSubmitClicked) {
Text(
text = submitText,
style = if (emphasizeSubmitButton) {
ElementTheme.typography.fontBodyMdMedium
} else {
ElementTheme.typography.fontBodyMdRegular
}
)
}
Button(
text = submitText,
buttonSize = ButtonSize.Medium,
onClick = onSubmitClicked,
)
}
}
},

View file

@ -26,11 +26,9 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.Dp
import io.element.android.libraries.designsystem.preview.ElementThemedPreview
import io.element.android.libraries.designsystem.preview.PreviewGroup
import io.element.android.libraries.designsystem.utils.BooleanProvider
import io.element.android.libraries.ui.strings.CommonStrings
@OptIn(ExperimentalMaterial3Api::class)
@ -44,7 +42,6 @@ fun ConfirmationDialog(
submitText: String = stringResource(id = CommonStrings.action_ok),
cancelText: String = stringResource(id = CommonStrings.action_cancel),
thirdButtonText: String? = null,
emphasizeSubmitButton: Boolean = false,
onCancelClicked: () -> Unit = onDismiss,
onThirdButtonClicked: () -> Unit = {},
shape: Shape = AlertDialogDefaults.shape,
@ -71,7 +68,6 @@ fun ConfirmationDialog(
titleContentColor = titleContentColor,
textContentColor = textContentColor,
tonalElevation = tonalElevation,
emphasizeSubmitButton = emphasizeSubmitButton,
)
}
}
@ -87,7 +83,6 @@ private fun ConfirmationDialogContent(
title: String? = null,
thirdButtonText: String? = null,
onThirdButtonClicked: () -> Unit = {},
emphasizeSubmitButton: Boolean = false,
shape: Shape = AlertDialogDefaults.shape,
containerColor: Color = AlertDialogDefaults.containerColor,
iconContentColor: Color = AlertDialogDefaults.iconContentColor,
@ -106,7 +101,6 @@ private fun ConfirmationDialogContent(
onCancelClicked = onCancelClicked,
thirdButtonText = thirdButtonText,
onThirdButtonClicked = onThirdButtonClicked,
emphasizeSubmitButton = emphasizeSubmitButton,
shape = shape,
containerColor = containerColor,
iconContentColor = iconContentColor,
@ -119,7 +113,7 @@ private fun ConfirmationDialogContent(
@Preview(group = PreviewGroup.Dialogs)
@Composable
internal fun ConfirmationDialogPreview(@PreviewParameter(BooleanProvider::class) emphasizeSubmitButton: Boolean) =
internal fun ConfirmationDialogPreview() =
ElementThemedPreview {
DialogPreview {
ConfirmationDialogContent(
@ -130,7 +124,6 @@ internal fun ConfirmationDialogPreview(@PreviewParameter(BooleanProvider::class)
thirdButtonText = "Disable",
onSubmitClicked = {},
onCancelClicked = {},
emphasizeSubmitButton = emphasizeSubmitButton,
)
}
}

View file

@ -18,7 +18,7 @@ package io.element.android.libraries.designsystem.components.dialogs
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.AlertDialogDefaults
import androidx.compose.material3.TextButton
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
@ -28,10 +28,9 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp
import io.element.android.libraries.designsystem.preview.ElementThemedPreview
import io.element.android.libraries.designsystem.preview.PreviewGroup
import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.theme.ElementTheme
import io.element.android.libraries.ui.strings.CommonStrings
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun RetryDialog(
content: String,
@ -48,44 +47,22 @@ fun RetryDialog(
textContentColor: Color = AlertDialogDefaults.textContentColor,
tonalElevation: Dp = AlertDialogDefaults.TonalElevation,
) {
AlertDialog(
modifier = modifier,
onDismissRequest = onDismiss,
title = {
Text(
text = title,
style = ElementTheme.typography.fontHeadingSmRegular,
)
},
text = {
Text(
text = content,
style = ElementTheme.typography.fontBodyMdRegular,
)
},
confirmButton = {
TextButton(onClick = onRetry) {
Text(
text = retryText,
style = ElementTheme.typography.fontBodyMdRegular,
)
}
},
dismissButton = {
TextButton(onClick = onDismiss) {
Text(
text = dismissText,
style = ElementTheme.typography.fontBodyMdRegular,
)
}
},
shape = shape,
containerColor = containerColor,
iconContentColor = iconContentColor,
titleContentColor = titleContentColor,
textContentColor = textContentColor,
tonalElevation = tonalElevation,
)
AlertDialog(modifier = modifier, onDismissRequest = onDismiss) {
RetryDialogContent(
title = title,
content = content,
retryText = retryText,
dismissText = dismissText,
onRetry = onRetry,
onDismiss = onDismiss,
shape = shape,
containerColor = containerColor,
iconContentColor = iconContentColor,
titleContentColor = titleContentColor,
textContentColor = textContentColor,
tonalElevation = tonalElevation,
)
}
}
@Composable

View file

@ -24,8 +24,8 @@ import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import io.element.android.libraries.designsystem.preview.ElementPreviewDark
import io.element.android.libraries.designsystem.preview.ElementPreviewLight
import io.element.android.libraries.designsystem.theme.components.ButtonSize
import io.element.android.libraries.designsystem.theme.components.OutlinedButton
import io.element.android.libraries.designsystem.theme.components.Text
/**
* Debug tool to add a vertical and a horizontal ruler on top of the content.
@ -76,8 +76,10 @@ internal fun WithRulerDarkPreview() =
@Composable
private fun ContentToPreview() {
WithRulers(xRulersOffset = 20.dp, yRulersOffset = 15.dp) {
OutlinedButton(onClick = {}) {
Text(text = "A Button with rulers on it!")
}
OutlinedButton(
text = "A Button with rulers on it!",
buttonSize = ButtonSize.Medium,
onClick = {},
)
}
}

View file

@ -18,68 +18,323 @@ package io.element.android.libraries.designsystem.theme.components
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.IntrinsicSize
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.RowScope
import androidx.compose.material3.ButtonColors
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.progressSemantics
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Share
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.ButtonElevation
import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.RectangleShape
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.graphics.vector.rememberVectorPainter
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import io.element.android.libraries.designsystem.preview.ElementThemedPreview
import io.element.android.libraries.designsystem.preview.PreviewGroup
import io.element.android.libraries.theme.ElementTheme
// Designs: https://www.figma.com/file/G1xy0HDZKJf5TCRFmKb5d5/Compound-Android-Components?type=design&mode=design&t=U03tOFZz5FSLVUMa-1
@Composable
fun Button(
text: String,
onClick: () -> Unit,
modifier: Modifier = Modifier,
enabled: Boolean = true,
shape: Shape = ElementButtonDefaults.shape,
colors: ButtonColors = ElementButtonDefaults.buttonColors(),
elevation: ButtonElevation? = ElementButtonDefaults.buttonElevation(),
border: BorderStroke? = null,
contentPadding: PaddingValues = ElementButtonDefaults.ContentPadding,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
content: @Composable RowScope.() -> Unit
buttonSize: ButtonSize = ButtonSize.Large,
showProgress: Boolean = false,
leadingIcon: IconSource? = null,
) = ButtonInternal(text, onClick, ButtonStyle.Filled, modifier, enabled, buttonSize, showProgress, leadingIcon)
@Composable
fun OutlinedButton(
text: String,
onClick: () -> Unit,
modifier: Modifier = Modifier,
enabled: Boolean = true,
buttonSize: ButtonSize = ButtonSize.Large,
showProgress: Boolean = false,
leadingIcon: IconSource? = null,
) = ButtonInternal(text, onClick, ButtonStyle.Outlined, modifier, enabled, buttonSize, showProgress, leadingIcon)
@Composable
fun TextButton(
text: String,
onClick: () -> Unit,
modifier: Modifier = Modifier,
enabled: Boolean = true,
buttonSize: ButtonSize = ButtonSize.Large,
showProgress: Boolean = false,
leadingIcon: IconSource? = null,
) = ButtonInternal(text, onClick, ButtonStyle.Text, modifier, enabled, buttonSize, showProgress, leadingIcon)
@Composable
private fun ButtonInternal(
text: String,
onClick: () -> Unit,
style: ButtonStyle,
modifier: Modifier = Modifier,
enabled: Boolean = true,
size: ButtonSize = ButtonSize.Large,
showProgress: Boolean = false,
leadingIcon: IconSource? = null,
) {
val minHeight = when (size) {
ButtonSize.Medium -> 40.dp
ButtonSize.Large -> 48.dp
}
val paddingValues = when (size) {
ButtonSize.Medium -> {
when (style) {
ButtonStyle.Text -> PaddingValues(horizontal = 12.dp, vertical = 10.dp)
else -> PaddingValues(horizontal = 16.dp, vertical = 10.dp)
}
}
ButtonSize.Large -> {
when (style) {
ButtonStyle.Text -> PaddingValues(horizontal = 16.dp, vertical = 13.dp)
else -> PaddingValues(horizontal = 24.dp, vertical = 13.dp)
}
}
}
val shape = when (style) {
ButtonStyle.Filled, ButtonStyle.Outlined -> RoundedCornerShape(percent = 50)
ButtonStyle.Text -> RectangleShape
}
val colors = when (style) {
ButtonStyle.Filled -> ButtonDefaults.buttonColors(
containerColor = ElementTheme.materialColors.primary,
contentColor = ElementTheme.materialColors.onPrimary,
disabledContainerColor = ElementTheme.colors.bgActionPrimaryDisabled,
disabledContentColor = ElementTheme.colors.textOnSolidPrimary
)
ButtonStyle.Outlined, ButtonStyle.Text -> ButtonDefaults.buttonColors(
containerColor = Color.Transparent,
contentColor = ElementTheme.materialColors.primary,
disabledContainerColor = Color.Transparent,
disabledContentColor = ElementTheme.colors.textDisabled,
)
}
val border = when (style) {
ButtonStyle.Filled, ButtonStyle.Text -> null
ButtonStyle.Outlined -> BorderStroke(
width = 1.dp,
color = ElementTheme.colors.borderInteractiveSecondary
)
}
val textStyle = when (size) {
ButtonSize.Medium -> MaterialTheme.typography.labelLarge
ButtonSize.Large -> ElementTheme.typography.fontBodyLgMedium
}
androidx.compose.material3.Button(
onClick = onClick,
modifier = modifier,
onClick = {
if (!showProgress) {
onClick()
}
},
modifier = modifier.heightIn(min = minHeight),
enabled = enabled,
shape = shape,
colors = colors,
elevation = elevation,
elevation = null,
border = border,
contentPadding = contentPadding,
interactionSource = interactionSource,
content = content,
)
contentPadding = paddingValues,
interactionSource = remember { MutableInteractionSource() },
) {
when {
showProgress -> {
CircularProgressIndicator(
modifier = Modifier
.progressSemantics()
.size(20.dp),
color = LocalContentColor.current,
strokeWidth = 2.dp,
)
}
leadingIcon != null -> {
androidx.compose.material.Icon(
painter = leadingIcon.getPainter(),
contentDescription = null,
tint = LocalContentColor.current,
modifier = Modifier.size(20.dp),
)
}
else -> Unit
}
Text(
text = text,
style = textStyle,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
modifier = Modifier.padding(horizontal = 8.dp),
)
}
}
object ElementButtonDefaults {
val ContentPadding = PaddingValues(horizontal = 24.dp, vertical = 14.dp)
val shape: Shape @Composable get() = ButtonDefaults.shape
@Composable
fun buttonElevation(): ButtonElevation = ButtonDefaults.buttonElevation()
sealed interface IconSource {
data class Resource(val id: Int) : IconSource
data class Vector(val vector: ImageVector) : IconSource
@Composable
fun buttonColors(): ButtonColors = ButtonDefaults.buttonColors()
fun getPainter(): Painter = when (this) {
is Resource -> painterResource(id)
is Vector -> rememberVectorPainter(image = vector)
}
}
enum class ButtonSize {
Medium, Large
}
private enum class ButtonStyle {
Filled, Outlined, Text
}
@Preview(group = PreviewGroup.Buttons)
@Composable
internal fun ButtonPreview() = ElementThemedPreview {
Column {
Button(onClick = {}, enabled = true) {
Text(text = "Click me! - Enabled")
}
Button(onClick = {}, enabled = false) {
Text(text = "Click me! - Disabled")
internal fun FilledButtonMediumPreview() {
ButtonCombinationPreview(
buttonStyle = ButtonStyle.Filled,
buttonSize = ButtonSize.Medium,
)
}
@Preview(group = PreviewGroup.Buttons)
@Composable
internal fun FilledButtonLargePreview() {
ButtonCombinationPreview(
buttonStyle = ButtonStyle.Filled,
buttonSize = ButtonSize.Large,
)
}
@Preview(group = PreviewGroup.Buttons)
@Composable
internal fun OutlinedButtonMediumPreview() {
ButtonCombinationPreview(
buttonStyle = ButtonStyle.Outlined,
buttonSize = ButtonSize.Medium,
)
}
@Preview(group = PreviewGroup.Buttons)
@Composable
internal fun OutlinedButtonLargePreview() {
ButtonCombinationPreview(
buttonStyle = ButtonStyle.Outlined,
buttonSize = ButtonSize.Large,
)
}
@Preview(group = PreviewGroup.Buttons)
@Composable
internal fun TextButtonMediumPreview() {
ButtonCombinationPreview(
buttonStyle = ButtonStyle.Text,
buttonSize = ButtonSize.Medium,
)
}
@Preview(group = PreviewGroup.Buttons)
@Composable
internal fun TextButtonLargePreview() {
ButtonCombinationPreview(
buttonStyle = ButtonStyle.Text,
buttonSize = ButtonSize.Large,
)
}
@Composable
private fun ButtonCombinationPreview(
buttonStyle: ButtonStyle,
buttonSize: ButtonSize,
modifier: Modifier = Modifier,
) {
ElementThemedPreview {
Column(
verticalArrangement = Arrangement.spacedBy(8.dp),
modifier = Modifier
.padding(16.dp)
.width(IntrinsicSize.Max),
) {
// Normal
ButtonRowPreview(
modifier = Modifier.then(modifier),
buttonStyle = buttonStyle,
buttonSize = buttonSize,
)
// With icon
ButtonRowPreview(
modifier = Modifier.then(modifier),
leadingIcon = IconSource.Vector(Icons.Outlined.Share),
buttonStyle = buttonStyle,
buttonSize = buttonSize,
)
// With progress
ButtonRowPreview(
modifier = Modifier.then(modifier),
showProgress = true,
buttonStyle = buttonStyle,
buttonSize = buttonSize,
)
}
}
}
@Composable
private fun ButtonRowPreview(
buttonStyle: ButtonStyle,
buttonSize: ButtonSize,
modifier: Modifier = Modifier,
leadingIcon: IconSource? = null,
showProgress: Boolean = false,
) {
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(8.dp, Alignment.CenterHorizontally)) {
ButtonInternal(
text = "A button",
showProgress = showProgress,
onClick = {},
style = buttonStyle,
size = buttonSize,
leadingIcon = leadingIcon,
modifier = Modifier.then(modifier),
)
ButtonInternal(
text = "A button",
showProgress = showProgress,
enabled = false,
onClick = {},
style = buttonStyle,
size = buttonSize,
leadingIcon = leadingIcon,
modifier = Modifier.then(modifier),
)
}
}

View file

@ -1,91 +0,0 @@
/*
* Copyright (c) 2023 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.theme.components
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.RowScope
import androidx.compose.material3.ButtonColors
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.ButtonElevation
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import io.element.android.libraries.designsystem.preview.ElementThemedPreview
import io.element.android.libraries.designsystem.preview.PreviewGroup
@Composable
fun OutlinedButton(
onClick: () -> Unit,
modifier: Modifier = Modifier,
enabled: Boolean = true,
shape: Shape = ElementOutlinedButtonDefaults.shape,
colors: ButtonColors = ElementOutlinedButtonDefaults.buttonColors(),
elevation: ButtonElevation? = ElementOutlinedButtonDefaults.buttonElevation(),
border: BorderStroke? = ElementOutlinedButtonDefaults.border,
contentPadding: PaddingValues = ElementOutlinedButtonDefaults.ContentPadding,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
content: @Composable RowScope.() -> Unit
) {
androidx.compose.material3.Button(
onClick = onClick,
modifier = modifier,
enabled = enabled,
shape = shape,
colors = colors,
elevation = elevation,
border = border,
contentPadding = contentPadding,
interactionSource = interactionSource,
content = content,
)
}
object ElementOutlinedButtonDefaults {
val ContentPadding = PaddingValues(horizontal = 24.dp, vertical = 14.dp)
val shape: Shape @Composable get() = ButtonDefaults.outlinedShape
val border: BorderStroke @Composable get() = ButtonDefaults.outlinedButtonBorder
@Composable
fun buttonElevation(): ButtonElevation = ButtonDefaults.buttonElevation()
@Composable
fun buttonColors(): ButtonColors = ButtonDefaults.outlinedButtonColors()
}
@Preview(group = PreviewGroup.Buttons)
@Composable
internal fun OutlinedButtonsPreview() = ElementThemedPreview { ContentToPreview() }
@Composable
private fun ContentToPreview() {
Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
OutlinedButton(onClick = {}, enabled = true) {
Text(text = "Click me! - Enabled")
}
OutlinedButton(onClick = {}, enabled = false) {
Text(text = "Click me! - Disabled")
}
}
}

View file

@ -1,76 +0,0 @@
/*
* Copyright (c) 2023 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.theme.components
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.RowScope
import androidx.compose.material3.ButtonColors
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.ButtonElevation
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.tooling.preview.Preview
import io.element.android.libraries.designsystem.preview.ElementThemedPreview
import io.element.android.libraries.designsystem.preview.PreviewGroup
@Composable
fun TextButton(
onClick: () -> Unit,
modifier: Modifier = Modifier,
enabled: Boolean = true,
shape: Shape = ButtonDefaults.textShape,
colors: ButtonColors = ButtonDefaults.textButtonColors(),
elevation: ButtonElevation? = null,
border: BorderStroke? = null,
contentPadding: PaddingValues = ButtonDefaults.TextButtonContentPadding,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
content: @Composable RowScope.() -> Unit
) {
androidx.compose.material3.TextButton(
onClick = onClick,
modifier = modifier,
enabled = enabled,
shape = shape,
colors = colors,
elevation = elevation,
border = border,
contentPadding = contentPadding,
interactionSource = interactionSource,
content = content,
)
}
@Preview(group = PreviewGroup.Buttons)
@Composable
internal fun TextButtonPreview() = ElementThemedPreview { ContentToPreview() }
@Composable
private fun ContentToPreview() {
Column {
TextButton(onClick = {}, enabled = true) {
Text(text = "Click me! - Enabled")
}
TextButton(onClick = {}, enabled = false) {
Text(text = "Click me! - Disabled")
}
}
}

View file

@ -32,16 +32,13 @@ import io.element.android.libraries.designsystem.theme.components.DropdownMenu
import io.element.android.libraries.designsystem.theme.components.DropdownMenuItem
import io.element.android.libraries.designsystem.theme.components.DropdownMenuItemText
import io.element.android.libraries.designsystem.theme.components.Icon
import io.element.android.libraries.designsystem.theme.components.Text
@Preview(group = PreviewGroup.Menus)
@Composable
internal fun MenuPreview() {
ElementThemedPreview {
var isExpanded by remember { mutableStateOf(false) }
Button(onClick = { isExpanded = !isExpanded }) {
Text("Toggle")
}
Button(text = "Toggle", onClick = { isExpanded = !isExpanded })
DropdownMenu(expanded = isExpanded, onDismissRequest = { isExpanded = false }) {
for (i in 0..5) {
val leadingIcon: @Composable (() -> Unit)? = if (i in 2..3) {