Code Monkey home page Code Monkey logo

theo's Introduction

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"
  ]
}

theo's People

Contributors

aputinski avatar beauroberts avatar ch-rhymoore avatar corygibbons avatar craveytrain avatar dennisreimann avatar dependabot-preview[bot] avatar gerald-fullam-kr avatar greenkeeper[bot] avatar iilei avatar kaelig avatar kylegach avatar lautr avatar lmnelson avatar micahwood avatar notbear avatar shaunbent avatar stevenbenisek avatar studiospindle avatar svc-scm avatar tomger avatar trazek avatar yashwanth777 avatar zahnster avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

theo's Issues

json argument format of a "format" doesn't match the README

In the readme:

// Here is the layout of the json argument
{
  // An object containing the transformed properties
  "props": {},
  // An array of the keys for easy iteration
  "propKeys": []
}

my main file looks like this:

#!/usr/bin/env node

var gulp = require('gulp');
var theo = require('theo');

// formats
theo.registerFormat('test', function(json, options) {
  console.log(json); // <-- logging here
  return json;
});

// ios
gulp.src('variables/brand-colors.json')
  .pipe(theo.plugins.transform('ios'))
  .pipe(theo.plugins.format('test'))
  .pipe(gulp.dest('dist'));

when I console log the json file I get the format

{
  aliases: {},
  // An ARRAY containing the transformed properties
  "props": [], // <-- array
  // An array of the keys for easy iteration
  "propKeys": []
}

an item in the array looks like:

 { value: 'rgb(223, 224, 230)',
   type: 'color',
   category: 'color',
   name: 'GRAY_90' } 

Or did I misinterpret something?

Cheers :)

GH repo description

I see that the repo description still says A set of Gulp plugins for transforming and formatting Design Tokens perhaps worth updating it to Theo is a an abstraction for transforming and formatting Design Tokens.?

Just noticed by twitting that the shared link contain that old meta description.

Show decent error message when custom template JSON output fails

If the custom template is JSON theo tries to prettify the output and should show a better error message of the JSON isn't valid.

Current error reads as:

[15:37:03] SyntaxError: Unexpected token t
    at Object.parse (native)
    at convertTheme (/Users/srohde/Documents/s1variables/node_modules/theo/lib/theo.js:262:45)

Support Import Pre-process function

This might take a bit of work, but thought I'd suggest it at least.

props:
  myComponent:
    button:
      foreground:
        base: "#333"
        hover: "#000"
        active: "#333"
        disabled: "#777"
      background:
        base: "#fff"
        hover: "#ddd"
        active: "#fff"
        disabled: "#ccc"

output of this would be the same as if I strung all of them together (or delimiter of choice).

  myComponent_button_foreground: "#333"
  myComponent_button_foreground_hover: "#000"
  ...

The gist here would be that if a prop is an object, that object is passed into the parsing function along with the parent string. And if that next value is an object, it's passed down again with the parent appended to the parent string.

Turtles all the way down.

Allow for `line-height` options.

Right now we allow for united line-heights (ie: 24px, 1.5em).

Some people (including myself) prefer unitless line-heights: 1.5.

On line 415:

<div class="line-height-example" style="line-height: {{value}}; background-size: 100% {{value}}">

We need a way for Theo to add an em at the end of {{value}} if there is not a unit.

So: if no units (1.5), make the output have a unit (1.5em).

Alternatively, some people prefer percentages. If a percentage is used (example: 150%), we should translate that to 1.5em.

What are aliases?

I find myself pretty confused by aliases and uncertain where to get clarification on their purpose.

An alias, in US English, means "a name someone is otherwise called or also known as, an additional name that a person sometimes uses." In people terms, an alias is not the legal or official name of the person--basically, it's not their canonical name within the social system.

So when I started experimenting, I approached it from that perspective:

{
  "props": {
    "cloudBurstBlue": {
      "value": "#20315A"
    }
  },
  "aliases": {
    "blue": "cloudBurstBlue"
  }
}

My intention was to set up a canonical name for a color, e.g. cloudBurstBlue, within the design system. Then, I wished to create aliases for that color, alternative names by which that color might called at other times within implementations, e.g. blue.

The output I got was this:

:root {
  --cloud-burst-blue: #20315A;
}

No alias in the output, which was disconcerting. Can't use the alternative name if it's not present, after all. In trying to figure out what I did wrong, I began to get the impression that values, if I wish to reuse them under other names, have to be set up in the aliases--exactly the opposite way from my intuition and with a special syntax:

{
  "props": {
    "blue": {
      "value": "{!cloudBurstBlue}"
    }
  },
  "aliases": {
    "cloudBurstBlue": {
      "value": "#20315A"
    }
  }
}

Then the output I got was this:

:root {
  --blue: #20315A;
}

I only get one name in my result again. So I infer the alias feature is a preprocessor-style feature; it gets compiled away by Theo. (Which I also found somewhat unexpected for no particular reason.)

Is my new understanding of "aliases" as a compile-time-only feature that allows an "alias" to be used to reference its own value an accurate one?

Error: Cannot find module 'json5'

5.0.0-beta.6 gives me the following error when running gulp:

Error: Cannot find module 'json5'
    at Function.Module._resolveFilename (module.js:469:15)
    at Function.Module._load (module.js:417:25)
    at Module.require (module.js:497:17)
    at require (internal/module.js:20:19)
    at Object.<anonymous> (C:\Users\Username\Projects\Projectname\node_modules\theo\dist\props\index.js:28:13)
    at Module._compile (module.js:570:32)
    at Object.Module._extensions..js (module.js:579:10)
    at Module.load (module.js:487:32)
    at tryModuleLoad (module.js:446:12)
    at Function.Module._load (module.js:438:3)

How do you deal with size values on different platforms

For Android I'd like the values to end in sp/dp.
On Web I'd like the values to end in px.
On iOS I'd like the values not to have a postfix, and be rendered as integers without quotes.

What's the recommended strategy? I'm trying to use the CLI and not write a custom formatter.

Misspelled `font-families` type parameter in render function

First off — thanks for posting this repo. Awesome way to think about handling a design system's config single source of truth for settings.

I did some digging into the code since there isn't any documentation on the types of categories you can add. I noticed on line 321 of lib/formats/html.js that:

${this.renderSection('font-families', 'Font Families')}

...should actually be:

${this.renderSection('font-family', 'Font Families')}

Just a simple spelling change is needed here. Happy to do a pull request if needed.

PS — Any existing way to output kebab case values in the app.html file instead of camel case?

When using aliases, every other color transform fails (=> #000000)

Using theo 3.0.3 and:

// main.js

#!/usr/bin/env node

var gulp = require('gulp');
var theo = require('theo');

// web
gulp.src('variables/brand-colors.json')
  .pipe(theo.plugins.transform('web'))
  .pipe(theo.plugins.format('less'))
  .pipe(gulp.dest('dist'));

// ios
gulp.src('variables/brand-colors.json')
  .pipe(theo.plugins.transform('ios'))
  .pipe(theo.plugins.format('ios.json'))
  .pipe(gulp.dest('dist'));

// android
gulp.src('variables/brand-colors.json')
  .pipe(theo.plugins.transform('android'))
  .pipe(theo.plugins.format('android.xml'))
  .pipe(gulp.dest('dist'));
// brand-colors.json

{
  "aliases": {
    "GRAY_HUE": 226,
    "SAT_20_AND_BELOW": "10%",
    "SAT_OVER_20": "3%"
  },
  "props": {
    "GRAY_8": {
      "value": "hsv({!GRAY_HUE}, {!SAT_20_AND_BELOW}, 8%)",
      "type": "color",
      "category": "color"
    },
    "GRAY_12": {
      "value": "hsv({!GRAY_HUE}, {!SAT_20_AND_BELOW}, 12%)",
      "type": "color",
      "category": "color"
    },
    "GRAY_15": {
      "value": "hsv({!GRAY_HUE}, {!SAT_20_AND_BELOW}, 15%)",
      "type": "color",
      "category": "color"
    },
    "GRAY_20": {
      "value": "hsv({!GRAY_HUE}, {!SAT_20_AND_BELOW}, 20%)",
      "type": "color",
      "category": "color"
    },
    "GRAY_25": {
      "value": "hsv({!GRAY_HUE}, {!SAT_OVER_20}, 25%)",
      "type": "color",
      "category": "color"
    },
    "GRAY_55": {
      "value": "hsv({!GRAY_HUE}, {!SAT_OVER_20}, 55%)",
      "type": "color",
      "category": "color"
    },
    "GRAY_70": {
      "value": "hsv({!GRAY_HUE}, {!SAT_OVER_20}, 70%)",
      "type": "color",
      "category": "color"
    },
    "GRAY_80": {
      "value": "hsv({!GRAY_HUE}, {!SAT_OVER_20}, 80%)",
      "type": "color",
      "category": "color"
    },
    "GRAY_90": {
      "value": "hsv({!GRAY_HUE}, {!SAT_OVER_20}, 90%)",
      "type": "color",
      "category": "color"
    },
    "GRAY_95": {
      "value": "hsv({!GRAY_HUE}, {!SAT_OVER_20}, 95%)",
      "type": "color",
      "category": "color"
    }
  }
}
// brand-colors.less
@gray-8: rgb(18, 19, 20);
@gray-12: rgb(0, 0, 0);
@gray-15: rgb(34, 35, 38);
@gray-20: rgb(0, 0, 0);
@gray-25: rgb(62, 62, 64);
@gray-55: rgb(0, 0, 0);
@gray-70: rgb(173, 174, 179);
@gray-80: rgb(0, 0, 0);
@gray-90: rgb(223, 224, 230);
@gray-95: rgb(0, 0, 0);

brand-colors.ios.json and brand-colors.android.xml also get every other color as black

Add support for node_module imports

At present the imports field only allows for relative file imports.

"imports": ["./some/dir/file.json"]

I'd like to add support for node_module imports so tokens can be separately packaged and distributed.

"imports": ["package-name"]

Would that be something you're interested in adding? If so, I'm happy to submit a PR.

Using `imports` results in tokens being transformed multiple times

When using the imports feature to pull in tokens which exist in other files, the tokens are being processed twice. The second time the tokens are processed, they transform the result of the first time the tokens are processed and not the source original files.

Whilst this doesn't seem to cause any problems for most tokens/transforms this does break the Android 8 Digit HEX transform, as this relies on a regex and a replace. This results in an invalid output as the replace regex is called on an already transformed token.

Example

# tokens.yml
props:
  COLOR_GREEN:
    category: colors
    type: color
    value: "#1DB954"

imports:
  - ./colors.yml
# colors.yml
props:
  COLOR_GREEN_NESTED:
    category: colors
    type: color
    value: '#1DB954'

Expected Output

<?xml version="1.0" encoding="utf-8"?>
<resources>
  <color name="COLOR_GREEN_NESTED" category="colors">#ff1db954</color>
  <color name="COLOR_GREEN" category="colors">#ff1db954</color>
</resources>

Actual Output

<?xml version="1.0" encoding="UTF-8"?>
<resources>
  <property name="COLOR_GREEN_NESTED" category="colors" value="#54ff1db9"/>
  <property name="COLOR_GREEN" category="colors" value="#ff1db954"/>
</resources>

The the color/hex8argb transform is working as expected, the issue is somewhere else in the code, I would assume around how imports are handled.

I've tested this with a few versions and this isn't a problem for 5.0.0 but does effect 6.0.0-beta.7 and above.

`glob` is not listed as a dependency, but used in `6.0.0-alpha.6`

Hello, it appears that glob is missing as a dependency in the latest version of Theo.

Error: Cannot find module 'glob'
    at Function.Module._resolveFilename (module.js:469:15)
    at Function.Module._load (module.js:417:25)
    at Module.require (module.js:497:17)
    at require (internal/module.js:20:19)
    at Object.<anonymous> (/Users/<redacted>/node_modules/theo/lib/register.js:6:14)
    at Module._compile (module.js:570:32)
    at Object.Module._extensions..js (module.js:579:10)
    at Module.load (module.js:487:32)
    at tryModuleLoad (module.js:446:12)
    at Function.Module._load (module.js:438:3)

It is referenced in register.js:

glob

Allow an imported alias/prop to be overridden by a locally defined alias/prop with the same name

Hey guys! Thanks again for a great tool that we make use of every day.

I noticed in the 6.0 alpha that an imported alias/prop will override a locally defined alias/prop with the same name (see mergeImports). Was that intentional? If not, it would be cool if the locally defined aliases and props could override the imported ones. We currently use this kind of behaviour for theming - there's a default theme and other themes can apply changes on top of it without having to redefine all of the aliases.

multi-line strings leads to SyntaxError

Hi,

when I just tried to implement a custom html renderer with markdown support in the comments I came across the following:

Valid yaml with markdown inside:

# design-tokens/app.yaml
props:
  COLOR_BRAND:
    value: "{!CASPER}"
    type: color
    category: background-color
    comment: >
      folded

       * bullet
       * list

Output:

// common.js
module.exports = {
// folded

 * bullet
 * list

  colorBrand: "rgb(159, 170, 181)",
}

Which produces a Syntax Error.

I am aware it might seem like a contrived scenario. However, if you would accept a PR I would happily contribute.

Opinions?

Enumerate transform/format options in docs

We'd like to have access to the alias used in a property. Not sure if this is 100% possible (or desirable), especially if you want to be able to do things like value: "{!bar} * 2". If this is the case, just passing the entire string through would make it parseable.

I was going to take a look at this today (as I need it for what we're trying to do), but wanted to put it out there and get some feedback.

aliases:
  bar: blue
props:
  foo: 
    value: "{!bar}"
propsMap: function (prop) {
    console.log(prop.name, prop.value, prop.alias); // returns foo, blue, bar or foo, blue, {!bar}
    return prop;
}

Allow aliases to rely on other aliases

Hi,
when I want to define aliases dependent on aliases. Those are not resolved:

aliases: {
  BASE-LINE-HEIGHT: "1.5";
  BASE-SPACING: "{!BASE-LINE-HEIGHT} * 1em";
  SMALL-SPACING: "{!BASE-SPACING} / 2";
}

resolves to:
$small-spacing: {!BASE-LINE-HEIGHT} * 1em / 2;

Comments not visible in .scss output

@jina mentioned in one of her talks, the possibility to have comments in the tokens-file. Currently I'm using .yml as my input, and .scss as my output.

Input:

---
  global:
    type: "color"
    category: "text-color"
    cssProperties: "color"
  props:
    foo:
      value: "#bada55"
      comment: "A badass color"
    bar:
      value: "#dac0de"
      comment: "Only for the nerds"
    baz:
      value: "#c00ffe"
      comment: "That's a wierd coffee"

Output:

$foo: rgb(186, 218, 85);
$bar: rgb(218, 192, 222);
$baz: rgb(192, 15, 254);

I was kinda expecting something like this:

// A badass color
$foo: rgb(186, 218, 85);
// Only for the nerds
$bar: rgb(218, 192, 222);
// That's a wierd coffee
$baz: rgb(192, 15, 254);

But if I change the output to either raw.json or ios.json, I get the key/value for "comment":

{
  "aliases": {},
  "props": {
    "foo": {
      "type": "color",
      "category": "text-color",
      "cssProperties": "color",
      "value": "#bada55",
      "comment": "A badass color",
      "name": "foo"
    },
    ...
    ...
  },
  "propKeys": [
    "foo",
    "bar",
    "baz"
  ]
}

Still, I'd love to have them available in .scss as well.

My gulp-task looks like this:

gulp.task('tokens-web', function() {
    return gulp.src('path/to/tokens/*.yml')
        .pipe(theo.plugins.transform('web'))
        .pipe(theo.plugins.format('scss'))
        .pipe(rename({
            prefix: '_'
        }))
        .pipe(gulp.dest('path/to/output'));
});

Nothing fancy going on there, except I'm using rename to add an '_' since I'd like the rendered output to be a scss-partial.

Is this not supported, or am I missing something?

Able to search across all values...

Would be cool be able to search across all values, var names, and comments.

You might be thinking... why couldn't you just find (cmd+f) the page to find your values?

Well, depending on how many values exist in your project and what you're working on, you may want to scope to particular things, like input (re: input text color states, input background-color states, input font-size). It would help us get in the habit of entering comments per var.

Immutable.List(...).traverse is not a function

Raised an issue over in gulp-theo but wanted to post here as this repo seems a little better monitored.

After installing the most recent version of gulp-theo we receive the following error when running the defined task:

(node:57669) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 8): TypeError in plugin 'gulp-theo'
Message:
    Immutable.List(...).traverse is not a function

Issue on gulp-theo: salesforce-ux/gulp-theo#2

Allow a token prop to reference another token prop

Currently a token "prop" can reference an alias "prop", so they can share the same value (and in general, multiple tokens can use the same alias).

But I find myself more and more declaring props like this:

{
  "global": {
    "type": "token",
    "category": "base",
    "meta": {
      "platform": "mw"
    }
  },
  "aliases": {
    "TOKEN_BORDER_RADIUS": {
      "value": "3px"
    },
    "TOKEN_SPACING_GAP": {
      "value": "15px"
    }
  },
  "props": {
    "TOKEN_BORDER_RADIUS": {
      "value": "3px"
    },
    "TOKEN_SPACING_GAP": {
      "value": "15px"
    }
  }
}

because I need not only to declare a "token" but also to use that value in other tokens (imagine using the border radius in a "buttons" token group).

What I am suggesting is to allow token props not only to reference aliases like in this case, but also other tokens (a-la-Sass, to be clear: something like a variable referencing another variable).

Now, in this case I see two possible options:

  1. use a different syntax to reference another prop, as compared to the one for the alias:
"TOKEN_COLOR_X": {
  "value": "{!ALIAS_COLOR_X}"
}

vs

"TOKEN_COLOR_X": {
  "value": "{$TOKEN_COLOR_Y}"
}
  1. use the same syntax to reference both aliases and props:
"TOKEN_COLOR_X": {
  "value": "{!ALIAS_COLOR_X}"
},
"TOKEN_SIZE_A": {
  "value": "{!TOKEN_SIZE_B}"
}

The pro of the first implementation is that is 100% safe, since is a new syntax will be backwards compatible. The con is that is a new syntax to add, increasing the complexity of the API.

The pro of the second implementation is that "it just works" and is simple to understand (the complexity will be in the implementation of the logic behind, but that's another story). The con is that in some case could conflict with the previous version of the API (think the example of a token prop referencing an alias with the same name: now we don't have problems, with this change it would raise a conflict).

What do you think? Ideas, opinions, reasons why it would be a bad idea?

Also, open for discussion: will a token prop referencing another token prop inherits its extra attributes (type, category, metas, etc.)? Which by the way is the same question I have about aliases, but is not yet totally clear to me what is the expected behaviour :)

Sort section by...

Theo should be able to sort sections by:

property name (A-Z, Z-A)
numerical value (large to small, small to large)
color value (rainbow, light to dark, dark to light)

Depending on who is looking at your guide, sorting will help them choose the appropriate variable for your system.

Render tokens in source order

Currently, generated tokens are streamed randomly into source files. Would be nice to have the output file of variables reflect the same source order they were imported in.

Can I register a new transform using the CLI?

I'd like to append sp/dp to my Android font sizes.

$theo tokens.yml --transform android,myCustomTransform --format android.xml

It looks like I can create JS files with transforms in the "./transforms" folder, but that I can't create new groups like "myCustomTransform" that trigger the valueTransform.

心得

学习连接:https://github.com/salesforce-ux/theo
Theo初学理解:

最简单的用法:利用Theo以文件流读写的形式,将源文件props.json转换,生成不同平台可以解析的变量定义文件;各平台依据自己的样式语法使用变量实现业务。

对于我们OMNICHANNEL,现主要是在web端的多主题base同源。(目前不同主题,有不同base.css)
Props.json的example如下:
{
"props": {
"color_brand": {
"value": "#ff0000",
"type": "color"
}
},
"global": {
"category": "some-category",
".meta": {
"foo": "baz"
}
}
}
那么讨论:
我们是否可以将这样的transform/format转换思路用于我们套件,将现有的两种主体base文件,抽取变量集成独立文件;针对此变量定义文件,编写props.json源文件。
此后定制开发,可以根据基线的props.json以及原定制局点前端视觉风格,定制一份props.json文件、生成具有定制性的变量定义文件。

UnhandledPromiseRejectionWarning with theo v6.0.0-beta.8 through gulp-theo (Win10)

I'm running Node v8.9.1 with npm 5.5.1 on Windows 10.

I'm having "gulp-theo": "^1.0.0-beta.1" as a direct dependency. This package uses theo as a sub-dependency. For a while now, upon npm install, it installed theo v6.0.0-beta.7. Everything worked.

But doing a clean install of my node_modules now gave me theo v.6.0.0-beta.8. This produced the following error on my gulp tokens task:

Starting 'tokens'...
(node:426736) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 2): [object Object]
(node:426736) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.

(task then exits)

I've tested this on two different windows machines. Same result.
By adding "theo": "^6.0.0-beta.7" as a direct dependency in my package.json makes it work again.

My gulp-taks looks like this:

const theo = require('gulp-theo');

theo.registerFormat('scss', `// Source: {{stem meta.file}}
    {{#each props as |prop|}}
        {{#if prop.comment~}}
        // {{{prop.comment}}}
        {{/if~}}
        $color-{{prop.name}}: {{{prop.value}}};\n
    {{/each}}
`);

gulp.task('tokens', function () {
    return gulp.src([
        paths.src + '/assets/styles/tokens/*.yml',
        '!' + paths.src + '/assets/styles/tokens/_*'
    ])
        .pipe(theo.plugin(
            {
                format: {
                    type: 'scss'
                }
            }
        ))
        .pipe(rename({
            prefix: '_'
        }))
        .pipe(gulp.dest(paths.src + '/assets/styles/sass/01-config/tokens/'));
});

Can't install v6.0.0-beta.3 as a dependency

Running npm install with theo version 6.0.0-beta.3 as a dependency fails with the following error:
npm ERR! enoent ENOENT: no such file or directory, chmod 'C:\Users\adarlow\Documents\Design Systems\temp\test-theo\node_modules\theo\bin\theo.js'

This can be verified with a simple package.json and a fresh install:

{
  "name": "test-theo",
  "version": "0.0.1",
  "description": "Testing whether theo installs",
  "dependencies": {
    "theo": "6.0.0-beta.3"
  },
  "author": "ah-adarlow",
  "license": "UNLICENSED"
}

It works fine with the previous version.

Prettify CSS output

If the output of a custom template is CSS the output should be prettified

Don't merge prop names in different categories

When importing a few files, any prop names that repeat will be overwritten.

The Issue

Given a set of files:

# margins.yml

props:
  - name: auto
    value: auto
  - name: px
    value: 1px
  - name: 1
    value: 0.25rem

global:
  category: margins
  type: length


# paddings.yml

props:
  - name: px
    value: 1px
  - name: 1
    value: 0.25rem

global:
  category: paddings
  type: length


# dimensions.yml

imports:
  - "./margins.yml"
  - "./paddings.yml"

Expected Output

When converting dimensions.yml, using a slightly modified map.scss format that groups by cateogry instead of file-name, I expect to see:

// spacings.scss

$margins: (
    'auto': (auto),
    'px': (1px),
    '1': (0.25rem)
);

$paddings: (
    'px': (1px),
    '1': (0.25rem)
);

Actual Output

Instead I'm seeing:

// spacings.scss

$margins: (
    'auto': (auto)
);

$paddings: (
    'px': (1px),
    '1': (0.25rem)
);

In other words, the repeated prop names ("px" and "1") are omitted from the $margins map.

Rationale

Since I'm using SCSS maps, I don't need to prefix the name of the token with the category, however this means that between categories I am reusing the same name.

BEM syntax support

Do you think there might be a way for Theo to support BEM syntax in the future? The conversion of design tokens' names into kebab case makes SCSS/LESS variables difficult for a person to parse, and the ability to use a BEM naming scheme would be helpful.

For example,
background-button-primary-active is harder to parse than
background_button-primary--active.

Theo seems really useful, but I'm concerned that consumers of my design tokens may have difficulty reading and understanding names that are entirely in kebab case. (If you have anecdotes regarding how well this works at Salesforce—e.g. my concerns being a non-issue—I'd love to get that insight.)

[question] Possible to change output value in templates?

I'm trying to understand how I can get stuff like gradients and transition easings to work well across platforms.

For example, I want this in my scss:
$transition-easing: cubic-bezier(.23,1,.32,1);

And in the json I want this:
"transitionEasing": [.23,1,.32,1]

Currently working around it by just setting ".23,1,.32,1" as the value and then wrapping it in functions in scss and js. That becomes especially ugly in js, as value can only be strings so I have to manually split and parseFloat.

Is this possible to do this in a nicer way? How?

Question about possible theming using aliases

I am trying to figure out a good way to organize the tokens for a possible easy theming solution. My goal was to try to do the following:

With this hierarchy example:

project/
  aliases/
    color-palette.yml
    theme-one-colors.yml
    theme-two-colors.yml
  theme-one.yml
  theme-two.yml

I have an "all available colors" palette.

./aliases/color-palette.yml

---
  aliases:
    PALETTE_GREEN:  "#80ce4d"
    PALETTE_ORANGE: "#ff9426"
    PALETTE_RED:    "#e84f4f"
    PALETTE_YELLOW: "#ffd726"
    ...

Then I have one theme using a few of those colors,

./aliases/theme-one-colors.yml

---
  imports:
    - "./color-palette.yml"
  aliases:
    COLOR_DEFAULT: "{!PALETTE_GREEN}"
    COLOR_PRIMARY: "{!PALETTE_YELLOW}"
    ...

and a second theme using different colors.

./aliases/theme-two-colors.yml

---
  imports:
    - "./color-palette.yml"
  aliases:
    COLOR_DEFAULT: "{!PALETTE_RED}"
    COLOR_PRIMARY: "{!PALETTE_ORANGE}"
    ...

I want to link to the same set of components, but import a different alias file to set their color values:

./theme-one.yml

---
  imports:
    - "./aliases/theme-one.yml"
    - "./components/background-color.yml"
    - "./components/font-color.yml"
    - "./components/text-color.yml"
    ...
./theme-two.yml

---
  imports:
    - "./aliases/theme-two.yml"
    - "./components/background-color.yml"
    - "./components/font-color.yml"
    - "./components/text-color.yml"
    ...

My goal is to keep the same list of tokens, but create themes simply by switching the imported alias file. Currently aliases are included on an individual file basis, but I'm trying to avoid maintaining separate sets of component/files with the same content and only a different import alias value.

Do you have any thoughts or suggestions?

YAML vs YML file extensions

I made the mistake of using a .yaml extension and got an error of Cannot read property 'props' of undefined. I'm guessing it only checks for yml? Would be nice to support both, or at least give proper error message if .yaml.

I might try to tackle this if you have a preference on which approach.

Resolution of aliases is not cross-file between imported files

This is probably a regression in Theo6. When you use multiple imports in a file, the alias resolved in previous files are not available inside other imported files.

It's easy to reproduce.

Given this main file:

main.json

{
  "global": {
    "type": "token",
    "category": "global"
  },
  "imports": [
    "./imported1.json",
    "./imported2.json"
  ]
}

and these two imported files:
imported1.json

{
  "global": {
    "type": "token",
    "category": "global"
  },
  "props": {
    "TOKEN_COLOR_FILE1": {
      "value": "{!ALIAS_COLOR1}"
    }
  },
  "aliases": {
    "ALIAS_COLOR1": {
      "value": "#CC0000"
    }
  }
}

imported2.json

{
  "global": {
    "type": "token",
    "category": "global"
  },
  "props": {
    "TOKEN_COLOR_FILE2_IMPORTED_ALIAS": {
      "value": "{!ALIAS_COLOR1}"
    }
  }
}

the generated file in Theo5 is correctly:
main.scss (as an example)

$token-color-file1: #CC0000;
$token-color-file2-imported-alias: #CC0000;

while in Theo6 a compilation error is raised in the console:

{ Error: Alias "ALIAS_COLOR1" not found
    at allMatches.reduce (MY_FOLDER_PATH/theo6/node_modules/gulp-theo/node_modules/theo/lib/definition.js:171:42)
    at MY_FOLDER_PATH/theo6/node_modules/immutable/dist/immutable.js:4420:31
    at List.__iterate (MY_FOLDER_PATH/theo6/node_modules/immutable/dist/immutable.js:2208:13)
    at List.mixin.reduce (MY_FOLDER_PATH/theo6/node_modules/immutable/dist/immutable.js:4415:12)
    at value.update.v (MY_FOLDER_PATH/theo6/node_modules/gulp-theo/node_modules/theo/lib/definition.js:170:40)
    at updateInDeepMap (MY_FOLDER_PATH/theo6/node_modules/immutable/dist/immutable.js:1973:22)
    at updateInDeepMap (MY_FOLDER_PATH/theo6/node_modules/immutable/dist/immutable.js:1982:23)
    at Map.updateIn (MY_FOLDER_PATH/theo6/node_modules/immutable/dist/immutable.js:1280:26)
    at Map.update (MY_FOLDER_PATH/theo6/node_modules/immutable/dist/immutable.js:1272:14)
    at props.map (MY_FOLDER_PATH/theo6/node_modules/gulp-theo/node_modules/theo/lib/definition.js:169:15)
  name: 'Error',
  message: 'Alias "ALIAS_COLOR1" not found',
  stack: '...',
  showStack: false,
  showProperties: true,
  plugin: 'gulp-theo' }

Make Aliases work within a value when not exclusive

When aliases are used exclusively as a value, they render the value in the HTML output fine. But when aliases are used as part of another value, they don't render properly.

See screenshot for example; top values were the Alias exclusively while bottom values are part of another value:

screenshot 2014-06-25 14 06 23

Only show active categories

Would be nice to check the loaded JSON for supported categories and only show those.

Currently it shows all supported categories, but if your JSON doesn't contain the supported is still shows those category header, each with an empty section.

Export raw values in raw.json export

The raw.json format exports a JSON file of tokens.

We want all the metadata and raw values (as they appeared before transformation) to also be included in the output so it can be reused for static analysis.

Input

{
  "props": {
    "TOKEN_A": {
      "value": "{!red}",
      "meta": "foo"
    },
  },
  "aliases": {
    "red": {
      "value": "#FF0000"
    }
  },
  "global": {
    "type": "token",
    "category": "test"
  }
}

Output

{
  "props": {
    "TOKEN_A": {
      "name": "TOKEN_A",
      "value": "#FF0000",
      "type": "token",
      "category": "test",
      "rawValue": "{!red}",
      "meta": "foo"
    },
  },
  "aliases": {
    "red": {
      "value": "#FF0000"
    }
  },
  "global": {
    "type": "token",
    "category": "test"
  }
}

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.