UNPKG

style-dictionary

Version:

Style once, use everywhere. A build system for creating cross-platform styles.

1,696 lines (1,621 loc) 52.3 kB
/* * Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with * the License. A copy of the License is located at * * http://www.apache.org/licenses/LICENSE-2.0 * * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR * CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions * and limitations under the License. */ import * as prettier from 'prettier/standalone'; import * as prettierPluginBabel from 'prettier/plugins/babel'; import * as prettierPluginEstree from 'prettier/plugins/estree'; import * as prettierPluginTypescript from 'prettier/plugins/typescript'; import { fileHeader, formattedVariables, getTypeScriptType, iconsWithPrefix, minifyDictionary, sortByReference, createPropertyFormatter, sortByName, setSwiftFileProperties, setComposeObjectProperties, } from './formatHelpers/index.js'; import { stripMeta as stripMetaUtil } from '../utils/stripMeta.js'; import androidColorsTemplate from './templates/android/colors.template.js'; import androidDimensTemplate from './templates/android/dimens.template.js'; import androidFontDimensTemplate from './templates/android/fontDimens.template.js'; import androidIntegersTemplate from './templates/android/integers.template.js'; import androidResourcesTemplate from './templates/android/resources.template.js'; import androidStringsTemplate from './templates/android/strings.template.js'; import composeObjectTemplate from './templates/compose/object.kt.template.js'; import cssFontsTemplate from './templates/css/fonts.css.template.js'; import flutterClassDartTemplate from './templates/flutter/class.dart.template.js'; import iosColorsHTemplate from './templates/ios/colors.h.template.js'; import iosColorsMTemplate from './templates/ios/colors.m.template.js'; import iosSingletonHTemplate from './templates/ios/singleton.h.template.js'; import iosSingletonMTemplate from './templates/ios/singleton.m.template.js'; import iosStaticHTemplate from './templates/ios/static.h.template.js'; import iosStaticMTemplate from './templates/ios/static.m.template.js'; import iosStringsHTemplate from './templates/ios/strings.h.template.js'; import iosStringsMTemplate from './templates/ios/strings.m.template.js'; import iosSwiftAnyTemplate from './templates/ios-swift/any.swift.template.js'; import scssMapDeepTemplate from './templates/scss/map-deep.template.js'; import scssMapFlatTemplate from './templates/scss/map-flat.template.js'; import macrosTemplate from './templates/ios/macros.template.js'; import plistTemplate from './templates/ios/plist.template.js'; import { commentPositions, commentStyles, fileHeaderCommentStyles, formats as fileFormats, propertyFormatNames, } from '../enums/index.js'; import { addComment } from './formatHelpers/createPropertyFormatter.js'; /** * @typedef {import('../../types/Format.d.ts').Format} Format * @typedef {import('../../types/Format.d.ts').FormatFnArguments} FormatArgs * @typedef {import('../../types/File.d.ts').FormattingOverrides} FormattingOverrides * @typedef {import('../../types/Format.d.ts').OutputReferences} OutputReferences * @typedef {import('../../types/DesignToken.d.ts').TransformedToken} Token * @typedef {import('../../types/DesignToken.d.ts').TransformedTokens} Tokens * @typedef {import('../../types/Config.d.ts').Config} Config * @typedef {import('../../types/Config.d.ts').LocalOptions} LocalOptions * @typedef {import('../utils/stripMeta.js').StripMetaOptions} StripMetaOptions */ const { above } = commentPositions; const { none } = commentStyles; const { long, short, xml } = fileHeaderCommentStyles; const { css, sass, less, stylus } = propertyFormatNames; const { cssVariables, cssFonts, scssMapFlat, scssMapDeep, scssVariables, scssIcons, lessVariables, lessIcons, stylusVariables, javascriptEs6, javascriptEsm, javascriptModule, javascriptModuleFlat, javascriptObject, javascriptUmd, typescriptEs6Declarations, typescriptModuleDeclarations, androidResources, androidColors, androidDimens, androidFontDimens, androidIntegers, androidStrings, composeObject, iosMacros, iosPlist, iosSingletonM, iosSingletonH, iosStaticH, iosStaticM, iosColorsH, iosColorsM, iosStringsH, iosStringsM, iosSwiftClassSwift, iosSwiftEnumSwift, iosSwiftAnySwift, json, jsonAsset, jsonNested, jsonFlat, sketchPalette, sketchPaletteV2, flutterClassDart, } = fileFormats; /** * @namespace Formats */ /** * Strip meta properties from tokens object * * @param {Tokens} tokens * @param {Config & LocalOptions & { stripMeta: boolean | StripMetaOptions}} options */ function stripMetaProps(tokens, options) { const sdMetaProps = ['attributes', 'filePath', 'name', 'path', 'comment']; const { stripMeta, usesDtcg } = options; let opts = /** @type {StripMetaOptions} */ ({ usesDtcg }); if (stripMeta) { if (stripMeta === true) { opts.strip = sdMetaProps; } else { opts = { usesDtcg: usesDtcg ?? false, ...stripMeta, }; } tokens = stripMetaUtil(tokens, opts); } return tokens; } /** * Prettier format JS contents * @param {string} content * @param {boolean} [ts] whether or not to use typescript */ async function formatJS(content, ts = false) { return prettier.format(content, { parser: ts ? `typescript` : `babel`, plugins: [ prettierPluginBabel, /** @type {import('prettier').Plugin} */ (prettierPluginEstree), prettierPluginTypescript, ], }); } /** * Remove prefix because the prefix option for createPropertyFormatter * is not the same as the prefix inside header comment * @param {FormattingOverrides} [formatting] */ function getFormattingCloneWithoutPrefix(formatting) { const formattingWithoutPrefix = structuredClone(formatting) ?? {}; // @ts-expect-error users are not supposed to pass "prefix" but they might because it used to be supported delete formattingWithoutPrefix.prefix; return formattingWithoutPrefix; } /** * @type {Record<string, Format['format']>} */ const formats = { /** * Creates a CSS file with variable definitions based on the style dictionary * * @memberof Formats * @kind member * * @example * ```css * :root { * --color-background-base: #f0f0f0; * --color-background-alt: #eeeeee; * } * ``` */ [cssVariables]: async function ({ dictionary, options = {}, file }) { const selector = Array.isArray(options.selector) ? options.selector : options.selector ? [options.selector] : [`:root`]; const { outputReferences, outputReferenceFallbacks, usesDtcg, formatting } = options; const header = await fileHeader({ file, formatting: getFormattingCloneWithoutPrefix(formatting), options, }); const indentation = formatting?.indentation || ' '; const nestInSelector = ( /** @type {string} */ content, /** @type {string} */ selector, /** @type {string} */ indentation, ) => { return `${indentation}${selector} {\n` + content + `\n${indentation}}`; }; const variables = formattedVariables({ format: css, dictionary, outputReferences, outputReferenceFallbacks, formatting: { ...formatting, indentation: indentation.repeat(selector.length), }, usesDtcg, }); return ( header + selector .reverse() .reduce( (content, currentSelector, index) => nestInSelector( content, currentSelector, indentation.repeat(selector.length - 1 - index), ), variables, ) + '\n' ); }, /** * Creates a SCSS file with a flat map based on the style dictionary * * Name the map by adding a 'mapName' attribute on the file object in your config. * * @memberof Formats * @kind member * @example * ```scss * $tokens: ( * 'color-background-base': #f0f0f0; * 'color-background-alt': #eeeeee; * ) * ``` */ [scssMapFlat]: async function ({ dictionary, options, file }) { const { allTokens } = dictionary; const { formatting } = options; const header = await fileHeader({ file, commentStyle: long, formatting: getFormattingCloneWithoutPrefix(formatting), options, }); return scssMapFlatTemplate({ allTokens, options, header }); }, /** * Creates a SCSS file with a deep map based on the style dictionary. * * Name the map by adding a 'mapName' attribute on the file object in your config. * * @memberof Formats * @kind member * @example * ```scss * $color-background-base: #f0f0f0 !default; * $color-background-alt: #eeeeee !default; * * $tokens: { * 'color': ( * 'background': ( * 'base': $color-background-base, * 'alt': $color-background-alt * ) * ) * ) * ``` */ [scssMapDeep]: async function ({ dictionary, options, file }) { // Default the "themeable" option to true for backward compatibility. const { outputReferences, themeable = true, formatting, usesDtcg } = options; const header = await fileHeader({ file, commentStyle: long, formatting: getFormattingCloneWithoutPrefix(formatting), options, }); return ( '\n' + header + formattedVariables({ format: sass, dictionary, outputReferences, themeable, formatting, usesDtcg, }) + '\n' + scssMapDeepTemplate({ dictionary, options }) ); }, /** * Creates a SCSS file with variable definitions based on the style dictionary. * * Add `!default` to any variable by setting a `themeable: true` attribute in the token's definition. * * @memberof Formats * @kind member * @example * ```scss * $color-background-base: #f0f0f0; * $color-background-alt: #eeeeee !default; * ``` */ [scssVariables]: async function ({ dictionary, options, file }) { const { outputReferences, themeable = false, formatting, usesDtcg } = options; const header = await fileHeader({ file, commentStyle: short, formatting: getFormattingCloneWithoutPrefix(formatting), options, }); return ( header + formattedVariables({ format: sass, dictionary, outputReferences, themeable, formatting, usesDtcg, }) + '\n' ); }, /** * Creates a SCSS file with variable definitions and helper classes for icons * * @memberof Formats * @kind member * @example * ```scss * $content-icon-email: '\E001'; * .icon.email:before { content:$content-icon-email; } * ``` */ [scssIcons]: async function ({ dictionary, options, file, platform }) { const { formatting } = options; const header = await fileHeader({ file, commentStyle: short, formatting: getFormattingCloneWithoutPrefix(formatting), options, }); return header + iconsWithPrefix('$', dictionary.allTokens, options, platform); }, /** * Creates a LESS file with variable definitions based on the style dictionary * * @memberof Formats * @kind member * @example * ```less * \@color-background-base: #f0f0f0; * \@color-background-alt: #eeeeee; * ``` */ [lessVariables]: async function ({ dictionary, options, file }) { const { outputReferences, formatting, usesDtcg } = options; const header = await fileHeader({ file, commentStyle: short, formatting: getFormattingCloneWithoutPrefix(formatting), options, }); return ( header + formattedVariables({ format: less, dictionary, outputReferences, formatting, usesDtcg, }) + '\n' ); }, /** * Creates a LESS file with variable definitions and helper classes for icons * * @memberof Formats * @kind member * @example * ```less * \@content-icon-email: '\E001'; * .icon.email:before { content:\@content-icon-email; } * ``` */ [lessIcons]: async function ({ dictionary, options, file, platform }) { const { formatting } = options; const header = await fileHeader({ file, commentStyle: short, formatting: getFormattingCloneWithoutPrefix(formatting), options, }); return header + iconsWithPrefix('@', dictionary.allTokens, options, platform); }, /** * Creates a Stylus file with variable definitions based on the style dictionary * * @memberof Formats * @kind member * @example * ```stylus * $color-background-base= #f0f0f0; * $color-background-alt= #eeeeee; * ``` */ [stylusVariables]: async function ({ dictionary, options, file, platform }) { const { formatting, usesDtcg } = options; const outputReferences = !!platform.options?.outputReferences; const header = await fileHeader({ file, commentStyle: short, formatting: getFormattingCloneWithoutPrefix(formatting), options, }); return ( header + formattedVariables({ format: stylus, dictionary, outputReferences, formatting, usesDtcg, }) + '\n' ); }, /** * Creates a CommonJS module with the whole style dictionary * * @memberof Formats * @kind member * @example * ```js * module.exports = { * color: { * base: { * red: { * value: '#ff0000' * } * } * } * } * ``` */ [javascriptModule]: async function ({ dictionary, file, options }) { const { formatting } = options; const header = await fileHeader({ file, formatting: getFormattingCloneWithoutPrefix(formatting), options, }); const content = header + 'module.exports = ' + JSON.stringify(dictionary.tokens, null, 2) + ';\n'; return formatJS(content); }, /** * Creates a CommonJS module with the whole style dictionary flattened to a single level. * * @memberof Formats * @kind member * @example * ```js * module.exports = { * "ColorBaseRed": "#ff0000" *} *``` */ [javascriptModuleFlat]: async function ({ dictionary, file, options }) { const { formatting } = options; const header = await fileHeader({ file, formatting: getFormattingCloneWithoutPrefix(formatting), options, }); const content = header + 'module.exports = ' + '{\n' + dictionary.allTokens .map(function (token) { return ` "${token.name}": ${JSON.stringify( options.usesDtcg ? token.$value : token.value, )}`; }) .join(',\n') + '\n}' + ';\n'; return formatJS(content); }, /** * Creates a JS file a global var that is a plain javascript object of the style dictionary. * Name the variable by adding a 'name' attribute on the file object in your config. * * @memberof Formats * @kind member * @example * ```js * var StyleDictionary = { * color: { * base: { * red: { * value: '#ff0000' * } * } * } * } * ``` */ [javascriptObject]: async function ({ dictionary, file, options }) { const { formatting } = options; const header = await fileHeader({ file, formatting: getFormattingCloneWithoutPrefix(formatting), options, }); const content = header + 'var ' + (file.options?.name || '_styleDictionary') + ' = ' + JSON.stringify(dictionary.tokens, null, 2) + ';\n'; return formatJS(content); }, /** * Creates a [UMD](https://github.com/umdjs/umd) module of the style * dictionary. Name the module by adding a 'name' attribute on the file object * in your config. * * @memberof Formats * @kind member * @example * ```js * (function(root, factory) { * if (typeof module === "object" && module.exports) { * module.exports = factory(); * } else if (typeof exports === "object") { * exports["_styleDictionary"] = factory(); * } else if (typeof define === "function" && define.amd) { * define([], factory); * } else { * root["_styleDictionary"] = factory(); * } * }(this, function() { * return { * "color": { * "red": { * "value": "#FF0000" * } * } * }; * })) * ``` */ [javascriptUmd]: async function ({ dictionary, file, options }) { const name = file.options?.name || '_styleDictionary'; const { formatting } = options; const header = await fileHeader({ file, formatting: getFormattingCloneWithoutPrefix(formatting), options, }); const content = header + '(function(root, factory) {\n' + ' if (typeof module === "object" && module.exports) {\n' + ' module.exports = factory();\n' + ' } else if (typeof exports === "object") {\n' + ' exports["' + name + '"] = factory();\n' + ' } else if (typeof define === "function" && define.amd) {\n' + ' define([], factory);\n' + ' } else {\n' + ' root["' + name + '"] = factory();\n' + ' }\n' + '}(this, function() {\n' + ' return ' + JSON.stringify(dictionary.tokens, null, 2) + ';\n' + '}))\n'; return formatJS(content); }, /** * Creates a ES6 module of the style dictionary. * * ```json * { * "platforms": { * "js": { * "transformGroup": "js", * "files": [ * { * "format": "javascript/es6", * "destination": "colors.js", * "filter": { * "type": "color" * } * } * ] * } * } * } * ``` * * @memberof Formats * @kind member * @example * ```js * export const ColorBackgroundBase = '#ffffff'; * export const ColorBackgroundAlt = '#fcfcfcfc'; * ``` */ [javascriptEs6]: async function ({ dictionary, file, options }) { const { formatting } = options; const header = await fileHeader({ file, formatting: getFormattingCloneWithoutPrefix(formatting), options, }); const content = [ header, dictionary.allTokens.map((token) => { const value = JSON.stringify(options.usesDtcg ? token.$value : token.value); const comment = options.usesDtcg ? token.$description : token.comment; const to_ret = `export const ${token.name} = ${value};`; const format = { commentStyle: short, indentation: '', ...formatting, }; return comment ? addComment(to_ret, comment, format) : to_ret; }), ] .flat() .join('\n'); return formatJS(content); }, /** * Creates a ES6 module with the whole style dictionary * * @memberof Formats * @kind member * @example * ```js * export default { * "color": { * "base": { * "red": "#ff0000" * } * } * } * ``` */ [javascriptEsm]: async function ({ dictionary, file, options }) { const { formatting, minify = false, flat = false } = options; let { tokens } = dictionary; tokens = stripMetaProps( tokens, /** @type {LocalOptions & Config & { stripMeta: boolean | StripMetaOptions}} */ (options), ); const header = await fileHeader({ file, formatting: getFormattingCloneWithoutPrefix(formatting), options, }); let dictionaryString; if (flat) { dictionaryString = JSON.stringify( Object.fromEntries( dictionary.allTokens.map((token) => [ token.name, options.usesDtcg ? token.$value : token.value, ]), ), null, 2, ); } else { dictionaryString = JSON.stringify( minify ? minifyDictionary(tokens, options.usesDtcg) : tokens, null, 2, ); } const content = `${header}export default ${dictionaryString};\n`; return formatJS(content); }, // TypeScript declarations /** * Creates TypeScript declarations for ES6 modules * * ```json * { * "platforms": { * "ts": { * "transformGroup": "js", * "files": [ * { * "format": "javascript/es6", * "destination": "colors.js" * }, * { * "format": "typescript/es6-declarations", * "destination": "colors.d.ts" * } * ] * } * } * } * ``` * * @memberof Formats * @kind member * @example * ```typescript * export const ColorBackgroundBase : string; * export const ColorBackgroundAlt : string; * ``` */ [typescriptEs6Declarations]: async function ({ dictionary, file, options }) { const { formatting } = options; const header = await fileHeader({ file, formatting: getFormattingCloneWithoutPrefix(formatting), options, }); const content = [ header, dictionary.allTokens.map((token) => { const typescriptType = getTypeScriptType( options.usesDtcg ? token.$value : token.value, options, ); const comment = options.usesDtcg ? token.$description : token.comment; const to_ret = `export const ${token.name} : ${typescriptType};`; const format = { commentPosition: above, commentStyle: long, indentation: '', ...formatting, }; return comment ? addComment(to_ret, comment, format) : to_ret; }), ] .flat() .join('\n'); return formatJS(content, true); }, /** * Creates TypeScript declarations for CommonJS module * * ```json * { * "platforms": { * "ts": { * "transformGroup": "js", * "files": [ * { * "format": "javascript/module", * "destination": "colors.js" * }, * { * "format": "typescript/module-declarations", * "destination": "colors.d.ts" * } * ] * } * } * } * ``` * * @memberof Formats * @kind member * @example * ```typescript * export default tokens; * declare interface DesignToken { value: string; name?: string; path?: string[]; comment?: string; attributes?: any; original?: any; } * declare const tokens: { * "color": { * "red": DesignToken * } * } * ``` * * As you can see above example output this does not generate 100% accurate d.ts. * This is a compromise between of what style-dictionary can do to help and not bloating the library with rarely used dependencies. * * Thankfully you can extend style-dictionary very easily: * * ```js * const JsonToTS = require('json-to-ts'); * StyleDictionaryPackage.registerFormat({ * name: 'typescript/accurate-module-declarations', * format: function({ dictionary }) { * return 'declare const root: RootObject\n' + * 'export default root\n' + * JsonToTS(dictionary.tokens).join('\n'); * }, * }); * ``` */ [typescriptModuleDeclarations]: async function ({ dictionary, file, options }) { const { moduleName = `tokens` } = options; /** * @param {Tokens} obj * @returns */ function treeWalker(obj) { let type = Object.create(null); const propToCheck = options.usesDtcg ? '$value' : 'value'; if (Object.hasOwn(obj, propToCheck)) { type = 'DesignToken'; } else { for (let k in obj) if (Object.hasOwn(obj, k)) { switch (typeof obj[k]) { case 'object': type[k] = treeWalker(obj[k]); } } } return type; } // TODO: find a browser+node compatible way to read from '../../types/DesignToken.d.ts' const designTokenInterface = `interface DesignToken { ${options.usesDtcg ? '$' : ''}value?: any; ${options.usesDtcg ? '$' : ''}type?: string; ${options.usesDtcg ? '$description?: string;' : 'comment?: string;'} name?: string; themeable?: boolean; attributes?: Record<string, unknown>; [key: string]: any; }`; const { formatting } = options; const header = await fileHeader({ file, formatting: getFormattingCloneWithoutPrefix(formatting), options, }); const output = header + `export default ${moduleName}; declare ${designTokenInterface} declare const ${moduleName}: ${JSON.stringify(treeWalker(dictionary.tokens), null, 2)}`; // JSON stringify will quote strings, because this is a type we need // it unquoted. const content = output.replace(/"DesignToken"/g, 'DesignToken') + '\n'; return formatJS(content, true); }, // Android templates /** * Creates a [resource](https://developer.android.com/guide/topics/resources/providing-resources) xml file. It is recommended to use a filter with this format * as it is generally best practice in Android development to have resource files * organized by type (color, dimension, string, etc.). However, a resource file * with mixed resources will still work. * * This format will try to use the proper resource type for each token based on * the category (color => color, size => dimen, etc.). However if you want to * force a particular resource type you can provide a 'resourceType' attribute * on the file configuration. You can also provide a 'resourceMap' if you * don't use Style Dictionary's built-in CTI structure. * * @memberof Formats * @kind member * @example * ```xml * <?xml version="1.0" encoding="UTF-8"?> * <resources> * <color name="color_base_red_5">#fffaf3f2</color> * <color name="color_base_red_30">#fff0cccc</color> * <dimen name="size_font_base">14sp</color> * ``` */ [androidResources]: async function ({ dictionary, file, options }) { const { formatting } = options; const header = await fileHeader({ file, commentStyle: xml, formatting: getFormattingCloneWithoutPrefix(formatting), options, }); return androidResourcesTemplate({ dictionary, file, header, options }); }, /** * Creates a color resource xml file with all the colors in your style dictionary. * * It is recommended to use the 'android/resources' format with a custom filter * instead of this format: * * ```javascript * format: formats.androidResources, * filter: { * attributes: { category: 'color' } * } * ``` * * @memberof Formats * @kind member * @example * ```xml * <?xml version="1.0" encoding="UTF-8"?> * <resources> * <color name="color_base_red_5">#fffaf3f2</color> * <color name="color_base_red_30">#fff0cccc</color> * <color name="color_base_red_60">#ffe19d9c</color> * ``` */ [androidColors]: async function ({ dictionary, options, file }) { const { formatting } = options; const header = await fileHeader({ file, commentStyle: xml, formatting: getFormattingCloneWithoutPrefix(formatting), options, }); return androidColorsTemplate({ dictionary, options, header }); }, /** * Creates a dimen resource xml file with all the sizes in your style dictionary. * * It is recommended to use the 'android/resources' format with a custom filter * instead of this format: * * ```javascript * format: formats.androidResources, * filter: { * attributes: { category: 'size' } * } * ``` * * @memberof Formats * @kind member * @example * ```xml * <?xml version="1.0" encoding="UTF-8"?> * <resources> * <dimen name="size_padding_tiny">5.00dp</dimen> * <dimen name="size_padding_small">10.00dp</dimen> * <dimen name="size_padding_medium">15.00dp</dimen> * ``` */ [androidDimens]: async function ({ dictionary, options, file }) { const { formatting } = options; const header = await fileHeader({ file, commentStyle: xml, formatting: getFormattingCloneWithoutPrefix(formatting), options, }); return androidDimensTemplate({ dictionary, options, header }); }, /** * Creates a dimen resource xml file with all the font sizes in your style dictionary. * * It is recommended to use the 'android/resources' format with a custom filter * instead of this format: * * ```javascript * format: formats.androidResources, * filter: { * attributes: { category: 'size' } * } * ``` * * @memberof Formats * @kind member * @example * ```xml * <?xml version="1.0" encoding="UTF-8"?> * <resources> * <dimen name="size_font_tiny">10.00sp</dimen> * <dimen name="size_font_small">13.00sp</dimen> * <dimen name="size_font_medium">15.00sp</dimen> * ``` */ [androidFontDimens]: async function ({ dictionary, options, file }) { const { formatting } = options; const header = await fileHeader({ file, commentStyle: xml, formatting: getFormattingCloneWithoutPrefix(formatting), options, }); return androidFontDimensTemplate({ dictionary, options, header }); }, /** * Creates a resource xml file with all the integers in your style dictionary. It filters your * design tokens by `token.type === 'time'` * * It is recommended to use the 'android/resources' format with a custom filter * instead of this format: * * ```javascript * format: formats.androidResources, * filter: { * attributes: { category: 'time' } * } * ``` * * @memberof Formats * @kind member * @todo Update the filter on this. * @example * ```xml * <?xml version="1.0" encoding="UTF-8"?> * <resources> * <integer name="time_duration_short">1000</integer> * <integer name="time_duration_medium">2000</integer> * <integer name="time_duration_long">4000</integer> * ``` */ [androidIntegers]: async function ({ dictionary, options, file }) { const { formatting } = options; const header = await fileHeader({ file, commentStyle: xml, formatting: getFormattingCloneWithoutPrefix(formatting), options, }); return androidIntegersTemplate({ dictionary, options, header }); }, /** * Creates a resource xml file with all the strings in your style dictionary. Filters your * design tokens by `token.type === 'content'` * * It is recommended to use the 'android/resources' format with a custom filter * instead of this format: * * ```javascript * format: formats.androidResources, * filter: { * attributes: { category: 'content' } * } * ``` * * @memberof Formats * @kind member * @example * ```xml * <?xml version="1.0" encoding="UTF-8"?> * <resources> * <string name="content_icon_email">&#xE001;</string> * <string name="content_icon_chevron_down">&#xE002;</string> * <string name="content_icon_chevron_up">&#xE003;</string> * ``` */ [androidStrings]: async function ({ dictionary, options, file }) { const { formatting } = options; const header = await fileHeader({ file, commentStyle: xml, formatting: getFormattingCloneWithoutPrefix(formatting), options, }); return androidStringsTemplate({ dictionary, options, header }); }, // Compose templates /** * Creates a Kotlin file for Compose containing an object with a `val` for each property. * * @memberof Formats * @kind member * @typedef {Object} composeObjectOpts * @property {String} [composeObjectOpts.className] The name of the generated Kotlin object * @property {String} [composeObjectOpts.packageName] The package for the generated Kotlin object * @property {String[]} [composeObjectOpts.import=['androidx.compose.ui.graphics.Color', 'androidx.compose.ui.unit.*']] - Modules to import. Can be a string or array of strings * @property {boolean} [composeObjectOpts.showFileHeader=true] - Whether or not to include a comment that has the build date * @property {OutputReferences} [composeObjectOpts.outputReferences=false] - Whether or not to keep [references](/#/formats?id=references-in-output-files) (a -> b -> c) in the output. * @param {FormatArgs & { options?: composeObjectOpts }} options * @example * ```kotlin * package com.example.tokens; * * import androidx.compose.ui.graphics.Color * * object StyleDictionary { * val colorBaseRed5 = Color(0xFFFAF3F2) * } * ``` */ [composeObject]: async function ({ dictionary, options, file }) { const { allTokens, tokens, unfilteredTokens } = dictionary; const { outputReferences, formatting, usesDtcg } = options; const formatProperty = createPropertyFormatter({ outputReferences, dictionary, formatting: { suffix: '', commentStyle: /** @type {'short' | 'long' | 'none'} */ (none), // We will add the comment in the format template ...formatting, }, usesDtcg, }); let sortedTokens; if (outputReferences) { sortedTokens = [...allTokens].sort(sortByReference(tokens, { unfilteredTokens })); } else { sortedTokens = [...allTokens].sort(sortByName); } options = setComposeObjectProperties(options); const header = await fileHeader({ file, commentStyle: short, formatting: getFormattingCloneWithoutPrefix(formatting), options, }); return composeObjectTemplate({ allTokens: sortedTokens, options, formatProperty, header }); }, // iOS templates /** * Creates an Objective-C header file with macros for design tokens * * @memberof Formats * @kind member * @example * ```objective-c * #import <Foundation/Foundation.h> * #import <UIKit/UIKit.h> * * #define ColorFontLink [UIColor colorWithRed:0.00f green:0.47f blue:0.80f alpha:1.00f] * #define SizeFontTiny 176.00f * ``` */ [iosMacros]: async function ({ dictionary, options, file }) { const { formatting } = options; const header = await fileHeader({ file, commentStyle: short, formatting: getFormattingCloneWithoutPrefix(formatting), options, }); return macrosTemplate({ dictionary, options, file, header }); }, /** * Creates an Objective-C plist file * * @memberof Formats * @kind member * @todo Fix this template and add example and usage */ [iosPlist]: async function ({ dictionary, options, file }) { const { formatting } = options; const header = await fileHeader({ file, commentStyle: xml, formatting: getFormattingCloneWithoutPrefix(formatting), options, }); return plistTemplate({ dictionary, options, header }); }, /** * Creates an Objective-C implementation file of a style dictionary singleton class * * @memberof Formats * @kind member * @todo Add example and usage */ [iosSingletonM]: async function ({ dictionary, options, file }) { const { formatting } = options; const header = await fileHeader({ file, commentStyle: short, formatting: getFormattingCloneWithoutPrefix(formatting), options, }); return iosSingletonMTemplate({ dictionary, options, file, header }); }, /** * Creates an Objective-C header file of a style dictionary singleton class * * @memberof Formats * @kind member * @todo Add example and usage */ [iosSingletonH]: async function ({ file, options }) { const { formatting } = options; const header = await fileHeader({ file, commentStyle: short, formatting: getFormattingCloneWithoutPrefix(formatting), options, }); return iosSingletonHTemplate({ file, options, header }); }, /** * Creates an Objective-C header file of a static style dictionary class * * @memberof Formats * @kind member * @todo Add example and usage */ [iosStaticH]: async function ({ dictionary, file, options }) { const { formatting } = options; const header = await fileHeader({ file, commentStyle: short, formatting: getFormattingCloneWithoutPrefix(formatting), options, }); return iosStaticHTemplate({ dictionary, file, options, header }); }, /** * Creates an Objective-C implementation file of a static style dictionary class * * @memberof Formats * @kind member * @todo Add example and usage */ [iosStaticM]: async function ({ dictionary, options, file }) { const { formatting } = options; const header = await fileHeader({ file, commentStyle: short, formatting: getFormattingCloneWithoutPrefix(formatting), options, }); return iosStaticMTemplate({ dictionary, options, file, header }); }, /** * Creates an Objective-C header file of a color class * * @memberof Formats * @kind memberx * @todo Add example and usage */ [iosColorsH]: async function ({ dictionary, options, file }) { const { formatting } = options; const header = await fileHeader({ file, commentStyle: short, formatting: getFormattingCloneWithoutPrefix(formatting), options, }); return iosColorsHTemplate({ dictionary, file, options, header }); }, /** * Creates an Objective-C implementation file of a color class * * @memberof Formats * @kind member * @todo Add example and usage */ [iosColorsM]: async function ({ dictionary, options, file }) { const { formatting } = options; const header = await fileHeader({ file, commentStyle: short, formatting: getFormattingCloneWithoutPrefix(formatting), options, }); return iosColorsMTemplate({ dictionary, options, file, header }); }, /** * Creates an Objective-C header file of strings * * @memberof Formats * @kind member * @todo Add example and usage */ [iosStringsH]: async function ({ dictionary, file, options }) { const { formatting } = options; const header = await fileHeader({ file, commentStyle: short, formatting: getFormattingCloneWithoutPrefix(formatting), options, }); return iosStringsHTemplate({ dictionary, file, options, header }); }, /** * Creates an Objective-C implementation file of strings * * @memberof Formats * @kind member * @todo Add example and usage */ [iosStringsM]: async function ({ dictionary, options, file }) { const { formatting } = options; const header = await fileHeader({ file, commentStyle: short, formatting: getFormattingCloneWithoutPrefix(formatting), options, }); return iosStringsMTemplate({ dictionary, options, file, header }); }, /** * Creates a Swift implementation file of a class with values. It adds default `class` object type, `public` access control and `UIKit` import. * * @memberof Formats * @kind member * @typedef {Object} iosSwiftClassOpts * @property {String} [iosSwiftClassOpts.accessControl='public'] - Level of [access](https://docs.swift.org/swift-book/LanguageGuide/AccessControl.html) of the generated swift object * @property {String[]} [iosSwiftClassOpts.import='UIKit'] - Modules to import. Can be a string or array of strings * @property {String} [iosSwiftClassOpts.className] - The name of the generated Swift class * @property {boolean} [iosSwiftClassOpts.showFileHeader=true] - Whether or not to include a comment that has the build date * @property {OutputReferences} [iosSwiftClassOpts.outputReferences=false] - Whether or not to keep [references](/#/formats?id=references-in-output-files) (a -> b -> c) in the output. * @param {FormatArgs & { options?: iosSwiftClassOpts }} options * @example * ```swift * public class StyleDictionary { * public static let colorBackgroundDanger = UIColor(red: 1.000, green: 0.918, blue: 0.914, alpha: 1) * } * ``` */ [iosSwiftClassSwift]: async function ({ dictionary, options, file, platform }) { const { allTokens, tokens, unfilteredTokens } = dictionary; const { outputReferences, formatting, usesDtcg } = options; options = setSwiftFileProperties(options, 'class', platform.transformGroup); const formatProperty = createPropertyFormatter({ outputReferences, dictionary, formatting: { suffix: '', ...formatting, }, usesDtcg, }); let sortedTokens; if (outputReferences) { sortedTokens = [...allTokens].sort(sortByReference(tokens, { unfilteredTokens, usesDtcg })); } else { sortedTokens = [...allTokens].sort(sortByName); } const header = await fileHeader({ file, commentStyle: short, formatting: getFormattingCloneWithoutPrefix(formatting), options, }); return iosSwiftAnyTemplate({ allTokens: sortedTokens, file, options, formatProperty, header }); }, /** * Creates a Swift implementation file of an enum with values. It adds default `enum` object type, `public` access control and `UIKit` import. * * @memberof Formats * @kind member * @typedef {Object} iosSwiftEnumOpts * @property {String} [iosSwiftEnumOpts.accessControl='public'] - Level of [access](https://docs.swift.org/swift-book/LanguageGuide/AccessControl.html) of the generated swift object * @property {String[]} [iosSwiftEnumOpts.import='UIKit'] - Modules to import. Can be a string or array of strings * @property {String} [iosSwiftEnumOpts.className] - The name of the generated Swift enum * @property {boolean} [iosSwiftEnumOpts.showFileHeader=true] - Whether or not to include a comment that has the build date * @property {OutputReferences} [iosSwiftEnumOpts.outputReferences=false] - Whether or not to keep [references](/#/formats?id=references-in-output-files) (a -> b -> c) in the output. * @param {FormatArgs & { options?: iosSwiftEnumOpts }} options * @example * ```swift * public enum StyleDictionary { * public static let colorBackgroundDanger = UIColor(red: 1.000, green: 0.918, blue: 0.914, alpha: 1) * } * ``` */ [iosSwiftEnumSwift]: async function ({ dictionary, options, file, platform }) { const { allTokens, tokens, unfilteredTokens } = dictionary; const { outputReferences, formatting, usesDtcg } = options; options = setSwiftFileProperties(options, 'enum', platform.transformGroup); const formatProperty = createPropertyFormatter({ outputReferences, dictionary, formatting: { suffix: '', ...formatting, }, usesDtcg, }); let sortedTokens; if (outputReferences) { sortedTokens = [...allTokens].sort(sortByReference(tokens, { unfilteredTokens, usesDtcg })); } else { sortedTokens = [...allTokens].sort(sortByName); } const header = await fileHeader({ file, commentStyle: short, formatting: getFormattingCloneWithoutPrefix(formatting), options, }); return iosSwiftAnyTemplate({ allTokens: sortedTokens, file, options, formatProperty, header }); }, /** * Creates a Swift implementation file of any given type with values. It has by default `class` object type, `public` access control and `UIKit` import. * * ```javascript * format: 'ios-swift/any.swift', * import: ['UIKit', 'AnotherModule'], * objectType: 'struct', * accessControl: 'internal', * ``` * * @memberof Formats * @kind member * @typedef {Object} iosSwiftAnyOpts * @property {string} [iosSwiftAnyOpts.accessControl='public'] - Level of [access](https://docs.swift.org/swift-book/LanguageGuide/AccessControl.html) of the generated swift object * @property {string[]} [iosSwiftAnyOpts.import='UIKit'] - Modules to import. Can be a string or array of strings * @property {String} [iosSwiftAnyOpts.className] - The name of the generated Swift object * @property {string} [iosSwiftAnyOpts.objectType='class'] - The type of the generated Swift object * @property {boolean} [iosSwiftAnyOpts.showFileHeader=true] - Whether or not to include a comment that has the build date * @property {OutputReferences} [iosSwiftAnyOpts.outputReferences=false] - Whether or not to keep [references](/#/formats?id=references-in-output-files) (a -> b -> c) in the output. * @param {FormatArgs & { options?: iosSwiftAnyOpts }} options * @example * ```swift * import UIKit * import AnotherModule * * internal struct StyleDictionary { * internal static let colorBackgroundDanger = UIColor(red: 1.000, green: 0.918, blue: 0.914, alpha: 1) * } * ``` */ [iosSwiftAnySwift]: async function ({ dictionary, options, file, platform }) { const { allTokens, tokens, unfilteredTokens } = dictionary; const { outputReferences, formatting, usesDtcg } = options; options = setSwiftFileProperties(options, options.objectType, platform.transformGroup); const formatProperty = createPropertyFormatter({ outputReferences, dictionary, formatting: { suffix: '', ...formatting, }, usesDtcg, }); let sortedTokens; if (outputReferences) { sortedTokens = [...allTokens].sort(sortByReference(tokens, { unfilteredTokens, usesDtcg })); } else { sortedTokens = [...allTokens].sort(sortByName); } const header = await fileHeader({ file, commentStyle: short, formatting: getFormattingCloneWithoutPrefix(formatting), options, }); return iosSwiftAnyTemplate({ allTokens: sortedTokens, file, options, formatProperty, header }); }, // Css templates /** * Creates CSS file with @font-face declarations * * @memberof Formats * @kind member * @todo Add example and usage */ [cssFonts]: ({ dictionary }) => cssFontsTemplate(dictionary.tokens), // Web templates /** * Creates a JSON file of the style dictionary. * * @memberof Formats * @kind member * @example * ```json * { * "color": { * "base": { * "red": { * "value": "#ff0000" * } * } * } * } * ``` */ [json]: function ({ dictionary, options }) { let { tokens } = dictionary; tokens = stripMetaProps( tokens, /** @type {LocalOptions & Config & { stripMeta: boolean | StripMetaOptions}} */ (options), ); return JSON.stringify(tokens, null, 2) + '\n'; }, /** * Creates a JSON file of the assets defined in the style dictionary. * * @memberof Formats * @kind member * @example * ```js * { * "asset": { * "image": { * "logo": { * "value": "assets/logo.png" * } * } * } * } * ``` */ [jsonAsset]: function ({ dictionary }) { return JSON.stringify({ asset: dictionary.tokens.asset }, null, 2); }, /** * Creates a JSON nested file of the style dictionary. * @memberof Formats * @kind member * @example * ```json * { * "color": { * "base": { * "red": "#ff0000" * } * } * } * ``` */ [jsonNested]: function ({ dictionary, options }) { return JSON.stringify(minifyDictionary(dictionary.tokens, options.usesDtcg), null, 2) + '\n'; }, /** * Creates a JSON flat file of the style dictionary. * * @memberof Formats * @kind member * @example * ```json * { * "color-base-red": "#ff0000" * } * ``` */ [jsonFlat]: function ({ dictionary, options }) { return ( '{\n' + dictionary.allTokens .map(function (token) { return ` "${token.name}": ${JSON.stringify( options.usesDtcg ? token.$value : token.value, )}`; }) .join(',\n') + '\n}' + '\n' ); }, /** * Creates a sketchpalette file of all the base colors * * @memberof Formats * @kind member * @example * ```json * { * "compatibleVersion": "1.0", * "pluginVersion": "1.1", * "colors": [ * "#ffffff", * "#ff0000", * "#fcfcfc" * ] * } * ``` */ [sketchPalette]: function ({ dictionary, options }) { const to_ret = { compatibleVersion: '1.0', pluginVersion: '1.1', /** @type {any[]} */ colors: [], }; to_ret.colors = dictionary.allTokens .filter(function (token) { return token.type === 'color'; }) .map(function (token) { return options.usesDtcg ? token.$value : token.value; }); return JSON.stringify(to_ret, null, 2) + '\n'; }, /** * Creates a sketchpalette file compatible with version 2 of * the sketchpalette plugin. To use this you should use the * 'color/sketch' transform to get the correct value for the colors. * * @memberof Formats * @kind member * @example * ```json * { * "compatibleVersion": "2.0", * "pluginVersion": "2.2", * "colors": [ * {name: "red", r: 1.0, g: 0.0, b: 0.0, a: 1.0}, * {name: "green", r: 0.0, g: 1.0, b: 0.0, a: 1.0}, * {name: "blue", r: 0.0, g: 0.0, b: 1.0, a: 1.0} * ] * } * ``` */ [sketchPaletteV2]: function ({ dictionary, options }) { const to_ret = { compatibleVersion: '2.0', pluginVersion: '2.2', colors: dictionary.allTokens.map(function (token) { // Merging the token's value, which should be an object with r,g,b