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

The multi-file method #3

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
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
13 changes: 11 additions & 2 deletions actions/android/vector.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,18 @@ const s2v = require('svg2vectordrawable');
* @param {String} options.svg - The content of the SVG that will be turned into a vector drawable. The SVG content at this point should have had all the token references inside of it resolved.
* @param {String} options.name - The name of the image token
* @param {String} options.androidPath - The build path for Android. This will be defined in the configuration
* @param {String} options.mode - The current mode (light or dark) Style Dictionary is being run in.
*/
function androidVector({ androidPath, svg, name }) {
const outputPath = `${androidPath}drawable/${name}.xml`;
function androidVector({ androidPath, svg, name, mode }) {
// Android doesn't support high contrast modes
if ([`hc`,`hcDark`].includes(mode)) {
return;
}

const outputPath = mode === `dark` ?
`${androidPath}drawable-night/${name}.xml` :
`${androidPath}drawable/${name}.xml`;

fs.ensureFileSync(outputPath);

// s2v will generate an Android vector drawable file
Expand Down
8 changes: 4 additions & 4 deletions actions/generateGraphics.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ module.exports = {
// and resolved dictionary object containing all the tokens and the platform configuration
// of the platform that called this action.
do: (dictionary, config) => {
const { androidPath, iosPath, buildPath } = config;
const { androidPath, iosPath, buildPath, mode } = config;

dictionary.allProperties
.filter(token => {
Expand All @@ -37,17 +37,17 @@ module.exports = {
const svg = src(dictionary.properties);

// Make sure the directory exists and write the new SVG file
const outputPath = `${buildPath||''}${name}.svg`;
const outputPath = `${buildPath||''}${name}-${mode}.svg`;
fs.ensureFileSync(outputPath);
fs.writeFileSync(outputPath, svg);
console.log(`✔︎ ${outputPath}`);

// This will take the SVG and convert it into Android Vector Drawable format
androidVector({ androidPath, name, svg });
androidVector({ androidPath, name, svg, mode });

// This will take the SVG and convert it to a PNG and create the metadata
// for an iOS imageset
iosImageset({ iosPath, name, svg });
iosImageset({ iosPath, name, svg, mode });
});
},

Expand Down
42 changes: 31 additions & 11 deletions actions/ios/colorsets.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
const fs = require('fs-extra');
const { contents, idiom } = require('./consts');
const { contents, darkAppearance, idiom, hcAppearance } = require('./consts');

/**
* This action will iterate over all the colors in the Style Dictionary
* and for each one write a colorset.
* and for each one write a colorset with light and (optional) dark
* mode versions.
*/
module.exports = {
// This is going to run once per theme.
do: (dictionary, platform) => {
const assetPath = `${platform.buildPath}/StyleDictionary.xcassets`;
fs.ensureDirSync(assetPath)
Expand All @@ -17,17 +19,35 @@ module.exports = {
const colorsetPath = `${assetPath}/${token.name}.colorset`;
fs.ensureDirSync(colorsetPath);

const colorset = {
colors: [{
idiom,
color: {
'color-space': `srgb`,
components: token.value
}
}],
...contents
// The colorset might already exist because Style Dictionary is run multiple
// times with different configurations. If the colorset already exists we want
// to modify it rather than writing over it.
const colorset = fs.existsSync(`${colorsetPath}/Contents.json`) ?
fs.readJsonSync(`${colorsetPath}/Contents.json`) :
{ ...contents, colors: [] }

const color = {
idiom,
color: {
'color-space': `srgb`,
components: token.value
}
};

if (platform.mode === `dark`) {
color.appearances = [darkAppearance];
}

if (platform.mode === `hc`) {
color.appearances = [hcAppearance];
}

if (platform.mode === `hcDark`) {
color.appearances = [darkAppearance, hcAppearance];
}

colorset.colors.push(color);

fs.writeFileSync(`${colorsetPath}/Contents.json`, JSON.stringify(colorset, null, 2));
});
},
Expand Down
41 changes: 32 additions & 9 deletions actions/ios/imagesets.js
Original file line number Diff line number Diff line change
@@ -1,26 +1,49 @@
const fs = require('fs-extra');
const sharp = require('sharp');
const { contents, idiom } = require('./consts');
const { contents, darkAppearance, idiom, hcAppearance } = require('./consts');

/**
* This function will generate an imageset for iOS
* @param {Object} options
* @param {String} options.svg - The content of the SVG that will be turned into a PNG. The SVG content at this point should have had all the token references inside of it resolved.
* @param {String} options.name - The name of the image token
* @param {String} options.iosPath - The build path for iOS. This will be defined in the configuration
* @param {String} options.mode - The current mode (light or dark) Style Dictionary is building in.
*/
function generateImageset({ svg, name, iosPath }) {
function generateImageset({ svg, name, iosPath, mode }) {
const outputPath = `${iosPath}StyleDictionary.xcassets/${name}.imageset`;
fs.ensureDirSync(outputPath);

const filename = `img.png`;
const imageset = {
...contents,
images: [{
idiom,
filename
}]
// The imageset might already exist because Style Dictionary is run multiple
// times with different configurations. If the imageset already exists we want
// to modify it rather than writing over it.
const imageset = fs.existsSync(`${outputPath}/Contents.json`) ?
fs.readJsonSync(`${outputPath}/Contents.json`) :
{ ...contents, images: [] }

let filename = `img.png`;
let image = {
idiom
};

if (mode === `dark`) {
filename = `img-dark.png`;
image.appearances = [darkAppearance];
}

if (mode === `hc`) {
filename = `img-hc.png`;
image.appearances = [hcAppearance];
}

if (mode === `hcDark`) {
filename = `img-hc-dark.png`;
image.appearances = [darkAppearance, hcAppearance];
}

// Add the image to the images array of the imageset object.
image.filename = filename;
imageset.images.push(image);

// Here we are using the sharp library for image processing that will take
// the SVG content and render it as a PNG
Expand Down
2 changes: 1 addition & 1 deletion android/demo/src/main/res/values/styles.xml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<resources>
<style name="AppTheme" parent="Theme.MaterialComponents.Light.DarkActionBar">
<style name="AppTheme" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
<item name="colorPrimary">@color/color_brand_primary_base</item>
<item name="colorPrimaryDark">@color/color_brand_primary_dark</item>
<item name="colorAccent">@color/color_brand_secondary_base</item>
Expand Down
177 changes: 159 additions & 18 deletions build.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ fs.removeSync(androidPath);
console.log(`cleaning ${webPath}...`);
fs.removeSync(webPath);


StyleDictionary.extend({
// Adding custom actions, transforms, and formats
const styleDictionary = StyleDictionary.extend({
// custom actions
action: {
generateColorsets: require('./actions/ios/colorsets'),
Expand All @@ -32,11 +32,32 @@ StyleDictionary.extend({
swiftColor: require('./formats/swiftColor'),
swiftImage: require('./formats/swiftImage'),
},

});

const modes = [`light`, `dark`, `hc`, `hcDark`];

const assets = {
transforms: [`attribute/cti`,`color/hex`,`size/remToFloat`,`name/ti/camel`],
buildPath: `${webPath}/images/`,
iosPath,
androidPath,
actions: [`generateGraphics`]
};

const iosColors = {
buildPath: iosPath,
transforms: [`attribute/cti`,`colorRGB`,`name/ti/camel`],
actions: [`generateColorsets`]
};

console.log(`☀️ Building light mode...`);
styleDictionary.extend({
source: [
`tokens/**/*.json5`
// this is saying find any files in the tokens folder
// that does not have .dark or .light, but ends in .json5
`tokens/**/!(*.${modes.join(`|*.`)}).json5`
],

platforms: {
css: {
transformGroup: `css`,
Expand All @@ -58,20 +79,15 @@ StyleDictionary.extend({
format: `json/flat`
}]
},

assets: Object.assign(assets, {
// mode lets the custom actions know which color mode they are being run on
mode: `light`
}),

assets: {
transforms: [`attribute/cti`,`color/hex`,`size/remToFloat`,`name/ti/camel`],
buildPath: `${webPath}/images/`,
iosPath,
androidPath,
actions: [`generateGraphics`]
},

iosColors: {
buildPath: iosPath,
transforms: [`attribute/cti`,`colorRGB`,`name/ti/camel`],
actions: [`generateColorsets`]
},
iosColors: Object.assign(iosColors, {
mode: `light`
}),

iOS: {
buildPath: iosPath,
Expand Down Expand Up @@ -103,6 +119,10 @@ StyleDictionary.extend({
format: `android/resources`,
filter: (token) => token.attributes.category === `color`,
options: {
// this is important!
// this will keep token references intact so that we don't need
// to generate *all* color resources for dark mode, only
// the specific ones that change
outputReferences: true
},
},{
Expand All @@ -118,4 +138,125 @@ StyleDictionary.extend({
}]
}
}
}).buildAllPlatforms();


// Dark Mode
// we will only build the files we need to, we don't need to rebuild all the files
console.log(`\n\n🌙 Building dark mode...`);
styleDictionary.extend({
// Using the include array so that theme token overrides don't show
// warnings in the console.
include: [
`tokens/**/!(*.${modes.join(`|*.`)}).json5`
],
source: [
`tokens/**/*.dark.json5`
],
platforms: {
css: {
transformGroup: `css`,
buildPath: webPath,
files: [{
destination: `variables-dark.css`,
format: `css/variables`,
// only putting in the tokens from files with '.dark' in the filepath
filter: (token) => token.filePath.indexOf(`.dark`) > -1,
options: {
outputReferences: true
}
}]
},

assets: Object.assign(assets, {
mode: `dark`
}),

iosColors: Object.assign(iosColors, {
mode: `dark`
}),

android: {
transformGroup: `android`,
buildPath: androidPath,
files: [{
destination: `values-night/colors.xml`,
format: `android/resources`,
// only outputting the tokens from files with '.dark' in the filepath
filter: (token) => token.filePath.indexOf(`.dark`) > -1
}]
}
}
}).buildAllPlatforms();

// High-Contrast Dark Mode
// we will only build the files we need to, we don't need to rebuild all the files
console.log(`\n\n🌈🌙 Building high-contrast dark mode...`);
styleDictionary.extend({
include: [
`tokens/**/!(*.${modes.join(`|*.`)}).json5`
],
source: [
`tokens/**/*.hcDark.json5`
],

platforms: {
css: {
transformGroup: `css`,
buildPath: webPath,
files: [{
destination: `variables-hc-dark.css`,
format: `css/variables`,
filter: (token) => token.filePath.indexOf(`.hcDark`) > -1,
options: {
outputReferences: true
}
}]
},

// Because iOS only has good support for high-contrast modes
// we will only build the necessary files for iOS:
assets: Object.assign(assets, {
mode: `hcDark`
}),

iosColors: Object.assign(iosColors, {
mode: `hcDark`
}),
}
}).buildAllPlatforms();

// High-Contrast Light Mode
// we will only build the files we need to, we don't need to rebuild all the files
console.log(`\n\n🌈☀️ Building high-contrast light mode...`);
styleDictionary.extend({
include: [
`tokens/**/!(*.${modes.join(`|*.`)}).json5`
],
source: [
`tokens/**/*.hc.json5`
],

platforms: {
css: {
transformGroup: `css`,
buildPath: webPath,
files: [{
destination: `variables-hc.css`,
format: `css/variables`,
filter: (token) => token.filePath.indexOf(`.hc`) > -1,
options: {
outputReferences: true
}
}]
},

assets: Object.assign(assets, {
mode: `hc`
}),

iosColors: Object.assign(iosColors, {
mode: `hc`
}),
}
}).buildAllPlatforms();
Loading