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.