Add react-base16-styling package (#563)

This commit is contained in:
Nathan Bierema 2020-08-06 17:46:08 -04:00 committed by GitHub
parent ad281ba07a
commit 7497595b81
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 572 additions and 13 deletions

View File

@ -0,0 +1,4 @@
{
"presets": ["@babel/preset-env"],
"plugins": ["@babel/plugin-proposal-class-properties"]
}

View File

@ -0,0 +1,91 @@
# react-base16-styling [![Build Status](https://img.shields.io/travis/alexkuz/react-base16-styling/master.svg)](https://travis-ci.org/alexkuz/react-base16-styling) [![Latest Stable Version](https://img.shields.io/npm/v/react-base16-styling.svg)](https://www.npmjs.com/package/react-base16-styling)
React styling with base16 color scheme support
Inspired by [react-themeable](https://github.com/markdalgleish/react-themeable), this utility provides flexible theming for your component with [base16](https://github.com/chriskempson/base16) theme support.
## Install
```
yarn add react-base16-styling
```
## Usage
```jsx
import { createStyling } from 'react-base16-styling';
import base16Themes from './base16Themes';
function getStylingFromBase16(base16Theme) {
return {
myComponent: {
backgroundColor: base16Theme.base00
},
myComponentToggleColor({ style, className }, clickCount) {
return {
style: {
...style,
backgroundColor: clickCount % 2 ? 'red' : 'blue'
}
};
}
};
}
const createStylingFromTheme = createStyling(getStylingFromBase16, {
defaultBase16: base16Themes.solarized,
base16Themes
});
class MyComponent extends Component {
state = { clickCount: 0 };
render() {
const { theme } = this.props;
const { clickCount } = this.state;
const styling = createStylingFromTheme(theme);
return (
<div {...styling('myComponent')}>
<a onClick={() => this.setState({ clickCount: clickCount + 1 })}>
Click Me
</a>
<div {...styling('myComponentToggleColor', clickCount)}>
{clickCount}
</div>
</div>
);
}
}
```
## `createStyling`
```js
function(getStylingFromBase16, defaultStylingOptions, themeOrStyling)
```
| Argument | Signature | Description |
| ----------------------- | --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `getStylingFromBase16` | `function(base16Theme)` | creates object with default stylings for your component, using provided base16 theme. |
| `defaultStylingOptions` | `{ defaultBase16, base16Themes }` | optional parameters, allow to set default `base16` theme and additional `base16` themes for component. |
| `themeOrStyling` | `string` or `object` | `base16` theme name, `base16` theme object or styling object. Theme name can have a modifier: `"themeName:inverted"` to invert theme colors (see [[#invertTheme]]) |
Styling object values could be: - objects (treated as style definitions) - strings (class names) - functions (they may be provided with additional parameters and should return object { style, className })
## `getBase16Theme`
```js
function(themeOrStyling, base16Themes)
```
Helper method that returns `base16` theme object if `themeOrStyling` is `base16` theme name or theme object.
## `invertTheme`
```js
function(theme)
```
Helper method that inverts `base16` theme colors, creating light theme out of dark one or vice versa.

View File

@ -0,0 +1,43 @@
{
"name": "react-base16-styling",
"version": "0.6.0",
"description": "React styling with base16 color scheme support",
"main": "lib/index.js",
"scripts": {
"clean": "rimraf lib",
"build": "babel src --out-dir lib",
"test": "jest",
"prepare": "npm run build",
"prepublishOnly": "npm run test && npm run clean && npm run build"
},
"repository": {
"type": "git",
"url": "git+https://github.com/reduxjs/redux-devtools.git"
},
"keywords": [
"react",
"theme",
"base16",
"styling"
],
"author": "Alexander <alexkuz@gmail.com> (http://kuzya.org/)",
"license": "MIT",
"bugs": {
"url": "https://github.com/reduxjs/redux-devtools/issues"
},
"homepage": "https://github.com/reduxjs/redux-devtools",
"devDependencies": {
"@babel/cli": "^7.10.5",
"@babel/core": "^7.11.0",
"@babel/plugin-proposal-class-properties": "^7.10.4",
"@babel/plugin-transform-runtime": "^7.11.0",
"@babel/preset-env": "^7.11.0",
"rimraf": "^2.7.1"
},
"dependencies": {
"base16": "^1.0.0",
"lodash.curry": "^4.1.1",
"lodash.flow": "^3.5.0",
"pure-color": "^1.3.0"
}
}

View File

@ -0,0 +1,30 @@
export function yuv2rgb(yuv) {
var y = yuv[0],
u = yuv[1],
v = yuv[2],
r,
g,
b;
r = y * 1 + u * 0 + v * 1.13983;
g = y * 1 + u * -0.39465 + v * -0.5806;
b = y * 1 + u * 2.02311 + v * 0;
r = Math.min(Math.max(0, r), 1);
g = Math.min(Math.max(0, g), 1);
b = Math.min(Math.max(0, b), 1);
return [r * 255, g * 255, b * 255];
}
export function rgb2yuv(rgb) {
var r = rgb[0] / 255,
g = rgb[1] / 255,
b = rgb[2] / 255;
var y = r * 0.299 + g * 0.587 + b * 0.114;
var u = r * -0.14713 + g * -0.28886 + b * 0.436;
var v = r * 0.615 + g * -0.51499 + b * -0.10001;
return [y, u, v];
}

View File

@ -0,0 +1,212 @@
import curry from 'lodash.curry';
import * as base16 from 'base16';
import rgb2hex from 'pure-color/convert/rgb2hex';
import parse from 'pure-color/parse';
import flow from 'lodash.flow';
import { yuv2rgb, rgb2yuv } from './colorConverters';
const DEFAULT_BASE16 = base16.default;
const BASE16_KEYS = Object.keys(DEFAULT_BASE16);
// we need a correcting factor, so that a dark, but not black background color
// converts to bright enough inversed color
const flip = x => (x < 0.25 ? 1 : x < 0.5 ? 0.9 - x : 1.1 - x);
const invertColor = flow(
parse,
rgb2yuv,
([y, u, v]) => [flip(y), u, v],
yuv2rgb,
rgb2hex
);
const merger = function merger(styling) {
return prevStyling => ({
className: [prevStyling.className, styling.className]
.filter(Boolean)
.join(' '),
style: { ...(prevStyling.style || {}), ...(styling.style || {}) }
});
};
const mergeStyling = function mergeStyling(customStyling, defaultStyling) {
if (customStyling === undefined) {
return defaultStyling;
}
if (defaultStyling === undefined) {
return customStyling;
}
const customType = typeof customStyling;
const defaultType = typeof defaultStyling;
switch (customType) {
case 'string':
switch (defaultType) {
case 'string':
return [defaultStyling, customStyling].filter(Boolean).join(' ');
case 'object':
return merger({ className: customStyling, style: defaultStyling });
case 'function':
return (styling, ...args) =>
merger({
className: customStyling
})(defaultStyling(styling, ...args));
}
break;
case 'object':
switch (defaultType) {
case 'string':
return merger({ className: defaultStyling, style: customStyling });
case 'object':
return { ...defaultStyling, ...customStyling };
case 'function':
return (styling, ...args) =>
merger({
style: customStyling
})(defaultStyling(styling, ...args));
}
break;
case 'function':
switch (defaultType) {
case 'string':
return (styling, ...args) =>
customStyling(
merger(styling)({
className: defaultStyling
}),
...args
);
case 'object':
return (styling, ...args) =>
customStyling(
merger(styling)({
style: defaultStyling
}),
...args
);
case 'function':
return (styling, ...args) =>
customStyling(defaultStyling(styling, ...args), ...args);
}
}
};
const mergeStylings = function mergeStylings(customStylings, defaultStylings) {
const keys = Object.keys(defaultStylings);
for (const key in customStylings) {
if (keys.indexOf(key) === -1) keys.push(key);
}
return keys.reduce(
(mergedStyling, key) => (
(mergedStyling[key] = mergeStyling(
customStylings[key],
defaultStylings[key]
)),
mergedStyling
),
{}
);
};
const getStylingByKeys = (mergedStyling, keys, ...args) => {
if (keys === null) {
return mergedStyling;
}
if (!Array.isArray(keys)) {
keys = [keys];
}
const styles = keys.map(key => mergedStyling[key]).filter(Boolean);
const props = styles.reduce(
(obj, s) => {
if (typeof s === 'string') {
obj.className = [obj.className, s].filter(Boolean).join(' ');
} else if (typeof s === 'object') {
obj.style = { ...obj.style, ...s };
} else if (typeof s === 'function') {
obj = { ...obj, ...s(obj, ...args) };
}
return obj;
},
{ className: '', style: {} }
);
if (!props.className) {
delete props.className;
}
if (Object.keys(props.style).length === 0) {
delete props.style;
}
return props;
};
export const invertTheme = theme =>
Object.keys(theme).reduce(
(t, key) => (
(t[key] = /^base/.test(key)
? invertColor(theme[key])
: key === 'scheme'
? theme[key] + ':inverted'
: theme[key]),
t
),
{}
);
export const createStyling = curry(
(getStylingFromBase16, options = {}, themeOrStyling = {}, ...args) => {
const { defaultBase16 = DEFAULT_BASE16, base16Themes = null } = options;
const base16Theme = getBase16Theme(themeOrStyling, base16Themes);
if (base16Theme) {
themeOrStyling = {
...base16Theme,
...themeOrStyling
};
}
const theme = BASE16_KEYS.reduce(
(t, key) => ((t[key] = themeOrStyling[key] || defaultBase16[key]), t),
{}
);
const customStyling = Object.keys(themeOrStyling).reduce(
(s, key) =>
BASE16_KEYS.indexOf(key) === -1
? ((s[key] = themeOrStyling[key]), s)
: s,
{}
);
const defaultStyling = getStylingFromBase16(theme);
const mergedStyling = mergeStylings(customStyling, defaultStyling);
return curry(getStylingByKeys, 2)(mergedStyling, ...args);
},
3
);
export const getBase16Theme = (theme, base16Themes) => {
if (theme && theme.extend) {
theme = theme.extend;
}
if (typeof theme === 'string') {
const [themeName, modifier] = theme.split(':');
theme = (base16Themes || {})[themeName] || base16[themeName];
if (modifier === 'inverted') {
theme = invertTheme(theme);
}
}
return theme && theme.hasOwnProperty('base00') ? theme : undefined;
};

View File

@ -0,0 +1,189 @@
import { createStyling, invertTheme, getBase16Theme } from '../src';
import apathy from 'base16/lib/apathy';
const base16Theme = {
scheme: 'myscheme',
author: 'me',
base00: '#000000',
base01: '#222222',
base02: '#444444',
base03: '#666666',
base04: '#999999',
base05: '#bbbbbb',
base06: '#dddddd',
base07: '#ffffff',
base08: '#ff0000',
base09: '#ff9900',
base0A: '#ffff00',
base0B: '#999900',
base0C: '#009999',
base0D: '#009900',
base0E: '#9999ff',
base0F: '#ff0099'
};
const invertedBase16Theme = {
scheme: 'myscheme:inverted',
author: 'me',
base00: '#ffffff',
base01: '#ffffff',
base02: '#a2a1a2',
base03: '#807f80',
base04: '#807f80',
base05: '#5e5d5e',
base06: '#3c3b3c',
base07: '#1a191a',
base08: '#ff4d4d',
base09: '#cb6500',
base0A: '#545400',
base0B: '#a2a20a',
base0C: '#0fa8a8',
base0D: '#32cb32',
base0E: '#6868ce',
base0F: '#ff2ac3'
};
const apathyInverted = {
author: 'jannik siebert (https://github.com/janniks)',
base00: '#efffff',
base01: '#e3ffff',
base02: '#daffff',
base03: '#67a49a',
base04: '#66a399',
base05: '#51857c',
base06: '#3c635d',
base07: '#2a3f3c',
base08: '#2f8779',
base09: '#4e89a6',
base0A: '#8391db',
base0B: '#b167bf',
base0C: '#c8707e',
base0D: '#a7994f',
base0E: '#469038',
base0F: '#3a9257',
scheme: 'apathy:inverted'
};
const getStylingFromBase16 = base16 => ({
testClass: 'testClass',
testStyle: {
color: base16.base00
},
testFunc: ({ style }, arg) => ({
className: 'testClass--' + arg,
style: {
...style,
width: 0,
color: base16.base00
}
}),
baseStyle: {
color: 'red'
},
additionalStyle: {
border: 0
}
});
test('invertTheme', () => {
expect(invertTheme(base16Theme)).toEqual(invertedBase16Theme);
});
test('getBase16Theme', () => {
expect(getBase16Theme('apathy')).toEqual(apathy);
expect(getBase16Theme({ extend: 'apathy' })).toEqual(apathy);
expect(getBase16Theme('apathy:inverted')).toEqual(apathyInverted);
expect(getBase16Theme({})).toBe(undefined);
});
test('createStyling (default)', () => {
const styling = createStyling(getStylingFromBase16, {
defaultBase16: apathy
});
const defaultStyling = styling(undefined);
expect(defaultStyling('testClass')).toEqual({ className: 'testClass' });
expect(defaultStyling('testStyle')).toEqual({
style: { color: apathy.base00 }
});
expect(defaultStyling('testFunc', 'mod')).toEqual({
className: 'testClass--mod',
style: {
width: 0,
color: apathy.base00
}
});
});
test('createStyling (custom)', () => {
const styling = createStyling(getStylingFromBase16, {
defaultBase16: apathy
});
let customStyling = styling({
testClass: 'customClass',
testStyle: { height: 0 },
testFunc: (styling, arg) => ({
className: styling.className + ' customClass--' + arg,
style: {
...styling.style,
border: 0
}
})
});
expect(customStyling('testClass')).toEqual({
className: 'testClass customClass'
});
expect(customStyling('testStyle')).toEqual({
style: { color: apathy.base00, height: 0 }
});
expect(customStyling('testFunc', 'mod')).toEqual({
className: 'testClass--mod customClass--mod',
style: {
width: 0,
color: apathy.base00,
border: 0
}
});
customStyling = styling({
testClass: () => ({
className: 'customClass'
}),
testStyle: () => ({
style: {
border: 0
}
})
});
expect(customStyling('testClass')).toEqual({ className: 'customClass' });
expect(customStyling('testStyle')).toEqual({ style: { border: 0 } });
});
test('createStyling (multiple)', () => {
const styling = createStyling(getStylingFromBase16, {
defaultBase16: apathy
});
let customStyling = styling({
baseStyle: ({ style }) => ({ style: { ...style, color: 'blue' } })
});
expect(customStyling(['baseStyle', 'additionalStyle'])).toEqual({
style: {
color: 'blue',
border: 0
}
});
customStyling = styling({
additionalStyle: ({ style }) => ({ style: { ...style, border: 1 } })
});
expect(customStyling(['baseStyle', 'additionalStyle'])).toEqual({
style: {
color: 'red',
border: 1
}
});
});

View File

@ -10539,7 +10539,7 @@ lodash.clonedeep@4.5.0, lodash.clonedeep@^4.5.0:
resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef" resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef"
integrity sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8= integrity sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=
lodash.curry@^4.0.1: lodash.curry@^4.0.1, lodash.curry@^4.1.1:
version "4.1.1" version "4.1.1"
resolved "https://registry.yarnpkg.com/lodash.curry/-/lodash.curry-4.1.1.tgz#248e36072ede906501d75966200a86dab8b23170" resolved "https://registry.yarnpkg.com/lodash.curry/-/lodash.curry-4.1.1.tgz#248e36072ede906501d75966200a86dab8b23170"
integrity sha1-JI42By7ekGUB11lmIAqG2riyMXA= integrity sha1-JI42By7ekGUB11lmIAqG2riyMXA=
@ -10566,7 +10566,7 @@ lodash.flattendeep@^4.4.0:
resolved "https://registry.yarnpkg.com/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz#fb030917f86a3134e5bc9bec0d69e0013ddfedb2" resolved "https://registry.yarnpkg.com/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz#fb030917f86a3134e5bc9bec0d69e0013ddfedb2"
integrity sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI= integrity sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI=
lodash.flow@^3.3.0: lodash.flow@^3.3.0, lodash.flow@^3.5.0:
version "3.5.0" version "3.5.0"
resolved "https://registry.yarnpkg.com/lodash.flow/-/lodash.flow-3.5.0.tgz#87bf40292b8cf83e4e8ce1a3ae4209e20071675a" resolved "https://registry.yarnpkg.com/lodash.flow/-/lodash.flow-3.5.0.tgz#87bf40292b8cf83e4e8ce1a3ae4209e20071675a"
integrity sha1-h79AKSuM+D5OjOGjrkIJ4gBxZ1o= integrity sha1-h79AKSuM+D5OjOGjrkIJ4gBxZ1o=
@ -12956,7 +12956,7 @@ punycode@^2.1.0, punycode@^2.1.1:
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec"
integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==
pure-color@^1.2.0: pure-color@^1.2.0, pure-color@^1.3.0:
version "1.3.0" version "1.3.0"
resolved "https://registry.yarnpkg.com/pure-color/-/pure-color-1.3.0.tgz#1fe064fb0ac851f0de61320a8bf796836422f33e" resolved "https://registry.yarnpkg.com/pure-color/-/pure-color-1.3.0.tgz#1fe064fb0ac851f0de61320a8bf796836422f33e"
integrity sha1-H+Bk+wrIUfDeYTIKi/eWg2Qi8z4= integrity sha1-H+Bk+wrIUfDeYTIKi/eWg2Qi8z4=
@ -13136,16 +13136,6 @@ react-base16-styling@^0.5.1, react-base16-styling@^0.5.3:
lodash.flow "^3.3.0" lodash.flow "^3.3.0"
pure-color "^1.2.0" pure-color "^1.2.0"
react-base16-styling@^0.6.0:
version "0.6.0"
resolved "https://registry.yarnpkg.com/react-base16-styling/-/react-base16-styling-0.6.0.tgz#ef2156d66cf4139695c8a167886cb69ea660792c"
integrity sha1-7yFW1mz0E5aVyKFniGy2nqZgeSw=
dependencies:
base16 "^1.0.0"
lodash.curry "^4.0.1"
lodash.flow "^3.3.0"
pure-color "^1.2.0"
react-bootstrap@^0.30.6: react-bootstrap@^0.30.6:
version "0.30.10" version "0.30.10"
resolved "https://registry.yarnpkg.com/react-bootstrap/-/react-bootstrap-0.30.10.tgz#dbba6909595f2af4d91937db0f96ec8c2df2d1a8" resolved "https://registry.yarnpkg.com/react-bootstrap/-/react-bootstrap-0.30.10.tgz#dbba6909595f2af4d91937db0f96ec8c2df2d1a8"