diff --git a/CHANGELOG.md b/CHANGELOG.md index a461a55d229..e61e641039b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ ## [`master`](https://github.com/elastic/eui/tree/master) +- Changed `EuiBadge` to use EUI palette colors ([#2455](https://github.com/elastic/eui/pull/2455)) +- Darkened a few `euiPaletteColorBlind` colors ([#2455](https://github.com/elastic/eui/pull/2455)) - Fixed bug in `EuiCard` where button text was not properly aligned ([#2741](https://github.com/elastic/eui/pull/2741)) - Converted `EuiRange` to TypeScript ([#2732](https://github.com/elastic/eui/pull/2732)) - Converted `EuiDualRange` to TypeScript ([#2732](https://github.com/elastic/eui/pull/2732)) diff --git a/package.json b/package.json index 0486947bc35..8b6f24133e4 100644 --- a/package.json +++ b/package.json @@ -48,8 +48,8 @@ "@types/lodash": "^4.14.116", "@types/numeral": "^0.0.25", "@types/react-beautiful-dnd": "^10.1.0", - "classnames": "^2.2.5", "chroma-js": "^2.0.4", + "classnames": "^2.2.5", "highlight.js": "^9.12.0", "html": "^1.0.0", "keymirror": "^0.1.1", diff --git a/src-docs/src/views/badge/badge.js b/src-docs/src/views/badge/badge.js index c4037b5fb53..89a15b13fb0 100644 --- a/src-docs/src/views/badge/badge.js +++ b/src-docs/src/views/badge/badge.js @@ -1,9 +1,13 @@ -import React from 'react'; +import React, { Fragment, useState } from 'react'; import { EuiBadge, EuiFlexItem, EuiFlexGroup, + EuiSpacer, + EuiSwitch, + EuiText, + EuiTitle, } from '../../../../src/components'; const badges = [ @@ -14,19 +18,75 @@ const badges = [ 'accent', 'warning', 'danger', - '#000', - '#fea27f', ]; -export default () => ( - - {badges.map(badge => ( - - {badge} - - ))} - - disabled - - -); +const customBadges = [ + '#DDD', + '#AAA', + '#666', + '#333', + '#BADA55', + '#FCF7BC', + '#FEA27F', + '#FFA500', + '#0000FF', +]; + +export default () => { + const [isDisabled, setDisabled] = useState(false); + + return ( + + +

Accepted color names

+
+ + + {badges.map(badge => ( + + {badge} + + ))} + + + +

Custom color examples

+
+ + + {customBadges.map(badge => ( + + {badge} + + ))} + + + +

Disabled state

+
+ + + Regardless of the assigned color, all badges use the same disabled state + styles. + + + setDisabled(e.target.checked)} + /> + + + + + {isDisabled ? 'Disabled badge' : 'Disable me!'} + + + +
+ ); +}; diff --git a/src-docs/src/views/badge/badge_button.js b/src-docs/src/views/badge/badge_button.js index ab92de70ce7..2d65989cbff 100644 --- a/src-docs/src/views/badge/badge_button.js +++ b/src-docs/src/views/badge/badge_button.js @@ -1,37 +1,45 @@ import React from 'react'; -import { EuiBadge } from '../../../../src/components'; +import { + EuiBadge, + EuiFlexGroup, + EuiFlexItem, +} from '../../../../src/components'; export default () => ( -
- window.alert('Badge clicked')} - onClickAriaLabel="Example of onclick event for the button" - data-test-sub="testExample1"> - onClick on badge itself - - - window.alert('Icon inside badge clicked')} - iconOnClickAriaLabel="Example of onclick event for icon within the button" - data-test-sub="testExample2"> - onClick on icon within badge - - - window.alert('Badge clicked')} - onClickAriaLabel="Example of onclick event for the button" - iconOnClick={() => window.alert('Icon inside badge clicked')} - iconOnClickAriaLabel="Example of onclick event for icon within the button" - data-test-sub="testExample3"> - onClick on itself and the icon - -
+ + + window.alert('Badge clicked')} + onClickAriaLabel="Example of onClick event for the button" + data-test-sub="testExample1"> + onClick on text within badge + + + + window.alert('Icon inside badge clicked')} + iconOnClickAriaLabel="Example of onClick event for icon within the button" + data-test-sub="testExample2"> + onClick on icon within badge + + + + window.alert('Badge clicked')} + onClickAriaLabel="Example of onClick event for the button" + iconOnClick={() => window.alert('Icon inside badge clicked')} + iconOnClickAriaLabel="Example of onClick event for icon within the button" + data-test-sub="testExample3"> + onClick on both text and icon within badge + + + ); diff --git a/src-docs/src/views/badge/badge_example.js b/src-docs/src/views/badge/badge_example.js index 06362dd8872..3568874491d 100644 --- a/src-docs/src/views/badge/badge_example.js +++ b/src-docs/src/views/badge/badge_example.js @@ -17,26 +17,79 @@ import { import Badge from './badge'; const badgeSource = require('!!raw-loader!./badge'); const badgeHtml = renderToHtml(Badge); +const badgeSnippet = [ + `Default +`, + `Hollow +`, + `Primary +`, + `Custom +`, + `Disabled +`, +]; import BadgeWithIcon from './badge_with_icon'; const badgeWithIconSource = require('!!raw-loader!./badge_with_icon'); const badgeWithIconHtml = renderToHtml(BadgeWithIcon); +const badgeWithIconSnippet = `Label +`; import BadgeButton from './badge_button'; const badgeButtonSource = require('!!raw-loader!./badge_button'); const badgeButtonHtml = renderToHtml(BadgeButton); +const badgeButtonSnippet = [ + ` + Clickable text +`, + ` + Text with clickable icon +`, + ` + Clickable text with clickable icon +`, +]; + +import BadgeTruncate from './badge_truncate'; +const badgeTruncateSource = require('!!raw-loader!./badge_truncate'); +const badgeTruncateHtml = renderToHtml(BadgeTruncate); import BetaBadge from './beta_badge'; const betaBadgeSource = require('!!raw-loader!./beta_badge'); const betaBadgeHtml = renderToHtml(BetaBadge); +const betaBadgeSnippet = [ + ` +`, + ` +`, + ` +`, +]; import NotificationBadge from './notification_badge'; const notificationBadgeSource = require('!!raw-loader!./notification_badge'); const notificationBadgeHtml = renderToHtml(NotificationBadge); - -import BadgeTruncate from './badge_truncate'; -const badgeTruncateSource = require('!!raw-loader!./badge_truncate'); -const badgeTruncateHtml = renderToHtml(BadgeTruncate); +const notificationBadgeSnippet = `3 +`; export const BadgeExample = { title: 'Badge', @@ -58,10 +111,11 @@ export const BadgeExample = { they will automatically space themselves if you use them in a repetitive fashion it is good form to wrap them using a{' '} FlexGroup so that they will wrap when width is - constrained (as is done artificially in the example below). + constrained (as seen in the custom color example below).

), props: { EuiBadge }, + snippet: badgeSnippet, demo: , }, { @@ -77,6 +131,7 @@ export const BadgeExample = { }, ], text:

Badges can use icons on the left and right (default) sides.

, + snippet: badgeWithIconSnippet, demo: , }, { @@ -111,6 +166,7 @@ export const BadgeExample = { ), + snippet: badgeButtonSnippet, demo: , }, { @@ -178,6 +234,7 @@ export const BadgeExample = { ), props: { EuiBetaBadge }, + snippet: betaBadgeSnippet, demo: , }, { @@ -194,13 +251,14 @@ export const BadgeExample = { ], text: (

- Used to showcase the number of notifications, alerts or hidden - selections. Typically used in{' '} - EuiHeader or (eventually){' '} - EuiFilterButtons. + Used to showcase the number of notifications, alerts, or hidden + selections. This badge type is commonly used in the{' '} + EuiHeader and{' '} + EuiFilterButton components.

), props: { EuiNotificationBadge }, + snippet: notificationBadgeSnippet, demo: , }, ], diff --git a/src-docs/src/views/badge/badge_with_icon.js b/src-docs/src/views/badge/badge_with_icon.js index 7ae47352f8d..e11d1bbb77c 100644 --- a/src-docs/src/views/badge/badge_with_icon.js +++ b/src-docs/src/views/badge/badge_with_icon.js @@ -4,10 +4,10 @@ import { EuiBadge } from '../../../../src/components'; export default () => (
- Default - - - Primary + + Hollow + + Default
); diff --git a/src-docs/src/views/badge/beta_badge.js b/src-docs/src/views/badge/beta_badge.js index f0c7dd0f5e8..f956c8e8765 100644 --- a/src-docs/src/views/badge/beta_badge.js +++ b/src-docs/src/views/badge/beta_badge.js @@ -17,7 +17,7 @@ export default () => (   - +

Beta badges will also line up nicely with titles   { const [extendedColorStops, setExtendedColorStops] = useState([ { stop: 100, - color: '#5BBAA0', + color: '#54B399', }, { stop: 250, diff --git a/src-docs/src/views/color_picker/utils.js b/src-docs/src/views/color_picker/utils.js index 1a385e08eeb..6bb640ec1a5 100644 --- a/src-docs/src/views/color_picker/utils.js +++ b/src-docs/src/views/color_picker/utils.js @@ -10,7 +10,7 @@ export const useColorStop = (useRandomColor = false) => { const [colorStops, setColorStops] = useState([ { stop: 20, - color: '#5BBAA0', + color: '#54B399', }, { stop: 50, diff --git a/src-docs/src/views/combo_box/colors.js b/src-docs/src/views/combo_box/colors.js index ea806efbc2b..41186d0075e 100644 --- a/src-docs/src/views/combo_box/colors.js +++ b/src-docs/src/views/combo_box/colors.js @@ -39,15 +39,15 @@ export default class extends Component { { label: "Pandora is one of Saturn's moons, named for a Titaness of Greek mythology", - color: '#F19F58', + color: '#DA8B45', }, { label: 'Tethys', - color: '#EEAFCF', + color: '#CA8EAE', }, { label: 'Hyperion', - color: '#CDBD9D', + color: '#B9A888', }, ]; diff --git a/src-docs/src/views/combo_box/render_option.js b/src-docs/src/views/combo_box/render_option.js index 9472ed6e8a4..4a6adbcdf22 100644 --- a/src-docs/src/views/combo_box/render_option.js +++ b/src-docs/src/views/combo_box/render_option.js @@ -67,21 +67,21 @@ export default class extends Component { }, label: "Pandora is one of Saturn's moons, named for a Titaness of Greek mythology", - color: '#F19F58', + color: '#DA8B45', }, { value: { size: 9, }, label: 'Tethys', - color: '#EEAFCF', + color: '#CA8EAE', }, { value: { size: 4, }, label: 'Hyperion', - color: '#CDBD9D', + color: '#B9A888', }, ]; diff --git a/src-docs/src/views/icon/icon_colors.js b/src-docs/src/views/icon/icon_colors.js index 49bd1108b2e..14b759f7a3a 100644 --- a/src-docs/src/views/icon/icon_colors.js +++ b/src-docs/src/views/icon/icon_colors.js @@ -32,7 +32,7 @@ const iconColors = [ 'subdued', 'ghost', '#490', - '#F19F58', + '#DA8B45', '#DDDDDD', ]; diff --git a/src-docs/src/views/icon/icon_example.js b/src-docs/src/views/icon/icon_example.js index 214862b8a35..d194d78daea 100644 --- a/src-docs/src/views/icon/icon_example.js +++ b/src-docs/src/views/icon/icon_example.js @@ -77,7 +77,7 @@ import IconColors from './icon_colors'; const iconColorsSource = require('!!raw-loader!./icon_colors'); const iconColorsSnippet = [ '', - '', + '', ]; import Accessibility from './accessibility'; diff --git a/src/components/avatar/__snapshots__/avatar.test.tsx.snap b/src/components/avatar/__snapshots__/avatar.test.tsx.snap index 57b4f1fe9d5..c2deff588fb 100644 --- a/src/components/avatar/__snapshots__/avatar.test.tsx.snap +++ b/src/components/avatar/__snapshots__/avatar.test.tsx.snap @@ -21,7 +21,7 @@ exports[`EuiAvatar is rendered 1`] = ` aria-label="aria-label" class="euiAvatar euiAvatar--m euiAvatar--user testClass1 testClass2" data-test-subj="test subject string" - style="background-image:none;background-color:#EEAFCF;color:#000000" + style="background-image:none;background-color:#CA8EAE;color:#000000" title="name" > `; @@ -60,7 +60,7 @@ exports[`EuiAvatar props initials is rendered 1`] = `
- - - Content - - - -`; - -exports[`EuiBadge props color hollow is rendered 1`] = ` - 50) { - color: $euiColorInk; - } @else { - color: $euiColorGhost; - } - } -} - // Hollow has a border and is mostly used for autocompleters. .euiBadge--hollow { background-color: $euiColorEmptyShade; diff --git a/src/components/badge/badge.tsx b/src/components/badge/badge.tsx index dc51f9d4a22..48434625814 100644 --- a/src/components/badge/badge.tsx +++ b/src/components/badge/badge.tsx @@ -6,10 +6,9 @@ import React, { } from 'react'; import classNames from 'classnames'; import { CommonProps, ExclusiveUnion, keysOf, PropsOf } from '../common'; - -import { isColorDark, hexToRgb } from '../../services/color'; +import chroma from 'chroma-js'; +import { euiPaletteColorBlind } from '../../services/color/eui_palettes'; import { EuiInnerText } from '../inner_text'; - import { EuiIcon, IconColor, IconType } from '../icon'; type IconSide = 'left' | 'right'; @@ -21,7 +20,7 @@ type WithButtonProps = { onClick: MouseEventHandler; /** - * Aria label applied to the iconOnClick button + * Aria label applied to the onClick button */ onClickAriaLabel: string; } & Omit, 'onClick' | 'color'>; @@ -68,17 +67,36 @@ export type EuiBadgeProps = { ExclusiveUnion & ExclusiveUnion; -const colorToClassNameMap: { [color in IconColor]: string } = { - default: 'euiBadge--default', - primary: 'euiBadge--primary', - secondary: 'euiBadge--secondary', - accent: 'euiBadge--accent', - warning: 'euiBadge--warning', - danger: 'euiBadge--danger', - hollow: 'euiBadge--hollow', +// TODO - replace with variables once https://github.com/elastic/eui/issues/2731 is closed +const colorInk = '#000'; +const colorGhost = '#fff'; + +// The color blind palette has some stricter accessibility needs with regards to +// charts and contrast. We can ease (brighten) that requirement here since our +// accessibility concerns pertain to foreground (text) and background contrast +const brightenValue = 0.5; + +const colorToHexMap: { [color in IconColor]: string } = { + // TODO - replace with variable once https://github.com/elastic/eui/issues/2731 is closed + default: '#d3dae6', + primary: chroma(euiPaletteColorBlind()[1]) + .brighten(brightenValue) + .hex(), + secondary: chroma(euiPaletteColorBlind()[0]) + .brighten(brightenValue) + .hex(), + accent: chroma(euiPaletteColorBlind()[2]) + .brighten(brightenValue) + .hex(), + warning: chroma(euiPaletteColorBlind()[5]) + .brighten(brightenValue) + .hex(), + danger: chroma(euiPaletteColorBlind()[9]) + .brighten(brightenValue) + .hex(), }; -export const COLORS = keysOf(colorToClassNameMap); +export const COLORS = keysOf(colorToHexMap); const iconSideToClassNameMap: { [side in IconSide]: string } = { left: 'euiBadge--iconLeft', @@ -103,17 +121,46 @@ export const EuiBadge: FunctionComponent = ({ }) => { checkValidColor(color); - let optionalColorClass = null; let optionalCustomStyles: object | undefined = undefined; let textColor = null; + // TODO - replace with variable once https://github.com/elastic/eui/issues/2731 is closed + const wcagContrastBase = 4.5; // WCAG AA contrast level + let wcagContrast = null; + let colorHex = null; + // Check if a valid color name was provided if (COLORS.indexOf(color) > -1) { - optionalColorClass = colorToClassNameMap[color]; - } else { - if (isColorDark(...hexToRgb(color))) { - textColor = '#FFFFFF'; - } else { - textColor = '#000000'; + // Get the hex equivalent for the provided color name + colorHex = colorToHexMap[color]; + + // Set dark or light text color based upon best contrast + textColor = setTextColor(colorHex); + + optionalCustomStyles = { + backgroundColor: colorHex, + color: textColor, + }; + } else if (color !== 'hollow') { + // This is a custom color that is neither from the base palette nor hollow + // Let's do our best to ensure that it provides sufficient contrast + + // Set dark or light text color based upon best contrast + textColor = setTextColor(color); + + // Check the contrast + wcagContrast = getColorContrast(textColor, color); + + if (wcagContrast < wcagContrastBase) { + // It's low contrast, so lets show a warning in the console + console.warn( + 'Warning: ', + color, + ' badge has low contrast of ', + wcagContrast.toFixed(2), + '. Should be above ', + wcagContrastBase, + '.' + ); } optionalCustomStyles = { backgroundColor: color, color: textColor }; @@ -124,9 +171,9 @@ export const EuiBadge: FunctionComponent = ({ { 'euiBadge-isClickable': onClick && !iconOnClick, 'euiBadge-isDisabled': isDisabled, + 'euiBadge--hollow': color === 'hollow', }, iconSideToClassNameMap[iconSide], - optionalColorClass, className ); @@ -235,13 +282,32 @@ export const EuiBadge: FunctionComponent = ({ } }; +function getColorContrast(textColor: string, color: string) { + const contrastValue = chroma.contrast(textColor, color); + return contrastValue; +} + +function setTextColor(bgColor: string) { + const textColor = + getColorContrast(colorInk, bgColor) > getColorContrast(colorGhost, bgColor) + ? colorInk + : colorGhost; + + return textColor; +} + function checkValidColor(color: null | IconColor | string) { const validHex = /(^#[0-9A-F]{6}$)|(^#[0-9A-F]{3}$)/i; - if (color != null && !validHex.test(color) && !COLORS.includes(color)) { + if ( + color != null && + !validHex.test(color) && + !COLORS.includes(color) && + color !== 'hollow' + ) { console.warn( - 'EuiBadge expects a valid color. This can either be a three ' + - `or six character hex value or one of the following: ${COLORS}` + 'EuiBadge expects a valid color. This can either be a three or six ' + + `character hex value, hollow, or one of the following: ${COLORS}` ); } } diff --git a/src/components/breadcrumbs/__snapshots__/breadcrumbs.test.tsx.snap b/src/components/breadcrumbs/__snapshots__/breadcrumbs.test.tsx.snap index b328119bbe4..1c62d24b757 100644 --- a/src/components/breadcrumbs/__snapshots__/breadcrumbs.test.tsx.snap +++ b/src/components/breadcrumbs/__snapshots__/breadcrumbs.test.tsx.snap @@ -201,7 +201,8 @@ exports[`EuiBreadcrumbs props render a popover is rendered 1`] = ` >
@@ -648,10 +648,10 @@ exports[`renders inline EuiColorPicker 1`] = ` class="euiFlexItem euiFlexItem--flexGrowZero" >