From dff29fa270d0322a32a6e6cd0a417b1e7532966e Mon Sep 17 00:00:00 2001 From: ganfra Date: Tue, 14 Jan 2025 16:12:43 +0100 Subject: [PATCH] change(design) : introduce Announcement component --- .../designsystem/components/Announcement.kt | 208 ++++++++++++++++++ 1 file changed, 208 insertions(+) create mode 100644 libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/Announcement.kt diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/Announcement.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/Announcement.kt new file mode 100644 index 0000000000..e47570981f --- /dev/null +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/Announcement.kt @@ -0,0 +1,208 @@ +/* + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * Please see LICENSE files in the repository root for full details. + */ + +package io.element.android.libraries.designsystem.components + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement.spacedBy +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.runtime.Composable +import androidx.compose.runtime.Immutable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import io.element.android.compound.theme.ElementTheme +import io.element.android.compound.tokens.generated.CompoundIcons +import io.element.android.libraries.designsystem.preview.ElementPreview +import io.element.android.libraries.designsystem.preview.PreviewsDayNight +import io.element.android.libraries.designsystem.theme.components.Button +import io.element.android.libraries.designsystem.theme.components.ButtonSize +import io.element.android.libraries.designsystem.theme.components.Icon +import io.element.android.libraries.designsystem.theme.components.Surface +import io.element.android.libraries.designsystem.theme.components.Text +import io.element.android.libraries.ui.strings.CommonStrings + +@Composable +fun Announcement( + title: String, + description: String?, + type: AnnouncementType, + modifier: Modifier = Modifier, +) { + when (type) { + is AnnouncementType.Informative -> InformativeAnnouncement( + title = title, + description = description, + isError = type.isError, + modifier = modifier, + ) + is AnnouncementType.Actionable -> ActionableAnnouncement( + title = title, + description = description, + actionText = type.actionText, + onActionClick = type.onActionClick, + onDismissClick = type.onDismissClick, + modifier = modifier, + ) + } +} + +@Immutable +sealed interface AnnouncementType { + data class Informative(val isError: Boolean = false) : AnnouncementType + data class Actionable( + val actionText: String, + val onActionClick: () -> Unit, + val onDismissClick: (() -> Unit)?, + ) : AnnouncementType +} + +@Composable +private fun ActionableAnnouncement( + title: String, + description: String?, + actionText: String, + onActionClick: () -> Unit, + onDismissClick: (() -> Unit)?, + modifier: Modifier = Modifier, +) { + AnnouncementSurface(modifier) { + Column { + TitleAndDescription( + title = title, + description = description, + trailingContent = onDismissClick?.let { + { + Icon( + modifier = Modifier.clickable(onClick = onDismissClick), + imageVector = CompoundIcons.Close(), + contentDescription = stringResource(CommonStrings.action_close) + ) + } + } + ) + Spacer(Modifier.height(16.dp)) + Button( + text = actionText, + size = ButtonSize.Medium, + onClick = onActionClick, + modifier = Modifier.fillMaxWidth(), + ) + } + } +} + +@Composable +private fun InformativeAnnouncement( + title: String, + description: String?, + isError: Boolean, + modifier: Modifier = Modifier, +) { + AnnouncementSurface(modifier = modifier) { + Row { + Icon( + imageVector = if (isError) CompoundIcons.Error() else CompoundIcons.Info(), + tint = if (isError) ElementTheme.colors.iconCriticalPrimary else ElementTheme.colors.iconPrimary, + contentDescription = null, + ) + Spacer(Modifier.width(12.dp)) + TitleAndDescription( + title = title, + description = description, + titleColor = if (isError) ElementTheme.colors.textCriticalPrimary else ElementTheme.colors.textPrimary, + ) + } + } +} + +@Composable +private fun TitleAndDescription( + title: String, + description: String?, + modifier: Modifier = Modifier, + titleColor: Color = ElementTheme.colors.textPrimary, + descriptionColor: Color = ElementTheme.colors.textSecondary, + trailingContent: (@Composable () -> Unit)? = null, +) { + Column(modifier = modifier) { + Row { + Text( + text = title, + style = ElementTheme.typography.fontBodyLgMedium, + color = titleColor, + modifier = Modifier.weight(1f), + ) + if (trailingContent != null) { + Spacer(Modifier.width(12.dp)) + trailingContent() + } + } + if (description != null) { + Spacer(Modifier.height(4.dp)) + Text( + text = description, + style = ElementTheme.typography.fontBodyMdRegular, + color = descriptionColor, + ) + } + } +} + +@Composable +private fun AnnouncementSurface( + modifier: Modifier = Modifier, + content: @Composable () -> Unit, +) { + Surface( + modifier = modifier.fillMaxWidth(), + shape = RoundedCornerShape(size = 12.dp), + color = ElementTheme.colors.bgSubtleSecondary + ) { + Box(modifier = Modifier.padding(16.dp)) { + content() + } + } +} + +@PreviewsDayNight +@Composable +internal fun AnnouncementPreview() = ElementPreview { + Column( + verticalArrangement = spacedBy(16.dp), + modifier = Modifier.padding(16.dp) + ) { + Announcement( + title = "Headline", + description = "Text description goes here.", + type = AnnouncementType.Informative(isError = false), + ) + Announcement( + title = "Headline", + description = "Text description goes here.", + type = AnnouncementType.Informative(isError = true), + ) + Announcement( + title = "Headline", + description = "Text description goes here.", + type = AnnouncementType.Actionable( + actionText = "Label", + onActionClick = {}, + onDismissClick = {}, + ), + ) + } +}