bernie
bernie

Reputation: 10390

Adding parent css class to mixins using LESS constructs such as "td&"

I wish to nest a number of rules inside a parent css selector using Less CSS. Usually this works just fine, but I've encountered a construct in Twitter Bootstrap's css that's problematic. Here's the pattern in question as a mixin:

.responsive-invisibility() {
    &,
  tr&,
  th&,
  td& { display: none !important; }
}

Here's how I wish to use it:

body.force-sm-viewport-or-higher {
    .visible-xs {
      .responsive-invisibility();
    }
    
    /* more stuff */
}

Here's what this produces:

body.force-sm-viewport-or-higher .visible-xs,
trbody.force-sm-viewport-or-higher .visible-xs,
thbody.force-sm-viewport-or-higher .visible-xs,
tdbody.force-sm-viewport-or-higher .visible-xs {
  display: none !important;
}

What I want:

body.force-sm-viewport-or-higher .visible-xs,
body.force-sm-viewport-or-higher tr.visible-xs,
body.force-sm-viewport-or-higher th.visible-xs,
body.force-sm-viewport-or-higher td.visible-xs {
  display: none !important;
}

Is this possible without "expanding" manually the mixins inside a body.force-sm-viewport-or-higher parent? Expanding the mixins would be very error-prone and tedious when upgrading Bootstrap.

NOTE

Here's where I found out what using the ampersand after a nested class name does: LESS CSS: abusing the & Operator when nesting?

EDIT 1

I also need to do the same thing with this mixin which is more complicated.

.responsive-visibility() {
  display: block !important;
  table&  { display: table; }
  tr&     { display: table-row !important; }
  th&,
  td&     { display: table-cell !important; }
}

Upvotes: 2

Views: 874

Answers (2)

seven-phases-max
seven-phases-max

Reputation: 11820

#1

Possible solution is to provide class name to append as mixin parameter:

.responsive-invisibility(@postfix) {
      @{postfix},
    tr@{postfix},
    th@{postfix},
    td@{postfix} {display: none !important;}
}

body.force-sm-viewport-or-higher {
    .responsive-invisibility(~'.visible-xs');
    /* more stuff */
}

Edit: you need to define that mixin on you own, i.e. do not modify the original Bootstrap .responsive-invisibility, then you'll be able to use both w/o conflict.

#2

A future-proof compatible solution similar to the solution suggested in @Scotts answer (and isnpuired by it) but producing less junk:

.invisible_ {
    .responsive-invisibility(); // use original Bootstrap mixin
}

.responsive-invisibility(@postfix) {
      @{postfix},
    tr@{postfix},
    th@{postfix},
    td@{postfix} {&:extend(.invisible_);}
}

body.force-sm-viewport-or-higher {
    .responsive-invisibility(~'.visible-xs');
    /* more stuff */
}

This way you won't have to modify invisibility styles if they do that in Bootstrap (but you still have to keep tracking the tr, th, td list in case they change it).

Upvotes: 3

ScottS
ScottS

Reputation: 72261

Agree with seven-phases-max that it is not possible. I have only been able to come up with a similar workaround to his, though in this version you do not repeat the property code block from bootstrap as it still calls the original bootstrap mixin. But what it is going to do is pollute your css with a bunch of "junk" that is not needed.

Make This LESS extension

.responsive-invisibility(@selector) {
  & @{selector},
  & tr@{selector},
  & th@{selector},
  & td@{selector}{ 
    .responsive-invisibility(); 
  }
}

Use it "outside" the selector you intend to target like so

Note this can take a long selector string--it does not need to be just one class.

body.force-sm-viewport-or-higher {
    .visible-xs {
      /*visible-xs stuff*/
    }
    .responsive-invisibility(~'.visible-xs');

    /* more stuff */
}

CSS Output (note the "junk" you get along with your desired output)

body.force-sm-viewport-or-higher {
  /* more stuff */
}
body.force-sm-viewport-or-higher .visible-xs {
  /*visible-xs stuff*/
}
body.force-sm-viewport-or-higher .visible-xs,     //you want this
body.force-sm-viewport-or-higher tr.visible-xs,   //you want this
body.force-sm-viewport-or-higher th.visible-xs,   //you want this
body.force-sm-viewport-or-higher td.visible-xs,   //you want this
trbody.force-sm-viewport-or-higher .visible-xs,   //junk
trbody.force-sm-viewport-or-higher tr.visible-xs, //junk
trbody.force-sm-viewport-or-higher th.visible-xs, //junk
trbody.force-sm-viewport-or-higher td.visible-xs, //junk
thbody.force-sm-viewport-or-higher .visible-xs,   //junk
thbody.force-sm-viewport-or-higher tr.visible-xs, //junk
thbody.force-sm-viewport-or-higher th.visible-xs, //junk
thbody.force-sm-viewport-or-higher td.visible-xs, //junk
tdbody.force-sm-viewport-or-higher .visible-xs,   //junk
tdbody.force-sm-viewport-or-higher tr.visible-xs, //junk
tdbody.force-sm-viewport-or-higher th.visible-xs, //junk
tdbody.force-sm-viewport-or-higher td.visible-xs  //junk
{
  display: none !important;
}

This may be more "upgrade" friendly since it still ultimately calls boostrap's mixin, but it is not very friendly on the css output.

Upvotes: 3

Related Questions