sass-planifolia

Vanilla Sass helper functions
git clone https://git.ce9e.org/sass-planifolia.git

commit
6ce951d825b44c7c5622b117969008a4b0f2531c
parent
ad96d0577fc76ae29ec0ee0005de6e0141b2ce4d
Author
Tobias Bengfort <tobias.bengfort@gmx.net>
Date
2016-06-12 06:58
Merge branch 'feature-grid'

Diffstat

A sass/grid.scss 208 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A test/grid.js 90 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
M test/shared.js 26 +++++++++++++++++++++++++-

3 files changed, 323 insertions, 1 deletions


diff --git a/sass/grid.scss b/sass/grid.scss

@@ -0,0 +1,208 @@
   -1     1 ////
   -1     2 /// There already so many grid systems out there. So why write yet another one?
   -1     3 /// Why not?
   -1     4 ///
   -1     5 /// The math behind grids is extremely simple, so you should be able to come up
   -1     6 /// with some custom mixins for your project in less than 5 minutes. The
   -1     7 /// complexity in many frameworks comes from a flexibility that you will
   -1     8 /// probably not need.
   -1     9 ///
   -1    10 /// This module collects all the best practices I found around the topic of
   -1    11 /// grids. Most importantly, it offers `calc()` based gutters with arbitrary
   -1    12 /// units.  Please, do not feel pressured to use this, but maybe it is
   -1    13 /// interesting to read.
   -1    14 ///
   -1    15 /// Many grid systems offer classes that you can apply directly to your HTML.
   -1    16 /// This is inflexible and violates against separation of content and
   -1    17 /// presentation. So they are out.
   -1    18 ///
   -1    19 /// There are basically 4 options how to do grids:
   -1    20 ///
   -1    21 /// - Inline has the issue that whitespace is significant, so it violates the
   -1    22 ///   separation of content and presentation.
   -1    23 /// - Flexbox is not ready yet and if it was it would be expressive enough to be
   -1    24 ///   used without the overhead of a grid system.
   -1    25 /// - Floats were the implementation of choice for quite some time, but they are
   -1    26 ///   not very robust and prone to rounding errors.
   -1    27 /// - Isolation is an extension of floats that is much more robust by specifying
   -1    28 ///   the position of each cell independently of the others. This is the method
   -1    29 ///   that is used here.
   -1    30 ///
   -1    31 /// Then, there is the topic of gutters. With inline and float, the gutters
   -1    32 /// influence the position of subsequent cells, so it is quite complex to get
   -1    33 /// them right. With isolation, this is much simpler and more flexible.
   -1    34 ///
   -1    35 /// With this module, you can decide whether you want to have gutters on the
   -1    36 /// sides. More importantly, you can use gutters of any unit (e.g. em or rem).
   -1    37 /// However, you also need to define a fallback for browsers that do not provide
   -1    38 /// `calc()`.
   -1    39 ///
   -1    40 /// You can use the `grid-span($width, $position)` mixin or use the atomic
   -1    41 /// mixins directly. Note that you have to clear the container yourself.
   -1    42 ///
   -1    43 /// More high-level functionality (e.g. responsive layouts) can easily be added
   -1    44 /// on top of this. This module already contains a `grid-same-with()` mixin that
   -1    45 /// covers some usecases.
   -1    46 ///
   -1    47 /// @group grid
   -1    48 ////
   -1    49 
   -1    50 /// Default configuration.
   -1    51 ///
   -1    52 /// Most mixins and functions accept a map that can overwrite these values
   -1    53 /// locally.
   -1    54 ///
   -1    55 /// @prop {number} columns [12] - Total number of columns.
   -1    56 /// @prop {width} gutter [1rem] - Gutter width.
   -1    57 /// @prop {width} gutter-fallback [2%] - Gutter width if `calc()` is
   -1    58 ///   not available. Must be a percentage.
   -1    59 /// @prop {string} gutter-position ['inside'] - Position of gutters.
   -1    60 ///   One of 'inside', 'outside'.
   -1    61 $grid: (
   -1    62   'columns': 12,
   -1    63   'gutter': 1rem,
   -1    64   'gutter-fallback': 2%,
   -1    65   'gutter-position': 'inside',
   -1    66 );
   -1    67 
   -1    68 @function _grid-get($key, $settings: ()) {
   -1    69   @if map-has-key($settings, $key) {
   -1    70     @return map-get($settings, $key);
   -1    71   } @else {
   -1    72     @return map-get($grid, $key);
   -1    73   }
   -1    74 }
   -1    75 
   -1    76 /// Set default value in $grid.
   -1    77 ///
   -1    78 /// @param {string} $key
   -1    79 /// @param {any} $value
   -1    80 @mixin grid-set($key, $value) {
   -1    81   @if map-has-key($grid, $key) {
   -1    82     $grid: map-merge($grid, ($key: $value)) !global;
   -1    83   } @else {
   -1    84     @error 'grid setting does not exist: #{$key}';
   -1    85   }
   -1    86 }
   -1    87 
   -1    88 @function _grid-width($w, $gutter, $settings) {
   -1    89   $gutter-position: _grid-get('gutter-position', $settings);
   -1    90 
   -1    91   @if $gutter-position == 'inside' {
   -1    92     @return (100% + $gutter) * $w - $gutter;
   -1    93   } @else if $gutter-position == 'outside' {
   -1    94     @return (100% - $gutter) * $w - $gutter;
   -1    95   } @else {
   -1    96     @error 'invalid grid-gutter-position "#{$gutter-position}"! Choose one of "inside", "outside".'
   -1    97   }
   -1    98 }
   -1    99 
   -1   100 @function _grid-width-calc($w, $gutter, $settings) {
   -1   101   $gutter-position: _grid-get('gutter-position', $settings);
   -1   102 
   -1   103   @if $gutter-position == 'inside' {
   -1   104     @return calc((100% + #{$gutter}) * #{$w} - #{$gutter});
   -1   105   } @else if $gutter-position == 'outside' {
   -1   106     @return calc((100% - #{$gutter}) * #{$w} - #{$gutter});
   -1   107   }
   -1   108 }
   -1   109 
   -1   110 @function _grid-position($p, $gutter, $settings) {
   -1   111   $gutter-position: _grid-get('gutter-position', $settings);
   -1   112 
   -1   113   @if $gutter-position == 'inside' {
   -1   114     @return (100% + $gutter) * $p;
   -1   115   } @else if $gutter-position == 'outside' {
   -1   116     @return (100% - $gutter) * $p + $gutter;
   -1   117   } @else {
   -1   118     @error 'invalid grid-gutter-position "#{$gutter-position}"! Choose one of "inside", "outside".'
   -1   119   }
   -1   120 }
   -1   121 
   -1   122 @function _grid-position-calc($p, $gutter, $settings) {
   -1   123   $gutter-position: _grid-get('gutter-position', $settings);
   -1   124 
   -1   125   @if $gutter-position == 'inside' {
   -1   126     @return calc((100% + #{$gutter}) * #{$p});
   -1   127   } @else if $gutter-position == 'outside' {
   -1   128     @return calc((100% - #{$gutter}) * #{$p} + #{$gutter});
   -1   129   }
   -1   130 }
   -1   131 
   -1   132 /// Atomic width mixin.
   -1   133 ///
   -1   134 /// @param {number} $width - Number of columns.
   -1   135 /// @param {map} $settings [()]
   -1   136 @mixin grid-width($width, $settings: ()) {
   -1   137   $columns: _grid-get('columns', $settings);
   -1   138   $gutter: _grid-get('gutter', $settings);
   -1   139   $gutter-fallback: _grid-get('gutter-fallback', $settings);
   -1   140 
   -1   141   $w: $width / $columns;
   -1   142 
   -1   143   @if unit($gutter) == '%' {
   -1   144     width: _grid-width($w, $gutter, $settings);
   -1   145   } @else {
   -1   146     width: _grid-width($w, $gutter-fallback, $settings);
   -1   147     width: _grid-width-calc($w, $gutter, $settings);
   -1   148   }
   -1   149 }
   -1   150 
   -1   151 /// Atomic position mixin.
   -1   152 ///
   -1   153 /// @param {number} $position - Number of columns (starting from 0).
   -1   154 /// @param {map} $settings [()]
   -1   155 @mixin grid-position($position, $settings: ()) {
   -1   156   $columns: _grid-get('columns', $settings);
   -1   157   $gutter: _grid-get('gutter', $settings);
   -1   158   $gutter-fallback: _grid-get('gutter-fallback', $settings);
   -1   159 
   -1   160   $p: $position / $columns;
   -1   161 
   -1   162   @if unit($gutter) == '%' {
   -1   163     margin-left: _grid-position($p, $gutter, $settings);
   -1   164   } @else {
   -1   165     margin-left: _grid-position($p, $gutter-fallback, $settings);
   -1   166     margin-left: _grid-position-calc($p, $gutter, $settings);
   -1   167   }
   -1   168 }
   -1   169 
   -1   170 /// Basic cell styling.
   -1   171 ///
   -1   172 /// @param {map} $settings [()]
   -1   173 @mixin grid-cell($settings: ()) {
   -1   174   float: left;
   -1   175   margin-right: -100%;
   -1   176 }
   -1   177 
   -1   178 /// Shortcut for a cell.
   -1   179 ///
   -1   180 /// Note that you need to clear the container yourself.
   -1   181 ///
   -1   182 /// @param {number} $width - Number of columns.
   -1   183 /// @param {number} $position - Number of columns (starting from 0).
   -1   184 /// @param {map} $settings [()]
   -1   185 @mixin grid-span($width, $position, $settings: ()) {
   -1   186   @include grid-cell($settings);
   -1   187   @include grid-width($width, $settings);
   -1   188   @include grid-position($position, $settings);
   -1   189 }
   -1   190 
   -1   191 /// Layout $n cells per row with equal width.
   -1   192 ///
   -1   193 /// @param {number} $n - Number of cells per row.
   -1   194 /// @param {map} $settings [()]
   -1   195 @mixin grid-same-width($n, $settings: ()) {
   -1   196   $columns: _grid-get('columns', $settings);
   -1   197   $width: $columns / $n;
   -1   198 
   -1   199   @include grid-cell($settings);
   -1   200   @include grid-width($width, $settings);
   -1   201 
   -1   202   @for $i from 1 through $n {
   -1   203     &:nth-child(#{$n}n+#{$i}) {
   -1   204       @include grid-position($width * ($i - 1), $settings);
   -1   205       clear: if($i == 1, both, none);
   -1   206     }
   -1   207   }
   -1   208 }

diff --git a/test/grid.js b/test/grid.js

@@ -0,0 +1,90 @@
   -1     1 var path = require('path');
   -1     2 var assert = require('assert');
   -1     3 var Sassaby = require('sassaby');
   -1     4 var shared = require('./shared');
   -1     5 
   -1     6 describe('grid', function() {
   -1     7   var file = path.resolve(__dirname, '../sass/grid.scss');
   -1     8   var sassaby = new Sassaby(file);
   -1     9 
   -1    10   describe('_grid-get', function() {
   -1    11     it('get value', function() {
   -1    12       sassaby.func('_grid-get').calledWithArgs('columns').equals('12');
   -1    13     });
   -1    14     it('get overwritten value', function() {
   -1    15       sassaby.func('_grid-get').calledWithArgs('columns', '("columns": 13)').equals(13);
   -1    16     });
   -1    17   });
   -1    18 
   -1    19   describe('grid-width', function() {
   -1    20     it('uses calc() for output', function() {
   -1    21       var call = sassaby.includedMixin('grid-width').calledWithArgs('1');
   -1    22       shared.declares(call, 'width', 'calc((100% + 1rem) * .08333 - 1rem)');
   -1    23     });
   -1    24     it('provides a fallback', function() {
   -1    25       var call = sassaby.includedMixin('grid-width').calledWithArgs('1');
   -1    26       shared.declares(call, 'width', '6.5%');
   -1    27     });
   -1    28     it('grid-width(2)', function() {
   -1    29       var call = sassaby.includedMixin('grid-width').calledWithArgs('2');
   -1    30       shared.declares(call, 'width', 'calc((100% + 1rem) * .16667 - 1rem)');
   -1    31       shared.declares(call, 'width', '15%');
   -1    32     });
   -1    33     it('uses gutter setting', function() {
   -1    34       var call = sassaby.includedMixin('grid-width').calledWithArgs('1', '(gutter: 2em)');
   -1    35       shared.declares(call, 'width', 'calc((100% + 2em) * .08333 - 2em)');
   -1    36       shared.declares(call, 'width', '6.5%');
   -1    37     });
   -1    38     it('uses gutter-fallback setting', function() {
   -1    39       var call = sassaby.includedMixin('grid-width').calledWithArgs('1', '(gutter-fallback: 8%)');
   -1    40       shared.declares(call, 'width', 'calc((100% + 1rem) * .08333 - 1rem)');
   -1    41       shared.declares(call, 'width', '1%');
   -1    42     });
   -1    43     it('uses columns setting', function() {
   -1    44       var call = sassaby.includedMixin('grid-width').calledWithArgs('1', '(columns: 8)');
   -1    45       shared.declares(call, 'width', 'calc((100% + 1rem) * .125 - 1rem)');
   -1    46       shared.declares(call, 'width', '10.75%');
   -1    47     });
   -1    48     it('uses gutter-position setting', function() {
   -1    49       var call = sassaby.includedMixin('grid-width').calledWithArgs('1', '(gutter-position: "outside")');
   -1    50       shared.declares(call, 'width', 'calc((100% - 1rem) * .08333 - 1rem)');
   -1    51       shared.declares(call, 'width', '6.16667%');
   -1    52     });
   -1    53   });
   -1    54 
   -1    55   describe('grid-position', function() {
   -1    56     it('uses calc() for output', function() {
   -1    57       var call = sassaby.includedMixin('grid-position').calledWithArgs('1');
   -1    58       shared.declares(call, 'margin-left', 'calc((100% + 1rem) * .08333)');
   -1    59     });
   -1    60     it('provides a fallback', function() {
   -1    61       var call = sassaby.includedMixin('grid-position').calledWithArgs('1');
   -1    62       shared.declares(call, 'margin-left', '8.5%');
   -1    63     });
   -1    64     it('grid-position(2)', function() {
   -1    65       var call = sassaby.includedMixin('grid-position').calledWithArgs('2');
   -1    66       shared.declares(call, 'margin-left', 'calc((100% + 1rem) * .16667)');
   -1    67       shared.declares(call, 'margin-left', '17%');
   -1    68     });
   -1    69     it('uses gutter setting', function() {
   -1    70       var call = sassaby.includedMixin('grid-position').calledWithArgs('1', '(gutter: 2em)');
   -1    71       shared.declares(call, 'margin-left', 'calc((100% + 2em) * .08333)');
   -1    72       shared.declares(call, 'margin-left', '8.5%');
   -1    73     });
   -1    74     it('uses gutter-fallback setting', function() {
   -1    75       var call = sassaby.includedMixin('grid-position').calledWithArgs('1', '(gutter-fallback: 8%)');
   -1    76       shared.declares(call, 'margin-left', 'calc((100% + 1rem) * .08333)');
   -1    77       shared.declares(call, 'margin-left', '9%');
   -1    78     });
   -1    79     it('uses columns setting', function() {
   -1    80       var call = sassaby.includedMixin('grid-position').calledWithArgs('1', '(columns: 8)');
   -1    81       shared.declares(call, 'margin-left', 'calc((100% + 1rem) * .125)');
   -1    82       shared.declares(call, 'margin-left', '12.75%');
   -1    83     });
   -1    84     it('uses gutter-position setting', function() {
   -1    85       var call = sassaby.includedMixin('grid-position').calledWithArgs('1', '(gutter-position: "outside")');
   -1    86       shared.declares(call, 'margin-left', 'calc((100% - 1rem) * .08333+1rem)');
   -1    87       shared.declares(call, 'margin-left', '10.16667%');
   -1    88     });
   -1    89   });
   -1    90 });

diff --git a/test/shared.js b/test/shared.js

@@ -10,6 +10,30 @@ var similar = function(result, actual, threshold) {
   10    10   assert(Math.abs(value - actual) < threshold, message);
   11    11 };
   12    12 
   -1    13 var declares = function(result, key, value) {
   -1    14   var rules = result.css.split(/[{};]/);
   -1    15   var found = false;
   -1    16   var values = [];
   -1    17 
   -1    18   for (var i = 0; i < rules.length; i++) {
   -1    19     var rule = rules[i].split(':');
   -1    20 
   -1    21     if (rule.length == 2 && rule[0] == key) {
   -1    22       values.push(rule[1]);
   -1    23 
   -1    24       if (rule[1] == value) {
   -1    25         found = true;
   -1    26         break;
   -1    27       }
   -1    28     }
   -1    29   }
   -1    30 
   -1    31   var message = 'Rule "' + key + ': ' + value + '" could not be found.'
   -1    32   message += ' Other values for this key: ' + values.join(', ');
   -1    33   assert(found, message);
   -1    34 };
   -1    35 
   13    36 module.exports = {
   14    -1   similar: similar
   -1    37   similar: similar,
   -1    38   declares: declares,
   15    39 };