From 56a2eda3e154aa8552eb6a4b7122bd13c63173f2 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 30 Apr 2026 13:16:11 +0300 Subject: [PATCH] Updated on 2026-08-14 --- core/ui/ds-tokens | 2 +- .../com/tangem/core/ui/res/TangemTheme.kt | 32 + .../tangem/core/ui/res/TangemThemeRedesign.kt | 25 +- .../tangem/core/ui/res/generated/.tokens-hash | 2 +- .../ui/res/generated/TangemColorPalette.kt | 149 ++++ .../core/ui/res/generated/TangemColors3.kt | 714 ++++++++++++++++ .../ui/res/generated/TangemColors3Dark.kt | 173 ++++ .../ui/res/generated/TangemColors3Light.kt | 173 ++++ .../ui/res/generated/TangemDarkColorTokens.kt | 118 --- .../core/ui/res/generated/TangemDimens3.kt | 108 +++ .../ui/res/generated/TangemDimensionTokens.kt | 72 -- .../res/generated/TangemLightColorTokens.kt | 118 --- .../ui/res/generated/TangemOpacityTokens.kt | 22 - .../ui/res/generated/TangemShadowTokens.kt | 15 - .../ui/res/generated/TangemTypography3.kt | 112 +++ .../res/generated/TangemTypographyTokens.kt | 88 -- core/ui/token-gen/README.md | 21 + core/ui/token-gen/build-tokens.mjs | 767 +++++++++++++----- 18 files changed, 2080 insertions(+), 631 deletions(-) create mode 100644 core/ui/src/main/java/com/tangem/core/ui/res/generated/TangemColorPalette.kt create mode 100644 core/ui/src/main/java/com/tangem/core/ui/res/generated/TangemColors3.kt create mode 100644 core/ui/src/main/java/com/tangem/core/ui/res/generated/TangemColors3Dark.kt create mode 100644 core/ui/src/main/java/com/tangem/core/ui/res/generated/TangemColors3Light.kt delete mode 100644 core/ui/src/main/java/com/tangem/core/ui/res/generated/TangemDarkColorTokens.kt create mode 100644 core/ui/src/main/java/com/tangem/core/ui/res/generated/TangemDimens3.kt delete mode 100644 core/ui/src/main/java/com/tangem/core/ui/res/generated/TangemDimensionTokens.kt delete mode 100644 core/ui/src/main/java/com/tangem/core/ui/res/generated/TangemLightColorTokens.kt delete mode 100644 core/ui/src/main/java/com/tangem/core/ui/res/generated/TangemOpacityTokens.kt delete mode 100644 core/ui/src/main/java/com/tangem/core/ui/res/generated/TangemShadowTokens.kt create mode 100644 core/ui/src/main/java/com/tangem/core/ui/res/generated/TangemTypography3.kt delete mode 100644 core/ui/src/main/java/com/tangem/core/ui/res/generated/TangemTypographyTokens.kt create mode 100644 core/ui/token-gen/README.md diff --git a/core/ui/ds-tokens b/core/ui/ds-tokens index f4d2156236..27202508b6 160000 --- a/core/ui/ds-tokens +++ b/core/ui/ds-tokens @@ -1 +1 @@ -Subproject commit f4d2156236d7f77b61f26250351f6bfa72262a69 +Subproject commit 27202508b606f54c276afa577a2f3e7a3da27e8b diff --git a/core/ui/src/main/java/com/tangem/core/ui/res/TangemTheme.kt b/core/ui/src/main/java/com/tangem/core/ui/res/TangemTheme.kt index cb9dc458ca..f61cdd1722 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/res/TangemTheme.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/res/TangemTheme.kt @@ -29,6 +29,10 @@ import com.tangem.core.ui.haptic.DefaultHapticManager import com.tangem.core.ui.haptic.HapticManager import com.tangem.core.ui.haptic.VibratorHapticManager import com.tangem.core.ui.message.EventMessageHandler +import com.tangem.core.ui.res.generated.TangemColors3 +import com.tangem.core.ui.res.generated.TangemDimens3 +import com.tangem.core.ui.res.generated.TangemTypography3 +import com.tangem.core.ui.res.generated.lightColors3 import com.tangem.core.ui.windowsize.WindowSize import com.tangem.core.ui.windowsize.rememberWindowSize import com.tangem.domain.apptheme.model.AppThemeMode @@ -161,11 +165,17 @@ object TangemTheme { @ReadOnlyComposable get() = LocalTangemColors.current + @Deprecated("Use colors3 instead", ReplaceWith("TangemTheme.colors3")) val colors2: TangemColors2 @Composable @ReadOnlyComposable get() = LocalTangemColors2.current + val colors3: TangemColors3 + @Composable + @ReadOnlyComposable + get() = LocalTangemColors3.current + val typography: TangemTypography @Composable @ReadOnlyComposable @@ -176,6 +186,11 @@ object TangemTheme { @ReadOnlyComposable get() = TangemTypography2(InterFamily) + val typography3: TangemTypography3 + @Composable + @ReadOnlyComposable + get() = LocalTangemTypography3.current + val dimens: TangemDimens @Composable @ReadOnlyComposable @@ -186,6 +201,11 @@ object TangemTheme { @ReadOnlyComposable get() = LocalTangemDimens2.current + val dimens3: TangemDimens3 + @Composable + @ReadOnlyComposable + get() = LocalTangemDimens3.current + val shapes: TangemShapes @Composable @ReadOnlyComposable @@ -366,6 +386,10 @@ internal val LocalTangemColors2 = staticCompositionLocalOf { error("No TangemColors2 provided") } +internal val LocalTangemColors3 = staticCompositionLocalOf { + lightColors3() +} + internal val LocalTangemTypography = staticCompositionLocalOf { TangemTypography(RobotoFamily) } @@ -382,6 +406,14 @@ private val LocalTangemDimens2 = staticCompositionLocalOf { TangemDimens2() } +internal val LocalTangemDimens3 = staticCompositionLocalOf { + TangemDimens3() +} + +internal val LocalTangemTypography3 = staticCompositionLocalOf { + TangemTypography3(InterFamily) +} + private val LocalTangemShapes = staticCompositionLocalOf { error("No TangemShapes provided") } diff --git a/core/ui/src/main/java/com/tangem/core/ui/res/TangemThemeRedesign.kt b/core/ui/src/main/java/com/tangem/core/ui/res/TangemThemeRedesign.kt index 8428d0f240..9fcfadf1f8 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/res/TangemThemeRedesign.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/res/TangemThemeRedesign.kt @@ -10,6 +10,10 @@ import androidx.compose.runtime.* import androidx.compose.ui.Modifier import com.tangem.core.ui.components.haze.ProvideHaze import com.tangem.core.ui.components.haze.hazeSourceTangem +import com.tangem.core.ui.res.generated.TangemDimens3 +import com.tangem.core.ui.res.generated.TangemTypography3 +import com.tangem.core.ui.res.generated.darkColors3 +import com.tangem.core.ui.res.generated.lightColors3 /** * Provides additional theming for redesigned components. @@ -21,17 +25,30 @@ fun TangemThemeRedesign(content: @Composable () -> Unit) { val themeColors = if (LocalIsInDarkTheme.current) darkThemeColors() else lightThemeColors(redesign = true) val rememberedColors = remember { themeColors } .apply { update(themeColors) } + + val themeColors3 = if (LocalIsInDarkTheme.current) darkColors3() else lightColors3() + val rememberedColors3 = remember { themeColors3 } + .apply { update(themeColors3) } + val rootBackgroundColor = rememberedColors.background.secondary + val tangemDimens3 = remember { TangemDimens3() } + val tangemTypography3 = remember { TangemTypography3(InterFamily) } + val tangemTypography2 = remember { TangemTypography2(InterFamily) } + val tangemTypography = remember { TangemTypography(InterFamily) } + MaterialTheme( - colorScheme = tangemColorScheme(colors = themeColors), + colorScheme = tangemColorScheme(colors = rememberedColors), ) { CompositionLocalProvider( LocalRedesignEnabled provides true, - LocalTangemColors provides themeColors, + LocalTangemColors provides rememberedColors, LocalTangemColors2 provides if (LocalIsInDarkTheme.current) darkThemeColors2() else lightThemeColors2(), - LocalTangemTypography2 provides TangemTypography2(InterFamily), - LocalTangemTypography provides TangemTypography(InterFamily), + LocalTangemColors3 provides rememberedColors3, + LocalTangemDimens3 provides tangemDimens3, + LocalTangemTypography3 provides tangemTypography3, + LocalTangemTypography2 provides tangemTypography2, + LocalTangemTypography provides tangemTypography, LocalRootBackgroundColor provides remember(rootBackgroundColor) { mutableStateOf(rootBackgroundColor) }, ) { CompositionLocalProvider( diff --git a/core/ui/src/main/java/com/tangem/core/ui/res/generated/.tokens-hash b/core/ui/src/main/java/com/tangem/core/ui/res/generated/.tokens-hash index 14287aaf78..ff5efb6203 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/res/generated/.tokens-hash +++ b/core/ui/src/main/java/com/tangem/core/ui/res/generated/.tokens-hash @@ -1 +1 @@ -ddef7c44794c12a02c136fcc17f01fe51b6372d33a6a7eda4913d2d1b1f86203 +84387e888f54e5056380c38e077962bdfa4a32cfca194d822c13aa7e35661968 diff --git a/core/ui/src/main/java/com/tangem/core/ui/res/generated/TangemColorPalette.kt b/core/ui/src/main/java/com/tangem/core/ui/res/generated/TangemColorPalette.kt new file mode 100644 index 0000000000..2a72bad4db --- /dev/null +++ b/core/ui/src/main/java/com/tangem/core/ui/res/generated/TangemColorPalette.kt @@ -0,0 +1,149 @@ +@file:Suppress("all") + +package com.tangem.core.ui.res.generated + +import androidx.compose.ui.graphics.Color + +/** + * Auto-generated from design tokens. Do not edit manually. + */ +internal object TangemColorPalette { + object Base { + val black = Color(0xFF000000) + val white = Color(0xFFFFFFFF) + } + + object Neutral { + val `5` = Color(0xFFF4F4F4) + val `10` = Color(0xFFEAEAEA) + val `20` = Color(0xFFD0D0D0) + val `30` = Color(0xFFB5B5B5) + val `40` = Color(0xFF989898) + val `50` = Color(0xFF838383) + val `60` = Color(0xFF6F6F6F) + val `70` = Color(0xFF4A4A4A) + val `80` = Color(0xFF2C2C2C) + val `90` = Color(0xFF1B1B1B) + val `95` = Color(0xFF0F0F0F) + } + + object Blue { + val `5` = Color(0xFFE9F7FF) + val `10` = Color(0xFFDBF1FF) + val `20` = Color(0xFF98D7FF) + val `30` = Color(0xFF5EBDF9) + val `40` = Color(0xFF109FF0) + val `50` = Color(0xFF0090F9) + val `60` = Color(0xFF0077E1) + val `70` = Color(0xFF0C58AF) + val `80` = Color(0xFF143C70) + val `90` = Color(0xFF1B304A) + val `95` = Color(0xFF101C2C) + } + + object Violet { + val `5` = Color(0xFFF6F1FF) + val `10` = Color(0xFFEEE7FD) + val `20` = Color(0xFFDAC8FB) + val `30` = Color(0xFFC5A5FC) + val `40` = Color(0xFFB07BFD) + val `50` = Color(0xFFA967FD) + val `60` = Color(0xFF9258DC) + val `70` = Color(0xFF67419B) + val `80` = Color(0xFF473068) + val `90` = Color(0xFF332846) + val `95` = Color(0xFF201B2A) + } + + object Red { + val `5` = Color(0xFFFFF0F7) + val `10` = Color(0xFFFFE8EC) + val `20` = Color(0xFFFFC0C3) + val `30` = Color(0xFFFF979D) + val `40` = Color(0xFFFF5E66) + val `50` = Color(0xFFFE4142) + val `60` = Color(0xFFE12C2E) + val `70` = Color(0xFF9E2729) + val `80` = Color(0xFF6D2323) + val `90` = Color(0xFF4C2121) + val `95` = Color(0xFF2B1818) + } + + object Orange { + val `5` = Color(0xFFFFF0EA) + val `10` = Color(0xFFFFE5DB) + val `20` = Color(0xFFFFC3AD) + val `30` = Color(0xFFFF9976) + val `40` = Color(0xFFFA6931) + val `50` = Color(0xFFF25508) + val `60` = Color(0xFFD3480E) + val `70` = Color(0xFF953715) + val `80` = Color(0xFF652A18) + val `90` = Color(0xFF43251A) + val `95` = Color(0xFF2A1A13) + } + + object Green { + val `5` = Color(0xFFEBF6ED) + val `10` = Color(0xFFDCFBE3) + val `20` = Color(0xFF9EE1AB) + val `30` = Color(0xFF64C973) + val `40` = Color(0xFF2DAE3B) + val `50` = Color(0xFF2DA30D) + val `60` = Color(0xFF208900) + val `70` = Color(0xFF1E6110) + val `80` = Color(0xFF1C4415) + val `90` = Color(0xFF1D3319) + val `95` = Color(0xFF162114) + } + + object Yellow { + val `5` = Color(0xFFFAF3E5) + val `10` = Color(0xFFFEF5E5) + val `20` = Color(0xFFF7CA75) + val `30` = Color(0xFFF4B42F) + val `40` = Color(0xFFEFA210) + val `50` = Color(0xFFE68A03) + val `60` = Color(0xFFCD7A11) + val `70` = Color(0xFF965001) + val `80` = Color(0xFF573414) + val `90` = Color(0xFF3D2918) + val `95` = Color(0xFF241B13) + } + + object Opaque { + object BaseBlack { + val `5` = Color(0x0D000000) + val `10` = Color(0x1A000000) + val `15` = Color(0x26000000) + val `20` = Color(0x33000000) + val `25` = Color(0x40000000) + val `30` = Color(0x4D000000) + val `40` = Color(0x66000000) + val `50` = Color(0x80000000) + val `60` = Color(0x99000000) + val `80` = Color(0xCC000000) + } + + object BaseWhite { + val `5` = Color(0x0DFFFFFF) + val `10` = Color(0x1AFFFFFF) + val `15` = Color(0x26FFFFFF) + val `20` = Color(0x33FFFFFF) + val `25` = Color(0x40FFFFFF) + val `30` = Color(0x4DFFFFFF) + val `40` = Color(0x66FFFFFF) + val `50` = Color(0x80FFFFFF) + val `60` = Color(0x99FFFFFF) + val `80` = Color(0xCCFFFFFF) + } + + object Neutral95 { + val `5` = Color(0x0D0F0F0F) + val `10` = Color(0x1A0F0F0F) + val `15` = Color(0x260F0F0F) + val `40` = Color(0x660F0F0F) + val `60` = Color(0x990F0F0F) + } + } +} \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/res/generated/TangemColors3.kt b/core/ui/src/main/java/com/tangem/core/ui/res/generated/TangemColors3.kt new file mode 100644 index 0000000000..12e2412528 --- /dev/null +++ b/core/ui/src/main/java/com/tangem/core/ui/res/generated/TangemColors3.kt @@ -0,0 +1,714 @@ +@file:Suppress("all") + +package com.tangem.core.ui.res.generated + +import androidx.compose.runtime.Stable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue +import androidx.compose.ui.graphics.Color + +/** + * Auto-generated from design tokens. Do not edit manually. + */ +@Stable +class TangemColors3 internal constructor( + val text: Text, + val bg: Bg, + val icon: Icon, + val border: Border, + val overlay: Overlay, + val interaction: Interaction, + val material: Material, +) { + + @Stable + class Text internal constructor( + primary: Color, + secondary: Color, + tertiary: Color, + brand: Color, + val staticLight: StaticLight, + val staticDark: StaticDark, + val inverse: Inverse, + val status: Status, + val accent: Accent, + ) { + var primary by mutableStateOf(primary) + private set + var secondary by mutableStateOf(secondary) + private set + var tertiary by mutableStateOf(tertiary) + private set + var brand by mutableStateOf(brand) + private set + + @Stable + class StaticLight internal constructor( + primary: Color, + secondary: Color, + tertiary: Color, + ) { + var primary by mutableStateOf(primary) + private set + var secondary by mutableStateOf(secondary) + private set + var tertiary by mutableStateOf(tertiary) + private set + + fun update(other: StaticLight) { + primary = other.primary + secondary = other.secondary + tertiary = other.tertiary + } + } + + @Stable + class StaticDark internal constructor( + primary: Color, + secondary: Color, + tertiary: Color, + ) { + var primary by mutableStateOf(primary) + private set + var secondary by mutableStateOf(secondary) + private set + var tertiary by mutableStateOf(tertiary) + private set + + fun update(other: StaticDark) { + primary = other.primary + secondary = other.secondary + tertiary = other.tertiary + } + } + + @Stable + class Inverse internal constructor( + primary: Color, + secondary: Color, + tertiary: Color, + ) { + var primary by mutableStateOf(primary) + private set + var secondary by mutableStateOf(secondary) + private set + var tertiary by mutableStateOf(tertiary) + private set + + fun update(other: Inverse) { + primary = other.primary + secondary = other.secondary + tertiary = other.tertiary + } + } + + @Stable + class Status internal constructor( + success: Color, + error: Color, + warning: Color, + info: Color, + ) { + var success by mutableStateOf(success) + private set + var error by mutableStateOf(error) + private set + var warning by mutableStateOf(warning) + private set + var info by mutableStateOf(info) + private set + + fun update(other: Status) { + success = other.success + error = other.error + warning = other.warning + info = other.info + } + } + + @Stable + class Accent internal constructor( + blue: Color, + violet: Color, + red: Color, + orange: Color, + yellow: Color, + green: Color, + ) { + var blue by mutableStateOf(blue) + private set + var violet by mutableStateOf(violet) + private set + var red by mutableStateOf(red) + private set + var orange by mutableStateOf(orange) + private set + var yellow by mutableStateOf(yellow) + private set + var green by mutableStateOf(green) + private set + + fun update(other: Accent) { + blue = other.blue + violet = other.violet + red = other.red + orange = other.orange + yellow = other.yellow + green = other.green + } + } + + fun update(other: Text) { + primary = other.primary + secondary = other.secondary + tertiary = other.tertiary + brand = other.brand + staticLight.update(other.staticLight) + staticDark.update(other.staticDark) + inverse.update(other.inverse) + status.update(other.status) + accent.update(other.accent) + } + } + + @Stable + class Bg internal constructor( + primary: Color, + secondary: Color, + tertiary: Color, + brand: Color, + inverse: Color, + base: Color, + disabled: Color, + val opaque: Opaque, + val status: Status, + val accent: Accent, + ) { + var primary by mutableStateOf(primary) + private set + var secondary by mutableStateOf(secondary) + private set + var tertiary by mutableStateOf(tertiary) + private set + var brand by mutableStateOf(brand) + private set + var inverse by mutableStateOf(inverse) + private set + var base by mutableStateOf(base) + private set + var disabled by mutableStateOf(disabled) + private set + + @Stable + class Opaque internal constructor( + primary: Color, + secondary: Color, + ) { + var primary by mutableStateOf(primary) + private set + var secondary by mutableStateOf(secondary) + private set + + fun update(other: Opaque) { + primary = other.primary + secondary = other.secondary + } + } + + @Stable + class Status internal constructor( + success: Color, + successSubtle: Color, + error: Color, + errorSubtle: Color, + warning: Color, + warningSubtle: Color, + info: Color, + infoSubtle: Color, + ) { + var success by mutableStateOf(success) + private set + var successSubtle by mutableStateOf(successSubtle) + private set + var error by mutableStateOf(error) + private set + var errorSubtle by mutableStateOf(errorSubtle) + private set + var warning by mutableStateOf(warning) + private set + var warningSubtle by mutableStateOf(warningSubtle) + private set + var info by mutableStateOf(info) + private set + var infoSubtle by mutableStateOf(infoSubtle) + private set + + fun update(other: Status) { + success = other.success + successSubtle = other.successSubtle + error = other.error + errorSubtle = other.errorSubtle + warning = other.warning + warningSubtle = other.warningSubtle + info = other.info + infoSubtle = other.infoSubtle + } + } + + @Stable + class Accent internal constructor( + blue: Color, + violet: Color, + red: Color, + orange: Color, + yellow: Color, + green: Color, + ) { + var blue by mutableStateOf(blue) + private set + var violet by mutableStateOf(violet) + private set + var red by mutableStateOf(red) + private set + var orange by mutableStateOf(orange) + private set + var yellow by mutableStateOf(yellow) + private set + var green by mutableStateOf(green) + private set + + fun update(other: Accent) { + blue = other.blue + violet = other.violet + red = other.red + orange = other.orange + yellow = other.yellow + green = other.green + } + } + + fun update(other: Bg) { + primary = other.primary + secondary = other.secondary + tertiary = other.tertiary + brand = other.brand + inverse = other.inverse + base = other.base + disabled = other.disabled + opaque.update(other.opaque) + status.update(other.status) + accent.update(other.accent) + } + } + + @Stable + class Icon internal constructor( + primary: Color, + secondary: Color, + tertiary: Color, + brand: Color, + staticLight: Color, + staticDark: Color, + inverse: Color, + val status: Status, + val accent: Accent, + ) { + var primary by mutableStateOf(primary) + private set + var secondary by mutableStateOf(secondary) + private set + var tertiary by mutableStateOf(tertiary) + private set + var brand by mutableStateOf(brand) + private set + var staticLight by mutableStateOf(staticLight) + private set + var staticDark by mutableStateOf(staticDark) + private set + var inverse by mutableStateOf(inverse) + private set + + @Stable + class Status internal constructor( + success: Color, + error: Color, + warning: Color, + info: Color, + ) { + var success by mutableStateOf(success) + private set + var error by mutableStateOf(error) + private set + var warning by mutableStateOf(warning) + private set + var info by mutableStateOf(info) + private set + + fun update(other: Status) { + success = other.success + error = other.error + warning = other.warning + info = other.info + } + } + + @Stable + class Accent internal constructor( + blue: Color, + violet: Color, + red: Color, + orange: Color, + yellow: Color, + green: Color, + ) { + var blue by mutableStateOf(blue) + private set + var violet by mutableStateOf(violet) + private set + var red by mutableStateOf(red) + private set + var orange by mutableStateOf(orange) + private set + var yellow by mutableStateOf(yellow) + private set + var green by mutableStateOf(green) + private set + + fun update(other: Accent) { + blue = other.blue + violet = other.violet + red = other.red + orange = other.orange + yellow = other.yellow + green = other.green + } + } + + fun update(other: Icon) { + primary = other.primary + secondary = other.secondary + tertiary = other.tertiary + brand = other.brand + staticLight = other.staticLight + staticDark = other.staticDark + inverse = other.inverse + status.update(other.status) + accent.update(other.accent) + } + } + + @Stable + class Border internal constructor( + primary: Color, + secondary: Color, + tertiary: Color, + brand: Color, + val inverse: Inverse, + val status: Status, + val accent: Accent, + ) { + var primary by mutableStateOf(primary) + private set + var secondary by mutableStateOf(secondary) + private set + var tertiary by mutableStateOf(tertiary) + private set + var brand by mutableStateOf(brand) + private set + + @Stable + class Inverse internal constructor( + primary: Color, + secondary: Color, + tertiary: Color, + ) { + var primary by mutableStateOf(primary) + private set + var secondary by mutableStateOf(secondary) + private set + var tertiary by mutableStateOf(tertiary) + private set + + fun update(other: Inverse) { + primary = other.primary + secondary = other.secondary + tertiary = other.tertiary + } + } + + @Stable + class Status internal constructor( + success: Color, + successSubtle: Color, + error: Color, + errorSubtle: Color, + warning: Color, + warningSubtle: Color, + info: Color, + infoSubtle: Color, + ) { + var success by mutableStateOf(success) + private set + var successSubtle by mutableStateOf(successSubtle) + private set + var error by mutableStateOf(error) + private set + var errorSubtle by mutableStateOf(errorSubtle) + private set + var warning by mutableStateOf(warning) + private set + var warningSubtle by mutableStateOf(warningSubtle) + private set + var info by mutableStateOf(info) + private set + var infoSubtle by mutableStateOf(infoSubtle) + private set + + fun update(other: Status) { + success = other.success + successSubtle = other.successSubtle + error = other.error + errorSubtle = other.errorSubtle + warning = other.warning + warningSubtle = other.warningSubtle + info = other.info + infoSubtle = other.infoSubtle + } + } + + @Stable + class Accent internal constructor( + blue: Color, + violet: Color, + red: Color, + orange: Color, + yellow: Color, + green: Color, + ) { + var blue by mutableStateOf(blue) + private set + var violet by mutableStateOf(violet) + private set + var red by mutableStateOf(red) + private set + var orange by mutableStateOf(orange) + private set + var yellow by mutableStateOf(yellow) + private set + var green by mutableStateOf(green) + private set + + fun update(other: Accent) { + blue = other.blue + violet = other.violet + red = other.red + orange = other.orange + yellow = other.yellow + green = other.green + } + } + + fun update(other: Border) { + primary = other.primary + secondary = other.secondary + tertiary = other.tertiary + brand = other.brand + inverse.update(other.inverse) + status.update(other.status) + accent.update(other.accent) + } + } + + @Stable + class Overlay internal constructor( + modal: Color, + ) { + var modal by mutableStateOf(modal) + private set + + fun update(other: Overlay) { + modal = other.modal + } + } + + @Stable + class Interaction internal constructor( + pressStaticLight: Color, + pressStaticDark: Color, + val press: Press, + val focusRing: FocusRing, + ) { + var pressStaticLight by mutableStateOf(pressStaticLight) + private set + var pressStaticDark by mutableStateOf(pressStaticDark) + private set + + @Stable + class Press internal constructor( + default: Color, + inverse: Color, + ) { + var default by mutableStateOf(default) + private set + var inverse by mutableStateOf(inverse) + private set + + fun update(other: Press) { + default = other.default + inverse = other.inverse + } + } + + @Stable + class FocusRing internal constructor( + default: Color, + brand: Color, + ) { + var default by mutableStateOf(default) + private set + var brand by mutableStateOf(brand) + private set + + fun update(other: FocusRing) { + default = other.default + brand = other.brand + } + } + + fun update(other: Interaction) { + pressStaticLight = other.pressStaticLight + pressStaticDark = other.pressStaticDark + press.update(other.press) + focusRing.update(other.focusRing) + } + } + + @Stable + class Material internal constructor( + val tint: Tint, + val fill: Fill, + val lighten: Lighten, + val softLight: SoftLight, + val border: Border, + ) { + + @Stable + class Tint internal constructor( + glass: Color, + blur: Color, + solid: Color, + ) { + var glass by mutableStateOf(glass) + private set + var blur by mutableStateOf(blur) + private set + var solid by mutableStateOf(solid) + private set + + fun update(other: Tint) { + glass = other.glass + blur = other.blur + solid = other.solid + } + } + + @Stable + class Fill internal constructor( + glass: Color, + blur: Color, + solid: Color, + ) { + var glass by mutableStateOf(glass) + private set + var blur by mutableStateOf(blur) + private set + var solid by mutableStateOf(solid) + private set + + fun update(other: Fill) { + glass = other.glass + blur = other.blur + solid = other.solid + } + } + + @Stable + class Lighten internal constructor( + glass: Color, + blur: Color, + solid: Color, + ) { + var glass by mutableStateOf(glass) + private set + var blur by mutableStateOf(blur) + private set + var solid by mutableStateOf(solid) + private set + + fun update(other: Lighten) { + glass = other.glass + blur = other.blur + solid = other.solid + } + } + + @Stable + class SoftLight internal constructor( + glass: Color, + blur: Color, + solid: Color, + ) { + var glass by mutableStateOf(glass) + private set + var blur by mutableStateOf(blur) + private set + var solid by mutableStateOf(solid) + private set + + fun update(other: SoftLight) { + glass = other.glass + blur = other.blur + solid = other.solid + } + } + + @Stable + class Border internal constructor( + start: Color, + mid: Color, + end: Color, + ) { + var start by mutableStateOf(start) + private set + var mid by mutableStateOf(mid) + private set + var end by mutableStateOf(end) + private set + + fun update(other: Border) { + start = other.start + mid = other.mid + end = other.end + } + } + + fun update(other: Material) { + tint.update(other.tint) + fill.update(other.fill) + lighten.update(other.lighten) + softLight.update(other.softLight) + border.update(other.border) + } + } + + fun update(other: TangemColors3) { + text.update(other.text) + bg.update(other.bg) + icon.update(other.icon) + border.update(other.border) + overlay.update(other.overlay) + interaction.update(other.interaction) + material.update(other.material) + } +} \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/res/generated/TangemColors3Dark.kt b/core/ui/src/main/java/com/tangem/core/ui/res/generated/TangemColors3Dark.kt new file mode 100644 index 0000000000..a2fa0d535e --- /dev/null +++ b/core/ui/src/main/java/com/tangem/core/ui/res/generated/TangemColors3Dark.kt @@ -0,0 +1,173 @@ +@file:Suppress("all") + +package com.tangem.core.ui.res.generated + +import androidx.compose.ui.graphics.Color + +/** + * Auto-generated from design tokens. Do not edit manually. + * Theme: Dark + */ +internal fun darkColors3() = + TangemColors3( + text = TangemColors3.Text( + primary = TangemColorPalette.Base.white, + secondary = TangemColorPalette.Opaque.BaseWhite.`60`, + tertiary = TangemColorPalette.Opaque.BaseWhite.`30`, + brand = TangemColorPalette.Blue.`50`, + staticLight = TangemColors3.Text.StaticLight( + primary = TangemColorPalette.Base.black, + secondary = TangemColorPalette.Opaque.BaseBlack.`60`, + tertiary = TangemColorPalette.Opaque.BaseBlack.`40`, + ), + staticDark = TangemColors3.Text.StaticDark( + primary = TangemColorPalette.Base.white, + secondary = TangemColorPalette.Opaque.BaseWhite.`60`, + tertiary = TangemColorPalette.Opaque.BaseWhite.`30`, + ), + inverse = TangemColors3.Text.Inverse( + primary = TangemColorPalette.Neutral.`95`, + secondary = TangemColorPalette.Opaque.Neutral95.`60`, + tertiary = TangemColorPalette.Opaque.Neutral95.`40`, + ), + status = TangemColors3.Text.Status( + success = TangemColorPalette.Green.`40`, + error = TangemColorPalette.Red.`40`, + warning = TangemColorPalette.Yellow.`40`, + info = TangemColorPalette.Blue.`40`, + ), + accent = TangemColors3.Text.Accent( + blue = TangemColorPalette.Blue.`40`, + violet = TangemColorPalette.Violet.`40`, + red = TangemColorPalette.Red.`40`, + orange = TangemColorPalette.Orange.`40`, + yellow = TangemColorPalette.Yellow.`40`, + green = TangemColorPalette.Green.`40`, + ), + ), + bg = TangemColors3.Bg( + primary = TangemColorPalette.Neutral.`95`, + secondary = TangemColorPalette.Neutral.`90`, + tertiary = TangemColorPalette.Neutral.`80`, + brand = TangemColorPalette.Blue.`50`, + inverse = TangemColorPalette.Neutral.`5`, + base = TangemColorPalette.Base.black, + disabled = TangemColorPalette.Neutral.`70`, + opaque = TangemColors3.Bg.Opaque( + primary = TangemColorPalette.Opaque.BaseWhite.`5`, + secondary = TangemColorPalette.Opaque.BaseWhite.`10`, + ), + status = TangemColors3.Bg.Status( + success = TangemColorPalette.Green.`50`, + successSubtle = TangemColorPalette.Green.`90`, + error = TangemColorPalette.Red.`50`, + errorSubtle = TangemColorPalette.Red.`90`, + warning = TangemColorPalette.Yellow.`50`, + warningSubtle = TangemColorPalette.Yellow.`90`, + info = TangemColorPalette.Blue.`50`, + infoSubtle = TangemColorPalette.Blue.`90`, + ), + accent = TangemColors3.Bg.Accent( + blue = TangemColorPalette.Blue.`50`, + violet = TangemColorPalette.Violet.`50`, + red = TangemColorPalette.Red.`50`, + orange = TangemColorPalette.Orange.`50`, + yellow = TangemColorPalette.Yellow.`50`, + green = TangemColorPalette.Green.`50`, + ), + ), + icon = TangemColors3.Icon( + primary = TangemColorPalette.Base.white, + secondary = TangemColorPalette.Opaque.BaseWhite.`60`, + tertiary = TangemColorPalette.Opaque.BaseWhite.`30`, + brand = TangemColorPalette.Blue.`50`, + staticLight = TangemColorPalette.Base.black, + staticDark = TangemColorPalette.Base.white, + inverse = TangemColorPalette.Neutral.`95`, + status = TangemColors3.Icon.Status( + success = TangemColorPalette.Green.`40`, + error = TangemColorPalette.Red.`40`, + warning = TangemColorPalette.Yellow.`40`, + info = TangemColorPalette.Blue.`40`, + ), + accent = TangemColors3.Icon.Accent( + blue = TangemColorPalette.Blue.`40`, + violet = TangemColorPalette.Violet.`40`, + red = TangemColorPalette.Red.`40`, + orange = TangemColorPalette.Orange.`40`, + yellow = TangemColorPalette.Yellow.`40`, + green = TangemColorPalette.Green.`40`, + ), + ), + border = TangemColors3.Border( + primary = TangemColorPalette.Opaque.BaseWhite.`5`, + secondary = TangemColorPalette.Opaque.BaseWhite.`10`, + tertiary = TangemColorPalette.Opaque.BaseWhite.`20`, + brand = TangemColorPalette.Blue.`50`, + inverse = TangemColors3.Border.Inverse( + primary = TangemColorPalette.Opaque.BaseBlack.`5`, + secondary = TangemColorPalette.Opaque.BaseBlack.`10`, + tertiary = TangemColorPalette.Opaque.BaseBlack.`20`, + ), + status = TangemColors3.Border.Status( + success = TangemColorPalette.Green.`40`, + successSubtle = TangemColorPalette.Green.`80`, + error = TangemColorPalette.Red.`40`, + errorSubtle = TangemColorPalette.Red.`80`, + warning = TangemColorPalette.Yellow.`40`, + warningSubtle = TangemColorPalette.Yellow.`80`, + info = TangemColorPalette.Blue.`40`, + infoSubtle = TangemColorPalette.Blue.`80`, + ), + accent = TangemColors3.Border.Accent( + blue = TangemColorPalette.Blue.`40`, + violet = TangemColorPalette.Violet.`40`, + red = TangemColorPalette.Red.`40`, + orange = TangemColorPalette.Orange.`40`, + yellow = TangemColorPalette.Yellow.`40`, + green = TangemColorPalette.Green.`40`, + ), + ), + overlay = TangemColors3.Overlay( + modal = TangemColorPalette.Opaque.BaseBlack.`80`, + ), + interaction = TangemColors3.Interaction( + pressStaticLight = TangemColorPalette.Opaque.BaseBlack.`10`, + pressStaticDark = TangemColorPalette.Opaque.BaseWhite.`10`, + press = TangemColors3.Interaction.Press( + default = TangemColorPalette.Opaque.BaseWhite.`10`, + inverse = TangemColorPalette.Opaque.BaseBlack.`10`, + ), + focusRing = TangemColors3.Interaction.FocusRing( + default = TangemColorPalette.Neutral.`5`, + brand = TangemColorPalette.Blue.`50`, + ), + ), + material = TangemColors3.Material( + tint = TangemColors3.Material.Tint( + glass = Color(0x662C2C2C), + blur = Color(0x00000000), + solid = Color(0x1AFFFFFF), + ), + fill = TangemColors3.Material.Fill( + glass = Color(0x00000000), + blur = Color(0x1AFFFFFF), + solid = Color(0xE62C2C2C), + ), + lighten = TangemColors3.Material.Lighten( + glass = Color(0x33181818), + blur = Color(0x00000000), + solid = Color(0x00000000), + ), + softLight = TangemColors3.Material.SoftLight( + glass = Color(0x1A000000), + blur = Color(0x00000000), + solid = Color(0x00000000), + ), + border = TangemColors3.Material.Border( + start = Color(0x33FFFFFF), + mid = Color(0x00FFFFFF), + end = Color(0x1AFFFFFF), + ), + ), + ) \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/res/generated/TangemColors3Light.kt b/core/ui/src/main/java/com/tangem/core/ui/res/generated/TangemColors3Light.kt new file mode 100644 index 0000000000..05c34c6f5e --- /dev/null +++ b/core/ui/src/main/java/com/tangem/core/ui/res/generated/TangemColors3Light.kt @@ -0,0 +1,173 @@ +@file:Suppress("all") + +package com.tangem.core.ui.res.generated + +import androidx.compose.ui.graphics.Color + +/** + * Auto-generated from design tokens. Do not edit manually. + * Theme: Light + */ +internal fun lightColors3() = + TangemColors3( + text = TangemColors3.Text( + primary = TangemColorPalette.Neutral.`95`, + secondary = TangemColorPalette.Opaque.Neutral95.`60`, + tertiary = TangemColorPalette.Opaque.Neutral95.`40`, + brand = TangemColorPalette.Blue.`50`, + staticLight = TangemColors3.Text.StaticLight( + primary = TangemColorPalette.Base.black, + secondary = TangemColorPalette.Opaque.BaseBlack.`60`, + tertiary = TangemColorPalette.Opaque.BaseBlack.`40`, + ), + staticDark = TangemColors3.Text.StaticDark( + primary = TangemColorPalette.Base.white, + secondary = TangemColorPalette.Opaque.BaseWhite.`60`, + tertiary = TangemColorPalette.Opaque.BaseWhite.`30`, + ), + inverse = TangemColors3.Text.Inverse( + primary = TangemColorPalette.Base.white, + secondary = TangemColorPalette.Opaque.BaseWhite.`60`, + tertiary = TangemColorPalette.Opaque.BaseWhite.`30`, + ), + status = TangemColors3.Text.Status( + success = TangemColorPalette.Green.`50`, + error = TangemColorPalette.Red.`50`, + warning = TangemColorPalette.Yellow.`50`, + info = TangemColorPalette.Blue.`50`, + ), + accent = TangemColors3.Text.Accent( + blue = TangemColorPalette.Blue.`50`, + violet = TangemColorPalette.Violet.`50`, + red = TangemColorPalette.Red.`50`, + orange = TangemColorPalette.Orange.`50`, + yellow = TangemColorPalette.Yellow.`50`, + green = TangemColorPalette.Green.`50`, + ), + ), + bg = TangemColors3.Bg( + primary = TangemColorPalette.Neutral.`5`, + secondary = TangemColorPalette.Base.white, + tertiary = TangemColorPalette.Neutral.`10`, + brand = TangemColorPalette.Blue.`50`, + inverse = TangemColorPalette.Neutral.`95`, + base = TangemColorPalette.Neutral.`5`, + disabled = TangemColorPalette.Neutral.`20`, + opaque = TangemColors3.Bg.Opaque( + primary = TangemColorPalette.Opaque.Neutral95.`5`, + secondary = TangemColorPalette.Opaque.Neutral95.`10`, + ), + status = TangemColors3.Bg.Status( + success = TangemColorPalette.Green.`50`, + successSubtle = TangemColorPalette.Green.`10`, + error = TangemColorPalette.Red.`50`, + errorSubtle = TangemColorPalette.Red.`10`, + warning = TangemColorPalette.Yellow.`50`, + warningSubtle = TangemColorPalette.Yellow.`10`, + info = TangemColorPalette.Blue.`50`, + infoSubtle = TangemColorPalette.Blue.`10`, + ), + accent = TangemColors3.Bg.Accent( + blue = TangemColorPalette.Blue.`50`, + violet = TangemColorPalette.Violet.`50`, + red = TangemColorPalette.Red.`50`, + orange = TangemColorPalette.Orange.`50`, + yellow = TangemColorPalette.Yellow.`50`, + green = TangemColorPalette.Green.`50`, + ), + ), + icon = TangemColors3.Icon( + primary = TangemColorPalette.Neutral.`95`, + secondary = TangemColorPalette.Opaque.Neutral95.`60`, + tertiary = TangemColorPalette.Opaque.Neutral95.`40`, + brand = TangemColorPalette.Blue.`50`, + staticLight = TangemColorPalette.Base.black, + staticDark = TangemColorPalette.Base.white, + inverse = TangemColorPalette.Base.white, + status = TangemColors3.Icon.Status( + success = TangemColorPalette.Green.`50`, + error = TangemColorPalette.Red.`50`, + warning = TangemColorPalette.Yellow.`50`, + info = TangemColorPalette.Blue.`50`, + ), + accent = TangemColors3.Icon.Accent( + blue = TangemColorPalette.Blue.`50`, + violet = TangemColorPalette.Violet.`50`, + red = TangemColorPalette.Red.`50`, + orange = TangemColorPalette.Orange.`50`, + yellow = TangemColorPalette.Yellow.`50`, + green = TangemColorPalette.Green.`50`, + ), + ), + border = TangemColors3.Border( + primary = TangemColorPalette.Opaque.BaseBlack.`5`, + secondary = TangemColorPalette.Opaque.BaseBlack.`10`, + tertiary = TangemColorPalette.Opaque.BaseBlack.`20`, + brand = TangemColorPalette.Blue.`50`, + inverse = TangemColors3.Border.Inverse( + primary = TangemColorPalette.Opaque.BaseWhite.`5`, + secondary = TangemColorPalette.Opaque.BaseWhite.`10`, + tertiary = TangemColorPalette.Opaque.BaseWhite.`20`, + ), + status = TangemColors3.Border.Status( + success = TangemColorPalette.Green.`50`, + successSubtle = TangemColorPalette.Green.`20`, + error = TangemColorPalette.Red.`50`, + errorSubtle = TangemColorPalette.Red.`20`, + warning = TangemColorPalette.Yellow.`50`, + warningSubtle = TangemColorPalette.Yellow.`20`, + info = TangemColorPalette.Blue.`50`, + infoSubtle = TangemColorPalette.Blue.`20`, + ), + accent = TangemColors3.Border.Accent( + blue = TangemColorPalette.Blue.`50`, + violet = TangemColorPalette.Violet.`50`, + red = TangemColorPalette.Red.`50`, + orange = TangemColorPalette.Orange.`50`, + yellow = TangemColorPalette.Yellow.`50`, + green = TangemColorPalette.Green.`50`, + ), + ), + overlay = TangemColors3.Overlay( + modal = TangemColorPalette.Opaque.BaseBlack.`60`, + ), + interaction = TangemColors3.Interaction( + pressStaticLight = TangemColorPalette.Opaque.BaseBlack.`10`, + pressStaticDark = TangemColorPalette.Opaque.BaseWhite.`10`, + press = TangemColors3.Interaction.Press( + default = TangemColorPalette.Opaque.BaseBlack.`10`, + inverse = TangemColorPalette.Opaque.BaseWhite.`10`, + ), + focusRing = TangemColors3.Interaction.FocusRing( + default = TangemColorPalette.Neutral.`95`, + brand = TangemColorPalette.Blue.`50`, + ), + ), + material = TangemColors3.Material( + tint = TangemColors3.Material.Tint( + glass = Color(0x00000000), + blur = Color(0x00000000), + solid = Color(0x1AFFFFFF), + ), + fill = TangemColors3.Material.Fill( + glass = Color(0x00000000), + blur = Color(0x99FFFFFF), + solid = Color(0xE6FFFFFF), + ), + lighten = TangemColors3.Material.Lighten( + glass = Color(0x80F7F7F7), + blur = Color(0x00000000), + solid = Color(0x00000000), + ), + softLight = TangemColors3.Material.SoftLight( + glass = Color(0x33000000), + blur = Color(0x00000000), + solid = Color(0x00000000), + ), + border = TangemColors3.Material.Border( + start = Color(0x26000000), + mid = Color(0x00000000), + end = Color(0x1A000000), + ), + ), + ) \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/res/generated/TangemDarkColorTokens.kt b/core/ui/src/main/java/com/tangem/core/ui/res/generated/TangemDarkColorTokens.kt deleted file mode 100644 index 0f731be297..0000000000 --- a/core/ui/src/main/java/com/tangem/core/ui/res/generated/TangemDarkColorTokens.kt +++ /dev/null @@ -1,118 +0,0 @@ -package com.tangem.core.ui.res.generated - -import androidx.compose.ui.graphics.Color - -/** - * Auto-generated from design tokens. Do not edit manually. - * Theme: Dark - */ -internal class TangemDarkColorTokens { - // color.text - val colorTextPrimary = Color(0xFFFFFFFF) - val colorTextSecondary = Color(0x99FFFFFF) - val colorTextTertiary = Color(0x4DFFFFFF) - val colorTextBrand = Color(0xFF0090F9) - val colorTextStaticLightPrimary = Color(0xFF000000) - val colorTextStaticLightSecondary = Color(0x99000000) - val colorTextStaticLightTertiary = Color(0x66000000) - val colorTextStaticDarkPrimary = Color(0xFFFFFFFF) - val colorTextStaticDarkSecondary = Color(0x99FFFFFF) - val colorTextStaticDarkTertiary = Color(0x4DFFFFFF) - val colorTextInversePrimary = Color(0xFF0F0F0F) - val colorTextInverseSecondary = Color(0x990F0F0F) - val colorTextInverseTertiary = Color(0x660F0F0F) - val colorTextStatusSuccess = Color(0xFF2DAE3B) - val colorTextStatusError = Color(0xFFFF5E66) - val colorTextStatusWarning = Color(0xFFEFA210) - val colorTextStatusInfo = Color(0xFF109FF0) - val colorTextAccentBlue = Color(0xFF109FF0) - val colorTextAccentViolet = Color(0xFFB07BFD) - val colorTextAccentRed = Color(0xFFFF5E66) - val colorTextAccentOrange = Color(0xFFFA6931) - val colorTextAccentYellow = Color(0xFFEFA210) - val colorTextAccentGreen = Color(0xFF2DAE3B) - - // color.bg - val colorBgPrimary = Color(0xFF0F0F0F) - val colorBgSecondary = Color(0xFF1B1B1B) - val colorBgTertiary = Color(0xFF2C2C2C) - val colorBgBrand = Color(0xFF0090F9) - val colorBgInverse = Color(0xFFF4F4F4) - val colorBgBase = Color(0xFF000000) - val colorBgOpaquePrimary = Color(0x0DFFFFFF) - val colorBgOpaqueSecondary = Color(0x1AFFFFFF) - val colorBgStatusSuccess = Color(0xFF2DA30D) - val colorBgStatusError = Color(0xFFF25508) - val colorBgStatusWarning = Color(0xFFE68A03) - val colorBgStatusInfo = Color(0xFF0090F9) - val colorBgAccentBlue = Color(0xFF0090F9) - val colorBgAccentViolet = Color(0xFFA967FD) - val colorBgAccentRed = Color(0xFFFE4142) - val colorBgAccentOrange = Color(0xFFF25508) - val colorBgAccentYellow = Color(0xFFE68A03) - val colorBgAccentGreen = Color(0xFF2DA30D) - - // color.icon - val colorIconPrimary = Color(0xFFFFFFFF) - val colorIconSecondary = Color(0x99FFFFFF) - val colorIconTertiary = Color(0x4DFFFFFF) - val colorIconBrand = Color(0xFF0090F9) - val colorIconStaticLight = Color(0xFF000000) - val colorIconStaticDark = Color(0xFFFFFFFF) - val colorIconInverse = Color(0xFF0F0F0F) - val colorIconStatusSuccess = Color(0xFF2DAE3B) - val colorIconStatusError = Color(0xFFFA6931) - val colorIconStatusWarning = Color(0xFFEFA210) - val colorIconStatusInfo = Color(0xFF109FF0) - val colorIconAccentBlue = Color(0xFF109FF0) - val colorIconAccentViolet = Color(0xFFB07BFD) - val colorIconAccentRed = Color(0xFFFF5E66) - val colorIconAccentOrange = Color(0xFFFA6931) - val colorIconAccentYellow = Color(0xFFEFA210) - val colorIconAccentGreen = Color(0xFF2DAE3B) - - // color.border - val colorBorderPrimary = Color(0x0DFFFFFF) - val colorBorderSecondary = Color(0x1AFFFFFF) - val colorBorderTertiary = Color(0x33FFFFFF) - val colorBorderBrand = Color(0xFF0090F9) - val colorBorderInversePrimary = Color(0x0D000000) - val colorBorderInverseSecondary = Color(0x1A000000) - val colorBorderInverseTertiary = Color(0x33000000) - val colorBorderStatusSuccess = Color(0xFF2DAE3B) - val colorBorderStatusError = Color(0xFFFA6931) - val colorBorderStatusWarning = Color(0xFFEFA210) - val colorBorderStatusInfo = Color(0xFF109FF0) - val colorBorderAccentBlue = Color(0xFF109FF0) - val colorBorderAccentViolet = Color(0xFFB07BFD) - val colorBorderAccentRed = Color(0xFFFF5E66) - val colorBorderAccentOrange = Color(0xFFFA6931) - val colorBorderAccentYellow = Color(0xFFEFA210) - val colorBorderAccentGreen = Color(0xFF2DAE3B) - - // color.overlay - val colorOverlayModal = Color(0xCC000000) - - // color.interaction - val colorInteractionPress = Color(0x1AFFFFFF) - val colorInteractionPressStaticLight = Color(0x1A000000) - val colorInteractionPressStaticDark = Color(0x1AFFFFFF) - val colorInteractionPressInverse = Color(0x1A000000) - - // color.material - val colorMaterialTintGlass = Color(0x66181818) - val colorMaterialTintBlur = Color(0x00000000) - val colorMaterialTintSolid = Color(0x1AFFFFFF) - val colorMaterialFillGlass = Color(0x00000000) - val colorMaterialFillBlur = Color(0x1AFFFFFF) - val colorMaterialFillSolid = Color(0xE62C2C2C) - val colorMaterialLightenGlass = Color(0x33181818) - val colorMaterialLightenBlur = Color(0x00000000) - val colorMaterialLightenSolid = Color(0x00000000) - val colorMaterialSoftLightGlass = Color(0x1A000000) - val colorMaterialSoftLightBlur = Color(0x00000000) - val colorMaterialSoftLightSolid = Color(0x00000000) - val colorMaterialBorderStart = Color(0x33FFFFFF) - val colorMaterialBorderMid = Color(0x00FFFFFF) - val colorMaterialBorderEnd = Color(0x1AFFFFFF) -} \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/res/generated/TangemDimens3.kt b/core/ui/src/main/java/com/tangem/core/ui/res/generated/TangemDimens3.kt new file mode 100644 index 0000000000..27f3b6c69e --- /dev/null +++ b/core/ui/src/main/java/com/tangem/core/ui/res/generated/TangemDimens3.kt @@ -0,0 +1,108 @@ +@file:Suppress("all") + +package com.tangem.core.ui.res.generated + +import androidx.compose.runtime.Stable +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp + +/** + * Auto-generated from design tokens. Do not edit manually. + */ +@Stable +class TangemDimens3 internal constructor( + val opacity: Opacity = Opacity(), + val blur: Blur = Blur(), + val borderRadius: BorderRadius = BorderRadius(), + val borderWidth: BorderWidth = BorderWidth(), + val size: Size = Size(), + val spacing: Spacing = Spacing(), +) { + @Stable + class Opacity internal constructor( + val disabled: Float = 0.4f, + ) + + @Stable + class Blur internal constructor( + val card: Dp = 48.dp, + val Button: Dp = 32.dp, + ) + + @Stable + class BorderRadius internal constructor( + val b100: Dp = 8.dp, + val b150: Dp = 12.dp, + val b200: Dp = 16.dp, + val b250: Dp = 20.dp, + val b300: Dp = 24.dp, + val b400: Dp = 32.dp, + val none: Dp = 0.dp, + val b075: Dp = 6.dp, + val b050: Dp = 4.dp, + val full: Dp = 999.dp, + ) + + @Stable + class BorderWidth internal constructor( + val none: Dp = 0.dp, + val xs: Dp = 0.5.dp, + val sm: Dp = 1.dp, + val md: Dp = 2.dp, + val lg: Dp = 4.dp, + ) + + @Stable + class Size internal constructor( + val s100: Dp = 8.dp, + val s125: Dp = 10.dp, + val s150: Dp = 12.dp, + val s200: Dp = 16.dp, + val s250: Dp = 20.dp, + val s300: Dp = 24.dp, + val s350: Dp = 28.dp, + val s400: Dp = 32.dp, + val s450: Dp = 36.dp, + val s500: Dp = 40.dp, + val s550: Dp = 44.dp, + val s600: Dp = 48.dp, + val s700: Dp = 56.dp, + val s800: Dp = 64.dp, + val s900: Dp = 72.dp, + val s1000: Dp = 80.dp, + val s1100: Dp = 88.dp, + val s1200: Dp = 96.dp, + val s025: Dp = 2.dp, + val s050: Dp = 4.dp, + val card: Card = Card(), + ) { + @Stable + class Card internal constructor( + val sm: Dp = 128.dp, + ) + } + + @Stable + class Spacing internal constructor( + val s100: Dp = 8.dp, + val s125: Dp = 10.dp, + val s150: Dp = 12.dp, + val s200: Dp = 16.dp, + val s250: Dp = 20.dp, + val s300: Dp = 24.dp, + val s350: Dp = 28.dp, + val s400: Dp = 32.dp, + val s450: Dp = 36.dp, + val s500: Dp = 40.dp, + val s550: Dp = 44.dp, + val s600: Dp = 48.dp, + val s700: Dp = 56.dp, + val s800: Dp = 64.dp, + val s900: Dp = 72.dp, + val s1000: Dp = 80.dp, + val s025: Dp = 2.dp, + val s050: Dp = 4.dp, + val s075: Dp = 6.dp, + val none: Dp = 0.dp, + ) +} \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/res/generated/TangemDimensionTokens.kt b/core/ui/src/main/java/com/tangem/core/ui/res/generated/TangemDimensionTokens.kt deleted file mode 100644 index dcf1be90a8..0000000000 --- a/core/ui/src/main/java/com/tangem/core/ui/res/generated/TangemDimensionTokens.kt +++ /dev/null @@ -1,72 +0,0 @@ -package com.tangem.core.ui.res.generated - -import androidx.compose.ui.unit.dp - -/** - * Auto-generated from design tokens. Do not edit manually. - */ -internal class TangemDimensionTokens { - // blur - val blurSizeCard = 48.dp - val blurSizeButton = 32.dp - - // border-radius - val borderRadius100 = 8.dp - val borderRadius150 = 12.dp - val borderRadius200 = 16.dp - val borderRadius250 = 20.dp - val borderRadius300 = 24.dp - val borderRadius400 = 32.dp - val borderRadiusNone = 0.dp - val borderRadius075 = 6.dp - val borderRadius050 = 4.dp - val borderRadiusFull = 999.dp - - // border-width - val borderWidthNone = 0.dp - val borderWidthXs = 0.5.dp - val borderWidthSm = 1.dp - val borderWidthMd = 2.dp - val borderWidthLg = 4.dp - - // size - val size100 = 8.dp - val size125 = 10.dp - val size150 = 12.dp - val size200 = 16.dp - val size250 = 20.dp - val size300 = 24.dp - val size350 = 28.dp - val size400 = 32.dp - val size450 = 36.dp - val size500 = 40.dp - val size550 = 44.dp - val size600 = 48.dp - val size700 = 56.dp - val size800 = 64.dp - val size1000 = 80.dp - val size1100 = 88.dp - val size1200 = 96.dp - val size025 = 2.dp - val size050 = 4.dp - val sizeCardSm = 128.dp - - // spacing - val spacing100 = 8.dp - val spacing150 = 12.dp - val spacing200 = 16.dp - val spacing250 = 20.dp - val spacing300 = 24.dp - val spacing350 = 28.dp - val spacing400 = 32.dp - val spacing450 = 36.dp - val spacing500 = 40.dp - val spacing550 = 44.dp - val spacing600 = 48.dp - val spacing700 = 56.dp - val spacing800 = 64.dp - val spacing1000 = 80.dp - val spacing025 = 2.dp - val spacing050 = 4.dp - val spacingNone = 0.dp -} \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/res/generated/TangemLightColorTokens.kt b/core/ui/src/main/java/com/tangem/core/ui/res/generated/TangemLightColorTokens.kt deleted file mode 100644 index 75c0758b25..0000000000 --- a/core/ui/src/main/java/com/tangem/core/ui/res/generated/TangemLightColorTokens.kt +++ /dev/null @@ -1,118 +0,0 @@ -package com.tangem.core.ui.res.generated - -import androidx.compose.ui.graphics.Color - -/** - * Auto-generated from design tokens. Do not edit manually. - * Theme: Light - */ -internal class TangemLightColorTokens { - // color.text - val colorTextPrimary = Color(0xFF0F0F0F) - val colorTextSecondary = Color(0x990F0F0F) - val colorTextTertiary = Color(0x660F0F0F) - val colorTextBrand = Color(0xFF0090F9) - val colorTextStaticLightPrimary = Color(0xFF000000) - val colorTextStaticLightSecondary = Color(0x99000000) - val colorTextStaticLightTertiary = Color(0x66000000) - val colorTextStaticDarkPrimary = Color(0xFFFFFFFF) - val colorTextStaticDarkSecondary = Color(0x99FFFFFF) - val colorTextStaticDarkTertiary = Color(0x4DFFFFFF) - val colorTextInversePrimary = Color(0xFFFFFFFF) - val colorTextInverseSecondary = Color(0x99FFFFFF) - val colorTextInverseTertiary = Color(0x4DFFFFFF) - val colorTextStatusSuccess = Color(0xFF2DA30D) - val colorTextStatusError = Color(0xFFFE4142) - val colorTextStatusWarning = Color(0xFFE68A03) - val colorTextStatusInfo = Color(0xFF0090F9) - val colorTextAccentBlue = Color(0xFF0090F9) - val colorTextAccentViolet = Color(0xFFA967FD) - val colorTextAccentRed = Color(0xFFFE4142) - val colorTextAccentOrange = Color(0xFFF25508) - val colorTextAccentYellow = Color(0xFFE68A03) - val colorTextAccentGreen = Color(0xFF2DA30D) - - // color.bg - val colorBgPrimary = Color(0xFFF4F4F4) - val colorBgSecondary = Color(0xFFFFFFFF) - val colorBgTertiary = Color(0xFFEAEAEA) - val colorBgBrand = Color(0xFF0090F9) - val colorBgInverse = Color(0xFFF4F4F4) - val colorBgBase = Color(0xFFF4F4F4) - val colorBgOpaquePrimary = Color(0x0DFFFFFF) - val colorBgOpaqueSecondary = Color(0x1AFFFFFF) - val colorBgStatusSuccess = Color(0xFF2DA30D) - val colorBgStatusError = Color(0xFFF25508) - val colorBgStatusWarning = Color(0xFFE68A03) - val colorBgStatusInfo = Color(0xFF0090F9) - val colorBgAccentBlue = Color(0xFF0090F9) - val colorBgAccentViolet = Color(0xFFA967FD) - val colorBgAccentRed = Color(0xFFFE4142) - val colorBgAccentOrange = Color(0xFFF25508) - val colorBgAccentYellow = Color(0xFFE68A03) - val colorBgAccentGreen = Color(0xFF2DA30D) - - // color.icon - val colorIconPrimary = Color(0xFF0F0F0F) - val colorIconSecondary = Color(0x990F0F0F) - val colorIconTertiary = Color(0x660F0F0F) - val colorIconBrand = Color(0xFF0090F9) - val colorIconStaticLight = Color(0xFF000000) - val colorIconStaticDark = Color(0xFFFFFFFF) - val colorIconInverse = Color(0xFFFFFFFF) - val colorIconStatusSuccess = Color(0xFF2DA30D) - val colorIconStatusError = Color(0xFFF25508) - val colorIconStatusWarning = Color(0xFFE68A03) - val colorIconStatusInfo = Color(0xFF0090F9) - val colorIconAccentBlue = Color(0xFF0090F9) - val colorIconAccentViolet = Color(0xFFA967FD) - val colorIconAccentRed = Color(0xFFFE4142) - val colorIconAccentOrange = Color(0xFFF25508) - val colorIconAccentYellow = Color(0xFFE68A03) - val colorIconAccentGreen = Color(0xFF2DA30D) - - // color.border - val colorBorderPrimary = Color(0x0D000000) - val colorBorderSecondary = Color(0x1A000000) - val colorBorderTertiary = Color(0x33000000) - val colorBorderBrand = Color(0xFF0090F9) - val colorBorderInversePrimary = Color(0x0D000000) - val colorBorderInverseSecondary = Color(0x1A000000) - val colorBorderInverseTertiary = Color(0x33000000) - val colorBorderStatusSuccess = Color(0xFF2DA30D) - val colorBorderStatusError = Color(0xFFF25508) - val colorBorderStatusWarning = Color(0xFFE68A03) - val colorBorderStatusInfo = Color(0xFF0090F9) - val colorBorderAccentBlue = Color(0xFF0090F9) - val colorBorderAccentViolet = Color(0xFFA967FD) - val colorBorderAccentRed = Color(0xFFFE4142) - val colorBorderAccentOrange = Color(0xFFF25508) - val colorBorderAccentYellow = Color(0xFFE68A03) - val colorBorderAccentGreen = Color(0xFF2DA30D) - - // color.overlay - val colorOverlayModal = Color(0x99000000) - - // color.interaction - val colorInteractionPress = Color(0x1A000000) - val colorInteractionPressStaticLight = Color(0x1A000000) - val colorInteractionPressStaticDark = Color(0x1AFFFFFF) - val colorInteractionPressInverse = Color(0x1AFFFFFF) - - // color.material - val colorMaterialTintGlass = Color(0x00000000) - val colorMaterialTintBlur = Color(0x00000000) - val colorMaterialTintSolid = Color(0x1AFFFFFF) - val colorMaterialFillGlass = Color(0x00000000) - val colorMaterialFillBlur = Color(0x99FFFFFF) - val colorMaterialFillSolid = Color(0xE6FFFFFF) - val colorMaterialLightenGlass = Color(0x80F7F7F7) - val colorMaterialLightenBlur = Color(0x00000000) - val colorMaterialLightenSolid = Color(0x00000000) - val colorMaterialSoftLightGlass = Color(0x33000000) - val colorMaterialSoftLightBlur = Color(0x00000000) - val colorMaterialSoftLightSolid = Color(0x00000000) - val colorMaterialBorderStart = Color(0x26000000) - val colorMaterialBorderMid = Color(0x00000000) - val colorMaterialBorderEnd = Color(0x1A000000) -} \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/res/generated/TangemOpacityTokens.kt b/core/ui/src/main/java/com/tangem/core/ui/res/generated/TangemOpacityTokens.kt deleted file mode 100644 index 0de7875fdb..0000000000 --- a/core/ui/src/main/java/com/tangem/core/ui/res/generated/TangemOpacityTokens.kt +++ /dev/null @@ -1,22 +0,0 @@ -package com.tangem.core.ui.res.generated - -/** - * Auto-generated from design tokens. Do not edit manually. - */ -internal class TangemOpacityTokens { - val opacity0 = 0f - val opacity5 = 0.05f - val opacity10 = 0.1f - val opacity15 = 0.15f - val opacity20 = 0.2f - val opacity25 = 0.25f - val opacity30 = 0.3f - val opacity40 = 0.4f - val opacity50 = 0.5f - val opacity60 = 0.6f - val opacity70 = 0.7f - val opacity80 = 0.8f - val opacity90 = 0.9f - val opacity100 = 1f - val opacityDisabled = 0.4f -} \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/res/generated/TangemShadowTokens.kt b/core/ui/src/main/java/com/tangem/core/ui/res/generated/TangemShadowTokens.kt deleted file mode 100644 index 5677a413a4..0000000000 --- a/core/ui/src/main/java/com/tangem/core/ui/res/generated/TangemShadowTokens.kt +++ /dev/null @@ -1,15 +0,0 @@ -package com.tangem.core.ui.res.generated - -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.unit.dp - -/** - * Auto-generated from design tokens. Do not edit manually. - */ -internal class TangemShadowTokens { - val shadowButtonBlur = 40.dp - val shadowButtonOffsetX = 0.dp - val shadowButtonOffsetY = 8.dp - val shadowButtonSpread = 0.dp - val shadowButtonColor = Color(0x1A000000) -} \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/res/generated/TangemTypography3.kt b/core/ui/src/main/java/com/tangem/core/ui/res/generated/TangemTypography3.kt new file mode 100644 index 0000000000..10e6eb6ccb --- /dev/null +++ b/core/ui/src/main/java/com/tangem/core/ui/res/generated/TangemTypography3.kt @@ -0,0 +1,112 @@ +@file:Suppress("all") + +package com.tangem.core.ui.res.generated + +import androidx.compose.runtime.Stable +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.LineBreak +import androidx.compose.ui.text.style.LineHeightStyle +import androidx.compose.ui.unit.sp + +/** + * Auto-generated from design tokens. Do not edit manually. + */ +@Stable +class TangemTypography3 internal constructor(fontFamily: FontFamily) { + val display: Display = Display(fontFamily) + val heading: Heading = Heading(fontFamily) + val body: Body = Body(fontFamily) + val subheading: Subheading = Subheading(fontFamily) + val caption: Caption = Caption(fontFamily) + + @Stable + class Display internal constructor(fontFamily: FontFamily) { + val medium: TextStyle = TextStyle( + fontFamily = fontFamily, + fontWeight = FontWeight.SemiBold, + fontSize = 44.sp, + lineHeight = 52.sp, + letterSpacing = (-0.92).sp, + lineHeightStyle = LineHeightStyle( + alignment = LineHeightStyle.Alignment.Center, + trim = LineHeightStyle.Trim.None, + ), + lineBreak = LineBreak.Heading, + ) + } + + @Stable + class Heading internal constructor(fontFamily: FontFamily) { + val medium: TextStyle = TextStyle( + fontFamily = fontFamily, + fontWeight = FontWeight.SemiBold, + fontSize = 28.sp, + lineHeight = 33.sp, + letterSpacing = (-0.37).sp, + lineHeightStyle = LineHeightStyle( + alignment = LineHeightStyle.Alignment.Center, + trim = LineHeightStyle.Trim.None, + ), + lineBreak = LineBreak.Heading, + ) + val small: TextStyle = TextStyle( + fontFamily = fontFamily, + fontWeight = FontWeight.SemiBold, + fontSize = 20.sp, + lineHeight = 24.sp, + letterSpacing = (-0.12).sp, + lineHeightStyle = LineHeightStyle( + alignment = LineHeightStyle.Alignment.Center, + trim = LineHeightStyle.Trim.None, + ), + lineBreak = LineBreak.Heading, + ) + } + + @Stable + class Body internal constructor(fontFamily: FontFamily) { + val medium: TextStyle = TextStyle( + fontFamily = fontFamily, + fontWeight = FontWeight.Medium, + fontSize = 16.sp, + lineHeight = 20.sp, + letterSpacing = 0.02.sp, + lineHeightStyle = LineHeightStyle( + alignment = LineHeightStyle.Alignment.Center, + trim = LineHeightStyle.Trim.None, + ), + ) + } + + @Stable + class Subheading internal constructor(fontFamily: FontFamily) { + val medium: TextStyle = TextStyle( + fontFamily = fontFamily, + fontWeight = FontWeight.Medium, + fontSize = 14.sp, + lineHeight = 17.sp, + letterSpacing = 0.07.sp, + lineHeightStyle = LineHeightStyle( + alignment = LineHeightStyle.Alignment.Center, + trim = LineHeightStyle.Trim.None, + ), + ) + } + + @Stable + class Caption internal constructor(fontFamily: FontFamily) { + val medium: TextStyle = TextStyle( + fontFamily = fontFamily, + fontWeight = FontWeight.Medium, + fontSize = 12.sp, + lineHeight = 16.sp, + letterSpacing = 0.18.sp, + lineHeightStyle = LineHeightStyle( + alignment = LineHeightStyle.Alignment.Center, + trim = LineHeightStyle.Trim.None, + ), + ) + } +} \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/res/generated/TangemTypographyTokens.kt b/core/ui/src/main/java/com/tangem/core/ui/res/generated/TangemTypographyTokens.kt deleted file mode 100644 index d3fcd82486..0000000000 --- a/core/ui/src/main/java/com/tangem/core/ui/res/generated/TangemTypographyTokens.kt +++ /dev/null @@ -1,88 +0,0 @@ -package com.tangem.core.ui.res.generated - -import androidx.compose.ui.text.TextStyle -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.text.style.LineBreak -import androidx.compose.ui.text.style.LineHeightStyle -import androidx.compose.ui.unit.sp -import com.tangem.core.ui.res.InterFamily - -/** - * Auto-generated from design tokens. Do not edit manually. - */ -internal class TangemTypographyTokens { - val fontDisplayMedium = TextStyle( - fontFamily = InterFamily, - fontWeight = FontWeight.SemiBold, - fontSize = 44.sp, - lineHeight = 52.sp, - letterSpacing = (-0.92).sp, - lineHeightStyle = LineHeightStyle( - alignment = LineHeightStyle.Alignment.Center, - trim = LineHeightStyle.Trim.None, - ), - lineBreak = LineBreak.Heading, - ) - - val fontHeadingMedium = TextStyle( - fontFamily = InterFamily, - fontWeight = FontWeight.SemiBold, - fontSize = 28.sp, - lineHeight = 33.sp, - letterSpacing = (-0.37).sp, - lineHeightStyle = LineHeightStyle( - alignment = LineHeightStyle.Alignment.Center, - trim = LineHeightStyle.Trim.None, - ), - lineBreak = LineBreak.Heading, - ) - - val fontHeadingSmall = TextStyle( - fontFamily = InterFamily, - fontWeight = FontWeight.SemiBold, - fontSize = 20.sp, - lineHeight = 24.sp, - letterSpacing = (-0.12).sp, - lineHeightStyle = LineHeightStyle( - alignment = LineHeightStyle.Alignment.Center, - trim = LineHeightStyle.Trim.None, - ), - lineBreak = LineBreak.Heading, - ) - - val fontBodyMedium = TextStyle( - fontFamily = InterFamily, - fontWeight = FontWeight.Medium, - fontSize = 16.sp, - lineHeight = 20.sp, - letterSpacing = 0.02.sp, - lineHeightStyle = LineHeightStyle( - alignment = LineHeightStyle.Alignment.Center, - trim = LineHeightStyle.Trim.None, - ), - ) - - val fontSubheadingMedium = TextStyle( - fontFamily = InterFamily, - fontWeight = FontWeight.Medium, - fontSize = 14.sp, - lineHeight = 17.sp, - letterSpacing = 0.07.sp, - lineHeightStyle = LineHeightStyle( - alignment = LineHeightStyle.Alignment.Center, - trim = LineHeightStyle.Trim.None, - ), - ) - - val fontCaptionMedium = TextStyle( - fontFamily = InterFamily, - fontWeight = FontWeight.Medium, - fontSize = 12.sp, - lineHeight = 16.sp, - letterSpacing = 0.18.sp, - lineHeightStyle = LineHeightStyle( - alignment = LineHeightStyle.Alignment.Center, - trim = LineHeightStyle.Trim.None, - ), - ) -} \ No newline at end of file diff --git a/core/ui/token-gen/README.md b/core/ui/token-gen/README.md new file mode 100644 index 0000000000..62736c8beb --- /dev/null +++ b/core/ui/token-gen/README.md @@ -0,0 +1,21 @@ +# token-gen + +Generates Kotlin (Jetpack Compose) source files from design tokens defined in the `ds-tokens` git submodule. + +## Updating tokens + +1. Update the `ds-tokens` submodule to the latest commit: + ```bash + git submodule update --remote core/ui/ds-tokens + ``` +2. Re-run the build: + ```bash + cd core/ui/token-gen && npm run build + ``` +3. Commit both the submodule pointer and generated files. + +## How it works + +The script uses [Style Dictionary v5](https://styledictionary.com/) with [@tokens-studio/sd-transforms](https://github.com/tokens-studio/sd-transforms) to read JSON token files from `core/ui/ds-tokens/tokens/` and generate Kotlin files into `core/ui/src/main/java/com/tangem/core/ui/res/generated/`. + +All generated files are written to `com.tangem.core.ui.res.generated` and should not be edited manually. diff --git a/core/ui/token-gen/build-tokens.mjs b/core/ui/token-gen/build-tokens.mjs index 0add5d002f..9d30c49158 100644 --- a/core/ui/token-gen/build-tokens.mjs +++ b/core/ui/token-gen/build-tokens.mjs @@ -149,47 +149,368 @@ function groupByPath(tokens, depth = 1) { return groups; } +/** + * Build a tree of nested objects from entries. + * Each entry: { path: string[], value: string }. + * The last segment is the property name; preceding segments become nested objects. + */ +function buildPropertyTree(entries) { + const root = { props: [], children: new Map() }; + + for (const { path, value } of entries) { + let node = root; + for (let i = 0; i < path.length - 1; i++) { + const seg = path[i]; + if (!node.children.has(seg)) { + node.children.set(seg, { props: [], children: new Map() }); + } + node = node.children.get(seg); + } + const propName = path[path.length - 1]; + const existingIdx = node.props.findIndex(p => p.name === propName); + if (existingIdx >= 0) { + console.warn(` ⚠ Duplicate property path: ${path.join('.')} — overwriting`); + node.props[existingIdx] = { name: propName, value }; + } else { + node.props.push({ name: propName, value }); + } + } + + return root; +} + +/** + * Render a property tree as Kotlin nested objects. + * Returns an array of indented lines. + */ +function renderTree(node, indent = 1) { + const pad = ' '.repeat(indent); + const lines = []; + + for (const { name, value } of node.props) { + lines.push(`${pad}val ${name} = ${value}`); + } + + for (const [name, child] of node.children) { + if (lines.length > 0) lines.push(''); + lines.push(`${pad}object ${name} {`); + lines.push(...renderTree(child, indent + 1)); + lines.push(`${pad}}`); + } + + return lines; +} + +/** + * Render a @Stable class tree for dimension tokens. + * Each node with children becomes a nested @Stable class. + * Props carry { default, type } values. + */ +function renderStableDimenClass(className, node, indent) { + const pad = ' '.repeat(indent); + const pad1 = ' '.repeat(indent + 1); + const lines = []; + + lines.push(`${pad}@Stable`); + lines.push(`${pad}class ${className} internal constructor(`); + + for (const { name, value } of node.props) { + lines.push(`${pad1}val ${kotlinSafe(name)}: ${value.type} = ${value.default},`); + } + for (const [childName, childNode] of node.children) { + const typeName = capitalize(childName); + const propName = kotlinSafe(childName.charAt(0).toLowerCase() + childName.slice(1)); + lines.push(`${pad1}val ${propName}: ${typeName} = ${typeName}(),`); + } + + if (node.children.size === 0) { + lines.push(`${pad})`); + } else { + lines.push(`${pad}) {`); + + let first = true; + for (const [childName, childNode] of node.children) { + if (!first) lines.push(''); + first = false; + lines.push(...renderStableDimenClass(capitalize(childName), childNode, indent + 1)); + } + + lines.push(`${pad}}`); + } + + return lines; +} + +/** + * Capitalize the first letter of a string. + */ +function capitalize(str) { + return str.charAt(0).toUpperCase() + str.slice(1); +} + +/** + * Convert a kebab-case segment to PascalCase (for object names). + */ +function toPascalCase(seg) { + return seg + .split('-') + .map(part => capitalize(part)) + .join(''); +} + +/** + * Wrap a name in backticks if it starts with a digit or is a Kotlin hard keyword. + */ +const KOTLIN_HARD_KEYWORDS = new Set([ + 'as', 'break', 'class', 'continue', 'do', 'else', 'false', 'for', 'fun', + 'if', 'in', 'interface', 'is', 'null', 'object', 'package', 'return', + 'super', 'this', 'throw', 'true', 'try', 'typealias', 'typeof', 'val', + 'var', 'when', 'while', +]); + +function kotlinSafe(name) { + if (/^\d/.test(name) || KOTLIN_HARD_KEYWORDS.has(name)) return `\`${name}\``; + return name; +} + +// ── TangemColors3 helpers ───────────────────────────────────────────────────── + +/** Shared structure tree for TangemColors3, computed from light theme codeSyntax. */ +let colors3StructureTree = null; + +/** Extract codeSyntax.Android path, stripping TangemTheme.colors3. prefix. */ +function getAndroidCodeSyntax(token) { + const ext = token.$extensions?.['com.figma.codeSyntax']; + if (!ext?.Android) return null; + const prefix = 'TangemTheme.colors3.'; + const android = ext.Android; + return android.startsWith(prefix) ? android.slice(prefix.length) : null; +} + +/** Get the token's property path for the class tree from codeSyntax, or fallback to JSON path. */ +function colorTokenClassPath(token) { + const cs = getAndroidCodeSyntax(token); + if (cs) return cs.split('.'); + // Fallback for material tokens (no codeSyntax): use JSON path minus 'color' prefix + const pathSegs = token.path.slice(1); // remove 'color' + return pathSegs.map(seg => seg.replace(/-([a-z0-9])/g, (_, c) => c.toUpperCase())); +} + +/** + * Extract a simple palette reference from a token's original (unresolved) value. + * Returns the reference string like "palette.neutral.95" or null for complex values. + */ +function extractPaletteRef(token) { + const original = token.original?.$value; + if (typeof original !== 'string') return null; + const match = original.match(/^\{(palette\.[^}]+)\}$/); + return match ? match[1] : null; +} + +/** + * Convert a palette reference path to Kotlin code referencing TangemColorPalette. + * e.g., "palette.neutral.95" → "TangemColorPalette.Neutral.`95`" + * e.g., "palette.opaque.base-black.60" → "TangemColorPalette.Opaque.BaseBlack.`60`" + */ +function paletteRefToKotlin(refPath) { + const parts = refPath.split('.').slice(1); // drop "palette" + const objParts = parts.slice(0, -1).map(seg => toPascalCase(seg)); + const leaf = parts[parts.length - 1].replace(/-([a-z0-9])/g, (_, c) => c.toUpperCase()); + return `TangemColorPalette.${objParts.join('.')}.${kotlinSafe(leaf)}`; +} + +/** Get Kotlin value for a color token: palette reference if simple, else resolved Color literal. */ +function paletteRefOrColor(token) { + const ref = extractPaletteRef(token); + if (ref) return paletteRefToKotlin(ref); + return toComposeColor(token.$value, token.path.join('.')); +} + +/** + * Build a class structure tree from color tokens using codeSyntax paths. + * Each node: { props: [{name, jsonPath}], children: Map } + */ +function buildClassStructureTree(tokens) { + const root = { props: [], children: new Map() }; + + for (const token of tokens) { + const classPath = colorTokenClassPath(token); + const jsonPath = token.path.join('.'); + + let node = root; + for (let i = 0; i < classPath.length - 1; i++) { + const seg = classPath[i]; + if (!node.children.has(seg)) { + node.children.set(seg, { props: [], children: new Map() }); + } + node = node.children.get(seg); + } + + const propName = classPath[classPath.length - 1]; + const existingIdx = node.props.findIndex(p => p.name === propName); + if (existingIdx >= 0) { + console.warn(` ⚠ Duplicate codeSyntax path: ${classPath.join('.')} (${jsonPath}) — overwriting`); + node.props[existingIdx] = { name: propName, jsonPath }; + } else { + node.props.push({ name: propName, jsonPath }); + } + } + + return root; +} + +/** + * Resolve conflicts where a name appears as both a leaf property and a child class. + * Resolution: move the leaf into the child as "default". + */ +function resolveClassTreeConflicts(node) { + const propsToRemove = []; + + for (const [childName, childNode] of node.children) { + const conflictIdx = node.props.findIndex(p => p.name === childName); + if (conflictIdx >= 0) { + const prop = node.props[conflictIdx]; + console.log(` ℹ Conflict resolved: "${childName}" is both property and class → moved to "${childName}.default"`); + childNode.props.unshift({ name: 'default', jsonPath: prop.jsonPath }); + propsToRemove.push(conflictIdx); + } + } + + for (const idx of propsToRemove.sort((a, b) => b - a)) { + node.props.splice(idx, 1); + } + + for (const child of node.children.values()) { + resolveClassTreeConflicts(child); + } +} + +/** + * Render a @Stable class with mutableStateOf pattern for Compose theme colors. + * Returns array of Kotlin source lines. + */ +function renderStableClass(className, node, indent = 0) { + const pad = ' '.repeat(indent); + const pad1 = ' '.repeat(indent + 1); + const pad2 = ' '.repeat(indent + 2); + const lines = []; + + lines.push(`${pad}@Stable`); + lines.push(`${pad}class ${className} internal constructor(`); + + for (const { name } of node.props) { + lines.push(`${pad1}${kotlinSafe(name)}: Color,`); + } + for (const [childName] of node.children) { + lines.push(`${pad1}val ${childName}: ${capitalize(childName)},`); + } + + lines.push(`${pad}) {`); + + // mutableStateOf delegates for leaf Color props + if (node.props.length > 0) { + for (const { name } of node.props) { + const safe = kotlinSafe(name); + lines.push(`${pad1}var ${safe} by mutableStateOf(${safe})`); + lines.push(`${pad2}private set`); + } + } + + // Nested child classes + for (const [childName, childNode] of node.children) { + lines.push(''); + lines.push(...renderStableClass(capitalize(childName), childNode, indent + 1)); + } + + // update() function + lines.push(''); + lines.push(`${pad1}fun update(other: ${className}) {`); + for (const { name } of node.props) { + const safe = kotlinSafe(name); + lines.push(`${pad2}${safe} = other.${safe}`); + } + for (const [childName] of node.children) { + lines.push(`${pad2}${childName}.update(other.${childName})`); + } + lines.push(`${pad1}}`); + + lines.push(`${pad}}`); + + return lines; +} + +/** + * Render the content lines of a factory constructor call (param assignments + child constructors). + */ +function renderFactoryContent(classPath, node, valueMap, indent) { + const pad = ' '.repeat(indent); + const lines = []; + + for (const { name, jsonPath } of node.props) { + const value = valueMap.get(jsonPath); + if (!value) console.warn(` ⚠ No value for jsonPath "${jsonPath}" (property: ${name})`); + lines.push(`${pad}${kotlinSafe(name)} = ${value || 'Color.Unspecified'},`); + } + + for (const [childName, childNode] of node.children) { + const childClassPath = `${classPath}.${capitalize(childName)}`; + lines.push(`${pad}${childName} = ${childClassPath}(`); + lines.push(...renderFactoryContent(childClassPath, childNode, valueMap, indent + 1)); + lines.push(`${pad}),`); + } + + return lines; +} + +const FILE_SUPPRESS = '@file:Suppress("all")'; + // ── Custom formats ───────────────────────────────────────────────────────────── /** - * Kotlin format for color tokens. - * Generates: class TangemLightColorTokens / TangemDarkColorTokens + * Kotlin format for palette tokens. + * Generates nested objects: object Base { val black = ... }, object Neutral { val `5` = ... }, etc. */ StyleDictionary.registerFormat({ - name: 'kotlin/compose-colors', - format: ({ dictionary, options }) => { - const themeName = options.themeName; // "Light" or "Dark" - const objectName = `Tangem${themeName}ColorTokens`; - - const colorTokens = dictionary.allTokens.filter( - t => t.$type === 'color' && !isSourceOnlyToken(t), + name: 'kotlin/compose-palette', + format: ({ dictionary }) => { + const paletteTokens = dictionary.allTokens.filter( + t => t.$type === 'color' && t.path[0] === 'palette', ); - // Group by top-level category (color.text, color.bg, color.icon, etc.) - const groups = groupByPath(colorTokens, 2); - const sections = []; - - for (const [groupKey, tokens] of Object.entries(groups)) { - const comment = ` // ${groupKey}`; - const props = tokens.map(token => { - const propName = toCamelCase(token.path); - const value = toComposeColor(token.$value, token.path.join('.')); - return ` val ${propName} = ${value}`; + const entries = paletteTokens.map(token => { + // palette.base.black → ["Base", "black"] + // palette.neutral.5 → ["Neutral", "`5`"] + // palette.opaque.base-black.5 → ["Opaque", "BaseBlack", "`5`"] + const segments = token.path.slice(1); // drop "palette" + const path = segments.map((seg, i) => { + if (i < segments.length - 1) { + // intermediate segments → PascalCase object names + return toPascalCase(seg); + } + // leaf segment → property name, backtick-wrap if starts with digit + const camel = seg.replace(/-([a-z0-9])/g, (_, c) => c.toUpperCase()); + return kotlinSafe(camel); }); - sections.push([comment, ...props].join('\n')); - } + + const value = toComposeColor(token.$value, token.path.join('.')); + return { path, value }; + }); + + const tree = buildPropertyTree(entries); + const body = renderTree(tree); return [ + FILE_SUPPRESS, + '', `package ${PACKAGE}`, '', 'import androidx.compose.ui.graphics.Color', '', '/**', - ` * Auto-generated from design tokens. Do not edit manually.`, - ` * Theme: ${themeName}`, + ' * Auto-generated from design tokens. Do not edit manually.', ' */', - `internal class ${objectName} {`, - sections.join('\n\n'), + 'internal object TangemColorPalette {', + body.join('\n'), '}', '', ].join('\n'); @@ -197,148 +518,150 @@ StyleDictionary.registerFormat({ }); /** - * Kotlin format for dimension tokens (spacing, size, border-radius, border-width). + * Kotlin format for TangemDimens3 — structured dimension tokens as @Immutable data class. + * Generates nested @Immutable data classes from codeSyntax.Android paths (prefix: TangemTheme.dimens3.). + * Includes spacing, size, borderRadius, borderWidth, blur, and semantic opacity tokens. */ StyleDictionary.registerFormat({ - name: 'kotlin/compose-dimensions', + name: 'kotlin/compose-dimens3', format: ({ dictionary }) => { - const dimTokens = dictionary.allTokens.filter(t => { - // Exclude font tokens — letter-spacing values are already in typography tokens as sp - if (t.path[0] === 'font') return false; - return ( - (t.$type === 'dimension' || t.$type === 'borderRadius' || t.$type === 'borderWidth') && - !isSourceOnlyToken(t) - ); - }); + const prefix = 'TangemTheme.dimens3.'; - const groups = groupByPath(dimTokens, 1); - const sections = []; + const entries = []; + for (const token of dictionary.allTokens) { + const ext = token.$extensions?.['com.figma.codeSyntax']; + if (!ext?.Android?.startsWith(prefix)) continue; - for (const [groupKey, tokens] of Object.entries(groups)) { - const comment = ` // ${groupKey}`; - const props = tokens.map(token => { - const propName = toCamelCase(token.path); - // Resolved value is in px (number), convert to dp - const raw = parseFloat(token.$value); - if (isNaN(raw)) throw new Error(`Non-numeric dimension value for ${token.path.join('.')}: "${token.$value}"`); - const dpVal = `${raw}.dp`; - return ` val ${propName} = ${dpVal}`; - }); - sections.push([comment, ...props].join('\n')); + const codePath = ext.Android.slice(prefix.length); + const segPath = codePath.split('.'); + + const raw = parseFloat(token.$value); + const tp = token.path.join('.'); + if (isNaN(raw)) throw new Error(`Non-numeric value for ${tp}: "${token.$value}"`); + + // Opacity tokens → Float, all others → Dp + const isOpacity = token.$type === 'opacity'; + const value = isOpacity ? `${raw}f` : `${raw}.dp`; + const type = isOpacity ? 'Float' : 'Dp'; + + entries.push({ path: segPath, value, type }); } + const tree = buildPropertyTree(entries.map(e => ({ + path: e.path, + value: { default: e.value, type: e.type }, + }))); + + const classLines = renderStableDimenClass('TangemDimens3', tree, 0); + return [ + FILE_SUPPRESS, + '', `package ${PACKAGE}`, '', + 'import androidx.compose.runtime.Stable', + 'import androidx.compose.ui.unit.Dp', 'import androidx.compose.ui.unit.dp', '', '/**', ' * Auto-generated from design tokens. Do not edit manually.', ' */', - 'internal class TangemDimensionTokens {', - sections.join('\n\n'), - '}', + ...classLines, '', ].join('\n'); }, }); /** - * Kotlin format for opacity tokens. + * Kotlin format for TangemTypography3 — @Stable class with nested categories. + * Generates a class taking FontFamily, with nested classes for each typography category + * (display, heading, body, subheading, caption). */ StyleDictionary.registerFormat({ - name: 'kotlin/compose-opacity', - format: ({ dictionary }) => { - const opacityTokens = dictionary.allTokens.filter( - t => t.$type === 'opacity' && !isSourceOnlyToken(t), - ); - - const props = opacityTokens.map(token => { - const propName = toCamelCase(token.path); - const raw = parseFloat(token.$value); - if (isNaN(raw)) throw new Error(`Non-numeric opacity value for ${token.path.join('.')}: "${token.$value}"`); - const floatVal = `${raw}f`; - return ` val ${propName} = ${floatVal}`; - }); - - return [ - `package ${PACKAGE}`, - '', - '/**', - ' * Auto-generated from design tokens. Do not edit manually.', - ' */', - 'internal class TangemOpacityTokens {', - ...props, - '}', - '', - ].join('\n'); - }, -}); - -/** - * Kotlin format for typography tokens. - */ -StyleDictionary.registerFormat({ - name: 'kotlin/compose-typography', + name: 'kotlin/compose-typography3', format: ({ dictionary }) => { const typoTokens = dictionary.allTokens.filter( t => t.$type === 'typography' && !isSourceOnlyToken(t), ); - const props = typoTokens.map(token => { - const propName = toCamelCase(token.path); - const v = token.$value; + // Group by category (path[1]: display, heading, body, subheading, caption) + const categories = new Map(); + for (const token of typoTokens) { + const category = token.path[1]; + if (!categories.has(category)) categories.set(category, []); + categories.get(category).push(token); + } - // v is an object: { fontFamily, fontWeight, fontSize, lineHeight, letterSpacing, ... } - const tp = token.path.join('.'); - if (!v.fontWeight) throw new Error(`Missing fontWeight for ${tp}`); - const fontWeight = mapFontWeight(v.fontWeight); - const fontSize = parseFloat(v.fontSize); - if (isNaN(fontSize)) throw new Error(`Non-numeric fontSize for ${tp}: "${v.fontSize}"`); - const lineHeight = parseFloat(v.lineHeight); - if (isNaN(lineHeight)) throw new Error(`Non-numeric lineHeight for ${tp}: "${v.lineHeight}"`); - const letterSpacing = parseFloat(v.letterSpacing); - if (isNaN(letterSpacing)) throw new Error(`Non-numeric letterSpacing for ${tp}: "${v.letterSpacing}"`); + // Build nested class lines + const outerProps = []; + const innerClasses = []; - // display and heading categories get LineBreak.Heading (matches TangemTypography2) - const category = token.path[1]; // display, heading, body, subheading, caption + for (const [category, tokens] of categories) { + const className = capitalize(category); + const propName = category; const isHeading = category === 'display' || category === 'heading'; - const lines = [ - ` val ${propName} = TextStyle(`, - ` fontFamily = InterFamily,`, - ` fontWeight = ${fontWeight},`, - ` fontSize = ${fontSize}.sp,`, - ` lineHeight = ${lineHeight}.sp,`, - ` letterSpacing = ${letterSpacing < 0 ? `(${letterSpacing})` : letterSpacing}.sp,`, - ` lineHeightStyle = LineHeightStyle(`, - ` alignment = LineHeightStyle.Alignment.Center,`, - ` trim = LineHeightStyle.Trim.None,`, - ` ),`, - ]; - if (isHeading) { - lines.push(` lineBreak = LineBreak.Heading,`); - } - lines.push(` )`); + outerProps.push(` val ${propName}: ${className} = ${className}(fontFamily)`); - return lines.join('\n'); - }); + const classLines = [` @Stable`, ` class ${className} internal constructor(fontFamily: FontFamily) {`]; + + for (const token of tokens) { + const size = token.path[2]; // medium, small, etc. + const v = token.$value; + const tp = token.path.join('.'); + + if (!v.fontWeight) throw new Error(`Missing fontWeight for ${tp}`); + const fontWeight = mapFontWeight(v.fontWeight); + const fontSize = parseFloat(v.fontSize); + if (isNaN(fontSize)) throw new Error(`Non-numeric fontSize for ${tp}: "${v.fontSize}"`); + const lineHeight = parseFloat(v.lineHeight); + if (isNaN(lineHeight)) throw new Error(`Non-numeric lineHeight for ${tp}: "${v.lineHeight}"`); + const letterSpacing = parseFloat(v.letterSpacing); + if (isNaN(letterSpacing)) throw new Error(`Non-numeric letterSpacing for ${tp}: "${v.letterSpacing}"`); + + const spacingLiteral = letterSpacing < 0 ? `(${letterSpacing})` : `${letterSpacing}`; + + classLines.push(` val ${size}: TextStyle = TextStyle(`); + classLines.push(` fontFamily = fontFamily,`); + classLines.push(` fontWeight = ${fontWeight},`); + classLines.push(` fontSize = ${fontSize}.sp,`); + classLines.push(` lineHeight = ${lineHeight}.sp,`); + classLines.push(` letterSpacing = ${spacingLiteral}.sp,`); + classLines.push(` lineHeightStyle = LineHeightStyle(`); + classLines.push(` alignment = LineHeightStyle.Alignment.Center,`); + classLines.push(` trim = LineHeightStyle.Trim.None,`); + classLines.push(` ),`); + if (isHeading) { + classLines.push(` lineBreak = LineBreak.Heading,`); + } + classLines.push(` )`); + } + + classLines.push(` }`); + innerClasses.push(classLines.join('\n')); + } return [ + FILE_SUPPRESS, + '', `package ${PACKAGE}`, '', + 'import androidx.compose.runtime.Stable', 'import androidx.compose.ui.text.TextStyle', + 'import androidx.compose.ui.text.font.FontFamily', 'import androidx.compose.ui.text.font.FontWeight', 'import androidx.compose.ui.text.style.LineBreak', 'import androidx.compose.ui.text.style.LineHeightStyle', 'import androidx.compose.ui.unit.sp', - 'import com.tangem.core.ui.res.InterFamily', '', '/**', ' * Auto-generated from design tokens. Do not edit manually.', ' */', - 'internal class TangemTypographyTokens {', - props.join('\n\n'), + '@Stable', + 'class TangemTypography3 internal constructor(fontFamily: FontFamily) {', + outerProps.join('\n'), + '', + innerClasses.join('\n\n'), '}', '', ].join('\n'); @@ -361,54 +684,88 @@ function mapFontWeight(value) { } /** - * Kotlin format for shadow tokens. - * Shadow tokens are composite (type: shadow) with object $value containing - * blur, spread, color, offsetX, offsetY, type. + * Kotlin format for TangemColors3 class definition. + * Generates the @Stable class hierarchy with mutableStateOf + update() pattern. + * Structure is derived from light theme codeSyntax.Android fields. */ StyleDictionary.registerFormat({ - name: 'kotlin/compose-shadows', + name: 'kotlin/compose-colors3-class', format: ({ dictionary }) => { - const shadowTokens = dictionary.allTokens.filter( - t => (t.$type === 'shadow' || t.$type === 'boxShadow') && !isSourceOnlyToken(t), + const colorTokens = dictionary.allTokens.filter( + t => t.$type === 'color' && !isSourceOnlyToken(t), ); - const entries = shadowTokens.map(token => { - const propName = toCamelCase(token.path); - const v = token.$value; - const sp = token.path.join('.'); - if (!v) throw new Error(`Missing shadow value for ${sp}`); - const blur = parseFloat(v.blur); - if (isNaN(blur)) throw new Error(`Non-numeric blur for ${sp}: "${v.blur}"`); - const spread = parseFloat(v.spread); - if (isNaN(spread)) throw new Error(`Non-numeric spread for ${sp}: "${v.spread}"`); - const offsetX = parseFloat(v.offsetX); - if (isNaN(offsetX)) throw new Error(`Non-numeric offsetX for ${sp}: "${v.offsetX}"`); - const offsetY = parseFloat(v.offsetY); - if (isNaN(offsetY)) throw new Error(`Non-numeric offsetY for ${sp}: "${v.offsetY}"`); - if (!v.color) throw new Error(`Missing color for ${sp}`); - const colorVal = toComposeColor(v.color, sp + '.color'); + colors3StructureTree = buildClassStructureTree(colorTokens); + resolveClassTreeConflicts(colors3StructureTree); - return [ - ` val ${propName}Blur = ${blur}.dp`, - ` val ${propName}OffsetX = ${offsetX}.dp`, - ` val ${propName}OffsetY = ${offsetY}.dp`, - ` val ${propName}Spread = ${spread}.dp`, - ` val ${propName}Color = ${colorVal}`, - ].join('\n'); - }); + const classLines = renderStableClass('TangemColors3', colors3StructureTree, 0); return [ + FILE_SUPPRESS, + '', `package ${PACKAGE}`, '', + 'import androidx.compose.runtime.Stable', + 'import androidx.compose.runtime.getValue', + 'import androidx.compose.runtime.mutableStateOf', + 'import androidx.compose.runtime.setValue', 'import androidx.compose.ui.graphics.Color', - 'import androidx.compose.ui.unit.dp', '', '/**', ' * Auto-generated from design tokens. Do not edit manually.', ' */', - 'internal class TangemShadowTokens {', - entries.join('\n\n'), - '}', + ...classLines, + '', + ].join('\n'); + }, +}); + +/** + * Kotlin format for TangemColors3 light/dark factory functions. + * Generates lightColors3() / darkColors3() functions referencing TangemColorPalette. + */ +StyleDictionary.registerFormat({ + name: 'kotlin/compose-colors3-factory', + format: ({ dictionary, options }) => { + const themeName = options.themeName; + const funcName = `${themeName.toLowerCase()}Colors3`; + + // Ensure tree is built (should already be set by class format in Light build) + if (!colors3StructureTree) { + const colorTokens = dictionary.allTokens.filter( + t => t.$type === 'color' && !isSourceOnlyToken(t), + ); + colors3StructureTree = buildClassStructureTree(colorTokens); + resolveClassTreeConflicts(colors3StructureTree); + } + + // Build value map: jsonPath → Kotlin palette reference or Color literal + const colorTokens = dictionary.allTokens.filter( + t => t.$type === 'color' && !isSourceOnlyToken(t), + ); + const valueMap = new Map(); + for (const token of colorTokens) { + const jsonPath = token.path.join('.'); + valueMap.set(jsonPath, paletteRefOrColor(token)); + } + + const bodyLines = renderFactoryContent('TangemColors3', colors3StructureTree, valueMap, 2); + + return [ + FILE_SUPPRESS, + '', + `package ${PACKAGE}`, + '', + 'import androidx.compose.ui.graphics.Color', + '', + '/**', + ` * Auto-generated from design tokens. Do not edit manually.`, + ` * Theme: ${themeName}`, + ' */', + `internal fun ${funcName}() =`, + ' TangemColors3(', + ...bodyLines, + ' )', '', ].join('\n'); }, @@ -425,6 +782,27 @@ const composePlatformTransforms = [ for (const [themeName, { sets }] of Object.entries(themeBuilds)) { console.log(`\nBuilding ${themeName} color tokens...`); + const colorFilter = token => token.$type === 'color' && !isSourceOnlyToken(token); + + const files = []; + + // Only light build generates the class definition (canonical codeSyntax structure) + if (themeName === 'Light') { + files.push({ + destination: 'TangemColors3.kt', + format: 'kotlin/compose-colors3-class', + filter: colorFilter, + }); + } + + // Both themes generate factory functions + files.push({ + destination: `TangemColors3${themeName}.kt`, + format: 'kotlin/compose-colors3-factory', + options: { themeName }, + filter: colorFilter, + }); + const sd = new StyleDictionary({ source: sets.map(s => path.join(tokensDir, `${s}.json`)), preprocessors: ['tokens-studio'], @@ -434,24 +812,44 @@ for (const [themeName, { sets }] of Object.entries(themeBuilds)) { compose: { transforms: composePlatformTransforms, buildPath: outputDir + '/', - files: [ - { - destination: `Tangem${themeName}ColorTokens.kt`, - format: 'kotlin/compose-colors', - options: { themeName }, - filter: token => token.$type === 'color' && !isSourceOnlyToken(token), - }, - ], + files, }, }, }); await sd.buildAllPlatforms(); - console.log(` ✓ Tangem${themeName}ColorTokens.kt`); + if (themeName === 'Light') console.log(' ✓ TangemColors3.kt'); + console.log(` ✓ TangemColors3${themeName}.kt`); } -// Build theme-independent tokens (dimensions, opacity, typography, shadows) -console.log('\nBuilding dimension, opacity, typography, and shadow tokens...'); +// Build palette tokens +console.log('\nBuilding palette tokens...'); + +const paletteSd = new StyleDictionary({ + source: [...coreSets, 'semantic/size/opacity'].map(s => path.join(tokensDir, `${s}.json`)), + preprocessors: ['tokens-studio'], + usesDtcg: true, + log: { warnings: 'disabled', errors: { brokenReferences: 'console' } }, + platforms: { + compose: { + transforms: composePlatformTransforms, + buildPath: outputDir + '/', + files: [ + { + destination: 'TangemColorPalette.kt', + format: 'kotlin/compose-palette', + filter: token => token.$type === 'color' && token.path[0] === 'palette', + }, + ], + }, + }, +}); + +await paletteSd.buildAllPlatforms(); +console.log(' ✓ TangemColorPalette.kt'); + +// Build theme-independent tokens (dimensions, typography) +console.log('\nBuilding dimension and typography tokens...'); const sd = new StyleDictionary({ source: sharedBuildSets.map(s => path.join(tokensDir, `${s}.json`)), @@ -464,42 +862,22 @@ const sd = new StyleDictionary({ buildPath: outputDir + '/', files: [ { - destination: 'TangemDimensionTokens.kt', - format: 'kotlin/compose-dimensions', - filter: token => { - const t = token.$type; - return ( - token.path[0] !== 'font' && - (t === 'dimension' || t === 'borderRadius' || t === 'borderWidth') && - !isSourceOnlyToken(token) - ); - }, + destination: 'TangemDimens3.kt', + format: 'kotlin/compose-dimens3', }, { - destination: 'TangemOpacityTokens.kt', - format: 'kotlin/compose-opacity', - filter: token => token.$type === 'opacity' && !isSourceOnlyToken(token), - }, - { - destination: 'TangemTypographyTokens.kt', - format: 'kotlin/compose-typography', + destination: 'TangemTypography3.kt', + format: 'kotlin/compose-typography3', filter: token => token.$type === 'typography' && !isSourceOnlyToken(token), }, - { - destination: 'TangemShadowTokens.kt', - format: 'kotlin/compose-shadows', - filter: token => (token.$type === 'shadow' || token.$type === 'boxShadow') && !isSourceOnlyToken(token), - }, ], }, }, }); await sd.buildAllPlatforms(); -console.log(' ✓ TangemDimensionTokens.kt'); -console.log(' ✓ TangemOpacityTokens.kt'); -console.log(' ✓ TangemTypographyTokens.kt'); -console.log(' ✓ TangemShadowTokens.kt'); +console.log(' ✓ TangemDimens3.kt'); +console.log(' ✓ TangemTypography3.kt'); // ── Write source hash ───────────────────────────────────────────────────────── // Hash all token JSON files so Gradle can verify generated code matches ds-tokens. @@ -513,7 +891,12 @@ function computeTokensHash() { } } walk(tokensDir); - files.sort(); // deterministic order + // Sort by relative path with forward slashes to match Gradle's invariantSeparatorsPath sorting + files.sort((a, b) => { + const ra = path.relative(tokensDir, a).split(path.sep).join('/'); + const rb = path.relative(tokensDir, b).split(path.sep).join('/'); + return ra.localeCompare(rb); + }); const hash = crypto.createHash('sha256'); for (const file of files) {