2

I am looking for some sort of extend to media queries but I know extend is not the right thing here.

I have a mixin that is supposed to create classes and media queries for each. Unfortunately my current mixin will create those one by one as you would expect which causes specificity issues.

My current mixins:

//create two classes and their media queries
._responsive-margins-top( 1, 6px );
._responsive-margins-top( 7, 60px );

//globals
@screen-xs: 480px;
@screen-sm: 768px;
@screen-md: 992px;
@screen-lg: 1200px;

//////////////////////////////////////| MARGINGS MIXIN
/*
 * MARGINS-TOP
 *
 * @param  @el         {string}  Element name appendix, We use numbers
 * @param  @value      {margin}  Space for this margin, We use px
 */
._responsive-margins-top( @el, @value ) {
    @xs: ~"top-margin@{el}-xs";
    @sm: ~"top-margin@{el}-sm";
    @md: ~"top-margin@{el}-md";
    @lg: ~"top-margin@{el}-lg";

    .@{xs},
    .@{sm},
    .@{md},
    .@{lg} {
        &:extend(.no-top-margin-xs all);
    }

    //////////////////////////////////////| XS
    .@{xs} {
        margin-top: @value;
    }

    //////////////////////////////////////|  SM
    @media (min-width: @screen-sm) {
        .@{sm} {
            margin-top: @value;
        }
    }

    //////////////////////////////////////|  MD
    @media (min-width: @screen-md) {
        .@{md} {
            margin-top: @value;
        }
    }

    //////////////////////////////////////|  LG
    @media (min-width: @screen-lg) {
        .@{lg} {
            margin-top: @value;
        }
    }
}

The output of this is as follows:

.top-margin1-xs {
    margin-top: 6px;
}
@media (min-width: 768px) {
    .top-margin1-sm {
        margin-top: 6px;
    }
}
@media (min-width: 992px) {
    .top-margin1-md {
        margin-top: 6px;
    }
}
@media (min-width: 1200px) {
    .top-margin1-lg {
        margin-top: 6px;
    }
}
.top-margin7-xs {
    margin-top: 60px;
}
@media (min-width: 768px) {
    .top-margin7-sm {
        margin-top: 60px;
    }
}
@media (min-width: 992px) {
    .top-margin7-md {
        margin-top: 60px;
    }
}
@media (min-width: 1200px) {
    .top-margin7-lg {
        margin-top: 60px;
    }
}

However what I need is this:

.top-margin1-xs {
    margin-top: 6px;
}
.top-margin7-xs {
    margin-top: 60px;
}
@media (min-width: 768px) {
    .top-margin1-sm {
        margin-top: 6px;
    }
    .top-margin7-sm {
        margin-top: 60px;
    }
}
@media (min-width: 992px) {
    .top-margin1-md {
        margin-top: 6px;
    }
    .top-margin7-md {
        margin-top: 60px;
    }
}
@media (min-width: 1200px) {
    .top-margin1-lg {
        margin-top: 6px;
    }
    .top-margin7-lg {
        margin-top: 60px;
    }
}

Any help is appreciated. I suspect this might not be possible in Less?

Harry
  • 87,580
  • 25
  • 202
  • 214
Dominik
  • 6,078
  • 8
  • 37
  • 61

1 Answers1

3

Option 1: (if you know all the classes)

It is complicated for sure but you can achieve this using Less loops. The key parts are changing the parent mixin to accept multiple arguments (that is, the @el, @value pairs) and the addition of a new mixin to loop through the arguments and generate the required output.

._responsive-margins-top(1, 6px;7, 60px); /* send all el + value pairs as argument */

@screen-xs: 480px;
@screen-sm: 768px;
@screen-md: 992px;
@screen-lg: 1200px;

/* parent mixin supporting multiple args */
._responsive-margins-top(@args... ) { 

  .loop-args(length(@args), xs); /* generate classes for xs size */

  @media (min-width: @screen-sm) {
    /* call the loop within media query so that all classes are generated at one go */
    .loop-args(length(@args), sm); /* generate classes for sm size */
  }

  @media (min-width: @screen-md) {
    .loop-args(length(@args), md); /* generate classes for md size */
  }

  @media (min-width: @screen-lg) {
    .loop-args(length(@args), lg); /* generate classes for lg size */
  }
}

/* loop mixin for iterating through el + value pairs */
.loop-args(@index, @size) when (@index > 0){ 
  @arg: extract(@args, @index); /* extract each el + value pair based on iteration index */
  @el: extract(@arg, 1); /* extract 1st value in el + value pair*/
  @value: extract(@arg, 2); /* extract 2nd value in el + value pair */
  @sel: ~"top-margin@{el}-@{size}"; /* form selector by concatenating text + el + size */

  .@{sel} {margin-top: @value;}
  .loop-args(@index - 1, @size); /* call the next iteration */
}

Option 2: (if you need to add classes later w/o editing base file)

The below seems a bit too verbose for my liking but is an option that you could use for your use-case. It involves writing the rules into a common mixin name within the parent responsive margins mixin and then calling them under the media queries.

Framework (base.less):

 ._responsive-margins-top(1, 6px);
._responsive-margins-top(7, 60px);

@screen-xs: 480px;
@screen-sm: 768px;
@screen-md: 992px;
@screen-lg: 1200px;

._responsive-margins-top(@el, @value) {
  @xs: ~"top-margin@{el}-xs";
  @sm: ~"top-margin@{el}-sm";
  @md: ~"top-margin@{el}-md";
  @lg: ~"top-margin@{el}-lg";

  .xs() {
    .@{xs} {margin-top: @value;}
  }
  .sm() {
    .@{sm} {margin-top: @value;}
  }
  .md() {
    .@{md} {margin-top: @value;}
  }
  .lg() {
    .@{lg} {margin-top: @value;}
  }
}

& {.xs();}
@media (min-width: @screen-sm) {.sm();}
@media (min-width: @screen-md) {.md();}
@media (min-width: @screen-lg) {.lg();}

Add-on: (file with extra class(es) from front-end devs)

@import "base.less"; /* import the base */

._responsive-margins-top(9, 90px ); /* call the margins mixin */
Harry
  • 87,580
  • 25
  • 202
  • 214
  • This looks great but just one reservation. I need be able to add .`_responsive-margins-top` to the code at a later time and by that add another class I have not foreseen yet. Your solution assumes I know all of them already. – Dominik Apr 17 '16 at 06:00
  • @Dominik: At a later time when you need to another class you still need to edit the code and so instead of calling the mixin again just edit the earlier call. Place the call to the parent mixin and the bottom of the file so that you can edit it whenever necessary. Other than this, I don't think there is a Less solution :( – Harry Apr 17 '16 at 06:02
  • 1
    Thabks Harry. We are writing a framework for the company and discourage our front-enders from from editing the source. I have to look at loops and if I can't get it to work I'll accept your answer as it does answer my question. (Just not my use case) thanks! – Dominik Apr 17 '16 at 06:05
  • 1
    This is exactly what I was hoping for. Kudos to you good sir! – Dominik Apr 17 '16 at 08:17
  • 2
    The second snippet (nice btw.) can be simplified to [this](https://gist.github.com/seven-phases-max/e73e09436ce35438a14482499ddc6574). – seven-phases-max Apr 17 '16 at 15:36
  • This is brilliant @seven-phases-max! Thanks – Dominik Apr 17 '16 at 23:43