@@ -38,24 +38,24 @@ exports[`EuiHue accepts a color 1`] = `
exports[`EuiHue is rendered 1`] = `
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.
diff --git a/packages/eui/src/components/color_picker/_hue.scss b/packages/eui/src/components/color_picker/_hue.scss
deleted file mode 100644
index 32e48a33177..00000000000
--- a/packages/eui/src/components/color_picker/_hue.scss
+++ /dev/null
@@ -1,87 +0,0 @@
-// This wraps the range. It is needed because there is no way to do a linear gradient in ie11 for the track
-.euiHue {
- // stylelint-disable color-no-hex
- background: linear-gradient(to right,
- #FF3232 0%,
- #FFF130 20%,
- #45FF30 35%,
- #28FFF0 52%,
- #282CFF 71%,
- #FF28FB 88%,
- #FF0094 100%
- );
- // stylelint-enable color-no-hex
- height: $euiSizeL;
- position: relative;
-
- // To make our fake range skinny, we add some pseudo borders to fake the height of the gradient
- &::before,
- &::after {
- content: '';
- left: 0;
- position: absolute;
- height: $euiSizeS;
- background: $euiColorEmptyShade;
- width: 100%;
- }
-
- &::after {
- bottom: 0;
- }
-}
-
-// The range itself is the same height
-.euiHue__range {
- @include euiRangeThumbPerBrowser {
- @include euiCustomControl($type: 'round');
- @include euiRangeThumbStyle;
- }
-
- position: relative;
- height: $euiSizeL;
- width: calc(100% + 2px); // Allow for overlap
- margin: 0 -1px; // Use ^ overlap to allow thumb to fully cover gradient ends
- appearance: none;
- background: transparent;
- z-index: 2; // Needed to place the thumb above the :after pseudo border from .euiRange
-
- // Resets for the range
-
- // Disable linter for these very unique vendor controls
- // stylelint-disable property-no-vendor-prefix, selector-no-vendor-prefix
- &::-webkit-slider-thumb {
- -webkit-appearance: none;
- margin-top: 0;
- }
-
- &::-ms-thumb {
- margin-top: 0;
- }
-
- &::-ms-track {
- height: $euiSizeL;
- background: transparent;
- border-color: transparent;
- color: transparent;
- }
-
- &::-moz-focus-outer {
- border: none;
- }
-
- &::-ms-fill-lower,
- &::-ms-fill-upper {
- background: transparent;
- }
-
- // Thumb has trouble with animation, so we make something similar to `@include euiFocusRing`
- &:focus {
- @include euiRangeThumbPerBrowser {
- box-shadow: 0 0 0 $euiFocusRingSize $euiFocusRingColor;
- border-color: $euiColorPrimary;
- }
-
- // Focus is added to the thumb ^^ so we can remove the outer wrapping outline
- outline: none;
- }
-}
diff --git a/packages/eui/src/components/color_picker/_index.scss b/packages/eui/src/components/color_picker/_index.scss
deleted file mode 100644
index debe011a3c7..00000000000
--- a/packages/eui/src/components/color_picker/_index.scss
+++ /dev/null
@@ -1,3 +0,0 @@
-@import 'variables';
-@import 'hue';
-@import 'saturation';
diff --git a/packages/eui/src/components/color_picker/_saturation.scss b/packages/eui/src/components/color_picker/_saturation.scss
deleted file mode 100644
index 53d32cdab97..00000000000
--- a/packages/eui/src/components/color_picker/_saturation.scss
+++ /dev/null
@@ -1,57 +0,0 @@
-
-.euiSaturation {
- position: relative;
- width: 100%;
- padding-bottom: 100%;
- border-radius: $euiBorderRadius / 2;
- touch-action: none; // prevent TouchMove events from scrolling page
- z-index: 3; // Required to be above the hue slider, which can overlap
-
- .euiSaturation__lightness,
- .euiSaturation__saturation {
- position: absolute;
- top: -1px; // hides a slight color inconsistency
- bottom: 0;
- left: 0;
- right: 0;
- border-radius: $euiBorderRadius / 2;
- }
-
- .euiSaturation__lightness {
- background: linear-gradient(to right, $euiColorPickerValueRange0, $euiColorPickerValueRange1);
- }
-
- .euiSaturation__saturation {
- background: linear-gradient(to top, $euiColorPickerSaturationRange0, $euiColorPickerSaturationRange1);
- }
-
- .euiSaturation__indicator {
- position: absolute;
- height: $euiColorPickerIndicatorSize;
- width: $euiColorPickerIndicatorSize;
- border-radius: 100%;
- margin-top: $euiColorPickerIndicatorSize * -.5;
- margin-left: $euiColorPickerIndicatorSize * -.5;
- border: 1px solid $euiColorDarkestShade;
-
- &::before {
- content: '';
- position: absolute;
- top: 0;
- left: 0;
- right: 0;
- bottom: 0;
- border-radius: 100%;
- border: 1px solid $euiColorLightestShade;
- }
- }
-
- &:focus {
- outline: none; // Hide focus ring because of `tabindex=0` on Safari
-
- .euiSaturation__indicator {
- box-shadow: 0 0 0 $euiFocusRingSize $euiFocusRingColor;
- border-color: $euiColorPrimary;
- }
- }
-}
diff --git a/packages/eui/src/components/color_picker/_variables.scss b/packages/eui/src/components/color_picker/_variables.scss
deleted file mode 100644
index 08cd1445f7b..00000000000
--- a/packages/eui/src/components/color_picker/_variables.scss
+++ /dev/null
@@ -1,5 +0,0 @@
-$euiColorPickerValueRange0: rgba(255, 255, 255, 1);
-$euiColorPickerValueRange1: rgba(255, 255, 255, 0);
-$euiColorPickerSaturationRange0: rgba(0, 0, 0, 1);
-$euiColorPickerSaturationRange1: rgba(0, 0, 0, 0);
-$euiColorPickerIndicatorSize: $euiSizeM;
diff --git a/packages/eui/src/components/color_picker/hue.styles.ts b/packages/eui/src/components/color_picker/hue.styles.ts
new file mode 100644
index 00000000000..49811a9524b
--- /dev/null
+++ b/packages/eui/src/components/color_picker/hue.styles.ts
@@ -0,0 +1,95 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import { css } from '@emotion/react';
+
+import { UseEuiTheme, transparentize } from '../../services';
+import { logicalCSS, mathWithUnits } from '../../global_styling';
+import {
+ euiRangeThumbPerBrowser,
+ euiRangeThumbStyle,
+ euiRangeThumbFocusBoxShadow,
+} from '../form/range/range.styles';
+
+export const euiHueStyles = (euiThemeContext: UseEuiTheme) => {
+ const { euiTheme } = euiThemeContext;
+
+ const height = euiTheme.size.m;
+ const thumbSize = euiTheme.size.l;
+ const thumbBorder = mathWithUnits(
+ euiTheme.border.width.thick,
+ (x) => x * 1.5
+ );
+ const thumbBoxShadow = `
+ 0 2px 2px -1px ${transparentize(euiTheme.colors.shadow, 0.2)},
+ 0 1px 5px -2px ${transparentize(euiTheme.colors.shadow, 0.2)}`;
+
+ return {
+ // This wraps the range and sets a rainbow gradient,
+ // which allows the range thumb to be larger than the visible track
+ euiHue: css`
+ ${logicalCSS('height', height)}
+ border-radius: ${height};
+ /* stylelint-disable color-no-hex */
+ background: linear-gradient(
+ to right,
+ #ff3232 0%,
+ #fff130 20%,
+ #45ff30 35%,
+ #28fff0 52%,
+ #282cff 71%,
+ #ff28fb 88%,
+ #ff0094 100%
+ );
+ /* stylelint-enable color-no-hex */
+ `,
+
+ euiHue__range: css`
+ ${logicalCSS('height', thumbSize)}
+ /* Allow for overlap */
+ ${logicalCSS('width', `calc(100% + 2px)`)}
+ /* Use ^ overlap to allow thumb to fully cover gradient ends */
+ ${logicalCSS('margin-horizontal', '-1px')}
+ ${logicalCSS(
+ 'margin-top',
+ mathWithUnits(height, (x) => x / -2)
+ )}
+
+ /* Resets for the range */
+ appearance: none;
+ background: transparent;
+
+ /* stylelint-disable property-no-vendor-prefix */
+ &::-webkit-slider-thumb {
+ -webkit-appearance: none;
+ }
+ /* stylelint-enable property-no-vendor-prefix */
+
+ /* Indicator styles - for some incredibly bizarre reason, stylelint is unhappy about
+ the semicolons here and can't be stylelint-disabled, hence the syntax workaround */
+ ${euiRangeThumbPerBrowser(
+ [
+ euiRangeThumbStyle(euiThemeContext),
+ 'background-color: inherit',
+ `border-width: ${thumbBorder}`,
+ 'border-radius: 100%',
+ `box-shadow: ${thumbBoxShadow}`,
+ ].join(';\n')
+ )}
+
+ /* Remove wrapping outline and show focus on thumb only */
+ &:focus {
+ outline: none;
+ }
+
+ &:focus-visible {
+ ${euiRangeThumbPerBrowser(euiRangeThumbFocusBoxShadow(euiThemeContext))}
+ }
+ `,
+ };
+};
diff --git a/packages/eui/src/components/color_picker/hue.test.tsx b/packages/eui/src/components/color_picker/hue.test.tsx
index 02d36c8bcd2..4e191fd5764 100644
--- a/packages/eui/src/components/color_picker/hue.test.tsx
+++ b/packages/eui/src/components/color_picker/hue.test.tsx
@@ -8,6 +8,7 @@
import React from 'react';
import { requiredProps } from '../../test/required_props';
+import { shouldRenderCustomStyles } from '../../test/internal';
import { render } from '../../test/rtl';
import { EuiHue } from './hue';
@@ -17,6 +18,15 @@ const onChange = () => {
};
describe('EuiHue', () => {
+ shouldRenderCustomStyles(
, {
+ skip: { style: true },
+ });
+ // `style` goes onto a different element than `className`s
+ shouldRenderCustomStyles(
, {
+ skip: { css: true, className: true },
+ targetSelector: '.euiHue__range',
+ });
+
test('is rendered', () => {
const { container } = render(
diff --git a/packages/eui/src/components/color_picker/hue.tsx b/packages/eui/src/components/color_picker/hue.tsx
index 62ed118d0c5..fad046a5544 100644
--- a/packages/eui/src/components/color_picker/hue.tsx
+++ b/packages/eui/src/components/color_picker/hue.tsx
@@ -12,11 +12,14 @@ import React, {
FunctionComponent,
} from 'react';
import classNames from 'classnames';
-import { CommonProps } from '../common';
+import { useEuiMemoizedStyles } from '../../services';
+import { CommonProps } from '../common';
import { EuiScreenReaderOnly } from '../accessibility';
import { EuiI18n } from '../i18n';
+import { euiHueStyles } from './hue.styles';
+
const HUE_RANGE = 359;
export type EuiHueProps = Omit<
@@ -37,12 +40,15 @@ export const EuiHue: FunctionComponent
= ({
onChange,
...rest
}) => {
+ const classes = classNames('euiHue', className);
+ const styles = useEuiMemoizedStyles(euiHueStyles);
+
const handleChange = (e: ChangeEvent) => {
onChange(Number(e.target.value));
};
- const classes = classNames('euiHue', className);
+
return (
-
+
= ({
max={HUE_RANGE}
step={1}
type="range"
+ css={styles.euiHue__range}
className="euiHue__range"
value={hue}
onChange={handleChange}
diff --git a/packages/eui/src/components/color_picker/saturation.styles.ts b/packages/eui/src/components/color_picker/saturation.styles.ts
new file mode 100644
index 00000000000..a2198d81e80
--- /dev/null
+++ b/packages/eui/src/components/color_picker/saturation.styles.ts
@@ -0,0 +1,88 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import { css } from '@emotion/react';
+
+import { UseEuiTheme } from '../../services';
+import {
+ logicalCSS,
+ logicalSizeCSS,
+ mathWithUnits,
+} from '../../global_styling';
+
+export const euiSaturationStyles = (euiThemeContext: UseEuiTheme) => {
+ const { euiTheme } = euiThemeContext;
+
+ const indicatorSize = euiTheme.size.m;
+
+ const borderRadius = mathWithUnits(
+ euiTheme.border.radius.medium,
+ (x) => x / 2
+ );
+
+ return {
+ euiSaturation: css`
+ z-index: 3; /* Required to be above the hue slider, which can overlap */
+ position: relative;
+ ${logicalCSS('width', '100%')}
+ ${logicalCSS('padding-bottom', '100%')}
+ border-radius: ${borderRadius};
+ touch-action: none; /* prevent TouchMove events from scrolling page */
+
+ &:focus,
+ &:focus-within {
+ outline: none; /* Hide focus ring from tabindex=0 */
+
+ .euiSaturation__indicator {
+ outline: none; /* Standardize indicator focus ring */
+ box-shadow: 0 0 0 ${euiTheme.focus.width} ${euiTheme.colors.primary};
+ border-color: ${euiTheme.colors.primary};
+ }
+ }
+ `,
+
+ euiSaturation__lightness: css`
+ position: absolute;
+ inset: 0;
+ ${logicalCSS('top', '-1px')} /* Hides a slight color inconsistency */
+
+ border-radius: ${borderRadius};
+ background: linear-gradient(
+ to right,
+ rgba(255, 255, 255, 1),
+ rgba(255, 255, 255, 0)
+ );
+ `,
+ euiSaturation__saturation: css`
+ position: absolute;
+ inset: 0;
+ ${logicalCSS('top', '-1px')} /* Hides a slight color inconsistency */
+
+ border-radius: ${borderRadius};
+ background: linear-gradient(to top, rgba(0, 0, 0, 1), rgba(0, 0, 0, 0));
+ `,
+
+ euiSaturation__indicator: css`
+ position: absolute;
+ ${logicalSizeCSS(indicatorSize)}
+ transform: translateX(-50%) translateY(-50%);
+ border: ${euiTheme.border.width.thin} solid
+ ${euiTheme.colors.darkestShade};
+ border-radius: 100%;
+
+ &::before {
+ content: '';
+ position: absolute;
+ inset: 0;
+ border-radius: 100%;
+ border: ${euiTheme.border.width.thin} solid
+ ${euiTheme.colors.lightestShade};
+ }
+ `,
+ };
+};
diff --git a/packages/eui/src/components/color_picker/saturation.test.tsx b/packages/eui/src/components/color_picker/saturation.test.tsx
index 729914cebb7..4d97fa38398 100644
--- a/packages/eui/src/components/color_picker/saturation.test.tsx
+++ b/packages/eui/src/components/color_picker/saturation.test.tsx
@@ -8,6 +8,7 @@
import React from 'react';
import { requiredProps } from '../../test/required_props';
+import { shouldRenderCustomStyles } from '../../test/internal';
import { render } from '../../test/rtl';
import { EuiSaturation } from './saturation';
@@ -17,6 +18,8 @@ const onChange = () => {
};
describe('EuiHue', () => {
+ shouldRenderCustomStyles( );
+
test('is rendered', () => {
const { container } = render(
diff --git a/packages/eui/src/components/color_picker/saturation.tsx b/packages/eui/src/components/color_picker/saturation.tsx
index 74acecaa070..38e8e7f2da8 100644
--- a/packages/eui/src/components/color_picker/saturation.tsx
+++ b/packages/eui/src/components/color_picker/saturation.tsx
@@ -13,16 +13,24 @@ import React, {
useEffect,
useRef,
useState,
+ useCallback,
} from 'react';
import classNames from 'classnames';
import { ColorSpaces } from 'chroma-js';
-import { CommonProps } from '../common';
-import { keys, useMouseMove } 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';
import { getEventPosition } from './utils';
+import { euiSaturationStyles } from './saturation.styles';
export type SaturationClientRect = Pick<
ClientRect,
@@ -53,13 +61,19 @@ export const EuiSaturation = forwardRef(
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 });
+ const instructionsId = `${id}-instructions`;
+ const indicatorId = `${id}-saturationIndicator`;
const [roleDescString, instructionsString] = useEuiI18n(
['euiSaturation.ariaLabel', 'euiSaturation.screenReaderInstructions'],
[
@@ -88,73 +102,84 @@ export const EuiSaturation = forwardRef(
}
}, [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) => {
- 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 handleKeyDown = useCallback(
+ (event: KeyboardEvent) => {
+ 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]
+ );
- const classes = classNames('euiSaturation', className);
- const instructionsId = `${id}-instructions`;
return (
(
onTouchMove={handleInteraction}
onKeyDown={handleKeyDown}
ref={ref}
+ css={styles.euiSaturation}
className={classes}
data-test-subj={dataTestSubj}
style={{
@@ -170,13 +196,21 @@ export const EuiSaturation = forwardRef
(
tabIndex={-1}
{...rest}
>
-
-
+
{
export const euiRangeThumbPerBrowser = (content: string) => {
return `
&::-webkit-slider-thumb { ${content}; }
- &::-moz-range-thumb { ${content}; }
+ &::-moz-range-thumb { ${content}; }
&::-ms-thumb {${content}; }
`;
};
diff --git a/packages/eui/src/components/index.scss b/packages/eui/src/components/index.scss
index e878de77884..f04354b18aa 100644
--- a/packages/eui/src/components/index.scss
+++ b/packages/eui/src/components/index.scss
@@ -1,6 +1,5 @@
// Components
-@import 'color_picker/index';
@import 'combo_box/index';
@import 'date_picker/index';
@import 'datagrid/index';
diff --git a/packages/eui/src/themes/amsterdam/overrides/_hue.scss b/packages/eui/src/themes/amsterdam/overrides/_hue.scss
deleted file mode 100644
index d9fcff400a1..00000000000
--- a/packages/eui/src/themes/amsterdam/overrides/_hue.scss
+++ /dev/null
@@ -1,43 +0,0 @@
-.euiHue {
- position: relative;
- height: $euiSizeM;
- border-radius: $euiSizeM;
-
- &::before,
- &::after {
- display: none;
- }
-
- &__range {
- @include euiRangeThumbPerBrowser {
- border: 3px solid $euiRangeThumbBorderColor;
- box-shadow:
- 0 2px 2px -1px rgba($euiShadowColor, .2),
- 0 1px 5px -2px rgba($euiShadowColor, .2);
- background-color: inherit;
- }
-
- top: - $euiSizeM / 2;
-
- &:focus {
- @include euiRangeThumbPerBrowser {
- @include euiRangeThumbFocusBoxShadow;
- border: 3px solid $euiRangeThumbBorderColor;
- }
-
- outline: none;
- }
-
- &:focus:not(:focus-visible) {
- @include euiRangeThumbPerBrowser {
- box-shadow:
- 0 2px 2px -1px rgba($euiShadowColor, .2),
- 0 1px 5px -2px rgba($euiShadowColor, .2);
- }
- }
-
- &:focus:focus-visible {
- outline: none;
- }
- }
-}
diff --git a/packages/eui/src/themes/amsterdam/overrides/_index.scss b/packages/eui/src/themes/amsterdam/overrides/_index.scss
index c40e872f408..ba2afa490fd 100644
--- a/packages/eui/src/themes/amsterdam/overrides/_index.scss
+++ b/packages/eui/src/themes/amsterdam/overrides/_index.scss
@@ -3,4 +3,3 @@
@import 'form_control_layout';
@import 'form_control_layout_delimited';
@import 'form_controls';
-@import 'hue';