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

[Lens] Dynamic Coloring Design Tweaks #7

Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.lnsDynamicColoringRow {
align-items: center;
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import {
} from '../../shared_components/';
import { PalettePanelContainer } from './palette_panel_container';
import { findMinMaxByColumnId } from './shared_utils';
import './dimension_editor.scss';

const idPrefix = htmlIdGenerator()();

Expand Down Expand Up @@ -245,10 +246,11 @@ export function TableDimensionEditor(
</EuiFormRow>
{hasDynamicColoring && (
<EuiFormRow
className="lnsDynamicColoringRow"
display="columnCompressed"
fullWidth
label={i18n.translate('xpack.lens.paletteTableGradient.label', {
defaultMessage: 'Color palette',
defaultMessage: 'Color',
})}
>
<EuiFlexGroup alignItems="center" gutterSize="s" responsive={false}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ export function PalettePanelContainer({
>
<strong>
{i18n.translate('xpack.lens.table.palettePanelTitle', {
defaultMessage: 'Color palette settings',
defaultMessage: 'Edit color',
})}
</strong>
</h2>
Expand Down
332 changes: 169 additions & 163 deletions x-pack/plugins/lens/public/shared_components/coloring/color_stops.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@ import React, { useState, useEffect } from 'react';
import type { FocusEvent } from 'react';
import { i18n } from '@kbn/i18n';
import {
EuiFormRow,
EuiFieldNumber,
EuiColorPicker,
EuiButtonIcon,
EuiFlexItem,
EuiFlexGroup,
EuiButtonEmpty,
EuiSpacer,
EuiScreenReaderOnly,
} from '@elastic/eui';
import { DEFAULT_COLOR } from './constants';
Expand Down Expand Up @@ -70,178 +70,184 @@ export const CustomStops = ({

return (
<>
<EuiFlexGroup>
{sortedReason ? (
<EuiScreenReaderOnly>
<div>
<p aria-live="assertive">
{sortedReason
? i18n.translate('xpack.lens.dynamicColoring.customPalette.sortReason', {
defaultMessage: 'Color stops have been sorted due to new stop value {value}',
values: {
value: sortedReason,
},
})
: null}
</p>
</div>
<p aria-live="assertive">
{i18n.translate('xpack.lens.dynamicColoring.customPalette.sortReason', {
defaultMessage: 'Color stops have been sorted due to new stop value {value}',
values: {
value: sortedReason,
},
})}
</p>
</EuiScreenReaderOnly>
<EuiFlexItem data-test-subj={`${dataTestPrefix}_dynamicColoring_custom_stops`}>
{localColorStops.map(({ color, stop }, index) => {
const prevStopValue = Number(localColorStops[index - 1]?.stop ?? -Infinity);
const nextStopValue = Number(localColorStops[index + 1]?.stop ?? Infinity);
const errorMessages = [];
// do not show color error messages if number field is already in error
if (!isValidColor(color) && errorMessages.length === 0) {
errorMessages.push(
i18n.translate('xpack.lens.dynamicColoring.customPalette.hexWarningLabel', {
defaultMessage: 'Color must provide a valid hex value',
})
);
}
return (
<EuiFormRow
key={index}
display="rowCompressed"
isInvalid={Boolean(errorMessages.length)}
error={errorMessages[0]}
data-test-subj={`${dataTestPrefix}_dynamicColoring_stop_row_${index}`}
onBlur={(e: FocusEvent<HTMLDivElement>) => {
// sort the stops when the focus leaves the row container
const shouldSort = Number(stop) > nextStopValue || prevStopValue > Number(stop);
const isFocusStillInContent =
(e.currentTarget as Node)?.contains(e.relatedTarget as Node) || popoverInFocus;
if (shouldSort && !isFocusStillInContent) {
setLocalColorStops(
[...localColorStops].sort(
({ stop: stopA }, { stop: stopB }) => Number(stopA) - Number(stopB)
)
);
setSortReason(stop);
}
}}
>
<EuiFlexGroup gutterSize="s" responsive={false}>
<EuiFlexItem>
<EuiFieldNumber
compressed
data-test-subj={`${dataTestPrefix}_dynamicColoring_stop_value_${index}`}
value={stop}
min={-Infinity}
onChange={({ target }) => {
const newStopString = target.value.trim();
const newColorStops = [...localColorStops];
newColorStops[index] = {
color,
stop: newStopString,
};
setLocalColorStops(newColorStops);
}}
append={rangeType === 'percent' ? '%' : undefined}
) : null}

<EuiFlexGroup
data-test-subj={`${dataTestPrefix}_dynamicColoring_custom_stops`}
direction="column"
gutterSize="s"
>
{localColorStops.map(({ color, stop }, index) => {
const prevStopValue = Number(localColorStops[index - 1]?.stop ?? -Infinity);
const nextStopValue = Number(localColorStops[index + 1]?.stop ?? Infinity);

// TODO: Commented out the below error message code because 1) the individual color stops no longer use an `EuiFormRow` component and 2) I don't think such error message are needed, as the `EuiColorPicker` dropdown already has the hex field displaying such an error in the case of an invalid hex value. Instead of having a redundant error message, can we instead change it so that if an invalid hex value remains on blur of the `EuiColorPicker`, we simply revert to last known good hex value?
// const errorMessages = [];
// // do not show color error messages if number field is already in error
// if (!isValidColor(color) && errorMessages.length === 0) {
// errorMessages.push(
// i18n.translate('xpack.lens.dynamicColoring.customPalette.hexWarningLabel', {
// defaultMessage: 'Color must provide a valid hex value',
// })
// );
// }

return (
<EuiFlexItem
key={index}
// TODO: Commented out the below props, as they no longer apply to this component (as it has changed to an `EuiFlexItem`). Please see above comment for an alternative to adding a duplicative error message.
// isInvalid={Boolean(errorMessages.length)}
// error={errorMessages[0]}
data-test-subj={`${dataTestPrefix}_dynamicColoring_stop_row_${index}`}
onBlur={(e: FocusEvent<HTMLDivElement>) => {
// sort the stops when the focus leaves the row container
const shouldSort = Number(stop) > nextStopValue || prevStopValue > Number(stop);
const isFocusStillInContent =
(e.currentTarget as Node)?.contains(e.relatedTarget as Node) || popoverInFocus;
if (shouldSort && !isFocusStillInContent) {
setLocalColorStops(
[...localColorStops].sort(
({ stop: stopA }, { stop: stopB }) => Number(stopA) - Number(stopB)
)
);
setSortReason(stop);
}
}}
>
<EuiFlexGroup alignItems="center" gutterSize="s" responsive={false}>
<EuiFlexItem>
<EuiFieldNumber
compressed
data-test-subj={`${dataTestPrefix}_dynamicColoring_stop_value_${index}`}
value={stop}
min={-Infinity}
onChange={({ target }) => {
const newStopString = target.value.trim();
const newColorStops = [...localColorStops];
newColorStops[index] = {
color,
stop: newStopString,
};
setLocalColorStops(newColorStops);
}}
append={rangeType === 'percent' ? '%' : undefined}
aria-label={i18n.translate(
'xpack.lens.dynamicColoring.customPalette.stopAriaLabel',
{
defaultMessage: 'Stop {index}',
values: {
index: index + 1,
},
}
)}
/>
</EuiFlexItem>

<EuiFlexItem
data-test-subj={`${dataTestPrefix}_dynamicColoring_stop_color_${index}`}
>
<EuiColorPicker
key={stop}
onChange={(newColor) => {
const newColorStops = [...localColorStops];
newColorStops[index] = { color: newColor, stop };
setLocalColorStops(newColorStops);
}}
secondaryInputDisplay="top"
color={color}
isInvalid={!isValidColor(color)}
showAlpha
compressed
onFocus={() => setPopoverInFocus(true)}
onBlur={() => setPopoverInFocus(false)}
/>
</EuiFlexItem>

<EuiFlexItem grow={false}>
<TooltipWrapper
tooltipContent={i18n.translate(
'xpack.lens.dynamicColoring.customPalette.deleteButtonDisabled',
{
defaultMessage:
'This color stop cannot be deleted, as two or more stops are required',
}
)}
condition={!shouldEnableDelete}
>
<EuiButtonIcon
iconType="trash"
color="danger"
aria-label={i18n.translate(
'xpack.lens.dynamicColoring.customPalette.stopAriaLabel',
'xpack.lens.dynamicColoring.customPalette.deleteButtonAriaLabel',
{
defaultMessage: 'Stop {index}',
values: {
index: index + 1,
},
defaultMessage: 'Delete',
}
)}
/>
</EuiFlexItem>
<EuiFlexItem
data-test-subj={`${dataTestPrefix}_dynamicColoring_stop_color_${index}`}
>
<EuiColorPicker
key={stop}
onChange={(newColor) => {
const newColorStops = [...localColorStops];
newColorStops[index] = { color: newColor, stop };
setLocalColorStops(newColorStops);
}}
secondaryInputDisplay="top"
color={color}
isInvalid={!isValidColor(color)}
showAlpha
compressed
onFocus={() => setPopoverInFocus(true)}
onBlur={() => setPopoverInFocus(false)}
/>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<TooltipWrapper
tooltipContent={i18n.translate(
'xpack.lens.dynamicColoring.customPalette.deleteButtonDisabled',
title={i18n.translate(
'xpack.lens.dynamicColoring.customPalette.deleteButtonLabel',
{
defaultMessage:
'This color stop cannot be deleted, as two or more stops are required',
defaultMessage: 'Delete',
}
)}
condition={!shouldEnableDelete}
>
<EuiButtonIcon
iconType="trash"
color="danger"
aria-label={i18n.translate(
'xpack.lens.dynamicColoring.customPalette.deleteButtonAriaLabel',
{
defaultMessage: 'Delete',
}
)}
title={i18n.translate(
'xpack.lens.dynamicColoring.customPalette.deleteButtonLabel',
{
defaultMessage: 'Delete',
}
)}
onClick={() => {
const newColorStops = localColorStops.filter((_, i) => i !== index);
setLocalColorStops(newColorStops);
}}
data-test-subj={`${dataTestPrefix}_dynamicColoring_removeStop_${index}`}
isDisabled={!shouldEnableDelete}
/>
</TooltipWrapper>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFormRow>
);
})}
</EuiFlexItem>
</EuiFlexGroup>
<EuiFlexGroup>
<EuiFlexItem grow={false}>
<EuiButtonEmpty
data-test-subj={`${dataTestPrefix}_dynamicColoring_addStop`}
iconType="plusInCircle"
color="primary"
aria-label={i18n.translate('xpack.lens.dynamicColoring.customPalette.addColorStop', {
defaultMessage: 'Add color stop',
})}
size="xs"
onClick={() => {
const newColorStops = [...localColorStops];
const length = newColorStops.length;
const { max } = getDataMinMax(rangeType, dataBounds);
const step = getStepValue(
colorStops,
newColorStops.map(({ color, stop }) => ({ color, stop: Number(stop) })),
max
);
const prevColor = localColorStops[length - 1].color || DEFAULT_COLOR;
const newStop = step + Number(localColorStops[length - 1].stop);
newColorStops.push({
color: prevColor,
stop: String(newStop),
});
setLocalColorStops(newColorStops);
}}
>
{i18n.translate('xpack.lens.dynamicColoring.customPalette.addColorStop', {
defaultMessage: 'Add color stop',
})}
</EuiButtonEmpty>
</EuiFlexItem>
onClick={() => {
const newColorStops = localColorStops.filter((_, i) => i !== index);
setLocalColorStops(newColorStops);
}}
data-test-subj={`${dataTestPrefix}_dynamicColoring_removeStop_${index}`}
isDisabled={!shouldEnableDelete}
/>
</TooltipWrapper>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
);
})}
</EuiFlexGroup>

<EuiSpacer size="s" />

<EuiButtonEmpty
data-test-subj={`${dataTestPrefix}_dynamicColoring_addStop`}
iconType="plusInCircle"
color="primary"
aria-label={i18n.translate('xpack.lens.dynamicColoring.customPalette.addColorStop', {
defaultMessage: 'Add color stop',
})}
size="xs"
flush="left"
onClick={() => {
const newColorStops = [...localColorStops];
const length = newColorStops.length;
const { max } = getDataMinMax(rangeType, dataBounds);
const step = getStepValue(
colorStops,
newColorStops.map(({ color, stop }) => ({ color, stop: Number(stop) })),
max
);
const prevColor = localColorStops[length - 1].color || DEFAULT_COLOR;
const newStop = step + Number(localColorStops[length - 1].stop);
newColorStops.push({
color: prevColor,
stop: String(newStop),
});
setLocalColorStops(newColorStops);
}}
>
{i18n.translate('xpack.lens.dynamicColoring.customPalette.addColorStop', {
defaultMessage: 'Add color stop',
})}
</EuiButtonEmpty>
</>
);
};
Loading