figma-plugin icon indicating copy to clipboard operation
figma-plugin copied to clipboard

Update documentation to explain how to transform shadow tokens

Open dermyhughes opened this issue 3 years ago • 8 comments

Describe the bug When trying to use a box shadow token, the child properties are not being parsed in CSS or SCSS (or any flat format) on Style Dictionary.

To Reproduce Steps to reproduce the behavior:

  "set": {
    "radio": {
      "unchecked": {
        "focus": {
          "box-shadow": {
            "value": {
              "x": "0",
              "y": "0",
              "blur": "0",
              "spread": "3",
              "color": "rgba({cta.color.primary}, 0.3)",
              "type": "dropShadow"
            },
            "description": "Focus ring for radio button",
            "type": "boxShadow"
          }
        }
      }

I have passed this token through the token-transformer package. This token is returning as follows:

--radio-unchecked-focus-box-shadow: [object Object];
$radio-unchecked-focus-box-shadow: [object Object];
export const RadioFocusBoxShadow = {"x":0,"y":0,"blur":0,"spread":3,"color":"#3d4cbf4d","type":"dropShadow"};

Expected behavior I would expect the Box Shadow properties to be spread as separate tokens, similar to how typography is handled.

dermyhughes avatar Dec 17 '21 15:12 dermyhughes

@dermyhughes I ran into the same problem, did you solve it

xuj2017 avatar Dec 21 '21 08:12 xuj2017

I'm wondering how array shadow tokens should be transformed, having keys with "0", "1", "2" etc would probably not be ideal. Instead the current array we output would still allow you to write your own transform and convert that to a valid css shadow syntax.

six7 avatar Dec 22 '21 08:12 six7

I've written a simple transformer to handle this for CSS/SCSS, but haven't given much thought as to how it would work for other formats. Maybe this is more a Style Dictionary issue than Figma Tokens.

function isShadow(token) {
  return token.type === 'boxShadow';
}

StyleDictionary.registerTransform({
  name: 'shadow/spreadShadow',
  type: 'value',
  matcher: isShadow,
  transformer: (token) => {
    const shadow = Object.values(token.value);
    const [x, y, blur, spread, color] = shadow.map((s) => s.toString());
    return `${x}px ${y}px ${blur}px ${spread}px ${color}`;
  }
});

dermyhughes avatar Dec 22 '21 13:12 dermyhughes

That's awesome. I think a lot depends on the W3C standard to define a common format. Every tool will write it their own style until then, so I doubt that SD will write something specific for Figma Tokens.

six7 avatar Dec 22 '21 17:12 six7

We also had a use case needing a single string for both es6 and css variables, figured I'd share here:

Thanks @dermyhughes for pointing us in the right direction!

StyleDictionary.registerTransform({
  name: 'shadow/spreadShadow',
  type: 'value',
  matcher: function(token) {
    return token.type === 'boxShadow';
  },
  transformer: (token) => {
    const shadows = Object.values(token.value);
    const result = shadows.map(shadow => `${shadow.x} ${shadow.y} ${shadow.blur} ${shadow.spread} ${shadow.color}`);
    return result.join(',');
  }
});

dznjudo avatar Jan 04 '22 13:01 dznjudo

I think we want to solve this on a transformation level (style dictionary). The current way of giving the whole shadow object is fine IMO, as users can then transform these to whatever they'd want.

However, we should update docs to include something to point users in the right direction, and using the transforms mentioned above would be a great start.

six7 avatar Jan 22 '22 18:01 six7

Sharing my CSS transform as well - this supports stacked shadow tokens, as well as aliased color references, and inset shadows:

StyleDictionaryPackage.registerTransform({
  name: 'shadow/css',
  type: 'value',
  // necessary in case the color is an alias reference, or the shadows themselves are aliased
  transitive: true,
  matcher: (token) => token.type === 'boxShadow',
  transformer: (token) => {
    // allow both single and multi shadow tokens
    const shadow = Array.isArray(token.value) ? token.value : [token.value];

    const value = shadow.map((s) => {
      const { x, y, blur, color, type } = s;
      // support inset shadows as well
      return `${type === 'innerShadow' ? 'inset ' : ''}${x}px ${y}px ${blur}px ${color}`;
    });

    return value.join(', ');
  },
});

mihkeleidast avatar May 04 '22 05:05 mihkeleidast

Hey @mihkeleidast , thx for sharing your solution 👍🏻

I agree with @six7 that this should be tackled at the transformation level, instead of expanding the shadow token. There will be different ways of outputting shadows on different platforms and that's what SD and platform specific transforms do best. See a similar discussion around Support Composite Token in the SD repo.

For anyone wanting to create a custom transform here's a slightly modified version of @mihkeleidast 's code, that also includes the spread:

/**
 * Based on: https://github.com/six7/figma-tokens/issues/379#issuecomment-1116953915
 */
StyleDictionaryPackage.registerTransform({
  name: "shadow/css",
  type: "value",
  transitive: true, // Necessary when the color is an alias reference, or the shadows themselves are aliased
  matcher: (token) => token.type === "boxShadow",
  transformer: (token) => {
    // Allow both single and multi shadow tokens:
    const shadows = Array.isArray(token.value) ? token.value : [token.value];

    const transformedShadows = shadows.map((shadow) => {
      const { x, y, blur, spread, color, type } = shadow;
      const inset = type === "innerShadow" ? "inset " : "";
      return `${inset}${x}px ${y}px ${blur}px ${spread}px ${color}`;
    });

    return transformedShadows.join(", ");
  },
});

jakobe avatar Sep 14 '22 11:09 jakobe

Closing this as sd-transforms now supports most features token-transformer has supported. Added a page to our docs.

  • https://docs.tokens.studio/transforming/style-dictionary
  • https://www.npmjs.com/package/@tokens-studio/sd-transforms
  • https://configurator.tokens.studio/

six7 avatar Apr 19 '23 20:04 six7