From bd4a1711426abdc32e0f6f7ccfdc6948a61798b8 Mon Sep 17 00:00:00 2001 From: Clint Andrew Hall Date: Tue, 2 Apr 2019 12:19:05 -0500 Subject: [PATCH] [canvas] Color Widgets - TS + Examples (#31391) (#33220) * [canvas] Color Widgets - TS + Examples * Addressing feedback --- package.json | 1 + x-pack/package.json | 3 + .../color_dot.examples.storyshot | 209 ++++ .../__examples__/color_dot.examples.tsx | 37 + .../color_dot/{color_dot.js => color_dot.tsx} | 20 +- .../color_dot/{index.js => index.ts} | 0 .../color_manager.examples.storyshot | 795 ++++++++++++ .../__examples__/color_manager.examples.tsx | 88 ++ .../components/color_manager/color_manager.js | 51 - .../color_manager/color_manager.tsx | 81 ++ .../index.js => color_manager/index.ts} | 5 +- .../color_palette.examples.storyshot | 1073 +++++++++++++++++ .../__examples__/color_palette.examples.tsx | 60 + .../components/color_palette/color_palette.js | 40 - .../color_palette/color_palette.tsx | 80 ++ .../color_palette/{index.js => index.ts} | 0 .../color_picker.examples.storyshot | 1025 ++++++++++++++++ .../__examples__/color_picker.examples.tsx | 93 ++ .../components/color_picker/color_picker.js | 32 - .../components/color_picker/color_picker.tsx | 75 ++ .../color_picker/{index.js => index.ts} | 0 .../item_grid.examples.storyshot | 227 ++++ .../__examples__/item_grid.examples.tsx | 44 + .../index.js => item_grid/index.ts} | 7 +- .../public/components/item_grid/item_grid.js | 42 - .../public/components/item_grid/item_grid.tsx | 65 + .../lib/__tests__/readable_color.test.ts | 22 + .../{readable_color.js => readable_color.ts} | 4 +- yarn.lock | 17 + 29 files changed, 4018 insertions(+), 178 deletions(-) create mode 100644 x-pack/plugins/canvas/public/components/color_dot/__examples__/__snapshots__/color_dot.examples.storyshot create mode 100644 x-pack/plugins/canvas/public/components/color_dot/__examples__/color_dot.examples.tsx rename x-pack/plugins/canvas/public/components/color_dot/{color_dot.js => color_dot.tsx} (55%) rename x-pack/plugins/canvas/public/components/color_dot/{index.js => index.ts} (100%) create mode 100644 x-pack/plugins/canvas/public/components/color_manager/__examples__/__snapshots__/color_manager.examples.storyshot create mode 100644 x-pack/plugins/canvas/public/components/color_manager/__examples__/color_manager.examples.tsx delete mode 100644 x-pack/plugins/canvas/public/components/color_manager/color_manager.js create mode 100644 x-pack/plugins/canvas/public/components/color_manager/color_manager.tsx rename x-pack/plugins/canvas/public/components/{item_grid/index.js => color_manager/index.ts} (72%) create mode 100644 x-pack/plugins/canvas/public/components/color_palette/__examples__/__snapshots__/color_palette.examples.storyshot create mode 100644 x-pack/plugins/canvas/public/components/color_palette/__examples__/color_palette.examples.tsx delete mode 100644 x-pack/plugins/canvas/public/components/color_palette/color_palette.js create mode 100644 x-pack/plugins/canvas/public/components/color_palette/color_palette.tsx rename x-pack/plugins/canvas/public/components/color_palette/{index.js => index.ts} (100%) create mode 100644 x-pack/plugins/canvas/public/components/color_picker/__examples__/__snapshots__/color_picker.examples.storyshot create mode 100644 x-pack/plugins/canvas/public/components/color_picker/__examples__/color_picker.examples.tsx delete mode 100644 x-pack/plugins/canvas/public/components/color_picker/color_picker.js create mode 100644 x-pack/plugins/canvas/public/components/color_picker/color_picker.tsx rename x-pack/plugins/canvas/public/components/color_picker/{index.js => index.ts} (100%) create mode 100644 x-pack/plugins/canvas/public/components/item_grid/__examples__/__snapshots__/item_grid.examples.storyshot create mode 100644 x-pack/plugins/canvas/public/components/item_grid/__examples__/item_grid.examples.tsx rename x-pack/plugins/canvas/public/components/{color_manager/index.js => item_grid/index.ts} (55%) delete mode 100644 x-pack/plugins/canvas/public/components/item_grid/item_grid.js create mode 100644 x-pack/plugins/canvas/public/components/item_grid/item_grid.tsx create mode 100644 x-pack/plugins/canvas/public/lib/__tests__/readable_color.test.ts rename x-pack/plugins/canvas/public/lib/{readable_color.js => readable_color.ts} (78%) diff --git a/package.json b/package.json index ffd056eb3defe..32442ac2ce17c 100644 --- a/package.json +++ b/package.json @@ -113,6 +113,7 @@ "@kbn/ui-framework": "1.0.0", "@types/json-stable-stringify": "^1.0.32", "@types/lodash.clonedeep": "^4.5.4", + "@types/recompose": "^0.30.5", "JSONStream": "1.1.1", "abortcontroller-polyfill": "^1.1.9", "angular": "1.6.9", diff --git a/x-pack/package.json b/x-pack/package.json index 4b4fbcb157053..c2e1c6969be4e 100644 --- a/x-pack/package.json +++ b/x-pack/package.json @@ -39,6 +39,8 @@ "@storybook/react": "^4.0.7", "@types/angular": "1.6.50", "@types/cheerio": "^0.22.10", + "@types/chroma-js": "^1.4.1", + "@types/color": "^3.0.0", "@types/d3-array": "^1.2.1", "@types/d3-scale": "^2.0.0", "@types/d3-shape": "^1.3.1", @@ -67,6 +69,7 @@ "@types/storybook__addon-info": "^3.4.2", "@types/storybook__react": "^4.0.0", "@types/supertest": "^2.0.5", + "@types/tinycolor2": "^1.4.1", "@types/uuid": "^3.4.4", "abab": "^1.0.4", "ansi-colors": "^3.0.5", diff --git a/x-pack/plugins/canvas/public/components/color_dot/__examples__/__snapshots__/color_dot.examples.storyshot b/x-pack/plugins/canvas/public/components/color_dot/__examples__/__snapshots__/color_dot.examples.storyshot new file mode 100644 index 0000000000000..c571f476f82a4 --- /dev/null +++ b/x-pack/plugins/canvas/public/components/color_dot/__examples__/__snapshots__/color_dot.examples.storyshot @@ -0,0 +1,209 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Storyshots components/ColorDot color dots 1`] = ` +Array [ +
+
+
+
, +
+
+
+
, +
+
+
+
, +
+
+
+
, +] +`; + +exports[`Storyshots components/ColorDot color dots with children 1`] = ` +Array [ +
+
+
+ + + +
+
, +
+
+
+ + + +
+
, +
+
+
+ + + +
+
, +
+
+
+ + + +
+
, +] +`; + +exports[`Storyshots components/ColorDot invalid dots 1`] = `null`; diff --git a/x-pack/plugins/canvas/public/components/color_dot/__examples__/color_dot.examples.tsx b/x-pack/plugins/canvas/public/components/color_dot/__examples__/color_dot.examples.tsx new file mode 100644 index 0000000000000..ed589bc25d912 --- /dev/null +++ b/x-pack/plugins/canvas/public/components/color_dot/__examples__/color_dot.examples.tsx @@ -0,0 +1,37 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { EuiIcon } from '@elastic/eui'; +import { storiesOf } from '@storybook/react'; +import React from 'react'; +import { ColorDot } from '../color_dot'; + +storiesOf('components/ColorDot', module) + .addParameters({ info: { propTablesExclude: [EuiIcon] } }) + .add('color dots', () => [ + , + , + , + , + ]) + .add('invalid dots', () => [ + , + , + , + ]) + .add('color dots with children', () => [ + + + , + + + , + + + , + + + , + ]); diff --git a/x-pack/plugins/canvas/public/components/color_dot/color_dot.js b/x-pack/plugins/canvas/public/components/color_dot/color_dot.tsx similarity index 55% rename from x-pack/plugins/canvas/public/components/color_dot/color_dot.js rename to x-pack/plugins/canvas/public/components/color_dot/color_dot.tsx index fb6ec2eb73c68..9e1f56ef2770a 100644 --- a/x-pack/plugins/canvas/public/components/color_dot/color_dot.js +++ b/x-pack/plugins/canvas/public/components/color_dot/color_dot.tsx @@ -4,14 +4,27 @@ * you may not use this file except in compliance with the Elastic License. */ -import React from 'react'; import PropTypes from 'prop-types'; +import React, { ReactNode, SFC } from 'react'; +import tinycolor from 'tinycolor2'; + +export interface Props { + /** Any valid CSS color. If not a valid CSS string, the dot will not render */ + value: string; + /** Nodes to display within the dot. Should fit within the constraints. */ + children?: ReactNode; +} + +export const ColorDot: SFC = ({ value, children }) => { + const tc = tinycolor(value); + if (!tc.isValid()) { + return null; + } -export const ColorDot = ({ value, children }) => { return (
-
+
{children}
@@ -21,5 +34,4 @@ export const ColorDot = ({ value, children }) => { ColorDot.propTypes = { value: PropTypes.string, children: PropTypes.node, - handleClick: PropTypes.func, }; diff --git a/x-pack/plugins/canvas/public/components/color_dot/index.js b/x-pack/plugins/canvas/public/components/color_dot/index.ts similarity index 100% rename from x-pack/plugins/canvas/public/components/color_dot/index.js rename to x-pack/plugins/canvas/public/components/color_dot/index.ts diff --git a/x-pack/plugins/canvas/public/components/color_manager/__examples__/__snapshots__/color_manager.examples.storyshot b/x-pack/plugins/canvas/public/components/color_manager/__examples__/__snapshots__/color_manager.examples.storyshot new file mode 100644 index 0000000000000..4edd07a1691c8 --- /dev/null +++ b/x-pack/plugins/canvas/public/components/color_manager/__examples__/__snapshots__/color_manager.examples.storyshot @@ -0,0 +1,795 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Storyshots components/ColorManager default 1`] = ` +Array [ +
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+ + +
+
, +
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+ + +
+
, +] +`; + +exports[`Storyshots components/ColorManager interactive 1`] = ` +
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+ + +
+
+`; + +exports[`Storyshots components/ColorManager invalid colors 1`] = ` +Array [ +
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+ + +
+
, +
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+ + +
+
, +] +`; + +exports[`Storyshots components/ColorManager with buttons 1`] = ` +Array [ +
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+ + +
+
, +
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+ + +
+
, +
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+ + +
+
, +] +`; diff --git a/x-pack/plugins/canvas/public/components/color_manager/__examples__/color_manager.examples.tsx b/x-pack/plugins/canvas/public/components/color_manager/__examples__/color_manager.examples.tsx new file mode 100644 index 0000000000000..2519cdd0bd0b9 --- /dev/null +++ b/x-pack/plugins/canvas/public/components/color_manager/__examples__/color_manager.examples.tsx @@ -0,0 +1,88 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { action } from '@storybook/addon-actions'; +import { storiesOf } from '@storybook/react'; +import React from 'react'; +import { ColorManager } from '../color_manager'; + +class Interactive extends React.Component<{}, { value: string }> { + public state = { + value: '', + }; + + public render() { + return ( + this.setState({ value })} + value={this.state.value} + /> + ); + } +} + +storiesOf('components/ColorManager', module) + .addParameters({ + info: { + inline: true, + styles: { + infoBody: { + margin: 20, + }, + infoStory: { + margin: '40px 60px', + width: '320px', + }, + }, + }, + }) + .add('default', () => [ + , + , + ]) + .add('invalid colors', () => [ + , + , + ]) + .add('with buttons', () => [ + , + , + , + ]) + .add('interactive', () => , { + info: { + inline: true, + source: false, + propTablesExclude: [Interactive], + styles: { + infoBody: { + margin: 20, + }, + infoStory: { + margin: '40px 60px', + width: '320px', + }, + }, + }, + }); diff --git a/x-pack/plugins/canvas/public/components/color_manager/color_manager.js b/x-pack/plugins/canvas/public/components/color_manager/color_manager.js deleted file mode 100644 index e83d1733d7a7f..0000000000000 --- a/x-pack/plugins/canvas/public/components/color_manager/color_manager.js +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import React from 'react'; -import PropTypes from 'prop-types'; -import { EuiFieldText, EuiButtonIcon, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; -import { ColorDot } from '../color_dot/color_dot'; - -export const ColorManager = ({ value, addColor, removeColor, onChange }) => ( - - - - - - onChange(e.target.value)} - /> - - {(addColor || removeColor) && ( - - {addColor && ( - addColor(value)} - /> - )} - {removeColor && ( - removeColor(value)} - /> - )} - - )} - -); - -ColorManager.propTypes = { - value: PropTypes.string, - addColor: PropTypes.func, - removeColor: PropTypes.func, - onChange: PropTypes.func.isRequired, -}; diff --git a/x-pack/plugins/canvas/public/components/color_manager/color_manager.tsx b/x-pack/plugins/canvas/public/components/color_manager/color_manager.tsx new file mode 100644 index 0000000000000..76e6cdab93fd5 --- /dev/null +++ b/x-pack/plugins/canvas/public/components/color_manager/color_manager.tsx @@ -0,0 +1,81 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { EuiButtonIcon, EuiFieldText, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import PropTypes from 'prop-types'; +import React, { SFC } from 'react'; +import tinycolor from 'tinycolor2'; +import { ColorDot } from '../color_dot/color_dot'; + +export interface Props { + /** The function to call when the Add Color button is clicked. The button will not appear if there is no handler. */ + onAddColor?: (value: string) => void; + /** The function to call when the value is changed */ + onChange: (value: string) => void; + /** The function to call when the Remove Color button is clicked. The button will not appear if there is no handler. */ + onRemoveColor?: (value: string) => void; + /** + * The value of the color manager. Only honors hexadecimal values. + * @default '' + */ + value?: string; +} + +export const ColorManager: SFC = ({ value = '', onAddColor, onRemoveColor, onChange }) => { + const tc = tinycolor(value); + const validColor = tc.isValid() && tc.getFormat() === 'hex'; + + if (value.length > 0 && !value.startsWith('#')) { + value = '#' + value; + } + + const add = ( + onAddColor && onAddColor(value)} + /> + ); + + const remove = ( + onRemoveColor && onRemoveColor(value)} + /> + ); + + return ( + + + + + + 0} + placeholder="#hex color" + onChange={e => onChange(e.target.value)} + /> + + {(add || remove) && ( + + {add} + {remove} + + )} + + ); +}; + +ColorManager.propTypes = { + onAddColor: PropTypes.func, + onChange: PropTypes.func.isRequired, + onRemoveColor: PropTypes.func, + value: PropTypes.string, +}; diff --git a/x-pack/plugins/canvas/public/components/item_grid/index.js b/x-pack/plugins/canvas/public/components/color_manager/index.ts similarity index 72% rename from x-pack/plugins/canvas/public/components/item_grid/index.js rename to x-pack/plugins/canvas/public/components/color_manager/index.ts index bb9c04f01326d..206220130ca10 100644 --- a/x-pack/plugins/canvas/public/components/item_grid/index.js +++ b/x-pack/plugins/canvas/public/components/color_manager/index.ts @@ -5,6 +5,7 @@ */ import { pure } from 'recompose'; -import { ItemGrid as Component } from './item_grid'; -export const ItemGrid = pure(Component); +import { ColorManager as Component } from './color_manager'; + +export const ColorManager = pure(Component); diff --git a/x-pack/plugins/canvas/public/components/color_palette/__examples__/__snapshots__/color_palette.examples.storyshot b/x-pack/plugins/canvas/public/components/color_palette/__examples__/__snapshots__/color_palette.examples.storyshot new file mode 100644 index 0000000000000..34a1b2ffa5c7d --- /dev/null +++ b/x-pack/plugins/canvas/public/components/color_palette/__examples__/__snapshots__/color_palette.examples.storyshot @@ -0,0 +1,1073 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Storyshots components/ColorPalette interactive 1`] = ` +
+
+ + + + + + +
+
+`; + +exports[`Storyshots components/ColorPalette six colors 1`] = ` +Array [ +
+
+ + + + + + +
+
, +
+
+ + + + + + +
+
, +] +`; + +exports[`Storyshots components/ColorPalette six colors, value missing 1`] = ` +
+
+ + + + + + +
+
+`; + +exports[`Storyshots components/ColorPalette six colors, wrap at 4 1`] = ` +
+
+ + + + +
+
+ + +
+
+`; + +exports[`Storyshots components/ColorPalette three colors 1`] = ` +Array [ +
+
+ + + +
+
, +
+
+ + + +
+
, +] +`; diff --git a/x-pack/plugins/canvas/public/components/color_palette/__examples__/color_palette.examples.tsx b/x-pack/plugins/canvas/public/components/color_palette/__examples__/color_palette.examples.tsx new file mode 100644 index 0000000000000..6a2a6ec511260 --- /dev/null +++ b/x-pack/plugins/canvas/public/components/color_palette/__examples__/color_palette.examples.tsx @@ -0,0 +1,60 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { action } from '@storybook/addon-actions'; +import { storiesOf } from '@storybook/react'; +import React from 'react'; +import { ColorPalette } from '../color_palette'; + +const THREE_COLORS = ['#fff', '#666', '#000']; +const SIX_COLORS = ['#fff', '#666', '#000', '#abc', '#def', '#abcdef']; + +class Interactive extends React.Component<{}, { value: string }> { + public state = { + value: '', + }; + + public render() { + return ( + this.setState({ value })} + value={this.state.value} + /> + ); + } +} + +storiesOf('components/ColorPalette', module) + .add('three colors', () => [ + , + , + ]) + .add('six colors', () => [ + , + , + ]) + .add('six colors, wrap at 4', () => ( + + )) + .add('six colors, value missing', () => ( + + )) + .add('interactive', () => , { + info: { + inline: true, + source: false, + propTablesExclude: [Interactive], + styles: { + infoBody: { + margin: 20, + }, + infoStory: { + margin: '40px 60px', + }, + }, + }, + }); diff --git a/x-pack/plugins/canvas/public/components/color_palette/color_palette.js b/x-pack/plugins/canvas/public/components/color_palette/color_palette.js deleted file mode 100644 index 5df8e6ad21b83..0000000000000 --- a/x-pack/plugins/canvas/public/components/color_palette/color_palette.js +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import React from 'react'; -import PropTypes from 'prop-types'; -import { EuiIcon, EuiLink } from '@elastic/eui'; -import { readableColor } from '../../lib/readable_color'; -import { ColorDot } from '../color_dot'; -import { ItemGrid } from '../item_grid'; - -export const ColorPalette = ({ value, colors, colorsPerRow, onChange }) => ( -
- - {({ item: color }) => ( - onChange(color)} - className="canvasColorPalette__dot" - > - - {color === value && ( - - )} - - - )} - -
-); - -ColorPalette.propTypes = { - colors: PropTypes.array.isRequired, - onChange: PropTypes.func.isRequired, - value: PropTypes.string, - colorsPerRow: PropTypes.number, -}; diff --git a/x-pack/plugins/canvas/public/components/color_palette/color_palette.tsx b/x-pack/plugins/canvas/public/components/color_palette/color_palette.tsx new file mode 100644 index 0000000000000..d4a2f01a58201 --- /dev/null +++ b/x-pack/plugins/canvas/public/components/color_palette/color_palette.tsx @@ -0,0 +1,80 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { EuiIcon, EuiLink } from '@elastic/eui'; +import PropTypes from 'prop-types'; +import React, { SFC } from 'react'; +import tinycolor from 'tinycolor2'; +import { readableColor } from '../../lib/readable_color'; +import { ColorDot } from '../color_dot'; +import { ItemGrid } from '../item_grid'; + +export interface Props { + /** + * An array of hexadecimal color values. Non-hex will be ignored. + * @default [] + */ + colors?: string[]; + /** + * The number of colors to display before wrapping to a new row. + * @default 6 + */ + colorsPerRow?: number; + /** The function to call when the color is changed. */ + onChange: (value: string) => void; + /** + * The value of the color in the selector. Should be hexadecimal. If it is not in the colors array, it will be ignored. + * @default '' + */ + value?: string; +} + +export const ColorPalette: SFC = ({ + colors = [], + colorsPerRow = 6, + onChange, + value = '', +}) => { + if (colors.length === 0) { + return null; + } + + colors = colors.filter(color => { + const providedColor = tinycolor(color); + return providedColor.isValid() && providedColor.getFormat() === 'hex'; + }); + + return ( +
+ + {color => { + const match = tinycolor.equals(color, value); + const icon = match ? ( + + ) : null; + + return ( + !match && onChange(color)} + className="canvasColorPalette__dot" + > + {icon} + + ); + }} + +
+ ); +}; + +ColorPalette.propTypes = { + colors: PropTypes.array, + colorsPerRow: PropTypes.number, + onChange: PropTypes.func.isRequired, + value: PropTypes.string, +}; diff --git a/x-pack/plugins/canvas/public/components/color_palette/index.js b/x-pack/plugins/canvas/public/components/color_palette/index.ts similarity index 100% rename from x-pack/plugins/canvas/public/components/color_palette/index.js rename to x-pack/plugins/canvas/public/components/color_palette/index.ts diff --git a/x-pack/plugins/canvas/public/components/color_picker/__examples__/__snapshots__/color_picker.examples.storyshot b/x-pack/plugins/canvas/public/components/color_picker/__examples__/__snapshots__/color_picker.examples.storyshot new file mode 100644 index 0000000000000..e0517d4e859cc --- /dev/null +++ b/x-pack/plugins/canvas/public/components/color_picker/__examples__/__snapshots__/color_picker.examples.storyshot @@ -0,0 +1,1025 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Storyshots components/ColorPicker interactive 1`] = ` +
+
+
+ + + + + + +
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+ + +
+
+
+`; + +exports[`Storyshots components/ColorPicker six colors 1`] = ` +
+
+
+ + + + + + +
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+ + +
+
+
+`; + +exports[`Storyshots components/ColorPicker six colors, value missing 1`] = ` +
+
+
+ + + + + + +
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+ + +
+
+
+`; + +exports[`Storyshots components/ColorPicker three colors 1`] = ` +
+
+
+ + + +
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+ + +
+
+
+`; diff --git a/x-pack/plugins/canvas/public/components/color_picker/__examples__/color_picker.examples.tsx b/x-pack/plugins/canvas/public/components/color_picker/__examples__/color_picker.examples.tsx new file mode 100644 index 0000000000000..97209500f8ee4 --- /dev/null +++ b/x-pack/plugins/canvas/public/components/color_picker/__examples__/color_picker.examples.tsx @@ -0,0 +1,93 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { action } from '@storybook/addon-actions'; +import { storiesOf } from '@storybook/react'; +import React from 'react'; +import { ColorPicker } from '../color_picker'; + +const THREE_COLORS = ['#fff', '#666', '#000']; +const SIX_COLORS = ['#fff', '#666', '#000', '#abc', '#def', '#abcdef']; + +class Interactive extends React.Component<{}, { value: string; colors: string[] }> { + public state = { + value: '', + colors: SIX_COLORS, + }; + + public render() { + return ( + this.setState({ colors: this.state.colors.concat(value) })} + onRemoveColor={value => + this.setState({ colors: this.state.colors.filter(color => color !== value) }) + } + onChange={value => this.setState({ value })} + value={this.state.value} + /> + ); + } +} + +storiesOf('components/ColorPicker', module) + .addParameters({ + info: { + inline: true, + styles: { + infoBody: { + margin: 20, + }, + infoStory: { + margin: '40px 60px', + width: '320px', + }, + }, + }, + }) + .add('three colors', () => ( + + )) + .add('six colors', () => ( + + )) + .add('six colors, value missing', () => ( + + )) + .add('interactive', () => , { + info: { + inline: true, + source: false, + propTablesExclude: [Interactive], + styles: { + infoBody: { + margin: 20, + }, + infoStory: { + margin: '40px 60px', + width: '320px', + }, + }, + }, + }); diff --git a/x-pack/plugins/canvas/public/components/color_picker/color_picker.js b/x-pack/plugins/canvas/public/components/color_picker/color_picker.js deleted file mode 100644 index 9415eb400abd8..0000000000000 --- a/x-pack/plugins/canvas/public/components/color_picker/color_picker.js +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import React from 'react'; -import PropTypes from 'prop-types'; -import { ColorPalette } from '../color_palette'; -import { ColorManager } from '../color_manager'; - -export const ColorPicker = ({ onChange, value, colors, addColor, removeColor }) => { - return ( -
- - -
- ); -}; - -ColorPicker.propTypes = { - value: PropTypes.string, - onChange: PropTypes.func, - colors: PropTypes.array, - addColor: PropTypes.func, - removeColor: PropTypes.func, -}; diff --git a/x-pack/plugins/canvas/public/components/color_picker/color_picker.tsx b/x-pack/plugins/canvas/public/components/color_picker/color_picker.tsx new file mode 100644 index 0000000000000..111b858ece8a0 --- /dev/null +++ b/x-pack/plugins/canvas/public/components/color_picker/color_picker.tsx @@ -0,0 +1,75 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import PropTypes from 'prop-types'; +import React, { SFC } from 'react'; +import tinycolor from 'tinycolor2'; +import { ColorManager } from '../color_manager'; +import { ColorPalette } from '../color_palette'; + +export interface Props { + /** + * An array of hexadecimal color values. Non-hex will be ignored. + * @default [] + */ + colors?: string[]; + /** The function to call when the Add Color button is clicked. The button will not appear if there is no handler. */ + onAddColor?: (value: string) => void; + /** The function to call when the color is changed. */ + onChange: (value: string) => void; + /** The function to call when the Remove Color button is clicked. The button will not appear if there is no handler. */ + onRemoveColor?: (value: string) => void; + /** + * The value of the color in the selector. Should be hexadecimal. If it is not in the colors array, it will be ignored. + * @default '' + */ + value?: string; +} + +export const ColorPicker: SFC = ({ + colors = [], + value = '', + onAddColor, + onChange, + onRemoveColor, +}) => { + const tc = tinycolor(value); + const isValidColor = tc.isValid() && tc.getFormat() === 'hex'; + + colors = colors.filter(color => { + const providedColor = tinycolor(color); + return providedColor.isValid() && providedColor.getFormat() === 'hex'; + }); + + let canRemove = false; + let canAdd = false; + + if (isValidColor) { + const match = colors.filter(color => tinycolor.equals(value, color)); + canRemove = match.length > 0; + canAdd = match.length === 0; + } + + return ( +
+ + +
+ ); +}; + +ColorPicker.propTypes = { + colors: PropTypes.array, + onAddColor: PropTypes.func, + onChange: PropTypes.func.isRequired, + onRemoveColor: PropTypes.func, + value: PropTypes.string, +}; diff --git a/x-pack/plugins/canvas/public/components/color_picker/index.js b/x-pack/plugins/canvas/public/components/color_picker/index.ts similarity index 100% rename from x-pack/plugins/canvas/public/components/color_picker/index.js rename to x-pack/plugins/canvas/public/components/color_picker/index.ts diff --git a/x-pack/plugins/canvas/public/components/item_grid/__examples__/__snapshots__/item_grid.examples.storyshot b/x-pack/plugins/canvas/public/components/item_grid/__examples__/__snapshots__/item_grid.examples.storyshot new file mode 100644 index 0000000000000..f2205d74033ab --- /dev/null +++ b/x-pack/plugins/canvas/public/components/item_grid/__examples__/__snapshots__/item_grid.examples.storyshot @@ -0,0 +1,227 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Storyshots components/ItemGrid color dot grid 1`] = ` +
+
+
+
+
+
+
+
+
+
+
+
+
+
+`; + +exports[`Storyshots components/ItemGrid complex grid 1`] = ` +
+
+
+
+ + + +
+
+
+
+
+ + + +
+
+
+
+
+ + + +
+
+
+`; + +exports[`Storyshots components/ItemGrid icon grid 1`] = ` +
+ + + + + + + + + +
+`; + +exports[`Storyshots components/ItemGrid simple grid 1`] = ` +
+
+ a +
+
+ b +
+
+ c +
+
+`; diff --git a/x-pack/plugins/canvas/public/components/item_grid/__examples__/item_grid.examples.tsx b/x-pack/plugins/canvas/public/components/item_grid/__examples__/item_grid.examples.tsx new file mode 100644 index 0000000000000..632e238d1868b --- /dev/null +++ b/x-pack/plugins/canvas/public/components/item_grid/__examples__/item_grid.examples.tsx @@ -0,0 +1,44 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { EuiIcon, IconType } from '@elastic/eui'; +import { storiesOf } from '@storybook/react'; +import React from 'react'; +import { readableColor } from '../../../lib/readable_color'; +import { ColorDot } from '../../color_dot'; +import { ItemGrid } from '../item_grid'; + +storiesOf('components/ItemGrid', module) + .add('simple grid', () => ( +
{item}
} /> + )) + .add('icon grid', () => ( + } + /> + )) + .add('color dot grid', () => ( + + {item => } + + )) + .add('complex grid', () => ( + + } + > + {item => ( + + + + )} + + )); diff --git a/x-pack/plugins/canvas/public/components/color_manager/index.js b/x-pack/plugins/canvas/public/components/item_grid/index.ts similarity index 55% rename from x-pack/plugins/canvas/public/components/color_manager/index.js rename to x-pack/plugins/canvas/public/components/item_grid/index.ts index 119859ad0cddd..9a5ef08b8171e 100644 --- a/x-pack/plugins/canvas/public/components/color_manager/index.js +++ b/x-pack/plugins/canvas/public/components/item_grid/index.ts @@ -4,8 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { compose, withState } from 'recompose'; +import { pure } from 'recompose'; +import { ItemGrid as Component, Props as ComponentProps } from './item_grid'; -import { ColorManager as Component } from './color_manager'; - -export const ColorManager = compose(withState('adding', 'setAdding', false))(Component); +export const ItemGrid = pure>(Component); diff --git a/x-pack/plugins/canvas/public/components/item_grid/item_grid.js b/x-pack/plugins/canvas/public/components/item_grid/item_grid.js deleted file mode 100644 index 156d348f38dcd..0000000000000 --- a/x-pack/plugins/canvas/public/components/item_grid/item_grid.js +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import React from 'react'; -import PropTypes from 'prop-types'; -import { last } from 'lodash'; - -const defaultPerRow = 6; - -export const ItemGrid = ({ items, itemsPerRow, children }) => { - if (!items) { - return null; - } - - const rows = items.reduce( - (rows, item) => { - if (last(rows).length >= (itemsPerRow || defaultPerRow)) { - rows.push([]); - } - - last(rows).push(children({ item })); - - return rows; - }, - [[]] - ); - - return rows.map((row, i) => ( -
- {row} -
- )); -}; - -ItemGrid.propTypes = { - items: PropTypes.array.isRequired, - itemsPerRow: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), - children: PropTypes.func.isRequired, -}; diff --git a/x-pack/plugins/canvas/public/components/item_grid/item_grid.tsx b/x-pack/plugins/canvas/public/components/item_grid/item_grid.tsx new file mode 100644 index 0000000000000..234f505071669 --- /dev/null +++ b/x-pack/plugins/canvas/public/components/item_grid/item_grid.tsx @@ -0,0 +1,65 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { last } from 'lodash'; +import PropTypes from 'prop-types'; +import React, { Fragment, ReactElement, ValidationMap } from 'react'; + +const PER_ROW_DEFAULT = 6; + +export interface Props { + /** A collection of 'things' to be iterated upon by the children prop function. */ + items: T[]; + /** + * The number of items per row. + * @default 6 + */ + itemsPerRow?: number; + /** A function with which to iterate upon the items collection, producing nodes. */ + children: (item: T) => ReactElement; +} + +// We need this type in order to define propTypes on the object. It's a bit redundant, +// but TS needs to know that ItemGrid can have propTypes defined on it. +interface ItemGridType { + (props: Props): ReactElement; + propTypes?: ValidationMap>; +} + +export const ItemGrid: ItemGridType = function ItemGridFunc({ + items = [], + itemsPerRow = PER_ROW_DEFAULT, + children, +}: Props) { + const reducedRows = items.reduce( + (rows: Array>>, item: any) => { + if (last(rows).length >= itemsPerRow) { + rows.push([]); + } + + last(rows).push(children(item)); + + return rows; + }, + [[]] as Array>> + ); + + return ( + + {reducedRows.map((row, i) => ( +
+ {row} +
+ ))} +
+ ); +}; + +ItemGrid.propTypes = { + items: PropTypes.array, + itemsPerRow: PropTypes.number, + children: PropTypes.func.isRequired, +}; diff --git a/x-pack/plugins/canvas/public/lib/__tests__/readable_color.test.ts b/x-pack/plugins/canvas/public/lib/__tests__/readable_color.test.ts new file mode 100644 index 0000000000000..bd79655ca727b --- /dev/null +++ b/x-pack/plugins/canvas/public/lib/__tests__/readable_color.test.ts @@ -0,0 +1,22 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { readableColor } from '../readable_color'; + +describe('readableColor', () => { + test('light', () => { + expect(readableColor('#000')).toEqual('#FFF'); + expect(readableColor('#000', '#EEE', '#111')).toEqual('#EEE'); + expect(readableColor('#111')).toEqual('#FFF'); + expect(readableColor('#111', '#EEE', '#111')).toEqual('#EEE'); + }); + test('dark', () => { + expect(readableColor('#FFF')).toEqual('#333'); + expect(readableColor('#FFF', '#EEE', '#111')).toEqual('#111'); + expect(readableColor('#EEE')).toEqual('#333'); + expect(readableColor('#EEE', '#EEE', '#111')).toEqual('#111'); + }); +}); diff --git a/x-pack/plugins/canvas/public/lib/readable_color.js b/x-pack/plugins/canvas/public/lib/readable_color.ts similarity index 78% rename from x-pack/plugins/canvas/public/lib/readable_color.js rename to x-pack/plugins/canvas/public/lib/readable_color.ts index 0dd7e2dec1c55..bad68e1b1a0bf 100644 --- a/x-pack/plugins/canvas/public/lib/readable_color.js +++ b/x-pack/plugins/canvas/public/lib/readable_color.ts @@ -6,9 +6,7 @@ import chroma from 'chroma-js'; -export function readableColor(background, light, dark) { - light = light || '#FFF'; - dark = dark || '#333'; +export function readableColor(background: string, light: string = '#FFF', dark: string = '#333') { try { return chroma.contrast(background, '#000') < 7 ? light : dark; } catch (e) { diff --git a/yarn.lock b/yarn.lock index 9ffdc68f25389..bd044040d7db9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1877,6 +1877,11 @@ resolved "https://registry.yarnpkg.com/@types/cheerio/-/cheerio-0.22.10.tgz#780d552467824be4a241b29510a7873a7432c4a6" integrity sha512-fOM/Jhv51iyugY7KOBZz2ThfT1gwvsGCfWxpLpZDgkGjpEO4Le9cld07OdskikLjDUQJ43dzDaVRSFwQlpdqVg== +"@types/chroma-js@^1.4.1": + version "1.4.1" + resolved "https://registry.yarnpkg.com/@types/chroma-js/-/chroma-js-1.4.1.tgz#7c52d461173d569ba1f27e0c2dd26ee76691ec82" + integrity sha512-i9hUiO3bwgmzZUDwBuR65WqsBQ/nwN+H2fKX0bykXCdd8cFQEuIj8vI7FXjyb2f5z5h+pv76I/uakikKSgaqTA== + "@types/chromedriver@^2.38.0": version "2.38.0" resolved "https://registry.yarnpkg.com/@types/chromedriver/-/chromedriver-2.38.0.tgz#971032b73eb7f44036f4f5bed59a7fd5b468014f" @@ -2480,6 +2485,13 @@ dependencies: "@types/react" "*" +"@types/recompose@^0.30.5": + version "0.30.5" + resolved "https://registry.yarnpkg.com/@types/recompose/-/recompose-0.30.5.tgz#09890e3c504546b38193479e610e427ac0888393" + integrity sha512-PEQvFmudB9n0+ZvD8l7lh0olGAWmVAuVwCM4eotzWouH8/Kcr8/EcZyLhYILqoTlqzi6ey/3kbKQzJ/h3KkyXw== + dependencies: + "@types/react" "*" + "@types/reduce-reducers@^0.1.3": version "0.1.3" resolved "https://registry.yarnpkg.com/@types/reduce-reducers/-/reduce-reducers-0.1.3.tgz#69f252207622ced7e063c7526ad46ec60b69f0c0" @@ -2587,6 +2599,11 @@ resolved "https://registry.yarnpkg.com/@types/tempy/-/tempy-0.1.0.tgz#8ba0339dcd5abb554f301683dc3396d153ec5bfd" integrity sha512-2qeSxI2bMucW58Jsj8jrBXZxobtcKkvO44AvJzKGaD8+m/3KRuBqeKitJ5U6sqy3a9tFsqhzsxMkqR4Wcl6AmQ== +"@types/tinycolor2@^1.4.1": + version "1.4.1" + resolved "https://registry.yarnpkg.com/@types/tinycolor2/-/tinycolor2-1.4.1.tgz#2f5670c9d1d6e558897a810ed284b44918fc1253" + integrity sha512-25L/RL5tqZkquKXVHM1fM2bd23qjfbcPpAZ2N/H05Y45g3UEi+Hw8CbDV28shKY8gH1SHiLpZSxPI1lacqdpGg== + "@types/type-detect@^4.0.1": version "4.0.1" resolved "https://registry.yarnpkg.com/@types/type-detect/-/type-detect-4.0.1.tgz#3b0f5ac82ea630090cbf57c57a1bf5a63a29b9b6"