Detalhes do pacote

theo

salesforce-ux42.1kBSD-3-Clause8.1.5

Design Tokens formatter

css, design, properties, tokens

readme (leia-me)

Theo logo Theo

Build Status NPM version

Theo is an abstraction for transforming and formatting Design Tokens.

Looking for the gulp plugin?

As of Theo v6, the gulp plugin is distributed as a separate package: gulp-theo.

Example

# buttons.yml
props:
  button_background:
    value: "{!primary_color}"
imports:
  - ./aliases.yml
global:
  type: color
  category: buttons
# aliases.yml
aliases:
  primary_color:
    value: "#0070d2"
const theo = require("theo");

theo
  .convert({
    transform: {
      type: "web",
      file: "buttons.yml"
    },
    format: {
      type: "scss"
    }
  })
  .then(scss => {
    // $button-background: rgb(0, 112, 210);
  })
  .catch(error => console.log(`Something went wrong: ${error}`));

Transforms

Theo is divided into two primary features: transforms and formats.

Transforms are a named group of value transforms. Theo ships with several predefined transforms.

Name Value Transforms
raw []
web ['color/rgb']
ios ['color/rgb', 'relative/pixelValue', 'percentage/float']
android ['color/hex8argb', 'relative/pixelValue', 'percentage/float']

Value Transforms

Value transforms are used to conditionaly transform the value of a property. Below are the value transforms that ship with Theo along with the predicate that triggers them.

Name Predicate Description
color/rgb prop.type === 'color' Convert to rgb
color/hex prop.type === 'color' Convert to hex
color/hex8rgba prop.type === 'color' Convert to hex8rgba
color/hex8argb prop.type === 'color' Convert to hex8argb
percentage/float /%/.test(prop.value) Convert a percentage to a decimal percentage
relative/pixel isRelativeSpacing Convert a r/em value to a pixel value
relative/pixelValue isRelativeSpacing Convert a r/em value to a pixel value (excluding the px suffix)

Custom Transforms / Value Transforms

/*
{
  CUSTOM_EASING: {
    type: 'easing',
    value: [1,2,3,4]
  }
}
*/

theo.registerValueTransform(
  // Name to be used with registerTransform()
  "easing/web",
  // Determine if the value transform
  // should be run on the specified prop
  prop => prop.get("type") === "easing",
  // Return the new value
  prop => {
    const [x1, y1, x2, y2] = prop.get("value").toArray();
    return `cubic-bezier(${x1}, ${y1}, ${x2}, ${y2})`;
  }
);

// Override the default "web" transform
theo.registerTransform("web", ["color/rgb", "easing/web"]);

Formats

Theo ships with the following predefined formats.

custom-properties.css

:root {
  /* If prop has 'comment' key, that value will go here. */
  --prop-name: PROP_VALUE;
}

cssmodules.css

/* If prop has 'comment' key, that value will go here. */
@value prop-name: PROP_VALUE;

scss

// If prop has 'comment' key, that value will go here.
$prop-name: PROP_VALUE;

sass

// If prop has 'comment' key, that value will go here.
$prop-name: PROP_VALUE

less

// If prop has 'comment' key, that value will go here.
@prop-name: PROP_VALUE;

styl

// If prop has 'comment' key, that value will go here.
$prop-name = PROP_VALUE

map.scss

$file-name-map: (
  // If prop has 'comment' key, that value will go here.
  "prop-name": (PROP_VALUE),
);

map.variables.scss

$file-name-map: (
  // If prop has 'comment' key, that value will go here.
  "prop-name": ($prop-name)
);

list.scss

$file-name-list: (
  // If prop has 'comment' key, that value will go here.
  "prop-name"
);

module.js

// If prop has 'comment' key, that value will go here.
export const propName = "PROP_VALUE";

common.js

module.exports = {
  // If prop has 'comment' key, that value will go here.
  propName: "PROP_VALUE"
};

html

// When passing "format" options to theo.convert(), this format can be
// passed with an additional options object.
let formatOptions = {
  type: "html",
  options: {
    transformPropName: name => name.toUpperCase()
  }
};

Configurable options

Option Type Default Description
transformPropName function lodash/camelCase Converts name to camel case.

Supported categories

Tokens are grouped by category then categories are conditionally rendered under a human-friendly display name. Tokens with category values not in this list will still be converted and included in the generated output for all other formats.

Category Friendly Name
spacing Spacing
sizing Sizing
font Fonts
font-style Font Styles
font-weight Font Weights
font-size Font Sizes
line-height Line Heights
font-family Font Families
border-style Border Styles
border-color Border Colors
radius Radius
border-radius Border Radii
hr-color Horizontal Rule Colors
background-color Background Colors
gradient Gradients
background-gradient Background Gradients
drop-shadow Drop Shadows
box-shadow Box Shadows
inner-shadow Inner Drop Shadows
text-color Text Colors
text-shadow Text Shadows
time Time
media-query Media Queries

json

{
  "PROP_NAME": "PROP_VALUE"
}

raw.json

{
  props: {
    PROP_NAME: {
      value: "PROP_VALUE",
      type: "PROP_TYPE",
      category: "PROP_CATEGORY"
    }
  }
}

ios.json

{
  properties: [
    {
      name: "propName",
      value: "PROP_VALUE",
      type: "PROP_TYPE",
      category: "PROP_CATEGORY"
    }
  ]
}

android.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
  <color name="PROP_NAME" category="PROP_CATEGORY">PROP_VALUE</color>
  <dimen name="PROP_NAME" category="PROP_CATEGORY">PROP_VALUE</dimen>
  <string name="PROP_NAME" category="PROP_CATEGORY">PROP_VALUE</string>
  <integer name="PROP_NAME" category="PROP_CATEGORY">PROP_VALUE</integer>
  <property name="PROP_NAME" category="PROP_CATEGORY">PROP_VALUE</property>
</resources>

aura.tokens

<aura:tokens>
  <aura:token name="propName" value="PROP_VALUE" />
</aura:tokens>

Custom Format (Handlebars)

const theo = require("theo");

theo.registerFormat(
  "array.js",
  `
  // Source: {{stem meta.file}}
  module.exports = [
    {{#each props as |prop|}}
      {{#if prop.comment}}{{{commoncomment prop.comment}}}{{/if}}
      ['{{camelcase prop.name}}', '{{prop.value}}'],
    {{/each}}
  ]
`
);

A plethora of handlebars helpers, such as camelcase and stem, are available and will assist in formatting strings in templates.

Custom Format (function)

You may also register a format using a function:

const camelCase = require("lodash/camelCase");
const path = require("path");
const theo = require("theo");

theo.registerFormat("array.js", result => {
  // "result" is an Immutable.Map
  // https://facebook.github.io/immutable-js/
  return `
    module.exports = [
      // Source: ${path.basename(result.getIn(["meta", "file"]))}
      ${result
        .get("props")
        .map(
          prop => `
        ['${camelCase(prop.get("name"))}', '${prop.get("value")}'],
      `
        )
        .toJS()}
    ]
  `;
});

API

type ConvertOptions = {
  transform: TransformOptions,
  format: FormatOptions,
  /*
    This option configures theo to resolve aliases. It is set (true) by default and
    currently CANNOT be disabled.
  */
  resolveAliases?: boolean,

  // This option configures theo to resolve aliases in metadata. This is off (false) by default.
  resolveMetaAliases?: boolean
}

type TransformOptions = {
  // If no "type" is specified, values will not be transformed
  type?: string,
  // Path to a token file
  // or just a filename if using the "data" option
  file: string,
  // Pass in a data string instead of reading from a file
  data?: string
}

type FormatOptions = {
  type: string,
  // Available to the format function/template
  options?: object
}

type Prop = Immutable.Map
type Result = Immutable.Map

theo.convert(options: ConvertOptions): Promise<string>

theo.convertSync(options: ConvertOptions): string

theo.registerFormat(
  name: string,
  // Either a handlebars template string
  // or a function that returns a string
  format: string | (result: Result) => string
): void

theo.registerValueTransform(
  // Referenced in "registerTransform"
  name: string,
  // Indicate if the transform should run for the provided prop
  predicate: (prop: Prop) => boolean,
  // Return the new "value"
  transform: (prop: Prop) => any
): void

theo.registerTransform(
  name: string,
  // An array of registered value transforms
  valueTransforms: Array<string>
): void

CLI

Please refer to the documentation of the CLI

Design Tokens

Theo consumes Design Token files which are a central location to store design related information such as colors, fonts, widths, animations, etc. These raw values can then be transformed and formatted to meet the needs of any platform.

Let's say you have a web, native iOS, and native Android application that would like to share information such as background colors.

The web might like to consume the colors as hsla values formatted as Sass variables in an .scss file.

iOS might like rgba values formatted as .json.

Finally, Android might like 8 Digit Hex (AARRGGBB) values formatted as .xml.

Instead of hard coding this information in each platform/format, Theo can consume the centralized Design Tokens and output files for each platform.

Spec

A Design Token file is written in either JSON (JSON5 supported) or YAML and should conform to the following spec:

{
  // Required
  // A map of property names and value objects
  props: {
    color_brand: {
      // Required
      // Can be any valid JSON value
      value: "#ff0000",

      // Required
      // Describe the type of value
      // [color|number|...]
      type: "color",

      // Required
      // Describe the category of this property
      // Often used for style guide generation
      category: "background",

      // Optional
      // This value will be included during transform
      // but excluded during formatting
      meta: {
        // This value might be needed for some special transform
        foo: "bar"
      }
    }
  },

  // Optional
  // Alternatively, you can define props as an array
  // Useful for maintaining source order in output tokens
  props: [
    {
      // Required
      name: "color_brand"

      // All other properties same as above
    }
  ],

  // Optional
  // This object will be merged into each property
  // Values defined on a property level will take precedence
  global: {
    category: "some-category",
    meta: {
      foo: "baz"
    }
  },

  // Optional
  // Share values across multiple props
  // Aliases are resolved like: {!sky}
  aliases: {
    sky: "blue",
    grass: {
      value: "green",
      yourMetadata: "How grass looks"
    }
  },

  // Optional
  // Array of design token files to be imported
  // "aliases" will be imported as well
  // "aliases" will already be resolved
  // "global" will already be merged into each prop
  // Imports resolve according to the Node.js module resolution algorithm:
  // https://nodejs.org/api/modules.html#modules_all_together
  imports: [
    // Absolute file path
    "/home/me/file.json",
    // Relative file path: resolves from the directory of the file where the import occurs
    "./some/dir/file.json",
    // Module path
    "some-node-module"
  ]
}

changelog (log de mudanças)

Change Log

All notable changes to this project will be documented in this file.

The format is based on Keep a Changelog and this project adheres to Semantic Versioning.

8.1.1

  • Added resolveMetaAliases option to resolve aliases in metadata. - see #172

8.0.1

  • Upgraded vulnerable dependency

8.0.0

⚠️ Breaking changes

  • [android.xml] Moves value from from attribute (#144)
  • [android.xml] Output dp values (#156)
  • [android.xml] Use the right Android XML tags (#154)
  • [Android] Convert invalid hyphens to underscores for Android (#159)

7.0.1

  • Correctly handle number types

7.0.0

⚠️ Breaking changes

  • Due to how imports are resolved, my-aliases will now point to ./node_modules/my-aliases instead of ./my-aliases. Local imports should include the relative path (./, ../)

6.0.0

Theo v6 is a complete re-write that allowed us to fix some long standing issues and separate the core engine from the Gulp plugin.

  • Handlebars support for registerFormat() (@kaelig)
  • Formats can now receive additional options
  • Added new formats
  • CLI support (@nickbalestra @tomger)
  • Array support for "props" (as long as each prop has a "name" key) which will preserve prop order in the final output
  • Bug fixes and documentation for several existing formats (@corygibbons @dennisreimann @micahwood @didoo)

Big thanks to @kaelig for helping kickstart this release and to all the alpha/beta testers who reported issues and fixed bugs!

⚠️ Breaking changes

  • Aliases are only available to files that directly import them – see #101
  • The Gulp plugin is in a separate gulp-theo package
  • Renamed the .meta key to meta
  • Removed the includeRawValue option in favor of always adding an originalValue key in each transformed prop

Migration guide

If you would like to keep using Theo as a Gulp plugin with Theo v6, here is what a typical update would look like:

<= 5.0.0

npm install theo --save-dev
const gulp = require("gulp");
const theo = require("theo");

// Transform design/props.yml to dist/props.scss
gulp
  .src("design/props.yml")
  .pipe(theo.plugins.transform("web"))
  .pipe(theo.plugins.format("scss"))
  .pipe(gulp.dest("dist"));

>= 6.0.0

The Gulp plugin is in a separate gulp-theo package:

npm install gulp-theo --save-dev
const gulp = require("gulp");
const theo = require("gulp-theo");

// Transform design/props.yml to dist/props.scss
gulp
  .src("design/props.yml")
  .pipe(
    theo.plugin({
      transform: { type: "web" },
      format: { type: "scss" }
    })
  )
  .pipe(gulp.dest("dist"));

5.0.0

Theo v5.0.0 comes with a ton of improvements and drops support for Node.js < 6.

A massive thanks to the contributors who made this release possible, especially to @micahgodbolt.

View all pull requests merged in v5.0.0

  • Support for *.yaml files (#60)
  • Support for JSON5 syntax (an improvement on JSON)
  • Improved styleguide theme (#56)
  • Aliases can reference other aliases (#69)
  • Users may now pre-process the input with custom functions (#71)
  • Improved test results and moved test suite to Jest
  • JavaScript is now linted using our internal standards
  • Removed React from devDependencies
  • Added an EditorConfig file
  • Inline comments in the output of popular formats (e.g. scss) (fixes #58)
  • Breaking change: removed the color/hex8 transform. Instead, use color/hex8argb in Android, and color/hex8rgba in, for example, CSS level 4 values
  • Breaking change: Node.js 6 and up is required
  • Various tweaks and fixes

⚠️ Breaking changes

Dropped support for Node.js v4

Theo v5 is now compatible with Node.js v6.3 and up, dropping support for Node.js v4.

Error handling

Pointing to a non-existing alias now throws an error instead of failing silently.

Kebab case

Lodash's implementation of kebabCase was dropped because it separated numbers as words:

  • Lodash: A1 -> a-1
  • Theo: A1 -> a1

4.x.x

See https://github.com/salesforce-ux/theo/releases