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 c74dce4d36..e3bb4e61e4 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 @@ -388,6 +388,24 @@ internal data class TangemMessageBannerStory( } } +internal data class TextStyleStory( + val style: Style, + val textScale: Float, + val onStyleChange: (Style) -> Unit, + val onTextScaleChange: (Float) -> Unit, +) : DsStoryBookPage { + + /** DS3 typography3 text styles available in the preview. Labels match the Figma naming. */ + enum class Style(val label: String) { + Display("Display"), + HeadM("Head.M"), + HeadS("Head.S"), + Body("Body"), + SubH("Sub.H"), + Caption("Caption"), + } +} + 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 8fb85b511b..88d7b8883f 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 @@ -26,6 +26,7 @@ import com.tangem.feature.tester.presentation.storybook.page.ds.messagebanner.ta 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.textstyle.textStyleStoryFactory import com.tangem.feature.tester.presentation.storybook.page.ds.topnavigation.tangemTopNavigationStoryFactory private data class DsStoryItem(val title: String, val factory: StoryPageFactory) @@ -43,6 +44,7 @@ private fun buildDsStories() = listOf( DsStoryItem(title = "🧭 TangemTopNavigation", factory = tangemTopNavigationStoryFactory), DsStoryItem(title = "💫 TangemGlowRing", factory = tangemGlowRingStoryFactory), DsStoryItem(title = "📢 TangemMessageBanner", factory = tangemMessageBannerStoryFactory), + DsStoryItem(title = "🔤 Text styles", factory = textStyleStoryFactory), ) @Composable diff --git a/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/storybook/page/ds/textstyle/Build.kt b/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/storybook/page/ds/textstyle/Build.kt new file mode 100644 index 0000000000..1a8d4152ea --- /dev/null +++ b/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/storybook/page/ds/textstyle/Build.kt @@ -0,0 +1,21 @@ +package com.tangem.feature.tester.presentation.storybook.page.ds.textstyle + +import com.tangem.feature.tester.presentation.storybook.entity.TextStyleStory +import com.tangem.feature.tester.presentation.storybook.viewmodel.StateUpdater +import com.tangem.feature.tester.presentation.storybook.viewmodel.storyPageFactory + +internal fun StateUpdater.build(): TextStyleStory { + return TextStyleStory( + style = TextStyleStory.Style.HeadM, + textScale = 1f, + onStyleChange = { style -> + updateStory { it.copy(style = style) } + }, + onTextScaleChange = { scale -> + updateStory { it.copy(textScale = scale) } + }, + ) +} + +internal val textStyleStoryFactory + 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/textstyle/TextStyleStory.kt b/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/storybook/page/ds/textstyle/TextStyleStory.kt new file mode 100644 index 0000000000..0a66cadfc4 --- /dev/null +++ b/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/storybook/page/ds/textstyle/TextStyleStory.kt @@ -0,0 +1,192 @@ +@file:Suppress("MagicNumber") + +package com.tangem.feature.tester.presentation.storybook.page.ds.textstyle + +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.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.statusBarsPadding +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Slider +import androidx.compose.material3.SliderDefaults +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.ReadOnlyComposable +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.Density +import androidx.compose.ui.unit.dp +import com.tangem.core.ui.res.TangemTheme +import com.tangem.feature.tester.presentation.storybook.entity.TextStyleStory + +@Composable +internal fun TextStyleStory(state: TextStyleStory, modifier: Modifier = Modifier) { + Column( + modifier = modifier + .statusBarsPadding() + .fillMaxSize() + .background(TangemTheme.colors2.surface.level1), + verticalArrangement = Arrangement.spacedBy(24.dp), + ) { + ComponentPreview(style = state.style, textScale = state.textScale) + StyleSelector(selected = state.style, onSelect = state.onStyleChange) + TextScaleSlider(value = state.textScale, onChange = state.onTextScaleChange) + } +} + +@Composable +private fun ComponentPreview(style: TextStyleStory.Style, textScale: Float) { + val textStyle = style.toTextStyle() + val baseDensity = LocalDensity.current + val scaledDensity = remember(baseDensity, textScale) { + Density(density = baseDensity.density, fontScale = textScale) + } + Box( + contentAlignment = Alignment.Center, + modifier = Modifier + .fillMaxWidth() + .height(320.dp) + .padding(horizontal = 16.dp) + .clip(RoundedCornerShape(16.dp)) + .background(TangemTheme.colors2.surface.level2) + .padding(24.dp), + ) { + CompositionLocalProvider(LocalDensity provides scaledDensity) { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(24.dp), + ) { + SampleText(text = "multiline\nstring", style = textStyle) + Column(horizontalAlignment = Alignment.CenterHorizontally) { + SampleText(text = "one string", style = textStyle) + SampleText(text = "separate string", style = textStyle) + } + } + } + } +} + +@Composable +private fun SampleText(text: String, style: TextStyle) { + Text( + text = text, + style = style, + color = TangemTheme.colors3.text.primary, + textAlign = TextAlign.Center, + ) +} + +@Composable +private fun StyleSelector(selected: TextStyleStory.Style, onSelect: (TextStyleStory.Style) -> Unit) { + Column( + verticalArrangement = Arrangement.spacedBy(8.dp), + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + ) { + Text( + text = "Text style", + style = TangemTheme.typography.subtitle1, + color = TangemTheme.colors.text.primary1, + ) + val shape = RoundedCornerShape(50) + Row( + modifier = Modifier + .fillMaxWidth() + .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), + ) { + TextStyleStory.Style.entries.forEach { style -> + StyleChip( + label = style.label, + selected = style == selected, + onClick = { onSelect(style) }, + modifier = Modifier.weight(1f), + ) + } + } + } +} + +@Composable +private fun TextScaleSlider(value: Float, onChange: (Float) -> Unit) { + Column( + verticalArrangement = Arrangement.spacedBy(8.dp), + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + ) { + Text( + text = "Text scale: ${"%.2f".format(value)}x", + style = TangemTheme.typography.subtitle1, + color = TangemTheme.colors.text.primary1, + ) + Slider( + modifier = Modifier.fillMaxWidth(), + value = value, + onValueChange = onChange, + valueRange = 0.5f..2f, + steps = 14, + colors = SliderDefaults.colors( + thumbColor = TangemTheme.colors.text.accent, + activeTrackColor = TangemTheme.colors.text.accent, + activeTickColor = TangemTheme.colors2.surface.level3, + inactiveTrackColor = TangemTheme.colors2.surface.level3, + inactiveTickColor = TangemTheme.colors.text.accent, + ), + ) + } +} + +@Composable +private fun StyleChip(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 +@ReadOnlyComposable +private fun TextStyleStory.Style.toTextStyle(): TextStyle = when (this) { + TextStyleStory.Style.Display -> TangemTheme.typography3.display.medium + TextStyleStory.Style.HeadM -> TangemTheme.typography3.heading.medium + TextStyleStory.Style.HeadS -> TangemTheme.typography3.heading.small + TextStyleStory.Style.Body -> TangemTheme.typography3.body.medium + TextStyleStory.Style.SubH -> TangemTheme.typography3.subheading.medium + TextStyleStory.Style.Caption -> TangemTheme.typography3.caption.medium +} \ 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 79dd299759..721a0b4433 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 @@ -48,6 +48,7 @@ import com.tangem.feature.tester.presentation.storybook.page.ds.messagebanner.Ta 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.textstyle.TextStyleStory import com.tangem.feature.tester.presentation.storybook.page.ds.topnavigation.TangemTopNavigationStory import com.tangem.feature.tester.presentation.storybook.page.headerrow.TangemHeaderRowStory import com.tangem.feature.tester.presentation.storybook.page.message.TangemMessageStory @@ -105,6 +106,7 @@ internal fun StoryBookScreen(state: StoryBookUM, modifier: Modifier = Modifier) is TangemFadeStory -> TangemFadeStory(state = storyState) is TangemTopNavigationStory -> TangemTopNavigationStory(state = storyState) is TangemMessageBannerStory -> TangemMessageBannerStory(state = storyState) + is TextStyleStory -> TextStyleStory(state = storyState) } } } \ No newline at end of file