Handy style utilities for React Native and React Native Web.
- ๐ฆ Lightweight (~2 KB)
- ๐ Fast (main work happens outside of component)
- ๐ No dependencies
- ๐ฎโโ๏ธ Typed with TypeScript
- โ๏ธ Supports both Native and Web
import { DynamicStyleSheet, variants, maxWidth } from '@pulsar/core';
// Use `DynamicStyleSheet` in place of `StyleSheet`.
// It accepts a function whose first argument is a theme object,
// and returns styles as it does regular `StyleSheet`.
const useStyles = DynamicStyleSheet.create(theme => ({
button: {
borderRadius: theme.radii.ios,
// You can define any component variations with `variants` helper.
...variants({
primary: {
backgroundColor: theme.colors.primary
},
secondary: {
backgroundColor: theme.colors.secondary
}
}),
// Media-queries can be used as well.
...maxWidth(theme.breakpoints.tablet, {
height: 50
})
}
}));
const Button = ({ children, variant }) => {
// `DynamicStyleSheet` returns a custom react hook.
// It has optional parameter - props from which depend variants described above.
const styles = useStyles({ variant });
return (
// styles.button here is an array of combined styles
<View style={styles.button}>{children}</View>
);
};
@pulsar/core
doesn't call StyleSheet.create()
during components rendering. All variants and media queries are
flattened into main object and styles are created once during calling of DynamicStyleSheet.create()
.
The result looks like this:
// {
// 'button': {},
// '_var:variant:primary:button': {},
// '_var:variant:secondary:button': {},
// '_media:max-width:768:button': {},
// }
Instead of calling StyleSheet.create()
during rendering, the custom hook returned from DynamicStyleSheet.create()
just manipulates with already existing and transpiled styles.
In the case above styles.button
will contain an array of
styles [styles['button'], styles['_var:variant:primary:button'], styles['_media:max-width:768:button']]
. So you don't
have to worry about merging all those keys together.
yarn add @pulsar/core
yarn add --dev babel-plugin-preval
Add babel-plugin-preval
to your babel config. Please note that preval
plugin should be listed first in plugins
array (details):
module.exports = {
presets: ['module:metro-react-native-babel-preset'],
plugins: ['preval'],
};
- Import media-queries polyfill in the root of your app. Typically
index.ts
. It's required to emulate media queries in RN.
import '@pulsar/core/dist/polyfill';
- Overwrite
Theme
interface with your theme shape to enable properties validation and autocompletion. Create a.d.ts
file, e.g.pulsar__core.d.ts
.
// pulsar__core.d.ts
import '@pulsar/core';
declare module '@pulsar/core' {
export interface Theme {
// You can define any properties you want.
breakpoints: {
phone: 320,
tablet: 768,
desktop: 1280
},
colors: {
primary: string,
secondary: string,
}
}
}
Then define your light and dark themes using Theme
interface.
// themes/themes.ts
import { Theme } from '@pulsar/core';
export const light: Theme = {
breakpoints: {},
colors: {}
}
- Wrap your app in
<ThemeProvider />
, passing light and dark themes as value. It's required to access the current theme viauseTheme()
hook.
import { ThemeProvider } from '@pulsar/core';
import { lightTheme, darkTheme } from './path/to/your/themes';
const pulsarConfig = {
light: lightTheme,
dark: darkTheme,
}
const App = () => (
<ThemeProvider value={pulsarConfig}>
{/* the rest of your app */}
</ThemeProvider>
)
- Create
.pulsar.config.js
config file in the root of your project. This config should export light and dark themes and follow the following shape:
// .pulsar.config.js
module.exports = {
themes: {
light: { /* your theme definition here */ },
dark: { /* your theme definition here */ },
}
}
Or if you have your themes defined somewhere in src
code, you can just re-export them to pulsar. E.g:
// .pulsar.config.js
const { light } = require('./src/themes/light');
const { dark } = require('./src/themes/dark');
module.exports = {
themes: { light, dark },
};
Typical usage of @pulsar/core
looks like this:
import { DynamicStyleSheet, variants } from '@pulsar/core';
const useStyles = DynamicStyleSheet.create(theme => ({
button: {
borderRadius: 8,
...variants({
primary: {
backgroundColor: theme.colors.primary
},
secondary: {
backgroundColor: theme.colors.secondary
}
})
},
}));
const Button = ({ children, variant }) => {
const styles = useStyles({ variant });
return (
<View style={s.button}>{children}</View>
)
};
Variants allow defining different component states dependent on its props.
import { DynamicStyleSheet, variants } from '@pulsar/core';
DynamicStyleSheet.create(theme => ({
button: {
// Define button size variants
...variants({
prop: 'size',
variants: {
small: {
height: 30,
},
normal: {
height: 40,
},
large: {
height: 50,
},
}
}),
// Shorthand for `prop = 'variant'`
...variants({
primary: {
color: theme.colors.primary,
},
secondary: {
color: theme.cosors.secondary,
},
tertiary: {
color: theme.cosors.tertiary,
}
}),
// Arrays can be used as well
...variants([
{
prop: 'variant', variants: {}
},
{
prop: 'size', variants: {}
},
]),
}
}));
Available media-queries:
- minWidth
- maxWidth
- minHeight
- maxHeight
import { DynamicStyleSheet, maxWidth } from '@pulsar/core';
DynamicStyleSheet.create(() => ({
button: {
alignSelf: 'flex-start',
...minWidth(768, {
alignSelf: 'stretch'
});
}
}));