I think i may be using this wrong, but...
Closed this issue · 4 comments
I really like the idea of styled-system
.
During my little play with it today, I've defined a basic media object component, with the idea of using styled-system
underneath to power the variables that could change (margins).
Here's my primitive styled component (using emotion) i'm also using a little helper to toggle styles:
Helper:
import { css } from 'emotion';
const styledIf = (method, condition) => (...names) => (...args) => props =>
names[method](name => Boolean(props[name]) === condition) && css(...args);
const is = styledIf('every', true);
const isNot = styledIf('every', false);
const isOr = styledIf('some', true);
const isSomeNot = styledIf('some', false);
export default is;
export { isNot, isOr, isSomeNot };
Media object component:
import styled, { css } from 'react-emotion';
import is, { isNot, isOr, isSomeNot } from '../helpers/styled-is';
import { space } from 'styled-system';
import system from 'system-components';
/* ==========================================================================
#MEDIA
========================================================================== */
export const MediaImg = styled.div`
> img {
display: block;
}
${space};
`;
export const MediaBody = styled.div`
overflow: hidden;
display: block;
&,
> :last-child {
margin-bottom: 0;
}
`;
const MediaDefault = css`
${MediaImg} {
float: left;
/*
* This is to be controlled by styled-system.
* Allows us to set mr={1 | 2 | 3 | etc}
* How do we set a default for this?
*/
${'' /* margin-right: 8px; */}
}
`;
const MediaReverse = css`
${MediaImg} {
float: right;
/*
* This is to be controlled by styled-system.
* Allows us to set ml={1 | 2 | 3 | etc}
* How do we set a default for this?
*/
${'' /* margin-left: 8px; */}
}
`;
export const Media = styled.div`
display: block;
${isNot('reverse')`
${MediaDefault};
`};
${is('reverse')`
${MediaReverse};
`};
&:after {
content: "" !important;
display: block !important;
clear: both !important;
}
`;
Usage:
<Media>
<MediaImg mr={3}>
<img src="https://placeimg.com/400/200/animals" alt=""/>
</MediaImg>
<MediaBody>Hello</MediaBody>
</Media>
<Media reverse>
<MediaImg ml={3}>
<img src="https://placeimg.com/400/200/animals" alt=""/>
</MediaImg>
<MediaBody>Hello</MediaBody>
</Media>
All well and good so far!!
However.
I really want to be able to set the mr={3}
attribute on <MediaImg>
to default to {1}
if it's not explicitly set. I tried setting margin-right: 8px
in the styled-component above (it's commented out), but it overrides styled-system
.
This is where i stumbled across this project (system-components).
I thought maybe i could initialise my pieces differently and use system
to set the default of mr
and then extend this and apply the rest of my CSS:
let MediaImgDefaults = system({
mr: 1
});
export const MediaImg = MediaImgDefaults.extend`
> img {
display: block;
}
`;
const MediaDefault = css`
${MediaImg} {
float: left;
}
`;
const MediaReverse = css`
${MediaImg} {
float: right;
}
`;
export const Media = styled.div`
display: block;
${isNot('reverse')`
${MediaDefault};
`};
${is('reverse')`
${MediaReverse};
`};
&:after {
content: "" !important;
display: block !important;
clear: both !important;
}
`;
But this is failing with this error:
TypeError: Cannot call a class as a function
So i assume i'm using this completely wrong. I'm hoping someone can (from my intentions) understand what i was trying to achieve, and point me in the right direction to using this projects properly? I've only just started exploring the concepts of CSS in JS, so i may be doing things in an unconventional way (sorry!).
You should be able to do what you want by setting defaultProps:
export const MediaImg = styled.div`
> img {
display: block;
}
${space};
`;
MediaImg.defaultProps = {
mr: 1
};
Also in a situation where you do want to set a spacing value directly in the styled component it's best to avoid px values if you can and fetch directly from the theme.
import { theme } from 'styled-system';
styled.div`
// margin-left: 8px
margin-left: ${theme('space.2')};
`;
Thanks @corygibbons that makes a lot of sense! That's exactly what i was looking for!
So in my example, is there any way i can swap the defaultProps from mr: 1
to ml: 1
based on a conditional higher up in the component tree i.e. the parent? I want to swap margin-right
to margin-left
if the component is in reverse view, but that parameter lives on the parent <Media>
element.
export const MediaImg = styled.div`
> img {
display: block;
}
${space};
`;
MediaImg.defaultProps = {
mr: 1
};
/*
* MediaImg CSS properties are modified here.
* These are the default rules that can toggle depending on whether
* the media object is rendered in default view or reverse view.
*/
const MediaDefault = css`
${MediaImg} {
float: left;
}
`;
/* Rules for when the Media object is in reverse. */
const MediaReverse = css`
${MediaImg} {
float: right;
}
`;
/*
* Apply those toggle rules depending on a parameter on the Media component
*/
export const Media = styled.div`
display: block;
${isNot('reverse')`
${MediaDefault};
`};
${is('reverse')`
${MediaReverse};
`};
${clearfix};
`;
So we toggle child component rules based on whether a reverse
parameter is present on <Media>
(<Media reverse>
). Is there anyway i can toggle the default props on MediaImg
based on whether that parameter exists on the parent <Media>
element? This is the last piece in the puzzle!
(I'm aware this might be unconventional, so any suggestions how to do this a better way is appreciated!)
As stated above, if you're using the space
function, I would recommend setting all default margin and padding styles as defaultProps
on the component.
As for this error: TypeError: Cannot call a class as a function
This can happen when you're attempting to use a component as a child selector in styled-components, but it is a plain React component (not a styled-component) - i.e. styled-components is assuming your component is a style function. I'm not sure exactly where that's happening in your code, because it's a little tricky to understand, but that's my guess as to why you're seeing that.
As a general rule, I'd avoid using nested selectors as much as possible with styled-components and React. You can often achieve similar results with React components and props.
For conditional logic like the ml
vs. mr
thing, I'd suggest just handling that with a plain React component, perhaps a wrapper around what you're building with styled-components.
Hope that helps!
Also, the title of this issue includes RFC, but this isn't really a proposal for changing the API, is it? Please let me know if I misunderstood
Heads up that this repo has moved to https://github.com/jxnblk/styled-system/tree/master/system-components and I'll be archiving this one soon