Updated on 2026-08-14
This commit is contained in:
parent
5da322576b
commit
c037bec366
15 changed files with 782 additions and 116 deletions
|
|
@ -80,6 +80,7 @@ fun GhostTangemButton(
|
||||||
contentColor = contentColor,
|
contentColor = contentColor,
|
||||||
isEnabled = isEnabled,
|
isEnabled = isEnabled,
|
||||||
isLoading = isLoading,
|
isLoading = isLoading,
|
||||||
|
hasPadding = false,
|
||||||
size = size,
|
size = size,
|
||||||
iconPosition = iconPosition,
|
iconPosition = iconPosition,
|
||||||
iconRes = iconRes,
|
iconRes = iconRes,
|
||||||
|
|
|
||||||
|
|
@ -43,8 +43,9 @@ private const val LOADING_ANIMATION_DURATION = 150
|
||||||
* @param text TextReference for the button label.
|
* @param text TextReference for the button label.
|
||||||
* @param iconRes Drawable resource ID for the icon to be displayed in the button.
|
* @param iconRes Drawable resource ID for the icon to be displayed in the button.
|
||||||
* @param iconPosition Position of the icon (Start or End).
|
* @param iconPosition Position of the icon (Start or End).
|
||||||
* @param isEnabled Boolean indicating whether the button is enabled.
|
* @param isEnabled Boolean indicating whether the button is enabled.
|
||||||
* @param isLoading Boolean indicating whether the button is in a loading state.
|
* @param isLoading Boolean indicating whether the button is in a loading state.
|
||||||
|
* @param hasPadding Boolean indicating whether the button should have padding around its content.
|
||||||
* @param contentColor Color of the button content (text and icon).
|
* @param contentColor Color of the button content (text and icon).
|
||||||
* @param size TangemButtonSize defining the size of the button.
|
* @param size TangemButtonSize defining the size of the button.
|
||||||
*
|
*
|
||||||
|
|
@ -60,6 +61,7 @@ internal fun TangemButtonInternal(
|
||||||
iconPosition: TangemButtonIconPosition = TangemButtonIconPosition.Start,
|
iconPosition: TangemButtonIconPosition = TangemButtonIconPosition.Start,
|
||||||
isEnabled: Boolean = true,
|
isEnabled: Boolean = true,
|
||||||
isLoading: Boolean = false,
|
isLoading: Boolean = false,
|
||||||
|
hasPadding: Boolean = true,
|
||||||
contentColor: Color = TangemTheme.colors2.text.neutral.primary,
|
contentColor: Color = TangemTheme.colors2.text.neutral.primary,
|
||||||
size: TangemButtonSize = TangemButtonSize.X15,
|
size: TangemButtonSize = TangemButtonSize.X15,
|
||||||
) {
|
) {
|
||||||
|
|
@ -72,7 +74,7 @@ internal fun TangemButtonInternal(
|
||||||
.conditionalCompose(text == null) {
|
.conditionalCompose(text == null) {
|
||||||
width(size.toHeightDp())
|
width(size.toHeightDp())
|
||||||
}
|
}
|
||||||
.conditionalCompose(text != null) {
|
.conditionalCompose(text != null && hasPadding) {
|
||||||
padding(size.toPaddingDp())
|
padding(size.toPaddingDp())
|
||||||
}
|
}
|
||||||
.animateContentSize(),
|
.animateContentSize(),
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,346 @@
|
||||||
|
package com.tangem.core.ui.ds.field.search
|
||||||
|
|
||||||
|
import android.content.res.Configuration
|
||||||
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
|
import androidx.compose.animation.core.animateFloatAsState
|
||||||
|
import androidx.compose.animation.core.tween
|
||||||
|
import androidx.compose.animation.expandHorizontally
|
||||||
|
import androidx.compose.animation.shrinkHorizontally
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
|
import androidx.compose.foundation.interaction.collectIsFocusedAsState
|
||||||
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.foundation.text.BasicTextField
|
||||||
|
import androidx.compose.foundation.text.KeyboardActions
|
||||||
|
import androidx.compose.foundation.text.KeyboardOptions
|
||||||
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.ripple
|
||||||
|
import androidx.compose.runtime.*
|
||||||
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.BiasAlignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.alpha
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.focus.FocusManager
|
||||||
|
import androidx.compose.ui.focus.FocusRequester
|
||||||
|
import androidx.compose.ui.focus.focusRequester
|
||||||
|
import androidx.compose.ui.focus.onFocusChanged
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.graphics.Shape
|
||||||
|
import androidx.compose.ui.graphics.SolidColor
|
||||||
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
|
import androidx.compose.ui.platform.LocalFocusManager
|
||||||
|
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
|
||||||
|
import androidx.compose.ui.platform.SoftwareKeyboardController
|
||||||
|
import androidx.compose.ui.platform.testTag
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
|
import androidx.compose.ui.res.vectorResource
|
||||||
|
import androidx.compose.ui.text.input.ImeAction
|
||||||
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
|
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.util.fastForEach
|
||||||
|
import com.tangem.core.ui.R
|
||||||
|
import com.tangem.core.ui.components.fields.entity.SearchBarUM
|
||||||
|
import com.tangem.core.ui.ds.button.GhostTangemButton
|
||||||
|
import com.tangem.core.ui.ds.button.TangemButtonSize
|
||||||
|
import com.tangem.core.ui.extensions.resolveReference
|
||||||
|
import com.tangem.core.ui.extensions.resourceReference
|
||||||
|
import com.tangem.core.ui.extensions.stringReference
|
||||||
|
import com.tangem.core.ui.res.TangemTheme
|
||||||
|
import com.tangem.core.ui.res.TangemThemePreviewRedesign
|
||||||
|
import com.tangem.core.ui.test.BaseSearchBarTestTags
|
||||||
|
import com.tangem.core.ui.test.SearchBarTestTags
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shape options for [TangemSearchField].
|
||||||
|
*/
|
||||||
|
enum class TangemFieldShape {
|
||||||
|
RoundedCorners,
|
||||||
|
Circle,
|
||||||
|
;
|
||||||
|
|
||||||
|
@ReadOnlyComposable
|
||||||
|
@Composable
|
||||||
|
internal fun toShape(): Shape {
|
||||||
|
return when (this) {
|
||||||
|
RoundedCorners -> RoundedCornerShape(TangemTheme.dimens2.x4)
|
||||||
|
Circle -> CircleShape
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom search field component that provides a user-friendly interface for searching and filtering content.
|
||||||
|
*
|
||||||
|
* [Figma](https://www.figma.com/design/RU7AIgwHtGdMfy83T5UOoR/Core-Library?node-id=8445-19217&m=dev)
|
||||||
|
*
|
||||||
|
* @param state The state of the search field, including the current query, placeholder text, and active status.
|
||||||
|
* @param shape The shape of the search field, which can be either rounded corners or a circle.
|
||||||
|
* @param modifier The modifier to be applied to the search field.
|
||||||
|
* @param enabled Whether the search field is enabled or not.
|
||||||
|
* @param focusRequester The [FocusRequester] used to request focus on the search field when it becomes active.
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
fun TangemSearchField(
|
||||||
|
state: SearchBarUM,
|
||||||
|
shape: TangemFieldShape,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
enabled: Boolean = true,
|
||||||
|
focusRequester: FocusRequester = remember { FocusRequester() },
|
||||||
|
) {
|
||||||
|
val keyboardController = LocalSoftwareKeyboardController.current
|
||||||
|
val focusManager = LocalFocusManager.current
|
||||||
|
val interactionSource = remember { MutableInteractionSource() }
|
||||||
|
var isInitialComposition by rememberSaveable { mutableStateOf(true) }
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
isInitialComposition = false
|
||||||
|
}
|
||||||
|
BasicTextField(
|
||||||
|
modifier = modifier
|
||||||
|
.heightIn(min = TangemTheme.dimens2.x11)
|
||||||
|
.onFocusChanged { focusState ->
|
||||||
|
if (!isInitialComposition) {
|
||||||
|
if (focusState.isFocused) {
|
||||||
|
state.onActiveChange(true)
|
||||||
|
} else {
|
||||||
|
state.onActiveChange(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.focusRequester(focusRequester)
|
||||||
|
.testTag(BaseSearchBarTestTags.SEARCH_BAR),
|
||||||
|
enabled = enabled,
|
||||||
|
value = state.query,
|
||||||
|
onValueChange = state.onQueryChange,
|
||||||
|
interactionSource = interactionSource,
|
||||||
|
keyboardOptions = KeyboardOptions(
|
||||||
|
imeAction = ImeAction.Search,
|
||||||
|
),
|
||||||
|
keyboardActions = KeyboardActions(
|
||||||
|
onSearch = {
|
||||||
|
keyboardController?.hide()
|
||||||
|
focusManager.clearFocus()
|
||||||
|
},
|
||||||
|
),
|
||||||
|
singleLine = true,
|
||||||
|
maxLines = 1,
|
||||||
|
textStyle = TangemTheme.typography2.bodySemibold16.copy(
|
||||||
|
color = TangemTheme.colors2.text.neutral.primary,
|
||||||
|
),
|
||||||
|
cursorBrush = SolidColor(TangemTheme.colors2.graphic.neutral.primary),
|
||||||
|
decorationBox = @Composable { innerTextField ->
|
||||||
|
DecorationBox(
|
||||||
|
state = state,
|
||||||
|
shape = shape,
|
||||||
|
innerTextField = innerTextField,
|
||||||
|
focusManager = focusManager,
|
||||||
|
interactionSource = interactionSource,
|
||||||
|
color = TangemTheme.colors2.field.backgroundDefault,
|
||||||
|
keyboardController = keyboardController,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("LongParameterList")
|
||||||
|
@Composable
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
private fun DecorationBox(
|
||||||
|
state: SearchBarUM,
|
||||||
|
shape: TangemFieldShape,
|
||||||
|
focusManager: FocusManager,
|
||||||
|
interactionSource: MutableInteractionSource,
|
||||||
|
color: Color,
|
||||||
|
keyboardController: SoftwareKeyboardController?,
|
||||||
|
innerTextField: @Composable () -> Unit,
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
) {
|
||||||
|
val isFocused by interactionSource.collectIsFocusedAsState()
|
||||||
|
val alignmentBias by animateFloatAsState(
|
||||||
|
targetValue = if (isFocused) -1f else 0f,
|
||||||
|
animationSpec = tween(),
|
||||||
|
label = "AlignmentBias",
|
||||||
|
)
|
||||||
|
|
||||||
|
Box(
|
||||||
|
contentAlignment = BiasAlignment(horizontalBias = alignmentBias, verticalBias = 0f),
|
||||||
|
modifier = Modifier
|
||||||
|
.weight(1f)
|
||||||
|
.background(color, shape.toShape())
|
||||||
|
.padding(TangemTheme.dimens2.x3),
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens2.x1),
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
modifier = Modifier
|
||||||
|
.size(TangemTheme.dimens2.x5)
|
||||||
|
.testTag(SearchBarTestTags.ICON),
|
||||||
|
painter = painterResource(id = R.drawable.ic_search_default_24),
|
||||||
|
tint = TangemTheme.colors2.graphic.neutral.tertiaryConstant,
|
||||||
|
contentDescription = null,
|
||||||
|
)
|
||||||
|
Field(
|
||||||
|
state = state,
|
||||||
|
innerTextField = innerTextField,
|
||||||
|
)
|
||||||
|
ClearButton(
|
||||||
|
state = state,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
CancelButton(
|
||||||
|
state = state,
|
||||||
|
keyboardController = keyboardController,
|
||||||
|
focusManager = focusManager,
|
||||||
|
isActive = isFocused,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun Field(state: SearchBarUM, innerTextField: @Composable () -> Unit) {
|
||||||
|
Box(
|
||||||
|
contentAlignment = Alignment.CenterStart,
|
||||||
|
modifier = Modifier
|
||||||
|
.heightIn(min = TangemTheme.dimens2.x5)
|
||||||
|
.width(IntrinsicSize.Max),
|
||||||
|
) {
|
||||||
|
innerTextField()
|
||||||
|
|
||||||
|
val placeholderOpacity by remember(state.query) {
|
||||||
|
derivedStateOf {
|
||||||
|
if (state.query.isNotEmpty()) {
|
||||||
|
0f
|
||||||
|
} else {
|
||||||
|
1f
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = state.placeholderText.resolveReference(),
|
||||||
|
color = TangemTheme.colors2.text.neutral.tertiary,
|
||||||
|
style = TangemTheme.typography2.bodySemibold16,
|
||||||
|
maxLines = 1,
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
modifier = Modifier
|
||||||
|
.testTag(SearchBarTestTags.PLACEHOLDER_TEXT)
|
||||||
|
.alpha(placeholderOpacity),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun ClearButton(state: SearchBarUM) {
|
||||||
|
if (state.query.isNotEmpty()) {
|
||||||
|
Box(modifier = Modifier.fillMaxWidth()) {
|
||||||
|
Icon(
|
||||||
|
imageVector = ImageVector.vectorResource(R.drawable.ic_close_new_20),
|
||||||
|
tint = TangemTheme.colors2.graphic.neutral.tertiary,
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier
|
||||||
|
.align(Alignment.CenterEnd)
|
||||||
|
.size(TangemTheme.dimens2.x5)
|
||||||
|
.clip(CircleShape)
|
||||||
|
.clickable(
|
||||||
|
onClick = state.onClearClick,
|
||||||
|
interactionSource = remember { MutableInteractionSource() },
|
||||||
|
indication = ripple(bounded = false),
|
||||||
|
)
|
||||||
|
.testTag(SearchBarTestTags.CLEAR_BUTTON),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun CancelButton(
|
||||||
|
state: SearchBarUM,
|
||||||
|
isActive: Boolean,
|
||||||
|
focusManager: FocusManager,
|
||||||
|
keyboardController: SoftwareKeyboardController?,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
) {
|
||||||
|
AnimatedVisibility(
|
||||||
|
visible = isActive,
|
||||||
|
enter = expandHorizontally(),
|
||||||
|
exit = shrinkHorizontally(),
|
||||||
|
modifier = modifier,
|
||||||
|
) {
|
||||||
|
GhostTangemButton(
|
||||||
|
text = resourceReference(R.string.common_cancel),
|
||||||
|
size = TangemButtonSize.X9,
|
||||||
|
modifier = Modifier.padding(start = TangemTheme.dimens2.x3),
|
||||||
|
onClick = {
|
||||||
|
if (state.query.isNotEmpty()) {
|
||||||
|
state.onQueryChange("")
|
||||||
|
}
|
||||||
|
focusManager.clearFocus()
|
||||||
|
keyboardController?.hide()
|
||||||
|
state.onActiveChange(false)
|
||||||
|
state.onClearClick()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// region Preview
|
||||||
|
@Composable
|
||||||
|
@Preview(showBackground = true, widthDp = 360)
|
||||||
|
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||||
|
private fun TangemSearchField_Preview(@PreviewParameter(TangemSearchFieldPreviewProvider::class) params: SearchBarUM) {
|
||||||
|
var state by remember { mutableStateOf(params) }
|
||||||
|
TangemThemePreviewRedesign {
|
||||||
|
Column(
|
||||||
|
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens2.x4),
|
||||||
|
modifier = Modifier
|
||||||
|
.background(TangemTheme.colors2.surface.level1)
|
||||||
|
.padding(TangemTheme.dimens2.x4),
|
||||||
|
) {
|
||||||
|
TangemFieldShape.entries.fastForEach { shape ->
|
||||||
|
TangemSearchField(
|
||||||
|
state = state.copy(
|
||||||
|
onActiveChange = { state = state.copy(isActive = it) },
|
||||||
|
onQueryChange = { state = state.copy(query = it) },
|
||||||
|
onClearClick = { state = state.copy(query = "") },
|
||||||
|
),
|
||||||
|
shape = shape,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TangemSearchFieldPreviewProvider : PreviewParameterProvider<SearchBarUM> {
|
||||||
|
override val values: Sequence<SearchBarUM>
|
||||||
|
get() = sequenceOf(
|
||||||
|
SearchBarUM(
|
||||||
|
placeholderText = stringReference("Crypto, news and more"),
|
||||||
|
query = "BTC",
|
||||||
|
onQueryChange = {},
|
||||||
|
isActive = true,
|
||||||
|
onActiveChange = {},
|
||||||
|
),
|
||||||
|
SearchBarUM(
|
||||||
|
placeholderText = stringReference("Crypto, news and more"),
|
||||||
|
query = "",
|
||||||
|
onQueryChange = {},
|
||||||
|
isActive = false,
|
||||||
|
onActiveChange = {},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
// endregion
|
||||||
|
|
@ -5,23 +5,18 @@ import androidx.annotation.DrawableRes
|
||||||
import androidx.compose.animation.*
|
import androidx.compose.animation.*
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.shape.CircleShape
|
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
|
||||||
import androidx.compose.ui.graphics.Color
|
|
||||||
import androidx.compose.ui.graphics.lerp
|
|
||||||
import androidx.compose.ui.graphics.vector.ImageVector
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
import androidx.compose.ui.res.vectorResource
|
import androidx.compose.ui.res.vectorResource
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||||
import androidx.compose.ui.unit.Dp
|
|
||||||
import com.tangem.core.ui.R
|
import com.tangem.core.ui.R
|
||||||
import com.tangem.core.ui.extensions.*
|
import com.tangem.core.ui.extensions.*
|
||||||
import com.tangem.core.ui.res.TangemTheme
|
import com.tangem.core.ui.res.TangemTheme
|
||||||
|
|
@ -48,6 +43,7 @@ fun TangemTopBar(
|
||||||
title: TextReference? = null,
|
title: TextReference? = null,
|
||||||
subtitle: TextReference? = null,
|
subtitle: TextReference? = null,
|
||||||
startAction: TangemTopBarActionUM? = null,
|
startAction: TangemTopBarActionUM? = null,
|
||||||
|
type: TangemTopBarType = TangemTopBarType.Default,
|
||||||
endActions: ImmutableList<TangemTopBarActionUM> = persistentListOf(),
|
endActions: ImmutableList<TangemTopBarActionUM> = persistentListOf(),
|
||||||
@DrawableRes titleIconRes: Int? = null,
|
@DrawableRes titleIconRes: Int? = null,
|
||||||
) {
|
) {
|
||||||
|
|
@ -55,17 +51,21 @@ fun TangemTopBar(
|
||||||
title = title,
|
title = title,
|
||||||
subtitle = subtitle,
|
subtitle = subtitle,
|
||||||
titleIconRes = titleIconRes,
|
titleIconRes = titleIconRes,
|
||||||
|
type = type,
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
startContent = if (startAction != null) {
|
startContent = if (startAction != null) {
|
||||||
{ TangemTopBarActionContent(startAction) }
|
{ TangemTopBarActionContent(actionUM = startAction, type = type) }
|
||||||
} else {
|
} else {
|
||||||
null
|
null
|
||||||
},
|
},
|
||||||
endContent = if (endActions.isNotEmpty()) {
|
endContent = if (endActions.isNotEmpty()) {
|
||||||
{
|
{
|
||||||
Row(horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens2.x5)) {
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens2.x5),
|
||||||
|
) {
|
||||||
endActions.forEach { action ->
|
endActions.forEach { action ->
|
||||||
TangemTopBarActionContent(action)
|
TangemTopBarActionContent(actionUM = action, type = type)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -88,54 +88,88 @@ fun TangemTopBar(
|
||||||
@Composable
|
@Composable
|
||||||
fun TangemTopBar(
|
fun TangemTopBar(
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
|
type: TangemTopBarType = TangemTopBarType.Default,
|
||||||
title: TextReference? = null,
|
title: TextReference? = null,
|
||||||
subtitle: TextReference? = null,
|
subtitle: TextReference? = null,
|
||||||
@DrawableRes titleIconRes: Int? = null,
|
@DrawableRes titleIconRes: Int? = null,
|
||||||
startContent: @Composable (() -> Unit)? = null,
|
startContent: @Composable (() -> Unit)? = null,
|
||||||
endContent: @Composable (() -> Unit)? = null,
|
endContent: @Composable (() -> Unit)? = null,
|
||||||
) {
|
) {
|
||||||
Box(
|
TangemTopBar(
|
||||||
|
modifier = modifier,
|
||||||
|
type = type,
|
||||||
|
startContent = startContent,
|
||||||
|
endContent = endContent,
|
||||||
|
content = {
|
||||||
|
Column(
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens2.x0_5),
|
||||||
|
) {
|
||||||
|
TangemTopBarTitle(title = title, titleIconRes = titleIconRes)
|
||||||
|
AnimatedVisibility(
|
||||||
|
visible = subtitle != null,
|
||||||
|
label = "Subtitle Visibility",
|
||||||
|
) {
|
||||||
|
val wrappedSubtitle = remember(this) { requireNotNull(subtitle) }
|
||||||
|
Text(
|
||||||
|
text = wrappedSubtitle.resolveAnnotatedReference(),
|
||||||
|
color = TangemTheme.colors2.text.neutral.secondary,
|
||||||
|
style = TangemTheme.typography2.bodyRegular15,
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
maxLines = 1,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A top bar composable that displays a title and optional start and end icons.
|
||||||
|
* [Figma](https://www.figma.com/design/RU7AIgwHtGdMfy83T5UOoR/Core-Library?node-id=8435-74860&m=dev)
|
||||||
|
*
|
||||||
|
* @param modifier Modifier to be applied to the top bar.
|
||||||
|
* @param type Type of the top bar, which determines its size and padding.
|
||||||
|
* @param content Composable content to be displayed in the center of the top bar
|
||||||
|
* @param startContent Optional composable content to be displayed at the start (left) of the top bar.
|
||||||
|
* @param endContent Optional composable content to be displayed at the end (right) of the top bar.
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
fun TangemTopBar(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
type: TangemTopBarType = TangemTopBarType.Default,
|
||||||
|
content: @Composable () -> Unit,
|
||||||
|
startContent: @Composable (() -> Unit)? = null,
|
||||||
|
endContent: @Composable (() -> Unit)? = null,
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.height(TangemTheme.dimens2.x16)
|
.heightIn(min = type.getSize())
|
||||||
.padding(TangemTheme.dimens2.x4, TangemTheme.dimens2.x3),
|
.padding(type.getPadding()),
|
||||||
) {
|
) {
|
||||||
AnimatedVisibility(
|
AnimatedContent(
|
||||||
visible = startContent != null,
|
targetState = startContent != null,
|
||||||
modifier = Modifier.align(Alignment.CenterStart),
|
modifier = Modifier.size(TangemTheme.dimens2.x11),
|
||||||
label = "Start Content Visibility",
|
label = "Start Content Visibility",
|
||||||
) {
|
) { isVisible ->
|
||||||
startContent?.invoke()
|
if (isVisible) {
|
||||||
}
|
startContent?.invoke()
|
||||||
|
|
||||||
Column(
|
|
||||||
modifier = Modifier
|
|
||||||
.align(Alignment.Center)
|
|
||||||
.fillMaxWidth(),
|
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
|
||||||
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens2.x0_5),
|
|
||||||
) {
|
|
||||||
TangemTopBarTitle(title = title, titleIconRes = titleIconRes)
|
|
||||||
AnimatedVisibility(
|
|
||||||
visible = subtitle != null,
|
|
||||||
label = "Subtitle Visibility",
|
|
||||||
) {
|
|
||||||
val wrappedSubtitle = remember(this) { requireNotNull(subtitle) }
|
|
||||||
Text(
|
|
||||||
text = wrappedSubtitle.resolveAnnotatedReference(),
|
|
||||||
color = TangemTheme.colors2.text.neutral.secondary,
|
|
||||||
style = TangemTheme.typography2.bodyRegular15,
|
|
||||||
textAlign = TextAlign.Center,
|
|
||||||
maxLines = 1,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
AnimatedVisibility(
|
|
||||||
visible = endContent != null,
|
content()
|
||||||
modifier = Modifier.align(Alignment.CenterEnd),
|
|
||||||
|
AnimatedContent(
|
||||||
|
targetState = endContent != null,
|
||||||
|
modifier = Modifier.size(TangemTheme.dimens2.x11),
|
||||||
label = "End Content Visibility",
|
label = "End Content Visibility",
|
||||||
) {
|
) { isVisible ->
|
||||||
endContent?.invoke()
|
if (isVisible) {
|
||||||
|
endContent?.invoke()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -151,7 +185,6 @@ private fun TangemTopBarTitle(title: TextReference?, @DrawableRes titleIconRes:
|
||||||
val wrappedTitle = remember(this) { requireNotNull(title) }
|
val wrappedTitle = remember(this) { requireNotNull(title) }
|
||||||
|
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
horizontalArrangement = Arrangement.spacedBy(
|
horizontalArrangement = Arrangement.spacedBy(
|
||||||
space = TangemTheme.dimens2.x1,
|
space = TangemTheme.dimens2.x1,
|
||||||
alignment = Alignment.CenterHorizontally,
|
alignment = Alignment.CenterHorizontally,
|
||||||
|
|
@ -184,33 +217,6 @@ private fun TangemTopBarTitle(title: TextReference?, @DrawableRes titleIconRes:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun TangemTopBarActionContent(
|
|
||||||
actionUM: TangemTopBarActionUM,
|
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
iconSize: Dp = TangemTheme.dimens2.x8,
|
|
||||||
) {
|
|
||||||
val background = lerp(
|
|
||||||
start = Color.Transparent,
|
|
||||||
stop = TangemTheme.colors2.button.backgroundSecondary,
|
|
||||||
fraction = actionUM.ghostModeProgress,
|
|
||||||
)
|
|
||||||
val padding = (TangemTheme.dimens2.x10 - iconSize) / 2
|
|
||||||
Icon(
|
|
||||||
imageVector = ImageVector.vectorResource(id = actionUM.iconRes),
|
|
||||||
contentDescription = null,
|
|
||||||
tint = TangemTheme.colors2.graphic.neutral.primary,
|
|
||||||
modifier = modifier
|
|
||||||
.size(TangemTheme.dimens2.x10)
|
|
||||||
.clip(CircleShape)
|
|
||||||
.conditional(actionUM.isActionable) { background(background) }
|
|
||||||
.conditionalCompose(actionUM.isActionable && actionUM.onClick != null) {
|
|
||||||
clickableSingle(onClick = requireNotNull(actionUM.onClick))
|
|
||||||
}
|
|
||||||
.padding(padding),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// region Preview
|
// region Preview
|
||||||
@Composable
|
@Composable
|
||||||
@Preview(showBackground = true, widthDp = 375)
|
@Preview(showBackground = true, widthDp = 375)
|
||||||
|
|
@ -221,6 +227,7 @@ private fun TangemTopBar_Preview(@PreviewParameter(PreviewProvider::class) param
|
||||||
title = params.title,
|
title = params.title,
|
||||||
subtitle = params.subtitle,
|
subtitle = params.subtitle,
|
||||||
titleIconRes = params.titleIconRes,
|
titleIconRes = params.titleIconRes,
|
||||||
|
type = TangemTopBarType.BottomSheet,
|
||||||
modifier = Modifier.background(TangemTheme.colors2.surface.level1),
|
modifier = Modifier.background(TangemTheme.colors2.surface.level1),
|
||||||
startContent = params.startActionUM?.let { { TangemTopBarActionContent(it) } },
|
startContent = params.startActionUM?.let { { TangemTopBarActionContent(it) } },
|
||||||
endContent = if (params.endActions.isNotEmpty()) {
|
endContent = if (params.endActions.isNotEmpty()) {
|
||||||
|
|
@ -256,11 +263,13 @@ private class PreviewProvider : PreviewParameterProvider<TangemTopBarPreviewData
|
||||||
onClick = {},
|
onClick = {},
|
||||||
isActionable = false,
|
isActionable = false,
|
||||||
),
|
),
|
||||||
endActions = persistentListOf(TangemTopBarActionUM(
|
endActions = persistentListOf(
|
||||||
iconRes = R.drawable.ic_more_default_24,
|
TangemTopBarActionUM(
|
||||||
onClick = {},
|
iconRes = R.drawable.ic_more_default_24,
|
||||||
isActionable = false,
|
onClick = {},
|
||||||
)),
|
isActionable = false,
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
TangemTopBarPreviewData(
|
TangemTopBarPreviewData(
|
||||||
title = stringReference("Title"),
|
title = stringReference("Title"),
|
||||||
|
|
@ -271,12 +280,14 @@ private class PreviewProvider : PreviewParameterProvider<TangemTopBarPreviewData
|
||||||
isActionable = true,
|
isActionable = true,
|
||||||
ghostModeProgress = 1f,
|
ghostModeProgress = 1f,
|
||||||
),
|
),
|
||||||
endActions = persistentListOf(TangemTopBarActionUM(
|
endActions = persistentListOf(
|
||||||
iconRes = R.drawable.ic_more_default_24,
|
TangemTopBarActionUM(
|
||||||
onClick = {},
|
iconRes = R.drawable.ic_more_default_24,
|
||||||
isActionable = true,
|
onClick = {},
|
||||||
ghostModeProgress = 1f,
|
isActionable = true,
|
||||||
)),
|
ghostModeProgress = 1f,
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
TangemTopBarPreviewData(
|
TangemTopBarPreviewData(
|
||||||
title = stringReference("Title"),
|
title = stringReference("Title"),
|
||||||
|
|
@ -287,12 +298,14 @@ private class PreviewProvider : PreviewParameterProvider<TangemTopBarPreviewData
|
||||||
onClick = {},
|
onClick = {},
|
||||||
isActionable = false,
|
isActionable = false,
|
||||||
),
|
),
|
||||||
endActions = persistentListOf(TangemTopBarActionUM(
|
endActions = persistentListOf(
|
||||||
iconRes = R.drawable.ic_more_default_24,
|
TangemTopBarActionUM(
|
||||||
onClick = {},
|
iconRes = R.drawable.ic_more_default_24,
|
||||||
isActionable = true,
|
onClick = {},
|
||||||
ghostModeProgress = 1f,
|
isActionable = true,
|
||||||
)),
|
ghostModeProgress = 1f,
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
TangemTopBarPreviewData(
|
TangemTopBarPreviewData(
|
||||||
subtitle = stringReference("Subtitle"),
|
subtitle = stringReference("Subtitle"),
|
||||||
|
|
@ -302,19 +315,23 @@ private class PreviewProvider : PreviewParameterProvider<TangemTopBarPreviewData
|
||||||
onClick = {},
|
onClick = {},
|
||||||
isActionable = true,
|
isActionable = true,
|
||||||
),
|
),
|
||||||
endActions = persistentListOf(TangemTopBarActionUM(
|
endActions = persistentListOf(
|
||||||
iconRes = R.drawable.ic_more_default_24,
|
TangemTopBarActionUM(
|
||||||
onClick = {},
|
iconRes = R.drawable.ic_more_default_24,
|
||||||
isActionable = false,
|
onClick = {},
|
||||||
)),
|
isActionable = false,
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
TangemTopBarPreviewData(
|
TangemTopBarPreviewData(
|
||||||
title = stringReference("Title"),
|
title = stringReference("Title"),
|
||||||
endActions = persistentListOf(TangemTopBarActionUM(
|
endActions = persistentListOf(
|
||||||
iconRes = R.drawable.ic_more_default_24,
|
TangemTopBarActionUM(
|
||||||
onClick = {},
|
iconRes = R.drawable.ic_more_default_24,
|
||||||
isActionable = false,
|
onClick = {},
|
||||||
)),
|
isActionable = false,
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
TangemTopBarPreviewData(
|
TangemTopBarPreviewData(
|
||||||
title = stringReference("Title"),
|
title = stringReference("Title"),
|
||||||
|
|
@ -353,11 +370,13 @@ private class PreviewProvider : PreviewParameterProvider<TangemTopBarPreviewData
|
||||||
onClick = {},
|
onClick = {},
|
||||||
isActionable = true,
|
isActionable = true,
|
||||||
),
|
),
|
||||||
endActions = persistentListOf(TangemTopBarActionUM(
|
endActions = persistentListOf(
|
||||||
iconRes = R.drawable.ic_more_default_24,
|
TangemTopBarActionUM(
|
||||||
onClick = {},
|
iconRes = R.drawable.ic_more_default_24,
|
||||||
isActionable = false,
|
onClick = {},
|
||||||
)),
|
isActionable = false,
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,53 @@
|
||||||
|
package com.tangem.core.ui.ds.topbar
|
||||||
|
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.graphics.lerp
|
||||||
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
|
import androidx.compose.ui.res.vectorResource
|
||||||
|
import com.tangem.core.ui.extensions.clickableSingle
|
||||||
|
import com.tangem.core.ui.extensions.conditional
|
||||||
|
import com.tangem.core.ui.extensions.conditionalCompose
|
||||||
|
import com.tangem.core.ui.res.TangemTheme
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Composable for top bar action content
|
||||||
|
*
|
||||||
|
* @param actionUM user model for top bar action
|
||||||
|
* @param modifier modifier for this composable
|
||||||
|
* @param type type of top bar, affects size and padding of action content
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
fun TangemTopBarActionContent(
|
||||||
|
actionUM: TangemTopBarActionUM,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
type: TangemTopBarType = TangemTopBarType.Default,
|
||||||
|
) {
|
||||||
|
val background = lerp(
|
||||||
|
start = Color.Transparent,
|
||||||
|
stop = TangemTheme.colors2.button.backgroundSecondary,
|
||||||
|
fraction = actionUM.ghostModeProgress,
|
||||||
|
)
|
||||||
|
val padding = (TangemTheme.dimens2.x11 - type.getSideContentSize()) / 2
|
||||||
|
|
||||||
|
Icon(
|
||||||
|
imageVector = ImageVector.vectorResource(id = actionUM.iconRes),
|
||||||
|
contentDescription = null,
|
||||||
|
tint = TangemTheme.colors2.graphic.neutral.primary,
|
||||||
|
modifier = modifier
|
||||||
|
.size(TangemTheme.dimens2.x11)
|
||||||
|
.clip(CircleShape)
|
||||||
|
.conditional(actionUM.isActionable) { background(background) }
|
||||||
|
.conditionalCompose(actionUM.isActionable && actionUM.onClick != null) {
|
||||||
|
clickableSingle(onClick = requireNotNull(actionUM.onClick))
|
||||||
|
}
|
||||||
|
.padding(padding),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,45 @@
|
||||||
|
package com.tangem.core.ui.ds.topbar
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.ReadOnlyComposable
|
||||||
|
import androidx.compose.ui.unit.Dp
|
||||||
|
import com.tangem.core.ui.res.TangemTheme
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Type of top bar, affects size and padding of content
|
||||||
|
*
|
||||||
|
* @see TangemTopBar
|
||||||
|
*/
|
||||||
|
enum class TangemTopBarType {
|
||||||
|
Default,
|
||||||
|
BottomSheet,
|
||||||
|
;
|
||||||
|
|
||||||
|
@ReadOnlyComposable
|
||||||
|
@Composable
|
||||||
|
fun getSize(): Dp {
|
||||||
|
return when (this) {
|
||||||
|
Default -> TangemTheme.dimens2.x16
|
||||||
|
BottomSheet -> TangemTheme.dimens2.x19
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ReadOnlyComposable
|
||||||
|
@Composable
|
||||||
|
fun getSideContentSize(): Dp {
|
||||||
|
return when (this) {
|
||||||
|
Default -> TangemTheme.dimens2.x8
|
||||||
|
BottomSheet -> TangemTheme.dimens2.x7
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ReadOnlyComposable
|
||||||
|
@Composable
|
||||||
|
fun getPadding(): PaddingValues {
|
||||||
|
return when (this) {
|
||||||
|
Default -> PaddingValues(horizontal = TangemTheme.dimens2.x4, vertical = TangemTheme.dimens2.x3)
|
||||||
|
BottomSheet -> PaddingValues(TangemTheme.dimens2.x4)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -3,6 +3,8 @@
|
||||||
package com.tangem.core.ui.res
|
package com.tangem.core.ui.res
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.text.selection.LocalTextSelectionColors
|
||||||
|
import androidx.compose.foundation.text.selection.TextSelectionColors
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
|
@ -31,15 +33,27 @@ fun TangemThemeRedesign(content: @Composable () -> Unit) {
|
||||||
LocalTangemTypography2 provides TangemTypography2(InterFamily),
|
LocalTangemTypography2 provides TangemTypography2(InterFamily),
|
||||||
LocalRootBackgroundColor provides remember(rootBackgroundColor) { mutableStateOf(rootBackgroundColor) },
|
LocalRootBackgroundColor provides remember(rootBackgroundColor) { mutableStateOf(rootBackgroundColor) },
|
||||||
) {
|
) {
|
||||||
ProvideHaze {
|
CompositionLocalProvider(
|
||||||
Box(Modifier.hazeSourceTangem(zIndex = 1f)) {
|
LocalTextSelectionColors provides TangemTextSelectionColors2,
|
||||||
content()
|
) {
|
||||||
|
ProvideHaze {
|
||||||
|
Box(Modifier.hazeSourceTangem(zIndex = 1f)) {
|
||||||
|
content()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val TangemTextSelectionColors2: TextSelectionColors
|
||||||
|
@Composable
|
||||||
|
@ReadOnlyComposable
|
||||||
|
get() = TextSelectionColors(
|
||||||
|
handleColor = TangemTheme.colors2.text.neutral.primary,
|
||||||
|
backgroundColor = TangemTheme.colors2.text.neutral.primary.copy(alpha = 0.3f),
|
||||||
|
)
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@ReadOnlyComposable
|
@ReadOnlyComposable
|
||||||
private fun lightThemeColors2(): TangemColors2 {
|
private fun lightThemeColors2(): TangemColors2 {
|
||||||
|
|
@ -48,7 +62,7 @@ private fun lightThemeColors2(): TangemColors2 {
|
||||||
primary = TangemColorPalette.Dark6,
|
primary = TangemColorPalette.Dark6,
|
||||||
primaryInverted = TangemColorPalette.White,
|
primaryInverted = TangemColorPalette.White,
|
||||||
secondary = TangemColorPalette.Dark2,
|
secondary = TangemColorPalette.Dark2,
|
||||||
tertiary = TangemColorPalette.Dark3,
|
tertiary = TangemColorPalette.Dark1,
|
||||||
primaryInvertedConstant = TangemColorPalette.White,
|
primaryInvertedConstant = TangemColorPalette.White,
|
||||||
),
|
),
|
||||||
status = TangemColors2.Text.Status(
|
status = TangemColors2.Text.Status(
|
||||||
|
|
@ -67,7 +81,7 @@ private fun lightThemeColors2(): TangemColors2 {
|
||||||
tertiary = TangemColorPalette.Dark3,
|
tertiary = TangemColorPalette.Dark3,
|
||||||
quaternary = TangemColorPalette.Light4,
|
quaternary = TangemColorPalette.Light4,
|
||||||
primaryInvertedConstant = TangemColorPalette.White,
|
primaryInvertedConstant = TangemColorPalette.White,
|
||||||
tertiaryConstant = TangemColorPalette.Dark3,
|
tertiaryConstant = TangemColorPalette.Dark1,
|
||||||
),
|
),
|
||||||
status = TangemColors2.Graphic.Status(
|
status = TangemColors2.Graphic.Status(
|
||||||
accent = TangemColorPalette.Azure,
|
accent = TangemColorPalette.Azure,
|
||||||
|
|
@ -243,7 +257,7 @@ private fun darkThemeColors2(): TangemColors2 {
|
||||||
secondary = TangemColorPalette.Light5,
|
secondary = TangemColorPalette.Light5,
|
||||||
tertiary = TangemColorPalette.Dark3,
|
tertiary = TangemColorPalette.Dark3,
|
||||||
quaternary = TangemColorPalette.Dark3,
|
quaternary = TangemColorPalette.Dark3,
|
||||||
tertiaryConstant = TangemColorPalette.Dark3,
|
tertiaryConstant = TangemColorPalette.Dark1,
|
||||||
primaryInvertedConstant = TangemColorPalette.White,
|
primaryInvertedConstant = TangemColorPalette.White,
|
||||||
),
|
),
|
||||||
status = TangemColors2.Graphic.Status(
|
status = TangemColors2.Graphic.Status(
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,9 @@
|
||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:width="24dp"
|
android:width="24dp"
|
||||||
android:height="24dp"
|
android:height="24dp"
|
||||||
android:autoMirrored="true"
|
|
||||||
android:viewportWidth="24"
|
android:viewportWidth="24"
|
||||||
android:viewportHeight="24">
|
android:viewportHeight="24">
|
||||||
<path
|
<path
|
||||||
android:fillColor="#000000"
|
android:pathData="M4,10.535C4,9.636 4.17,8.793 4.51,8.006C4.856,7.214 5.331,6.518 5.936,5.919C6.541,5.314 7.241,4.843 8.034,4.506C8.834,4.169 9.687,4 10.594,4C11.507,4 12.361,4.169 13.154,4.506C13.954,4.843 14.656,5.314 15.261,5.919C15.866,6.518 16.338,7.214 16.678,8.006C17.024,8.793 17.197,9.636 17.197,10.535C17.197,11.241 17.086,11.913 16.865,12.55C16.649,13.187 16.349,13.767 15.966,14.292L19.66,17.969C19.773,18.076 19.857,18.202 19.911,18.346C19.97,18.491 20,18.643 20,18.804C20,19.029 19.949,19.232 19.846,19.414C19.743,19.596 19.603,19.738 19.425,19.839C19.247,19.947 19.041,20 18.809,20C18.647,20 18.49,19.971 18.339,19.912C18.193,19.858 18.061,19.772 17.942,19.655L14.224,15.97C13.705,16.312 13.138,16.583 12.523,16.781C11.912,16.973 11.269,17.07 10.594,17.07C9.687,17.07 8.834,16.901 8.034,16.564C7.241,16.227 6.541,15.759 5.936,15.159C5.331,14.56 4.856,13.866 4.51,13.08C4.17,12.288 4,11.439 4,10.535ZM5.726,10.535C5.726,11.204 5.85,11.83 6.098,12.413C6.352,12.997 6.703,13.511 7.151,13.955C7.6,14.394 8.118,14.739 8.707,14.991C9.296,15.242 9.925,15.368 10.594,15.368C11.269,15.368 11.901,15.242 12.49,14.991C13.079,14.739 13.595,14.394 14.038,13.955C14.486,13.511 14.837,12.997 15.091,12.413C15.344,11.83 15.471,11.204 15.471,10.535C15.471,9.871 15.344,9.248 15.091,8.664C14.837,8.081 14.486,7.567 14.038,7.123C13.595,6.679 13.079,6.333 12.49,6.087C11.901,5.836 11.269,5.71 10.594,5.71C9.925,5.71 9.296,5.836 8.707,6.087C8.118,6.333 7.6,6.679 7.151,7.123C6.703,7.567 6.352,8.081 6.098,8.664C5.85,9.248 5.726,9.871 5.726,10.535Z"
|
||||||
android:pathData="M9.5,3C11.224,3 12.877,3.685 14.096,4.904C15.315,6.123 16,7.776 16,9.5C16,11.11 15.41,12.59 14.44,13.73L14.71,14H15.5L20.5,19L19,20.5L14,15.5V14.71L13.73,14.44C12.59,15.41 11.11,16 9.5,16C7.776,16 6.123,15.315 4.904,14.096C3.685,12.877 3,11.224 3,9.5C3,7.776 3.685,6.123 4.904,4.904C6.123,3.685 7.776,3 9.5,3ZM9.5,5C7,5 5,7 5,9.5C5,12 7,14 9.5,14C12,14 14,12 14,9.5C14,7 12,5 9.5,5Z" />
|
android:fillColor="#000000"/>
|
||||||
</vector>
|
</vector>
|
||||||
|
|
|
||||||
5
core/ui/src/main/res/drawable/ic_search_default_24.xml
Normal file
5
core/ui/src/main/res/drawable/ic_search_default_24.xml
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:viewportHeight="20" android:viewportWidth="20" android:width="24dp">
|
||||||
|
|
||||||
|
<path android:fillColor="#919191" android:pathData="M8.666,2.333C12.164,2.333 15,5.169 15,8.666C15,10.054 14.551,11.336 13.794,12.38L17.373,15.959C17.764,16.35 17.764,16.983 17.373,17.373C16.983,17.764 16.35,17.764 15.959,17.373L12.38,13.794C11.336,14.551 10.054,15 8.666,15C5.169,15 2.333,12.164 2.333,8.666C2.333,5.169 5.169,2.333 8.666,2.333ZM8.666,4.333C6.273,4.333 4.333,6.273 4.333,8.666C4.333,11.059 6.273,13 8.666,13C11.059,13 13,11.059 13,8.666C13,6.273 11.059,4.333 8.666,4.333Z"/>
|
||||||
|
|
||||||
|
</vector>
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package com.tangem.feature.tester.presentation.storybook.entity
|
package com.tangem.feature.tester.presentation.storybook.entity
|
||||||
|
|
||||||
import com.tangem.core.ui.ds.badge.TangemBadgeColor
|
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.message.TangemMessageEffect
|
||||||
|
|
||||||
internal sealed interface StoryBookPage
|
internal sealed interface StoryBookPage
|
||||||
|
|
@ -54,3 +55,8 @@ internal data class TangemHeaderRowStory(
|
||||||
val isBalanceHidden: Boolean,
|
val isBalanceHidden: Boolean,
|
||||||
val onBalanceHiddenToggle: () -> Unit,
|
val onBalanceHiddenToggle: () -> Unit,
|
||||||
) : StoryBookPage
|
) : StoryBookPage
|
||||||
|
|
||||||
|
internal data class TangemSearchFieldStory(
|
||||||
|
val selectedShape: TangemFieldShape,
|
||||||
|
val onShapeChange: (TangemFieldShape) -> Unit,
|
||||||
|
) : StoryBookPage
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
package com.tangem.feature.tester.presentation.storybook.page.searchfield
|
||||||
|
|
||||||
|
import com.tangem.core.ui.ds.field.search.TangemFieldShape
|
||||||
|
import com.tangem.feature.tester.presentation.storybook.entity.TangemSearchFieldStory
|
||||||
|
import com.tangem.feature.tester.presentation.storybook.viewmodel.StateUpdater
|
||||||
|
import com.tangem.feature.tester.presentation.storybook.viewmodel.storyPageFactory
|
||||||
|
|
||||||
|
internal fun StateUpdater<TangemSearchFieldStory>.build(): TangemSearchFieldStory {
|
||||||
|
return TangemSearchFieldStory(
|
||||||
|
selectedShape = TangemFieldShape.RoundedCorners,
|
||||||
|
onShapeChange = { shape ->
|
||||||
|
updateStory { it.copy(selectedShape = shape) }
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal val tangemSearchFieldStoryFactory
|
||||||
|
get() = storyPageFactory(StateUpdater<TangemSearchFieldStory>::build)
|
||||||
|
|
@ -0,0 +1,151 @@
|
||||||
|
@file:Suppress("MagicNumber", "LongMethod")
|
||||||
|
|
||||||
|
package com.tangem.feature.tester.presentation.storybook.page.searchfield
|
||||||
|
|
||||||
|
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.*
|
||||||
|
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.fields.entity.SearchBarUM
|
||||||
|
import com.tangem.core.ui.ds.field.search.TangemFieldShape
|
||||||
|
import com.tangem.core.ui.ds.field.search.TangemSearchField
|
||||||
|
import com.tangem.core.ui.extensions.resourceReference
|
||||||
|
import com.tangem.core.ui.res.TangemTheme
|
||||||
|
import com.tangem.feature.tester.presentation.storybook.entity.TangemSearchFieldStory
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
internal fun TangemSearchFieldStory(state: TangemSearchFieldStory, modifier: Modifier = Modifier) {
|
||||||
|
var query by remember { mutableStateOf("") }
|
||||||
|
var isActive by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
LazyColumn(
|
||||||
|
contentPadding = PaddingValues(bottom = 16.dp),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
|
modifier = modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.statusBarsPadding()
|
||||||
|
.background(TangemTheme.colors2.surface.level1),
|
||||||
|
) {
|
||||||
|
stickyHeader("shape_toggle") {
|
||||||
|
ShapeToggle(
|
||||||
|
selected = state.selectedShape,
|
||||||
|
onSelect = state.onShapeChange,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.background(TangemTheme.colors2.surface.level1)
|
||||||
|
.padding(horizontal = 16.dp, vertical = 8.dp),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
item("empty") {
|
||||||
|
VariantSection(label = "Empty") {
|
||||||
|
TangemSearchField(
|
||||||
|
state = SearchBarUM(
|
||||||
|
placeholderText = resourceReference(R.string.manage_tokens_search_placeholder),
|
||||||
|
query = query,
|
||||||
|
onQueryChange = { query = it },
|
||||||
|
isActive = isActive,
|
||||||
|
onActiveChange = { isActive = it },
|
||||||
|
onClearClick = { query = "" },
|
||||||
|
),
|
||||||
|
shape = state.selectedShape,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
item("prefilled") {
|
||||||
|
VariantSection(label = "Pre-filled") {
|
||||||
|
var prefilledQuery by remember { mutableStateOf("Bitcoin") }
|
||||||
|
var isPrefilledActive by remember { mutableStateOf(false) }
|
||||||
|
TangemSearchField(
|
||||||
|
state = SearchBarUM(
|
||||||
|
placeholderText = resourceReference(R.string.manage_tokens_search_placeholder),
|
||||||
|
query = prefilledQuery,
|
||||||
|
onQueryChange = { prefilledQuery = it },
|
||||||
|
isActive = isPrefilledActive,
|
||||||
|
onActiveChange = { isPrefilledActive = it },
|
||||||
|
onClearClick = { prefilledQuery = "" },
|
||||||
|
),
|
||||||
|
shape = state.selectedShape,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun ShapeToggle(
|
||||||
|
selected: TangemFieldShape,
|
||||||
|
onSelect: (TangemFieldShape) -> 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),
|
||||||
|
) {
|
||||||
|
TangemFieldShape.entries.forEach { fieldShape ->
|
||||||
|
ShapeChip(
|
||||||
|
label = fieldShape.name,
|
||||||
|
selected = fieldShape == selected,
|
||||||
|
onClick = { onSelect(fieldShape) },
|
||||||
|
modifier = Modifier.weight(1f),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun ShapeChip(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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -23,6 +23,7 @@ import com.tangem.feature.tester.presentation.storybook.page.contextmenu.tangemC
|
||||||
import com.tangem.feature.tester.presentation.storybook.page.headerrow.tangemHeaderRowStoryFactory
|
import com.tangem.feature.tester.presentation.storybook.page.headerrow.tangemHeaderRowStoryFactory
|
||||||
import com.tangem.feature.tester.presentation.storybook.page.message.tangemMessageStoryFactory
|
import com.tangem.feature.tester.presentation.storybook.page.message.tangemMessageStoryFactory
|
||||||
import com.tangem.feature.tester.presentation.storybook.page.opportunities.opportunitiesBGStoryFactory
|
import com.tangem.feature.tester.presentation.storybook.page.opportunities.opportunitiesBGStoryFactory
|
||||||
|
import com.tangem.feature.tester.presentation.storybook.page.searchfield.tangemSearchFieldStoryFactory
|
||||||
import com.tangem.feature.tester.presentation.storybook.page.tabs.tangemSegmentedPickerStoryFactory
|
import com.tangem.feature.tester.presentation.storybook.page.tabs.tangemSegmentedPickerStoryFactory
|
||||||
import com.tangem.feature.tester.presentation.storybook.page.tokenrow.tangemTokenRowStoryFactory
|
import com.tangem.feature.tester.presentation.storybook.page.tokenrow.tangemTokenRowStoryFactory
|
||||||
|
|
||||||
|
|
@ -39,6 +40,7 @@ private fun buildStories() = listOf(
|
||||||
StoryItem(title = "🪙 Token Row", factory = tangemTokenRowStoryFactory),
|
StoryItem(title = "🪙 Token Row", factory = tangemTokenRowStoryFactory),
|
||||||
StoryItem(title = "📑 Header Row", factory = tangemHeaderRowStoryFactory),
|
StoryItem(title = "📑 Header Row", factory = tangemHeaderRowStoryFactory),
|
||||||
StoryItem(title = "📋 Context Menu", factory = tangemContextMenuStoryFactory),
|
StoryItem(title = "📋 Context Menu", factory = tangemContextMenuStoryFactory),
|
||||||
|
StoryItem(title = "🔍 Search Field", factory = tangemSearchFieldStoryFactory),
|
||||||
)
|
)
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ import com.tangem.feature.tester.presentation.storybook.entity.TangemCheckboxSto
|
||||||
import com.tangem.feature.tester.presentation.storybook.entity.TangemHeaderRowStory
|
import com.tangem.feature.tester.presentation.storybook.entity.TangemHeaderRowStory
|
||||||
import com.tangem.feature.tester.presentation.storybook.entity.TangemContextMenuStory
|
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.TangemMessageStory
|
||||||
|
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.TangemSegmentedPickerStory
|
||||||
import com.tangem.feature.tester.presentation.storybook.entity.TangemTokenRowStory
|
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.background.NorthernLightsStory
|
||||||
|
|
@ -26,6 +27,7 @@ import com.tangem.feature.tester.presentation.storybook.page.tabs.TangemSegmente
|
||||||
import com.tangem.feature.tester.presentation.storybook.page.tokenrow.TangemTokenRowStory
|
import com.tangem.feature.tester.presentation.storybook.page.tokenrow.TangemTokenRowStory
|
||||||
import com.tangem.feature.tester.presentation.storybook.page.headerrow.TangemHeaderRowStory
|
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.contextmenu.TangemContextMenuStory
|
||||||
|
import com.tangem.feature.tester.presentation.storybook.page.searchfield.TangemSearchFieldStory
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
internal fun StoryBookScreen(state: StoryBookUM, modifier: Modifier = Modifier) {
|
internal fun StoryBookScreen(state: StoryBookUM, modifier: Modifier = Modifier) {
|
||||||
|
|
@ -48,6 +50,7 @@ internal fun StoryBookScreen(state: StoryBookUM, modifier: Modifier = Modifier)
|
||||||
is TangemTokenRowStory -> TangemTokenRowStory(state = storyState)
|
is TangemTokenRowStory -> TangemTokenRowStory(state = storyState)
|
||||||
is TangemHeaderRowStory -> TangemHeaderRowStory(state = storyState)
|
is TangemHeaderRowStory -> TangemHeaderRowStory(state = storyState)
|
||||||
is TangemContextMenuStory -> TangemContextMenuStory(state = storyState)
|
is TangemContextMenuStory -> TangemContextMenuStory(state = storyState)
|
||||||
|
is TangemSearchFieldStory -> TangemSearchFieldStory(state = storyState)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -38,6 +38,7 @@ import com.tangem.core.ui.ds.row.token.TangemTokenRow
|
||||||
import com.tangem.core.ui.ds.topbar.TangemTopBar
|
import com.tangem.core.ui.ds.topbar.TangemTopBar
|
||||||
import com.tangem.core.ui.ds.topbar.TangemTopBarActionContent
|
import com.tangem.core.ui.ds.topbar.TangemTopBarActionContent
|
||||||
import com.tangem.core.ui.ds.topbar.TangemTopBarActionUM
|
import com.tangem.core.ui.ds.topbar.TangemTopBarActionUM
|
||||||
|
import com.tangem.core.ui.ds.topbar.TangemTopBarType
|
||||||
import com.tangem.core.ui.extensions.resourceReference
|
import com.tangem.core.ui.extensions.resourceReference
|
||||||
import com.tangem.core.ui.reordarable.ReorderableItem
|
import com.tangem.core.ui.reordarable.ReorderableItem
|
||||||
import com.tangem.core.ui.res.TangemTheme
|
import com.tangem.core.ui.res.TangemTheme
|
||||||
|
|
@ -77,6 +78,7 @@ internal fun OrganizeTokensContent(
|
||||||
title = {
|
title = {
|
||||||
TangemTopBar(
|
TangemTopBar(
|
||||||
title = resourceReference(R.string.organize_tokens_title),
|
title = resourceReference(R.string.organize_tokens_title),
|
||||||
|
type = TangemTopBarType.BottomSheet,
|
||||||
endContent = {
|
endContent = {
|
||||||
TangemTopBarActionContent(
|
TangemTopBarActionContent(
|
||||||
actionUM = TangemTopBarActionUM(
|
actionUM = TangemTopBarActionUM(
|
||||||
|
|
@ -85,7 +87,7 @@ internal fun OrganizeTokensContent(
|
||||||
onClick = { isShowDropdownMenu = true },
|
onClick = { isShowDropdownMenu = true },
|
||||||
ghostModeProgress = 0f,
|
ghostModeProgress = 0f,
|
||||||
),
|
),
|
||||||
iconSize = TangemTheme.dimens2.x7,
|
type = TangemTopBarType.BottomSheet,
|
||||||
)
|
)
|
||||||
OrganizeDropDownMenu(
|
OrganizeDropDownMenu(
|
||||||
organizeMenuUM = organizeTokensUM.organizeMenuUM,
|
organizeMenuUM = organizeTokensUM.organizeMenuUM,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue