aphrodite icon indicating copy to clipboard operation
aphrodite copied to clipboard

how to write Nesting ?

Open zhishaofei3 opened this issue 6 years ago • 9 comments

.jump-btn {
            width: 32px;
            height: 32px;
            background: url('jump_btn.png') no-repeat 0 0;
            &.disabled {
                background-position: -32px 0;
            }
            &.back {
                background-position: -64px 0;
            }
}

zhishaofei3 avatar Apr 04 '18 07:04 zhishaofei3

This is somewhat not supported. The only real nesting that is supported is pseudo selectors (:hover, :active, etc) and media queries. The way you would do it in Aphrodite would be to do something like

const styles = StyleSheet.create({
  jumpBtn: {
    width: 32,
    height: 32,
    background: 'background: url('jump_btn.png') no-repeat 0 0',
  },
  disabled: {
    backgroundPosition: '-32px 0',
  },
  back: {
    backgroundPosition: '-64px 0',
  },
});
const classes = css(styles.jumpBtn, isDisabled && styles.disabled, isBack && styles.back);

There is an escape hatch by doing something like

const styles = StyleSheet.create({
  jumpBtn: {
    ...styles,
    ':not(foobar).disabled': { ....disabledStyles }
  },
});
const classes = 'disabled' + css(styles.jumpBtn)

I would try hard to not use this though.

dmiller9911 avatar Apr 05 '18 23:04 dmiller9911

thank you verymuch @dmiller9911

zhishaofei3 avatar Apr 10 '18 03:04 zhishaofei3

You can do that pretty easily by adding a custom selectorHandler with StyleSheet.extend

import { StyleSheet as Aphrodite } from 'aphrodite';
const { StyleSheet, css } = Aphrodite.extend([{
  selectorHandler: (selector, baseSelector, generateSubtreeStyles) => {
    if (selector[0] === '>') {
      const tag = selector.slice(1);
      const nestedTag = generateSubtreeStyles(`${baseSelector} ${tag}`);
      return nestedTag;
    }
    return null;
  }
}]);

Then just write your style as :

{
    color: "red",
    "> p": {
      textTransform: 'uppercase',
      fontSize: '0.8em',
      textDecoration: 'none'
    }
  }

Even better, just wrap all your styles into a function like this :

import _ from 'lodash';
function mapNestedStyle(stylesheet) {
  _.each(stylesheet, (item, selectorKey) => {
    const selectors = item._definition;
    _.each(selectors, (selector, key) => {
      if (_.isPlainObject(selector) && key[0] !== '>' && key[0] !== ':' && key[0] !== '@') {
        delete selectors[key];
        selectors[`> ${key}`] = selector;
      }
    });
  });
  return stylesheet;
}

So that you can just write :

{
    color: "red",
    p: {
      textTransform: 'uppercase',
      fontSize: '0.8em',
      textDecoration: 'none'
    }
  }

This can be automated using a React HOC.

I hope nesting will become part of the core functionalities at some point :)

nakwa avatar May 13 '18 16:05 nakwa

@nakwa Perfect!

I do think selectors for ' ' (space - all descendants) and >(direct child descendants) should be included - it seems wrong to have to add styles to every component that needs styling. For example, every label in a form... I want to just put the styles on the form to apply to all the labels.

I modified as follows

const { StyleSheet, css } = Aphrodite.extend([{
  selectorHandler: (selector, baseSelector, generateSubtreeStyles) => {
    if (selector[0] === ' ') {
      const tag = selector.slice(1);
      const nestedTag = generateSubtreeStyles(`${baseSelector} ${tag}`);
      return nestedTag;
    } else if (selector[0] === '>') {
      const tag = selector.slice(1);
      const nestedTag = generateSubtreeStyles(`${baseSelector} > ${tag}`);
      return nestedTag;
    }
    return null;
  }
}]);

so now this works:

Form: {
      borderWidth: 1,
      borderStyle: "solid",
      borderColor: "black",
      margin: 5,
      padding: 5,
      borderRadius: 7,
      " label" :{
        backgroundColor: "pink",
      }
    }

and produces:

.Form_1pi38cr {
    border-width: 1px !important;
    border-style: solid !important;
    border-color: rgb(66, 139, 202) !important;
    margin: 5px !important;
    padding: 5px !important;
    border-radius: 7px !important;
}
.Form_1pi38cr label {
    background-color: rgb(91, 192, 222) !important;
}

Would be great for it to be core functionality. Might look at the code base. I use typescript so had to modify the typings file slightly for linting to pass.

Sibz avatar May 28 '18 10:05 Sibz

Wasn't so hard to implement in base library: https://github.com/Khan/aphrodite/compare/master...Sibz:nesting

I'll ponder over the code a bit longer and then I might make it a PR.

Sibz avatar May 28 '18 17:05 Sibz

Yea, @Sibz , actually I think using a (space) or > ends up being quite unreadable. I've updated my script to only enable nesting when the first char of the selector is &, which I believe is the current proposal for nesting in CSS: https://tabatkins.github.io/specs/css-nesting/

nakwa avatar Jun 01 '18 10:06 nakwa

Also, in order to support the , selector (applying style to multiple selectors), and ensure it is still within the aphrodite scope :

import _ from 'lodash';
import { StyleSheet as Aphrodite } from 'aphrodite/no-important';

const { StyleSheet, css } = Aphrodite.extend([{
  selectorHandler: (selector, baseSelector, generateSubtreeStyles) => {
    const nestedTags = [];
    const selectors = selector.split(',');
    _.each(selectors, (subselector, key) => {
      if (selector[0] === '&') {
        const tag = key === 0 ? subselector.slice(1) : subselector;
        const nestedTag = generateSubtreeStyles(`${baseSelector} ${tag}`.replace(/ +(?= )/g, ''));
        nestedTags.push(nestedTag);
      }
    });
    return _.isEmpty(nestedTags) ? null : _.flattenDeep(nestedTags);
  }
}]);

export { StyleSheet, css };

so that :

    '& .container-fluid, .container-fluid > .row': {
      height: '100%',
      '& .woop': {
        backgroundColor: 'red'
      }
    },

generates :

[
  ".component_s3hzln .container-fluid{height:100%;}", 
  ".component_s3hzln .container-fluid .woop{background-color:red;}", 
  ".component_s3hzln .container-fluid > .row{height:100%;}", 
  ".component_s3hzln .container-fluid > .row .woop{background-color:red;}"
]

nakwa avatar Jun 01 '18 11:06 nakwa

This is really neat. I think it would be awesome if we could support nesting like this. If someone has time to put together a PR for this it would be greatly appreciated.

kevinbarabash avatar Apr 05 '19 03:04 kevinbarabash

I can, I used the actual CSS selectors [space] and > to support multi selection. It's been a long time since that project but it worked a dream!

Sibz avatar Apr 05 '19 03:04 Sibz