Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[EuiColorPicker] Add isClearable and placeholder options #3689

Merged
merged 10 commits into from
Jul 2, 2020
37 changes: 37 additions & 0 deletions src-docs/src/views/color_picker/color_picker_example.js
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,17 @@ const customBadgeSnippet = `// Be sure to provide relevant accessibility to unma
/>
`;

import Empty from './empty_state';
const emptySource = require('!!raw-loader!./empty_state');
const emptyHtml = renderToHtml(CustomButton);
const emptySnippet = `<EuiColorPicker
onChange={handleChange}
color={chosenColor}
placeholder="Auto"
isClearable={true}
/>
`;

import Modes from './modes';
const modesSource = require('!!raw-loader!./modes');
const modesHtml = renderToHtml(Modes);
Expand Down Expand Up @@ -551,6 +562,32 @@ export const ColorPickerExample = {
snippet: [customButtonSnippet, customBadgeSnippet],
demo: <CustomButton />,
},
{
title: 'Empty state',
source: [
{
type: GuideSectionTypes.JS,
code: emptySource,
},
{
type: GuideSectionTypes.HTML,
code: emptyHtml,
},
],
text: (
<>
<p>
For instances where an &quot;empty&quot; color picker has meaning
other than transparent color value, use the{' '}
<EuiCode>placeholder</EuiCode> prop to provide context. Removing
color selection and returning to the default state can be made
easier by setting <EuiCode>isClearable=true</EuiCode>.
</p>
</>
),
snippet: emptySnippet,
demo: <Empty />,
},
{
title: 'Inline',
source: [
Expand Down
1 change: 1 addition & 0 deletions src-docs/src/views/color_picker/custom_button.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ export default () => {
aria-label="Select a new color"
/>
}
isClearable={true}
/>
</EuiFormRow>
<EuiSpacer />
Expand Down
24 changes: 24 additions & 0 deletions src-docs/src/views/color_picker/empty_state.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import React, { useMemo } from 'react';

import { EuiColorPicker, EuiFormRow } from '../../../../src/components';

import { useColorPickerState } from '../../../../src/services';

export default () => {
const [color, setColor, errors] = useColorPickerState();
const isInvalid = useMemo(() => color !== '' && !!errors, [color, errors]);

return (
<React.Fragment>
<EuiFormRow label="Pick a color" isInvalid={isInvalid} error={errors}>
<EuiColorPicker
onChange={setColor}
color={color}
isInvalid={isInvalid}
placeholder="Auto"
isClearable={true}
/>
</EuiFormRow>
</React.Fragment>
);
};
139 changes: 139 additions & 0 deletions src/components/color_picker/__snapshots__/color_picker.test.tsx.snap
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,81 @@ exports[`renders EuiColorPicker 1`] = `
</div>
`;

exports[`renders EuiColorPicker with a clearable input 1`] = `
<div
class="euiPopover euiPopover--anchorDownLeft euiPopover--displayBlock euiColorPicker__popoverAnchor"
>
<div
class="euiPopover__anchor"
>
<div
class="euiFormControlLayout"
>
<div
class="euiFormControlLayout__childrenWrapper"
>
<div
style="color:rgb(255,238,221)"
>
<div
class="euiFormControlLayout"
>
<div
class="euiFormControlLayout__childrenWrapper"
>
<input
aria-label="Press the down key to open a popover containing color options"
autocomplete="off"
class="euiFieldText euiColorPicker__input euiFieldText--withIcon"
data-test-subj="colorPickerAnchor"
type="text"
value="#FFEEDD"
/>
<div
class="euiFormControlLayoutIcons"
>
<span
class="euiFormControlLayoutCustomIcon"
>
<div
aria-hidden="true"
class="euiFormControlLayoutCustomIcon__icon"
data-euiicon-type="swatchInput"
/>
</span>
</div>
</div>
</div>
</div>
<div
class="euiFormControlLayoutIcons euiFormControlLayoutIcons--right"
>
<button
aria-label="Clear input"
class="euiFormControlLayoutClearButton"
type="button"
>
<div
class="euiFormControlLayoutClearButton__icon"
data-euiicon-type="cross"
/>
</button>
<span
class="euiFormControlLayoutCustomIcon"
>
<div
aria-hidden="true"
class="euiFormControlLayoutCustomIcon__icon"
data-euiicon-type="arrowDown"
/>
</span>
</div>
</div>
</div>
</div>
</div>
`;

exports[`renders EuiColorPicker with a color swatch when color is defined 1`] = `
<div
class="euiPopover euiPopover--anchorDownLeft euiPopover--displayBlock euiColorPicker__popoverAnchor"
Expand Down Expand Up @@ -130,6 +205,70 @@ exports[`renders EuiColorPicker with a color swatch when color is defined 1`] =
</div>
`;

exports[`renders EuiColorPicker with a custom placeholder 1`] = `
<div
class="euiPopover euiPopover--anchorDownLeft euiPopover--displayBlock euiColorPicker__popoverAnchor"
>
<div
class="euiPopover__anchor"
>
<div
class="euiFormControlLayout"
>
<div
class="euiFormControlLayout__childrenWrapper"
>
<div>
<div
class="euiFormControlLayout"
>
<div
class="euiFormControlLayout__childrenWrapper"
>
<input
aria-label="Press the down key to open a popover containing color options"
autocomplete="off"
class="euiFieldText euiColorPicker__input euiFieldText--withIcon"
data-test-subj="colorPickerAnchor"
placeholder="Auto"
type="text"
value=""
/>
<div
class="euiFormControlLayoutIcons"
>
<span
class="euiFormControlLayoutCustomIcon"
>
<div
aria-hidden="true"
class="euiFormControlLayoutCustomIcon__icon"
data-euiicon-type="stopSlash"
/>
</span>
</div>
</div>
</div>
</div>
<div
class="euiFormControlLayoutIcons euiFormControlLayoutIcons--right"
>
<span
class="euiFormControlLayoutCustomIcon"
>
<div
aria-hidden="true"
class="euiFormControlLayoutCustomIcon__icon"
data-euiicon-type="arrowDown"
/>
</span>
</div>
</div>
</div>
</div>
</div>
`;

exports[`renders EuiColorPicker with an empty swatch when color is "" 1`] = `
<div
class="euiPopover euiPopover--anchorDownLeft euiPopover--displayBlock euiColorPicker__popoverAnchor"
Expand Down
38 changes: 38 additions & 0 deletions src/components/color_picker/color_picker.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,25 @@ test('renders EuiColorPicker with a color swatch when color is defined', () => {
expect(colorPicker).toMatchSnapshot();
});

test('renders EuiColorPicker with a custom placeholder', () => {
const colorPicker = render(
<EuiColorPicker onChange={onChange} placeholder="Auto" {...requiredProps} />
);
expect(colorPicker).toMatchSnapshot();
});

test('renders EuiColorPicker with a clearable input', () => {
const colorPicker = render(
<EuiColorPicker
onChange={onChange}
color={'#ffeedd'}
isClearable={true}
{...requiredProps}
/>
);
expect(colorPicker).toMatchSnapshot();
});

test('popover color selector is not shown by default', () => {
const colorPicker = mount(
<EuiColorPicker onChange={onChange} color="#ffeedd" {...requiredProps} />
Expand Down Expand Up @@ -282,6 +301,25 @@ test('Setting a new alpha value calls onChange', () => {
});
});

test('Clicking the "clear" button calls onChange', () => {
const colorPicker = mount(
<EuiColorPicker
onChange={onChange}
color="#ffeedd"
isClearable={true}
{...requiredProps}
/>
);

colorPicker.find('.euiFormControlLayoutClearButton').simulate('click');
expect(onChange).toBeCalled();
expect(onChange).toBeCalledWith('', {
hex: '',
isValid: false,
rgba: [NaN, NaN, NaN, 1],
});
});

test('default mode does renders child components', () => {
const colorPicker = mount(
<EuiColorPicker onChange={onChange} color="#ffeedd" {...requiredProps} />
Expand Down
55 changes: 43 additions & 12 deletions src/components/color_picker/color_picker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,14 @@ export interface EuiColorPickerProps
* Placement option for a secondary color value input.
*/
secondaryInputDisplay?: 'top' | 'bottom' | 'none';
/**
* Add a button to the primary input to clear its value.
*/
isClearable?: boolean;
/**
* Text to replace the default 'Transparent' placeholder for unset color values.
*/
placeholder?: string;
}

function isKeyboardEvent(
Expand Down Expand Up @@ -207,6 +215,8 @@ export const EuiColorPicker: FunctionComponent<EuiColorPickerProps> = ({
showAlpha = false,
format,
secondaryInputDisplay = 'none',
isClearable = false,
placeholder,
}) => {
const preferredFormat = useMemo(() => {
if (format) return format;
Expand Down Expand Up @@ -361,6 +371,13 @@ export const EuiColorPicker: FunctionComponent<EuiColorPickerProps> = ({
}
};

const handleClearInput = () => {
handleOnChange('');
if (secondaryInputDisplay === 'none' && isColorSelectorShown) {
closeColorSelector();
}
};

const updateWithHsv = (hsv: ColorSpaces['hsv']) => {
const color = chroma.hsv(...hsv).alpha(alphaChannel);
let formatted;
Expand Down Expand Up @@ -436,18 +453,27 @@ export const EuiColorPicker: FunctionComponent<EuiColorPickerProps> = ({
display="rowCompressed"
isInvalid={isInvalid}
error={isInvalid ? colorErrorMessage : null}>
<EuiFieldText
compressed={true}
value={color ? color.toUpperCase() : HEX_FALLBACK}
placeholder={!color ? transparent : undefined}
onChange={handleColorInput}
isInvalid={isInvalid}
disabled={disabled}
<EuiFormControlLayout
clear={
isClearable && color && !readOnly && !disabled
? { onClick: handleClearInput }
: undefined
}
readOnly={readOnly}
aria-label={colorLabel}
autoComplete="off"
data-test-subj={`${secondaryInputDisplay}ColorPickerInput`}
/>
compressed={compressed}>
<EuiFieldText
compressed={true}
value={color ? color.toUpperCase() : HEX_FALLBACK}
placeholder={!color ? placeholder || transparent : undefined}
onChange={handleColorInput}
isInvalid={isInvalid}
disabled={disabled}
readOnly={readOnly}
aria-label={colorLabel}
autoComplete="off"
data-test-subj={`${secondaryInputDisplay}ColorPickerInput`}
/>
</EuiFormControlLayout>
</EuiFormRow>
)}
</EuiI18n>
Expand Down Expand Up @@ -557,6 +583,11 @@ export const EuiColorPicker: FunctionComponent<EuiColorPickerProps> = ({
}
: undefined
}
clear={
isClearable && color && !readOnly && !disabled
? { onClick: handleClearInput }
: undefined
}
readOnly={readOnly}
fullWidth={fullWidth}
compressed={compressed}
Expand Down Expand Up @@ -585,7 +616,7 @@ export const EuiColorPicker: FunctionComponent<EuiColorPickerProps> = ({
onClick={handleInputActivity}
onKeyDown={handleInputActivity}
value={color ? color.toUpperCase() : HEX_FALLBACK}
placeholder={!color ? transparent : undefined}
placeholder={!color ? placeholder || transparent : undefined}
id={id}
onChange={handleColorInput}
icon={chromaColor ? 'swatchInput' : 'stopSlash'}
Expand Down