From 1853aed90304ffe6bc3f63201592c279b9ac153c Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 22 Jul 2026 15:08:50 +0300 Subject: [PATCH] Updated on 2026-08-14 --- .../ds2/messagebanner/TangemMessageBanner.kt | 77 +++++++++++++++---- .../storybook/entity/StoryBookPage.kt | 2 + .../storybook/page/ds/messagebanner/Build.kt | 2 + .../messagebanner/TangemMessageBannerStory.kt | 10 +++ 4 files changed, 78 insertions(+), 13 deletions(-) diff --git a/core/ui/src/main/java/com/tangem/core/ui/ds2/messagebanner/TangemMessageBanner.kt b/core/ui/src/main/java/com/tangem/core/ui/ds2/messagebanner/TangemMessageBanner.kt index b186ea78cd..145f3188c6 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/ds2/messagebanner/TangemMessageBanner.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/ds2/messagebanner/TangemMessageBanner.kt @@ -12,9 +12,13 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.ripple.RippleAlpha import androidx.compose.material3.Icon +import androidx.compose.material3.LocalRippleConfiguration +import androidx.compose.material3.RippleConfiguration import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.Immutable import androidx.compose.runtime.ReadOnlyComposable import androidx.compose.ui.Alignment @@ -35,6 +39,7 @@ import com.tangem.core.ui.ds2.glowring.TangemGlowRing import com.tangem.core.ui.ds2.surface.TangemSurface import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.clickableSingle +import com.tangem.core.ui.extensions.conditionalCompose import com.tangem.core.ui.extensions.resolveAnnotatedReference import com.tangem.core.ui.extensions.stringReference import com.tangem.core.ui.res.TangemTheme @@ -46,11 +51,15 @@ import com.tangem.core.ui.res.generated.icons.ic_cross_circle_20_filled * Design-system v2 (DS3) **Message Banner** — low-level slot API: a [content] block above an * optional action-button row. For the common title/description layout, prefer the `title` overload. * + * Version: 1.2 + * * [Figma](https://www.figma.com/design/AsnJ5CPHib4Qxw12gszjMS/%F0%9F%92%A0-DS-Components?node-id=5475-7680&m=dev) * * @param variant Visual appearance — background color + glow ring. * @param showGlowRing Whether the glow ring is drawn around the banner. `false` shows only the * background. + * @param onClick Makes the whole banner clickable. Honored only when no action buttons are present — ignored while + * [secondaryButton] or [primaryButton] is set. * @param secondaryButton Start action. `null` hides it. * @param primaryButton End action. `null` hides it. * @param content The banner body above the buttons. @@ -60,26 +69,33 @@ fun TangemMessageBanner( modifier: Modifier = Modifier, variant: TangemMessageBanner.Variant = TangemMessageBanner.Variant.Default, showGlowRing: Boolean = true, + onClick: (() -> Unit)? = null, secondaryButton: TangemMessageBanner.Button? = null, primaryButton: TangemMessageBanner.Button? = null, content: @Composable ColumnScope.() -> Unit, ) { val tokens = variant.tokens() + val isClickable = onClick != null && secondaryButton == null && primaryButton == null Box(modifier = modifier) { - TangemSurface( - modifier = Modifier.fillMaxWidth(), - color = tokens.background, - shape = RoundedCornerShape(28.dp), - ) { - Column( - modifier = Modifier - .fillMaxWidth() - .padding(16.dp), - verticalArrangement = Arrangement.spacedBy(24.dp), + WithMessageBannerRipple(enabled = isClickable) { + TangemSurface( + modifier = Modifier.fillMaxWidth(), + color = tokens.background, + shape = RoundedCornerShape(28.dp), ) { - content() - MessageBannerButtons(secondaryButton = secondaryButton, primaryButton = primaryButton) + Column( + modifier = Modifier + .fillMaxWidth() + .conditionalCompose(isClickable) { + clickableSingle(role = Role.Button) { onClick?.invoke() } + } + .padding(16.dp), + verticalArrangement = Arrangement.spacedBy(16.dp), + ) { + content() + MessageBannerButtons(secondaryButton = secondaryButton, primaryButton = primaryButton) + } } } if (showGlowRing) { @@ -96,6 +112,8 @@ fun TangemMessageBanner( * Design-system v2 (DS3) **Message Banner** — title/description header with optional slots and an * action-button row. * + * Version: 1.2 + * * [Figma](https://www.figma.com/design/AsnJ5CPHib4Qxw12gszjMS/%F0%9F%92%A0-DS-Components?node-id=5475-7680&m=dev) * * @param title Banner headline. @@ -103,6 +121,8 @@ fun TangemMessageBanner( * @param contentAlign Horizontal alignment of the text block. * @param showGlowRing Whether the glow ring is drawn around the banner. `false` shows only the * background. + * @param onClick Makes the whole banner clickable. Honored only when no action buttons are present — ignored while + * [secondaryButton] or [primaryButton] is set. * @param description Secondary line under the [title]. `null` hides it. * @param secondaryButton Start action. `null` hides it. * @param primaryButton End action. `null` hides it. @@ -118,6 +138,7 @@ fun TangemMessageBanner( variant: TangemMessageBanner.Variant = TangemMessageBanner.Variant.Default, contentAlign: TangemMessageBanner.ContentAlign = TangemMessageBanner.ContentAlign.Start, showGlowRing: Boolean = true, + onClick: (() -> Unit)? = null, description: TextReference? = null, secondaryButton: TangemMessageBanner.Button? = null, primaryButton: TangemMessageBanner.Button? = null, @@ -129,6 +150,7 @@ fun TangemMessageBanner( modifier = modifier, variant = variant, showGlowRing = showGlowRing, + onClick = onClick, secondaryButton = secondaryButton, primaryButton = primaryButton, ) { @@ -211,7 +233,7 @@ private fun MessageBannerTextWrapper( ) } extraBottomSlot?.let { slot -> - Column(modifier = Modifier.padding(top = 12.dp)) { slot() } + Column(modifier = Modifier.padding(top = 8.dp)) { slot() } } } } @@ -321,6 +343,29 @@ fun TangemMessageBanner.CloseButton( ) } +/** Overrides the ripple for a clickable banner; pass-through when [enabled] is `false`. */ +@Composable +private fun WithMessageBannerRipple(enabled: Boolean, content: @Composable () -> Unit) { + if (enabled) { + CompositionLocalProvider(LocalRippleConfiguration provides messageBannerRipple(), content = content) + } else { + content() + } +} + +/** Press ripple of a clickable banner — the `color/interaction/press/static-light` token. */ +@Composable +@ReadOnlyComposable +private fun messageBannerRipple(): RippleConfiguration = RippleConfiguration( + color = TangemTheme.colors3.interaction.press.staticLight, + rippleAlpha = RippleAlpha( + draggedAlpha = 0f, + focusedAlpha = 0f, + hoveredAlpha = 0.05f, + pressedAlpha = 0.1f, + ), +) + /** Resolved appearance tokens for a [TangemMessageBanner.Variant]. */ private data class MessageBannerTokens(val background: Color, val glowRing: TangemGlowRing.Variant) @@ -383,6 +428,12 @@ private fun TangemMessageBannerPreview() { }, primaryButton = TangemMessageBanner.Button(text = stringReference("Invite friends"), onClick = {}), ) + TangemMessageBanner( + modifier = Modifier.fillMaxWidth(), + title = stringReference("Clickable banner"), + description = stringReference("Whole banner is tappable when no buttons are set."), + onClick = {}, + ) } } diff --git a/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/storybook/entity/StoryBookPage.kt b/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/storybook/entity/StoryBookPage.kt index c2cb7afe7d..b7adb1f6e4 100644 --- a/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/storybook/entity/StoryBookPage.kt +++ b/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/storybook/entity/StoryBookPage.kt @@ -384,6 +384,7 @@ internal data class TangemMessageBannerStory( val hasSlotStart: Boolean, val hasSlotEnd: Boolean, val hasExtraContent: Boolean, + val isClickable: Boolean, val background: Background, val onVariantChange: (TangemMessageBanner.Variant) -> Unit, val onContentAlignChange: (TangemMessageBanner.ContentAlign) -> Unit, @@ -395,6 +396,7 @@ internal data class TangemMessageBannerStory( val onSlotStartToggle: () -> Unit, val onSlotEndToggle: () -> Unit, val onExtraContentToggle: () -> Unit, + val onClickableToggle: () -> Unit, val onBackgroundChange: (Background) -> Unit, ) : DsStoryBookPage { diff --git a/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/storybook/page/ds/messagebanner/Build.kt b/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/storybook/page/ds/messagebanner/Build.kt index 172cac1142..d2f78444b7 100644 --- a/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/storybook/page/ds/messagebanner/Build.kt +++ b/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/storybook/page/ds/messagebanner/Build.kt @@ -18,6 +18,7 @@ internal fun StateUpdater.build(): TangemMessageBanner hasSlotStart = true, hasSlotEnd = true, hasExtraContent = true, + isClickable = false, background = Background.BgSecondary, onVariantChange = { variant -> updateStory { it.copy(variant = variant) } }, onContentAlignChange = { align -> updateStory { it.copy(contentAlign = align) } }, @@ -29,6 +30,7 @@ internal fun StateUpdater.build(): TangemMessageBanner onSlotStartToggle = { updateStory { it.copy(hasSlotStart = !it.hasSlotStart) } }, onSlotEndToggle = { updateStory { it.copy(hasSlotEnd = !it.hasSlotEnd) } }, onExtraContentToggle = { updateStory { it.copy(hasExtraContent = !it.hasExtraContent) } }, + onClickableToggle = { updateStory { it.copy(isClickable = !it.isClickable) } }, onBackgroundChange = { background -> updateStory { it.copy(background = background) } }, ) } diff --git a/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/storybook/page/ds/messagebanner/TangemMessageBannerStory.kt b/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/storybook/page/ds/messagebanner/TangemMessageBannerStory.kt index 67990c2159..aa3d7dfdb3 100644 --- a/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/storybook/page/ds/messagebanner/TangemMessageBannerStory.kt +++ b/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/storybook/page/ds/messagebanner/TangemMessageBannerStory.kt @@ -95,6 +95,11 @@ private fun PreviewBanner(state: TangemMessageBannerStory) { variant = state.variant, contentAlign = state.contentAlign, showGlowRing = state.hasGlowRing, + onClick = if (state.isClickable) { + {} + } else { + null + }, title = stringReference("Would you predict?"), description = if (state.hasDescription) { stringReference("France will win FIFA 2026") @@ -233,6 +238,11 @@ private fun Toggles(state: TangemMessageBannerStory) { ToggleRow(label = "slotStart", checked = state.hasSlotStart, onToggle = state.onSlotStartToggle) ToggleRow(label = "slotEnd", checked = state.hasSlotEnd, onToggle = state.onSlotEndToggle) ToggleRow(label = "extraContent", checked = state.hasExtraContent, onToggle = state.onExtraContentToggle) + ToggleRow( + label = "clickable (no buttons only)", + checked = state.isClickable, + onToggle = state.onClickableToggle, + ) } } }