Dominik
Dominik

Reputation: 6313

Combine Less mixin output

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?

Upvotes: 2

Views: 83

Answers (1)

Harry
Harry

Reputation: 89750

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 */

Upvotes: 3

Related Questions