Search code examples
csslessless-mixins

Less CSS - cut down repetition


I have the following code in Less:

I'm pretty sure this can be abstracted out further with some kind of mixin, but I've been scratching my head for a while now. I would like to be able to pass in a variable such as @xs or @xs-gutter and have the function pad out the code.

Any ideas?

.padding
{
    &.bottom {
        padding-bottom: @xs-gutter;
    }
    &.left {
        padding-left: @xs-gutter;
    }
    &.right {
        padding-right: @xs-gutter;
    }
    &.top {
        padding-top: @xs-gutter;
    }

    @media @sm-screen {
        &.bottom {
            padding-bottom: @sm-gutter;
        }
        &.left {
            padding-left: @sm-gutter;
        }
        &.right {
            padding-right: @sm-gutter;
        }
        &.top {
            padding-top: @sm-gutter;
        }
    }
    @media @md-screen {
        &.bottom {
            padding-bottom: @md-gutter;
        }
        &.left {
            padding-left: @md-gutter;
        }
        &.right {
            padding-right: @md-gutter;
        }
        &.top {
            padding-top: @md-gutter;
        }
    }
    @media @lg-screen {
        &.bottom {
            padding-bottom: @lg-gutter;
        }
        &.left {
            padding-left: @lg-gutter;
        }
        &.right {
            padding-right: @lg-gutter;
        }
        &.top {
            padding-top: @lg-gutter;
        }
    }
}

Solution

  • You could use loops and array list to reduce the repetition in the code. The below is a sample snippet on how to achieve reduction.Refer inline comments for explanation on what the code is doing.

    Note: I have made the actual padding generation mixin as a separate one which takes the sides as an argument because you can re-use that mixin to generate padding for multiple sides (by passing the sides and gutter as arguments) without generating media-queries for them.

    @gutters: 4px, 6px, 8px, 10px; // the gutter sizes corresponding to each screen size
    @media-sizes: xs, sm, lg, md; // possible screen sizes
    @media-conditions: ~"(min-width: 100px)", ~"(min-width: 150px)", ~"(min-width: 200px)", ~"(min-width: 250px)"; // media condition for each screen size
    
    .media-generator(){
        .loop-sizes(length(@media-sizes)); // loop through all screen sizes
        .loop-sizes(@screenIndex) when (@screenIndex > 0) {
            & when (extract(@media-sizes, @screenIndex) = xs){ // since we need xs as default
                .padding-per-side(extract(@gutters, 1); left; right; bottom; top);
            }
            & when not (extract(@media-sizes, @screenIndex) = xs){ // when screen size is not xs
                @condition: extract(@media-conditions, @screenIndex); // extract media condition corresponding to screen type
                @media @condition{
                    .padding-per-side(extract(@gutters, @screenIndex); left; right; bottom; top); // call the mixin to generate padding for all sides
                }
            }
            .loop-sizes(@screenIndex - 1);
        }
    }
    .padding-per-side(@gutter; @sides...){
        .loop-sides(length(@sides));
        .loop-sides(@index) when (@index > 0){
            @side: extract(@sides, @index);
            &.@{side}{
                padding-@{side}: @gutter;
            }
            .loop-sides(@index - 1);
        }
    }
    .padding{
        .media-generator(); // generate padding for all sides and screens like in question
    }
    
    #demo{ // extra :)
        .padding-per-side(10px; left;right); // generates 10px padding for left and right
    }
    

    The below is an enhanced version of the above which allows us to generate the padding with media queries only for some sides. The difference between the below snippet and the above one is that here you can generate padding for specific sides alone along with their media-query version.

    @gutters: 4px, 6px, 8px, 10px;
    @media-sizes: xs, sm, lg, md;
    @media-conditions: ~"(min-width: 100px)", ~"(min-width: 150px)", ~"(min-width: 200px)", ~"(min-width: 250px)";
    
    .media-generator(@sides...){
        & when (length(@sides) = 0){
            .loop-sizes(length(@media-sizes));
            .loop-sizes(@screenIndex) when (@screenIndex > 0) {
                & when (extract(@media-sizes, @screenIndex) = xs){
                    .padding-per-side(extract(@gutters, 1); left; right; bottom; top);
                }
                & when not (extract(@media-sizes, @screenIndex) = xs){
                    @condition: extract(@media-conditions, @screenIndex);
                    @media @condition{
                        .padding-per-side(extract(@gutters, @screenIndex); left; right; bottom; top);
                    }
                }
                .loop-sizes(@screenIndex - 1);
            }
        }
        & when not (length(@sides) = 0){
            .loop-sizes(length(@media-sizes));
            .loop-sizes(@screenIndex) when (@screenIndex > 0) {
                & when (extract(@media-sizes, @screenIndex) = xs){
                    .padding-per-side(extract(@gutters, 1); @sides);
                }
                & when not (extract(@media-sizes, @screenIndex) = xs){
                    @condition: extract(@media-conditions, @screenIndex);
                    @media @condition{
                        .padding-per-side(extract(@gutters, @screenIndex); @sides);
                    }
                }
                .loop-sizes(@screenIndex - 1);
            }
        }   
    }
    .padding-per-side(@gutter; @sides...){
        .loop-sides(length(@sides));
        .loop-sides(@index) when (@index > 0){
            @side: extract(@sides, @index);
            &.@{side}{
                padding-@{side}: @gutter;
            }
            .loop-sides(@index - 1);
        }
    }
    .padding{
        .media-generator(left; right); // specify sides if needed else leave blank
    }