- commit
- 14c2e7897326fb50016433bbcce4333e4385d96b
- parent
- ad96d0577fc76ae29ec0ee0005de6e0141b2ce4d
- Author
- Tobias Bengfort <tobias.bengfort@gmx.net>
- Date
- 2016-04-23 19:23
add 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 };