diff --git a/src-docs/src/views/emotion/canopy.tsx b/src-docs/src/views/emotion/canopy.tsx
deleted file mode 100644
index efa9e84416b..00000000000
--- a/src-docs/src/views/emotion/canopy.tsx
+++ /dev/null
@@ -1,310 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import * as React from 'react';
-import { css } from '@emotion/react';
-import chroma from 'chroma-js';
-import { EuiSpacer } from '../../../../src/components/spacer';
-import { EuiIcon } from '../../../../src/components/icon';
-import {
- mergeDeep,
- useEuiTheme,
- withEuiTheme,
- WithEuiThemeProps,
- EuiThemeProvider,
- computed,
- euiThemeDefault,
- buildTheme,
- EuiThemeModifications,
-} from '../../../../src/services';
-
-const View = () => {
- const { euiTheme, colorMode } = useEuiTheme();
- return (
-
+ EUI is highly tokenized and highly recommends using the following
+ Sass variables when customizing on top of EUI. This way your
+ customizations stay up to date with EUI's theming.
+
+
+ For more information on how to consume these Sass variables in your
+ project, see the{' '}
+
+ Consuming wiki page
+
+ .
+
+ The animation values provide some easy and
+ consistent ways for adding transition and animation effects and
+ timing.
+
+
+
+
+
+
+
+
+
+ These are general properties that can be used to create
+ subtle animations or transitions that share similar timing
+ and easing functions.
+
+ }
+ themeValues={Object.keys(speedTypes).map((prop) => {
+ return (
+
+ updateAnimation(prop, value)}
+ />
+
+
+
+
+
+ );
+ })}
+ />
+
+
+
+ EUI utilizes the following constants to maintain a similar
+ 'bounce' to its animations.
+
+ }
+ themeValues={Object.keys(easingTypes).map((prop) => {
+ return (
+
+ updateAnimation(prop, value)}
+ />
+
+
+
+
+
+ );
+ })}
+ />
+ >
+ ),
+ },
+ {
+ id: 'themeAnimationTabUsage',
+ name: 'Usage',
+ content: (
+ <>
+
+
+ The simplest and most common usage of the animation speeds
+ is to apply them to custom transitions like hover effects.
+
+ }
+ example={
+
+ Hover me
+
+ }
+ snippet={'transition: background ${euiTheme.animation.slow};'}
+ />
+
+
+ When moving or changing the{' '}
+ size of elements on the page, it's
+ good to add a slight ease to the transition or animation.
+
+ }
+ example={
+
+ The border theme key contains both individual
+ border property values and full shorthand border properties.
+
+
+
+
+
+
+
+ These basic properties make up the thickness, color and
+ corner radii which can be used individually.
+
+ }
+ themeValues={Object.keys(valueProps).map((prop) => (
+
+ updateBorder(prop, value)}
+ example={
+ prop === 'color' ? (
+
+ ) : undefined
+ }
+ />
+
+ ))}
+ />
+
+
+
+ These common border types string together the base
+ properties to form common full border{' '}
+ properties.
+
+ }
+ themeValues={Object.keys(typeProps).map((prop) => (
+
+ updateBorder(prop, value)}
+ stringProps={{ style: { width: 160 } }}
+ buttonStyle={[
+ style,
+ css`
+ border: ${borderClone[prop]};
+ `,
+ ]}
+ />
+
+ ))}
+ />
+ >
+ ),
+ },
+ {
+ id: 'themeBorderTabUsage',
+ name: 'Usage',
+ content: (
+ <>
+
+
+ The simplest form of consuming border styles is using one
+ of the full types which provides the color, width and
+ style.
+
+ }
+ example={
+
+ {`border: ${euiTheme.border.thick}`}
+
+ }
+ snippet={'border: ${euiTheme.border.thick};'}
+ />
+
+
+ You can also strictly use the border values within a
+ single shorthand property.
+
+ }
+ example={
+
+ The colors theme key is a mix of hard-coded hex
+ values and computed colors. The colorMode{' '}
+ determines which values to return based on{' '}
+ LIGHT or DARK mode.
+
+
+ When switching between light and dark color modes, the theme keys
+ do not change, only their values do. This is why most keys are not
+ named for their evaluated value but by their{' '}
+ purpose.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ colorMode
+
+
+
+
+
+ {colorMode}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Elastic has two main brand colors. The other three are
+ used for statefulness like indicating between successful
+ and dangerous actions.
+
+ }
+ property="colors"
+ themeValues={brandKeys.map((color) => (
+
+ }
+ onUpdate={(hex) => updateColor(color, hex)}
+ />
+
+ ))}
+ />
+
+
+
+
+ Each brand color also has a corresponding text variant
+ that has been calculated for proper (4.5) contrast against{' '}
+ colors.body and should be used
+ specifically when coloring text. As is used in{' '}
+
+ EuiTextColor
+
+ .
+
+ }
+ property="colors"
+ themeValues={brandTextKeys.map((color, index) => (
+
+ updateColor(color, hex)}
+ example={
+
+ }
+ />
+
+ ))}
+ />
+
+
+
+
+ A six-color grayscale palette. Variation beyond these
+ colors is minimal and always done through computations
+ against this set.
+
+ }
+ property="colors"
+ themeValues={shadeKeys.map((color) => (
+
+ }
+ onUpdate={(hex) => updateColor(color, hex)}
+ />
+
+ ))}
+ />
+
+
+
+
+ Specific text colors calculated off either the brand or
+ shade colors.
+
+ }
+ property="colors"
+ themeValues={textKeys.map((color) => (
+
+ updateColor(color, hex)}
+ example={
+
+ }
+ />
+
+ ))}
+ />
+
+
+
+ These are used a lot for special cases.}
+ property="colors"
+ themeValues={specialKeys.map((color) => {
+ if (color.includes('Text')) {
+ return (
+
+ updateColor(color, hex)}
+ example={
+
+ }
+ />
+
+ );
+ } else {
+ return (
+
+
+ }
+ onUpdate={(hex) => updateColor(color, hex)}
+ />
+
+ );
+ }
+ })}
+ />
+
+
+
+ These are constant no matter the theme or color mode.
+ }
+ property="colors"
+ themeValues={
+ <>
+
+
+ }
+ />
+
+
+
+ }
+ />
+
+ >
+ }
+ />
+ >
+ ),
+ },
+ {
+ id: 'themeColorsTabUsage',
+ name: 'Usage',
+ content: (
+ <>
+
+
+ Most usages of the colors can be implemented simply by
+ pulling and applying the values.
+
+ }
+ example={
+
+ background: {euiTheme.colors.warning}
+
+ }
+ snippet={'background: ${euiTheme.colors.warning};'}
+ />
+
+
+ Since the EUI colors usually evaluate to a hex value, the
+ easiest way to perform color operations like transparency,
+ shading, or tinting is by using the EUI provided methods
+ of transparentize(),{' '}
+ shade(), and tint(){' '}
+ respectively.
+
+ }
+ example={
+
+ }
+ snippet={
+ 'background: ${transparentize(euiTheme.colors.warning, .25)};'
+ }
+ />
+
+
+ Remember, when using any of the EUI colors for text, use
+ the text specific variant.
+
+ }
+ example={
+
+ color: {euiTheme.colors.warningText}
+
+ }
+ snippet={'color: ${euiTheme.colors.warningText};'}
+ />
+
+
+ If your background color is anything other than or darker
+ than the body color, you will want to
+ re-calculate the high contrast version by using the{' '}
+
+ makeHighContrastColor(foreground)(background)
+ {' '}
+ method.
+
+ }
+ example={
+
+ All sizing values. including font size, are calculated from a single{' '}
+ base integer and converted to pixel or rem string
+ values.
+
+
+
+
+
+
+
+ All sizing values are calculated from a single{' '}
+ base integer and converted to pixel
+ string values.
+
+ }
+ themeValues={
+
+ updateBase('base', value)}
+ />
+
+ }
+ />
+
+
+ It is recommended not to adjust the computed sizes and
+ only adjust the top level base value.
+
+ }
+ property="size"
+ themeValues={Object.keys(sizes).map((size) => (
+
+
+
+ ))}
+ />
+ >
+ ),
+ },
+ {
+ id: 'themeSizingTabUsage',
+ name: 'Usage',
+ content: (
+ <>
+
+
+ You can use calculations on top of the base value, just be
+ sure to append the px unit to the end.
+
+ }
+ example={
+
+ {`padding: ${euiTheme.base * 2}px`}
+
+ }
+ snippet={'padding: ${euiTheme.base * 2}px;'}
+ />
+
+
+ Using the values as they are is straight foward.
+ }
+ example={
+
+ When doing calculations on top of the size values, you
+ have to use the{' '}
+
+ CSS calc() method
+ {' '}
+ because the value that is returned is only a string
+ value with the unit.
+
+ The typography specific theme keys start with the{' '}
+ font key.
+
+
+
+
+
+
+
+
+
+
+ The base font settings determine things like{' '}
+ family and{' '}
+ featureSettings.
+
+
+ The lineHeightMultiplier established
+ the line-height in percentages compared to the
+ font-size, but it is the baseline{' '}
+ integer that establishes the final pixel/rem value by
+ ensuring it falls on a multiplier of this baseline.
+
+ Matches up colloqual weight names with their appropriate
+ number values.
+
+
+ These default weights are what is manually pulled from
+ Google fonts. If you intend to change these numbers,
+ switch to a variable font or change your font import to
+ include those you've selected.
+
+ The typographic scale that is used to calculate the font
+ size variables. These are multipliers applied the{' '}
+ euiTheme.base value.
+
+
+ The default scale is loosely based on the{' '}
+
+ Major Third (1.250) typographic scale
+
+ .
+
+
+ You do not want to consume this scale directly. Instead,
+ you will want to use the calculated font sizing keys
+ such as font.s or{' '}
+ font.s.fontSize. See the{' '}
+ Usage tab for more details.
+
+ >
+ }
+ property="font"
+ themeValues={scaleKeys.map((key) => (
+
+ updateScale(key, value)}
+ numberProps={{ step: 0.1, style: { width: '6em' } }}
+ groupProps={{ alignItems: 'center' }}
+ />
+
+ ))}
+ />
+ >
+ ),
+ },
+ {
+ id: 'themeTypographyTabUsage',
+ name: 'Usage',
+ content: (
+ <>
+
+
+ All of EUI defaults to this base font family. However, you
+ can use it to override a custom component that is not
+ inheriting this family.
+
+ }
+ example={
+
+ {'I am a code element forced to default font family'}
+
+ }
+ snippet={'font-family: ${euiTheme.font.family};'}
+ />
+
+
+ To maintain consistency, EUI established the font weight
+ patterns directly in the text and title components.
+ However, we recommend using the theme keys instead of
+ `font-weight: bold` in your css to ensure proper alignment
+ with the imported font family.
+
+ }
+ example={
+
+ To ensure proper baseline alignment and readable
+ line-height, the theme's font sizing keys come with
+ both font-size and line-height. Because
+ of this, we recommend the array approach to applying
+ with Emotion.
+
+
+ While this works well, we still recommend using the{' '}
+
+ EuiText
+ {' '}
+ component directly and sticking to the sizing props
+ provided.
+
+ >
+ }
+ example={
+
+
+ Orbiting this at a distance of roughly ninety-two
+ million miles is an utterly insignificant little blue
+ green planet whose ape- descended life forms are so
+ amazingly primitive that they still think digital
+ watches are a pretty neat idea.
+
+ You can still grab just the font-size value if need be.
+
+ >
+ }
+ example={
+
+
+ Orbiting this at a distance of roughly ninety-two
+ million miles is an utterly insignificant little blue
+ green planet whose ape- descended life forms are so
+ amazingly primitive that they still think digital
+ watches are a pretty neat idea.
+
- The euiColorPrimary color has been changed to{' '}
+ The colors.primary value has been changed to{' '}
#db1dde (#e378e4 for dark mode)
- and so the calculated values of euiColorPrimaryText{' '}
- and euiFocusBackgroundColor have also been updated.
+ and so the calculated value of colors.primaryText{' '}
+ has also been updated.
diff --git a/src-docs/src/views/theme/consuming.tsx b/src-docs/src/views/theme/consuming.tsx
index 4fe1eb59f97..c013804fbb0 100644
--- a/src-docs/src/views/theme/consuming.tsx
+++ b/src-docs/src/views/theme/consuming.tsx
@@ -12,15 +12,15 @@ export default () => {
{' '}
This primary color will adjust based on the light or dark theme value
The padding of this box is created using calc(){' '}
diff --git a/src-docs/src/views/theme/consuming_hoc.tsx b/src-docs/src/views/theme/consuming_hoc.tsx
index 71126c7d213..7fb7b8acc11 100644
--- a/src-docs/src/views/theme/consuming_hoc.tsx
+++ b/src-docs/src/views/theme/consuming_hoc.tsx
@@ -9,9 +9,9 @@ class Block extends React.Component {
const { theme } = this.props;
const divStyle = css`
- background: ${theme.euiTheme.colors.euiColorLightShade};
- padding: ${theme.euiTheme.sizes.euiSizeXL};
- border-radius: ${theme.euiTheme.borders.euiBorderRadius};
+ background: ${theme.euiTheme.colors.lightShade};
+ padding: ${theme.euiTheme.size.xl};
+ border-radius: ${theme.euiTheme.border.radius};
`;
return (
diff --git a/src-docs/src/views/theme/create_computed.tsx b/src-docs/src/views/theme/create_computed.tsx
index 9bc013ffaab..2ce8fd6f4a3 100644
--- a/src-docs/src/views/theme/create_computed.tsx
+++ b/src-docs/src/views/theme/create_computed.tsx
@@ -1,5 +1,4 @@
import React, { FunctionComponent, ReactNode } from 'react';
-import { tint, shade } from '../../../../src/services/theme/theme';
import { EuiIcon } from '../../../../src/components/icon';
import { EuiCode } from '../../../../src/components/code';
import { EuiText } from '../../../../src/components/text';
@@ -8,6 +7,7 @@ import {
EuiThemeProvider,
useEuiTheme,
} from '../../../../src/services';
+import { shade, tint } from '../../../../src/services/color';
interface ThemeExtensions {
colors: {
@@ -24,7 +24,7 @@ const Box: FunctionComponent<{ children: ReactNode }> = ({ children }) => {
@@ -19,8 +19,8 @@ const Box: FunctionComponent<{ children: ReactNode }> = ({ children }) => {
export default () => {
const overrides = {
colors: {
- light: { euiColorLightShade: '#d3e6df' },
- dark: { euiColorLightShade: '#394c4b' },
+ LIGHT: { lightShade: '#d3e6df' },
+ DARK: { lightShade: '#394c4b' },
},
};
@@ -29,7 +29,7 @@ export default () => {
The background of this box is using the locally overridden value of{' '}
- theme.colors.euiColorLightShade
+ euiTheme.colors.lightShade
diff --git a/src-docs/src/views/theme/theme_example.js b/src-docs/src/views/theme/theme_example.js
index 00adf63bca5..6adfcaa1888 100644
--- a/src-docs/src/views/theme/theme_example.js
+++ b/src-docs/src/views/theme/theme_example.js
@@ -9,6 +9,7 @@ import {
EuiSpacer,
EuiCallOut,
EuiCode,
+ EuiLink,
} from '../../../../src/components';
import { EuiThemeProvider } from '../../../../src/services';
@@ -38,6 +39,8 @@ const createComputedHtml = renderToHtml(CreateComputed);
export const ThemeExample = {
title: 'Theme provider',
+ isNew: true,
+ beta: true,
intro: (
<>
@@ -52,13 +55,51 @@ export const ThemeExample = {
size="s"
title="The following examples assume that you have wrapped your entire application with this provider."
/>
-
>
),
sections: [
{
title: 'EuiThemeProvider',
- text:
TODO
,
+ text: (
+ <>
+
+ The context layer that enables theming (including the default theme
+ styles) comes from EuiThemeProvider. Simply put,
+ this is a thin wrapper around and caching layer built onto{' '}
+ React.Context.Provider.
+
+
+ Typically your app will only need a single instance at the top level
+ and the functionality will flow down the component tree. It is also
+ possible to use several nested theme providers. In this case each
+ nested provider will inherit from its closest ancestor provider.
+
+
+ EuiThemeProvider accepts three props, all of
+ which have default values and are therefore optional. To use the
+ default EUI theme, no configuration is required.
+
+
+
+ theme: EuiThemeSystem Raw theme
+ values. Calculated values are acceptable.
+
+
+ colorMode: EuiThemeColorMode{' '}
+ Simply {"'light'"} or {"'dark'"}
+
+
+ modify: EuiThemeModifications{' '}
+ Overrides and modifications for theme values.
+
+
+
+ The concept for each prop is explained in subsequent sections. More
+ information on the full shape of an EUI theme, see the{' '}
+ EuiTheme page.
+
- While it is usually best to keep all components rendering in the
- same light or dark color mode, some components benefit from an
- exaggerated change in contrast from the current theme. For this you
- can specify EuiThemeProvider's{' '}
+ While it is usually best to keep all consumptions of the global
+ variables rendering in the same light or dark color mode, some
+ instances benefit from an exaggerated change in contrast from the
+ current theme. For this you can specify{' '}
+ EuiThemeProvider's{' '}
colorMode to always be{' '}
{'"light"'}, {'"dark"'}, or{' '}
{'"inverse"'} which sets it to the opposite of
@@ -206,8 +248,8 @@ export const ThemeExample = {
For instance, we compute text variants of our base colors. So
- locally overriding the euiColorPrimary color will
- automatically cascade to the euiColorPrimaryText.
+ locally overriding the colors.primary color will
+ automatically cascade to the colors.primaryText.
You can however, directly override computed values as well by
passing a custom value to this theme variable.
@@ -235,9 +277,6 @@ export const ThemeExample = {
specific theme variables. Instead, you should append custom keys to
the theme.
-
- TODO: Indicate type support for custom keys.
-
>
),
demo: ,
diff --git a/src-docs/src/views/theme/values.js b/src-docs/src/views/theme/values.js
new file mode 100644
index 00000000000..d52f49c2cc5
--- /dev/null
+++ b/src-docs/src/views/theme/values.js
@@ -0,0 +1,148 @@
+import React from 'react';
+import { Link } from 'react-router-dom';
+import {
+ useEuiTheme,
+ mergeDeep,
+ EuiThemeProvider,
+} from '../../../../src/services';
+
+import { GuidePage } from '../../components';
+
+import Colors from './_colors';
+import Size from './_size';
+import Typography from './_typography';
+import Border from './_border';
+import Animation from './_animation';
+import Breakpoints from './_breakpoints';
+
+import {
+ EuiSpacer,
+ EuiCodeBlock,
+ EuiBottomBar,
+ EuiFlexGroup,
+ EuiFlexItem,
+ EuiCode,
+ EuiFlyout,
+ EuiFlyoutHeader,
+ EuiFlyoutBody,
+ EuiTitle,
+} from '../../../../src/components';
+import { EuiHorizontalRule } from '../../../../src/components/horizontal_rule';
+import { EuiButton, EuiButtonEmpty } from '../../../../src/components/button';
+import { EuiCopy } from '../../../../src/components/copy';
+import { EuiCallOut } from '../../../../src/components/call_out';
+
+const JsonFlyout = ({ setIsOpen }) => {
+ const { euiTheme } = useEuiTheme();
+ return (
+ setIsOpen(false)}>
+
+
+
+ The euiTheme() hook is only available for
+ consuming the values. Modifying or overriding the values will not
+ have any effect on the individual EUI components, yet. Instead,
+ you still need to use the{' '}
+ Sass method.
+
+
+ }>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {Object.keys(overrides).length > 0 && (
+ <>
+
+
+ Clear overrides
+
+
+
+
+ {(copy) => (
+
+ Copy overrides
+
+ )}
+
+
+ >
+ )}
+
+
+ setJsonFlyoutIsOpen(true)}
+ color="ghost">
+ View theme JSON
+
+
+
+
+
+
+ {jsonFlyoutIsOpen && }
+
+
+ );
+};
diff --git a/src/components/color_picker/_color_picker_swatch.scss b/src/components/color_picker/_color_picker_swatch.scss
index a9fbbe25751..ba8c5adebfd 100644
--- a/src/components/color_picker/_color_picker_swatch.scss
+++ b/src/components/color_picker/_color_picker_swatch.scss
@@ -8,7 +8,11 @@
border: solid 1px transparentize($euiColorFullShade, .9);
box-shadow: inset 0 0 0 1px transparentize($euiColorEmptyShade, .95);
+ &:disabled {
+ cursor: default;
+ }
+
&:focus {
@include euiFocusRing;
}
-}
\ No newline at end of file
+}
diff --git a/src/components/common.ts b/src/components/common.ts
index 094b2aec3dd..9cb88edb806 100644
--- a/src/components/common.ts
+++ b/src/components/common.ts
@@ -47,6 +47,13 @@ export function keysOf(obj: T): K[] {
return Object.keys(obj) as K[];
}
+/**
+ * Like `keyof typeof`, but for getting values instead of keys
+ * ValueOf
+ * Results in `'value1' | 'value2'`
+ */
+export type ValueOf = T[keyof T];
+
export type PropsOf = C extends SFC
? SFCProps
: C extends FunctionComponent
diff --git a/src/global_styling/functions/_colors.ts b/src/global_styling/functions/_colors.ts
new file mode 100644
index 00000000000..22006541e1f
--- /dev/null
+++ b/src/global_styling/functions/_colors.ts
@@ -0,0 +1,86 @@
+/*
+ * 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 chroma from 'chroma-js';
+import { shade, tint, lightness as getLightness } from '../../services/color';
+import { getOn } from '../../services/theme/utils';
+
+/**
+ * Creates a new color that meets or exceeds WCAG level AA
+ * @param foreground - Color to manipulate
+ * @param ratio - Amount to change in absolute terms. 0-1.
+ * *
+ * @param themeOrBackground - Color to use as the contrast basis
+ */
+export const makeHighContrastColor = (_foreground: string, ratio = 4.5) => (
+ themeOrBackground:
+ | string
+ | {
+ colors: { body: string };
+ [key: string]: any;
+ }
+) => {
+ const foreground = (typeof themeOrBackground === 'object'
+ ? getOn(themeOrBackground, _foreground)
+ : _foreground) as string;
+ const background =
+ typeof themeOrBackground === 'object'
+ ? themeOrBackground.colors.body
+ : themeOrBackground;
+ let contrast = chroma.contrast(foreground, background);
+
+ // Determine the lightness factor of the background color first to
+ // determine whether to shade or tint the foreground.
+ const brightness = getLightness(background);
+
+ let highContrastTextColor = foreground;
+
+ while (contrast < ratio) {
+ if (brightness > 50) {
+ highContrastTextColor = shade(highContrastTextColor, 0.05);
+ } else {
+ highContrastTextColor = tint(highContrastTextColor, 0.05);
+ }
+
+ contrast = chroma.contrast(highContrastTextColor, background);
+
+ const lightness = getLightness(highContrastTextColor);
+
+ if (lightness < 5) {
+ console.warn(
+ 'High enough contrast could not be determined. Most likely your background color does not adjust for light mode.'
+ );
+ return highContrastTextColor;
+ }
+
+ if (lightness > 95) {
+ console.warn(
+ 'High enough contrast could not be determined. Most likely your background color does not adjust for dark mode.'
+ );
+ return highContrastTextColor;
+ }
+ }
+
+ return highContrastTextColor;
+};
+
+/**
+ * Creates a new color with increased contrast
+ * Disabled content only needs a contrast of at least 2 because there is no interaction available
+ * @param foreground - Color to manipulate
+ * *
+ * @param themeOrBackground - Color to use as the contrast basis
+ */
+export const makeDisabledContrastColor = ($color: string) => (
+ themeOrBackground:
+ | string
+ | {
+ colors: { body: string };
+ [key: string]: any;
+ }
+) => makeHighContrastColor($color, 2)(themeOrBackground);
diff --git a/src/global_styling/functions/_typography.ts b/src/global_styling/functions/_typography.ts
new file mode 100644
index 00000000000..3c44e485cd7
--- /dev/null
+++ b/src/global_styling/functions/_typography.ts
@@ -0,0 +1,34 @@
+/*
+ * 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 { _EuiThemeFontBase } from '../variables/_typography';
+
+// Typography functions
+
+// Calculates the line-height to the closest multiple of the baseline
+// EX: A proper line-height for text is 1.5 times the font-size.
+// If our base font size (euiFontSize) is 16, and our baseline is 4. To ensure the
+// text stays on the baseline, we pass a multiplier to calculate a line-height.
+
+export function fontSizeFromScale(base: number, scale: number) {
+ const pixelValue = Math.round(base * scale);
+ return `${pixelValue / base}rem`;
+}
+
+export function lineHeightFromBaseline(
+ base: number,
+ font: _EuiThemeFontBase,
+ scale: number
+) {
+ const { lineHeightMultiplier, baseline } = font;
+
+ const pixelValue =
+ Math.floor(Math.round(base * scale * lineHeightMultiplier) / baseline) *
+ baseline;
+ return `${pixelValue / base}rem`;
+}
diff --git a/src/global_styling/mixins/_helpers.ts b/src/global_styling/mixins/_helpers.ts
new file mode 100644
index 00000000000..1961440107f
--- /dev/null
+++ b/src/global_styling/mixins/_helpers.ts
@@ -0,0 +1,136 @@
+/*
+ * 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 chroma from 'chroma-js';
+import { useEuiTheme } from '../../services/theme/hooks';
+import { transparentize } from '../../services/color';
+import { useOverflowShadow } from './_shadow';
+
+// Helper mixins
+
+// Useful border shade when dealing with images of unknown color.
+export const useInnerBorder = ({
+ type = 'dark',
+ borderRadius = 0,
+ alpha = 0.1,
+}: {
+ type?: 'light' | 'dark';
+ borderRadius?: number;
+ alpha?: number;
+}) => {
+ const {
+ euiTheme: { colors },
+ } = useEuiTheme();
+ const color = chroma(
+ type === 'dark' ? colors.darkestShade : colors.emptyShade
+ )
+ .alpha(alpha)
+ .css();
+
+ return `
+ position: relative;
+
+ &:after {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ border-radius: ${borderRadius};
+ content: '';
+ pointer-events: none;
+ border: 1px solid ${color};
+ }
+ `;
+};
+
+// Set scroll bar appearance on Chrome (and firefox).
+export const useScrollBar = ({
+ thumbColor: _thumbColor,
+ trackBackgroundColor: _trackBackgroundColor,
+}: {
+ thumbColor?: string;
+ trackBackgroundColor?: string;
+} = {}) => {
+ const {
+ euiTheme: { colors, size },
+ } = useEuiTheme();
+ const thumbColor = _thumbColor || colors.darkShade;
+ const trackBackgroundColor = _trackBackgroundColor || 'transparent';
+ // Firefox's scrollbar coloring cascades, but the sizing does not,
+ // so it's being added to this mixin for allowing support wherever custom scrollbars are
+ return `
+ scrollbar-width: thin;
+ &::-webkit-scrollbar {
+ width: ${size.base};
+ height: ${size.base};
+ }
+ &::-webkit-scrollbar-thumb {
+ background-color: ${transparentize(thumbColor, 0.5)};
+ border: calc(${size.base} * 0.75) solid ${trackBackgroundColor};
+ background-clip: content-box;
+ }
+ &::-webkit-scrollbar-corner,
+ &::-webkit-scrollbar-track {
+ background-color: ${trackBackgroundColor};
+ }
+ `;
+};
+
+/**
+ * 1. Focus rings shouldn't be visible on scrollable regions, but a11y requires them to be focusable.
+ * Browser's supporting `:focus-visible` will still show outline on keyboard focus only.
+ * Others like Safari, won't show anything at all.
+ */
+
+// Just overflow and scrollbars
+export const useYScroll = () => `
+ ${useScrollBar()}
+ height: 100%;
+ overflow-y: auto;
+ overflow-x: hidden;
+ &:focus {
+ outline: none; /* 1 */
+ }
+`;
+export const useXScroll = () => `
+ ${useScrollBar()}
+ overflow-x: auto;
+
+ &:focus {
+ outline: none; /* 1 */
+ }
+`;
+
+// // The full overflow with shadow
+export const useYScrollWithShadows = () => `
+ ${useYScroll()}
+ ${useOverflowShadow({ direction: 'y' })}
+`;
+
+export const useXScrollWithShadows = () => `
+ ${useXScroll()}
+ ${useOverflowShadow({ direction: 'x' })}
+`;
+
+// Hiding elements offscreen to only be read by screen reader
+export const useScreenReaderOnly = () => `
+ position: absolute;
+ left: -10000px;
+ top: auto;
+ width: 1px;
+ height: 1px;
+ overflow: hidden;
+`;
+
+// Doesn't have reduced motion turned on
+export const useCanAnimate = (content: string) => `
+ @media screen and (prefers-reduced-motion: no-preference) {
+ ${content}
+ }
+`;
diff --git a/src/global_styling/mixins/_shadow.ts b/src/global_styling/mixins/_shadow.ts
new file mode 100644
index 00000000000..b4c9b89d20a
--- /dev/null
+++ b/src/global_styling/mixins/_shadow.ts
@@ -0,0 +1,246 @@
+/*
+ * 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 chroma from 'chroma-js';
+import { useEuiTheme } from '../../services/theme/hooks';
+import { lightness, tint, transparentize } from '../../services/color';
+
+export const useSlightShadow = ({
+ color,
+ opacity,
+}: {
+ color?: string;
+ opacity?: number;
+} = {}) => {
+ const {
+ euiTheme: { colors },
+ } = useEuiTheme();
+ const rgba = chroma(color || colors.shadow)
+ .alpha(opacity || 0.3)
+ .css();
+ return `box-shadow: 0 2px 2px -1px ${rgba};`;
+};
+
+export const useBottomShadowSmall = ({
+ color,
+ opacity,
+}: {
+ color?: string;
+ opacity?: number;
+} = {}) => {
+ const {
+ euiTheme: { colors },
+ } = useEuiTheme();
+ const rgba = chroma(color || colors.shadow)
+ .alpha(opacity || 0.3)
+ .css();
+ return `
+ box-shadow:
+ 0 2px 2px -1px ${rgba},
+ 0 1px 5px -2px ${rgba};
+ `;
+};
+
+export const useBottomShadowMedium = ({
+ color,
+ opacity,
+}: {
+ color?: string;
+ opacity?: number;
+} = {}) => {
+ const {
+ euiTheme: { colors },
+ } = useEuiTheme();
+ const rgba = chroma(color || colors.shadow)
+ .alpha(opacity || 0.2)
+ .css();
+ return `
+ box-shadow:
+ 0 6px 12px -1px ${rgba},
+ 0 4px 4px -1px ${rgba},
+ 0 2px 2px 0 ${rgba};
+ `;
+};
+
+// Similar to shadow medium but without the bottom depth. Useful for popovers
+// that drop UP rather than DOWN.
+export const useBottomShadowFlat = ({
+ color,
+ opacity,
+}: {
+ color?: string;
+ opacity?: number;
+} = {}) => {
+ const {
+ euiTheme: { colors },
+ } = useEuiTheme();
+ const rgba = chroma(color || colors.shadow)
+ .alpha(opacity || 0.2)
+ .css();
+ return `
+ box-shadow:
+ 0 0 12px -1px ${rgba},
+ 0 0 4px -1px ${rgba},
+ 0 0 2px 0 ${rgba};
+ `;
+};
+
+// adjustBorder allows the border color to match the drop shadow better so that there's better
+// distinction between element bounds and the shadow (crisper borders)
+export const useBottomShadow = ({
+ color: _color,
+ opacity,
+ adjustBorders,
+}: {
+ color?: string;
+ opacity?: number;
+ adjustBorders?: boolean;
+} = {}) => {
+ const {
+ euiTheme: { border, colors },
+ } = useEuiTheme();
+ const color = _color || colors.shadow;
+ const rgba = chroma(color)
+ .alpha(opacity || 0.2)
+ .css();
+
+ const adjustedBorders =
+ adjustBorders && !(lightness(border.color) < 50)
+ ? `
+ border-color: ${tint(color, 0.75)};
+ border-top-color: ${tint(color, 0.8)};
+ border-bottom-color: ${tint(color, 0.55)};
+ `
+ : '';
+
+ return `
+ box-shadow:
+ 0 12px 24px 0 ${rgba},
+ 0 6px 12px 0 ${rgba},
+ 0 4px 4px 0 ${rgba},
+ 0 2px 2px 0 ${rgba};
+ ${adjustedBorders}
+ `;
+};
+
+export const useBottomShadowLarge = ({
+ color: _color,
+ opacity,
+ adjustBorders,
+ reverse,
+}: {
+ color?: string;
+ opacity?: number;
+ adjustBorders?: boolean;
+ reverse?: boolean;
+} = {}) => {
+ const {
+ euiTheme: { border, colors },
+ } = useEuiTheme();
+ const color = _color || colors.shadow;
+ const rgba = chroma(color)
+ .alpha(opacity || 0.1)
+ .css();
+
+ // Never adjust borders if the border color is already on the dark side (dark theme)
+ const adjustedBorders =
+ adjustBorders && !(lightness(border.color) < 50)
+ ? `
+ border-color: ${tint(color, 0.75)};
+ border-top-color: ${tint(color, 0.8)};
+ border-bottom-color: ${tint(color, 0.55)};
+ `
+ : '';
+
+ if (reverse) {
+ return `
+ box-shadow:
+ 0 -40px 64px 0 ${rgba},
+ 0 -24px 32px 0 ${rgba},
+ 0 -16px 16px 0 ${rgba},
+ 0 -8px 8px 0 ${rgba};
+ ${adjustedBorders}
+ `;
+ } else {
+ return `
+ box-shadow:
+ 0 40px 64px 0 ${rgba},
+ 0 24px 32px 0 ${rgba},
+ 0 16px 16px 0 ${rgba},
+ 0 8px 8px 0 ${rgba},
+ 0 4px 4px 0 ${rgba},
+ 0 2px 2px 0 ${rgba};
+ ${adjustedBorders}
+ `;
+ }
+};
+
+export const useSlightShadowHover = ({
+ color,
+ opacity: _opacity,
+}: {
+ color?: string;
+ opacity?: number;
+} = {}) => {
+ const {
+ euiTheme: { colors },
+ } = useEuiTheme();
+ const opacity = _opacity || 0.3;
+ const rgba1 = chroma(color || colors.shadow)
+ .alpha(opacity)
+ .css();
+ const rgba2 = chroma(color || colors.shadow)
+ .alpha(opacity / 2)
+ .css();
+ return `
+ box-shadow:
+ 0 4px 8px 0 ${rgba2},
+ 0 2px 2px -1px ${rgba1};
+ `;
+};
+
+export const useSlightShadowActive = useSlightShadowHover;
+
+export const useOverflowShadow = ({
+ direction: _direction,
+ side: _side,
+}: {
+ direction?: 'y' | 'x';
+ side?: 'both' | 'start' | 'end';
+} = {}) => {
+ const direction = _direction || 'y';
+ const side = _side || 'both';
+ const {
+ euiTheme: { size },
+ } = useEuiTheme();
+ const hideHeight = `calc(${size.base} * 0.75 * 1.25)`;
+ const gradientStart = `
+ ${transparentize('red', 0.9)} 0%,
+ ${transparentize('red', 0)} ${hideHeight};
+ `;
+ const gradientEnd = `
+ ${transparentize('red', 0)} calc(100% - ${hideHeight}),
+ ${transparentize('red', 0.9)} 100%;
+ `;
+ let gradient = '';
+ if (side) {
+ if (side === 'both') {
+ gradient = `${gradientStart}, ${gradientEnd}`;
+ } else if (side === 'start') {
+ gradient = `${gradientStart}`;
+ } else {
+ gradient = `${gradientEnd}`;
+ }
+ }
+
+ if (direction === 'y') {
+ return `mask-image: linear-gradient(to bottom, ${gradient})`;
+ } else {
+ return `mask-image: linear-gradient(to right, ${gradient})`;
+ }
+};
diff --git a/src/global_styling/variables/_animations.ts b/src/global_styling/variables/_animations.ts
new file mode 100644
index 00000000000..5c083375f16
--- /dev/null
+++ b/src/global_styling/variables/_animations.ts
@@ -0,0 +1,42 @@
+/*
+ * 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 { CSSProperties } from 'react';
+
+export interface _EuiThemeAnimationSpeed {
+ extraFast: CSSProperties['animationDuration'];
+ fast: CSSProperties['animationDuration'];
+ normal: CSSProperties['animationDuration'];
+ slow: CSSProperties['animationDuration'];
+ extraSlow: CSSProperties['animationDuration'];
+}
+export interface _EuiThemeAnimationEasing {
+ bounce: CSSProperties['animationTimingFunction'];
+ resistance: CSSProperties['animationTimingFunction'];
+}
+
+export type EuiThemeAnimation = _EuiThemeAnimationEasing &
+ _EuiThemeAnimationSpeed;
+
+export const animation_speed: _EuiThemeAnimationSpeed = {
+ extraFast: '90ms',
+ fast: '150ms',
+ normal: '250ms',
+ slow: '350ms',
+ extraSlow: '500ms',
+};
+
+export const animation_ease: _EuiThemeAnimationEasing = {
+ bounce: 'cubic-bezier(.34, 1.61, .7, 1)',
+ resistance: 'cubic-bezier(.694, .0482, .335, 1)',
+};
+
+export const animation: EuiThemeAnimation = {
+ ...animation_speed,
+ ...animation_ease,
+};
diff --git a/src/global_styling/variables/_borders.ts b/src/global_styling/variables/_borders.ts
new file mode 100644
index 00000000000..6cb4adbc9de
--- /dev/null
+++ b/src/global_styling/variables/_borders.ts
@@ -0,0 +1,72 @@
+/*
+ * 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 { CSSProperties } from 'react';
+import { ColorModeSwitch } from '../../services/theme/types';
+import { computed } from '../../services/theme/utils';
+import { sizeToPixel } from './_size';
+
+export interface _EuiThemeBorderValues {
+ /**
+ * Color for all borders; Default is `colors.lightShade`
+ */
+ color: ColorModeSwitch;
+ /**
+ * Thinnest width for border
+ */
+ widthThin: CSSProperties['borderWidth'];
+ /**
+ * Thickest width for border
+ */
+ widthThick: CSSProperties['borderWidth'];
+ /**
+ * Main corner radius size
+ */
+ radius: CSSProperties['borderRadius'];
+ /**
+ * Small corner radius size
+ */
+ radiusSmall: CSSProperties['borderRadius'];
+}
+
+export interface _EuiThemeBorderTypes {
+ /**
+ * Full `border` property string computed using `border.widthThin` and `border.color`
+ */
+ thin: CSSProperties['border'];
+ /**
+ * Full `border` property string computed using `border.widthThick` and `border.color`
+ */
+ thick: CSSProperties['border'];
+ /**
+ * Full editable style `border` property string computed using `border.widthThick` and `border.color`
+ */
+ editable: CSSProperties['border'];
+}
+
+export type EuiThemeBorder = _EuiThemeBorderValues & _EuiThemeBorderTypes;
+
+export const border: EuiThemeBorder = {
+ color: computed(([lightShade]) => lightShade, ['colors.lightShade']),
+ widthThin: '1px',
+ widthThick: '2px',
+ radius: computed(sizeToPixel(0.25)),
+ radiusSmall: computed(sizeToPixel(0.125)),
+ thin: computed(([widthThin, color]) => `${widthThin} solid ${color}`, [
+ 'border.widthThin',
+ 'border.color',
+ ]),
+ thick: computed(([widthThick, color]) => `${widthThick} solid ${color}`, [
+ 'border.widthThick',
+ 'border.color',
+ ]),
+ editable: computed(([widthThick, color]) => `${widthThick} dotted ${color}`, [
+ 'border.widthThick',
+ 'border.color',
+ ]),
+};
diff --git a/src/global_styling/variables/_breakpoint.ts b/src/global_styling/variables/_breakpoint.ts
new file mode 100644
index 00000000000..c3643f06a78
--- /dev/null
+++ b/src/global_styling/variables/_breakpoint.ts
@@ -0,0 +1,24 @@
+/*
+ * 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.
+ */
+
+export type _EuiBreakpointSize = 'xs' | 's' | 'm' | 'l' | 'xl';
+
+export type EuiThemeBreakpoint = {
+ /**
+ * Set the minimum window width at which to start to the breakpoint
+ */
+ [key in _EuiBreakpointSize]: number;
+};
+
+export const breakpoint: EuiThemeBreakpoint = {
+ xl: 1200,
+ l: 992,
+ m: 768,
+ s: 575,
+ xs: 0,
+};
diff --git a/src/global_styling/variables/_colors.ts b/src/global_styling/variables/_colors.ts
new file mode 100644
index 00000000000..30dd95fbdc0
--- /dev/null
+++ b/src/global_styling/variables/_colors.ts
@@ -0,0 +1,289 @@
+/*
+ * 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 { saturate, shade, tint } from '../../services/color';
+import { computed } from '../../services/theme/utils';
+import {
+ ColorModeSwitch,
+ StrictColorModeSwitch,
+} from '../../services/theme/types';
+import {
+ makeDisabledContrastColor,
+ makeHighContrastColor,
+} from '../functions/_colors';
+
+/*
+ * TYPES
+ */
+
+/**
+ * Top 5 colors
+ */
+export type _EuiThemeBrandColors = {
+ /**
+ * Main brand color and used for most call to actions like buttons and links.
+ */
+ primary: ColorModeSwitch;
+ /**
+ * Pulls attention to key indicators like notifications or number of selections.
+ */
+ accent: ColorModeSwitch;
+ /**
+ * Used for positive messages/graphics and additive actions.
+ */
+ success: ColorModeSwitch;
+ /**
+ * Used for warnings and actions that have a potential to be destructive.
+ */
+ warning: ColorModeSwitch;
+ /**
+ * Used for negative messages/graphics like errors and destructive actions.
+ */
+ danger: ColorModeSwitch;
+};
+
+/**
+ * Every brand color must have a contrast computed text equivelant
+ */
+export type _EuiThemeBrandTextColors = {
+ /**
+ * Typically computed against colors.primary
+ */
+ primaryText: ColorModeSwitch;
+ /**
+ * Typically computed against colors.accent
+ */
+ accentText: ColorModeSwitch;
+ /**
+ * Typically computed against colors.success
+ */
+ successText: ColorModeSwitch;
+ /**
+ * Typically computed against colors.warning
+ */
+ warningText: ColorModeSwitch;
+ /**
+ * Typically computed against colors.danger
+ */
+ dangerText: ColorModeSwitch;
+};
+
+export type _EuiThemeShadeColors = {
+ /**
+ * Used as the background color of primary page content and panels including modals and flyouts.
+ */
+ emptyShade: ColorModeSwitch;
+ /**
+ * Used to lightly shade areas that contain secondary content or contain panel-like components.
+ */
+ lightestShade: ColorModeSwitch;
+ /**
+ * Used for most borders and dividers (horizontal rules).
+ */
+ lightShade: ColorModeSwitch;
+ /**
+ * The middle gray for all themes; this is the base for colors.subdued.
+ */
+ mediumShade: ColorModeSwitch;
+ /**
+ * Slightly subtle graphic color
+ */
+ darkShade: ColorModeSwitch;
+ /**
+ * Used as the text color and the background color for inverted components like tooltips and the control bar.
+ */
+ darkestShade: ColorModeSwitch;
+ /**
+ * The opposide of `emptyShade`
+ */
+ fullShade: ColorModeSwitch;
+};
+
+export type _EuiThemeTextColors = {
+ /**
+ * Computed against colors.darkestShade
+ */
+ text: ColorModeSwitch;
+ /**
+ * Computed against colors.text.
+ */
+ title: ColorModeSwitch;
+ /**
+ * Computed against colors.mediumShade
+ */
+ subdued: ColorModeSwitch;
+ /**
+ * Computed against colors.primaryText
+ */
+ link: ColorModeSwitch;
+};
+
+export type _EuiThemeSpecialColors = {
+ /**
+ * The background color for the whole window (body) and is a computed value of colors.lightestShade.
+ * Provides denominator (background) value for contrast calculations.
+ */
+ body: ColorModeSwitch;
+ /**
+ * Used to highlight text when matching against search strings
+ */
+ highlight: ColorModeSwitch;
+ /**
+ * Computed against colors.darkestShade
+ */
+ disabled: ColorModeSwitch;
+ /**
+ * Computed against colors.disabled
+ */
+ disabledText: ColorModeSwitch;
+ /**
+ * Base color for shadows that gets transparentized
+ */
+ shadow: ColorModeSwitch;
+};
+
+export type _EuiThemeConstantColors = {
+ /**
+ * Purest white
+ */
+ ghost: string;
+ /**
+ * Purest black
+ */
+ ink: string;
+};
+
+export type _EuiThemeColors = _EuiThemeBrandColors &
+ _EuiThemeBrandTextColors &
+ _EuiThemeShadeColors &
+ _EuiThemeSpecialColors &
+ _EuiThemeTextColors;
+
+/*
+ * LIGHT THEME
+ * Only split up in the light theme to access the keys by section in the docs
+ */
+
+export const brand_colors: _EuiThemeBrandColors = {
+ primary: '#006BB4',
+ accent: '#DD0A73',
+ success: '#017D73',
+ warning: '#F5A700',
+ danger: '#BD271E',
+};
+
+export const brand_text_colors: _EuiThemeBrandTextColors = {
+ primaryText: computed(makeHighContrastColor('colors.primary')),
+ accentText: computed(makeHighContrastColor('colors.accent')),
+ successText: computed(makeHighContrastColor('colors.success')),
+ warningText: computed(makeHighContrastColor('colors.warning')),
+ dangerText: computed(makeHighContrastColor('colors.danger')),
+};
+
+export const shade_colors: _EuiThemeShadeColors = {
+ emptyShade: '#FFF',
+ lightestShade: '#F5F7FA',
+ lightShade: '#D3DAE6',
+ mediumShade: '#98A2B3',
+ darkShade: '#69707D',
+ darkestShade: '#343741',
+ fullShade: '#000',
+};
+
+export const special_colors: _EuiThemeSpecialColors = {
+ body: computed(([lightestShade]) => tint(lightestShade, 0.5), [
+ 'colors.lightestShade',
+ ]),
+ highlight: '#FFFCDD',
+ disabled: computed(([darkestShade]) => tint(darkestShade, 0.7), [
+ 'colors.darkestShade',
+ ]),
+ disabledText: computed(makeDisabledContrastColor('colors.disabled')),
+ shadow: computed(({ colors }) =>
+ shade(saturate(colors.mediumShade, 0.25), 0.5)
+ ),
+};
+
+export const text_colors: _EuiThemeTextColors = {
+ text: computed(makeHighContrastColor('colors.darkestShade')),
+ title: computed(
+ ([{ text, body }]) => makeHighContrastColor(shade(text, 0.5))(body),
+ ['colors']
+ ),
+ subdued: computed(makeHighContrastColor('colors.mediumShade')),
+ link: computed(([primaryText]) => primaryText, ['colors.primaryText']),
+};
+
+export const light_colors: _EuiThemeColors = {
+ ...brand_colors,
+ ...shade_colors,
+ ...special_colors,
+ // Need to come after special colors so they can react to `body`
+ ...brand_text_colors,
+ ...text_colors,
+};
+
+/*
+ * DARK THEME
+ */
+
+export const dark_shades: _EuiThemeShadeColors = {
+ emptyShade: '#1D1E24',
+ lightestShade: '#25262E',
+ lightShade: '#343741',
+ mediumShade: '#535966',
+ darkShade: '#98A2B3',
+ darkestShade: '#D4DAE5',
+ fullShade: '#FFF',
+};
+
+export const dark_colors: _EuiThemeColors = {
+ // Brand
+ primary: '#1BA9F5',
+ accent: '#F990C0',
+ success: '#7DE2D1',
+ warning: '#FFCE7A',
+ danger: '#F66',
+ ...dark_shades,
+
+ // Special
+ body: computed(([lightestShade]) => shade(lightestShade, 0.45), [
+ 'colors.lightestShade',
+ ]),
+ highlight: '#2E2D25',
+ disabled: computed(([darkestShade]) => tint(darkestShade, 0.7), [
+ 'colors.darkestShade',
+ ]),
+ disabledText: computed(makeDisabledContrastColor('colors.disabled')),
+ shadow: computed(({ colors }) =>
+ shade(saturate(colors.mediumShade, 0.25), 0.5)
+ ),
+
+ // Need to come after special colors so they can react to `body`
+ ...brand_text_colors,
+
+ // Text
+ text: '#DFE5EF',
+ title: computed(([text]) => text, ['colors.text']),
+ subdued: computed(makeHighContrastColor('colors.mediumShade')),
+ link: computed(([primaryText]) => primaryText, ['colors.primaryText']),
+};
+
+/*
+ * FULL
+ */
+
+export type EuiThemeColors = StrictColorModeSwitch<_EuiThemeColors> &
+ _EuiThemeConstantColors;
+
+export const colors: EuiThemeColors = {
+ ghost: '#FFF',
+ ink: '#000',
+ LIGHT: light_colors,
+ DARK: dark_colors,
+};
diff --git a/src/global_styling/variables/_colors_vis.ts b/src/global_styling/variables/_colors_vis.ts
new file mode 100644
index 00000000000..84c6b24bd51
--- /dev/null
+++ b/src/global_styling/variables/_colors_vis.ts
@@ -0,0 +1,79 @@
+/*
+ * 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.
+ */
+
+// Visualization colors
+
+// Maps allow for easier JSON usage
+// Use map_merge(euiColorVisColors, $yourMap) to change individual colors after importing ths file
+// The `behindText` variant is a direct copy of the hex output by the JS euiPaletteColorBlindBehindText() function
+const euiPaletteColorBlind = {
+ 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',
+ },
+};
+
+export const colorVis = {
+ euiColorVis0: euiPaletteColorBlind.euiColorVis0.graphic,
+ euiColorVis1: euiPaletteColorBlind.euiColorVis1.graphic,
+ euiColorVis2: euiPaletteColorBlind.euiColorVis2.graphic,
+ euiColorVis3: euiPaletteColorBlind.euiColorVis3.graphic,
+ euiColorVis4: euiPaletteColorBlind.euiColorVis4.graphic,
+ euiColorVis5: euiPaletteColorBlind.euiColorVis5.graphic,
+ euiColorVis6: euiPaletteColorBlind.euiColorVis6.graphic,
+ euiColorVis7: euiPaletteColorBlind.euiColorVis7.graphic,
+ euiColorVis8: euiPaletteColorBlind.euiColorVis8.graphic,
+ euiColorVis9: euiPaletteColorBlind.euiColorVis9.graphic,
+
+ euiColorVis0_behindText: euiPaletteColorBlind.euiColorVis0.behindText,
+ euiColorVis1_behindText: euiPaletteColorBlind.euiColorVis1.behindText,
+ euiColorVis2_behindText: euiPaletteColorBlind.euiColorVis2.behindText,
+ euiColorVis3_behindText: euiPaletteColorBlind.euiColorVis3.behindText,
+ euiColorVis4_behindText: euiPaletteColorBlind.euiColorVis4.behindText,
+ euiColorVis5_behindText: euiPaletteColorBlind.euiColorVis5.behindText,
+ euiColorVis6_behindText: euiPaletteColorBlind.euiColorVis6.behindText,
+ euiColorVis7_behindText: euiPaletteColorBlind.euiColorVis7.behindText,
+ euiColorVis8_behindText: euiPaletteColorBlind.euiColorVis8.behindText,
+ euiColorVis9_behindText: euiPaletteColorBlind.euiColorVis9.behindText,
+};
diff --git a/src/global_styling/variables/_size.ts b/src/global_styling/variables/_size.ts
new file mode 100644
index 00000000000..100ca3aa438
--- /dev/null
+++ b/src/global_styling/variables/_size.ts
@@ -0,0 +1,48 @@
+/*
+ * 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 { computed } from '../../services/theme/utils';
+
+// const usingFullTheme = `sizeToPixel(0.25)({ base: 16, [...] })`
+// const usingBaseValue = `sizeToPixel(0.25)(16)`
+export const sizeToPixel = (scale: number = 1) => (
+ themeOrBase: number | { base: number; [key: string]: any }
+) => {
+ const base = typeof themeOrBase === 'object' ? themeOrBase.base : themeOrBase;
+ return `${base * scale}px`;
+};
+
+export type EuiThemeBase = number;
+
+export const base: EuiThemeBase = 16;
+
+export type EuiThemeSize = {
+ xxs: string;
+ xs: string;
+ s: string;
+ m: string;
+ base: string;
+ l: string;
+ xl: string;
+ xxl: string;
+ xxxl: string;
+ xxxxl: string;
+};
+
+export const size: EuiThemeSize = {
+ xxs: computed(sizeToPixel(0.125)),
+ xs: computed(sizeToPixel(0.25)),
+ s: computed(sizeToPixel(0.5)),
+ m: computed(sizeToPixel(0.75)),
+ base: computed(sizeToPixel()),
+ l: computed(sizeToPixel(1.5)),
+ xl: computed(sizeToPixel(2)),
+ xxl: computed(sizeToPixel(2.5)),
+ xxxl: computed(sizeToPixel(3)),
+ xxxxl: computed(sizeToPixel(4)),
+};
diff --git a/src/global_styling/variables/_states.ts b/src/global_styling/variables/_states.ts
new file mode 100644
index 00000000000..0b67436fcfd
--- /dev/null
+++ b/src/global_styling/variables/_states.ts
@@ -0,0 +1,74 @@
+/*
+ * 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 { computed } from '../../services/theme/utils';
+import { ColorModeSwitch } from '../../services/theme/types';
+import { shade, tint, transparentize } from '../../services/color';
+import { CSSProperties } from 'react';
+import { sizeToPixel } from './_size';
+
+export interface _EuiThemeFocusOutline {
+ /**
+ * A single CSS property: value
+ */
+ [key: string]: ColorModeSwitch;
+}
+
+export interface _EuiThemeFocus {
+ /**
+ * Color is used deterministically by the legacy theme, and as fallback for Amsterdam
+ */
+ color: ColorModeSwitch;
+ /**
+ * Used to transprentize any color at certain values
+ */
+ transparency: ColorModeSwitch;
+ /**
+ * Default color plus transparency
+ */
+ backgroundColor: ColorModeSwitch;
+ /**
+ * Width is the thickness of the outline or faux ring
+ */
+ width: CSSProperties['borderWidth'];
+ /**
+ * Larger thickness of the outline for larger components
+ */
+ widthLarge: CSSProperties['borderWidth'];
+ /**
+ * Using `outline` is new for Amsterdam but is set to `none` in legacy theme
+ */
+ outline: _EuiThemeFocusOutline;
+}
+
+export const focus: _EuiThemeFocus = {
+ color: computed(({ colors }) => transparentize(colors.primary, 0.3)),
+ transparency: { LIGHT: 0.1, DARK: 0.3 },
+ backgroundColor: {
+ LIGHT: computed(
+ ([primary, transparency]) => tint(primary, 1 - transparency),
+ ['colors.primary', 'focus.transparency']
+ ),
+ DARK: computed(
+ ([primary, transparency]) => shade(primary, 1 - transparency),
+ ['colors.primary', 'focus.transparency']
+ ),
+ },
+
+ // Sizing
+ widthLarge: computed(sizeToPixel(0.25)),
+ width: computed(sizeToPixel(0.125)),
+
+ // Outline
+ outline: {
+ 'box-shadow': computed(([color, width]) => `0 0 0 ${width} ${color}`, [
+ 'focus.color',
+ 'focus.width',
+ ]),
+ },
+};
diff --git a/src/global_styling/variables/_typography.ts b/src/global_styling/variables/_typography.ts
new file mode 100644
index 00000000000..e344fe8202b
--- /dev/null
+++ b/src/global_styling/variables/_typography.ts
@@ -0,0 +1,105 @@
+/*
+ * 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 { CSSProperties } from 'react';
+import { keysOf } from '../../components/common';
+import { computed } from '../../services/theme/utils';
+
+/*
+ * Font scale
+ */
+
+// Typographic scale -- loosely based on Major Third (1.250)
+export const fontScale = {
+ xxxs: 0.5625,
+ xxs: 0.6875,
+ xs: 0.75,
+ s: 0.875,
+ m: 1,
+ l: 1.25,
+ xl: 1.75,
+ xxl: 2.125,
+};
+
+export const SCALES = keysOf(fontScale);
+export type _EuiThemeFontScale = keyof typeof fontScale;
+
+/*
+ * Font base settings
+ */
+
+export type _EuiThemeFontBase = {
+ /**
+ * The whole font family stack for all parts of the UI.
+ * We encourage only customizing the first font in the stack.
+ */
+ family: string;
+ /**
+ * The font family used for monospace UI elements like EuiCode
+ */
+ familyCode?: string;
+ /**
+ * Controls advanced features OpenType fonts.
+ * https://developer.mozilla.org/en-US/docs/Web/CSS/font-feature-settings
+ */
+ featureSettings?: string;
+ /**
+ * A computed number that is 1/4 of `base`
+ */
+ baseline: number;
+ /**
+ * Establishes the ideal line-height percentage, but it is the `baseline` integer that establishes the final pixel/rem value
+ */
+ lineHeightMultiplier: number;
+};
+
+// Families & base font settings
+export const fontBase: _EuiThemeFontBase = {
+ family: "'Inter UI', BlinkMacSystemFont, Helvetica, Arial, sans-serif",
+ familyCode: "'Roboto Mono', Menlo, Courier, monospace",
+
+ // Careful using ligatures. Code editors like ACE will often error because of width calculations
+ featureSettings: "'calt' 1, 'kern' 1, 'liga' 1",
+
+ baseline: computed(([base]) => base / 4, ['base']),
+ lineHeightMultiplier: 1.5,
+};
+
+/*
+ * Font weights
+ */
+export interface _EuiThemeFontWeight {
+ light: CSSProperties['fontWeight'];
+ regular: CSSProperties['fontWeight'];
+ medium: CSSProperties['fontWeight'];
+ semiBold: CSSProperties['fontWeight'];
+ bold: CSSProperties['fontWeight'];
+}
+
+export const fontWeight: _EuiThemeFontWeight = {
+ light: 300,
+ regular: 400,
+ medium: 500,
+ semiBold: 600,
+ bold: 700,
+};
+
+/*
+ * Font
+ */
+
+export type EuiThemeFont = _EuiThemeFontBase & {
+ scale: { [key in _EuiThemeFontScale]: number };
+ weight: _EuiThemeFontWeight;
+};
+
+export const font: EuiThemeFont = {
+ ...fontBase,
+ scale: fontScale,
+ weight: fontWeight,
+};
diff --git a/src/global_styling/variables/_z_index.ts b/src/global_styling/variables/_z_index.ts
new file mode 100644
index 00000000000..2661f1c47e7
--- /dev/null
+++ b/src/global_styling/variables/_z_index.ts
@@ -0,0 +1,59 @@
+/*
+ * 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 { computed } from '../../services/theme/utils';
+
+// Z-Index
+
+// Remember that z-index is relative to parent and based on the stacking context.
+// z-indexes only compete against other z-indexes when they exist as children of
+// that shared parent.
+
+// That means a popover with a settings of 2, will still show above a modal
+// with a setting of 100, if it is within that modal and not besides it.
+
+// Generally that means it's a good idea to consider things added to this file
+// as competitive only as siblings.
+
+// https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Positioning/Understanding_z_index/The_stacking_context
+
+export interface EuiThemeZIndex {
+ level0: number;
+ level1: number;
+ level2: number;
+ level3: number;
+ level4: number;
+ level5: number;
+ level6: number;
+ level7: number;
+ level8: number;
+ level9: number;
+}
+
+export const zIndex: EuiThemeZIndex = {
+ level0: 0,
+ level1: 1000,
+ level2: 2000,
+ level3: 3000,
+ level4: 4000,
+ level5: 5000,
+ level6: 6000,
+ level7: 7000,
+ level8: 8000,
+ level9: 9000,
+
+ // --> These should be declared at the component level
+ // content: computed(({ zIndex }) => zIndex.level0),
+ // header: computed(({ zIndex }) => zIndex.level1),
+ // contentMenu: computed(({ zIndex }) => zIndex.level2),
+ // flyout: computed(({ zIndex }) => zIndex.level3),
+ // navigation: computed(({ zIndex }) => zIndex.level4),
+ // mask: computed(({ zIndex }) => zIndex.level6),
+ // modal: computed(({ zIndex }) => zIndex.level8),
+ // toastList: computed(({ zIndex }) => zIndex.level9),
+};
diff --git a/src/global_styling/variables/text.ts b/src/global_styling/variables/text.ts
new file mode 100644
index 00000000000..9486350dabb
--- /dev/null
+++ b/src/global_styling/variables/text.ts
@@ -0,0 +1,36 @@
+/*
+ * 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 { CSSProperties } from 'react';
+import { computed } from '../../services/theme/utils';
+import {
+ fontSizeFromScale,
+ lineHeightFromBaseline,
+} from '../functions/_typography';
+import { _EuiThemeFontScale, SCALES } from './_typography';
+
+export type EuiThemeFontSize = {
+ [mapType in _EuiThemeFontScale]: {
+ fontSize: CSSProperties['fontSize'];
+ lineHeight: CSSProperties['lineHeight'];
+ };
+};
+
+export const fontSize: EuiThemeFontSize = SCALES.reduce((acc, elem) => {
+ acc[elem] = {
+ fontSize: computed(([base, scale]) => fontSizeFromScale(base, scale), [
+ 'base',
+ `font.scale.${elem}`,
+ ]),
+ lineHeight: computed(
+ ([base, font]) => lineHeightFromBaseline(base, font, font.scale[elem]),
+ ['base', 'font']
+ ),
+ };
+ return acc;
+}, {} as EuiThemeFontSize);
diff --git a/src/global_styling/variables/title.ts b/src/global_styling/variables/title.ts
new file mode 100644
index 00000000000..35605012a19
--- /dev/null
+++ b/src/global_styling/variables/title.ts
@@ -0,0 +1,76 @@
+/*
+ * 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 { CSSProperties } from 'react';
+import { computed } from '../../services/theme/utils';
+import { _EuiThemeFontScale, SCALES } from './_typography';
+
+export type EuiThemeTitle = {
+ [size in _EuiThemeFontScale]: {
+ color: string;
+ fontSize: string;
+ fontWeight: CSSProperties['fontWeight'];
+ letterSpacing?: string;
+ lineHeight: string;
+ };
+};
+
+const titlesPartial: {
+ [size in _EuiThemeFontScale]: {
+ fontWeight: string;
+ letterSpacing?: string;
+ };
+} = {
+ xxxs: {
+ fontWeight: 'bold',
+ letterSpacing: undefined,
+ },
+ xxs: {
+ fontWeight: 'bold',
+ letterSpacing: undefined,
+ },
+ xs: {
+ fontWeight: 'bold',
+ letterSpacing: undefined,
+ },
+ s: {
+ fontWeight: 'bold',
+ letterSpacing: undefined,
+ },
+ m: {
+ fontWeight: 'semiBold',
+ letterSpacing: '-.02em',
+ },
+ l: {
+ fontWeight: 'medium',
+ letterSpacing: '-.025em',
+ },
+ xl: {
+ fontWeight: 'light',
+ letterSpacing: '-.04em',
+ },
+ xxl: {
+ fontWeight: 'light',
+ letterSpacing: '-.03em',
+ },
+};
+
+export const title: EuiThemeTitle = SCALES.reduce((acc, size) => {
+ acc[size] = {
+ fontSize: computed(([{ fontSize }]) => fontSize, [`font.size.${size}`]),
+ lineHeight: computed(([{ lineHeight }]) => lineHeight, [
+ `font.size.${size}`,
+ ]),
+ color: computed(([color]) => color, ['colors.title']),
+ fontWeight: computed(([fontWeight]) => fontWeight, [
+ `font.weight.${titlesPartial[size].fontWeight}`,
+ ]),
+ letterSpacing: titlesPartial[size].letterSpacing,
+ };
+ return acc;
+}, {} as EuiThemeTitle);
diff --git a/src/services/color/index.ts b/src/services/color/index.ts
index ae5beae8b03..03a6bdf33b2 100644
--- a/src/services/color/index.ts
+++ b/src/services/color/index.ts
@@ -39,3 +39,4 @@ export {
} from './eui_palettes';
export { rgbDef, HSV, RGB } from './color_types';
export { getSteppedGradient } from './stepped_gradient';
+export * from './manipulation';
diff --git a/src/services/color/manipulation.ts b/src/services/color/manipulation.ts
new file mode 100644
index 00000000000..07533aeb4c2
--- /dev/null
+++ b/src/services/color/manipulation.ts
@@ -0,0 +1,74 @@
+/*
+ * 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.
+ */
+
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import chroma from 'chroma-js';
+
+/**
+ * Makes a color more transparent.
+ * @param color - Color to manipulate
+ * @param alpha - alpha channel value. From 0-1.
+ */
+export const transparentize = (color: string, alpha: number) =>
+ chroma(color).alpha(alpha).css();
+
+/**
+ * Mixes a provided color with white.
+ * @param color - Color to mix with white
+ * @param ratio - Mix weight. From 0-1. Larger value indicates more white.
+ */
+export const tint = (color: string, ratio: number) =>
+ chroma.mix(color, '#fff', ratio, 'rgb').hex();
+
+/**
+ * Mixes a provided color with black.
+ * @param color - Color to mix with black
+ * @param ratio - Mix weight. From 0-1. Larger value indicates more black.
+ */
+export const shade = (color: string, ratio: number) =>
+ chroma.mix(color, '#000', ratio, 'rgb').hex();
+
+/**
+ * Increases the saturation of a color by manipulating the hsl saturation.
+ * @param color - Color to manipulate
+ * @param amount - Amount to change in absolute terms. 0-1.
+ */
+export const saturate = (color: string, amount: number) =>
+ chroma(color).set('hsl.s', `+${amount}`).css();
+
+/**
+ * Decreases the saturation of a color by manipulating the hsl saturation.
+ * @param color - Color to manipulate
+ * @param amount - Amount to change in absolute terms. 0-1.
+ */
+export const desaturate = (color: string, amount: number) =>
+ chroma(color).set('hsl.s', `-${amount}`).css();
+
+/**
+ * Returns the lightness value of a color. 0-100
+ * @param color
+ */
+export const lightness = (color: string) => chroma(color).get('hsl.l') * 100;
diff --git a/src/services/color_picker/color_picker.ts b/src/services/color_picker/color_picker.ts
index cc922e66967..9610956222b 100644
--- a/src/services/color_picker/color_picker.ts
+++ b/src/services/color_picker/color_picker.ts
@@ -46,10 +46,16 @@ export const useColorStopsState = (
return [colorStops, updateColorStops, addColor];
};
-export const useColorPickerState = (initialColor = '') => {
+export type EuiSetColorMethod = (
+ text: string,
+ { hex, isValid }: { hex: string; isValid: boolean }
+) => void;
+export const useColorPickerState = (
+ initialColor = ''
+): [color: string, setColor: EuiSetColorMethod, errors: string[] | null] => {
const [color, setColorValue] = useState(initialColor);
const [isValid, setIsValid] = useState(true);
- const setColor = (text: string, { isValid }: { isValid: boolean }) => {
+ const setColor: EuiSetColorMethod = (text, { isValid }) => {
setColorValue(text);
setIsValid(isValid);
};
diff --git a/src/services/color_picker/index.ts b/src/services/color_picker/index.ts
index 568b05f056f..cc292ce895b 100644
--- a/src/services/color_picker/index.ts
+++ b/src/services/color_picker/index.ts
@@ -6,4 +6,8 @@
* Side Public License, v 1.
*/
-export { useColorPickerState, useColorStopsState } from './color_picker';
+export {
+ useColorPickerState,
+ useColorStopsState,
+ EuiSetColorMethod,
+} from './color_picker';
diff --git a/src/services/index.ts b/src/services/index.ts
index 8c005b840bf..3c9f32efcbf 100644
--- a/src/services/index.ts
+++ b/src/services/index.ts
@@ -62,9 +62,19 @@ export {
euiPaletteGray,
HSV,
getSteppedGradient,
+ transparentize,
+ tint,
+ shade,
+ saturate,
+ desaturate,
+ lightness,
} from './color';
-export { useColorPickerState, useColorStopsState } from './color_picker';
+export {
+ useColorPickerState,
+ useColorStopsState,
+ EuiSetColorMethod,
+} from './color_picker';
export * from './console';
@@ -138,11 +148,7 @@ export {
mergeDeep,
setOn,
Computed,
- euiThemeDefault,
- EuiThemeDefault,
- EuiThemeAmsterdam,
- euiThemeAmsterdam,
- EuiThemeColor,
+ ComputedThemeShape,
EuiThemeColorMode,
EuiThemeComputed,
EuiThemeModifications,
diff --git a/src/services/theme/README.md b/src/services/theme/README.md
index 59aea98c0d0..6dcc3a975d5 100644
--- a/src/services/theme/README.md
+++ b/src/services/theme/README.md
@@ -30,8 +30,16 @@ These properties specify that the value depends upon some other value in the the
```js
computed(
+ ([size]) => size * 2 // predicate. What to do with the dependency values,
['sizes.euiSize'], // dependency array, referencing other properties in the theme object
- ([size]) => size * 2 // predicate. What to do with the dependency values
+)
+```
+
+The dependency array is optional. Omitting the array gives access to the computed theme.
+
+```js
+computed(
+ (theme) => theme.sizes.euiSize * 2
)
```
diff --git a/src/services/theme/context.ts b/src/services/theme/context.ts
index 568e28f8d55..50fade58981 100644
--- a/src/services/theme/context.ts
+++ b/src/services/theme/context.ts
@@ -13,7 +13,7 @@ import {
EuiThemeModifications,
EuiThemeComputed,
} from './types';
-import { EuiThemeDefault } from './theme';
+import { EuiThemeDefault } from '../../themes/eui/theme';
import { DEFAULT_COLOR_MODE, getComputed } from './utils';
export const EuiSystemContext = createContext(EuiThemeDefault);
diff --git a/src/services/theme/index.ts b/src/services/theme/index.ts
index 660f4a4d968..219ad81a4db 100644
--- a/src/services/theme/index.ts
+++ b/src/services/theme/index.ts
@@ -26,16 +26,10 @@ export {
Computed,
} from './utils';
export {
- EuiThemeColor,
+ ComputedThemeShape,
EuiThemeColorMode,
EuiThemeComputed,
EuiThemeModifications,
EuiThemeShape,
EuiThemeSystem,
} from './types';
-export {
- EuiThemeDefault,
- euiThemeDefault,
- EuiThemeAmsterdam,
- euiThemeAmsterdam,
-} from './theme';
diff --git a/src/services/theme/theme.ts b/src/services/theme/theme.ts
deleted file mode 100644
index 0b8c8d50465..00000000000
--- a/src/services/theme/theme.ts
+++ /dev/null
@@ -1,487 +0,0 @@
-/*
- * 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 chroma from 'chroma-js';
-import { buildTheme, computed, COLOR_MODE_KEY } from './utils';
-
-export const tint = (color: string, ratio: number) =>
- chroma.mix(color, '#fff', ratio).hex();
-export const shade = (color: string, ratio: number) =>
- chroma.mix(color, '#000', ratio).hex();
-// TODO
-const makeHighContrastColor = (color: string) => color;
-// TODO
-const makeDisabledContrastColor = (color: string) => color;
-// TODO
-const transparentize = (color: string, ratio: number) =>
- ratio ? color : color;
-
-const poles = {
- euiColorGhost: '#FFF',
- euiColorInk: '#000',
-};
-
-const graysLight = {
- euiColorEmptyShade: '#FFF',
- euiColorLightestShade: '#F5F7FA',
- euiColorLightShade: '#D3DAE6',
- euiColorMediumShade: '#98A2B3',
- euiColorDarkShade: '#69707D',
- euiColorDarkestShade: '#343741',
- euiColorFullShade: '#000',
-};
-
-const textVariants = {
- euiColorPrimaryText: computed(
- ['colors.euiColorPrimary'],
- ([euiColorPrimary]) => makeHighContrastColor(euiColorPrimary)
- ),
- euiColorSecondaryText: computed(
- ['colors.euiColorSecondary'],
- ([euiColorSecondary]) => makeHighContrastColor(euiColorSecondary)
- ),
- euiColorAccentText: computed(['colors.euiColorAccent'], ([euiColorAccent]) =>
- makeHighContrastColor(euiColorAccent)
- ),
- euiColorWarningText: computed(
- ['colors.euiColorWarning'],
- ([euiColorWarning]) => makeHighContrastColor(euiColorWarning)
- ),
- euiColorDangerText: computed(['colors.euiColorDanger'], ([euiColorDanger]) =>
- makeHighContrastColor(euiColorDanger)
- ),
- euiColorDisabledText: computed(
- ['colors.euiColorDisabled'],
- ([euiColorDisabled]) => makeDisabledContrastColor(euiColorDisabled)
- ),
- euiColorSuccessText: computed(
- ['colors.euiColorSecondaryText'],
- ([euiColorSecondaryText]) => euiColorSecondaryText
- ),
- euiLinkColor: computed(
- ['colors.euiColorPrimaryText'],
- ([euiColorPrimaryText]) => euiColorPrimaryText
- ),
-};
-
-/* DEFAULT THEME */
-
-export const light = {
- euiColorPrimary: '#006BB4',
- euiColorSecondary: '#017D73',
- euiColorAccent: '#DD0A73',
-
- // These colors stay the same no matter the theme
- ...poles,
-
- // Status
- euiColorSuccess: computed(
- ['colors.euiColorSecondary'],
- ([euiColorSecondary]) => euiColorSecondary
- ),
- euiColorDanger: '#BD271E',
- euiColorWarning: '#F5A700',
-
- // Grays
- ...graysLight,
-
- // Backgrounds
- euiPageBackgroundColor: computed(
- ['colors.euiColorLightestShade'],
- ([euiColorLightestShade]) => tint(euiColorLightestShade, 0.5)
- ),
- euiColorHighlight: '#FFFCDD',
-
- // Every color below must be based mathematically on the set above and in a particular order.
- euiTextColor: computed(
- ['colors.euiColorDarkestShade'],
- ([euiColorDarkestShade]) => euiColorDarkestShade
- ),
- euiTitleColor: computed(['colors.euiTextColor'], ([euiTextColor]) =>
- shade(euiTextColor, 0.5)
- ),
- euiTextSubduedColor: computed(
- ['colors.euiColorMediumShade'],
- ([euiColorMediumShade]) => makeHighContrastColor(euiColorMediumShade)
- ),
- euiColorDisabled: computed(['colors.euiTextColor'], ([euiTextColor]) =>
- tint(euiTextColor, 0.7)
- ),
-
- // Contrasty text variants
- ...textVariants,
-
- // State
- euiFocusTransparency: 0.1,
- euiFocusBackgroundColor: computed(
- ['colors.euiColorPrimary', 'colors.euiFocusTransparency'],
- ([euiColorPrimary, euiFocusTransparency]) =>
- tint(euiColorPrimary, 1 - euiFocusTransparency)
- ),
-};
-
-const graysDark = {
- euiColorEmptyShade: '#1D1E24',
- euiColorLightestShade: '#25262E',
- euiColorLightShade: '#343741',
- euiColorMediumShade: '#535966',
- euiColorDarkShade: '#98A2B3',
- euiColorDarkestShade: '#D4DAE5',
- euiColorFullShade: '#FFF',
-};
-
-export const dark = {
- // These colors stay the same no matter the theme
- ...poles,
-
- // Core
- euiColorPrimary: '#1BA9F5',
- euiColorSecondary: '#7DE2D1',
- euiColorAccent: '#F990C0',
-
- // Status
- euiColorSuccess: computed(
- ['colors.euiColorSecondary'],
- ([euiColorSecondary]) => euiColorSecondary
- ),
- euiColorWarning: '#FFCE7A',
- euiColorDanger: '#F66',
-
- // Grays
- ...graysDark,
-
- // Backgrounds
- euiPageBackgroundColor: computed(
- ['colors.euiColorLightestShade'],
- ([euiColorLightestShade]) => shade(euiColorLightestShade, 0.3)
- ),
- euiColorHighlight: '#2E2D25',
-
- // Variations from core
- euiTextColor: '#DFE5EF',
- euiTitleColor: computed(
- ['colors.euiTextColor'],
- ([euiTextColor]) => euiTextColor
- ),
- euiTextSubduedColor: computed(
- ['colors.euiColorMediumShade'],
- ([euiColorMediumShade]) => makeHighContrastColor(euiColorMediumShade)
- ),
- euiColorDisabled: computed(['colors.euiTextColor'], ([euiTextColor]) =>
- shade(euiTextColor, 0.7)
- ),
-
- // Contrasty text variants
- ...textVariants,
-
- // State
- euiFocusTransparency: 0.3,
- euiFocusBackgroundColor: computed(
- ['colors.euiColorPrimary', 'colors.euiFocusTransparency'],
- ([euiColorPrimary, euiFocusTransparency]) =>
- shade(euiColorPrimary, 1 - euiFocusTransparency)
- ),
-};
-
-// Visualization colors
-
-// Maps allow for easier JSON usage
-// Use map_merge(euiColorVisColors, $yourMap) to change individual colors after importing ths file
-// The `behindText` variant is a direct copy of the hex output by the JS euiPaletteColorBlindBehindText() function
-const euiPaletteColorBlind = {
- 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',
- },
-};
-
-const colorVis = {
- euiColorVis0: euiPaletteColorBlind.euiColorVis0.graphic,
- euiColorVis1: euiPaletteColorBlind.euiColorVis1.graphic,
- euiColorVis2: euiPaletteColorBlind.euiColorVis2.graphic,
- euiColorVis3: euiPaletteColorBlind.euiColorVis3.graphic,
- euiColorVis4: euiPaletteColorBlind.euiColorVis4.graphic,
- euiColorVis5: euiPaletteColorBlind.euiColorVis5.graphic,
- euiColorVis6: euiPaletteColorBlind.euiColorVis6.graphic,
- euiColorVis7: euiPaletteColorBlind.euiColorVis7.graphic,
- euiColorVis8: euiPaletteColorBlind.euiColorVis8.graphic,
- euiColorVis9: euiPaletteColorBlind.euiColorVis9.graphic,
-
- euiColorVis0_behindText: euiPaletteColorBlind.euiColorVis0.behindText,
- euiColorVis1_behindText: euiPaletteColorBlind.euiColorVis1.behindText,
- euiColorVis2_behindText: euiPaletteColorBlind.euiColorVis2.behindText,
- euiColorVis3_behindText: euiPaletteColorBlind.euiColorVis3.behindText,
- euiColorVis4_behindText: euiPaletteColorBlind.euiColorVis4.behindText,
- euiColorVis5_behindText: euiPaletteColorBlind.euiColorVis5.behindText,
- euiColorVis6_behindText: euiPaletteColorBlind.euiColorVis6.behindText,
- euiColorVis7_behindText: euiPaletteColorBlind.euiColorVis7.behindText,
- euiColorVis8_behindText: euiPaletteColorBlind.euiColorVis8.behindText,
- euiColorVis9_behindText: euiPaletteColorBlind.euiColorVis9.behindText,
-};
-
-const base = 16;
-
-const sizes = {
- euiSize: computed(['base'], ([base]) => `${base}px`),
- euiSizeXS: computed(['base'], ([base]) => `${base * 0.25}px`),
- euiSizeS: computed(['base'], ([base]) => `${base * 0.5}px`),
- euiSizeM: computed(['base'], ([base]) => `${base * 0.75}px`),
- euiSizeL: computed(['base'], ([base]) => `${base * 1.5}px`),
- euiSizeXL: computed(['base'], ([base]) => `${base * 2}px`),
- euiSizeXXL: computed(['base'], ([base]) => `${base * 2.5}px`),
-
- euiButtonMinWidth: computed(['base'], ([base]) => `${base * 7}px`),
-
- euiScrollBar: computed(['sizes.euiSize'], ([euiSize]) => euiSize),
- euiScrollBarCorner: computed(
- ['sizes.euiSizeS'],
- ([euiSizeS]) => `calc(${euiSizeS} * 0.75)`
- ),
-};
-
-const borderRadius = {
- euiBorderRadius: '4px',
- euiBorderRadiusSmall: computed(
- ['borders.euiBorderRadius'],
- ([euiBorderRadius]) => `calc(${euiBorderRadius} * 0.5)`
- ),
-};
-
-const borders = {
- euiBorderWidthThin: '1px',
- euiBorderWidthThick: '2px',
-
- euiBorderColor: computed(
- ['colors.euiColorLightShade'],
- ([euiColorLightShade]) => euiColorLightShade
- ),
-
- euiBorderThick: computed(
- ['borders.euiBorderWidthThick', 'borders.euiBorderColor'],
- ([euiBorderWidthThick, euiBorderColor]) =>
- `${euiBorderWidthThick} solid ${euiBorderColor}`
- ),
- euiBorderThin: computed(
- ['borders.euiBorderWidthThin', 'borders.euiBorderColor'],
- ([euiBorderWidthThin, euiBorderColor]) =>
- `${euiBorderWidthThin} solid ${euiBorderColor}`
- ),
- euiBorderEditable: computed(
- ['borders.euiBorderWidthThick', 'borders.euiBorderColor'],
- ([euiBorderWidthThick, euiBorderColor]) =>
- `${euiBorderWidthThick} dotted ${euiBorderColor}`
- ),
-};
-
-export const euiThemeDefault = {
- [COLOR_MODE_KEY]: {
- light,
- dark,
- },
- colorVis,
- base,
- sizes,
- borders: {
- ...borderRadius,
- ...borders,
- },
- buttons: {
- [COLOR_MODE_KEY]: {
- light: {
- custom: computed(
- ['colors.euiColorPrimary'],
- ([primary]) => primary /*'#000'*/
- ),
- },
- dark: { custom: '#fff' },
- },
- },
-};
-
-export const EuiThemeDefault = buildTheme(euiThemeDefault, 'EUI_THEME_DEFAULT');
-
-/* AMSTERDAM THEME */
-
-export const amsterdam_light = {
- euiColorPrimary: '#07C',
- euiColorSecondary: '#00BFB3',
- euiColorAccent: '#F04E98',
-
- // These colors stay the same no matter the theme
- ...poles,
-
- // Status
- euiColorSuccess: computed(
- ['colors.euiColorSecondary'],
- ([euiColorSecondary]) => euiColorSecondary
- ),
- euiColorDanger: '#BD271E',
- euiColorWarning: '#FEC514',
- euiColorDisabled: '#ABB4C4',
-
- // Grays
- ...graysLight,
-
- // Backgrounds
- euiPageBackgroundColor: computed(
- ['colors.euiColorLightestShade'],
- ([euiColorLightestShade]) => tint(euiColorLightestShade, 0.5)
- ),
- euiColorHighlight: computed(['colors.euiColorWarning'], ([euiColorWarning]) =>
- tint(euiColorWarning, 0.9)
- ),
-
- // Every color below must be based mathematically on the set above and in a particular order.
- euiTextColor: computed(
- ['colors.euiColorDarkestShade'],
- ([euiColorDarkestShade]) => euiColorDarkestShade
- ),
- euiTitleColor: computed(['colors.euiTextColor'], ([euiTextColor]) =>
- shade(euiTextColor, 0.5)
- ),
- euiTextSubduedColor: computed(
- ['colors.euiColorDarkShade'],
- ([euiColorDarkShade]) => euiColorDarkShade
- ),
-
- // Contrasty text variants
- ...textVariants,
-
- // State
- euiFocusTransparency: 0.9,
- euiFocusBackgroundColor: computed(
- ['colors.euiColorPrimary', 'colors.euiFocusTransparency'],
- ([euiColorPrimary, euiFocusTransparency]) =>
- transparentize(euiColorPrimary, euiFocusTransparency)
- ),
-};
-
-export const amsterdam_dark = {
- // These colors stay the same no matter the theme
- ...poles,
-
- // Core
- euiColorPrimary: '#36A2EF',
- euiColorSecondary: '#7DDED8',
- euiColorAccent: '#F68FBE',
-
- // Status
- euiColorSuccess: computed(
- ['colors.euiColorSecondary'],
- ([euiColorSecondary]) => euiColorSecondary
- ),
- euiColorWarning: '#F3D371',
- euiColorDanger: '#F86B63',
- euiColorDisabled: '#515761',
-
- // Grays
- ...graysDark,
-
- // Backgrounds
- euiPageBackgroundColor: computed(
- ['colors.euiColorLightestShade'],
- ([euiColorLightestShade]) => shade(euiColorLightestShade, 0.3)
- ),
- euiColorHighlight: '#2E2D25',
-
- // Variations from core
- euiTextColor: '#DFE5EF',
- euiTitleColor: computed(
- ['colors.euiTextColor'],
- ([euiTextColor]) => euiTextColor
- ),
- euiTextSubduedColor: computed(
- ['colors.euiColorMediumShade'],
- ([euiColorMediumShade]) => makeHighContrastColor(euiColorMediumShade)
- ),
-
- // Contrasty text variants
- ...textVariants,
-
- // State
- euiFocusTransparency: 0.7,
- euiFocusBackgroundColor: computed(
- ['colors.euiColorPrimary', 'colors.euiFocusTransparency'],
- ([euiColorPrimary, euiFocusTransparency]) =>
- transparentize(euiColorPrimary, euiFocusTransparency)
- ),
-};
-
-const amsterdam_borderRadius = {
- euiBorderRadius: computed(
- ['sizes.euiSizeS'],
- ([euiSizeS]) => `calc(${euiSizeS} * 0.75)`
- ),
- euiBorderRadiusSmall: computed(
- ['sizes.euiSizeS'],
- ([euiSizeS]) => `calc(${euiSizeS} * 0.5)`
- ),
-};
-
-export const euiThemeAmsterdam = {
- [COLOR_MODE_KEY]: {
- light: amsterdam_light,
- dark: amsterdam_dark,
- },
- colorVis,
- base,
- sizes,
- borders: {
- ...amsterdam_borderRadius,
- ...borders,
- },
- buttons: {
- [COLOR_MODE_KEY]: {
- light: {
- custom: '#000',
- },
- dark: { custom: '#fff' },
- },
- },
-};
-
-export const EuiThemeAmsterdam = buildTheme(
- euiThemeAmsterdam,
- 'EUI_THEME_AMSTERDAM'
-);
diff --git a/src/services/theme/types.ts b/src/services/theme/types.ts
index bd5b079a6c0..76dd5a3dc03 100644
--- a/src/services/theme/types.ts
+++ b/src/services/theme/types.ts
@@ -6,18 +6,51 @@
* Side Public License, v 1.
*/
-import { RecursiveOmit, RecursivePartial } from '../../components/common';
-import { euiThemeDefault } from './theme';
+import { RecursivePartial, ValueOf } from '../../components/common';
+import { EuiThemeAnimation } from '../../global_styling/variables/_animations';
+import { EuiThemeBreakpoint } from '../../global_styling/variables/_breakpoint';
+import { EuiThemeBorder } from '../../global_styling/variables/_borders';
+import { EuiThemeColors } from '../../global_styling/variables/_colors';
+import {
+ EuiThemeBase,
+ EuiThemeSize,
+} from '../../global_styling/variables/_size';
+import { EuiThemeFont } from '../../global_styling/variables/_typography';
+import { _EuiThemeFocus } from '../../global_styling/variables/_states';
-type EuiThemeColorModeInverse = 'inverse';
-type EuiThemeColorModeStandard = 'light' | 'dark';
+export const COLOR_MODES_STANDARD = {
+ light: 'LIGHT',
+ dark: 'DARK',
+} as const;
+export const COLOR_MODES_INVERSE = 'INVERSE' as const;
+
+type EuiThemeColorModeInverse = typeof COLOR_MODES_INVERSE;
+type EuiThemeColorModeStandard = ValueOf;
export type EuiThemeColorMode =
| string
| EuiThemeColorModeStandard
| EuiThemeColorModeInverse;
-export type EuiThemeShape = typeof euiThemeDefault;
-export type EuiThemeColor = EuiThemeShape['colors']['light'];
+export type ColorModeSwitch =
+ | {
+ [key in EuiThemeColorModeStandard]: T;
+ }
+ | T;
+
+export type StrictColorModeSwitch = {
+ [key in EuiThemeColorModeStandard]: T;
+};
+
+export type EuiThemeShape = {
+ colors: EuiThemeColors;
+ base: EuiThemeBase;
+ size: EuiThemeSize;
+ font: EuiThemeFont;
+ border: EuiThemeBorder;
+ focus: _EuiThemeFocus;
+ animation: EuiThemeAnimation;
+ breakpoint: EuiThemeBreakpoint;
+};
export type EuiThemeSystem = {
root: EuiThemeShape & T;
@@ -27,14 +60,28 @@ export type EuiThemeSystem = {
export type EuiThemeModifications = RecursivePartial;
-type Colorless = RecursiveOmit;
-// I don't like this.
-// Requires manually maintaining sections (e.g., `buttons`) containing colorMode options.
-// Also cannot account for extended theme sections (`T`) that use colorMode options.
-export type EuiThemeComputed = Colorless & {
+export type ComputedThemeShape<
+ T,
+ P = string | number | bigint | boolean | null | undefined
+> = T extends P | ColorModeSwitch
+ ? T extends ColorModeSwitch
+ ? X extends P
+ ? X
+ : {
+ [K in keyof (X &
+ Exclude<
+ T,
+ keyof X | keyof StrictColorModeSwitch
+ >)]: ComputedThemeShape<
+ (X & Exclude)[K],
+ P
+ >;
+ }
+ : T
+ : {
+ [K in keyof T]: ComputedThemeShape;
+ };
+
+export type EuiThemeComputed = ComputedThemeShape & {
themeName: string;
- colors: EuiThemeColor;
- buttons: Colorless & {
- colors: EuiThemeShape['buttons']['colors']['light'];
- };
-} & T;
+};
diff --git a/src/services/theme/utils.test.ts b/src/services/theme/utils.test.ts
index f408d359fc7..52bc023cebd 100644
--- a/src/services/theme/utils.test.ts
+++ b/src/services/theme/utils.test.ts
@@ -16,36 +16,29 @@ import {
getComputed,
buildTheme,
mergeDeep,
- currentColorModeOnly,
} from './utils';
describe('isInverseColorMode', () => {
it("true only if 'inverse'", () => {
- expect(isInverseColorMode('light')).toBe(false);
- expect(isInverseColorMode('dark')).toBe(false);
+ expect(isInverseColorMode('LIGHT')).toBe(false);
+ expect(isInverseColorMode('DARK')).toBe(false);
expect(isInverseColorMode('custom')).toBe(false);
expect(isInverseColorMode()).toBe(false);
- expect(isInverseColorMode('inverse')).toBe(true);
+ expect(isInverseColorMode('INVERSE')).toBe(true);
});
});
describe('getColorMode', () => {
- it("defaults to 'light'", () => {
- expect(getColorMode()).toEqual('light');
+ it("defaults to 'LIGHT'", () => {
+ expect(getColorMode()).toEqual('LIGHT');
});
it('uses `parentMode` as fallback', () => {
- expect(getColorMode(undefined, 'dark')).toEqual('dark');
+ expect(getColorMode(undefined, 'DARK')).toEqual('DARK');
});
- it("understands 'inverse'", () => {
- expect(getColorMode('inverse', 'dark')).toEqual('light');
- expect(getColorMode('inverse', 'light')).toEqual('dark');
- expect(getColorMode('inverse')).toEqual('light');
- });
- it('respects custom modes', () => {
- expect(getColorMode('custom')).toEqual('custom');
- expect(getColorMode('custom', 'light')).toEqual('custom');
- expect(getColorMode(undefined, 'custom')).toEqual('custom');
- expect(getColorMode('light', 'custom')).toEqual('light');
+ it("understands 'INVERSE'", () => {
+ expect(getColorMode('INVERSE', 'DARK')).toEqual('LIGHT');
+ expect(getColorMode('INVERSE', 'LIGHT')).toEqual('DARK');
+ expect(getColorMode('INVERSE')).toEqual('LIGHT');
});
});
@@ -63,9 +56,8 @@ describe('getOn', () => {
},
},
colors: {
- light: { primary: '#000' },
- dark: { primary: '#FFF' },
- custom: { primary: '#333' },
+ LIGHT: { primary: '#000' },
+ DARK: { primary: '#FFF' },
},
};
it('gets values at the given path', () => {
@@ -80,10 +72,9 @@ describe('getOn', () => {
expect(getOn(obj, 'other.thing.number', '')).toEqual(0);
expect(getOn(obj, 'other.thing.func', '')).toBeInstanceOf(Function);
});
- it('does can shortcut color modes', () => {
- expect(getOn(obj, 'colors.primary', 'light')).toEqual('#000');
- expect(getOn(obj, 'colors.primary', 'dark')).toEqual('#FFF');
- expect(getOn(obj, 'colors.primary', 'custom')).toEqual('#333');
+ it('can shortcut color modes', () => {
+ expect(getOn(obj, 'colors.primary', 'LIGHT')).toEqual('#000');
+ expect(getOn(obj, 'colors.primary', 'DARK')).toEqual('#FFF');
});
it('will not error', () => {
expect(getOn(obj, 'nope', '')).toBe(undefined);
@@ -131,24 +122,34 @@ describe('setOn', () => {
});
describe('computed', () => {
- it('should transform to Computed', () => {
- const output = computed(['path.to'], ([path]) => path);
+ it('should transform to Computed with dependencies array', () => {
+ const output = computed(([path]) => path, ['path.to']);
expect(output).toBeInstanceOf(Computed);
expect(output.computer).toBeInstanceOf(Function);
expect(output.dependencies).toEqual(['path.to']);
});
+ it('should transform to Computed with single dependency', () => {
+ const output = computed((path) => path, 'path.to');
+ expect(output).toBeInstanceOf(Computed);
+ expect(output.computer).toBeInstanceOf(Function);
+ expect(output.dependencies).toEqual('path.to');
+ });
+ it('should transform to Computed without dependencies array', () => {
+ const output = computed((path) => path);
+ expect(output).toBeInstanceOf(Computed);
+ });
});
const theme = buildTheme(
{
colors: {
- light: {
+ LIGHT: {
primary: '#000',
- secondary: computed(['colors.primary'], ([primary]) => `${primary}000`),
+ secondary: computed(([primary]) => `${primary}000`, ['colors.primary']),
},
- dark: {
+ DARK: {
primary: '#FFF',
- secondary: computed(['colors.primary'], ([primary]) => `${primary}FFF`),
+ secondary: computed((theme) => `${theme.colors.primary}FFF`),
},
},
sizes: {
@@ -159,14 +160,14 @@ const theme = buildTheme(
);
describe('getComputed', () => {
it('computes all values and returns only the current color mode', () => {
- // @ts-ignore intentionally not using a full EUI theme definition
- expect(getComputed(theme, {}, 'light')).toEqual({
+ // @ts-expect-error intentionally not using a full EUI theme definition
+ expect(getComputed(theme, {}, 'LIGHT')).toEqual({
colors: { primary: '#000', secondary: '#000000' },
sizes: { small: 8 },
themeName: 'minimal',
});
- // @ts-ignore intentionally not using a full EUI theme definition
- expect(getComputed(theme, {}, 'dark')).toEqual({
+ // @ts-expect-error intentionally not using a full EUI theme definition
+ expect(getComputed(theme, {}, 'DARK')).toEqual({
colors: { primary: '#FFF', secondary: '#FFFFFF' },
sizes: { small: 8 },
themeName: 'minimal',
@@ -174,8 +175,8 @@ describe('getComputed', () => {
});
it('respects simple overrides', () => {
expect(
- // @ts-ignore intentionally not using a full EUI theme definition
- getComputed(theme, buildTheme({ sizes: { small: 4 } }, ''), 'light')
+ // @ts-expect-error intentionally not using a full EUI theme definition
+ getComputed(theme, buildTheme({ sizes: { small: 4 } }, ''), 'LIGHT')
).toEqual({
colors: { primary: '#000', secondary: '#000000' },
sizes: { small: 4 },
@@ -185,10 +186,10 @@ describe('getComputed', () => {
it('respects overrides in computation', () => {
expect(
getComputed(
- // @ts-ignore intentionally not using a full EUI theme definition
+ // @ts-expect-error intentionally not using a full EUI theme definition
theme,
- buildTheme({ colors: { light: { primary: '#CCC' } } }, ''),
- 'light'
+ buildTheme({ colors: { LIGHT: { primary: '#CCC' } } }, ''),
+ 'LIGHT'
)
).toEqual({
colors: { primary: '#CCC', secondary: '#CCC000' },
@@ -199,10 +200,10 @@ describe('getComputed', () => {
it('respects property extensions', () => {
expect(
getComputed(
- // @ts-ignore intentionally not using a full EUI theme definition
+ // @ts-expect-error intentionally not using a full EUI theme definition
theme,
- buildTheme({ colors: { light: { tertiary: '#333' } } }, ''),
- 'light'
+ buildTheme({ colors: { LIGHT: { tertiary: '#333' } } }, ''),
+ 'LIGHT'
)
).toEqual({
colors: { primary: '#000', secondary: '#000000', tertiary: '#333' },
@@ -213,10 +214,10 @@ describe('getComputed', () => {
it('respects section extensions', () => {
expect(
getComputed(
- // @ts-ignore intentionally not using a full EUI theme definition
+ // @ts-expect-error intentionally not using a full EUI theme definition
theme,
buildTheme({ custom: { myProp: '#333' } }, ''),
- 'light'
+ 'LIGHT'
)
).toEqual({
colors: { primary: '#000', secondary: '#000000' },
@@ -228,22 +229,21 @@ describe('getComputed', () => {
it('respects extensions in computation', () => {
expect(
getComputed(
- // @ts-ignore intentionally not using a full EUI theme definition
+ // @ts-expect-error intentionally not using a full EUI theme definition
theme,
buildTheme(
{
colors: {
- light: {
- tertiary: computed(
- ['colors.primary'],
- ([primary]) => `${primary}333`
- ),
+ LIGHT: {
+ tertiary: computed(([primary]) => `${primary}333`, [
+ 'colors.primary',
+ ]),
},
},
},
''
),
- 'light'
+ 'LIGHT'
)
).toEqual({
colors: { primary: '#000', secondary: '#000000', tertiary: '#000333' },
@@ -276,29 +276,3 @@ describe('mergeDeep', () => {
).toEqual({ a: 1, b: { c: { d: 3, e: 5 } } });
});
});
-
-describe('currentColorModeOnly', () => {
- const theme = {
- colors: {
- light: {
- primary: '#000',
- },
- dark: {
- primary: '#FFF',
- },
- },
- sizes: {
- small: 8,
- },
- };
- it('object with only the current color mode colors', () => {
- expect(currentColorModeOnly('light', theme)).toEqual({
- colors: { primary: '#000' },
- sizes: { small: 8 },
- });
- expect(currentColorModeOnly('dark', theme)).toEqual({
- colors: { primary: '#FFF' },
- sizes: { small: 8 },
- });
- });
-});
diff --git a/src/services/theme/utils.ts b/src/services/theme/utils.ts
index 2cfc6687324..9257033e8ce 100644
--- a/src/services/theme/utils.ts
+++ b/src/services/theme/utils.ts
@@ -12,29 +12,32 @@ import {
EuiThemeSystem,
EuiThemeShape,
EuiThemeComputed,
+ COLOR_MODES_STANDARD,
+ COLOR_MODES_INVERSE,
} from './types';
-export const COLOR_MODE_KEY = 'colors';
-export const DEFAULT_COLOR_MODE = 'light';
+export const DEFAULT_COLOR_MODE = COLOR_MODES_STANDARD.light;
const isObject = (obj: any) => obj && typeof obj === 'object';
export const isInverseColorMode = (colorMode?: EuiThemeColorMode) => {
- return colorMode === 'inverse';
+ return colorMode === COLOR_MODES_INVERSE;
};
export const getColorMode = (
colorMode?: EuiThemeColorMode,
parentColorMode?: EuiThemeColorMode
) => {
- if (colorMode == null) {
+ const mode = colorMode?.toUpperCase();
+ if (mode == null) {
return parentColorMode || DEFAULT_COLOR_MODE;
- } else if (isInverseColorMode(colorMode)) {
- return parentColorMode === 'dark' || parentColorMode === undefined
- ? 'light'
- : 'dark';
+ } else if (isInverseColorMode(mode)) {
+ return parentColorMode === COLOR_MODES_STANDARD.dark ||
+ parentColorMode === undefined
+ ? COLOR_MODES_STANDARD.light
+ : COLOR_MODES_STANDARD.dark;
} else {
- return colorMode;
+ return mode;
}
};
@@ -47,14 +50,20 @@ export const getOn = (
let node = model;
while (path.length) {
const segment = path.shift()!;
+
if (node.hasOwnProperty(segment) === false) {
- return undefined;
- }
- if (colorMode && segment === COLOR_MODE_KEY) {
- if (node[segment].hasOwnProperty(colorMode) === false) {
- return undefined;
+ if (
+ colorMode &&
+ node.hasOwnProperty(colorMode) === true &&
+ node[colorMode].hasOwnProperty(segment) === true
+ ) {
+ if (node[colorMode][segment] instanceof Computed) {
+ node = node[colorMode][segment].getValue(null, null, node, colorMode);
+ } else {
+ node = node[colorMode][segment];
+ }
} else {
- node = node[segment][colorMode];
+ return undefined;
}
} else {
if (node[segment] instanceof Computed) {
@@ -91,8 +100,8 @@ export const setOn = (
export class Computed {
constructor(
- public dependencies: string[],
- public computer: (...values: any[]) => T
+ public computer: (...values: any[]) => T,
+ public dependencies: string | string[] = []
) {}
getValue(
@@ -101,10 +110,20 @@ export class Computed {
working: EuiThemeComputed,
colorMode: EuiThemeColorMode
) {
+ if (!this.dependencies.length) {
+ return this.computer(working);
+ }
+ if (!Array.isArray(this.dependencies)) {
+ return this.computer(
+ getOn(working, this.dependencies) ??
+ getOn(modifications, this.dependencies, colorMode) ??
+ getOn(base, this.dependencies, colorMode)
+ );
+ }
return this.computer(
this.dependencies.map((dependency) => {
return (
- getOn(working, dependency, colorMode) ??
+ getOn(working, dependency) ??
getOn(modifications, dependency, colorMode) ??
getOn(base, dependency, colorMode)
);
@@ -113,12 +132,21 @@ export class Computed {
}
}
-export const computed = (
- dependencies: string[],
- computer: (values: any[]) => T
-) => {
- return (new Computed(dependencies, computer) as unknown) as T;
-};
+export function computed(computer: (value: EuiThemeComputed) => T): T;
+export function computed(
+ computer: (value: any[]) => T,
+ dependencies: string[]
+): T;
+export function computed(
+ computer: (value: any) => T,
+ dependencies: string
+): T;
+export function computed(
+ comp: ((value: T) => T) | ((value: any) => T) | ((value: any[]) => T),
+ dep?: string | string[]
+) {
+ return new Computed(comp, dep);
+}
export const getComputed = (
base: EuiThemeSystem,
@@ -134,27 +162,31 @@ export const getComputed = (
path?: string
) {
Object.keys(base).forEach((key) => {
- const arr = path?.split('.') || [];
- const last = arr[arr.length - 1];
- if (last === COLOR_MODE_KEY && key !== colorMode) {
- // Intentional no-op
- } else {
- const newPath = path ? `${path}.${key}` : `${key}`;
- const existing = checkExisting && getOn(output, newPath);
- if (!existing || isObject(existing)) {
- const baseValue =
- base[key] instanceof Computed
- ? base[key].getValue(base.root, over.root, output, colorMode)
- : base[key];
- const overValue =
- over[key] instanceof Computed
- ? over[key].getValue(base.root, over.root, output, colorMode)
- : over[key];
- if (isObject(baseValue)) {
- loop(baseValue, overValue ?? {}, checkExisting, newPath);
- } else {
- setOn(output, newPath, overValue ?? baseValue);
- }
+ let newPath = path ? `${path}.${key}` : `${key}`;
+ if ([...Object.values(COLOR_MODES_STANDARD), colorMode].includes(key)) {
+ if (key !== colorMode) {
+ return;
+ } else {
+ const colorModeSegment = new RegExp(
+ `(\\.${colorMode}\\b)|(\\b${colorMode}\\.)`
+ );
+ newPath = newPath.replace(colorModeSegment, '');
+ }
+ }
+ const existing = checkExisting && getOn(output, newPath);
+ if (!existing || isObject(existing)) {
+ const baseValue =
+ base[key] instanceof Computed
+ ? base[key].getValue(base.root, over.root, output, colorMode)
+ : base[key];
+ const overValue =
+ over[key] instanceof Computed
+ ? over[key].getValue(base.root, over.root, output, colorMode)
+ : over[key];
+ if (isObject(baseValue) && !Array.isArray(baseValue)) {
+ loop(baseValue, overValue ?? {}, checkExisting, newPath);
+ } else {
+ setOn(output, newPath, overValue ?? baseValue);
}
}
});
@@ -163,7 +195,7 @@ export const getComputed = (
loop(base, over);
// Compute and apply extension values only
loop(over, {}, true);
- return currentColorModeOnly(colorMode, (output as unknown) as T);
+ return output as EuiThemeComputed;
};
export const buildTheme = (model: T, key: string) => {
@@ -208,7 +240,7 @@ export const buildTheme = (model: T, key: string) => {
const target = property === 'root' ? _target : _target.model || _target;
// @ts-ignore `string` index signature
const value = target[property];
- if (typeof value === 'object' && value !== null) {
+ if (isObject(value) && !Array.isArray(value)) {
return new Proxy(
{
model: value,
@@ -270,26 +302,3 @@ export const mergeDeep = (
return target;
};
-
-export const currentColorModeOnly = (
- colorMode: EuiThemeColorMode,
- _theme: { [key: string]: any }
-): EuiThemeComputed => {
- const theme: { [key: string]: any } = {};
-
- Object.keys(_theme).forEach((key) => {
- if (key === COLOR_MODE_KEY) {
- theme[key] = _theme[key][colorMode];
- } else {
- const themeValue = _theme[key];
-
- if (isObject(themeValue)) {
- theme[key] = currentColorModeOnly(colorMode, themeValue);
- } else {
- theme[key] = themeValue;
- }
- }
- });
-
- return theme as EuiThemeComputed;
-};
diff --git a/src/themes/eui-amsterdam/global_styling/variables/_borders.ts b/src/themes/eui-amsterdam/global_styling/variables/_borders.ts
new file mode 100644
index 00000000000..cdba42f89df
--- /dev/null
+++ b/src/themes/eui-amsterdam/global_styling/variables/_borders.ts
@@ -0,0 +1,20 @@
+/*
+ * 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 { computed } from '../../../../services/theme/utils';
+import {
+ border,
+ EuiThemeBorder,
+} from '../../../../global_styling/variables/_borders';
+import { sizeToPixel } from '../../../../global_styling/variables/_size';
+
+export const border_ams: EuiThemeBorder = {
+ ...border,
+ radius: computed(sizeToPixel(0.375)),
+ radiusSmall: computed(sizeToPixel(0.25)),
+};
diff --git a/src/themes/eui-amsterdam/global_styling/variables/_colors.ts b/src/themes/eui-amsterdam/global_styling/variables/_colors.ts
new file mode 100644
index 00000000000..4b39e31df95
--- /dev/null
+++ b/src/themes/eui-amsterdam/global_styling/variables/_colors.ts
@@ -0,0 +1,101 @@
+/*
+ * 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 { shade, tint } from '../../../../services/color';
+import { computed } from '../../../../services/theme/utils';
+import {
+ makeHighContrastColor,
+ makeDisabledContrastColor,
+} from '../../../../global_styling/functions/_colors';
+import {
+ _EuiThemeColors,
+ brand_text_colors,
+ shade_colors,
+ EuiThemeColors,
+ dark_shades,
+} from '../../../../global_styling/variables/_colors';
+
+/*
+ * LIGHT THEME
+ */
+
+export const light_colors_ams: _EuiThemeColors = {
+ // Brand
+ primary: '#07C',
+ accent: '#F04E98',
+ success: '#00BFB3',
+ warning: '#FEC514',
+ danger: '#BD271E',
+
+ // Shades
+ ...shade_colors,
+ lightestShade: '#f0f4fb',
+
+ // Special
+ body: computed(([lightestShade]) => tint(lightestShade, 0.5), [
+ 'colors.lightestShade',
+ ]),
+ highlight: computed(([warning]) => tint(warning, 0.9), ['colors.warning']),
+ disabled: '#ABB4C4',
+ disabledText: computed(makeDisabledContrastColor('colors.disabled')),
+ shadow: computed(({ colors }) => colors.ink),
+
+ // Need to come after special colors so they can react to `body`
+ ...brand_text_colors,
+
+ // Text
+ text: computed(([darkestShade]) => darkestShade, ['colors.darkestShade']),
+ title: computed(([text]) => shade(text, 0.5), ['colors.text']),
+ subdued: computed(makeHighContrastColor('colors.darkShade')),
+ link: computed(([primaryText]) => primaryText, ['colors.primaryText']),
+};
+
+/*
+ * DARK THEME
+ */
+
+export const dark_colors_ams: _EuiThemeColors = {
+ // Brand
+ primary: '#36A2EF',
+ accent: '#F68FBE',
+ success: '#7DDED8',
+ warning: '#F3D371',
+ danger: '#F86B63',
+
+ // Shades
+ ...dark_shades,
+
+ // Special
+ body: computed(([lightestShade]) => shade(lightestShade, 0.45), [
+ 'colors.lightestShade',
+ ]),
+ highlight: '#2E2D25',
+ disabled: '#515761',
+ disabledText: computed(makeDisabledContrastColor('colors.disabled')),
+ shadow: computed(({ colors }) => colors.ink),
+
+ // Need to come after special colors so they can react to `body`
+ ...brand_text_colors,
+
+ // Text
+ text: '#DFE5EF',
+ title: computed(([text]) => text, ['colors.text']),
+ subdued: computed(makeHighContrastColor('colors.mediumShade')),
+ link: computed(([primaryText]) => primaryText, ['colors.primaryText']),
+};
+
+/*
+ * FULL
+ */
+
+export const colors_ams: EuiThemeColors = {
+ ghost: '#FFF',
+ ink: '#000',
+ LIGHT: light_colors_ams,
+ DARK: dark_colors_ams,
+};
diff --git a/src/themes/eui-amsterdam/global_styling/variables/_states.ts b/src/themes/eui-amsterdam/global_styling/variables/_states.ts
new file mode 100644
index 00000000000..caf7d7d0b92
--- /dev/null
+++ b/src/themes/eui-amsterdam/global_styling/variables/_states.ts
@@ -0,0 +1,28 @@
+/*
+ * 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 { computed } from '../../../../services/theme/utils';
+import { transparentize } from '../../../../services/color';
+import {
+ focus,
+ _EuiThemeFocus,
+} from '../../../../global_styling/variables/_states';
+
+export const focus_ams: _EuiThemeFocus = {
+ ...focus,
+ color: 'currentColor',
+ transparency: { LIGHT: 0.9, DARK: 0.7 },
+ backgroundColor: computed(({ colors, focus }) =>
+ transparentize(colors.primary, focus.transparency)
+ ),
+
+ // Outline
+ outline: {
+ outline: computed(({ focus }) => `${focus.width} solid ${focus.color}`),
+ },
+};
diff --git a/src/themes/eui-amsterdam/global_styling/variables/_typography.ts b/src/themes/eui-amsterdam/global_styling/variables/_typography.ts
new file mode 100644
index 00000000000..ee0c0845ee0
--- /dev/null
+++ b/src/themes/eui-amsterdam/global_styling/variables/_typography.ts
@@ -0,0 +1,17 @@
+/*
+ * 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 { font } from '../../../../global_styling/variables/_typography';
+
+/**
+ * Amsterdam theme just changes the main font from the beta Inter UI to Inter
+ */
+export const font_ams = {
+ ...font,
+ family: "'Inter', BlinkMacSystemFont, Helvetica, Arial, sans-serif",
+};
diff --git a/src/themes/eui-amsterdam/global_styling/variables/title.ts b/src/themes/eui-amsterdam/global_styling/variables/title.ts
new file mode 100644
index 00000000000..9e8f2c22535
--- /dev/null
+++ b/src/themes/eui-amsterdam/global_styling/variables/title.ts
@@ -0,0 +1,25 @@
+/*
+ * 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 {
+ title,
+ EuiThemeTitle,
+} from '../../../../global_styling/variables/title';
+import { SCALES } from '../../../../global_styling/variables/_typography';
+import { computed } from '../../../../services/theme/utils';
+
+// For Amsterdam, change all font-weights to bold and remove letter-spacing
+
+export const title_ams: EuiThemeTitle = SCALES.reduce((acc, elem) => {
+ acc[elem] = {
+ ...title[elem],
+ fontWeight: computed(([fontWeight]) => fontWeight, ['font.weight.bold']),
+ letterSpacing: undefined,
+ };
+ return acc;
+}, {} as EuiThemeTitle);
diff --git a/src/themes/eui-amsterdam/theme.ts b/src/themes/eui-amsterdam/theme.ts
new file mode 100644
index 00000000000..669d9fd7bc4
--- /dev/null
+++ b/src/themes/eui-amsterdam/theme.ts
@@ -0,0 +1,37 @@
+/*
+ * 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 { buildTheme, EuiThemeShape } from '../../services/theme';
+import { animation } from '../../global_styling/variables/_animations';
+import { breakpoint } from '../../global_styling/variables/_breakpoint';
+import { base, size } from '../../global_styling/variables/_size';
+
+import { colors_ams } from './global_styling/variables/_colors';
+import { font_ams } from './global_styling/variables/_typography';
+import { border_ams } from './global_styling/variables/_borders';
+import { focus_ams } from './global_styling/variables/_states';
+import { fontSize } from '../../global_styling/variables/text';
+
+export const euiThemeAmsterdam: EuiThemeShape = {
+ colors: colors_ams,
+ base,
+ size,
+ font: {
+ ...font_ams,
+ ...fontSize,
+ },
+ border: border_ams,
+ focus: focus_ams,
+ animation,
+ breakpoint,
+};
+
+export const EuiThemeAmsterdam = buildTheme(
+ euiThemeAmsterdam,
+ 'EUI_THEME_AMSTERDAM'
+);
diff --git a/src/themes/eui/theme.ts b/src/themes/eui/theme.ts
new file mode 100644
index 00000000000..3bec96cbf84
--- /dev/null
+++ b/src/themes/eui/theme.ts
@@ -0,0 +1,34 @@
+/*
+ * 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 { buildTheme } from '../../services/theme/utils';
+import { EuiThemeShape } from '../../services/theme/types';
+import { animation } from '../../global_styling/variables/_animations';
+import { breakpoint } from '../../global_styling/variables/_breakpoint';
+import { colors } from '../../global_styling/variables/_colors';
+import { base, size } from '../../global_styling/variables/_size';
+import { focus } from '../../global_styling/variables/_states';
+import { font } from '../../global_styling/variables/_typography';
+import { border } from '../../global_styling/variables/_borders';
+import { fontSize } from '../../global_styling/variables/text';
+
+export const euiThemeDefault: EuiThemeShape = {
+ colors,
+ base,
+ size,
+ font: {
+ ...font,
+ ...fontSize,
+ },
+ border,
+ focus,
+ animation,
+ breakpoint,
+};
+
+export const EuiThemeDefault = buildTheme(euiThemeDefault, 'EUI_THEME_DEFAULT');