Updated on 2026-08-14

This commit is contained in:
Tangem 2025-12-23 15:19:26 +05:00
parent 6f095384c1
commit 4b07ed004f
8 changed files with 999 additions and 16 deletions

View file

@ -0,0 +1,125 @@
package com.tangem.core.ui.ds.button
import android.content.res.Configuration
import androidx.annotation.DrawableRes
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.R
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreviewRedesign
/**
* [Accent Tangem button](https://www.figma.com/design/RU7AIgwHtGdMfy83T5UOoR/Core-Library?node-id=8004-26798)
*
* @param onClick Lambda to be invoked when the button is clicked.
* @param modifier Modifier to be applied to the button.
* @param text TextReference for the button label.
* @param iconRes Drawable resource ID for the icon to be displayed in the button.
* @param iconPosition Position of the icon (Start or End).
* @param enabled Boolean indicating whether the button is enabled.
* @param size TangemButtonSize defining the size of the button.
* @param state TangemButtonState defining the current state of the button.
* @param shape TangemButtonShape defining the shape of the button.
*
[REDACTED_AUTHOR]
*/
@Composable
fun AccentTangemButton(
onClick: () -> Unit,
modifier: Modifier = Modifier,
text: TextReference? = null,
@DrawableRes iconRes: Int? = null,
iconPosition: TangemButtonIconPosition = TangemButtonIconPosition.Start,
enabled: Boolean = true,
size: TangemButtonSize = TangemButtonSize.X15,
state: TangemButtonState = TangemButtonState.Default,
shape: TangemButtonShape = TangemButtonShape.Default,
) {
TangemButtonInternal(
onClick = onClick,
modifier = modifier
.clip(shape.toShape(size))
.then(
when (state) {
TangemButtonState.Disabled,
TangemButtonState.Default,
-> Modifier.background(TangemTheme.colors2.button.backgroundPositive)
TangemButtonState.Loading,
TangemButtonState.Pressed,
-> Modifier
.background(TangemTheme.colors2.button.backgroundPositive)
.background(TangemTheme.colors2.overlay.overlaySecondary)
},
),
text = text,
contentColor = TangemTheme.colors2.text.neutral.primaryInvertedConstant,
iconRes = iconRes,
enabled = enabled,
size = size,
state = state,
iconPosition = iconPosition,
)
}
// region Preview
@Composable
@Preview(showBackground = true, widthDp = 480)
@Preview(showBackground = true, widthDp = 480, uiMode = Configuration.UI_MODE_NIGHT_YES)
private fun AccentTangemButton_Preview(
@PreviewParameter(AccentTangemButtonPreviewProvider::class) params: TangemButtonState,
) {
TangemThemePreviewRedesign {
Row(
horizontalArrangement = Arrangement.spacedBy(21.dp),
modifier = Modifier
.background(TangemTheme.colors2.surface.level1)
.padding(8.dp),
) {
repeat(4) { yIndex ->
val shape = if (yIndex < 2) TangemButtonShape.Default else TangemButtonShape.Rounded
val text = if (yIndex % 2 == 1) null else stringReference("Button")
Column(verticalArrangement = Arrangement.spacedBy(10.dp)) {
repeat(2) { xIndex ->
val iconPosition = if (xIndex == 1) {
TangemButtonIconPosition.Start
} else {
TangemButtonIconPosition.End
}
AccentTangemButton(
onClick = {},
text = text,
size = TangemButtonSize.X15,
shape = shape,
iconPosition = iconPosition,
iconRes = R.drawable.ic_tangem_24,
state = params,
)
}
}
}
}
}
}
private class AccentTangemButtonPreviewProvider : PreviewParameterProvider<TangemButtonState> {
override val values: Sequence<TangemButtonState>
get() = sequenceOf(
TangemButtonState.Default,
TangemButtonState.Pressed,
TangemButtonState.Loading,
TangemButtonState.Disabled,
)
}
// endregion

View file

@ -0,0 +1,111 @@
package com.tangem.core.ui.ds.button
import android.content.res.Configuration
import androidx.annotation.DrawableRes
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.R
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreviewRedesign
/**
* [Ghost Tangem button](https://www.figma.com/design/RU7AIgwHtGdMfy83T5UOoR/Core-Library?node-id=5854-4804)
*
* @param onClick Lambda to be invoked when the button is clicked.
* @param modifier Modifier to be applied to the button.
* @param text TextReference for the button label.
* @param iconRes Drawable resource ID for the icon to be displayed in the button.
* @param iconPosition Position of the icon (Start or End).
* @param enabled Boolean indicating whether the button is enabled.
* @param size TangemButtonSize defining the size of the button.
* @param state TangemButtonState defining the current state of the button.
*
[REDACTED_AUTHOR]
*/
@Composable
fun GhostTangemButton(
onClick: () -> Unit,
modifier: Modifier = Modifier,
text: TextReference? = null,
@DrawableRes iconRes: Int? = null,
enabled: Boolean = true,
size: TangemButtonSize = TangemButtonSize.X15,
state: TangemButtonState = TangemButtonState.Default,
iconPosition: TangemButtonIconPosition = TangemButtonIconPosition.Start,
) {
val contentColor = when (state) {
TangemButtonState.Disabled -> TangemTheme.colors2.text.status.disabled
else -> TangemTheme.colors2.text.neutral.primary
}
TangemButtonInternal(
onClick = onClick,
modifier = modifier,
text = text,
contentColor = contentColor,
enabled = enabled,
size = size,
state = state,
iconPosition = iconPosition,
iconRes = iconRes,
)
}
// region Preview
@Composable
@Preview(showBackground = true, widthDp = 480)
@Preview(showBackground = true, widthDp = 480, uiMode = Configuration.UI_MODE_NIGHT_YES)
private fun GhostTangemButton_Preview(
@PreviewParameter(GhostTangemButtonPreviewProvider::class) params: TangemButtonState,
) {
TangemThemePreviewRedesign {
Row(
horizontalArrangement = Arrangement.spacedBy(21.dp),
modifier = Modifier
.background(TangemTheme.colors2.surface.level1)
.padding(8.dp),
) {
repeat(4) { yIndex ->
val text = if (yIndex % 2 == 1) null else stringReference("Button")
Column(verticalArrangement = Arrangement.spacedBy(10.dp)) {
repeat(2) { xIndex ->
val iconPosition = if (xIndex == 1) {
TangemButtonIconPosition.Start
} else {
TangemButtonIconPosition.End
}
GhostTangemButton(
onClick = {},
text = text,
size = TangemButtonSize.X15,
iconPosition = iconPosition,
iconRes = R.drawable.ic_tangem_24,
state = params,
)
}
}
}
}
}
}
private class GhostTangemButtonPreviewProvider : PreviewParameterProvider<TangemButtonState> {
override val values: Sequence<TangemButtonState>
get() = sequenceOf(
TangemButtonState.Default,
TangemButtonState.Pressed,
TangemButtonState.Loading,
TangemButtonState.Disabled,
)
}
// endregion

View file

@ -0,0 +1,132 @@
package com.tangem.core.ui.ds.button
import android.content.res.Configuration
import androidx.annotation.DrawableRes
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.R
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreviewRedesign
/**
* [Outline Tangem button](https://www.figma.com/design/RU7AIgwHtGdMfy83T5UOoR/Core-Library?node-id=5854-4800)
*
* @param onClick Lambda to be invoked when the button is clicked.
* @param modifier Modifier to be applied to the button.
* @param text TextReference for the button label.
* @param iconRes Drawable resource ID for the icon to be displayed in the button.
* @param iconPosition Position of the icon (Start or End).
* @param enabled Boolean indicating whether the button is enabled.
* @param size TangemButtonSize defining the size of the button.
* @param state TangemButtonState defining the current state of the button.
* @param shape TangemButtonShape defining the shape of the button.
*
[REDACTED_AUTHOR]
*/
@Composable
fun OutlineTangemButton(
onClick: () -> Unit,
modifier: Modifier = Modifier,
text: TextReference? = null,
@DrawableRes iconRes: Int? = null,
iconPosition: TangemButtonIconPosition = TangemButtonIconPosition.Start,
enabled: Boolean = true,
size: TangemButtonSize = TangemButtonSize.X15,
state: TangemButtonState = TangemButtonState.Default,
shape: TangemButtonShape = TangemButtonShape.Default,
) {
val backgroundModifier = when (state) {
TangemButtonState.Loading,
TangemButtonState.Pressed,
TangemButtonState.Disabled,
TangemButtonState.Default,
-> Modifier
.background(TangemTheme.colors2.surface.level1)
.border(
width = 1.dp,
color = TangemTheme.colors2.border.neutral.primary,
shape = shape.toShape(size),
)
}
val contentColor = when (state) {
TangemButtonState.Disabled -> TangemTheme.colors2.text.status.disabled
else -> TangemTheme.colors2.text.neutral.primary
}
TangemButtonInternal(
onClick = onClick,
modifier = modifier
.clip(shape.toShape(size))
.then(backgroundModifier),
text = text,
contentColor = contentColor,
iconRes = iconRes,
enabled = enabled,
size = size,
state = state,
iconPosition = iconPosition,
)
}
// region Preview
@Composable
@Preview(showBackground = true, widthDp = 480)
@Preview(showBackground = true, widthDp = 480, uiMode = Configuration.UI_MODE_NIGHT_YES)
private fun OutlineTangemButton_Preview(
@PreviewParameter(OutlineTangemButtonPreviewProvider::class) params: TangemButtonState,
) {
TangemThemePreviewRedesign {
Row(
horizontalArrangement = Arrangement.spacedBy(21.dp),
modifier = Modifier
.background(TangemTheme.colors2.surface.level1)
.padding(8.dp),
) {
repeat(4) { yIndex ->
val shape = if (yIndex < 2) TangemButtonShape.Default else TangemButtonShape.Rounded
val text = if (yIndex % 2 == 1) null else stringReference("Button")
Column(verticalArrangement = Arrangement.spacedBy(10.dp)) {
repeat(2) { xIndex ->
val iconPosition = if (xIndex == 0) {
TangemButtonIconPosition.Start
} else {
TangemButtonIconPosition.End
}
OutlineTangemButton(
onClick = {},
text = text,
size = TangemButtonSize.X15,
shape = shape,
iconPosition = iconPosition,
iconRes = R.drawable.ic_tangem_24,
state = params,
)
}
}
}
}
}
}
private class OutlineTangemButtonPreviewProvider : PreviewParameterProvider<TangemButtonState> {
override val values: Sequence<TangemButtonState>
get() = sequenceOf(
TangemButtonState.Default,
TangemButtonState.Pressed,
TangemButtonState.Loading,
TangemButtonState.Disabled,
)
}
// endregion

View file

@ -0,0 +1,127 @@
package com.tangem.core.ui.ds.button
import android.content.res.Configuration
import androidx.annotation.DrawableRes
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.R
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreviewRedesign
/**
* [Primary Inverse Tangem button](https://www.figma.com/design/RU7AIgwHtGdMfy83T5UOoR/Core-Library?node-id=7545-78314)
*
* @param onClick Lambda to be invoked when the button is clicked.
* @param modifier Modifier to be applied to the button.
* @param text TextReference for the button label.
* @param iconRes Drawable resource ID for the icon to be displayed in the button.
* @param iconPosition Position of the icon (Start or End).
* @param enabled Boolean indicating whether the button is enabled.
* @param size TangemButtonSize defining the size of the button.
* @param state TangemButtonState defining the current state of the button.
* @param shape TangemButtonShape defining the shape of the button.
*
[REDACTED_AUTHOR]
*/
@Composable
fun PrimaryInverseTangemButton(
onClick: () -> Unit,
modifier: Modifier = Modifier,
text: TextReference? = null,
@DrawableRes iconRes: Int? = null,
iconPosition: TangemButtonIconPosition = TangemButtonIconPosition.Start,
enabled: Boolean = true,
size: TangemButtonSize = TangemButtonSize.X15,
state: TangemButtonState = TangemButtonState.Default,
shape: TangemButtonShape = TangemButtonShape.Default,
) {
val backgroundModifier = when (state) {
TangemButtonState.Default -> Modifier.background(TangemTheme.colors2.button.backgroundPrimaryInverse)
TangemButtonState.Disabled -> Modifier.background(TangemTheme.colors2.button.backgroundDisabled)
TangemButtonState.Loading,
TangemButtonState.Pressed,
-> Modifier
.background(TangemTheme.colors2.button.backgroundPrimaryInverse)
.background(TangemTheme.colors2.overlay.overlayPrimary)
}
val contentColor = when (state) {
TangemButtonState.Disabled -> TangemTheme.colors2.text.status.disabled
else -> TangemTheme.colors2.text.neutral.primary
}
TangemButtonInternal(
onClick = onClick,
modifier = modifier
.clip(shape.toShape(size))
.then(backgroundModifier),
text = text,
contentColor = contentColor,
enabled = enabled,
size = size,
state = state,
iconPosition = iconPosition,
iconRes = iconRes,
)
}
// region Preview
@Composable
@Preview(showBackground = true, widthDp = 480)
@Preview(showBackground = true, widthDp = 480, uiMode = Configuration.UI_MODE_NIGHT_YES)
private fun PrimaryInverseTangemButton_Preview(
@PreviewParameter(PrimaryInverseTangemButtonPreviewProvider::class) params: TangemButtonState,
) {
TangemThemePreviewRedesign {
Row(
horizontalArrangement = Arrangement.spacedBy(21.dp),
modifier = Modifier
.background(TangemTheme.colors2.surface.level2)
.padding(8.dp),
) {
repeat(4) { yIndex ->
val shape = if (yIndex < 2) TangemButtonShape.Default else TangemButtonShape.Rounded
val text = if (yIndex % 2 == 1) null else stringReference("Button")
Column(verticalArrangement = Arrangement.spacedBy(10.dp)) {
repeat(2) { xIndex ->
val iconPosition = if (xIndex == 0) {
TangemButtonIconPosition.Start
} else {
TangemButtonIconPosition.End
}
PrimaryInverseTangemButton(
onClick = {},
text = text,
size = TangemButtonSize.X15,
shape = shape,
iconPosition = iconPosition,
iconRes = R.drawable.ic_tangem_24,
state = params,
)
}
}
}
}
}
}
private class PrimaryInverseTangemButtonPreviewProvider : PreviewParameterProvider<TangemButtonState> {
override val values: Sequence<TangemButtonState>
get() = sequenceOf(
TangemButtonState.Default,
TangemButtonState.Pressed,
TangemButtonState.Loading,
TangemButtonState.Disabled,
)
}
// endregion

View file

@ -0,0 +1,122 @@
package com.tangem.core.ui.ds.button
import android.content.res.Configuration
import androidx.annotation.DrawableRes
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.R
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreviewRedesign
/**
* [Primary Tangem button](https://www.figma.com/design/RU7AIgwHtGdMfy83T5UOoR/Core-Library?node-id=5854-4732&t=euYo1qCxPlQl3Fa6-4)
*
* @param onClick Lambda to be invoked when the button is clicked.
* @param modifier Modifier to be applied to the button.
* @param text TextReference for the button label.
* @param iconRes Drawable resource ID for the icon to be displayed in the button.
* @param iconPosition Position of the icon (Start or End).
* @param enabled Boolean indicating whether the button is enabled.
* @param size TangemButtonSize defining the size of the button.
* @param state TangemButtonState defining the current state of the button.
* @param shape TangemButtonShape defining the shape of the button.
*
[REDACTED_AUTHOR]
*/
@Composable
fun PrimaryTangemButton(
onClick: () -> Unit,
modifier: Modifier = Modifier,
text: TextReference? = null,
@DrawableRes iconRes: Int? = null,
iconPosition: TangemButtonIconPosition = TangemButtonIconPosition.Start,
enabled: Boolean = true,
size: TangemButtonSize = TangemButtonSize.X15,
state: TangemButtonState = TangemButtonState.Default,
shape: TangemButtonShape = TangemButtonShape.Default,
) {
val backgroundModifier = when (state) {
TangemButtonState.Loading,
TangemButtonState.Default,
-> Modifier.background(TangemTheme.colors2.button.backgroundPrimary)
TangemButtonState.Disabled -> Modifier.background(TangemTheme.colors2.button.backgroundDisabled)
TangemButtonState.Pressed -> Modifier
.background(TangemTheme.colors2.button.backgroundPrimary)
.background(TangemTheme.colors2.overlay.overlaySecondary)
}
val contentColor = when (state) {
TangemButtonState.Disabled -> TangemTheme.colors2.text.status.disabled
else -> TangemTheme.colors2.text.neutral.primaryInverted
}
TangemButtonInternal(
onClick = onClick,
modifier = modifier
.clip(shape.toShape(size))
.then(backgroundModifier),
text = text,
contentColor = contentColor,
enabled = enabled,
size = size,
state = state,
iconPosition = iconPosition,
iconRes = iconRes,
)
}
// region Preview
@Composable
@Preview(showBackground = true, widthDp = 480)
@Preview(showBackground = true, widthDp = 480, uiMode = Configuration.UI_MODE_NIGHT_YES)
private fun PrimaryTangemButton_Preview(
@PreviewParameter(PrimaryTangemButtonPreviewProvider::class) params: TangemButtonState,
) {
TangemThemePreviewRedesign {
Row(
horizontalArrangement = Arrangement.spacedBy(21.dp),
modifier = Modifier
.background(TangemTheme.colors2.surface.level1)
.padding(8.dp),
) {
repeat(4) { yIndex ->
val shape = if (yIndex < 2) TangemButtonShape.Default else TangemButtonShape.Rounded
val text = if (yIndex % 2 == 1) null else stringReference("Button")
Column(verticalArrangement = Arrangement.spacedBy(10.dp)) {
repeat(TangemButtonIconPosition.entries.size) { xIndex ->
PrimaryTangemButton(
onClick = {},
text = text,
size = TangemButtonSize.X15,
shape = shape,
iconPosition = TangemButtonIconPosition.entries[xIndex],
iconRes = R.drawable.ic_tangem_24,
state = params,
)
}
}
}
}
}
}
private class PrimaryTangemButtonPreviewProvider : PreviewParameterProvider<TangemButtonState> {
override val values: Sequence<TangemButtonState>
get() = sequenceOf(
TangemButtonState.Default,
TangemButtonState.Pressed,
TangemButtonState.Loading,
TangemButtonState.Disabled,
)
}
// endregion

View file

@ -0,0 +1,125 @@
package com.tangem.core.ui.ds.button
import android.content.res.Configuration
import androidx.annotation.DrawableRes
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.R
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreviewRedesign
/**
* [Secondary Tangem button](https://www.figma.com/design/RU7AIgwHtGdMfy83T5UOoR/Core-Library?node-id=5854-4796)
*
* @param onClick Lambda to be invoked when the button is clicked.
* @param modifier Modifier to be applied to the button.
* @param text TextReference for the button label.
* @param iconRes Drawable resource ID for the icon to be displayed in the button.
* @param iconPosition Position of the icon (Start or End).
* @param enabled Boolean indicating whether the button is enabled.
* @param size TangemButtonSize defining the size of the button.
* @param state TangemButtonState defining the current state of the button.
* @param shape TangemButtonShape defining the shape of the button.
*
[REDACTED_AUTHOR]
*/
@Composable
fun SecondaryTangemButton(
onClick: () -> Unit,
modifier: Modifier = Modifier,
text: TextReference? = null,
@DrawableRes iconRes: Int? = null,
iconPosition: TangemButtonIconPosition = TangemButtonIconPosition.Start,
enabled: Boolean = true,
size: TangemButtonSize = TangemButtonSize.X15,
state: TangemButtonState = TangemButtonState.Default,
shape: TangemButtonShape = TangemButtonShape.Default,
) {
val backgroundModifier = when (state) {
TangemButtonState.Loading,
TangemButtonState.Default,
-> Modifier.background(TangemTheme.colors2.button.backgroundSecondary)
TangemButtonState.Disabled -> Modifier.background(TangemTheme.colors2.button.backgroundDisabled)
TangemButtonState.Pressed -> Modifier.background(TangemTheme.colors2.overlay.overlayPrimary)
}
val contentColor = when (state) {
TangemButtonState.Disabled -> TangemTheme.colors2.text.status.disabled
else -> TangemTheme.colors2.text.neutral.primary
}
TangemButtonInternal(
onClick = onClick,
modifier = modifier
.clip(shape.toShape(size))
.then(backgroundModifier),
text = text,
contentColor = contentColor,
iconRes = iconRes,
enabled = enabled,
size = size,
state = state,
iconPosition = iconPosition,
)
}
// region Preview
@Composable
@Preview(showBackground = true, widthDp = 480)
@Preview(showBackground = true, widthDp = 480, uiMode = Configuration.UI_MODE_NIGHT_YES)
private fun SecondaryTangemButton_Preview(
@PreviewParameter(SecondaryTangemButtonPreviewProvider::class) params: TangemButtonState,
) {
TangemThemePreviewRedesign {
Row(
horizontalArrangement = Arrangement.spacedBy(21.dp),
modifier = Modifier
.background(TangemTheme.colors2.surface.level1)
.padding(8.dp),
) {
repeat(4) { yIndex ->
val shape = if (yIndex < 2) TangemButtonShape.Default else TangemButtonShape.Rounded
val text = if (yIndex % 2 == 1) null else stringReference("Button")
Column(verticalArrangement = Arrangement.spacedBy(10.dp)) {
repeat(2) { xIndex ->
val iconPosition = if (xIndex == 0) {
TangemButtonIconPosition.Start
} else {
TangemButtonIconPosition.End
}
SecondaryTangemButton(
onClick = {},
text = text,
size = TangemButtonSize.X15,
shape = shape,
iconPosition = iconPosition,
iconRes = R.drawable.ic_tangem_24,
state = params,
)
}
}
}
}
}
}
private class SecondaryTangemButtonPreviewProvider : PreviewParameterProvider<TangemButtonState> {
override val values: Sequence<TangemButtonState>
get() = sequenceOf(
TangemButtonState.Default,
TangemButtonState.Pressed,
TangemButtonState.Loading,
TangemButtonState.Disabled,
)
}
// endregion

View file

@ -0,0 +1,256 @@
package com.tangem.core.ui.ds.button
import androidx.annotation.DrawableRes
import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.animateContentSize
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.TextAutoSize
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.ReadOnlyComposable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.StrokeCap
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
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.resolveReference
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.test.BaseButtonTestTags
/**
* A customizable button component that supports text, icons, and different states.
*
* @param onClick Lambda to be invoked when the button is clicked.
* @param modifier Modifier to be applied to the button.
* @param text TextReference for the button label.
* @param iconRes Drawable resource ID for the icon to be displayed in the button.
* @param iconPosition Position of the icon (Start or End).
* @param enabled Boolean indicating whether the button is enabled.
* @param contentColor Color of the button content (text and icon).
* @param size TangemButtonSize defining the size of the button.
* @param state TangemButtonState defining the current state of the button.
*
[REDACTED_AUTHOR]
*/
@Composable
internal fun TangemButtonInternal(
onClick: () -> Unit,
modifier: Modifier = Modifier,
text: TextReference? = null,
@DrawableRes iconRes: Int? = null,
iconPosition: TangemButtonIconPosition = TangemButtonIconPosition.Start,
enabled: Boolean = true,
contentColor: Color = TangemTheme.colors2.text.neutral.primary,
size: TangemButtonSize = TangemButtonSize.X15,
state: TangemButtonState = TangemButtonState.Default,
) {
Row(
modifier = modifier
.testTag(BaseButtonTestTags.BUTTON)
.height(size.toHeightDp())
.conditionalCompose(text == null) {
width(size.toHeightDp())
}
.clickableSingle(enabled = enabled, onClick = onClick, role = Role.Button)
.conditionalCompose(text != null) {
padding(horizontal = size.toPaddingDp())
}
.animateContentSize(),
horizontalArrangement = Arrangement.spacedBy(4.dp, Alignment.CenterHorizontally),
verticalAlignment = Alignment.CenterVertically,
) {
AnimatedVisibility(
visible = iconRes != null && iconPosition == TangemButtonIconPosition.Start,
modifier = Modifier.size(size = size.toContentSize()),
) {
val wrappedIconRes = remember(this) { requireNotNull(iconRes) }
TangemButtonIcon(iconRes = wrappedIconRes, state = state, iconColor = contentColor, size = size)
}
AnimatedVisibility(text != null && state != TangemButtonState.Loading) {
val wrappedText = remember(this) { requireNotNull(text) }
val textStyle = size.toTextStyle()
Text(
text = wrappedText.resolveReference(),
style = textStyle,
color = contentColor,
textAlign = TextAlign.Center,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
autoSize = TextAutoSize.StepBased(
minFontSize = 12.sp,
maxFontSize = textStyle.fontSize,
),
modifier = Modifier.testTag(BaseButtonTestTags.TEXT),
)
}
AnimatedVisibility(
visible = iconRes != null && iconPosition == TangemButtonIconPosition.End,
modifier = Modifier.size(size = size.toContentSize()),
) {
val wrappedIconRes = remember(this) { requireNotNull(iconRes) }
TangemButtonIcon(iconRes = wrappedIconRes, state = state, iconColor = contentColor, size = size)
}
}
}
@Composable
private fun TangemButtonIcon(
@DrawableRes iconRes: Int,
iconColor: Color,
state: TangemButtonState,
size: TangemButtonSize,
) {
AnimatedContent(state) { targetState ->
when (targetState) {
TangemButtonState.Loading -> CircularProgressIndicator(
color = iconColor,
strokeWidth = 2.dp,
strokeCap = StrokeCap.Round,
modifier = Modifier.padding(
when (size) {
TangemButtonSize.X7,
TangemButtonSize.X8,
TangemButtonSize.X9,
TangemButtonSize.X10,
-> 0.5.dp
TangemButtonSize.X12,
TangemButtonSize.X15,
-> 4.5.dp
},
),
)
else -> Icon(
painter = painterResource(id = iconRes),
contentDescription = null,
tint = iconColor,
)
}
}
}
/**
* Defines the shape of the Tangem button.
*/
enum class TangemButtonShape {
Default,
Rounded,
;
@ReadOnlyComposable
@Composable
internal fun toShape(size: TangemButtonSize) = RoundedCornerShape(
when (this) {
Default -> size.toShapeRadius()
Rounded -> 100.dp
},
)
}
/**
* Defines the size of the Tangem button.
*/
enum class TangemButtonSize {
X7,
X8,
X9,
X10,
X12,
X15,
;
@ReadOnlyComposable
@Composable
internal fun toHeightDp() = when (this) {
X7 -> TangemTheme.dimens2.x7
X8 -> TangemTheme.dimens2.x8
X9 -> TangemTheme.dimens2.x9
X10 -> TangemTheme.dimens2.x10
X12 -> TangemTheme.dimens2.x12
X15 -> TangemTheme.dimens2.x15
}
@ReadOnlyComposable
@Composable
internal fun toPaddingDp() = when (this) {
X7 -> TangemTheme.dimens2.x2
X8,
X9,
X10,
-> TangemTheme.dimens2.x3
X12,
X15,
-> TangemTheme.dimens2.x6
}
@ReadOnlyComposable
@Composable
internal fun toContentSize() = when (this) {
X7,
X8,
X9,
X10,
-> TangemTheme.dimens2.x5
X12,
X15,
-> TangemTheme.dimens2.x7
}
@ReadOnlyComposable
@Composable
internal fun toShapeRadius() = when (this) {
X7,
X8,
X9,
X10,
-> TangemTheme.dimens2.x2
X12 -> TangemTheme.dimens2.x3
X15 -> TangemTheme.dimens2.x4
}
@ReadOnlyComposable
@Composable
internal fun toTextStyle(): TextStyle = when (this) {
X7 -> TangemTheme.typography2.bodyRegular14
X8,
X9,
X10,
X12,
X15,
-> TangemTheme.typography2.bodySemibold16
}
}
/**
* Defines the state of the Tangem button.
*/
enum class TangemButtonState {
Default,
Disabled,
Pressed,
Loading,
}
/**
* Defines the position of the icon in the Tangem button.
*/
enum class TangemButtonIconPosition {
Start,
End,
}

View file

@ -54,23 +54,8 @@ fun Modifier.conditional(condition: Boolean, modifier: Modifier.() -> Modifier):
@Composable
fun Modifier.conditionalCompose(
condition: Boolean,
modifier: @Composable Modifier.() -> Modifier = { Modifier },
): Modifier {
return if (condition) {
then(modifier(Modifier))
} else {
this
}
}
/**
* Conditionally applies a modifier based on a boolean condition.
*/
@Composable
fun Modifier.conditionalCompose(
condition: Boolean,
modifier: @Composable Modifier.() -> Modifier = { Modifier },
otherModifier: @Composable Modifier.() -> Modifier = { this },
modifier: @Composable Modifier.() -> Modifier = { Modifier },
): Modifier {
return if (condition) {
then(modifier(Modifier))