typestyle icon indicating copy to clipboard operation
typestyle copied to clipboard

Using typestyle-generated classes in $nest selectors

Open benduran opened this issue 5 years ago • 5 comments

Hi everyone! First off, I'd like to say that I've been using TypeStyle in a few libraries and apps (in production!) and I think it is fantastic. Please, keep up the good work 👍

That being said, naturally, I do have a question / suggestion about the ability to use top-level generated TypeStyle classNames in $nest selectors. Sometimes, due to library updates or integration issues, relying on a component to always generate the same nested HTML is spotty (though, in an ideal world, this would not be the case). As such, I've created a simple TypeStyle Extensions library which allows an engineer to use their top-level classnames in a manner similar to jss: https://github.com/benduran/typestyle-extensions

The idea behind this library was to have a single function call that would cover ~90% of all CSS-in-JS styling solutions an engineer would need for common paradigms. Here's the example demonstrated in the typestyle-extensions readme:

import { createStyles } from 'typestyle-extensions';

const styles = createStyles({
  icon: {
    fontSize: '1em',
    height: '1em',
    width: '1em',
  },
  myButton: {
    $nest: {
      '&:hover': {
        $nest: {
          '& > $icon': { // Use the generated classname for "icon" here in a nested selector
            color: 'pink',
          },
        },
        backgroundColor: 'blue',
        color: 'white',
      },
    },
    backgroundColor: 'transparent',
    border: '1px solid blue',
    color: 'blue',
  },
});

// Your React component
const MyComponent = () => (
  <button className={styles.myButton}>
    <svg className={styles.icon}>{ /*...*/ }</svg>
  </button>
);

export default MyComponent;

I'd love to get your feedback on this idea and whether or not the core TypeStyle library could benefit from having such things included in it, or if it would be better off keeping this as a separate extensions library. Thanks!

benduran avatar Jun 10 '19 14:06 benduran

Thanks for the kind words! :rose: :heart: 💯

I don't like the type unsafety of $icon. My current solution consists of known classes. Here is an example from our internal library, that we are sadly unable to open source as a whole 😢

// styles.ts

export namespace KnownClasses {
  // some styling class names
  export const onErrorGoRed = 'ap-on-error-go-red';
  export const inTooltipBeWhite = 'ap-in-tooltip-be-white';

  // some action class names 
  export const onErrorFocusHere = 'ap-error-focus-here';
  export const onSectionEnterFocusHere = 'ap-section-enter-focus-here';
}

// Something that applies styles to its children

export const errorClass = typestyle.style({
    color: Colors.errorColor,
    $nest: {
      ['& .' + KnownClasses.onErrorGoRed]: {
        color: Colors.errorColor
      }
    }
  });

// Something that listens to changes requested by parent e.g. paragraphs

export const pClass = typestyle.style(
    wrapStyle,
    {
      color: Colors.normalColor,
      fontFamily: FontFamilies.normal,
      fontSize: FontSizes.paragraph,
      lineHeight: FontSizes.paragraphLineHeight,
      margin: '0px',
    }
  ) + ' ' + KnownClasses.onErrorGoRed;

basarat avatar Jun 11 '19 00:06 basarat

Thanks for sharing. Your proposal feels similar in some ways, but obviously differs a little on that it requires managing the class names manually in an app integrated with TypeStyle.

With regards to your comment on using the $icon syntax for marking that a parent class selector should be used, would you have any recommendations to improve upon this?

benduran avatar Jun 12 '19 15:06 benduran

We have been dealing with the same challenge, and made an enriched variant of stylesheet() that can make $nest selectors refer to other classes in a typesafe manner.

I've created a gist to demonstrate the idea here: https://gist.github.com/mntnoe/7c26f39e44223e3741ae4307a9d52b8b

Any feedback is greatly appreciated.

mntnoe avatar Jan 29 '20 16:01 mntnoe

I think perhaps something like this might be workable:

const styles = stylesheet({
  card: {},
  button: (parent) => ({
    color: 'red',
    parent.card({
      '&:hover' {
        color: 'blue'
      }
    })
  }),
});

The parameter for each function could have a function for each top level class that is in the stylesheet that would act as a parent selector. With the output effectively being something like this (with automatic $debugName as is typical)

.card {}

.button {
  color: red;
}

.card .button:hover {
  color: blue;
}

I think this would be backward compatible, but might require rethinking the order in which the stylesheet is evaluated. If we assume all top level classes should be unique, it might be a lot simpler.

notoriousb1t avatar Mar 14 '20 22:03 notoriousb1t

I did some prototyping in TypeStyle and found something that could work.

See prototype test here with usage: https://github.com/typestyle/typestyle/blob/prototype-nested/src/tests/basic.tsx#L142

See prototype implementation here: https://github.com/typestyle/typestyle/blob/prototype-nested/src/internal/typestyle.ts#L210

There is certainly some API niceness to work out, but this feature seems possible to add without adding too much more code. It would change stylesheet to not deduplicate styles though.

notoriousb1t avatar Mar 15 '20 20:03 notoriousb1t