Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions Sources/FigmaGen/Commands/TokensCommand.swift
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,30 @@ final class TokensCommand: AsyncExecutableCommand {
"""
)

let gradientTemplate = Key<String>(
"--gradient-template",
description: """
Path to the template file.
If no template is passed a default template will be used.
"""
)

let gradientTemplateOptions = VariadicKey<String>(
"--gradient-options",
description: """
An option that will be merged with template context, and overwrite any values of the same name.
Can be repeated multiple times and must be in the format: -o "name:value".
"""
)

let gradientDestination = Key<String>(
"--gradient-destination",
description: """
The path to the file to generate.
By default, generated code will be printed on stdout.
"""
)

// MARK: - Initializers

init(generator: TokensGenerator) {
Expand Down Expand Up @@ -345,6 +369,13 @@ extension TokensCommand {
templateOptions: resolveTemplateOptions(bordersTemplateOptions.value),
destination: bordersDestination.value
)
],
gradient: [
TemplateConfiguration(
template: gradientTemplate.value,
templateOptions: resolveTemplateOptions(gradientTemplateOptions.value),
destination: gradientDestination.value
)
]
)
)
Expand Down
13 changes: 12 additions & 1 deletion Sources/FigmaGen/Dependencies.swift
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,10 @@ enum Dependencies {

static let boxShadowTokensContextProvider: BoxShadowTokensContextProvider = DefaultBoxShadowTokensContextProvider()

static let gradientTokensContextProvider: ColorTokensContextProvider = DefaultGradientTokensContextProvider(
tokensResolver: tokensResolver
)

// MARK: -

static let templateContextCoder: TemplateContextCoder = DefaultTemplateContextCoder()
Expand Down Expand Up @@ -170,6 +174,7 @@ enum Dependencies {

static let themeTokensGenerator: ThemeTokensGenerator = DefaultThemeTokensGenerator(
colorTokensContextProvider: colorTokensContextProvider,
gradientTokensContextProvider: gradientTokensContextProvider,
boxShadowsContextProvider: boxShadowTokensContextProvider,
templateRenderer: templateRenderer
)
Expand All @@ -184,6 +189,11 @@ enum Dependencies {
templateRenderer: templateRenderer
)

static let gradientTokensGenerator: GradientTokensGenerator = DefaultGradientTokensGenerator(
templateRenderer: templateRenderer,
gradientProvider: gradientTokensContextProvider
)

static let tokensGenerator: TokensGenerator = DefaultTokensGenerator(
tokensProvider: tokensProvider,
tokensGenerationParametersResolver: tokensGenerationParametersResolver,
Expand All @@ -194,7 +204,8 @@ enum Dependencies {
boxShadowTokensGenerator: boxShadowTokensGenerator,
themeTokensGenerator: themeTokensGenerator,
spacingTokensGenerator: spacingTokensGenerator,
bordersTokensGenerator: borderTokensGenerator
bordersTokensGenerator: borderTokensGenerator,
gradientTokensGenerator: gradientTokensGenerator
)

static let libraryGenerator: LibraryGenerator = DefaultLibraryGenerator(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import Foundation

struct ColorToken: Encodable {
struct ColorToken: TokenProtocol, Encodable {

// MARK: - Nested Types

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import Foundation

struct LinearGradientToken: TokenProtocol, Encodable {

struct ColorStop: Encodable {
let color: String
let percentage: CGFloat
}

// Нужен, т.к CGPoint нельзя использовать корректно в stencil шаблоне
struct Point: Encodable {
let x: CGFloat
let y: CGFloat
}

struct GradientThemeValue: Encodable {
let stops: [ColorStop]
let startPoint: Point
let endPoint: Point
}

let path: [String]
let name: String

let dayTheme: GradientThemeValue
let nightTheme: GradientThemeValue
let zpDayTheme: GradientThemeValue
}
12 changes: 12 additions & 0 deletions Sources/FigmaGen/Generators/Tokens/Contexts/TokenProtocol.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import Foundation

protocol TokenProtocol {
var name: String { get }
var path: [String] { get }
}

extension TokenProtocol {
var name: String {
path.joined(separator: ".")
}
}
15 changes: 13 additions & 2 deletions Sources/FigmaGen/Generators/Tokens/DefaultTokensGenerator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ final class DefaultTokensGenerator: TokensGenerator {
let themeTokensGenerator: ThemeTokensGenerator
let spacingTokensGenerator: SpacingTokensGenerator
let bordersTokensGenerator: BorderTokensGenerator

let gradientTokensGenerator: GradientTokensGenerator

// MARK: - Initializers

Expand All @@ -30,7 +30,8 @@ final class DefaultTokensGenerator: TokensGenerator {
boxShadowTokensGenerator: BoxShadowTokensGenerator,
themeTokensGenerator: ThemeTokensGenerator,
spacingTokensGenerator: SpacingTokensGenerator,
bordersTokensGenerator: BorderTokensGenerator
bordersTokensGenerator: BorderTokensGenerator,
gradientTokensGenerator: GradientTokensGenerator
) {
self.tokensProvider = tokensProvider
self.tokensGenerationParametersResolver = tokensGenerationParametersResolver
Expand All @@ -42,6 +43,7 @@ final class DefaultTokensGenerator: TokensGenerator {
self.themeTokensGenerator = themeTokensGenerator
self.spacingTokensGenerator = spacingTokensGenerator
self.bordersTokensGenerator = bordersTokensGenerator
self.gradientTokensGenerator = gradientTokensGenerator
}

// MARK: - Instance Methods
Expand All @@ -57,6 +59,7 @@ final class DefaultTokensGenerator: TokensGenerator {
try generateThemeTokens(parameters: parameters, tokenValues: tokenValues)
try generateSpacingTokens(parameters: parameters, tokenValues: tokenValues)
try generateBorderTokens(parameters: parameters, tokenValues: tokenValues)
try generateGradientTokens(parameters: parameters, tokenValues: tokenValues)
}

private func fetchTokens(from parameters: TokensGenerationParameters) async throws -> TokenValues {
Expand Down Expand Up @@ -133,6 +136,14 @@ final class DefaultTokensGenerator: TokensGenerator {
)
}

private func generateGradientTokens(parameters: TokensGenerationParameters, tokenValues: TokenValues) throws {
try generateTokens(
gradientTokensGenerator,
renderParameters: parameters.tokens.gradientRenderParameters,
tokenValues: tokenValues
)
}

private func generateTokens(
_ generator: BaseTokenGenerator,
renderParameters: [RenderParameters]?,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,11 @@ final class DefaultTokensGenerationParametersResolver: TokensGenerationParameter
defaultTemplateType: .native(name: "BorderTokens")
)

let gradientRenderParameters = renderParametersResolver.resolveRenderParameters(
templates: configuration.templates?.gradient,
defaultTemplateType: .native(name: "GradientTokens")
)

return TokensGenerationParameters(
file: file,
remoteFile: remoteFile,
Expand All @@ -105,7 +110,8 @@ final class DefaultTokensGenerationParametersResolver: TokensGenerationParameter
boxShadowRenderParameters: boxShadowRenderParameters,
themeRenderParameters: themeRenderParameters,
spacingRenderParameters: spacingRenderParameters,
bordersRenderParameters: borderRenderParameters
bordersRenderParameters: borderRenderParameters,
gradientRenderParameters: gradientRenderParameters
)
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ final class DefaultColorTokensGenerator: ColorTokensGenerator {
// MARK: - Instance Methods

func generate(renderParameters: RenderParameters, tokenValues: TokenValues) throws {
let context = try colorTokensContextProvider.fetchColorTokensContext(from: tokenValues)
let context = try colorTokensContextProvider.extractTokenContext(from: tokenValues)

try templateRenderer.renderTemplate(
renderParameters.template,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import Foundation

struct DefaultGradientTokensGenerator: GradientTokensGenerator {

// MARK: - Instance properties

private let templateRenderer: TemplateRenderer
private let provider: ColorTokensContextProvider

init(
templateRenderer: TemplateRenderer,
gradientProvider: ColorTokensContextProvider
) {
self.templateRenderer = templateRenderer
self.provider = gradientProvider
}

// MARK: - GradientTokensGenerator

func generate(renderParameters: RenderParameters, tokenValues: TokenValues) throws {
let gradientContext = try provider.extractTokenContext(from: tokenValues)

try templateRenderer.renderTemplate(
renderParameters.template,
to: renderParameters.destination,
context: ["gradients": gradientContext]
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import Foundation

protocol GradientTokensGenerator: BaseTokenGenerator { }
Original file line number Diff line number Diff line change
Expand Up @@ -5,33 +5,38 @@ final class DefaultThemeTokensGenerator: ThemeTokensGenerator {
// MARK: - Instance Properties

let colorTokensContextProvider: ColorTokensContextProvider
let gradientTokensContextProvider: ColorTokensContextProvider
let boxShadowsContextProvider: BoxShadowTokensContextProvider
let templateRenderer: TemplateRenderer

// MARK: - Initializers

init(
colorTokensContextProvider: ColorTokensContextProvider,
gradientTokensContextProvider: ColorTokensContextProvider,
boxShadowsContextProvider: BoxShadowTokensContextProvider,
templateRenderer: TemplateRenderer
) {
self.colorTokensContextProvider = colorTokensContextProvider
self.gradientTokensContextProvider = gradientTokensContextProvider
self.boxShadowsContextProvider = boxShadowsContextProvider
self.templateRenderer = templateRenderer
}

// MARK: - Instance Methods

func generate(renderParameters: RenderParameters, tokenValues: TokenValues) throws {
let colorsContext = try colorTokensContextProvider.fetchColorTokensContext(from: tokenValues)
let colorsContext = try colorTokensContextProvider.extractTokenContext(from: tokenValues)
let boxShadowsContext = try boxShadowsContextProvider.fetchBoxShadowTokensContext(from: tokenValues)
let gradientsContext = try gradientTokensContextProvider.extractTokenContext(from: tokenValues)

try templateRenderer.renderTemplate(
renderParameters.template,
to: renderParameters.destination,
context: [
"colors": colorsContext,
"boxShadows": boxShadowsContext
"boxShadows": boxShadowsContext,
"gradients": gradientsContext
]
)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import Foundation

extension ColorTokensContextProvider {

func structure<T: Encodable & TokenProtocol>(
tokens: [T],
atNamePath namePath: [String] = [],
contextName: String = "tokens"
) -> [String: Any] {
var structuredTokens: [String: Any] = [:]

if let name = namePath.last {
structuredTokens["name"] = name
}

if !namePath.isEmpty {
structuredTokens["path"] = namePath
}

let filteredTokens = tokens
.filter { $0.path.count == namePath.count + 1 }
.sorted { $0.name.lowercased() < $1.name.lowercased() }

if !filteredTokens.isEmpty {
structuredTokens[contextName] = filteredTokens
}

let childTokens = tokens.filter { $0.path.count > namePath.count + 1 }

let children = Dictionary(grouping: childTokens) { $0.path[namePath.count] }
.sorted { $0.key < $1.key }
.map { name, tokens in
structure(tokens: tokens, atNamePath: namePath + [name], contextName: contextName)
}

if !children.isEmpty {
structuredTokens["children"] = children
}

return structuredTokens
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@ protocol ColorTokensContextProvider {

// MARK: - Instance Methods

func fetchColorTokensContext(from tokenValues: TokenValues) throws -> [String: Any]
func extractTokenContext(from tokenValues: TokenValues) throws -> [String: Any]
}
Original file line number Diff line number Diff line change
Expand Up @@ -101,43 +101,9 @@ final class DefaultColorTokensContextProvider: ColorTokensContextProvider {
)
}

private func structure(tokenColors: [ColorToken], atNamePath namePath: [String] = []) -> [String: Any] {
var structuredColors: [String: Any] = [:]

if let name = namePath.last {
structuredColors["name"] = name
}

if !namePath.isEmpty {
structuredColors["path"] = namePath
}

let colors = tokenColors
.filter { $0.path.count == namePath.count + 1 }
.sorted { $0.name.lowercased() < $1.name.lowercased() }

if !colors.isEmpty {
structuredColors["colors"] = colors
}

let childTokenColors = tokenColors.filter { $0.path.count > namePath.count + 1 }

let children = Dictionary(grouping: childTokenColors) { $0.path[namePath.count] }
.sorted { $0.key < $1.key }
.map { name, colors in
structure(tokenColors: colors, atNamePath: namePath + [name])
}

if !children.isEmpty {
structuredColors["children"] = children
}

return structuredColors
}

// MARK: -

func fetchColorTokensContext(from tokenValues: TokenValues) throws -> [String: Any] {
func extractTokenContext(from tokenValues: TokenValues) throws -> [String: Any] {
let colors: [ColorToken] = try tokenValues.hhDay.compactMap { (token: TokenValue) in
guard case .color(let dayValue) = token.type else {
return nil
Expand All @@ -157,6 +123,6 @@ final class DefaultColorTokensContextProvider: ColorTokensContextProvider {
)
}

return structure(tokenColors: colors)
return structure(tokens: colors, contextName: "colors")
}
}
Loading