diff --git a/core/ui/src/main/java/com/tangem/core/ui/ds/button/TangemButtonInternal.kt b/core/ui/src/main/java/com/tangem/core/ui/ds/button/TangemButtonInternal.kt index 35206c39f3..88202ae1fe 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/ds/button/TangemButtonInternal.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/ds/button/TangemButtonInternal.kt @@ -214,12 +214,15 @@ private inline fun ProvideButtonRippleConfiguration(crossinline content: @Compos @Composable private fun TangemButtonIcon(tangemIconUM: TangemIconUM?, isVisible: Boolean, size: TangemButtonSize) { + val wrappedIconUM = rememberLastNonNull(tangemIconUM) + AnimatedVisibility( visible = isVisible, modifier = Modifier.size(size = size.toContentSize()), ) { - val wrappedIconUM = remember(tangemIconUM) { requireNotNull(tangemIconUM) } - TangemIcon(tangemIconUM = wrappedIconUM) + wrappedIconUM?.let { + TangemIcon(tangemIconUM = it) + } } } diff --git a/core/ui/src/main/java/com/tangem/core/ui/ds2/search/TangemSearch.kt b/core/ui/src/main/java/com/tangem/core/ui/ds2/search/TangemSearch.kt new file mode 100644 index 0000000000..eb1bdad170 --- /dev/null +++ b/core/ui/src/main/java/com/tangem/core/ui/ds2/search/TangemSearch.kt @@ -0,0 +1,325 @@ +package com.tangem.core.ui.ds2.search + +import android.content.res.Configuration +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.core.CubicBezierEasing +import androidx.compose.animation.core.tween +import androidx.compose.animation.expandHorizontally +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.animation.scaleIn +import androidx.compose.animation.scaleOut +import androidx.compose.animation.shrinkHorizontally +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.heightIn +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.text.BasicTextField +import androidx.compose.foundation.text.KeyboardActions +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material3.Icon +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.Immutable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.focus.FocusRequester +import androidx.compose.ui.focus.focusRequester +import androidx.compose.ui.focus.onFocusChanged +import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.platform.LocalFocusManager +import androidx.compose.ui.platform.LocalSoftwareKeyboardController +import androidx.compose.ui.semantics.contentDescription +import androidx.compose.ui.semantics.semantics +import androidx.compose.ui.text.PlatformTextStyle +import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.text.input.KeyboardType +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.datasource.CollectionPreviewParameterProvider +import androidx.compose.ui.unit.dp +import com.tangem.core.ui.ds.image.TangemIconUM +import com.tangem.core.ui.ds2.button.TangemButton +import com.tangem.core.ui.ds2.surface.TangemSurface +import com.tangem.core.ui.extensions.TextReference +import com.tangem.core.ui.extensions.resolveReference +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.res.generated.icons.Icons +import com.tangem.core.ui.res.generated.icons.ic_cross_20 +import com.tangem.core.ui.res.generated.icons.ic_cross_circle_20_filled +import com.tangem.core.ui.res.generated.icons.ic_search_20 + +/** + * Search component (DS V3) + * + * [Figma](https://www.figma.com/design/AsnJ5CPHib4Qxw12gszjMS/%F0%9F%92%A0-DS-Components?node-id=3953-249&m=dev) + * + * @param state Hoisted state holding the query, active flag, placeholder, and callbacks. + * When [TangemSearch.State.onCloseClick] is `null`, the trailing close button is omitted. + * @param modifier Modifier applied to the outer row container. + * @param focusRequester Focus requester wired to the text field. Pass a hoisted instance to + * programmatically focus the field from the caller (e.g., on screen entry). Tapping anywhere on + * the surface also invokes [FocusRequester.requestFocus] on this instance. + */ +@Composable +fun TangemSearch( + state: TangemSearch.State, + modifier: Modifier = Modifier, + focusRequester: FocusRequester = remember { FocusRequester() }, +) { + val openingEasing = CubicBezierEasing(a = 0.8f, b = 0f, c = 0.2f, d = 1f) + val closingEasing = CubicBezierEasing(a = 0.8f, b = 0f, c = 0.8f, d = 1f) + + Row( + modifier = modifier, + verticalAlignment = Alignment.CenterVertically, + ) { + TangemSurface( + modifier = Modifier + .weight(1f) + .heightIn(min = TangemTheme.dimens3.size.s550), + isMaterial = true, + shape = CircleShape, + onClick = focusRequester::requestFocus, + ) { + SearchField(state = state, focusRequester = focusRequester) + } + + AnimatedVisibility( + visible = state.onCloseClick != null && (state.isActive || state.query.isNotEmpty()), + enter = expandHorizontally(animationSpec = tween(durationMillis = 150, easing = openingEasing)) + + fadeIn(animationSpec = tween(durationMillis = 150, delayMillis = 150, easing = openingEasing)) + + scaleIn(animationSpec = tween(durationMillis = 150, delayMillis = 150, easing = openingEasing)), + exit = fadeOut(animationSpec = tween(durationMillis = 150, easing = closingEasing)) + + scaleOut(animationSpec = tween(durationMillis = 150, easing = closingEasing)) + + shrinkHorizontally(animationSpec = tween(durationMillis = 150, easing = closingEasing)), + ) { + CloseButton(onClick = state.onCloseClick ?: {}) + } + } +} + +object TangemSearch { + + @Immutable + data class State( + val placeholderText: TextReference, + val query: String, + val onQueryChange: (String) -> Unit, + val isActive: Boolean, + val onActiveChange: (Boolean) -> Unit, + val onClearClick: () -> Unit = { }, + val onCloseClick: (() -> Unit)? = null, + ) +} + +@Composable +private fun SearchField(state: TangemSearch.State, focusRequester: FocusRequester) { + Row(verticalAlignment = Alignment.CenterVertically) { + Row( + modifier = Modifier + .weight(1f) + .padding( + start = TangemTheme.dimens3.spacing.s150, + top = TangemTheme.dimens3.spacing.s150, + bottom = TangemTheme.dimens3.spacing.s150, + ), + verticalAlignment = Alignment.CenterVertically, + ) { + Icon( + modifier = Modifier.padding(end = TangemTheme.dimens3.spacing.s100), + imageVector = Icons.ic_search_20, + tint = TangemTheme.colors3.icon.primary, + contentDescription = null, + ) + QueryTextField(state = state, focusRequester = focusRequester) + } + + AnimatedVisibility( + visible = state.query.isNotEmpty(), + enter = fadeIn(), + exit = fadeOut(), + ) { + ClearButton( + onClick = { + state.onQueryChange("") + state.onClearClick() + }, + ) + } + } +} + +@Composable +private fun QueryTextField(state: TangemSearch.State, focusRequester: FocusRequester) { + val keyboardController = LocalSoftwareKeyboardController.current + val focusManager = LocalFocusManager.current + // Initial composition reports isFocused = false. Without this guard the field would + // clobber a parent-supplied `state.isActive = true` on the first frame. + var isInitialComposition by remember { mutableStateOf(true) } + LaunchedEffect(Unit) { isInitialComposition = false } + + val placeholder = state.placeholderText.resolveReference() + val sharedTextStyle = TangemTheme.typography3.body.medium.copy( + platformStyle = PlatformTextStyle(includeFontPadding = false), + ) + BasicTextField( + modifier = Modifier + .fillMaxWidth() + .focusRequester(focusRequester) + .semantics { contentDescription = placeholder } + .onFocusChanged { focusState -> + if (!isInitialComposition) { + state.onActiveChange(focusState.isFocused) + } + }, + value = state.query, + onValueChange = state.onQueryChange, + singleLine = true, + textStyle = sharedTextStyle.copy(color = TangemTheme.colors3.text.primary), + cursorBrush = SolidColor(TangemTheme.colors3.icon.brand), + keyboardOptions = KeyboardOptions( + keyboardType = KeyboardType.Text, + imeAction = ImeAction.Search, + ), + keyboardActions = KeyboardActions( + onSearch = { + keyboardController?.hide() + focusManager.clearFocus() + }, + ), + decorationBox = { innerTextField -> + Box { + if (state.query.isEmpty()) { + Text( + modifier = Modifier.padding(end = TangemTheme.dimens3.spacing.s250), + text = placeholder, + style = sharedTextStyle, + color = TangemTheme.colors3.text.secondary, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + ) + } + innerTextField() + } + }, + ) +} + +@Composable +private fun ClearButton(onClick: () -> Unit) { + TangemButton( + modifier = Modifier.padding(end = TangemTheme.dimens3.spacing.s050), + size = TangemButton.Size.X9, + variant = TangemButton.Variant.Ghost, + iconStart = TangemIconUM.Icon(Icons.ic_cross_circle_20_filled), + onClick = onClick, + ) +} + +@Composable +private fun CloseButton(onClick: () -> Unit) { + val keyboardController = LocalSoftwareKeyboardController.current + val focusManager = LocalFocusManager.current + TangemButton( + modifier = Modifier.padding(start = TangemTheme.dimens3.spacing.s100), + size = TangemButton.Size.X11, + variant = TangemButton.Variant.Material, + iconStart = TangemIconUM.Icon(Icons.ic_cross_20), + onClick = { + keyboardController?.hide() + focusManager.clearFocus() + onClick() + }, + ) +} + +@Preview(showBackground = true) +@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES) +@Composable +private fun Preview(@PreviewParameter(TangemSearchStateProvider::class) state: TangemSearch.State) { + TangemThemePreviewRedesign { + Box(modifier = Modifier.background(TangemTheme.colors3.bg.secondary)) { + TangemSearch( + state = state, + modifier = Modifier + .fillMaxWidth() + .padding(16.dp), + ) + } + } +} + +private class TangemSearchStateProvider : CollectionPreviewParameterProvider( + collection = listOf( + // Idle: empty + inactive — no clear, no close. + TangemSearch.State( + placeholderText = stringReference("Search"), + query = "", + onQueryChange = {}, + isActive = false, + onActiveChange = {}, + onCloseClick = {}, + ), + // Focused empty: active + empty — close visible, clear hidden. + TangemSearch.State( + placeholderText = stringReference("Search"), + query = "", + onQueryChange = {}, + isActive = true, + onActiveChange = {}, + onCloseClick = {}, + ), + // Typing: active + query — clear and close both visible. + TangemSearch.State( + placeholderText = stringReference("Search"), + query = "Bitcoin", + onQueryChange = {}, + isActive = true, + onActiveChange = {}, + onClearClick = {}, + onCloseClick = {}, + ), + // Filled but blurred: query persists without focus — close stays visible. + TangemSearch.State( + placeholderText = stringReference("Search"), + query = "Ethereum", + onQueryChange = {}, + isActive = false, + onActiveChange = {}, + onClearClick = {}, + onCloseClick = {}, + ), + // Embedded variant: no close affordance at all (onCloseClick = null). + TangemSearch.State( + placeholderText = stringReference("Filter tokens"), + query = "USDT", + onQueryChange = {}, + isActive = true, + onActiveChange = {}, + onClearClick = {}, + onCloseClick = null, + ), + // Long query that exercises clipping inside the surface. + TangemSearch.State( + placeholderText = stringReference("Search"), + query = "A very long search query that should clip nicely", + onQueryChange = {}, + isActive = true, + onActiveChange = {}, + onClearClick = {}, + onCloseClick = {}, + ), + ), +) \ No newline at end of file diff --git a/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/storybook/entity/StoryBookPage.kt b/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/storybook/entity/StoryBookPage.kt index 5ee9733bf2..80da0f8c8d 100644 --- a/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/storybook/entity/StoryBookPage.kt +++ b/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/storybook/entity/StoryBookPage.kt @@ -291,6 +291,33 @@ internal data class TangemTopNavigationStory( } } +@Suppress("BooleanPropertyNaming") +internal data class TangemSearchStory( + val background: Background, + val placeholder: Placeholder, + val hasCloseButton: Boolean, + val onBackgroundChange: (Background) -> Unit, + val onPlaceholderChange: (Placeholder) -> Unit, + val onCloseButtonToggle: () -> Unit, +) : DsStoryBookPage { + + /** Backdrop the search preview is rendered on top of. */ + enum class Background(val label: String) { + Rainbow("rainbow"), + BgPrimary("bg.primary"), + BgSecondary("bg.secondary"), + BgBrand("bg.brand"), + BgInverse("bg.inverse"), + } + + /** Placeholder length variants — short typical label vs. long string to test layout. */ + enum class Placeholder(val label: String, val text: String) { + Short("short", "Search"), + Medium("medium", "Filter tokens"), + Long("long", "Search by name, symbol or contract address"), + } +} + internal data class TangemBadgeV2Story( val variant: TangemBadge.Variant, val status: TangemBadge.Status, diff --git a/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/storybook/page/ds/DsComponentsListScreen.kt b/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/storybook/page/ds/DsComponentsListScreen.kt index 5f5fdadcb0..5fb1754b5a 100644 --- a/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/storybook/page/ds/DsComponentsListScreen.kt +++ b/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/storybook/page/ds/DsComponentsListScreen.kt @@ -20,6 +20,7 @@ import com.tangem.feature.tester.presentation.storybook.page.ds.button.tangemBut import com.tangem.feature.tester.presentation.storybook.page.ds.fade.tangemFadeStoryFactory import com.tangem.feature.tester.presentation.storybook.page.ds.loader.tangemLoaderStoryFactory import com.tangem.feature.tester.presentation.storybook.page.ds.row.tangemRowStoryFactory +import com.tangem.feature.tester.presentation.storybook.page.ds.search.tangemSearchStoryFactory import com.tangem.feature.tester.presentation.storybook.page.ds.shimmer.tangemShimmerStoryFactory import com.tangem.feature.tester.presentation.storybook.page.ds.topnavigation.tangemTopNavigationStoryFactory @@ -30,6 +31,7 @@ private fun buildDsStories() = listOf( DsStoryItem(title = "🔘 TangemButton", factory = tangemButtonStoryFactory), DsStoryItem(title = "🏷️ TangemBadge", factory = tangemBadgeV2StoryFactory), DsStoryItem(title = "📋 TangemRow", factory = tangemRowStoryFactory), + DsStoryItem(title = "🔎 TangemSearch", factory = tangemSearchStoryFactory), DsStoryItem(title = "✨ TangemShimmer", factory = tangemShimmerStoryFactory), DsStoryItem(title = "🌫️ TangemFade", factory = tangemFadeStoryFactory), DsStoryItem(title = "🧭 TangemTopNavigation", factory = tangemTopNavigationStoryFactory), diff --git a/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/storybook/page/ds/search/Build.kt b/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/storybook/page/ds/search/Build.kt new file mode 100644 index 0000000000..5b7b454860 --- /dev/null +++ b/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/storybook/page/ds/search/Build.kt @@ -0,0 +1,25 @@ +package com.tangem.feature.tester.presentation.storybook.page.ds.search + +import com.tangem.feature.tester.presentation.storybook.entity.TangemSearchStory +import com.tangem.feature.tester.presentation.storybook.viewmodel.StateUpdater +import com.tangem.feature.tester.presentation.storybook.viewmodel.storyPageFactory + +internal fun StateUpdater.build(): TangemSearchStory { + return TangemSearchStory( + background = TangemSearchStory.Background.BgSecondary, + placeholder = TangemSearchStory.Placeholder.Short, + hasCloseButton = true, + onBackgroundChange = { background -> + updateStory { it.copy(background = background) } + }, + onPlaceholderChange = { placeholder -> + updateStory { it.copy(placeholder = placeholder) } + }, + onCloseButtonToggle = { + updateStory { it.copy(hasCloseButton = !it.hasCloseButton) } + }, + ) +} + +internal val tangemSearchStoryFactory + get() = storyPageFactory(StateUpdater::build) \ No newline at end of file diff --git a/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/storybook/page/ds/search/TangemSearchStory.kt b/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/storybook/page/ds/search/TangemSearchStory.kt new file mode 100644 index 0000000000..2acb3acec8 --- /dev/null +++ b/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/storybook/page/ds/search/TangemSearchStory.kt @@ -0,0 +1,286 @@ +@file:Suppress("MagicNumber") + +package com.tangem.feature.tester.presentation.storybook.page.ds.search + +import androidx.compose.animation.core.LinearEasing +import androidx.compose.animation.core.RepeatMode +import androidx.compose.animation.core.animateFloat +import androidx.compose.animation.core.infiniteRepeatable +import androidx.compose.animation.core.rememberInfiniteTransition +import androidx.compose.animation.core.tween +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.TileMode +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.unit.dp +import com.tangem.core.ui.ds2.search.TangemSearch +import com.tangem.core.ui.extensions.stringReference +import com.tangem.core.ui.res.TangemTheme +import com.tangem.feature.tester.presentation.storybook.entity.TangemSearchStory +import com.tangem.feature.tester.presentation.storybook.entity.TangemSearchStory.Background +import com.tangem.feature.tester.presentation.storybook.entity.TangemSearchStory.Placeholder + +@Composable +internal fun TangemSearchStory(state: TangemSearchStory, modifier: Modifier = Modifier) { + Column( + modifier = modifier + .fillMaxSize() + .background(TangemTheme.colors.background.primary) + .padding(vertical = 16.dp), + verticalArrangement = Arrangement.spacedBy(16.dp), + ) { + ComponentPreview(state = state) + Column( + modifier = Modifier.verticalScroll(rememberScrollState()), + verticalArrangement = Arrangement.spacedBy(16.dp), + ) { + BackgroundSelector(selected = state.background, onSelect = state.onBackgroundChange) + PlaceholderSelector(selected = state.placeholder, onSelect = state.onPlaceholderChange) + Toggles(state = state) + } + } +} + +@Composable +private fun ComponentPreview(state: TangemSearchStory) { + var query by remember { mutableStateOf("") } + var isActive by remember { mutableStateOf(false) } + + Box( + contentAlignment = Alignment.Center, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp) + .clip(RoundedCornerShape(16.dp)), + ) { + PreviewBackground(background = state.background, modifier = Modifier.matchParentSize()) + TangemSearch( + state = TangemSearch.State( + placeholderText = stringReference(state.placeholder.text), + query = query, + onQueryChange = { query = it }, + isActive = isActive, + onActiveChange = { isActive = it }, + onClearClick = { query = "" }, + // Close handles keyboard + focus internally; parent just clears the text. + onCloseClick = if (state.hasCloseButton) { + { query = "" } + } else { + null + }, + ), + modifier = Modifier + .fillMaxWidth() + .padding(16.dp), + ) + } +} + +@Composable +private fun PreviewBackground(background: Background, modifier: Modifier = Modifier) { + when (background) { + Background.Rainbow -> BlurTestBackground(modifier = modifier) + Background.BgPrimary -> Box(modifier.background(TangemTheme.colors3.bg.primary)) + Background.BgSecondary -> Box(modifier.background(TangemTheme.colors3.bg.secondary)) + Background.BgBrand -> Box(modifier.background(TangemTheme.colors3.bg.brand)) + Background.BgInverse -> Box(modifier.background(TangemTheme.colors3.bg.inverse)) + } +} + +@Composable +private fun BlurTestBackground(modifier: Modifier = Modifier) { + val bands = remember { + listOf( + Color(0xFFFF1744), // red + Color(0xFFFF9100), // orange + Color(0xFFFFEA00), // yellow + Color(0xFF00E676), // green + Color(0xFF00B8D4), // cyan + Color(0xFF2962FF), // blue + Color(0xFFD500F9), // magenta + ) + } + val stops = remember(bands) { + buildList { + bands.forEachIndexed { index, color -> + val start = index.toFloat() / bands.size + val end = (index + 1).toFloat() / bands.size + add(start to color) + add(end to color) + } + }.toTypedArray() + } + val tilePx = with(LocalDensity.current) { 320.dp.toPx() } + val transition = rememberInfiniteTransition(label = "blur-bg") + val offset by transition.animateFloat( + initialValue = 0f, + targetValue = tilePx, + animationSpec = infiniteRepeatable( + animation = tween(durationMillis = 4_000, easing = LinearEasing), + repeatMode = RepeatMode.Restart, + ), + label = "blur-bg-offset", + ) + Box( + modifier = modifier.background( + brush = Brush.linearGradient( + colorStops = stops, + start = Offset(offset, 0f), + end = Offset(offset + tilePx, 0f), + tileMode = TileMode.Repeated, + ), + ), + ) +} + +@Composable +private fun BackgroundSelector(selected: Background, onSelect: (Background) -> Unit) { + Section(label = "Background") { + ChipGrid( + items = Background.entries, + label = { it.label }, + isSelected = { it == selected }, + onSelect = onSelect, + ) + } +} + +@Composable +private fun PlaceholderSelector(selected: Placeholder, onSelect: (Placeholder) -> Unit) { + Section(label = "Placeholder") { + ChipGrid( + items = Placeholder.entries, + label = { it.label }, + isSelected = { it == selected }, + onSelect = onSelect, + ) + } +} + +@Composable +private fun Toggles(state: TangemSearchStory) { + Section(label = "Flags") { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + verticalArrangement = Arrangement.spacedBy(8.dp), + ) { + ToggleRow( + label = "close button (onCloseClick)", + checked = state.hasCloseButton, + onToggle = state.onCloseButtonToggle, + ) + } + } +} + +@Composable +private fun Section(label: String, content: @Composable () -> Unit) { + Column(verticalArrangement = Arrangement.spacedBy(8.dp)) { + Text( + modifier = Modifier.padding(horizontal = 16.dp), + text = label, + style = TangemTheme.typography.subtitle1, + color = TangemTheme.colors.text.primary1, + ) + content() + } +} + +@Composable +private fun ChipGrid(items: List, label: (T) -> String, isSelected: (T) -> Boolean, onSelect: (T) -> Unit) { + val shape = RoundedCornerShape(50) + Row( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp) + .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), + ) { + items.forEach { item -> + Chip( + label = label(item), + selected = isSelected(item), + onClick = { onSelect(item) }, + modifier = Modifier.weight(1f), + ) + } + } +} + +@Composable +private fun Chip(label: String, selected: Boolean, onClick: () -> Unit, modifier: Modifier = Modifier) { + val chipShape = RoundedCornerShape(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 ToggleRow(label: String, checked: Boolean, onToggle: () -> Unit) { + Row( + modifier = Modifier + .fillMaxWidth() + .clip(RoundedCornerShape(12.dp)) + .background(TangemTheme.colors2.surface.level2) + .clickable(onClick = onToggle) + .padding(horizontal = 12.dp, vertical = 12.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween, + ) { + Text( + text = label, + style = TangemTheme.typography.body2, + color = TangemTheme.colors.text.primary1, + ) + Text( + text = if (checked) "ON" else "OFF", + style = TangemTheme.typography.caption2, + color = if (checked) TangemTheme.colors.text.accent else TangemTheme.colors.text.secondary, + ) + } +} \ No newline at end of file diff --git a/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/storybook/ui/StoryBookScreen.kt b/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/storybook/ui/StoryBookScreen.kt index b3fbfe500d..0ca00f04d3 100644 --- a/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/storybook/ui/StoryBookScreen.kt +++ b/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/storybook/ui/StoryBookScreen.kt @@ -42,6 +42,7 @@ import com.tangem.feature.tester.presentation.storybook.page.ds.button.TangemBut import com.tangem.feature.tester.presentation.storybook.page.ds.fade.TangemFadeStory import com.tangem.feature.tester.presentation.storybook.page.ds.loader.TangemLoaderStory import com.tangem.feature.tester.presentation.storybook.page.ds.row.TangemRowStory +import com.tangem.feature.tester.presentation.storybook.page.ds.search.TangemSearchStory import com.tangem.feature.tester.presentation.storybook.page.ds.shimmer.TangemShimmerStory import com.tangem.feature.tester.presentation.storybook.page.ds.topnavigation.TangemTopNavigationStory import com.tangem.feature.tester.presentation.storybook.page.headerrow.TangemHeaderRowStory @@ -92,6 +93,7 @@ internal fun StoryBookScreen(state: StoryBookUM, modifier: Modifier = Modifier) is TangemButtonStory -> TangemButtonStory(state = storyState) is TangemBadgeV2Story -> TangemBadgeV2Story(state = storyState) is TangemRowStory -> TangemRowStory(state = storyState) + is TangemSearchStory -> TangemSearchStory(state = storyState) is TangemShimmerStory -> TangemShimmerStory(state = storyState) is TangemFadeStory -> TangemFadeStory(state = storyState) is TangemTopNavigationStory -> TangemTopNavigationStory(state = storyState)