amzn / style-dictionary

A build system for creating cross-platform styles.
https://amzn.github.io/style-dictionary/#/
Apache License 2.0
3.77k stars 527 forks source link

[Feature] (Transformer to) allow splitting tokens #695

Open lukasoppermann opened 2 years ago

lukasoppermann commented 2 years ago

Hey @dbanksdesign and @chazzmoney I think especially considering the upcoming composite tokens this could be very helpful.

I am running into this problem currently with font styles. My font styles (exported from figma) has letter-spacing. Sadly in css letter-spacing is not part of the font property but a separate attribute.

My need is to transform this input:

'font': {
  type: 'font-style',
  value: {
    fontSize: 16,
    fontWeight: 700,
    //...
    letterSpacing: 0.136
  }
}

Into this css:

font: 700 16px /*...*/;
letter-spacing: 0.136px;

While this is technically possible in a format I feel that is not the correct place for it, as it has not so much to do with the formatting.

dbanksdesign commented 2 years ago

This would be a bit more difficult, but nothing is impossible. The difficulty in this approach is the end result for the generated code for each platform would mean splitting up a single token in different ways. Style Dictionary has the assumption that a design token is a name and a value and like you pointed out, the format should just format how a given language like CSS writes that declaration, [name] : [value] ; for example. This works fine in cases where a composite token can still be represented as a single declaration, for example having a border token that can be represented like [border width] [border style] [border color] in CSS because a transform can take an object and turn it into that string. While it doesn't fit with the spirit of what the format is supposed to do, that is the only way I can think of to split a single token into multiple tokens in an output. A better place would be if Style Dictionary had a pre-format step where you could mutate the dictionary object, but that sounds a bit weird too.

Another option would be to have a token group rather than a composite token and then split each font property into its own token inside the group. This might be a bit cleaner to implement a format for since it wouldn't involve splitting a token.

Can you zoom out a bit, what is the full output you want to have? A single text style in figma outputs to a helper class in CSS?

lukasoppermann commented 2 years ago

Hey @dbanksdesign,

I totally understand this. Actually I find a pre-format step very good, I just thought it was more complicated. This pre-format step would have to be run on a per platform or per file basis (better would be to allow both).

In the w3c specs it is stated that groups are not supposed to have any meaning, it's just an ordering system, however a font-style needs to be grouped. It is actually part of the same token. This is why I think a composite token is better.

Zooming out

I actually translate the tokens into css variables:

 --background: #ffffffff;
 --breakpoints-xl: 1792px;
 --radius-round: 5px 10px 5px 5px;
 --typography-title1: 700 114/131.1 "RTL United Text", sans-serif;
 --typography-title1-letter-spacing: 0.135px; /* <-- this does not work yet */
 --gradients-image-overlay: linear-gradient(180deg, rgb(0, 0, 0) 0%, rgba(0, 0, 0, 0.8) 100%);
 --elevation-small: 0px 1px 0px 0px rgba(23, 24, 26, 0.1);

The composite token for fonts looks like this (plugin output):

{
      name: 'title 1',
      description: 'a font token',
      type: 'custom-fontStyle',
      value: {
        fontSize: 114,
        textDecoration: 'underline',
        fontFamily: 'Helvetica',
        fontWeight: 700,
        fontStyle: 'italic',
        fontStretch: 'normal',
        letterSpacing: 1.2,
        lineHeight: 131.1,
        paragraphIndent: 0,
        paragraphSpacing: 12,
        textCase: 'none'
      }
}
BrettJephson commented 2 years ago

I'm bumping up against the same problem with JSON exported from Figma (via Figma-tokens plugin). Is there a workaround that others have used? Or is this feature in consideration?

juwain commented 2 years ago

BTW, Figma tokens plugin supports splitting typography tokens into separate ones during export. You should check "Expand Typography" field in plugin UI.

Screenshot 2022-03-11 at 10 47 43

So the result will be:

"fontFamily": {
  "value": "{typography.font-family.sans-serif}",
  "type": "fontFamily"
},
"fontWeight": {
  "value": "{typography.font-weight.normal}",
  "type": "fontWeight"
},
"lineHeight": {
  "value": "{typography.line-height.32px}",
  "type": "lineHeight"
},
"fontSize": {
  "value": "{typography.font-size.24px}",
  "type": "fontSize"
},
"letterSpacing": {
  "value": "{typography.letter-spacing.02%}",
  "type": "letterSpacing"
}
jakobe commented 1 year ago

This would be a bit more difficult, but nothing is impossible. The difficulty in this approach is the end result for the generated code for each platform would mean splitting up a single token in different ways. Style Dictionary has the assumption that a design token is a name and a value and like you pointed out, the format should just format how a given language like CSS writes that declaration, [name] : [value] ; for example. This works fine in cases where a composite token can still be represented as a single declaration, for example having a border token that can be represented like [border width] [border style] [border color] in CSS because a transform can take an object and turn it into that string. While it doesn't fit with the spirit of what the format is supposed to do, that is the only way I can think of to split a single token into multiple tokens in an output. A better place would be if Style Dictionary had a pre-format step where you could mutate the dictionary object, but that sounds a bit weird too.

Another option would be to have a token group rather than a composite token and then split each font property into its own token inside the group. This might be a bit cleaner to implement a format for since it wouldn't involve splitting a token.

Can you zoom out a bit, what is the full output you want to have? A single text style in figma outputs to a helper class in CSS?

Hey @dbanksdesign, just stumbled upon this issue. I actually chimed in on #848 with a similar idea (mutating the dictionary object by expanding composite tokens into separate tokens). While this might seem a bit weird it has the benefit of making a lot of formats just work out of the box (e.g. css/variables, scss/map-flat, scss/variables).

I also vented the idea of a pre-format step - and if run per platform and/or file as suggested, that would not tie it directly with the format and could be reused for different files and formats within that platform. Maybe 2 brains having the same thought is hinting at something? 🤔 😅

bennypowers commented 1 year ago

This would be very helpful for colours, as well:

ideally, style-dictionary would let us write a transform to automatically split single colour inputs to multiple values, for example hex to rgb, hsl:

IN:

color:
  blue:
    500:
      $value: '#0066cc'

OUT:

:root {
  --rh-color-blue-500: #0066cc;
  --rh-color-blue-500-rgb: 0, 102, 204;
  --rh-color-blue-500-hsl: 210 100% 40%;
}

USAGE:

#play-button {
  background-color: hsl(var(--rh-color-blue-500-hsl) / var(--rh-opacity-50));
}
yringler commented 1 year ago

Here's a gist. It has a self contained class, which can be used with a formatter to expand a token.

jossmac commented 11 months ago

Adding a case for things like https://seek-oss.github.io/capsize/

Input:

text: {
  regular: {
    size: { $value: '14px', $type: 'dimension' },
    lineheight: { $value: 1.4, $type: 'number' },
  }
}

Output:

text: {
  regular: {
    size: { $value: '14px', $type: 'dimension' },
    lineheight: { $value: 1.4, $type: 'number' },
    baselineTrim: { $value: '-0.3364em', $type: 'dimension' },
    capHeightTrim: { $value: '-0.3364em', $type: 'dimension' },
    capHeight: { $value: '10.1818px', $type: 'dimension' },
  }
}
jorenbroekema commented 2 months ago

Technically you can now do this in V4 with the Preprocessor hook.

It does require you to create your own function that recursively goes through the tokens and mutates the object to your liking.

I'm also adding a feature to expand composite type tokens (object values) either globally, per platform, or conditionally per token, but this feature is slightly limited in that it's either "expand" or "don't expand" and it doesn't allow: