diff --git a/CHANGELOG.md b/CHANGELOG.md index 6bd92990b9d..3631267aab8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ - Added exports for `EuiSteps` and related components types ([#3471](https://github.com/elastic/eui/pull/3471)) - Added `displayName` to components using `React.forwardRef` ([#3451](https://github.com/elastic/eui/pull/3451)) - Added event target checker for `EuiOverlayMask`'s `onClick` prop ([#3462](https://github.com/elastic/eui/pull/3462)) +- Added `EuiColorPalettePicker` component ([#3192](https://github.com/elastic/eui/pull/3192)) - Added `left-start` popover placement to `EuiDatePicker` ([#3511](https://github.com/elastic/eui/pull/3511)) **Bug Fixes** @@ -30,7 +31,6 @@ - Added `partition` key to `EuiChartThemeType` for Partition chart support ([#3387](https://github.com/elastic/eui/pull/3387)) - Updated `EuiImage`'s `caption` prop type from `string` to `ReactNode` ([#3387](https://github.com/elastic/eui/pull/3387)) - Improved contrast for `EuiCollapsibleNav` close button ([#3465](https://github.com/elastic/eui/pull/3465)) -- Added exports for `EuiSteps` and related components types ([#3471](https://github.com/elastic/eui/pull/3471)) **Bug Fixes** diff --git a/src-docs/src/views/color_picker/color_palette_picker.tsx b/src-docs/src/views/color_picker/color_palette_picker.tsx new file mode 100644 index 00000000000..44bdcb91362 --- /dev/null +++ b/src-docs/src/views/color_picker/color_palette_picker.tsx @@ -0,0 +1,92 @@ +import React, { useState } from 'react'; +import { + euiPaletteColorBlind, + euiPaletteForStatus, + euiPaletteForTemperature, +} from '../../../../src/services'; +import { EuiSwitch } from '../../../../src/components/form'; +import { EuiSpacer } from '../../../../src/components/spacer'; +import { EuiCode } from '../../../../src/components/code'; +import { + EuiColorPalettePicker, + EuiColorPalettePickerPaletteProps, +} from '../../../../src/components/color_picker/color_palette_picker'; +// @ts-ignore +import { DisplayToggles } from '../form_controls/display_toggles'; + +const palettes: EuiColorPalettePickerPaletteProps[] = [ + { + value: 'pallette_1', + title: 'EUI color blind (fixed)', + palette: euiPaletteColorBlind(), + type: 'fixed', + }, + { + value: 'pallette_2', + title: 'EUI palette for temperature (fixed)', + palette: euiPaletteForTemperature(5), + type: 'fixed', + }, + { + value: 'pallette_3', + title: 'Grayscale (gradient with stops)', + palette: [ + { + stop: 100, + color: 'white', + }, + { + stop: 250, + color: 'gray', + }, + { + stop: 350, + color: 'dimgray', + }, + { + stop: 470, + color: 'black', + }, + ], + type: 'gradient', + }, + { + value: 'pallette_4', + title: 'EUI palette for status (gradient)', + palette: euiPaletteForStatus(5), + type: 'gradient', + }, + { + value: 'custom', + title: 'Plain text as a custom option', + type: 'text', + }, +]; + +export const ColorPalettePicker = () => { + const [selectionDisplay, setSelectionDisplay] = useState(false); + const [pallette, setPallette] = useState('pallette_1'); + + return ( + <> + + Display selected item as a title + + } + checked={selectionDisplay} + onChange={() => setSelectionDisplay(!selectionDisplay)} + /> + + + + + + ); +}; diff --git a/src-docs/src/views/color_picker/color_picker_example.js b/src-docs/src/views/color_picker/color_picker_example.js index 5aaf9300bd8..da8f6b04405 100644 --- a/src-docs/src/views/color_picker/color_picker_example.js +++ b/src-docs/src/views/color_picker/color_picker_example.js @@ -7,10 +7,12 @@ import { GuideSectionTypes } from '../../components'; import { EuiCode, EuiColorPicker, + EuiColorPalettePicker, EuiColorStops, EuiSpacer, EuiText, } from '../../../../src/components'; +import { EuiColorPalettePickerPalette } from './props'; import { ColorPicker } from './color_picker'; const colorPickerSource = require('!!raw-loader!./color_picker'); @@ -23,6 +25,23 @@ const colorPickerSnippet = ` `; +import { ColorPalettePicker } from './color_palette_picker'; +const colorPalettePickerSource = require('!!raw-loader!./color_palette_picker'); +const colorPalettePickerHtml = renderToHtml(ColorPalettePicker); +const colorPalettePickerSnippet = ` +`; + import { ColorStops } from './color_stops'; const colorStopsSource = require('!!raw-loader!./color_stops'); const colorStopsHtml = renderToHtml(ColorStops); @@ -248,8 +267,10 @@ export const ColorPickerExample = {

- Two components exist to aid color selection:{' '} - EuiColorPicker and EuiColorStops. + Three components exist to aid color selection:{' '} + EuiColorPicker,{' '} + EuiColorPalettePicker and{' '} + EuiColorStops.

@@ -291,6 +312,39 @@ export const ColorPickerExample = { snippet: colorPickerSnippet, demo: , }, + { + title: 'Color palette picker', + text: ( + + +

+ Use EuiColorPalettePicker to select palettes to + apply colors to data visualization like maps and charts. +

+

+ Use the palettes prop to pass your palettes as + an array of objects. For each object, you should pass a palette + (array of hex values) and specify the type. Use{' '} + fixed palettes for categorical data and{' '} + gradient palettes for continuous data. +

+
+
+ ), + source: [ + { + type: GuideSectionTypes.JS, + code: colorPalettePickerSource, + }, + { + type: GuideSectionTypes.HTML, + code: colorPalettePickerHtml, + }, + ], + props: { EuiColorPalettePicker, EuiColorPalettePickerPalette }, + snippet: colorPalettePickerSnippet, + demo: , + }, { title: 'Color stops', text: ( diff --git a/src-docs/src/views/color_picker/props.tsx b/src-docs/src/views/color_picker/props.tsx new file mode 100644 index 00000000000..f4cb616a8eb --- /dev/null +++ b/src-docs/src/views/color_picker/props.tsx @@ -0,0 +1,7 @@ +import React, { FunctionComponent } from 'react'; + +import { EuiColorPalettePickerPaletteProps } from '../../../../src/components/color_picker/color_palette_picker'; + +export const EuiColorPalettePickerPalette: FunctionComponent< + EuiColorPalettePickerPaletteProps +> = () =>
; diff --git a/src/components/color_picker/_index.scss b/src/components/color_picker/_index.scss index b42d746aef0..cd7341001db 100644 --- a/src/components/color_picker/_index.scss +++ b/src/components/color_picker/_index.scss @@ -4,3 +4,4 @@ @import 'hue'; @import 'saturation'; @import 'color_stops/index'; +@import 'color_palette_picker/index'; \ No newline at end of file diff --git a/src/components/color_picker/color_palette_picker/__snapshots__/color_palette_picker.test.tsx.snap b/src/components/color_picker/color_palette_picker/__snapshots__/color_palette_picker.test.tsx.snap new file mode 100644 index 00000000000..90d8ed2e996 --- /dev/null +++ b/src/components/color_picker/color_palette_picker/__snapshots__/color_palette_picker.test.tsx.snap @@ -0,0 +1,347 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`EuiColorPalettePicker is rendered 1`] = ` +
+
+ +
+
+ + Select an option: , is selected + +
+
+
+`; + +exports[`EuiColorPalettePicker is rendered with a selected custom text 1`] = ` +
+
+ +
+
+ + Select an option: Plain text as a custom option, is selected + + +
+ + +
+
+
+
+`; + +exports[`EuiColorPalettePicker is rendered with a selected fixed palette 1`] = ` +
+
+ +
+
+ + Select an option: +
+ , is selected + + +
+ + +
+
+
+
+`; + +exports[`EuiColorPalettePicker is rendered with a selected gradient palette 1`] = ` +
+
+ +
+
+ + Select an option: +
+ , is selected + + +
+ + +
+
+
+
+`; + +exports[`EuiColorPalettePicker is rendered with a selected gradient palette with stops 1`] = ` +
+
+ +
+
+ + Select an option: +
+ , is selected + + +
+ + +
+
+
+
+`; + +exports[`EuiColorPalettePicker is rendered with the prop selectionDisplay set as title 1`] = ` +
+
+ +
+
+ + Select an option: Palette 1, is selected + + +
+ + +
+
+
+
+`; diff --git a/src/components/color_picker/color_palette_picker/_color_palette_picker.scss b/src/components/color_picker/color_palette_picker/_color_palette_picker.scss new file mode 100644 index 00000000000..a3c9973090c --- /dev/null +++ b/src/components/color_picker/color_palette_picker/_color_palette_picker.scss @@ -0,0 +1,16 @@ +.euiColorPalettePicker { + &__itemTitle { + @include euiFontSizeXS; + } + + &__itemGradient { + border-radius: $euiBorderRadius; + overflow: hidden; + height: $euiSizeS; + box-shadow: inset 0 0 0 1px transparentize($euiColorFullShade, .9); + } + + &__itemTitle + &__itemGradient { + margin-top: $euiSizeXS; + } +} diff --git a/src/components/color_picker/color_palette_picker/_index.scss b/src/components/color_picker/color_palette_picker/_index.scss new file mode 100644 index 00000000000..d3ac33fbc4d --- /dev/null +++ b/src/components/color_picker/color_palette_picker/_index.scss @@ -0,0 +1 @@ +@import 'color_palette_picker'; \ No newline at end of file diff --git a/src/components/color_picker/color_palette_picker/color_palette_picker.test.tsx b/src/components/color_picker/color_palette_picker/color_palette_picker.test.tsx new file mode 100644 index 00000000000..d484a523a9f --- /dev/null +++ b/src/components/color_picker/color_palette_picker/color_palette_picker.test.tsx @@ -0,0 +1,154 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file 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 CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React from 'react'; +import { render } from 'enzyme'; + +import { + EuiColorPalettePicker, + EuiColorPalettePickerPaletteProps, +} from './color_palette_picker'; +import { requiredProps } from '../../../test'; + +jest.mock('./../../../services/accessibility', () => ({ + htmlIdGenerator: () => () => 'generated-id', +})); + +const palettes: EuiColorPalettePickerPaletteProps[] = [ + { + value: 'paletteFixed', + title: 'Palette 1', + palette: ['#1fb0b2', '#ffdb6d', '#ee9191', '#ffffff', '#888094'], + type: 'fixed', + }, + { + value: 'paletteLinear', + title: 'Linear Gradient', + palette: ['#1fb0b2', '#ffdb6d', '#ee9191', '#ffffff', '#888094'], + type: 'gradient', + }, + { + value: 'paletteLinearStops', + title: 'Linear Gradient with stops', + palette: [ + { + stop: 100, + color: '#54B399', + }, + { + stop: 250, + color: '#D36086', + }, + { + stop: 350, + color: '#9170B8', + }, + { + stop: 470, + color: '#F5A700', + }, + ], + type: 'gradient', + }, + { + value: 'custom', + title: 'Plain text as a custom option', + type: 'text', + }, +]; + +describe('EuiColorPalettePicker', () => { + test('is rendered', () => { + const component = render( + {}} + /> + ); + + expect(component).toMatchSnapshot(); + }); + + test('is rendered with a selected fixed palette', () => { + const component = render( + {}} + valueOfSelected="paletteFixed" + /> + ); + + expect(component).toMatchSnapshot(); + }); + + test('is rendered with a selected gradient palette', () => { + const component = render( + {}} + valueOfSelected="paletteLinear" + /> + ); + + expect(component).toMatchSnapshot(); + }); + + test('is rendered with a selected gradient palette with stops', () => { + const component = render( + {}} + valueOfSelected="paletteLinearStops" + /> + ); + + expect(component).toMatchSnapshot(); + }); + + test('is rendered with a selected custom text', () => { + const component = render( + {}} + valueOfSelected="custom" + /> + ); + + expect(component).toMatchSnapshot(); + }); + + test('is rendered with the prop selectionDisplay set as title ', () => { + const component = render( + {}} + valueOfSelected="paletteFixed" + selectionDisplay="title" + /> + ); + + expect(component).toMatchSnapshot(); + }); +}); diff --git a/src/components/color_picker/color_palette_picker/color_palette_picker.tsx b/src/components/color_picker/color_palette_picker/color_palette_picker.tsx new file mode 100644 index 00000000000..3ecca462b40 --- /dev/null +++ b/src/components/color_picker/color_palette_picker/color_palette_picker.tsx @@ -0,0 +1,186 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file 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 CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React, { FunctionComponent } from 'react'; + +import { EuiSuperSelect } from '../../form'; + +import { CommonProps } from '../../common'; + +import { getLinearGradient, getFixedLinearGradient } from '../utils'; +import { ColorStop } from '../color_stops'; + +import { EuiSuperSelectProps } from '../../form/super_select'; + +interface EuiColorPalettePickerPaletteText { + /** + * For storing unique value of item + */ + value: string; + /** + * The name of your palette + */ + title: string; + /** + * `text`: a text only option (a title is required). + */ + type: 'text'; + /** + * Array of color `strings` or `ColorStops` in the form of + * `{ stop: number, color: string }`. The stops must be numbers in an ordered range. + */ + palette?: string[] | ColorStop[]; +} + +interface EuiColorPalettePickerPaletteFixed { + /** + * For storing unique value of item + */ + value: string; + /** + * The name of your palette + */ + title?: string; + /** + * `fixed`: individual color blocks + */ + type: 'fixed'; + /** + * Array of color `strings`. + */ + palette: string[]; +} + +interface EuiColorPalettePickerPaletteGradient { + /** + * For storing unique value of item + */ + value: string; + /** + * The name of your palette + */ + title?: string; + /** + * `gradient`: each color fades into the next + */ + type: 'gradient'; + /** + * Array of color `strings` or `ColorStops` in the form of + * `{ stop: number, color: string }`. The stops must be numbers in an ordered range. + */ + palette: string[] | ColorStop[]; +} + +export type EuiColorPalettePickerPaletteProps = + | EuiColorPalettePickerPaletteText + | EuiColorPalettePickerPaletteFixed + | EuiColorPalettePickerPaletteGradient; + +export type EuiColorPalettePickerProps = CommonProps & + Omit< + EuiSuperSelectProps, + 'options' | 'itemLayoutAlign' | 'hasDividers' + > & { + /** + * Specify what should be displayed after a selection: a `palette` or `title` + */ + selectionDisplay?: 'palette' | 'title'; + + /** + * An array of #EuiColorPalettePickerPalette objects + */ + palettes: EuiColorPalettePickerPaletteProps[]; + }; + +export const EuiColorPalettePicker: FunctionComponent< + EuiColorPalettePickerProps +> = ({ + className, + compressed = false, + disabled, + fullWidth = false, + isInvalid = false, + onChange, + readOnly = false, + valueOfSelected, + palettes, + append, + prepend, + selectionDisplay = 'palette', + ...rest +}) => { + const getPalette = ( + item: + | EuiColorPalettePickerPaletteFixed + | EuiColorPalettePickerPaletteGradient + ) => { + const background = + item.type === 'fixed' + ? getFixedLinearGradient(item.palette) + : getLinearGradient(item.palette); + + return ( +
+ ); + }; + + const paletteOptions = palettes.map( + (item: EuiColorPalettePickerPaletteProps) => { + const paletteForDisplay = item.type !== 'text' ? getPalette(item) : null; + return { + value: String(item.value), + inputDisplay: + selectionDisplay === 'title' || item.type === 'text' + ? item.title + : paletteForDisplay, + dropdownDisplay: ( +
+ {item.title && item.type !== 'text' && ( +
+ {item.title} +
+ )} + {item.type === 'text' ? item.title : paletteForDisplay} +
+ ), + }; + } + ); + + return ( + + ); +}; diff --git a/src/components/color_picker/color_palette_picker/index.ts b/src/components/color_picker/color_palette_picker/index.ts new file mode 100644 index 00000000000..33fc24197d9 --- /dev/null +++ b/src/components/color_picker/color_palette_picker/index.ts @@ -0,0 +1,24 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file 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 CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export { + EuiColorPalettePicker, + EuiColorPalettePickerProps, + EuiColorPalettePickerPaletteProps, +} from './color_palette_picker'; diff --git a/src/components/color_picker/index.ts b/src/components/color_picker/index.ts index d51e16e9cb4..6d38e67bfdf 100644 --- a/src/components/color_picker/index.ts +++ b/src/components/color_picker/index.ts @@ -22,3 +22,8 @@ export { EuiColorPickerSwatch } from './color_picker_swatch'; export { EuiHue } from './hue'; export { EuiSaturation } from './saturation'; export { EuiColorStops } from './color_stops'; +export { + EuiColorPalettePicker, + EuiColorPalettePickerProps, + EuiColorPalettePickerPaletteProps, +} from './color_palette_picker'; diff --git a/src/components/color_picker/utils.ts b/src/components/color_picker/utils.ts index d8b3f63a847..11e1c6d4403 100644 --- a/src/components/color_picker/utils.ts +++ b/src/components/color_picker/utils.ts @@ -19,6 +19,7 @@ import { MouseEvent as ReactMouseEvent, TouchEvent, useEffect } from 'react'; import chroma, { ColorSpaces } from 'chroma-js'; +import { ColorStop } from './color_stops'; export const getEventPosition = ( location: { x: number; y: number }, @@ -156,3 +157,77 @@ export const getChromaColor = (input?: string | null, allowOpacity = false) => { } return null; }; + +// Given an array of objects with key value pairs stop/color returns a css linear-gradient +// Or given an array of hex colors returns a css linear-gradient +export const getLinearGradient = (palette: string[] | ColorStop[]) => { + const intervals = palette.length; + + let linearGradient; + + const paletteHasStops = palette.some((item: string | ColorStop) => { + return typeof item === 'object'; + }); + + if (paletteHasStops) { + const paletteColorStop = palette as ColorStop[]; + + linearGradient = `linear-gradient(to right, ${ + paletteColorStop[0].color + } 0%,`; + + const decimal = 100 / paletteColorStop[paletteColorStop.length - 1].stop; + + for (let i = 1; i < intervals - 1; i++) { + linearGradient = `${linearGradient} ${ + paletteColorStop[i].color + }\ ${Math.floor(paletteColorStop[i].stop * decimal)}%,`; + } + + const linearGradientStyle = `${linearGradient} ${ + paletteColorStop[palette.length - 1].color + } 100%)`; + + return linearGradientStyle; + } else { + linearGradient = `linear-gradient(to right, ${palette[0]} 0%,`; + + for (let i = 1; i < intervals - 1; i++) { + linearGradient = `${linearGradient} ${palette[i]}\ ${Math.floor( + (100 * i) / (intervals - 1) + )}%,`; + } + + const linearGradientStyle = `${linearGradient} ${ + palette[palette.length - 1] + } 100%)`; + + return linearGradientStyle; + } +}; + +// Given an array of hex colors returns a css linear-gradient with individual color blocks +export const getFixedLinearGradient = (palette: string[]) => { + const intervals = palette.length; + + let fixedLinearGradient; + + for (let i = 0; i < intervals; i++) { + const initialColorStop = `${palette[0]} 0%, ${palette[0]}\ ${Math.floor( + (100 * 1) / intervals + )}%`; + const colorStop = `${palette[i]}\ ${Math.floor((100 * i) / intervals)}%, ${ + palette[i] + }\ ${Math.floor((100 * (i + 1)) / intervals)}%`; + + if (i === 0) { + fixedLinearGradient = `linear-gradient(to right, ${initialColorStop},`; + } else if (i === palette.length - 1) { + fixedLinearGradient = `${fixedLinearGradient} ${colorStop})`; + } else { + fixedLinearGradient = `${fixedLinearGradient} ${colorStop},`; + } + } + + return fixedLinearGradient; +}; diff --git a/src/components/index.js b/src/components/index.js index 8373faab657..63b39e01e3c 100644 --- a/src/components/index.js +++ b/src/components/index.js @@ -68,6 +68,8 @@ export { EuiSaturation, } from './color_picker'; +export { EuiColorPalettePicker } from './color_picker/color_palette_picker'; + export { EuiComboBox } from './combo_box'; export { EuiComment, EuiCommentList } from './comment_list';