Skip to content

Commit

Permalink
Refactor component function to create subclass
Browse files Browse the repository at this point in the history
  • Loading branch information
blakeembrey committed Jul 5, 2015
1 parent 0d0c053 commit 1c9640f
Show file tree
Hide file tree
Showing 8 changed files with 151 additions and 70 deletions.
31 changes: 30 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ var App = React.createClass({

})

// Wrap the component with a higher order component.
// Extend `App` to create a new class with style injection.
App = Style.component(App)

// Render to the document.
Expand All @@ -58,6 +58,35 @@ React.render(<App />, document.body)

**Note:** You should render `Style.Element` at the root level of your application, but it must be a child of `Style.component()`. I recommend rendering it last so it receives all styles after the first render (required for isomorphic applications).

### With ES6/7

```js
import { create, injectStyle } from 'react-free-style'

const Style = create()

const TEXT_STYLE = Style.registerStyle({
backgroundColor: 'red'
})

@injectStyle(Style)
class App extends React.Component {

render () {
return (
<div className={TEXT_STYLE.className}>
Hello world!

<Style.Element />
</div>
)
}

}

React.render(<App />, document.body)
```

### Register Style

Register a [name spaced style](https://github.com/blakeembrey/free-style#namespaced-styles) object.
Expand Down
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,11 @@
"istanbul": "^0.3.13",
"mocha": "^2.1.0",
"pre-commit": "^1.0.6",
"typescript": "^1.5.0-alpha"
"typescript": "microsoft/TypeScript"
},
"dependencies": {
"free-style": "^0.5.2"
"free-style": "^0.5.4",
"xtend": "^4.0.0"
},
"peerDependencies": {
"react": ">=0.12.0"
Expand Down
57 changes: 43 additions & 14 deletions src/react-free-style.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,21 @@

import { expect } from 'chai'
import * as React from 'react'
import { create, ReactFreeStyle, FreeStyle } from './react-free-style'
import { create, ReactFreeStyle, FreeStyle, injectStyle } from './react-free-style'

describe('react free style', function () {
var Style: ReactFreeStyle
let Style: ReactFreeStyle

beforeEach(function () {
Style = create()
})

it('should render the main example', function () {
var TEXT_STYLE = Style.registerStyle({
const TEXT_STYLE = Style.registerStyle({
backgroundColor: 'red'
})

var App = Style.component(React.createClass({
const App = Style.component(React.createClass({

displayName: 'App',

Expand All @@ -40,14 +40,14 @@ describe('react free style', function () {
})

it('should render the example dynamic styles', function () {
var inlineStyle: FreeStyle.Style
let inlineStyle: FreeStyle.Style

var BUTTON_STYLE = Style.registerStyle({
const BUTTON_STYLE = Style.registerStyle({
backgroundColor: 'red',
padding: 10
})

var ButtonComponent = React.createClass({
const ButtonComponent = React.createClass({

contextTypes: {
freeStyle: React.PropTypes.object.isRequired
Expand All @@ -69,7 +69,7 @@ describe('react free style', function () {

})

var App = Style.component(React.createClass({
const App = Style.component(React.createClass({

render: function () {
return React.createElement(
Expand All @@ -95,17 +95,17 @@ describe('react free style', function () {
})

it('should work with children', function () {
var ChildStyle = create()
const ChildStyle = create()

var APP_STYLE = Style.registerStyle({
const APP_STYLE = Style.registerStyle({
color: 'blue'
})

var BUTTON_STYLE = ChildStyle.registerStyle({
const BUTTON_STYLE = ChildStyle.registerStyle({
backgroundColor: 'red'
})

var Button = ChildStyle.component(React.createClass({
const Button = ChildStyle.component(React.createClass({

render: function () {
return React.createElement(
Expand All @@ -117,7 +117,7 @@ describe('react free style', function () {

}))

var Child = ChildStyle.component(React.createClass({
const Child = ChildStyle.component(React.createClass({

render: function () {
return React.createElement(
Expand All @@ -129,7 +129,7 @@ describe('react free style', function () {

}))

var App = Style.component(React.createClass({
const App = Style.component(React.createClass({

render: function () {
return React.createElement(
Expand All @@ -151,4 +151,33 @@ describe('react free style', function () {
'</div>'
)
})

it('should set display name to the component name', function () {
const TEXT_STYLE = Style.registerStyle({
backgroundColor: 'red'
})

@injectStyle(Style)
class App extends React.Component<{}, {}> {

render () {
return React.createElement(
'div',
{ className: TEXT_STYLE.className },
'Hello world!',
React.createElement(Style.Element)
)
}

}

expect((<any> App).displayName).to.equal('App')

expect(React.renderToStaticMarkup(React.createElement(App))).to.equal(
'<div class="' + TEXT_STYLE.className + '">' +
'Hello world!' +
'<style>' + TEXT_STYLE.selector + '{background-color:red;}</style>' +
'</div>'
)
})
})
99 changes: 55 additions & 44 deletions src/react-free-style.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import React = require('react')
import ReactCurrentOwner = require('react/lib/ReactCurrentOwner')
import ExecutionEnvironment = require('react/lib/ExecutionEnvironment')
import extend = require('xtend')
export import FreeStyle = require('free-style')

declare var module: any
declare const module: any

/**
* Create a specialized free style instance.
Expand All @@ -12,8 +12,6 @@ export class ReactFreeStyle extends FreeStyle.FreeStyle {

/**
* Expose the `StyleElement` for use.
*
* @type {StyleElement}
*/
Element = StyleElement

Expand All @@ -33,66 +31,66 @@ export class ReactFreeStyle extends FreeStyle.FreeStyle {
}

/**
* Wrap a React component in a higher order `ReactFreeStyle` component.
*
* @param {React.ComponentClass<any>} component
* @return {React.ComponentClass<any>}
* Create a React component that inherits from a user component. This is
* required for methods on the user component to continue working once
* wrapped with the style functionality.
*/
component (component: React.ComponentClass<any>): React.ClassicComponentClass<{}> {
component (Component: React.ComponentClass<any>): React.ComponentClass<any> {
/**
* Alias `free-style` instance for changes.
*/
var freeStyle = this
const freeStyle = this
const proto = Component.prototype

/**
* Create a higher order style component.
*/
var ReactFreeStyleComponent = React.createClass({
class ReactFreeStyleComponent extends Component {
context: any
_freeStyle = freeStyle
_parentFreeStyle = this.context.freeStyle || new ReactFreeStyle()

displayName: 'ReactFreeStyle',
// Make sure debugging with React looks the same.
static displayName = (<any> Component).displayName || (<any> Component).name

contextTypes: {
static contextTypes = extend(Component.contextTypes, {
freeStyle: React.PropTypes.object
},
})

childContextTypes: {
static childContextTypes = extend(Component.childContextTypes, {
freeStyle: React.PropTypes.object.isRequired
},
})

getChildContext () {
return {
return extend((proto.getChildContext || noop).call(this), {
freeStyle: this._parentFreeStyle
}
},

getInitialState () {
return { freeStyle }
},
})
}

componentWillUpdate () {
// Hook into component updates to keep styles in sync over hot code
// reloads. This works great with React Hot Loader!
if (module.hot && this.state.freeStyle.id !== freeStyle.id) {
if (module.hot && this._freeStyle.id !== freeStyle.id) {
this._parentFreeStyle.detach(this._freeStyle)
this._parentFreeStyle.attach(freeStyle)
this._parentFreeStyle.detach(this.state.freeStyle)
this.state.freeStyle = freeStyle
this._freeStyle = freeStyle
}
},

;(proto.componentWillUpdate || noop).apply(this, arguments)
}

componentWillMount () {
this._parentFreeStyle = this.context.freeStyle || new ReactFreeStyle()
this._parentFreeStyle.attach(this.state.freeStyle)
},
this._parentFreeStyle.attach(this._freeStyle)

;(proto.componentWillMount || noop).call(this)
}

componentWillUnmount () {
this._parentFreeStyle.detach(this.state.freeStyle)
},
this._parentFreeStyle.detach(this._freeStyle)

render () {
return React.createElement(component, this.props)
;(proto.componentWillUnmount || noop).call(this)
}
}

})
// Alias `render` to the prototype for React Hot Loader to pick up changes.
;(<any> ReactFreeStyleComponent).prototype.render = proto.render

return ReactFreeStyleComponent
}
Expand All @@ -113,15 +111,11 @@ export class StyleElement extends React.Component<{}, {}> {
onChange = () => this.forceUpdate()

componentWillMount () {
if (ExecutionEnvironment.canUseDOM) {
this.context.freeStyle.addChangeListener(this.onChange)
}
this.context.freeStyle.addChangeListener(this.onChange)
}

componentWillUnmount () {
if (ExecutionEnvironment.canUseDOM) {
this.context.freeStyle.removeChangeListener(this.onChange)
}
this.context.freeStyle.removeChangeListener(this.onChange)
}

render () {
Expand All @@ -132,6 +126,23 @@ export class StyleElement extends React.Component<{}, {}> {

}

/**
* Create a React Free Style instance.
*/
export function create () {
return new ReactFreeStyle()
}

/**
* Accept a style instance for use with decorators.
*/
export function injectStyle (Style: ReactFreeStyle) {
return function <T> (Component: T): T {
return <any> Style.component(<any> Component)
}
}

/**
* Noop.
*/
function noop () {}
3 changes: 2 additions & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
"declaration": true,
"noImplicitAny": true,
"removeComments": false,
"sourceMap": true
"sourceMap": true,
"experimentalDecorators": true
},
"files": [
"src/react-free-style.ts",
Expand Down
8 changes: 0 additions & 8 deletions typings/_custom/react.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,3 @@ declare module 'react/lib/ReactCurrentOwner' {

export = ReactCurrentOwner
}

declare module 'react/lib/ExecutionEnvironment' {
var ExecutionEnvironment: {
canUseDOM: boolean
}

export = ExecutionEnvironment
}
17 changes: 17 additions & 0 deletions typings/_custom/xtend.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
declare module 'xtend/mutable' {
function extend <T> (dest: T, ...src: Object[]): T

export = extend
}

declare module 'xtend/immutable' {
function extend <T> (dest: T, ...src: Object[]): T

export = extend
}

declare module 'xtend' {
import immutable = require('xtend/immutable')

export = immutable
}
1 change: 1 addition & 0 deletions typings/tsd.d.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/// <reference path="react/react.d.ts" />
/// <reference path="_custom/react.d.ts" />
/// <reference path="_custom/xtend.d.ts" />
/// <reference path="../node_modules/free-style/free-style.d.ts" />
/// <reference path="chai/chai.d.ts" />
/// <reference path="mocha/mocha.d.ts" />

0 comments on commit 1c9640f

Please sign in to comment.