javivelasco / react-css-themr

Easy theming and composition for CSS Modules.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Inject parent class into children

denisborovikov opened this issue · comments

Let's imagine we have a button component with a base theme.

// SuccessButton.js
import React, { Component } from 'react';
import { themr } from 'react-css-themr';
import successTheme from './SuccessButton.css';

@themr('MySuccessButton', successTheme)
class Button extends Component {
  render() {
    const { theme, icon, children } = this.props;
    return (
      <button className={theme.button}>
        { icon ? <i className={theme.icon}>{icon}</i> : null}
        <span className={theme.content}>{children}</span>
      </button>
    )
  }
}

export default Button;

And we want to extend its styles inside of our own component.

import React from 'react';
import SuccessButton from 'SuccessButon';
import style from './Section.css';

export default () => (
  <section className={style.section}>
    <div className={style.content}>
        <SuccessButton theme={style}>Yai!</SuccessButton>
    </div>
  </section>
);

Section.css

.content { border: 1px solid red; }
.button  { text-transform: uppercase; }

As you can see the problem here is the same name of content node. We can't use the same css file for styles of our own component and component we use inside if they have the same class names.

We may try to update our own component and move styles into two different files:

import React from 'react';
import SuccessButton from 'SuccessButton';
import style from './Section.css';
import styleButton from './SectionButton.css';

export default () => (
  <section className={style.section}>
    <div className={style.content}>
        <SuccessButton theme={styleButton}>Yai!</SuccessButton>
    </div>
  </section>
);

Section.css

.content { border: 1px solid red; }

SectionButton.css

.button  { text-transform: uppercase; }

It will work. But what if we want to apply some styles on SuccessButton that depends of styles of our section component. I.e. make button's background red when hovering section.

It's possible to do when styles of our own component and SuccessButton are in the same file but impossible when they are in different files as they have its own scope.

So my idea is to have ability to "inject" specific parent classes to appropriate child classes.

Update the last example:

import React from 'react';
import SuccessButton from 'SuccessButton';
import style from './Section.css';
import styleButton from './SectionButton.css';

export default () => (
  <section className={style.section}>
    <div className={style.content}>
        <SuccessButton theme={styleButton} mapClasses={{button: styles.redButton}}>Yai!</SuccessButton>
    </div>
  </section>
);

Section.css

.content { border: 1px solid red; }
.section:hover .redButton { background: red }

SectionButton.css

.button  { text-transform: uppercase; }

redButton class will be added to the button node and will be available from our component.

I use wrapper for themr now in my project to add support of mapClasses property.

import compose from 'recompose/compose';
import mapProps from 'recompose/mapProps';
import { themr } from 'react-css-themr';

const themrmap = (...args) => compose(
  themr(...args),
  mapProps(({ ...props, mapClasses, theme }) => ({
    ...props,
    theme: mapClasses
      ? Object.keys(mapClasses).reduce((p, c) => ({
        ...p,
        [c]: `${theme[c]} ${mapClasses[c]}`,
      }), theme)
      : theme,
  })),
);

export default themrmap;

Do you think you can use this idea in the themr? Sorry If my explanation is not clear enough. Feel free to ask any questions.

Nailed it! I've been thinking about doing the exact same thing you proposed. It would be backwards compatible and solves the issue. I had this very issue with Dropdown Component in react toolbox. It uses an Input under the covers and defines the same classes for label, error, etc. This would make this lib way better than it is right now since this is the most important caveat I see by now.

Do you want to PR? It shouldn't be difficult

I'm still thinking about this... What about namespacing with an attribute instead of mapping? Less flexible but cleaner API. For example:

/* Section.css */
.content {
    & .buttonContent{
      color: red;
    }
}

And assuming that your Button has a classname API with a content node:

import React from 'react';
import Button from './ThemedButton.js';
import theme from './section.css';

const Section = () => (
  <section className={theme.content}>
    <Button theme={theme} namespace="button" />
  </section>
);

We can even support this namespace setup from the themr factory function (even falling back to the component identifier)so a button can have by default a namespace that could be enabled using a simple prop namespace:

const ThemedButton = themr('button', style)(Button);
// Or... themr('ContextIdButton', style, { namespace: 'button' })(Button);
<ThemeButton theme={theme} namespaced />

Or using recompose under the hood as you do now. Thoughts?

Namespacing looks good. Not so obvious as mapping but you don't have to list all classes manually. I like to use namespace as a second prop with the theme as you can see the namespace's name at the same place where you use it (in your parent component, not button itself.)

Great!

Here how I see it. namespace and theme props should be used together. I don't know if it makes sense to add support of the namespace with default/context styles.