Skip to content

Commit

Permalink
[EuiColorPicker] Add optional color value input (#3336)
Browse files Browse the repository at this point in the history
* inputDisplay prop for popover panel input

* tests

* docs

* reinstate validation

* clean up

* use secondaryInputDispay name

* Update src-docs/src/views/color_picker/color_picker_example.js

Co-Authored-By: Caroline Horn <[email protected]>

* reinstate readonly input

* readonly and input spacing cleanup

* CL

Co-authored-by: Caroline Horn <[email protected]>
  • Loading branch information
thompsongl and cchaos authored Apr 21, 2020
1 parent d2d69ae commit 409edb9
Show file tree
Hide file tree
Showing 6 changed files with 160 additions and 81 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
- Added `popoverPlacement` prop in `EuiDatePicker` ([#3359](https://github.com/elastic/eui/pull/3359))
- Extended `EuiDatePicker`'s `startDate` and `endDate` types to accept `null` values for better interoperability ([#3343](https://github.com/elastic/eui/pull/3343))
- Added `EuiCommentList` component ([#3344](https://github.com/elastic/eui/pull/3344))
- Added secondary color value input element to `EuiColorPicker` ([#3336](https://github.com/elastic/eui/pull/3336))

**Bug Fixes**

Expand Down
23 changes: 17 additions & 6 deletions src-docs/src/views/color_picker/color_picker_example.js
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ const customButtonHtml = renderToHtml(CustomButton);
const customButtonSnippet = `<EuiColorPicker
onChange={handleChange}
color={chosenColor}
secondaryInputDisplay="top"
button={
<EuiColorPickerSwatch
color={chosenColor}
Expand All @@ -135,6 +136,7 @@ const customBadgeSnippet = `// Be sure to provide relevant accessibility to unma
onChange={handleChange}
color={chosenColor}
isInvalid={hasErrors}
secondaryInputDisplay="bottom"
button={
<EuiBadge
color={chosenColor ? chosenColor : 'hollow'}
Expand Down Expand Up @@ -467,12 +469,21 @@ export const ColorPickerExample = {
},
],
text: (
<p>
Available only in <strong>EuiColorPicker</strong>. You can optionally
use a custom button as the trigger for selection using the{' '}
<EuiCode>button</EuiCode> prop. Please remember to add accessibility
to this component, using proper button markup and aria labeling.
</p>
<>
<p>
Available only in <strong>EuiColorPicker</strong>. You can
optionally use a custom button as the trigger for selection using
the <EuiCode>button</EuiCode> prop. Please remember to add
accessibility to this component, using proper button markup and aria
labeling.
</p>
<p>
Additionally, use the <EuiCode>secondaryInputDisplay</EuiCode> prop
to show a secondary or alternative color value input. Options
include <EuiCode>top</EuiCode> and <EuiCode>bottom</EuiCode>{' '}
placement.
</p>
</>
),
snippet: [customButtonSnippet, customBadgeSnippet],
demo: <CustomButton />,
Expand Down
2 changes: 2 additions & 0 deletions src-docs/src/views/color_picker/custom_button.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export const CustomButton = () => {
<EuiColorPicker
onChange={handleColorChange}
color={color}
secondaryInputDisplay="top"
button={
<EuiColorPickerSwatch
color={selectedColor}
Expand All @@ -36,6 +37,7 @@ export const CustomButton = () => {
onChange={handleColorChange}
color={color}
isInvalid={!!errors}
secondaryInputDisplay="bottom"
button={
<EuiBadge
color={selectedColor ? selectedColor : 'hollow'}
Expand Down
36 changes: 35 additions & 1 deletion src/components/color_picker/color_picker.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -283,7 +283,7 @@ test('Setting a new alpha value calls onChange', () => {
});
});

test('default mode does redners child components', () => {
test('default mode does renders child components', () => {
const colorPicker = mount(
<EuiColorPicker onChange={onChange} color="#ffeedd" {...requiredProps} />
);
Expand Down Expand Up @@ -334,3 +334,37 @@ test('picker mode does not render swatches', () => {
const swatches = colorPicker.find('button.euiColorPicker__swatchSelect');
expect(swatches.length).toBe(0);
});

test('secondaryInputDisplay `top` has a popover panel input', () => {
const colorPicker = mount(
<EuiColorPicker
onChange={onChange}
secondaryInputDisplay="top"
color="#ffeedd"
{...requiredProps}
/>
);

findTestSubject(colorPicker, 'colorPickerAnchor').simulate('click');
const inputTop = findTestSubject(colorPicker, 'topColorPickerInput');
const inputBottom = findTestSubject(colorPicker, 'bottomColorPickerInput');
expect(inputTop.length).toBe(1);
expect(inputBottom.length).toBe(0);
});

test('secondaryInputDisplay `bottom` has a popover panel input', () => {
const colorPicker = mount(
<EuiColorPicker
onChange={onChange}
secondaryInputDisplay="bottom"
color="#ffeedd"
{...requiredProps}
/>
);

findTestSubject(colorPicker, 'colorPickerAnchor').simulate('click');
const inputTop = findTestSubject(colorPicker, 'topColorPickerInput');
const inputBottom = findTestSubject(colorPicker, 'bottomColorPickerInput');
expect(inputTop.length).toBe(0);
expect(inputBottom.length).toBe(1);
});
82 changes: 70 additions & 12 deletions src/components/color_picker/color_picker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import {
EuiFieldText,
EuiFormControlLayout,
EuiFormControlLayoutProps,
EuiFormRow,
EuiRange,
} from '../form';
import { EuiI18n } from '../i18n';
Expand All @@ -59,7 +60,7 @@ import {
} from './utils';

type EuiColorPickerDisplay = 'default' | 'inline';
type EuiColorPickerMode = 'default' | 'swatch' | 'picker';
type EuiColorPickerMode = 'default' | 'swatch' | 'picker' | 'secondaryInput';

export interface EuiColorPickerOutput {
rgba: ColorSpaces['rgba'];
Expand Down Expand Up @@ -106,7 +107,7 @@ export interface EuiColorPickerProps
*/
isInvalid?: boolean;
/**
* Choose between swatches with gradient picker (default), swatches only, or gradient picker only.
* Choose between swatches with gradient picker (default), swatches only, gradient picker only, or secondary input only.
*/
mode?: EuiColorPickerMode;
/**
Expand Down Expand Up @@ -140,6 +141,10 @@ export interface EuiColorPickerProps
* Default is to display the last format entered by the user
*/
format?: 'hex' | 'rgba';
/**
* Placement option for a secondary color value input.
*/
secondaryInputDisplay?: 'top' | 'bottom' | 'none';
}

function isKeyboardEvent(
Expand Down Expand Up @@ -201,6 +206,7 @@ export const EuiColorPicker: FunctionComponent<EuiColorPickerProps> = ({
append,
showAlpha = false,
format,
secondaryInputDisplay = 'none',
}) => {
const preferredFormat = useMemo(() => {
if (format) return format;
Expand Down Expand Up @@ -248,7 +254,8 @@ export const EuiColorPicker: FunctionComponent<EuiColorPickerProps> = ({
const classes = classNames('euiColorPicker', className);
const popoverClass = 'euiColorPicker__popoverAnchor';
const panelClasses = classNames('euiColorPicker__popoverPanel', {
'euiColorPicker__popoverPanel--pickerOnly': mode === 'picker',
'euiColorPicker__popoverPanel--pickerOnly':
mode === 'picker' && secondaryInputDisplay !== 'bottom',
'euiColorPicker__popoverPanel--customButton': button,
});
const swatchClass = 'euiColorPicker__swatchSelect';
Expand Down Expand Up @@ -414,9 +421,49 @@ export const EuiColorPicker: FunctionComponent<EuiColorPickerProps> = ({
}
};

const inlineInput = secondaryInputDisplay !== 'none' && (
<EuiI18n
tokens={[
'euiColorPicker.colorLabel',
'euiColorPicker.colorErrorMessage',
'euiColorPicker.transparent',
]}
defaults={['Color value', 'Invalid color value', 'Transparent']}>
{([colorLabel, colorErrorMessage, transparent]: string[]) => (
<EuiFormRow
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}
readOnly={readOnly}
aria-label={colorLabel}
autoComplete="off"
data-test-subj={`${secondaryInputDisplay}ColorPickerInput`}
/>
</EuiFormRow>
)}
</EuiI18n>
);

const showSecondaryInputOnly = mode === 'secondaryInput';
const showPicker = mode !== 'swatch' && !showSecondaryInputOnly;
const showSwatches = mode !== 'picker' && !showSecondaryInputOnly;

const composite = (
<React.Fragment>
{mode !== 'swatch' && (
<>
{secondaryInputDisplay === 'top' && (
<>
{inlineInput}
<EuiSpacer size="s" />
</>
)}
{showPicker && (
<div onKeyDown={handleOnKeyDown}>
<EuiSaturation
id={id}
Expand All @@ -433,7 +480,7 @@ export const EuiColorPicker: FunctionComponent<EuiColorPickerProps> = ({
/>
</div>
)}
{mode !== 'picker' && (
{showSwatches && (
<EuiFlexGroup wrap responsive={false} gutterSize="s" role="listbox">
{swatches.map((swatch, index) => (
<EuiFlexItem grow={false} key={swatch}>
Expand All @@ -456,8 +503,14 @@ export const EuiColorPicker: FunctionComponent<EuiColorPickerProps> = ({
))}
</EuiFlexGroup>
)}
{secondaryInputDisplay === 'bottom' && (
<>
{mode !== 'picker' && <EuiSpacer size="s" />}
{inlineInput}
</>
)}
{showAlpha && (
<React.Fragment>
<>
<EuiSpacer size="s" />
<EuiI18n
token="euiColorPicker.alphaLabel"
Expand All @@ -477,9 +530,9 @@ export const EuiColorPicker: FunctionComponent<EuiColorPickerProps> = ({
/>
)}
</EuiI18n>
</React.Fragment>
</>
)}
</React.Fragment>
</>
);

let buttonOrInput;
Expand Down Expand Up @@ -514,18 +567,23 @@ export const EuiColorPicker: FunctionComponent<EuiColorPickerProps> = ({
color: colorStyle,
}}>
<EuiI18n
tokens={['euiColorPicker.openLabel', 'euiColorPicker.closeLabel']}
tokens={[
'euiColorPicker.openLabel',
'euiColorPicker.closeLabel',
'euiColorPicker.transparent',
]}
defaults={[
'Press the escape key to close the popover',
'Press the down key to open a popover containing color options',
'Transparent',
]}>
{([openLabel, closeLabel]: string[]) => (
{([openLabel, closeLabel, transparent]: string[]) => (
<EuiFieldText
className={inputClasses}
onClick={handleInputActivity}
onKeyDown={handleInputActivity}
value={color ? color.toUpperCase() : HEX_FALLBACK}
placeholder={!color ? 'Transparent' : undefined}
placeholder={!color ? transparent : undefined}
id={id}
onChange={handleColorInput}
icon={chromaColor ? 'swatchInput' : 'stopSlash'}
Expand Down
Loading

0 comments on commit 409edb9

Please sign in to comment.