diff --git a/.github/ISSUE_TEMPLATE/---bug--npm-packages--.md b/.github/ISSUE_TEMPLATE/---bug--npm-packages--.md new file mode 100644 index 00000000..aab5236f --- /dev/null +++ b/.github/ISSUE_TEMPLATE/---bug--npm-packages--.md @@ -0,0 +1,33 @@ +--- +name: "\U0001F41B Bug [npm packages] " +about: Report a problem with the @leonardo-contrast-colors module +title: '' +labels: bug +assignees: '' + +--- + +## Description + + + +## Steps to reproduce + + + +## Expected behavior + + + +## Screenshots + + +## Leonardo package and version + + + +## Environment + - **Browser(s) and OS(s):** + +## Additional context + diff --git a/.github/ISSUE_TEMPLATE/--bug-web-tool.md b/.github/ISSUE_TEMPLATE/--bug-web-tool.md new file mode 100644 index 00000000..fa4e0002 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/--bug-web-tool.md @@ -0,0 +1,33 @@ +--- +name: "\U0001F41B Bug [Web Tool] " +about: Report a problem with the Leonardo web interface +title: '' +labels: UI, bug +assignees: '' + +--- + +## Description + + + +## Steps to reproduce + +1. Go to http://leonardocolor.io/ +2. Click on ... +3. Observe ... + + +## Expected behavior + + + +## Screenshots + + + +## Environment + - **Browser(s) and OS(s):** + +## Additional context + diff --git a/.github/ISSUE_TEMPLATE/--feature-request--npm-packages-.md b/.github/ISSUE_TEMPLATE/--feature-request--npm-packages-.md new file mode 100644 index 00000000..c675f96c --- /dev/null +++ b/.github/ISSUE_TEMPLATE/--feature-request--npm-packages-.md @@ -0,0 +1,23 @@ +--- +name: "✨ Feature request [npm packages]" +about: Suggest a new feature for the Leonardo npm modules +title: '' +labels: enhancement, npm packages +assignees: '' + +--- + +## Description + + + +## Why do you need this feature? + + + +## Leonardo package and version + + + +## Additional context + diff --git a/.github/ISSUE_TEMPLATE/--feature-web-tool.md b/.github/ISSUE_TEMPLATE/--feature-web-tool.md new file mode 100644 index 00000000..601800ca --- /dev/null +++ b/.github/ISSUE_TEMPLATE/--feature-web-tool.md @@ -0,0 +1,19 @@ +--- +name: "✨ Feature request [Web Tool]" +about: Suggest a new feature for the Leonardo web interface +title: '' +labels: UI, enhancement +assignees: '' + +--- + +## Description + + + +## Why do you need this feature? + + + +## Additional context + diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index b7eb0b33..ec781fdb 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -3,8 +3,8 @@ ## Description diff --git a/README.md b/README.md index f7807d87..ed373585 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,9 @@ ![Leonardo logo](.github/Leonardo_Logo.png) +[![npm version](https://badge.fury.io/js/%40adobe%2Fleonardo-contrast-colors.svg)](https://www.npmjs.com/package/@adobe/leonardo-contrast-colors) +[![license](https://img.shields.io/github/license/adobe/leonardo)](https://github.com/adobe/leonardo/blob/master/LICENSE) +[![Pull requests welcome](https://img.shields.io/badge/PRs-welcome-blueviolet)](https://github.com/adobe/leonardo/blob/master/.github/CONTRIBUTING.md) [![Web UI](https://img.shields.io/badge/web%20tool-leonardocolor.io-blue)](https://leonardocolor.io) + # Leonardo Authoring [adaptive color palettes](#what-is-adaptive-color) for generating color based on a desired contrast ratio. @@ -8,15 +12,14 @@ For a detailed walkthrough of Leonardo, [check out this article](https://medium. ## Project Goals To make it easier for designers and engineers to leverage color science to create custom interpolations for a value scale, and to make it easier for designers and engineers to conform to [WCAG minimum contrast standards](https://www.w3.org/TR/WCAG21/#contrast-minimum) by using contrast ratio as the starting point, rather than a post-color-selection auditing process. -1. [Leonardo web application](leonardo-web-application) +1. [Leonardo web application](#leonardo-web-application) 2. [Show me a demo](#show-me-a-demo) 3. [What is "adaptive color"?](#what-is-adaptive-color) 4. [Using Leonardo](#using-leonardo) -5. [API Reference](#api-reference) -6. [Why are not all contrast ratios available?](#why-are-not-all-contrast-ratios-available) -7. [D3 Color](#d3-color) -8. [Contributing](#contributing) -9. [Licensing](#licensing) +5. [Why are not all contrast ratios available?](#why-are-not-all-contrast-ratios-available) +6. [D3 Color](#d3-color) +7. [Contributing](#contributing) +8. [Licensing](#licensing) ## Leonardo web application @@ -67,5 +70,36 @@ The Leonardo web app leverages d3 for additional features such as generating 2d ## Contributing Contributions are welcomed! Read the [Contributing Guide](./.github/CONTRIBUTING.md) for more information. +## Development + +To get started [developing Leonardo UI](packages/ui#development): + +*Note: [Yarn](https://yarnpkg.com/) must be installed on your machine* + +```sh +# Install dependencies +yarn install + +# Change directory to Leonardo UI +cd packages/ui + +# Run local server +yarn dev +``` + +To get started [developing Leonardo `contrast-colors` package](packages/contrast-colors#development): + +```sh +# From root, change directory to contrast-colors +cd packages/contrast-colors + +# Run tests and watch for changes +yarn dev +``` + +Then, visit the live reloading web UIs here: +http://localhost:1234/index.html +http://localhost:1234/demo.html + ## Licensing This project is licensed under the Apache V2 License. See [LICENSE](LICENSE) for more information. diff --git a/cssnano.config.js b/cssnano.config.js new file mode 100644 index 00000000..37481d92 --- /dev/null +++ b/cssnano.config.js @@ -0,0 +1,14 @@ +module.exports = { + preset: [ + "default", + { + svgo: { + plugins: [ + { + convertStyleToAttrs: false + } + ] + } + } + ] +}; diff --git a/packages/contrast-colors/.npmignore b/packages/contrast-colors/.npmignore new file mode 100644 index 00000000..dbd17a1c --- /dev/null +++ b/packages/contrast-colors/.npmignore @@ -0,0 +1,2 @@ +node_modules/ +test/ diff --git a/packages/contrast-colors/CHANGELOG.md b/packages/contrast-colors/CHANGELOG.md new file mode 100644 index 00000000..32563260 --- /dev/null +++ b/packages/contrast-colors/CHANGELOG.md @@ -0,0 +1,77 @@ +# Change Log + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +# [1.0.0-alpha.8](https://github.com/adobe/leonardo/compare/@adobe/leonardo-contrast-colors@1.0.0-alpha.7...@adobe/leonardo-contrast-colors@1.0.0-alpha.8) (2020-09-08) + +**Note:** Version bump only for package @adobe/leonardo-contrast-colors + + + + + +# [1.0.0-alpha.7](https://github.com/adobe/leonardo/compare/@adobe/leonardo-contrast-colors@1.0.0-alpha.6...@adobe/leonardo-contrast-colors@1.0.0-alpha.7) (2020-08-18) + +**Note:** Version bump only for package @adobe/leonardo-contrast-colors + + + + + +# [1.0.0-alpha.6](https://github.com/adobe/leonardo/compare/@adobe/leonardo-contrast-colors@1.0.0-alpha.5...@adobe/leonardo-contrast-colors@1.0.0-alpha.6) (2020-08-12) + +**Note:** Version bump only for package @adobe/leonardo-contrast-colors + + + + + +# [1.0.0-alpha.5](https://github.com/adobe/leonardo/compare/@adobe/leonardo-contrast-colors@1.0.0-alpha.4...@adobe/leonardo-contrast-colors@1.0.0-alpha.5) (2020-05-04) + + +### Bug Fixes + +* ensured smooth is false by default & verified tests passing and UI not impacted by addition ([9722d5b](https://github.com/adobe/leonardo/commit/9722d5b422e60c62243cfae58f21bafbb286854c)) +* remove Babel, use ESM wrapper approach for Node 13.x ESM support + CJS support ([#72](https://github.com/adobe/leonardo/issues/72)) ([7541dc1](https://github.com/adobe/leonardo/commit/7541dc1189403039b900ef08ca82023d31063b58)) + + + + + +# [1.0.0-alpha.4](https://github.com/adobe/leonardo/compare/@adobe/leonardo-contrast-colors@1.0.0-alpha.3...@adobe/leonardo-contrast-colors@1.0.0-alpha.4) (2020-02-28) + +**Note:** Version bump only for package @adobe/leonardo-contrast-colors + + + + + +# [1.0.0-alpha.3](https://github.com/adobe/leonardo/compare/@adobe/leonardo-contrast-colors@1.0.0-alpha.2...@adobe/leonardo-contrast-colors@1.0.0-alpha.3) (2020-01-21) + + +### Bug Fixes + +* corrected midtone color outputs ([#46](https://github.com/adobe/leonardo/issues/46)) ([5c780b7](https://github.com/adobe/leonardo/commit/5c780b7a1f0355f985591076a27f1764e1faee3c)) + + + + + +# [1.0.0-alpha.2](https://github.com/adobe/leonardo/compare/@adobe/leonardo-contrast-colors@1.0.0-alpha.1...@adobe/leonardo-contrast-colors@1.0.0-alpha.2) (2019-12-12) + + +### Bug Fixes + +* **contrast-colors:** Add d3 as dependency ([#25](https://github.com/adobe/leonardo/issues/25)) ([34940e0](https://github.com/adobe/leonardo/commit/34940e00f52fa69b413b7c882a79c4d158b19a3b)) + + + + + +# 1.0.0-alpha.1 (2019-12-09) + + +### Features + +* added throw error for testing missing parameters ([#90](https://github.com/adobe/leonardo/issues/90)) ([2f5dff7](https://github.com/adobe/leonardo/commit/2f5dff7ced7756ef860ba9e5e661cf5fc1e20a2e)) diff --git a/packages/contrast-colors/README.md b/packages/contrast-colors/README.md index 2e92b50c..1c9f0cf6 100644 --- a/packages/contrast-colors/README.md +++ b/packages/contrast-colors/README.md @@ -1,48 +1,267 @@ # `@adobe/leonardo-contrast-colors` +[![npm version](https://badge.fury.io/js/%40adobe%2Fleonardo-contrast-colors.svg)](https://badge.fury.io/js/%40adobe%2Fleonardo-contrast-colors) +![Libraries.io dependency status for latest release, scoped npm package](https://img.shields.io/librariesio/release/npm/@adobe/leonardo-contrast-colors) [![license](https://img.shields.io/github/license/adobe/leonardo)](https://github.com/adobe/leonardo/blob/master/LICENSE) [![Pull requests welcome](https://img.shields.io/badge/PRs-welcome-blueviolet)](https://github.com/adobe/leonardo/blob/master/.github/CONTRIBUTING.md) + This package contains all the functions for generating colors by target contrast ratio. -## Using Leonardo +## Using Leonardo Contrast Colors + +### Install the package: -### Installing ``` npm i @adobe/leonardo-contrast-colors ``` -Pass your colors and desired ratios. See additional options below. +### Import the package: + +#### CJS (Node 12.x) + ```js -import { generateContrastColors } from '@adobe/leonardo-contrast-colors'; +const { generateAdaptiveTheme } = require('@adobe/leonardo-contrast-colors'); +``` + +#### ESM (Node 13.x) -// returns rgb value -let colors = generateContrastColors({colorKeys: ["#ff00ff"], base: "#ffffff", ratios: [4.5]}); +```js +import { generateAdaptiveTheme } from '@adobe/leonardo-contrast-colors'; +``` + +### Pass your colors and desired ratios (see additional options below): + +```js +// returns theme colors as JSON +let myTheme = generateAdaptiveTheme({ + colorScales: [ + { + name: 'gray', + colorKeys: ['#cacaca'], + ratios: { + 'GRAY_LOW_CONTRAST': 2, + 'GRAY_LARGE_TEXT': 3, + 'GRAY_TEXT': 4.5, + 'GRAY_HIGH_CONTRAST': 8 + } + }, + { + name: 'blue', + colorKeys: ['#5CDBFF', '#0000FF'], + ratios: { + 'BLUE_LARGE_TEXT': 3, + 'BLUE_TEXT': 4.5 + } + }, + { + name: 'red', + colorKeys: ['#FF9A81', '#FF0000'], + ratios: { + 'RED_LARGE_TEXT': 3, + 'RED_TEXT': 4.5 + } + } + ], + baseScale: 'gray', + brightness: 97 +}); ``` ## API Reference +### `generateAdaptiveTheme` + +Function used to create a fully adaptive contrast-based color palette/theme using Leonardo. Parameters are destructured and need to be explicitly called, such as `colorKeys: ["#f26322"]`. Parameters can be passed as a config JSON file for modularity and simplicity. + +```js +generateAdaptiveTheme({colorScales, baseScale}); // returns function +generateAdaptiveTheme({colorScales, baseScale, brightness}); // returns color objects +``` + +Returned function: +```js +myTheme(brightness, contrast); +``` + +#### `colorScales` *[array of objects]*: +Each object contains the necessary parameters for [generating colors by contrast](#generateContrastColors) with the exception of the `name` and `ratios` parameter. For `generateAdaptiveTheme`, [ratios can be an array or an object](#ratios-array-or-object). + +Example of `colorScales` object with all options: + +```js + { + name: 'blue', + colorKeys: ['#5CDBFF', '#0000FF'], + colorSpace: 'LCH', + ratios: { + 'blue--largeText': 3, + 'blue--normalText': 4.5 + } + } +``` + +#### `baseScale` *string (enum)*: +String value matching the `name` of a `colorScales` object to be used as a [base scale](#generateBaseScale) (background color). This creates a scale of values from 0-100 in lightness, which is used for `brightness` parameter. Ie. `brightness: 90` returns the 90% lightness value of the base scale. + +#### `name` *string*: +Unique name for each color scale. This value refers to the entire color group _(eg "blue")_ and will be used for the output color keys, ie `blue100: '#5CDBFF'` + +#### `ratios` *array* or *object*: +List of numbers to be used as target contrast ratios. If entered as an array, swatch names are incremented in `100`s such as `blue100`, `blue200` based on the color scale [name](#name-string). + +Alternatively, `ratios` can be an object with custom keys to name each color, such as `['Blue_Large_Text', 'Blue_Normal_Text']`. + +#### `brightness` *number*: +Optional value from 0-100 indicating the brightness of the base / background color. If undefined, `generateAdaptiveTheme` will return a function + +#### `contrast` *integer*: +Optional value to increase contrast of your generated colors. This value is multiplied against all ratios defined for each color scale. + +#### `output` *string (enum)*: +String value of the desired color space and output format for the generated colors. Output formats conform to the [W3C CSS Color Module Level 4](https://www.w3.org/TR/css-color-4/) spec for the supported options. + +| Output option | Sample value | +|---------------|--------------| +| `'HEX'` _(default)_ | `#RRGGBB` | +| `'RGB'` | `rgb(255, 255, 255)` | +| `'HSL'` | `hsl(360deg, 0%, 100%)` | +| `'HSV'` | `hsv(360deg, 0%, 100%)` | +| `'HSLuv'` | `hsluv(360, 0, 100)` | +| `'LAB'` | `lab(100%, 0, 0)` | +| `'LCH'` | `lch(100%, 0, 360deg)` | +| `'CAM02'` | `jab(100%, 0, 0)`| +| `'CAM02p'` | `jch(100%, 0, 360deg)` | + + +#### Function outputs and examples +The `generateAdaptiveTheme` function returns an array of color objects. Each key is named by concatenating the user-defined color name (above) with a numeric value. + +Colors with a **positive contrast ratio** with the base (ie, 2:1) will be named in increments of 100. For example, `gray100`, `gray200`. + +Colors with a **negative contrast ratio** with the base (ie -2:1) will be named in increments less than 100 and based on the number of negative values declared. For example, if there are 3 negative values `[-1.4, -1.3, -1.2, 1, 2, 3]`, the name for those values will be incremented by 100/4 (length plus one to avoid a `0` value), such as `gray25`, `gray50`, and `gray75`. + +Here is an example output from a theme: +```js +[ + { background: "#e0e0e0" }, + { + name: 'gray', + values: [ + {name: "gray100", contrast: 1, value: "#e0e0e0"}, + {name: "gray200", contrast: 2, value: "#a0a0a0"}, + {name: "gray300", contrast: 3, value: "#808080"}, + {name: "gray400", contrast: 4.5, value: "#646464"} + ] + }, + { + name: 'blue', + values: [ + {name: "blue100", contrast: 2, value: "#b18cff"}, + {name: "blue200", contrast: 3, value: "#8d63ff"}, + {name: "blue300", contrast: 4.5, value: "#623aff"}, + {name: "blue400", contrast: 8, value: "#1c0ad1"} + ] + } +] +``` + +#### Examples +###### Creating your theme as a function +```js +let myPalette = { + colorScales: [ + { + name: 'gray', + colorKeys: ['#cacaca'], + colorspace: 'HSL', + ratios: [1, 2, 3, 4.5, 8, 12] + }, + { + name: 'blue', + colorKeys: ['#5CDBFF', '#0000FF'], + colorspace: 'HSL', + ratios: [3, 4.5] + }, + { + name: 'red', + colorKeys: ['#FF9A81', '#FF0000'], + colorspace: 'HSL', + ratios: [3, 4.5] + } + ], + baseScale: 'gray' +} + +let myTheme = generateAdaptiveTheme(myPalette); + +myTheme(95, 1.2) // outputs colors with background lightness of 95 and ratios increased by 1.2 +``` + +###### Creating static instances of your theme +```js +// theme on light gray +let lightTheme = generateAdaptiveTheme(95); + +// theme on dark gray with increased contrast +let darkTheme = generateAdaptiveTheme(20, 1.3); +``` + +###### Assigning output to CSS properties +```js +let varPrefix = '--'; + +// Iterate each color object +for (let i = 0; i < myTheme.length; i++) { + // Iterate each value object within each color object + for(let j = 0; j < myTheme[i].values.length; j++) { + // output "name" of color and prefix + let key = myTheme[i].values[j].name; + let prop = varPrefix.concat(key); + // output value of color + let value = myTheme[i].values[j].value; + // create CSS property with name and value + document.documentElement.style + .setProperty(prop, value); + } +} +``` + ### generateContrastColors Primary function used to generate colors based on target contrast ratios. Parameters are destructured and need to be explicitly called, such as `colorKeys: ["#f26322"]`. -``` +```js generateContrastColors({colorKeys, base, ratios, colorspace}) ``` -**colorKeys** *[array]*: list of colors referenced to generate a lightness scale. Much like [key frames](https://en.wikipedia.org/wiki/Key_frame), key colors are single points by which additional colors will be interpolated between. +#### `colorKeys` *[array]*: +List of colors referenced to generate a lightness scale. Much like [key frames](https://en.wikipedia.org/wiki/Key_frame), key colors are single points by which additional colors will be interpolated between. -**base** *string*: references the color value that the color is to be generated from. +#### `base` *string*: +References the color value that the color is to be generated from. -**ratios** *[array]*: list of numbers to be used as target contrast ratios. +#### `ratios` *[array]*: +List of numbers to be used as target contrast ratios. -**colorspace** *string*: the colorspace in which the key colors will be interpolated within. Below are the available options: +#### `colorspace` *string*: +The colorspace in which the key colors will be interpolated within. Below are the available options: -- [Lch](https://en.wikipedia.org/wiki/HCL_color_space) -- [Lab](https://en.wikipedia.org/wiki/CIELAB_color_space) +- [LCH](https://en.wikipedia.org/wiki/HCL_color_space) +- [LAB](https://en.wikipedia.org/wiki/CIELAB_color_space) - [CAM02](https://en.wikipedia.org/wiki/CIECAM02) - [HSL](https://en.wikipedia.org/wiki/HSL_and_HSV) - [HSLuv](https://en.wikipedia.org/wiki/HSLuv) - [HSV](https://en.wikipedia.org/wiki/HSL_and_HSV) - [RGB](https://en.wikipedia.org/wiki/RGB_color_space) +### generateBaseScale + +This function is used to generate a color scale tailored specifically for use as a brightness scale when using Leonardo for brightness and contrast controls. Colors are generated that match HSLuv lightness values from `0` to `100` and are output as hex values. + +```js +generateBaseScale({colorKeys, colorspace}) +``` + +Only accepts **colorKeys** and **colorspace** parameters, as defined above for [`generateContrastColors`](#generateContrastColors) + ## Why are not all contrast ratios available? You may notice the tool takes an input (target ratio) but most often outputs a contrast ratio slightly higher. This has to do with the available colors in the RGB color space, and the math associated with calculating these ratios. diff --git a/packages/contrast-colors/curve.js b/packages/contrast-colors/curve.js new file mode 100644 index 00000000..71734889 --- /dev/null +++ b/packages/contrast-colors/curve.js @@ -0,0 +1,124 @@ +/* +Copyright 2019 Adobe. All rights reserved. +This file is licensed to you under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. You may obtain a copy +of the License at http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software distributed under +the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS +OF ANY KIND, either express or implied. See the License for the specific language +governing permissions and limitations under the License. +*/ + +const base3 = (t, p1, p2, p3, p4) => { + const t1 = -3 * p1 + 9 * p2 - 9 * p3 + 3 * p4, + t2 = t * t1 + 6 * p1 - 12 * p2 + 6 * p3; + return t * t2 - 3 * p1 + 3 * p2; +}; + +const belzen = exports.bezlen = (x1, y1, x2, y2, x3, y3, x4, y4, z) => { + if (z == null) { + z = 1; + } + z = z > 1 ? 1 : z < 0 ? 0 : z; + var z2 = z / 2, + n = 12, + Tvalues = [-.1252,.1252,-.3678,.3678,-.5873,.5873,-.7699,.7699,-.9041,.9041,-.9816,.9816], + Cvalues = [0.2491,0.2491,0.2335,0.2335,0.2032,0.2032,0.1601,0.1601,0.1069,0.1069,0.0472,0.0472], + sum = 0; + for (var i = 0; i < n; i++) { + var ct = z2 * Tvalues[i] + z2, + xbase = base3(ct, x1, x2, x3, x4), + ybase = base3(ct, y1, y2, y3, y4), + comb = xbase * xbase + ybase * ybase; + sum += Cvalues[i] * Math.sqrt(comb); + } + return z2 * sum; +}; + +const findDotsAtSegment = exports.findDotsAtSegment = (p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t) => { + const t1 = 1 - t, + t12 = t1 * t1, + t13 = t12 * t1, + t2 = t * t, + t3 = t2 * t, + x = t13 * p1x + t12 * 3 * t * c1x + t1 * 3 * t * t * c2x + t3 * p2x, + y = t13 * p1y + t12 * 3 * t * c1y + t1 * 3 * t * t * c2y + t3 * p2y; + return { + x: x, + y: y + }; +}; + +const catmullRom2bezier = exports.catmullRom2bezier = (crp, z) => { + const d = []; + let end = {x: +crp[0], y: +crp[1]}; + for (let i = 0, iLen = crp.length; iLen - 2 * !z > i; i += 2) { + const p = [ + {x: +crp[i - 2], y: +crp[i - 1]}, + {x: +crp[i], y: +crp[i + 1]}, + {x: +crp[i + 2], y: +crp[i + 3]}, + {x: +crp[i + 4], y: +crp[i + 5]} + ]; + if (z) { + if (!i) { + p[0] = {x: +crp[iLen - 2], y: +crp[iLen - 1]}; + } else if (iLen - 4 == i) { + p[3] = {x: +crp[0], y: +crp[1]}; + } else if (iLen - 2 == i) { + p[2] = {x: +crp[0], y: +crp[1]}; + p[3] = {x: +crp[2], y: +crp[3]}; + } + } else { + if (iLen - 4 == i) { + p[3] = p[2]; + } else if (!i) { + p[0] = {x: +crp[i], y: +crp[i + 1]}; + } + } + d.push([ + end.x, + end.y, + (-p[0].x + 6 * p[1].x + p[2].x) / 6, + (-p[0].y + 6 * p[1].y + p[2].y) / 6, + (p[1].x + 6 * p[2].x - p[3].x) / 6, + (p[1].y + 6 * p[2].y - p[3].y) / 6, + p[2].x, + p[2].y + ]); + end = p[2]; + } + + return d; +}; + +const prepareCurve = exports.prepareCurve = (p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y) => { + const len = Math.floor(bezlen(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y) * .75); + const map = new Map; + for (let i = 0; i <= len; i++) { + const t = i / len; + map.set(t, findDotsAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t)); + } + return x => { + const keys = Array.from(map.keys()); + let p = map.get(keys[0]); + const last = map.get(keys[keys.length - 1]); + if (x < p.x || x > last.x) { + return null; + } + for (let i = 0; i < keys.length; i++) { + const value = map.get(keys[i]); + if (value.x >= x) { + const x1 = p.x; + const x2 = value.x; + const y1 = p.y; + const y2 = value.y; + if (!i) { + return y2; + } + return (x - x1) * (y2 - y1) / (x2 - x1) + y1; + } + p = value; + } + }; +}; diff --git a/packages/contrast-colors/d3.js b/packages/contrast-colors/d3.js new file mode 100644 index 00000000..1e489203 --- /dev/null +++ b/packages/contrast-colors/d3.js @@ -0,0 +1,71 @@ +/* +Copyright 2019 Adobe. All rights reserved. +This file is licensed to you under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. You may obtain a copy +of the License at http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software distributed under +the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS +OF ANY KIND, either express or implied. See the License for the specific language +governing permissions and limitations under the License. +*/ + +const d3 = require('d3'); +const d3cam02 = require('d3-cam02'); +const d3hsluv = require('d3-hsluv'); +const d3hsv = require('d3-hsv'); + +const d3plus = { + ...d3, + ...d3cam02, + ...d3hsluv, + ...d3hsv +}; + +d3plus.interpolateJch = (start, end) => { + // constant, linear, and colorInterpolate are taken from d3-interpolate + // the colorInterpolate function is `nogamma` in the d3-interpolate's color.js + const constant = x => () => x; + const linear = (a, d) => t => a + t * d; + const colorInterpolate = (a, b) => { + const d = b - a; + return d ? linear(a, d) : constant(isNaN(a) ? b : a); + } + + start = d3.jch(start); + end = d3.jch(end); + + const zero = Math.abs(start.h - end.h); + const plus = Math.abs(start.h - (end.h + 360)); + const minus = Math.abs(start.h - (end.h - 360)); + if (plus < zero && plus < minus) { + end.h += 360; + } + if (minus < zero && minus < plus) { + end.h -= 360; + } + + const startc = d3.hcl(start + '').c; + const endc = d3.hcl(end + '').c; + if (!startc) { + start.h = end.h; + } + if (!endc) { + end.h = start.h; + } + + const J = colorInterpolate(start.J, end.J), + C = colorInterpolate(start.C, end.C), + h = colorInterpolate(start.h, end.h), + opacity = colorInterpolate(start.opacity, end.opacity); + + return t => { + start.J = J(t); + start.C = C(t); + start.h = h(t); + start.opacity = opacity(t); + return start + ''; + }; +}; + +module.exports = d3plus; diff --git a/packages/contrast-colors/index.d.ts b/packages/contrast-colors/index.d.ts new file mode 100644 index 00000000..50103c8e --- /dev/null +++ b/packages/contrast-colors/index.d.ts @@ -0,0 +1,124 @@ +/* +Copyright 2020 Adobe. All rights reserved. +This file is licensed to you under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. You may obtain a copy +of the License at http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software distributed under +the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS +OF ANY KIND, either express or implied. See the License for the specific language +governing permissions and limitations under the License. +*/ + +export as namespace ContrastColors; +export = ContrastColors; + +declare namespace ContrastColors { + type InterpolationColorspace = 'CAM02' | 'LCH' | 'LAB' | 'HSL' | 'HSLuv' | 'RGB' | 'HSV'; + + type Colorspace = 'CAM02' | 'CAM02p' | 'LCH' | 'LAB' | 'HSL' | 'HSLuv' | 'RGB' | 'HSV' | 'HEX'; + + type RGBArray = number[]; + + type AdaptiveThemeBackground = { background: string }; + + type AdaptiveThemeColorScheme = { + name: string, + values: { + name: string, + contrast: number, + value: string + }[]; + }; + + type AdaptiveTheme = (AdaptiveThemeBackground | AdaptiveThemeColorScheme)[]; + + interface ColorScale { + colorKeys: string[], + colorspace: Colorspace, + shift: number, + colors: string[], + scale: ((d: any) => string) | d3.ScaleLinear, + colorsHex: string[] + } + + interface NamedColorScale { + name: string, + colorKeys: string[], + colorspace: InterpolationColorspace, + ratios: number[] | { [key: string]: number }, + smooth?: boolean + } + + function createScale({ + swatches, + colorKeys, + colorspace, + shift, + fullScale, + smooth + }: { + swatches: number, + colorKeys: string[], + colorspace?: InterpolationColorspace, + shift?: number, + fullScale?: boolean, + smooth?: boolean + }): ColorScale | never; + + function luminance(r: number, g: number, b: number): number; + + function contrast(color: RGBArray, base: RGBArray, baseV: number): number; + + function binarySearch(list: number[], value: number, baseLum: number): number; + + function generateBaseScale({ + colorKeys, + colorspace, + smooth + }: { + colorKeys: string[], + colorspace?: Colorspace, + smooth?: boolean + }): string[]; + + function generateContrastColors({ + colorKeys, + base, + ratios, + colorspace, + smooth, + output + }: { + colorKeys: string[], + base: string, + ratios: number[], + colorspace?: InterpolationColorspace, + smooth?: boolean, + output?: Colorspace + }): string[] | never; + + function minPositive(r: number[]): number | never; + + function ratioName(r: number[]): number[] | never; + + function generateAdaptiveTheme({ + colorScales, + baseScale, + brightness, + contrast, + output + }: { + colorScales: NamedColorScale[], + baseScale: string, + brightness?: T, + contrast?: number, + output?: Colorspace, + }): T extends number ? AdaptiveTheme : (brightness: number, constrast?: number) => AdaptiveTheme | never; + + function fixColorValue( + color: string, + format: Colorspace, + object?: T + ): T extends false ? string : { [key: string]: number }; +} \ No newline at end of file diff --git a/packages/contrast-colors/index.js b/packages/contrast-colors/index.js index deda0b6b..9cbfed22 100644 --- a/packages/contrast-colors/index.js +++ b/packages/contrast-colors/index.js @@ -10,18 +10,186 @@ OF ANY KIND, either express or implied. See the License for the specific languag governing permissions and limitations under the License. */ -import * as d3 from 'd3'; -import * as d3cam02 from 'd3-cam02'; -import * as d3hsluv from 'd3-hsluv'; -import * as d3hsv from 'd3-hsv'; -Object.assign(d3, d3hsluv, d3hsv, d3cam02); +const d3 = require('./d3.js'); + +const { catmullRom2bezier, prepareCurve } = require('./curve.js'); +const { color } = require('./d3.js'); + +function smoothScale(ColorsArray, domains, space) { + const points = space.channels.map(() => []); + ColorsArray.forEach((color, i) => + points.forEach((point, j) => + point.push(domains[i], color[space.channels[j]]) + ) + ); + if (space.name == "hcl") { + const point = points[1]; + for (let i = 1; i < point.length; i += 2) { + if (isNaN(point[i])) { + point[i] = 0; + } + } + } + points.forEach(point => { + const nans = []; + // leading NaNs + for (let i = 1; i < point.length; i += 2) { + if (isNaN(point[i])) { + nans.push(i); + } else { + nans.forEach(j => point[j] = point[i]); + nans.length = 0; + break; + } + } + // all are grey case + if (nans.length) { + // hue is not important except for JCh + const safeJChHue = d3.jch("#ccc").h; + nans.forEach(j => point[j] = safeJChHue); + } + nans.length = 0; + // trailing NaNs + for (let i = point.length - 1; i > 0; i -= 2) { + if (isNaN(point[i])) { + nans.push(i); + } else { + nans.forEach(j => point[j] = point[i]); + break; + } + } + // other NaNs + for (let i = 1; i < point.length; i += 2) { + if (isNaN(point[i])) { + point.splice(i - 1, 2); + i -= 2; + } + } + // force hue to go on the shortest route + if (space.name in {hcl: 1, hsl: 1, hsluv: 1, hsv: 1, jch: 1}) { + let prev = point[1]; + let addon = 0; + for (let i = 3; i < point.length; i += 2) { + const p = point[i] + addon; + const zero = Math.abs(prev - p); + const plus = Math.abs(prev - (p + 360)); + const minus = Math.abs(prev - (p - 360)); + if (plus < zero && plus < minus) { + addon += 360; + } + if (minus < zero && minus < plus) { + addon -= 360; + } + point[i] += addon; + prev = point[i]; + } + } + }) + const prep = points.map(point => + catmullRom2bezier(point).map(curve => + prepareCurve(...curve) + ) + ); + return d => { + const ch = prep.map(p => { + for (let i = 0; i < p.length; i++) { + const res = p[i](d); + if (res != null) { + return res; + } + } + }); + + if (space.name == 'jch' && ch[1] < 0) { + ch[1] = 0; + } + + return d3[space.name](...ch) + ""; + }; +} + +const colorSpaces = { + CAM02: { + name: 'jab', + channels: ['J', 'a', 'b'], + interpolator: d3.interpolateJab, + function: d3.jab + }, + CAM02p: { + name: 'jch', + channels: ['J', 'C', 'h'], + interpolator: d3.interpolateJch, + function: d3.jch + }, + LCH: { + name: 'lch', // named per correct color definition order + channels: ['h', 'c', 'l'], + interpolator: d3.interpolateHcl, + white: d3.hcl(NaN, 0, 100), + black: d3.hcl(NaN, 0, 0), + function: d3.hcl + }, + LAB: { + name: 'lab', + channels: ['l', 'a', 'b'], + interpolator: d3.interpolateLab, + function: d3.lab + }, + HSL: { + name: 'hsl', + channels: ['h', 's', 'l'], + interpolator: d3.interpolateHsl, + function: d3.hsl + }, + HSLuv: { + name: 'hsluv', + channels: ['l', 'u', 'v'], + interpolator: d3.interpolateHsluv, + white: d3.hsluv(NaN, NaN, 100), + black: d3.hsluv(NaN, NaN, 0), + function: d3.hsluv + }, + RGB: { + name: 'rgb', + channels: ['r', 'g', 'b'], + interpolator: d3.interpolateRgb, + function: d3.rgb + }, + HSV: { + name: 'hsv', + channels: ['h', 's', 'v'], + interpolator: d3.interpolateHsv, + function: d3.hsv + }, + HEX: { + name: 'hex', + channels: ['r', 'g', 'b'], + interpolator: d3.interpolateRgb, + function: d3.rgb + } +}; function cArray(c) { - let L = d3.hsluv(c).l; - let U = d3.hsluv(c).u; - let V = d3.hsluv(c).v; + const color = d3.hsluv(c); + const L = color.l; + const U = color.u; + const V = color.v; - return new Array(L, U, V); + return [L, U, V]; +} + +function removeDuplicates(originalArray, prop) { + var newArray = []; + var lookupObject = {}; + + for(var i in originalArray) { + lookupObject[originalArray[i][prop]] = originalArray[i]; + } + + for(i in lookupObject) { + newArray.push(lookupObject[i]); + } + return newArray; } function createScale({ @@ -29,8 +197,14 @@ function createScale({ colorKeys, colorspace = 'LAB', shift = 1, - fullScale = true + fullScale = true, + smooth = false } = {}) { + const space = colorSpaces[colorspace]; + if (!space) { + throw new Error(`Colorspace “${colorspace}” not supported`); + } + let domains = colorKeys .map(key => swatches - swatches * (d3.hsluv(key).v / 100)) .sort((a, b) => a - b) @@ -73,94 +247,35 @@ function createScale({ let ColorsArray = []; let scale; - if (colorspace == 'CAM02') { - if (fullScale == true) { - ColorsArray = ColorsArray.concat('#ffffff', sortedColor, '#000000'); - } else { - ColorsArray = ColorsArray.concat(sortedColor); - } - ColorsArray = ColorsArray.map(d => d3.jab(d)); - - scale = d3.scaleLinear() - .range(ColorsArray) - .domain(domains) - .interpolate(d3.interpolateJab); - } - else if (colorspace == 'LCH') { - ColorsArray = ColorsArray.map(d => d3.hcl(d)); - if (fullScale == true) { - ColorsArray = ColorsArray.concat(d3.hcl(NaN, 0, 100), sortedColor, d3.hcl(NaN, 0, 0)); - } else { - ColorsArray = ColorsArray.concat(sortedColor); - } - scale = d3.scaleLinear() - .range(ColorsArray) - .domain(domains) - .interpolate(d3.interpolateHcl); + if (fullScale) { + ColorsArray = [space.white || '#fff', ...sortedColor, space.black || '#000']; + } else { + ColorsArray = sortedColor; } - else if (colorspace == 'LAB') { - if (fullScale == true) { - ColorsArray = ColorsArray.concat('#ffffff', sortedColor, '#000000'); - } else { - ColorsArray = ColorsArray.concat(sortedColor); - } - ColorsArray = ColorsArray.map(d => d3.lab(d)); - - scale = d3.scaleLinear() - .range(ColorsArray) - .domain(domains) - .interpolate(d3.interpolateLab); + const stringColors = ColorsArray; + ColorsArray = ColorsArray.map(d => d3[space.name](d)); + if (space.name == 'hcl') { + // special case for HCL if C is NaN we should treat it as 0 + ColorsArray.forEach(c => c.c = isNaN(c.c) ? 0 : c.c); } - else if (colorspace == 'HSL') { - if (fullScale == true) { - ColorsArray = ColorsArray.concat('#ffffff', sortedColor, '#000000'); - } else { - ColorsArray = ColorsArray.concat(sortedColor); - } - ColorsArray = ColorsArray.map(d => d3.hsl(d)); - scale = d3.scaleLinear() - .range(ColorsArray) - .domain(domains) - .interpolate(d3.interpolateHsl); - } - else if (colorspace == 'HSLuv') { - ColorsArray = ColorsArray.map(d => d3.hsluv(d)); - if (fullScale == true) { - ColorsArray = ColorsArray.concat(d3.hsluv(NaN, NaN, 100), sortedColor, d3.hsluv(NaN, NaN, 0)); - } else { - ColorsArray = ColorsArray.concat(sortedColor); + if (space.name == 'jch') { + // JCh has some “random” hue for grey colors. + // Replacing it to NaN, so we can apply the same method of dealing with them. + for (let i = 0; i < stringColors.length; i++) { + const color = d3.hcl(stringColors[i]); + if (!color.c) { + ColorsArray[i].h = NaN; + } } - scale = d3.scaleLinear() - .range(ColorsArray) - .domain(domains) - .interpolate(d3.interpolateHsluv); } - else if (colorspace == 'RGB') { - if (fullScale == true) { - ColorsArray = ColorsArray.concat('#ffffff', sortedColor, '#000000'); - } else { - ColorsArray = ColorsArray.concat(sortedColor); - } - ColorsArray = ColorsArray.map(d => d3.rgb(d)); - scale = d3.scaleLinear() - .range(ColorsArray) - .domain(domains) - .interpolate(d3.interpolateRgb); - } - else if (colorspace == 'HSV') { - if (fullScale == true) { - ColorsArray = ColorsArray.concat('#ffffff', sortedColor, '#000000'); - } else { - ColorsArray = ColorsArray.concat(sortedColor); - } - ColorsArray = ColorsArray.map(d => d3.hsv(d)); + + if (smooth) { + scale = smoothScale(ColorsArray, domains, space); + } else { scale = d3.scaleLinear() .range(ColorsArray) .domain(domains) - .interpolate(d3.interpolateHsv); - } - else { - throw new Error(`Colorspace ${colorspace} not supported`); + .interpolate(space.interpolator); } let Colors = d3.range(swatches).map(d => scale(d)); @@ -169,7 +284,7 @@ function createScale({ // Return colors as hex values for interpolators. let colorsHex = []; - for (let i=0; i { return { value: Math.round(cArray(c)[2]), index: i } }); + + let filteredArr = removeDuplicates(colorObj, "value") + .map(data => newColors[data.index]); + + return filteredArr; +} + function generateContrastColors({ colorKeys, base, ratios, - colorspace = 'LAB' + colorspace = 'LAB', + smooth = false, + output = 'HEX' } = {}) { if (!base) { throw new Error(`Base is undefined`); @@ -195,38 +332,151 @@ function generateContrastColors({ if (!colorKeys) { throw new Error(`Color Keys are undefined`); } + for (let i=0; i { let rgbArray = [d3.rgb(scaleData.scale(d)).r, d3.rgb(scaleData.scale(d)).g, d3.rgb(scaleData.scale(d)).b]; let baseRgbArray = [d3.rgb(base).r, d3.rgb(base).g, d3.rgb(base).b]; - let ca = contrast(rgbArray, baseRgbArray).toFixed(2); + let ca = contrast(rgbArray, baseRgbArray, baseV).toFixed(2); return Number(ca); }); let contrasts = Contrasts.filter(el => el != null); - let baseLum = luminance(d3.rgb(base).r, d3.rgb(base).g, d3.rgb(base).b); - let newColors = []; ratios = ratios.map(Number); // Return color matching target ratio, or closest number for (let i=0; i < ratios.length; i++){ - let r = binarySearch(contrasts, ratios[i], baseLum); - newColors.push(d3.rgb(scaleData.colors[r]).hex()); + let r = binarySearch(contrasts, ratios[i], baseV); + + // use fixColorValue function to convert each color to the specified + // output format. + newColors.push(fixColorValue(scaleData.colors[r], output)); + } return newColors; } +// Helper function to change any NaN to a zero +function filterNaN(x) { + if(isNaN(x)) { + return 0; + } else { + return x; + } +} + +// Helper function for rounding color values to whole numbers +function fixColorValue(color, format, object = false) { + let colorObj = colorSpaces[format].function(color); + let propArray = colorSpaces[format].channels; + + let newColorObj = { + [propArray[0]]: filterNaN(colorObj[propArray[0]]), + [propArray[1]]: filterNaN(colorObj[propArray[1]]), + [propArray[2]]: filterNaN(colorObj[propArray[2]]) + } + + // HSLuv + if (format === "HSLuv") { + for (let i = 0; i < propArray.length; i++) { + + let roundedPct = Math.round(newColorObj[propArray[i]]); + newColorObj[propArray[i]] = roundedPct; + } + } + // LAB, LCH, JAB, JCH + else if (format === "LAB" || format === "LCH" || format === "CAM02" || format === "CAM02p") { + for (let i = 0; i < propArray.length; i++) { + let roundedPct = Math.round(newColorObj[propArray[i]]); + + if (propArray[i] === "h" && !object) { + roundedPct = roundedPct + "deg"; + } + if (propArray[i] === "l" && !object || propArray[i] === "J" && !object) { + roundedPct = roundedPct + "%"; + } + + newColorObj[propArray[i]] = roundedPct; + + } + } + else { + for (let i = 0; i < propArray.length; i++) { + if (propArray[i] === "s" || propArray[i] === "l" || propArray[i] === "v") { + // leave as decimal format + let roundedPct = parseFloat(newColorObj[propArray[i]].toFixed(2)); + if(object) { + newColorObj[propArray[i]] = roundedPct; + } + else { + newColorObj[propArray[i]] = Math.round(roundedPct * 100) + "%"; + } + } + else { + let roundedPct = parseFloat(newColorObj[propArray[i]].toFixed()); + if (propArray[i] === "h" && !object) { + roundedPct = roundedPct + "deg"; + } + newColorObj[propArray[i]] = roundedPct; + } + } + } + + let stringName = colorSpaces[format].name; + let stringValue; + + if (format === "HEX") { + stringValue = d3.rgb(color).formatHex(); + } else { + let str0, srt1, str2; + if (format === "LCH") { + // Have to force opposite direction of array index for LCH + // because d3 defines the channel order as "h, c, l" but we + // want the output to be in the correct format + str0 = newColorObj[propArray[2]] + ", "; + str1 = newColorObj[propArray[1]] + ", "; + str2 = newColorObj[propArray[0]]; + } + else { + str0 = newColorObj[propArray[0]] + ", "; + str1 = newColorObj[propArray[1]] + ", "; + str2 = newColorObj[propArray[2]]; + } + + stringValue = stringName + "(" + str0 + str1 + str2 + ")"; + } + + if (object) { + // return colorObj; + return newColorObj; + } else { + return stringValue; + } +} + function luminance(r, g, b) { let a = [r, g, b].map((v) => { v /= 255; @@ -237,19 +487,14 @@ function luminance(r, g, b) { return (a[0] * 0.2126) + (a[1] * 0.7152) + (a[2] * 0.0722); } -// function percievedLum(r, g, b) { -// return (0.299*r + 0.587*g + 0.114*b); -// } - -// Separate files in a lib folder as well. -function contrast(color, base) { +function contrast(color, base, baseV) { let colorLum = luminance(color[0], color[1], color[2]); let baseLum = luminance(base[0], base[1], base[2]); let cr1 = (colorLum + 0.05) / (baseLum + 0.05); let cr2 = (baseLum + 0.05) / (colorLum + 0.05); - if (baseLum < 0.5) { + if (baseV < 0.5) { if (cr1 >= 1) { return cr1; } @@ -267,6 +512,177 @@ function contrast(color, base) { } } +function minPositive(r) { + if (!r) { throw new Error('Array undefined');} + if (!Array.isArray(r)) { throw new Error('Passed object is not an array');} + let arr = []; + + for(let i=0; i < r.length; i++) { + if(r[i] >= 1) { + arr.push(r[i]); + } + } + return Math.min(...arr); +} + +function ratioName(r) { + if (!r) { throw new Error('Ratios undefined');} + r = r.sort(function(a, b){return a - b}); // sort ratio array in case unordered + + let min = minPositive(r); + let minIndex = r.indexOf(min); + let nArr = []; // names array + + let rNeg = r.slice(0, minIndex); + let rPos = r.slice(minIndex, r.length); + + // Name the negative values + for (let i=0; i < rNeg.length; i++) { + let d = 1/(rNeg.length + 1); + let m = d * 100; + let nVal = m * (i + 1); + nArr.push(Number(nVal.toFixed())); + } + // Name the positive values + for (let i=0; i < rPos.length; i++) { + nArr.push((i+1)*100); + } + nArr.sort(function(a, b){return a - b}); // just for safe measure + + return nArr; +} + +function generateAdaptiveTheme({ + colorScales, + baseScale, + brightness, + contrast = 1, + output = 'HEX' +}) { + if (!baseScale) { + throw new Error('baseScale is undefined'); + } + let found = false; + for(let i = 0; i < colorScales.length; i++) { + if (colorScales[i].name !== baseScale) { + found = true; + } + } + if (found = false) { + throw new Error('baseScale must match the name of a colorScales object'); + } + + if (!colorScales) { + throw new Error('colorScales are undefined'); + } + if (!Array.isArray(colorScales)) { + throw new Error('colorScales must be an array of objects'); + } + for (let i=0; i < colorScales.length; i ++) { + // if (colorScales[i].swatchNames) { // if the scale has custom swatch names + // let ratioLength = colorScales[i].ratios.length; + // let swatchNamesLength = colorScales[i].swatchNames.length; + + // if (ratioLength !== swatchNamesLength) { + // throw new Error('`${colorScales[i].name}`ratios and swatchNames must be equal length') + // } + // } + } + + if (brightness === undefined) { + return function(brightness, contrast) { + return generateAdaptiveTheme({baseScale: baseScale, colorScales: colorScales, brightness: brightness, contrast: contrast, output: output}); + } + } + else { + // Find color object matching base scale + let baseIndex = colorScales.findIndex( x => x.name === baseScale ); + let baseKeys = colorScales[baseIndex].colorKeys; + let baseMode = colorScales[baseIndex].colorspace; + let smooth = colorScales[baseIndex].smooth; + + // define params to pass as bscale + let bscale = generateBaseScale({colorKeys: baseKeys, colorspace: baseMode, smooth: smooth}); // base parameter to create base scale (0-100) + let bval = bscale[brightness]; + let baseObj = { + background: bval + }; + + let arr = []; + arr.push(baseObj); + + for (let i = 0; i < colorScales.length; i++) { + if (!colorScales[i].name) { + throw new Error('Color missing name'); + } + let name = colorScales[i].name; + + let ratioInput = colorScales[i].ratios; + let ratios; + let swatchNames; + // assign ratios array whether input is array or object + if(Array.isArray(ratioInput)) { + ratios = ratioInput; + } else { + ratios = Object.values(ratioInput); + swatchNames = Object.keys(ratioInput); + } + + let smooth = colorScales[i].smooth; + let newArr = []; + let colorObj = { + name: name, + values: newArr + }; + + ratios = ratios.map(function(d) { + let r; + if(d > 1) { + r = ((d-1) * contrast) + 1; + } + else if(d < -1) { + r = ((d+1) * contrast) - 1; + } + else { + r = 1; + } + return Number(r.toFixed(2)); + }); + + let outputColors = generateContrastColors({ + colorKeys: colorScales[i].colorKeys, + colorspace: colorScales[i].colorspace, + ratios: ratios, + base: bval, + smooth: smooth, + output: output + }); + + for (let i=0; i < outputColors.length; i++) { + let n; + if(!swatchNames) { + let rVal = ratioName(ratios)[i]; + n = name.concat(rVal); + } + else { + n = swatchNames[i]; + } + + let obj = { + name: n, + contrast: ratios[i], + value: outputColors[i] + }; + newArr.push(obj) + } + arr.push(colorObj); + + } + + return arr; + } +} + // Binary search to find index of contrast ratio that is input // Modified from https://medium.com/hackernoon/programming-with-js-binary-search-aaf86cef9cb3 function binarySearch(list, value, baseLum) { @@ -308,8 +724,15 @@ function binarySearch(list, value, baseLum) { return (list[middle] == !value) ? closest : middle // how it was originally expressed } -exports.createScale = createScale; -exports.luminance = luminance; -exports.contrast = contrast; -exports.binarySearch = binarySearch; -exports.generateContrastColors = generateContrastColors; +module.exports = { + createScale, + luminance, + contrast, + binarySearch, + generateBaseScale, + generateContrastColors, + minPositive, + ratioName, + generateAdaptiveTheme, + fixColorValue +}; diff --git a/packages/contrast-colors/package.json b/packages/contrast-colors/package.json index c2e86bfb..d84a2374 100644 --- a/packages/contrast-colors/package.json +++ b/packages/contrast-colors/package.json @@ -1,21 +1,24 @@ { "name": "@adobe/leonardo-contrast-colors", - "version": "0.1.0", + "version": "1.0.0-alpha.8", "description": "Generate colors based on a desired contrast ratio", "repository": "git@github.com:adobe/leonardo.git", - "main": "index.js", + "main": "./index.js", + "module": "./wrapper.mjs", + "exports": { + ".": { + "require": "./index.js", + "default": "./wrapper.mjs" + } + }, "scripts": { - "dev": "ava test/*.js --verbose --watch", - "test": "ava test/*.js --verbose", - "coverage": "nyc report -r lcovonly && codecov" + "dev": "jest --watch", + "test": "jest" }, "author": "Nate Baldwin ", "license": "Apache-2.0", "dependencies": { - "ava": "^2.4.0", - "d3": "^5.12.0", - "esm": "^3.2.25", - "nyc": "^14.1.1", + "d3": "^5.14.2", "d3-3d": "0.0.9", "d3-cam02": "^0.1.5", "d3-hsluv": "^0.1.2", @@ -23,10 +26,5 @@ }, "publishConfig": { "access": "public" - }, - "ava": { - "require": [ - "esm" - ] } } diff --git a/packages/contrast-colors/test/binarySearch.js b/packages/contrast-colors/test/binarySearch.test.js similarity index 77% rename from packages/contrast-colors/test/binarySearch.js rename to packages/contrast-colors/test/binarySearch.test.js index 1eb00340..cdcc91e9 100644 --- a/packages/contrast-colors/test/binarySearch.js +++ b/packages/contrast-colors/test/binarySearch.test.js @@ -9,80 +9,61 @@ OF ANY KIND, either express or implied. See the License for the specific languag governing permissions and limitations under the License. */ -import test from 'ava'; -import { binarySearch } from '../index.js'; +const { binarySearch } = require('../index.js'); -test('should return index of exact match (ascending)', function(t) { +test('should return index of exact match (ascending)', function() { let list = [1, 2, 3, 3.07, 3.1, 3.12, 3.13, 3.14, 3.3, 5, 12]; let value = 3.12; let baseLum = 0.7; let searchIndex = binarySearch(list, value, baseLum); // returns index - t.is( - searchIndex, - 5 // list[5] // 3.12 indexed at 5 - ); + expect(searchIndex).toBe(5); // list[5] // 3.12 indexed at 5 }); -test('should return index of exact match (descending)', function(t) { +test('should return index of exact match (descending)', function() { let list = [12, 5, 3.3, 3.14, 3.13, 3.12, 3.1, 3.07, 3, 2, 1]; let value = 3.12; let baseLum = 0.3; let searchIndex = binarySearch(list, value, baseLum); // returns index - t.is( - searchIndex, - 5 // list[5] // 3.12 indexed at 5 - ); + expect(searchIndex).toBe(5); // list[5] // 3.12 indexed at 5 }); -test('should return index of closest match (ascending)', function(t) { +test('should return index of closest match (ascending)', function() { let list = [1, 2, 3, 3.07, 3.1, 3.12, 3.13, 3.14, 3.3, 5, 12]; let value = 3.09; let baseLum = 0.7; let searchIndex = binarySearch(list, value, baseLum); // returns index - t.is( - searchIndex, - 4 // list[4] // 3.1 indexed at 4 - ); + expect(searchIndex).toBe(4); // list[4] // 3.1 indexed at 4 }); -test('should return exact match (ascending)', function(t) { +test('should return exact match (ascending)', function() { let list = [1, 2, 3, 3.07, 3.1, 3.12, 3.13, 3.14, 3.3, 5, 12]; let value = 3.12; let baseLum = 0.7; let searchIndex = binarySearch(list, value, baseLum); // returns index let searchResult = list[searchIndex]; - t.is( - searchResult, - 3.12 - ); + expect(searchResult).toBe(3.12); }); -test('should return exact match (descending)', function(t) { +test('should return exact match (descending)', function() { let list = [12, 5, 3.3, 3.14, 3.13, 3.12, 3.1, 3.07, 3, 2, 1]; let value = 3.12; let baseLum = 0.3; let searchIndex = binarySearch(list, value, baseLum); // returns index let searchResult = list[searchIndex]; - t.is( - searchResult, - 3.12 - ); + expect(searchResult).toBe(3.12); }); -test('should return closest match (ascending)', function(t) { +test('should return closest match (ascending)', function() { let list = [1, 2, 3, 3.07, 3.1, 3.12, 3.13, 3.14, 3.3, 5, 12]; let value = 3.09; let baseLum = 0.7; let searchIndex = binarySearch(list, value, baseLum); // returns index let searchResult = list[searchIndex]; - t.is( - searchResult, - 3.1 - ); + expect(searchResult).toBe(3.1); }); diff --git a/packages/contrast-colors/test/contrast.js b/packages/contrast-colors/test/contrast.test.js similarity index 74% rename from packages/contrast-colors/test/contrast.js rename to packages/contrast-colors/test/contrast.test.js index 899324b5..4dcce20f 100644 --- a/packages/contrast-colors/test/contrast.js +++ b/packages/contrast-colors/test/contrast.test.js @@ -1,3 +1,4 @@ + /* Copyright 2019 Adobe. All rights reserved. This file is licensed to you under the Apache License, Version 2.0 (the "License"); @@ -9,23 +10,16 @@ OF ANY KIND, either express or implied. See the License for the specific languag governing permissions and limitations under the License. */ -import test from 'ava'; -import { contrast } from '../index.js'; +const { contrast } = require('../index.js'); -test('should provide negative contrast (-1.55...)', function(t) { +test('should provide negative contrast (-1.55...)', function() { let contrastValue = contrast([255, 255, 255], [207, 207, 207]); // white is UI color, gray is base. Should return negative whole number - t.is( - contrastValue, - -1.5579550563651177 - ); + expect(contrastValue).toBe(-1.5579550563651177); }); -test('should provide positive contrast (1.55...)', function(t) { +test('should provide positive contrast (1.55...)', function() { let contrastValue = contrast([207, 207, 207], [255, 255, 255]); // gray is UI color, white is base. Should return positive whole number - t.is( - contrastValue, - 1.5579550563651177 - ); + expect(contrastValue).toBe(1.5579550563651177); }); diff --git a/packages/contrast-colors/test/createScale.js b/packages/contrast-colors/test/createScale.test.js similarity index 84% rename from packages/contrast-colors/test/createScale.js rename to packages/contrast-colors/test/createScale.test.js index fd1c4496..682d0b9b 100644 --- a/packages/contrast-colors/test/createScale.js +++ b/packages/contrast-colors/test/createScale.test.js @@ -9,14 +9,12 @@ OF ANY KIND, either express or implied. See the License for the specific languag governing permissions and limitations under the License. */ -import test from 'ava'; -import { createScale } from '../index.js'; +const { createScale } = require('../index.js'); -test('should generate 8 colors in Lab', function(t) { +test('should generate 8 colors in Lab', function() { let scale = createScale({swatches: 8, colorKeys: ['#CCFFA9', '#FEFEC5', '#5F0198'], colorspace: 'LAB', shift: 1, fullScale: true}); - t.deepEqual( - scale.colors, + expect(scale.colors).toEqual( [ 'rgb(255, 255, 255)', 'rgb(196, 229, 169)', @@ -29,8 +27,7 @@ test('should generate 8 colors in Lab', function(t) { ] ); - t.deepEqual( - scale.colorKeys, + expect(scale.colorKeys).toEqual( [ '#CCFFA9', '#FEFEC5', '#5F0198' ] ); }); diff --git a/packages/contrast-colors/test/fixColorValue.test.js b/packages/contrast-colors/test/fixColorValue.test.js new file mode 100644 index 00000000..75dad0c6 --- /dev/null +++ b/packages/contrast-colors/test/fixColorValue.test.js @@ -0,0 +1,142 @@ +/* +Copyright 2020 Adobe. All rights reserved. +This file is licensed to you under the Apache License, Version 2.0 (the 'License'); +you may not use this file except in compliance with the License. You may obtain a copy +of the License at http://www.apache.org/licenses/LICENSE2.0 +Unless required by applicable law or agreed to in writing, software distributed under +the License is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS +OF ANY KIND, either express or implied. See the License for the specific language +governing permissions and limitations under the License. +*/ + +const { fixColorValue } = require('../index.js'); + +test('should return color object for HSL color', function() { + let result = fixColorValue('#2c66f1', 'HSL', true);; + + expect(result).toEqual({ "h": 222, "s": 0.88, "l": 0.56}); + +}); + +test('should return string format for HSL color', function() { + let result = fixColorValue('#2c66f1', 'HSL');; + + expect(result).toEqual('hsl(222deg, 88%, 56%)'); + +}); + + +test('should return color object for RGB color', function() { + let result = fixColorValue('#2c66f1', 'RGB', true);; + + expect(result).toEqual({ "r": 44, "g": 102, "b": 241}); + +}); + +test('should return string format for RGB color', function() { + let result = fixColorValue('#2c66f1', 'RGB');; + + expect(result).toEqual('rgb(44, 102, 241)'); + +}); + + +test('should return color object for HEX color', function() { + let result = fixColorValue('#2c66f1', 'HEX', true);; + + expect(result).toEqual({ "r": 44, "g": 102, "b": 241}); + +}); + +test('should return string format for HEX color', function() { + let result = fixColorValue('#2c66f1', 'HEX');; + + expect(result).toEqual('#2c66f1'); + +}); + +test('should return color object for HSV color', function() { + let result = fixColorValue('#2c66f1', 'HSV', true);; + + expect(result).toEqual({ "h": 222, "s": 0.82, "v": 0.95}); + +}); + +test('should return string format for HSV color', function() { + let result = fixColorValue('#2c66f1', 'HSV');; + + expect(result).toEqual('hsv(222deg, 82%, 95%)'); + +}); + + +test('should return color object for HSLuv color', function() { + let result = fixColorValue('#2c66f1', 'HSLuv', true);; + + expect(result).toEqual({ "l": 260, "u": 91, "v": 47}); + +}); + +test('should return string format for HSLuv color', function() { + let result = fixColorValue('#2c66f1', 'HSLuv');; + + expect(result).toEqual('hsluv(260, 91, 47)'); + +}); + +test('should return color object for LAB color', function() { + let result = fixColorValue('#2c66f1', 'LAB', true);; + + expect(result).toEqual({ "l": 46, "a": 22, "b": -77}); + +}); + +test('should return string format for LAB color', function() { + let result = fixColorValue('#2c66f1', 'LAB');; + + expect(result).toEqual('lab(46%, 22, -77)'); + +}); + +test('should return color object for LCH color', function() { + let result = fixColorValue('#2c66f1', 'LCH', true);; + + expect(result).toEqual({ "l": 46, "c": 80, "h": 286}); + +}); + +test('should return string format for LCH color', function() { + let result = fixColorValue('#2c66f1', 'LCH');; + + expect(result).toEqual('lch(46%, 80, 286deg)'); + +}); + + +test('should return color object for CAM02 color', function() { + let result = fixColorValue('#2c66f1', 'CAM02', true);; + + expect(result).toEqual({ "J": 48, "a": -7, "b": -35}); + +}); + +test('should return string format for CAM02 color', function() { + let result = fixColorValue('#2c66f1', 'CAM02');; + + expect(result).toEqual('jab(48%, -7, -35)'); + +}); + +test('should return color object for CAM02 (polar) color', function() { + let result = fixColorValue('#2c66f1', 'CAM02p', true);; + + expect(result).toEqual({ "J": 35, "C": 75, "h": 258}); + +}); + +test('should return string format for CAM02 (polar) color', function() { + let result = fixColorValue('#2c66f1', 'CAM02p');; + + expect(result).toEqual('jch(35%, 75, 258deg)'); + +}); \ No newline at end of file diff --git a/packages/contrast-colors/test/generateAdaptiveTheme.test.js b/packages/contrast-colors/test/generateAdaptiveTheme.test.js new file mode 100644 index 00000000..19958886 --- /dev/null +++ b/packages/contrast-colors/test/generateAdaptiveTheme.test.js @@ -0,0 +1,608 @@ +/* +Copyright 2019 Adobe. All rights reserved. +This file is licensed to you under the Apache License, Version 2.0 (the 'License'); +you may not use this file except in compliance with the License. You may obtain a copy +of the License at http://www.apache.org/licenses/LICENSE2.0 +Unless required by applicable law or agreed to in writing, software distributed under +the License is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS +OF ANY KIND, either express or implied. See the License for the specific language +governing permissions and limitations under the License. +*/ + +const { generateAdaptiveTheme } = require('../index.js'); + +test('should generate theme for three colors', function() { + let theme = generateAdaptiveTheme({ + baseScale: "gray", + colorScales: [ + { + name: "gray", + colorKeys: ['#cacaca', '#323232'], + colorspace: 'HSL', + ratios: [1, 1.2, 1.4, 2, 3, 4.5, 6, 8, 12, 21] + }, + { + name: "blue", + colorKeys: ['#0000ff'], + colorspace: 'LAB', + ratios: [2, 3, 4.5, 8, 12] + }, + { + name: "red", + colorKeys: ['#ff0000'], + colorspace: 'RGB', + ratios: [2, 3, 4.5, 8, 12] + } + ]}); + let themeLight = theme(90);; + + expect(themeLight).toEqual([ + { background: "#e1e1e1" }, + { + name: 'gray', + values: [ + {name: "gray100", contrast: 1, value: "#e0e0e0"}, + {name: "gray200", contrast: 1.2, value: "#cecece"}, + {name: "gray300", contrast: 1.4, value: "#bfbfbf"}, + {name: "gray400", contrast: 2, value: "#a0a0a0"}, + {name: "gray500", contrast: 3, value: "#808080"}, + {name: "gray600", contrast: 4.5, value: "#646464"}, + {name: "gray700", contrast: 6, value: "#525252"}, + {name: "gray800", contrast: 8, value: "#404040"}, + {name: "gray900", contrast: 12, value: "#232323"}, + {name: "gray1000", contrast: 21, value: "#000000"} + ] + }, + { + name: 'blue', + values: [ + {name: "blue100", contrast: 2, value: "#b18cff"}, + {name: "blue200", contrast: 3, value: "#8d63ff"}, + {name: "blue300", contrast: 4.5, value: "#623aff"}, + {name: "blue400", contrast: 8, value: "#1c0ad1"}, + {name: "blue500", contrast: 12, value: "#211068"} + ] + }, + { + name: 'red', + values: [ + {name: "red100", contrast: 2, value: "#ff7474"}, + {name: "red200", contrast: 3, value: "#ff1313"}, + {name: "red300", contrast: 4.5, value: "#cc0000"}, + {name: "red400", contrast: 8, value: "#860000"}, + {name: "red500", contrast: 12, value: "#500000"} + ] + } + ]); + +}); + +test('should generate theme for three colors in LCH format', function() { + let theme = generateAdaptiveTheme({ + baseScale: "gray", + colorScales: [ + { + name: "gray", + colorKeys: ['#cacaca', '#323232'], + colorspace: 'HSL', + ratios: [1, 1.2, 1.4, 2, 3, 4.5, 6, 8, 12, 21] + }, + { + name: "blue", + colorKeys: ['#0000ff'], + colorspace: 'LAB', + ratios: [2, 3, 4.5, 8, 12] + }, + { + name: "red", + colorKeys: ['#ff0000'], + colorspace: 'RGB', + ratios: [2, 3, 4.5, 8, 12] + } + ], + output: "LCH"}); + let themeLight = theme(90);; + + expect(themeLight).toEqual([ + { background: "#e1e1e1" }, + { + name: 'gray', + values: [ + {name: "gray100", contrast: 1, value: "lch(89%, 0, 0deg)"}, + {name: "gray200", contrast: 1.2, value: "lch(83%, 0, 0deg)"}, + {name: "gray300", contrast: 1.4, value: "lch(77%, 0, 0deg)"}, + {name: "gray400", contrast: 2, value: "lch(66%, 0, 0deg)"}, + {name: "gray500", contrast: 3, value: "lch(54%, 0, 0deg)"}, + {name: "gray600", contrast: 4.5, value: "lch(42%, 0, 0deg)"}, + {name: "gray700", contrast: 6, value: "lch(35%, 0, 0deg)"}, + {name: "gray800", contrast: 8, value: "lch(27%, 0, 0deg)"}, + {name: "gray900", contrast: 12, value: "lch(14%, 0, 0deg)"}, + {name: "gray1000", contrast: 21, value: "lch(0%, 0, 0deg)"} + ] + }, + { + name: 'blue', + values: [ + {name: "blue100", contrast: 2, value: "lch(65%, 62, 302deg)"}, + {name: "blue200", contrast: 3, value: "lch(53%, 86, 301deg)"}, + {name: "blue300", contrast: 4.5, value: "lch(41%, 109, 301deg)"}, + {name: "blue400", contrast: 8, value: "lch(25%, 110, 301deg)"}, + {name: "blue500", contrast: 12, value: "lch(13%, 57, 301deg)"} + ] + }, + { + name: 'red', + values: [ + {name: "red100", contrast: 2, value: "lch(66%, 61, 27deg)"}, + {name: "red200", contrast: 3, value: "lch(55%, 103, 39deg)"}, + {name: "red300", contrast: 4.5, value: "lch(43%, 90, 41deg)"}, + {name: "red400", contrast: 8, value: "lch(28%, 65, 39deg)"}, + {name: "red500", contrast: 12, value: "lch(14%, 42, 33deg)"} + ] + } + ]); + +}); + +test('should generate theme for three colors with negative ratios', function() { + let theme = generateAdaptiveTheme({ + baseScale: "gray", + colorScales: [ + { + name: "gray", + colorKeys: ['#cacaca'], + colorspace: 'HSL', + ratios: [-1.8, -1.2, 1, 1.2, 1.4, 2, 3, 4.5, 6, 8, 12, 21] + }, + { + name: "blue", + colorKeys: ['#0000ff'], + colorspace: 'LAB', + ratios: [2, 3, 4.5, 8, 12] + }, + { + name: "red", + colorKeys: ['#ff0000'], + colorspace: 'RGB', + ratios: [2, 3, 4.5, 8, 12] + } + ]}); + let themeLight = theme(90);; + + expect(themeLight).toEqual([ + { background: "#e1e1e1" }, + { + name: "gray", + values: [ + {name: "gray33", contrast: -1.8, value: "#ffffff"}, + {name: "gray67", contrast: -1.2, value: "#f5f5f5"}, + {name: "gray100", contrast: 1, value: "#e0e0e0"}, + {name: "gray200", contrast: 1.2, value: "#cecece"}, + {name: "gray300", contrast: 1.4, value: "#c0c0c0"}, + {name: "gray400", contrast: 2, value: "#a0a0a0"}, + {name: "gray500", contrast: 3, value: "#808080"}, + {name: "gray600", contrast: 4.5, value: "#646464"}, + {name: "gray700", contrast: 6, value: "#525252"}, + {name: "gray800", contrast: 8, value: "#404040"}, + {name: "gray900", contrast: 12, value: "#242424"}, + {name: "gray1000", contrast: 21, value: "#000000"} + ] + }, + { + name: "blue", + values: [ + {name: "blue100", contrast: 2, value: "#b18cff"}, + {name: "blue200", contrast: 3, value: "#8d63ff"}, + {name: "blue300", contrast: 4.5, value: "#623aff"}, + {name: "blue400", contrast: 8, value: "#1c0ad1"}, + {name: "blue500", contrast: 12, value: "#211068"} + ] + }, + { + name: "red", + values: [ + {name: "red100", contrast: 2, value: "#ff7474"}, + {name: "red200", contrast: 3, value: "#ff1313"}, + {name: "red300", contrast: 4.5, value: "#cc0000"}, + {name: "red400", contrast: 8, value: "#860000"}, + {name: "red500", contrast: 12, value: "#500000"} + ] + } + ]); +}); + +test('should generate theme for three colors using variables as parameters', function() { + let tempRatios = [2, 3, 4.5, 8, 12]; + let baseRatios = [1, 1.2, 1.4, 2, 3, 4.5, 6, 8, 12, 21]; + let gray = { + name: "gray", + colorKeys: ['#cacaca'], + colorspace: 'HSL', + ratios: baseRatios + }; + let blue = { + name: "blue", + colorKeys: ['#0000ff'], + colorspace: 'LAB', + ratios: tempRatios + }; + let red = { + name: "red", + colorKeys: ['#ff0000'], + colorspace: 'RGB', + ratios: tempRatios + }; + let brightness = 90; + + let theme = generateAdaptiveTheme({baseScale: gray.name, colorScales: [gray, blue, red]}); + let themeLight = theme(90);; + + expect(themeLight).toEqual([ + { background: "#e1e1e1" }, + { + name: 'gray', + values: [ + {name: "gray100", contrast: 1, value: "#e0e0e0"}, + {name: "gray200", contrast: 1.2, value: "#cecece"}, + {name: "gray300", contrast: 1.4, value: "#c0c0c0"}, + {name: "gray400", contrast: 2, value: "#a0a0a0"}, + {name: "gray500", contrast: 3, value: "#808080"}, + {name: "gray600", contrast: 4.5, value: "#646464"}, + {name: "gray700", contrast: 6, value: "#525252"}, + {name: "gray800", contrast: 8, value: "#404040"}, + {name: "gray900", contrast: 12, value: "#242424"}, + {name: "gray1000", contrast: 21, value: "#000000"} + ] + }, + { + name: 'blue', + values: [ + {name: "blue100", contrast: 2, value: "#b18cff"}, + {name: "blue200", contrast: 3, value: "#8d63ff"}, + {name: "blue300", contrast: 4.5, value: "#623aff"}, + {name: "blue400", contrast: 8, value: "#1c0ad1"}, + {name: "blue500", contrast: 12, value: "#211068"} + ] + }, + { + name: 'red', + values: [ + {name: "red100", contrast: 2, value: "#ff7474"}, + {name: "red200", contrast: 3, value: "#ff1313"}, + {name: "red300", contrast: 4.5, value: "#cc0000"}, + {name: "red400", contrast: 8, value: "#860000"}, + {name: "red500", contrast: 12, value: "#500000"} + ] + } + ]); + +}); + +test('should generate theme with increased contrast', function() { + let theme = generateAdaptiveTheme({ + baseScale: 'gray', + colorScales: [ + { + name: "gray", + colorKeys: ['#cacaca'], + colorspace: 'HSL', + ratios: [-1.8, -1.2, 1, 1.2, 1.4, 2, 3, 4.5, 6, 8, 12, 21] + }, + { + name: "blue", + colorKeys: ['#0000ff'], + colorspace: 'LAB', + ratios: [2, 3, 4.5, 8, 12] + }, + { + name: "red", + colorKeys: ['#ff0000'], + colorspace: 'RGB', + ratios: [2, 3, 4.5, 8, 12] + } + ]}); + let themeLight = theme(90, 1.4);; + + expect(themeLight).toEqual([ + { background: "#e1e1e1" }, + { + name: 'gray', + values: [ + {name: "gray33", contrast: -2.12, value: "#ffffff"}, + {name: "gray67", contrast: -1.28, value: "#fdfdfd"}, + {name: "gray100", contrast: 1, value: "#e0e0e0"}, + {name: "gray200", contrast: 1.28, value: "#c8c8c8"}, + {name: "gray300", contrast: 1.56, value: "#b5b5b5"}, + {name: "gray400", contrast: 2.4, value: "#929292"}, + {name: "gray500", contrast: 3.8, value: "#707070"}, + {name: "gray600", contrast: 5.9, value: "#525252"}, + {name: "gray700", contrast: 8, value: "#404040"}, + {name: "gray800", contrast: 10.8, value: "#2c2c2c"}, + {name: "gray900", contrast: 16.4, value: "#000000"}, + {name: "gray1000", contrast: 29, value: "#000000"} + ] + }, + { + name: 'blue', + values: [ + {name: "blue100", contrast: 2.4, value: "#a179ff"}, + {name: "blue200", contrast: 3.8, value: "#764bff"}, + {name: "blue300", contrast: 5.9, value: "#3418ff"}, + {name: "blue400", contrast: 10.8, value: "#231086"}, + {name: "blue500", contrast: 16.4, value: "#000000"} + ] + }, + { + name: 'red', + values: [ + {name: "red100", contrast: 2.4, value: "#ff5555"}, + {name: "red200", contrast: 3.8, value: "#e10000"}, + {name: "red300", contrast: 5.9, value: "#aa0000"}, + {name: "red400", contrast: 10.8, value: "#5f0000"}, + {name: "red500", contrast: 16.4, value: "#000000"} + ] + } + ]); + +}); + +test('should generate white theme with increased contrast', function() { + let theme = generateAdaptiveTheme({ + baseScale: 'gray', + colorScales: [ + { + name: "gray", + colorKeys: ['#cacaca'], + colorspace: 'HSL', + ratios: [-1.8, -1.2, 1, 1.2, 1.4, 2, 3, 4.5, 6, 8, 12, 21] + }, + { + name: "blue", + colorKeys: ['#0000ff'], + colorspace: 'LAB', + ratios: [2, 3, 4.5, 8, 12] + }, + { + name: "red", + colorKeys: ['#ff0000'], + colorspace: 'RGB', + ratios: [2, 3, 4.5, 8, 12] + } + ]}); + let themeLight = theme(100, 2);; + + expect(themeLight).toEqual([ + { background: "#fefefe" }, + { + name: 'gray', + values: [ + {name: "gray33", contrast: -2.6, value: "#ffffff"}, + {name: "gray67", contrast: -1.4, value: "#ffffff"}, + {name: "gray100", contrast: 1, value: "#fefefe"}, + {name: "gray200", contrast: 1.4, value: "#d9d9d9"}, + {name: "gray300", contrast: 1.8, value: "#c0c0c0"}, + {name: "gray400", contrast: 3, value: "#949494"}, + {name: "gray500", contrast: 5, value: "#6f6f6f"}, + {name: "gray600", contrast: 8, value: "#505050"}, + {name: "gray700", contrast: 11, value: "#3b3b3b"}, + {name: "gray800", contrast: 15, value: "#272727"}, + {name: "gray900", contrast: 23, value: "#000000"}, + {name: "gray1000", contrast: 41, value: "#000000"} + ] + }, + { + name: 'blue', + values: [ + {name: "blue100", contrast: 3, value: "#a47cff"}, + {name: "blue200", contrast: 5, value: "#744aff"}, + {name: "blue300", contrast: 8, value: "#2610ff"}, + {name: "blue400", contrast: 15, value: "#221073"}, + {name: "blue500", contrast: 23, value: "#000000"} + ] + }, + { + name: 'red', + values: [ + {name: "red100", contrast: 3, value: "#ff5c5c"}, + {name: "red200", contrast: 5, value: "#e00000"}, + {name: "red300", contrast: 8, value: "#a60000"}, + {name: "red400", contrast: 15, value: "#560000"}, + {name: "red500", contrast: 23, value: "#000000"} + ] + } + ]); + +}); + +test('should generate dark theme with increased contrast', function() { + let theme = generateAdaptiveTheme({ + baseScale: 'gray', + colorScales: [ + { + name: "gray", + colorKeys: ['#cacaca'], + colorspace: 'HSL', + ratios: [-1.8, -1.2, 1, 1.2, 1.4, 2, 3, 4.5, 6, 8, 12, 21] + }, + { + name: "blue", + colorKeys: ['#0000ff'], + colorspace: 'LAB', + ratios: [2, 3, 4.5, 8, 12] + }, + { + name: "red", + colorKeys: ['#ff0000'], + colorspace: 'RGB', + ratios: [2, 3, 4.5, 8, 12] + } + ]}); + let themeLight = theme(20, 1.5);; + + expect(themeLight).toEqual([ + { background: "#303030" }, + { + name: 'gray', + values: [ + {name: "gray33", contrast: -2.2, value: "#000000"}, + {name: "gray67", contrast: -1.3, value: "#1c1c1c"}, + {name: "gray100", contrast: 1, value: "#303030"}, + {name: "gray200", contrast: 1.3, value: "#414141"}, + {name: "gray300", contrast: 1.6, value: "#4f4f4f"}, + {name: "gray400", contrast: 2.5, value: "#6b6b6b"}, + {name: "gray500", contrast: 4, value: "#8e8e8e"}, + {name: "gray600", contrast: 6.25, value: "#b3b3b3"}, + {name: "gray700", contrast: 8.5, value: "#d0d0d0"}, + {name: "gray800", contrast: 11.5, value: "#efefef"}, + {name: "gray900", contrast: 17.5, value: "#ffffff"}, + {name: "gray1000", contrast: 31, value: "#ffffff"} + ] + }, + { + name: 'blue', + values: [ + {name: "blue100", contrast: 2.5, value: "#6f45ff"}, + {name: "blue200", contrast: 4, value: "#9d73ff"}, + {name: "blue300", contrast: 6.25, value: "#c3a3ff"}, + {name: "blue400", contrast: 11.5, value: "#f4edff"}, + {name: "blue500", contrast: 17.5, value: "#ffffff"} + ] + }, + { + name: 'red', + values: [ + {name: "red100", contrast: 2.5, value: "#da0000"}, + {name: "red200", contrast: 4, value: "#ff4b4b"}, + {name: "red300", contrast: 6.25, value: "#ff9494"}, + {name: "red400", contrast: 11.5, value: "#ffebeb"}, + {name: "red500", contrast: 17.5, value: "#ffffff"} + ] + } + ]); +}); + + +test('should generate colors with user-defined names', function() { + let theme = generateAdaptiveTheme({ + baseScale: 'gray', + colorScales: [ + { + name: "gray", + colorKeys: ['#cacaca'], + colorspace: 'HSL', + ratios: { + 'GRAY_1': -1.8, + 'GRAY_2': -1.2, + 'GRAY_3': 1, + 'GRAY_4': 1.2, + 'GRAY_5': 1.4, + 'GRAY_6': 2, + 'GRAY_7': 3, + 'GRAY_8': 4.5, + 'GRAY_9': 6, + 'GRAY_10': 8, + 'GRAY_11': 12, + 'GRAY_12': 21 + } + }, + { + name: "blue", + colorKeys: ['#0000ff'], + colorspace: 'LAB', + ratios: { + 'BLUE_LOW_CONTRAST': 2, + 'BLUE_LARGE_TEXT': 3, + 'BLUE_TEXT': 4.5, + 'BLUE_HIGH_CONTRAST': 8, + 'BLUE_HIGHEST_CONTRAST': 12 + } + }, + { + name: "red", + colorKeys: ['#ff0000'], + colorspace: 'RGB', + ratios: { + 'red--lowContrast': 2, + 'red--largeText': 3, + 'red--text': 4.5, + 'red--highContrast': 8, + 'red--highestContrast': 12 + } + } + ]}); + let themeLight = theme(20, 1.5);; + + expect(themeLight).toEqual([ + { background: "#303030" }, + { + name: 'gray', + values: [ + {name: "GRAY_1", contrast: -2.2, value: "#000000"}, + {name: "GRAY_2", contrast: -1.3, value: "#1c1c1c"}, + {name: "GRAY_3", contrast: 1, value: "#303030"}, + {name: "GRAY_4", contrast: 1.3, value: "#414141"}, + {name: "GRAY_5", contrast: 1.6, value: "#4f4f4f"}, + {name: "GRAY_6", contrast: 2.5, value: "#6b6b6b"}, + {name: "GRAY_7", contrast: 4, value: "#8e8e8e"}, + {name: "GRAY_8", contrast: 6.25, value: "#b3b3b3"}, + {name: "GRAY_9", contrast: 8.5, value: "#d0d0d0"}, + {name: "GRAY_10", contrast: 11.5, value: "#efefef"}, + {name: "GRAY_11", contrast: 17.5, value: "#ffffff"}, + {name: "GRAY_12", contrast: 31, value: "#ffffff"} + ] + }, + { + name: 'blue', + values: [ + {name: "BLUE_LOW_CONTRAST", contrast: 2.5, value: "#6f45ff"}, + {name: "BLUE_LARGE_TEXT", contrast: 4, value: "#9d73ff"}, + {name: "BLUE_TEXT", contrast: 6.25, value: "#c3a3ff"}, + {name: "BLUE_HIGH_CONTRAST", contrast: 11.5, value: "#f4edff"}, + {name: "BLUE_HIGHEST_CONTRAST", contrast: 17.5, value: "#ffffff"} + ] + }, + { + name: 'red', + values: [ + {name: "red--lowContrast", contrast: 2.5, value: "#da0000"}, + {name: "red--largeText", contrast: 4, value: "#ff4b4b"}, + {name: "red--text", contrast: 6.25, value: "#ff9494"}, + {name: "red--highContrast", contrast: 11.5, value: "#ffebeb"}, + {name: "red--highestContrast", contrast: 17.5, value: "#ffffff"} + ] + } + ]); +}); + + +// Should throw errors +test('should throw error, not valid base scale option', function() { + expect( + () => { + let theme = generateAdaptiveTheme({ + baseScale: 'orange', + colorScales: [ + { + name: "gray", + colorKeys: ['#cacaca'], + colorspace: 'HSL', + ratios: [-1.8, -1.2, 1, 1.2, 1.4, 2, 3, 4.5, 6, 8, 12, 21] + }, + { + name: "blue", + colorKeys: ['#0000ff'], + colorspace: 'LAB', + ratios: [2, 3, 4.5, 8, 12] + }, + { + name: "red", + colorKeys: ['#ff0000'], + colorspace: 'RGB', + ratios: [2, 3, 4.5, 8, 12] + } + ], + brightness: 97 + }); + } + ).toThrow(); +}); diff --git a/packages/contrast-colors/test/generateBaseScale.test.js b/packages/contrast-colors/test/generateBaseScale.test.js new file mode 100644 index 00000000..4783b5e9 --- /dev/null +++ b/packages/contrast-colors/test/generateBaseScale.test.js @@ -0,0 +1,41 @@ +/* +Copyright 2019 Adobe. All rights reserved. +This file is licensed to you under the Apache License, Version 2.0 (the 'License'); +you may not use this file except in compliance with the License. You may obtain a copy +of the License at http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software distributed under +the License is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS +OF ANY KIND, either express or implied. See the License for the specific language +governing permissions and limitations under the License. +*/ + +const { generateBaseScale } = require('../index.js'); + +// Test simple generation in all color spaces +test('should generate 101 grayscale colors in default LAB colorspace)', function() { + let colors = generateBaseScale({colorKeys: ['#ffffff']});; + + expect(colors).toEqual([ '#000000', '#020202', '#060606', '#0a0a0a', '#0d0d0d', '#101010', '#131313', '#151515', '#171717', '#191919', '#1b1b1b', '#1d1d1d', '#1f1f1f', '#212121', '#232323', '#252525', '#272727', '#292929', '#2b2b2b', '#2e2e2e', '#303030', '#323232', '#343434', '#363636', '#383838', '#3b3b3b', '#3d3d3d', '#3f3f3f', '#414141', '#444444', '#464646', '#484848', '#4b4b4b', '#4d4d4d', '#4f4f4f', '#525252', '#545454', '#565656', '#595959', '#5b5b5b', '#5e5e5e', '#606060', '#626262', '#656565', '#676767', '#6a6a6a', '#6c6c6c', '#6f6f6f', '#717171', '#747474', '#767676', '#797979', '#7b7b7b', '#7e7e7e', '#808080', '#838383', '#858585', '#888888', '#8b8b8b', '#8d8d8d', '#909090', '#929292', '#959595', '#989898', '#9a9a9a', '#9d9d9d', '#a0a0a0', '#a2a2a2', '#a5a5a5', '#a8a8a8', '#aaaaaa', '#adadad', '#b0b0b0', '#b2b2b2', '#b5b5b5', '#b8b8b8', '#bababa', '#bdbdbd', '#c0c0c0', '#c3c3c3', '#c5c5c5', '#c8c8c8', '#cbcbcb', '#cecece', '#d1d1d1', '#d3d3d3', '#d6d6d6', '#d9d9d9', '#dcdcdc', '#dfdfdf', '#e1e1e1', '#e4e4e4', '#e7e7e7', '#eaeaea', '#ededed', '#f0f0f0', '#f3f3f3', '#f5f5f5', '#f8f8f8', '#fbfbfb', '#fefefe' ]); + +}); + +test('should generate 101 blue toned grayscale colors in HSL space)', function() { + let colors = generateBaseScale({colorKeys: ['#4a5b7b','#72829c','#a6b2c6'], colorspace: 'HSL'});; + + expect(colors).toEqual([ '#000000', '#020203', '#050608', '#080a0d', '#0a0d11', '#0d1015', '#0f1319', '#11151c', '#12171f', '#141921', '#161b24', '#171d27', '#191f2a', '#1b212c', '#1c232f', '#1e2532', '#202734', '#222938', '#242c3b', '#252e3e', '#273040', '#283243', '#2a3446', '#2c374a', '#2e394d', '#303b4f', '#313d52', '#343f56', '#354259', '#37445b', '#39465e', '#3b4862', '#3d4b65', '#3e4d68', '#414f6b', '#42526e', '#445471', '#465775', '#485978', '#4a5b7c', '#4c5e7e', '#4f6080', '#516383', '#536585', '#566887', '#586a89', '#5b6d8c', '#5d6f8e', '#607291', '#627493', '#657795', '#687a98', '#6a7c99', '#6e7f9a', '#70819b', '#74849e', '#7686a0', '#7989a2', '#7c8ba5', '#7e8ea7', '#8291a9', '#8493ab', '#8796ae', '#8a98b0', '#8c9bb2', '#8f9eb5', '#93a0b7', '#95a3b9', '#98a6bc', '#9ba8be', '#9eabc0', '#a1aec2', '#a4b0c4', '#a7b3c7', '#aab6c9', '#aeb9cb', '#b0bbcc', '#b3becf', '#b7c0d1', '#bac3d3', '#bdc6d4', '#c0c9d7', '#c4ccd9', '#c6cedb', '#c9d1dd', '#cdd4df', '#d1d7e1', '#d3d9e3', '#d6dce5', '#d9dfe7', '#dde2e9', '#e1e5eb', '#e3e7ed', '#e6eaef', '#eaedf1', '#edf0f4', '#f1f3f6', '#f4f5f8', '#f7f8fa', '#fafbfc', '#fdfefe' ]); + +}); + +test('should generate 101 blue toned grayscale colors in LAB space)', function() { + let colors = generateBaseScale({colorKeys: ['#4a5b7b','#72829c','#a6b2c6'], colorspace: 'LAB'});; + + expect(colors).toEqual([ '#000000', '#020202', '#050608', '#080a0d', '#0b0d10', '#0e1013', '#111317', '#131519', '#14171c', '#16191e', '#181b21', '#191d24', '#1b1f27', '#1c2129', '#1e232c', '#20252f', '#212732', '#232935', '#252b39', '#272e3c', '#28303e', '#2a3241', '#2b3444', '#2e3647', '#2f394b', '#313b4e', '#333d50', '#343f54', '#364257', '#38445a', '#39465d', '#3c4861', '#3d4b64', '#3f4d67', '#414f6b', '#43526e', '#445471', '#475675', '#485978', '#4b5c7b', '#4d5e7d', '#4f607f', '#526382', '#546583', '#576886', '#596a88', '#5c6d8a', '#5e6f8c', '#61728e', '#647490', '#677793', '#697995', '#6c7c97', '#6f7f99', '#71819b', '#74849d', '#7786a0', '#7989a2', '#7c8ca4', '#7f8ea6', '#8291a9', '#8493ab', '#8796ad', '#8a98b0', '#8d9bb2', '#909eb4', '#93a0b6', '#95a3b9', '#99a6bb', '#9ca8be', '#9eabc0', '#a1aec2', '#a4b0c4', '#a7b3c7', '#aab6c9', '#aeb9cb', '#b0bbcd', '#b4becf', '#b7c0d1', '#bac3d3', '#bdc6d5', '#c0c9d7', '#c4ccd9', '#c6cedb', '#cad1dd', '#cdd4df', '#d1d7e1', '#d3d9e3', '#d6dce5', '#dadfe7', '#dde2e9', '#e1e5ec', '#e3e7ed', '#e7eaef', '#eaedf1', '#edf0f4', '#f1f2f6', '#f4f5f8', '#f7f8fa', '#fafbfc', '#fdfefe' ]); + +}); + +test('should generate 101 blue colors in HSLuv space)', function() { + let colors = generateBaseScale({colorKeys: ['#6FA7FF', '#B5E6FF'], colorspace: 'HSLuv'});; + + expect(colors).toEqual([ '#000001', '#000207', '#000612', '#000a1b', '#000d20', '#001025', '#00132a', '#00152e', '#001731', '#001935', '#001b38', '#001d3b', '#001f3f', '#002143', '#002347', '#00254b', '#00284e', '#002a52', '#002c55', '#002e59', '#00305c', '#003260', '#003465', '#003768', '#00396c', '#003b6f', '#003d74', '#004078', '#00427b', '#00447f', '#004784', '#004987', '#004b8b', '#004d90', '#005094', '#005297', '#00559c', '#00579f', '#0059a4', '#005ca8', '#005eac', '#0061b1', '#0063b4', '#0066b9', '#0068bd', '#006ac2', '#006dc5', '#0070cb', '#0072ce', '#0074d3', '#0077d7', '#0079dc', '#007ce0', '#007fe4', '#0081e8', '#0084ed', '#0086f2', '#0089f6', '#008cfb', '#008efe', '#2191ff', '#2f93ff', '#3b96ff', '#4799ff', '#4e9bff', '#579eff', '#5da0ff', '#64a3ff', '#6ca6ff', '#6fa9ff', '#70acff', '#71b0ff', '#73b3ff', '#75b6ff', '#77baff', '#79bdff', '#7cc0ff', '#7fc3ff', '#82c7ff', '#86caff', '#8acdff', '#8ed0ff', '#92d2ff', '#96d5ff', '#9ad8ff', '#9fdbff', '#a4deff', '#aae1ff', '#aee3ff', '#b4e6ff', '#bbe8ff', '#c1eaff', '#c9ecff', '#d1efff', '#d7f1ff', '#ddf3ff', '#e3f5ff', '#eaf7ff', '#f1faff', '#f6fcff', '#fcfeff' ]); + +}); diff --git a/packages/contrast-colors/test/generateContrastColors.js b/packages/contrast-colors/test/generateContrastColors.js deleted file mode 100644 index 2fe31189..00000000 --- a/packages/contrast-colors/test/generateContrastColors.js +++ /dev/null @@ -1,158 +0,0 @@ -/* -Copyright 2019 Adobe. All rights reserved. -This file is licensed to you under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. You may obtain a copy -of the License at http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software distributed under -the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS -OF ANY KIND, either express or implied. See the License for the specific language -governing permissions and limitations under the License. -*/ - -import test from 'ava'; -import { generateContrastColors } from '../index.js'; -// Test simple generation in all color spaces -test('should generate 2 colors (CAM02 interpolation)', function(t) { - let colors = generateContrastColors({colorKeys: ['#2451FF', '#C9FEFE', '#012676'], base: '#f5f5f5', ratios: [3, 4.5], colorspace: 'CAM02'});; - - t.deepEqual( - colors, - [ '#5490e0', '#2c66f1' ] - ); - -}); -test('should generate 2 colors (LAB interpolation)', function(t) { - let colors = generateContrastColors({colorKeys: ['#2451FF', '#C9FEFE', '#012676'], base: '#f5f5f5', ratios: [3, 4.5], colorspace: 'LAB'});; - - t.deepEqual( - colors, - [ '#7383ff', '#435eff' ] - ); -}); -test('should generate 2 colors (LCH interpolation)', function(t) { - let colors = generateContrastColors({colorKeys: ['#2451FF', '#C9FEFE', '#012676'], base: '#f5f5f5', ratios: [3, 4.5], colorspace: 'LCH'});; - - t.deepEqual( - colors, - [ '#008fff', '#0065ff' ] - ); -}); -test('should generate 2 colors (HSL interpolation)', function(t) { - let colors = generateContrastColors({colorKeys: ['#2451FF', '#C9FEFE', '#012676'], base: '#f5f5f5', ratios: [3, 4.5], colorspace: 'HSL'});; - - t.deepEqual( - colors, - [ '#478cfe', '#2d62ff' ] - ); -}); -test('should generate 2 colors (HSLuv interpolation)', function(t) { - let colors = generateContrastColors({colorKeys: ['#2451FF', '#C9FEFE', '#012676'], base: '#f5f5f5', ratios: [3, 4.5], colorspace: 'HSLuv'});; - - t.deepEqual( - colors, - [ '#1896dc', '#066aea' ] - ); -}); -test('should generate 2 colors (HSV interpolation)', function(t) { - let colors = generateContrastColors({colorKeys: ['#2451FF', '#C9FEFE', '#012676'], base: '#f5f5f5', ratios: [3, 4.5], colorspace: 'HSV'});; - - t.deepEqual( - colors, - [ '#478cff', '#2d62ff' ] - ); -}); -test('should generate 2 colors (RGB interpolation)', function(t) { - let colors = generateContrastColors({colorKeys: ['#2451FF', '#C9FEFE', '#012676'], base: '#f5f5f5', ratios: [3, 4.5], colorspace: 'RGB'});; - - t.deepEqual( - colors, - [ '#5988ff', '#3360ff' ] - ); -}); - -test('should generate 2 colors on dark background', function(t) { - let colors = generateContrastColors({colorKeys: ['#2451FF', '#C9FEFE', '#012676'], base: "#323232",ratios: [3, 4.5], colorspace: "LCH"}); // positive & negative ratios - - t.deepEqual( - colors, - [ '#0074ff', '#009fff' ] - ); -}); - -// Check bidirectionality of contrast ratios (positive vs negative) -test('should generate 2 colors with bidirectional contrast (light background)', function(t) { - let colors = generateContrastColors({colorKeys: ["#012676"], base: "#D8D8D8",ratios: [-1.25,4.5], colorspace: "LCH"}); // positive & negative ratios - - t.deepEqual( - colors, - [ '#efeff6', '#56599a' ] - ); -}); -test('should generate 2 colors with bidirectional contrast (dark background)', function(t) { - let colors = generateContrastColors({colorKeys: ["#012676"], base: "#323232",ratios: [-1.25,4.5], colorspace: "LCH"}); // positive & negative ratios - - t.deepEqual( - colors, - [ '#101c51', '#9695c0' ] - ); -}); - -// Contrast gamuts -test('should generate black when ratio darker than available colors', function(t) { - let colors = generateContrastColors({colorKeys: ['#2451FF', '#C9FEFE', '#012676'], base: "#d8d8d8",ratios: [21], colorspace: "LCH"}); // positive & negative ratios - - t.deepEqual( - colors, - [ '#000000' ] - ); -}); -test('should generate white when ratio lighter than available colors', function(t) { - let colors = generateContrastColors({colorKeys: ['#2451FF', '#C9FEFE', '#012676'], base: "#323232",ratios: [21], colorspace: "LCH"}); // positive & negative ratios - - t.deepEqual( - colors, - [ '#ffffff' ] - ); -}); -test('should generate white when negative ratio lighter than available colors', function(t) { - let colors = generateContrastColors({colorKeys: ['#2451FF', '#C9FEFE', '#012676'], base: "#f5f5f5",ratios: [-21], colorspace: "LCH"}); // positive & negative ratios - - t.deepEqual( - colors, - [ '#ffffff' ] - ); -}); -test('should generate black when negative ratio lighter than available colors', function(t) { - let colors = generateContrastColors({colorKeys: ['#2451FF', '#C9FEFE', '#012676'], base: "#323232",ratios: [-21], colorspace: "LCH"}); // positive & negative ratios - - t.deepEqual( - colors, - [ '#000000' ] - ); -}); - -// Expected errors -test('should generate no colors, missing colorKeys', function(t) { - t.throws( - () => { - let colors = generateContrastColors({base: '#f5f5f5', ratios: [3, 4.5]}) // no key colors - } - ); -}); - -test('should generate no colors, missing ratios', function(t) { - - t.throws( - () => { - let colors = generateContrastColors({colorKeys: ['#2451FF', '#C9FEFE', '#012676'], base: '#f5f5f5'}) // no ratios - } - ); -}); - -test('should generate no colors, missing base', function(t) { - t.throws( - () => { - let colors = generateContrastColors({colorKeys: ['#2451FF', '#C9FEFE', '#012676'], ratios: [3, 4.5]}) // no base - } - ); -}); diff --git a/packages/contrast-colors/test/generateContrastColors.test.js b/packages/contrast-colors/test/generateContrastColors.test.js new file mode 100644 index 00000000..a5049b0b --- /dev/null +++ b/packages/contrast-colors/test/generateContrastColors.test.js @@ -0,0 +1,203 @@ +/* +Copyright 2019 Adobe. All rights reserved. +This file is licensed to you under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. You may obtain a copy +of the License at http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software distributed under +the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS +OF ANY KIND, either express or implied. See the License for the specific language +governing permissions and limitations under the License. +*/ + +const { generateContrastColors } = require('../index.js'); + +// Test simple generation in all color spaces +test('should generate 2 colors (CAM02 interpolation)', function() { + let colors = generateContrastColors({colorKeys: ['#2451FF', '#C9FEFE', '#012676'], base: '#f5f5f5', ratios: [3, 4.5], colorspace: 'CAM02'});; + + expect(colors).toEqual([ '#5490e0', '#2c66f1' ]); + +}); + +test('should generate 2 named colors (CAM02 interpolation)', function() { + let colors = generateContrastColors({name: 'Cerulean', colorKeys: ['#2451FF', '#C9FEFE', '#012676'], base: '#f5f5f5', ratios: [3, 4.5], colorspace: 'CAM02'});; + + expect(colors).toEqual([ '#5490e0', '#2c66f1' ]); + +}); + +test('should generate 2 colors (LAB interpolation)', function() { + let colors = generateContrastColors({colorKeys: ['#2451FF', '#C9FEFE', '#012676'], base: '#f5f5f5', ratios: [3, 4.5], colorspace: 'LAB'});; + + expect(colors).toEqual([ '#7383ff', '#435eff' ]); +}); + +test('should generate 2 colors (LCH interpolation)', function() { + let colors = generateContrastColors({colorKeys: ['#2451FF', '#C9FEFE', '#012676'], base: '#f5f5f5', ratios: [3, 4.5], colorspace: 'LCH'});; + + expect(colors).toEqual([ '#008fff', '#0065ff' ]); +}); + +test('should generate 2 colors (HSL interpolation)', function() { + let colors = generateContrastColors({colorKeys: ['#2451FF', '#C9FEFE', '#012676'], base: '#f5f5f5', ratios: [3, 4.5], colorspace: 'HSL'});; + + expect(colors).toEqual([ '#478cfe', '#2d62ff' ]); +}); + +test('should generate 2 colors (HSLuv interpolation)', function() { + let colors = generateContrastColors({colorKeys: ['#2451FF', '#C9FEFE', '#012676'], base: '#f5f5f5', ratios: [3, 4.5], colorspace: 'HSLuv'});; + + expect(colors).toEqual([ '#1896dc', '#066aea' ]); +}); + +test('should generate 2 colors (HSV interpolation)', function() { + let colors = generateContrastColors({colorKeys: ['#2451FF', '#C9FEFE', '#012676'], base: '#f5f5f5', ratios: [3, 4.5], colorspace: 'HSV'});; + + expect(colors).toEqual([ '#478cff', '#2d62ff' ]); +}); + +test('should generate 2 colors (RGB interpolation)', function() { + let colors = generateContrastColors({colorKeys: ['#2451FF', '#C9FEFE', '#012676'], base: '#f5f5f5', ratios: [3, 4.5], colorspace: 'RGB'});; + + expect(colors).toEqual([ '#5988ff', '#3360ff' ]); +}); + +test('should generate 2 colors on dark background', function() { + let colors = generateContrastColors({colorKeys: ['#2451FF', '#C9FEFE', '#012676'], base: "#323232",ratios: [3, 4.5], colorspace: "LCH"}); // positive & negative ratios + + expect(colors).toEqual([ '#0074ff', '#009fff' ]); +}); + +// Check bidirectionality of contrast ratios (positive vs negative) +test('should generate 2 colors with bidirectional contrast (light background)', function() { + let colors = generateContrastColors({colorKeys: ["#012676"], base: "#D8D8D8",ratios: [-1.25,4.5], colorspace: "LCH"}); // positive & negative ratios + + expect(colors).toEqual([ '#efeff6', '#56599a' ]); +}); + +test('should generate 2 colors with bidirectional contrast (dark background)', function() { + let colors = generateContrastColors({colorKeys: ["#012676"], base: "#323232",ratios: [-1.25,4.5], colorspace: "LCH"}); // positive & negative ratios + + expect(colors).toEqual([ '#101c51', '#9695c0' ]); +}); + +// Contrast gamuts +test('should generate black when ratio darker than available colors', function() { + let colors = generateContrastColors({colorKeys: ['#2451FF', '#C9FEFE', '#012676'], base: "#d8d8d8",ratios: [21], colorspace: "LCH"}); // positive & negative ratios + + expect(colors).toEqual([ '#000000' ]); +}); + +test('should generate white when ratio lighter than available colors', function() { + let colors = generateContrastColors({colorKeys: ['#2451FF', '#C9FEFE', '#012676'], base: "#323232",ratios: [21], colorspace: "LCH"}); // positive & negative ratios + + expect(colors).toEqual([ '#ffffff' ]); +}); + +test('should generate white when negative ratio lighter than available colors', function() { + let colors = generateContrastColors({colorKeys: ['#2451FF', '#C9FEFE', '#012676'], base: "#f5f5f5",ratios: [-21], colorspace: "LCH"}); // positive & negative ratios + + expect(colors).toEqual([ '#ffffff' ]); +}); + +test('should generate black when negative ratio lighter than available colors', function() { + let colors = generateContrastColors({colorKeys: ['#2451FF', '#C9FEFE', '#012676'], base: "#323232",ratios: [-21], colorspace: "LCH"}); // positive & negative ratios + + expect(colors).toEqual([ '#000000' ]); +}); + +// Mid-Tone Backgrounds +test('should generate slightly lighter & darker grays on a darker midtone gray background', function() { + let colors = generateContrastColors({colorKeys: ['#000000'], base: "#737373",ratios: [1.2, -1.2], colorspace: "LCH"}); // positive & negative ratios + + expect(colors).toEqual([ '#808080', '#666666' ]); +}); +test('should generate slightly lighter & darker grays on a lighter midtone gray background', function() { + let colors = generateContrastColors({colorKeys: ['#000000'], base: "#787878",ratios: [1.2, -1.2], colorspace: "LCH"}); // positive & negative ratios + + expect(colors).toEqual([ '#6b6b6b', '#858585' ]); +}); +test('should generate slightly lighter & darker oranges on a darker midtone slate background', function() { + let colors = generateContrastColors({colorKeys: ["#ff8602","#ab3c00","#ffd88b"], base: "#537a9c",ratios: [1.2, -1.2], colorspace: "LCH"}); // positive & negative ratios + + expect(colors).toEqual([ '#d66102', '#b64601' ]); +}); +test('should generate slightly lighter & darker oranges on a lighter midtone slate background', function() { + let colors = generateContrastColors({colorKeys: ["#ff8602","#ab3c00","#ffd88b"], base: "#537b9d",ratios: [1.2, -1.2], colorspace: "LCH"}); // positive & negative ratios + + expect(colors).toEqual([ '#b84601', '#d76202' ]); +}); + +// Output formats +test('should generate 2 colors in HEX format', function() { + let colors = generateContrastColors({colorKeys: ['#2451FF', '#C9FEFE', '#012676'], base: '#f5f5f5', ratios: [3, 4.5], colorspace: 'CAM02'});; + + expect(colors).toEqual([ '#5490e0', '#2c66f1' ]); +}); +test('should generate 2 colors in RGB format', function() { + let colors = generateContrastColors({colorKeys: ['#2451FF', '#C9FEFE', '#012676'], base: '#f5f5f5', ratios: [3, 4.5], colorspace: 'CAM02', output: 'RGB'});; + + expect(colors).toEqual([ 'rgb(84, 144, 224)', 'rgb(44, 102, 241)' ]); +}); +test('should generate 2 colors in HSL format', function() { + let colors = generateContrastColors({colorKeys: ['#2451FF', '#C9FEFE', '#012676'], base: '#f5f5f5', ratios: [3, 4.5], colorspace: 'CAM02', output: 'HSL'});; + + expect(colors).toEqual([ 'hsl(214deg, 69%, 60%)', 'hsl(222deg, 88%, 56%)' ]); +}); +test('should generate 2 colors in HSV format', function() { + let colors = generateContrastColors({colorKeys: ['#2451FF', '#C9FEFE', '#012676'], base: '#f5f5f5', ratios: [3, 4.5], colorspace: 'CAM02', output: 'HSV'});; + + expect(colors).toEqual([ 'hsv(214deg, 63%, 88%)', 'hsv(222deg, 82%, 95%)' ]); +}); +test('should generate 2 colors in LAB format', function() { + let colors = generateContrastColors({colorKeys: ['#2451FF', '#C9FEFE', '#012676'], base: '#f5f5f5', ratios: [3, 4.5], colorspace: 'CAM02', output: 'LAB'});; + + expect(colors).toEqual([ 'lab(58%, -1, -47)', 'lab(46%, 22, -77)' ]); +}); +test('should generate 2 colors in LCH format', function() { + let colors = generateContrastColors({colorKeys: ['#2451FF', '#C9FEFE', '#012676'], base: '#f5f5f5', ratios: [3, 4.5], colorspace: 'CAM02', output: 'LCH'});; + + expect(colors).toEqual([ 'lch(58%, 47, 269deg)', 'lch(46%, 80, 286deg)' ]); +}); + +// Expected errors +test('should generate no colors, missing colorKeys', function() { + expect( + () => { + let colors = generateContrastColors({base: '#f5f5f5', ratios: [3, 4.5]}) // no key colors + } + ).toThrow(); +}); + +test('should generate no colors, missing ratios', function() { + + expect( + () => { + let colors = generateContrastColors({colorKeys: ['#2451FF', '#C9FEFE', '#012676'], base: '#f5f5f5'}) // no ratios + } + ).toThrow(); +}); + +test('should generate no colors, missing base', function() { + expect( + () => { + let colors = generateContrastColors({colorKeys: ['#2451FF', '#C9FEFE', '#012676'], ratios: [3, 4.5]}) // no base + } + ).toThrow(); +}); + +test('should throw error, missing hash on hex value', function() { + expect( + () => { + let colors = generateContrastColors({colorKeys: ['#2451FF', 'C9FEFE', '#012676'], ratios: [3, 4.5]}) // third color missing hash # + } + ).toThrow(); +}); + +test('should throw error, incomplete hex value', function() { + expect( + () => { + let colors = generateContrastColors({colorKeys: ['#2451FF', '#C9FEF', '#012676'], ratios: [3, 4.5]}) // third color missing final hex code + } + ).toThrow(); +}); diff --git a/packages/contrast-colors/test/minPositive.test.js b/packages/contrast-colors/test/minPositive.test.js new file mode 100644 index 00000000..73fe7a92 --- /dev/null +++ b/packages/contrast-colors/test/minPositive.test.js @@ -0,0 +1,26 @@ +/* +Copyright 2019 Adobe. All rights reserved. +This file is licensed to you under the Apache License, Version 2.0 (the 'License'); +you may not use this file except in compliance with the License. You may obtain a copy +of the License at http://www.apache.org/licenses/LICENSE2.0 +Unless required by applicable law or agreed to in writing, software distributed under +the License is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS +OF ANY KIND, either express or implied. See the License for the specific language +governing permissions and limitations under the License. +*/ + +const { minPositive } = require('../index.js'); + +test('should return 1', function() { + let result = minPositive([1, 1.2, 1.4, 2, 3, 4.5, 6, 8, 12, 21]);; + + expect(result).toEqual(1); + +}); + +test('should return 2', function() { + let result = minPositive([-3, -2, -1.2, 2, 3, 4.5, 6, 8, 12, 21]);; + + expect(result).toEqual(2); + +}); diff --git a/packages/contrast-colors/test/ratioName.test.js b/packages/contrast-colors/test/ratioName.test.js new file mode 100644 index 00000000..1b7be405 --- /dev/null +++ b/packages/contrast-colors/test/ratioName.test.js @@ -0,0 +1,27 @@ +/* +Copyright 2019 Adobe. All rights reserved. +This file is licensed to you under the Apache License, Version 2.0 (the 'License'); +you may not use this file except in compliance with the License. You may obtain a copy +of the License at http://www.apache.org/licenses/LICENSE2.0 +Unless required by applicable law or agreed to in writing, software distributed under +the License is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS +OF ANY KIND, either express or implied. See the License for the specific language +governing permissions and limitations under the License. +*/ + +const { ratioName } = require('../index.js'); + +test('should output 10 numbers incremented by 100', function() { + let theme = ratioName([1, 1.2, 1.4, 2, 3, 4.5, 6, 8, 12, 21]);; + + expect(theme).toEqual([100, 200, 300, 400, 500, 600, 700, 800, 900, 1000]); + +}); + + +test('should output 10 numbers with first at 50', function() { + let theme = ratioName([-1.5, 1.2, 1.4, 2, 3, 4.5, 6, 8, 12, 21]);; + + expect(theme).toEqual([50, 100, 200, 300, 400, 500, 600, 700, 800, 900]); + +}); diff --git a/packages/contrast-colors/wrapper.mjs b/packages/contrast-colors/wrapper.mjs new file mode 100644 index 00000000..93cfff2d --- /dev/null +++ b/packages/contrast-colors/wrapper.mjs @@ -0,0 +1,13 @@ +import contrastColors from './index.js'; + +export const createScale = contrastColors.createScale; +export const luminance = contrastColors.luminance; +export const contrast = contrastColors.contrast; +export const binarySearch = contrastColors.binarySearch; +export const generateBaseScale = contrastColors.generateBaseScale; +export const generateContrastColors = contrastColors.generateContrastColors; +export const minPositive = contrastColors.minPositive; +export const ratioName = contrastColors.ratioName; +export const generateAdaptiveTheme = contrastColors.generateAdaptiveTheme; + +export default contrastColors; diff --git a/packages/ui/CHANGELOG.md b/packages/ui/CHANGELOG.md new file mode 100644 index 00000000..f2a97b65 --- /dev/null +++ b/packages/ui/CHANGELOG.md @@ -0,0 +1,96 @@ +# Change Log + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +## [1.3.2](https://github.com/adobe/leonardo/compare/@adobe/leonardo-ui@1.3.1...@adobe/leonardo-ui@1.3.2) (2020-09-08) + + +### Bug Fixes + +* title/description for contrast checker page ([a70813c](https://github.com/adobe/leonardo/commit/a70813cd7104c993a1ef30c698cc78e30931b072)) + + + + + +## [1.3.1](https://github.com/adobe/leonardo/compare/@adobe/leonardo-ui@1.3.0...@adobe/leonardo-ui@1.3.1) (2020-08-18) + +**Note:** Version bump only for package @adobe/leonardo-ui + + + + + +# [1.3.0](https://github.com/adobe/leonardo/compare/@adobe/leonardo-ui@1.2.2...@adobe/leonardo-ui@1.3.0) (2020-08-12) + + +### Bug Fixes + +* string separated by commas and space resulted in extra space in array ([#89](https://github.com/adobe/leonardo/issues/89)) ([89f54a4](https://github.com/adobe/leonardo/commit/89f54a44eb39ab7eed2d224a12f79bfbac22fa3b)) + + +### Features + +* add support to numeric-only hex values in bulk import ([#83](https://github.com/adobe/leonardo/issues/83)) ([#87](https://github.com/adobe/leonardo/issues/87)) ([da4f9f7](https://github.com/adobe/leonardo/commit/da4f9f78be8260fddb80ae1673dcd7088978ad69)) + + + + + +## [1.2.2](https://github.com/adobe/leonardo/compare/@adobe/leonardo-ui@1.2.1...@adobe/leonardo-ui@1.2.2) (2020-05-04) + + +### Bug Fixes + +* remove Babel, use ESM wrapper approach for Node 13.x ESM support + CJS support ([#72](https://github.com/adobe/leonardo/issues/72)) ([7541dc1](https://github.com/adobe/leonardo/commit/7541dc1189403039b900ef08ca82023d31063b58)) + + + + + +## [1.2.1](https://github.com/adobe/leonardo/compare/@adobe/leonardo-ui@1.2.0...@adobe/leonardo-ui@1.2.1) (2020-02-28) + + +### Bug Fixes + +* corrected charts with proper colors and data ([#53](https://github.com/adobe/leonardo/issues/53)) ([91097fd](https://github.com/adobe/leonardo/commit/91097fdb1a6a0eb2c4add7537d970ca0633994ea)) + + + + + +# [1.2.0](https://github.com/adobe/leonardo/compare/@adobe/leonardo-ui@1.1.0...@adobe/leonardo-ui@1.2.0) (2020-01-21) + + +### Bug Fixes + +* changed form to div in converter to prevent refresh on submit ([#43](https://github.com/adobe/leonardo/issues/43)) ([34a7686](https://github.com/adobe/leonardo/commit/34a76865638e4b001b491ac2ecca227ba060af05)) +* **ui:** Update URL in README ([#28](https://github.com/adobe/leonardo/issues/28)) ([a1294f3](https://github.com/adobe/leonardo/commit/a1294f3e6cd29e5cdb7166a9ebdc7a66d98d3cc0)) + + +### Features + +* added hex to output conversion types ([#41](https://github.com/adobe/leonardo/issues/41)) ([e27fc86](https://github.com/adobe/leonardo/commit/e27fc860488112e58e453364e9318a88908c403d)) +* **ui:** Increment ratio input by whole number when Shift is pressed ([6705c44](https://github.com/adobe/leonardo/commit/6705c44d3f1bf9b16ac5cb7040dfdb99af66afe0)) + + + + + +# 1.1.0 (2019-12-09) + + +### Bug Fixes + +* add a color if no colors are defined ([1477c6c](https://github.com/adobe/leonardo/commit/1477c6cfa5ee71d7eb21dbbf0ed5072136e360e0)) +* don't make assumptions about pathName ([78300b8](https://github.com/adobe/leonardo/commit/78300b879f9309d3b18ebc474cc93c77ca533cf2)) +* fix icons in UI, correct Spectrum CSS version #s ([e43ef12](https://github.com/adobe/leonardo/commit/e43ef12281177bd34f9f9a577494c192ba407eb7)) +* get dependencies correctly ([a99b47d](https://github.com/adobe/leonardo/commit/a99b47d6c793a2d4aae9ee607d1720317be4cdd4)) + + +### Features + +* adding link to demo app in UI ([52d8b1e](https://github.com/adobe/leonardo/commit/52d8b1e1d86d6bd85d7000675126e27da186dd3f)) +* colorscale as HTML Canvas ([e0eb9b4](https://github.com/adobe/leonardo/commit/e0eb9b46173a4d80c083f22b61b3eae7f49ce5fb)) +* expose functions so they can be ran in the console ([#86](https://github.com/adobe/leonardo/issues/86)) ([651fd19](https://github.com/adobe/leonardo/commit/651fd1952e3b317dd6c4187ce2d393bfb7bed91e)) diff --git a/packages/ui/README.md b/packages/ui/README.md index b4b9d8c2..b46eceeb 100644 --- a/packages/ui/README.md +++ b/packages/ui/README.md @@ -1,6 +1,7 @@ # `@adobe/leonardo-ui` +[![license](https://img.shields.io/github/license/adobe/leonardo)](https://github.com/adobe/leonardo/blob/master/LICENSE) [![Pull requests welcome](https://img.shields.io/badge/PRs-welcome-blueviolet)](https://github.com/adobe/leonardo/blob/master/.github/CONTRIBUTING.md) -The Leonardo tool UI, deployed at http://www.leonardiocolor.io/ +The Leonardo tool UI, deployed at http://www.leonardocolor.io/ ## Contributing Contributions are welcomed! Read the [Contributing Guide](../../.github/CONTRIBUTING.md) for more information. diff --git a/packages/ui/package-lock.json b/packages/ui/package-lock.json new file mode 100644 index 00000000..2f2a97cd --- /dev/null +++ b/packages/ui/package-lock.json @@ -0,0 +1,525 @@ +{ + "name": "@adobe/leonardo-ui", + "version": "1.3.2", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@adobe/focus-ring-polyfill": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/@adobe/focus-ring-polyfill/-/focus-ring-polyfill-0.1.5.tgz", + "integrity": "sha512-OLa/TlzPv6vzMPi3DT9/Gefu/HJptcBcFmMYTpeNTZ6Y+t2TL+CZtjGlu438O2V03c86KEEgMlm9nQ70kW3bPw==" + }, + "@adobe/leonardo-contrast-colors": { + "version": "1.0.0-alpha.7", + "resolved": "https://registry.npmjs.org/@adobe/leonardo-contrast-colors/-/leonardo-contrast-colors-1.0.0-alpha.7.tgz", + "integrity": "sha512-S0u1fpE2RvP180I+r7YVaNMxibp54SR3wl9PGjb70sJJolOtMtCG6n8lA9ry4MDORtqOyfAMIGMuvD3Xuj2y+g==", + "requires": { + "d3": "^5.14.2", + "d3-3d": "0.0.9", + "d3-cam02": "^0.1.5", + "d3-hsluv": "^0.1.2", + "d3-hsv": "^0.1.0" + } + }, + "@adobe/spectrum-css-workflow-icons": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@adobe/spectrum-css-workflow-icons/-/spectrum-css-workflow-icons-1.0.0.tgz", + "integrity": "sha512-aJbk7XhsUNefoufMpneq2aIT3VuD++KUY0C08VKbt5p/WOW0aDX8WYavUxW4oiK5cAgsvapcaPaS+eWpBMwXLg==" + }, + "@spectrum-css/alert": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@spectrum-css/alert/-/alert-2.0.5.tgz", + "integrity": "sha512-dBC5vLSkNazPs5w7nz6hH5QBLTujosJh4PhwpKOS9BXWtx5HzmpA0ZoEpJJOWbfAwwT0x93F+eqHOfup7WBV4g==" + }, + "@spectrum-css/button": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@spectrum-css/button/-/button-2.2.0.tgz", + "integrity": "sha512-1dccGF47Xf8uKEThnAjwXMsOE5PPMDrWK1S1x6ZWWXHJ1kQAkx7ZtekLYLvh588/IuEHUcPW3M4r9gvUuGXR5g==" + }, + "@spectrum-css/buttongroup": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@spectrum-css/buttongroup/-/buttongroup-2.2.1.tgz", + "integrity": "sha512-9yMsNuvF9LQVSMOJlkwi2cWY0vAj1Z/ic3xoTcKkm8WTTJHv7lHVwU+XF0L2XpmnYNsMVICLB5YhCzjTu5GEsg==" + }, + "@spectrum-css/checkbox": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@spectrum-css/checkbox/-/checkbox-2.1.0.tgz", + "integrity": "sha512-par9qG6+3hxNrPzg1mx46OZHsViSKlOKJIGoa8L2kTS5XLVWCTl7XH+QE55X/QPcUrmItX72terrJr0FZu9pjw==" + }, + "@spectrum-css/colorhandle": { + "version": "1.0.0-beta.1", + "resolved": "https://registry.npmjs.org/@spectrum-css/colorhandle/-/colorhandle-1.0.0-beta.1.tgz", + "integrity": "sha512-f0efiI3axD15lyRffU7XLu0Mx0EHBEhsbWaBg54AL6mANT8WrmdayDM+/UR0fOUlY7SXt7R5ErpmSQ8XvJ6siQ==" + }, + "@spectrum-css/colorloupe": { + "version": "1.0.0-beta.1", + "resolved": "https://registry.npmjs.org/@spectrum-css/colorloupe/-/colorloupe-1.0.0-beta.1.tgz", + "integrity": "sha512-/XrkAO2jwb0DEODu9IHHJmZsv3pi0RETgizvVXIENBiY6bGUGtSc2sP2nNK6gh26Ju/KfCixcmPT4Cu3MC+bYQ==" + }, + "@spectrum-css/colorslider": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/@spectrum-css/colorslider/-/colorslider-1.0.0-beta.2.tgz", + "integrity": "sha512-M1XdTU5ok/4EqsTJ5gogtk6T1m9rhqxeaF7KNK1zvBktmBVNr6BRyTEimGGhG1Ngv7yUNdQJXcV7cMiPLVesDg==" + }, + "@spectrum-css/dialog": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@spectrum-css/dialog/-/dialog-2.0.5.tgz", + "integrity": "sha512-GKyRtwoUCJVu4toMOTtvFo+nH/O5nwaqzaz7Z/R6RznbzhZAyrEuVCdKHQLnfCuhX/kGVSEu54w/H9KWu1UPRg==" + }, + "@spectrum-css/dropdown": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@spectrum-css/dropdown/-/dropdown-2.1.5.tgz", + "integrity": "sha512-v1LVDuF0KZBSqmSyv67w2VXCpd9L2hdqFDFuTogQeBMrvvS3A1Go2JPmwnZXZLH0wDKqzuoExNMcG0JAvgJO/A==" + }, + "@spectrum-css/fieldgroup": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@spectrum-css/fieldgroup/-/fieldgroup-2.0.6.tgz", + "integrity": "sha512-ISjr23n2QYlG1PhNzbw7Ja9Bd41tuNwAxQrAJyyr1lTCxIq/hHOHm5XPEympRshvUOnLiumpjsAYvK9Cr2lTjQ==" + }, + "@spectrum-css/fieldlabel": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@spectrum-css/fieldlabel/-/fieldlabel-2.0.6.tgz", + "integrity": "sha512-ejfgMksfVWUlKWGHNUwlyYI/QEVWMKVZMgRIEYrWhPk1fPykEJPt3YMXprHFj3auzQPR+Y6thSsVlfxJ1kmUAg==" + }, + "@spectrum-css/icon": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@spectrum-css/icon/-/icon-2.1.1.tgz", + "integrity": "sha512-Ddnjw3YTZiuJC8uVJ9x6i+ufQN+Vp4vibc+YxE7nXn8tWzT8zTRzwv6oHwSG9m+KzonTfnwqPuQx6ZA8vbK9xA==" + }, + "@spectrum-css/illustratedmessage": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@spectrum-css/illustratedmessage/-/illustratedmessage-2.0.0.tgz", + "integrity": "sha512-8AN8K0QJB9pBofjfje+jNSzML+d21tRLkI2CtziyzvQfKOHj0u7ofxHQqP/r08Ot/lC54gGA3dcHxDjCjhv6gA==" + }, + "@spectrum-css/link": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@spectrum-css/link/-/link-2.0.6.tgz", + "integrity": "sha512-vXs5JyDTxc/zk+Kuj8UTIg2NqkSWkVwg71TbL3o5K90c5xvmDIX1B/j8eFj7WPJiwsYSDJJKiBvXlTvpWrTgqA==" + }, + "@spectrum-css/menu": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@spectrum-css/menu/-/menu-2.1.5.tgz", + "integrity": "sha512-NJjiYpCFrBDLs0bjBAZFZO3/4Nz9Z8X/FFebFkbKYbOnECS9ZUe2mXvDhNB1yDRQksBWwo3m76+/OU2VfBYDQg==" + }, + "@spectrum-css/page": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@spectrum-css/page/-/page-2.0.7.tgz", + "integrity": "sha512-0e8gne8/3yuEjkbbT7F+K/E15Z2BbL/q/r+Z2KM9Kew6uLnTJawFzUPR67zcSVZw8LicWVMxVu9o5KbhEhXMcw==", + "requires": { + "@spectrum-css/vars": "^2.3.0" + } + }, + "@spectrum-css/radio": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@spectrum-css/radio/-/radio-2.1.0.tgz", + "integrity": "sha512-FuCnFu8qOZ1IK/vboWv/SBxEy88qmhAj0BWKi5c1KVrYpujPpxJ03QzBy2dd9we3Yh28XOObbnda+6qXVVzyWg==" + }, + "@spectrum-css/slider": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@spectrum-css/slider/-/slider-2.1.0.tgz", + "integrity": "sha512-oROCNx6HMY5nPPXBmH515dDpE/45hssaUleIC/HtgdOdwHiNdbGVFV3IK5xpPgYPCAzNlrB9BRsL8DSz2nDUQw==" + }, + "@spectrum-css/tabs": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@spectrum-css/tabs/-/tabs-2.1.5.tgz", + "integrity": "sha512-k6Gw6cuSPu8DZGS+pGRB/0HsInjFR5TlYeOYcIXMUQ1iQAofqsFkc+RV7aRYpNNYeq+xkBTq8wGEvKVQXzOm9Q==" + }, + "@spectrum-css/textfield": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@spectrum-css/textfield/-/textfield-2.0.6.tgz", + "integrity": "sha512-lbr9HuXGOjmZ6K0MmODA3u7DRaaVk1P2aqqJkKy6xIY9u2nRKg/nna3TANJRqVnYMvRAdsWiH2vTWTBkRrCsGg==" + }, + "@spectrum-css/toast": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@spectrum-css/toast/-/toast-2.0.5.tgz", + "integrity": "sha512-oXiLO6GdgGy79NRIAuinQNw0IVhklVoAlRj2gjUtXOz1Avik5r2QaZrH31BvjLvlnf56Va3d9XFQg9eXcCby4Q==" + }, + "@spectrum-css/tooltip": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@spectrum-css/tooltip/-/tooltip-2.0.5.tgz", + "integrity": "sha512-AmPTbrm54TPzB7hHi0H31EbP0Q0DzZxprQ3xEtWy6ps6TCThIS2GkAOEKg3MdMQxcGu/QwQM9TrgBdALNQp/XA==" + }, + "@spectrum-css/typography": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@spectrum-css/typography/-/typography-2.1.3.tgz", + "integrity": "sha512-73lu0Le1zt+etNTS+kJsn+0TJzpy8aiTT6TCkVuJyUY9yxLcKXdV7mYRaz4U7jEYn39MKpndGFXkA7Qq0u16dw==" + }, + "@spectrum-css/underlay": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@spectrum-css/underlay/-/underlay-2.0.7.tgz", + "integrity": "sha512-KF3Yre/y89enQpoaFEwuPCx8tHTYeQJBjpxqAgamo+XS2ENz4Cbxxy+xJJX9AUiGwNe3LNKN5iSTnZ655DBmEA==" + }, + "@spectrum-css/vars": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@spectrum-css/vars/-/vars-2.3.0.tgz", + "integrity": "sha512-k4YwFbv9+7QzBrc3ezvGuHxI3nDTG/6qJsAmhYS1vEyxpxjKmRfiz59P+fqT12PQNjxeNYHyVz1kRgaLA6gw2w==" + }, + "clipboard": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/clipboard/-/clipboard-2.0.6.tgz", + "integrity": "sha512-g5zbiixBRk/wyKakSwCKd7vQXDjFnAMGHoEyBogG/bw9kTD9GvdAvaoRR1ALcEzt3pVKxZR0pViekPMIS0QyGg==", + "requires": { + "good-listener": "^1.2.2", + "select": "^1.1.2", + "tiny-emitter": "^2.0.0" + } + }, + "color-blind": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/color-blind/-/color-blind-0.1.1.tgz", + "integrity": "sha1-1s+XtjX7ZgXJ3MSO+1jV65cqNx4=", + "requires": { + "onecolor": "^2.5.0" + } + }, + "commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" + }, + "d3": { + "version": "5.16.0", + "resolved": "https://registry.npmjs.org/d3/-/d3-5.16.0.tgz", + "integrity": "sha512-4PL5hHaHwX4m7Zr1UapXW23apo6pexCgdetdJ5kTmADpG/7T9Gkxw0M0tf/pjoB63ezCCm0u5UaFYy2aMt0Mcw==", + "requires": { + "d3-array": "1", + "d3-axis": "1", + "d3-brush": "1", + "d3-chord": "1", + "d3-collection": "1", + "d3-color": "1", + "d3-contour": "1", + "d3-dispatch": "1", + "d3-drag": "1", + "d3-dsv": "1", + "d3-ease": "1", + "d3-fetch": "1", + "d3-force": "1", + "d3-format": "1", + "d3-geo": "1", + "d3-hierarchy": "1", + "d3-interpolate": "1", + "d3-path": "1", + "d3-polygon": "1", + "d3-quadtree": "1", + "d3-random": "1", + "d3-scale": "2", + "d3-scale-chromatic": "1", + "d3-selection": "1", + "d3-shape": "1", + "d3-time": "1", + "d3-time-format": "2", + "d3-timer": "1", + "d3-transition": "1", + "d3-voronoi": "1", + "d3-zoom": "1" + } + }, + "d3-3d": { + "version": "0.0.9", + "resolved": "https://registry.npmjs.org/d3-3d/-/d3-3d-0.0.9.tgz", + "integrity": "sha512-Rvcgy5QZriGQAgoCzxyBkC8yZXKghCFw1RHARp/ybvrapFseMDyIbVPaBUgtNt8k3JDeEOdgN7OhFWhWB9m4Gg==" + }, + "d3-array": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-1.2.4.tgz", + "integrity": "sha512-KHW6M86R+FUPYGb3R5XiYjXPq7VzwxZ22buHhAEVG5ztoEcZZMLov530mmccaqA1GghZArjQV46fuc8kUqhhHw==" + }, + "d3-axis": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-1.0.12.tgz", + "integrity": "sha512-ejINPfPSNdGFKEOAtnBtdkpr24c4d4jsei6Lg98mxf424ivoDP2956/5HDpIAtmHo85lqT4pruy+zEgvRUBqaQ==" + }, + "d3-brush": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-1.1.6.tgz", + "integrity": "sha512-7RW+w7HfMCPyZLifTz/UnJmI5kdkXtpCbombUSs8xniAyo0vIbrDzDwUJB6eJOgl9u5DQOt2TQlYumxzD1SvYA==", + "requires": { + "d3-dispatch": "1", + "d3-drag": "1", + "d3-interpolate": "1", + "d3-selection": "1", + "d3-transition": "1" + } + }, + "d3-cam02": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/d3-cam02/-/d3-cam02-0.1.5.tgz", + "integrity": "sha1-5+s00G3gOtDJJBNKCoRmbZCWges=", + "requires": { + "d3-color": "1" + } + }, + "d3-chord": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/d3-chord/-/d3-chord-1.0.6.tgz", + "integrity": "sha512-JXA2Dro1Fxw9rJe33Uv+Ckr5IrAa74TlfDEhE/jfLOaXegMQFQTAgAw9WnZL8+HxVBRXaRGCkrNU7pJeylRIuA==", + "requires": { + "d3-array": "1", + "d3-path": "1" + } + }, + "d3-collection": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/d3-collection/-/d3-collection-1.0.7.tgz", + "integrity": "sha512-ii0/r5f4sjKNTfh84Di+DpztYwqKhEyUlKoPrzUFfeSkWxjW49xU2QzO9qrPrNkpdI0XJkfzvmTu8V2Zylln6A==" + }, + "d3-color": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-1.4.1.tgz", + "integrity": "sha512-p2sTHSLCJI2QKunbGb7ocOh7DgTAn8IrLx21QRc/BSnodXM4sv6aLQlnfpvehFMLZEfBc6g9pH9SWQccFYfJ9Q==" + }, + "d3-contour": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-1.3.2.tgz", + "integrity": "sha512-hoPp4K/rJCu0ladiH6zmJUEz6+u3lgR+GSm/QdM2BBvDraU39Vr7YdDCicJcxP1z8i9B/2dJLgDC1NcvlF8WCg==", + "requires": { + "d3-array": "^1.1.1" + } + }, + "d3-dispatch": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-1.0.6.tgz", + "integrity": "sha512-fVjoElzjhCEy+Hbn8KygnmMS7Or0a9sI2UzGwoB7cCtvI1XpVN9GpoYlnb3xt2YV66oXYb1fLJ8GMvP4hdU1RA==" + }, + "d3-drag": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-1.2.5.tgz", + "integrity": "sha512-rD1ohlkKQwMZYkQlYVCrSFxsWPzI97+W+PaEIBNTMxRuxz9RF0Hi5nJWHGVJ3Om9d2fRTe1yOBINJyy/ahV95w==", + "requires": { + "d3-dispatch": "1", + "d3-selection": "1" + } + }, + "d3-dsv": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-1.2.0.tgz", + "integrity": "sha512-9yVlqvZcSOMhCYzniHE7EVUws7Fa1zgw+/EAV2BxJoG3ME19V6BQFBwI855XQDsxyOuG7NibqRMTtiF/Qup46g==", + "requires": { + "commander": "2", + "iconv-lite": "0.4", + "rw": "1" + } + }, + "d3-ease": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-1.0.7.tgz", + "integrity": "sha512-lx14ZPYkhNx0s/2HX5sLFUI3mbasHjSSpwO/KaaNACweVwxUruKyWVcb293wMv1RqTPZyZ8kSZ2NogUZNcLOFQ==" + }, + "d3-fetch": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-1.2.0.tgz", + "integrity": "sha512-yC78NBVcd2zFAyR/HnUiBS7Lf6inSCoWcSxFfw8FYL7ydiqe80SazNwoffcqOfs95XaLo7yebsmQqDKSsXUtvA==", + "requires": { + "d3-dsv": "1" + } + }, + "d3-force": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-1.2.1.tgz", + "integrity": "sha512-HHvehyaiUlVo5CxBJ0yF/xny4xoaxFxDnBXNvNcfW9adORGZfyNF1dj6DGLKyk4Yh3brP/1h3rnDzdIAwL08zg==", + "requires": { + "d3-collection": "1", + "d3-dispatch": "1", + "d3-quadtree": "1", + "d3-timer": "1" + } + }, + "d3-format": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-1.4.5.tgz", + "integrity": "sha512-J0piedu6Z8iB6TbIGfZgDzfXxUFN3qQRMofy2oPdXzQibYGqPB/9iMcxr/TGalU+2RsyDO+U4f33id8tbnSRMQ==" + }, + "d3-geo": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-1.12.1.tgz", + "integrity": "sha512-XG4d1c/UJSEX9NfU02KwBL6BYPj8YKHxgBEw5om2ZnTRSbIcego6dhHwcxuSR3clxh0EpE38os1DVPOmnYtTPg==", + "requires": { + "d3-array": "1" + } + }, + "d3-hierarchy": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-1.1.9.tgz", + "integrity": "sha512-j8tPxlqh1srJHAtxfvOUwKNYJkQuBFdM1+JAUfq6xqH5eAqf93L7oG1NVqDa4CpFZNvnNKtCYEUC8KY9yEn9lQ==" + }, + "d3-hsluv": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/d3-hsluv/-/d3-hsluv-0.1.2.tgz", + "integrity": "sha512-nDmxHpYqjrDSEaS9F0S8lufW75Xs+JuzbVBTJsgUMTDhZVNTwVZsNVG9uiGv62KwL0HR5Fe2bxO0/Z0ZAMOu2g==", + "requires": { + "d3-color": "^1.0.3" + } + }, + "d3-hsv": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/d3-hsv/-/d3-hsv-0.1.0.tgz", + "integrity": "sha1-yVvomzQXeinnCghI25XX8CYeqZs=", + "requires": { + "d3-color": "1" + } + }, + "d3-interpolate": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-1.4.0.tgz", + "integrity": "sha512-V9znK0zc3jOPV4VD2zZn0sDhZU3WAE2bmlxdIwwQPPzPjvyLkd8B3JUVdS1IDUFDkWZ72c9qnv1GK2ZagTZ8EA==", + "requires": { + "d3-color": "1" + } + }, + "d3-path": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-1.0.9.tgz", + "integrity": "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==" + }, + "d3-polygon": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-1.0.6.tgz", + "integrity": "sha512-k+RF7WvI08PC8reEoXa/w2nSg5AUMTi+peBD9cmFc+0ixHfbs4QmxxkarVal1IkVkgxVuk9JSHhJURHiyHKAuQ==" + }, + "d3-quadtree": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-1.0.7.tgz", + "integrity": "sha512-RKPAeXnkC59IDGD0Wu5mANy0Q2V28L+fNe65pOCXVdVuTJS3WPKaJlFHer32Rbh9gIo9qMuJXio8ra4+YmIymA==" + }, + "d3-random": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/d3-random/-/d3-random-1.1.2.tgz", + "integrity": "sha512-6AK5BNpIFqP+cx/sreKzNjWbwZQCSUatxq+pPRmFIQaWuoD+NrbVWw7YWpHiXpCQ/NanKdtGDuB+VQcZDaEmYQ==" + }, + "d3-scale": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-2.2.2.tgz", + "integrity": "sha512-LbeEvGgIb8UMcAa0EATLNX0lelKWGYDQiPdHj+gLblGVhGLyNbaCn3EvrJf0A3Y/uOOU5aD6MTh5ZFCdEwGiCw==", + "requires": { + "d3-array": "^1.2.0", + "d3-collection": "1", + "d3-format": "1", + "d3-interpolate": "1", + "d3-time": "1", + "d3-time-format": "2" + } + }, + "d3-scale-chromatic": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-1.5.0.tgz", + "integrity": "sha512-ACcL46DYImpRFMBcpk9HhtIyC7bTBR4fNOPxwVSl0LfulDAwyiHyPOTqcDG1+t5d4P9W7t/2NAuWu59aKko/cg==", + "requires": { + "d3-color": "1", + "d3-interpolate": "1" + } + }, + "d3-selection": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-1.4.2.tgz", + "integrity": "sha512-SJ0BqYihzOjDnnlfyeHT0e30k0K1+5sR3d5fNueCNeuhZTnGw4M4o8mqJchSwgKMXCNFo+e2VTChiSJ0vYtXkg==" + }, + "d3-shape": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-1.3.7.tgz", + "integrity": "sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==", + "requires": { + "d3-path": "1" + } + }, + "d3-time": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-1.1.0.tgz", + "integrity": "sha512-Xh0isrZ5rPYYdqhAVk8VLnMEidhz5aP7htAADH6MfzgmmicPkTo8LhkLxci61/lCB7n7UmE3bN0leRt+qvkLxA==" + }, + "d3-time-format": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-2.3.0.tgz", + "integrity": "sha512-guv6b2H37s2Uq/GefleCDtbe0XZAuy7Wa49VGkPVPMfLL9qObgBST3lEHJBMUp8S7NdLQAGIvr2KXk8Hc98iKQ==", + "requires": { + "d3-time": "1" + } + }, + "d3-timer": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-1.0.10.tgz", + "integrity": "sha512-B1JDm0XDaQC+uvo4DT79H0XmBskgS3l6Ve+1SBCfxgmtIb1AVrPIoqd+nPSv+loMX8szQ0sVUhGngL7D5QPiXw==" + }, + "d3-transition": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-1.3.2.tgz", + "integrity": "sha512-sc0gRU4PFqZ47lPVHloMn9tlPcv8jxgOQg+0zjhfZXMQuvppjG6YuwdMBE0TuqCZjeJkLecku/l9R0JPcRhaDA==", + "requires": { + "d3-color": "1", + "d3-dispatch": "1", + "d3-ease": "1", + "d3-interpolate": "1", + "d3-selection": "^1.1.0", + "d3-timer": "1" + } + }, + "d3-voronoi": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/d3-voronoi/-/d3-voronoi-1.1.4.tgz", + "integrity": "sha512-dArJ32hchFsrQ8uMiTBLq256MpnZjeuBtdHpaDlYuQyjU0CVzCJl/BVW+SkszaAeH95D/8gxqAhgx0ouAWAfRg==" + }, + "d3-zoom": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-1.8.3.tgz", + "integrity": "sha512-VoLXTK4wvy1a0JpH2Il+F2CiOhVu7VRXWF5M/LroMIh3/zBAC3WAt7QoIvPibOavVo20hN6/37vwAsdBejLyKQ==", + "requires": { + "d3-dispatch": "1", + "d3-drag": "1", + "d3-interpolate": "1", + "d3-selection": "1", + "d3-transition": "1" + } + }, + "delegate": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/delegate/-/delegate-3.2.0.tgz", + "integrity": "sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw==" + }, + "good-listener": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/good-listener/-/good-listener-1.2.2.tgz", + "integrity": "sha1-1TswzfkxPf+33JoNR3CWqm0UXFA=", + "requires": { + "delegate": "^3.1.2" + } + }, + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "loadicons": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/loadicons/-/loadicons-1.0.0.tgz", + "integrity": "sha512-KSywiudfuOK5sTdhNMM8hwRpMxZ5TbQlU4ZijMxUFwRW7jpxUmb9YJoLIzDn7+xuxeLzCZWBmLJS2JDjDWCpsw==" + }, + "onecolor": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/onecolor/-/onecolor-2.5.0.tgz", + "integrity": "sha1-Ila2UdyAfBAfAK7b1JklxXpEMcE=" + }, + "rw": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", + "integrity": "sha1-P4Yt+pGrdmsUiF700BEkv9oHT7Q=" + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "select": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/select/-/select-1.1.2.tgz", + "integrity": "sha1-DnNQrN7ICxEIUoeG7B1EGNEbOW0=" + }, + "tiny-emitter": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.1.0.tgz", + "integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==" + } + } +} diff --git a/packages/ui/package.json b/packages/ui/package.json index aa3ff061..09734a74 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -1,18 +1,20 @@ { "name": "@adobe/leonardo-ui", "private": true, - "version": "1.0.0", + "version": "1.3.2", "description": "Demonstration UI for Leonardo", "repository": "git@github.com:adobe/leonardo.git", "author": "Nate Baldwin ", "scripts": { - "dev": "yarn copyIcons && npx parcel src/index.html src/demo.html", + "serve": "npx parcel src/index.html src/theme.html src/converter.html src/contrast-checker.html src/demo.html --no-hmr", + "dev": "yarn copyIcons && yarn serve", "copyIcons": "mkdir -p dist && yarn copyWorkflowIcons && yarn copyUIIcons", "copyWorkflowIcons": "cp -r ../../node_modules/@adobe/spectrum-css-workflow-icons/dist/spectrum-icons.svg dist/", "copyUIIcons": "cp -r ../../node_modules/@spectrum-css/icon/dist/spectrum-css-icons.svg dist/", + "copyCNAME": "cp -r src/CNAME dist/CNAME", "postPublish": "yarn deploySite", - "buildSite": "npx parcel build src/index.html src/demo.html --public-url ./", - "deploySite": "yarn buildSite && yarn copyIcons && npx ghpages -p dist" + "buildSite": "npx parcel build src/index.html src/theme.html src/converter.html src/contrast-checker.html src/demo.html --public-url ./ && yarn copyCNAME && yarn copyIcons", + "deploySite": "yarn buildSite && npx ghpages -p dist" }, "keywords": [ "accessibility", @@ -35,22 +37,27 @@ ], "license": "Apache-2.0", "devDependencies": { + "ghpages": "^0.0.10", "parcel-bundler": "^1.12.4", "sass": "^1.23.6" }, "dependencies": { "@adobe/focus-ring-polyfill": "^0.1.5", - "@adobe/leonardo-contrast-colors": "^0.1.0", + "@adobe/leonardo-contrast-colors": "^1.0.0-alpha.8", "@adobe/spectrum-css-workflow-icons": "^1.0.0", "@spectrum-css/alert": "^2.0.2", "@spectrum-css/button": "^2.0.2", "@spectrum-css/buttongroup": "^2.0.2", "@spectrum-css/checkbox": "^2.0.2", + "@spectrum-css/colorhandle": "^1.0.0-beta.1", + "@spectrum-css/colorloupe": "^1.0.0-beta.1", + "@spectrum-css/colorslider": "^1.0.0-beta.2", "@spectrum-css/dialog": "^2.0.2", "@spectrum-css/dropdown": "^2.1.1", "@spectrum-css/fieldgroup": "^2.0.2", "@spectrum-css/fieldlabel": "^2.0.2", "@spectrum-css/icon": "^2.0.2", + "@spectrum-css/illustratedmessage": "2.0.0", "@spectrum-css/link": "^2.0.2", "@spectrum-css/menu": "^2.1.1", "@spectrum-css/page": "^2.0.2", @@ -58,17 +65,18 @@ "@spectrum-css/slider": "^2.0.2", "@spectrum-css/tabs": "^2.1.1", "@spectrum-css/textfield": "^2.0.2", + "@spectrum-css/toast": "^2.0.4", "@spectrum-css/tooltip": "^2.0.2", "@spectrum-css/typography": "^2.0.2", "@spectrum-css/underlay": "^2.0.2", "@spectrum-css/vars": "^2.0.2", "clipboard": "^2.0.4", + "color-blind": "^0.1.1", "d3": "^5.12.0", "d3-3d": "0.0.9", "d3-cam02": "^0.1.5", "d3-hsluv": "^0.1.2", "d3-hsv": "^0.1.0", - "ghpages": "^0.0.10", "loadicons": "^1.0.0" } } diff --git a/packages/ui/src/CNAME b/packages/ui/src/CNAME new file mode 100644 index 00000000..bf9d5ed2 --- /dev/null +++ b/packages/ui/src/CNAME @@ -0,0 +1 @@ +leonardocolor.io diff --git a/packages/ui/src/charts.js b/packages/ui/src/charts.js index e0a7670a..655ccbb9 100644 --- a/packages/ui/src/charts.js +++ b/packages/ui/src/charts.js @@ -15,7 +15,7 @@ import * as d3 from 'd3'; import * as d33d from 'd3-3d'; Object.assign(d3, d33d); -import contrastColors from '@adobe/leonardo-contrast-colors'; +import * as contrastColors from '@adobe/leonardo-contrast-colors'; let chart3dColorspace = document.getElementById('chart3dColorspace'); let dest = document.querySelector('.chart3D'); @@ -133,6 +133,9 @@ let yScale3d = d3._3d() .scale(scale); function processData(data, tt){ + let colorInterpSpace = document.querySelector('select[name="mode"]').value; + let chartColors = getChartColors(colorInterpSpace); + let color = d3.scaleOrdinal(chartColors); /* ----------- GRID ----------- */ @@ -495,6 +498,7 @@ function toggleGraphs() { } function createAllCharts(mode) { + mode = document.getElementById('chart2dColorspace').value; let chart3 = document.getElementById('chart3Wrapper'); if (mode=="LCH") { @@ -562,6 +566,7 @@ function createAllCharts(mode) { let chartColors = []; + function getChartColors(mode) { let shift = document.getElementById('shiftInput').value; @@ -578,13 +583,13 @@ function getChartColors(mode) { } -function showCharts(mode) { +function showCharts(mode, interpolation) { document.getElementById('chart1').innerHTML = ' '; document.getElementById('chart2').innerHTML = ' '; document.getElementById('chart3').innerHTML = ' '; document.getElementById('contrastChart').innerHTML = ' '; - chartColors = getChartColors(mode); + chartColors = getChartColors(interpolation); createAllCharts(mode); }; diff --git a/packages/ui/src/contrast-checker.html b/packages/ui/src/contrast-checker.html new file mode 100644 index 00000000..4568feeb --- /dev/null +++ b/packages/ui/src/contrast-checker.html @@ -0,0 +1,142 @@ + + + + + + Leonardo: Color Converter + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +

Leonardo

+
+ +
+
+
+
+

Color Contrast Checker

+

Uses WCAG relative luminance formula and Level AA criteria

+
+ +
+
+
+
+
+ + +
+
+
+ + + + + +
+ + +
+
+ +
+ + +
+
+
+ + + +
+
+

Supports hex, rgb, hsl, hsv, hsluv, lab, lch, and jab with alpha

+
+
+ +
+

Contrast ratio:

+
+
+
+ +
+ Large text is 18pt or 14pt bold. + Small text is less than 18pt or less than 14pt bold. + + + +
+
+ + +
+
+ + + + diff --git a/packages/ui/src/contrast-checker.js b/packages/ui/src/contrast-checker.js new file mode 100644 index 00000000..699420aa --- /dev/null +++ b/packages/ui/src/contrast-checker.js @@ -0,0 +1,445 @@ +/* +Copyright 2019 Adobe. All rights reserved. +This file is licensed to you under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. You may obtain a copy +of the License at http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software distributed under +the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS +OF ANY KIND, either express or implied. See the License for the specific language +governing permissions and limitations under the License. +*/ +import '@spectrum-css/vars/dist/spectrum-global.css'; +import '@spectrum-css/vars/dist/spectrum-medium.css'; +import '@spectrum-css/vars/dist/spectrum-light.css'; + +import '@spectrum-css/page/dist/index-vars.css'; +import '@spectrum-css/icon/dist/index-vars.css'; +import '@spectrum-css/link/dist/index-vars.css'; +import '@spectrum-css/alert/dist/index-vars.css'; +import '@spectrum-css/radio/dist/index-vars.css'; +import '@spectrum-css/dialog/dist/index-vars.css'; +import '@spectrum-css/button/dist/index-vars.css'; +import '@spectrum-css/fieldgroup/dist/index-vars.css'; +import '@spectrum-css/textfield/dist/index-vars.css'; +import '@spectrum-css/dropdown/dist/index-vars.css'; +import '@spectrum-css/fieldlabel/dist/index-vars.css'; +import '@spectrum-css/checkbox/dist/index-vars.css'; +import '@spectrum-css/buttongroup/dist/index-vars.css'; +import '@spectrum-css/tooltip/dist/index-vars.css'; +import '@spectrum-css/slider/dist/index-vars.css'; +import '@spectrum-css/colorslider/dist/index-vars.css'; +import '@spectrum-css/colorhandle/dist/index-vars.css'; +import '@spectrum-css/colorloupe/dist/index-vars.css'; +import '@spectrum-css/tabs/dist/index-vars.css'; +import '@spectrum-css/typography/dist/index-vars.css'; + +import './scss/style.scss'; +import './scss/colorinputs.scss'; +import './scss/converter.scss'; + +import '@adobe/focus-ring-polyfill'; + +import loadIcons from 'loadicons'; +loadIcons('./spectrum-css-icons.svg'); +loadIcons('./spectrum-icons.svg'); + +import * as d3 from 'd3'; + +// Import d3 plugins and add them to the d3 namespace +import * as d3cam02 from 'd3-cam02'; +import * as d3hsluv from 'd3-hsluv'; +import * as d3hsv from 'd3-hsv'; +import * as d33d from 'd3-3d'; +Object.assign(d3, d3cam02, d3hsluv, d3hsv, d33d); + +import * as contrastColors from '@adobe/leonardo-contrast-colors'; + +// expose functions so they can be ran in the console +window.luminance = contrastColors.luminance; +window.contrast = contrastColors.contrast; +window.contrastColors = contrastColors; + +let inputForeground = document.getElementById('foregroundInput'); +let inputBackground = document.getElementById('backgroundInput'); +let output = document.getElementById('output'); +// let type = document.getElementById('type'); +let colorInputForeground = document.getElementById('foregroundColorInput'); +let colorInputBackground = document.getElementById('backgroundColorInput'); + +function setup() { + inputForeground.defaultValue = 'rgb(0, 0, 0)'; + inputBackground.defaultValue = 'rgb(255, 255, 255)'; +} + +function alphaBlend(c1, c2) { + let r1, g1, b1, a1; + let r2, g2, b2; + + r1 = c1[0]; + g1 = c1[1]; + b1 = c1[2]; + a1 = c1[3]; + + r2 = c2[0]; + g2 = c2[1]; + b2 = c2[2]; + + let r3 = r2 + (r1-r2)*a1 + let g3 = g2 + (g1-g2)*a1 + let b3 = b2 + (b1-b2)*a1 + + let c3 = [r3, g3, b3]; + + return c3; +} + +function inputConvert(val) { + let valNums, valArr; + let a = 1; + + if(val.match(/^rgb\(/)) { + valNums = val.match(/\(.*?\)/g).toString().replace("(", "").replace(")", "").trim(); // find numbers only + valArr = valNums.split(','); // split numbers into array + // convert to proper format (percentages) + val = d3.rgb(Number(valArr[0]), Number(valArr[1]), Number(valArr[2])).formatRgb(); + } + // RGBa + if(val.match(/^rgba\(/)) { + valNums = val.match(/\(.*?\)/g).toString().replace("(", "").replace(")", "").trim(); // find numbers only + valArr = valNums.split(','); // split numbers into array + // convert to proper format (percentages) + val = d3.rgb(Number(valArr[0]), Number(valArr[1]), Number(valArr[2]), Number(valArr[3])).formatRgb(); + a = d3.rgb(val).opacity; + } + if(val.match(/^hsl\(/)) { + valNums = val.match(/\(.*?\)/g).toString().replace("(", "").replace(")", "").trim(); // find numbers only + valArr = valNums.split(','); // split numbers into array + // convert to proper format (percentages) + val = d3.hsl(Number(valArr[0]), Number(valArr[1]/100), Number(valArr[2]/100)).formatHsl(); + } + // HSLa + if(val.match(/^hsla\(/)) { + valNums = val.match(/\(.*?\)/g).toString().replace("(", "").replace(")", "").trim(); // find numbers only + valArr = valNums.split(','); // split numbers into array + // convert to proper format (percentages) + val = d3.hsl(Number(valArr[0]), Number(valArr[1]/100), Number(valArr[2]/100), Number(valArr[3])).formatHsl(); + a = d3.rgb(val).opacity; + } + if(val.match(/^hsv\(/)) { + valNums = val.match(/\(.*?\)/g).toString().replace("(", "").replace(")", "").trim(); // find numbers only + valArr = valNums.split(','); // split numbers into array + val = d3.hsv(Number(valArr[0]), Number(valArr[1]/100), Number(valArr[2]/100)).formatHsl(); + } + // HSVa + if(val.match(/^hsva\(/)) { + valNums = val.match(/\(.*?\)/g).toString().replace("(", "").replace(")", "").trim(); // find numbers only + valArr = valNums.split(','); // split numbers into array + val = d3.hsv(Number(valArr[0]), Number(valArr[1]/100), Number(valArr[2]/100), Number(valArr[3])).formatHsl(); + a = d3.rgb(val).opacity; + } + if(val.match(/^lab\(/)) { + valNums = val.match(/\(.*?\)/g).toString().replace("(", "").replace(")", "").trim(); // find numbers only + valArr = valNums.split(','); // split numbers into array + val = d3.lab(Number(valArr[0]), Number(valArr[1]), Number(valArr[2])).formatHsl(); + } + // LABa + if(val.match(/^laba\(/)) { + valNums = val.match(/\(.*?\)/g).toString().replace("(", "").replace(")", "").trim(); // find numbers only + valArr = valNums.split(','); // split numbers into array + val = d3.lab(Number(valArr[0]), Number(valArr[1]), Number(valArr[2]), Number(valArr[3])).formatHsl(); + a = d3.rgb(val).opacity; + } + if(val.match(/^lch\(/)) { + valNums = val.match(/\(.*?\)/g).toString().replace("(", "").replace(")", "").trim(); // find numbers only + valArr = valNums.split(','); // split numbers into array + val = d3.hcl(Number(valArr[2]), Number(valArr[1]), Number(valArr[0])).formatHsl(); + } + // LCHa + if(val.match(/^lcha\(/)) { + valNums = val.match(/\(.*?\)/g).toString().replace("(", "").replace(")", "").trim(); // find numbers only + valArr = valNums.split(','); // split numbers into array + val = d3.hcl(Number(valArr[2]), Number(valArr[1]), Number(valArr[0]), Number(valArr[3])).formatHsl(); + a = d3.rgb(val).opacity; + } + if(val.match(/^jab\(/)) { + valNums = val.match(/\(.*?\)/g).toString().replace("(", "").replace(")", "").trim(); // find numbers only + valArr = valNums.split(','); // split numbers into array + val = d3.jab(Number(valArr[0]), Number(valArr[1]), Number(valArr[2])).formatHsl(); + } + // JABa + if(val.match(/^jaba\(/)) { + valNums = val.match(/\(.*?\)/g).toString().replace("(", "").replace(")", "").trim(); // find numbers only + valArr = valNums.split(','); // split numbers into array + val = d3.jab(Number(valArr[0]), Number(valArr[1]), Number(valArr[2]), Number(valArr[3])).formatHsl(); + a = d3.rgb(val).opacity; + } + if(val.match(/^hsluv\(/)) { + valNums = val.match(/\(.*?\)/g).toString().replace("(", "").replace(")", "").trim(); // find numbers only + valArr = valNums.split(','); // split numbers into array + val = d3.hsluv(Number(valArr[0]), Number(valArr[1]), Number(valArr[2])).formatHsl(); + } + // HSLuva + if(val.match(/^hsluva\(/)) { + valNums = val.match(/\(.*?\)/g).toString().replace("(", "").replace(")", "").trim(); // find numbers only + valArr = valNums.split(','); // split numbers into array + val = d3.hsluv(Number(valArr[0]), Number(valArr[1]), Number(valArr[2]), Number(valArr[3])).formatHsl(); + a = d3.rgb(val).opacity; + } + let r = d3.rgb(val).r; + let g = d3.rgb(val).g; + let b = d3.rgb(val).b; + + + return [Number(r.toFixed()), Number(g.toFixed()), Number(b.toFixed()), Number(a.toFixed(2))]; +} + +function colorInputSync() { + let valFg = inputForeground.value; + let colorInputForeground = document.getElementById('foregroundColorInput'); + let valFgRgb = inputConvert(valFg); + let colorInputFgVal = d3.rgb(valFgRgb[0], valFgRgb[1], valFgRgb[2]).formatHex(); + colorInputForeground.value = colorInputFgVal; + + let valBg = inputBackground.value; + let colorInputBackground = document.getElementById('backgroundColorInput'); + let valBgRgb = inputConvert(valBg); + let colorInputBgVal = d3.rgb(valBgRgb[0], valBgRgb[1], valBgRgb[2]).formatHex(); + colorInputBackground.value = colorInputBgVal; +} + +function colorSlider() { + let input = document.getElementById('foregroundInput'); + let inputColor = input.value; + let grad = document.getElementById('alphaSliderGradient'); + let range = document.getElementById('alphaSliderRange'); + let handle = document.getElementById('alphaSliderHandle'); + let handleWrap = document.getElementById('alphaSliderHandleWrap'); + let sliderWidth = 208; + + let c = inputConvert(inputColor); + let color = 'rgba('+ c[0] + ', ' + c[1] + ', ' + c[2] + ', ' + c[3] + ')'; + + let start = 'rgba(' + d3.rgb(color).r + ', ' + d3.rgb(color).g + ', ' + d3.rgb(color).b + ', ' + '0' + ')'; // transform this to rgba fomat + let end = 'rgba(' + d3.rgb(color).r + ', ' + d3.rgb(color).g + ', ' + d3.rgb(color).b + ', ' + '1' + ')'; // transform this to rgba fomat + let linearGrad = 'linear-gradient(to right, ' + start + ' 0%, ' + end + ' 100%)'; + + handle.style.backgroundColor = color; + + grad.style.backgroundImage = linearGrad; + range.value = c[3] * 100; + handleWrap.style.left = sliderWidth * c[3]; +} + +function sliderRangeInteraction(value) { + let sliderWidth = 208; + let handleWrap = document.getElementById('alphaSliderHandleWrap'); + let pos = value / 100; + let posString = ', ' + `${pos}` + ')'; + + let colorInput = document.getElementById('foregroundInput'); + let inputVal = colorInput.value; + let newVal = inputVal; + + if(inputVal.match(/^#/)) { + let rgb = d3.rgb(inputVal).formatRgb(); // first put in RGB format + newVal = rgb.replace('rgb', 'rgba').replace(')', posString); + } + if(inputVal.match(/^rgb\(/)) { + newVal = inputVal.replace('rgb', 'rgba').replace(')', posString); + } + if(inputVal.match(/^hsl\(/)) { + newVal = inputVal.replace('hsl', 'hsla').replace(')', posString); + } + if(inputVal.match(/^hsv\(/)) { + newVal = inputVal.replace('hsv', 'hsva').replace(')', posString); + } + if(inputVal.match(/^lab\(/)) { + newVal = inputVal.replace('lab', 'laba').replace(')', posString); + } + if(inputVal.match(/^lch\(/)) { + newVal = inputVal.replace('lch', 'lcha').replace(')', posString); + } + if(inputVal.match(/^jab\(/)) { + newVal = inputVal.replace('jab', 'jaba').replace(')', posString); + } + if(inputVal.match(/^hsluv\(/)) { + newVal = inputVal.replace('hsluv', 'hsluva').replace(')', posString); + } + if( + inputVal.match(/^rgba\(/) + || inputVal.match(/^hsla\(/) + || inputVal.match(/^hsva\(/) + || inputVal.match(/^laba\(/) + || inputVal.match(/^lcha\(/) + || inputVal.match(/^jaba\(/) + || inputVal.match(/^hsluva\(/) + ) { + let alphaString = pos + ')'; + newVal = inputVal.replace(/[^,]+$/, alphaString); + } + + colorInput.value = newVal; + handleWrap.style.left = sliderWidth * pos; + + returnContrast(); +} + + +function returnContrast() { + colorInputSync(); + colorSlider(); + + let foregroundInput = inputForeground.value; + let backgroundInput = inputBackground.value; + let output = document.getElementById('ratioOutput'); + output.innerHTML = ' '; + + let rat = document.createElement('span'); + rat.className = 'contrastRatio'; + + // let fg = [d3.rgb(foreground).r, d3.rgb(foreground).g, d3.rgb(foreground).b]; + // let bg = [d3.rgb(background).r, d3.rgb(background).g, d3.rgb(background).b]; + + let fg = inputConvert(foregroundInput); + let bg = inputConvert(backgroundInput); + + let foreground; + if (fg.length > 3 ) { + foreground = 'rgba('+ fg[0] + ', ' + fg[1] + ', ' + fg[2] + ', ' + fg[3] + ')'; + } + else { + foreground = 'rgb('+ fg[0] + ', ' + fg[1] + ', ' + fg[2] + ')'; + } + let background = 'rgb('+ bg[0] + ', ' + bg[1] + ', ' + bg[2] + ')'; + + let baseV = (d3.hsluv(backgroundInput).v) / 100; + + // blend alpha foreground over background in case color is transparent + let fgBlend = alphaBlend(fg, bg); + + let ratio = contrastColors.contrast(fgBlend, bg, baseV); + // get rid of that negative ratio... + if (ratio < 0) { + ratio = ratio * -1; + } + if (isNaN(ratio)) { + ratio = '-'; + } + + let ratioText = document.createTextNode(ratio.toFixed(2)); + let ratioToOneText = document.createTextNode(':1'); + let ratioToOne = document.createElement('span'); + ratioToOne.classList.add('ratioToOne'); + ratioToOne.appendChild(ratioToOneText); + + let lgTextWrap = document.createElement('div'); + let smTextWrap = document.createElement('div'); + let uiTextWrap = document.createElement('div'); + lgTextWrap.classList.add('passFail'); + smTextWrap.classList.add('passFail'); + uiTextWrap.classList.add('passFail'); + let lgTextHead = document.createTextNode('Large text'); + let smTextHead = document.createTextNode('Small text'); + let uiTextHead = document.createTextNode('Graphics and UI components'); + let lgHeading = document.createElement('span'); + lgHeading.classList.add('heading--passFail', 'spectrum-Detail', 'spectrum-Detail--M'); + let smHeading = document.createElement('span'); + smHeading.classList.add('heading--passFail', 'spectrum-Detail', 'spectrum-Detail--M'); + let uiHeading = document.createElement('span'); + uiHeading.classList.add('heading--passFail', 'spectrum-Detail', 'spectrum-Detail--M'); + + lgHeading.appendChild(lgTextHead); + smHeading.appendChild(smTextHead); + uiHeading.appendChild(uiTextHead); + + let badgeLg = document.createElement('div'); + badgeLg.className = 'badge'; + let badgeSm = document.createElement('div'); + badgeSm.className = 'badge'; + let badgeUi = document.createElement('div'); + badgeUi.className = 'badge'; + + if(ratio < 3) { + badgeLg.classList.add('badge--error'); + badgeLg.classList.remove('badge--success'); + badgeLg.innerHTML = 'Fail (AA)'; + badgeSm.classList.add('badge--error'); + badgeSm.classList.remove('badge--success'); + badgeSm.innerHTML = 'Fail (AA)'; + badgeUi.classList.add('badge--error'); + badgeUi.classList.remove('badge--success'); + badgeUi.innerHTML = 'Fail (AA)'; + } + if(ratio > 3 && ratio < 4.5) { + badgeLg.classList.add('badge--success'); + badgeLg.classList.remove('badge--error'); + badgeLg.innerHTML = 'Pass (AA)'; + badgeSm.classList.add('badge--error'); + badgeSm.classList.remove('badge--success'); + badgeSm.innerHTML = 'Fail (AA)'; + badgeUi.classList.add('badge--success'); + badgeUi.classList.remove('badge--error'); + badgeUi.innerHTML = 'Pass (AA)'; + } + if(ratio > 4.5) { + badgeLg.classList.add('badge--success'); + badgeLg.classList.remove('badge--error'); + badgeLg.innerHTML = 'Pass (AA)'; + badgeSm.classList.add('badge--success'); + badgeSm.classList.remove('badge--error'); + badgeSm.innerHTML = 'Pass (AA)'; + badgeUi.classList.add('badge--success'); + badgeUi.classList.remove('badge--error'); + badgeUi.innerHTML = 'Pass (AA)'; + } + + lgTextWrap.appendChild(lgHeading); + lgTextWrap.appendChild(badgeLg); + smTextWrap.appendChild(smHeading); + smTextWrap.appendChild(badgeSm); + uiTextWrap.appendChild(uiHeading); + uiTextWrap.appendChild(badgeUi); + + rat.appendChild(ratioText); + rat.appendChild(ratioToOne); + output.appendChild(rat); + output.appendChild(lgTextWrap); + output.appendChild(smTextWrap); + output.appendChild(uiTextWrap); + + buildDemo(foreground, background); +} + +function buildDemo(text, background) { + let demo = document.getElementById('demoWrapper'); + let largeTxt = document.getElementById('largeText'); + let smallTxt = document.getElementById('smallText'); + let button = document.getElementById('demoButton'); + // let icon = document.getElementById('demoIcon'); + let buttonText = document.getElementById('demoButtonText'); + + demo.style.backgroundColor = background; + button.style.backgroundColor = background; + button.style.borderColor = text; + // icon.style.color = text; + buttonText.style.color = text; + largeTxt.style.color = text; + smallTxt.style.color = text; +} + +colorInputForeground.addEventListener('input', function() { inputForeground.value = colorInputForeground.value; returnContrast(); }); +colorInputBackground.addEventListener('input', function() { inputBackground.value = colorInputBackground.value; returnContrast(); }); +inputForeground.addEventListener('input', returnContrast); +inputBackground.addEventListener('input', returnContrast); +// type.addEventListener('input', returnColor); + +setup(); +returnContrast(); + +window.sliderRangeInteraction = sliderRangeInteraction; + +export { + sliderRangeInteraction, + returnContrast +} diff --git a/packages/ui/src/converter.html b/packages/ui/src/converter.html new file mode 100644 index 00000000..9ece4e89 --- /dev/null +++ b/packages/ui/src/converter.html @@ -0,0 +1,97 @@ + + + + + + Leonardo: Color Converter + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +

Leonardo

+
+ +
+
+
+
+

Color Converter

+ Built using d3-color +

Text field supports hex, rgb, hsl, hsv, hsluv, lab, lch, and jab (CAM02).

+
+
+
+ + + +
+
+ +
+ + + + +
+
+
+
+
+
+ +
+
+ + + + diff --git a/packages/ui/src/converter.js b/packages/ui/src/converter.js new file mode 100644 index 00000000..f5f3efb4 --- /dev/null +++ b/packages/ui/src/converter.js @@ -0,0 +1,196 @@ +/* +Copyright 2019 Adobe. All rights reserved. +This file is licensed to you under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. You may obtain a copy +of the License at http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software distributed under +the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS +OF ANY KIND, either express or implied. See the License for the specific language +governing permissions and limitations under the License. +*/ +import '@spectrum-css/vars/dist/spectrum-global.css'; +import '@spectrum-css/vars/dist/spectrum-medium.css'; +import '@spectrum-css/vars/dist/spectrum-light.css'; + +import '@spectrum-css/page/dist/index-vars.css'; +import '@spectrum-css/icon/dist/index-vars.css'; +import '@spectrum-css/link/dist/index-vars.css'; +import '@spectrum-css/alert/dist/index-vars.css'; +import '@spectrum-css/radio/dist/index-vars.css'; +import '@spectrum-css/dialog/dist/index-vars.css'; +import '@spectrum-css/button/dist/index-vars.css'; +import '@spectrum-css/fieldgroup/dist/index-vars.css'; +import '@spectrum-css/textfield/dist/index-vars.css'; +import '@spectrum-css/dropdown/dist/index-vars.css'; +import '@spectrum-css/fieldlabel/dist/index-vars.css'; +import '@spectrum-css/checkbox/dist/index-vars.css'; +import '@spectrum-css/buttongroup/dist/index-vars.css'; +import '@spectrum-css/tooltip/dist/index-vars.css'; +import '@spectrum-css/slider/dist/index-vars.css'; +import '@spectrum-css/tabs/dist/index-vars.css'; +import '@spectrum-css/typography/dist/index-vars.css'; + +import './scss/style.scss'; +import './scss/colorinputs.scss'; +import './scss/converter.scss'; + +import '@adobe/focus-ring-polyfill'; + +import loadIcons from 'loadicons'; +loadIcons('./spectrum-css-icons.svg'); +loadIcons('./spectrum-icons.svg'); + +import * as d3 from 'd3'; + +// Import d3 plugins and add them to the d3 namespace +import * as d3cam02 from 'd3-cam02'; +import * as d3hsluv from 'd3-hsluv'; +import * as d3hsv from 'd3-hsv'; +import * as d33d from 'd3-3d'; +Object.assign(d3, d3cam02, d3hsluv, d3hsv, d33d); + +let input = document.getElementById('input'); +let output = document.getElementById('output'); +let type = document.getElementById('type'); +let colorInput = document.getElementById('colorInput'); + +function convert(c, typeId = type.value) { + let A, B, C; + + if(typeId == 'Hex') { + return d3.rgb(c).formatHex(); + } + + else { + if(typeId == 'HSLuv') { + A = d3.hsluv(c).l; + B = d3.hsluv(c).u; + C = d3.hsluv(c).v; + } + if(typeId == 'HSL') { + A = d3.hsl(c).h; + B = d3.hsl(c).s * 100; + C = d3.hsl(c).l * 100; + } + if(typeId == 'HSV') { + A = d3.hsv(c).h; + B = d3.hsv(c).s * 100; + C = d3.hsv(c).v * 100; + } + if(typeId == 'CAM02') { + A = d3.jab(c).J; + B = d3.jab(c).a; + C = d3.jab(c).b; + } + if(typeId == 'Lab') { + A = d3.lab(c).l; + B = d3.lab(c).a; + C = d3.lab(c).b; + } + if(typeId == 'Lch') { + A = d3.hcl(c).l; + B = d3.hcl(c).c; + C = d3.hcl(c).h; + } + if(typeId == 'RGB') { + A = d3.rgb(c).r; + B = d3.rgb(c).g; + C = d3.rgb(c).b; + } + + return new Array(A.toFixed(), B.toFixed(), C.toFixed()); + } +} + +function returnColor() { + let val = input.value; + let valNums, valArr; + + if(val.match(/^hsl\(/)) { + valNums = val.match(/\(.*?\)/g).toString().replace("(", "").replace(")", "").trim(); // find numbers only + valArr = valNums.split(','); // split numbers into array + // convert to proper format (percentages) + val = d3.hsl(Number(valArr[0]), Number(valArr[1]/100), Number(valArr[2]/100)).formatHsl(); + } + if(val.match(/^hsv\(/)) { + valNums = val.match(/\(.*?\)/g).toString().replace("(", "").replace(")", "").trim(); // find numbers only + valArr = valNums.split(','); // split numbers into array + val = d3.hsv(Number(valArr[0]), Number(valArr[1]/100), Number(valArr[2]/100)).formatHsl(); + } + if(val.match(/^lab\(/)) { + valNums = val.match(/\(.*?\)/g).toString().replace("(", "").replace(")", "").trim(); // find numbers only + valArr = valNums.split(','); // split numbers into array + val = d3.lab(Number(valArr[0]), Number(valArr[1]), Number(valArr[2])).formatHsl(); + } + if(val.match(/^lch\(/)) { + valNums = val.match(/\(.*?\)/g).toString().replace("(", "").replace(")", "").trim(); // find numbers only + valArr = valNums.split(','); // split numbers into array + val = d3.hcl(Number(valArr[2]), Number(valArr[1]), Number(valArr[0])).formatHsl(); + } + if(val.match(/^jab\(/)) { + valNums = val.match(/\(.*?\)/g).toString().replace("(", "").replace(")", "").trim(); // find numbers only + valArr = valNums.split(','); // split numbers into array + val = d3.jab(Number(valArr[0]), Number(valArr[1]), Number(valArr[2])).formatHsl(); + } + if(val.match(/^hsluv\(/)) { + valNums = val.match(/\(.*?\)/g).toString().replace("(", "").replace(")", "").trim(); // find numbers only + valArr = valNums.split(','); // split numbers into array + val = d3.hsluv(Number(valArr[0]), Number(valArr[1]), Number(valArr[2])).formatHsl(); + } + + let typeId = type.value; + let typeArr; + let colorInput = document.getElementById('colorInput'); + let valRgb = convert(val, 'RGB'); + let colorInputVal = d3.rgb(valRgb[0], valRgb[1], valRgb[2]).formatHex(); + colorInput.value = colorInputVal; + + if(typeId == 'HSV') { typeArr = ['H', 'S', 'V']; } + if(typeId == 'HSL') { typeArr = ['H', 'S', 'L']; } + if(typeId == 'HSLuv') { typeArr = ['H (l)', 'S (u)', 'L (v)']; } + if(typeId == 'CAM02') { typeArr = ['J', 'a', 'b']; typeId = 'jab';} + if(typeId == 'Lab') { typeArr = ['L', 'a', 'b']; } + if(typeId == 'Lch') { typeArr = ['L', 'c', 'h']; } + if(typeId == 'RGB') { typeArr = ['R', 'G', 'B']; } + + if(val.length >= 10 && val.charAt(0) !== '#' || val.length > 6 && val.charAt(0) == '#') { + let newVal = convert(val); + output.innerHTML = ' '; + + if(typeId == 'Hex') { + let d = document.createElement('div'); + let str = document.createTextNode(newVal); + d.appendChild(str); + output.appendChild(d); + } + else { + let d = document.createElement('div'); + let a = document.createElement('div'); + let b = document.createElement('div'); + let c = document.createElement('div'); + let str = document.createTextNode((typeId.toLowerCase()) + '(' + newVal + ')'); + let arr1 = document.createTextNode(typeArr[0] + ': ' + newVal[0]); + let arr2 = document.createTextNode(typeArr[1] + ': ' + newVal[1]); + let arr3 = document.createTextNode(typeArr[2] + ': ' + newVal[2]); + a.appendChild(arr1); + b.appendChild(arr2); + c.appendChild(arr3); + d.appendChild(str); + + output.appendChild(d); + output.appendChild(a); + output.appendChild(b); + output.appendChild(c); + } + } +} + +colorInput.addEventListener('input', function() { input.value = colorInput.value; returnColor(); }); +input.addEventListener('input', returnColor); +type.addEventListener('input', returnColor); + +export { + convert, + returnColor +} diff --git a/packages/ui/src/demo.css b/packages/ui/src/demo.css index d02c26ef..4e064bd7 100644 --- a/packages/ui/src/demo.css +++ b/packages/ui/src/demo.css @@ -18,30 +18,8 @@ governing permissions and limitations under the License. // Grays: // http://localhost:8080/?color=707080&base=fafafb&tint=cacad0&shade=333351&ratios=1%2C1.25%2C1.94%2C3%2C3.99%2C5.22%2C6.96%2C9.3%2C12.45%2C15&mode=LAB */ - +@import url('https://fonts.googleapis.com/css?family=Nunito:300,400,600,700,800&display=swap'); :root { - /* Define variables to use in the app. These will have values replaced via Leonardo */ - --blue100: #d4e4e9; - --blue200: #599ebc; - --blue300: #337eaa; - - --red100: #ffd8d7; - --red200: #e35f46; - --red300: #d2311a; - - --gray50: #ffffff; - --gray100: #fafafb; - --gray200: #d1d1d7; - --gray300: #a9a9b2; - --gray400: #868593; - --gray500: #707080; - --gray700: #5f5e73; - --gray800: #4c4c64; - --gray900: #393955; - --gray1000: #262539; - --gray1100: #16151e; - - /* Alias global values for contextual use */ --blueBackground: var(--blue100); --blueLargeText: var(--blue200); @@ -58,16 +36,33 @@ governing permissions and limitations under the License. --borderColor: var(--gray200); --shadowColor: var(gray1100); /* --shadow: 2px 2px 2px var(--shadowColor); */ + --hour-height: 64px; + --shadow-color: rgba(0, 0, 0, 0.1); +} +@media screen and (min-width: 320px) { + :root { + --detail-width: 320px; + --calendarHeaderPadding: 0 188px 16px 0; + } +} +@media screen and (min-width: 860px) { + :root { + --detail-width: 420px; + --calendarHeaderPadding: 0 288px 16px 0; + } } body { font-size: 100%; margin: 0; padding: 0; - background-color: var(--backgroundDefault); + background-color: var(--background); color: var(--smallText); display: flex; flex-direction: column; + height: 100vh; + overflow: hidden; + font-family: 'Nunito', sans-serif; } header { color: #000000; @@ -79,6 +74,7 @@ header { align-items: center; background-color: #ffffff; border-bottom: 1px solid rgba(0, 0, 0, 0.25); + z-index: 1; } header h2 { margin: 0; @@ -100,10 +96,280 @@ label { /* Demo App Styles */ #demoApp { - padding: 24px 32px; - max-width: 800px; - align-self: center; + display: grid; + grid-template-columns: auto var(--detail-width); + grid-column-gap: 0px; + grid-row-gap: 0px; + grid-template-areas: + "master detail"; + overflow: hidden; + height: calc(100vh - 48px); +} + +#master { + grid-area: master; + display: flex; + flex-direction: row; + height: 100vh; + max-width: calc(100vw - var(--detail-width)); + background-color: var(--backgroundDefault); + padding: 16px 0 16px 16px; +} +#detail { + grid-area: detail; + max-width: var(--detail-width); + display: flex; + flex-direction: column; + justify-content: space-between; + height: 100vh; + padding: 8px 32px 16px 16px; + background-color: var(--backgroundDefault); + border-left: 1px solid var(--gray300); + box-shadow: -1px 0 16px 0 var(--shadow-color), 0 0 32px 0 var(--shadow-color); + z-index: 1; +} + +/* Calendar classes */ +.calendarWrapper { + display: flex; + flex-direction: column; +} +.calendarTimeColumn { + padding-top: 114px; + padding-right: 8px; + display: flex; + align-items: center; + flex-direction: column; + font-size: 12px; +} +.calendarTime { + display: flex; + height: calc(var(--hour-height) + 2px); + align-items: center; +} +.calendarHeader { + display: flex; + align-items: flex-end; + height: 64px; + padding: var(--calendarHeaderPadding); + justify-content: space-between; + flex-direction: row; +} +#calendar { + display: flex; + flex-direction: row; + width: 100%; + min-height: 100%; + overflow: hidden; + border: 1px solid var(--gray300); + border-right: 0; + border-bottom: 0; + border-radius: 6px 6px 0 0; +} +.calendarColumn { + display: flex; + width: calc(100vw / 5 - 40px); + position: relative; + flex-direction: column; + background-color: var(--gray50); + border: 0; + border-right: 1px solid var(--gray300); + border-collapse:collapse; +} +.calendarColumnHeader { + padding: 16px; + background-color: var(--gray200); + border-bottom: 1px solid var(--gray300); + color: var(--gray900); + font-weight: lighter; + font-size: 18px; +} + +/* Make first column appear selected */ +#calendarColumn0 .calendarColumnHeader { + border-top: 4px solid var(--purple600); + padding: 12px 16px 16px 16px; + color: var(--purple700); +} + +.calendar30, +.calendar60 { + display: flex; + min-height: calc(var(--hour-height) / 2); + align-items: center; +} +.calendar30 { + border-bottom: 1px dashed var(--gray300); +} +.calendar60 { + border-bottom: 1px solid var(--gray300); +} +.event { + position: absolute; + display: flex; + flex-direction: column; + border-radius: 6px; + border-width: 1px; + border-left-width: 6px; + left: 6px; + border-size: 1px; + border-style: solid; + overflow: hidden; +} +.eventSingle { + width: calc(100% - 20px); +} +.eventDouble { + width: calc((100% - 30px) / 2); +} +.eventTriple { + width: calc((100% - 28px) / 3); +} +.eventDouble ~ .eventDouble { + left: calc(50% + 2px); +} +.event30 { + height: calc(var(--hour-height) / 2); +} +.event30 .eventMeta { display: none; } +.event60 { + height: var(--hour-height); +} +.event90 { + height: calc(var(--hour-height) / 2 + var(--hour-height) + 2px); +} +.event120 { + height: calc(var(--hour-height) * 2 + 2px); +} +/* Categories */ +.catDefault { + background-color: var(--gray200); + border-color: var(--gray700); +} +.catDefault .eventTitle { + color: var(--gray1100); +} +.catDefault .eventMeta { + color: var(--gray800); +} + +.catPrimary { + background-color: var(--purple200); + border-color: var(--purple600); +} +.catPrimary .eventTitle { + color: var(--purple800); +} +.catPrimary .eventMeta { + color: var(--purple700); +} + +.catPersonal { + background-color: var(--green200); + border-color: var(--green600); +} +.catPersonal .eventTitle { + color: var(--green800); +} +.catPersonal .eventMeta { + color: var(--green700); +} + +.catImportant { + background-color: var(--gold200); + border-color: var(--gold600); +} +.catImportant .eventTitle { + color: var(--gold800); +} +.catImportant .eventMeta { + color: var(--gold700); +} + +.catBlue { + background-color: var(--blue200); + border-color: var(--blue600); +} +.catBlue .eventTitle { + color: var(--blue800); +} +.catBlue .eventMeta { + color: var(--blue700); +} +.catBlue.is-selected { + background-color: var(--blue600); + border-color: var(--blue600); +} +.catBlue.is-selected .eventTitle { + color: var(--blue100); +} +.catBlue.is-selected .eventMeta { + color: var(--blue200); +} + +.catUrgent { + background-color: var(--red200); + border-color: var(--red600); +} +.catUrgent .eventTitle { + color: var(--red800); +} +.catUrgent .eventMeta { + color: var(--red700); +} + +.eventTitle, +.eventMeta { + padding: 0 10px; +} +.eventTitle { + font-size: 14px; + font-weight: bold; + padding-top:8px; +} +.eventMeta { + font-size: 12px; +} + +.detailTitle { + margin-bottom: 4px; + color: var(--gray1000); +} +.detailMeta { + margin: 0; + padding: 0; + line-height: 1.3; + color: var(--gray800); +} +.detailMeta:last-of-type { + margin-bottom: 14px; +} +.detailContent { + border: 1px solid var(--gray300); + background-color: var(--gray50); + padding: 10px 12px; + border-radius: 6px; +} +.detailContent p { + color: var(--gray1000); + margin: 0; + font-size: 16px; +} +.detailContent p + p { + margin-top: 14px; +} +.detailLabel { + font-size: 14px; + font-weight: bold; + padding: 16px 0 4px; + margin: 0; + color: var(--gray900); } +.detailFooter { + display: flex; + margin-bottom: 74px; +} + .tabs { display: flex; @@ -156,12 +422,10 @@ li.active::after { } h3 { - color: var(--gray500); + color: var(--gray1000); padding: 0; - margin: 24px 0 0; - text-transform: uppercase; - letter-spacing: 0.06em; - font-size: 12px; + margin: 0; + font-size: 22px; font-weight: 600; } @@ -173,19 +437,53 @@ h1 { color: var(--blueLargeText); } -.well { - display: flex; - padding: 16px; - background-color: var(--gray50); - border: 1px solid var(--gray200); - border-radius: 4px; -} - p { line-height: 1.5; } -#demoApp form { +a { + color: var(--blue800); +} +a:visited { + color: var(--purple800); +} +a:hover, +a:active, +a:focus { + color: var(--blue900); +} + +button, +.button { + -webkit-appearance: none; + -moz-appearance: none; + -o-appearance: none; + font-size: 14px; + padding: 10px 12px; + border-width: 2px; + text-decoration: none; + display: block; + border-width: 2px; + border-style: solid; +} + + +button + button, +.button + .button { + margin-left: 10px; +} +.buttonPrimary { + color: var(--gray100); + background-color: var(--purple600); + border-color: var(--purple600); +} +.buttonSecondary { + color: var(--purple800); + background-color: transparent; + border-color: var(--purple700); +} + +/* #demoApp form { margin-top: 16px; } #demoApp .form-Item { @@ -194,13 +492,7 @@ p { align-items: flex-start; margin-bottom: 12px; } -#demoApp label, -#demoApp input { - font-size: 1em; -} -#demoApp label { - margin: 12px 0 8px; -} + #demoApp input[type='text'] { width: 300px; padding: 5px 6px 6px; @@ -236,12 +528,13 @@ p { #demoApp .alert.alert-error { border-color: var(--red200); color: var(--red300); -} +} */ -#demoApp h4 { - margin: 0; - padding: 24px 0 16px; -} -#demoApp p { - font-size: 0.875em; +code { + font-size: 14px; + padding: 4px 6px; + background-color: var(--background); + border: 1px solid var(--gray200); + color: var(--green600); + border-radius: 4px; } diff --git a/packages/ui/src/demo.html b/packages/ui/src/demo.html index b286ec97..70d23743 100644 --- a/packages/ui/src/demo.html +++ b/packages/ui/src/demo.html @@ -12,6 +12,15 @@ + + + @@ -22,51 +31,68 @@
-

Demo App

+

Leonardo Demo App

- +
- +
- +
-

Demo overline

-

Demo Page Title

- -
-

This is some demo text that holds the place of real text, which is not to be confused with actual text because actual text is not written is such enormously long running sentences that seem to have no end, and at some point become extremely difficult to comprehend the tone and duration at which the sentence will continue, gathering such accelleration into the unknown that it's unintelligible nature becomes a wonder in and of its self.

- -

This is a form

-
- Please enter your information +
+
+ 7am + 8am + 9am + 10am + 11am + 12pm + 1pm + 2pm + 3pm + 4pm + 5pm + 6pm + 7pm + 8pm + 9pm + 10pm + 11pm
-
-
- - +
+
+

Calendar

+
-
- - +
+
+
+
+
+

Leonardo integration

+

https://leonardocolor.io

+ +
+

+ This app demonstrates how Leonardo can be used to create an adaptive color palette. The adaptibility of color is surfaced to the end-user via the brightness and contrast sliders at the top of the page. +

+

+ Each color is generated using generateAdaptiveTheme(). Take a look at here to see how this demo app is built. +

- - +
+
+ + +
diff --git a/packages/ui/src/demo.js b/packages/ui/src/demo.js index 2aa97d31..bc953770 100644 --- a/packages/ui/src/demo.js +++ b/packages/ui/src/demo.js @@ -10,99 +10,231 @@ OF ANY KIND, either express or implied. See the License for the specific languag governing permissions and limitations under the License. */ -import contrastColors from '@adobe/leonardo-contrast-colors'; +import { generateContrastColors, generateBaseScale, generateAdaptiveTheme } from '@adobe/leonardo-contrast-colors'; import './demo.css'; + +function setup() { + let br = document.getElementById('sliderBrightness'); + br.min= "0"; + br.max= "100"; + br.defaultValue = "95"; + + let calendar = document.getElementById('calendar') + calendar.innerHTML = ' '; + + let monthNames = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" + ]; + + let d = new Date(); + let month = monthNames[d.getMonth()]; + + let colNum = 5; + for (let i = 0; i Define colors as configs and scales. - var baseRatios = [-1.1,1,1.25,1.94,3,3.99,5.22,6.96,9.30,12.45,15]; - var uiRatios = [1.3,3.5,5]; - - var grayScale = contrastColors.createScale({ - swatches: 100, - colorKeys: ['#000036', '#f9ffff'], - colorspace: 'LAB' - }); - var blueScale = contrastColors.createScale({ - colorKeys: ['#0272d4','#b2f0ff','#55cfff','#0037d7'], - colorspace: "CAM02" - }); - - var redScale = contrastColors.createScale({ - colorKeys: ["#ea2825","#ffc1ad","#fd937e"], - colorspace: "LAB" - }); - - var base = grayScale.colors[4]; - br.min= "-15"; - br.max= "0"; + let br = document.getElementById('sliderBrightness'); + let con = document.getElementById('sliderContrast'); + let mode = document.getElementById('darkMode'); - if(mode.checked == true) { - brVal = 84 + brVal; + let brVal = br.value; + let conVal = con.value; - var base = grayScale.colors[brVal]; + if(mode.checked == true) { + br.min= "0"; + br.max= "30"; + if(brVal > 30) { + brVal = 15; + br.value = 15; + } + + document.documentElement.style.setProperty('--shadow-color', 'rgba(0, 0, 0, 0.5)'); } else { + br.min= "85"; + br.max= "100"; + if(brVal < 80) { + brVal = 95; + br.value = 95; + } + + document.documentElement.style.setProperty('--shadow-color', 'rgba(0, 0, 0, 0.1)'); + } - var base = grayScale.colors[brVal]; + let myTheme = generateAdaptiveTheme({ + baseScale: "gray", + colorScales: [grayScale, purpleScale, blueScale, greenScale, redScale, goldScale], + brightness: brVal, + contrast: conVal}); + + console.log(myTheme); + + let varPrefix = '--'; + + for (let i=0; i - + + + + @@ -49,239 +47,261 @@ - +
- - -

Leonardo

-
-
- About - Docs - Github octocat -
-
-
-
-
-

Key Colors

-
- - - +
+ + +

Leonardo

+
+ +
+
+ Color +
+
+ Theme
-
-
-
-
-
- -
- - - - -
-
-
-
-
-
-
-
-
-