Closed denchen closed 5 years ago
I want to answer this properly, but I'm going to be offline for the next few days. Hopefully, someone else can step in and help answer some of these questions before I get back. š
I kinda solved #2 above by passing in a series of objects to styled
, so something like
const Button = styled.button({
backgroundColor: 'white',
color: 'green',
fontSize: '16px',
padding: '10px 20px'
}, (props) => {
let css = {};
if (props.primary) {
css.backgroundColor = 'white';
css.color = 'red'
}
}
if (props.secondary) {
css.backgroundColor = 'gray';
css.color = 'blue';
if (props.active) {
css.color = 'lightblue';
}
}
}
return css;
});
I don't know if this is the ideal solution. The main downside is the use of camelCase notation instead of actual CSS notation, and the fact that it doesn't feel as clean as a pure CSS file. But it's manageable.
You're looking for css.
Let's say we have the following program:
import { css, sheet } from "emotion";
const linkClass = css`
&, &:active, &.visited {
color: blue;
cursor: pointer;
}
&:hover {
color: purple;
}
`;
console.log(linkClass);
console.log(sheet.sheet.cssRules);
With a .babelrc
of
{
"presets": [
["env", {
"targets": {
"node": "current"
}
}]
],
"plugins": [
"emotion/babel"
]
}
that compiles to:
// compiled.js
"use strict";
var _emotion = require("emotion");
const linkClass = /*#__PURE__*/(0, _emotion.css)([], [], function createEmotionStyledRules() {
return [{
"&, &:active, &.visited": {
"color": "blue",
"cursor": "pointer"
},
"&:hover": {
"color": "purple"
}
}];
});
console.log(linkClass);
console.log(_emotion.sheet.sheet.cssRules);
The output of compiled.js
when run is
css-10pfecp
[ { cssText: '.css-10pfecp, .css-10pfecp:active, .css-10pfecp.visited{color:blue;cursor:pointer;}' },
{ cssText: '.css-10pfecp:hover{color:purple;}' } ]
There are two things to notice here. One is that linkClass
is the className string, so you can put it anywhere you can use a normal classname. The other is that the rules that get inserted into the DOM.
.css-10pfecp, .css-10pfecp:active, .css-10pfecp.visited{
color:blue;
cursor:pointer;
}
I'll use an example that's probably a bit more complex than what you need. Assuming you have a Button
which is a base level component (that is, configurable based on props) as such:
import { h } from "preact";
import styled from "preact-emotion";
export default styled.button`
cursor: pointer;
display: inline-block;
min-height: 1em;
outline: none;
border: none;
vertical-align: ${({ verticalAlign }) => verticalAlign};
background: ${({ background }) => background};
color: ${({ textColor }) => textColor};
font-family: ${({ fontFamily }) => fontFamily};
...
`
You can create then the variant
-> object of configurable props mapping as such:
import { h, Component } from "preact";
import Button from "@ocode/button";
import { buttons, sizes } from "@ocode/constants/lib/buttons";
export default class Buttons extends Component {
render({ size, variant, children, ...props }) {
const b = buttons[variant] || buttons.primary;
const s = sizes[size] || sizes.default;
return (
<Button {...props} {...b} {...s}>
{children}
</Button>
);
}
);
Alternatively, a simpler approach would be to keep everything in the same file and use a props to construct composes
. You can imagine that this function can contain any logic you want it to including fallbacks to defaults etc.
const primary = css`
background-color: blue;
font-size: 20px;
`
const secondary = css`
background-color: red;
font-size: 50px;
`
export default styled.button`
composes: ${({primary}) => primary ? secondary : blue};
border-radius: 3px;
`
But can theming be used with the css
syntax? Why can't I do something like this?
css
returns a class name and doesn't know if it's being used inside React/Preact/Angular/raw JS/etc. For example, earlier in this post, I used it inside of some random node code.
Perhaps there's a way to treat css`` as something that returns a function and does fancy detection etc but I don't currently need or use themable utility classes so I'll let someone else speak to that. I generally use utility components which abstract the need for dealing with classes.
@ChristopherBiscardi Thanks for the detailed response.
Thanks, that was the solution I was hoping for. I tested out your example, and it worked exactly as I expected.
Your second example is pretty easy to understand, and i'll probably end up doing something really similar. But I do have a question about composes
. I notice it gets applied first. So shouldn't what goes in composes
be the base style, and what's in the main styled.button
be for overriding that base class with whatever props (primary
, secondary
)? In your example, if there is no primary
or secondary
and I still want to set a background-color
, where would I put it?
As for your first example, I'm a little confused what this is doing:
<Button {...props} {...b} {...s}>
Are you expecting b
and s
to be arrays? Objects? Classnames?
I ended up forgoing theming and just creating my own constants file with all the theming I needed (mainly colors) and importing that where ever I needed (eg. import {colors} from 'colors';
). It seemed much cleaner and easier that way.
Ah yeah, so this line in that example:
import { buttons, sizes } from "@ocode/constants/lib/buttons";
imports objects shaped like this:
import { lighten, darken } from "polished";
import colors from "./colors";
export const buttons = {
primary: {
color: "white",
backgroundColor: colors.brand,
borderColor: colors.brand,
disabledColor: lighten(0.2, colors.brand),
hoverBackgroundColor: darken(0.2, colors.brand),
hoverBackgroundBordercolor: darken(0.2, colors.brand)
},
digital: {
borderColor: colors.digitalBlue,
backgroundColor: colors.digitalBlue,
color: "#fff",
disabledColor: lighten(0.2, colors.digitalBlue),
hoverBackgroundColor: darken(0.2, colors.digitalBlue),
hoverBackgroundBordercolor: darken(0.2, colors.digitalBlue)
},
...
};
export const sizes = {
xsmall: {
paddingX: ".5rem",
paddingY: ".2rem",
fontSize: ".75rem",
lineHeight: "1.5"
},
...
};
Where the pattern is basically:
const thing = {
variant: cssObj
}
so when you spread {...b}
you're spreading a cssObj
, which is just the configurable props from the base Button
that are relevant for primary
or large
or whatever.
So shouldn't what goes in composes be the base style...
I avoid this kind of inheritance patterning generally but you could do it that way.
In your example, if there is no primary or secondary and I still want to set a background-color, where would I put it?
If there's no primary
or secondary
I usually default to primary
for example. You could also just leave composes
blank for those values and expect that they'll be passed in piecemeal to each prop. You could even put logic there to prevent them from being set if there is a primary
, etc.
It's up to what you want to accomplish really, there are a few options.
I ended up forgoing theming and just creating my own constants file with all the theming I needed (mainly colors) and importing that where ever I needed (eg. import {colors} from 'colors';). It seemed much cleaner and easier that way.
Yeah, that's a valid option if you'll only have a single theme and the values are static, etc. I'm doing something similar but using styled-theming, etc to set the JSON in the context at the root of my app so I can swap themes for different users, etc.
@ChristopherBiscardi Thank you. I think I have a much better understanding of how everything comes together now.
I do have one last question: how do you handle a component whose styling may depend on the CSS of a parent DIV or component? For example, I have a <Button>
, but if it's with a <ButtonGroup>
, such as:
<ButtonGroup>
<Button>A</Button>
<Button>B</Button>
</ButtonGroup>
there needs to be some adjustment to the <Button>
CSS (for example, padding). Would the pattern be to add the styling of <Button>
within <ButtonGroup>
, such as:
const ButtonGroup = styled.div`
/* button group styling */
& button {
/* button styling */
}
`
Or is there another way to do this?
This gets more into React/Preact/etc code than it has to do with emotion. The concept is that you want your button to be able to handle applying its own styles based on props for any context you intend to render it in. In the following example, I use the btnGrouper
function to apply different paddings based on a prop that is passed in from ButtonGroup
. Since ButtonGroup
knows the relevant information about its children, we can apply the required props to <Button>
in render
. (please excuse the naming, etc I rushed-wrote this example on the emotion site š
)
function btnGrouper({ firstInGroup, lastInGroup }) {
if (firstInGroup) {
return "0 30px";
}
if (lastInGroup) {
return "30px 0";
}
return "30px";
}
const Button = styled("button")`padding: ${btnGrouper};`;
class ButtonGroup extends React.Component {
render() {
const count = React.Children.count(this.props.children);
return (
<span>
{React.Children.map(this.props.children, (child, i) => {
return React.cloneElement(child, {
firstInGroup: i === 0,
lastInGroup: i === count - 1
});
})}
</span>
);
}
}
render(
<ButtonGroup>
<Button>A</Button>
<Button>B</Button>
<Button>c</Button>
</ButtonGroup>,
mountNode
);
And here's a screenshot of the result:
If you do it this way, the Button
owns it's own styles rather than making ButtonGroup
override Button
styles. This makes it easier to maintain and modify because you're locating all Button
related styles in Button
and ButtonGroup
is only responsible for grouping logic.
This issue has been automatically marked as stale because it has not had recent activity. It will be closed in 7 days if no further activity occurs. Thank you for your contributions.
I'm not sure if this is the right place to ask general questions on using Emotion, so I apologize in advance (if there's a better forum for this, please let me know). First off, I'm fairly new to React, and I'm very new to CSS-in-JS, so I'm still trying to wrap my head around the concepts. I've gone over the documentation, but I still have questions regarding usage. I have a bunch of CSS from an old Ember project that I want to port to ReactJS, so that's going to be my starting point in terms of what I want to accomplish.
1. Base classes
In my old project, I had a bunch of base classes that are meant to be used anywhere. For example:
I should be able to drop this into anywhere -- a
<div>
, a<button>
,<MyFancyBox>
-- and on a case by case basis (eg, I'm outputting a list with each<li>
except the third one having.link
). What's the paradigm in Emotion to do this?2. Adapting based on props.
One of the examples from styled-components was that of
<Button>
and being able to compose<Button primary>
and<Button secondary>
. The way I understand it, the way you would implement this is:This seems cumbersome. If both
primary
andsecondary
are modifying, say, 8-10 properties, then our CSS will be filled with${props => props.primary ? ...}
over and over again. And let's say we add another prop on top of that. Let's say we have anactive
prop that modifies most of the same parameters, BUT only in the case ofsecondary
and notprimary
. In plain CSS, this would beSo in Emotion, how do I break this down into something readable and maintainable?
3. Using theming
I'm a little unclear on where you can use theming. In your docs, you have this example:
But can theming be used with the
css` `
syntax? Why can't I do something like this?What I end up seeing in the Dev Console is something like this:
So I'm not exactly sure how to use
props
within acss` `
tag.Thanks for any answers!