From 9301ea438d67a86e4408ed1b13610e3e6e96f902 Mon Sep 17 00:00:00 2001 From: Nathan Bierema Date: Sun, 16 Aug 2020 01:07:13 -0400 Subject: [PATCH] that's cool --- jest.config.js | 2 +- package.json | 6 + packages/react-base16-styling/package.json | 10 +- packages/react-base16-styling/src/index.ts | 166 ++++++++++++------ packages/react-base16-styling/src/types.ts | 31 +++- .../react-base16-styling/test/index.test.ts | 75 ++++---- yarn.lock | 21 ++- 7 files changed, 200 insertions(+), 111 deletions(-) diff --git a/jest.config.js b/jest.config.js index 7e1daca2..18639473 100644 --- a/jest.config.js +++ b/jest.config.js @@ -1,3 +1,3 @@ module.exports = { - setupFiles: ['devui/tests/setup.js'], + projects: ['/packages/*'], }; diff --git a/package.json b/package.json index 550dc83f..119dddc7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,11 @@ { "private": true, "devDependencies": { + "@babel/cli": "^7.10.5", + "@babel/core": "^7.11.1", + "@babel/plugin-proposal-class-properties": "^7.10.4", + "@babel/preset-env": "^7.11.0", + "@babel/preset-typescript": "^7.10.4", "@types/jest": "^26.0.9", "@typescript-eslint/eslint-plugin": "^3.9.0", "@typescript-eslint/parser": "^3.9.0", @@ -16,6 +21,7 @@ "lerna": "^3.22.1", "lint-staged": "^10.2.11", "prettier": "^2.0.5", + "rimraf": "^3.0.2", "ts-jest": "^26.2.0", "typescript": "^3.9.7" }, diff --git a/packages/react-base16-styling/package.json b/packages/react-base16-styling/package.json index 118deda9..2dd5e90f 100644 --- a/packages/react-base16-styling/package.json +++ b/packages/react-base16-styling/package.json @@ -37,19 +37,13 @@ }, "dependencies": { "@types/base16": "^1.0.2", - "@types/color-string": "^1.5.0", "@types/lodash.curry": "^4.1.6", "base16": "^1.0.0", - "color-string": "^1.5.3", + "color": "^3.1.2", "csstype": "^3.0.2", "lodash.curry": "^4.1.1" }, "devDependencies": { - "@babel/cli": "^7.10.5", - "@babel/core": "^7.11.1", - "@babel/plugin-proposal-class-properties": "^7.10.4", - "@babel/preset-env": "^7.11.0", - "@babel/preset-typescript": "^7.10.4", - "rimraf": "^3.0.2" + "@types/color": "^3.0.1" } } diff --git a/packages/react-base16-styling/src/index.ts b/packages/react-base16-styling/src/index.ts index 7e42d094..ad2c3105 100644 --- a/packages/react-base16-styling/src/index.ts +++ b/packages/react-base16-styling/src/index.ts @@ -1,8 +1,17 @@ -import curry from 'lodash.curry'; import * as base16 from 'base16'; import { Base16Theme } from 'base16'; -import { get, to } from 'color-string'; -import { Color, yuv2rgb, rgb2yuv } from './colorConverters'; +import Color from 'color'; +import * as CSS from 'csstype'; +import curry from 'lodash.curry'; +import { Color as ColorTuple, yuv2rgb, rgb2yuv } from './colorConverters'; +import { + Styling, + StylingConfig, + StylingFunction, + StylingValue, + StylingValueFunction, + Theme, +} from './types'; const DEFAULT_BASE16 = base16.default; @@ -12,17 +21,16 @@ const BASE16_KEYS = Object.keys(DEFAULT_BASE16); // converts to bright enough inversed color const flip = (x: number) => (x < 0.25 ? 1 : x < 0.5 ? 0.9 - x : 1.1 - x); -const invertColor = (colorString: string) => { - const color = get.rgb(colorString); - if (!color) throw new Error(`Unable to parse color: ${colorString}`); - const [y, u, v] = rgb2yuv([color[0], color[1], color[2]]); - const flippedYuv: Color = [flip(y), u, v]; +const invertColor = (hexString: string) => { + const color = Color(hexString); + const [y, u, v] = rgb2yuv(color.array() as ColorTuple); + const flippedYuv: ColorTuple = [flip(y), u, v]; const rgb = yuv2rgb(flippedYuv); - return to.hex(rgb); + return Color.rgb(rgb).hex(); }; -const merger = function merger(styling) { - return (prevStyling) => ({ +const merger = (styling: Styling) => { + return (prevStyling: Styling) => ({ className: [prevStyling.className, styling.className] .filter(Boolean) .join(' '), @@ -30,7 +38,10 @@ const merger = function merger(styling) { }); }; -const mergeStyling = function mergeStyling(customStyling, defaultStyling) { +const mergeStyling = function mergeStyling( + customStyling: StylingValue, + defaultStyling: StylingValue +): StylingValue | undefined { if (customStyling === undefined) { return defaultStyling; } @@ -47,53 +58,68 @@ const mergeStyling = function mergeStyling(customStyling, defaultStyling) { case 'string': return [defaultStyling, customStyling].filter(Boolean).join(' '); case 'object': - return merger({ className: customStyling, style: defaultStyling }); + return merger({ + className: customStyling as string, + style: defaultStyling as CSS.Properties, + }); case 'function': - return (styling, ...args) => + return (styling: Styling, ...args: any[]) => merger({ - className: customStyling, - })(defaultStyling(styling, ...args)); + className: customStyling as string, + })((defaultStyling as StylingValueFunction)(styling, ...args)); } break; case 'object': switch (defaultType) { case 'string': - return merger({ className: defaultStyling, style: customStyling }); + return merger({ + className: defaultStyling as string, + style: customStyling as CSS.Properties, + }); case 'object': - return { ...defaultStyling, ...customStyling }; + return { + ...(defaultStyling as CSS.Properties), + ...(customStyling as CSS.Properties), + }; case 'function': - return (styling, ...args) => + return (styling: Styling, ...args: any[]) => merger({ - style: customStyling, - })(defaultStyling(styling, ...args)); + style: customStyling as CSS.Properties, + })((defaultStyling as StylingValueFunction)(styling, ...args)); } break; case 'function': switch (defaultType) { case 'string': return (styling, ...args) => - customStyling( + (customStyling as StylingValueFunction)( merger(styling)({ - className: defaultStyling, + className: defaultStyling as string, }), ...args ); case 'object': return (styling, ...args) => - customStyling( + (customStyling as StylingValueFunction)( merger(styling)({ - style: defaultStyling, + style: defaultStyling as CSS.Properties, }), ...args ); case 'function': return (styling, ...args) => - customStyling(defaultStyling(styling, ...args), ...args); + (customStyling as StylingValueFunction)( + (defaultStyling as StylingValueFunction)(styling, ...args), + ...args + ); } } }; -const mergeStylings = function mergeStylings(customStylings, defaultStylings) { +const mergeStylings = ( + customStylings: StylingConfig, + defaultStylings: StylingConfig +): StylingConfig => { const keys = Object.keys(defaultStylings); for (const key in customStylings) { if (keys.indexOf(key) === -1) keys.push(key); @@ -101,19 +127,23 @@ const mergeStylings = function mergeStylings(customStylings, defaultStylings) { return keys.reduce( (mergedStyling, key) => ( - (mergedStyling[key] = mergeStyling( - customStylings[key], - defaultStylings[key] - )), + (mergedStyling[key as keyof StylingConfig] = mergeStyling( + customStylings[key] as StylingValue, + defaultStylings[key] as StylingValue + ) as StylingValue), mergedStyling ), - {} + {} as StylingConfig ); }; -const getStylingByKeys = (mergedStyling, keys, ...args) => { +const getStylingByKeys = ( + mergedStyling: StylingConfig, + keys: string | string[], + ...args: any[] +): Styling => { if (keys === null) { - return mergedStyling; + return mergedStyling as Styling; } if (!Array.isArray(keys)) { @@ -148,42 +178,55 @@ const getStylingByKeys = (mergedStyling, keys, ...args) => { return props; }; -export const invertBase16Theme = (base16Theme: Base16Theme) => +export const invertBase16Theme = (base16Theme: Base16Theme): Base16Theme => Object.keys(base16Theme).reduce( (t, key) => ( - (t[key] = /^base/.test(key) - ? invertColor(base16Theme[key]) + (t[key as keyof Base16Theme] = /^base/.test(key) + ? invertColor(base16Theme[key as keyof Base16Theme]) : key === 'scheme' ? base16Theme[key] + ':inverted' - : base16Theme[key]), + : base16Theme[key as keyof Base16Theme]), t ), - {} + {} as Base16Theme ); export const createStyling = curry( - (getStylingFromBase16, options = {}, themeOrStyling = {}, ...args) => { + ( + getStylingFromBase16: (base16Theme: Base16Theme) => StylingConfig, + options: { + defaultBase16?: Base16Theme; + base16Themes?: { [themeName: string]: Base16Theme }; + } = {}, + themeOrStyling: Theme = {}, + ...args + ): StylingFunction => { const { defaultBase16 = DEFAULT_BASE16, base16Themes = null } = options; const base16Theme = getBase16Theme(themeOrStyling, base16Themes); if (base16Theme) { themeOrStyling = { ...base16Theme, - ...themeOrStyling, + ...(themeOrStyling as Base16Theme | StylingConfig), }; } const theme = BASE16_KEYS.reduce( - (t, key) => ((t[key] = themeOrStyling[key] || defaultBase16[key]), t), - {} + (t, key) => ( + (t[key as keyof Base16Theme] = + (themeOrStyling as Base16Theme)[key as keyof Base16Theme] || + defaultBase16[key as keyof Base16Theme]), + t + ), + {} as Base16Theme ); const customStyling = Object.keys(themeOrStyling).reduce( (s, key) => BASE16_KEYS.indexOf(key) === -1 - ? ((s[key] = themeOrStyling[key]), s) + ? ((s[key] = (themeOrStyling as StylingConfig)[key]), s) : s, - {} + {} as StylingConfig ); const defaultStyling = getStylingFromBase16(theme); @@ -195,38 +238,53 @@ export const createStyling = curry( 3 ); -export const getBase16Theme = (theme, base16Themes) => { - if (theme && theme.extend) { - theme = theme.extend; +const isStylingConfig = (theme: Theme): theme is StylingConfig => + !!(theme as StylingConfig).extend; + +export const getBase16Theme = ( + theme: Theme, + base16Themes?: { [themeName: string]: Base16Theme } | null +): Base16Theme | undefined => { + if (theme && isStylingConfig(theme) && theme.extend) { + theme = theme.extend as string | Base16Theme; } if (typeof theme === 'string') { const [themeName, modifier] = theme.split(':'); - theme = (base16Themes || {})[themeName] || base16[themeName]; + if (base16Themes) { + theme = base16Themes[themeName]; + } else { + theme = base16[themeName as keyof typeof base16]; + } if (modifier === 'inverted') { theme = invertBase16Theme(theme); } } return theme && Object.prototype.hasOwnProperty.call(theme, 'base00') - ? theme + ? (theme as Base16Theme) : undefined; }; -export const invertTheme = (theme) => { +export const invertTheme = (theme: Theme): Theme => { if (typeof theme === 'string') { return `${theme}:inverted`; } - if (theme && theme.extend) { + if (theme && isStylingConfig(theme) && theme.extend) { if (typeof theme.extend === 'string') { return { ...theme, extend: `${theme.extend}:inverted` }; } - return { ...theme, extend: invertBase16Theme(theme.extend) }; + return { + ...theme, + extend: invertBase16Theme(theme.extend as Base16Theme), + }; } if (theme) { - return invertBase16Theme(theme); + return invertBase16Theme(theme as Base16Theme); } + + return theme; }; diff --git a/packages/react-base16-styling/src/types.ts b/packages/react-base16-styling/src/types.ts index 89ebca97..f5e93f0e 100644 --- a/packages/react-base16-styling/src/types.ts +++ b/packages/react-base16-styling/src/types.ts @@ -1,11 +1,32 @@ import { Base16Theme } from 'base16'; import * as CSS from 'csstype'; -export type StylingValue = string | CSS.Properties | - -export interface StylingConfig { - extend?: string | Base16Theme; - [name: string]: StylingValue; +export interface Styling { + className?: string; + style?: CSS.Properties; } +export type StylingValueFunction = ( + styling: Styling, + ...rest: any[] +) => Styling; + +export type StylingValue = + | string + | CSS.Properties + | StylingValueFunction; + +export type StylingConfig = { + // Should actually only be string | Base16Theme + extend?: string | Base16Theme | StylingValue; +} & { + // Should actually only be StylingValue + [name: string]: StylingValue | string | Base16Theme; +}; + export type Theme = string | Base16Theme | StylingConfig; + +export type StylingFunction = ( + keys: string | string[], + ...rest: any[] +) => Styling; diff --git a/packages/react-base16-styling/test/index.test.ts b/packages/react-base16-styling/test/index.test.ts index b1b71766..1ee0e6ae 100644 --- a/packages/react-base16-styling/test/index.test.ts +++ b/packages/react-base16-styling/test/index.test.ts @@ -1,5 +1,6 @@ import { createStyling, invertBase16Theme, getBase16Theme } from '../src'; -import { apathy } from 'base16'; +import { apathy, Base16Theme } from 'base16'; +import { Styling, StylingConfig } from '../src/types'; const base16Theme = { scheme: 'myscheme', @@ -25,52 +26,52 @@ const base16Theme = { 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', + 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', + 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', + 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', + base0F: '#3A9257', scheme: 'apathy:inverted', }; -const getStylingFromBase16 = (base16) => ({ +const getStylingFromBase16 = (base16: Base16Theme): StylingConfig => ({ testClass: 'testClass', testStyle: { color: base16.base00, }, - testFunc: ({ style }, arg) => ({ - className: 'testClass--' + arg, + testFunc: ({ style }, arg: string) => ({ + className: `testClass--${arg}`, style: { ...style, width: 0, @@ -93,7 +94,7 @@ test('getBase16Theme', () => { expect(getBase16Theme('apathy')).toEqual(apathy); expect(getBase16Theme({ extend: 'apathy' })).toEqual(apathy); expect(getBase16Theme('apathy:inverted')).toEqual(apathyInverted); - expect(getBase16Theme({})).toBe(undefined); + expect(getBase16Theme({})).toBeUndefined(); }); test('createStyling (default)', () => { @@ -122,8 +123,8 @@ test('createStyling (custom)', () => { let customStyling = styling({ testClass: 'customClass', testStyle: { height: 0 }, - testFunc: (styling, arg) => ({ - className: styling.className + ' customClass--' + arg, + testFunc: (styling: Styling, arg: string) => ({ + className: `${styling.className!} customClass--${arg}`, style: { ...styling.style, border: 0, diff --git a/yarn.lock b/yarn.lock index 530bb714..a0cb21be 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3055,15 +3055,24 @@ resolved "https://registry.yarnpkg.com/@types/classnames/-/classnames-2.2.10.tgz#cc658ca319b6355399efc1f5b9e818f1a24bf999" integrity sha512-1UzDldn9GfYYEsWWnn/P4wkTlkZDH7lDb0wBMGbtIQc9zXEQq7FlKBdZUn6OBqD8sKZZ2RQO2mAjGpXiDGoRmQ== -"@types/color-name@^1.1.1": +"@types/color-convert@*": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@types/color-convert/-/color-convert-1.9.0.tgz#bfa8203e41e7c65471e9841d7e306a7cd8b5172d" + integrity sha512-OKGEfULrvSL2VRbkl/gnjjgbbF7ycIlpSsX7Nkab4MOWi5XxmgBYvuiQ7lcCFY5cPDz7MUNaKgxte2VRmtr4Fg== + dependencies: + "@types/color-name" "*" + +"@types/color-name@*", "@types/color-name@^1.1.1": version "1.1.1" resolved "https://registry.yarnpkg.com/@types/color-name/-/color-name-1.1.1.tgz#1c1261bbeaa10a8055bbc5d8ab84b7b2afc846a0" integrity sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ== -"@types/color-string@^1.5.0": - version "1.5.0" - resolved "https://registry.yarnpkg.com/@types/color-string/-/color-string-1.5.0.tgz#0a9f3ee6bc9bd4f89693fc3f2bc30d47ad68afc3" - integrity sha512-17/8LWbkfvoKgfnnBvKbUnPTzPtzBlSELf5FPvVt9dRTQW88igOYZZ78us9dmbY1301VtTBEnuPTKspvmtGDxA== +"@types/color@^3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@types/color/-/color-3.0.1.tgz#2900490ed04da8116c5058cd5dba3572d5a25071" + integrity sha512-oeUWVaAwI+xINDUx+3F2vJkl/vVB03VChFF/Gl3iQCdbcakjuoJyMOba+3BXRtnBhxZ7uBYqQBi9EpLnvSoztA== + dependencies: + "@types/color-convert" "*" "@types/eslint-visitor-keys@^1.0.0": version "1.0.0" @@ -5454,7 +5463,7 @@ color-name@^1.0.0, color-name@~1.1.4: resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== -color-string@^1.5.2, color-string@^1.5.3: +color-string@^1.5.2: version "1.5.3" resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.5.3.tgz#c9bbc5f01b58b5492f3d6857459cb6590ce204cc" integrity sha512-dC2C5qeWoYkxki5UAXapdjqO672AM4vZuPGRQfO8b5HKuKGBbKWpITyDYN7TOFKvRW7kOgAn3746clDBMDJyQw==