Skip to content

Instantly share code, notes, and snippets.

@sgregson
Last active October 20, 2015 14:30
Show Gist options
  • Save sgregson/72d7983445b912fe640f to your computer and use it in GitHub Desktop.
Save sgregson/72d7983445b912fe640f to your computer and use it in GitHub Desktop.
LibSASS Configurable Objects (Syntax 2)
// ----
// libsass (v3.2.5)
// ----
// LibSass Configurable Objects Syntax 2
// uses actual selector to include configuration
// NOTE: currently unworkable without passing in selector reference due to https://github.com/sass/libsass/issues/548
//
// @author Spencer Gregson
// @credit http://hugogiraudel.com/2014/05/05/bringing-configuration-objects-to-sass/ - a modular way to build partials with signatures
// @credit map-merge-deep() - Pedr Browne (https://gist.github.com/Undistraction/681818d751b6dd06c0cc), edited for compatibility with libsass 3.0.2, no dependencies
//////////////////////REQUIRED UTILITIES////////////////////////////////////////
// map-merge-deep()
//
// Merges the contents of two maps, depth-first
// ```map-merge-deep($base, $ext)```
//
// Styleguide functions.map-merge-deep
@function map-merge-deep($map-old, $map-new, $overwrite: true) {
// Iterate through each value of the new map
@each $key, $new-value in $map-new {
@if map-has-key($map-old, $key) {
// There is an existing key
$old-value: map-get($map-old, $key);
@if type-of($new-value) == map and type-of($old-value) == map {
// If both are maps, recurse regardless of $overwrite
$merged-value: map-merge-deep($old-value, $new-value);
$map-old: map-merge($map-old, ($key: $merged-value));
} @else {
// Otherwise check $overwrite
@if $overwrite{
$map-old: map-merge($map-old, ($key: $new-value));
}
}
} @else {
// There is no existing key so add
$map-old: map-merge($map-old, ($key: $new-value));
}
}
@return $map-old;
}
// init()
//
// Alias to initialize a mixin configuration
// deeply merges two maps - where the keys exist in both, the second takes priority
// ```
// $conf: init((
// '.button': ( color: red ),
// '.button:hover': ( text-decoration: underline )
// ), $conf);
// ```
//
// Styleguide functions.init
@function init($base, $ext) {
// $ext: map-merge-deep($ext, ('scope': #{&}));
@return map-merge-deep($base, $ext);
}
// selector()
//
// Renders a selector using the given configuration object
// Each key-value pair in $conf will output as a CSS property-value
// + When the value is 'null', the property won't render
// + When the selector is 'null', the selector won't render
//
// Styleguide mixins.selector
@mixin selector($sel, $conf) {
// remove everything if the selector instance is nulled-out
@if map-get($conf, $sel) != null {
#{$sel} {
// debug: map-values(map-get($conf, $sel));
// content from the selector initialization
@content;
// CSS property-value pairs from the selector instance
@each $key, $value in map-get($conf, $sel) {
// Permit duplicate keys using an underscore prefix (use case: rgba() degradation)
@if str-index($key, '_') == 1 {
$key: str-slice($key, 2);
}
@if str-index($key, '@extend') != 1 {
#{$key}: #{$value};
} @else {
@extend #{$value};
}
}
}
}
}
@mixin config($sel, $conf) {
// $scope: if(map-get($conf, 'scope') != '', str-length(map-get($conf, 'scope')), 0 );
// $sel: if($scope > 0, str-slice(#{&}, $scope+2), #{&});
@if map-get($conf, $sel) != null {
// content from the selector initialization
@content;
// CSS property-value pairs from the selector instance
@each $key, $value in map-get($conf, $sel) {
// Permit duplicate keys using an underscore prefix (use case: rgba() degradation)
@if str-index($key, '_') == 1 {
$key: str-slice($key, 2);
}
@if str-index($key, '@extend') != 1 {
#{$key}: #{$value};
} @else {
@extend #{$value};
}
}
}
}
//////////////////////GLOBALS///////////////////////////////////////////////////
/*//////////////////////
//// SIMPLE EXAMPLE ////
//////////////////////*/
/*SHARED*/
$primary: #0072BC;
@mixin box_sizing($type) {
-webkit-box-sizing: $type;
-moz-box-sizing: $type;
box-sizing: $type;
}
%full_width { width: 100%; }
%full_height { height: 100%; }
// 1) INITIALIZE the component:
// required properties/settings go into the content of the @include selector() directive
// any properties which you expect to vary live in the init() call
@mixin buttons($conf: ()) {
$conf: init((
'.button': (
color: red
),
'.button:hover': (
text-decoration: underline
)
), $conf);
.button {
// REQUIRED PARAMETERS
@extend %full_width;
//Configurable parameters
@include config('.button', $conf);
&:hover {
@include config('.button:hover', $conf);
}
}
}
// 2) INSTANTIATE the element
// keys set in the instance will either:
// * override the default value
// * append new property
/*Page Instance 1 (defaults) */
@include buttons();
/*Page Instance 2 (override+append) */
.parent {
@include buttons((
'.button': (
color: blue,
background: white,
'@extend': '%full_height'
),
'.button:hover': (
text-decoration: null
)
));
}
/*//////////////////////
//// SIMPLE EXAMPLE ////
//////////////////////*/
/*SHARED*/
.button, .parent .button {
width: 100%;
}
.parent .button {
height: 100%;
}
/*Page Instance 1 (defaults) */
.button {
color: red;
}
.button:hover {
text-decoration: underline;
}
/*Page Instance 2 (override+append) */
.parent .button {
color: blue;
background: white;
}
.parent .button:hover {
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment