diff --git a/src-docs/src/views/icon/custom_tokens.js b/src-docs/src/views/icon/custom_tokens.tsx similarity index 77% rename from src-docs/src/views/icon/custom_tokens.js rename to src-docs/src/views/icon/custom_tokens.tsx index df450ae57c4..d112a17bdc0 100644 --- a/src-docs/src/views/icon/custom_tokens.js +++ b/src-docs/src/views/icon/custom_tokens.tsx @@ -59,7 +59,7 @@ export default () => ( iconType="tokenStruct" size="m" shape="circle" - color="#FF0000" + color="#b9f6c8" /> @@ -70,12 +70,32 @@ export default () => ( paddingSize="m" > { - '' + '' } + + + + + + + {''} + + + + ( style={{ minWidth: 96 }} > ( paddingSize="m" > { - '' + '' } diff --git a/src-docs/src/views/icon/icon_example.js b/src-docs/src/views/icon/icon_example.js index c8e66dd0ba1..341f0030a06 100644 --- a/src-docs/src/views/icon/icon_example.js +++ b/src-docs/src/views/icon/icon_example.js @@ -162,11 +162,11 @@ export const IconExample = { demo: , }, { + title: 'Custom tokens', wrapText: false, text: ( <> -

Custom tokens

By default, an iconType with the token prefix (i.e. those listed above) will have predefined styles. However, diff --git a/src-docs/src/views/icon/tokens.js b/src-docs/src/views/icon/tokens.tsx similarity index 94% rename from src-docs/src/views/icon/tokens.js rename to src-docs/src/views/icon/tokens.tsx index 184c5c3a626..039c4cfa261 100644 --- a/src-docs/src/views/icon/tokens.js +++ b/src-docs/src/views/icon/tokens.tsx @@ -10,7 +10,9 @@ import { EuiSpacer, } from '../../../../src/components'; -const tokens = [ +import type { EuiTokenMapType } from '../../../../src/components/token/token_map'; + +const tokens: EuiTokenMapType[] = [ 'tokenAlias', 'tokenAnnotation', 'tokenArray', diff --git a/src-docs/src/views/theme/_json/eui_theme_dark.json b/src-docs/src/views/theme/_json/eui_theme_dark.json index 978804edb50..b62e2064b34 100644 --- a/src-docs/src/views/theme/_json/eui_theme_dark.json +++ b/src-docs/src/views/theme/_json/eui_theme_dark.json @@ -249,54 +249,6 @@ "warning": "#f3d371", "danger": "#f86b63" }, - "euiTokenGrayColor": "#535966", - "euiTokenTypes": { - "euiColorVis0": { - "graphic": "#54b399", - "behindText": "#6dccb1" - }, - "euiColorVis1": { - "graphic": "#6092c0", - "behindText": "#79aad9" - }, - "euiColorVis2": { - "graphic": "#d36086", - "behindText": "#ee789d" - }, - "euiColorVis3": { - "graphic": "#9170b8", - "behindText": "#a987d1" - }, - "euiColorVis4": { - "graphic": "#ca8eae", - "behindText": "#e4a6c7" - }, - "euiColorVis5": { - "graphic": "#d6bf57", - "behindText": "#f1d86f" - }, - "euiColorVis6": { - "graphic": "#b9a888", - "behindText": "#d2c0a0" - }, - "euiColorVis7": { - "graphic": "#da8b45", - "behindText": "#f5a35c" - }, - "euiColorVis8": { - "graphic": "#aa6556", - "behindText": "#c47c6c" - }, - "euiColorVis9": { - "graphic": "#e7664c", - "behindText": "#ff7e62" - }, - "gray": { - "graphic": "#535966", - "behindText": "#535966" - } - }, - "euiTokenTypeKeys": "'euiColorVis0', 'euiColorVis1', 'euiColorVis2', 'euiColorVis3', 'euiColorVis4', 'euiColorVis5', 'euiColorVis6', 'euiColorVis7', 'euiColorVis8', 'euiColorVis9', 'gray'", "euiPopoverArrowSize": "12px", "euiContrastRatioText": 4.5, "euiContrastRatioGraphic": 3, diff --git a/src-docs/src/views/theme/_json/eui_theme_light.json b/src-docs/src/views/theme/_json/eui_theme_light.json index acaa0f3983a..609edb604a6 100644 --- a/src-docs/src/views/theme/_json/eui_theme_light.json +++ b/src-docs/src/views/theme/_json/eui_theme_light.json @@ -249,54 +249,6 @@ "warning": "#fec514", "danger": "#bd271e" }, - "euiTokenGrayColor": "#69707d", - "euiTokenTypes": { - "euiColorVis0": { - "graphic": "#54b399", - "behindText": "#6dccb1" - }, - "euiColorVis1": { - "graphic": "#6092c0", - "behindText": "#79aad9" - }, - "euiColorVis2": { - "graphic": "#d36086", - "behindText": "#ee789d" - }, - "euiColorVis3": { - "graphic": "#9170b8", - "behindText": "#a987d1" - }, - "euiColorVis4": { - "graphic": "#ca8eae", - "behindText": "#e4a6c7" - }, - "euiColorVis5": { - "graphic": "#d6bf57", - "behindText": "#f1d86f" - }, - "euiColorVis6": { - "graphic": "#b9a888", - "behindText": "#d2c0a0" - }, - "euiColorVis7": { - "graphic": "#da8b45", - "behindText": "#f5a35c" - }, - "euiColorVis8": { - "graphic": "#aa6556", - "behindText": "#c47c6c" - }, - "euiColorVis9": { - "graphic": "#e7664c", - "behindText": "#ff7e62" - }, - "gray": { - "graphic": "#69707d", - "behindText": "#69707d" - } - }, - "euiTokenTypeKeys": "'euiColorVis0', 'euiColorVis1', 'euiColorVis2', 'euiColorVis3', 'euiColorVis4', 'euiColorVis5', 'euiColorVis6', 'euiColorVis7', 'euiColorVis8', 'euiColorVis9', 'gray'", "euiPopoverArrowSize": "12px", "euiContrastRatioText": 4.5, "euiContrastRatioGraphic": 3, diff --git a/src/components/datagrid/controls/__snapshots__/column_sorting.test.tsx.snap b/src/components/datagrid/controls/__snapshots__/column_sorting.test.tsx.snap index 28edd0fec5f..cd88127432b 100644 --- a/src/components/datagrid/controls/__snapshots__/column_sorting.test.tsx.snap +++ b/src/components/datagrid/controls/__snapshots__/column_sorting.test.tsx.snap @@ -111,7 +111,7 @@ exports[`useDataGridColumnSorting columnSorting renders a toolbar button/popover class="euiFlexItem euiFlexItem--flexGrowZero" > diff --git a/src/components/icon/assets/tokenStruct.tsx b/src/components/icon/assets/tokenStruct.tsx index 24e3070f5e3..1e13ab1672b 100644 --- a/src/components/icon/assets/tokenStruct.tsx +++ b/src/components/icon/assets/tokenStruct.tsx @@ -30,7 +30,7 @@ const EuiIconTokenStruct = ({ {title ? {title} : null} ); diff --git a/src/components/icon/svgs/tokens/tokenStruct.svg b/src/components/icon/svgs/tokens/tokenStruct.svg index 3eae7a65f6f..93265ccfaf9 100644 --- a/src/components/icon/svgs/tokens/tokenStruct.svg +++ b/src/components/icon/svgs/tokens/tokenStruct.svg @@ -1,3 +1,3 @@ - + diff --git a/src/components/index.scss b/src/components/index.scss index 845b1ae6a3f..f4bb1d5ce59 100644 --- a/src/components/index.scss +++ b/src/components/index.scss @@ -39,6 +39,5 @@ @import 'suggest/index'; @import 'table/index'; @import 'tabs/index'; -@import 'token/index'; @import 'tool_tip/index'; @import 'tour/index'; diff --git a/src/components/token/__snapshots__/token.test.tsx.snap b/src/components/token/__snapshots__/token.test.tsx.snap index 50627a08a4e..5d9500135b0 100644 --- a/src/components/token/__snapshots__/token.test.tsx.snap +++ b/src/components/token/__snapshots__/token.test.tsx.snap @@ -2,7 +2,7 @@ exports[`EuiToken is rendered 1`] = ` { + const isVizColor = typeof color === 'number'; + + const iconColor = isVizColor ? visColors[color] : euiTheme.colors.darkShade; + + const isDarkMode = colorMode === 'DARK'; + + const backgroundDarkColor = isVizColor + ? visColorsBehindText[color] + : euiTheme.colors.darkShade; + + const backgroundLightColor = isDarkMode + ? shade(iconColor, 0.7) + : tint(iconColor, 0.9); + + const lightColor = makeHighContrastColor(iconColor)(backgroundLightColor); + + const boxShadowColor = isDarkMode + ? shade(iconColor, 0.6) + : tint(iconColor, 0.7); + + const darkColor = isColorDark(...chroma(backgroundDarkColor).rgb()) + ? euiTheme.colors.ghost + : euiTheme.colors.ink; + + switch (fill) { + case 'none': + return ` + // Without a background, the fill color should be the graphic color + color: ${iconColor}; + `; + case 'light': + return ` + color: ${lightColor}; + background-color: ${backgroundLightColor}; + box-shadow: inset 0 0 0 1px ${boxShadowColor}; + `; + case 'dark': + return ` + color: ${darkColor}; + background-color: ${backgroundDarkColor}; + `; + } +}; + +export const euiTokenStyles = ( + { euiTheme, colorMode }: UseEuiTheme, + fill: TokenFill +) => ({ + // Base + euiToken: css` + display: inline-flex; + align-items: center; + justify-content: center; + + svg { + ${logicalCSS('height', '100%')} + margin: auto; + } + `, + // Shapes + circle: css` + border-radius: 50%; + `, + square: css` + border-radius: ${euiTheme.border.radius.small}; + `, + rectangle: css` + box-sizing: content-box; + border-radius: ${euiTheme.border.radius.small}; + `, + // Sizes + xs: css` + ${logicalSizeCSS(euiTheme.size.s, euiTheme.size.s)}; + + &[class*='-square'] { + border-radius: calc(${euiTheme.border.radius.small} / 2); + } + + &[class*='-rectangle'] { + ${logicalCSS( + 'padding-vertical', + '1px' + )}; // adds a small padding so that the icon is not touching the border + ${logicalCSS('padding-horizontal', euiTheme.size.xs)}; + border-radius: calc(${euiTheme.border.radius.small} / 2); + } + `, + s: css` + ${logicalSizeCSS(euiTheme.size.base, euiTheme.size.base)}; + + &[class*='-rectangle'] { + ${logicalCSS('padding-horizontal', euiTheme.size.xs)}; + } + `, + m: css` + ${logicalSizeCSS(euiTheme.size.l, euiTheme.size.l)}; + + &[class*='-rectangle'] { + ${logicalCSS('padding-horizontal', euiTheme.size.s)}; + } + `, + l: css` + ${logicalSizeCSS(euiTheme.size.xl, euiTheme.size.xl)}; + + &[class*='-rectangle'] { + ${logicalCSS('padding-horizontal', euiTheme.size.s)}; + } + `, + // Colors + euiColorVis0: css(getTokenColor(euiTheme, colorMode, fill, 0)), + euiColorVis1: css(getTokenColor(euiTheme, colorMode, fill, 1)), + euiColorVis2: css(getTokenColor(euiTheme, colorMode, fill, 2)), + euiColorVis3: css(getTokenColor(euiTheme, colorMode, fill, 3)), + euiColorVis4: css(getTokenColor(euiTheme, colorMode, fill, 4)), + euiColorVis5: css(getTokenColor(euiTheme, colorMode, fill, 5)), + euiColorVis6: css(getTokenColor(euiTheme, colorMode, fill, 6)), + euiColorVis7: css(getTokenColor(euiTheme, colorMode, fill, 7)), + euiColorVis8: css(getTokenColor(euiTheme, colorMode, fill, 8)), + euiColorVis9: css(getTokenColor(euiTheme, colorMode, fill, 9)), + gray: css(getTokenColor(euiTheme, colorMode, fill, 'gray')), + customColor: css``, + // Fills + light: css``, + dark: css``, + none: css``, +}); diff --git a/src/components/token/token.test.tsx b/src/components/token/token.test.tsx index 4c44c912cf5..7d117991f05 100644 --- a/src/components/token/token.test.tsx +++ b/src/components/token/token.test.tsx @@ -9,8 +9,10 @@ import React from 'react'; import { render } from 'enzyme'; import { requiredProps } from '../../test'; +import { shouldRenderCustomStyles } from '../../test/internal'; -import { EuiToken, COLORS, SHAPES, SIZES, FILLS } from './token'; +import { EuiToken } from './token'; +import { COLORS, SHAPES, SIZES, FILLS } from './token_types'; import { TOKEN_MAP } from './token_map'; import { keysOf } from '../common'; @@ -18,6 +20,8 @@ const tokenTypes = keysOf(TOKEN_MAP); const tokenColors = COLORS; describe('EuiToken', () => { + shouldRenderCustomStyles(); + test('is rendered', () => { const component = render(); diff --git a/src/components/token/token.tsx b/src/components/token/token.tsx index e4a2ddbbbe5..95ce82eba31 100644 --- a/src/components/token/token.tsx +++ b/src/components/token/token.tsx @@ -6,109 +6,24 @@ * Side Public License, v 1. */ -import React, { FunctionComponent, HTMLAttributes } from 'react'; -import defaults from 'lodash/defaults'; +import React, { FunctionComponent } from 'react'; import classNames from 'classnames'; -import { CommonProps, keysOf } from '../common'; -import { isColorDark, hexToRgb } from '../../services'; +import { useEuiTheme, isColorDark, hexToRgb } from '../../services'; -import { IconType, EuiIcon, IconSize } from '../icon'; +import { EuiIcon, IconSize } from '../icon'; import { EuiTokenMapType, TOKEN_MAP } from './token_map'; - -type TokenSize = 'xs' | 's' | 'm' | 'l'; -type TokenShape = 'circle' | 'square' | 'rectangle'; -type TokenFill = 'dark' | 'light' | 'none'; -type TokenColor = - | 'euiColorVis0' - | 'euiColorVis1' - | 'euiColorVis2' - | 'euiColorVis3' - | 'euiColorVis4' - | 'euiColorVis5' - | 'euiColorVis6' - | 'euiColorVis7' - | 'euiColorVis8' - | 'euiColorVis9' - | 'gray'; - -const sizeToClassMap: { [size in TokenSize]: string } = { - xs: 'euiToken--xsmall', - s: 'euiToken--small', - m: 'euiToken--medium', - l: 'euiToken--large', -}; - -export const SIZES = keysOf(sizeToClassMap); - -const shapeToClassMap: { [shape in TokenShape]: string } = { - circle: 'euiToken--circle', - square: 'euiToken--square', - rectangle: 'euiToken--rectangle', -}; - -export const SHAPES = keysOf(shapeToClassMap); - -const fillToClassMap: { [fill in TokenFill]: string | null } = { - none: null, - light: 'euiToken--light', - dark: 'euiToken--dark', -}; - -export const FILLS = keysOf(fillToClassMap); - -const colorToClassMap: { [color in TokenColor]: string } = { - euiColorVis0: 'euiToken--euiColorVis0', - euiColorVis1: 'euiToken--euiColorVis1', - euiColorVis2: 'euiToken--euiColorVis2', - euiColorVis3: 'euiToken--euiColorVis3', - euiColorVis4: 'euiToken--euiColorVis4', - euiColorVis5: 'euiToken--euiColorVis5', - euiColorVis6: 'euiToken--euiColorVis6', - euiColorVis7: 'euiToken--euiColorVis7', - euiColorVis8: 'euiToken--euiColorVis8', - euiColorVis9: 'euiToken--euiColorVis9', - gray: 'euiToken--gray', -}; - -export const COLORS = keysOf(colorToClassMap); - -export interface TokenProps { - /** - * An EUI icon type - */ - iconType: IconType; - /** - * For best results use one of the vis color names (or 'gray'). - * Or supply your own color (can be used with dark or no fill only). - * Default: `gray` - */ - color?: TokenColor | string; - /** - * Outer shape surrounding the icon - * Default: `circle` - */ - shape?: TokenShape; - /** - * `light` for lightened color with border, `dark` for solid, or `none` - * Default: `light` - */ - fill?: TokenFill; - /** - * Size of the token - */ - size?: TokenSize; - /** - * The icon's title. Required for accessibility - */ - title?: string; - 'aria-label'?: string; - 'aria-labelledby'?: string; - 'aria-describedby'?: string; -} - -export type EuiTokenProps = CommonProps & - TokenProps & - Omit, 'title'>; +import { COLORS } from './token_types'; +import type { + EuiTokenProps, + TokenColor, + TokenSize, + TokenShape, + TokenFill, +} from './token_types'; +import { euiTokenStyles } from './token.styles'; + +const isTokenColor = (color: string): color is TokenColor => + COLORS.includes(color as TokenColor); export const EuiToken: FunctionComponent = ({ iconType, @@ -138,58 +53,58 @@ export const EuiToken: FunctionComponent = ({ finalSize = 'm'; } - const currentDisplay = { - color, - fill, - shape, - }; - let finalDisplay; - // If the iconType passed is one of the prefab token types, // grab its properties - if (typeof iconType === 'string' && iconType in TOKEN_MAP) { - const tokenDisplay = TOKEN_MAP[iconType as EuiTokenMapType]; - finalDisplay = defaults(currentDisplay, tokenDisplay); + const tokenDefaults = + typeof iconType === 'string' && iconType in TOKEN_MAP + ? TOKEN_MAP[iconType as EuiTokenMapType] + : {}; + + const finalColor = color || tokenDefaults.color || 'gray'; + const finalShape = shape || tokenDefaults.shape || 'circle'; + let finalFill = fill || 'light'; + + const euiTheme = useEuiTheme(); + const styles = euiTokenStyles(euiTheme, finalFill); + + let cssStyles = [ + styles.euiToken, + styles[finalShape as TokenShape], + styles[finalFill as TokenFill], + styles[size as TokenSize], + ]; + + let finalStyle = style; + + if (isTokenColor(finalColor)) { + cssStyles = [...cssStyles, styles[finalColor as TokenColor]]; + } else if (finalFill === 'none') { + // When a custom HEX color is passed and the token doesn't have any fill (no background), + // the icon gets that passed color + cssStyles = [...cssStyles, styles.customColor]; + finalStyle = { color: finalColor, ...style }; } else { - finalDisplay = currentDisplay; + // When a custom HEX color is passed and the token has a fill (light or dark), + // the background gets the custom color and the icon gets white or black based on the passed color + // The fill='light' (lightened background) will always be overridden by fill='dark' (opaque background) + // to better handle custom colors + const isFinalColorDark = isColorDark(...hexToRgb(finalColor)); + const lightOrDarkColor = isFinalColorDark ? '#FFFFFF' : '#000000'; + + cssStyles = [...cssStyles, styles.customColor]; + + finalFill = 'dark'; + finalStyle = { + color: lightOrDarkColor, + backgroundColor: finalColor, + ...style, + }; } - const finalColor = finalDisplay.color || 'gray'; - const finalShape = finalDisplay.shape || 'circle'; - let finalFill = finalDisplay.fill || 'light'; - - // Color can be a named space via euiColorVis - let colorClass; - if (finalColor in colorToClassMap) { - colorClass = colorToClassMap[finalColor as TokenColor]; - } - // Or it can be a string which adds inline styles for the - else { - // text color if fill='none' or - if (finalFill === 'none') { - style.color = finalColor; - } - // full background color if fill='dark' and overrides fill='light' with dark - else { - finalFill = 'dark'; - style.backgroundColor = finalColor; - style.color = isColorDark(...hexToRgb(finalColor)) - ? '#FFFFFF' - : '#000000'; - } - } - - const classes = classNames( - 'euiToken', - colorClass, - shapeToClassMap[finalShape], - fillToClassMap[finalFill], - sizeToClassMap[size], - className - ); + const classes = classNames('euiToken', className); return ( - + , 'title'>; diff --git a/src/themes/amsterdam/overrides/_index.scss b/src/themes/amsterdam/overrides/_index.scss index 134807ca3c5..37642e4d7db 100644 --- a/src/themes/amsterdam/overrides/_index.scss +++ b/src/themes/amsterdam/overrides/_index.scss @@ -33,5 +33,4 @@ @import 'side_nav'; @import 'steps'; @import 'tabs'; -@import 'token'; @import 'tooltip'; diff --git a/src/themes/amsterdam/overrides/_token.scss b/src/themes/amsterdam/overrides/_token.scss deleted file mode 100644 index 7302be7663c..00000000000 --- a/src/themes/amsterdam/overrides/_token.scss +++ /dev/null @@ -1,4 +0,0 @@ -.euiToken--square { - // Same border radius as the legacy theme - border-radius: $euiBorderRadiusSmall - 1px; -} diff --git a/upcoming_changelogs/6067.md b/upcoming_changelogs/6067.md new file mode 100644 index 00000000000..8f2ed2f53a1 --- /dev/null +++ b/upcoming_changelogs/6067.md @@ -0,0 +1,5 @@ +- Updated `tokenFile`, `tokenSymbol` and `tokenRepo` default shapes to `square` instead of `rectangle` + +**CSS-in-JS conversions** + +- Converted `EuiToken` to Emotion