Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/css var deep #428

Closed
wants to merge 10 commits into from
12 changes: 12 additions & 0 deletions __tests__/formats/__snapshots__/all.test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,18 @@ exports[`formats all should match css/variables snapshot 1`] = `
"
`;

exports[`formats all should match css/variables-deep snapshot 1`] = `
"/**
* Do not edit directly
* Generated on Sat, 01 Jan 2000 00:00:00 GMT
*/

:root {
--color_red: #FF0000; /* comment */
}
"
`;

exports[`formats all should match flutter/class.dart snapshot 1`] = `
"
//
Expand Down
31 changes: 31 additions & 0 deletions __tests__/formats/__snapshots__/cssVariablesDeep.test.js.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`formats css/variables-deep should generate a css variables file with alias references 1`] = `
"/**
* Do not edit directly
* Generated on Sat, 01 Jan 2000 00:00:00 GMT
*/

:root {
--color-blue-20: #0000ff;
--primary: var(--color-blue-20);
--icon: \\"acme-icon\\";
--width: 100px;
}
"
`;

exports[`formats css/variables-deep should work with a prefix 1`] = `
"/**
* Do not edit directly
* Generated on Sat, 01 Jan 2000 00:00:00 GMT
*/

:root {
--color-blue-20: #0000ff;
--primary: var(--acme-color-blue-20);
--icon: \\"acme-icon\\";
--width: 100px;
}
"
`;
71 changes: 71 additions & 0 deletions __tests__/formats/cssVariablesDeep.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
var fs = require('fs-extra');
var helpers = require('../__helpers');
const formats = require('../../lib/common/formats');

var file = {
"destination": "__output/",
"format": "css/variables-deep"
};

var dictionary = {
"allProperties": [
{
name: "color-blue-20",
value: "#0000ff",
original: {
value: "#0000ff",
},
},
{
name: "primary",
value: "#00ff00",
original: {
value: "{color.blue-20.value}",
},
},
{
name: "icon",
value: "acme-icon",
attributes: { "data-type": "string" },
},
{
name: "width",
value: "100px",
attributes: { "data-type": "number" },
}
]
};

describe('formats', () => {
describe('css/variables-deep', () => {

// mock the Date.now() call to a fixed value
const constantDate = new Date('2000-01-01');
const globalDate = global.Date;

var formatter = formats['css/variables-deep'].bind(file);

beforeEach(() => {
global.Date = function() { return constantDate };
helpers.clearOutput();
});

afterEach(() => {
global.Date = globalDate;
helpers.clearOutput();
});

it('should generate a css variables file with alias references', () => {
fs.writeFileSync('./__tests__/__output/output.css', formatter(dictionary, {}) );
const testFile = fs.readFileSync("./__tests__/__output/output.css", "UTF-8");
expect(testFile).toMatchSnapshot();
});

it('should work with a prefix', () => {
fs.writeFileSync('./__tests__/__output/output.css', formatter(dictionary, { prefix: "acme" }) );
const testFile = fs.readFileSync("./__tests__/__output/output.css", "UTF-8");
expect(testFile).toMatchSnapshot();
});
});

});
24 changes: 23 additions & 1 deletion lib/common/formats.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@

var fs = require('fs'),
_ = require('lodash'),
GroupMessages = require('../utils/groupMessages');
GroupMessages = require('../utils/groupMessages'),
cssFormatter = require('./formats/css-variables');

var SASS_MAP_FORMAT_DEPRECATION_WARNINGS = GroupMessages.GROUP.SassMapFormatDeprecationWarnings;

Expand Down Expand Up @@ -108,6 +109,27 @@ module.exports = {
'\n}\n';
},

/**
* Creates a CSS file with variable definitions that maintain their alias references
*
* @memberof Formats
* @kind member
* @example
* ```css
* :root {
* --color-primary: #f0f0f0;
* --button-primary-background-color: var(--color-primary);
* }
* ```
*/
'css/variables-deep': function(dictionary, config) {
const newConfig = Object.assign({}, config, {
deep: true,
header: fileHeader(this.options)
});
return cssFormatter(dictionary, newConfig);
},

/**
* Creates a SCSS file with a flat map based on the style dictionary
*
Expand Down
72 changes: 72 additions & 0 deletions lib/common/formats/css-variables.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
function formatter(dictionary, config) {
const header = config.header || '';
const body = dictionary.allProperties.map(mapProp.bind(this, config));
return `${header}:root {\n${body.join("\n")}\n}\n`;
}

function mapProp(config, prop) {
const comment = (prop.comment) ? ` /* ${prop.comment} */` : '';
const varName = makePropCSSVar(prop.name);
const value = setValue(prop, config);
let token = ` ${varName}: ${value};${comment}`;

if (config.dark && prop.attributes && prop.attributes.dark) {
const darkProp = generateDarkToken(prop);
const darkValue = setValue(darkProp, config);
token += `;\n ${varName}-dark: ${darkValue};`;
}

return token;
}

function setValue(prop, config) {
if (typeof prop.value === "object") {
throwObjectError(prop);
}

const nVal = prop.value;
const oVal = prop.original && prop.original.value;
if (config.deep && nVal !== oVal && isAlias(oVal)) {
return useReferenceValue(oVal, config.prefix);
}
return typeIsString(prop) ? `"${nVal}"` : nVal;
}

function generateDarkToken(prop) {
let returnProp = prop;
if (prop.attributes && prop.attributes.dark) {
returnProp = {
name: prop.name+'-dark',
value: prop.attributes.dark.value,
original: { value: prop.attributes.dark.original },
};
}
return returnProp;
}

function throwObjectError(prop) {
let message = `"${prop.name}" has an original value of "${prop.original.value}". \n`;
message += "This points to an object. ";
message += "Reference the object's \"value\" key if it's an alias \n";
throw new Error(message);
}

function isAlias(str) {
// is string and matches "{text.value}"
return !!(typeof str === "string" && str.match(/(^{.*\.value}$)/gm));
}

function typeIsString(prop) {
return !!(prop.attributes && prop.attributes["data-type"] === "string");
}

function makePropCSSVar(name) {
return `--${name}`;
}

function useReferenceValue(value, prefix) {
const head = prefix ? `var(--${prefix}-` : "var(--";
return `${head}${value.replace(/\./g, "-").replace(/({|-value})/g, "")})`;
}

module.exports = formatter;