-
-
Notifications
You must be signed in to change notification settings - Fork 397
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Styled API #1094
Styled API #1094
Changes from 7 commits
79f9723
568c5b2
c98e9c1
18c9a62
ddfd381
92fa857
6778dc1
e3c8db2
7858e7c
3c34638
302f1cf
d6cc7e4
f284c0e
9f3691f
53e7f6e
69fcfb7
1b3f9c9
4fe6114
6833bb9
85d0c65
a8c792e
21ff93f
42d950a
c9a0361
3de05e7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
declare function describe(string, Function): void | ||
declare function it(string, Function): void |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
// @flow | ||
/* eslint-disable react/prop-types, react/require-default-props */ | ||
|
||
import React from 'react' | ||
import isPropValid from '@emotion/is-prop-valid' | ||
import type {StatelessFunctionalComponent, ComponentType} from 'react' | ||
import withStyles from './withStyles' | ||
import type {Options, Style, Classes} from './types' | ||
|
||
// Props we don't want to forward. | ||
const reservedProps = { | ||
classes: true, | ||
as: true, | ||
theme: true | ||
} | ||
|
||
type StyledProps = { | ||
className?: string, | ||
as?: string, | ||
classes?: Classes | ||
} | ||
|
||
export default <Props: {}>( | ||
type: string | StatelessFunctionalComponent<Props> | ComponentType<Props> | ||
) => { | ||
const isTag = typeof type === 'string' | ||
|
||
return <Theme: {}>(style: Style<Theme>, options?: Options<Theme>) => { | ||
const Styled = (props: StyledProps) => { | ||
const {classes, as, className} = props | ||
const childProps: Props = ({}: any) | ||
for (const prop in props) { | ||
if (prop in reservedProps) continue | ||
// We don't want to pass non-dom props to the DOM, | ||
// but we still wat to forward them to a uses component. | ||
if (isTag && !isPropValid(prop)) continue | ||
childProps[prop] = props[prop] | ||
} | ||
// $FlowFixMe flow seems to not know that `classes` will be provided by the HOC, not by element creator. | ||
childProps.className = className ? `${className} ${classes.css}` : classes.css | ||
return React.createElement(as || type, childProps) | ||
} | ||
|
||
// $FlowFixMe flow seems to not know that `classes` will be provided by the HOC, not by element creator. | ||
return withStyles({css: style}, {...options, injectTheme: true})(Styled) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This implementation is good for now, but I would like to optimize it, later on, to not render two extra react components for example. I'm not sure if we should use the hooks implementation in here yet, as this would make the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I actually think performance is critical and this amount of components for each styled one is really bad, that's why I think hooks api is the only way forward with this API. For those who use older react versions they still can use the old styled implementation. |
||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,196 @@ | ||
// @flow | ||
/* eslint-disable react/prop-types */ | ||
import expect from 'expect.js' | ||
import React from 'react' | ||
import TestRenderer from 'react-test-renderer' | ||
import {stripIndent} from 'common-tags' | ||
import {styled, SheetsRegistry, JssProvider, ThemeProvider} from '.' | ||
|
||
const createGenerateId = () => { | ||
let counter = 0 | ||
return rule => `${rule.key}-${counter++}` | ||
} | ||
|
||
describe('React-JSS: styled', () => { | ||
it('should render static styles', () => { | ||
const registry = new SheetsRegistry() | ||
const Div = styled('div')({color: 'red'}) | ||
const renderer = TestRenderer.create( | ||
<JssProvider registry={registry} generateId={createGenerateId()}> | ||
<Div /> | ||
</JssProvider> | ||
) | ||
expect(registry.toString()).to.be(stripIndent` | ||
.css-0 { | ||
color: red; | ||
} | ||
`) | ||
const {className, classes} = renderer.root.findByType('div').props | ||
expect(className).to.be('css-0') | ||
expect(classes).to.be(undefined) | ||
}) | ||
|
||
it('should render dynamic styles', () => { | ||
const registry = new SheetsRegistry() | ||
const Div = styled('div')({ | ||
color: 'red', | ||
width: () => 10 | ||
}) | ||
const renderer = TestRenderer.create( | ||
<JssProvider registry={registry} generateId={createGenerateId()}> | ||
<Div /> | ||
</JssProvider> | ||
) | ||
expect(registry.toString()).to.be(stripIndent` | ||
.css-0 { | ||
color: red; | ||
} | ||
.css-0-1 { | ||
width: 10px; | ||
} | ||
`) | ||
expect(renderer.root.findByType('div').props.className).to.be('css-0 css-0-1') | ||
}) | ||
|
||
it('should merge with user class name', () => { | ||
const registry = new SheetsRegistry() | ||
const Div = styled('div')({color: 'red'}) | ||
const renderer = TestRenderer.create( | ||
<JssProvider registry={registry} generateId={createGenerateId()}> | ||
<Div className="my-class" /> | ||
</JssProvider> | ||
) | ||
expect(registry.toString()).to.be(stripIndent` | ||
.css-0 { | ||
color: red; | ||
} | ||
`) | ||
expect(renderer.root.findByType('div').props.className).to.be('my-class css-0') | ||
}) | ||
|
||
it('should use "as" prop', () => { | ||
const registry = new SheetsRegistry() | ||
const Div = styled('div')({color: 'red'}) | ||
const renderer = TestRenderer.create( | ||
<JssProvider registry={registry} generateId={createGenerateId()}> | ||
<Div as="button" /> | ||
</JssProvider> | ||
) | ||
expect(registry.toString()).to.be(stripIndent` | ||
.css-0 { | ||
color: red; | ||
} | ||
`) | ||
expect(renderer.root.findByType('button').props.className).to.be('css-0') | ||
}) | ||
|
||
it('should not leak non-dom attrs', () => { | ||
const registry = new SheetsRegistry() | ||
const Div = styled('div')({ | ||
color: 'red', | ||
width: props => props.s | ||
}) | ||
const renderer = TestRenderer.create( | ||
<JssProvider registry={registry} generateId={createGenerateId()}> | ||
<Div s={10} /> | ||
</JssProvider> | ||
) | ||
expect(registry.toString()).to.be(stripIndent` | ||
.css-0 { | ||
color: red; | ||
} | ||
.css-0-1 { | ||
width: 10px; | ||
} | ||
`) | ||
const {className, s} = renderer.root.findByType('div').props | ||
expect(className).to.be('css-0 css-0-1') | ||
expect(s).to.be(undefined) | ||
}) | ||
|
||
it('should compose with styled component', () => { | ||
const registry = new SheetsRegistry() | ||
const BaseDiv = styled('div')({color: 'red'}) | ||
const Div = styled(BaseDiv)({width: 10}) | ||
const renderer = TestRenderer.create( | ||
<JssProvider registry={registry} generateId={createGenerateId()}> | ||
<Div /> | ||
</JssProvider> | ||
) | ||
expect(registry.toString()).to.be(stripIndent` | ||
.css-1 { | ||
color: red; | ||
} | ||
.css-0 { | ||
width: 10px; | ||
} | ||
`) | ||
const {className} = renderer.root.findByType('div').props | ||
expect(className).to.be('css-0 css-1') | ||
}) | ||
|
||
it('should style any component', () => { | ||
const registry = new SheetsRegistry() | ||
const BaseDiv = ({className}) => <div className={className} /> | ||
const Div = styled(BaseDiv)({width: 10}) | ||
const renderer = TestRenderer.create( | ||
<JssProvider registry={registry} generateId={createGenerateId()}> | ||
<Div /> | ||
</JssProvider> | ||
) | ||
expect(registry.toString()).to.be(stripIndent` | ||
.css-0 { | ||
width: 10px; | ||
} | ||
`) | ||
const {className} = renderer.root.findByType('div').props | ||
expect(className).to.be('css-0') | ||
}) | ||
|
||
it.skip('should target another styled component (not sure if we really need this)', () => { | ||
const registry = new SheetsRegistry() | ||
const Span = styled('span')({color: 'red'}) | ||
const Div = styled('div')({ | ||
// $FlowFixMe | ||
[Span]: { | ||
color: 'green' | ||
} | ||
}) | ||
|
||
const renderer = TestRenderer.create( | ||
<JssProvider registry={registry} generateId={createGenerateId()}> | ||
<Div /> | ||
</JssProvider> | ||
) | ||
expect(registry.toString()).to.be(stripIndent` | ||
.css-0 { | ||
width: 10px; | ||
} | ||
`) | ||
expect(renderer.root.findByType('div').props.className).to.be('XXX') | ||
expect(renderer.root.findByType('span').props.className).to.be('XXX') | ||
}) | ||
|
||
it('should render theme', () => { | ||
const registry = new SheetsRegistry() | ||
const Div = styled('div')({ | ||
color: 'red', | ||
margin: props => props.theme.spacing | ||
}) | ||
TestRenderer.create( | ||
<JssProvider registry={registry} generateId={createGenerateId()}> | ||
<ThemeProvider theme={{spacing: 10}}> | ||
<Div /> | ||
</ThemeProvider> | ||
</JssProvider> | ||
) | ||
expect(registry.toString()).to.be(stripIndent` | ||
.css-0 { | ||
color: red; | ||
} | ||
.css-0-1 { | ||
margin: 10px; | ||
} | ||
`) | ||
}) | ||
}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
changes here are unrelated, but for some reason after I added tests for styled api, this tests started to fail, something weird started happening with module id, like there is a new moduleId module evaluation, I couldn't fully understand that, but I just started resetinng moduleId here.