Skip to content
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .babelrc
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{
presets: [
'es2015',
'react',
],
plugins: [
Expand Down
3 changes: 3 additions & 0 deletions .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,6 @@ parser: babel-eslint

env:
jest: true

globals:
ReactClass: true
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this needed, where do we use such a global?

Copy link
Copy Markdown
Member Author

@lttb lttb Apr 23, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

eslint consider flow types and it works with rules like 'no-undef`, but it dosnt about this flow type. so we need to add it as global.

but maybe there is better solution with some plugin etc., I'll take a look

16 changes: 11 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,16 +31,21 @@ const PrimaryButton = styled(Button, {
Using base Style Sheet we can share classes between styled primitives.

```js
import { Styled } from 'styled-jss'
import injectSheet from 'react-jss'
import { Styled, injectStyled } from 'styled-jss'

// Base styles, like a regular jss object.
const styled = Styled({
root: {
margin: 10
margin: 10,
'& $baseButton': {
fontSize: 16
}
},
baseButton: {
padding: 10
padding: 10,
'& + &': {
marginLeft: 10
}
}
})

Expand All @@ -58,11 +63,12 @@ const PrimaryButton = styled(NormalButton, {
// One can use classes AND styled primitives.
const MyComponent = ({classes}) => (
<div className={classes.root}>
<NormalButton>normal button</NormalButton>
<PrimaryButton>primary button</PrimaryButton>
</div>
)

const MyStyledComponent = injectSheet(styled.styles)(MyComponent)
const MyStyledComponent = injectStyled(styled)(MyComponent)
```

### With custom JSS setup:
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
"babel-plugin-transform-class-properties": "^6.23.0",
"babel-plugin-transform-es2015-modules-commonjs": "^6.23.0",
"babel-plugin-transform-object-rest-spread": "^6.23.0",
"babel-preset-es2015": "^6.24.1",
"babel-preset-react": "^6.23.0",
"eslint": "^3.13.0",
"eslint-config-airbnb": "^14.1.0",
Expand Down
128 changes: 70 additions & 58 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,108 +1,118 @@
import {PureComponent, createElement} from 'react'
import {create as createJss, getDynamicStyles} from 'jss'
import preset from 'jss-preset-default'
import filterProps from './utils/filter-props'

import filterProps from './utils/filterProps'
import composeClasses from './utils/composeClasses'
import generateTagName from './utils/generateTagName'
import type {
BaseStylesType,
ComponentStyleType,
StyledType,
StyledElementAttrsType,
StyledElementType,
TagNameOrStyledElementType,
StyledElementPropsType
} from './types'

const jssDefault = createJss(preset())

type StyledElementAttrsType = { tag: string, styles: Object }
type StyledElementType = Function & StyledElementAttrsType
type tagOrStyledElementTypeype = string | StyledElementType
type StyledElementPropsType = {
classes: Object,
children: ?any,
className: ?string,
}

const createStyled = (jss?: Function = jssDefault) => (baseStyles: Object = {}) => {
let sheet
let dynamicSheet
let counter = 0
const createStyled = (
jss?: Function = jssDefault
) => (
baseStyles: BaseStylesType = {}
): StyledType => {
const sheets = {}

const mountSheets = () => {
if (!sheets.staticSheet) {
sheets.staticSheet = jss.createStyleSheet(baseStyles, {
meta: 'StaticBaseSheet',
}).attach()

sheets.dynamicSheet = jss.createStyleSheet({}, {
link: true,
meta: 'DynamicComponentSheet',
}).attach()
}
}

const styled = (
tagOrStyledElement: tagOrStyledElementTypeype,
ownStyles: Object
tagNameOrStyledElement: TagNameOrStyledElementType,
ownStyle: ComponentStyleType
): StyledElementType => {
const {tag, styles}: StyledElementAttrsType = typeof tagOrStyledElement === 'string'
? {tag: tagOrStyledElement, styles: {}}
: tagOrStyledElement
const {tagName, style}: StyledElementAttrsType = typeof tagNameOrStyledElement === 'string'
? {tagName: tagNameOrStyledElement, style: {}}
: tagNameOrStyledElement

const elementStyles = {...styles, ...ownStyles}
const dynamicStyles = getDynamicStyles(elementStyles)
const staticTag = `${tag}-${++counter}`
const elementStyle = {...style, ...ownStyle}
const dynamicStyle = getDynamicStyles(elementStyle)
const staticTagName = generateTagName(tagName)

return class StyledElement extends PureComponent {
static tag = tag

static styles = elementStyles
static tagName: string = tagName
static style: ComponentStyleType = elementStyle

props: StyledElementPropsType

tagScoped = ''
dynamicTagName = ''
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we could set dynamicTagName = generateTagName(tagName) here and remove constructor override, right?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nope, cause we can call Components with different styles

<Button color="pink" />
<Button color="green" />

and we need to have a scoped tagName for each instance

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

right


constructor(props) {
super(props)
this.tagScoped = `${tag}-${++counter}`
this.dynamicTagName = generateTagName(tagName)
}

componentWillMount() {
if (!sheet) {
sheet = jss.createStyleSheet(baseStyles, {
link: true,
meta: 'StaticBaseSheet',
}).attach()

dynamicSheet = jss.createStyleSheet({}, {
link: true,
meta: 'DynamicComponentSheet',
}).attach()
}
mountSheets()

if (!sheet.getRule(staticTag)) {
sheet.addRule(staticTag, elementStyles)
if (!sheets.staticSheet.getRule(staticTagName)) {
sheets.staticSheet.addRule(staticTagName, elementStyle)
}

if (dynamicStyles && !dynamicSheet.getRule(this.tagScoped)) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of tagScoped: tagId

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe dynamicTagName?

dynamicSheet
if (dynamicStyle && !sheets.dynamicSheet.getRule(this.dynamicTagName)) {
sheets.dynamicSheet
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just realized we don't need sheets object any more.

Copy link
Copy Markdown
Member Author

@lttb lttb Apr 24, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we need, because we need mutated sheets here too https://github.com/cssinjs/styled-jss/pull/6/files#diff-f336995dd331140b2022485b207c9fc1R10

if we just export them, with reassign we will lost links to new sheets

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we reassign them somewhere else than in mountSheets?? Should evtl mountSheets return them?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no, you are right, we can just return them.
will fix it

.detach()
.addRule(this.tagScoped, dynamicStyles)
dynamicSheet
.update(this.tagScoped, this.props)
.addRule(this.dynamicTagName, dynamicStyle)
sheets.dynamicSheet
.update(this.dynamicTagName, this.props)
.attach()
.link()
}
}

componentWillReceiveProps(nextProps: StyledElementPropsType) {
if (dynamicStyles) {
dynamicSheet.update(this.tagScoped, nextProps)
if (dynamicStyle) {
sheets.dynamicSheet.update(this.dynamicTagName, nextProps)
}
}

componentWillUnmount() {
dynamicSheet.deleteRule(this.tagScoped)
sheets.dynamicSheet.deleteRule(this.dynamicTagName)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do we remove the rule, we could reuse that rule next time the component mounts, no?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

because we generate new scoped name in constructor, so we can't get and old dynamicTagName for the pervious instance

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why? the instance will be reused in react ....

Copy link
Copy Markdown
Member Author

@lttb lttb Apr 24, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

right, but react will call constructor and we will lose it

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

well, don't redefine it in this case if it is already defined?

}

render() {
if (!sheet) return null
if (!sheets.staticSheet) return null

const {children, className, ...attrs} = this.props

const props = filterProps(attrs)
const tagClass = [
sheet.classes[staticTag],
dynamicSheet.classes[this.tagScoped],
className,
]
.filter(Boolean)
.join(' ')

return createElement(tag, {...props, className: tagClass}, children)
const tagClass = composeClasses(
sheets.staticSheet.classes[staticTagName],
sheets.dynamicSheet.classes[this.dynamicTagName],
className
)

return createElement(tagName, {...props, className: tagClass}, children)
}
}
}

return Object.assign(styled, {styles: baseStyles})
return Object.assign(styled, {
sheets,
mountSheets,
styles: baseStyles
})
}

const defaultStyledCreator = createStyled()
Expand All @@ -114,3 +124,5 @@ export {
}

export default defaultStyled

export {default as injectStyled} from './injectStyled'
23 changes: 23 additions & 0 deletions src/injectStyled.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import {createElement} from 'react'

import composeClasses from './utils/composeClasses'
import type {StyledType} from './types'

const injectStyled = (styled: StyledType) => (InnerComponent: ReactClass<any>) => {
styled.mountSheets()

const {sheets} = styled
const {staticSheet, dynamicSheet} = sheets

const classNames = Object.keys({...staticSheet.classes, ...dynamicSheet.classes})

const classes = [...classNames]
.reduce((acc, name) => ({
...acc,
[name]: composeClasses(staticSheet.classes[name], dynamicSheet.classes[name]),
}), {})

return (props: Object) => createElement(InnerComponent, {classes, ...props})
}

export default injectStyled
4 changes: 2 additions & 2 deletions src/tests/App.jsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React from 'react'
import type {StyledType} from '../types'


export default (styled: Function) => {
export default (styled: StyledType) => {
const App = styled('div', {
margin: 50,
})
Expand Down
45 changes: 38 additions & 7 deletions src/tests/__snapshots__/index.spec.jsx.snap
Original file line number Diff line number Diff line change
@@ -1,26 +1,26 @@
exports[`test renders correctly App with default Styled 1`] = `
<div
className="div-1-0-9">
className="div-14-0-9">
<header
className="header-2-0-10">
className="header-15-0-10">
<h1
className="h1-5-0-11">
className="h1-18-0-11">
Title
</h1>
</header>
<section
className="section-3-0-12">
className="section-16-0-12">
<button
className="button-6-0-13 button-11-0-14">
className="button-19-0-13 button-24-0-14">
primitive test
</button>
<button
className="button-6-0-13 button-12-0-15">
className="button-19-0-13 button-25-0-15">
dynamic primitive test
</button>
</section>
<section
className="section-4-0-16">
className="section-17-0-16">
Another section
</section>
</div>
Expand Down Expand Up @@ -53,3 +53,34 @@ exports[`test renders correctly App with default styled 1`] = `
</section>
</div>
`;

exports[`test renders correctly App with injectStyled 1`] = `
<div
className="root-0-17">
<div
className="div-27-0-19">
<header
className="header-28-0-20">
<h1
className="h1-31-0-21">
Title
</h1>
</header>
<section
className="section-29-0-22">
<button
className="button-32-0-23 button-37-0-24">
primitive test
</button>
<button
className="button-32-0-23 button-38-0-25">
dynamic primitive test
</button>
</section>
<section
className="section-30-0-26">
Another section
</section>
</div>
</div>
`;
21 changes: 20 additions & 1 deletion src/tests/index.spec.jsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import renderer from 'react-test-renderer'
import React from 'react'

import styled, {Styled} from '../'
import styled, {Styled, injectStyled} from '../'

import CreateApp from './App'

Expand All @@ -25,3 +25,22 @@ it('renders correctly App with default Styled', () => {

expect(tree).toMatchSnapshot()
})

it('renders correctly App with injectStyled', () => {
const customStyled = Styled({
root: {
fontSize: 16
},
baseButton: {
color: 'red',
},
})

const App = CreateApp(customStyled)
const StyledApp = injectStyled(customStyled)(({classes}) => (
<div className={classes.root}><App /></div>
))
const tree = renderer.create(<StyledApp />).toJSON()

expect(tree).toMatchSnapshot()
})
Loading