Updated on 2026-08-14

This commit is contained in:
Tangem 2026-05-21 14:12:01 +02:00
parent 4ae70b5391
commit 4e53603fdf
6 changed files with 527 additions and 31 deletions

View file

@ -1,12 +1,14 @@
package com.tangem.feature.tester.presentation.storybook.entity
import androidx.compose.runtime.Immutable
import com.tangem.core.ui.ds.badge.TangemBadgeColor
import com.tangem.core.ui.ds.field.search.TangemFieldShape
import com.tangem.core.ui.ds.message.TangemMessageEffect
import com.tangem.core.ui.ds.topbar.TangemTopBarType
import com.tangem.core.ui.ds2.badge.TangemBadge
import com.tangem.core.ui.ds2.button.TangemButton
import com.tangem.core.ui.ds2.loader.TangemLoaderSize
import com.tangem.core.ui.ds.message.TangemMessageEffect
import com.tangem.core.ui.ds.topbar.TangemTopBarType
import com.tangem.core.ui.ds2.shimmers.TextShimmerStyle
internal sealed interface StoryBookPage
@ -104,6 +106,43 @@ internal data class TangemLoaderStory(
val onSizeChange: (TangemLoaderSize) -> Unit,
) : DsStoryBookPage
@Immutable
internal data class TangemShimmerStory(
val textStyle: TextShimmerStyle,
val radius: RadiusOption,
val rectangleWidth: RectangleWidthOption,
val rectangleHeight: RectangleHeightOption,
val onTextStyleChange: (TextShimmerStyle) -> Unit,
val onRadiusChange: (RadiusOption) -> Unit,
val onRectangleWidthChange: (RectangleWidthOption) -> Unit,
val onRectangleHeightChange: (RectangleHeightOption) -> Unit,
) : DsStoryBookPage {
/** Selectable corner radius (matches `borderRadius` tokens). */
enum class RadiusOption(val label: String) {
R4("4dp"),
R8("8dp"),
R16("16dp"),
R24("24dp"),
R32("32dp"),
FULL("full"),
}
enum class RectangleWidthOption(val label: String) {
W80("80dp"),
W160("160dp"),
W240("240dp"),
FILL("fill"),
}
enum class RectangleHeightOption(val label: String) {
H16("16dp"),
H24("24dp"),
H40("40dp"),
H64("64dp"),
}
}
internal data class TangemButtonStory(
val variant: TangemButton.Variant,
val size: TangemButton.Size,

View file

@ -18,6 +18,7 @@ import com.tangem.feature.tester.presentation.storybook.entity.StoryPageFactory
import com.tangem.feature.tester.presentation.storybook.page.ds.badge.tangemBadgeV2StoryFactory
import com.tangem.feature.tester.presentation.storybook.page.ds.button.tangemButtonStoryFactory
import com.tangem.feature.tester.presentation.storybook.page.ds.loader.tangemLoaderStoryFactory
import com.tangem.feature.tester.presentation.storybook.page.ds.shimmer.tangemShimmerStoryFactory
private data class DsStoryItem(val title: String, val factory: StoryPageFactory)
@ -25,6 +26,7 @@ private fun buildDsStories() = listOf(
DsStoryItem(title = "⏳ TangemLoader", factory = tangemLoaderStoryFactory),
DsStoryItem(title = "🔘 TangemButton", factory = tangemButtonStoryFactory),
DsStoryItem(title = "🏷️ TangemBadge", factory = tangemBadgeV2StoryFactory),
DsStoryItem(title = "✨ TangemShimmer", factory = tangemShimmerStoryFactory),
)
@Composable

View file

@ -0,0 +1,30 @@
package com.tangem.feature.tester.presentation.storybook.page.ds.shimmer
import com.tangem.core.ui.ds2.shimmers.TextShimmerStyle
import com.tangem.feature.tester.presentation.storybook.entity.TangemShimmerStory
import com.tangem.feature.tester.presentation.storybook.viewmodel.StateUpdater
import com.tangem.feature.tester.presentation.storybook.viewmodel.storyPageFactory
internal fun StateUpdater<TangemShimmerStory>.build(): TangemShimmerStory {
return TangemShimmerStory(
textStyle = TextShimmerStyle.BODY,
radius = TangemShimmerStory.RadiusOption.R24,
rectangleWidth = TangemShimmerStory.RectangleWidthOption.W240,
rectangleHeight = TangemShimmerStory.RectangleHeightOption.H24,
onTextStyleChange = { textStyle ->
updateStory { it.copy(textStyle = textStyle) }
},
onRadiusChange = { radius ->
updateStory { it.copy(radius = radius) }
},
onRectangleWidthChange = { width ->
updateStory { it.copy(rectangleWidth = width) }
},
onRectangleHeightChange = { height ->
updateStory { it.copy(rectangleHeight = height) }
},
)
}
internal val tangemShimmerStoryFactory
get() = storyPageFactory(StateUpdater<TangemShimmerStory>::build)

View file

@ -0,0 +1,223 @@
@file:Suppress("MagicNumber")
package com.tangem.feature.tester.presentation.storybook.page.ds.shimmer
import androidx.compose.foundation.*
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.ds2.shimmers.RectangleShimmer
import com.tangem.core.ui.ds2.shimmers.TextShimmer
import com.tangem.core.ui.ds2.shimmers.TextShimmerStyle
import com.tangem.core.ui.res.TangemTheme
import com.tangem.feature.tester.presentation.storybook.entity.TangemShimmerStory
import com.tangem.feature.tester.presentation.storybook.entity.TangemShimmerStory.*
@Composable
internal fun TangemShimmerStory(state: TangemShimmerStory, modifier: Modifier = Modifier) {
Column(
modifier = modifier
.statusBarsPadding()
.fillMaxSize()
.background(TangemTheme.colors3.bg.primary)
.verticalScroll(rememberScrollState())
.padding(vertical = 16.dp),
verticalArrangement = Arrangement.spacedBy(16.dp),
) {
ComponentPreview(state = state)
ChipSection(label = "Text style") {
ChipGrid(
items = TextShimmerStyle.entries,
label = { it.chipLabel() },
isSelected = { it == state.textStyle },
onSelect = state.onTextStyleChange,
)
}
ChipSection(label = "Radius") {
ChipGrid(
items = RadiusOption.entries,
label = { it.label },
isSelected = { it == state.radius },
onSelect = state.onRadiusChange,
)
}
ChipSection(label = "Rectangle width") {
ChipGrid(
items = RectangleWidthOption.entries,
label = { it.label },
isSelected = { it == state.rectangleWidth },
onSelect = state.onRectangleWidthChange,
)
}
ChipSection(label = "Rectangle height") {
ChipGrid(
items = RectangleHeightOption.entries,
label = { it.label },
isSelected = { it == state.rectangleHeight },
onSelect = state.onRectangleHeightChange,
)
}
}
}
@Composable
private fun ComponentPreview(state: TangemShimmerStory) {
val radius = state.radius.value()
Box(
contentAlignment = Alignment.Center,
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp)
.clip(RoundedCornerShape(TangemTheme.dimens3.borderRadius.b200))
.background(TangemTheme.colors3.bg.secondary)
.padding(vertical = 24.dp, horizontal = 16.dp),
) {
Column(
verticalArrangement = Arrangement.spacedBy(20.dp),
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier.fillMaxWidth(),
) {
PreviewLabel(text = "RectangleShimmer")
RectangleShimmerPreview(
width = state.rectangleWidth,
height = state.rectangleHeight,
radius = radius,
)
PreviewLabel(text = "TextShimmer · ${state.textStyle.chipLabel()}")
TextShimmer(
text = SAMPLE_TEXT,
style = state.textStyle,
radius = radius,
)
}
}
}
@Composable
private fun RectangleShimmerPreview(width: RectangleWidthOption, height: RectangleHeightOption, radius: Dp) {
val sizeModifier = when (width) {
RectangleWidthOption.FILL -> Modifier.fillMaxWidth()
else -> Modifier.width(width.value())
}.height(height.value())
RectangleShimmer(
modifier = sizeModifier,
radius = radius,
)
}
@Composable
private fun PreviewLabel(text: String) {
Text(
text = text,
style = TangemTheme.typography3.caption.medium,
color = TangemTheme.colors3.text.secondary,
)
}
// region Chip selector — uses ds2 surfaces/typography so the controls match the redesign.
@Composable
private fun ChipSection(label: String, content: @Composable () -> Unit) {
Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
Text(
modifier = Modifier.padding(horizontal = 16.dp),
text = label,
style = TangemTheme.typography3.subheading.medium,
color = TangemTheme.colors3.text.primary,
)
content()
}
}
@Composable
private fun <T> ChipGrid(items: List<T>, label: (T) -> String, isSelected: (T) -> Boolean, onSelect: (T) -> Unit) {
val shape = RoundedCornerShape(TangemTheme.dimens3.borderRadius.full)
Row(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp)
.clip(shape)
.background(TangemTheme.colors3.bg.opaque.primary)
.border(
width = TangemTheme.dimens3.borderWidth.sm,
color = TangemTheme.colors3.border.primary,
shape = shape,
)
.padding(4.dp),
horizontalArrangement = Arrangement.spacedBy(4.dp),
) {
items.forEach { item ->
Chip(
label = label(item),
selected = isSelected(item),
onClick = { onSelect(item) },
modifier = Modifier.weight(1f),
)
}
}
}
@Composable
private fun Chip(label: String, selected: Boolean, onClick: () -> Unit, modifier: Modifier = Modifier) {
val chipShape = RoundedCornerShape(TangemTheme.dimens3.borderRadius.full)
Box(
contentAlignment = Alignment.Center,
modifier = modifier
.clip(chipShape)
.background(
if (selected) TangemTheme.colors3.bg.opaque.secondary else TangemTheme.colors3.bg.opaque.primary,
)
.clickable(onClick = onClick)
.padding(vertical = 8.dp, horizontal = 4.dp),
) {
Text(
text = label,
style = TangemTheme.typography3.caption.medium,
color = if (selected) TangemTheme.colors3.text.primary else TangemTheme.colors3.text.secondary,
)
}
}
// endregion
private fun TextShimmerStyle.chipLabel(): String = when (this) {
TextShimmerStyle.DISPLAY -> "Display"
TextShimmerStyle.HEADING_MEDIUM -> "Head.M"
TextShimmerStyle.HEADING_SMALL -> "Head.S"
TextShimmerStyle.BODY -> "Body"
TextShimmerStyle.SUBHEADING -> "Sub.H"
TextShimmerStyle.CAPTION -> "Caption"
}
private fun RadiusOption.value(): Dp = when (this) {
RadiusOption.R4 -> 4.dp
RadiusOption.R8 -> 8.dp
RadiusOption.R16 -> 16.dp
RadiusOption.R24 -> 24.dp
RadiusOption.R32 -> 32.dp
RadiusOption.FULL -> 1000.dp
}
private fun RectangleWidthOption.value(): Dp = when (this) {
RectangleWidthOption.W80 -> 80.dp
RectangleWidthOption.W160 -> 160.dp
RectangleWidthOption.W240 -> 240.dp
RectangleWidthOption.FILL -> 0.dp // unused — handled separately
}
private fun RectangleHeightOption.value(): Dp = when (this) {
RectangleHeightOption.H16 -> 16.dp
RectangleHeightOption.H24 -> 24.dp
RectangleHeightOption.H40 -> 40.dp
RectangleHeightOption.H64 -> 64.dp
}
private const val SAMPLE_TEXT = "Sample shimmer text"

View file

@ -4,51 +4,29 @@ import androidx.activity.compose.BackHandler
import androidx.compose.animation.AnimatedContent
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import com.tangem.feature.tester.presentation.storybook.entity.ButtonsStory
import com.tangem.feature.tester.presentation.storybook.entity.DeviceIconStory
import com.tangem.feature.tester.presentation.storybook.entity.DsComponentsListStory
import com.tangem.feature.tester.presentation.storybook.entity.NorthernLightsStory
import com.tangem.feature.tester.presentation.storybook.entity.OpportunitiesBGStory
import com.tangem.feature.tester.presentation.storybook.entity.PlaceholderStory
import com.tangem.feature.tester.presentation.storybook.entity.ProgressIndicatorStory
import com.tangem.feature.tester.presentation.storybook.entity.TangemBadgeStory
import com.tangem.feature.tester.presentation.storybook.entity.TangemBadgeV2Story
import com.tangem.feature.tester.presentation.storybook.entity.TangemButtonStory
import com.tangem.feature.tester.presentation.storybook.entity.StoryBookUM
import com.tangem.feature.tester.presentation.storybook.entity.StoryList
import com.tangem.feature.tester.presentation.storybook.entity.TangemCheckboxStory
import com.tangem.feature.tester.presentation.storybook.entity.TangemHeaderRowStory
import com.tangem.feature.tester.presentation.storybook.entity.TangemLoaderStory
import com.tangem.feature.tester.presentation.storybook.entity.TangemContextMenuStory
import com.tangem.feature.tester.presentation.storybook.entity.TangemMessageStory
import com.tangem.feature.tester.presentation.storybook.entity.TangemPagerIndicatorStory
import com.tangem.feature.tester.presentation.storybook.entity.TangemSearchFieldStory
import com.tangem.feature.tester.presentation.storybook.entity.TangemSegmentedPickerStory
import com.tangem.feature.tester.presentation.storybook.entity.TangemTabStory
import com.tangem.feature.tester.presentation.storybook.entity.TangemTokenRowStory
import com.tangem.feature.tester.presentation.storybook.entity.TangemTopBarStory
import com.tangem.feature.tester.presentation.storybook.entity.TypographyStory
import com.tangem.feature.tester.presentation.storybook.entity.*
import com.tangem.feature.tester.presentation.storybook.page.background.NorthernLightsStory
import com.tangem.feature.tester.presentation.storybook.page.badge.TangemBadgeStory
import com.tangem.feature.tester.presentation.storybook.page.buttons.ButtonsStory
import com.tangem.feature.tester.presentation.storybook.page.checkbox.TangemCheckboxStory
import com.tangem.feature.tester.presentation.storybook.page.contextmenu.TangemContextMenuStory
import com.tangem.feature.tester.presentation.storybook.page.deviceicon.DeviceIconStory
import com.tangem.feature.tester.presentation.storybook.page.ds.DsComponentsListStory
import com.tangem.feature.tester.presentation.storybook.page.ds.badge.TangemBadgeV2Story
import com.tangem.feature.tester.presentation.storybook.page.ds.button.TangemButtonStory
import com.tangem.feature.tester.presentation.storybook.page.ds.loader.TangemLoaderStory
import com.tangem.feature.tester.presentation.storybook.page.opportunities.OpportunitiesBGStory
import com.tangem.feature.tester.presentation.storybook.page.checkbox.TangemCheckboxStory
import com.tangem.feature.tester.presentation.storybook.page.ds.shimmer.TangemShimmerStory
import com.tangem.feature.tester.presentation.storybook.page.headerrow.TangemHeaderRowStory
import com.tangem.feature.tester.presentation.storybook.page.message.TangemMessageStory
import com.tangem.feature.tester.presentation.storybook.page.opportunities.OpportunitiesBGStory
import com.tangem.feature.tester.presentation.storybook.page.pagerindicator.TangemPagerIndicatorStory
import com.tangem.feature.tester.presentation.storybook.page.placeholder.PlaceholderStory
import com.tangem.feature.tester.presentation.storybook.page.progress.ProgressIndicatorStory
import com.tangem.feature.tester.presentation.storybook.page.searchfield.TangemSearchFieldStory
import com.tangem.feature.tester.presentation.storybook.page.tab.TangemTabStory
import com.tangem.feature.tester.presentation.storybook.page.tabs.TangemSegmentedPickerStory
import com.tangem.feature.tester.presentation.storybook.page.tokenrow.TangemTokenRowStory
import com.tangem.feature.tester.presentation.storybook.page.topbar.TangemTopBarStory
import com.tangem.feature.tester.presentation.storybook.page.headerrow.TangemHeaderRowStory
import com.tangem.feature.tester.presentation.storybook.page.contextmenu.TangemContextMenuStory
import com.tangem.feature.tester.presentation.storybook.page.searchfield.TangemSearchFieldStory
import com.tangem.feature.tester.presentation.storybook.page.typography.TypographyStory
@Suppress("CyclomaticComplexMethod")
@ -85,6 +63,7 @@ internal fun StoryBookScreen(state: StoryBookUM, modifier: Modifier = Modifier)
is TangemLoaderStory -> TangemLoaderStory(state = storyState)
is TangemButtonStory -> TangemButtonStory(state = storyState)
is TangemBadgeV2Story -> TangemBadgeV2Story(state = storyState)
is TangemShimmerStory -> TangemShimmerStory(state = storyState)
}
}
}