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.
I made a quick example project to answer this question: https://github.com/dbanksdesign/style-dictionary-example-colorsets
The example answers 2 questions:
dictionary.allProperties in a custom action.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:
contents variable you forgot to wrap color-space and components with color key. alpha: `${token.value.a.toFixed(3)}`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));
});
}```
Most helpful comment
Awesome @dbanksdesign, works a charm - thanks so much :)