diff --git a/packages/sketch/src/commands/colors/index.js b/packages/sketch/src/commands/colors/index.js index 8b8807840172..25314275a4ef 100644 --- a/packages/sketch/src/commands/colors/index.js +++ b/packages/sketch/src/commands/colors/index.js @@ -5,5 +5,5 @@ * LICENSE file in the root directory of this source tree. */ -export { sync } from './sync'; +export { sync, syncColorVars } from './sync'; export { generate } from './generate'; diff --git a/packages/sketch/src/commands/colors/sync.js b/packages/sketch/src/commands/colors/sync.js index 7703e8c59b35..0c88c6f46721 100644 --- a/packages/sketch/src/commands/colors/sync.js +++ b/packages/sketch/src/commands/colors/sync.js @@ -7,10 +7,16 @@ import { Document } from 'sketch/dom'; import { command } from '../command'; -import { syncColorStyles } from '../../sharedStyles/colors'; +import { syncColorStyles, syncColorVariables } from '../../sharedStyles/colors'; export function sync() { command('commands/colors/sync', () => { syncColorStyles({ document: Document.getSelectedDocument() }); }); } + +export function syncColorVars() { + command('commands/colors/syncvars', () => { + syncColorVariables({ document: Document.getSelectedDocument() }); + }); +} diff --git a/packages/sketch/src/commands/index.js b/packages/sketch/src/commands/index.js index 9183632ad5e5..ddf03a22a1fd 100644 --- a/packages/sketch/src/commands/index.js +++ b/packages/sketch/src/commands/index.js @@ -12,7 +12,11 @@ import 'regenerator-runtime/runtime'; // triggered by having separate entrypoints. Most notably we would encounter // parse errors because the bundlers were being generated incorrectly during // incremental rebuilds. -export { sync as syncColors, generate as generateColors } from './colors'; +export { + sync as syncColors, + syncColorVars, + generate as generateColors, +} from './colors'; export { generate as generateIcons } from './icons'; export { syncSmallIcons, syncLargeIcons } from './icons'; export { sync as syncThemes, generate as generateThemes } from './themes'; diff --git a/packages/sketch/src/manifest.json b/packages/sketch/src/manifest.json index 8de9594bbfe3..c4b722fdaef3 100644 --- a/packages/sketch/src/manifest.json +++ b/packages/sketch/src/manifest.json @@ -10,6 +10,12 @@ "script": "commands/index.js", "handler": "syncColors" }, + { + "name": "Sync color variables", + "identifier": "carbon.elements.colors.syncvars", + "script": "commands/index.js", + "handler": "syncColorVars" + }, { "name": "Generate color page", "identifier": "carbon.elements.colors.generate", @@ -77,6 +83,7 @@ "title": "Colors", "items": [ "carbon.elements.colors.sync", + "carbon.elements.colors.syncvars", "carbon.elements.colors.generate" ] }, @@ -97,7 +104,10 @@ }, { "title": "Type", - "items": ["carbon.elements.type.sync", "carbon.elements.type.generate"] + "items": [ + "carbon.elements.type.sync", + "carbon.elements.type.generate" + ] }, { "title": "Test", diff --git a/packages/sketch/src/sharedStyles/colors.js b/packages/sketch/src/sharedStyles/colors.js index 5ea0afae2771..3e0d2535d101 100644 --- a/packages/sketch/src/sharedStyles/colors.js +++ b/packages/sketch/src/sharedStyles/colors.js @@ -8,23 +8,38 @@ import { colors } from '@carbon/colors'; import { formatTokenName } from '@carbon/themes'; import { syncColorStyle } from '../tools/sharedStyles'; +import { syncColorVariable } from '../tools/colorVariables'; // We separate out certain colors that are not a part of the primary swatches // that we need to render const { black, white, orange, yellow, ...swatches } = colors; /** - * Our shared style name will need to have the `color` namespace alongside a - * name for the swatch, the style type, and an optional grade. - * @param {object} params - formatSharedColorStyleName parameters - * @param {string} params.name - * @param {string?} params.grade + * Format name for shared layer styles or color variables + * Shared styles names will need to have the `color` namespace + * Both shared styles and color variables need a name for the swatch and an + * optional grade. + * @param {object} params + * @param {string} params.name - kebab cased color name + * @param {string?} params.grade - color grade + * @param {string?} params.formatFor - color name output format * @returns {string} */ -function formatSharedColorStyleName({ name, grade }) { - return ['color', name.split('-').join(' '), grade] - .filter(Boolean) - .join(' / '); +function formatColorName({ name, grade, formatFor }) { + const formattedName = name.split('-').join(' '); + switch (formatFor) { + case 'sharedLayerStyle': + return ['color', formattedName, grade].filter(Boolean).join(' / '); + case 'colorVariable': + return [ + grade ? `${formattedName}/${formattedName}` : formattedName, + grade, + ] + .filter(Boolean) + .join(' '); + default: + return ''; + } } /** @@ -39,7 +54,7 @@ export function syncColorStyles({ document }) { const result = Object.keys(swatches[swatchName]).map((grade) => { return syncColorStyle({ document, - name: formatSharedColorStyleName({ name, grade }), + name: formatColorName({ name, grade, formatFor: 'sharedLayerStyle' }), value: swatches[swatchName][grade], }); }); @@ -54,10 +69,45 @@ export function syncColorStyles({ document }) { ].map(([name, value]) => { return syncColorStyle({ document, - name: formatSharedColorStyleName({ name }), + name: formatColorName({ name, formatFor: 'sharedLayerStyle' }), value, }); }); return sharedStyles.concat(singleColors); } + +/** + * Sync color variables (Swatches) in the given document and return an array + * @param {object} params - syncColorVariables parameters + * @param {Document} params.document + * @returns {Array} + */ +export function syncColorVariables({ document }) { + const colorVariables = Object.keys(swatches).flatMap((swatchName) => { + const name = formatTokenName(swatchName); + const result = Object.keys(swatches[swatchName]).map((grade) => { + return syncColorVariable({ + document, + name: formatColorName({ name, grade, formatFor: 'colorVariable' }), + color: swatches[swatchName][grade], + }); + }); + return result; + }); + + const singleColors = [ + ['black', black['100']], + ['white', white['0']], + ['orange', orange['40']], + ['yellow', yellow['30']], + ].map(([name, color]) => { + return syncColorVariable({ + document, + name: formatColorName({ name, formatFor: 'colorVariable' }), + color, + }); + }); + + return colorVariables.concat(singleColors); +} diff --git a/packages/sketch/src/tools/colorVariables.js b/packages/sketch/src/tools/colorVariables.js new file mode 100644 index 000000000000..584ab55e919c --- /dev/null +++ b/packages/sketch/src/tools/colorVariables.js @@ -0,0 +1,67 @@ +/** + * Copyright IBM Corp. 2018, 2018 + * + * This source code is licensed under the Apache-2.0 license found in the + * LICENSE file in the root directory of this source tree. + */ + +/* global MSColor */ + +import { Swatch } from 'sketch/dom'; + +/** + * Creates or updates a color variable (Swatch) in the current working document + * @param {object} params - syncColorVariable parameters + * @param {Document} params.document + * @param {string} params.name - color name + * @param {string} params.color - color hex + * @returns {void} + */ +export function syncColorVariable({ document, name, color }) { + // check existing color variables + const documentColorVariables = document.swatches; + const colorVariable = documentColorVariables.find( + (swatch) => swatch.name === name + ); + + // generate new Swatch + const generatedSwatch = Swatch.from({ + name, + color, + }); + + // create and add new color variable if existing Swatch not found + if (!colorVariable) { + document.swatches.push(generatedSwatch); + return; + } + + // update existing color variable + if (colorVariable.color !== generatedSwatch.color) { + // slice up Sketch swatch color hex since they use rgba hex + const generatedColor = generatedSwatch.color.slice(0, -2); + const generatedAlpha = generatedSwatch.color.slice(-2); + + /** + * currently (May 2021) need native API to update color of a swatch + * ref: https://sketchplugins.com/d/2205-js-api-guide-whats-up-with-color-variables + */ + colorVariable.sketchObject.updateWithColor( + MSColor.colorWithHex_alpha( + generatedColor, + // convert hex alpha channel to decimal + parseInt(generatedAlpha, 16) / 255 + ) + ); + + /** + * because the color does not get updated automatically we have to manually + * call an update method. may possibly be fixed in future Sketch API + * versions + */ + const swatchContainer = document.sketchObject + .documentData() + .sharedSwatches(); + swatchContainer.updateReferencesToSwatch(colorVariable.sketchObject); + } +}