Here at Buildkite, weāve got a slightly odd situation with our styles. Weāve traditionally used Basscss in an attempt to make sure weāre DRY and things are conceptually simple, and partly in an effort to keep engineers and CSS usually-far-apart.
Weāve now hit the point where we want some bespoke stuff, and I think Styled Components best fills the gap we have - we want simplicity on one end and explicity and specificity on the other. Basscss fills the former, Styled Components the latter.
Iāve found, with this, that I want some sort of composition functionality. Given that the current approach would seem to be, implement your styles in one place, and set the classes you expect to compose elsewhere, it feels like itās a bit less than ideal!
After a bit of fiddling Iāve settled for now on the workaround of setting defaultProps.className on the styled component;
const Title = styled.h1`
font-size: 1.5em;
text-align: center;
color: palevioletred;
`;
Title.defaultProps = {
className: 'meow nya'
};
// => <h1 className="meow nya kZQsGZ" ā¦
That way, the composition is in the same āplaceā as the Styled Component definition ā but I wonder if the Styled Components API _itself_ should explicitly support this!
Having thought about the API surface area, it feels like a JS approach isnāt perhaps the best, as I feel while it would probably be the simplest solution to engineer, it would probably produce a rather more confusing and ugly API;
const Title = styled.h1.withClass("meow nya")`
font-size: 1.5em;
text-align: center;
color: palevioletred;
`; // => <h1 className="meow nya kZQsGZ" ā¦
// ostensibly the same as
styled('h1').withClass("meow nya")//ā¦
Instead, Iād propose an approach making use of the ability to extend syntax with PostCSS, like this;
const Title = styled.h1`
@compose 'meow nya';
@compose 'purr';
font-size: 1.5em;
text-align: center;
color: palevioletred;
`; // => <h1 className="meow nya purr kZQsGZ" ā¦
A few more things which would require consideration;
_Partly adapted from a brief, hit-and-run discussion in the Styled Components gitter_ š
Bonus round: CSS technology soup;
import { meow, nya } from 'some-css-module.css';
const Title = styled.h1`
@compose ${meow};
@compose ${nya};
font-size: 1.5em;
text-align: center;
color: palevioletred;
`; // => <h1 className="c_windows_system32_contrived-examples_some-css-module__meow c_windows_system32_contrived-examples_some-css-module__nya kZQsGZ" ā¦
Ok so I _do_ think this is worth supporting, as using immutable, atomic classnames for the backbone of your styling then using SC to write per-component deviations is kinda neat, and a nice way for people to ease in to more JS-based abstractions.
But @composes is tricky. We support nesting in SC but @composes can't work within nested components, so we'll have the same logical inconsistency that CSS Modules suffers, where composes: works _mostly_ like @extend in Sass but not quite. So I'm very hesitant to add some CSS extension that only works at the root level. I'm actually hesitant to add _anything_ custom in the CSS blocks tbh, since one of the points of this project is to move more things into JS but keep the same fluidity of writing CSS.
That said, there are two options that I can see fitting with our existing API. These are:
const Title = styled.h1`
${ compose(' meow nya') }
${ compose('purr') }
font-size: 1.5em;
text-align: center;
color: palevioletred;
`
Then we need compose() to return some special object that we detect during the interpolation and apply to the parent component instead of the CSS snippet we're building. That's... possible but a bit unexpected, and suffers from the same problem that it doesn't work with nesting.
My other idea is this, a throwback to the good old days of HAML :)
const Title = styled('h1.meow.nya.purr')`
font-size: 1.5em;
text-align: center;
color: palevioletred;
`
Semantically, that makes more sense to me, because styled(x) means you're styling an x. And it's kinda ok that that x has classes already attached. But it's a bit of a weird mashup of syntaxes.
What do you think?
Iām with you on not trying to extend your syntax! Totally makes sense and the other option would kind of be a pandoraās box š¦
Interpolation of a compose object seems, frankly, even messier?
Perhaps youāre onto the right thing with that latter option. Iād be a little wary to mix concerns in the initial argument like that - the fewer things weāre āparsingā the better, I think :wink:
How about something like this?
const Title = styled('h1', 'meow nya purr')`
font-size: 1.5em;
text-align: center;
color: palevioletred;
`
Leveraging the second argument means you could, ostensibly, keep your shortcut functions very simple - youād probably be able to just use bind to make this simple, in factā¦
// src/constructors/styled.js
const styled = (tag: Target, composeClass: string) =>
(strings: Array<string>, ...interpolations: Array<Interpolation>) =>
styledComponent(tag, css(strings, ...interpolations), composeClass)
/* Shorthands for all valid HTML Elements */
// Thanks to ReactDOMFactories for this handy list!
//...
styled.h1 = styled.bind(null, 'h1')
const Title = styled.h1('meow nya purr')`
font-size: 1.5em;
text-align: center;
color: palevioletred;
`
Hmm, the second argument thing I'm not so keen on. But this kinda would work:
const Title = styled.h1.classes('meow nya purr')`
font-size: 1.5em;
text-align: center;
color: palevioletred;
`
The implementation of that would be much nicer, too. Only once the template function is actually evaluated does the component get created, so chaining a method like classes just creates a new template function with a closure over the classnames. This is all relevant to #227's changes that I'm working on at the moment, the difference being that this would be a user-facing API so it needs to be... nice for humans :)
I would suggest naming the method className instead of classes, to be more in line with react.
const Title = styled.h1.className('meow nya purr')`
font-size: 1.5em;
text-align: center;
color: palevioletred;
`;
But if you're going with this, I would also suggest a style or css method, just for synthetic suger:
const Title = styled.h1
.className('meow nya purr')
.style`
font-size: 1.5em;
text-align: center;
color: palevioletred;
`;
Whoa, very interesting! I only picked classes because I had thought about applying a complex object ala the classnames library, but given these are defined at the top level (not inside a render method with access to props) that probably doesn't make sense. However, the className prop would still be used to pass _extra_ classes in so I probably don't want to collide the name.
I kinda don't mind this syntax as a kind of 'expanded' mode btw:
const Title = styled.h1
.displayName('Title')
.identifier('Title-a1b2c3d4')
.classes('meow nya purr')
.css`
font-size: 1.5em;
text-align: center;
color: palevioletred;
`
<Title className='extra'>
// => <h1 class="Title-a1b2c3d4 e9f8a7b6 meow nya purr extra">...
This sits kinda well with me as a flexible way to attach extra configuration to a component as it's created. The displayName and identifier stuff is gonna be injected by the babel transform, btw, so don't worry about that, so only classes|classNames and css|styles (still open on the naming here) would be added to the public API.
I'm gonna give this a crack and see how it feels.
This sounds like a pretty promising compromise! Would this make calling .css mandatory or would it be skippable at the top (styled.h1 or styled('h1')) level?
In the mean time Iāve actually realised I could actually totally just make a simple wrapper component which lets you do this;
const Title = styled(classed('h1', 'meow nya purr'))`
font-size: 1.5em;
text-align: center;
color: palevioletred;
`
I guess thinking about it this way Iād actually still be open to this concept being rejected as ānot accepted to be styled-componentsā responsibilityā š
The .css would be optional yeah. And yeah your idea would work but then you've got an extra layer of components. I'd like to avoid that if possible. I've finished my proof-of-concept, I think it looks kinda good.
To be honest, i don't see the benefit here. Imho, styled-components main goal should be to enable simple, feature-rich styling of components. I would not mix that with other style systems such as resets, grids or whatever which can very well be added using attributes of the styled components:
const Title = styled.h1`
font-size: 1.5em;
text-align: center;
color: palevioletred;
`;
// in a render method
<Title className="meow nya" />
// => <h1 className="meow nya kZQsGZ" ā¦
Isn't this already possible and fine as is?
@code-punkt: the thing we have here though is a mix of approaches, like it or not we have a mature app which is already using Basscss. We're not mixing with a reset or a grid, but the proposed approach allows us to better encapsulate the way our existing classes interact with the specific enhancements we plan to use styled-components for.
Yes, that syntax is possible, but tha syntax requires us to physically separate our use-specific styles (which we are authoring with styled-components) and the class-per-thing approach of Basscss, when ultimately in each component we make in this way, the former relies on the latter.
@ticky So that syntax is possible - glad to hear. What i don't get is the "physical separation" argument. Why is my proposed syntax not a good idea?
Like I said, it has an advantage in _encapsulation_.
Your styled component definition will very likely exist outside your react component class definition. There could be dozens or hundreds of lines between the definition of the styled component and the actual use of it.
Thus, the classes it may in fact rely on can be logically, conceptually and physically separated (by many lines of code) from the definition of the styled component CSS, which would actually seem to be counter to the encapsulation goals of styled components.
And yes, the use of third-party styles could be argued to be counter to those same goals, however, functionality already exists within styled components to apply additional class names, so I contend that styled components does not in fact intend to obviate their use in your code entirely.
This functionality, therefore, merely ensures that they are applied more consistently where needed.
Yeah I'm with @ticky on this one too. The whole point of styled components' API versus, say, Glamor, is this deliberate encapsulation. Let me see if I can demonstrate:
/* glamor-ish-style */
<div {...css({
display: 'flex',
alignItems: 'center',
justifyContent: 'center'
})}>
<h1 {...css({
color: 'palevioletred',
background: 'papayawhip'
})>
Hello World
</h1>
</div>
/* styled components style */
const Outer = styled.div`
display: flex;
align-items: center;
justify-content: center;
`
const Title = styled.h1`
color: palevioletred;
background: papayawhip;
`
<Outer>
<Title>Hello World</Title>
</Outer>
Now, of course in Glamor you'd extract out the styling blocks to variables but you don't _have_ to. In Styled Components you're (mostly) forced to name everything. By naming something you give it a real layer to attach meaning to, and something that can travel between files. Styled Components is basically driven off the assertion that that separation is really good ā that all your styling is contained in a logical unit.
In that vein, this doesn't pass the test:
const Outer = styled.div`
justify-content: center;
`
const Title = styled.h1`
color: palevioletred;
background: papayawhip;
`
<Outer className="flex flex-center">
<Title>Hello World</Title>
</Outer>
But a proper classes method (or some custom interpolation) does. So... I think we should add it. It also has implications for #227 (which currently implements classes but @mxstbr doesn't like the syntax yet š)
Any other API options I'm missing?
Another API would probably:
const Title = styled.h1`
color: palevioletred;
background: papayawhip;
`
Title.classes = "flex flex-center"
IMO this doesn't look as clean as your approach but is a more React style (propTypes, defaultProps)
A recompose compatible .compose api would fill in a lot of holes.
import {mapProps} from 'recompose';
const withClassName = (name) =>
mapProps((ownProps) => {
className: [name, ... ownProps.className],
...ownProps,
});
const Title = styled.
.compose(
withClassName('blah')
).h1`
color: palevioletred;
background: papayawhip;
`
I like the idea of using a composer for this. I guess that means there is actually nothing needed in this library? Because styled.h1, is already returning a normal component that's compatible with any react compose interface, like recompose.
The code below is just the example from @nelix quickly rewritten; but I guess it should work as is:
import { compose, mapProps } from 'recompose';
import styled from 'styled-components';
export const Component = styled.h1`
color: palevioletred;
background: papayawhip;
`;
const withClassName = className => mapProps(props => ({
...props,
className: [className, props.className].join(' ')
}));
export default compose(
withClassName('faded')
)(Component);
This will definitely be helpful in transitioning apps to a more css-in-js approach.I can already see a use case for my project where i still use some of Bootstrap's styles and want to apply them to a collection of components.
The way i do it now is explicitly specifying the inline css's injection order in the <head> section by tweaking style-loader's config(i use webpack) .This is necessary because i ran into source-order specificity problems when tweaking some styles...SC's inline css was included before Bootstrap's by the bundler so my styles where overwritten by the defaults. @geelen also wrote about it in #227(the af bug).
By the way, in your documentation you state that style-loader's injection order cannot be controllled.Actually the insertAt option gives a little bit of control, at least for solving simple problems....
Yeah definitely using something like a recompose API would work and, in fact, already does, because this is Just Reactā¢. But I think first-class support for this makes a lot of sense, and becomes part of the public API that can be ported to non-React environments.
Still trying to find a syntax that I really like though. Gone a bit off classes and chaining, and haven't had a good solid block of time to think about alternatives.
The more I think of this, the less reason I see to implement this functionality in styled-components. It is already supported by using recompose. What does first-class support add to this?
I've setup a bin to show this functionality working: http://www.webpackbin.com/VkZLCnnXG.
Basically:
title.js
import { compose, mapProps } from 'recompose';
import styled from 'styled-components';
export const Component = styled.h1`
color: palevioletred;
background: papayawhip;
`;
const withClassName = className => mapProps(props => ({
...props,
className: [className, props.className].join(' ')
}));
export default compose(
withClassName('faded')
)(Component);
otherfile.js
import Title, { Component as TitleComponent } from './Title';
const HelloWorld = () => (
<div>
<Title>Recomposed Styled title</Title>
<TitleComponent>Styled title, non recomposed</TitleComponent>
<Title className="blue">Recomposed styled title, Layout added .blue</Title>
<TitleComponent className="red">Styled title, Layout added .red, non recomposed</TitleComponent>
</div>
);
IMO this does make more sense to be included with Styled Components. To me, it feels consistent with the goals of Styled Components, and the response of ājust add this additional dependency which mostly does unrelated thingsā feels weird to me.
I agree with @ticky here...Introducing recompose as a dependency, which also brings along functionality the developer _probably_(?) doesn't need, to support a Styled-Components use-case seems a bit strange...Maybe it's me...Btw, i have nothing against recompose,i actually like what they're doing!
This brings me to another question.Should the Styled-Components styles be more specific than the "global" ones?Because, If left to it's own fate, rule specificity will depend on source-order inclusion of the CSS.Please correct me if i'm wrong here.
Thanks for the code examples @smeijer, you helped me clarify a few things in my head š.
You don't need a dependency for this at all, no need to introduce recompose:
// MyComp.js
const StyledComp = styled.div``
export default ({ className, ...props }) => <StyledComp className={`meow nya ${className}`} {...props} />
Everybody who import MyComp from '../components/MyComp' gets the "meow nya" className, they are part of the component!
Responding to the response to the styled(classes('h1', 'meow nya')) idea by @ticky above:
And yeah your idea would work but then you've got an extra layer of components. I'd like to avoid that if possible.
I don't quite understand why we should introduce additional API surface just to add functionality that's already trivial to do? Sure, you have one layer of component more, but that's a way better tradeoff than us adding more complexity and API surface. (which impacts every single person working with styled-components, whereas a single wrapper more doesn't impact anybody)
Right now I'm not a huge fan of this API extension to be honest. Am I missing something? (Note: I haven't practically tried any of these yet, all theoretical at this point)
Excellent points.I guess your wrapper also takes care of encapsulation in the SC spirit,everything is in the same file.
I don't quite understand why we should introduce additional API surface just to add functionality that's already trivial to do?
You could argue the same for Styled Components itself! š Something being "trivial" for someone with a thorough understanding of the mechanics underneath is _very_ different from something being afforded, even encouraged by the design of an interface.
I think attaching classnames to a styled component _does_ deserve first-class treatment because of the architecture it permits. Small, atomic, static CSS as your "backbone" plus per-component CSS that's expressed as snippets of CSS. I think it's a real best-of-both worlds situation, not just for existing apps but for new projects too (i.e. I'd recommend this to a lot of teams that I've worked with recently)
Doing it manually seems like a lot of noise for not much signal:
const _X = styled.div` ... `
const X = ({ className, ...props }) => <_X className={`foo bar ${className}`} {...props} />
/* vs */
const X = styled.div.classes('foo bar')` ... `
Beyond classnames, I think extends is a much better way of sharing CSS between components than what we have right now:
const X = styled(Y)` ... ` // where Y is a Styled Component
/* vs */
const X = styled.div.extends(Y)` ... `
I'm fairly convinced these are both good changes, but again, pretty happy to change up the syntax:
/* 1 */
const X = styled.tagName
.extends(Y)
.classes('foo bar baz')
`
...
`
/* 2 */
const X = styled.tagName`
${ addClasses('foo bar baz') }
${ copyStylesFrom(Y) }
`
/* 3 */
@extends(Y)
@classes('foo bar baz')
const X = styled.tagName` ... `
/* 4 */
const X = styled.tagName.withConfig({ classes: 'foo bar baz', extends: Y})`
...
`
/* 5 */
const X = styled(extends(Y, classes('tagName', 'foo bar baz')))`
...
`
#1 is certainly my favourite. It doesn't require extra imports, the implementation is straightforward, it's concise, etc. #2 is clever and potentially more powerful (if we defined a mechanism for these interpolations to modify the parent component then users could define their own) but I think it's too surprising. #3 might not even be valid, I dunno, but annotations aren't in a great state atm. #4 is totally fine but seems unnecessary, and complicates the fact we already are using withConfig for internal usage. #5 is too Polish for my liking.
Any others?
I like #1, and #5 (try indenting #5, it's not that bad :P)
Don't really like chaining, because it means thst unused things can't be removed by tree shaking.
The most important thing to me is that things compose, so compatibility with rambda/recompoe/lodash's compose functions (without much curry) is ideal.
By default, styled() is already a composing stuff, functions and strings... we can use another type to extend props.
Basically the only goal, is to mapProp, I don't think the api would ever need any other composition?
import styled from 'styled-components';
export const Component = styled.h1`
color: palevioletred;
background: papayawhip;
`;
export default styled({
className: (cn) = > cn + ' myClass',
// maybe useful for refs and keys too?
})(Component);
Its a bit verbose....
import styled from 'styled-components';
const withClass = (extraClass) => ({className: (cn) => `${cn} ${extraClass}`});
export const Component = styled.h1`
color: palevioletred;
background: papayawhip;
`;
export default styled(withClass('MyClass'))(Component);
Getting stupid now.
import styled from 'styled-components';
export const Component = styled.h1`
color: palevioletred;
background: papayawhip;
`;
export default styled({
onChange: (handle) => (evt) => handle(evt.target.value.strip())
})(Component);
Hmm, you might be on to something there. Maybe #184 is related too. @mxstbr what's the React Native problem you talk about over there? Maybe some kind of generic prop-injecting syntax could solve this and other things.
Btw @nelix I wouldn't worry too much about tree shaking in this case. The implementation for the chained classes method is literally one line.
This is what I talk about there: #149, which is tangentially related to #184
Can we solve all of them in one swoop with the same API do you think??
That's what I'm thinking but I don't really have my head around the API options that would suit #149. I'll start digging tho
import React from 'react';
import styled, {classProp, styleObjProp} from 'styled-components';
import Link from 'react-router'; // or CSSTransitionGroup, react-native
const HotLink = styled(Link)`
background: white;
${classProp('activeClassName')`
color: red;
`}
${styleObjProp('containerStyle')`
display: flex;
`}
`
Basically classProp generates a className and passes it in as that value, you could also have it accept a function or fancy mapProp stuff.
Another idea.
import React from 'react';
import styled, {css, mapStyle} from 'styled-components';
import Link from 'react-router';
const active = css`
color: red;
`
const base = css`
color: blue;
`
const HotLink = styled(
Link,
mapStyle(
({className}, asClass, asObj) => ({
activeClassName: [asClass(active), className].join(' '),
className: asClass(base),
style: asObj(base),
})
)
)`
background: white;
`
// OR
const HotLink = styled(
Link,
mapStyle({
activeClassName: active,
className: base,
})
)`
background: white;
`
This api also has room for things like the withRef(cb) and moving the theme context stuff out of the base component?
What about.. ?
import React from 'react';
import styled, {css, mapStyle} from 'styled-components';
import Link from 'react-router';
const HotLink = styled(
Link,
mapStyle(
({className}, {base, active, default}, {asClass, asObj}) => ({
activeClassName: [asClass(active),
className: [asClass(base), className].join(' '),
style: asObj(base),
})
)
)`
background: white;
${css('active')`
color: red;
`}
${css('web')`display: block;`}
${css('base')`
display: flex;
`}
`
Hmm, I think your first example is getting somewhere, but I really don't see what's going on in the second two. I don't think we should provide arbitrary functions like that as a second argument, and the whole thing gives me a very redux mapStateToProps vibe, which... I think is distracting from the purpose of SC.
Remember that you can always wrap components in others, so if we introduce anything into SC itself it has to be simpler and more descriptive than doing it with two components.
But back to your first idea, I have been thinking that we might be able to come up with an API for interpolations to change the construction of the SC they act upon. So, something like:
import styled, {css, props} from 'styled-components'
import Link from 'react-router'
const HotLink = styled(Link)`
background: white;
${ props.addClasses('meow nya purr') }
${ props.passAs('activeClassName')`
color: red;
` }
${ props.passAsObject('containerStyle')`
display: flex;
` }
`
The tricky thing is what do these interpolations return? The API between the interpolation and the Styled-Component-during-construction is the thing that's bothering me. The cool thing is though, if we get it right, it means that people could write their own magic interpolations, kinda like React mixins.
Maybe @robrkerr might have some ideas? If there was a magic interpolation to make a Styled Component work on the web and on React Native (including the touch component wrapper), what would it need to do? Is that even possible?
What if we just treat classNames a symbols, and mangle (or not) them and pass them in via their name as props.
import styled, {css, withProps, asObject} from 'styled-components'
import Link from 'react-router'
// just an example to show an api that can be extended later
// it could easily be styled(Link, asObject(['white', 'list']))
// the other use case is if you have the same styles with the same states
// but you need to apply it to 2 different components with different apis
// const OtherLink(HotLink, mapProps({containerStyle: 'style'}))
// but then again, I guess this sort of thing should be done with _.partial or something
const HotLink = styled(Link,
withProps(({containerStyle}) =>
({containerStyle: asObject(containerStyle)})`
background: white;
.activeClassName {
color: red;
}
.containerStyle {
display: flex;
}
`
I don't have any solutions to this but I've recently had a go at sharing as much code as possible on a project between React and React Native using Styled Components. Here are some things I came across (I was/am very new to RN):
<TouchableHighlight onPress={clickHandler}>
<View>{children}</View>
</TouchableHighlight>
I think we've decided to go with Cordova over React Native for this project so I've stopped exploring this for the moment.
One thing that occurs now though is that it might make sense to pass a RN flag passed down globally in (or alongside) the theme object (using context) so that it was available to SCs to switch between those two different 'contexts'. But you'd still have to have a way for them to wrap themselves in different RN elements.
Anyway I like the ideas being proposed here. Hopefully some of what I've added helps with the discussion :)
Ok, looks like that React Native problem will be lessened by #279 so let's focus back on the API options for the three problems we're facing:
classNameI think the only option that can handle all three is an interpolation syntax, so I'm going to experiment with that.
Just to add a little "Oh Yes Please!" onto this ticket:
I use materializecss for my styling. I love it, but I have to specify a lot of classnames.
Consider this list of three buttons taken from their sample code at
http://materializecss.com/buttons.html
<a class="waves-effect waves-light btn">button</a>
<a class="waves-effect waves-light btn"><i class="material-icons left">cloud</i>button</a>
<a class="waves-effect waves-light btn"><i class="material-icons right">cloud</i>button</a>
I wonder if it's possible to chain the template literal functions like this:
// pseudo-code:
// const NewClass = style.DIVNAME` CSS `.classNames` class name list `;
// or even just
// const NewClass = style.DIVNAME` CSS `.classNames('class name list');
// Then I could write:
const MyButton =
styled.a`color:red; `
.classNames` waves-effect waves-light btn `;
const MyCloud =
styled
.div` /* Not needed */ `
.classNames`
material-icons
${props => props.left ? 'left' }
${props => props.right ? 'right' }
`;
That would make the same example at the top be written as:
<MyButton>button</MyButton>
<MyButton><MyCloud left /></MyButton>
<MyButton><MyCloud right /></MyButton>
The saved typing alone makes that tremendously appealing. And to my mind it's a very pleasant and concise way of specifying how I need the component to respond to it's visual properties.
As it is today, my components look like somebody sneezed the word "className" all over them. Being able to do this with styled-components would make my code a LOT more eloquent.
I'm fairly new to Javascript coming from a C# background. But I LOVE styled-components.
Any syntax to support classNames will be a huge huge win.
@jason-henriksen I think some of the nuance in your comment got broken by the fact the code isnāt in code blocks! The syntax for that is to put three backticks (`s!) on a newline before your code!
@ticky I was in the process of fixing it when I saw your comment. You beat me to it!
I wonder if it's possible to chain the template literal functions like this:
So... it turns out this is valid syntax, just in case you're looking for a good horror story:
oh`my``god`
I'm not sure whether to be amazed or appalled š
incredible, what a world.
when I first saw
styled.div`css`;
My eyes bugged out a bit. I had to go look it up because I would never have guessed that to be valid...
Coming from more than 20 years of C based languages JavaScript is near constantly making my head explode. But, if we have it and it's consistent with the existing API... let's go for it!
@geelen i'm amazed š¤£
const classFromProp = className =>
props => props[className] ? ` ${className}` : ''
const MyButton = styled.a('waves-effect waves-light btn')`
color: red;
`
const MyCloud = styled.div(`
material-icons ${classFromProp('left')} ${classFromProp('right')}
`)
Adding classnames to a component
Passing CSS to a prop other than className
for these two, how about:
styled.div.props({
className: 'myClassNames',
needsAStyleClass: styledClass`
color: green;
background: blue;
`,
needsAStyleObject: styledObject`
color: white;
background: black;
`
})`
border-radius: blue;
`
styledClass and styledObject are tag functions that return a className or a style object.
Saying component X extends Y
I think the most intuitive extends would be for styled components to be usable as template tags
const MyButton = styled.button`
color: blue;
`
const MyDisabledButton = MyButton`
color: grey;
`
You could achieve the above by checking the arity of the MyComponent function, and if it's 2, return a new component, otherwise, business as usual.
Or if that's too gross, maybe a static method:
const MyDisabledButton = MyButton.extend`
color: grey;
`
Whoa this does look cool!
const MyButton = styled.button`
color: blue;
`
const MyDisabledButton = MyButton`
color: grey;
`
Not sure if it's feasible, but I definitely see beauty in that API!
it should be straight forward to implement, here's an example of how you might do it.
class StyledComponent {
constructor() {
// React would never pass a string as props when
// initializing a component
if(typeof arguments[0] === 'string')
return styled(StyledComponent).apply(null, arguments)
}
// Otherwise do your usual constructor stuff.
}
}
I'm actually working on implementing this with a slightly different api. But I'm new to javascript and npm exports so I'm learning as I go. I'll post a pull request when I getting running, hopefully next week.
const MyDisabledButton = MyButton`
color: grey;
`
Looks interesting, but I want to mention that it will be harder to detect that MyDisableButton is a styled-component in a plugin. If MyButton is imported from another file the only way to detect is to analyze contents of template literal and I would not rely on it.
@vdanchenkov Is that necessarily a bad thing? That just means you can extend component styles without the styled-components dependency.
EDIT:
It could actually be a benefit if you can extend a library's styled-components without the dependency.
I don't like this api at all, sorry. Apart from being incredibly surprising (wait it's a React component _and_ a template literal function?) I think the implementation is... deeply unsettling (constructors not returning instances of their class):
const MyDisabledButton = MyButton`
color: grey;
`
This api is totally fine though:
const MyDisabledButton = MyButton.extend`
color: grey;
`
So much cleaner!
Doesnāt the existing infrastructure pretty handily support style composition anyway?
const MyButton = styled.button`
color: blue;
`
const MyDisabledButton = styled(MyButton)`
color: grey;
`
Yeah we're ditching that for consistency. If you call styled(X) you should _always_ get a component that wraps X and passes className to it
The API I've been working on looks like this:
const Foo = styled.div` color:blue; `
const FooPlus = classed(Foo, `btnClass wavesClass`);
The object "classed" takes the output of styled and inserts a list of interpolated class names that will be appended into className every time FooPlus is used.
I preferred this API because it leaves the existing API completely un-touched. If you don't want to care about the class names, you don't have to. Even if you're just looking at the code for styled-components itself you could ignore this extension. All the classed() function does is add a classNamesList array to the function returned by styled and then that code inserts the extra classnames if present.
@geelen : Let me know if you're open to this idea and I'll send you what I have so far.
@jason-henriksen thanks but I think we're going to push forward with attrs (#365) since it solves several other use-cases as well. But there's nothing stopping you from writing classed to call attrs under the hood, if you prefer that API in your own projects š
@geelen #365 makes non-stop rocking possible. Definitely a great way to go that solves this problem in a much more general way.
I actually built basically the same thing earlier in the thread, @jason-henriksen ;P
The attrs, extend and extendWith methods in v2 should totally cover most of the concerns here, if I'm not mistaken?
extendWith was renamed to withComponent! :blush:
Most helpful comment
Whoa, very interesting! I only picked
classesbecause I had thought about applying a complex object ala the classnames library, but given these are defined at the top level (not inside a render method with access to props) that probably doesn't make sense. However, theclassNameprop would still be used to pass _extra_ classes in so I probably don't want to collide the name.I kinda don't mind this syntax as a kind of 'expanded' mode btw:
This sits kinda well with me as a flexible way to attach extra configuration to a component as it's created. The
displayNameandidentifierstuff is gonna be injected by the babel transform, btw, so don't worry about that, so onlyclasses|classNamesandcss|styles(still open on the naming here) would be added to the public API.I'm gonna give this a crack and see how it feels.