[Compound] Implement dialogs (#1043)

* Implement dialogs following Compound tokens

* Update screenshots

* Fix confirmation dialog preview

* Update screenshots

* Add changelog

* Add Figma designs link

---------

Co-authored-by: ElementBot <benoitm+elementbot@element.io>
This commit is contained in:
Jorge Martin Espinosa
2023-08-11 14:44:40 +02:00
committed by GitHub
parent 0773e99216
commit 37a4d49494
134 changed files with 399 additions and 382 deletions

1
changelog.d/1043.misc Normal file
View File

@@ -0,0 +1 @@
Compound: implement dialogs.

View File

@@ -35,7 +35,7 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog
import androidx.compose.ui.window.DialogProperties
import io.element.android.libraries.designsystem.components.dialogs.DialogPreview
import io.element.android.libraries.designsystem.theme.components.DialogPreview
import io.element.android.libraries.designsystem.preview.ElementThemedPreview
import io.element.android.libraries.designsystem.preview.PreviewGroup
import io.element.android.libraries.designsystem.theme.components.CircularProgressIndicator

View File

@@ -17,18 +17,15 @@
package io.element.android.libraries.designsystem.components.dialogs
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.AlertDialogDefaults
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
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.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.DialogPreview
import io.element.android.libraries.designsystem.theme.components.SimpleAlertDialogContent
import io.element.android.libraries.ui.strings.CommonStrings
@OptIn(ExperimentalMaterial3Api::class)
@@ -44,13 +41,6 @@ fun ConfirmationDialog(
thirdButtonText: String? = null,
onCancelClicked: () -> Unit = onDismiss,
onThirdButtonClicked: () -> Unit = {},
shape: Shape = AlertDialogDefaults.shape,
containerColor: Color = AlertDialogDefaults.containerColor,
iconContentColor: Color = AlertDialogDefaults.iconContentColor,
// According to the design team, `primary` should be used here instead of the default `onSurface`
titleContentColor: Color = MaterialTheme.colorScheme.primary,
textContentColor: Color = AlertDialogDefaults.textContentColor,
tonalElevation: Dp = AlertDialogDefaults.TonalElevation,
) {
AlertDialog(modifier = modifier, onDismissRequest = onDismiss) {
ConfirmationDialogContent(
@@ -62,12 +52,6 @@ fun ConfirmationDialog(
onSubmitClicked = onSubmitClicked,
onCancelClicked = onCancelClicked,
onThirdButtonClicked = onThirdButtonClicked,
shape = shape,
containerColor = containerColor,
iconContentColor = iconContentColor,
titleContentColor = titleContentColor,
textContentColor = textContentColor,
tonalElevation = tonalElevation,
)
}
}
@@ -83,12 +67,6 @@ private fun ConfirmationDialogContent(
title: String? = null,
thirdButtonText: String? = null,
onThirdButtonClicked: () -> Unit = {},
shape: Shape = AlertDialogDefaults.shape,
containerColor: Color = AlertDialogDefaults.containerColor,
iconContentColor: Color = AlertDialogDefaults.iconContentColor,
titleContentColor: Color = AlertDialogDefaults.titleContentColor,
textContentColor: Color = AlertDialogDefaults.textContentColor,
tonalElevation: Dp = AlertDialogDefaults.TonalElevation,
icon: @Composable (() -> Unit)? = null,
) {
SimpleAlertDialogContent(
@@ -101,12 +79,6 @@ private fun ConfirmationDialogContent(
onCancelClicked = onCancelClicked,
thirdButtonText = thirdButtonText,
onThirdButtonClicked = onThirdButtonClicked,
shape = shape,
containerColor = containerColor,
iconContentColor = iconContentColor,
titleContentColor = titleContentColor,
textContentColor = textContentColor,
tonalElevation = tonalElevation,
icon = icon,
)
}
@@ -114,7 +86,7 @@ private fun ConfirmationDialogContent(
@Preview(group = PreviewGroup.Dialogs)
@Composable
internal fun ConfirmationDialogPreview() =
ElementThemedPreview {
ElementThemedPreview(showBackground = false) {
DialogPreview {
ConfirmationDialogContent(
content = "Content",
@@ -124,6 +96,7 @@ internal fun ConfirmationDialogPreview() =
thirdButtonText = "Disable",
onSubmitClicked = {},
onCancelClicked = {},
onThirdButtonClicked = {},
)
}
}

View File

@@ -17,17 +17,15 @@
package io.element.android.libraries.designsystem.components.dialogs
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.AlertDialogDefaults
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
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.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.DialogPreview
import io.element.android.libraries.designsystem.theme.components.SimpleAlertDialogContent
import io.element.android.libraries.ui.strings.CommonStrings
@OptIn(ExperimentalMaterial3Api::class)
@@ -38,12 +36,6 @@ fun ErrorDialog(
title: String = ErrorDialogDefaults.title,
submitText: String = ErrorDialogDefaults.submitText,
onDismiss: () -> Unit = {},
shape: Shape = AlertDialogDefaults.shape,
containerColor: Color = AlertDialogDefaults.containerColor,
iconContentColor: Color = AlertDialogDefaults.iconContentColor,
titleContentColor: Color = AlertDialogDefaults.titleContentColor,
textContentColor: Color = AlertDialogDefaults.textContentColor,
tonalElevation: Dp = AlertDialogDefaults.TonalElevation,
) {
AlertDialog(modifier = modifier, onDismissRequest = onDismiss) {
ErrorDialogContent(
@@ -51,12 +43,6 @@ fun ErrorDialog(
content = content,
submitText = submitText,
onSubmitText = onDismiss,
shape = shape,
containerColor = containerColor,
iconContentColor = iconContentColor,
titleContentColor = titleContentColor,
textContentColor = textContentColor,
tonalElevation = tonalElevation,
)
}
}
@@ -68,12 +54,6 @@ private fun ErrorDialogContent(
title: String = ErrorDialogDefaults.title,
submitText: String = ErrorDialogDefaults.submitText,
onSubmitText: () -> Unit = {},
shape: Shape = AlertDialogDefaults.shape,
containerColor: Color = AlertDialogDefaults.containerColor,
iconContentColor: Color = AlertDialogDefaults.iconContentColor,
titleContentColor: Color = AlertDialogDefaults.titleContentColor,
textContentColor: Color = AlertDialogDefaults.textContentColor,
tonalElevation: Dp = AlertDialogDefaults.TonalElevation,
) {
SimpleAlertDialogContent(
modifier = modifier,
@@ -81,12 +61,6 @@ private fun ErrorDialogContent(
content = content,
cancelText = submitText,
onCancelClicked = onSubmitText,
shape = shape,
containerColor = containerColor,
iconContentColor = iconContentColor,
titleContentColor = titleContentColor,
textContentColor = textContentColor,
tonalElevation = tonalElevation,
)
}
@@ -98,7 +72,7 @@ object ErrorDialogDefaults {
@Preview(group = PreviewGroup.Dialogs)
@Composable
internal fun ErrorDialogPreview() {
ElementThemedPreview {
ElementThemedPreview(showBackground = false) {
DialogPreview {
ErrorDialogContent(
content = "Content",

View File

@@ -17,17 +17,15 @@
package io.element.android.libraries.designsystem.components.dialogs
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.AlertDialogDefaults
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
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.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.DialogPreview
import io.element.android.libraries.designsystem.theme.components.SimpleAlertDialogContent
import io.element.android.libraries.ui.strings.CommonStrings
@OptIn(ExperimentalMaterial3Api::class)
@@ -40,12 +38,6 @@ fun RetryDialog(
dismissText: String = RetryDialogDefaults.dismissText,
onRetry: () -> Unit = {},
onDismiss: () -> Unit = {},
shape: Shape = AlertDialogDefaults.shape,
containerColor: Color = AlertDialogDefaults.containerColor,
iconContentColor: Color = AlertDialogDefaults.iconContentColor,
titleContentColor: Color = AlertDialogDefaults.titleContentColor,
textContentColor: Color = AlertDialogDefaults.textContentColor,
tonalElevation: Dp = AlertDialogDefaults.TonalElevation,
) {
AlertDialog(modifier = modifier, onDismissRequest = onDismiss) {
RetryDialogContent(
@@ -55,12 +47,6 @@ fun RetryDialog(
dismissText = dismissText,
onRetry = onRetry,
onDismiss = onDismiss,
shape = shape,
containerColor = containerColor,
iconContentColor = iconContentColor,
titleContentColor = titleContentColor,
textContentColor = textContentColor,
tonalElevation = tonalElevation,
)
}
}
@@ -74,12 +60,6 @@ private fun RetryDialogContent(
dismissText: String = RetryDialogDefaults.dismissText,
onRetry: () -> Unit = {},
onDismiss: () -> Unit = {},
shape: Shape = AlertDialogDefaults.shape,
containerColor: Color = AlertDialogDefaults.containerColor,
iconContentColor: Color = AlertDialogDefaults.iconContentColor,
titleContentColor: Color = AlertDialogDefaults.titleContentColor,
textContentColor: Color = AlertDialogDefaults.textContentColor,
tonalElevation: Dp = AlertDialogDefaults.TonalElevation,
) {
SimpleAlertDialogContent(
modifier = modifier,
@@ -89,12 +69,6 @@ private fun RetryDialogContent(
onSubmitClicked = onRetry,
cancelText = dismissText,
onCancelClicked = onDismiss,
shape = shape,
containerColor = containerColor,
iconContentColor = iconContentColor,
titleContentColor = titleContentColor,
textContentColor = textContentColor,
tonalElevation = tonalElevation,
)
}
@@ -106,13 +80,12 @@ object RetryDialogDefaults {
@Preview(group = PreviewGroup.Dialogs)
@Composable
internal fun RetryDialogPreview() = ElementThemedPreview { ContentToPreview() }
@Composable
private fun ContentToPreview() {
DialogPreview {
RetryDialogContent(
content = "Content",
)
internal fun RetryDialogPreview() {
ElementThemedPreview(showBackground = false) {
DialogPreview {
RetryDialogContent(
content = "Content",
)
}
}
}

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package io.element.android.libraries.designsystem.components.dialogs
package io.element.android.libraries.designsystem.theme.components
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
@@ -22,28 +22,32 @@ import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.sizeIn
import androidx.compose.material3.AlertDialogDefaults
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Notifications
import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.ProvideTextStyle
import androidx.compose.material3.Surface
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.ReadOnlyComposable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.layout.Layout
import androidx.compose.ui.layout.Placeable
import androidx.compose.ui.tooling.preview.Preview
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.designsystem.preview.ElementThemedPreview
import io.element.android.libraries.designsystem.preview.PreviewGroup
import io.element.android.libraries.theme.ElementTheme
import kotlin.math.max
// Figma designs: https://www.figma.com/file/G1xy0HDZKJf5TCRFmKb5d5/Compound-Android-Components?type=design&node-id=911%3A343492&mode=design&t=jeyd1bXKOOx8y10r-1
@Composable
internal fun SimpleAlertDialogContent(
content: String,
@@ -55,12 +59,6 @@ internal fun SimpleAlertDialogContent(
onSubmitClicked: () -> Unit = {},
thirdButtonText: String? = null,
onThirdButtonClicked: () -> Unit = {},
shape: Shape = AlertDialogDefaults.shape,
containerColor: Color = AlertDialogDefaults.containerColor,
iconContentColor: Color = AlertDialogDefaults.iconContentColor,
titleContentColor: Color = AlertDialogDefaults.titleContentColor,
textContentColor: Color = AlertDialogDefaults.textContentColor,
tonalElevation: Dp = AlertDialogDefaults.TonalElevation,
icon: @Composable (() -> Unit)? = null,
) {
AlertDialogContent(
@@ -93,26 +91,26 @@ internal fun SimpleAlertDialogContent(
}
},
modifier = modifier,
title = {
if (title != null) {
title = title?.let { titleText ->
@Composable {
Text(
text = title,
style = ElementTheme.typography.fontHeadingSmRegular,
text = titleText,
style = ElementTheme.typography.fontHeadingSmMedium,
)
}
},
text = {
Text(
text = content,
style = ElementTheme.typography.fontBodyMdRegular,
style = ElementTheme.materialTypography.bodyMedium,
)
},
shape = shape,
containerColor = containerColor,
iconContentColor = iconContentColor,
titleContentColor = titleContentColor,
textContentColor = textContentColor,
tonalElevation = tonalElevation,
shape = DialogContentDefaults.shape,
containerColor = DialogContentDefaults.containerColor,
iconContentColor = DialogContentDefaults.iconContentColor,
titleContentColor = DialogContentDefaults.titleContentColor,
textContentColor = DialogContentDefaults.textContentColor,
tonalElevation = 0.dp,
icon = icon,
// Note that a button content color is provided here from the dialog's token, but in
// most cases, TextButtons should be used for dismiss and confirm buttons.
@@ -122,6 +120,9 @@ internal fun SimpleAlertDialogContent(
)
}
/**
* Copy of M3's `AlertDialogContent` so we can use it for previews.
*/
@Composable
internal fun AlertDialogContent(
buttons: @Composable () -> Unit,
@@ -144,13 +145,13 @@ internal fun AlertDialogContent(
tonalElevation = tonalElevation,
) {
Column(
modifier = Modifier.padding(DialogPadding)
modifier = Modifier.padding(DialogContentDefaults.externalPadding)
) {
icon?.let {
CompositionLocalProvider(LocalContentColor provides iconContentColor) {
Box(
Modifier
.padding(IconPadding)
.padding(DialogContentDefaults.iconPadding)
.align(Alignment.CenterHorizontally)
) {
icon()
@@ -164,7 +165,7 @@ internal fun AlertDialogContent(
Box(
// Align the title to the center when an icon is present.
Modifier
.padding(TitlePadding)
.padding(DialogContentDefaults.titlePadding)
.align(
if (icon == null) {
Alignment.Start
@@ -186,7 +187,7 @@ internal fun AlertDialogContent(
Box(
Modifier
.weight(weight = 1f, fill = false)
.padding(TextPadding)
.padding(DialogContentDefaults.textPadding)
.align(Alignment.Start)
) {
text()
@@ -210,7 +211,7 @@ internal fun AlertDialogContent(
* customization.
*/
@Composable
internal fun AlertDialogFlowRow(
private fun AlertDialogFlowRow(
mainAxisSpacing: Dp,
crossAxisSpacing: Dp,
content: @Composable () -> Unit
@@ -237,7 +238,8 @@ internal fun AlertDialogFlowRow(
if (sequences.isNotEmpty()) {
crossAxisSpace += crossAxisSpacing.roundToPx()
}
sequences += currentSequence.toList()
// Ensures that confirming actions appear above dismissive actions.
sequences.add(0, currentSequence.toList())
crossAxisSizes += currentCrossAxisSize
crossAxisPositions += crossAxisSpace
@@ -281,12 +283,11 @@ internal fun AlertDialogFlowRow(
placeables[j].width +
if (j < placeables.lastIndex) mainAxisSpacing.roundToPx() else 0
}
val arrangement = Arrangement.Bottom
// TODO(soboleva): rtl support
// Handle vertical direction
val arrangement = Arrangement.End
val mainAxisPositions = IntArray(childrenMainAxisSizes.size) { 0 }
with(arrangement) {
arrange(mainAxisLayoutSize, childrenMainAxisSizes, mainAxisPositions)
arrange(mainAxisLayoutSize, childrenMainAxisSizes,
layoutDirection, mainAxisPositions)
}
placeables.forEachIndexed { j, placeable ->
placeable.place(
@@ -311,14 +312,87 @@ internal fun DialogPreview(content: @Composable () -> Unit) {
}
}
// Paddings for each of the dialog's parts.
private val DialogPadding = PaddingValues(all = 24.dp)
private val IconPadding = PaddingValues(bottom = 16.dp)
private val TitlePadding = PaddingValues(bottom = 16.dp)
private val TextPadding = PaddingValues(bottom = 24.dp)
internal object DialogContentDefaults {
val shape = RoundedCornerShape(12.dp)
val externalPadding = PaddingValues(all = 24.dp)
val titlePadding = PaddingValues(bottom = 16.dp)
val iconPadding = PaddingValues(bottom = 8.dp)
val textPadding = PaddingValues(bottom = 16.dp)
val containerColor: Color
@Composable
@ReadOnlyComposable
get()= ElementTheme.colors.bgCanvasDefault
val textContentColor: Color
@Composable
@ReadOnlyComposable
get()= ElementTheme.materialColors.onSurfaceVariant
val titleContentColor: Color
@Composable
@ReadOnlyComposable
get()= ElementTheme.materialColors.onSurface
val iconContentColor: Color
@Composable
@ReadOnlyComposable
get()= ElementTheme.materialColors.primary
}
// Paddings for each of the dialog's parts. Taken from M3 source code.
internal val ButtonsMainAxisSpacing = 8.dp
internal val ButtonsCrossAxisSpacing = 12.dp
internal val DialogMinWidth = 280.dp
internal val DialogMaxWidth = 560.dp
@Preview(group = PreviewGroup.Dialogs, name = "Dialog with title, icon and ok button")
@Composable
@Suppress("MaxLineLength")
internal fun DialogWithTitleIconAndOkButtonPreview() {
ElementThemedPreview(showBackground = false) {
DialogPreview {
SimpleAlertDialogContent(
icon = {
Icon(imageVector = Icons.Default.Notifications, contentDescription = null)
},
title = "Dialog Title",
content = "A dialog is a type of modal window that appears in front of app content to provide critical information, or prompt for a decision to be made. Learn more",
cancelText = "OK",
onCancelClicked = {},
)
}
}
}
@Preview(group = PreviewGroup.Dialogs, name = "Dialog with title and ok button")
@Composable
@Suppress("MaxLineLength")
internal fun DialogWithTitleAndOkButtonPreview() {
ElementThemedPreview(showBackground = false) {
DialogPreview {
SimpleAlertDialogContent(
title = "Dialog Title",
content = "A dialog is a type of modal window that appears in front of app content to provide critical information, or prompt for a decision to be made. Learn more",
cancelText = "OK",
onCancelClicked = {},
)
}
}
}
@Preview(group = PreviewGroup.Dialogs, name = "Dialog with only message and ok button")
@Composable
@Suppress("MaxLineLength")
internal fun DialogWithOnlyMessageAndOkButtonPreview() {
ElementThemedPreview(showBackground = false) {
DialogPreview {
SimpleAlertDialogContent(
content = "A dialog is a type of modal window that appears in front of app content to provide critical information, or prompt for a decision to be made. Learn more",
cancelText = "OK",
onCancelClicked = {},
)
}
}
}

View File

@@ -103,7 +103,7 @@ private fun ButtonInternal(
ButtonSize.Large -> 48.dp
}
val paddingValues = when (size) {
val contentPadding = when (size) {
ButtonSize.Medium -> {
when (style) {
ButtonStyle.Text -> PaddingValues(horizontal = 12.dp, vertical = 10.dp)
@@ -151,6 +151,11 @@ private fun ButtonInternal(
ButtonSize.Large -> ElementTheme.typography.fontBodyLgMedium
}
val internalPadding = when {
style == ButtonStyle.Text -> if (leadingIcon != null) PaddingValues(start = 8.dp) else PaddingValues(0.dp)
else -> PaddingValues(horizontal = 8.dp)
}
androidx.compose.material3.Button(
onClick = {
if (!showProgress) {
@@ -163,7 +168,7 @@ private fun ButtonInternal(
colors = colors,
elevation = null,
border = border,
contentPadding = paddingValues,
contentPadding = contentPadding,
interactionSource = remember { MutableInteractionSource() },
) {
when {
@@ -191,7 +196,7 @@ private fun ButtonInternal(
style = textStyle,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
modifier = Modifier.padding(horizontal = 8.dp),
modifier = Modifier.padding(internalPadding),
)
}
}

View File

@@ -23,7 +23,7 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.rememberDatePickerState
import androidx.compose.runtime.Composable
import androidx.compose.ui.tooling.preview.Preview
import io.element.android.libraries.designsystem.components.dialogs.AlertDialogContent
import io.element.android.libraries.designsystem.theme.components.AlertDialogContent
import io.element.android.libraries.designsystem.preview.ElementPreviewDark
import io.element.android.libraries.designsystem.preview.ElementPreviewLight
import io.element.android.libraries.designsystem.preview.PreviewGroup

View File

@@ -24,7 +24,7 @@ import androidx.compose.material3.TimePickerLayoutType
import androidx.compose.material3.rememberTimePickerState
import androidx.compose.runtime.Composable
import androidx.compose.ui.tooling.preview.Preview
import io.element.android.libraries.designsystem.components.dialogs.AlertDialogContent
import io.element.android.libraries.designsystem.theme.components.AlertDialogContent
import io.element.android.libraries.designsystem.preview.ElementPreviewDark
import io.element.android.libraries.designsystem.preview.ElementPreviewLight
import io.element.android.libraries.designsystem.preview.ElementThemedPreview

View File

@@ -68,6 +68,14 @@ object ElementTheme {
*/
val typography: TypographyTokens = TypographyTokens
/**
* Material 3 [Typography] tokens. In Figma, these have the `M3 Typography/` prefix.
*/
val materialTypography: Typography
@Composable
@ReadOnlyComposable
get()= MaterialTheme.typography
/**
* Returns whether the theme version used is the light or the dark one.
*/

Some files were not shown because too many files have changed in this diff Show More