lukasoppermann / design-tokens

🎨 Figma plugin to export design tokens to json in an amazon style dictionary compatible format.
https://www.figma.com/community/plugin/888356646278934516/Design-Tokens
MIT License
964 stars 134 forks source link

Cant Export Spacing as Single Value #177

Open sir-captainmorgan21 opened 2 years ago

sir-captainmorgan21 commented 2 years ago

We are using Tailwind. For things like spacing, we only need the following json schema to configure it for tailwind.

{
   "4": "4px",
   "8": "8px",
   "16": "16px"
}

and so on.

Right now, the spacing export exports with the assumption we need tokens for top, left, right, bottom. We end up with this after running style dictionary

{
  "4": {"top":4,"bottom":4,"left":4,"right":4},
  "8": {"top":8,"bottom":8,"left":8,"right":8},
  "12": {"top":12,"bottom":12,"left":12,"right":12},
  "16": {"top":16,"bottom":16,"left":16,"right":16},
  "24": {"top":24,"bottom":24,"left":24,"right":24},
  "32": {"top":32,"bottom":32,"left":32,"right":32},
  "40": {"top":40,"bottom":40,"left":40,"right":40},
  "48": {"top":48,"bottom":48,"left":48,"right":48},
  "64": {"top":54,"bottom":54,"left":54,"right":54},
  "96": {"top":96,"bottom":96,"left":96,"right":96}
}

Could there possibly be an option to export spacing with just the spacing value? so instead of...

"4": {
    "description": null,
    "type": "custom-spacing",
    "value": {
      "top": 4,
      "bottom": 4,
      "left": 4,
      "right": 4
    },
    "extensions": {
      "org.lukasoppermann.figmaDesignTokens": {
        "exportKey": "spacing"
      }
    }
  }

We'd be able to get...

"4": {
    "description": null,
    "type": "custom-spacing",
    "value": 4,
    "unit": "px",
    "extensions": {
      "org.lukasoppermann.figmaDesignTokens": {
        "exportKey": "spacing"
      }
    }
  },
sir-captainmorgan21 commented 2 years ago

It also looks like the unit isnt included in the export

lukasoppermann commented 2 years ago

Hey,

so I think adding some kind of option could be possible. Something like "simplified export" that would merge together all the values.

However you could also easily solve this via transformers for style dictionary. If you are interested we can create the transformers together in PR to the examples folder.

If so, I would suggest creating a new folder tailwind were we can setup everything for tailwind with style dictionary (https://github.com/lukasoppermann/design-tokens/tree/main/examples) I will sort the rest of the example folder afterwards

sir-captainmorgan21 commented 2 years ago

@lukasoppermann yea would love to contribute. Do I need to be added as a contributor, or should I just fork?

Are you saying we start to place style-dictionary transformers in the examples folder under /libs? I didn't realize you had those transformers there available. You dont publish via npm do you?!

lukasoppermann commented 2 years ago

Hey, sorry, somehow this got lost. You can just fork and send a PR.

The transformers are not published via npm, no. But this is a good idea. Maybe a repo with transformers would be really helpful, what do you think?

Did you get around writing any transformers yet?

sir-captainmorgan21 commented 2 years ago

Yea, I think a repo of transformers would be nice. That would provide an end to end suite of tools to support the workflow.

sir-captainmorgan21 commented 2 years ago

I have not written any transformers yet, though.

sir-captainmorgan21 commented 2 years ago

@lukasoppermann here is a transformer I wrote:

StyleDictionary.registerFormat({
  name: "json/tailwind-number",
  formatter: ({dictionary}) => {
    let output = '{\n';
    output = output + dictionary.allTokens.map(token => {
      const tokenName = token.name;
      const trimmedValue = tokenName.slice((tokenName.length - (tokenName.indexOf('-') + 1)) * -1);
      return `  "${trimmedValue}": "${trimmedValue}px"`;
    }).join(',\n');
    output = output + '\n}';
    return output;
  }
})

Its meant to covert the tokens exported for radius and spacing, and convert them into the following format for tailwind:

{
  "4": "4px",
  "8": "8px",
  "12": "12px",
  "16": "16px",
  "24": "24px",
  "32": "32px",
  "40": "40px",
  "48": "48px",
  "64": "64px",
  "96": "96px"
}
sir-captainmorgan21 commented 2 years ago

Got another one for tailwind. Generate json thatll match the schema needed to use tailwind's plugin feature to add a utility.

StyleDictionary.registerFormat({
  name: 'json/tw-typography',
  formatter: ({dictionary}) => {
    const typographyTokens = dictionary.tokens.typography
    let output = '{\n';

    Object.keys(typographyTokens).forEach((type, typeIndex, typeArray) => {
      const typographyType = typographyTokens[type];
      Object.keys(typographyType).forEach((size, sizeIndex, sizeArray) => {
        const typeSize = typographyType[size];
        output = output + `  "${type}-${size}": {\n`;
        Object.keys(typeSize).forEach((prop, propIndex, propArray) => {
          const isLastProp = propIndex === propArray.length - 1;
          console.log(typeSize[prop].type);
          console.log(typeSize[prop].value);
          output = output + `    "${prop}": "${typeSize[prop].value}`
            + (typeSize[prop].type === 'dimension' ? 'px"' : '"');
          output = output + (isLastProp ? '\n' : ',\n');
        });
        //${JSON.stringify(typographyTokens[type][size], null, 2)}`;
        const isLastToken = (typeIndex === typeArray.length - 1) && (sizeIndex === sizeArray.length - 1);
        output = output + (isLastToken ? '  }\n' : '  },\n');
      });
    });

    return output + '}';
  }
});

Itll format the typography tokens from Figma....

{
  "typography": {
    "display": {
      "100": {
        "fontSize": {
          "type": "dimension",
          "value": 56
        },
        "textDecoration": {
          "type": "string",
          "value": "none"
        },
        "fontFamily": {
          "type": "string",
          "value": "Nunito"
        },
        "fontWeight": {
          "type": "number",
          "value": 800
        },
        "fontStyle": {
          "type": "string",
          "value": "normal"
        },
        "fontStretch": {
          "type": "string",
          "value": "normal"
        },
        "letterSpacing": {
          "type": "dimension",
          "value": -1.12
        },
        "lineHeight": {
          "type": "dimension",
          "value": 64
        },
        "paragraphIndent": {
          "type": "dimension",
          "value": 0
        },
        "paragraphSpacing": {
          "type": "dimension",
          "value": 0
        },
        "textCase": {
          "type": "string",
          "value": "none"
        }
      },
      "200": {
        "fontSize": {
          "type": "dimension",
          "value": 64
        },
        "textDecoration": {
          "type": "string",
          "value": "none"
        },
        "fontFamily": {
          "type": "string",
          "value": "Nunito"
        },
        "fontWeight": {
          "type": "number",
          "value": 800
        },
        "fontStyle": {
          "type": "string",
          "value": "normal"
        },
        "fontStretch": {
          "type": "string",
          "value": "normal"
        },
        "letterSpacing": {
          "type": "dimension",
          "value": -1.28
        },
        "lineHeight": {
          "type": "dimension",
          "value": 72
        },
        "paragraphIndent": {
          "type": "dimension",
          "value": 0
        },
        "paragraphSpacing": {
          "type": "dimension",
          "value": 0
        },
        "textCase": {
          "type": "string",
          "value": "none"
        }
      }
    }
  }
}

like so....

{
  "display-100": {
    "fontSize": "56px",
    "textDecoration": "none",
    "fontFamily": "Nunito",
    "fontWeight": "800",
    "fontStyle": "normal",
    "fontStretch": "normal",
    "letterSpacing": "-1.12px",
    "lineHeight": "64px",
    "paragraphIndent": "0px",
    "paragraphSpacing": "0px",
    "textCase": "none"
  },
  "display-200": {
    "fontSize": "64px",
    "textDecoration": "none",
    "fontFamily": "Nunito",
    "fontWeight": "800",
    "fontStyle": "normal",
    "fontStretch": "normal",
    "letterSpacing": "-1.28px",
    "lineHeight": "72px",
    "paragraphIndent": "0px",
    "paragraphSpacing": "0px",
    "textCase": "none"
  }
}

This one is also making assumptions about how the styles are set up. The depth of the tokens could be an option provided to the formatter. IE... We have tokens where its heading.100.desktop and heading.100.tablet... etc. as opposed to just display.100

sir-captainmorgan21 commented 2 years ago

@lukasoppermann where would you want this style dictionary formatter package to live in the repo?

sir-captainmorgan21 commented 2 years ago

Got an parameterized formatter for typography tokens

StyleDictionary.registerFormat({
  name: 'json/tw-typography',
  formatter: ({dictionary, options}) => {

    const buildTypographyProperties = (obj, path = [], depth = 0, fromLastKey = true) => {
      if (depth !== options.tokenDepth - 1) {
        let json = [];
        Object.keys(obj).forEach((key, index, array) => {
          const isLastKey = index === array.length - 1;
          json.push(buildTypographyProperties(obj[key], path.concat(key), depth + 1, fromLastKey && isLastKey));
        });
        return json.join('');
      } else {
        let json = [`  "${path.join('-').replace(' ', '-')}": { \n`];
        Object.keys(obj).forEach((key, index, array) => {
          const isLastProp = index === array.length - 1;
          json.push(`    "${key}": "${obj[key].value}` + (obj[key].type === 'dimension' ? 'px"' : '"'));
          json.push(isLastProp ? '\n' : ',\n');
        });
        json.push(fromLastKey ? '  }\n' : '  },\n');
        return json.join('');
      }
    };

    const typographyTokens = dictionary.tokens.typography
    let output = '{\n';
    output = output + buildTypographyProperties(typographyTokens);
    return output + '}';
  }
});
lukasoppermann commented 2 years ago

Nice. I think it would make most sense to create a separate repo so people can download it and use it without having the whole plugin code in their npm. What do you think?

sir-captainmorgan21 commented 2 years ago

Agreed

lukasoppermann commented 2 years ago

Okay, sorry, I am currently extremely at my capacity. 😢

While trying to create a new repo, I thought, why don't we use this: https://github.com/lukasoppermann/design-token-transformer

I try to push it now, but my thoughts are:

Feel free to send a PR to the repo. This is the branch I am working on: https://github.com/lukasoppermann/design-token-transformer/tree/suggested-transformers

And sorry again for the sluggish response.

sir-captainmorgan21 commented 2 years ago

No worries! Sounds good. This is perfect!

lukasoppermann commented 2 years ago

Hey, I merged the changes now. Android is done, so you can use that as an example.

I also added a transform:android script that runs the example.

We still need to add it for iOS and web and of course for your examples. And we need to adjust the documentation on this project and the transformers to tell people about it.

sir-captainmorgan21 commented 2 years ago

@lukasoppermann sorry. Been swamped with standing up our design system, but gonna hopefully start on this soon