Style-dictionary: Config.js with json properties

Created on 26 Nov 2020  路  8Comments  路  Source: amzn/style-dictionary

Hello,

I'm following this example:
https://github.com/amzn/style-dictionary/blob/main/examples/advanced/node-modules-as-config-and-properties/config.js

And I want to convert all my colors to XCode colorsets, hence for this I need to convert each color to separate file, which is there in example:

      files: Object.keys(properties.color).map((colorType) => ({
        destination: `${colorType}.js`,
        format: 'javascript/es6',
        // Filters can be functions that return a boolean
        filter: (prop) => prop.attributes.type === colorType
      }))

But there is issue with properties variable:

const properties = require('./properties');

This code does import all properties in .js format, but I can't change properties and having simple json files as here https://github.com/amzn/style-dictionary/tree/main/examples/basic/properties. How to get this const properties from json files.

Most helpful comment

Awesome @dbanksdesign, works a charm - thanks so much :)

All 8 comments

I made a quick example project to answer this question: https://github.com/dbanksdesign/style-dictionary-example-colorsets

The example answers 2 questions:

  1. How to combine JSON files outside of Style Dictionary to use the object to create files in the Style Dictionary configuration. However I don't think that is the best way to create iOS colorsets because a colorset is a single color and it would be hard to create an array of all the colors without using the dictionary.allProperties in a custom action.
  2. How to create iOS colorsets using a custom action. This is how I create colorsets my projects, but if others have other ways to do this I would love to see them!

Posting the code here (note this might not match the example if I update the example in the future):

const combineJSON = require('style-dictionary/lib/utils/combineJSON');
const fs = require('fs-extra');
const glob = require('glob');
const Color = require('tinycolor2');

// Use style dictionary's internal method for combining JSON files
// to get a properties object
const properties = combineJSON([`tokens/**/*.json`], true);

// The directory to build the colorsets in
const assetsDir = 'build/ios/Assets.xcassets';

// Custom action for creating iOS colorsets
const createColorsets = (dictionary) => {
  dictionary.allProperties
    // grab all colors
    .filter(token => token.attributes.category === 'color')
    .forEach(token => {
      const folder = `${assetsDir}/${token.name}.colorset`;
      const file = `${folder}/Contents.json`;
      const contents = {
        colors: [
          {
            'color-space': "srgb",
            idiom: "universal",
            components: {
              alpha: `${token.value.a}`,
              red: `${token.value.r}`,
              green: `${token.value.g}`,
              blue: `${token.value.b}`,
            }
          }
        ]
      };
      // create the directory if it doesn't exist
      fs.ensureDirSync(folder);
      // create the Contents.json file
      fs.writeFileSync(file, JSON.stringify(contents, null, 2));
    });
}

// Exporting an object as the style dictionary configuration
// see 'build' npm script in package.json for how this is used
module.exports = {
  // Directly adding custom actions on the configuration
  // You could also do: StyleDictionary.registerAction() as well
  action: {
    createColorsets: {
      do: createColorsets,
      undo: function(dictionary, config) {
        // clean up colorsets we build when the package is cleaned
        glob(`${assetsDir}/*.colorset`, function (error, results) {
          results.forEach((colorsetFolder) => {
            fs.removeSync(colorsetFolder);
          });
        });
      }
    },
  },

  // Same with custom transforms, I just like doing it this way
  transform: {
    colorRGB: {
      type: `value`,
      matcher: (token) => token.attributes.category === 'color',
      transformer: (token) => {
        return Color(token.value).toRgb();
      }
    }
  },

  // Adding the properties object here directly rather than using `source`
  properties: properties,

  platforms: {
    js: {
      transformGroup: 'js',
      buildPath: `build/web/`,
      // Now you can access the properties object to build files based on
      // its structure
      files: Object.keys(properties.color).map((colorType) => ({
        destination: `${colorType}.js`,
        format: `javascript/es6`,
        filter: (token) => token.attributes.type === colorType
      }))
    },
    iOSColors: {
      transforms: [`attribute/cti`,`colorRGB`,`name/cti/pascal`],
      // Using our custom action to build colorsets
      actions: [`createColorsets`]
    }
  }
}

Hopefully that answers your questions, if not let me know!

Few notes:

  • Inside contents variable you forgot to wrap color-space and components with color key.
  • Alpha component should be decimal format, hence it will be like: alpha: `${token.value.a.toFixed(3)}`
  • We can use existing transformer in transforms array: attribute/color, and change action a bit, to use attributes instead of direct value: alpha: `${token.attributes.rgb.a.toFixed(3)}`

@dbanksdesign This is awesome! Thanks so much for providing this solution - does/can this support dark mode too? Also can this example work with this example? https://github.com/amzn/style-dictionary/tree/master/examples/advanced/multi-brand-multi-platform

This sample code could support dark mode. However there are a few different ways you can implement dark mode in style dictionary, and it would change depending on how you implement dark mode. The sample code should work with the multi-brand-multi-platform example.

Hi @dbanksdesign, thanks for that. I have been working on adding this to the multi-brand multi-platform example. Stuck on how to add the brand to the createColorSet function. Any help is appreciated :)

const StyleDictionaryPackage = require('style-dictionary');
const combineJSON = require('style-dictionary/lib/utils/combineJSON');
const fs = require('fs-extra');
const _ = require('lodash');
const glob = require('glob');
const Color = require('tinycolor2');

const properties = combineJSON([ `tmp/**/*.json`], true);

// The directory to build the colorsets in
const assetsDir = `ios/tokens/Classes/${brand}/Assets.xcassets`;

const createColorsets = (dictionary, brand) => {
dictionary.allProperties
    // grab all colors
    .filter(token => token.attributes.category === 'color')
    .forEach(token => {
        const folder = `${assetsDir}/Assets.xcassets/${token.name}.colorset`;
        const file = `${folder}/Contents.json`;
        const contents = {
            colors: [
                {
                    'color-space': "srgb",
                    idiom: "universal",
                    components: {
                        alpha: `${token.value.a}`,
                        red: `${token.value.r}`,
                        green: `${token.value.g}`,
                        blue: `${token.value.b}`,
                    }
                }
            ]
        };
        // create the directory if it doesn't exist
        fs.ensureDirSync(folder);
        // create the Contents.json file
        fs.writeFileSync(file, JSON.stringify(contents, null, 2));
    });
}

function getStyleDictionaryConfig(brand, platform) {

return {
        source: [
                `tmp/**/*.json`
        ],
        platforms: {
                js: {
                transformGroup: 'js',
                buildPath: `ios/tokens/Classes/${brand}/`,
                files: Object.keys(properties.color).map((colorType) => ({
                        destination: `${colorType}.js`,
                        format: `javascript/es6`,
                        filter: (token) => token.attributes.type === colorType
                    }))
            },
            iOSColors: {
                    transforms: [`attribute/cti`,`colorRGB`,`name/cti/pascal`],
                    actions: [`createColorsets`]
            },
    }
};
}


console.log('Build started...');
console.log('\n==============================================');

//REGISTER ACTION FOR IOS COLOURS
StyleDictionaryPackage.registerAction({
name: 'createColorsets',
do: function(dictionary, config) {
    createColorsets(dictionary);
},
undo: function(dictionary, config) {
    //clean up colorsets we build when the package is cleaned
        glob(`${assetsDir}/*.colorset`, function (error, results) {
                results.forEach((colorsetFolder) => {
                        fs.removeSync(colorsetFolder);
                });
    });
}
});


StyleDictionaryPackage.registerTransform({
name: 'colorRGB', 
type: 'value',
matcher: function(prop) {
            return prop.attributes.category === 'color';
},
transformer: function(prop) {
    return Color(prop.value).toRgb();
}
});

// PROCESS THE DESIGN TOKENS FOR THE DIFFEREN BRANDS AND PLATFORMS
['ios', 'android', 'js', 'iOSColors'].map(function(platform) {
['theme'].map(function(brand) {

        console.log('\n==============================================');
        console.log(`\nProcessing: [${platform}] [${brand}]`);

        const StyleDictionary = StyleDictionaryPackage.extend(getStyleDictionaryConfig(brand, platform));

        if (platform === 'js') {
                StyleDictionary.buildPlatform('js');
        } else if (platform === 'iOSColors') {
            StyleDictionary.buildPlatform('iOSColors');
        } else if (platform === 'ios') {
            StyleDictionary.buildPlatform('ios');
        } else if (platform === 'android') {
                    StyleDictionary.buildPlatform('android');
        } 

        console.log('\nEnd processing');

    })
})

console.log('\n==============================================');
console.log('\nBuild completed!');

@kellettemma try this:

const StyleDictionaryPackage = require('style-dictionary');
const fs = require('fs-extra');
const glob = require('glob');
const Color = require('tinycolor2');

// Custom action for creating iOS colorsets
const createColorsets = (dictionary, platform) => {
  // grab the 'brand' property on the platform
  const { brand } = platform;
  dictionary.allProperties
    // grab all colors
    .filter(token => token.attributes.category === 'color')
    .forEach(token => {
      const folder = `build/ios/${brand}/Assets.xcassets/${token.name}.colorset`;
      const file = `${folder}/Contents.json`;
      const contents = {
        colors: [
          {
            'color-space': "srgb",
            idiom: "universal",
            components: {
              alpha: `${token.value.a}`,
              red: `${token.value.r}`,
              green: `${token.value.g}`,
              blue: `${token.value.b}`,
            }
          }
        ]
      };
      // create the directory if it doesn't exist
      fs.ensureDirSync(folder);
      // create the Contents.json file
      fs.writeFileSync(file, JSON.stringify(contents, null, 2));
    });
}

//REGISTER ACTION FOR IOS COLOURS
StyleDictionaryPackage.registerAction({
  name: 'createColorsets',
  do: function(dictionary, platform) {
    createColorsets(dictionary, platform);
  },
  undo: function(dictionary, platform) {
    const { brand } = platform;
    const assetsDir = `build/ios/${brand}/Assets.xcassets`;
    glob(`${assetsDir}/*.colorset`, function (error, results) {
      results.forEach((colorsetFolder) => {
        fs.removeSync(colorsetFolder);
      });
    });
  }
});

StyleDictionaryPackage.registerTransform({
  name: 'colorRGB',
  type: 'value',
  matcher: function(prop) {
    return prop.attributes.category === 'color';
  },
  transformer: function(prop) {
    return Color(prop.value).toRgb();
  }
});

function getStyleDictionaryConfig(brand, platform) {
  return {
    include: [`tokens/**/*.json`],
    source: [
      `brand/${brand}/**/*.json`
    ],
    platforms: {
      iOSColors: {
        // you can add extra information here that formats and actions can access
        // adding a 'brand' property
        brand,
        transforms: [`attribute/cti`,`colorRGB`,`name/cti/pascal`],
        actions: [`createColorsets`]
      },
    }
  };
}

['iOSColors'].map(function(platform) {
  ['brand-1','brand-2'].map(function(brand) {
    console.log('\n==============================================');
    console.log(`\nProcessing: [${platform}] [${brand}]`);

    const StyleDictionary = StyleDictionaryPackage.extend(getStyleDictionaryConfig(brand, platform));

    StyleDictionary.buildPlatform(platform);

    console.log('\nEnd processing');
  });
});

console.log('\n==============================================');
console.log('\nBuild completed!');

I simplified the example just to make it hopefully easier to understand.

Awesome @dbanksdesign, works a charm - thanks so much :)

Hi @dbanksdesign, one last thing :) What is the best way to add a dark mode? I plan of having a token with two values e.g.

{ "color": { "background": { "on-light": {"value": "#f8f8f8"}, "on-dark": {"value": "#000000"} }, "on-surface": { "on-light": {"value": "#282828"}, "on-dark": {"value": "#ffffff"} } } }

and based on the item output light and dark mode

``const createColorsets = (dictionary, platform) => { // grab the 'brand' property on the platform const { brand } = platform; dictionary.allProperties // grab all colors .filter(token => token.attributes.category === 'color') .forEach(token => { const folder =ios/MadeDesignTokens/Classes/${brand}/Assets.xcassets/${token.name}.colorset; const file =${folder}/Contents.json`;
const lightMode = token.attributes.item === 'on-light';
const darkMode = token.attributes.item === 'on-dark';

  const contents = {
    colors: [
      {
        "color" : {
          "color-space": "srgb",
          // if lightMode output these values
          components: {
            alpha: `${token.value.a}`,
            red: `${token.value.r}`,
            green: `${token.value.g}`,
            blue: `${token.value.b}`,
          }
        },
        idiom: "universal",
      },{
     appearances:[
        {
           appearance:"luminosity",
           value: "dark"
        }
     ],
     "color" : {
      "color-space": "srgb",
        // if darkMode output these values
      components: {
        alpha: `${token.value.a}`,
        red: `${token.value.r}`,
        green: `${token.value.g}`,
        blue: `${token.value.b}`,
      }
    },
    idiom: "universal",
  },
    ]
  };
  // create the directory if it doesn't exist
  fs.ensureDirSync(folder);
  // create the Contents.json file
  fs.writeFileSync(file, JSON.stringify(contents, null, 2));
});

}```

Was this page helpful?
0 / 5 - 0 ratings

Related issues

valiksb picture valiksb  路  4Comments

blackfalcon picture blackfalcon  路  4Comments

clepore picture clepore  路  5Comments

chemoish picture chemoish  路  3Comments

johnny353535 picture johnny353535  路  3Comments