react-themeable-ts
v1.0.0
Published
Handle classname and style props in React
Downloads
33
Maintainers
Readme
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-themeable
does not handlekey
in returned payload at all. That means that radium users must manually supplykey
to 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
theme
prop 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
style
prop 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:
theme
prop will accept the classes/stylest
prop 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
theme
object 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
[email protected]
because ofpackage.json
-prepare
script. (only required to run hook when publish)npm -g i gulp-cli jest-cli
oryarn global add gulp-cli jest-cli
.
Usage
gulp --tasks
to get going.
Developing
jest --watchAll
to watch recompiled files and rerun tests.
Testing
Supports:
jest
, needsjest-cli
installed. it will execute the transpiled files from typescript.
Dist
gulp
will run default task which consist of running tasks:lint
,clean
,build
,minify
thenjest
and collect coverage.