From 67b917162ae403ea81da31c427cb27717c91211c Mon Sep 17 00:00:00 2001 From: jorenbroekema Date: Thu, 7 Dec 2023 19:10:03 +0100 Subject: [PATCH] fix: add support for UIColor format in ts/color/modifiers --- .changeset/green-forks-rhyme.md | 5 ++ package-lock.json | 14 +++- package.json | 2 + src/color-modifiers/modifyColor.ts | 25 +++++++ test/integration/swift-UI-color.test.ts | 70 +++++++++++++++++++ .../tokens/swift-UI-colors.tokens.json | 34 +++++++++ .../transformColorModifiers.spec.ts | 19 +++++ 7 files changed, 166 insertions(+), 3 deletions(-) create mode 100644 .changeset/green-forks-rhyme.md create mode 100644 test/integration/swift-UI-color.test.ts create mode 100644 test/integration/tokens/swift-UI-colors.tokens.json diff --git a/.changeset/green-forks-rhyme.md b/.changeset/green-forks-rhyme.md new file mode 100644 index 0000000..d5aa750 --- /dev/null +++ b/.changeset/green-forks-rhyme.md @@ -0,0 +1,5 @@ +--- +'@tokens-studio/sd-transforms': patch +--- + +Workaround fix in color modifiers transform to allow UIColor format. This workaround should be removed (in a breaking change) if https://github.com/amzn/style-dictionary/issues/1063 gets resolved and post-transitive transforms become a thing. diff --git a/package-lock.json b/package-lock.json index 814d09e..2217492 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@tokens-studio/sd-transforms", - "version": "0.11.9", + "version": "0.12.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@tokens-studio/sd-transforms", - "version": "0.11.9", + "version": "0.12.1", "license": "MIT", "dependencies": { "@tokens-studio/types": "^0.2.4", @@ -23,6 +23,7 @@ "@esm-bundle/chai": "^4.3.4-fix.0", "@rollup/plugin-commonjs": "^24.1.0", "@rollup/plugin-typescript": "^11.0.0", + "@types/tinycolor2": "^1.4.6", "@typescript-eslint/eslint-plugin": "^5.54.0", "@typescript-eslint/parser": "^5.54.0", "@web/dev-server-esbuild": "^0.3.3", @@ -40,12 +41,13 @@ "prettier-package-json": "^2.8.0", "rimraf": "^4.1.3", "rollup": "^3.18.0", + "tinycolor2": "^1.6.0", "ts-mocha": "^10.0.0", "ts-node": "^10.9.1", "typescript": "^4.9.5" }, "engines": { - "node": ">=15.14.0" + "node": ">=17.0.0" } }, "node_modules/@75lb/deep-merge": { @@ -1603,6 +1605,12 @@ "@types/node": "*" } }, + "node_modules/@types/tinycolor2": { + "version": "1.4.6", + "resolved": "https://registry.npmjs.org/@types/tinycolor2/-/tinycolor2-1.4.6.tgz", + "integrity": "sha512-iEN8J0BoMnsWBqjVbWH/c0G0Hh7O21lpR2/+PrvAVgWdzL7eexIFm4JN/Wn10PTcmNdtS6U67r499mlWMXOxNw==", + "dev": true + }, "node_modules/@types/ws": { "version": "7.4.7", "resolved": "https://registry.npmjs.org/@types/ws/-/ws-7.4.7.tgz", diff --git a/package.json b/package.json index c7a79e3..a86feeb 100644 --- a/package.json +++ b/package.json @@ -53,6 +53,7 @@ "@esm-bundle/chai": "^4.3.4-fix.0", "@rollup/plugin-commonjs": "^24.1.0", "@rollup/plugin-typescript": "^11.0.0", + "@types/tinycolor2": "^1.4.6", "@typescript-eslint/eslint-plugin": "^5.54.0", "@typescript-eslint/parser": "^5.54.0", "@web/dev-server-esbuild": "^0.3.3", @@ -70,6 +71,7 @@ "prettier-package-json": "^2.8.0", "rimraf": "^4.1.3", "rollup": "^3.18.0", + "tinycolor2": "^1.6.0", "ts-mocha": "^10.0.0", "ts-node": "^10.9.1", "typescript": "^4.9.5" diff --git a/src/color-modifiers/modifyColor.ts b/src/color-modifiers/modifyColor.ts index 6c1702a..4a1cdd3 100644 --- a/src/color-modifiers/modifyColor.ts +++ b/src/color-modifiers/modifyColor.ts @@ -5,6 +5,28 @@ import { darken } from './darken.js'; import { lighten } from './lighten.js'; import { ColorModifier } from '@tokens-studio/types'; +// Users using UIColor swift format are blocked from using such transform in +// combination with this color modify transform when using references. +// This is because reference value props are deferred so the UIColor +// transform always applies first to non-reference tokens, and only after that +// can the color modifier transitive transform apply to deferred tokens, at which point +// it is already UIColor format. +// We can remove this hotfix later once https://github.com/amzn/style-dictionary/issues/1063 +// is resolved. Then users can use a post-transitive transform for more fine grained control +function parseUIColor(value: string): string { + const reg = new RegExp( + `UIColor\\(red: (?[\\d\\.]+?), green: (?[\\d\\.]+?), blue: (?[\\d\\.]+?), alpha: (?[\\d\\.]+?)\\)`, + ); + const match = value.match(reg); + if (match?.groups) { + const { red, green, blue, alpha } = match.groups; + return `rgba(${parseFloat(red) * 255}, ${parseFloat(green) * 255}, ${ + parseFloat(blue) * 255 + }, ${alpha})`; + } + return value; +} + export function modifyColor( baseColor: string | undefined, modifier: ColorModifier, @@ -12,6 +34,9 @@ export function modifyColor( if (baseColor === undefined) { return baseColor; } + + baseColor = parseUIColor(baseColor); + const color = new Color(baseColor); let returnedColor = color; try { diff --git a/test/integration/swift-UI-color.test.ts b/test/integration/swift-UI-color.test.ts new file mode 100644 index 0000000..64b75aa --- /dev/null +++ b/test/integration/swift-UI-color.test.ts @@ -0,0 +1,70 @@ +import { expect } from '@esm-bundle/chai'; +import StyleDictionary from 'style-dictionary'; +import Color from 'tinycolor2'; +import { promises } from 'fs'; +import path from 'path'; +import { cleanup, init } from './utils.js'; + +const outputDir = 'test/integration/tokens/'; +const outputFileName = 'vars.css'; +const outputFilePath = path.resolve(outputDir, outputFileName); + +StyleDictionary.registerTransform({ + name: 'transitive/color/UIColorSwift', + type: 'value', + transitive: true, + transformer: token => { + const { r, g, b, a } = Color(token.value).toRgb(); + const rFixed = (r / 255.0).toFixed(3); + const gFixed = (g / 255.0).toFixed(3); + const bFixed = (b / 255.0).toFixed(3); + return `UIColor(red: ${rFixed}, green: ${gFixed}, blue: ${bFixed}, alpha: ${a})`; + }, +}); + +const cfg = { + source: ['test/integration/tokens/swift-UI-colors.tokens.json'], + platforms: { + css: { + transforms: [ + 'ts/color/modifiers', + 'attribute/cti', + 'transitive/color/UIColorSwift', + 'name/cti/camel', + ], + buildPath: outputDir, + files: [ + { + destination: outputFileName, + format: 'ios-swift/class.swift', + }, + ], + }, + }, +}; + +let dict: StyleDictionary.Core | undefined; + +describe('outputReferences integration', () => { + beforeEach(() => { + if (dict) { + cleanup(dict); + } + dict = init(cfg, { 'ts/color/modifiers': { format: 'hex' } }); + }); + + afterEach(() => { + if (dict) { + cleanup(dict); + } + }); + + it('supports UIColor with color modifiers', async () => { + const file = await promises.readFile(outputFilePath, 'utf-8'); + console.log(file); + expect(file).to + .include(` public static let colorDanger = UIColor(red: 0.251, green: 0.000, blue: 0.000, alpha: 1) + public static let colorError = UIColor(red: 0.125, green: 0.000, blue: 0.000, alpha: 1) + public static let colorRed = UIColor(red: 1.000, green: 0.000, blue: 0.000, alpha: 1)`); + }); +}); diff --git a/test/integration/tokens/swift-UI-colors.tokens.json b/test/integration/tokens/swift-UI-colors.tokens.json new file mode 100644 index 0000000..3ef621e --- /dev/null +++ b/test/integration/tokens/swift-UI-colors.tokens.json @@ -0,0 +1,34 @@ +{ + "color": { + "red": { + "value": "#f00", + "type": "color" + }, + "danger": { + "value": "{color.red}", + "type": "color", + "$extensions": { + "studio.tokens": { + "modify": { + "type": "darken", + "value": "0.75", + "space": "hsl" + } + } + } + }, + "error": { + "value": "{color.danger}", + "type": "color", + "$extensions": { + "studio.tokens": { + "modify": { + "type": "darken", + "value": "0.5", + "space": "hsl" + } + } + } + } + } +} diff --git a/test/spec/color-modifiers/transformColorModifiers.spec.ts b/test/spec/color-modifiers/transformColorModifiers.spec.ts index 7b52831..da757c1 100644 --- a/test/spec/color-modifiers/transformColorModifiers.spec.ts +++ b/test/spec/color-modifiers/transformColorModifiers.spec.ts @@ -291,4 +291,23 @@ describe('transform color modifiers', () => { expect(transformColorModifiers(token)).to.equal('#983735'); }); + + it('supports UIColor(r,g,b,a) format', () => { + const token = { + value: 'UIColor(red: 1, green: 0, blue: 0.5, alpha: 0.5)', + type: 'color', + $extensions: { + 'studio.tokens': { + modify: { + type: 'darken', + value: '0.5', + space: 'srgb', + format: 'srgb', + }, + }, + }, + }; + + expect(transformColorModifiers(token)).to.equal('rgb(50% 0% 25% / 0.5)'); + }); });