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

[Emotion] Convert EuiSaturation and EuiHue #7859

Merged
Original file line number Diff line number Diff line change
Expand Up @@ -308,12 +308,12 @@ exports[`EuiColorPicker inline 1`] = `
/>
</div>
<button
aria-describedby="undefined-instructions"
aria-describedby="generated-id-instructions"
aria-label="#ffeedd"
aria-roledescription="HSV color mode saturation and value 2-axis slider"
class="euiSaturation__indicator emotion-euiSaturation__indicator"
id="undefined-saturationIndicator"
style="left: 0px; top: 0px;"
id="generated-id-saturationIndicator"
style="inset-inline-start: 0; inset-block-start: 0;"
/>
<span
aria-live="assertive"
Expand All @@ -323,7 +323,7 @@ exports[`EuiColorPicker inline 1`] = `
</span>
<span
hidden=""
id="undefined-instructions"
id="generated-id-instructions"
>
Arrow keys to navigate the square color gradient. Coordinates will be used to calculate HSV color mode 'saturation' and 'value' numbers, in the range of 0 to 1. Left and right to change the saturation. Up and down change the value.
</span>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,19 @@ exports[`EuiHue accepts a color 1`] = `
/>
</div>
<button
aria-describedby="undefined-instructions"
aria-describedby="generated-id-instructions"
aria-roledescription="HSV color mode saturation and value 2-axis slider"
class="euiSaturation__indicator emotion-euiSaturation__indicator"
id="undefined-saturationIndicator"
style="left: 0px; top: 0px;"
id="generated-id-saturationIndicator"
style="inset-inline-start: 0; inset-block-start: 0;"
/>
<span
aria-live="assertive"
hidden=""
/>
<span
hidden=""
id="undefined-instructions"
id="generated-id-instructions"
>
Arrow keys to navigate the square color gradient. Coordinates will be used to calculate HSV color mode 'saturation' and 'value' numbers, in the range of 0 to 1. Left and right to change the saturation. Up and down change the value.
</span>
Expand All @@ -51,19 +51,19 @@ exports[`EuiHue is rendered 1`] = `
/>
</div>
<button
aria-describedby="undefined-instructions"
aria-describedby="generated-id-instructions"
aria-roledescription="HSV color mode saturation and value 2-axis slider"
class="euiSaturation__indicator emotion-euiSaturation__indicator"
id="undefined-saturationIndicator"
style="left: 0px; top: 0px;"
id="generated-id-saturationIndicator"
style="inset-inline-start: 0; inset-block-start: 0;"
/>
<span
aria-live="assertive"
hidden=""
/>
<span
hidden=""
id="undefined-instructions"
id="generated-id-instructions"
>
Arrow keys to navigate the square color gradient. Coordinates will be used to calculate HSV color mode 'saturation' and 'value' numbers, in the range of 0 to 1. Left and right to change the saturation. Up and down change the value.
</span>
Expand Down
159 changes: 90 additions & 69 deletions packages/eui/src/components/color_picker/saturation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,19 @@ import React, {
useEffect,
useRef,
useState,
useCallback,
} from 'react';
import classNames from 'classnames';
import { ColorSpaces } from 'chroma-js';

import { keys, useMouseMove, useEuiMemoizedStyles } from '../../services';
import {
keys,
useMouseMove,
useEuiMemoizedStyles,
useGeneratedHtmlId,
} from '../../services';
import { isNil } from '../../services/predicate';
import { logicalStyles } from '../../global_styling';
import { CommonProps } from '../common';
import { useEuiI18n } from '../i18n';

Expand Down Expand Up @@ -54,13 +61,19 @@ export const EuiSaturation = forwardRef<HTMLDivElement, EuiSaturationProps>(
color = colorDefaultValue,
'data-test-subj': dataTestSubj = 'euiSaturation',
hex,
id,
id: _id,
onChange,
onKeyDown,
...rest
},
ref
) => {
const classes = classNames('euiSaturation', className);
const styles = useEuiMemoizedStyles(euiSaturationStyles);

const id = useGeneratedHtmlId({ conditionalId: _id });
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice catch! 👍

const instructionsId = `${id}-instructions`;
const indicatorId = `${id}-saturationIndicator`;
const [roleDescString, instructionsString] = useEuiI18n(
['euiSaturation.ariaLabel', 'euiSaturation.screenReaderInstructions'],
[
Expand Down Expand Up @@ -89,75 +102,83 @@ export const EuiSaturation = forwardRef<HTMLDivElement, EuiSaturationProps>(
}
}, [color, lastColor]);

const calculateColor = ({
top,
height,
left,
width,
}: SaturationClientRect): ColorSpaces['hsv'] => {
const [h] = color;
const s = left / width;
const v = 1 - top / height;
return [h, s, v];
};

const handleUpdate = (box: SaturationClientRect) => {
const { left, top } = box;
setIndicator({ left, top });
const newColor = calculateColor(box);
setLastColor(newColor);
onChange(newColor);
};
const handleChange = (location: { x: number; y: number }) => {
if (isNil(boxRef?.current)) return;
const box = getEventPosition(location, boxRef.current);
handleUpdate(box);
};
const calculateColor = useCallback(
({
top,
height,
left,
width,
}: SaturationClientRect): ColorSpaces['hsv'] => {
const [h] = color;
const s = left / width;
const v = 1 - top / height;
return [h, s, v];
},
[color]
);

const handleUpdate = useCallback(
(box: SaturationClientRect) => {
const { left, top } = box;
setIndicator({ left, top });
const newColor = calculateColor(box);
setLastColor(newColor);
onChange(newColor);
},
[calculateColor, onChange]
);
const handleChange = useCallback(
(location: { x: number; y: number }) => {
if (isNil(boxRef?.current)) return;
const box = getEventPosition(location, boxRef.current);
handleUpdate(box);
},
[handleUpdate]
);

const [handleMouseDown, handleInteraction] = useMouseMove(
handleChange,
boxRef.current
);
const handleKeyDown = (event: KeyboardEvent<HTMLDivElement>) => {
onKeyDown?.(event);
if (isNil(boxRef?.current)) return;
const { height, width } = boxRef.current.getBoundingClientRect();
const { left, top } = indicator;
const heightScale = height / 100;
const widthScale = width / 100;
let newLeft = left;
let newTop = top;

switch (event.key) {
case keys.ARROW_DOWN:
event.preventDefault();
newTop = top < height ? top + heightScale : height;
break;
case keys.ARROW_LEFT:
event.preventDefault();
newLeft = left > 0 ? left - widthScale : 0;
break;
case keys.ARROW_UP:
event.preventDefault();
newTop = top > 0 ? top - heightScale : 0;
break;
case keys.ARROW_RIGHT:
event.preventDefault();
newLeft = left < width ? left + widthScale : width;
break;
default:
return;
}

const newPosition = { left: newLeft, top: newTop };
setIndicator(newPosition);
const newColor = calculateColor({ width, height, ...newPosition });
onChange(newColor);
};

const classes = classNames('euiSaturation', className);
const styles = useEuiMemoizedStyles(euiSaturationStyles);

const instructionsId = `${id}-instructions`;
const handleKeyDown = useCallback(
(event: KeyboardEvent<HTMLDivElement>) => {
onKeyDown?.(event);
if (isNil(boxRef?.current)) return;
const { height, width } = boxRef.current.getBoundingClientRect();
const { left, top } = indicator;
const heightScale = height / 100;
const widthScale = width / 100;
let newLeft = left;
let newTop = top;

switch (event.key) {
case keys.ARROW_DOWN:
event.preventDefault();
newTop = top < height ? top + heightScale : height;
break;
case keys.ARROW_LEFT:
event.preventDefault();
newLeft = left > 0 ? left - widthScale : 0;
break;
case keys.ARROW_UP:
event.preventDefault();
newTop = top > 0 ? top - heightScale : 0;
break;
case keys.ARROW_RIGHT:
event.preventDefault();
newLeft = left < width ? left + widthScale : width;
break;
default:
return;
}

const newPosition = { left: newLeft, top: newTop };
setIndicator(newPosition);
const newColor = calculateColor({ width, height, ...newPosition });
onChange(newColor);
},
[calculateColor, indicator, onChange, onKeyDown]
);

return (
<div
Expand Down Expand Up @@ -186,10 +207,10 @@ export const EuiSaturation = forwardRef<HTMLDivElement, EuiSaturationProps>(
/>
</div>
<button
id={`${id}-saturationIndicator`}
id={indicatorId}
css={styles.euiSaturation__indicator}
className="euiSaturation__indicator"
style={{ ...indicator }}
style={logicalStyles(indicator)}
aria-roledescription={roleDescString}
aria-label={hex}
aria-describedby={instructionsId}
Expand Down