react-themeable-ts v1.0.0
react-themeable-ts
A CommonJS/ES2015/ES2017 module, inspired by react-themeable and react-themr.
Features:
- Written in typescript and typings are auto generated.
- Unlike
react-themeabledoes not handlekeyin returned payload at all. That means that radium users must manually supplykeyto their react elements. You are better off using react-themeable instead. - Main function theme parameter can be empty, in case you do conditional stuff and it ends up being falsely.
- You may mix classnames and style objects, they will be merged in the order you specify the arguments.
- Fully memoized if you use it in combination with the higher order component decorator. That means it's really fast on re-renders, as it only calculates if the
themeprop changes. - Allows you to do conditional classes if you wrap an object with an array, that is because an object will be used as a
styleprop payload. All other argument payload types will be passed to classname module.
Usage
<MyComponent theme={theme} />Install
npm -S i react-themeable-ts
Or
yarn
Usage
react-themeable-ts exports themeable and internal function typescript interfaces.
This function is designed to accept a theme prop inside of your render method. This then returns a small helper function that accepts a series of style names.
const theme = themeable(this.props.theme);
...
<div {...theme(...styleNames)} />This helper function detects whether a theme is class or style based, and creates the appropriate attributes for you.
These attributes are either a style object or classname string depending on the payload.
For example:
import React, { Component } from 'react';
import { themeable } from 'react-themeable-ts';
class MyComponent extends Component {
render() {
const theme = themeable(this.props.theme);
return (
<div {...theme('root')}>
<div {...theme('foo', 'bar')}>Foo Bar</div>
<div {...theme('baz')}>Baz</div>
</div>
);
}
}A theme can now be passed to your component like so:
CSS Modules - maps to className prop to elements
MyComponentTheme.css
.root { color: black; }
.foo { color: red; }
.foo:hover { color: green; }
.bar { color: blue; }import theme from './MyComponentTheme.css';
...
<MyComponent theme={theme} />Aphrodite
import { StyleSheet, css } from 'aphrodite';
const theme = StyleSheet.create({
root: {
color: 'black'
},
foo: {
color: 'red',
':hover': {
color: 'green'
}
},
bar: {
color: 'blue'
}
});
...
<MyComponent theme={[ theme, css ]} />React Style
import StyleSheet from 'react-style';
const theme = StyleSheet.create({
root: {
color: 'black'
},
foo: {
color: 'red'
},
bar: {
color: 'blue'
}
});
...
<MyComponent theme={theme} />JSS
Import jss from 'jss';
const sheet = jss.createStyleSheet ({
root: {
color: 'black'
},
foo: {
color: 'red'
},
bar: {
color: 'blue'
}
}).attach();
...
<MyComponent theme={sheet.classes} />Global CSS
.MyComponent__root { color: black; }
.MyComponent__foo { color: red; }
.MyComponent__foo:hover { color: green; }
.MyComponent__bar { color: blue; }const theme = {
root: 'MyComponent__root',
foo: 'MyComponent__foo',
bar: 'MyComponent__bar'
};
...
<MyComponent theme={theme} />Inline styles
const theme = {
root: {
color: 'black'
},
foo: {
color: 'red'
},
bar: {
color: 'blue'
}
};
...
<MyComponent theme={theme} />HOC - Higher order component
When decorating a stateless function/Class the defaults are:
themeprop will accept the classes/stylestprop will become the resulting function invoked withthemeable(props.theme)from above.- The themable function will be memoized and will return a new function if only the main
themeobject changes. The returning themeable function is in turn also memoized and will not compute unless argument changed which was not already memoized.
Examples:
Defaults
import { PureComponent } from 'react';
import { themeDecorator } from 'react-themeable-ts';
@themeDecorator()
class MyComponent extends PureComponent {
render() {
const theme = this.props.t;
return (
<div {...theme('root')}>
<span {...theme('span1')}>
My test component
</span>
<p {...theme('p1')}>
My test component
</p>
</div>
);
}
};
// Component used
const MyJSXElement = (
<MyComponent
theme={{
root: 'class-root',
span1: 'class-span1',
p1: 'class-p1',
}}
>
);With custom options
import { PureComponent } from 'react';
import { themeDecorator } from 'react-themeable-ts';
@themeDecorator({
// Rename default from 'theme' to something else
themeKey: 'customThemeProp',
// Rename default `t` to something else
themeProp: 'myThemeableFn',
})
class MyComponent extends PureComponent {
render() {
const theme = this.props.myThemeableFn;
return (
<div {...theme('root')}>
<span {...theme('span1')}>
My test component
</span>
<p {...theme('p1')}>
My test component
</p>
</div>
);
}
};
// Component used
const MyJSXElement = (
<MyComponent
customThemeProp={{
root: 'class-root',
span1: 'class-span1',
p1: 'class-p1',
}}
>
);ThemeProvider - Wrapper component
Optional HOC you can use if you feel like globally handling themes via React context.
It accepts one single prop reactThemeable as an object which you can freely nest however you please.
Examples:
import { PureComponent, PropTypes } from 'react';
import { ThemeProvider } from 'react-themable-ts';
class App extends PureComponent {
render() {
return (
<ThemeProvider
reactThemeable={{
ComponentWithContext2: {
root: 'class-root',
span1: 'class-span1',
p1: 'class-p1',
},
}}
>
{props.children}
</ThemeProvider>
);
}
}
// Any component can then access the context for example:
import { themeable } from 'react-themeable-ts';
const MyStatelessComponent = (props, context) => {
const theme = themeable(context.reactThemeable.ComponentWithContext2);
return (
<div {...theme('root')}>
My content
</div>
);
};
MyStatelessComponent.contextTypes = {
reactThemeable: PropTypes.object.isRequired,
};Usage with HOC:
import { themeDecorator } from 'react-themeable-ts';
import { PureComponent, PropTypes } from 'react';
// First decorate a component with HOC
const decoratorFn = themeDecorator({
contextPath: ['reactThemeable', 'ComponentWithContext2'],
// or string with dots, see lodash.get method https://lodash.com/docs/4.17.4#get
// contextPath: 'reactThemeable.ComponentWithContext2',
});
const MyStatelessComponent = (props, context) => {
const { t } = props;
return (
<div {...t('root')}>
<span {...t('span1')}>
My test SFC component
</span>
<p {...t('p1')}>
My test SFC component
</p>
</div>
);
};
MyStatelessComponent.contextTypes = {
reactThemeable: PropTypes.object.isRequired,
};
const MyDecoratedComponent = decoratorFn(MyStatelessComponent);
// Then use in theme provider
// This works because the HOC comes with contextTypes enabled for prop reactThemeable.
class App extends React.PureComponent {
render() {
return (
<ThemeProvider
reactThemeable={{
ComponentWithContext2: {
root: 'class-root',
span1: 'class-span1',
p1: 'class-p1',
},
}}
>
<MyDecoratedComponent />
</ThemeProvider>
);
}
}API
themeable - Main function
import { themeable } from 'react-themeable-ts';
// Or without react as dependency
// import { themeable } from 'react-themeable-ts/themeable';Accepts any number of comma-separated values.
Also accepts nothing, in case you do conditional stuff and it ends up being falsely.
The falsely values will be omitted and string values will be mapped to either className string or style object and then merged in order you specify the arguments.
Examples:
className
const theme = {
root: 'root-class'
elephant: ''
};
const t = themeable(theme);
t('root');
// {
// className: 'root-class'
// }
t('root', 'does-not-exist', null, undefined, 'another one', 'elephant');
// {
// className: 'root-class'
// }style
const theme = {
root: {
color: 'red',
display: 'none'
}
};
const t = themeable(theme);
t('root');
// {
// style: {
// color: 'red',
// display: 'none'
// }
// }
t('root', 'does-not-exist', null, undefined, 'another one');
// {
// style: {
// color: 'red',
// display: 'none'
// }
// }Advanced className and style and conditional mix, the real power of the library.
describe('advanced classnames with classnames module', () => {
const styles = {
root: 'rrr',
common: 'common-class',
foo: 'fff',
bar: {
color: 'blue',
fontWeight: 'bold',
},
baz: 'bbb',
hover: {
color: 'red',
display: 'block',
},
base: {
fontWeight: 400,
backgroundColor: 'blue',
},
con1: ['con1', 'con11'],
con2: [{
con2: true,
con22: 'truthy string',
}],
con3: [
{
con3: false,
},
'con33',
],
};
const advClassnames = themeable(styles);
it('it passes all but objects to classname module', () => {
expect(advClassnames(
0,
null,
undefined,
'root',
'common',
'foo',
'bar',
'baz',
'hover',
'does not exist',
'',
'base',
'con1',
'con2',
'con3'
))
.to.deep.equal({
style: {
color: 'red',
fontWeight: 400,
display: 'block',
backgroundColor: 'blue',
},
className: 'rrr common-class fff bbb con1 con11 con2 con22 con33',
});
});
});themeDecorator - HOC
import { themeDecorator } from 'react-themeable-ts';Accepts single argument and is an object with props:
| Parameter | Default | Type | Description |
|---|---|---|---|
| themeKey(optional) | theme | string | The prop the HOC will use as argument for themeable. |
| themeProp(optional) | t | string | The prop name passed prop to DecoratedComponent as the returned themeable function. |
| memoizeeOpts(optional) | { length: 1, max: 10 } | Object | options passed to memoizee function. |
| contextPath(optional) | undefined | string/string[] | If truthy will be used as a path variable to navigate in context and will override themeKey. It accepts lodash.get method argument to navigate in context prop. |
Contributing
Requires
npm@4.xbecause ofpackage.json-preparescript. (only required to run hook when publish)npm -g i gulp-cli jest-clioryarn global add gulp-cli jest-cli.
Usage
gulp --tasksto get going.
Developing
jest --watchAllto watch recompiled files and rerun tests.
Testing
Supports:
jest, needsjest-cliinstalled. it will execute the transpiled files from typescript.
Dist
gulpwill run default task which consist of running tasks:lint,clean,build,minifythenjestand collect coverage.