htmlcsslessless-mixins

How to pass multiple class names to a mixin in LESS


I'm making a view that displays transactions on an account, and I want to colour-code the transaction type/state. I also want to show a legend explaining the colour codes.

I want an end result that's structured like this:

HTML

<table id="transactions">
  <thead>
    <tr>
      <th colspan="2">
        Transactions
      </th>
    </tr>
  </thead>
  <tbody>
    <tr class="credit">
      <td>A credit</td>
      <td>$1.00</td>
    </tr>
    <tr class="debit paid">
      <td>A paid debit</td>
      <td>($2.00)</td>
    </tr>
    <tr class="debit unpaid">
      <td>An unpaid debit</td>
      <td>($3.00)</td>
    </tr>
  </tbody>
</table>
<hr/>
<table id="legend">
  <thead>
    <tr>
      <th colspan="3">
        Legend
      </th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td class="credit">Credit</td>
      <td class="debit paid">Paid</td>
      <td class="debit unpaid">Unpaid</td>
    </tr>
  </tbody>
</table>

CSS

table#transactions > tbody > tr.credit {
  color: limegreen;
}

table#legend > tbody > tr > td.credit {
  color: limegreen;
}

table#transactions > tbody > tr.debit.paid {
  color: goldenrod;
}

table#legend > tbody > tr > td.debit.paid {
  color: goldenrod;
}

table#transactions > tbody > tr.debit.unpaid {
  color: crimson;
}

table#legend > tbody > tr > td.debit.unpaid {
  color: crimson;
}

(CodePen)

Note that the "debits" use two class names, to tell them apart from the credits.

Clearly there's a bit of redundancy there, which I tried to refactor into this (invalid) LESS code:

.transaction-status(@class, @color) {
  table#transactions > tbody > tr@{class} {
    color: @color;
  }

  table#legend > tbody > tr > td@{class} {
    color: @color;
  }  
}

.transaction-status(.credit, limegreen);
.transaction-status(.debit.paid, goldenrod);
.transaction-status(.debit.unpaid, crimson);

A possible workaround would be to rejigger things so the different transaction types have a single unique class name but that feels like time travelling to the time of IE6. I.e. I'm aware of, but would like to avoid this valid LESS, which seems so close, and yet so far:

.transaction-status(@class, @color) {
  table#transactions > tbody > tr.@{class} {
    color: @color;
  }

  table#legend > tbody > tr > td.@{class} {
    color: @color;
  }  
}

.transaction-status(credit, limegreen);
.transaction-status(debit-paid, goldenrod);
.transaction-status(debit-unpaid, crimson);

I tried quoting the class names, but even though that makes the first LESS sample compile, the quotes are passed to the output CSS. So, is there a way to pass something else than an "identifier" as a parameter to a LESS mixin, and have it work in selector interpolation correctly?


Solution

  • Option 1:

    As you mentioned, one option is to pass the value within quotes. But when that is done, you need to make sure to remove them before using them to perform selector interpolation. This can be done by either using the ~() or the e() built-in functions. Both of them will strip out the quotes from the input value. Once this is done, the temporary variable whose value doesnt have the quotes can be used for selector interpolation like below:

    .transaction-status(@class, @color) {
        @className: ~"@{class}";
        table#transactions > tbody > tr@{className} {
            color: @color;
        }
    
        table#legend > tbody > tr > td@{className} {
            color: @color;
        }  
    }
    
    .transaction-status(".credit", limegreen);
    .transaction-status(".debit.paid", goldenrod);
    .transaction-status(".debit.unpaid", crimson);
    

    Option 2: (a bit round about in my opinion)

    You can also use the ... option to pass in as many classes as required (note that this needs a small change to the order in which the inputs are passed) and then convert it to a string and use the replace function to add the .. The replace function is required because the converted string would be of the format class1 class2 (space separator).

    .transaction-status(@color, @class...) {
        @className: e(replace("@{class}" , " " , ".", 'g' )); 
        /* arguments: input string, pattern to match, replacement value, regex flags */
        table#transactions > tbody > tr.@{className} {
            color: @color;
        }
    
        table#legend > tbody > tr > td.@{className} {
            color: @color;
        }   
    }
    
    .transaction-status(limegreen, credit);
    .transaction-status(goldenrod, debit, paid);
    .transaction-status(crimson, debit, unpaid);
    

    Note: Option 2 would work only with Less v.1.7.0 and above because the replace function was introduced only in v1.7.0.