@convoy/dapper
Advanced tools
Comparing version 1.0.48 to 2.0.0
{ | ||
"name": "@convoy/dapper", | ||
"version": "1.0.48", | ||
"version": "2.0.0", | ||
"description": "Styling library", | ||
@@ -28,3 +28,3 @@ "license": "Apache-2.0", | ||
"dependencies": { | ||
"hyphenate-style-name": "^1.0.2", | ||
"hyphenate-style-name": "1.0.2", | ||
"inline-style-prefixer": "^2.0.5", | ||
@@ -47,2 +47,5 @@ "lodash": "^4.17.4", | ||
"@types/sinon-chai": "2.7.27", | ||
"@types/source-map": "^0.5.0", | ||
"@types/tapable": "^0.2.3", | ||
"@types/uglify-js": "^2.6.29", | ||
"@types/webpack": "2.2.4", | ||
@@ -49,0 +52,0 @@ "chai": "3.5.0", |
541
README.md
@@ -1,3 +0,24 @@ | ||
# Dapper | ||
<img width="35%" height="35%" src="https://cloud.githubusercontent.com/assets/4240309/25258098/c3c37a1c-25f1-11e7-802f-ca47f7991000.png" /> | ||
[![CircleCI](https://img.shields.io/circleci/project/github/convoyinc/dapper.svg)](https://circleci.com/gh/convoyinc/dapper) | ||
[![Codecov](https://img.shields.io/codecov/c/github/convoyinc/dapper.svg)](https://codecov.io/gh/convoyinc/dapper) | ||
[![npm (scoped)](https://img.shields.io/npm/v/@convoy/dapper.svg)](https://www.npmjs.com/package/@convoy/dapper) | ||
Dapper is a Javascript/TypeScript styling library ([CSS-in-JS](https://speakerdeck.com/vjeux/react-css-in-js) or CSS-in-TS). It features: | ||
- Dynamic styles using modes, (i.e. in React, it styles based on props and state) | ||
- TypeScript autocomplete and build-time checks | ||
- Utilizes some of the best features of LESS/SASS CSS such as | ||
- nested styles | ||
- parent selectors | ||
- CSS features such as | ||
- media queries | ||
- keyframes | ||
- pseudo classes and psuedo elements | ||
- auto-prefixing (for cross-browser compatibility) | ||
- unitless values (use 5 instead of '5px') | ||
- paddingHorizontal, paddingVertical, and same for margin | ||
- Additional helpers to inject arbitrary CSS (great when styling 3rd party code) | ||
## Getting Started | ||
@@ -7,2 +28,5 @@ | ||
## TypeScript/Javascript | ||
Most examples are shown in TypeScript, but dapper works great with Javascript. TypeScript provides autocompletion of your styles and at build time checks that those styles exist. | ||
## React usage | ||
@@ -17,3 +41,3 @@ | ||
root: { | ||
backgroundColor: '#eee', | ||
padding: 5, | ||
}, | ||
@@ -32,4 +56,13 @@ }); | ||
``` | ||
Dapper generates a `<style>` element dynamically with autogenerated classes for each style rule. | ||
The above generates the following CSS: | ||
``` | ||
.dapper-root-a { | ||
padding: 5px; | ||
} | ||
``` | ||
### Dynamic Styles | ||
Dapper enables dynamic styles using "modes", a series of functions that defines all the different "modes" or ways your component can look. This creates a nice separation of concerns removing a lot of if/else branching logic from your render function. | ||
@@ -39,4 +72,8 @@ ```tsx | ||
interface Props { | ||
highlight: boolean; | ||
} | ||
interface State { | ||
hovered: boolean; | ||
value: string; | ||
} | ||
@@ -46,10 +83,10 @@ | ||
root: { | ||
backgroundColor: '#eee', | ||
'$hover': { | ||
backgroundColor: '#fff', | ||
backgroundColor: '#EEE', | ||
$highlight: { | ||
backgroundColor: '#FFF', | ||
}, | ||
}, | ||
label: { | ||
'$hover': { | ||
color: '#999', | ||
input: { | ||
$tooLong: { | ||
color: 'red', | ||
}, | ||
@@ -60,7 +97,9 @@ }, | ||
const MODES = { | ||
hovered: ({ state }: { state: State }) => state.hovered, | ||
highlight: ({ props }: { props: Props }) => props.highlight, | ||
tooLong: ({ state }: { state: State }) => state.value.length > 8, | ||
}; | ||
export default class Button extends React.Component<Props, State> { | ||
state = { hovered: false }; | ||
state = { value: '' }; | ||
styles = dapper.reactTo(this, STYLES, MODES); | ||
@@ -70,8 +109,7 @@ | ||
return ( | ||
<div | ||
className={this.styles.root} | ||
onMouseEnter={this._onMouseEnter} | ||
onMouseLeave={this._onMouseLeave} | ||
> | ||
<div className={this.styles.label}>BUTTEN</div> | ||
<div className={this.styles.root}> | ||
<input | ||
value={this.state.value} | ||
onChange={this._onChange} | ||
/> | ||
</div> | ||
@@ -81,24 +119,38 @@ ); | ||
_onMouseEnter = () => { | ||
this.setState({ hovered: true }); | ||
_onChange = ev => { | ||
this.setState({ value: ev.target.value }); | ||
} | ||
} | ||
``` | ||
_onMouseLeave = () => { | ||
this.setState({ hovered: false }); | ||
} | ||
The above generates the following CSS: | ||
``` | ||
.dapper-root-a { | ||
background-color: #EEE; | ||
} | ||
.dapper-root-highlight-b { | ||
background-color: #FFF; | ||
} | ||
.dapper-input-tooLong-c { | ||
color: red; | ||
}, | ||
``` | ||
and applies clases like `.dapper-input-highlight-b` dynamically to the div if `props.highlight` is `true`, and similarly adds `.dapper-input-tooLong-c` to the input when `state.value.length > 8`. | ||
## Pseudo-selectors and psuedo-elements | ||
CSS pseudo-selectors, such as `:hover` and `:active` and pseudo-elements such as `::before` and `::after` are supported as you might expect. | ||
## Basic usage | ||
```tsx | ||
import * as dapper from '@convoy/dapper'; | ||
```jsx | ||
import StyleSheet from '@convoy/dapper'; | ||
const STYLES = StyleSheet.createSimple({ | ||
const STYLES = dapper.compile({ | ||
root: { | ||
display: 'flex', | ||
backgroundColor: '#BBB', | ||
padding: 8, | ||
width: '200px', | ||
':hover': { | ||
backgroundColor: '#EEE', | ||
}, | ||
'::after': { | ||
content: '"+"', | ||
}, | ||
}, | ||
@@ -108,5 +160,7 @@ }); | ||
export default class Button extends React.Component<Props, State> { | ||
styles = dapper.reactTo(this, STYLES); | ||
render() { | ||
return ( | ||
<div className={STYLES.root} /> | ||
<div className={this.styles.root} /> | ||
); | ||
@@ -116,113 +170,247 @@ } | ||
``` | ||
The above generates the following CSS: | ||
``` | ||
.dapper-root-a:hover { | ||
background-color: #EEE; | ||
} | ||
## Prop or state based styling | ||
.dapper-root-a::after { | ||
content: "+"; | ||
} | ||
``` | ||
```jsx | ||
import StyleSheet from '@convoy/dapper'; | ||
## Placeholders | ||
Placeholders allow you to reference other styles names inside of a style rule. This can be helpful for cascading styles. Such as in this example, when `child` should look different when inside of `parentA` vs `parentB`. | ||
export interface Props { | ||
large?: boolean; | ||
ghost?: boolean; | ||
className?: string; | ||
```tsx | ||
import * as dapper from '@convoy/dapper'; | ||
const STYLES = dapper.compile({ | ||
parentA: { | ||
'{child}': { | ||
backgroundColor: 'red', | ||
}, | ||
}, | ||
parentB: { | ||
'{child}': { | ||
backgroundColor: 'blue', | ||
}, | ||
}, | ||
child: { | ||
padding: 5, | ||
} | ||
}); | ||
export default class Button extends React.Component<Props, State> { | ||
styles = dapper.reactTo(this, STYLES); | ||
render() { | ||
const { styles } = this; | ||
return ( | ||
<div> | ||
<div className={styles.parentA}> | ||
<div className={styles.child} /> | ||
</div> | ||
<div className={styles.parentB}> | ||
<div className={styles.child} /> | ||
</div> | ||
</div> | ||
); | ||
} | ||
} | ||
``` | ||
The above generates the following CSS: | ||
``` | ||
.dapper-parentA-a .dapper-child-c { | ||
background-color: red; | ||
} | ||
export interface State { | ||
hovered: boolean; | ||
.dapper-parentB-b .dapper-child-c { | ||
background-color: blue; | ||
} | ||
export type ModeState = { props: Props, state: State }; | ||
.dapper-child-c { | ||
padding: 5px; | ||
} | ||
``` | ||
const STYLES = StyleSheet.create({ | ||
## Parent selectors | ||
Parent selectors allow you to swap in the current selector into a new location in a selector. This is helpful when you want to prefix the generated classname for things like global classnames. | ||
```tsx | ||
import * as dapper from '@convoy/dapper'; | ||
const STYLES = dapper.compile({ | ||
root: { | ||
display: 'flex', | ||
backgroundColor: '#BBB', | ||
padding: 8, | ||
margin: { | ||
$large: 10, | ||
$ghost: 5, | ||
'html.wf-loading &': { | ||
opacity: 0, | ||
}, | ||
':hover': { | ||
backgroundColor: '#555', | ||
$ghost: { | ||
backgroundColor: '#000045', | ||
}, | ||
}, | ||
'@media (max-width: 800px)': { | ||
width: '100px', | ||
}, | ||
$ghost: { | ||
backgroundColor: 'white', | ||
'@media (max-width: 800px)': { | ||
backgroundColor: '#DDD', | ||
}, | ||
$large: { | ||
border: '1px solid #000', | ||
}, | ||
}, | ||
$large: { | ||
padding: '16px', | ||
fontSize: '20px', | ||
}, | ||
$hovered: { | ||
borderRight: '1px solid #000', | ||
}, | ||
}, | ||
text: { | ||
display: 'inline-block', | ||
}, | ||
}); | ||
const MODES = { | ||
large: ({ props }: ModeState) => !!props.large, | ||
hovered: ({ state }: ModeState) => state.hovered, | ||
ghost: ({ props }: ModeState) => !!props.ghost, | ||
}; | ||
export default class Button extends React.Component<Props, State> { | ||
state = { | ||
hovered: false, | ||
}; | ||
styles = dapper.reactTo(this, STYLES); | ||
styles = StyleSheet.compute(STYLES, MODES, { props: this.props, state: this.state }); | ||
componentWillUpdate(props: Props, state: State) { | ||
this.styles = StyleSheet.compute(STYLES, MODES, { props, state }); | ||
render() { | ||
return ( | ||
<div className={this.styles.root}> | ||
Hello World | ||
</div> | ||
); | ||
} | ||
} | ||
``` | ||
The above generates the following CSS: | ||
``` | ||
html.wf-loading .dapper-root-a { | ||
opacity: 0, | ||
} | ||
``` | ||
## Hover on parent element | ||
Placeholders and parent selectors makes it easy to support things like styling child elements based on pseudo class selectors of parents, like hover. This helps avoid creating onMouseEnter, onMouseLeave handlers. | ||
```tsx | ||
import * as dapper from '@convoy/dapper'; | ||
const STYLES = dapper.compile({ | ||
root: { | ||
padding: 5, | ||
}, | ||
child: { | ||
'{root}:hover &': { | ||
backgroundColor: '#EEE', | ||
}, | ||
}, | ||
}); | ||
export default class Button extends React.Component<Props, State> { | ||
styles = dapper.reactTo(this, STYLES); | ||
render() { | ||
return ( | ||
<div | ||
className={classnames(this.styles.root, this.props.className)} | ||
onMouseEnter={this._onMouseEnter} | ||
onMouseLeave={this._onMouseLeave} | ||
> | ||
{this._renderText()} | ||
<div className={this.styles.root}> | ||
<div className={this.styles.child} /> | ||
</div> | ||
); | ||
} | ||
} | ||
``` | ||
The above generates the following CSS: | ||
``` | ||
.dapper-root-a { | ||
padding: 5px; | ||
} | ||
_renderText() { | ||
.dapper-root-a:hover .dapper-child-b { | ||
background-color: #EEE; | ||
} | ||
``` | ||
## Media queries | ||
Dapper supports media queries, including nested media queries. | ||
```tsx | ||
import * as dapper from '@convoy/dapper'; | ||
const STYLES = dapper.compile({ | ||
root: { | ||
width: 200, | ||
'@media (max-width: 800px)': { | ||
'@media (orientation: landscape)': { | ||
width: 100, | ||
}, | ||
'@media (orientation: portrait)': { | ||
width: 60, | ||
}, | ||
}, | ||
}, | ||
}); | ||
export default class Button extends React.Component<Props, State> { | ||
styles = dapper.reactTo(this, STYLES); | ||
render() { | ||
return ( | ||
<span className={this.styles.text}> | ||
Button | ||
</span> | ||
<div className={this.styles.root} /> | ||
); | ||
} | ||
_onMouseEnter = () => { | ||
this.setState({hovered: true}); | ||
} | ||
``` | ||
The above generates the following CSS: | ||
``` | ||
.dapper-root-a { | ||
width: 200px; | ||
} | ||
@media (max-width: 800px) and (orientation: landscape) { | ||
.dapper-root-a { | ||
width: 100px; | ||
} | ||
} | ||
@media (max-width: 800px) and (orientation: portrait) { | ||
.dapper-root-a { | ||
width: 60px; | ||
} | ||
} | ||
``` | ||
_onMouseLeave = () => { | ||
this.setState({hovered: false}); | ||
## Nesting media queries/modes/pseudo | ||
Media queries, modes and pseudo class/element selectors can be nested within CSS properties to make things more readable. | ||
```js | ||
const STYLES = dapper.compile({ | ||
root: { | ||
padding: { | ||
$small: 2, | ||
$medium: 4, | ||
$large: 8, | ||
}, | ||
backgroundColor: { | ||
':hover': '#EEE', | ||
':focus': '#DDD', | ||
}, | ||
width: { | ||
'@media (max-width: 500px)': 100, | ||
'@media (min-width: 500px)': 400, | ||
}, | ||
}, | ||
}); | ||
``` | ||
The above generates the following CSS: | ||
``` | ||
.dapper-root-a.dapper-root-small-b { | ||
padding: 2px; | ||
} | ||
.dapper-root-a.dapper-root-medium-b { | ||
padding: 4px; | ||
} | ||
.dapper-root-a.dapper-root-large-b { | ||
padding: 8px; | ||
} | ||
.dapper-root-a:hover { | ||
background-color: '#EEE'; | ||
} | ||
.dapper-root-a:focus { | ||
background-color: '#DDD'; | ||
} | ||
@media (max-width: 500px) { | ||
.dapper-root-a { | ||
width: 100px; | ||
} | ||
} | ||
@media (min-width: 500px) { | ||
.dapper-root-a { | ||
width: 400px; | ||
} | ||
} | ||
``` | ||
## keyframes | ||
## keyframes (CSS Animations) | ||
Dappers keyframes function generates CSS animation names which can then be referenced in styles. | ||
```jsx | ||
import StyleSheet from '@convoy/dapper'; | ||
```tsx | ||
import * as dapper from '@convoy/dapper'; | ||
const fadeOut = StyleSheet.keyframes({ | ||
const fadeOut = dapper.keyframes({ | ||
'0%': { | ||
@@ -236,3 +424,3 @@ opacity: 0, | ||
const STYLES = StyleSheet.create({ | ||
const STYLES = dapper.compile({ | ||
root: { | ||
@@ -243,7 +431,45 @@ animation: `5s ${fadeOut} linear`, | ||
``` | ||
The above generates the following CSS: | ||
``` | ||
@keyframes dapper-anim-a { | ||
0% { | ||
opacity: 0; | ||
} | ||
100% { | ||
opacity: 1; | ||
} | ||
} | ||
.dapper-root-a { | ||
animation: 5s dapper-anim-a linear; | ||
} | ||
``` | ||
## renderStatic | ||
## paddingHorizontal, paddingVertical, marginHorizontal and marginVertical | ||
Dapper supports easy ways to add the same padding and margin on to the top and bottom or the left and right using paddingHorizontal and paddingVertical or the margin equivalents. | ||
```tsx | ||
import * as dapper from '@convoy/dapper'; | ||
const STYLES = dapper.compile({ | ||
root: { | ||
paddingHorizontal: 4, | ||
paddingVertical: 8, | ||
}, | ||
}); | ||
``` | ||
The above generates the following CSS: | ||
``` | ||
.dapper-root-a { | ||
padding-left: 4px; | ||
padding-right: 4px; | ||
padding-top: 8px; | ||
padding-bottom: 8px; | ||
} | ||
``` | ||
## renderStatic (arbitrary CSS) | ||
Sometimes you need to add arbitrary CSS to a document, such as when you are working with a third party library that controls its portion of the DOM tree. | ||
```js | ||
StyleSheet.renderStatic({ | ||
dapper.renderStatic({ | ||
'html, body': { | ||
@@ -255,15 +481,102 @@ backgroundColor: '#CCFFFF', | ||
}, | ||
'.pac-container': { | ||
backgroundColor: '#EEE', | ||
}, | ||
}); | ||
``` | ||
The above generates the following CSS: | ||
``` | ||
html, body { | ||
background-color: #CCFFFF; | ||
} | ||
@media (max-width: 800px) { | ||
html, body { | ||
background-color: #FFCCFF; | ||
} | ||
} | ||
.pac-container { | ||
background-color: #EEE; | ||
} | ||
``` | ||
## configure | ||
## configure (Configuration settings) | ||
Dapper works out of the box without any configuration needed. The default configuration can overridden globally however, by providing one or many of the following parameters: | ||
`node` (optional): The style element to render styles into. Default is a newly created style element appended to document.head. | ||
`classNamePrefix` (optional): The prefix to add all generated classnames. Default if `process.env.NODE_ENV === 'production'` is `d-`, otherwise the default is `dapper-`. | ||
`friendlyClassNames` (optional): A flag dictating that all generated classnames use the full key path of the style, making it easy to identify in browser dev tools where in code is responsible for a style. Default if `process.env.NODE_ENV === 'production'` is `false`, otherwise the default is `true`. | ||
`useInsertRule` (optional): A flag dictating that when rendering to the style element whether to use CSSStyleSheet.insertRule or innerHTML. Using insertRule is faster because it means the browser has less to repeatedly parse, but is more difficult to inspect using browser dev tools. Default if `process.env.NODE_ENV === 'production'` is `true`, otherwise the default is `false`. | ||
`omitUniqueSuffices` (optional): A flag allowing unique suffix generation to be turned off. Useful for snapshot testing, e.g.: | ||
![image](./omitUniqueSuffices.png) | ||
```js | ||
StyleSheet.configure({ | ||
dapper.configure({ | ||
node: document.querySelector('#stylesheet'), | ||
classNamePrefix: 'app-', | ||
friendlyClassNames: false, | ||
useInsertRule: true, | ||
}) | ||
``` | ||
Configuration can also be used per call to `compile`, `keyframes` and `renderStatic` to override the global configuration. This can be useful when you want to render to a different element which allows you to separately unload those styles. | ||
```js | ||
dapper.compile({ | ||
root: { | ||
padding: 5, | ||
}, | ||
}, { | ||
node: document.querySelector('#styles'), | ||
}); | ||
``` | ||
## compute | ||
Dapper exposes a `compute` function which takes the output of `compile`, any functions that define the modes and an object that defines the current state to compute the modes with and returns the classnames of the various styles. This function is useful even outside of React contexts or when rendering items in a list which have their own modes that aren't based directly on props or state. In React, we primarily use `reactTo`, which is a simple wrapper around `compute` that uses the component as the state to compute modes from. | ||
```tsx | ||
import * as dapper from '@convoy/dapper'; | ||
const STYLES = dapper.compile({ | ||
root: { | ||
width: 200, | ||
}, | ||
}); | ||
const ITEM_STYLES = dapper.compile({ | ||
root: { | ||
backgroundColor: '#CCC', | ||
}, | ||
}); | ||
const ITEM_MODES = { | ||
highlight: (item: Item) => item.highlight, | ||
}; | ||
export default class Button extends React.Component<Props, State> { | ||
styles = dapper.reactTo(this, STYLES); | ||
render() { | ||
return ( | ||
<div className={this.styles.root}> | ||
{this.props.items.map(this._renderItem)} | ||
</div> | ||
); | ||
} | ||
_renderItem(item) { | ||
const styles = dapper.compute(ITEM_STYLES, ITEM_MODES, item); | ||
return ( | ||
<div className={styles.root}> | ||
{item.label} | ||
</div> | ||
); | ||
} | ||
} | ||
``` | ||
## Contributing | ||
@@ -270,0 +583,0 @@ |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Empty package
Supply chain riskPackage does not contain any code. It may be removed, is name squatting, or the result of a faulty package publish.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
574
1
16479
35
3
0
3
+ Addedhyphenate-style-name@1.0.2(transitive)
- Removedhyphenate-style-name@1.1.0(transitive)
Updatedhyphenate-style-name@1.0.2