From c3a9703dfb5d105ecdd430045e96bf28ee690a0c Mon Sep 17 00:00:00 2001 From: Greg Thompson Date: Thu, 12 Sep 2019 15:10:02 -0500 Subject: [PATCH 1/8] inline version --- src/components/color_picker/color_picker.js | 95 ++++++++++++--------- 1 file changed, 54 insertions(+), 41 deletions(-) diff --git a/src/components/color_picker/color_picker.js b/src/components/color_picker/color_picker.js index a0f1d3d1d2e..ff9fb8259be 100644 --- a/src/components/color_picker/color_picker.js +++ b/src/components/color_picker/color_picker.js @@ -28,6 +28,7 @@ export const EuiColorPicker = ({ disabled, fullWidth = false, id, + inline = false, isInvalid, mode = 'default', onBlur, @@ -173,6 +174,51 @@ export const EuiColorPicker = ({ handleFinalSelection(); }; + const composite = ( + + {mode !== 'swatch' && ( +
+ + +
+ )} + {mode !== 'picker' && ( + + {swatches.map((swatch, index) => ( + + + {swatchAriaLabel => ( + handleSwatchSelection(swatch)} + aria-label={swatchAriaLabel} + role="option" + ref={index === 0 ? swatchRef : undefined} + /> + )} + + + ))} + + )} +
+ ); + let buttonOrInput; if (button) { buttonOrInput = cloneElement(button, { @@ -234,7 +280,9 @@ export const EuiColorPicker = ({ ); } - return ( + return inline ? ( +
{composite}
+ ) : (

- {mode !== 'swatch' && ( -
- - -
- )} - {mode !== 'picker' && ( - - {swatches.map((swatch, index) => ( - - - {swatchAriaLabel => ( - handleSwatchSelection(swatch)} - aria-label={swatchAriaLabel} - role="option" - ref={index === 0 ? swatchRef : undefined} - /> - )} - - - ))} - - )} + {composite}
@@ -324,6 +333,10 @@ EuiColorPicker.propTypes = { * Custom validation flag */ isInvalid: PropTypes.bool, + /** + * Renders inline, without an input element or popover + */ + inline: PropTypes.bool, /** * Choose between swatches with gradient picker (default), swatches only, or gradient picker only. */ From 11677676547e8a4bc03044e61bda387144843697 Mon Sep 17 00:00:00 2001 From: Greg Thompson Date: Fri, 13 Sep 2019 10:03:08 -0500 Subject: [PATCH 2/8] convert euicolorpicker to ts --- ...est.js.snap => color_picker.test.tsx.snap} | 0 ...r_picker.test.js => color_picker.test.tsx} | 7 +- .../{color_picker.js => color_picker.tsx} | 174 ++++++++++-------- .../color_picker/color_picker_swatch.tsx | 16 +- src/components/color_picker/index.d.ts | 48 ----- .../color_picker/{index.js => index.ts} | 0 src/components/color_picker/saturation.tsx | 7 +- src/components/index.d.ts | 1 - 8 files changed, 110 insertions(+), 143 deletions(-) rename src/components/color_picker/__snapshots__/{color_picker.test.js.snap => color_picker.test.tsx.snap} (100%) rename src/components/color_picker/{color_picker.test.js => color_picker.test.tsx} (99%) rename src/components/color_picker/{color_picker.js => color_picker.tsx} (78%) delete mode 100644 src/components/color_picker/index.d.ts rename src/components/color_picker/{index.js => index.ts} (100%) diff --git a/src/components/color_picker/__snapshots__/color_picker.test.js.snap b/src/components/color_picker/__snapshots__/color_picker.test.tsx.snap similarity index 100% rename from src/components/color_picker/__snapshots__/color_picker.test.js.snap rename to src/components/color_picker/__snapshots__/color_picker.test.tsx.snap diff --git a/src/components/color_picker/color_picker.test.js b/src/components/color_picker/color_picker.test.tsx similarity index 99% rename from src/components/color_picker/color_picker.test.js rename to src/components/color_picker/color_picker.test.tsx index 8bf365adac8..42f8af0f3cb 100644 --- a/src/components/color_picker/color_picker.test.js +++ b/src/components/color_picker/color_picker.test.tsx @@ -6,14 +6,11 @@ import { VISUALIZATION_COLORS, keyCodes } from '../../services'; import { requiredProps, findTestSubject, sleep } from '../../test'; jest.mock('../portal', () => ({ + // @ts-ignore EuiPortal: ({ children }) => children, })); -let onChange; - -beforeEach(() => { - onChange = jest.fn(); -}); +const onChange = jest.fn(); test('renders EuiColorPicker', () => { const colorPicker = render( diff --git a/src/components/color_picker/color_picker.js b/src/components/color_picker/color_picker.tsx similarity index 78% rename from src/components/color_picker/color_picker.js rename to src/components/color_picker/color_picker.tsx index ff9fb8259be..084a9a16bf9 100644 --- a/src/components/color_picker/color_picker.js +++ b/src/components/color_picker/color_picker.tsx @@ -1,15 +1,28 @@ -import React, { cloneElement, useEffect, useRef, useState } from 'react'; -import PropTypes from 'prop-types'; +import React, { + FunctionComponent, + HTMLAttributes, + ReactChild, + ReactElement, + cloneElement, + useEffect, + useRef, + useState, +} from 'react'; import classNames from 'classnames'; +import { CommonProps, Omit } from '../common'; + import { EuiScreenReaderOnly } from '../accessibility'; import { EuiColorPickerSwatch } from './color_picker_swatch'; import { EuiFocusTrap } from '../focus_trap'; import { EuiFlexGroup, EuiFlexItem } from '../flex'; -import { EuiFieldText, EuiFormControlLayout } from '../form'; +// @ts-ignore +import { EuiFieldText } from '../form/field_text'; +import { EuiFormControlLayout } from '../form/form_control_layout'; import { EuiI18n } from '../i18n'; import { EuiPopover } from '../popover'; import { + HSV, VISUALIZATION_COLORS, keyCodes, hexToHsv, @@ -20,7 +33,62 @@ import { import { EuiHue } from './hue'; import { EuiSaturation } from './saturation'; -export const EuiColorPicker = ({ +type EuiColorPickerMode = 'default' | 'swatch' | 'picker'; + +interface HTMLDivElementOverrides { + /** + * Hex string (3 or 6 character). Empty string will register as 'transparent' + */ + color?: string | null; + onBlur?: () => void; + onChange: (hex: string) => void; + onFocus?: () => void; +} +export interface EuiColorPickerProps + extends CommonProps, + Omit, keyof HTMLDivElementOverrides>, + HTMLDivElementOverrides { + /** + * Custom element to use instead of text input + */ + button?: ReactElement; + /** + * Use the compressed style for EuiFieldText + */ + compressed?: boolean; + disabled?: boolean; + fullWidth?: boolean; + id?: string; + /** + * Custom validation flag + */ + isInvalid?: boolean; + /** + * Renders inline, without an input element or popover + */ + inline?: boolean; + /** + * Choose between swatches with gradient picker (default), swatches only, or gradient picker only. + */ + mode?: EuiColorPickerMode; + /** + * Custom z-index for the popover + */ + popoverZIndex?: number; + readOnly?: boolean; + /** + * Array of hex strings (3 or 6 character) to use as swatch options. Defaults to EUI visualization colors + */ + swatches?: string[]; +} + +function isKeyboardEvent( + event: React.MouseEvent | React.KeyboardEvent +): event is React.KeyboardEvent { + return typeof event === 'object' && 'keyCode' in event; +} + +export const EuiColorPicker: FunctionComponent = ({ button, className, color, @@ -43,11 +111,11 @@ export const EuiColorPicker = ({ color ? hexToHsv(color) : hexToHsv('') ); const [lastHex, setLastHex] = useState(color); - const [inputRef, setInputRef] = useState(null); // Ideally this is uses `useRef`, but `EuiFieldText` isn't ready for that + const [inputRef, setInputRef] = useState(null); // Ideally this is uses `useRef`, but `EuiFieldText` isn't ready for that const [popoverShouldOwnFocus, setPopoverShouldOwnFocus] = useState(false); - const satruationRef = useRef(null); - const swatchRef = useRef(null); + const satruationRef = useRef(null); + const swatchRef = useRef(null); useEffect(() => { // Mimics `componentDidMount` and `componentDidUpdate` @@ -67,7 +135,7 @@ export const EuiColorPicker = ({ const testSubjAnchor = 'colorPickerAnchor'; const testSubjPopover = 'colorPickerPopover'; - const handleOnChange = hex => { + const handleOnChange = (hex: string) => { setLastHex(hex); onChange(hex); }; @@ -111,7 +179,7 @@ export const EuiColorPicker = ({ closeColorSelector(true); }; - const handleOnKeyDown = e => { + const handleOnKeyDown = (e: React.KeyboardEvent) => { if (e.keyCode === keyCodes.ENTER) { if (isColorSelectorShown) { handleFinalSelection(); @@ -121,16 +189,22 @@ export const EuiColorPicker = ({ } }; - const handleInputActivity = e => { - if (e.keyCode === keyCodes.ENTER) { - e.preventDefault(); - handleToggle(); - } else if (!e.keyCode) { + const handleInputActivity = ( + e: + | React.KeyboardEvent + | React.MouseEvent + ) => { + if (isKeyboardEvent(e)) { + if (e.keyCode === keyCodes.ENTER) { + e.preventDefault(); + handleToggle(); + } + } else { showColorSelector(); } }; - const handleToggleOnKeyDown = e => { + const handleToggleOnKeyDown = (e: React.KeyboardEvent) => { if (e.keyCode === keyCodes.DOWN) { e.preventDefault(); if (isColorSelectorShown) { @@ -144,14 +218,14 @@ export const EuiColorPicker = ({ } }; - const handleColorInput = e => { + const handleColorInput = (e: React.ChangeEvent) => { handleOnChange(e.target.value); if (isValidHex(e.target.value)) { setColorAsHsv(hexToHsv(e.target.value)); } }; - const handleColorSelection = color => { + const handleColorSelection = (color: HSV) => { const { h } = colorAsHsv; const hue = h ? h : 1; const newHsv = { ...color, h: hue }; @@ -159,7 +233,7 @@ export const EuiColorPicker = ({ setColorAsHsv(newHsv); }; - const handleHueSelection = hue => { + const handleHueSelection = (hue: number) => { const { s, v } = colorAsHsv; const satVal = s && v ? { s, v } : { s: 1, v: 1 }; const newHsv = { ...satVal, h: hue }; @@ -167,7 +241,7 @@ export const EuiColorPicker = ({ setColorAsHsv(newHsv); }; - const handleSwatchSelection = color => { + const handleSwatchSelection = (color: string) => { handleOnChange(color); setColorAsHsv(hexToHsv(color)); @@ -181,14 +255,14 @@ export const EuiColorPicker = ({ @@ -201,7 +275,7 @@ export const EuiColorPicker = ({ token="euiColorPicker.swatchAriaLabel" values={{ swatch }} default="Select {swatch} as the color"> - {swatchAriaLabel => ( + {(swatchAriaLabel: string) => (
+ style={{ color: showColor && color ? color : undefined }}> - {([openLabel, closeLabel]) => ( + {([openLabel, closeLabel]: ReactChild[]) => ( ); }; - -EuiColorPicker.propTypes = { - /** - * Custom element to use instead of text input - */ - button: PropTypes.node, - className: PropTypes.string, - /** - * Hex string (3 or 6 character). Empty string will register as 'transparent' - */ - color: PropTypes.string, - /** - * Use the compressed style for EuiFieldText - */ - compressed: PropTypes.bool, - disabled: PropTypes.bool, - id: PropTypes.string, - /** - * Custom validation flag - */ - isInvalid: PropTypes.bool, - /** - * Renders inline, without an input element or popover - */ - inline: PropTypes.bool, - /** - * Choose between swatches with gradient picker (default), swatches only, or gradient picker only. - */ - mode: PropTypes.oneOf(['default', 'swatch', 'picker']), - /** - * Function called when the popover closes - */ - onBlur: PropTypes.func, - /** - * (hex: string) => void - */ - onChange: PropTypes.func.isRequired, - /** - * Function called when the popover opens - */ - onFocus: PropTypes.func, - /** - * Array of hex strings (3 or 6 character) to use as swatch options. Defaults to EUI visualization colors - */ - swatches: PropTypes.arrayOf(PropTypes.string), - /** - * Custom z-index for the popover - */ - popoverZIndex: PropTypes.number, -}; diff --git a/src/components/color_picker/color_picker_swatch.tsx b/src/components/color_picker/color_picker_swatch.tsx index f67fb53ef84..006f0810acd 100644 --- a/src/components/color_picker/color_picker_swatch.tsx +++ b/src/components/color_picker/color_picker_swatch.tsx @@ -1,9 +1,4 @@ -import React, { - ButtonHTMLAttributes, - FunctionComponent, - Ref, - forwardRef, -} from 'react'; +import React, { ButtonHTMLAttributes, Ref, forwardRef } from 'react'; import classNames from 'classnames'; import { CommonProps, Omit } from '../common'; @@ -13,10 +8,11 @@ export type EuiColorPickerSwatchProps = CommonProps & color?: string; }; -export const EuiColorPickerSwatch: FunctionComponent< - EuiColorPickerSwatchProps -> = forwardRef( - ({ className, color, style, ...rest }, ref: Ref) => { +export const EuiColorPickerSwatch = forwardRef( + ( + { className, color, style, ...rest }: EuiColorPickerSwatchProps, + ref: Ref + ) => { const classes = classNames('euiColorPickerSwatch', className); return ( diff --git a/src/components/color_picker/index.d.ts b/src/components/color_picker/index.d.ts deleted file mode 100644 index 4c15e4087f7..00000000000 --- a/src/components/color_picker/index.d.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { FunctionComponent, HTMLAttributes, ReactElement } from 'react'; -import { CommonProps, Omit } from '../common'; - -import { EuiColorPickerSwatchProps } from './color_picker_swatch'; -import { EuiHueProps } from './hue'; -import { EuiSaturationProps } from './saturation'; - -declare module '@elastic/eui' { - /** - * @see './color_picker_swatch.js' - */ - export const EuiColorPickerSwatch: FunctionComponent< - EuiColorPickerSwatchProps - >; - /** - * @see './hue.js' - */ - export const EuiHue: FunctionComponent; - /** - * @see './saturation.js' - */ - export const EuiSaturation: FunctionComponent; - - /** - * @see './color_picker.js' - */ - interface HTMLDivElementOverrides { - color: string; - onBlur?: () => void; - onChange: (hex: string) => void; - onFocus?: () => void; - } - export type EuiColorPickerProps = CommonProps & - Omit, keyof HTMLDivElementOverrides> & - HTMLDivElementOverrides & { - button?: ReactElement; - compressed?: boolean; - disabled?: boolean; - fullWidth?: boolean; - isInvalid?: boolean; - mode?: 'default' | 'swatch' | 'picker'; - readOnly?: boolean; - swatches?: string[]; - popoverZIndex?: number; - }; - - export const EuiColorPicker: FunctionComponent; -} diff --git a/src/components/color_picker/index.js b/src/components/color_picker/index.ts similarity index 100% rename from src/components/color_picker/index.js rename to src/components/color_picker/index.ts diff --git a/src/components/color_picker/saturation.tsx b/src/components/color_picker/saturation.tsx index d0bdc94235a..093bdbb8044 100644 --- a/src/components/color_picker/saturation.tsx +++ b/src/components/color_picker/saturation.tsx @@ -1,5 +1,4 @@ import React, { - FunctionComponent, HTMLAttributes, KeyboardEvent, MouseEvent as ReactMouseEvent, @@ -36,6 +35,7 @@ export type SaturationPosition = Pick; interface HTMLDivElementOverrides { color?: HSV; + onChange: (color: HSV) => void; } export type EuiSaturationProps = Omit< HTMLAttributes, @@ -44,10 +44,9 @@ export type EuiSaturationProps = Omit< CommonProps & HTMLDivElementOverrides & { hex?: string; - onChange: (color: HSV) => void; }; -export const EuiSaturation: FunctionComponent = forwardRef( +export const EuiSaturation = forwardRef( ( { className, @@ -58,7 +57,7 @@ export const EuiSaturation: FunctionComponent = forwardRef( onChange, tabIndex = 0, ...rest - }, + }: EuiSaturationProps, ref: Ref ) => { const [indicator, setIndicator] = useState({ diff --git a/src/components/index.d.ts b/src/components/index.d.ts index 50edc2d4bb3..1ff46d81154 100644 --- a/src/components/index.d.ts +++ b/src/components/index.d.ts @@ -1,7 +1,6 @@ /// /// /// -/// /// /// /// From e97750e1948cce0aafb926519ea97acd516ce0ab Mon Sep 17 00:00:00 2001 From: Greg Thompson Date: Fri, 13 Sep 2019 10:03:41 -0500 Subject: [PATCH 3/8] inline docs --- .../color_picker/color_picker_example.js | 33 +++++++++++++++++++ src-docs/src/views/color_picker/inline.js | 30 +++++++++++++++++ 2 files changed, 63 insertions(+) create mode 100644 src-docs/src/views/color_picker/inline.js 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 2a7ca1e90a3..976f9ca8fb5 100644 --- a/src-docs/src/views/color_picker/color_picker_example.js +++ b/src-docs/src/views/color_picker/color_picker_example.js @@ -84,6 +84,18 @@ const modesPickerSnippet = `// Gradient map only /> `; +import { Inline } from './inline'; +const inlineSource = require('!!raw-loader!./inline'); +const inlineHtml = renderToHtml(Inline); +const inlineSnippet = `// Gradient map only + +`; + import Containers from './containers'; const containersSource = require('!!raw-loader!./containers'); const containersHtml = renderToHtml(Containers); @@ -215,6 +227,27 @@ export const ColorPickerExample = { snippet: [modesSwatchSnippet, modesPickerSnippet], demo: , }, + { + title: 'Inline', + source: [ + { + type: GuideSectionTypes.JS, + code: inlineSource, + }, + { + type: GuideSectionTypes.HTML, + code: inlineHtml, + }, + ], + text: ( +

+ Use the inline prop to display the color picker + without an input or popover. +

+ ), + snippet: inlineSnippet, + demo: , + }, { title: 'Containers', source: [ diff --git a/src-docs/src/views/color_picker/inline.js b/src-docs/src/views/color_picker/inline.js new file mode 100644 index 00000000000..918363f82ad --- /dev/null +++ b/src-docs/src/views/color_picker/inline.js @@ -0,0 +1,30 @@ +import React, { Component } from 'react'; + +import { EuiColorPicker } from '../../../../src/components'; +import { isValidHex } from '../../../../src/services'; + +export class Inline extends Component { + constructor(props) { + super(props); + this.state = { + color: '', + }; + } + + handleChange = value => { + this.setState({ color: value }); + }; + + render() { + const hasErrors = !isValidHex(this.state.color) && this.state.color !== ''; + + return ( + + ); + } +} From 3bd225a60d80de323ddd392a28b05205d64179c3 Mon Sep 17 00:00:00 2001 From: Greg Thompson Date: Fri, 13 Sep 2019 10:10:00 -0500 Subject: [PATCH 4/8] clean up --- src-docs/src/views/color_picker/color_picker_example.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) 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 976f9ca8fb5..ef104e9f711 100644 --- a/src-docs/src/views/color_picker/color_picker_example.js +++ b/src-docs/src/views/color_picker/color_picker_example.js @@ -87,8 +87,7 @@ const modesPickerSnippet = `// Gradient map only import { Inline } from './inline'; const inlineSource = require('!!raw-loader!./inline'); const inlineHtml = renderToHtml(Inline); -const inlineSnippet = `// Gradient map only - Date: Fri, 13 Sep 2019 10:13:41 -0500 Subject: [PATCH 5/8] add inline snapshot test --- .../__snapshots__/color_picker.test.tsx.snap | 183 ++++++++++++++++++ .../color_picker/color_picker.test.tsx | 12 ++ 2 files changed, 195 insertions(+) diff --git a/src/components/color_picker/__snapshots__/color_picker.test.tsx.snap b/src/components/color_picker/__snapshots__/color_picker.test.tsx.snap index 2c1ee677c44..06a2d8a4c2b 100644 --- a/src/components/color_picker/__snapshots__/color_picker.test.tsx.snap +++ b/src/components/color_picker/__snapshots__/color_picker.test.tsx.snap @@ -517,6 +517,189 @@ exports[`renders fullWidth EuiColorPicker 1`] = `
`; +exports[`renders inline EuiColorPicker 1`] = ` +
+
+
+

+ Use the arrow keys to navigate the square color gradient. The coordinates resulting from each key press will be used to calculate HSV color mode 'saturation' and 'value' numbers, in the range of 0 to 1. Left and right decrease and increase (respectively) the 'saturation' value. Up and down decrease and increase (respectively) the 'value' value. +

+

+ #ffeedd +

+
+
+
+
+
+ +

+ #ffeedd +

+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+`; + exports[`renders readOnly EuiColorPicker 1`] = `
{ expect(colorPicker).toMatchSnapshot(); }); +test('renders inline EuiColorPicker', () => { + const colorPicker = render( + + ); + expect(colorPicker).toMatchSnapshot(); +}); + test('renders EuiColorPicker with an empty swatch when color is null', () => { const colorPicker = render( From e115280ac7f3332f3d0252d5d30c2956bd596a1d Mon Sep 17 00:00:00 2001 From: Greg Thompson Date: Fri, 13 Sep 2019 12:37:34 -0500 Subject: [PATCH 6/8] forwardRef generics --- .../color_picker/color_picker_swatch.tsx | 34 +++++++++---------- src/components/color_picker/saturation.tsx | 7 ++-- 2 files changed, 19 insertions(+), 22 deletions(-) diff --git a/src/components/color_picker/color_picker_swatch.tsx b/src/components/color_picker/color_picker_swatch.tsx index 006f0810acd..9b53b8a3254 100644 --- a/src/components/color_picker/color_picker_swatch.tsx +++ b/src/components/color_picker/color_picker_swatch.tsx @@ -1,4 +1,4 @@ -import React, { ButtonHTMLAttributes, Ref, forwardRef } from 'react'; +import React, { ButtonHTMLAttributes, forwardRef } from 'react'; import classNames from 'classnames'; import { CommonProps, Omit } from '../common'; @@ -8,21 +8,19 @@ export type EuiColorPickerSwatchProps = CommonProps & color?: string; }; -export const EuiColorPickerSwatch = forwardRef( - ( - { className, color, style, ...rest }: EuiColorPickerSwatchProps, - ref: Ref - ) => { - const classes = classNames('euiColorPickerSwatch', className); +export const EuiColorPickerSwatch = forwardRef< + HTMLButtonElement, + EuiColorPickerSwatchProps +>(({ className, color, style, ...rest }, ref) => { + const classes = classNames('euiColorPickerSwatch', className); - return ( -