change(design) : introduce Announcement component
This commit is contained in:
parent
9946bf29fd
commit
69c555ae55
1 changed files with 208 additions and 0 deletions
|
|
@ -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 = {},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue