Skip to content

Commit

Permalink
Merge pull request #59 from hhru/MOB-35767_fix_color_generation
Browse files Browse the repository at this point in the history
Mob 35767 fix color generation
  • Loading branch information
avShabaeva authored Jan 17, 2024
2 parents c75e128 + 1db8052 commit a9e8943
Show file tree
Hide file tree
Showing 10 changed files with 190 additions and 63 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ final class DefaultBaseColorTokensGenerator: BaseColorTokensGenerator {

return BaseColorToken(
path: token.name.components(separatedBy: "."),
value: try tokensResolver.resolveHexColorValue(value, tokenValues: tokenValues)
value: try tokensResolver.resolveHexColorValue(value, tokenValues: tokenValues, theme: .undefined)
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ final class DefaultFontFamilyTokensGenerator: FontFamilyTokensGenerator {

return FontFamilyToken(
path: tokenValue.name.components(separatedBy: "."),
value: try tokensResolver.resolveValue(value, tokenValues: tokenValues)
value: try tokensResolver.resolveValue(value, tokenValues: tokenValues, theme: .undefined)
)
}
}
Expand All @@ -37,7 +37,7 @@ final class DefaultFontFamilyTokensGenerator: FontFamilyTokensGenerator {

return FontWeightToken(
path: tokenValue.name.components(separatedBy: "."),
value: try tokensResolver.resolveValue(value, tokenValues: tokenValues)
value: try tokensResolver.resolveValue(value, tokenValues: tokenValues, theme: .undefined)
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ final class DefaultSpacingTokensGenerator: SpacingTokensGenerator {

return SpacingToken(
path: token.name.components(separatedBy: "."),
value: try tokensResolver.resolveValue(value, tokenValues: tokenValues)
value: try tokensResolver.resolveValue(value, tokenValues: tokenValues, theme: .undefined)
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,11 @@ final class DefaultTypographyTokensGenerator: TypographyTokensGenerator {
tokenValues: TokenValues
) throws -> TypographyToken.LineHeightToken {
let lineHeightValue = value.lineHeight
let lineHeightResolvedValue = try tokensResolver.resolveValue(lineHeightValue, tokenValues: tokenValues)
let lineHeightResolvedValue = try tokensResolver.resolveValue(
lineHeightValue,
tokenValues: tokenValues,
theme: .undefined
)

guard lineHeightResolvedValue.hasSuffix("%") else {
return TypographyToken.LineHeightToken(
Expand All @@ -30,7 +34,9 @@ final class DefaultTypographyTokensGenerator: TypographyTokensGenerator {
)
}

let fontSize = Double(try tokensResolver.resolveValue(value.fontSize, tokenValues: tokenValues))
let fontSize = Double(
try tokensResolver.resolveValue(value.fontSize, tokenValues: tokenValues, theme: .undefined)
)
let lineHeight = Double(lineHeightResolvedValue.dropLast()).map { $0 / 100.0 }

guard let fontSize else {
Expand Down Expand Up @@ -59,12 +65,12 @@ final class DefaultTypographyTokensGenerator: TypographyTokensGenerator {

let letterSpacing = Double(
try tokensResolver
.resolveValue(letterSpacingValue, tokenValues: tokenValues)
.resolveValue(letterSpacingValue, tokenValues: tokenValues, theme: .undefined)
.removingFirst("%")
).map { $0 / 100.0 }

let fontSize = Double(
try tokensResolver.resolveValue(value.fontSize, tokenValues: tokenValues)
try tokensResolver.resolveValue(value.fontSize, tokenValues: tokenValues, theme: .undefined)
)

guard let fontSize else {
Expand All @@ -86,7 +92,7 @@ final class DefaultTypographyTokensGenerator: TypographyTokensGenerator {
private func makeContextToken(value: String, tokenValues: TokenValues) throws -> ContextToken {
ContextToken(
path: value.components(separatedBy: "."),
value: try tokensResolver.resolveValue(value, tokenValues: tokenValues)
value: try tokensResolver.resolveValue(value, tokenValues: tokenValues, theme: .undefined)
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,18 @@ final class DefaultColorTokensContextProvider: ColorTokensContextProvider {
return fallbackValue
}

return try tokensResolver.resolveHexColorValue(nightValue, tokenValues: tokenValues)
return try tokensResolver.resolveHexColorValue(
nightValue,
tokenValues: tokenValues,
theme: .night
)
}

private func resolveNightReference(
tokenName: String,
fallbackRefence: String,
tokenValues: TokenValues
) -> String {
) throws -> String {
guard let nightToken = tokenValues.night.first(where: { $0.name == tokenName }) else {
fallbackWarning(tokenName: tokenName)
return fallbackRefence
Expand All @@ -51,7 +55,7 @@ final class DefaultColorTokensContextProvider: ColorTokensContextProvider {
return fallbackRefence
}

return nightValue
return try tokensResolver.resolveBaseReference(nightValue, tokenValues: tokenValues.night)
}

private func makeColorToken(
Expand All @@ -62,26 +66,30 @@ final class DefaultColorTokensContextProvider: ColorTokensContextProvider {
) throws -> ColorToken {
let dayHexColorValue = try tokensResolver.resolveHexColorValue(
dayValue,
tokenValues: tokenValues,
theme: .day
)

let dayReference = try tokensResolver.resolveBaseReference(
dayValue,
tokenValues: tokenValues.day
)

let nightReference = try resolveNightReference(
tokenName: tokenName,
fallbackRefence: dayValue,
tokenValues: tokenValues
)

let nightHexColorValue = try resolveNightValue(
tokenName: tokenName,
fallbackValue: dayHexColorValue,
tokenValues: tokenValues
)

return ColorToken(
dayTheme: ColorToken.Theme(
value: dayHexColorValue,
reference: dayValue
),
nightTheme: ColorToken.Theme(
value: try resolveNightValue(
tokenName: tokenName,
fallbackValue: dayHexColorValue,
tokenValues: tokenValues
),
reference: resolveNightReference(
tokenName: tokenName,
fallbackRefence: dayValue,
tokenValues: tokenValues
)
),
dayTheme: ColorToken.Theme(value: dayHexColorValue, reference: dayReference),
nightTheme: ColorToken.Theme(value: nightHexColorValue, reference: nightReference),
name: tokenName,
path: path
)
Expand Down Expand Up @@ -131,7 +139,7 @@ final class DefaultColorTokensContextProvider: ColorTokensContextProvider {

let path = token.name.components(separatedBy: ".")

guard path[0] != "gradient" else {
guard path[0] != "gradient" && !dayValue.contains("gradient") else {
return nil
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,18 +60,18 @@ final class DefaultTokensResolver: TokensResolver {
}
}

private func resolveColorValue(_ value: String, tokenValues: TokenValues) throws -> Color {
private func resolveColorValue(_ value: String, tokenValues: TokenValues, theme: Theme) throws -> Color {
if value.hasPrefix("rgba") {
return try resolveRGBAColorValue(value, tokenValues: tokenValues)
return try resolveRGBAColorValue(value, tokenValues: tokenValues, theme: theme)
}

return try makeColor(hex: value, alpha: 1.0)
}

// MARK: - TokensResolver

func resolveValue(_ value: String, tokenValues: TokenValues) throws -> String {
let allTokens = tokenValues.all
func resolveValue(_ value: String, tokenValues: TokenValues, theme: Theme) throws -> String {
let themeTokens = tokenValues.getThemeTokenValues(theme: theme)

let resolvedValue = try value.replacingOccurrences(matchingPattern: #"\{.*?\}"#) { referenceName in
let referenceName = String(
Expand All @@ -80,22 +80,50 @@ final class DefaultTokensResolver: TokensResolver {
.dropLast()
)

guard let token = allTokens.first(where: { $0.name == referenceName }) else {
guard let token = themeTokens.first(where: { $0.name == referenceName }) else {
throw TokensGeneratorError(code: .referenceNotFound(name: referenceName))
}

guard let value = token.type.stringValue else {
throw TokensGeneratorError(code: .unexpectedTokenValueType(name: referenceName))
}

return try resolveValue(value, tokenValues: tokenValues)
return try resolveValue(value, tokenValues: tokenValues, theme: theme)
}

return evaluteValue(resolvedValue)
}

func resolveRGBAColorValue(_ value: String, tokenValues: TokenValues) throws -> Color {
let components = try resolveValue(value, tokenValues: tokenValues)
func resolveBaseReference(_ reference: String, tokenValues: [TokenValue]) throws -> String {
try reference.replacingOccurrences(matchingPattern: #"\{.*?\}"#) { referenceName in
if referenceName.contains("color.base") {
return referenceName
}

guard referenceName.contains("color.") else {
return referenceName
}

let referenceName = String(
referenceName
.dropFirst()
.dropLast()
)

guard let token = tokenValues.first(where: { $0.name == referenceName }) else {
throw TokensGeneratorError(code: .referenceNotFound(name: referenceName))
}

guard let value = token.type.stringValue else {
throw TokensGeneratorError(code: .unexpectedTokenValueType(name: referenceName))
}

return try resolveBaseReference(value, tokenValues: tokenValues)
}
}

func resolveRGBAColorValue(_ value: String, tokenValues: TokenValues, theme: Theme) throws -> Color {
let components = try resolveValue(value, tokenValues: tokenValues, theme: theme)
.slice(from: "(", to: ")", includingBounds: false)?
.components(separatedBy: ", ")

Expand All @@ -113,18 +141,17 @@ final class DefaultTokensResolver: TokensResolver {
return try makeColor(hex: hex, alpha: alpha / 100.0)
}

func resolveHexColorValue(_ value: String, tokenValues: TokenValues) throws -> String {
let resolvedValue = try resolveValue(value, tokenValues: tokenValues)
func resolveHexColorValue(_ value: String, tokenValues: TokenValues, theme: Theme) throws -> String {
let resolvedValue = try resolveValue(value, tokenValues: tokenValues, theme: theme)

if resolvedValue.hasPrefix("#") {
return resolvedValue
}

return try resolveColorValue(resolvedValue, tokenValues: tokenValues).hexString
return try resolveColorValue(resolvedValue, tokenValues: tokenValues, theme: theme).hexString
}

func resolveLinearGradientValue(_ value: String, tokenValues: TokenValues) throws -> LinearGradient {
let value = try resolveValue(value, tokenValues: tokenValues)
func resolveLinearGradientValue(_ value: String, tokenValues: TokenValues, theme: Theme) throws -> LinearGradient {
let value = try resolveValue(value, tokenValues: tokenValues, theme: theme)

guard let startFunctionIndex = value.firstIndex(of: "("), let endFunctionIndex = value.lastIndex(of: ")") else {
throw TokensGeneratorError(code: .failedToExtractLinearGradientParams(linearGradient: value))
Expand All @@ -148,7 +175,7 @@ final class DefaultTokensResolver: TokensResolver {

let percentage = String(rawColorStop[separatorRange.upperBound...])
let rawColor = String(rawColorStop[...separatorRange.lowerBound])
let color = try resolveColorValue(rawColor, tokenValues: tokenValues)
let color = try resolveColorValue(rawColor, tokenValues: tokenValues, theme: theme)

return LinearGradient.LinearColorStop(color: color, percentage: percentage)
}
Expand Down
24 changes: 20 additions & 4 deletions Sources/FigmaGen/Generators/Tokens/Resolver/TokensResolver.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,21 @@ protocol TokensResolver {
/// - Parameters:
/// - value: String value to resolve
/// - tokenValues: All token values
/// - theme: Theme
/// - Returns: Resolved value.
func resolveValue(_ value: String, tokenValues: TokenValues) throws -> String
func resolveValue(_ value: String, tokenValues: TokenValues, theme: Theme) throws -> String

/// Resolving `reference` from `tokenValues`.
///
/// Example: If reference `{color.background.primary}` and `tokenValues` has token with name
/// `color.background.primary` and reference `{color.base.black}`,
/// the function will return `{color.base.black}` else `{color.background.primary}`.
///
/// - Parameters:
/// - reference: String reference to resolve
/// - tokenValues: Tokens to search reference. Use theme specific tokens.
/// - Returns: Resolved value.
func resolveBaseReference(_ reference: String, tokenValues: [TokenValue]) throws -> String

/// Resolving references and mathematical expressions in `value` using ``resolveValue(_:tokenValues:)``
/// and convert `rgba()` to ``Color`` object
Expand All @@ -34,8 +47,9 @@ protocol TokensResolver {
/// )
/// ```
/// - tokenValues: All token values
/// - theme: Theme
/// - Returns: ``Color`` object with values resolved from `rgba()`
func resolveRGBAColorValue(_ value: String, tokenValues: TokenValues) throws -> Color
func resolveRGBAColorValue(_ value: String, tokenValues: TokenValues, theme: Theme) throws -> Color

/// Resolving references and mathematical expressions in `value` using ``resolveValue(_:tokenValues:)``
/// and convert `rgba()` to hex value
Expand All @@ -52,8 +66,9 @@ protocol TokensResolver {
/// ```
/// Or simple reference to another color: `{color.base.white}`
/// - tokenValues: All token values
/// - theme: Theme
/// - Returns: Hex value of the color
func resolveHexColorValue(_ value: String, tokenValues: TokenValues) throws -> String
func resolveHexColorValue(_ value: String, tokenValues: TokenValues, theme: Theme) throws -> String

/// Resolving references and mathematical expressions in `value` using ``resolveValue(_:tokenValues:)``
/// and convert `linear-gradient()` to ``LinearGradient`` object
Expand All @@ -73,6 +88,7 @@ protocol TokensResolver {
/// )
/// ```
/// - tokenValues: All token values
/// - theme: Theme
/// - Returns: ``LinearGradient`` object with values resolved from `linear-gradient()`
func resolveLinearGradientValue(_ value: String, tokenValues: TokenValues) throws -> LinearGradient
func resolveLinearGradientValue(_ value: String, tokenValues: TokenValues, theme: Theme) throws -> LinearGradient
}
7 changes: 7 additions & 0 deletions Sources/FigmaGen/Models/Token/Theme.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import Foundation

enum Theme: Codable {
case day
case night
case undefined
}
15 changes: 13 additions & 2 deletions Sources/FigmaGen/Models/Token/TokenValues.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,18 @@ struct TokenValues: Codable, Hashable {

// MARK: - Instance Properties

var all: [TokenValue] {
[core, semantic, colors, typography, day, night].flatMap { $0 }
/// Возвращает набор токенов для определенной темы.
/// Для undefined возвращается полный набор токенов. Нужен для Spacer, Font и других независимых от темы параметров.
func getThemeTokenValues(theme: Theme) -> [TokenValue] {
switch theme {
case .day:
return [day, core, semantic, colors, typography].flatMap { $0 }

case .night:
return [night, core, semantic, colors, typography].flatMap { $0 }

case .undefined:
return [core, semantic, colors, typography, day, night].flatMap { $0 }
}
}
}
Loading

0 comments on commit a9e8943

Please sign in to comment.