Skip to content

Instantly share code, notes, and snippets.

@tommycoppers
Last active August 29, 2015 13:57
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tommycoppers/06f71cf07686836395b9 to your computer and use it in GitHub Desktop.
Save tommycoppers/06f71cf07686836395b9 to your computer and use it in GitHub Desktop.
Breakpoints With Unit testing!
// ==================================================
// =================== Breakpoints ==================
// ==================================================
//
// --- Introduction ---
//
// Media Queries, namely Breakpoints, are key in responsive design / development.
// "breakpoint" allows for a robust approach to rapid development of Media Queries in your CSS.
// By defining a list of approved breakpoints, this library helps to ensure consistent Media Query output, relative to each project.
// In addition to defined breakpoints, you are allowed to pass in numbers of measure ( width of screen ) to fine tune one-off cases.
// ** Please emphasize that passing in numbers of measure really should be used minimally to promote consistency. **
// Note that this library functions on a mobile first philosophy, so media queries are by default paired with 'min-width'.
//
// --- Application ---
//
// Including a breakpoint can happen 1 of 3 ways
//
// - Starting point
// ** mobile first mentality
// ** Only define $breakpoint ( with a key from $breakpoints-list )
//
// - Range of breakpoints
// ** only affect from start of $breakpoint through start of $until
// ** Define both $breakpoint and $until ( both take a key from $breakpoints-list )
//
// - Isolated breakpoint
// ** affects from start of $breakpoint until the next defined breakpoint
// ** Define both $breakpoint ( key from $breakpoints-list ) and $only:true ( boolean )
//
// --- Examples ---
//
// .foo {
// @include breakpoint ( 'tablet' ) { // Start styles for .foo at 'tablet' size
// /* some styles */
// }
// }
//
// .bar {
// @include breakpoint ( 'mobile', $until: 'desktop' ) { // Start styles for .bar at 'mobile' and continue up until 'desktop' size
// /* some styles */
// }
// }
//
// .zoo {
// @include breakpoint ( 'mobile', $only: true ) { // Start styles for .zoo only between 'mobile' start and 'mobile' end
// /* some styles */
// }
// }
// Define all the breakpoints here ( Key, Value )
///////////////////////////////////////////////
// $breakpoints-list is a Sass map with a window width to a label.
// Being mobile first, the numbers represent the starting window width for each breakpoint
// Key - name of breakpoint {String}
// Value - Width of window {Number} ** Requires unit of measure **
$breakpoints-list: (
mobile : 0, // Mobile first! Defined in case mobile through ( breakpoint ) needs to be isolated
tablet : 550px, // ≈ tablet( ish )
desktop : 960px, // ≈ desktop( ish )
cinema : 1600px // ≈ cinema( ish )
);
// Breakpoint Manager
/////////////////////
// Create intelligent media queries
//
// @param $breakpoint
// --- Includes a minimum value in media query ---
// {String} - String must be a key in $breakpoints-list Map
// {Number} - Number must have unit of measure
//
// @param $until
// --- Includes a maximum value in media query ---
// {String} - String must be a key in $breakpoints-list Map
// {Number} - Number must have unit of measure
//
// @param $only
// --- Limit the minimum and maximum values to only the start and end of $breakpoint ---
// {Bool} - String must be a key in $breakpoints-list Map
@mixin breakpoint( $breakpoint, $until: false, $only: false ) {
// ___ Unit test the hell out of it ___
$errors: breakpoint-unit-test( $breakpoint, $until, $only );
// ___ Meat and Potatoes ___
@if not $errors {
// ___ Configure $until ___
// If a range of breakpoints is defined, start at $breakpoint and end at the unit before $until
@if $until {
$breakpoint-bp : get-breakpoint-value( $breakpoint );
$until-bp : get-until-breakpoint( $until );
@include _until-breakpoint ( $breakpoint-bp, $until-bp ) { @content; }
}
// ___ Configure $only ___
// If $only is set to true, start at $breakpoint and find the next defined breakpoint and end the unit before it begins
@else if $only {
@include _only-breakpoint ( $breakpoint ) { @content; }
}
// ___ Configure default
@else {
@include _default-breakpoint ( get-breakpoint-value( $breakpoint ) ) { @content; }
}
}
}
// Breakpoint Partials
///////////////////////
@mixin _default-breakpoint ( $breakpoint ) {
@if ( $breakpoint != get-first( $breakpoints-list, $filter: value ) ) and ( $breakpoint != 0 ) {
@media ( min-width: $breakpoint ) { @content; }
}
@else {
@content;
}
}
@mixin _until-breakpoint ( $breakpoint, $until-point ) {
@media ( min-width: $breakpoint ) and ( max-width: $until-point ) { @content; }
}
@mixin _only-breakpoint ( $breakpoint ) {
// Check if $breakpoint is the last defined breakpoint
@if $breakpoint != get-last( $breakpoints-list, $filter: key ) {
$bp-keys: map-keys( $breakpoints-list ); // Lists the breakpoint keys in the key:value pair
$bp-key-index-current: index( $bp-keys, $breakpoint ); // Log where the current breakpoint key is within the list of breakpoint keys
$bp-key-index-next: $bp-key-index-current+1; // Log the index for the next breakpoint within the list of breakpoint keys
$next-bp: nth( $bp-keys, $bp-key-index-next ); // Log the next breakpoint
$endpoint: map-get( $breakpoints-list, $next-bp ) - 1; // Get the value of the next input and subtract 1 unit
@media ( min-width: map-get( $breakpoints-list, $breakpoint ) ) and ( max-width: $endpoint ) { @content; }
}
@else {
@include _default-breakpoint ( get-breakpoint-value( $breakpoint ) ) { @content; }
}
}
// Unit Testing Function
////////////////////////
@function is-breakpoint ( $breakpoint ) {
@return map-has-key( $breakpoints-list, $breakpoint );
}
@function get-breakpoint-value ( $breakpoint ) {
@if type-of( $breakpoint ) == number {
@return $breakpoint;
}
@return map-get( $breakpoints-list, $breakpoint );
}
@function get-until-breakpoint ( $until-point ) {
@if type-of( $until-point ) == number {
@return $until-point;
}
@return map-get( $breakpoints-list, $until-point ) - 1; // Get the next breakpoint then subtract 1 unit
}
// -----------------------------------------
// Tests the parameters being passed into @mixin breakpoint against all rules and conditions
// @params - Equal to the @params in @mixin breakpoint
// @returns - Boolean representing if errors are present
@function breakpoint-unit-test ( $breakpoint, $until, $only ) {
// Set the test to passing by default until proven wrong.
$breakpoint-types : ( string, number ) !default;
// ___ Test $breakpoint ___
@if $breakpoint and index( $breakpoint-types, type-of( $breakpoint ) ) {
@if type-of( $breakpoint ) == string and not is-breakpoint( $breakpoint ) {
@warn 'Looks like #{$breakpoint} isn\'t a defined breakpoint. Acceptable breakpoints include: #{ map-keys( $breakpoints-list ) }';
@return true;
}
@if ( type-of( $breakpoint ) == number ) and (( unitless( $breakpoint ) ) and $breakpoint != 0) {
@warn '$breakpoint has no units! Gonna need to try again, buddy. Put in something like.. px or em... or something';
@return true;
}
}
@else {
@warn '$breakpoint has to be one of the following types: #{$breakpoint-types}. Yours is #{type-of( $breakpoint )}, silly';
@return true;
}
// ___ Test $until ___
@if $until {
@if index( $breakpoint-types, type-of( $until ) ) {
@if ( type-of( $until ) == string ) and ( not is-breakpoint( $until ) ) {
@warn ( "Looks like #{$until} isn't a defined breakpoint. Acceptable breakpoints include: #{ map-keys( $breakpoints-list ) }" );
@return true;
}
@if type-of( $until ) == number {
@if unitless( $until ) {
@warn '$until has no units! Gonna need to try again, buddy. Put in something like.. px or em... or something';
@return true;
}
@if $until <= get-breakpoint-value( $breakpoint ) {
@warn '$until has to be greater than $breakpoint ::: $breakpoint = #{get-breakpoint-value( $breakpoint )}, $until = #{$until}';
@return true;
}
}
}
@else {
@warn "$until has to be one of the following types: #{$breakpoint-types}. Yours is #{type-of( $until )}, silly";
@return true;
}
}
// ___ Test parameters ___
// You can't have a range of breakpoints and try to specify styles for only one breakpoint at a time. That's silly.
@if $until and $only {
@warn "Ooops!! You can't have both $until and $only! Pick one!!";
@return true;
}
@if $only and type-of( $breakpoint ) == number {
@warn 'Unfortunately you cannot set $only to true when $breakpoint is a number';
@return true;
}
@return false;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment