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 optional color value input #3336

Merged
merged 14 commits into from
Apr 21, 2020
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
- Added `panelStyle` prop to `EuiPopover` to distinguish style object configuration ([#3329](https://github.com/elastic/eui/pull/3329))
- 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