@convoy/dapper
Advanced tools
Comparing version 2.0.72 to 2.0.75
import { CompiledStyleSheet, StyleDeclaration } from './types'; | ||
import { Configuration } from './configure'; | ||
export default function compile<StyleSet extends StyleDeclaration>(styles: StyleSet, config?: Configuration): CompiledStyleSheet<keyof StyleSet>; | ||
export default function compile<TKeys extends string>(styles: StyleDeclaration<TKeys>, config?: Configuration): CompiledStyleSheet<TKeys>; |
@@ -7,9 +7,10 @@ "use strict"; | ||
var configure_1 = require("./configure"); | ||
var PLACEHOLDER_REGEX = /\{([^\}]+)\}/g; | ||
function compile(styles, config) { | ||
if (config === void 0) { config = configure_1.config; } | ||
config = tslib_1.__assign({}, configure_1.config, config); | ||
var _a = setClassNamesForStyles(styles, config), newStyles = _a.styles, classNames = _a.classNames; | ||
var _a = setClassNamesForStyleDeclaration(styles, config), newStyles = _a.styles, compiledStyles = _a.compiledStyles; | ||
var cssText = cssTextForStyles_1.default(newStyles); | ||
renderCSSText_1.default(cssText, config); | ||
return classNames; | ||
return compiledStyles; | ||
} | ||
@@ -19,5 +20,6 @@ Object.defineProperty(exports, "__esModule", { value: true }); | ||
// Replaces top level keys with css className text '.keyClassName' | ||
// and replaces $modes with LESS style parent selector '&.modeClassName' | ||
function setClassNamesForStyles(styles, config) { | ||
// Replaces $modes with LESS style parent selector '&.modeClassName' | ||
function setClassNamesForStyleDeclaration(styles, config) { | ||
var newStyles = {}; | ||
var compiledStyles = {}; | ||
var classNames = {}; | ||
@@ -29,8 +31,10 @@ for (var key in styles) { | ||
var newKey = "." + name_1; | ||
newStyles[newKey] = setClassNamesForStyle([key], value, classNamesForModes, config); | ||
classNames[key] = getCompiledStyle(name_1, classNamesForModes); | ||
newStyles[newKey] = setClassNamesForStyleRule([key], value, classNamesForModes, config); | ||
compiledStyles[key] = getCompiledClassName(name_1, classNamesForModes); | ||
classNames[key] = newKey; | ||
} | ||
return { styles: newStyles, classNames: classNames }; | ||
newStyles = substitutePlaceholders(newStyles, classNames); | ||
return { styles: newStyles, compiledStyles: compiledStyles }; | ||
} | ||
function setClassNamesForStyle(keys, style, classNamesForModes, config) { | ||
function setClassNamesForStyleRule(keys, style, classNamesForModes, config) { | ||
var newStyle = {}; | ||
@@ -49,4 +53,7 @@ for (var key in style) { | ||
} | ||
if (key.indexOf('&') !== -1 && keys.length && cssTextForStyles_1.isPseudoSelector(keys[keys.length - 1])) { | ||
throw new Error("Cannot have a parent selector as child of pseudo class/element: " + newKeys.join('|')); | ||
} | ||
if (typeof value === 'object' && !Array.isArray(value)) { | ||
newStyle[key] = setClassNamesForStyle(newKeys, value, classNamesForModes, config); | ||
newStyle[key] = setClassNamesForStyleRule(newKeys, value, classNamesForModes, config); | ||
} | ||
@@ -62,3 +69,3 @@ else { | ||
} | ||
function getCompiledStyle(className, classNamesForModes) { | ||
function getCompiledClassName(className, classNamesForModes) { | ||
if (!Object.keys(classNamesForModes).length) { | ||
@@ -79,2 +86,32 @@ return className; | ||
} | ||
function substitutePlaceholders(styles, classNames) { | ||
for (var key in styles) { | ||
var value = styles[key]; | ||
_substitutePlaceholders(value, classNames); | ||
} | ||
return styles; | ||
} | ||
function _substitutePlaceholders(styles, classNames) { | ||
var _loop_1 = function (key) { | ||
var value = styles[key]; | ||
var newKey = key.replace(PLACEHOLDER_REGEX, function (_substr, p1) { | ||
var className = classNames[p1]; | ||
if (!className) { | ||
throw new Error("Cannot find StyleRule key " + p1 + " referenced in placeholder " + key); | ||
} | ||
return className; | ||
}); | ||
if (newKey !== key) { | ||
styles[newKey] = value; | ||
delete styles[key]; | ||
} | ||
if (typeof value === 'object' && !Array.isArray(value)) { | ||
_substitutePlaceholders(value, classNames); | ||
} | ||
}; | ||
for (var key in styles) { | ||
_loop_1(key); | ||
} | ||
return styles; | ||
} | ||
//# sourceMappingURL=compile.js.map |
import { StyleDeclaration } from '../types'; | ||
export default function cssTextForStyles(styles: StyleDeclaration): string[]; | ||
export default function cssTextForStyles(styles: StyleDeclaration<string>): string[]; | ||
export declare function isPseudoSelector(property: string): boolean; |
@@ -5,2 +5,3 @@ "use strict"; | ||
var generateCSSDeclaration_1 = require("./generateCSSDeclaration"); | ||
var CSS_PROPERTY_REGEX = /^-?[_a-z][_a-z0-9-]*$/i; | ||
// Given LESS-like styles { '.className': { '&.modeClassName': { fontSize: 5 }}} | ||
@@ -49,30 +50,32 @@ // Returns CSS text to render | ||
var selector = ''; | ||
var parentSelectors = []; | ||
var medias = []; | ||
var pseudos = []; | ||
path.reverse().forEach(function (key) { | ||
var isPseudo = isPropertyPseudo(key); | ||
var isMediaQuery = isPropertyMediaQuery(key); | ||
var isParentSelector = isPropertyParentSelector(key); | ||
if (isParentSelector) { | ||
parentSelectors.push(key.slice(1).trim()); | ||
// Finds the last key which could be a css property | ||
var index = path.length - 1; | ||
for (; index >= 0; index--) { | ||
var key = path[index]; | ||
if (CSS_PROPERTY_REGEX.test(key)) { | ||
property = key; | ||
break; | ||
} | ||
else if (isPseudo) { | ||
pseudos.push(key); | ||
} | ||
if (!property) { | ||
throw new Error("No CSS property provided, just selectors " + path.join(', ') + " for value " + JSON.stringify(value)); | ||
} | ||
// Remove the css property | ||
path.splice(index, 1); | ||
// Build up the selector | ||
path.forEach(function (key) { | ||
if (hasParentSelector(key)) { | ||
selector = key.replace(/\&/g, selector); | ||
} | ||
else if (isMediaQuery) { | ||
else if (isMediaQuery(key)) { | ||
medias.push(key.slice(6).trim()); | ||
} | ||
else if (property) { | ||
selector = "" + key + parentSelectors.join('') + pseudos.reverse().join('') + " " + selector; | ||
parentSelectors = []; | ||
pseudos = []; | ||
else if (isPseudoSelector(key)) { | ||
selector += key; | ||
} | ||
else { | ||
property = key; | ||
selector += selector ? " " + key : key; | ||
} | ||
}); | ||
if (!property) { | ||
throw new Error("No CSS property provided, just selector " + selector + " for value " + JSON.stringify(value)); | ||
} | ||
if (!selector) { | ||
@@ -83,5 +86,5 @@ throw new Error("No CSS selector provided, just property " + property + " for value " + JSON.stringify(value)); | ||
return { | ||
selector: selector.trim(), | ||
selector: selector, | ||
declaration: declaration, | ||
media: generateCombinedMediaQuery(medias.reverse()), | ||
media: generateCombinedMediaQuery(medias), | ||
}; | ||
@@ -121,11 +124,12 @@ }); | ||
} | ||
function isPropertyPseudo(property) { | ||
function isPseudoSelector(property) { | ||
return property.charAt(0) === ':'; | ||
} | ||
function isPropertyMediaQuery(property) { | ||
exports.isPseudoSelector = isPseudoSelector; | ||
function isMediaQuery(property) { | ||
return property.substr(0, 6) === '@media'; | ||
} | ||
function isPropertyParentSelector(property) { | ||
return property.charAt(0) === '&'; | ||
function hasParentSelector(property) { | ||
return property.indexOf('&') !== -1; | ||
} | ||
//# sourceMappingURL=cssTextForStyles.js.map |
import { Configuration } from './configure'; | ||
import { StyleDeclaration } from './types'; | ||
export default function renderStatic(styles: StyleDeclaration, config?: Configuration): void; | ||
export default function renderStatic(styles: StyleDeclaration<string>, config?: Configuration): void; |
@@ -20,4 +20,4 @@ /** | ||
*/ | ||
export declare type StyleDeclaration = { | ||
[key: string]: StyleRule; | ||
export declare type StyleDeclaration<TKeys extends string> = { | ||
[TKey in TKeys]: StyleRule; | ||
}; | ||
@@ -24,0 +24,0 @@ /** |
@@ -216,3 +216,40 @@ "use strict"; | ||
}); | ||
it("supports rule name replacement", function () { | ||
var className = compile({ | ||
root: { | ||
color: 'red', | ||
}, | ||
child: { | ||
'{root}:hover &': { | ||
color: 'blue', | ||
}, | ||
}, | ||
}); | ||
expect(className).to.deep.equal({ root: 'dapper-root-a', child: 'dapper-child-b' }); | ||
expect(renderCSSTextStub).to.have.been.calledWith([ | ||
".dapper-root-a{color:red}", | ||
".dapper-root-a:hover .dapper-child-b{color:blue}", | ||
]); | ||
}); | ||
it("throws if mode or parent selector is child of pseudo", function () { | ||
expect(function () { return compile({ | ||
root: { | ||
':hover': { | ||
$mode: { | ||
color: 'blue', | ||
}, | ||
}, | ||
}, | ||
}); }).to.throw; | ||
expect(function () { return compile({ | ||
root: { | ||
':hover': { | ||
'&.blah': { | ||
color: 'blue', | ||
}, | ||
}, | ||
}, | ||
}); }).to.throw; | ||
}); | ||
}); | ||
//# sourceMappingURL=compile.js.map |
@@ -117,27 +117,3 @@ "use strict"; | ||
}); | ||
it("Handles pseudo classes with parent selectors", function () { | ||
renderStatic({ | ||
h1: { | ||
':hover': { | ||
'&.dude': { | ||
padding: 4, | ||
}, | ||
}, | ||
}, | ||
}); | ||
expect(renderCSSTextStub).to.have.been.calledWith(['h1.dude:hover{padding:4px}']); | ||
}); | ||
it("Handles multiple sibline pseudo classes with parent selectors", function () { | ||
renderStatic({ | ||
h1: { | ||
':hover': { | ||
'&.dude': { | ||
padding: 4, | ||
}, | ||
}, | ||
}, | ||
}); | ||
expect(renderCSSTextStub).to.have.been.calledWith(['h1.dude:hover{padding:4px}']); | ||
}); | ||
}); | ||
//# sourceMappingURL=renderStatic.js.map |
{ | ||
"name": "@convoy/dapper", | ||
"version": "2.0.72", | ||
"version": "2.0.75", | ||
"description": "Styling library", | ||
@@ -5,0 +5,0 @@ "license": "Apache-2.0", |
357
README.md
# Dapper | ||
[![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 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 +27,5 @@ | ||
## TypeScript/Javascript | ||
Most examples are shown in TypeScript, but dapper works great with Javascript. TypeScript provides autocompletion of your styles and build time checks that those styles exist. | ||
## React usage | ||
@@ -17,3 +40,3 @@ | ||
root: { | ||
backgroundColor: '#eee', | ||
padding: 5, | ||
}, | ||
@@ -34,2 +57,3 @@ }); | ||
### 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 +63,8 @@ ```tsx | ||
interface Props { | ||
highlight: boolean; | ||
} | ||
interface State { | ||
hovered: boolean; | ||
value: string; | ||
} | ||
@@ -46,10 +74,10 @@ | ||
root: { | ||
backgroundColor: '#eee', | ||
'$hover': { | ||
backgroundColor: '#fff', | ||
backgroundColor: '#EEE', | ||
$highlight: { | ||
backgroundColor: '#FFF', | ||
}, | ||
}, | ||
label: { | ||
'$hover': { | ||
color: '#999', | ||
input: { | ||
$tooLong: { | ||
color: 'red', | ||
}, | ||
@@ -60,7 +88,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 +100,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 +110,60 @@ ); | ||
_onMouseEnter = () => { | ||
this.setState({ hovered: true }); | ||
_onChange = ev => { | ||
this.setState({ value: ev.target.value }); | ||
} | ||
} | ||
``` | ||
_onMouseLeave = () => { | ||
this.setState({ hovered: false }); | ||
## Placeholders | ||
Placeholders allow you to reference other styles names inside of a style rule. This can be helpful for cascading styles. | ||
```tsx | ||
import * as dapper from '@convoy/dapper'; | ||
const STYLES = dapper.compile({ | ||
parentA: { | ||
'{child}': { | ||
backgroundColor: 'red', | ||
}, | ||
}, | ||
parentA: { | ||
'{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> | ||
); | ||
} | ||
} | ||
``` | ||
## 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. | ||
## 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', | ||
'html.wf-loading &': { | ||
opacity: 0, | ||
}, | ||
}, | ||
@@ -108,5 +173,9 @@ }); | ||
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}> | ||
Hello World | ||
</div> | ||
); | ||
@@ -117,112 +186,90 @@ } | ||
## Prop or state based styling | ||
## Hover | ||
Placeholders and parent selectors makes it easy to support things like styling subelements based on pseudo class selectors of parents, like hover. This helps avoid creating onMouseEnter, onMouseLeave handlers. | ||
```jsx | ||
import StyleSheet from '@convoy/dapper'; | ||
```tsx | ||
import * as dapper from '@convoy/dapper'; | ||
export interface Props { | ||
large?: boolean; | ||
ghost?: boolean; | ||
className?: string; | ||
} | ||
const STYLES = dapper.compile({ | ||
root: { | ||
padding: 5, | ||
}, | ||
child: { | ||
'{root}:hover &': { | ||
backgroundColor: '#EEE', | ||
}, | ||
}, | ||
}); | ||
export interface State { | ||
hovered: boolean; | ||
export default class Button extends React.Component<Props, State> { | ||
styles = dapper.reactTo(this, STYLES); | ||
render() { | ||
return ( | ||
<div className={this.styles.root}> | ||
<div className={this.styles.child} /> | ||
</div> | ||
); | ||
} | ||
} | ||
``` | ||
export type ModeState = { props: Props, state: State }; | ||
## Media queries | ||
Dapper supports media queries even nested media queries. | ||
```tsx | ||
import * as dapper from '@convoy/dapper'; | ||
const STYLES = StyleSheet.compile({ | ||
const STYLES = dapper.compile({ | ||
root: { | ||
display: 'flex', | ||
backgroundColor: '#BBB', | ||
padding: 8, | ||
margin: { | ||
$large: 10, | ||
$ghost: 5, | ||
}, | ||
':hover': { | ||
backgroundColor: '#555', | ||
$ghost: { | ||
backgroundColor: '#000045', | ||
}, | ||
}, | ||
width: 200, | ||
'@media (max-width: 800px)': { | ||
width: '100px', | ||
}, | ||
$ghost: { | ||
backgroundColor: 'white', | ||
'@media (max-width: 800px)': { | ||
backgroundColor: '#DDD', | ||
width: 100, | ||
'@media (max-height: 500px)': { | ||
width: 60, | ||
}, | ||
$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={classnames(this.styles.root, this.props.className)} | ||
onMouseEnter={this._onMouseEnter} | ||
onMouseLeave={this._onMouseLeave} | ||
> | ||
{this._renderText()} | ||
</div> | ||
<div className={this.styles.root} /> | ||
); | ||
} | ||
} | ||
``` | ||
_renderText() { | ||
return ( | ||
<span className={this.styles.text}> | ||
Button | ||
</span> | ||
); | ||
} | ||
## Nesting media queries/modes/pseudo | ||
Media queries, modes and pseudo class/element selectors can be nested within CSS properties to make things more readable. | ||
_onMouseEnter = () => { | ||
this.setState({hovered: true}); | ||
} | ||
_onMouseLeave = () => { | ||
this.setState({hovered: false}); | ||
} | ||
} | ||
```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, | ||
}, | ||
}, | ||
}); | ||
``` | ||
## 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 +283,3 @@ opacity: 0, | ||
const STYLES = StyleSheet.compile({ | ||
const STYLES = dapper.compile({ | ||
root: { | ||
@@ -244,6 +291,7 @@ animation: `5s ${fadeOut} linear`, | ||
## renderStatic | ||
## 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 +303,84 @@ backgroundColor: '#CCFFFF', | ||
}, | ||
'.pac-container': { | ||
backgroundColor: '#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`. | ||
```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 outside of React contexts and 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: { | ||
width: 200, | ||
}, | ||
}); | ||
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 +387,0 @@ |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
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
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
109753
1606
378