amzn / style-dictionary

A build system for creating cross-platform styles.
https://styledictionary.com
Apache License 2.0
3.9k stars 545 forks source link

Add Sass map format #137

Closed dbanksdesign closed 5 years ago

dbanksdesign commented 6 years ago

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 😊

jeaster12 commented 6 years ago

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 );

dbanksdesign commented 6 years ago

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?

jeaster12 commented 6 years ago

That would be perfect!

didoo commented 6 years ago

@jeaster12 a few questions to clarify:

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? 
cgray24 commented 6 years ago

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,
      )
  )
)
jeaster12 commented 6 years ago

@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

didoo commented 6 years ago

@dbanksdesign some thoughts about this issue:

--

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

chazzmoney commented 6 years ago

@bolora I think you want this functionality as well?

jeaster12 commented 6 years ago

@didoo here is the way I did it(crudely) with Theo...

https://github.com/Cupcake-Design-System/Cupcake-Sprinkles/blob/master/gulp/_default.js

bolora commented 6 years ago

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;
);
bolora commented 6 years ago

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;
      }
    }
  }
}
blackfalcon commented 6 years ago

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

jeaster12 commented 6 years ago

@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); }

blackfalcon commented 6 years ago

@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);
}
didoo commented 5 years ago

@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.

chazzmoney commented 5 years ago

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.

griiettner commented 3 years ago

I actually needed something more in line to what w find on frameworks like bootstrap, so I wrote a format and you can find it here. https://gist.github.com/griiettner/3a400039f97a400d0ffc9b6dfafa5f2d

On the current project I'm working on, was decided that Bootstrap 5 would be the base for the project, so I need, somehow, replicate the entire Bootstrap 5 variables and preserve the maps, because there is bunch of dependencies on it.