-
-
Save vjeux/8e27dd06c64dc566bfee705e1b19cb18 to your computer and use it in GitHub Desktop.
// Solution #1: Generic Override (like CSS) | |
var ButtonGroup = React.createClass({ | |
render() { | |
return <div>{this.props.buttons.map((btn, i) => | |
<Button style={{ | |
...(i !== 0 && {marginLeft: 0}) | |
...(i !== this.props.buttons.length - 1 && {marginRight: 0}) | |
}} /> | |
)}</div>; | |
} | |
}); | |
var Button = React.createClass({ | |
render() { | |
return <div style={{ | |
...styles.button, | |
...this.props.style, | |
}} />; | |
} | |
}); | |
// Solution #2: Encapsulated behavior | |
var ButtonGroup = React.createClass({ | |
render() { | |
return <div>{this.props.buttons.map((btn, i) => | |
<Button | |
hasLeftSibling={i !== 0} | |
hasRightSibling={i !== this.props.buttons.length - 1} | |
/> | |
)}</div>; | |
} | |
}); | |
var Button = React.createClass({ | |
render() { | |
return <div style={{ | |
...styles.button, | |
...(hasLeftSibling && {marginLeft: 0}), | |
...(hasRightSibling && {marginRight: 0}), | |
}} />; | |
} | |
}); |
I've been struggling with this as well, working on Elemental UI.
@taion's example is exactly where we're falling down right now. The user (React library consumer) should control the children in the <InlineForm>
component, which may even be nested:
<InlineForm>
<EmailInput />
<PasswordInput />
<SomeComponent>
<LoginButton />
</SomeComponent>
</InlineForm>
You want the <LoginButton>
component to be in control of its own styles and understand how to change them when it's wrapped inside an <InlineForm>
component, rather than being on its own. This is an inversion of control to the first example, where you'd have to encapsulate understanding of how <LoginButton>
(and any other component)'s styles should change inside the Form component.
I think that React context is a reasonable way to achieve this at a library level. It doesn't answer how we detect things like firstChild
and lastChild
. Maybe adding those props in the render loop is the right answer. This solution covers most of the requirements I've got:
var ButtonGroup = React.createClass({
childContextTypes: {
isInsideButtonGroup: React.PropTypes.boolean,
},
getChildContext() {
return { isInsideButtonGroup: true };
},
render() {
const childCount = React.Children.count(this.props.children);
return <div>{React.Children.map(this.props.children, (c, i) =>
React.cloneElement(c, {
isFirstChild: i === 0,
isLastChild: i === childCount - 1,
})
)}</div>;
}
});
var Button = React.createClass({
contextTypes: {
isInsideButtonGroup: React.PropTypes.boolean,
},
render() {
return <div style={{
...styles.button,
...(this.context.isInsideButtonGroup && styles.buttonInsideGroup),
...(!this.props.isFirstChild && {marginLeft: 0}),
...(!this.props.isLastChild && {marginRight: 0}),
}} />;
}
});
here's a take on the above with glamor -
let addToButtStyle = merge(
not(':first-child', { margin:0 }),
lastChild({ marginRight: 10 })
)
class ButtonGroup extends React.Component {
render() {
return <div>
{this.props.labels.map(label =>
<Button style={addToButtStyle}>{label}</Button>)}
</div>
}
}
let defaultStyle = {
marginLeft: 10,
display: 'inline',
border: '1px solid black'
}
class Button extends React.Component {
render() {
return <div {...merge(defaultStyle, this.props.style)}>{this.props.children}</div>
}
}
// alternately, with context
@btnmerge(merge( // assume this decorator magically comes from button.js
not(':first-child', { margin:0 }),
lastChild({ marginRight: 10 })
))
class ButtonGroup extends React.Component {
render() {
return <div>
{this.props.labels.map(label =>
<Button>{label}</Button>) // no props!
}
</div>
}
}
// button.js
let defaultStyle = {
marginLeft: 10,
display: 'inline',
border: '1px solid black'
}
class Button extends React.Component {
static contextTypes = {
// yada yada
}
render() {
return <div {...merge(defaultStyle, this.context.buttStyle)}>{this.props.children}</div>
}
}
Trying to move discussion to styled-components/spec#5.
I think these both work fairly well assuming that the children of the "group" component are homogenous.
A lot of the compile-to-CSS approaches actually give you something similar by letting you "escape hatch" with something like a
> *
selector, or various:nth-child
selectors. CSS modules can't avoid giving you this, and @threepointone's glamor has a similar concept as well.I think where this does less well is if the children of the group are not homogeneous, but you still want to apply some styling there. Imagine you had:
where this was just one example of a broader concept of "button in inline form".
I've written up an intentionally horrible example at https://gist.github.com/taion/ac60a7e54fb02e000b3a39fd3bb1e944 in a bit more detail.