From c13b966556fe4d1fe97f2d4d9eeb539d4df264a5 Mon Sep 17 00:00:00 2001 From: Arina Gazhina Date: Tue, 25 Aug 2026 00:44:23 +0300 Subject: [PATCH 01/14] fix(theme): elevations for semitransparent background --- .../atls/hyperion/ui/primitives/Elevation.kt | 36 +++++++++---------- .../ui/primitives/utils/DropShadowClipping.kt | 21 +++++++++++ .../primitives/utils/DropShadowIsolation.kt | 30 ++++++++++++++++ .../ui/primitives/utils/ShadowConversion.kt | 12 +++++++ 4 files changed, 79 insertions(+), 20 deletions(-) create mode 100644 mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/primitives/utils/DropShadowClipping.kt create mode 100644 mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/primitives/utils/DropShadowIsolation.kt create mode 100644 mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/primitives/utils/ShadowConversion.kt diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/primitives/Elevation.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/primitives/Elevation.kt index d2614d89b..41491f6b4 100644 --- a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/primitives/Elevation.kt +++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/primitives/Elevation.kt @@ -7,8 +7,9 @@ import androidx.compose.ui.draw.innerShadow import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.RectangleShape import androidx.compose.ui.graphics.Shape -import androidx.compose.ui.graphics.shadow.Shadow -import androidx.compose.ui.unit.DpOffset +import com.atls.hyperion.ui.primitives.utils.clearDropShadowInside +import com.atls.hyperion.ui.primitives.utils.isolateDropShadows +import com.atls.hyperion.ui.primitives.utils.toComposeShadow import com.atls.hyperion.ui.theme.tokens.elevation.Elevation import com.atls.hyperion.ui.theme.tokens.elevation.ShadowType @@ -17,34 +18,29 @@ fun Modifier.elevation( backgroundColor: Color, shape: Shape = RectangleShape ): Modifier { - val withDropShadows = elevation.shadows.fold(this) { modifier, shadow -> - if (shadow.type == ShadowType.Drop) { - modifier.dropShadow( - shape = shape, - shadow = shadow.toComposeShadow() - ) - } else { - modifier - } + val dropShadows = elevation.shadows.filter { it.type == ShadowType.Drop } + val withDropShadows = dropShadows.fold(isolateDropShadows(dropShadows)) { modifier, shadow -> + modifier.dropShadow( + shape = shape, + shadow = shadow.toComposeShadow(backgroundColor.alpha) + ) + } + val withoutDropShadowInside = if (dropShadows.isEmpty()) { + withDropShadows + } else { + withDropShadows.clearDropShadowInside(shape) } - val withBackground = withDropShadows.background(backgroundColor, shape) + val withBackground = withoutDropShadowInside.background(backgroundColor, shape) return elevation.shadows.fold(withBackground) { modifier, shadow -> if (shadow.type == ShadowType.Inner) { modifier.innerShadow( shape = shape, - shadow = shadow.toComposeShadow() + shadow = shadow.toComposeShadow(backgroundColor.alpha) ) } else { modifier } } } - -private fun com.atls.hyperion.ui.theme.tokens.elevation.Shadow.toComposeShadow() = Shadow( - radius = blur, - spread = spread, - offset = DpOffset(offsetX, offsetY), - color = color -) diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/primitives/utils/DropShadowClipping.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/primitives/utils/DropShadowClipping.kt new file mode 100644 index 000000000..69f0d3ce4 --- /dev/null +++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/primitives/utils/DropShadowClipping.kt @@ -0,0 +1,21 @@ +package com.atls.hyperion.ui.primitives.utils + +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.drawWithCache +import androidx.compose.ui.graphics.BlendMode +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.Shape +import androidx.compose.ui.graphics.drawOutline + +internal fun Modifier.clearDropShadowInside(shape: Shape): Modifier = drawWithCache { + val outline = shape.createOutline(size, layoutDirection, this) + + onDrawWithContent { + drawOutline( + outline = outline, + color = Color.Black, + blendMode = BlendMode.Clear + ) + drawContent() + } +} diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/primitives/utils/DropShadowIsolation.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/primitives/utils/DropShadowIsolation.kt new file mode 100644 index 000000000..33a65ef07 --- /dev/null +++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/primitives/utils/DropShadowIsolation.kt @@ -0,0 +1,30 @@ +package com.atls.hyperion.ui.primitives.utils + +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.drawWithCache +import androidx.compose.ui.geometry.Rect +import androidx.compose.ui.graphics.Paint +import androidx.compose.ui.graphics.drawscope.drawIntoCanvas +import com.atls.hyperion.ui.theme.tokens.elevation.Shadow +import kotlin.math.abs + +internal fun Modifier.isolateDropShadows(shadows: List): Modifier { + if (shadows.isEmpty()) return this + + return drawWithCache { + val layerPadding = shadows.maxOf { shadow -> + shadow.blur.toPx() + shadow.spread.toPx() + maxOf( + abs(shadow.offsetX.toPx()), + abs(shadow.offsetY.toPx()) + ) + }.coerceAtLeast(0f) + val layerBounds = Rect(0f, 0f, size.width, size.height).inflate(layerPadding) + val layerPaint = Paint() + + onDrawWithContent { + drawIntoCanvas { canvas -> canvas.saveLayer(layerBounds, layerPaint) } + drawContent() + drawIntoCanvas { canvas -> canvas.restore() } + } + } +} diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/primitives/utils/ShadowConversion.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/primitives/utils/ShadowConversion.kt new file mode 100644 index 000000000..5c7064832 --- /dev/null +++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/primitives/utils/ShadowConversion.kt @@ -0,0 +1,12 @@ +package com.atls.hyperion.ui.primitives.utils + +import androidx.compose.ui.unit.DpOffset +import com.atls.hyperion.ui.theme.tokens.elevation.Shadow +import androidx.compose.ui.graphics.shadow.Shadow as ComposeShadow + +internal fun Shadow.toComposeShadow(alphaMultiplier: Float) = ComposeShadow( + radius = blur, + spread = spread, + offset = DpOffset(offsetX, offsetY), + color = color.copy(alpha = color.alpha * alphaMultiplier) +) From 38dbfe2a18a5938283d895f8ac16c917f3e32afd Mon Sep 17 00:00:00 2001 From: Arina Gazhina Date: Tue, 25 Aug 2026 00:44:54 +0300 Subject: [PATCH 02/14] feat(button): expand button states --- .../atls/hyperion/ui/components/button/state/ButtonState.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/button/state/ButtonState.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/button/state/ButtonState.kt index 2f1d9a198..b4e006290 100644 --- a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/button/state/ButtonState.kt +++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/button/state/ButtonState.kt @@ -3,5 +3,6 @@ package com.atls.hyperion.ui.components.button.state enum class ButtonState { Default, Disabled, - Pressed + Pressed, + Focused } From e9c60a1b42e04aefc75e18d106e55b3b1436339c Mon Sep 17 00:00:00 2001 From: Arina Gazhina Date: Tue, 25 Aug 2026 01:00:09 +0300 Subject: [PATCH 03/14] feat(button): appearances update --- .../button/styles/appearance/Appearance.kt | 5 +- .../button/styles/appearance/Colors.kt | 30 ++--- .../button/styles/appearance/Variants.kt | 115 ++++++++++++------ 3 files changed, 98 insertions(+), 52 deletions(-) diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/button/styles/appearance/Appearance.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/button/styles/appearance/Appearance.kt index 1c1b0db9c..1c64ea6c3 100644 --- a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/button/styles/appearance/Appearance.kt +++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/button/styles/appearance/Appearance.kt @@ -1,20 +1,19 @@ package com.atls.hyperion.ui.components.button.styles.appearance -import androidx.compose.ui.unit.Dp import com.atls.hyperion.ui.components.button.state.ButtonState -import com.atls.hyperion.ui.theme.tokens.layout.Elevation data class ButtonAppearance( val default: Colors, val pressed: Colors, val disabled: Colors, - val shadowElevation: Dp = Elevation.zero + val focused: Colors ) { fun fromState(state: ButtonState): Colors { return when (state) { ButtonState.Default -> default ButtonState.Disabled -> disabled ButtonState.Pressed -> pressed + ButtonState.Focused -> focused } } diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/button/styles/appearance/Colors.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/button/styles/appearance/Colors.kt index 3431f7941..5dc02767b 100644 --- a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/button/styles/appearance/Colors.kt +++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/button/styles/appearance/Colors.kt @@ -2,21 +2,21 @@ package com.atls.hyperion.ui.components.button.styles.appearance import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color -import com.atls.hyperion.ui.theme.tokens.colors.LegacyColors as ThemeColors -sealed class Colors( - val textColor: Color, - val borderColor: Color = ThemeColors.Palette.transparent -) { - class Solid( - val backgroundColor: Color, - textColor: Color, - borderColor: Color = ThemeColors.Palette.transparent - ) : Colors(textColor, borderColor) +sealed interface Colors { + val content: Color + val border: Color - class Gradient( - val backgroundBrush: Brush, - textColor: Color, - borderColor: Color = ThemeColors.Palette.transparent - ) : Colors(textColor, borderColor) + data class Solid( + val background: Color, + override val content: Color, + override val border: Color = Color.Transparent + ) : Colors + + data class Gradient( + val background: Brush, + override val content: Color, + override val border: Color = Color.Transparent, + val backgroundAlpha: Float = 1f + ) : Colors } diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/button/styles/appearance/Variants.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/button/styles/appearance/Variants.kt index d495ef4bb..742de45d4 100644 --- a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/button/styles/appearance/Variants.kt +++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/button/styles/appearance/Variants.kt @@ -1,42 +1,89 @@ package com.atls.hyperion.ui.components.button.styles.appearance -import com.atls.hyperion.ui.theme.tokens.colors.LegacyColors -import com.atls.hyperion.ui.components.button.styles.appearance.Colors as ButtonColors +import androidx.compose.runtime.Composable +import androidx.compose.ui.graphics.Color +import com.atls.hyperion.ui.theme.LocalHyperionColors -fun ButtonAppearance.Companion.blue(): ButtonAppearance = - ButtonAppearance( - default = ButtonColors.Solid( - backgroundColor = LegacyColors.Button.Blue.Default.background, - textColor = LegacyColors.Button.Blue.Default.font, - borderColor = LegacyColors.Button.Blue.Default.border - ), - pressed = ButtonColors.Solid( - backgroundColor = LegacyColors.Button.Blue.Pressed.background, - textColor = LegacyColors.Button.Blue.Pressed.font, - borderColor = LegacyColors.Button.Blue.Pressed.border - ), - disabled = ButtonColors.Solid( - backgroundColor = LegacyColors.Button.Blue.Disabled.background, - textColor = LegacyColors.Button.Blue.Disabled.font, - borderColor = LegacyColors.Button.Blue.Disabled.border +@Composable +fun ButtonAppearance.Companion.primary(): ButtonAppearance { + val colors = LocalHyperionColors.current + + return ButtonAppearance( + default = Colors.Solid( + background = colors.action.base, + content = colors.text.inverse, + border = colors.action.base + ), + pressed = Colors.Solid( + background = colors.action.pressed, + content = colors.text.inverse, + border = colors.action.pressed + ), + disabled = Colors.Solid( + background = colors.action.disabled, + content = colors.text.muted, + border = colors.action.subtle + ), + focused = Colors.Solid( + background = colors.action.base, + content = colors.text.inverse, + border = colors.action.pressed ) ) +} -fun ButtonAppearance.Companion.lightBlue(): ButtonAppearance = - ButtonAppearance( - default = ButtonColors.Solid( - backgroundColor = LegacyColors.Button.LightBlue.Default.background, - textColor = LegacyColors.Button.LightBlue.Default.font, - borderColor = LegacyColors.Button.LightBlue.Default.border - ), - pressed = ButtonColors.Solid( - backgroundColor = LegacyColors.Button.LightBlue.Pressed.background, - textColor = LegacyColors.Button.LightBlue.Pressed.font, - borderColor = LegacyColors.Button.LightBlue.Pressed.border - ), - disabled = ButtonColors.Solid( - backgroundColor = LegacyColors.Button.LightBlue.Disabled.background, - textColor = LegacyColors.Button.LightBlue.Disabled.font, - borderColor = LegacyColors.Button.LightBlue.Disabled.border +@Composable +fun ButtonAppearance.Companion.secondary(): ButtonAppearance { + val colors = LocalHyperionColors.current + + return ButtonAppearance( + default = Colors.Solid( + background = colors.action.subtle, + content = colors.text.secondary, + border = colors.surface.muted + ), + pressed = Colors.Solid( + background = colors.text.secondary, + content = colors.text.muted, + border = colors.action.subtle + ), + disabled = Colors.Solid( + background = colors.surface.muted, + content = colors.text.tertiary, + border = colors.surface.muted + ), + focused = Colors.Solid( + background = colors.action.disabled, + content = colors.text.secondary, + border = colors.action.hover + ) + ) +} + +@Composable +fun ButtonAppearance.Companion.ghost(): ButtonAppearance { + val colors = LocalHyperionColors.current + + return ButtonAppearance( + default = Colors.Solid( + background = Color.Transparent, + content = colors.text.secondary, + border = Color.Transparent + ), + pressed = Colors.Solid( + background = colors.action.subtle, + content = colors.text.tertiary, + border = colors.action.subtle + ), + disabled = Colors.Solid( + background = Color.Transparent, + content = colors.text.tertiary, + border = Color.Transparent + ), + focused = Colors.Solid( + background = Color.Transparent, + content = colors.text.secondary, + border = colors.action.disabled ) ) +} From 8190aadb270935a925ebfac42cf4562d3296c5a1 Mon Sep 17 00:00:00 2001 From: Arina Gazhina Date: Tue, 25 Aug 2026 01:00:31 +0300 Subject: [PATCH 04/14] feat(button): shapes update --- .../button/styles/shape/Constants.kt | 10 ++ .../components/button/styles/shape/Shape.kt | 7 +- .../button/styles/shape/Variants.kt | 106 +++++++++--------- 3 files changed, 69 insertions(+), 54 deletions(-) create mode 100644 mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/button/styles/shape/Constants.kt diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/button/styles/shape/Constants.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/button/styles/shape/Constants.kt new file mode 100644 index 000000000..1b2c575dc --- /dev/null +++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/button/styles/shape/Constants.kt @@ -0,0 +1,10 @@ +package com.atls.hyperion.ui.components.button.styles.shape + +import androidx.compose.ui.unit.dp + +internal object MinHeight { + val xs = 24.dp + val sm = 28.dp + val md = 32.dp + val lg = 32.dp +} diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/button/styles/shape/Shape.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/button/styles/shape/Shape.kt index 5b4a745a3..8a2a35de4 100644 --- a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/button/styles/shape/Shape.kt +++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/button/styles/shape/Shape.kt @@ -3,12 +3,17 @@ package com.atls.hyperion.ui.components.button.styles.shape import androidx.compose.foundation.layout.PaddingValues import androidx.compose.ui.text.TextStyle import androidx.compose.ui.unit.Dp +import com.atls.hyperion.ui.theme.tokens.elevation.PressableElevationStates data class ButtonShape( + val minHeight: Dp, val cornerRadius: Dp, val paddings: PaddingValues, val typography: TextStyle, - val borderStroke: Dp + val borderWidth: Dp, + val gap: Dp, + val addonSize: Dp, + val elevations: PressableElevationStates ) { companion object Companion } diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/button/styles/shape/Variants.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/button/styles/shape/Variants.kt index 17bb096e9..32fcc6848 100644 --- a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/button/styles/shape/Variants.kt +++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/button/styles/shape/Variants.kt @@ -2,72 +2,72 @@ package com.atls.hyperion.ui.components.button.styles.shape import androidx.compose.foundation.layout.PaddingValues import androidx.compose.runtime.Composable -import androidx.compose.ui.text.TextStyle -import com.atls.hyperion.ui.theme.tokens.borders.BorderStroke -import com.atls.hyperion.ui.theme.tokens.layout.CornerRadius -import com.atls.hyperion.ui.theme.tokens.layout.Space -import com.atls.hyperion.ui.theme.typography.FontSize -import com.atls.hyperion.ui.theme.typography.LineHeight +import com.atls.hyperion.ui.theme.LocalHyperionElevations +import com.atls.hyperion.ui.theme.LocalHyperionTypography +import com.atls.hyperion.ui.theme.tokens.borders.BorderWidth +import com.atls.hyperion.ui.theme.tokens.layout.Radii +import com.atls.hyperion.ui.theme.tokens.layout.Spacing @Composable -fun ButtonShape.Companion.huge(): ButtonShape = +fun ButtonShape.Companion.xs(): ButtonShape = ButtonShape( - cornerRadius = CornerRadius.xl4, - paddings = PaddingValues(horizontal = Space.xl3, vertical = Space.lg), - typography = TextStyle(fontSize = FontSize.xl3, lineHeight = LineHeight.xl4), - borderStroke = BorderStroke.none + minHeight = MinHeight.xs, + cornerRadius = Radii.xs2, + paddings = PaddingValues( + horizontal = Spacing.component.sm, + vertical = Spacing.component.xs + ), + typography = LocalHyperionTypography.current.label.sm, + borderWidth = BorderWidth.sm, + gap = Spacing.component.xs, + addonSize = Spacing.component.lg, + elevations = LocalHyperionElevations.current.xs ) @Composable -fun ButtonShape.Companion.large(): ButtonShape = +fun ButtonShape.Companion.sm(): ButtonShape = ButtonShape( - cornerRadius = CornerRadius.xl4, - paddings = PaddingValues(horizontal = Space.xl3, vertical = Space.md), - typography = TextStyle(fontSize = FontSize.xl2, lineHeight = LineHeight.xl3), - borderStroke = BorderStroke.none + minHeight = MinHeight.sm, + cornerRadius = Radii.xs2, + paddings = PaddingValues( + horizontal = Spacing.component.md, + vertical = Spacing.component.sm + ), + typography = LocalHyperionTypography.current.body.md, + borderWidth = BorderWidth.sm, + gap = Spacing.component.md, + addonSize = Spacing.component.xl, + elevations = LocalHyperionElevations.current.sm ) @Composable -fun ButtonShape.Companion.semiMedium(): ButtonShape = +fun ButtonShape.Companion.md(): ButtonShape = ButtonShape( - cornerRadius = CornerRadius.xl4, - paddings = PaddingValues(horizontal = Space.xl, vertical = Space.sm), - typography = TextStyle(fontSize = FontSize.lg, lineHeight = LineHeight.xl), - borderStroke = BorderStroke.none + minHeight = MinHeight.md, + cornerRadius = Radii.xs2, + paddings = PaddingValues( + horizontal = Spacing.component.lg, + vertical = Spacing.component.md + ), + typography = LocalHyperionTypography.current.label.md, + borderWidth = BorderWidth.sm, + gap = Spacing.component.lg, + addonSize = Spacing.component.xl2, + elevations = LocalHyperionElevations.current.md ) @Composable -fun ButtonShape.Companion.medium(): ButtonShape = +fun ButtonShape.Companion.lg(): ButtonShape = ButtonShape( - cornerRadius = CornerRadius.xl4, - paddings = PaddingValues(horizontal = Space.lg, vertical = Space.xs), - typography = TextStyle(fontSize = FontSize.md, lineHeight = LineHeight.md), - borderStroke = BorderStroke.none - ) - -@Composable -fun ButtonShape.Companion.normal(): ButtonShape = - ButtonShape( - cornerRadius = CornerRadius.xl4, - paddings = PaddingValues(horizontal = Space.xl3, vertical = Space.xs2), - typography = TextStyle(fontSize = FontSize.xs, lineHeight = LineHeight.sm), - borderStroke = BorderStroke.none - ) - -@Composable -fun ButtonShape.Companion.small(): ButtonShape = - ButtonShape( - cornerRadius = CornerRadius.xl4, - paddings = PaddingValues(horizontal = Space.lg, vertical = Space.xs3), - typography = TextStyle(fontSize = FontSize.xs, lineHeight = LineHeight.xs), - borderStroke = BorderStroke.none - ) - -@Composable -fun ButtonShape.Companion.smallSizeMediumRadii(): ButtonShape = - ButtonShape( - cornerRadius = CornerRadius.xl2_5, - paddings = PaddingValues(horizontal = Space.lg, vertical = Space.xs3), - typography = TextStyle(fontSize = FontSize.xs, lineHeight = LineHeight.xs), - borderStroke = BorderStroke.none + minHeight = MinHeight.lg, + cornerRadius = Radii.xs2, + paddings = PaddingValues( + horizontal = Spacing.component.xl, + vertical = Spacing.component.sm + ), + typography = LocalHyperionTypography.current.label.lg, + borderWidth = BorderWidth.sm, + gap = Spacing.component.xl, + addonSize = Spacing.component.xl2, + elevations = LocalHyperionElevations.current.lg ) From f0d09bac60b76207029147132cb17f474649ba25 Mon Sep 17 00:00:00 2001 From: Arina Gazhina Date: Tue, 25 Aug 2026 01:20:52 +0300 Subject: [PATCH 05/14] refactor(button): layout --- .../ui/components/button/Container.kt | 71 +++++++++++++++ .../hyperion/ui/components/button/Content.kt | 84 +++++++++++++++++ .../hyperion/ui/components/button/Layout.kt | 90 +++++-------------- 3 files changed, 176 insertions(+), 69 deletions(-) create mode 100644 mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/button/Container.kt create mode 100644 mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/button/Content.kt diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/button/Container.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/button/Container.kt new file mode 100644 index 000000000..7fb6eebf2 --- /dev/null +++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/button/Container.kt @@ -0,0 +1,71 @@ +package com.atls.hyperion.ui.components.button + +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.clickable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.heightIn +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.LocalContentAlpha +import androidx.compose.material.LocalContentColor +import androidx.compose.material.Surface +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.semantics.Role +import com.atls.hyperion.ui.components.button.styles.appearance.Colors +import com.atls.hyperion.ui.components.button.styles.shape.ButtonShape +import com.atls.hyperion.ui.primitives.elevation +import com.atls.hyperion.ui.theme.tokens.elevation.Elevation + +@Composable +internal fun ButtonContainer( + modifier: Modifier, + enabled: Boolean, + interactionSource: MutableInteractionSource, + colors: Colors, + elevation: Elevation, + shape: ButtonShape, + onClick: () -> Unit, + content: @Composable () -> Unit +) { + val buttonShape = RoundedCornerShape(shape.cornerRadius) + val containerModifier = when (colors) { + is Colors.Solid -> modifier + .heightIn(min = shape.minHeight) + .elevation( + elevation = elevation, + backgroundColor = colors.background, + shape = buttonShape + ) + + is Colors.Gradient -> modifier + .heightIn(min = shape.minHeight) + .elevation( + elevation = elevation, + backgroundBrush = colors.background, + shape = buttonShape, + backgroundAlpha = colors.backgroundAlpha + ) + } + + Surface( + shape = buttonShape, + color = Color.Transparent, + contentColor = colors.content, + border = BorderStroke(shape.borderWidth, colors.border), + modifier = containerModifier.clickable( + interactionSource = interactionSource, + indication = null, + enabled = enabled, + role = Role.Button, + onClick = onClick + ) + ) { + CompositionLocalProvider( + LocalContentColor provides colors.content, + LocalContentAlpha provides 1f, + content = content + ) + } +} diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/button/Content.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/button/Content.kt new file mode 100644 index 000000000..60850656c --- /dev/null +++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/button/Content.kt @@ -0,0 +1,84 @@ +package com.atls.hyperion.ui.components.button + +import androidx.compose.foundation.layout.* +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.Dp +import com.atls.hyperion.ui.components.button.styles.shape.ButtonShape +import com.atls.hyperion.ui.shared.addon.Addon +import com.atls.hyperion.ui.shared.addon.AddonPosition +import com.atls.hyperion.ui.shared.addon.AddonSlotManager + +@Composable +internal fun ButtonContent( + addons: AddonSlotManager, + shape: ButtonShape, + content: @Composable () -> Unit +) { + val beforeAddons = addons.get(AddonPosition.Before) + val afterAddons = addons.get(AddonPosition.After) + val sideWidth = (shape.addonSize + shape.gap) * maxOf( + beforeAddons.size, + afterAddons.size + ).toFloat() + + Row( + modifier = Modifier.padding(shape.paddings), + verticalAlignment = Alignment.CenterVertically + ) { + Addons( + addons = beforeAddons, + width = sideWidth, + addonSize = shape.addonSize, + gap = shape.gap, + position = AddonPosition.Before + ) + Box(contentAlignment = Alignment.Center) { + content() + } + Addons( + addons = afterAddons, + width = sideWidth, + addonSize = shape.addonSize, + gap = shape.gap, + position = AddonPosition.After + ) + } +} + +@Composable +private fun Addons( + addons: List, + width: Dp, + addonSize: Dp, + gap: Dp, + position: AddonPosition +) { + Row( + modifier = Modifier.width(width), + horizontalArrangement = when (position) { + AddonPosition.Before -> Arrangement.End + AddonPosition.After -> Arrangement.Start + }, + verticalAlignment = Alignment.CenterVertically + ) { + if (addons.isNotEmpty() && position == AddonPosition.After) { + Spacer(modifier = Modifier.width(gap)) + } + addons.forEachIndexed { index, addon -> + if (index > 0) { + Spacer(modifier = Modifier.width(gap)) + } + Box( + modifier = Modifier.size(addonSize), + contentAlignment = Alignment.Center + ) { + addon.Content() + } + } + if (addons.isNotEmpty() && position == AddonPosition.Before) { + Spacer(modifier = Modifier.width(gap)) + } + } +} diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/button/Layout.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/button/Layout.kt index ee234ee13..785910036 100644 --- a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/button/Layout.kt +++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/button/Layout.kt @@ -1,32 +1,18 @@ package com.atls.hyperion.ui.components.button -import androidx.compose.foundation.BorderStroke -import androidx.compose.foundation.background import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.interaction.collectIsFocusedAsState import androidx.compose.foundation.interaction.collectIsPressedAsState -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.Button -import androidx.compose.material.ButtonDefaults -import androidx.compose.material.ExperimentalMaterialApi -import androidx.compose.material.LocalMinimumInteractiveComponentEnforcement import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.remember -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.shadow import com.atls.hyperion.ui.components.button.locals.LocalState import com.atls.hyperion.ui.components.button.state.ButtonState import com.atls.hyperion.ui.components.button.styles.appearance.ButtonAppearance -import com.atls.hyperion.ui.components.button.styles.appearance.Colors import com.atls.hyperion.ui.components.button.styles.shape.ButtonShape -import com.atls.hyperion.ui.shared.addon.AddonPosition import com.atls.hyperion.ui.shared.addon.AddonSlotManager -import com.atls.hyperion.ui.theme.tokens.layout.Elevation -import com.atls.hyperion.ui.theme.tokens.colors.LegacyColors as ThemeColors -@OptIn(ExperimentalMaterialApi::class) @Composable fun ButtonLayout( modifier: Modifier = Modifier, @@ -39,70 +25,36 @@ fun ButtonLayout( ) { val interactionSource = remember { MutableInteractionSource() } val isPressed = interactionSource.collectIsPressedAsState().value + val isFocused = interactionSource.collectIsFocusedAsState().value val state = when { !enabled -> ButtonState.Disabled isPressed -> ButtonState.Pressed + isFocused -> ButtonState.Focused else -> ButtonState.Default } - val colors = appearance.fromState(state) + val elevation = when (state) { + ButtonState.Default -> shape.elevations.default + ButtonState.Pressed -> shape.elevations.pressed + ButtonState.Disabled -> shape.elevations.disabled + ButtonState.Focused -> shape.elevations.disabled + } - CompositionLocalProvider( - LocalState provides state, - LocalMinimumInteractiveComponentEnforcement provides false - ) { - Button( - onClick = onClick, - shape = RoundedCornerShape(shape.cornerRadius), - colors = ButtonDefaults.buttonColors( - backgroundColor = when (colors) { - is Colors.Solid -> colors.backgroundColor - is Colors.Gradient -> ThemeColors.Palette.transparent - }, - contentColor = colors.textColor, - disabledBackgroundColor = when (colors) { - is Colors.Solid -> colors.backgroundColor - is Colors.Gradient -> ThemeColors.Palette.transparent - }, - disabledContentColor = colors.textColor - ), - border = BorderStroke(shape.borderStroke, colors.borderColor), - contentPadding = shape.paddings, + CompositionLocalProvider(LocalState provides state) { + ButtonContainer( + modifier = modifier, enabled = enabled, - elevation = ButtonDefaults.elevation( - defaultElevation = Elevation.zero, - pressedElevation = Elevation.zero, - disabledElevation = Elevation.zero - ), interactionSource = interactionSource, - modifier = modifier - .shadow( - elevation = appearance.shadowElevation, - shape = RoundedCornerShape(shape.cornerRadius), - clip = false - ) - .then( - if (colors is Colors.Gradient) { - Modifier.background( - colors.backgroundBrush, - RoundedCornerShape(shape.cornerRadius) - ) - } else Modifier - ) + colors = appearance.fromState(state), + elevation = elevation, + shape = shape, + onClick = onClick ) { - Row(verticalAlignment = Alignment.CenterVertically) { - addons.get(AddonPosition.Before).forEach { - it.Content() - it.Spacer() - } - - content() - - addons.get(AddonPosition.After).forEach { - it.Spacer() - it.Content() - } - } + ButtonContent( + addons = addons, + shape = shape, + content = content + ) } } } From f4042aa15527807b1535181f477aa6bf2d777b06 Mon Sep 17 00:00:00 2001 From: Arina Gazhina Date: Tue, 25 Aug 2026 01:22:16 +0300 Subject: [PATCH 06/14] feat(button): update stories --- .../ui/components/button/Component.kt | 2 - .../ui/components/button/stories/Component.kt | 196 +++++++++++++----- 2 files changed, 143 insertions(+), 55 deletions(-) diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/button/Component.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/button/Component.kt index 748afb949..ceb1f3f2e 100644 --- a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/button/Component.kt +++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/button/Component.kt @@ -4,7 +4,6 @@ import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.text.style.TextAlign -import com.atls.hyperion.ui.components.button.locals.LocalState import com.atls.hyperion.ui.components.button.styles.appearance.ButtonAppearance import com.atls.hyperion.ui.components.button.styles.shape.ButtonShape import com.atls.hyperion.ui.shared.addon.AddonSlotManager @@ -30,7 +29,6 @@ fun Button( Text( text = text, textAlign = TextAlign.Center, - color = appearance.fromState(LocalState.current).textColor, style = shape.typography ) } diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/button/stories/Component.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/button/stories/Component.kt index e3a0d064f..268eccaec 100644 --- a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/button/stories/Component.kt +++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/button/stories/Component.kt @@ -1,79 +1,169 @@ package com.atls.hyperion.ui.components.button.stories -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.LocalContentColor import androidx.compose.material.Switch import androidx.compose.material.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.runtime.* +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import com.atls.hyperion.storybook.shared.model.ComponentExample import com.atls.hyperion.storybook.shared.ui.ComponentVariants import com.atls.hyperion.ui.components.button.Button import com.atls.hyperion.ui.components.button.styles.appearance.ButtonAppearance -import com.atls.hyperion.ui.components.button.styles.appearance.blue -import com.atls.hyperion.ui.components.button.styles.appearance.lightBlue -import com.atls.hyperion.ui.components.button.styles.shape.ButtonShape -import com.atls.hyperion.ui.components.button.styles.shape.huge -import com.atls.hyperion.ui.components.button.styles.shape.large -import com.atls.hyperion.ui.components.button.styles.shape.medium -import com.atls.hyperion.ui.components.button.styles.shape.normal -import com.atls.hyperion.ui.components.button.styles.shape.semiMedium -import com.atls.hyperion.ui.components.button.styles.shape.small -import com.atls.hyperion.ui.components.button.styles.shape.smallSizeMediumRadii +import com.atls.hyperion.ui.components.button.styles.appearance.ghost +import com.atls.hyperion.ui.components.button.styles.appearance.primary +import com.atls.hyperion.ui.components.button.styles.appearance.secondary +import com.atls.hyperion.ui.components.button.styles.shape.* +import com.atls.hyperion.ui.generated.resources.Res +import com.atls.hyperion.ui.generated.resources.chevron_right import com.atls.hyperion.ui.primitives.HorizontalSpacer import com.atls.hyperion.ui.primitives.VerticalSpacer +import com.atls.hyperion.ui.primitives.icon.Icon +import com.atls.hyperion.ui.shared.addon.Addon +import com.atls.hyperion.ui.shared.addon.AddonPosition +import com.atls.hyperion.ui.shared.addon.AddonSlotManager +import com.atls.hyperion.ui.theme.LocalHyperionColors +import com.atls.hyperion.ui.theme.Theme +import com.atls.hyperion.ui.theme.tokens.elevation.PressableElevationStates import com.atls.hyperion.ui.theme.tokens.layout.Space +import com.atls.hyperion.ui.theme.tokens.layout.Spacing import com.atls.hyperion.ui.theme.tokens.layout.Weight +import org.jetbrains.compose.resources.painterResource class ButtonStory : ComponentExample { override val name: String = "Button" @Composable override fun Content() { + var darkTheme by remember { mutableStateOf(false) } + var focused by remember { mutableStateOf(false) } + var showIcons by remember { mutableStateOf(false) } var enabled by remember { mutableStateOf(true) } - Column { - Row( - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = Space.sm) - ) { - Text(modifier = Modifier.weight(Weight.full), text = "Enabled") - HorizontalSpacer(Space.sm) - Switch(checked = enabled, onCheckedChange = { enabled = it }) - } - VerticalSpacer(Space.sm) + Theme(darkTheme = darkTheme) { + val colors = LocalHyperionColors.current + + CompositionLocalProvider(LocalContentColor provides colors.text.primary) { + Column( + modifier = Modifier + .fillMaxSize() + .background(colors.surface.subtle) + .verticalScroll(rememberScrollState()) + ) { + Toggle( + label = "Dark theme", + checked = darkTheme, + onCheckedChange = { darkTheme = it } + ) + Toggle( + label = "Focused", + checked = focused, + onCheckedChange = { focused = it } + ) + Toggle( + label = "Icons", + checked = showIcons, + onCheckedChange = { showIcons = it } + ) + Toggle( + label = "Enabled", + checked = enabled, + onCheckedChange = { enabled = it } + ) + VerticalSpacer(Space.sm) + + ComponentVariants( + name = "Button", + appearances = listOf( + "Primary" to { ButtonAppearance.primary() }, + "Secondary" to { ButtonAppearance.secondary() }, + "Ghost" to { ButtonAppearance.ghost() } + ), + shapes = listOf( + "Xs" to { ButtonShape.xs() }, + "Sm" to { ButtonShape.sm() }, + "Md" to { ButtonShape.md() }, + "Lg" to { ButtonShape.lg() } + ) + ) { appearance: ButtonAppearance, shape: ButtonShape -> + val icon = object : Addon { + @Composable + override fun Content() { + Icon( + icon = painterResource(Res.drawable.chevron_right), + color = LocalContentColor.current, + size = shape.addonSize + ) + } + + @Composable + override fun Spacer() = Unit + } + val previewAppearance = if (focused) { + appearance.copy(default = appearance.focused) + } else { + appearance + } + val previewShape = if (focused) { + shape.copy( + elevations = object : PressableElevationStates by shape.elevations { + override val default = shape.elevations.disabled + } + ) + } else { + shape + } + val previewAddons = if (showIcons) { + AddonSlotManager( + mapOf( + AddonPosition.Before to listOf(icon), + AddonPosition.After to listOf(icon) + ) + ) + } else { + AddonSlotManager() + } - ComponentVariants( - name = "Button", - appearances = listOf( - "Blue" to { ButtonAppearance.blue() }, - "Light Blue" to { ButtonAppearance.lightBlue() } - ), - shapes = listOf( - "Huge" to { ButtonShape.huge() }, - "Large" to { ButtonShape.large() }, - "Semi Medium" to { ButtonShape.semiMedium() }, - "Medium" to { ButtonShape.medium() }, - "Normal" to { ButtonShape.normal() }, - "Small" to { ButtonShape.small() }, - "Small Medium Radii" to { ButtonShape.smallSizeMediumRadii() } - ) - ) { appearance: ButtonAppearance, shape: ButtonShape -> - Button( - text = "Button", - appearance = appearance, - shape = shape, - enabled = enabled, - onClick = {} - ) + Button( + text = "Button", + appearance = previewAppearance, + shape = previewShape, + enabled = enabled, + addons = previewAddons, + onClick = {} + ) + } + } } } } } + +@Composable +private fun Toggle( + label: String, + checked: Boolean, + onCheckedChange: (Boolean) -> Unit +) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = Spacing.component.md), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Text( + modifier = Modifier.weight(Weight.full), + text = label + ) + HorizontalSpacer(Space.sm) + Switch( + checked = checked, + onCheckedChange = onCheckedChange + ) + } +} From c6677f5ccd063ba62d3ab25ad5be9bb74ed8f378 Mon Sep 17 00:00:00 2001 From: Arina Gazhina Date: Tue, 25 Aug 2026 01:24:26 +0300 Subject: [PATCH 07/14] fix(theme): let gradient background passing in elevation --- .../atls/hyperion/ui/primitives/Elevation.kt | 29 +++++++++++++++---- 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/primitives/Elevation.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/primitives/Elevation.kt index 41491f6b4..514f6a268 100644 --- a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/primitives/Elevation.kt +++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/primitives/Elevation.kt @@ -4,9 +4,7 @@ import androidx.compose.foundation.background import androidx.compose.ui.Modifier import androidx.compose.ui.draw.dropShadow import androidx.compose.ui.draw.innerShadow -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.RectangleShape -import androidx.compose.ui.graphics.Shape +import androidx.compose.ui.graphics.* import com.atls.hyperion.ui.primitives.utils.clearDropShadowInside import com.atls.hyperion.ui.primitives.utils.isolateDropShadows import com.atls.hyperion.ui.primitives.utils.toComposeShadow @@ -17,12 +15,27 @@ fun Modifier.elevation( elevation: Elevation, backgroundColor: Color, shape: Shape = RectangleShape +): Modifier = elevation( + elevation = elevation, + backgroundBrush = SolidColor(backgroundColor), + shape = shape +) + +fun Modifier.elevation( + elevation: Elevation, + backgroundBrush: Brush, + shape: Shape = RectangleShape, + backgroundAlpha: Float = 1f ): Modifier { + val fillAlpha = backgroundAlpha * when (backgroundBrush) { + is SolidColor -> backgroundBrush.value.alpha + else -> 1f + } val dropShadows = elevation.shadows.filter { it.type == ShadowType.Drop } val withDropShadows = dropShadows.fold(isolateDropShadows(dropShadows)) { modifier, shadow -> modifier.dropShadow( shape = shape, - shadow = shadow.toComposeShadow(backgroundColor.alpha) + shadow = shadow.toComposeShadow(fillAlpha) ) } val withoutDropShadowInside = if (dropShadows.isEmpty()) { @@ -31,13 +44,17 @@ fun Modifier.elevation( withDropShadows.clearDropShadowInside(shape) } - val withBackground = withoutDropShadowInside.background(backgroundColor, shape) + val withBackground = withoutDropShadowInside.background( + brush = backgroundBrush, + shape = shape, + alpha = backgroundAlpha + ) return elevation.shadows.fold(withBackground) { modifier, shadow -> if (shadow.type == ShadowType.Inner) { modifier.innerShadow( shape = shape, - shadow = shadow.toComposeShadow(backgroundColor.alpha) + shadow = shadow.toComposeShadow(fillAlpha) ) } else { modifier From cf513423263a797c554b4833c65d449b015b85db Mon Sep 17 00:00:00 2001 From: Arina Gazhina Date: Tue, 25 Aug 2026 01:25:06 +0300 Subject: [PATCH 08/14] refactor(theme): remove legacy button colors --- .../ui/theme/tokens/colors/LegacyColors.kt | 38 ------------------- .../hyperion/ui/theme/tokens/colors/Light.kt | 2 +- 2 files changed, 1 insertion(+), 39 deletions(-) diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/theme/tokens/colors/LegacyColors.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/theme/tokens/colors/LegacyColors.kt index ddc14b583..8b78359ee 100644 --- a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/theme/tokens/colors/LegacyColors.kt +++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/theme/tokens/colors/LegacyColors.kt @@ -36,44 +36,6 @@ object LegacyColors { //TODO remove after components refactoring val green = Color(0xFF34C51D) } - object Button { - object Blue { - val Default = ColorSet( - font = Color(0xFFFFFFFF), - background = Color(0xFF416DDF), - border = Color(0x00000000) - ) - val Pressed = ColorSet( - font = Color(0xFFFFFFFF), - background = Color(0xFF0042EC), - border = Color(0x00000000) - ) - val Disabled = ColorSet( - font = Color(0xFF8A8A8A), - background = Color(0xFF2D2D2D), - border = Color(0x00000000) - ) - } - - object LightBlue { - val Default = ColorSet( - font = Color(0xFFFFFFFF), - background = Color(0xFF54A9EB), - border = Color(0x00000000) - ) - val Pressed = ColorSet( - font = Color(0xFFFFFFFF), - background = Color(0xFF54A9EB), - border = Color(0x00000000) - ) - val Disabled = ColorSet( - font = Color(0xFFFFFFFF), - background = Color(0xFFDDDDDD), - border = Color(0x00000000) - ) - } - } - object Input { object Blue { val Default = ColorSet( diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/theme/tokens/colors/Light.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/theme/tokens/colors/Light.kt index f9762fa6f..794d9c1d2 100644 --- a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/theme/tokens/colors/Light.kt +++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/theme/tokens/colors/Light.kt @@ -7,7 +7,7 @@ val lightColors = Colors( base = Color(0xFF1E56C7), hover = Color(0xFF1643A3), pressed = Color(0xFF12357F), - disabled = Color(0x401E56C7), + disabled = Color(0xFF6B86C8), subtle = Color(0x401E56C7) ), surface = SurfaceColors( From d6a7bcdcca0311ce33f8c9ff8d4ec4f3d0b78ee8 Mon Sep 17 00:00:00 2001 From: Arina Gazhina Date: Tue, 25 Aug 2026 01:27:12 +0300 Subject: [PATCH 09/14] chore(common): update stories using button --- .../modal/bottom/stories/Component.kt | 19 +++++++------------ .../modal/popup/stories/Component.kt | 18 +++++++----------- 2 files changed, 14 insertions(+), 23 deletions(-) diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/modal/bottom/stories/Component.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/modal/bottom/stories/Component.kt index 8bb61c140..83561cfa9 100644 --- a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/modal/bottom/stories/Component.kt +++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/modal/bottom/stories/Component.kt @@ -6,19 +6,14 @@ import androidx.compose.foundation.layout.padding import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Text import androidx.compose.material3.rememberModalBottomSheetState -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberCoroutineScope -import androidx.compose.runtime.setValue +import androidx.compose.runtime.* import androidx.compose.ui.Modifier import com.atls.hyperion.storybook.shared.model.ComponentExample import com.atls.hyperion.ui.components.button.Button import com.atls.hyperion.ui.components.button.styles.appearance.ButtonAppearance -import com.atls.hyperion.ui.components.button.styles.appearance.blue +import com.atls.hyperion.ui.components.button.styles.appearance.primary import com.atls.hyperion.ui.components.button.styles.shape.ButtonShape -import com.atls.hyperion.ui.components.button.styles.shape.normal +import com.atls.hyperion.ui.components.button.styles.shape.md import com.atls.hyperion.ui.components.modal.bottom.BottomDialog import com.atls.hyperion.ui.theme.tokens.layout.Space import kotlinx.coroutines.launch @@ -40,8 +35,8 @@ class BottomDialogStory : ComponentExample { ) { Button( text = "Show Bottom Dialog", - appearance = ButtonAppearance.blue(), - shape = ButtonShape.normal(), + appearance = ButtonAppearance.primary(), + shape = ButtonShape.md(), onClick = { showDialog = true } ) @@ -59,8 +54,8 @@ class BottomDialogStory : ComponentExample { Button( modifier = Modifier.padding(top = Space.sm), text = "Close", - appearance = ButtonAppearance.blue(), - shape = ButtonShape.normal(), + appearance = ButtonAppearance.primary(), + shape = ButtonShape.md(), onClick = { scope.launch { sheetState.hide() }.invokeOnCompletion { if (!sheetState.isVisible) { diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/modal/popup/stories/Component.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/modal/popup/stories/Component.kt index 3d24bb26e..00a175076 100644 --- a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/modal/popup/stories/Component.kt +++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/modal/popup/stories/Component.kt @@ -4,18 +4,14 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding 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.runtime.* import androidx.compose.ui.Modifier import com.atls.hyperion.storybook.shared.model.ComponentExample import com.atls.hyperion.ui.components.button.Button import com.atls.hyperion.ui.components.button.styles.appearance.ButtonAppearance -import com.atls.hyperion.ui.components.button.styles.appearance.blue +import com.atls.hyperion.ui.components.button.styles.appearance.primary import com.atls.hyperion.ui.components.button.styles.shape.ButtonShape -import com.atls.hyperion.ui.components.button.styles.shape.normal +import com.atls.hyperion.ui.components.button.styles.shape.md import com.atls.hyperion.ui.components.modal.popup.Popup import com.atls.hyperion.ui.theme.tokens.layout.Space @@ -33,8 +29,8 @@ class PopupStory : ComponentExample { ) { Button( text = "Show Popup", - appearance = ButtonAppearance.blue(), - shape = ButtonShape.normal(), + appearance = ButtonAppearance.primary(), + shape = ButtonShape.md(), onClick = { showDialog = true } ) @@ -51,8 +47,8 @@ class PopupStory : ComponentExample { Button( modifier = Modifier.padding(top = Space.sm), text = "Close", - appearance = ButtonAppearance.blue(), - shape = ButtonShape.normal(), + appearance = ButtonAppearance.primary(), + shape = ButtonShape.md(), onClick = { showDialog = false } ) } From 74a48be7473174d95916feaf51e27896b8ec4700 Mon Sep 17 00:00:00 2001 From: Arina Gazhina Date: Tue, 25 Aug 2026 01:27:51 +0300 Subject: [PATCH 10/14] chore(common): remove unused spacing --- .../kotlin/com/atls/hyperion/ui/theme/tokens/layout/Spaces.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/theme/tokens/layout/Spaces.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/theme/tokens/layout/Spaces.kt index 2d82eb0ca..f5610c966 100644 --- a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/theme/tokens/layout/Spaces.kt +++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/theme/tokens/layout/Spaces.kt @@ -6,7 +6,6 @@ object Space { //TODO remove after components refactoring val zero = 0.dp val xs5 = 2.dp val xs4 = 4.dp - val xs3 = 6.dp val xs2 = 8.dp val xs = 10.dp val sm = 12.dp From 5e9fb730f23a486ae4034c810cb35607d3f107dc Mon Sep 17 00:00:00 2001 From: Arina Gazhina Date: Tue, 25 Aug 2026 01:57:16 +0300 Subject: [PATCH 11/14] fix(button): align content by width --- .../hyperion/ui/components/button/Content.kt | 75 +++++++++++-------- 1 file changed, 44 insertions(+), 31 deletions(-) diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/button/Content.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/button/Content.kt index 60850656c..b6aee7372 100644 --- a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/button/Content.kt +++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/button/Content.kt @@ -18,49 +18,62 @@ internal fun ButtonContent( ) { val beforeAddons = addons.get(AddonPosition.Before) val afterAddons = addons.get(AddonPosition.After) - val sideWidth = (shape.addonSize + shape.gap) * maxOf( - beforeAddons.size, - afterAddons.size - ).toFloat() - Row( - modifier = Modifier.padding(shape.paddings), - verticalAlignment = Alignment.CenterVertically - ) { - Addons( - addons = beforeAddons, - width = sideWidth, - addonSize = shape.addonSize, - gap = shape.gap, - position = AddonPosition.Before - ) - Box(contentAlignment = Alignment.Center) { - content() + BoxWithConstraints(modifier = Modifier.padding(shape.paddings)) { + if (constraints.hasFixedWidth) { + Box(modifier = Modifier.fillMaxWidth()) { + Addons( + addons = beforeAddons, + addonSize = shape.addonSize, + gap = shape.gap, + position = AddonPosition.Before, + modifier = Modifier.align(Alignment.CenterStart) + ) + Box( + modifier = Modifier.align(Alignment.Center), + contentAlignment = Alignment.Center, + content = { content() } + ) + Addons( + addons = afterAddons, + addonSize = shape.addonSize, + gap = shape.gap, + position = AddonPosition.After, + modifier = Modifier.align(Alignment.CenterEnd) + ) + } + } else { + Row(verticalAlignment = Alignment.CenterVertically) { + Addons( + addons = beforeAddons, + addonSize = shape.addonSize, + gap = shape.gap, + position = AddonPosition.Before + ) + Box(contentAlignment = Alignment.Center) { + content() + } + Addons( + addons = afterAddons, + addonSize = shape.addonSize, + gap = shape.gap, + position = AddonPosition.After + ) + } } - Addons( - addons = afterAddons, - width = sideWidth, - addonSize = shape.addonSize, - gap = shape.gap, - position = AddonPosition.After - ) } } @Composable private fun Addons( addons: List, - width: Dp, addonSize: Dp, gap: Dp, - position: AddonPosition + position: AddonPosition, + modifier: Modifier = Modifier ) { Row( - modifier = Modifier.width(width), - horizontalArrangement = when (position) { - AddonPosition.Before -> Arrangement.End - AddonPosition.After -> Arrangement.Start - }, + modifier = modifier, verticalAlignment = Alignment.CenterVertically ) { if (addons.isNotEmpty() && position == AddonPosition.After) { From dddff7645081b1c6a6dccf1fe09864d5a08968fd Mon Sep 17 00:00:00 2001 From: Arina Gazhina Date: Tue, 25 Aug 2026 01:58:55 +0300 Subject: [PATCH 12/14] test(button): add layout controls to story --- .../ui/components/button/stories/Component.kt | 47 ++++++++++++++----- 1 file changed, 34 insertions(+), 13 deletions(-) diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/button/stories/Component.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/button/stories/Component.kt index 268eccaec..30adb7dd2 100644 --- a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/button/stories/Component.kt +++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/button/stories/Component.kt @@ -41,7 +41,9 @@ class ButtonStory : ComponentExample { override fun Content() { var darkTheme by remember { mutableStateOf(false) } var focused by remember { mutableStateOf(false) } - var showIcons by remember { mutableStateOf(false) } + var showLeadingIcon by remember { mutableStateOf(false) } + var showTrailingIcon by remember { mutableStateOf(false) } + var fullWidth by remember { mutableStateOf(false) } var enabled by remember { mutableStateOf(true) } Theme(darkTheme = darkTheme) { @@ -65,9 +67,19 @@ class ButtonStory : ComponentExample { onCheckedChange = { focused = it } ) Toggle( - label = "Icons", - checked = showIcons, - onCheckedChange = { showIcons = it } + label = "Leading icon", + checked = showLeadingIcon, + onCheckedChange = { showLeadingIcon = it } + ) + Toggle( + label = "Trailing icon", + checked = showTrailingIcon, + onCheckedChange = { showTrailingIcon = it } + ) + Toggle( + label = "Full width", + checked = fullWidth, + onCheckedChange = { fullWidth = it } ) Toggle( label = "Enabled", @@ -117,18 +129,27 @@ class ButtonStory : ComponentExample { } else { shape } - val previewAddons = if (showIcons) { - AddonSlotManager( - mapOf( - AddonPosition.Before to listOf(icon), - AddonPosition.After to listOf(icon) - ) + val previewAddons = AddonSlotManager( + mapOf( + AddonPosition.Before to if (showLeadingIcon) { + listOf(icon) + } else { + emptyList() + }, + AddonPosition.After to if (showTrailingIcon) { + listOf(icon) + } else { + emptyList() + } ) - } else { - AddonSlotManager() - } + ) Button( + modifier = if (fullWidth) { + Modifier.fillMaxWidth() + } else { + Modifier + }, text = "Button", appearance = previewAppearance, shape = previewShape, From 756c6ba22ea6fff7f9988c2ebac3801445afc0b5 Mon Sep 17 00:00:00 2001 From: Arina Gazhina Date: Tue, 25 Aug 2026 05:11:17 +0300 Subject: [PATCH 13/14] fix(button): reserve space around centered content --- .../com/atls/hyperion/ui/components/button/Content.kt | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/button/Content.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/button/Content.kt index b6aee7372..40d97193f 100644 --- a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/button/Content.kt +++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/button/Content.kt @@ -21,6 +21,11 @@ internal fun ButtonContent( BoxWithConstraints(modifier = Modifier.padding(shape.paddings)) { if (constraints.hasFixedWidth) { + val sideWidth = (shape.addonSize + shape.gap) * maxOf( + beforeAddons.size, + afterAddons.size + ).toFloat() + Box(modifier = Modifier.fillMaxWidth()) { Addons( addons = beforeAddons, @@ -30,7 +35,10 @@ internal fun ButtonContent( modifier = Modifier.align(Alignment.CenterStart) ) Box( - modifier = Modifier.align(Alignment.Center), + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = sideWidth) + .align(Alignment.Center), contentAlignment = Alignment.Center, content = { content() } ) From cb68e740bcc7ef8afdbafdb59aeabe80cda850fb Mon Sep 17 00:00:00 2001 From: Arina Gazhina Date: Wed, 26 Aug 2026 18:31:14 +0300 Subject: [PATCH 14/14] fix(button): center content vertically --- .../kotlin/com/atls/hyperion/ui/components/button/Content.kt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/button/Content.kt b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/button/Content.kt index 40d97193f..c9b8f6c21 100644 --- a/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/button/Content.kt +++ b/mobile/kmp/ui/src/commonMain/kotlin/com/atls/hyperion/ui/components/button/Content.kt @@ -19,7 +19,10 @@ internal fun ButtonContent( val beforeAddons = addons.get(AddonPosition.Before) val afterAddons = addons.get(AddonPosition.After) - BoxWithConstraints(modifier = Modifier.padding(shape.paddings)) { + BoxWithConstraints( + modifier = Modifier.padding(shape.paddings), + contentAlignment = Alignment.Center + ) { if (constraints.hasFixedWidth) { val sideWidth = (shape.addonSize + shape.gap) * maxOf( beforeAddons.size,