Style-dictionary: Add Sass map format

Created on 1 Oct 2018  路  16Comments  路  Source: amzn/style-dictionary

Instead of just sass variables, it would be good to have a sass map format. If someone could comment with what they expect the output of the format to look like, that would help in writing the formatter code 馃槉

format help wanted

Most helpful comment

@jeaster12 I am familiar with things like map-deep functions and things like sass-maps-plus like I mentioned in my comment.

My point was that by itself Sass does not support this nesting model yet as the issue #1739 I referred to has not been completed and holy crap ... that's 3 years ago now I made that comment.

And sure there is the syntax that you eluded to of nesting map-get calls, but damn ... that just sucks.

$thing: (
  thing: (
    thing: (
      thing: (
        thing: (
          thing: 10px
        )
      )
    )
  )
);

.thing {
  size: map-get(map-get(map-get(map-get(map-get($thing, thing), thing), thing), thing), thing);
}

All 16 comments

This would be great!

I would love a map output using variables so they can have the !default flag...

$shadow: (
xs: $shadow-xs,
sm: $shadow-sm
);

We also ran into some issues with syntax when using theo. We like to use functions and retrieve with t-shirt sizes so a developer doesn't have to know any unique values per map. Theo looks for a unique name for the key.

Ideally all of our maps can have a similar simple key without overwriting like so:

$shadow: (
xs: $shadow-xs,
sm: $shadow-sm
);

$space: (
xs: $space-xs,
sm: $space-sm
);

Awesome! So trying to come up with some psuedo-requirements:

  1. It should have configuration to set (or not) the !default flag
  2. It should be able to use the variable references as the values, rather than just the raw value. xs: 10px v. xs: $space-xs

For the key naming, my first thought would be for the keys and map nesting to match the object structure of the JSON of the design tokens. So having JSON like this:

{
  "size": {
    "font": {
      "small": { "value": "13px" },
      "medium": { "value": "15px" },
      "large": { "value": "17px" },
      "base": { "value": "{size.font.medium.value}" }
    }
  }
}

The output might look like

$size: (
  'font': (
    small: $size-font-small,
    medium: $size-font-medium,
    large: $size-font-large,
    base: $size-font-base
  )
);

Thoughts?

That would be perfect!

@jeaster12 a few questions to clarify:

  • do you expect the map to point to the actual values (as in the examples of this post: https://www.chenhuijing.com/blog/overengineering-sass-maps/) or to the tokens/sass variables (so that you can use the !default option for them)? In the second case, these are two different "features".
  • if you consider this example below, how would you expect the sass maps to be generated?
button.json:
{
    "button": {
        "border": {
            "radius": {
                "value": "3px"
            },
            "width": {
                "value": "1px"
            }
        }
    }
}

icon.json
{
    "icon": {
        "size": {
            "xsm": {
                "value": "10px",
            },
            "sm": {
                "value": "16px"
            },
            "md": {
                "value": "22px"
            },
            "lg": {
                "value": "30px"
            },
            "xlg": {
                "value": "36px"
            },
            "xxlg": {
                "value": "46px"
            }
        }
    }
}

consider that in this case, the Sass variables file is generated in this way:

$button-border-radius: 10px;
$button-border-width: 1px;
$icon-size-xsm: 10px;
$icon-size-sm: 16px;
$icon-size-md: 22px;
$icon-size-lg: 30px;
$icon-size-xlg: 36px;
$icon-size-xxlg: 46px;

would you expect a single map? multiple maps? an in that case, how a map "group" is declared or inferred from the declarations?

I was literally about to post about this on slack channel earlier after a week of playing around with creating one after seeing @dbanksdesign Big Design Talk.

I have been playing around with building a template, but my JS is rusty so I fear im not seeing the easier path to create it. Ive played around with reformatting a JSON output all the way to rebuilding the structure with allProperties with mixed success.

Ultimately I was able to create the 'mega' map i wanted, but my lack of JS skills here resulted in maps that worked in some areas are but then I hit a wall of values being strings so messed up in scss -> css or the keys not being strings so that key names in color values or numbers (ex Colors>Core>Red>50) caused issues. which sass seems to not like in maps, which I thought was a issue in Sass maps already fixed but im seeing it in my environment.

So if the experts could aid in providing this, it would be awesome. We had to give up on our map system just today to go back to straight variables to meet deadlines.

Having the maps in SCSS saves a huge amount of time on the CSS toolkit side when looping through them or using through deep get functions, so its a huge win to have them.

@didoo 'would you expect a single map? multiple maps? '
We may be wanting unique in our needs, but we are looking for a single map, basically mirroring the JSON output minor the other attributes and value declarations. Example below:

$bp-tokens: (
  color: (
    core: (
      blue: (
        10: #a3dcff,
        20: #6bbef2,
        30: #49aae6
      ),
      orange: (
        10: #ffe0d2,
        20: #ffbc9d,
        30: #ff9564
       )
    ),
    border: (
      dark: #ffffff,
      lighter: #dfdddd,
      light: #c6c3c3
    ),
    background: (
      dark: #ffffff,
      lighter: #dfdddd,
      light: #c6c3c3
    )
  ),
  space: (
      spacing: (
          'default': 16px,
          'xs':4px,
          'sm':8px,
          'md':16px,
          'lg':24px,
      ),
      inset: (
          'default': 16px 16px 16px 16px,
          'sm': 8px 8px 8px 8px,
          'md': 16px 16px 16px 16px,
      )
  )
)

@didoo I would use 2 formatters if needed - currently thats how we use Theo - we push out basically a huge variables file that has the !default flag. The additional map format then just uses the variables.

I think the most flexible would be if you get out what you put in. You could write one huge map, a nested map for the definitions of an entire element, or multiple simple maps.

In your first example above - this is the output I'd expect:

$button: (
  border: (
    radius: (
      sm: $border-radius-sm,
      md: $border-radius-md,
      lg: $border-radius-lg
    ),
    width: (
      sm: $border-width-sm,
      md: $border-width-md,
      lg: $border-width-lg
    )
  )
);

Deep maps like this would be super valuable for themes, color palettes, or - if preferred - writing all the attributes for a single component.

We actually break it into more globally usable attributes.
We would use a radius.json and define all the border radius values there.

$radius: (
  sm: $radius-sm,
  md: $radius-md,
  lg: $radius-lg,
  pill: $radius-pill
);

Then we write a simpler function to retrieve radius values and loop over it for helpers as well.

Here is an example SassMeister

@dbanksdesign some thoughts about this issue:

  • maybe we should break apart this one in three different tickets: _"add sass/flat format"_, _"add sass/nested format"_ (similar to what we have for JSON) and _"add option to show "!default" flag to sass variables"_
  • @jeaster12 request is very specific, he needs both the Sass variables (with/without !default) and a Sass map that references variables and not actual values. is it another "format"? or a combination of some "option and format"? is it worth to have this quite complex configuration as a pre-defined format, or better add a new "Sass" folder in the example folder, with an example of how to do it with custom formats/templates (and maybe some other examples of how to do things in a Sass-oriented way)?

--

Continuing on this: probably we can follow an approach similar to Theo, so people will be familiar with the formats and will expect a similar behavior:
https://github.com/salesforce-ux/theo/blob/master/lib/formats/map.scss.hbs
https://github.com/salesforce-ux/theo/blob/master/lib/formats/map.variables.scss.hbs

@bolora I think you want this functionality as well?

Maybe its better to only doing it if you explicitly tell it to
for example:

{
  "size": {
    "font": {
      "small": { "value": "13px" },
      "medium": { "value": "15px" },
      "large": { "value": "17px" },
      "base": { "value": "{size.font.medium.value}" }
    }
  }
}

would still yield:

$size-font-small: 13px;
$size-font-medium: 15px;
$size-font-large: 17px;
$size-font-base: 15px;

But something like this:

{
  "size": {
    "font": {
      "map: {
         "small": { "value": "13px" },
         "medium": { "value": "15px" },
         "large": { "value": "17px" },
         "base": { "value": "{size.font.medium.value}" }
      }
    }
  }
}

Might yield:

$size-font-small: 13px;
$size-font-medium: 15px;
$size-font-large: 17px;
$size-font-base: 15px;
$size-font (
         "small": $size-font-small;
         "medium": $size-font-medium;
         "large": $size-font-large;
         "base": $size-font-base;
);

BTW I use this because I create functions to call up the values

@function font-size($key: "base", $isMobile: null) {
  @if $isMobile == null {
    @return map-get($font-sizes, $key);
  } @else {
    @return map-get($font-sizes-mobile, $key);
  }
}

Then use it like this:

font-size: font-size(medium);

but a better example is using a map in a mixin:

@include make-color-backgrounds();

@mixin make-color-backgrounds ($color-list: $all-colors) {
  @each $color, $value in $color-list {
    .#{$prefix}bg-#{$color} {
      @include color-background($value);
      @if isDarkColor(#{$color}) {
        color: $color-white;
      }
    }
  }
}

Without a doubt, a !default flag option would be HUGE, although this has no impact on platforms other than Sass.

In regards to the mapped output, the example at the top of this thread is not Sass compatible?

$size: (
  'font': (
    small: $size-font-small,
    medium: $size-font-medium,
    large: $size-font-large,
    base: $size-font-base
  )
);

Allowing Sass to consume nested maps has been a HUGE ask for years and as far as I know, Ruby Sass, libSass and DartSass do not support this?

https://github.com/sass/sass/issues/1739

@blackfalcon what do you mean by compatible? as in map-get for nested maps isn't core functionality?

We write a function for retrieving values with map-deep-get - like this - Example of above

But if you didn't wish to use a separate function you could still do this I think -

h1 { font-size: map-get(map-get($size, font), small); }

@jeaster12 I am familiar with things like map-deep functions and things like sass-maps-plus like I mentioned in my comment.

My point was that by itself Sass does not support this nesting model yet as the issue #1739 I referred to has not been completed and holy crap ... that's 3 years ago now I made that comment.

And sure there is the syntax that you eluded to of nesting map-get calls, but damn ... that just sucks.

$thing: (
  thing: (
    thing: (
      thing: (
        thing: (
          thing: 10px
        )
      )
    )
  )
);

.thing {
  size: map-get(map-get(map-get(map-get(map-get($thing, thing), thing), thing), thing), thing);
}

@jeaster12 @blackfalcon @bolora I have submitted a PR #193 to add the Sass maps in two formats, _flat_ and _nested_.

If this is the input:

// --- file1.json ---
{
  "size": {
    "font": {
      "small" : { "value": "12px" },
      "large" : {
        "value": "18px",
        "comment": "this is a comment"
      }
    }
  }
}
// --- file2.json ---
{
  "color": {
    "base": {
      "red": {
        "value": "#FF0000"
      }
    },
    "white": { "value": "#ffffff" }
  }
}

this is the generated output:

tokens_map-flat.scss

$tokens: (
  'size-font-small': 12rem,
  // this is a comment
  'size-font-large': 18rem,
  'color-base-red': #ff0000,
  'color-white': #ffffff
);

tokens_map-deep.scss

$size-font-small: 12rem !default;
$size-font-large: 18rem !default; // this is a comment
$color-base-red: #ff0000 !default;
$color-white: #ffffff !default;

$tokens: (
  'size': (
    'font': (
      'small': $size-font-small,
      'large': $size-font-large
    )
  ),
  'color': (
    'base': (
      'red': $color-base-red
    ),
    'white': $color-white
  )
);

Let me know your thoughts.

While we have had some discussion on Sass vs Scss, the original intent of this issue was to add scss maps. As such, this is complete as of v2.7.0.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

matt-tyas picture matt-tyas  路  3Comments

chazzmoney picture chazzmoney  路  4Comments

dbanksdesign picture dbanksdesign  路  5Comments

johnny353535 picture johnny353535  路  3Comments

dbanksdesign picture dbanksdesign  路  3Comments