Updated on 2026-08-14

This commit is contained in:
Tangem 2026-02-27 15:54:11 +03:00
parent 31e0591ef0
commit 6cfb8213f0
23 changed files with 1163 additions and 6 deletions

View file

@ -171,11 +171,56 @@ Show every meaningful axis of variation in one place:
| **Shapes** (Default, Rounded) | One labeled group (`ShapeGroup`) per shape, iterate `TangemButtonShape.entries` |
| **Content** (text+icon vs icon-only) | Two columns per row |
| **Sizes** | Separate `LazyColumn` item per size group if needed |
| **Styles / Effects** (e.g. `TangemMessageEffect`) | Chip toggle — see below |
> **Prefer vertical stacking over horizontal.** A row should contain at most
> 23 components; more than that overflows on narrow screens. Use
> `Modifier.weight(1f)` on columns instead of fixed widths.
### Toggle for style/effect axes
When a discrete axis (e.g. a visual effect enum) would produce too many full-width
components on one screen, use a **sticky chip-picker** instead of stacking all values.
Make the page **stateful** and store the selected value in the `StoryBookPage` data class.
```
┌─────────────────────────────────┐ ← stickyHeader
│ Magic │ Card │ Warning │ None│ ← chip row (EffectToggle)
└─────────────────────────────────┘
No icon, no buttons
[ message with selected effect ]
With icon
[ message with selected effect ]
```
**Pattern:**
1. Add the selected value + callback to the `StoryBookPage` data class:
```kotlin
internal data class FooStory(
val selectedVariant: Variant,
val onVariantChange: (Variant) -> Unit,
) : StoryBookPage
```
2. Use a stateful `Build.kt` (see [Step 2](#2-create-pagefoobuildk)).
3. In the story composable, add a `stickyHeader` with a chip row:
```kotlin
stickyHeader("toggle") {
VariantToggle(
selected = state.selectedVariant,
onSelect = state.onVariantChange,
modifier = Modifier
.fillMaxWidth()
.background(TangemTheme.colors2.surface.level1)
.padding(horizontal = 16.dp, vertical = 8.dp),
)
}
```
4. Each `item` below uses `state.selectedVariant` for the component under test.
See `TangemMessageStory` for a complete example.
### Section structure (component grids)
Follow the pattern used in `ButtonsStory`:

View file

@ -1,11 +1,35 @@
package com.tangem.feature.tester.presentation.storybook.entity
import com.tangem.core.ui.ds.badge.TangemBadgeColor
import com.tangem.core.ui.ds.message.TangemMessageEffect
internal sealed interface StoryBookPage
internal data object StoryList : StoryBookPage
internal data object ButtonsStory : StoryBookPage
internal data class TangemBadgeStory(
val selectedColor: TangemBadgeColor,
val onColorChange: (TangemBadgeColor) -> Unit,
) : StoryBookPage
internal data object OpportunitiesBGStory : StoryBookPage
internal data class TangemCheckboxStory(
val isRoundedChecked: Boolean,
val onRoundedCheckedChange: (Boolean) -> Unit,
val isCircleChecked: Boolean,
val onCircleCheckedChange: (Boolean) -> Unit,
) : StoryBookPage
internal data object TangemSegmentedPickerStory : StoryBookPage
internal data class TangemMessageStory(
val selectedEffect: TangemMessageEffect,
val onEffectChange: (TangemMessageEffect) -> Unit,
) : StoryBookPage
internal data class NorthernLightsStory(
val variant: Variant,
val onVariantChange: (Variant) -> Unit,
@ -14,4 +38,9 @@ internal data class NorthernLightsStory(
Shader,
Simple,
}
}
}
internal data class TangemTokenRowStory(
val isBalanceHidden: Boolean,
val onBalanceHiddenToggle: () -> Unit,
) : StoryBookPage

View file

@ -1,3 +1,4 @@
@file:Suppress("MagicNumber", "LongMethod")
package com.tangem.feature.tester.presentation.storybook.page.background
import com.tangem.feature.tester.presentation.storybook.entity.NorthernLightsStory

View file

@ -1,4 +1,4 @@
@file:Suppress("MagicNumber")
@file:Suppress("MagicNumber", "LongMethod")
package com.tangem.feature.tester.presentation.storybook.page.background
import androidx.compose.foundation.background

View file

@ -0,0 +1,19 @@
@file:Suppress("MagicNumber", "LongMethod")
package com.tangem.feature.tester.presentation.storybook.page.badge
import com.tangem.core.ui.ds.badge.TangemBadgeColor
import com.tangem.feature.tester.presentation.storybook.entity.TangemBadgeStory
import com.tangem.feature.tester.presentation.storybook.viewmodel.StateUpdater
import com.tangem.feature.tester.presentation.storybook.viewmodel.storyPageFactory
internal fun StateUpdater<TangemBadgeStory>.build(): TangemBadgeStory {
return TangemBadgeStory(
selectedColor = TangemBadgeColor.Blue,
onColorChange = { color ->
updateStory { it.copy(selectedColor = color) }
},
)
}
internal val tangemBadgeStoryFactory
get() = storyPageFactory(StateUpdater<TangemBadgeStory>::build)

View file

@ -0,0 +1,222 @@
@file:Suppress("MagicNumber", "LongMethod")
package com.tangem.feature.tester.presentation.storybook.page.badge
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.HorizontalDivider
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 com.tangem.core.ui.R
import com.tangem.core.ui.ds.badge.*
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.res.TangemTheme
import com.tangem.feature.tester.presentation.storybook.entity.TangemBadgeStory
private const val STATE_LABEL_WIDTH = 80
@Composable
internal fun TangemBadgeStory(state: TangemBadgeStory, modifier: Modifier = Modifier) {
LazyColumn(
contentPadding = PaddingValues(bottom = 16.dp),
verticalArrangement = Arrangement.spacedBy(8.dp),
modifier = modifier
.statusBarsPadding()
.fillMaxSize()
.background(TangemTheme.colors2.surface.level1),
) {
stickyHeader("color_toggle") {
ColorToggle(
selected = state.selectedColor,
onSelect = state.onColorChange,
modifier = Modifier
.fillMaxWidth()
.background(TangemTheme.colors2.surface.level1)
.padding(horizontal = 16.dp, vertical = 8.dp),
)
}
TangemBadgeSize.entries.forEach { size ->
item(size.name) {
BadgeSizeSection(size = size, color = state.selectedColor)
}
}
}
}
@Composable
private fun ColorToggle(
selected: TangemBadgeColor,
onSelect: (TangemBadgeColor) -> Unit,
modifier: Modifier = Modifier,
) {
val shape = RoundedCornerShape(50)
Row(
modifier = modifier
.clip(shape)
.background(TangemTheme.colors2.surface.level2)
.border(width = 1.dp, color = TangemTheme.colors2.border.neutral.secondary, shape = shape)
.padding(4.dp),
horizontalArrangement = Arrangement.spacedBy(4.dp),
) {
TangemBadgeColor.entries.forEach { color ->
ColorChip(
label = color.name,
selected = color == selected,
onClick = { onSelect(color) },
modifier = Modifier.weight(1f),
)
}
}
}
@Composable
private fun ColorChip(label: String, selected: Boolean, onClick: () -> Unit, modifier: Modifier = Modifier) {
val chipShape = RoundedCornerShape(50)
Box(
contentAlignment = Alignment.Center,
modifier = modifier
.clip(chipShape)
.background(if (selected) TangemTheme.colors2.surface.level3 else TangemTheme.colors2.surface.level2)
.clickable(onClick = onClick)
.padding(vertical = 8.dp, horizontal = 4.dp),
) {
Text(
text = label,
style = TangemTheme.typography.caption2,
color = if (selected) TangemTheme.colors.text.primary1 else TangemTheme.colors.text.secondary,
)
}
}
@Composable
private fun BadgeSizeSection(size: TangemBadgeSize, color: TangemBadgeColor) {
Column(
verticalArrangement = Arrangement.spacedBy(8.dp),
modifier = Modifier
.fillMaxWidth()
.background(TangemTheme.colors2.surface.level1)
.padding(horizontal = 16.dp, vertical = 12.dp),
) {
Text(
text = size.name,
style = TangemTheme.typography.subtitle1,
color = TangemTheme.colors.text.primary1,
)
TangemBadgeShape.entries.forEach { shape ->
BadgeShapeGroup(size = size, shape = shape, color = color)
}
}
HorizontalDivider(
color = TangemTheme.colors2.border.neutral.secondary,
modifier = Modifier.padding(horizontal = 16.dp),
)
}
@Composable
private fun BadgeShapeGroup(size: TangemBadgeSize, shape: TangemBadgeShape, color: TangemBadgeColor) {
Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
Text(
text = shape.name,
style = TangemTheme.typography.body2,
color = TangemTheme.colors.text.secondary,
)
ColumnHeaderRow()
TangemBadgeType.entries.forEach { type ->
BadgeTypeRow(size = size, shape = shape, color = color, type = type)
}
}
}
@Composable
private fun ColumnHeaderRow() {
Row(
horizontalArrangement = Arrangement.spacedBy(8.dp),
verticalAlignment = Alignment.CenterVertically,
) {
Spacer(Modifier.width(STATE_LABEL_WIDTH.dp))
Text(
text = "Text + Icon",
style = TangemTheme.typography.caption2,
color = TangemTheme.colors.text.tertiary,
modifier = Modifier.weight(1f),
)
Text(
text = "Text",
style = TangemTheme.typography.caption2,
color = TangemTheme.colors.text.tertiary,
modifier = Modifier.weight(1f),
)
Text(
text = "Icon",
style = TangemTheme.typography.caption2,
color = TangemTheme.colors.text.tertiary,
modifier = Modifier.weight(1f),
)
}
}
@Composable
private fun BadgeTypeRow(
size: TangemBadgeSize,
shape: TangemBadgeShape,
color: TangemBadgeColor,
type: TangemBadgeType,
) {
Row(
horizontalArrangement = Arrangement.spacedBy(8.dp),
verticalAlignment = Alignment.CenterVertically,
) {
Text(
text = type.name,
style = TangemTheme.typography.caption2,
color = TangemTheme.colors.text.tertiary,
modifier = Modifier.width(STATE_LABEL_WIDTH.dp),
)
Box(
contentAlignment = Alignment.CenterStart,
modifier = Modifier.weight(1f),
) {
TangemBadge(
text = stringReference("New"),
iconRes = R.drawable.ic_information_24,
size = size,
shape = shape,
color = color,
type = type,
iconPosition = TangemBadgeIconPosition.Start,
)
}
Box(
contentAlignment = Alignment.CenterStart,
modifier = Modifier.weight(1f),
) {
TangemBadge(
text = stringReference("New"),
size = size,
shape = shape,
color = color,
type = type,
)
}
Box(
contentAlignment = Alignment.CenterStart,
modifier = Modifier.weight(1f),
) {
TangemBadge(
iconRes = R.drawable.ic_information_24,
size = size,
shape = shape,
color = color,
type = type,
iconPosition = TangemBadgeIconPosition.Start,
)
}
}
}

View file

@ -1,3 +1,4 @@
@file:Suppress("MagicNumber", "LongMethod")
package com.tangem.feature.tester.presentation.storybook.page.buttons
import com.tangem.feature.tester.presentation.storybook.entity.ButtonsStory

View file

@ -1,3 +1,4 @@
@file:Suppress("MagicNumber", "LongMethod")
package com.tangem.feature.tester.presentation.storybook.page.buttons
import androidx.compose.foundation.background
@ -24,6 +25,7 @@ internal fun ButtonsStory(modifier: Modifier = Modifier) {
contentPadding = PaddingValues(vertical = 16.dp),
verticalArrangement = Arrangement.spacedBy(8.dp),
modifier = modifier
.statusBarsPadding()
.fillMaxSize()
.background(TangemTheme.colors2.surface.level1),
) {

View file

@ -0,0 +1,22 @@
@file:Suppress("MagicNumber", "LongMethod")
package com.tangem.feature.tester.presentation.storybook.page.checkbox
import com.tangem.feature.tester.presentation.storybook.entity.TangemCheckboxStory
import com.tangem.feature.tester.presentation.storybook.viewmodel.StateUpdater
import com.tangem.feature.tester.presentation.storybook.viewmodel.storyPageFactory
internal fun StateUpdater<TangemCheckboxStory>.build(): TangemCheckboxStory {
return TangemCheckboxStory(
isRoundedChecked = false,
onRoundedCheckedChange = { checked ->
updateStory { it.copy(isRoundedChecked = checked) }
},
isCircleChecked = false,
onCircleCheckedChange = { checked ->
updateStory { it.copy(isCircleChecked = checked) }
},
)
}
internal val tangemCheckboxStoryFactory
get() = storyPageFactory(StateUpdater<TangemCheckboxStory>::build)

View file

@ -0,0 +1,137 @@
@file:Suppress("MagicNumber", "LongMethod")
package com.tangem.feature.tester.presentation.storybook.page.checkbox
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.ds.checkbox.TangemCheckbox
import com.tangem.core.ui.res.TangemTheme
import com.tangem.feature.tester.presentation.storybook.entity.TangemCheckboxStory
private const val STATE_LABEL_WIDTH = 80
@Composable
internal fun TangemCheckboxStory(state: TangemCheckboxStory, modifier: Modifier = Modifier) {
LazyColumn(
contentPadding = PaddingValues(vertical = 16.dp),
verticalArrangement = Arrangement.spacedBy(8.dp),
modifier = modifier
.fillMaxSize()
.background(TangemTheme.colors2.surface.level1),
) {
item("grid") {
CheckboxGrid(state = state)
}
}
}
@Composable
private fun CheckboxGrid(state: TangemCheckboxStory) {
Column(
verticalArrangement = Arrangement.spacedBy(8.dp),
modifier = Modifier
.fillMaxWidth()
.background(TangemTheme.colors2.surface.level1)
.padding(horizontal = 16.dp, vertical = 12.dp),
) {
ColumnHeaderRow()
CheckboxRow(
label = "Rounded",
isChecked = state.isRoundedChecked,
onCheckedChange = state.onRoundedCheckedChange,
isRounded = true,
)
CheckboxRow(
label = "Circle",
isChecked = state.isCircleChecked,
onCheckedChange = state.onCircleCheckedChange,
isRounded = false,
)
}
HorizontalDivider(
color = TangemTheme.colors2.border.neutral.secondary,
modifier = Modifier.padding(horizontal = 16.dp),
)
}
@Composable
private fun ColumnHeaderRow() {
Row(
horizontalArrangement = Arrangement.spacedBy(8.dp),
verticalAlignment = Alignment.CenterVertically,
) {
Spacer(Modifier.width(STATE_LABEL_WIDTH.dp))
Text(
text = "Enabled",
style = TangemTheme.typography.caption2,
color = TangemTheme.colors.text.tertiary,
modifier = Modifier.weight(1f),
)
Text(
text = "Disabled",
style = TangemTheme.typography.caption2,
color = TangemTheme.colors.text.tertiary,
modifier = Modifier.weight(1f),
)
Text(
text = "Disabled (on)",
style = TangemTheme.typography.caption2,
color = TangemTheme.colors.text.tertiary,
modifier = Modifier.weight(1f),
)
}
}
@Composable
private fun CheckboxRow(label: String, isChecked: Boolean, onCheckedChange: (Boolean) -> Unit, isRounded: Boolean) {
Row(
horizontalArrangement = Arrangement.spacedBy(8.dp),
verticalAlignment = Alignment.CenterVertically,
) {
Text(
text = label,
style = TangemTheme.typography.caption2,
color = TangemTheme.colors.text.tertiary,
modifier = Modifier.width(STATE_LABEL_WIDTH.dp),
)
Box(
contentAlignment = Alignment.CenterStart,
modifier = Modifier.weight(1f),
) {
TangemCheckbox(
isChecked = isChecked,
isRounded = isRounded,
isEnabled = true,
onCheckedChange = onCheckedChange,
)
}
Box(
contentAlignment = Alignment.CenterStart,
modifier = Modifier.weight(1f),
) {
TangemCheckbox(
isChecked = false,
isRounded = isRounded,
isEnabled = false,
onCheckedChange = {},
)
}
Box(
contentAlignment = Alignment.CenterStart,
modifier = Modifier.weight(1f),
) {
TangemCheckbox(
isChecked = true,
isRounded = isRounded,
isEnabled = false,
onCheckedChange = {},
)
}
}
}

View file

@ -0,0 +1,19 @@
@file:Suppress("MagicNumber", "LongMethod")
package com.tangem.feature.tester.presentation.storybook.page.message
import com.tangem.core.ui.ds.message.TangemMessageEffect
import com.tangem.feature.tester.presentation.storybook.entity.TangemMessageStory
import com.tangem.feature.tester.presentation.storybook.viewmodel.StateUpdater
import com.tangem.feature.tester.presentation.storybook.viewmodel.storyPageFactory
internal fun StateUpdater<TangemMessageStory>.build(): TangemMessageStory {
return TangemMessageStory(
selectedEffect = TangemMessageEffect.None,
onEffectChange = { newEffect ->
updateStory { it.copy(selectedEffect = newEffect) }
},
)
}
internal val tangemMessageStoryFactory
get() = storyPageFactory(StateUpdater<TangemMessageStory>::build)

View file

@ -0,0 +1,233 @@
@file:Suppress("MagicNumber", "LongMethod")
package com.tangem.feature.tester.presentation.storybook.page.message
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
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 com.tangem.core.ui.R
import com.tangem.core.ui.ds.button.TangemButtonType
import com.tangem.core.ui.ds.image.TangemIconUM
import com.tangem.core.ui.ds.message.*
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.res.TangemTheme
import com.tangem.feature.tester.presentation.storybook.entity.TangemMessageStory
import kotlinx.collections.immutable.persistentListOf
@Composable
internal fun TangemMessageStory(state: TangemMessageStory, modifier: Modifier = Modifier) {
LazyColumn(
contentPadding = PaddingValues(bottom = 16.dp),
verticalArrangement = Arrangement.spacedBy(8.dp),
modifier = modifier
.fillMaxSize()
.statusBarsPadding()
.background(TangemTheme.colors2.surface.level1),
) {
stickyHeader("effect_toggle") {
EffectToggle(
selected = state.selectedEffect,
onSelect = state.onEffectChange,
modifier = Modifier
.fillMaxWidth()
.background(TangemTheme.colors2.surface.level1)
.padding(horizontal = 16.dp, vertical = 8.dp),
)
}
item("plain") {
VariantSection(label = "No icon, no buttons") {
TangemMessage(
messageUM = TangemMessageUM(
id = "plain",
title = stringReference("Update available"),
subtitle = stringReference("A new firmware version is ready to install on your card."),
messageEffect = state.selectedEffect,
),
modifier = Modifier.fillMaxWidth(),
)
}
}
item("icon") {
VariantSection(label = "With icon") {
TangemMessage(
messageUM = TangemMessageUM(
id = "icon",
title = stringReference("Wallet backup missing"),
subtitle = stringReference("To protect your assets, complete the backup process."),
messageEffect = state.selectedEffect,
iconUM = TangemIconUM.Icon(R.drawable.ic_attention_default_24),
),
modifier = Modifier.fillMaxWidth(),
)
}
}
item("centered") {
VariantSection(label = "Centered") {
TangemMessage(
messageUM = TangemMessageUM(
id = "centered",
title = stringReference("Scan your card"),
subtitle = stringReference("Hold the card to the back of your phone."),
messageEffect = state.selectedEffect,
iconUM = TangemIconUM.Icon(R.drawable.ic_attention_default_24),
isCentered = true,
),
modifier = Modifier.fillMaxWidth(),
)
}
}
item("1btn") {
VariantSection(label = "With 1 button") {
TangemMessage(
messageUM = TangemMessageUM(
id = "1btn",
title = stringReference("Generate addresses"),
subtitle = stringReference("Generate addresses for 2 new networks using your card."),
messageEffect = state.selectedEffect,
iconUM = TangemIconUM.Icon(R.drawable.ic_attention_default_24),
buttonsUM = persistentListOf(
TangemMessageButtonUM(
text = stringReference("Generate"),
type = TangemButtonType.Primary,
iconRes = R.drawable.ic_tangem_24,
onClick = {},
),
),
),
modifier = Modifier.fillMaxWidth(),
)
}
}
item("2btn") {
VariantSection(label = "With 2 buttons") {
TangemMessage(
messageUM = TangemMessageUM(
id = "2btn",
title = stringReference("Rate the app"),
subtitle = stringReference("How do you like Tangem so far?"),
messageEffect = state.selectedEffect,
iconUM = TangemIconUM.Icon(R.drawable.ic_attention_default_24),
buttonsUM = persistentListOf(
TangemMessageButtonUM(
text = stringReference("Love it!"),
type = TangemButtonType.PrimaryInverse,
onClick = {},
),
TangemMessageButtonUM(
text = stringReference("Can be better"),
type = TangemButtonType.Primary,
onClick = {},
),
),
),
modifier = Modifier.fillMaxWidth(),
)
}
}
item("close") {
VariantSection(label = "With close button") {
TangemMessage(
messageUM = TangemMessageUM(
id = "close",
title = stringReference("Note top up"),
subtitle = stringReference("To activate the card, top it up with at least 1 XLM."),
messageEffect = state.selectedEffect,
iconUM = TangemIconUM.Icon(R.drawable.ic_attention_default_24),
onCloseClick = {},
),
modifier = Modifier.fillMaxWidth(),
)
}
}
}
}
@Composable
private fun EffectToggle(
selected: TangemMessageEffect,
onSelect: (TangemMessageEffect) -> Unit,
modifier: Modifier = Modifier,
) {
val shape = RoundedCornerShape(50)
Row(
modifier = modifier
.clip(shape)
.background(TangemTheme.colors2.surface.level2)
.border(
width = 1.dp,
color = TangemTheme.colors2.border.neutral.secondary,
shape = shape,
)
.padding(4.dp),
horizontalArrangement = Arrangement.spacedBy(4.dp),
) {
TangemMessageEffect.entries.forEach { effect ->
EffectChip(
label = effect.name,
selected = effect == selected,
onClick = { onSelect(effect) },
modifier = Modifier.weight(1f),
)
}
}
}
@Composable
private fun EffectChip(label: String, selected: Boolean, onClick: () -> Unit, modifier: Modifier = Modifier) {
val chipShape = RoundedCornerShape(50)
Box(
contentAlignment = Alignment.Center,
modifier = modifier
.clip(chipShape)
.background(
if (selected) {
TangemTheme.colors2.surface.level3
} else {
TangemTheme.colors2.surface.level2
},
)
.clickable(onClick = onClick)
.padding(vertical = 8.dp, horizontal = 4.dp),
) {
Text(
text = label,
style = TangemTheme.typography.caption2,
color = if (selected) {
TangemTheme.colors.text.primary1
} else {
TangemTheme.colors.text.secondary
},
)
}
}
@Composable
private fun VariantSection(label: String, content: @Composable ColumnScope.() -> Unit) {
Column(
verticalArrangement = Arrangement.spacedBy(8.dp),
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp),
) {
Text(
text = label,
style = TangemTheme.typography.body2,
color = TangemTheme.colors.text.secondary,
)
content()
}
}

View file

@ -0,0 +1,7 @@
@file:Suppress("MagicNumber", "LongMethod")
package com.tangem.feature.tester.presentation.storybook.page.opportunities
import com.tangem.feature.tester.presentation.storybook.entity.OpportunitiesBGStory
import com.tangem.feature.tester.presentation.storybook.entity.StoryPageFactory
internal val opportunitiesBGStoryFactory: StoryPageFactory = StoryPageFactory { OpportunitiesBGStory }

View file

@ -0,0 +1,122 @@
@file:Suppress("MagicNumber", "LongMethod")
package com.tangem.feature.tester.presentation.storybook.page.opportunities
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
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 com.tangem.core.ui.R
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
import com.tangem.core.ui.ds.image.TangemIcon
import com.tangem.core.ui.ds.image.TangemIconUM
import com.tangem.core.ui.ds.opportunities.OpportunitiesBG
import com.tangem.core.ui.res.TangemTheme
private data class IconVariant(
val label: String,
val icon: TangemIconUM,
)
private val variants = listOf(
IconVariant(
label = "Bitcoin",
icon = TangemIconUM.Currency(
CurrencyIconState.CoinIcon(
url = null,
fallbackResId = R.drawable.img_btc_22,
isGrayscale = false,
shouldShowCustomBadge = false,
),
),
),
IconVariant(
label = "Solana",
icon = TangemIconUM.Currency(
CurrencyIconState.CoinIcon(
url = null,
fallbackResId = R.drawable.img_solana_22,
isGrayscale = false,
shouldShowCustomBadge = false,
),
),
),
IconVariant(
label = "Avalanche",
icon = TangemIconUM.Currency(
CurrencyIconState.CoinIcon(
url = null,
fallbackResId = R.drawable.img_avalanche_22,
isGrayscale = false,
shouldShowCustomBadge = false,
),
),
),
IconVariant(
label = "BNB Smart Chain",
icon = TangemIconUM.Currency(
CurrencyIconState.CoinIcon(
url = null,
fallbackResId = R.drawable.img_bsc_22,
isGrayscale = false,
shouldShowCustomBadge = false,
),
),
),
IconVariant(
label = "Cardano",
icon = TangemIconUM.Currency(
CurrencyIconState.CoinIcon(
url = null,
fallbackResId = R.drawable.img_cardano_22,
isGrayscale = false,
shouldShowCustomBadge = false,
),
),
),
)
@Composable
internal fun OpportunitiesBGStory(modifier: Modifier = Modifier) {
LazyColumn(
contentPadding = PaddingValues(horizontal = 16.dp, vertical = 24.dp),
verticalArrangement = Arrangement.spacedBy(16.dp),
modifier = modifier
.fillMaxSize()
.background(TangemTheme.colors2.surface.level1),
) {
variants.forEach { variant ->
item(variant.label) {
OpportunitiesBG(
icon = variant.icon,
modifier = Modifier
.fillMaxWidth()
.clip(RoundedCornerShape(16.dp)),
) {
Row(
horizontalArrangement = Arrangement.spacedBy(12.dp),
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 20.dp, vertical = 24.dp),
) {
TangemIcon(
tangemIconUM = variant.icon,
modifier = Modifier.size(40.dp),
)
Text(
text = variant.label,
style = TangemTheme.typography.subtitle1,
color = TangemTheme.colors.text.primary1,
)
}
}
}
}
}
}

View file

@ -0,0 +1,7 @@
@file:Suppress("MagicNumber", "LongMethod")
package com.tangem.feature.tester.presentation.storybook.page.tabs
import com.tangem.feature.tester.presentation.storybook.entity.StoryPageFactory
import com.tangem.feature.tester.presentation.storybook.entity.TangemSegmentedPickerStory
internal val tangemSegmentedPickerStoryFactory: StoryPageFactory = StoryPageFactory { TangemSegmentedPickerStory }

View file

@ -0,0 +1,171 @@
@file:Suppress("MagicNumber", "LongMethod")
package com.tangem.feature.tester.presentation.storybook.page.tabs
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.ds.tabs.TangemSegmentUM
import com.tangem.core.ui.ds.tabs.TangemSegmentedPicker
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.res.TangemTheme
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
private val items2 = persistentListOf(
TangemSegmentUM(id = "all", title = stringReference("All")),
TangemSegmentUM(id = "tokens", title = stringReference("Tokens")),
)
private val items3 = persistentListOf(
TangemSegmentUM(id = "1d", title = stringReference("1D")),
TangemSegmentUM(id = "1w", title = stringReference("1W")),
TangemSegmentUM(id = "1m", title = stringReference("1M")),
)
private val items4 = persistentListOf(
TangemSegmentUM(id = "1d", title = stringReference("1D")),
TangemSegmentUM(id = "1w", title = stringReference("1W")),
TangemSegmentUM(id = "1m", title = stringReference("1M")),
TangemSegmentUM(id = "1y", title = stringReference("1Y")),
)
private val items5 = persistentListOf(
TangemSegmentUM(id = "send", title = stringReference("Send")),
TangemSegmentUM(id = "receive", title = stringReference("Receive")),
TangemSegmentUM(id = "swap", title = stringReference("Swap")),
TangemSegmentUM(id = "buy", title = stringReference("Buy")),
TangemSegmentUM(id = "sell", title = stringReference("Sell")),
)
private data class PickerConfig(
val label: String,
val hasSeparator: Boolean,
val isFixed: Boolean,
)
private val configs = listOf(
PickerConfig("Default", hasSeparator = false, isFixed = false),
PickerConfig("Separator", hasSeparator = true, isFixed = false),
PickerConfig("Fixed", hasSeparator = false, isFixed = true),
PickerConfig("Fixed + Separator", hasSeparator = true, isFixed = true),
)
@Composable
internal fun TangemSegmentedPickerStory(modifier: Modifier = Modifier) {
LazyColumn(
contentPadding = PaddingValues(vertical = 16.dp),
verticalArrangement = Arrangement.spacedBy(8.dp),
modifier = modifier
.fillMaxSize()
.background(TangemTheme.colors2.surface.level1),
) {
item("default_surface") {
PickerSection(
title = "Default surface",
isAltSurface = false,
background = TangemTheme.colors2.surface.level1,
)
}
item("alt_surface") {
PickerSection(
title = "Alt surface",
isAltSurface = true,
background = TangemTheme.colors2.surface.level2,
)
}
item("segment_count") {
SegmentCountSection()
}
}
}
@Composable
private fun PickerSection(title: String, isAltSurface: Boolean, background: Color) {
Column(
verticalArrangement = Arrangement.spacedBy(12.dp),
modifier = Modifier
.fillMaxWidth()
.background(background)
.padding(horizontal = 16.dp, vertical = 12.dp),
) {
Text(
text = title,
style = TangemTheme.typography.subtitle1,
color = TangemTheme.colors.text.primary1,
)
configs.forEach { config ->
PickerRow(config = config, isAltSurface = isAltSurface)
}
}
HorizontalDivider(
color = TangemTheme.colors2.border.neutral.secondary,
modifier = Modifier.padding(horizontal = 16.dp),
)
}
@Composable
private fun PickerRow(config: PickerConfig, isAltSurface: Boolean) {
Column(verticalArrangement = Arrangement.spacedBy(6.dp)) {
Text(
text = config.label,
style = TangemTheme.typography.body2,
color = TangemTheme.colors.text.secondary,
)
TangemSegmentedPicker(
items = items4,
hasSeparator = config.hasSeparator,
isFixed = config.isFixed,
isAltSurface = isAltSurface,
onClick = {},
modifier = if (config.isFixed) Modifier.fillMaxWidth() else Modifier,
)
}
}
@Composable
private fun SegmentCountSection() {
Column(
verticalArrangement = Arrangement.spacedBy(12.dp),
modifier = Modifier
.fillMaxWidth()
.background(TangemTheme.colors2.surface.level1)
.padding(horizontal = 16.dp, vertical = 12.dp),
) {
Text(
text = "Segment count",
style = TangemTheme.typography.subtitle1,
color = TangemTheme.colors.text.primary1,
)
SegmentCountRow(label = "2 segments", items = items2)
SegmentCountRow(label = "3 segments", items = items3)
SegmentCountRow(label = "4 segments", items = items4)
SegmentCountRow(label = "5 segments", items = items5)
}
HorizontalDivider(
color = TangemTheme.colors2.border.neutral.secondary,
modifier = Modifier.padding(horizontal = 16.dp),
)
}
@Composable
private fun SegmentCountRow(label: String, items: ImmutableList<TangemSegmentUM>) {
Column(verticalArrangement = Arrangement.spacedBy(6.dp)) {
Text(
text = label,
style = TangemTheme.typography.body2,
color = TangemTheme.colors.text.secondary,
)
TangemSegmentedPicker(
items = items,
isFixed = true,
onClick = {},
modifier = Modifier.fillMaxWidth(),
)
}
}

View file

@ -0,0 +1,16 @@
@file:Suppress("MagicNumber", "LongMethod")
package com.tangem.feature.tester.presentation.storybook.page.tokenrow
import com.tangem.feature.tester.presentation.storybook.entity.TangemTokenRowStory
import com.tangem.feature.tester.presentation.storybook.viewmodel.StateUpdater
import com.tangem.feature.tester.presentation.storybook.viewmodel.storyPageFactory
internal fun StateUpdater<TangemTokenRowStory>.build(): TangemTokenRowStory {
return TangemTokenRowStory(
isBalanceHidden = false,
onBalanceHiddenToggle = { updateStory { it.copy(isBalanceHidden = !it.isBalanceHidden) } },
)
}
internal val tangemTokenRowStoryFactory
get() = storyPageFactory(StateUpdater<TangemTokenRowStory>::build)

View file

@ -0,0 +1,72 @@
@file:Suppress("MagicNumber", "LongMethod")
package com.tangem.feature.tester.presentation.storybook.page.tokenrow
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Switch
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.ds.row.token.TangemTokenRow
import com.tangem.core.ui.ds.row.token.TangemTokenRow_PreviewProvider
import com.tangem.core.ui.res.TangemTheme
import com.tangem.feature.tester.presentation.storybook.entity.TangemTokenRowStory
@Composable
internal fun TangemTokenRowStory(state: TangemTokenRowStory, modifier: Modifier = Modifier) {
val rows = remember { TangemTokenRow_PreviewProvider().values.toList() }
LazyColumn(
contentPadding = PaddingValues(bottom = 16.dp),
modifier = modifier
.statusBarsPadding()
.fillMaxSize()
.background(TangemTheme.colors2.surface.level1),
) {
stickyHeader("balance_toggle") {
BalanceToggle(
isHidden = state.isBalanceHidden,
onToggle = state.onBalanceHiddenToggle,
modifier = Modifier
.fillMaxWidth()
.background(TangemTheme.colors2.surface.level1)
.padding(horizontal = 16.dp, vertical = 8.dp),
)
}
items(rows, key = { it.id }) { um ->
TangemTokenRow(
tokenRowUM = um,
isBalanceHidden = state.isBalanceHidden,
reorderableTokenListState = null,
modifier = Modifier.background(TangemTheme.colors2.surface.level1),
)
HorizontalDivider(
color = TangemTheme.colors2.border.neutral.secondary,
modifier = Modifier.padding(start = 16.dp),
)
}
}
}
@Composable
private fun BalanceToggle(isHidden: Boolean, onToggle: () -> Unit, modifier: Modifier = Modifier) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween,
modifier = modifier,
) {
Text(
text = "isBalanceHidden",
style = TangemTheme.typography.body2,
color = TangemTheme.colors.text.secondary,
)
Switch(checked = isHidden, onCheckedChange = { onToggle() })
}
}

View file

@ -16,13 +16,25 @@ import com.tangem.core.ui.res.TangemTheme
import com.tangem.feature.tester.presentation.storybook.entity.StoryBookUM
import com.tangem.feature.tester.presentation.storybook.entity.StoryPageFactory
import com.tangem.feature.tester.presentation.storybook.page.background.northernLightsStoryFactory
import com.tangem.feature.tester.presentation.storybook.page.badge.tangemBadgeStoryFactory
import com.tangem.feature.tester.presentation.storybook.page.buttons.buttonsStoryFactory
import com.tangem.feature.tester.presentation.storybook.page.opportunities.opportunitiesBGStoryFactory
import com.tangem.feature.tester.presentation.storybook.page.message.tangemMessageStoryFactory
import com.tangem.feature.tester.presentation.storybook.page.checkbox.tangemCheckboxStoryFactory
import com.tangem.feature.tester.presentation.storybook.page.tabs.tangemSegmentedPickerStoryFactory
import com.tangem.feature.tester.presentation.storybook.page.tokenrow.tangemTokenRowStoryFactory
private data class StoryItem(val title: String, val factory: StoryPageFactory)
private fun buildStories() = listOf(
StoryItem(title = "🔘 Buttons", factory = buttonsStoryFactory),
StoryItem(title = "🏷️ Badge", factory = tangemBadgeStoryFactory),
StoryItem(title = "✨ Opportunities BG", factory = opportunitiesBGStoryFactory),
StoryItem(title = "🌌 Northern Lights Background", factory = northernLightsStoryFactory),
StoryItem(title = "💬 Message", factory = tangemMessageStoryFactory),
StoryItem(title = "🗂️ Segmented Picker", factory = tangemSegmentedPickerStoryFactory),
StoryItem(title = "☑️ Checkbox", factory = tangemCheckboxStoryFactory),
StoryItem(title = "🪙 Token Row", factory = tangemTokenRowStoryFactory),
)
@Composable

View file

@ -6,10 +6,22 @@ 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.NorthernLightsStory
import com.tangem.feature.tester.presentation.storybook.entity.OpportunitiesBGStory
import com.tangem.feature.tester.presentation.storybook.entity.TangemBadgeStory
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.TangemMessageStory
import com.tangem.feature.tester.presentation.storybook.entity.TangemSegmentedPickerStory
import com.tangem.feature.tester.presentation.storybook.entity.TangemTokenRowStory
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.opportunities.OpportunitiesBGStory
import com.tangem.feature.tester.presentation.storybook.page.checkbox.TangemCheckboxStory
import com.tangem.feature.tester.presentation.storybook.page.message.TangemMessageStory
import com.tangem.feature.tester.presentation.storybook.page.tabs.TangemSegmentedPickerStory
import com.tangem.feature.tester.presentation.storybook.page.tokenrow.TangemTokenRowStory
@Composable
internal fun StoryBookScreen(state: StoryBookUM, modifier: Modifier = Modifier) {
@ -17,12 +29,19 @@ internal fun StoryBookScreen(state: StoryBookUM, modifier: Modifier = Modifier)
AnimatedContent(
targetState = state.currentPage,
contentKey = { it::class },
modifier = modifier,
) { storyState ->
when (storyState) {
StoryList -> StoryBookListScreen(state = state)
is NorthernLightsStory -> NorthernLightsStory(state = storyState)
ButtonsStory -> ButtonsStory()
is TangemBadgeStory -> TangemBadgeStory(state = storyState)
OpportunitiesBGStory -> OpportunitiesBGStory()
is TangemMessageStory -> TangemMessageStory(state = storyState)
is TangemCheckboxStory -> TangemCheckboxStory(state = storyState)
TangemSegmentedPickerStory -> TangemSegmentedPickerStory()
is TangemTokenRowStory -> TangemTokenRowStory(state = storyState)
}
}
}