Skip to content

Instantly share code, notes, and snippets.

@anatomic
Created July 2, 2014 15:59
Show Gist options
  • Save anatomic/02d65aebf5c502ac19b9 to your computer and use it in GitHub Desktop.
Save anatomic/02d65aebf5c502ac19b9 to your computer and use it in GitHub Desktop.
Working with !default and maps when creating a CSS framework
@import "main";
$list-margin: $base-spacing-unit!default;
@mixin create-horizontal-list($name: 'ui-list') {
#{$name} {
list-style: none;
margin: 0;
&__item {
display: inline;
&:after {
content: ',';
}
}
}
}
@include export('ui-list') {
@include create-horizontal-list;
}
@import "main"; // gives us access to the main framework features
@import "lists";
$nav-margin: $base-spacing-unit!default;
@mixin create-nav {
$list-margin: $nav-margin;
@include create-horitzontal-list('nav');
.nav {
.nav__item {
border-bottom: 1px solid;
&:after {
content : ' |';
}
}
}
}
$modules: ()!default;
$base-spacing-unit: 24px!default;
$base-ui-border-color: #dedede!default;
$base-ui-background-color: #f3f3f3!default;
@mixin export($name){
@if index($modules, $name) == false {
$modules: append($modules, $name);
@content;
}
}
@anatomic
Copy link
Author

anatomic commented Jul 2, 2014

The ideal here would be to make defaults available in maps that were namespaced:

$ui: ( 
  border: (color: #dedede, size: 1px, style: solid),
  background: #f3f3f3
);

With the above you could pull things out really neatly with map-get but problems come if the same file is imported multiple times. As an aside, the reason for multiple imports is to manage dependencies between components - the list classes might never be needed on their own but the mixins could be used by several components

If the core sass framework sets the $ui map without !default each time it is imported the default values will be overwritten.

// type.scss imported for first use
$brand: (
  blue: (
    font: (Helvetica, Arial, sans-serif),
    color: #00f
  ),
  red: (
    font: (Georgia, 'Times New Roman', serif),
    color: #f00
  )
);

//defaults set in main file
$brand: map-merge($brand, (blue: ( font: courier, color: #0f0)));

// type.scss imported for use by another file
$brand: (
  blue: (
    font: (Helvetica, Arial, sans-serif),
    color: #00f
  ),
  red: (
    font: (Georgia, 'Times New Roman', serif),
    color: #f00
  )
);

.text {
  font-family: map-get(map-get($brand, blue), font);
  color: map-get(map-get($brand, blue), color);
}

//output:
//.text {
//  font-family: Helvetica, Arial, sans-serif;
//  color: blue;
//}

If we set it up with !default and a developer wants to use the framework but override one element within the map (easily done if they are all separate variables) we can use map-merge but it seems unnecessarily complex and also fragile. I.e. traditionally you declare a variable as !default and you get that value only if not set elsewhere. If we use the map approach, we need to ensure that our !default is set up first, otherwise there is no known variable to merge with.

$brand: map-merge($brand, (blue: ( font: courier, color: #0f0)));

$brand: (
  blue: (
    font: (Helvetica, Arial, sans-serif),
    color: #00f
  ),
  red: (
    font: (Georgia, 'Times New Roman', serif),
    color: #f00
  )
)!default;

// Output: Undefined variable: "$brand".

If we put the framework first, there is a chance that some styles are exported without the correct values because we are unable to define our defaults first.

// framework
$colors: (brand: #ff0000, type: #000000)!default;

//ANYTHING DECLARED HERE WILL NOT HAVE THE CORRECT COLORS FOR TYPE

// main file
$colors: map-merge($colors, (type: #333333));

@anatomic
Copy link
Author

anatomic commented Jul 2, 2014

I did wonder about looking into whether you could use conditional control statements in a component to do the merging and it does work, but only if you want to change or add to the default map. It is impossible to take away.

// Declared as a project global
$breakpoints: (
  alpha: 30rem,
  beta: 48rem,
  gamma: 60rem
);

// How we might handle the declaration in a component

$default-breakpoints: (
  alpha: 40rem,
  beta: 60rem,
  gamma: 70rem,
  delta: 80rem
);

@if variable-exists(breakpoints) {
  // if we have set some defaults we'd better merge them last
  $breakpoints: map-merge($default-breakpoints, $breakpoints);
} 
@else {
  $breakpoints: $default-breakpoints;
}

@each $name,$breakpoint in $breakpoints {
  @media screen and (min-width: $breakpoint) {
    .text { content: "#{$name}"; }
  }
}

// if we use this method, how do we override with fewer
// entries in the map?  i.e. the project only wants alpha and beta

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment