diff --git a/.buildkite/scripts/pipelines/pipeline_deploy_new_docs.sh b/.buildkite/scripts/pipelines/pipeline_deploy_new_docs.sh index ebf6a66e8a1..df2598dce0b 100644 --- a/.buildkite/scripts/pipelines/pipeline_deploy_new_docs.sh +++ b/.buildkite/scripts/pipelines/pipeline_deploy_new_docs.sh @@ -33,7 +33,7 @@ analytics_vault="secret/ci/elastic-eui/analytics" export DOCS_BASE_URL="/${bucket_directory}" export DOCS_GOOGLE_TAG_MANAGER_ID="$(retry 5 vault read -field=google_tag_manager_id "${analytics_vault}")" -yarn workspaces foreach -Rpt --from @elastic/eui-website run build +yarn workspace @elastic/eui-website run build:workspaces echo "+++ Configuring environment for website deployment" diff --git a/.buildkite/scripts/pipelines/pipeline_test.sh b/.buildkite/scripts/pipelines/pipeline_test.sh index 8ec1d30c660..220e5313705 100644 --- a/.buildkite/scripts/pipelines/pipeline_test.sh +++ b/.buildkite/scripts/pipelines/pipeline_test.sh @@ -29,53 +29,53 @@ COMMAND="" case $TEST_TYPE in lint) echo "[TASK]: Running linters" - COMMAND="/opt/yarn*/bin/yarn --cwd packages/eui && yarn --cwd packages/eui lint" + COMMAND="/opt/yarn*/bin/yarn --cwd packages/eui && yarn --cwd packages/eui build:workspaces && yarn --cwd packages/eui lint" ;; unit:ts) echo "[TASK]: Running .ts and .js unit tests" - COMMAND="/opt/yarn*/bin/yarn --cwd packages/eui && yarn --cwd packages/eui test-unit --node-options=--max_old_space_size=2048 --testMatch=non-react" + COMMAND="/opt/yarn*/bin/yarn --cwd packages/eui && yarn --cwd packages/eui build:workspaces && yarn --cwd packages/eui test-unit --node-options=--max_old_space_size=2048 --testMatch=non-react" ;; unit:tsx:16) echo "[TASK]: Running Jest .tsx tests against React 16" DOCKER_OPTIONS+=(--env BUILDKITE_ANALYTICS_TOKEN="$(retry 5 vault read -field=jest_token_react16 "${buildkite_analytics_vault}")") - COMMAND="/opt/yarn*/bin/yarn --cwd packages/eui && yarn --cwd packages/eui test-unit --node-options=--max_old_space_size=2048 --react-version=16 --testMatch=react" + COMMAND="/opt/yarn*/bin/yarn --cwd packages/eui && yarn --cwd packages/eui build:workspaces && yarn --cwd packages/eui test-unit --node-options=--max_old_space_size=2048 --react-version=16 --testMatch=react" ;; unit:tsx:17) echo "[TASK]: Running Jest .tsx tests against React 17" DOCKER_OPTIONS+=(--env BUILDKITE_ANALYTICS_TOKEN="$(retry 5 vault read -field=jest_token_react17 "${buildkite_analytics_vault}")") - COMMAND="/opt/yarn*/bin/yarn --cwd packages/eui && yarn --cwd packages/eui test-unit --node-options=--max_old_space_size=2048 --react-version=17 --testMatch=react" + COMMAND="/opt/yarn*/bin/yarn --cwd packages/eui && yarn --cwd packages/eui build:workspaces && yarn --cwd packages/eui test-unit --node-options=--max_old_space_size=2048 --react-version=17 --testMatch=react" ;; unit:tsx) echo "[TASK]: Running Jest .tsx tests against React 18" DOCKER_OPTIONS+=(--env BUILDKITE_ANALYTICS_TOKEN="$(retry 5 vault read -field=jest_token_react18 "${buildkite_analytics_vault}")") - COMMAND="/opt/yarn*/bin/yarn --cwd packages/eui && yarn --cwd packages/eui test-unit --node-options=--max_old_space_size=2048 --testMatch=react" + COMMAND="/opt/yarn*/bin/yarn --cwd packages/eui && yarn --cwd packages/eui build:workspaces && yarn --cwd packages/eui test-unit --node-options=--max_old_space_size=2048 --testMatch=react" ;; cypress:16) echo "[TASK]: Running Cypress tests against React 16" DOCKER_OPTIONS+=(--env BUILDKITE_ANALYTICS_TOKEN="$(retry 5 vault read -field=cypress_token_react16 "${buildkite_analytics_vault}")") - COMMAND="/opt/yarn*/bin/yarn --cwd packages/eui && yarn --cwd packages/eui cypress install && yarn --cwd packages/eui test-cypress --node-options=--max_old_space_size=2048 --react-version=16" + COMMAND="/opt/yarn*/bin/yarn --cwd packages/eui && yarn --cwd packages/eui build:workspaces && yarn --cwd packages/eui cypress install && yarn --cwd packages/eui test-cypress --node-options=--max_old_space_size=2048 --react-version=16" ;; cypress:17) echo "[TASK]: Running Cypress tests against React 17" DOCKER_OPTIONS+=(--env BUILDKITE_ANALYTICS_TOKEN="$(retry 5 vault read -field=cypress_token_react17 "${buildkite_analytics_vault}")") - COMMAND="/opt/yarn*/bin/yarn --cwd packages/eui && yarn --cwd packages/eui cypress install && yarn --cwd packages/eui test-cypress --node-options=--max_old_space_size=2048 --react-version=17" + COMMAND="/opt/yarn*/bin/yarn --cwd packages/eui && yarn --cwd packages/eui build:workspaces && yarn --cwd packages/eui cypress install && yarn --cwd packages/eui test-cypress --node-options=--max_old_space_size=2048 --react-version=17" ;; cypress:18) echo "[TASK]: Running Cypress tests against React 18" DOCKER_OPTIONS+=(--env BUILDKITE_ANALYTICS_TOKEN="$(retry 5 vault read -field=cypress_token_react18 "${buildkite_analytics_vault}")") - COMMAND="/opt/yarn*/bin/yarn --cwd packages/eui && yarn --cwd packages/eui cypress install && yarn --cwd packages/eui test-cypress --node-options=--max_old_space_size=2048" + COMMAND="/opt/yarn*/bin/yarn --cwd packages/eui && yarn --cwd packages/eui build:workspaces && yarn --cwd packages/eui cypress install && yarn --cwd packages/eui test-cypress --node-options=--max_old_space_size=2048" ;; cypress:a11y) echo "[TASK]: Running Cypress accessibility tests against React 18" - COMMAND="/opt/yarn*/bin/yarn --cwd packages/eui && yarn --cwd packages/eui cypress install && yarn --cwd packages/eui run test-cypress-a11y --node-options=--max_old_space_size=2048" + COMMAND="/opt/yarn*/bin/yarn --cwd packages/eui && yarn --cwd packages/eui build:workspaces && yarn --cwd packages/eui cypress install && yarn --cwd packages/eui run test-cypress-a11y --node-options=--max_old_space_size=2048" ;; *) diff --git a/.buildkite/scripts/release/step_build.sh b/.buildkite/scripts/release/step_build.sh index 3a9251b6e28..01d96bf62d8 100755 --- a/.buildkite/scripts/release/step_build.sh +++ b/.buildkite/scripts/release/step_build.sh @@ -10,6 +10,7 @@ echo "+++ :yarn: Installing dependencies" yarn echo "+++ :yarn: Building @elastic/eui" +yarn build:workspaces yarn build echo "+++ :yarn: Built @elastic/eui" diff --git a/packages/docusaurus-theme/package.json b/packages/docusaurus-theme/package.json index 4765cf70a60..7f0eeef3a8c 100644 --- a/packages/docusaurus-theme/package.json +++ b/packages/docusaurus-theme/package.json @@ -34,8 +34,9 @@ "@docusaurus/theme-common": "^3.5.2", "@docusaurus/utils-validation": "^3.5.2", "@elastic/datemath": "^5.0.3", - "@elastic/eui": "97.2.0", + "@elastic/eui": "workspace:^", "@elastic/eui-docgen": "workspace:^", + "@elastic/eui-theme-borealis": "workspace:^", "@emotion/css": "^11.11.2", "@emotion/react": "^11.11.4", "@types/react-window": "^1.8.8", diff --git a/packages/docusaurus-theme/src/components/navbar_item/index.tsx b/packages/docusaurus-theme/src/components/navbar_item/index.tsx index 130047a50e8..f4bacf65cfd 100644 --- a/packages/docusaurus-theme/src/components/navbar_item/index.tsx +++ b/packages/docusaurus-theme/src/components/navbar_item/index.tsx @@ -91,9 +91,9 @@ export const NavbarItem = (props: Props) => { } = props; const isBrowser = useIsBrowser(); - const { theme } = useContext(AppThemeContext); + const { colorMode } = useContext(AppThemeContext); - const isDarkMode = theme === 'dark'; + const isDarkMode = colorMode === 'dark'; const styles = useEuiMemoizedStyles(getStyles); const cssStyles = [ diff --git a/packages/docusaurus-theme/src/components/theme_context/index.tsx b/packages/docusaurus-theme/src/components/theme_context/index.tsx index d8a394fc8d5..d1fe2208f15 100644 --- a/packages/docusaurus-theme/src/components/theme_context/index.tsx +++ b/packages/docusaurus-theme/src/components/theme_context/index.tsx @@ -5,17 +5,39 @@ import { useState, } from 'react'; import useIsBrowser from '@docusaurus/useIsBrowser'; -import { EUI_THEMES, EuiProvider, EuiThemeColorMode } from '@elastic/eui'; +import { + EuiProvider, + EuiThemeAmsterdam, + EuiThemeColorMode, +} from '@elastic/eui'; +import { EuiThemeBorealis } from '@elastic/eui-theme-borealis'; import { EuiThemeOverrides } from './theme_overrides'; -const EUI_THEME_NAMES = EUI_THEMES.map( - ({ value }) => value -) as EuiThemeColorMode[]; +const EXPERIMENTAL_THEMES = [ + { + text: 'Borealis', + value: EuiThemeBorealis.key, + provider: EuiThemeBorealis, + }, +]; + +export const AVAILABLE_THEMES = [ + { + text: 'Amsterdam', + value: EuiThemeAmsterdam.key, + provider: EuiThemeAmsterdam, + }, + ...EXPERIMENTAL_THEMES, +]; + +const EUI_COLOR_MODES = ['light', 'dark'] as EuiThemeColorMode[]; const defaultState = { - theme: EUI_THEME_NAMES[0] as EuiThemeColorMode, - changeTheme: (themeValue: EuiThemeColorMode) => {}, + colorMode: EUI_COLOR_MODES[0] as EuiThemeColorMode, + changeColorMode: (colorMode: EuiThemeColorMode) => {}, + theme: AVAILABLE_THEMES[0]!, + changeTheme: (themeValue: string) => {}, }; export const AppThemeContext = createContext(defaultState); @@ -24,25 +46,39 @@ export const AppThemeProvider: FunctionComponent = ({ children, }) => { const isBrowser = useIsBrowser(); - const [theme, setTheme] = useState(() => { + const [colorMode, setColorMode] = useState(() => { if (isBrowser) { - return localStorage.getItem('theme') as EuiThemeColorMode ?? defaultState.theme; + return ( + (localStorage.getItem('theme') as EuiThemeColorMode) ?? + defaultState.colorMode + ); } - return defaultState.theme; + return defaultState.colorMode; }); + const [theme, setTheme] = useState(defaultState.theme); + + const handleChangeTheme = (themeValue: string) => { + const themeObj = AVAILABLE_THEMES.find((t) => t.value === themeValue); + + setTheme((currentTheme) => themeObj ?? currentTheme); + }; + return ( {children} diff --git a/packages/docusaurus-theme/src/components/theme_switcher/index.tsx b/packages/docusaurus-theme/src/components/theme_switcher/index.tsx new file mode 100644 index 00000000000..eeb4a1706d3 --- /dev/null +++ b/packages/docusaurus-theme/src/components/theme_switcher/index.tsx @@ -0,0 +1,94 @@ +import { useContext, useEffect, useState } from 'react'; +import { css } from '@emotion/react'; +import { + EuiAvatar, + EuiButtonEmpty, + euiFocusRing, + EuiListGroup, + EuiListGroupItem, + EuiPopover, + useEuiMemoizedStyles, + useEuiTheme, + UseEuiTheme, +} from '@elastic/eui'; + +import { AppThemeContext, AVAILABLE_THEMES } from '../theme_context'; + +const getStyles = (euiThemeContext: UseEuiTheme) => { + const { euiTheme } = euiThemeContext; + + return { + button: css` + padding: 0; + `, + listItem: css` + .euiListGroupItem__button:focus-visible { + // overriding the global "outset" style to ensure the focus style is not cut off + ${euiFocusRing(euiThemeContext, 'inset', { + color: euiTheme.colors.primary, + })}; + } + `, + }; +}; + +export const ThemeSwitcher = () => { + const { euiTheme } = useEuiTheme(); + const [currentTheme, setCurrentTheme] = useState( + AVAILABLE_THEMES[0]?.value ?? '' + ); + const [isPopoverOpen, setPopoverOpen] = useState(false); + const { theme, changeTheme } = useContext(AppThemeContext); + + useEffect(() => { + changeTheme(currentTheme); + }, [currentTheme]); + + const styles = useEuiMemoizedStyles(getStyles); + + const button = ( + setPopoverOpen((isOpen) => !isOpen)} + aria-label={`${theme.text} theme`} + > + + + ); + + return ( + setPopoverOpen(false)} + button={button} + panelPaddingSize="xs" + repositionOnScroll + aria-label="EUI theme list" + > + + {AVAILABLE_THEMES && + AVAILABLE_THEMES.map((theme) => { + const isCurrentTheme = currentTheme === theme.value; + + const handleOnClick = () => { + setCurrentTheme(theme.value); + }; + + return ( + + ); + })} + + + ); +}; diff --git a/packages/docusaurus-theme/src/theme/ColorModeToggle/index.tsx b/packages/docusaurus-theme/src/theme/ColorModeToggle/index.tsx index aaefb2441bc..970e11e0397 100644 --- a/packages/docusaurus-theme/src/theme/ColorModeToggle/index.tsx +++ b/packages/docusaurus-theme/src/theme/ColorModeToggle/index.tsx @@ -17,20 +17,20 @@ function ColorModeToggle({ onChange, ...rest }: WrappedProps): JSX.Element { - const { theme, changeTheme } = useContext(AppThemeContext); + const { colorMode, changeColorMode } = useContext(AppThemeContext); useEffect(() => { - changeTheme(value); + changeColorMode(value); }, []); - const handleOnChange = (themeName: EuiThemeColorMode) => { - changeTheme(themeName); - onChange?.(themeName); + const handleOnChange = (colorMode: EuiThemeColorMode) => { + changeColorMode(colorMode); + onChange?.(colorMode); }; return ( diff --git a/packages/docusaurus-theme/src/theme/Logo/index.tsx b/packages/docusaurus-theme/src/theme/Logo/index.tsx index 3f64a7bd951..ff434592173 100644 --- a/packages/docusaurus-theme/src/theme/Logo/index.tsx +++ b/packages/docusaurus-theme/src/theme/Logo/index.tsx @@ -62,8 +62,8 @@ function LogoThemedImage({ alt: string; imageClassName?: string; }) { - const { theme } = useContext(AppThemeContext); - const isDarkMode = theme === 'dark'; + const { colorMode } = useContext(AppThemeContext); + const isDarkMode = colorMode === 'dark'; const styles = useEuiMemoizedStyles(getStyles); diff --git a/packages/docusaurus-theme/src/theme/Navbar/Content/index.tsx b/packages/docusaurus-theme/src/theme/Navbar/Content/index.tsx index 53b4f771ead..5fcac599372 100644 --- a/packages/docusaurus-theme/src/theme/Navbar/Content/index.tsx +++ b/packages/docusaurus-theme/src/theme/Navbar/Content/index.tsx @@ -15,7 +15,13 @@ import SearchBar from '@theme-original/SearchBar'; import NavbarMobileSidebarToggle from '@theme-original/Navbar/MobileSidebar/Toggle'; import NavbarLogo from '@theme-original/Navbar/Logo'; import NavbarSearch from '@theme-original/Navbar/Search'; -import { euiFocusRing, useEuiMemoizedStyles, UseEuiTheme } from '@elastic/eui'; +import { + euiFocusRing, + euiTextTruncate, + useEuiMemoizedStyles, + UseEuiTheme, + isExperimentalThemeEnabled, +} from '@elastic/eui'; import { euiFormControlText, euiFormVariables, @@ -24,6 +30,7 @@ import { import euiVersions from '@site/static/versions.json'; import { VersionSwitcher } from '../../../components/version_switcher'; +import { ThemeSwitcher } from '../../../components/theme_switcher'; const DOCS_PATH = '/docs'; @@ -67,6 +74,10 @@ const getStyles = (euiThemeContext: UseEuiTheme) => { @media (min-width: 997px) { gap: ${euiTheme.size.l}; } + + .navbar__link { + ${euiTextTruncate()} + } `, navbarItemsRight: css` gap: ${euiTheme.size.s}; @@ -125,6 +136,11 @@ const getStyles = (euiThemeContext: UseEuiTheme) => { display: none; } `, + themeSwitcher: css` + @media (max-width: 996px) { + display: none; + } + `, }; }; @@ -218,6 +234,12 @@ export default function NavbarContent(): JSX.Element { )} + + {isBrowser && isExperimentalThemeEnabled() && ( +
+ +
+ )} } /> diff --git a/packages/docusaurus-theme/src/theme/Navbar/MobileSidebar/Header/index.tsx b/packages/docusaurus-theme/src/theme/Navbar/MobileSidebar/Header/index.tsx index 08791e527a8..0e0daa13ed1 100644 --- a/packages/docusaurus-theme/src/theme/Navbar/MobileSidebar/Header/index.tsx +++ b/packages/docusaurus-theme/src/theme/Navbar/MobileSidebar/Header/index.tsx @@ -9,6 +9,8 @@ import useIsBrowser from '@docusaurus/useIsBrowser'; import euiVersions from '@site/static/versions.json'; import { VersionSwitcher } from '../../../../components/version_switcher'; +import { ThemeSwitcher } from '../../../../components/theme_switcher'; +import { isExperimentalThemeEnabled } from '@elastic/eui/lib/themes/themes'; const getStyles = ({ euiTheme }: UseEuiTheme) => ({ sidebar: css` @@ -64,6 +66,7 @@ export default function NavbarMobileSidebarHeader(): JSX.Element {
{isBrowser && versions && } + {isBrowser && isExperimentalThemeEnabled() && }
diff --git a/packages/eui-theme-borealis/.eslintignore b/packages/eui-theme-borealis/.eslintignore new file mode 100644 index 00000000000..4874b17b0b8 --- /dev/null +++ b/packages/eui-theme-borealis/.eslintignore @@ -0,0 +1,9 @@ +dist +node_modules +lib +types +**/*.d.ts +package.json +scripts +.eslintrc.js +babel.config.js diff --git a/packages/eui-theme-borealis/.eslintplugin.js b/packages/eui-theme-borealis/.eslintplugin.js new file mode 100644 index 00000000000..88d38f7aa99 --- /dev/null +++ b/packages/eui-theme-borealis/.eslintplugin.js @@ -0,0 +1,3 @@ +exports.rules = { + 'require-license-header': require('@elastic/eui-theme-common/scripts/eslint-plugin-local/require_license_header.js'), +}; diff --git a/packages/eui-theme-borealis/.eslintrc.js b/packages/eui-theme-borealis/.eslintrc.js new file mode 100644 index 00000000000..e33853c0255 --- /dev/null +++ b/packages/eui-theme-borealis/.eslintrc.js @@ -0,0 +1,114 @@ +const SSPL_ELASTIC_2_0_LICENSE_HEADER = ` +/* + * 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. + */ +`; + +module.exports = { + parser: '@typescript-eslint/parser', + parserOptions: { + project: ['./tsconfig.json'], + ecmaFeatures: { + jsx: true, + }, + }, + settings: { + 'import/resolver': { + node: { + extensions: ['.ts', '.tsx', '.js', '.json'], + }, + }, + }, + extends: [ + 'plugin:@typescript-eslint/recommended', + // Prettier options need to come last, in order to override other style rules + 'plugin:prettier/recommended', + ], + plugins: ['local', 'import'], + rules: { + 'block-scoped-var': 'error', + camelcase: 'off', + 'dot-notation': ['error', { allowKeywords: true }], + eqeqeq: ['error', 'always', { null: 'ignore' }], + 'guard-for-in': 'error', + 'new-cap': ['error', { capIsNewExceptions: ['Private'] }], + 'no-caller': 'error', + 'no-const-assign': 'error', + 'no-debugger': 'error', + 'no-empty': ['error', { allowEmptyCatch: true }], + 'no-eval': 'error', + 'no-extend-native': 'error', + 'no-global-assign': 'error', + 'no-loop-func': 'error', + 'no-restricted-globals': ['error', 'context'], + 'no-script-url': 'error', + 'no-sequences': 'error', + 'no-var': 'error', + 'no-with': 'error', + 'prefer-const': 'error', + 'prefer-template': 'error', + strict: ['error', 'never'], + 'valid-typeof': 'error', + + 'local/require-license-header': [ + 'warn', + { + license: SSPL_ELASTIC_2_0_LICENSE_HEADER, + }, + ], + + 'import/no-unresolved': ['error', { amd: true, commonjs: true }], + 'import/namespace': 'error', + 'import/default': 'error', + 'import/export': 'error', + 'import/no-named-as-default': 'error', + 'import/no-named-as-default-member': 'error', + 'import/no-duplicates': 'error', + + '@typescript-eslint/array-type': ['error', { default: 'array-simple' }], + '@typescript-eslint/ban-types': 'off', + '@typescript-eslint/explicit-function-return-type': 'off', + '@typescript-eslint/explicit-member-accessibility': 'off', + '@typescript-eslint/indent': 'off', + '@typescript-eslint/ban-tslint-comment': 'error', + '@typescript-eslint/no-empty-interface': 'off', + '@typescript-eslint/no-explicit-any': 'off', + '@typescript-eslint/no-non-null-assertion': 'off', + '@typescript-eslint/no-parameter-properties': 'off', + '@typescript-eslint/no-triple-slash-reference': 'off', + '@typescript-eslint/no-unused-vars': [ + 'error', + { argsIgnorePattern: '^_', ignoreRestSiblings: true }, + ], + '@typescript-eslint/no-use-before-define': 'off', + '@typescript-eslint/no-empty-function': 'off', + // It"s all very well saying that some types are trivially inferrable, + // but being explicit is still clearer. + '@typescript-eslint/no-inferrable-types': 'off', + '@typescript-eslint/explicit-module-boundary-types': 'off', + '@typescript-eslint/naming-convention': 'off', + '@typescript-eslint/ban-ts-comment': [ + 'error', + { + 'ts-ignore': 'allow-with-description', + 'ts-expect-error': 'allow-with-description', + }, + ], + '@typescript-eslint/consistent-type-exports': [ + 'error', + { fixMixedExportsWithInlineTypeSpecifier: false }, + ], + }, + overrides: [ + { + files: ['*.d.ts'], + rules: { + 'react/prefer-es6-class': 'off', + }, + }, + ], +}; diff --git a/packages/eui-theme-borealis/.gitignore b/packages/eui-theme-borealis/.gitignore new file mode 100644 index 00000000000..4450fe8d868 --- /dev/null +++ b/packages/eui-theme-borealis/.gitignore @@ -0,0 +1,11 @@ +# Dependencies +/node_modules + +# Production +/lib + +yarn-debug.log* +yarn-error.log* + +# Build-related files +.eslintcache \ No newline at end of file diff --git a/packages/eui-theme-borealis/.prettierrc b/packages/eui-theme-borealis/.prettierrc new file mode 100644 index 00000000000..b2f0fa8f00e --- /dev/null +++ b/packages/eui-theme-borealis/.prettierrc @@ -0,0 +1,7 @@ +{ + "parser": "typescript", + "printWidth": 80, + "semi": true, + "singleQuote": true, + "trailingComma": "es5" +} diff --git a/packages/eui-theme-borealis/.stylelintrc.js b/packages/eui-theme-borealis/.stylelintrc.js new file mode 100644 index 00000000000..42adc41f28c --- /dev/null +++ b/packages/eui-theme-borealis/.stylelintrc.js @@ -0,0 +1,163 @@ +const camelCaseRegex = '^[a-z][\\w-]*$'; // Note: also allows `_` as part of BEM naming +const camelCaseValueRegex = '/^[a-z][\\w.]*$/'; // Note: also allows `.` for JS objects +const cssInJsVarRegex = '/\\${[a-zA-Z]+/'; + +module.exports = { + // @see https://stylelint.io/user-guide/rules + rules: { + // Enforce camelCase naming + 'selector-class-pattern': camelCaseRegex, + 'keyframes-name-pattern': camelCaseRegex, + 'custom-property-pattern': camelCaseRegex, + + // Opinionated rules + 'declaration-no-important': true, + 'max-nesting-depth': [ + 2, + { + ignore: ['blockless-at-rules', 'pseudo-classes'], + }, + ], + 'block-no-empty': true, + 'selector-no-qualifying-type': [ + true, + { + ignore: ['attribute'], // Allows input[type=search] + }, + ], + + // Non-Prettier newline rules + // Put a line-break between sections of CSS, but allow quick one-liners for legibility + 'rule-empty-line-before': [ + 'always-multi-line', + { + ignore: ['first-nested', 'after-comment'], + }, + ], + 'comment-empty-line-before': null, + 'declaration-empty-line-before': null, + + // Value preferences + 'number-max-precision': null, + // Attempt to catch/flag non-variable color values + 'color-named': 'never', + 'color-no-hex': true, + // Prefer lowercase values, except for font names and currentColor + 'value-keyword-case': [ + 'lower', + { + ignoreProperties: ['font-family', '/^\\$eui[\\w]+/'], // Allow fonts and Sass variables + ignoreKeywords: ['currentColor'], + }, + ], + 'declaration-block-no-duplicate-properties': [ + true, + { + ignore: ['consecutive-duplicates'], // We occasionally use duplicate property values for cross-browser fallbacks + }, + ], + + // TODO: It may be worth investigating and updating these rules to their more modern counterparts + 'color-function-notation': 'legacy', + 'alpha-value-notation': 'number', + + // Disable various opinionated extended stylelint rules that EUI has not previously enforced + 'no-descending-specificity': null, + 'keyframe-selector-notation': null, + 'declaration-block-no-redundant-longhand-properties': null, + }, + overrides: [ + { + // TODO: Remove Sass-specific config & rules once we're completely off Sass + files: ['**/*.scss'], + ignoreFiles: [ + 'generator-eui/**/*.scss', + 'src/global_styling/react_date_picker/**/*.scss', + 'src/themes/amsterdam/global_styling/react_date_picker/**/*.scss', + 'src/components/date_picker/react-datepicker/**/*.scss', + ], + extends: [ + 'stylelint-config-standard-scss', + 'stylelint-config-prettier-scss', + ], + // @see https://github.com/stylelint-scss/stylelint-scss#list-of-rules + rules: { + // Casing + 'scss/dollar-variable-pattern': camelCaseRegex, + 'scss/at-mixin-pattern': camelCaseRegex, + 'scss/at-function-pattern': camelCaseRegex, + 'function-name-case': [ + 'lower', + { + ignoreFunctions: [`/${camelCaseRegex}/`, 'MIN'], + }, + ], + + // Whitespace/newlines + 'scss/at-if-closing-brace-space-after': 'always-intermediate', + 'scss/operator-no-unspaced': true, + + // Formatting rules deprecated as of v15 - keep them in Sass styles just in case until end of migration + // @see https://github.com/stylelint/stylelint/blob/main/docs/user-guide/rules.md#deprecated + 'color-hex-case': 'upper', + 'string-quotes': 'single', + // 2 spaces for indentation + indentation: [2, { indentInsideParens: 'once-at-root-twice-in-block' }], + // Mimic 1tbs `} else {` brace style, like our JS + 'block-opening-brace-space-before': 'always', + 'block-closing-brace-newline-before': 'always-multi-line', + // Ensure multiple selectors on one line each + 'selector-list-comma-newline-before': 'never-multi-line', + 'selector-list-comma-newline-after': 'always', + // Trim unnecessary newlines/whitespace + 'block-closing-brace-empty-line-before': 'never', + 'max-empty-lines': 1, + 'no-eol-whitespace': true, + // Enforce spacing around various syntax symbols (colons, operators, etc.) + 'declaration-colon-space-after': 'always-single-line', + 'declaration-colon-space-before': 'never', + 'function-calc-no-unspaced-operator': true, + 'selector-combinator-space-before': 'always', + 'selector-combinator-space-after': 'always', + // Ensure trailing semicolons are always present on non-oneliners + 'declaration-block-semicolon-newline-after': 'always-multi-line', + + // Disable various opinionated extended stylelint rules that EUI has not previously enforced + 'scss/no-global-function-names': null, + 'scss/dollar-variable-empty-line-before': null, + 'scss/at-rule-conditional-no-parentheses': null, + 'scss/double-slash-comment-empty-line-before': null, + 'scss/at-if-no-null': null, + 'selector-not-notation': null, // Enforce comma notation for CSS-in-JS moving forward + }, + }, + { + files: ['**/*.styles.ts', '**/*.ts', '**/*.tsx'], + extends: ['stylelint-config-standard'], + customSyntax: 'postcss-styled-syntax', + rules: { + // Unfortunately, double slash comments must be replaced with standard CSS /* */ comments + // Otherwise we get a parsing error - see https://github.com/hudochenkov/postcss-styled-syntax#known-issues + 'no-invalid-double-slash-comments': true, + // Empty style keys should be allowed, as Emotion still uses them for generating classNames + 'no-empty-source': null, + // Don't lint casing on interpolated JS vars + 'function-name-case': ['lower', { ignoreFunctions: [cssInJsVarRegex] }], + 'function-no-unknown': [true, { ignoreFunctions: [cssInJsVarRegex] }], + 'value-keyword-case': [ + 'lower', + { + ignoreProperties: ['font-family'], + ignoreKeywords: [camelCaseValueRegex], + }, + ], + // This is set to deprecate after stylelint v16, but in the meanwhile, is helpful + // for finding extraneous semicolons after utils that already output semicolons (e.g. logicalCSS()) + 'no-extra-semicolons': true, + + // Emotion uses the `label` property to generate the output className string + 'property-no-unknown': [true, { ignoreProperties: 'label' }], + }, + }, + ], +}; diff --git a/packages/eui-theme-borealis/LICENSE.txt b/packages/eui-theme-borealis/LICENSE.txt new file mode 100644 index 00000000000..74327a8f6f3 --- /dev/null +++ b/packages/eui-theme-borealis/LICENSE.txt @@ -0,0 +1,6 @@ +Source code in this repository is covered by (i) a dual license under the Server +Side Public License, v 1 and the Elastic License 2.0 or (ii) an Apache License +2.0 compatible license or (iii) solely under the Elastic License 2.0, in each +case, as noted in the applicable header. The default throughout the repository +is a dual license under the Server Side Public License, v 1 and the Elastic +License 2.0, unless the header specifies another license. diff --git a/packages/eui-theme-borealis/README.md b/packages/eui-theme-borealis/README.md new file mode 100644 index 00000000000..cb7c237d4a2 --- /dev/null +++ b/packages/eui-theme-borealis/README.md @@ -0,0 +1,19 @@ +# EUI theme **Borealis** + +This package contains specific style tokens for the EUI theme **Borealis** which is exported as `EuiThemeBorealis`. + +## Installing dependencies + +Please run `yarn` to install dependencies: + +```shell +yarn +``` + +## Building helper packages + +Before you run scripts, it's mandatory to build local dependency packages: + +```shell +yarn workspaces foreach -Rti --from @elastic/eui-theme-borealis run build +``` \ No newline at end of file diff --git a/packages/eui-theme-borealis/package.json b/packages/eui-theme-borealis/package.json new file mode 100644 index 00000000000..d56f9924db2 --- /dev/null +++ b/packages/eui-theme-borealis/package.json @@ -0,0 +1,60 @@ +{ + "name": "@elastic/eui-theme-borealis", + "version": "0.0.1", + "description": "A visual theme for EUI", + "license": "SEE LICENSE IN LICENSE.txt", + "scripts": { + "build:workspaces": "yarn workspaces foreach -Rti --from @elastic/eui-theme-borealis --exclude @elastic/eui-theme-borealis run build", + "build": "tsc", + "build-pack": "yarn build && npm pack", + "lint": "yarn tsc --noEmit && yarn lint-es && yarn lint-sass", + "lint-es": "eslint --cache src/**/*.ts --max-warnings 0", + "lint-sass": "yarn stylelint \"**/*.scss\" --quiet-deprecation-warnings", + "test": "jest", + "pre-push": "yarn build:workspaces && yarn lint && yarn test" + }, + "repository": { + "type": "git", + "url": "https://github.com/elastic/eui.git", + "directory": "packages/eui-theme-borealis" + }, + "private": true, + "devDependencies": { + "@elastic/eui-theme-common": "workspace:^", + "@types/jest": "^29.5.12", + "@types/prettier": "2.7.3", + "@typescript-eslint/eslint-plugin": "^5.59.7", + "@typescript-eslint/parser": "^5.59.7", + "eslint": "^8.41.0", + "eslint-config-prettier": "^8.8.0", + "eslint-plugin-import": "^2.27.5", + "eslint-plugin-jest": "^28.5.0", + "eslint-plugin-local": "^1.0.0", + "eslint-plugin-prettier": "^4.2.1", + "jest": "^29.7.0", + "prettier": "^2.8.8", + "stylelint": "^15.7.0", + "stylelint-config-prettier-scss": "^1.0.0", + "stylelint-config-standard": "^33.0.0", + "stylelint-config-standard-scss": "^9.0.0", + "typescript": "^5.6.2" + }, + "peerDependencies": { + "@elastic/eui-theme-common": "0.0.1" + }, + "main": "lib/index.js", + "exports": { + "./lib/*": "./lib/*", + ".": { + "default": "./lib/index.js" + } + }, + "files": [ + "lib/", + "src/**/*.scss", + "README.md" + ], + "installConfig": { + "hoistingLimits": "workspaces" + } +} diff --git a/packages/eui-theme-borealis/src/index.ts b/packages/eui-theme-borealis/src/index.ts new file mode 100644 index 00000000000..1427ca8b1e9 --- /dev/null +++ b/packages/eui-theme-borealis/src/index.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 '@elastic/eui-theme-common'; + +import { colors } from './variables/_colors'; +import { animation } from './variables/_animation'; +import { breakpoint } from './variables/_breakpoint'; +import { base, size } from './variables/_size'; +import { border } from './variables/_borders'; +import { levels } from './variables/_levels'; +import { font } from './variables/_typography'; +import { focus } from './variables/_states'; + +export const EUI_THEME_BOREALIS_KEY = 'EUI_THEME_BOREALIS'; + +export const euiThemeBorealis: EuiThemeShape = { + colors, + base, + size, + border, + font, + animation, + breakpoint, + levels, + focus, +}; + +export const EuiThemeBorealis = buildTheme( + euiThemeBorealis, + EUI_THEME_BOREALIS_KEY +); diff --git a/packages/eui-theme-borealis/src/theme_dark.scss b/packages/eui-theme-borealis/src/theme_dark.scss new file mode 100644 index 00000000000..64e6b27d3a0 --- /dev/null +++ b/packages/eui-theme-borealis/src/theme_dark.scss @@ -0,0 +1,8 @@ + +// color mode specific variables +@import './variables/colors_dark'; + + +// Global styling +@import 'node_modules/@elastic/eui-theme-common/src/global_styling/index'; +@import './variables/index'; \ No newline at end of file diff --git a/packages/eui-theme-borealis/src/theme_light.scss b/packages/eui-theme-borealis/src/theme_light.scss new file mode 100644 index 00000000000..29ab275fe73 --- /dev/null +++ b/packages/eui-theme-borealis/src/theme_light.scss @@ -0,0 +1,7 @@ +// color mode specific variables +@import './variables/colors_light'; + + +// Global styling +@import 'node_modules/@elastic/eui-theme-common/src/global_styling/index'; +@import './variables/index'; \ No newline at end of file diff --git a/packages/eui-theme-borealis/src/variables/_animation.ts b/packages/eui-theme-borealis/src/variables/_animation.ts new file mode 100644 index 00000000000..84933441db3 --- /dev/null +++ b/packages/eui-theme-borealis/src/variables/_animation.ts @@ -0,0 +1,31 @@ +/* + * 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 type { + _EuiThemeAnimationSpeeds, + _EuiThemeAnimationEasings, + _EuiThemeAnimation, +} from '@elastic/eui-theme-common'; + +export const animation_speed: _EuiThemeAnimationSpeeds = { + extraFast: '90ms', + fast: '150ms', + normal: '250ms', + slow: '350ms', + extraSlow: '500ms', +}; + +export const animation_ease: _EuiThemeAnimationEasings = { + 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/packages/eui-theme-borealis/src/variables/_borders.ts b/packages/eui-theme-borealis/src/variables/_borders.ts new file mode 100644 index 00000000000..35926695e77 --- /dev/null +++ b/packages/eui-theme-borealis/src/variables/_borders.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 { + computed, + type _EuiThemeBorder, + sizeToPixel, +} from '@elastic/eui-theme-common'; + +export const border: _EuiThemeBorder = { + color: computed(([lightShade]) => lightShade, ['colors.lightShade']), + width: { + thin: '1px', + thick: '2px', + }, + radius: { + medium: computed(sizeToPixel(0.375)), + small: computed(sizeToPixel(0.25)), + }, + thin: computed( + ([width, color]) => `${width.thin} solid ${color}`, + ['border.width', 'border.color'] + ), + thick: computed( + ([width, color]) => `${width.thick} solid ${color}`, + ['border.width', 'border.color'] + ), + editable: computed( + ([width, color]) => `${width.thick} dotted ${color}`, + ['border.width', 'border.color'] + ), +}; diff --git a/packages/eui-theme-borealis/src/variables/_breakpoint.ts b/packages/eui-theme-borealis/src/variables/_breakpoint.ts new file mode 100644 index 00000000000..facd8e14ba4 --- /dev/null +++ b/packages/eui-theme-borealis/src/variables/_breakpoint.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 type { _EuiThemeBreakpoints } from '@elastic/eui-theme-common'; + +export const breakpoint: _EuiThemeBreakpoints = { + xl: 1200, + l: 992, + m: 768, + s: 575, + xs: 0, +}; diff --git a/packages/eui-theme-borealis/src/variables/_buttons.scss b/packages/eui-theme-borealis/src/variables/_buttons.scss new file mode 100644 index 00000000000..f42b0c3264c --- /dev/null +++ b/packages/eui-theme-borealis/src/variables/_buttons.scss @@ -0,0 +1,4 @@ +$euiButtonColorDisabled: $euiColorDisabled; +$euiButtonColorDisabledText: $euiColorDisabledText; +$euiButtonDefaultTransparency: .8; +$euiButtonFontWeight: $euiFontWeightMedium; diff --git a/packages/eui-theme-borealis/src/variables/_colors.ts b/packages/eui-theme-borealis/src/variables/_colors.ts new file mode 100644 index 00000000000..09ff1a3e2f5 --- /dev/null +++ b/packages/eui-theme-borealis/src/variables/_colors.ts @@ -0,0 +1,19 @@ +/* + * 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 type { _EuiThemeColors } from '@elastic/eui-theme-common'; + +import { light_colors } from './_colors_light'; +import { dark_colors } from './_colors_dark'; + +export const colors: _EuiThemeColors = { + ghost: '#FFF', + ink: '#000', + LIGHT: light_colors, + DARK: dark_colors, +}; diff --git a/packages/eui-theme-borealis/src/variables/_colors_dark.scss b/packages/eui-theme-borealis/src/variables/_colors_dark.scss new file mode 100644 index 00000000000..e648b1b0841 --- /dev/null +++ b/packages/eui-theme-borealis/src/variables/_colors_dark.scss @@ -0,0 +1,49 @@ +// stylelint-disable color-no-hex + +// This extra import allows any variables that are created via functions to work when loaded into JS +@import 'node_modules/@elastic/eui-theme-common/src/global_styling/functions/index'; + +// These colors stay the same no matter the theme +$euiColorGhost: #FFF !default; +$euiColorInk: #000 !default; + +// Core +$euiColorPrimary: #0A0 !default; // test color for distinction +$euiColorAccent: #F68FBE !default; + +// Status +$euiColorSuccess: #7DDED8 !default; +$euiColorWarning: #F3D371 !default; +$euiColorDanger: #F86B63 !default; + +// Grays +$euiColorEmptyShade: #1D1E24 !default; +$euiColorLightestShade: #25262E !default; +$euiColorLightShade: #343741 !default; +$euiColorMediumShade: #535966 !default; +$euiColorDarkShade: #98A2B3 !default; +$euiColorDarkestShade: #D4DAE5 !default; +$euiColorFullShade: #FFF !default; + +// Backgrounds +$euiPageBackgroundColor: shade($euiColorLightestShade, 45%) !default; +$euiColorHighlight: #2E2D25 !default; + +// Variations from core +$euiTextColor: #DFE5EF !default; +$euiTitleColor: $euiTextColor !default; +$euiTextSubduedColor: makeHighContrastColor($euiColorMediumShade) !default; +$euiColorDisabled: #515761 !default; + +// Contrasty text variants +$euiColorPrimaryText: makeHighContrastColor($euiColorPrimary) !default; +$euiColorSuccessText: makeHighContrastColor($euiColorSuccess) !default; +$euiColorAccentText: makeHighContrastColor($euiColorAccent) !default; +$euiColorWarningText: makeHighContrastColor($euiColorWarning) !default; +$euiColorDangerText: makeHighContrastColor($euiColorDanger) !default; +$euiColorDisabledText: makeDisabledContrastColor($euiColorDisabled) !default; +$euiLinkColor: $euiColorPrimaryText !default; + +// Charts +$euiColorChartLines: $euiColorLightShade !default; +$euiColorChartBand: tint($euiColorLightestShade, 2.5%) !default; diff --git a/packages/eui-theme-borealis/src/variables/_colors_dark.ts b/packages/eui-theme-borealis/src/variables/_colors_dark.ts new file mode 100644 index 00000000000..88a280d27c2 --- /dev/null +++ b/packages/eui-theme-borealis/src/variables/_colors_dark.ts @@ -0,0 +1,73 @@ +/* + * 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, + type _EuiThemeBrandColors, + type _EuiThemeBrandTextColors, + type _EuiThemeShadeColors, + type _EuiThemeSpecialColors, + type _EuiThemeTextColors, + type _EuiThemeColorsMode, +} from '@elastic/eui-theme-common'; + +/* + * DARK THEME + */ + +export const dark_brand_colors: _EuiThemeBrandColors = { + primary: '#00aa00', // test color for distinction + accent: '#F68FBE', + success: '#7DDED8', + warning: '#F3D371', + danger: '#F86B63', +}; + +export const dark_brand_text_colors: _EuiThemeBrandTextColors = { + // temp. static values to remove dependency on makeHighContrastColor + primaryText: '#36a2ef', + accentText: '#f68fbe', + successText: '#7dded8', + warningText: '#f3d371', + dangerText: '#f86b63', +}; + +export const dark_shades: _EuiThemeShadeColors = { + emptyShade: '#1D1E24', + lightestShade: '#25262E', + lightShade: '#343741', + mediumShade: '#535966', + darkShade: '#98A2B3', + darkestShade: '#D4DAE5', + fullShade: '#FFF', +}; + +export const dark_special_colors: _EuiThemeSpecialColors = { + // temp. static values to remove dependency on shade and makeDisabledContrastColor + body: '#141519', + highlight: '#2E2D25', + disabled: '#515761', + disabledText: '#515761', + shadow: computed(({ colors }) => colors.ink), +}; + +export const dark_text_colors: _EuiThemeTextColors = { + text: '#DFE5EF', + title: computed(([text]) => text, ['colors.text']), + subduedText: '#81858f', // temp. static value to remove dependency makeDisabledContrastColor + link: computed(([primaryText]) => primaryText, ['colors.primaryText']), +}; + +export const dark_colors: _EuiThemeColorsMode = { + ...dark_brand_colors, + ...dark_shades, + ...dark_special_colors, + // Need to come after special colors so they can react to `body` + ...dark_brand_text_colors, + ...dark_text_colors, +}; diff --git a/packages/eui-theme-borealis/src/variables/_colors_light.scss b/packages/eui-theme-borealis/src/variables/_colors_light.scss new file mode 100644 index 00000000000..610ff5327bf --- /dev/null +++ b/packages/eui-theme-borealis/src/variables/_colors_light.scss @@ -0,0 +1,49 @@ +// stylelint-disable color-no-hex + +// This extra import allows any variables that are created via functions to work when loaded into JS +@import 'node_modules/@elastic/eui-theme-common/src/global_styling/functions/index'; + +// These colors stay the same no matter the theme +$euiColorGhost: #FFF !default; +$euiColorInk: #000 !default; + +// Core +$euiColorPrimary: #0F0 !default; // test color for distinction +$euiColorAccent: #F04E98 !default; + +// Status +$euiColorSuccess: #00BFB3 !default; +$euiColorWarning: #FEC514 !default; +$euiColorDanger: #BD271E !default; + +// Grays +$euiColorEmptyShade: #FFF !default; +$euiColorLightestShade: #F5F7FA !default; +$euiColorLightShade: #D3DAE6 !default; +$euiColorMediumShade: #98A2B3 !default; +$euiColorDarkShade: #69707D !default; +$euiColorDarkestShade: #343741 !default; +$euiColorFullShade: #000 !default; + +// Backgrounds +$euiPageBackgroundColor: tint($euiColorLightestShade, 50%) !default; +$euiColorHighlight: tint($euiColorWarning, 90%) !default; + +// Every color below must be based mathematically on the set above and in a particular order. +$euiTextColor: $euiColorDarkestShade !default; +$euiTitleColor: shade($euiTextColor, 50%) !default; +$euiTextSubduedColor: $euiColorDarkShade !default; +$euiColorDisabled: #ABB4C4 !default; + +// Contrasty text variants +$euiColorPrimaryText: makeHighContrastColor($euiColorPrimary) !default; +$euiColorSuccessText: makeHighContrastColor($euiColorSuccess) !default; +$euiColorAccentText: makeHighContrastColor($euiColorAccent) !default; +$euiColorWarningText: makeHighContrastColor($euiColorWarning) !default; +$euiColorDangerText: makeHighContrastColor($euiColorDanger) !default; +$euiColorDisabledText: makeDisabledContrastColor($euiColorDisabled) !default; +$euiLinkColor: $euiColorPrimaryText !default; + +// Charts +$euiColorChartLines: shade($euiColorLightestShade, 3%) !default; +$euiColorChartBand: $euiColorLightestShade !default; diff --git a/packages/eui-theme-borealis/src/variables/_colors_light.ts b/packages/eui-theme-borealis/src/variables/_colors_light.ts new file mode 100644 index 00000000000..95715428e71 --- /dev/null +++ b/packages/eui-theme-borealis/src/variables/_colors_light.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, + type _EuiThemeBrandColors, + type _EuiThemeBrandTextColors, + type _EuiThemeShadeColors, + type _EuiThemeSpecialColors, + type _EuiThemeTextColors, + type _EuiThemeColorsMode, +} from '@elastic/eui-theme-common'; + +/* + * LIGHT THEME + */ + +export const brand_colors: _EuiThemeBrandColors = { + primary: '#00ff00', // test color for distinction + accent: '#F04E98', + success: '#00BFB3', + warning: '#FEC514', + danger: '#BD271E', +}; + +export const brand_text_colors: _EuiThemeBrandTextColors = { + // temp. static values to remove dependency on makeHighContrastColor + primaryText: '#006bb8', + accentText: '#ba3d76', + successText: '#00BFB3', + warningText: '#83650a', + dangerText: '#bd271e', +}; + +export const shade_colors: _EuiThemeShadeColors = { + emptyShade: '#FFF', + lightestShade: '#F1F4FA', + lightShade: '#D3DAE6', + mediumShade: '#98A2B3', + darkShade: '#69707D', + darkestShade: '#343741', + fullShade: '#000', +}; + +export const special_colors: _EuiThemeSpecialColors = { + // temp. static values to remove dependency on tint and makeDisabledContrastColor + body: '#f7f8fc', + highlight: '#fff9e8', + disabled: '#ABB4C4', + disabledText: '#a2abba', + shadow: computed(({ colors }) => colors.ink), +}; + +export const text_colors: _EuiThemeTextColors = { + text: computed(([darkestShade]) => darkestShade, ['colors.darkestShade']), + // temp. static values to remove dependency on tint and makeDisabledContrastColor + title: '#1a1c21', + subduedText: '#646a77', + link: computed(([primaryText]) => primaryText, ['colors.primaryText']), +}; + +export const light_colors: _EuiThemeColorsMode = { + ...brand_colors, + ...shade_colors, + ...special_colors, + // Need to come after special colors so they can react to `body` + ...brand_text_colors, + ...text_colors, +}; diff --git a/packages/eui-theme-borealis/src/variables/_index.scss b/packages/eui-theme-borealis/src/variables/_index.scss new file mode 100644 index 00000000000..9768a81e8ee --- /dev/null +++ b/packages/eui-theme-borealis/src/variables/_index.scss @@ -0,0 +1,7 @@ +// Import base theme first, then override +@import 'node_modules/@elastic/eui-theme-common/src/global_styling/variables/index'; +@import 'states'; + +@import 'buttons'; +@import 'page'; +@import 'typography'; diff --git a/packages/eui-theme-borealis/src/variables/_levels.ts b/packages/eui-theme-borealis/src/variables/_levels.ts new file mode 100644 index 00000000000..1f4e2db0bb7 --- /dev/null +++ b/packages/eui-theme-borealis/src/variables/_levels.ts @@ -0,0 +1,21 @@ +/* + * 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 type { _EuiThemeLevels } from '@elastic/eui-theme-common'; + +export const levels: _EuiThemeLevels = { + toast: 9000, + modal: 8000, + mask: 6000, + navigation: 6000, + menu: 2000, + header: 1000, + flyout: 1000, + maskBelowHeader: 1000, + content: 0, +}; diff --git a/packages/eui-theme-borealis/src/variables/_page.scss b/packages/eui-theme-borealis/src/variables/_page.scss new file mode 100644 index 00000000000..318cae55681 --- /dev/null +++ b/packages/eui-theme-borealis/src/variables/_page.scss @@ -0,0 +1 @@ +$euiPageDefaultMaxWidth: map-get($euiBreakpoints, 'xl'); diff --git a/packages/eui-theme-borealis/src/variables/_size.ts b/packages/eui-theme-borealis/src/variables/_size.ts new file mode 100644 index 00000000000..1b3fe6a8ea2 --- /dev/null +++ b/packages/eui-theme-borealis/src/variables/_size.ts @@ -0,0 +1,29 @@ +/* + * 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, + sizeToPixel, + type _EuiThemeBase, + type _EuiThemeSizes, +} from '@elastic/eui-theme-common'; + +export const base: _EuiThemeBase = 16; + +export const size: _EuiThemeSizes = { + 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/packages/eui-theme-borealis/src/variables/_states.scss b/packages/eui-theme-borealis/src/variables/_states.scss new file mode 100644 index 00000000000..13e30784936 --- /dev/null +++ b/packages/eui-theme-borealis/src/variables/_states.scss @@ -0,0 +1,11 @@ +// Color when not using currentColor +$euiFocusRingColor: $euiColorPrimaryText; + +// Sizing +$euiFocusRingAnimStartSize: 2px; +$euiFocusRingSize: 2px; + +// Transparency +$euiFocusTransparency: lightOrDarkTheme(.9, .8); +$euiFocusTransparencyPercent: lightOrDarkTheme(90%, 80%); +$euiFocusBackgroundColor: transparentize($euiColorPrimary, $euiFocusTransparency); diff --git a/packages/eui-theme-borealis/src/variables/_states.ts b/packages/eui-theme-borealis/src/variables/_states.ts new file mode 100644 index 00000000000..776a00ffeca --- /dev/null +++ b/packages/eui-theme-borealis/src/variables/_states.ts @@ -0,0 +1,22 @@ +/* + * 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, + sizeToPixel, + type _EuiThemeFocus, +} from '@elastic/eui-theme-common'; + +export const focus: _EuiThemeFocus = { + // Focus ring + color: 'currentColor', + width: computed(sizeToPixel(0.125)), + // Focus background + transparency: { LIGHT: 0.1, DARK: 0.2 }, + backgroundColor: 'rgba(0, 119, 204, 0.1)', // temp. static value to remove dependency on transparentize +}; diff --git a/packages/eui-theme-borealis/src/variables/_typography.scss b/packages/eui-theme-borealis/src/variables/_typography.scss new file mode 100644 index 00000000000..824f56b42da --- /dev/null +++ b/packages/eui-theme-borealis/src/variables/_typography.scss @@ -0,0 +1,64 @@ +// Finally start using the non-beta version of Inter +$euiFontFamily: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol' !default; + +// Font sizes -- scale is loosely based on Major Third (1.250) with a base of 14px +// Base list is an altered scale based on 16px to match the resulted values below +$euiTextScale: 2.125, 1.6875, 1.375, 1.125, 1, .875, .75 !default; + +$euiFontSize: $euiSize - 2; // 14px + +$euiFontSizeXS: floor($euiFontSize * .86); // 12px // h6 +$euiFontSizeS: floor($euiFontSize * 1); // 14px // h5 --> Now the same as the base $euiFontSize +$euiFontSizeM: ceil($euiFontSize * 1.14); // 16px // h4 +$euiFontSizeL: ceil($euiFontSize * 1.57); // 22px // h3 +$euiFontSizeXL: floor($euiFontSize * 1.93); // 27px // h2 +$euiFontSizeXXL: floor($euiFontSize * 2.43); // 34px // h1 + +$euiBodyLineHeight: 1.142857143 !default; // 16px from a 14px base font size to ensure it aligns to our 16px grid + +$euiCodeFontWeightRegular: 400; +$euiCodeFontWeightBold: 700; + +// Normally functions are imported before variables in `_index.scss` files +// But because they need to consume some typography variables they need to live here +@function convertToRem($size) { + @return #{$size / $euiFontSize}rem; +} + +// Use 8px increments for base gridline +@function lineHeightFromBaseline($multiplier: 3) { + @return convertToRem(($euiSize / 2) * $multiplier); +} + +$euiTitles: ( + 'xxxs': ( + 'font-size': $euiFontSizeXS, + 'line-height': lineHeightFromBaseline(2), + 'font-weight': $euiFontWeightBold, + ), + 'xxs': ( + 'font-size': $euiFontSizeS, + 'line-height': lineHeightFromBaseline(3), + 'font-weight': $euiFontWeightBold, + ), + 'xs': ( + 'font-size': $euiFontSizeM, + 'line-height': lineHeightFromBaseline(3), + 'font-weight': $euiFontWeightBold, + ), + 's': ( + 'font-size': $euiFontSizeL, + 'line-height': lineHeightFromBaseline(4), + 'font-weight': $euiFontWeightBold, + ), + 'm': ( + 'font-size': $euiFontSizeXL, + 'line-height': lineHeightFromBaseline(4), + 'font-weight': $euiFontWeightBold, + ), + 'l': ( + 'font-size': $euiFontSizeXXL, + 'line-height': lineHeightFromBaseline(5), + 'font-weight': $euiFontWeightBold, + ), +); diff --git a/packages/eui-theme-borealis/src/variables/_typography.ts b/packages/eui-theme-borealis/src/variables/_typography.ts new file mode 100644 index 00000000000..6454c7b239c --- /dev/null +++ b/packages/eui-theme-borealis/src/variables/_typography.ts @@ -0,0 +1,62 @@ +/* + * 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, + type _EuiThemeFont, + type _EuiThemeFontBase, + type _EuiThemeFontScales, + type _EuiThemeFontWeights, +} from '@elastic/eui-theme-common'; + +// Typographic scale -- loosely based on Major Third (1.250) +export const fontScale: _EuiThemeFontScales = { + xxxs: 0.5625, + xxs: 0.6875, + xs: 0.75, + s: 0.875, + m: 1, + l: 1.375, + xl: 1.6875, + xxl: 2.125, +}; + +// Families & base font settings +export const fontBase: _EuiThemeFontBase = { + family: "'Inter', BlinkMacSystemFont, Helvetica, Arial, sans-serif", + familyCode: "'Roboto Mono', Menlo, Courier, monospace", + familySerif: 'Georgia, Times, Times New Roman, serif', + + // Careful using ligatures. Code editors like ACE will often error because of width calculations + featureSettings: "'calt' 1, 'kern' 1, 'liga' 1", + defaultUnits: 'rem', + + baseline: computed(([base]) => base / 4, ['base']), + lineHeightMultiplier: 1.5, +}; + +export const fontWeight: _EuiThemeFontWeights = { + light: 300, + regular: 400, + medium: 500, + semiBold: 600, + bold: 700, +}; + +export const font: _EuiThemeFont = { + ...fontBase, + scale: fontScale, + weight: fontWeight, + body: { + scale: 's', + weight: 'regular', + }, + title: { + weight: 'bold', + }, +}; diff --git a/packages/eui-theme-borealis/tsconfig.json b/packages/eui-theme-borealis/tsconfig.json new file mode 100644 index 00000000000..4415cdcb934 --- /dev/null +++ b/packages/eui-theme-borealis/tsconfig.json @@ -0,0 +1,20 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "compilerOptions": { + "rootDir": "src", + "outDir": "lib", + "target": "ES2020", + "lib": ["ESNext", "DOM"], + "moduleResolution": "Node", + "declaration": true, + "sourceMap": true, + "noEmitHelpers": true, + "incremental": true, + "esModuleInterop": true, + "strict": true, + "skipLibCheck": true, + "tsBuildInfoFile": "lib/.tsbuildinfo" + }, + "include": ["src"], + "exclude": ["node_modules"], + } \ No newline at end of file diff --git a/packages/eui-theme-common/.babelrc.js b/packages/eui-theme-common/.babelrc.js new file mode 100644 index 00000000000..0fc74dd1fe9 --- /dev/null +++ b/packages/eui-theme-common/.babelrc.js @@ -0,0 +1,27 @@ +module.exports = { + // We need to preserve comments as they are used by webpack for + // naming chunks during code-splitting. The compression step during + // bundling will remove them later. + comments: true, + + presets: [ + [ + '@babel/env', + { + // `targets` property set via `.browserslistrc` + useBuiltIns: process.env.NO_COREJS_POLYFILL ? false : 'usage', + corejs: !process.env.NO_COREJS_POLYFILL ? '3.6' : undefined, + modules: process.env.BABEL_MODULES + ? process.env.BABEL_MODULES === 'false' + ? false + : process.env.BABEL_MODULES + : 'commonjs', // babel's default is commonjs + }, + ], + ['@babel/react', { runtime: 'classic' }], + [ + '@babel/typescript', + { isTSX: true, allExtensions: true, allowDeclareFields: true }, + ], + ], +}; diff --git a/packages/eui-theme-common/.eslintignore b/packages/eui-theme-common/.eslintignore new file mode 100644 index 00000000000..c6be7e9a761 --- /dev/null +++ b/packages/eui-theme-common/.eslintignore @@ -0,0 +1,10 @@ +dist +node_modules +lib +types +**/*.d.ts +package.json +scripts +.eslintrc.js +babel.config.js +jest.config.js diff --git a/packages/eui-theme-common/.eslintplugin.js b/packages/eui-theme-common/.eslintplugin.js new file mode 100644 index 00000000000..25ddbe3db65 --- /dev/null +++ b/packages/eui-theme-common/.eslintplugin.js @@ -0,0 +1,3 @@ +exports.rules = { + 'require-license-header': require('./scripts/eslint-plugin-local/require_license_header.js'), +}; diff --git a/packages/eui-theme-common/.eslintrc.js b/packages/eui-theme-common/.eslintrc.js new file mode 100644 index 00000000000..e33853c0255 --- /dev/null +++ b/packages/eui-theme-common/.eslintrc.js @@ -0,0 +1,114 @@ +const SSPL_ELASTIC_2_0_LICENSE_HEADER = ` +/* + * 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. + */ +`; + +module.exports = { + parser: '@typescript-eslint/parser', + parserOptions: { + project: ['./tsconfig.json'], + ecmaFeatures: { + jsx: true, + }, + }, + settings: { + 'import/resolver': { + node: { + extensions: ['.ts', '.tsx', '.js', '.json'], + }, + }, + }, + extends: [ + 'plugin:@typescript-eslint/recommended', + // Prettier options need to come last, in order to override other style rules + 'plugin:prettier/recommended', + ], + plugins: ['local', 'import'], + rules: { + 'block-scoped-var': 'error', + camelcase: 'off', + 'dot-notation': ['error', { allowKeywords: true }], + eqeqeq: ['error', 'always', { null: 'ignore' }], + 'guard-for-in': 'error', + 'new-cap': ['error', { capIsNewExceptions: ['Private'] }], + 'no-caller': 'error', + 'no-const-assign': 'error', + 'no-debugger': 'error', + 'no-empty': ['error', { allowEmptyCatch: true }], + 'no-eval': 'error', + 'no-extend-native': 'error', + 'no-global-assign': 'error', + 'no-loop-func': 'error', + 'no-restricted-globals': ['error', 'context'], + 'no-script-url': 'error', + 'no-sequences': 'error', + 'no-var': 'error', + 'no-with': 'error', + 'prefer-const': 'error', + 'prefer-template': 'error', + strict: ['error', 'never'], + 'valid-typeof': 'error', + + 'local/require-license-header': [ + 'warn', + { + license: SSPL_ELASTIC_2_0_LICENSE_HEADER, + }, + ], + + 'import/no-unresolved': ['error', { amd: true, commonjs: true }], + 'import/namespace': 'error', + 'import/default': 'error', + 'import/export': 'error', + 'import/no-named-as-default': 'error', + 'import/no-named-as-default-member': 'error', + 'import/no-duplicates': 'error', + + '@typescript-eslint/array-type': ['error', { default: 'array-simple' }], + '@typescript-eslint/ban-types': 'off', + '@typescript-eslint/explicit-function-return-type': 'off', + '@typescript-eslint/explicit-member-accessibility': 'off', + '@typescript-eslint/indent': 'off', + '@typescript-eslint/ban-tslint-comment': 'error', + '@typescript-eslint/no-empty-interface': 'off', + '@typescript-eslint/no-explicit-any': 'off', + '@typescript-eslint/no-non-null-assertion': 'off', + '@typescript-eslint/no-parameter-properties': 'off', + '@typescript-eslint/no-triple-slash-reference': 'off', + '@typescript-eslint/no-unused-vars': [ + 'error', + { argsIgnorePattern: '^_', ignoreRestSiblings: true }, + ], + '@typescript-eslint/no-use-before-define': 'off', + '@typescript-eslint/no-empty-function': 'off', + // It"s all very well saying that some types are trivially inferrable, + // but being explicit is still clearer. + '@typescript-eslint/no-inferrable-types': 'off', + '@typescript-eslint/explicit-module-boundary-types': 'off', + '@typescript-eslint/naming-convention': 'off', + '@typescript-eslint/ban-ts-comment': [ + 'error', + { + 'ts-ignore': 'allow-with-description', + 'ts-expect-error': 'allow-with-description', + }, + ], + '@typescript-eslint/consistent-type-exports': [ + 'error', + { fixMixedExportsWithInlineTypeSpecifier: false }, + ], + }, + overrides: [ + { + files: ['*.d.ts'], + rules: { + 'react/prefer-es6-class': 'off', + }, + }, + ], +}; diff --git a/packages/eui-theme-common/.gitignore b/packages/eui-theme-common/.gitignore new file mode 100644 index 00000000000..4450fe8d868 --- /dev/null +++ b/packages/eui-theme-common/.gitignore @@ -0,0 +1,11 @@ +# Dependencies +/node_modules + +# Production +/lib + +yarn-debug.log* +yarn-error.log* + +# Build-related files +.eslintcache \ No newline at end of file diff --git a/packages/eui-theme-common/.prettierrc b/packages/eui-theme-common/.prettierrc new file mode 100644 index 00000000000..b2f0fa8f00e --- /dev/null +++ b/packages/eui-theme-common/.prettierrc @@ -0,0 +1,7 @@ +{ + "parser": "typescript", + "printWidth": 80, + "semi": true, + "singleQuote": true, + "trailingComma": "es5" +} diff --git a/packages/eui-theme-common/.stylelintrc.js b/packages/eui-theme-common/.stylelintrc.js new file mode 100644 index 00000000000..42adc41f28c --- /dev/null +++ b/packages/eui-theme-common/.stylelintrc.js @@ -0,0 +1,163 @@ +const camelCaseRegex = '^[a-z][\\w-]*$'; // Note: also allows `_` as part of BEM naming +const camelCaseValueRegex = '/^[a-z][\\w.]*$/'; // Note: also allows `.` for JS objects +const cssInJsVarRegex = '/\\${[a-zA-Z]+/'; + +module.exports = { + // @see https://stylelint.io/user-guide/rules + rules: { + // Enforce camelCase naming + 'selector-class-pattern': camelCaseRegex, + 'keyframes-name-pattern': camelCaseRegex, + 'custom-property-pattern': camelCaseRegex, + + // Opinionated rules + 'declaration-no-important': true, + 'max-nesting-depth': [ + 2, + { + ignore: ['blockless-at-rules', 'pseudo-classes'], + }, + ], + 'block-no-empty': true, + 'selector-no-qualifying-type': [ + true, + { + ignore: ['attribute'], // Allows input[type=search] + }, + ], + + // Non-Prettier newline rules + // Put a line-break between sections of CSS, but allow quick one-liners for legibility + 'rule-empty-line-before': [ + 'always-multi-line', + { + ignore: ['first-nested', 'after-comment'], + }, + ], + 'comment-empty-line-before': null, + 'declaration-empty-line-before': null, + + // Value preferences + 'number-max-precision': null, + // Attempt to catch/flag non-variable color values + 'color-named': 'never', + 'color-no-hex': true, + // Prefer lowercase values, except for font names and currentColor + 'value-keyword-case': [ + 'lower', + { + ignoreProperties: ['font-family', '/^\\$eui[\\w]+/'], // Allow fonts and Sass variables + ignoreKeywords: ['currentColor'], + }, + ], + 'declaration-block-no-duplicate-properties': [ + true, + { + ignore: ['consecutive-duplicates'], // We occasionally use duplicate property values for cross-browser fallbacks + }, + ], + + // TODO: It may be worth investigating and updating these rules to their more modern counterparts + 'color-function-notation': 'legacy', + 'alpha-value-notation': 'number', + + // Disable various opinionated extended stylelint rules that EUI has not previously enforced + 'no-descending-specificity': null, + 'keyframe-selector-notation': null, + 'declaration-block-no-redundant-longhand-properties': null, + }, + overrides: [ + { + // TODO: Remove Sass-specific config & rules once we're completely off Sass + files: ['**/*.scss'], + ignoreFiles: [ + 'generator-eui/**/*.scss', + 'src/global_styling/react_date_picker/**/*.scss', + 'src/themes/amsterdam/global_styling/react_date_picker/**/*.scss', + 'src/components/date_picker/react-datepicker/**/*.scss', + ], + extends: [ + 'stylelint-config-standard-scss', + 'stylelint-config-prettier-scss', + ], + // @see https://github.com/stylelint-scss/stylelint-scss#list-of-rules + rules: { + // Casing + 'scss/dollar-variable-pattern': camelCaseRegex, + 'scss/at-mixin-pattern': camelCaseRegex, + 'scss/at-function-pattern': camelCaseRegex, + 'function-name-case': [ + 'lower', + { + ignoreFunctions: [`/${camelCaseRegex}/`, 'MIN'], + }, + ], + + // Whitespace/newlines + 'scss/at-if-closing-brace-space-after': 'always-intermediate', + 'scss/operator-no-unspaced': true, + + // Formatting rules deprecated as of v15 - keep them in Sass styles just in case until end of migration + // @see https://github.com/stylelint/stylelint/blob/main/docs/user-guide/rules.md#deprecated + 'color-hex-case': 'upper', + 'string-quotes': 'single', + // 2 spaces for indentation + indentation: [2, { indentInsideParens: 'once-at-root-twice-in-block' }], + // Mimic 1tbs `} else {` brace style, like our JS + 'block-opening-brace-space-before': 'always', + 'block-closing-brace-newline-before': 'always-multi-line', + // Ensure multiple selectors on one line each + 'selector-list-comma-newline-before': 'never-multi-line', + 'selector-list-comma-newline-after': 'always', + // Trim unnecessary newlines/whitespace + 'block-closing-brace-empty-line-before': 'never', + 'max-empty-lines': 1, + 'no-eol-whitespace': true, + // Enforce spacing around various syntax symbols (colons, operators, etc.) + 'declaration-colon-space-after': 'always-single-line', + 'declaration-colon-space-before': 'never', + 'function-calc-no-unspaced-operator': true, + 'selector-combinator-space-before': 'always', + 'selector-combinator-space-after': 'always', + // Ensure trailing semicolons are always present on non-oneliners + 'declaration-block-semicolon-newline-after': 'always-multi-line', + + // Disable various opinionated extended stylelint rules that EUI has not previously enforced + 'scss/no-global-function-names': null, + 'scss/dollar-variable-empty-line-before': null, + 'scss/at-rule-conditional-no-parentheses': null, + 'scss/double-slash-comment-empty-line-before': null, + 'scss/at-if-no-null': null, + 'selector-not-notation': null, // Enforce comma notation for CSS-in-JS moving forward + }, + }, + { + files: ['**/*.styles.ts', '**/*.ts', '**/*.tsx'], + extends: ['stylelint-config-standard'], + customSyntax: 'postcss-styled-syntax', + rules: { + // Unfortunately, double slash comments must be replaced with standard CSS /* */ comments + // Otherwise we get a parsing error - see https://github.com/hudochenkov/postcss-styled-syntax#known-issues + 'no-invalid-double-slash-comments': true, + // Empty style keys should be allowed, as Emotion still uses them for generating classNames + 'no-empty-source': null, + // Don't lint casing on interpolated JS vars + 'function-name-case': ['lower', { ignoreFunctions: [cssInJsVarRegex] }], + 'function-no-unknown': [true, { ignoreFunctions: [cssInJsVarRegex] }], + 'value-keyword-case': [ + 'lower', + { + ignoreProperties: ['font-family'], + ignoreKeywords: [camelCaseValueRegex], + }, + ], + // This is set to deprecate after stylelint v16, but in the meanwhile, is helpful + // for finding extraneous semicolons after utils that already output semicolons (e.g. logicalCSS()) + 'no-extra-semicolons': true, + + // Emotion uses the `label` property to generate the output className string + 'property-no-unknown': [true, { ignoreProperties: 'label' }], + }, + }, + ], +}; diff --git a/packages/eui-theme-common/LICENSE.txt b/packages/eui-theme-common/LICENSE.txt new file mode 100644 index 00000000000..74327a8f6f3 --- /dev/null +++ b/packages/eui-theme-common/LICENSE.txt @@ -0,0 +1,6 @@ +Source code in this repository is covered by (i) a dual license under the Server +Side Public License, v 1 and the Elastic License 2.0 or (ii) an Apache License +2.0 compatible license or (iii) solely under the Elastic License 2.0, in each +case, as noted in the applicable header. The default throughout the repository +is a dual license under the Server Side Public License, v 1 and the Elastic +License 2.0, unless the header specifies another license. diff --git a/packages/eui-theme-common/README.md b/packages/eui-theme-common/README.md new file mode 100644 index 00000000000..294446d4b30 --- /dev/null +++ b/packages/eui-theme-common/README.md @@ -0,0 +1,11 @@ +# EUI common theming functionality and styling + +This package contains common code related to theming and styling. + +## Installing dependencies + +Please run `yarn` to install dependencies: + +```shell +yarn +``` \ No newline at end of file diff --git a/packages/eui-theme-common/babel.config.js b/packages/eui-theme-common/babel.config.js new file mode 100644 index 00000000000..8165fe45577 --- /dev/null +++ b/packages/eui-theme-common/babel.config.js @@ -0,0 +1,6 @@ +module.exports = { + presets: [ + ['@babel/preset-env', { targets: { node: 'current' } }], + '@babel/preset-typescript', + ], +}; diff --git a/packages/eui-theme-common/jest.config.js b/packages/eui-theme-common/jest.config.js new file mode 100644 index 00000000000..1b5b81ebf06 --- /dev/null +++ b/packages/eui-theme-common/jest.config.js @@ -0,0 +1,7 @@ +const config = { + moduleFileExtensions: ['ts', 'tsx', 'js', 'json'], + testEnvironment: 'node', + testMatch: ['**/*.test.js', '**/*.test.ts'], +}; + +module.exports = config; diff --git a/packages/eui-theme-common/package.json b/packages/eui-theme-common/package.json new file mode 100644 index 00000000000..2ddafb93b6d --- /dev/null +++ b/packages/eui-theme-common/package.json @@ -0,0 +1,78 @@ +{ + "name": "@elastic/eui-theme-common", + "version": "0.0.1", + "description": "EUI theme common", + "license": "SEE LICENSE IN LICENSE.txt", + "scripts": { + "build:clean": "rimraf lib/", + "build": "yarn build:clean && yarn build:compile && yarn build:compile:cjs && yarn build:types", + "build:compile": "tsc --project ./tsconfig.json", + "build:compile:cjs": "NODE_ENV=production babel src --out-dir=lib/cjs --extensions .js,.ts,.tsx --source-maps", + "build:types": "NODE_ENV=production tsc --project tsconfig.types.json", + "build-pack": "yarn build && npm pack", + "lint": "yarn tsc --noEmit && yarn lint-es && yarn lint-sass", + "lint-es": "eslint --cache src/**/*.ts --max-warnings 0", + "lint-sass": "yarn stylelint \"**/*.scss\" --quiet-deprecation-warnings", + "test": "jest", + "pre-push": "yarn lint && yarn test" + }, + "repository": { + "type": "git", + "url": "https://github.com/elastic/eui.git", + "directory": "packages/eui-theme-common" + }, + "private": true, + "devDependencies": { + "@babel/cli": "^7.21.5", + "@babel/core": "^7.21.8", + "@babel/preset-env": "^7.21.5", + "@babel/preset-react": "^7.18.6", + "@babel/preset-typescript": "^7.21.5", + "@emotion/react": "^11.11.0", + "@types/jest": "^29.5.12", + "@types/prettier": "2.7.3", + "@types/react": "^16.9 || ^17.0 || ^18.0", + "@types/react-dom": "^16.9 || ^17.0 || ^18.0", + "@typescript-eslint/eslint-plugin": "^5.59.7", + "@typescript-eslint/parser": "^5.59.7", + "eslint": "^8.41.0", + "eslint-config-prettier": "^8.8.0", + "eslint-plugin-import": "^2.27.5", + "eslint-plugin-jest": "^28.5.0", + "eslint-plugin-local": "^1.0.0", + "eslint-plugin-prettier": "^4.2.1", + "jest": "^29.7.0", + "prettier": "^2.8.8", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "rimraf": "^6.0.1", + "stylelint": "^15.7.0", + "stylelint-config-prettier-scss": "^1.0.0", + "stylelint-config-standard": "^33.0.0", + "stylelint-config-standard-scss": "^9.0.0", + "typescript": "4.5.3" + }, + "peerDependencies": { + "@emotion/react": "11.x", + "react": "^16.12 || ^17.0 || ^18.0", + "react-dom": "^16.12 || ^17.0 || ^18.0" + }, + "main": "lib/cjs/index.js", + "exports": { + "./lib/*": "./lib/*", + "./scripts/*": "./scripts/*", + ".": { + "require": "./lib/cjs/index.js", + "import": "./lib/esm/index.js", + "default": "./lib/cjs/index.js" + } + }, + "files": [ + "lib/", + "src/**/*.scss", + "README.md" + ], + "installConfig": { + "hoistingLimits": "workspaces" + } +} diff --git a/packages/eui-theme-common/scripts/eslint-plugin-local/require_license_header.js b/packages/eui-theme-common/scripts/eslint-plugin-local/require_license_header.js new file mode 100644 index 00000000000..23b83648dd1 --- /dev/null +++ b/packages/eui-theme-common/scripts/eslint-plugin-local/require_license_header.js @@ -0,0 +1,132 @@ +const eslintParser = require('@typescript-eslint/parser'); + +function assert(truth, message) { + if (truth) { + return; + } + + const error = new Error(message); + error.failedAssertion = true; + throw error; +} + +function normalizeWhitespace(string) { + return string.replace(/\s+/g, ' '); +} + +function init(context, program, initStep) { + try { + return initStep(); + } catch (error) { + if (error.failedAssertion) { + context.report({ + node: program, + message: error.message, + }); + } else { + throw error; + } + } +} + +function isHashbang(text) { + return text.trim().startsWith('#!') && !text.trim().includes('\n'); +} + +module.exports = { + meta: { + fixable: 'code', + schema: [ + { + type: 'object', + properties: { + license: { + type: 'string', + }, + }, + additionalProperties: false, + }, + ], + }, + create: (context) => { + return { + Program(program) { + const license = init(context, program, function () { + const options = context.options[0] || {}; + const license = options.license; + + assert(!!license, '"license" option is required'); + + const parsed = eslintParser.parse(license, { comment: true }); + assert( + !parsed.body.length, + '"license" option must only include a single comment' + ); + assert( + parsed.comments.length === 1, + '"license" option must only include a single comment' + ); + + return { + source: license, + nodeValue: normalizeWhitespace(parsed.comments[0].value), + }; + }); + + if (!license) { + return; + } + + const sourceCode = context.getSourceCode(); + const comment = sourceCode + .getAllComments() + .find( + (node) => normalizeWhitespace(node.value) === license.nodeValue + ); + + // no licence comment + if (!comment) { + context.report({ + message: 'File must start with a license header', + loc: { + start: { line: 1, column: 0 }, + end: { line: 1, column: sourceCode.lines[0].length - 1 }, + }, + fix(fixer) { + if (isHashbang(sourceCode.lines[0])) { + return undefined; + } + + return fixer.replaceTextRange([0, 0], license.source + '\n\n'); + }, + }); + return; + } + + // ensure there is nothing before the comment + const sourceBeforeNode = sourceCode + .getText() + .slice(0, sourceCode.getIndexFromLoc(comment.loc.start)); + if (sourceBeforeNode.length && !isHashbang(sourceBeforeNode)) { + context.report({ + node: comment, + message: 'License header must be at the very beginning of the file', + fix(fixer) { + // replace leading whitespace if possible + if (sourceBeforeNode.trim() === '') { + return fixer.replaceTextRange([0, sourceBeforeNode.length], ''); + } + + // inject content at top and remove node from current location + // if removing whitespace is not possible + return [ + fixer.remove(comment), + fixer.replaceTextRange([0, 0], license.source + '\n\n'), + ]; + }, + }); + } + }, + }; + }, +}; diff --git a/packages/eui-theme-common/scripts/eslint-plugin-local/require_license_header.test.js b/packages/eui-theme-common/scripts/eslint-plugin-local/require_license_header.test.js new file mode 100644 index 00000000000..904dff17bc0 --- /dev/null +++ b/packages/eui-theme-common/scripts/eslint-plugin-local/require_license_header.test.js @@ -0,0 +1,177 @@ +const { RuleTester } = require('eslint'); +const rule = require('./require_license_header'); +const dedent = require('dedent'); + +const ruleTester = new RuleTester({ + parser: require.resolve('@typescript-eslint/parser'), +}); + +ruleTester.run('@kbn/eslint/require-license-header', rule, { + valid: [ + { + code: dedent` + /* license */ + + console.log('foo') + `, + + options: [{ license: '/* license */' }], + }, + { + code: dedent` + // license + + console.log('foo') + `, + + options: [{ license: '// license' }], + }, + ], + + invalid: [ + // missing license option + { + code: dedent` + console.log('foo') + `, + + options: [], + errors: [ + { + message: '"license" option is required', + }, + ], + }, + + // content cannot contain multiple block comments + { + code: dedent` + console.log('foo') + `, + + options: [{ license: '/* one *//* two */' }], + errors: [ + { + message: '"license" option must only include a single comment', + }, + ], + }, + + // content cannot contain multiple line comments + { + code: dedent` + console.log('foo') + `, + + options: [{ license: `// one\n// two` }], + errors: [ + { + message: '"license" option must only include a single comment', + }, + ], + }, + + // content cannot contain expressions + { + code: dedent` + console.log('foo') + `, + + options: [ + { + license: dedent` + /* license */ + console.log('hello world'); + `, + }, + ], + errors: [ + { + message: '"license" option must only include a single comment', + }, + ], + }, + + // content is not a single comment + { + code: dedent` + console.log('foo') + `, + + options: [{ license: `console.log('hello world');` }], + errors: [ + { + message: '"license" option must only include a single comment', + }, + ], + }, + + // missing license header + { + code: dedent` + console.log('foo') + `, + + options: [{ license: '/* license */' }], + errors: [ + { + message: 'File must start with a license header', + }, + ], + + output: dedent` + /* license */ + + console.log('foo') + `, + }, + + // strips newlines before the license comment + { + code: + '\n\n' + + dedent` + /* license */ + + console.log('foo') + `, + + options: [{ license: '/* license */' }], + errors: [ + { + message: 'License header must be at the very beginning of the file', + }, + ], + + output: dedent` + /* license */ + + console.log('foo') + `, + }, + + // moves license header before other nodes if necessary + { + code: dedent` + /* not license */ + /* license */ + console.log('foo') + `, + + options: [{ license: '/* license */' }], + errors: [ + { + message: 'License header must be at the very beginning of the file', + }, + ], + + output: dedent` + /* license */ + + /* not license */ + + console.log('foo') + `, + }, + ], +}); diff --git a/packages/eui-theme-common/src/global_styling/functions/_colors.scss b/packages/eui-theme-common/src/global_styling/functions/_colors.scss new file mode 100644 index 00000000000..25834e10add --- /dev/null +++ b/packages/eui-theme-common/src/global_styling/functions/_colors.scss @@ -0,0 +1,138 @@ +// Converting a normal hex color to RBG +@function hexToRGB($color) { + @return 'rgb%28#{round(red($color))}, #{round(green($color))}, #{round(blue($color))}%29'; +} + +// Mixes a provided color with white. +@function tint($color, $percent) { + @return mix($euiColorGhost, $color, $percent); +} + +// Mixes a provided color with black. +@function shade($color, $percent) { + @return mix($euiColorInk, $color, $percent); +} + +// For theming. Checks the text color and tells us whether it's light or dark. +// Based on that we either tint (add white) or shade (add black). +@function tintOrShade($color, $tint, $shade) { + @if (lightness($euiTextColor) > 50) { + @return shade($color, $shade); + } @else { + @return tint($color, $tint); + } +} + +// The reverse of the above +@function shadeOrTint($color, $shade, $tint) { + @if (lightness($euiTextColor) < 50) { + @return shade($color, $shade); + } @else { + @return tint($color, $tint); + } +} + +// Similar to above, but uses the light or dark color based +// on whether it's the light or dark theme +@function lightOrDarkTheme($lightColor, $darkColor) { + @if (lightness($euiTextColor) < 50) { + @return $lightColor; + } @else { + @return $darkColor; + } +} + +// Calculates luminance, which is better than brightness for checking colors +// pow, nth functions come from the _math.scss functions +@function luminance($color) { + // Formula: http://www.w3.org/TR/2008/REC-WCAG20-20081211/#relativeluminancedef + $rgba: red($color), green($color), blue($color); + $rgba2: (); + + @for $i from 1 through 3 { + $rgb: nth($rgba, $i); + $rgb: $rgb / 255; + + $rgb: if($rgb < .03928, $rgb / 12.92, pow(($rgb + .055) / 1.055, 2.4)); + + $rgba2: append($rgba2, $rgb); + } + + @return .2126 * nth($rgba2, 1) + .7152 * nth($rgba2, 2) + .0722 * nth($rgba2, 3); +} + +// Calculate contrast +@function contrastRatio($background, $foreground) { + $backgroundLum: luminance($background) + .05; + $foregroundLum: luminance($foreground) + .05; + + @return max($backgroundLum, $foregroundLum) / min($backgroundLum, $foregroundLum); +} + +// Given $color, decide whether $lightText or $darkText should be used as the text color +// ex: chooseLightOrDarkText(#EEE, #FFF, #000) would return #000 because it has +// a higher contrast than #FFF against a #EEE background. +@function chooseLightOrDarkText($background, $lightText: $euiColorGhost, $darkText: $euiColorInk) { + $lightContrast: contrastRatio($background, $lightText); + $darkContrast: contrastRatio($background, $darkText); + + @if ($lightContrast > $darkContrast) { + @return $lightText; + } @else { + @return $darkText; + } +} + +// Given a $foreground and a $background, make the $foreground AA accessibility by slightly +// adjusting it till the contrast is high enough +// By default it will compare against the page background color + +// ex: makeContrastColor($lightPink, #FFF) would continually shade the pink until +// it had higher than 4.5 contrast on a white background. +$euiContrastRatioText: 4.5; +@function makeHighContrastColor($foreground, $background: $euiPageBackgroundColor, $ratio: $euiContrastRatioText) { + $contrast: contrastRatio($foreground, $background); + + // Determine the lightness factor of the background color first to + // determine whether to shade or tint the foreground. + $brightness: lightness($background); + + $highContrastTextColor: $foreground; + + @while ($contrast < $ratio) { + @if ($brightness > 50) { + $highContrastTextColor: shade($highContrastTextColor, 5%); + } @else { + $highContrastTextColor: tint($highContrastTextColor, 5%); + } + + $contrast: contrastRatio($highContrastTextColor, $background); + + @if (lightness($highContrastTextColor) < 5) { + @warn 'High enough contrast could not be determined. Most likely your background color does not adjust for light mode.'; + @return $highContrastTextColor; + } + + @if (lightness($highContrastTextColor) > 95) { + @warn 'High enough contrast could not be determined. Most likely your background color does not adjust for dark mode.'; + @return $highContrastTextColor; + } + } + + @return $highContrastTextColor; +} + +// Graphics such as stand alone icons and pieces of a graph only need a minimum ratio of 3:1 with its background. +// Therefore, we can reuse the `makeHighContrastColor()` function but only attain a min contrast of 3.0. +// It is still recommended to use `makeHighContrastColor()` to attain a 4.5:1 ratio if the graphic is small or thinly stroked. +// https://www.w3.org/WAI/GL/low-vision-a11y-tf/wiki/Informational_Graphic_Contrast_(Minimum) +$euiContrastRatioGraphic: 3; +@function makeGraphicContrastColor($color, $background: $euiPageBackgroundColor) { + @return makeHighContrastColor($color, $background, $euiContrastRatioGraphic); +} + +// Disabled content only needs a contrast of at least 2 because there is no interaction available +$euiContrastRatioDisabled: 2; +@function makeDisabledContrastColor($color, $background: $euiPageBackgroundColor) { + @return makeHighContrastColor($color, $background, $euiContrastRatioDisabled); +} diff --git a/packages/eui-theme-common/src/global_styling/functions/_index.scss b/packages/eui-theme-common/src/global_styling/functions/_index.scss new file mode 100644 index 00000000000..de8260b2bba --- /dev/null +++ b/packages/eui-theme-common/src/global_styling/functions/_index.scss @@ -0,0 +1,5 @@ +// Math needs to be first in the load order +@import 'math'; + +// Using math, we have functions to manipulate contrast / luminosity for accessibility +@import 'colors'; diff --git a/packages/eui-theme-common/src/global_styling/functions/_math.scss b/packages/eui-theme-common/src/global_styling/functions/_math.scss new file mode 100644 index 00000000000..cdec36f3e60 --- /dev/null +++ b/packages/eui-theme-common/src/global_styling/functions/_math.scss @@ -0,0 +1 @@ +@import 'math_pow'; \ No newline at end of file diff --git a/packages/eui-theme-common/src/global_styling/functions/_math_pow.scss b/packages/eui-theme-common/src/global_styling/functions/_math_pow.scss new file mode 100644 index 00000000000..2e2d784a845 --- /dev/null +++ b/packages/eui-theme-common/src/global_styling/functions/_math_pow.scss @@ -0,0 +1,82 @@ +/** +The MIT License (MIT) + +Copyright (c) 2015 strarsis https://github.com/strarsis/sass-math-pow + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + */ + +@function pow($number, $exp) { + $exp1: round($exp); + $result: powInt($number, $exp1); + + @if ($exp1 != $exp) { + $result: $result * mathExp(($exp - $exp1) * mathLn($number)); + } + + @return $result; +} + +@function powInt($number, $exp) { + @if $exp == 0 { + @return 1; + } @else if $exp < 0 { + @return 1 / powInt($number, -$exp); + } @else { + $e: floor($exp / 2); + $pow: pow($number, $e); + @if $e * 2 == $exp { + @return $pow * $pow; + } @else { + @return $pow * $pow * $number; + } + } +} + +@function mathExp($value) { + $item: 1; + $result: 1; + + @for $index from 1 to 100 { + $item: $item * $value / $index; + $result: $result + $item; + } + + @return $result; +} + +@function mathLn($value) { + $tenExp: 0; + $lnTen: 2.30258509; + + @while ($value > 1) { + $tenExp: $tenExp + 1; + $value: $value / 10; + } + + $item: -1; + $result: 0; + + @for $index from 1 to 100 { + $item: $item * (1 - $value); + $result: $result + $item / $index; + } + + @return $result + $tenExp * $lnTen; +} diff --git a/packages/eui-theme-common/src/global_styling/functions/index.ts b/packages/eui-theme-common/src/global_styling/functions/index.ts new file mode 100644 index 00000000000..5b07f8b8473 --- /dev/null +++ b/packages/eui-theme-common/src/global_styling/functions/index.ts @@ -0,0 +1,9 @@ +/* + * 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 * from './size'; diff --git a/packages/eui-theme-common/src/global_styling/functions/size.ts b/packages/eui-theme-common/src/global_styling/functions/size.ts new file mode 100644 index 00000000000..a96b5b85452 --- /dev/null +++ b/packages/eui-theme-common/src/global_styling/functions/size.ts @@ -0,0 +1,26 @@ +/* + * 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. + */ + +/* TODO: move to a shared module package */ + +/** + * Calculates the `px` value based on a scale multiplier + * @param scale - The font scale multiplier + * * + * @param themeOrBase - Theme base value + * * + * @returns string - Rem unit aligned to baseline + */ + +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`; + }; diff --git a/packages/eui-theme-common/src/global_styling/index.scss b/packages/eui-theme-common/src/global_styling/index.scss new file mode 100644 index 00000000000..5c29e0435a5 --- /dev/null +++ b/packages/eui-theme-common/src/global_styling/index.scss @@ -0,0 +1,18 @@ +// Core + +// Functions need to be first, since we use them in our variables and mixin definitions +@import 'functions/index'; + +// Variables come next, and are used in some mixins +@import 'variables/index'; + +// Mixins provide generic code expansion through helpers +@import 'mixins/index'; + +// Utility classes provide one-off selectors for common css problems +@import 'utility/index'; + +// The reset file has moved to global_styles.tsx + +// Customization of the React Date Picker +@import 'react_date_picker/index'; diff --git a/packages/eui-theme-common/src/global_styling/index.ts b/packages/eui-theme-common/src/global_styling/index.ts new file mode 100644 index 00000000000..47fa2bee007 --- /dev/null +++ b/packages/eui-theme-common/src/global_styling/index.ts @@ -0,0 +1,11 @@ +/* + * 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 * from './types'; +export * from './functions'; +export * from './variables'; diff --git a/packages/eui-theme-common/src/global_styling/mixins/_button.scss b/packages/eui-theme-common/src/global_styling/mixins/_button.scss new file mode 100644 index 00000000000..47c87b6e742 --- /dev/null +++ b/packages/eui-theme-common/src/global_styling/mixins/_button.scss @@ -0,0 +1,149 @@ +// Provides a solid reset and base for handling sizing layout +// Does not include any visual styles +@mixin euiButtonBase { + display: inline-block; + appearance: none; + cursor: pointer; + height: $euiButtonHeight; + line-height: $euiButtonHeight; // prevents descenders from getting cut off + text-align: center; + white-space: nowrap; + max-width: 100%; + vertical-align: middle; +} + +// Adds the focus (and hover) animation for translating up 1px +@mixin euiButtonFocus { + @include euiCanAnimate { + transition: transform $euiAnimSpeedNormal ease-in-out, background-color $euiAnimSpeedNormal ease-in-out; + + &:hover:not(:disabled) { + transform: translateY(-1px); + } + + &:focus { + animation: euiButtonActive $euiAnimSpeedNormal $euiAnimSlightBounce; + } + + &:active:not(:disabled) { + transform: translateY(1px); + } + } +} + +// All of the button base styles including the base, focus, font, and initial styles +// Does not include individual alterations like color or sizes +@mixin euiButton { + @include euiButtonBase; + @include euiFont; + @include euiFontSize; + @include euiButtonFocus; + + font-weight: $euiButtonFontWeight; + text-decoration: none; + outline-offset: -1px; + + &:hover:not(:disabled), + &:focus { + text-decoration: underline; + } +} + +// Correctly lays out the contents of a button when using the proper dom elements of: +// +// 1. Apply margin to all but last item in the flex. +// 2. Margin gets flipped because of the row-reverse. +@mixin euiButtonContent($isReverse: false) { + height: 100%; + width: 100%; + vertical-align: middle; + + .euiButtonContent__icon, + .euiButtonContent__spinner { + flex-shrink: 0; // Ensures the icons/spinner don't scale down below their intended size + } + + @if ($isReverse) { + flex-direction: row-reverse; + + > * + * { + margin-inline-start: 0; // 1, 2 + margin-inline-end: $euiSizeS; // 1, 2 + } + } @else { + display: flex; + justify-content: center; + align-items: center; + + > * + * { + margin-inline-start: $euiSizeS; // 1 + } + } +} + +@mixin euiButtonContentDisabled { + pointer-events: auto; + cursor: not-allowed; + + &:hover, + &:focus, + &:focus-within { + text-decoration: none; + } + + .euiButtonContent__spinner { + border-color: euiLoadingSpinnerBorderColors(currentColor); + } +} + +/* + * Creates the Amsterdam style of button with a transparent background + */ +@mixin euiButtonDefaultStyle($color: 'primary', $includeStates: true, $transparency: $euiButtonDefaultTransparency) { + $backgroundColor: $color; + + @if (map-has-key($euiButtonTypes, $color)) { + $backgroundColor: map-get($euiButtonTypes, $color); + } + + $percentConversion: $transparency * 100%; + // This variable simulates the possibly darkest background the button could be on + // Simulates the 20% opaque color on top of the page background color + $backgroundColorSimulated: mix($euiPageBackgroundColor, $backgroundColor, $percentConversion); + // Then we can calculate the darkest text color needed + color: makeHighContrastColor($backgroundColor, $backgroundColorSimulated); + // But still use transparency + background-color: transparentize($backgroundColor, $transparency); + + @if ($includeStates) { + &:not([class*='isDisabled']) { + &:hover, + &:focus { + // Duplicated from inert state simply to override default theme + background-color: transparentize($backgroundColor, $transparency); + } + } + } +} + +/* + * Creates the Amsterdam style of fill button + */ +@mixin euiButtonFillStyle($color: 'primary') { + $backgroundColor: $color; + + @if (map-has-key($euiButtonTypes, $color)) { + $backgroundColor: map-get($euiButtonTypes, $color); + } + + background-color: $backgroundColor; + color: chooseLightOrDarkText($backgroundColor); +} + +// Keyframe animation declarations can be found in +// utility/animations.scss diff --git a/packages/eui-theme-common/src/global_styling/mixins/_form.scss b/packages/eui-theme-common/src/global_styling/mixins/_form.scss new file mode 100644 index 00000000000..326a8491f87 --- /dev/null +++ b/packages/eui-theme-common/src/global_styling/mixins/_form.scss @@ -0,0 +1,273 @@ +@mixin euiFormControlLayoutPadding($numOfIcons, $side: 'right', $compressed: false) { + $iconSize: $euiSize; + $iconPadding: $euiFormControlPadding; + $marginBetweenIcons: $euiFormControlPadding / 2; + + @if ($compressed) { + $iconPadding: $euiFormControlCompressedPadding; + } + + @if variable-exists(numOfIcons) == false { + @error '$numOfIcons:integer (1-3) must be provided to @mixin euiFormControlLayoutPadding().'; + } @else if $numOfIcons == 1 { + padding-#{$side}: $iconPadding + $iconSize + $iconPadding; + } @else if $numOfIcons == 2 { + padding-#{$side}: $iconPadding + $iconSize + $marginBetweenIcons + $iconSize + $iconPadding; + } @else if $numOfIcons == 3 { + padding-#{$side}: $iconPadding + $iconSize + $marginBetweenIcons + $iconSize + $marginBetweenIcons + $iconSize + $iconPadding; + } +} + +@mixin euiPlaceholderPerBrowser { + // stylelint-disable selector-no-vendor-prefix + // Each prefix must be its own content block + &::-webkit-input-placeholder { @content; opacity: 1; } + &::-moz-placeholder { @content; opacity: 1; } + &:-ms-input-placeholder { @content; opacity: 1; } + &:-moz-placeholder { @content; opacity: 1; } + &::placeholder { @content; opacity: 1; } +} + +@function euiFormControlGradient($color: $euiColorPrimary) { + @return linear-gradient(to top, + $color, + $color 2px, + transparent 2px, + transparent 100% + ); +} + +@mixin euiFormControlText { + @include euiFont; + font-size: $euiFontSizeS; + color: $euiTextColor; + + @include euiPlaceholderPerBrowser { + color: $euiFormControlPlaceholderText; + } +} + +@mixin euiFormControlSize( + $height: $euiFormControlHeight, + $includeAlternates: false +) { + // Default + max-width: $euiFormMaxWidth; + width: 100%; + height: $height; + + @if ($includeAlternates) { + &--fullWidth { + max-width: 100%; + } + + &--compressed { + height: $euiFormControlCompressedHeight; + } + + &--inGroup { + height: 100%; + } + } +} + +@mixin euiFormControlWithIcon($isIconOptional: false, $side: 'left', $compressed: false) { + @if ($isIconOptional) { + @at-root { + #{&}--withIcon { + @include euiFormControlLayoutPadding(1, $side, $compressed); + } + } + } @else { + @include euiFormControlLayoutPadding(1, $side, $compressed); + } +} + +@mixin euiFormControlIsLoading($isNextToIcon: false) { + @at-root { + #{&}-isLoading { + @if ($isNextToIcon) { + @include euiFormControlLayoutPadding(2); + } @else { + @include euiFormControlLayoutPadding(1); + } + } + + #{&}-isLoading#{&}--compressed { + @if ($isNextToIcon) { + @include euiFormControlLayoutPadding(2, $compressed: true); + } @else { + @include euiFormControlLayoutPadding(1, $compressed: true); + } + } + } +} + +// 1. Must supply both values to background-size or some browsers apply the single value to both directions + +@mixin euiFormControlDefaultShadow($borderOnly: false) { + background-color: $euiFormBackgroundColor; + background-repeat: no-repeat; + background-size: 0% 100%; // 1 + + @if ($borderOnly) { + box-shadow: inset 0 0 0 1px $euiFormBorderColor; + } @else { + box-shadow: + #{$euiFormControlBoxShadow}, + inset 0 0 0 1px $euiFormBorderColor; + } + + transition: + box-shadow $euiAnimSpeedFast ease-in, + background-image $euiAnimSpeedFast ease-in, + background-size $euiAnimSpeedFast ease-in, + background-color $euiAnimSpeedFast ease-in; + + // Fixes bug in Firefox where adding a transition to the background-color + // caused a flash of differently styled dropdown. + @supports (-moz-appearance: none) { + // List *must* be in the same order as the above. + transition-property: box-shadow, background-image, background-size; + } +} + +@mixin euiFormControlFocusStyle($borderOnly: false) { + background-color: tintOrShade($euiColorEmptyShade, 0%, 40%); + background-image: euiFormControlGradient(); + background-size: 100% 100%; // 1 + outline: none; // Blanket remove all outlines relying on our own bottom border + + @if ($borderOnly) { + box-shadow: inset 0 0 0 1px $euiFormBorderColor; + } @else { + box-shadow: inset 0 0 0 1px $euiFormBorderColor; + } +} + +@mixin euiFormControlInvalidStyle { + background-image: euiFormControlGradient($euiColorDanger); + background-size: 100%; +} + +@mixin euiFormControlDisabledTextStyle { + color: $euiFormControlDisabledColor; + -webkit-text-fill-color: $euiFormControlDisabledColor; // Required for Safari +} + +@mixin euiFormControlDisabledStyle { + @include euiFormControlDisabledTextStyle; + cursor: not-allowed; + background: $euiFormBackgroundDisabledColor; + box-shadow: inset 0 0 0 1px $euiFormBorderDisabledColor; + + @include euiPlaceholderPerBrowser { + color: $euiFormControlDisabledColor; + } +} + +@mixin euiFormControlReadOnlyStyle { + cursor: default; + color: $euiTextColor; + -webkit-text-fill-color: $euiTextColor; // Required for Safari + // Use transparency since there is no border and in case form is on a non-white background + background: $euiFormBackgroundReadOnlyColor; + border-color: transparent; + box-shadow: inset 0 0 0 1px $euiFormBorderDisabledColor; +} + +// 2. Override invalid state with focus state. + +@mixin euiFormControlStyle($borderOnly: false, $includeStates: true, $includeSizes: true) { + @include euiFormControlSize($includeAlternates: $includeSizes); + @include euiFormControlDefaultShadow; + @include euiFormControlText; + + border: none; + border-radius: $euiFormControlBorderRadius; + padding: $euiFormControlPadding; + + @if ($includeStates) { + &:invalid { // 2 + @include euiFormControlInvalidStyle; + } + + &:focus { // 2 + @include euiFormControlFocusStyle; + } + + &:disabled { + @include euiFormControlDisabledStyle; + } + + &[readOnly] { + @include euiFormControlReadOnlyStyle; + } + + // Needs to be set for autofill + &:-webkit-autofill { + -webkit-text-fill-color: lightOrDarkTheme($euiColorDarkestShade, $euiColorLightShade); + + ~ .euiFormControlLayoutIcons { + color: lightOrDarkTheme($euiColorDarkestShade, $euiColorLightShade); + } + } + } + + @if ($includeSizes) { + &--compressed { + @include euiFormControlStyleCompressed($borderOnly, $includeStates); + } + + &--inGroup { + // stylelint-disable-next-line declaration-no-important + box-shadow: none !important; + border-radius: 0; + } + } +} + +@mixin euiFormControlStyleCompressed($borderOnly: false, $includeStates: true) { + @include euiFormControlDefaultShadow($borderOnly: true); + padding: $euiFormControlCompressedPadding; + border-radius: $euiFormControlCompressedBorderRadius; + + @if ($includeStates) { + &:invalid { // 2 + @include euiFormControlInvalidStyle; + } + + &:focus { // 2 + @include euiFormControlFocusStyle($borderOnly: true); + } + + &:disabled { + @include euiFormControlDisabledStyle; + } + + &[readOnly] { + @include euiFormControlReadOnlyStyle; + } + } +} + +@mixin euiHiddenSelectableInput { + position: absolute; + // stylelint-disable-next-line declaration-no-important + opacity: 0 !important; // Make sure it's still hidden when :disabled + width: 100%; + height: 100%; + cursor: pointer; +} + +// Adjusts form controls border radius +@mixin euiFormControlSideBorderRadius($borderRadius, $side, $internal: false) { + @if $internal == true { + $borderRadius: $borderRadius - 1; + } + @if $side == 'left' { + border-radius: $borderRadius 0 0 $borderRadius; + } @else if $side == 'right' { + border-radius: 0 $borderRadius $borderRadius 0; + } +} diff --git a/packages/eui-theme-common/src/global_styling/mixins/_helpers.scss b/packages/eui-theme-common/src/global_styling/mixins/_helpers.scss new file mode 100644 index 00000000000..00688ef63ef --- /dev/null +++ b/packages/eui-theme-common/src/global_styling/mixins/_helpers.scss @@ -0,0 +1,126 @@ +// Helper mixins + +// Set scroll bar appearance on Chrome (and firefox). +@mixin euiScrollBar($thumbColor: $euiColorDarkShade, $trackBackgroundColor: transparent, $size: 'thin') { + // 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 + scrollbar-color: transparentize($thumbColor, .5) $trackBackgroundColor; // Firefox support + + @if ($size == 'thin') { + scrollbar-width: thin; + } + + // stylelint-disable selector-no-vendor-prefix + &::-webkit-scrollbar { + width: $euiScrollBar; + height: $euiScrollBar; + } + + &::-webkit-scrollbar-thumb { + background-color: transparentize($thumbColor, .5); + background-clip: content-box; + border-radius: $euiScrollBar; + + @if ($size == 'thin') { + border: $euiScrollBarCornerThin solid $trackBackgroundColor; + } @else { + border: $euiScrollBarCorner solid $trackBackgroundColor; + } + } + + &::-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. + * 2. Force the `:focus-visible` when the `tabindex=0` (is tabbable) + */ + +// Just overflow and scrollbars +@mixin euiYScroll { + @include euiScrollBar; + height: 100%; + overflow-y: auto; + overflow-x: hidden; + + &:focus { + outline: none; /* 1 */ + } + + &[tabindex='0']:focus:focus-visible { + outline-style: auto; /* 2 */ + } +} + +@mixin euiXScroll { + @include euiScrollBar; + overflow-x: auto; + + &:focus { + outline: none; /* 1 */ + } + + &[tabindex='0']:focus:focus-visible { + outline-style: auto; /* 2 */ + } +} + +// The full overflow with shadow +@mixin euiYScrollWithShadows { + @include euiYScroll; + @include euiOverflowShadow('y'); +} + +@mixin euiXScrollWithShadows { + @include euiXScroll; + @include euiOverflowShadow('x'); +} + +/** + * For quickly applying a full-height element whether using flex or not + */ +@mixin euiFullHeight { + height: 100%; + flex: 1 1 auto; + overflow: hidden; +} + +// Hiding elements offscreen to only be read by screen reader +// See https://github.com/elastic/eui/pull/5130 and https://github.com/elastic/eui/pull/5152 for more info +@mixin euiScreenReaderOnly { + // Take the element out of the layout + position: absolute; + // Keep it vertically inline + top: auto; + // Chrome requires a left value, and Selenium (used by Kibana's FTR) requires an off-screen position for its .getVisibleText() to not register SR-only text + left: -10000px; + // The element must have a size (for some screen readers) + width: 1px; + height: 1px; + // But reduce the visible size to nothing + clip: rect(0 0 0 0); + clip-path: inset(50%); + // And ensure no overflows occur + overflow: hidden; + // Chrome requires the negative margin to not cause overflows of parent containers + margin: -1px; +} + +// Doesn't have reduced motion turned on +@mixin euiCanAnimate { + @media screen and (prefers-reduced-motion: no-preference) { + @content; + } +} + +// Does have reduced motion turned on +@mixin euiCantAnimate { + @media screen and (prefers-reduced-motion: reduce) { + @content; + } +} diff --git a/packages/eui-theme-common/src/global_styling/mixins/_index.scss b/packages/eui-theme-common/src/global_styling/mixins/_index.scss new file mode 100644 index 00000000000..7d0cba8a92e --- /dev/null +++ b/packages/eui-theme-common/src/global_styling/mixins/_index.scss @@ -0,0 +1,14 @@ +@import 'responsive'; +@import 'shadow'; +@import 'size'; +@import 'typography'; +@import 'helpers'; +@import 'states'; + +@import 'button'; +@import 'form'; +@import 'loading'; +@import 'link'; +@import 'panel'; +@import 'range'; +@import 'tool_tip'; diff --git a/packages/eui-theme-common/src/global_styling/mixins/_link.scss b/packages/eui-theme-common/src/global_styling/mixins/_link.scss new file mode 100644 index 00000000000..98dac59b9cc --- /dev/null +++ b/packages/eui-theme-common/src/global_styling/mixins/_link.scss @@ -0,0 +1,11 @@ +@mixin euiLink { + text-align: left; + + &:hover { + text-decoration: underline; + } + + &:focus { + text-decoration: underline; + } +} diff --git a/packages/eui-theme-common/src/global_styling/mixins/_loading.scss b/packages/eui-theme-common/src/global_styling/mixins/_loading.scss new file mode 100644 index 00000000000..0f72a8433f7 --- /dev/null +++ b/packages/eui-theme-common/src/global_styling/mixins/_loading.scss @@ -0,0 +1,6 @@ +@function euiLoadingSpinnerBorderColors( + $main: $euiColorLightShade, + $highlight: $euiColorPrimary +) { + @return $highlight $main $main $main; +} diff --git a/packages/eui-theme-common/src/global_styling/mixins/_panel.scss b/packages/eui-theme-common/src/global_styling/mixins/_panel.scss new file mode 100644 index 00000000000..4eb0a5fb55a --- /dev/null +++ b/packages/eui-theme-common/src/global_styling/mixins/_panel.scss @@ -0,0 +1,55 @@ +@mixin euiPanel($selector) { + @if variable-exists(selector) == false { + @error 'A $selector must be provided to @mixin euiPanel().'; + } @else { + #{$selector} { + flex-grow: 1; + + &#{$selector}--flexGrowZero { + flex-grow: 0; + } + + &#{$selector}--hasShadow { + @include euiBottomShadowMedium; + } + + &#{$selector}--hasBorder { + border: $euiBorderThin; + box-shadow: none; + } + + &#{$selector}--isClickable { + // transition the shadow + transition: all $euiAnimSpeedFast $euiAnimSlightResistance; + + &:enabled { // This is a good selector for buttons since it doesn't exist on divs + // in case of button wrapper which inherently is inline-block and no width + display: block; + width: 100%; + text-align: left; + } + + &:hover, + &:focus { + @include euiBottomShadow; + transform: translateY(-2px); + cursor: pointer; + } + } + + // Border Radii + @each $modifier, $amount in $euiPanelBorderRadiusModifiers { + &#{$selector}--#{$modifier} { + border-radius: $amount; + } + } + + // Background colors + @each $modifier, $amount in $euiPanelBackgroundColorModifiers { + &#{$selector}--#{$modifier} { + background-color: $amount; + } + } + } + } +} diff --git a/packages/eui-theme-common/src/global_styling/mixins/_range.scss b/packages/eui-theme-common/src/global_styling/mixins/_range.scss new file mode 100644 index 00000000000..ec47e39e2d6 --- /dev/null +++ b/packages/eui-theme-common/src/global_styling/mixins/_range.scss @@ -0,0 +1,62 @@ +/* +The CSS in JS version of this file lives in: + - src/components/form/range/range.styles.ts + +The following files still use the Sass version: + - src/themes/amsterdam/overrides/_color_stops.scss + - src/themes/amsterdam/overrides/_hue.scss +*/ + +@mixin euiRangeTrackSize($compressed: false) { + height: $euiRangeTrackHeight; + width: $euiRangeTrackWidth; + + @if ($compressed) { + height: $euiRangeTrackCompressedHeight; + } +} + +@mixin euiRangeTrackPerBrowser { + &::-webkit-slider-runnable-track { @content; } + &::-moz-range-track { @content; } + &::-ms-fill-lower { @content; } + &::-ms-fill-upper { @content; } +} + +@mixin euiRangeThumbBorder { + border: 2px solid $euiRangeThumbBorderColor; +} + +@mixin euiRangeThumbBoxShadow { + box-shadow: + 0 0 0 1px $euiRangeThumbBorderColor, + 0 2px 2px -1px rgba($euiShadowColor, .2), + 0 1px 5px -2px rgba($euiShadowColor, .2); +} + +@mixin euiRangeThumbFocusBoxShadow { + box-shadow: 0 0 0 2px $euiFocusRingColor; +} + +@mixin euiRangeThumbStyle { + @include euiRangeThumbBoxShadow; + @include euiRangeThumbBorder; + cursor: pointer; + background-color: $euiRangeThumbBackgroundColor; + padding: 0; + height: $euiRangeThumbHeight; + width: $euiRangeThumbWidth; + box-sizing: border-box; // required for firefox or the border makes the width and height to increase +} + +@mixin euiRangeThumbPerBrowser { + &::-webkit-slider-thumb { @content; } + &::-moz-range-thumb { @content; } + &::-ms-thumb { @content; } +} + +@mixin euiRangeThumbFocus { + @include euiRangeThumbBorder; + @include euiRangeThumbFocusBoxShadow; + background-color: $euiColorPrimary; +} diff --git a/packages/eui-theme-common/src/global_styling/mixins/_responsive.scss b/packages/eui-theme-common/src/global_styling/mixins/_responsive.scss new file mode 100644 index 00000000000..0fa3a9b08a8 --- /dev/null +++ b/packages/eui-theme-common/src/global_styling/mixins/_responsive.scss @@ -0,0 +1,44 @@ +// A sem-complicated mixin for breakpoints, that takes any number of +// named breakpoints that exists in $euiBreakpoints. + +@mixin euiBreakpoint($sizes...) { + // Loop through each size parameter + @each $size in $sizes { + // Store the location of the size in the list to check against + $index: index($euiBreakpointKeys, $size); + + // Check to make sure it exists in the allowed breakpoint names + @if ( $index ) { + + // Set the min size to the value of the size + $minSize: map-get($euiBreakpoints, $size); + + // If it is the last item, don't give it a max-width + @if ( $index == length($euiBreakpointKeys) ) { + @media only screen and (min-width: $minSize) { + @content; + } + // If it's not the last item, add a max-width + } @else { + + // Set the max size to the value of the next size (-1px as to not overlap) + $maxSize: map-get($euiBreakpoints, nth($euiBreakpointKeys, $index + 1)) - 1px; + + // If it's the the first item, don't set a min-width + @if ( $index == 1 ) { + @media only screen and (max-width: $maxSize) { + @content; + } + // Otherwise it should have a min and max width + } @else { + @media only screen and (min-width: $minSize) and (max-width: $maxSize) { + @content; + } + } + } + // If it's not a known breakpoint, throw a warning + } @else { + @warn "euiBreakpoint(): '#{$size}' is not a valid size in $euiBreakpoints. Accepted values are '#{$euiBreakpointKeys}'"; + } + } +} diff --git a/packages/eui-theme-common/src/global_styling/mixins/_shadow.scss b/packages/eui-theme-common/src/global_styling/mixins/_shadow.scss new file mode 100644 index 00000000000..1bc64eb085b --- /dev/null +++ b/packages/eui-theme-common/src/global_styling/mixins/_shadow.scss @@ -0,0 +1,108 @@ +@function shadowOpacity($opacity) { + @if (lightness($euiTextColor) < 50) { + @return $opacity * 1; + } @else { + @return $opacity * 2.5; + } +} + +@mixin euiSlightShadow($color: $euiShadowColor) { + box-shadow: + 0 .8px .8px rgba($color, shadowOpacity(.04)), + 0 2.3px 2px rgba($color, shadowOpacity(.03)); +} + +@mixin euiBottomShadowSmall($color: $euiShadowColor) { + box-shadow: + 0 .7px 1.4px rgba($color, shadowOpacity(.07)), + 0 1.9px 4px rgba($color, shadowOpacity(.05)), + 0 4.5px 10px rgba($color, shadowOpacity(.05)); +} + +@mixin euiBottomShadowMedium($color: $euiShadowColor) { + box-shadow: + 0 .9px 4px -1px rgba($color, shadowOpacity(.08)), + 0 2.6px 8px -1px rgba($color, shadowOpacity(.06)), + 0 5.7px 12px -1px rgba($color, shadowOpacity(.05)), + 0 15px 15px -1px rgba($color, shadowOpacity(.04)); +} + +// Similar to shadow medium but without the bottom depth. Useful for popovers +// that drop UP rather than DOWN. +@mixin euiBottomShadowFlat($color: $euiShadowColor) { + box-shadow: + 0 0 .8px rgba($color, shadowOpacity(.06)), + 0 0 2px rgba($color, shadowOpacity(.04)), + 0 0 5px rgba($color, shadowOpacity(.04)), + 0 0 17px rgba($color, shadowOpacity(.03)); +} + +@mixin euiBottomShadow($color: $euiShadowColor) { + box-shadow: + 0 1px 5px rgba($color, shadowOpacity(.1)), + 0 3.6px 13px rgba($color, shadowOpacity(.07)), + 0 8.4px 23px rgba($color, shadowOpacity(.06)), + 0 23px 35px rgba($color, shadowOpacity(.05)); +} + +@mixin euiBottomShadowLarge( + $color: $euiShadowColor, + $opacity: 0, + $reverse: false +) { + @if ($reverse) { + box-shadow: + 0 -2.7px 9px rgba($color, shadowOpacity(.13)), + 0 -9.4px 24px rgba($color, shadowOpacity(.09)), + 0 -21.8px 43px rgba($color, shadowOpacity(.08)); + } @else { + box-shadow: + 0 2.7px 9px rgba($color, shadowOpacity(.13)), + 0 9.4px 24px rgba($color, shadowOpacity(.09)), + 0 21.8px 43px rgba($color, shadowOpacity(.08)); + } +} + +@mixin euiSlightShadowHover($color: $euiShadowColor) { + box-shadow: + 0 1px 5px rgba($color, shadowOpacity(.1)), + 0 3.6px 13px rgba($color, shadowOpacity(.07)), + 0 8.4px 23px rgba($color, shadowOpacity(.06)), + 0 23px 35px rgba($color, shadowOpacity(.05)); +} + +// stylelint-disable color-named +@mixin euiOverflowShadow($direction: 'y', $side: 'both') { + $hideHeight: $euiScrollBarCornerThin * 1.25; + $gradient: null; + $gradientStart: + transparentize(red, .9) 0%, + transparentize(red, 0) $hideHeight; + $gradientEnd: + transparentize(red, 0) calc(100% - #{$hideHeight}), + transparentize(red, .9) 100%; + @if ($side == 'both' or $side == 'start' or $side == 'end') { + @if ($side == 'both') { + $gradient: $gradientStart, $gradientEnd; + } @else if ($side == 'start') { + $gradient: $gradientStart; + } @else { + $gradient: $gradientEnd; + } + } @else { + @warn "euiOverflowShadow() expects side to be 'both', 'start' or 'end' but got '#{$side}'"; + } + + @if ($direction == 'y') { + mask-image: linear-gradient(to bottom, #{$gradient}); + } @else if ($direction == 'x') { + mask-image: linear-gradient(to right, #{$gradient}); + } @else { + @warn "euiOverflowShadow() expects direction to be 'y' or 'x' but got '#{$direction}'"; + } + + // Chrome+Edge has a very bizarre edge case bug where `mask-image` stops working + // This workaround forces a stacking context on the scrolling container, which + // hopefully addresses the bug. @see https://github.com/elastic/eui/pull/7855 + transform: translateZ(0); +} diff --git a/packages/eui-theme-common/src/global_styling/mixins/_size.scss b/packages/eui-theme-common/src/global_styling/mixins/_size.scss new file mode 100644 index 00000000000..809dc870bf8 --- /dev/null +++ b/packages/eui-theme-common/src/global_styling/mixins/_size.scss @@ -0,0 +1,4 @@ +@mixin size($size) { + width: $size; + height: $size; +} diff --git a/packages/eui-theme-common/src/global_styling/mixins/_states.scss b/packages/eui-theme-common/src/global_styling/mixins/_states.scss new file mode 100644 index 00000000000..a2d1bc83aef --- /dev/null +++ b/packages/eui-theme-common/src/global_styling/mixins/_states.scss @@ -0,0 +1,50 @@ +@mixin euiFocusRing($size: 'small', $amsterdamOnlyProp: null) { + @if $size == 'large' { + // It's always OK to use the focus animation. This will take precedence over times we turn it off individually like EuiButtonEmpty + // stylelint-disable-next-line declaration-no-important + animation: $euiAnimSpeedSlow $euiAnimSlightResistance 1 normal forwards focusRingAnimateLarge !important; + } @else { + // stylelint-disable-next-line declaration-no-important + animation: $euiAnimSpeedSlow $euiAnimSlightResistance 1 normal forwards focusRingAnimate !important; + } +} + +// Keyframe animation declarations can be found in +// utility/animations.scss + +@mixin euiFocusBackground($color: $euiColorPrimary) { + background-color: tintOrShade($euiColorPrimary, ((1 - $euiFocusTransparency) * 100%), ((1 - $euiFocusTransparency) * 100%)); +} + +@mixin euiHoverState { + cursor: pointer; + text-decoration: underline; +} + +@mixin euiFocusState($color: $euiColorPrimary) { + @include euiHoverState; + @include euiFocusBackground($color); +} + +@mixin euiDisabledState($color: $euiButtonColorDisabledText) { + cursor: not-allowed; + text-decoration: none; + + @if ($color) { + color: $color; + } +} + +@mixin euiInteractiveStates($focusColor: $euiColorPrimary, $disabledColor: $euiButtonColorDisabledText) { + &:hover { + @include euiHoverState; + } + + &:focus { + @include euiFocusState($focusColor); + } + + &:disabled { + @include euiDisabledState($disabledColor); + } +} diff --git a/packages/eui-theme-common/src/global_styling/mixins/_tool_tip.scss b/packages/eui-theme-common/src/global_styling/mixins/_tool_tip.scss new file mode 100644 index 00000000000..d8feb0d4258 --- /dev/null +++ b/packages/eui-theme-common/src/global_styling/mixins/_tool_tip.scss @@ -0,0 +1,25 @@ +@mixin euiToolTipStyle($size: null) { + @include euiBottomShadow($color: $euiColorInk); + + $euiTooltipBackgroundColor: tintOrShade($euiColorFullShade, 25%, 100%) !default; + $euiTooltipBorderColor: tintOrShade($euiColorFullShade, 35%, 80%) !default; + + border-radius: $euiBorderRadius; + background-color: $euiTooltipBackgroundColor; + color: $euiColorGhost; + z-index: $euiZLevel9; + max-width: 256px; + overflow-wrap: break-word; + padding: $euiSizeS; + + .euiHorizontalRule { + background-color: $euiTooltipBorderColor; + } +} + +@mixin euiToolTipTitle { + font-weight: $euiFontWeightBold; + border-bottom: solid $euiBorderWidthThin $euiTooltipBorderColor; + padding-bottom: $euiSizeXS; + margin-bottom: $euiSizeXS; +} \ No newline at end of file diff --git a/packages/eui-theme-common/src/global_styling/mixins/_typography.scss b/packages/eui-theme-common/src/global_styling/mixins/_typography.scss new file mode 100644 index 00000000000..7090f0e2371 --- /dev/null +++ b/packages/eui-theme-common/src/global_styling/mixins/_typography.scss @@ -0,0 +1,167 @@ +// stylelint-disable property-no-vendor-prefix +// stylelint-disable declaration-no-important + +@function fontSizeToRemOrEm($size, $sizingMethod: 'rem') { + @if ($sizingMethod == 'rem') { + @return #{$size / $euiFontSize}rem; + } @else if ($sizingMethod == 'em') { + @return #{$size / $euiFontSize}em; + } +} + +// It can also be applied to calculate paddings +@function marginToRemOrEm($elementSize, $elementFontSize, $sizingMethod: 'rem') { + @if ($sizingMethod == 'rem') { + @return #{$elementSize / $euiFontSize}rem; + } @else if ($sizingMethod == 'em') { + @return #{$elementSize / $elementFontSize}em; + } +} + +// Spit out rem and px +@mixin fontSize($size: $euiFontSize, $sizingMethod: 'rem') { + @if ($sizingMethod == 'rem') { + font-size: $size; + font-size: fontSizeToRemOrEm($size, 'rem'); + } @else if ($sizingMethod == 'em') { + font-size: fontSizeToRemOrEm($size, 'em'); + } +} + +@mixin lineHeightFromBaseline($multiplier: 3) { + line-height: lineHeightFromBaseline($multiplier); +} + +// Some mixins that help us deal with browser scaling of text more consistently. +// Essentially, fonts across eui should scale against the root html element, not +// against parent inheritance. + +// Our base fonts + +@mixin euiFont { + font-family: $euiFontFamily; + font-weight: $euiFontWeightRegular; + letter-spacing: -.005em; + -webkit-text-size-adjust: 100%; + -ms-text-size-adjust: 100%; + font-kerning: normal; +} + +@mixin euiCodeFont { + font-family: $euiCodeFontFamily; + letter-spacing: normal; +} + +@mixin euiText { + color: $euiTextColor; + font-weight: $euiFontWeightRegular; +} + +@mixin euiTitle($size: 'm') { + @include euiTextBreakWord; + color: $euiTitleColor; + + @if (map-has-key($euiTitles, $size)) { + @each $property, $value in map-get($euiTitles, $size) { + @if ($property == 'font-size') { + @include fontSize($value); + } @else { + #{$property}: $value; + } + } + } @else { + @include fontSize($size); + @include lineHeightFromBaseline(3); + } +} + +// Font sizing extends, using rem mixin + +@mixin euiFontSizeXS { + @include fontSize($euiFontSizeXS); + line-height: $euiLineHeight; +} + +@mixin euiFontSizeS { + @include fontSize($euiFontSizeS); + line-height: $euiLineHeight; +} + +@mixin euiFontSize { + @include fontSize($euiFontSize); + line-height: $euiLineHeight; +} + +@mixin euiFontSizeM { + @include fontSize($euiFontSizeM); + line-height: $euiLineHeight; +} + +@mixin euiFontSizeL { + @include fontSize($euiFontSizeL); + line-height: $euiLineHeight; +} + +@mixin euiFontSizeXL { + @each $property, $value in map-get($euiTitles, 'm') { + @if ($property == 'font-size') { + @include fontSize($value); + } @else { + #{$property}: $value; + } + } + line-height: 1.25; +} + +@mixin euiFontSizeXXL { + @each $property, $value in map-get($euiTitles, 'l') { + @if ($property == 'font-size') { + @include fontSize($value); + } @else { + #{$property}: $value; + } + } + line-height: 1.25; +} + +@mixin euiTextBreakWord { + // https://css-tricks.com/snippets/css/prevent-long-urls-from-breaking-out-of-container/ + overflow-wrap: break-word !important; // makes sure the long string will wrap and not bust out of the container + word-break: break-word; +} + +/** + * Text truncation + * + * Prevent text from wrapping onto multiple lines, and truncate with an ellipsis. + * + * 1. Ensure that the node has a maximum width after which truncation can occur. + */ +@mixin euiTextTruncate { + max-width: 100%; // 1 + overflow: hidden !important; + text-overflow: ellipsis !important; + white-space: nowrap !important; +} + +@mixin euiNumberFormat { + font-feature-settings: $euiFontFeatureSettings, 'tnum' 1; // Fixed-width numbers for tabular data +} + +/** + * Text weight shifting + * + * When changing the font-weight based the state of the component + * this mixin will ensure that the sizing is dependent on the boldest + * weight so it doesn't shifter sibling content. + */ +@mixin euiTextShift($fontWeight: $euiFontWeightBold, $attr: 'data-text') { + &::after { + display: block; + content: attr(#{$attr}); + font-weight: $fontWeight; + height: 0; + overflow: hidden; + visibility: hidden; + } +} diff --git a/packages/eui-theme-common/src/global_styling/react_date_picker/_date_picker.scss b/packages/eui-theme-common/src/global_styling/react_date_picker/_date_picker.scss new file mode 100644 index 00000000000..84208a3a648 --- /dev/null +++ b/packages/eui-theme-common/src/global_styling/react_date_picker/_date_picker.scss @@ -0,0 +1,772 @@ +/* This file is a heavy retheme of react-datepicker's Sass as of v1.4.0 +** https://github.com/Hacker0x01/react-datepicker +** +** In places where features were disabled, I've commented out the original Sass +** selectors rather than removing it so we can better understand what's changed. +** Commented out selectors that don't have properties indicate that we are not +** using those dom elements for styling of any kind. For example, react-datepicker +** has lots of pointer arrows attached to its popovers, but we choose not to render +** then in any way. +** +** Similarly, you will also find several times where we use display: none to +** completely remove extraneous UI (they had some overly obvious legends for example). +*/ + + +// Because we don't have control over react-datepicker's dom we use SVG URIs for the navigation arrows. +// There is one for light and dark. +@mixin datePickerArrow { + background-position: center; + @if (lightness($euiColorEmptyShade) > 50) { + background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHZpZXdCb3g9IjAgMCAxNiAxNiI+ICA8ZGVmcz4gICAgPHBhdGggaWQ9ImFycm93X2Rvd24tYSIgZD0iTTEzLjA2ODg1MDgsNS4xNTcyNTAzOCBMOC4zODQyMzk3NSw5Ljc2ODI3NDI4IEM4LjE3MDU0NDE1LDkuOTc4NjEzMDggNy44Mjk5OTIxNCw5Ljk3OTE0MDk1IDcuNjE1NzYwMjUsOS43NjgyNzQyOCBMMi45MzExNDkxNSw1LjE1NzI1MDM4IEMyLjcxODEzNTksNC45NDc1ODMyMSAyLjM3Mjc3MzE5LDQuOTQ3NTgzMjEgMi4xNTk3NTk5NCw1LjE1NzI1MDM4IEMxLjk0Njc0NjY5LDUuMzY2OTE3NTYgMS45NDY3NDY2OSw1LjcwNjg1NTIyIDIuMTU5NzU5OTQsNS45MTY1MjI0IEw2Ljg0NDM3MTA0LDEwLjUyNzU0NjMgQzcuNDg1MTc0MjQsMTEuMTU4MjgzNiA4LjUxNjQ0OTc5LDExLjE1NjY4NTEgOS4xNTU2Mjg5NiwxMC41Mjc1NDYzIEwxMy44NDAyNDAxLDUuOTE2NTIyNCBDMTQuMDUzMjUzMyw1LjcwNjg1NTIyIDE0LjA1MzI1MzMsNS4zNjY5MTc1NiAxMy44NDAyNDAxLDUuMTU3MjUwMzggQzEzLjYyNzIyNjgsNC45NDc1ODMyMSAxMy4yODE4NjQxLDQuOTQ3NTgzMjEgMTMuMDY4ODUwOCw1LjE1NzI1MDM4IFoiLz4gIDwvZGVmcz4gIDxnIGZpbGwtcnVsZT0iZXZlbm9kZCI+ICAgIDx1c2UgZmlsbC1ydWxlPSJub256ZXJvIiB4bGluazpocmVmPSIjYXJyb3dfZG93bi1hIi8+ICA8L2c+PC9zdmc+); + } @else { + background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHZpZXdCb3g9IjAgMCAxNiAxNiI+ICA8ZGVmcz4gICAgPHBhdGggaWQ9ImRvd25hcnJvd3doaXRlLWEiIGQ9Ik0xMy4wNjg4NTA4LDUuMTU3MjUwMzggTDguMzg0MjM5NzUsOS43NjgyNzQyOCBDOC4xNzA1NDQxNSw5Ljk3ODYxMzA4IDcuODI5OTkyMTQsOS45NzkxNDA5NSA3LjYxNTc2MDI1LDkuNzY4Mjc0MjggTDIuOTMxMTQ5MTUsNS4xNTcyNTAzOCBDMi43MTgxMzU5LDQuOTQ3NTgzMjEgMi4zNzI3NzMxOSw0Ljk0NzU4MzIxIDIuMTU5NzU5OTQsNS4xNTcyNTAzOCBDMS45NDY3NDY2OSw1LjM2NjkxNzU2IDEuOTQ2NzQ2NjksNS43MDY4NTUyMiAyLjE1OTc1OTk0LDUuOTE2NTIyNCBMNi44NDQzNzEwNCwxMC41Mjc1NDYzIEM3LjQ4NTE3NDI0LDExLjE1ODI4MzYgOC41MTY0NDk3OSwxMS4xNTY2ODUxIDkuMTU1NjI4OTYsMTAuNTI3NTQ2MyBMMTMuODQwMjQwMSw1LjkxNjUyMjQgQzE0LjA1MzI1MzMsNS43MDY4NTUyMiAxNC4wNTMyNTMzLDUuMzY2OTE3NTYgMTMuODQwMjQwMSw1LjE1NzI1MDM4IEMxMy42MjcyMjY4LDQuOTQ3NTgzMjEgMTMuMjgxODY0MSw0Ljk0NzU4MzIxIDEzLjA2ODg1MDgsNS4xNTcyNTAzOCBaIi8+ICA8L2RlZnM+ICA8ZyBmaWxsPSJub25lIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiPiAgICA8dXNlIGZpbGw9IiNGRkYiIGZpbGwtcnVsZT0ibm9uemVybyIgeGxpbms6aHJlZj0iI2Rvd25hcnJvd3doaXRlLWEiLz4gIDwvZz48L3N2Zz4=); + } +} + +// The only "new" css in this component is a wrapper class for dealing with shadows. +// This is mostly here so that we can provide an inline version that doesn't have the +// shadows and depth. +.euiDatePicker { + .euiFormControlLayout { + height: auto; + } + + &.euiDatePicker--shadow { + .react-datepicker-popper { + @include euiBottomShadowMedium; + + border: $euiBorderThin; + background-color: $euiColorEmptyShade; + border-radius: 0 0 $euiBorderRadius $euiBorderRadius; + } + + // If the shadow is on, and it is inline, we need to put the shadow on the datepicker + // itself rather than the popper. + &.euiDatePicker--inline { + .react-datepicker { + @include euiBottomShadowMedium; + + border: $euiBorderThin; + background-color: $euiColorEmptyShade; + border-radius: $euiBorderRadius; + } + } + } +} + +// .react-datepicker-wrapper { +// } + +.react-datepicker { + @include euiFont; + font-size: $euiFontSizeXS; + color: $euiColorFullShade; + display: flex; + position: relative; + border-radius: $euiBorderRadius; +} + +// When in time only mode we make the dropdown look more like the combo box styling. +.react-datepicker--time-only { + + .react-datepicker__time-container { + + .react-datepicker__time { + + .react-datepicker__time-box { + width: 100%; + + .react-datepicker__time-list li.react-datepicker__time-list-item { + font-size: $euiFontSizeS; + text-align: left; + padding-left: $euiSizeXL + $euiSizeXS; + padding-right: $euiSizeXL + $euiSizeXS; + color: $euiTextColor; + + &.react-datepicker__time-list-item--selected { + color: $euiColorGhost; + } + + &.react-datepicker__time-list-item--disabled{ + color: $euiColorDisabledText + } + } + } + } + } + + + .react-datepicker__time-container { + border-left: 0; + } + + // .react-datepicker__triangle { + // } + // .react-datepicker__time { + // } + // .react-datepicker__time-box { + // } +} + +// .react-datepicker__triangle { +// } + +.euiDatePicker.euiDatePicker--shadow .react-datepicker-popper { + z-index: $euiZContentMenu; + animation: euiAnimFadeIn $euiAnimSpeedFast ease-in; + + &[data-placement^="bottom"] { + + // .react-datepicker__triangle { + // } + } + + &[data-placement^="top"] { + @include euiBottomShadowFlat; + + border-radius: $euiBorderRadius $euiBorderRadius 0 0; + + // .react-datepicker__triangle { + // } + } + + &[data-placement^="right"] { + margin-left: 0; + + // .react-datepicker__triangle { + // } + } + + &[data-placement^="left"] { + margin-right: 0; + + // .react-datepicker__triangle { + // } + } +} + +.react-datepicker__header { + text-align: center; + border-top-left-radius: $euiBorderRadius; + border-top-right-radius: $euiBorderRadius; + + &--time { + display: none; + } +} + +.react-datepicker__header__dropdown { + padding: $euiSize 0 $euiSizeS 0; +} + +.react-datepicker__year-dropdown-container--select, +.react-datepicker__month-dropdown-container--select, +.react-datepicker__month-year-dropdown-container--select, +.react-datepicker__year-dropdown-container--scroll, +.react-datepicker__month-dropdown-container--scroll, +.react-datepicker__month-year-dropdown-container--scroll { + display: inline-block; + margin: 0 $euiSizeXS; +} + +.react-datepicker__current-month, +.react-datepicker-time__header { + display: none; +} + +.react-datepicker-time__header { + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; +} + +.react-datepicker__navigation { + cursor: pointer; + position: absolute; + // Pixel pushing because these are icons against text + top: $euiSize + ($euiSizeXS / 2); + width: 0; + padding: 0; + z-index: 1; + text-indent: -999em; + overflow: hidden; + + &--previous { + @include datePickerArrow; + left: $euiSize + $euiSizeXS; + height: $euiSize; + width: $euiSize; + transform: rotate(90deg); + transition: transform $euiAnimSpeedExtraFast ease-in-out; + + &:hover, &:focus { + border-radius: $euiBorderRadius; + transform: scale(1.2) rotate(90deg); + } + + &:hover { + background-color: $euiColorLightestShade; + box-shadow: 0 0 0 ($euiSizeXS / 2) $euiColorLightestShade; + } + + &:focus { + background-color: $euiFocusBackgroundColor; + box-shadow: 0 0 0 ($euiSizeXS / 2) $euiFocusBackgroundColor; + } + + &--disabled, + &--disabled:hover { + cursor: not-allowed ; + opacity: .2; + } + } + + &--next { + @include datePickerArrow; + // Pixel value because of some padding on the icon + right: 20px; + height: $euiSize; + width: $euiSize; + transform: rotate(-90deg); + + &--with-time:not(&--with-today-button) { + // This a pixel value against the width of the cal. It needs + // to be left because the timepicker adds more width + left: 248px; + } + + &:hover, &:focus { + border-radius: $euiBorderRadius; + transform: scale(1.2) rotate(-90deg); + } + + &:hover { + background-color: $euiColorLightestShade; + box-shadow: 0 0 0 ($euiSizeXS / 2) $euiColorLightestShade; + } + + &:focus { + background-color: $euiFocusBackgroundColor; + box-shadow: 0 0 0 ($euiSizeXS / 2) $euiFocusBackgroundColor; + } + + &--disabled, + &--disabled:hover { + cursor: not-allowed ; + opacity: .2; + } + } + + &--years { + position: relative; + top: 0; + display: block; + margin-left: auto; + margin-right: auto; + + &-previous { + top: $euiSizeXS; + border-top-color: $euiColorLightestShade; + + &:hover { + border-top-color: darken($euiColorLightestShade, 10%); + } + } + + &-upcoming { + top: -$euiSizeXS; + border-bottom-color: $euiColorLightestShade; + + &:hover { + border-bottom-color: darken($euiColorLightestShade, 10%); + } + } + } +} + +// .react-datepicker__month-container { +// } + +.react-datepicker__month { + margin: 0 $euiSize $euiSize $euiSize; + text-align: center; + border-radius: $euiBorderRadius; +} + +.react-datepicker__time-container { + border-left: $euiBorderColor; + width: auto; + display: flex; + padding: $euiSize 0; + border-radius: 0 $euiBorderRadius $euiBorderRadius 0; + flex-grow: 1; + + // &--with-today-button { + // } + + .react-datepicker__time { + position: relative; + flex-grow: 1; + display: flex; + padding-left: $euiSizeXS; + flex-direction: column; + + .react-datepicker__time-box { + width: auto; + display: flex; + flex-direction: column; + flex-grow: 1; + + ul.react-datepicker__time-list { + @include euiScrollBar; + height: 204px !important; + display: flex; + flex-direction: column; + flex-grow: 1; + overflow-y: auto; + align-items: center; + + li.react-datepicker__time-list-item { + padding: $euiSizeXS $euiSizeS; + margin-bottom: $euiSizeXS; + text-align: right; + color: $euiColorDarkShade; + white-space: nowrap; + // IE needs this to fix collapsing flex + line-height: $euiSizeM; + + &:hover, + &:focus { + cursor: pointer; + text-decoration: underline; + } + &--selected { + background-color: $euiColorPrimary; + color: white; + border-radius: $euiBorderRadius / 2; + &:hover { + background-color: $euiColorPrimary; + } + } + &--disabled { + color: $euiColorLightShade; + + &:hover { + cursor: not-allowed; + text-decoration: none; + background-color: transparent; + } + } + } + } + } + } +} + +.react-datepicker__week-number { + color: $euiColorLightestShade; + display: inline-block; + width: $euiSizeXL; + line-height: $euiSizeXL - $euiSizeXS; + text-align: center; + margin: 0 $euiSizeXS; + &.react-datepicker__week-number--clickable { + cursor: pointer; + &:hover { + border-radius: $euiBorderRadius; + background-color: $euiColorEmptyShade; + } + } +} + +.react-datepicker__day-names, +.react-datepicker__week { + white-space: nowrap; +} + +.react-datepicker__day-name, +.react-datepicker__day, +.react-datepicker__time-name { + color: $euiColorFullShade; + display: inline-block; + width: $euiSizeXL; + line-height: $euiSizeXL - $euiSizeXS; + text-align: center; + margin: 0 $euiSizeXS / 2; +} + +.react-datepicker__day-name { + color: $euiColorDarkShade; + text-transform: uppercase; +} + +.react-datepicker__day { + cursor: pointer; + border: solid 2px transparent; + transition: transform $euiAnimSpeedExtraFast ease-in-out; + + &:hover:not(&--disabled) { + text-decoration: underline; + font-weight: $euiFontWeightBold; + transform: scale(1.2); + } + + &--today { + font-weight: bold; + color: $euiColorPrimary; + } + &--outside-month { + color: $euiColorDarkShade; + } + + &--highlighted { + border-radius: $euiBorderRadius; + background-color: $euiColorSuccess; + color: $euiColorGhost; + + &:hover { + background-color: darken($euiColorSuccess, 5%); + } + } + + &--in-range { + background-color: transparentize($euiColorPrimary, .9); + color: $euiColorFullShade; + border-radius: 0; + border-top: solid 6px $euiColorEmptyShade; + border-bottom: solid 6px $euiColorEmptyShade; + border-right: none; + border-left: none; + line-height: $euiSizeL - $euiSizeXS; + } + + &--selected, + &--in-selecting-range { + height: $euiSizeXL; + margin: 0 $euiSizeXS / 2; + border-radius: $euiBorderRadius; + background-color: $euiColorPrimary; + line-height: $euiSizeL + $euiSizeXS; + border: solid $euiSizeXS / 2 $euiColorPrimary; + color: $euiColorGhost; + + &:hover { + background-color: darken($euiColorPrimary, 5%); + } + } + + &--keyboard-selected { + border-radius: $euiBorderRadius; + border: solid $euiSizeXS / 2 $euiColorPrimary; + font-weight: $euiFontWeightBold; + + &:hover { + background-color: darken($euiColorPrimary, 5%); + color: $euiColorGhost; + } + } + + &--in-selecting-range:not(&--in-range) { + background-color: rgba($euiColorPrimary, 0.5); + } + + &--in-range:not(&--in-selecting-range) { + .react-datepicker__month--selecting-range & { + background-color: $euiColorEmptyShade; + color: $euiColorFullShade; + } + } + + &--disabled { + cursor: not-allowed; + color: $euiColorLightShade; + + &:hover { + background-color: transparent; + } + } +} + +.react-datepicker__input-container { + position: relative; +} + +.react-datepicker__year-read-view { + font-weight: $euiFontWeightLight; + color: $euiColorDarkShade; +} + +.react-datepicker__month-read-view { + font-weight: $euiFontWeightMedium; +} + +.react-datepicker__year-read-view, +.react-datepicker__month-read-view, +.react-datepicker__month-year-read-view { + font-size: $euiFontSizeL; + + &:hover { + cursor: pointer; + color: $euiColorPrimary; + + .react-datepicker__year-read-view--down-arrow, + .react-datepicker__month-read-view--down-arrow { + border-top-color: darken($euiColorLightestShade, 10%); + } + } + + &--down-arrow { + display: none; + } +} + +.react-datepicker__year-dropdown, +.react-datepicker__month-dropdown, +.react-datepicker__month-year-dropdown { + background-color: $euiColorEmptyShade; + position: absolute; + width: 100%; + height: 100%; + left: 0; + top: 0; + bottom: 0; + right: 0; + z-index: 1; + text-align: center; + border-radius: $euiBorderRadius; + display: flex; + flex-wrap: wrap; + animation: euiAnimFadeIn $euiAnimSpeedFast ease-in; + align-content: space-around; + align-items: center; + padding: $euiSizeS; + + + &:hover { + cursor: pointer; + } + + // &--scrollable { + // height: 150px; + // overflow-y: scroll; + // } +} + +// Strike that, reverse it Willy Wonka style +.react-datepicker__year-dropdown { + flex-wrap: wrap-reverse; + flex-direction: row-reverse; + justify-content: flex-end; +} + +.react-datepicker__year-option, +.react-datepicker__month-option, +.react-datepicker__month-year-option { + font-size: $euiFontSizeXS; + padding: $euiSizeS; + color: $euiColorDarkestShade; + flex-basis: 33.3%; + + &:first-of-type { + border-top-left-radius: $euiBorderRadius; + border-top-right-radius: $euiBorderRadius; + } + + &:last-of-type { + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + border-bottom-left-radius: $euiBorderRadius; + border-bottom-right-radius: $euiBorderRadius; + } + + &:hover { + background-color: $euiColorLightestShade; + + .react-datepicker__navigation--years-upcoming { + border-bottom-color: darken($euiColorLightestShade, 10%); + } + + .react-datepicker__navigation--years-previous { + border-top-color: darken($euiColorLightestShade, 10%); + } + } + + &--selected { + display: none; + } +} + +.react-datepicker__screenReaderOnly { + position: absolute; + left: -10000px; + top: auto; + width: 1px; + height: 1px; + overflow: hidden; +} + +.react-datepicker__year-option--preselected, +.react-datepicker__month-option--preselected { + background: $euiFocusBackgroundColor; +} + +.react-datepicker__year-option--selected_year.react-datepicker__year-option--preselected, +.react-datepicker__month-option--selected_month.react-datepicker__month-option--preselected{ + background: $euiColorPrimary; +} + +.react-datepicker__time-list-item--preselected, +.react-datepicker__year-option--preselected, +.react-datepicker__month-option--preselected { + background: darken($euiFocusBackgroundColor, 5%); +} + +.react-datepicker__time-container--focus { + background: $euiFocusBackgroundColor; +} + +.react-datepicker__month-read-view:focus, +.react-datepicker__year-read-view:focus { + text-decoration: underline; +} + +.react-datepicker__month--accessible:focus { + background: $euiFocusBackgroundColor; + + .react-datepicker__day--in-range:not(.react-datepicker__day--selected) { + border-top-color: $euiFocusBackgroundColor; + border-bottom-color: $euiFocusBackgroundColor; + } +} +.react-datepicker__navigation:focus { + background-color: $euiFocusBackgroundColor; +} + +// These selectors are not a typo. react-datepicker has a bug where these selectors +// output as "--selected_year". Sass has trouble compiling .--selected_year, so instead +// we use this generic selector get around it. +.react-datepicker__year-option--selected_year, +.react-datepicker__month-option--selected_month { + background: $euiColorPrimary; + color: $euiColorEmptyShade; + font-weight: $euiFontWeightBold; + border-radius: $euiBorderRadius; +} + +.react-datepicker__focusTrap { + display: flex; +} + +// The below is for the portal version of react-datepicker which we do not use. +// It is shown here just to know what their baseline includes. + +// .react-datepicker__close-icon { +// background-color: transparent; +// border: 0; +// cursor: pointer; +// display: inline-block; +// height: 0; +// outline: 0; +// padding: 0; +// vertical-align: middle; +// +// &::after { +// background-color: $euiColorPrimary; +// border-radius: 50%; +// bottom: 0; +// box-sizing: border-box; +// color: #fff; +// content: "\00d7"; +// cursor: pointer; +// font-size: 12px; +// height: 16px; +// width: 16px; +// line-height: 1; +// margin: -8px auto 0; +// padding: 2px; +// position: absolute; +// right: 7px; +// text-align: center; +// top: 50%; +// } +// } +// +// .react-datepicker__today-button { +// background: $euiColorEmptyShade; +// border-top: 1px solid $euiBorderColor; +// cursor: pointer; +// text-align: center; +// font-weight: bold; +// padding: 5px 0; +// clear: left; +// } +// +// .react-datepicker__portal { +// position: fixed; +// width: 100vw; +// height: 100vh; +// background-color: rgba(0, 0, 0, 0.8); +// left: 0; +// top: 0; +// justify-content: center; +// align-items: center; +// display: flex; +// z-index: 2147483647; +// +// .react-datepicker__day-name, +// .react-datepicker__day, +// .react-datepicker__time-name { +// width: 3rem; +// line-height: 3rem; +// } +// +// // Resize for small screens +// @media (max-width: 400px), (max-height: 550px) { +// .react-datepicker__day-name, +// .react-datepicker__day, +// .react-datepicker__time-name { +// width: 2rem; +// line-height: 2rem; +// } +// } +// +// .react-datepicker__current-month, +// .react-datepicker-time__header { +// font-size: $euiFontSizeXS * 1.8; +// } +// +// .react-datepicker__navigation { +// border: 1.8 * $euiSize solid transparent; +// } +// +// .react-datepicker__navigation--previous { +// border-right-color: $euiColorLightestShade; +// +// &:hover { +// border-right-color: darken($euiColorLightestShade, 10%); +// } +// +// &--disabled, +// &--disabled:hover { +// border-right-color: $datepicker__navigation-disabled-color; +// cursor: default; +// } +// } +// +// .react-datepicker__navigation--next { +// border-left-color: $euiColorLightestShade; +// +// &:hover { +// border-left-color: darken($euiColorLightestShade, 10%); +// } +// +// &--disabled, +// &--disabled:hover { +// border-left-color: $datepicker__navigation-disabled-color; +// cursor: default; +// } +// } +// } diff --git a/packages/eui-theme-common/src/global_styling/react_date_picker/_index.scss b/packages/eui-theme-common/src/global_styling/react_date_picker/_index.scss new file mode 100644 index 00000000000..48ea2f99771 --- /dev/null +++ b/packages/eui-theme-common/src/global_styling/react_date_picker/_index.scss @@ -0,0 +1,2 @@ +@import 'variables'; +@import 'date_picker'; diff --git a/packages/eui-theme-common/src/global_styling/react_date_picker/_variables.scss b/packages/eui-theme-common/src/global_styling/react_date_picker/_variables.scss new file mode 100644 index 00000000000..6a5abd4e528 --- /dev/null +++ b/packages/eui-theme-common/src/global_styling/react_date_picker/_variables.scss @@ -0,0 +1 @@ +$euiDatePickerCalendarWidth: 284px; \ No newline at end of file diff --git a/packages/eui-theme-common/src/global_styling/types.ts b/packages/eui-theme-common/src/global_styling/types.ts new file mode 100644 index 00000000000..e2b7a96a9ff --- /dev/null +++ b/packages/eui-theme-common/src/global_styling/types.ts @@ -0,0 +1,106 @@ +/* + * 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 type { CSSObject } from '@emotion/react'; + +import type { RecursivePartial, ValueOf } from '../types'; + +import { _EuiThemeAnimation } from './variables/animations'; +import { _EuiThemeBreakpoints } from './variables/breakpoint'; +import { _EuiThemeBorder } from './variables/borders'; +import { _EuiThemeColors } from './variables/colors'; +import { _EuiThemeBase, _EuiThemeSizes } from './variables/size'; +import { _EuiThemeFont } from './variables/typography'; +import { _EuiThemeFocus } from './variables/states'; +import { _EuiThemeLevels } from './variables/levels'; + +export const COLOR_MODES_STANDARD = { + light: 'LIGHT', + dark: 'DARK', +} as const; +export const COLOR_MODES_INVERSE = 'INVERSE' as const; + +export type EuiThemeColorModeInverse = typeof COLOR_MODES_INVERSE; +export type EuiThemeColorModeStandard = ValueOf; +export type EuiThemeColorMode = + | 'light' + | 'dark' + | EuiThemeColorModeStandard + | 'inverse' + | EuiThemeColorModeInverse; + +export type ColorModeSwitch = + | { + [key in EuiThemeColorModeStandard]: T; + } + | T; + +export type StrictColorModeSwitch = { + [key in EuiThemeColorModeStandard]: T; +}; + +export type EuiThemeShape = { + colors: _EuiThemeColors; + /** - Default value: 16 */ + base: _EuiThemeBase; + /** + * @see {@link https://eui.elastic.co/#/theming/sizing | Reference} for more information + */ + size: _EuiThemeSizes; + font: _EuiThemeFont; + border: _EuiThemeBorder; + focus: _EuiThemeFocus; + animation: _EuiThemeAnimation; + breakpoint: _EuiThemeBreakpoints; + levels: _EuiThemeLevels; +}; + +export type EuiThemeSystem = { + root: EuiThemeShape & T; + model: EuiThemeShape & T; + key: string; +}; + +export type EuiThemeModifications = RecursivePartial; + +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; +}; + +export type EuiThemeNested = { + isGlobalTheme: boolean; + hasDifferentColorFromGlobalTheme: boolean; + bodyColor: string; + colorClassName: string; + setGlobalCSSVariables: Function; + globalCSSVariables?: CSSObject; + setNearestThemeCSSVariables: Function; + themeCSSVariables?: CSSObject; +}; diff --git a/packages/eui-theme-common/src/global_styling/utility/_animations.scss b/packages/eui-theme-common/src/global_styling/utility/_animations.scss new file mode 100644 index 00000000000..c0cb6e7365a --- /dev/null +++ b/packages/eui-theme-common/src/global_styling/utility/_animations.scss @@ -0,0 +1,55 @@ +// Animations as utility so they don't get duplicated in compiled CSS + +@keyframes euiAnimFadeIn { + 0% { + opacity: 0; + } + + 100% { + opacity: 1; + } +} + +@keyframes euiGrow { + 0% { + opacity: 0; + } + + 1% { + opacity: 0; + transform: scale(0); + } + + 100% { + opacity: 1; + transform: scale(1); + } +} + +@keyframes focusRingAnimate { + 0% { + box-shadow: 0 0 0 $euiFocusRingAnimStartSize $euiFocusRingAnimStartColor; + } + + 100% { + box-shadow: 0 0 0 $euiFocusRingSize $euiFocusRingColor; + } +} + +@keyframes focusRingAnimateLarge { + 0% { + box-shadow: 0 0 0 $euiFocusRingAnimStartSizeLarge $euiFocusRingAnimStartColor; + } + + 100% { + box-shadow: 0 0 0 $euiFocusRingSizeLarge $euiFocusRingColor; + } +} + +// Component specific + +@keyframes euiButtonActive { + 50% { + transform: translateY(1px); + } +} diff --git a/packages/eui-theme-common/src/global_styling/utility/_index.scss b/packages/eui-theme-common/src/global_styling/utility/_index.scss new file mode 100644 index 00000000000..5c978807848 --- /dev/null +++ b/packages/eui-theme-common/src/global_styling/utility/_index.scss @@ -0,0 +1 @@ +@import 'animations'; diff --git a/packages/eui-theme-common/src/global_styling/variables/_animations.scss b/packages/eui-theme-common/src/global_styling/variables/_animations.scss new file mode 100644 index 00000000000..93b9daf1641 --- /dev/null +++ b/packages/eui-theme-common/src/global_styling/variables/_animations.scss @@ -0,0 +1,13 @@ +// Animations + +$euiAnimSlightBounce: cubic-bezier(.34, 1.61, .7, 1) !default; +$euiAnimSlightResistance: cubic-bezier(.694, .0482, .335, 1) !default; + +$euiAnimSpeedExtraFast: 90ms !default; +$euiAnimSpeedFast: 150ms !default; +$euiAnimSpeedNormal: 250ms !default; +$euiAnimSpeedSlow: 350ms !default; +$euiAnimSpeedExtraSlow: 500ms !default; + +// Keyframe animation declarations can be found in +// eui/utility/animations.scss diff --git a/packages/eui-theme-common/src/global_styling/variables/_borders.scss b/packages/eui-theme-common/src/global_styling/variables/_borders.scss new file mode 100644 index 00000000000..6fa0216ff3b --- /dev/null +++ b/packages/eui-theme-common/src/global_styling/variables/_borders.scss @@ -0,0 +1,11 @@ +// Borders + +$euiBorderWidthThin: 1px !default; +$euiBorderWidthThick: 2px !default; + +$euiBorderColor: $euiColorLightShade !default; +$euiBorderRadius: $euiSizeS * .75 !default; +$euiBorderRadiusSmall: $euiSizeS * .5 !default; +$euiBorderThick: $euiBorderWidthThick solid $euiBorderColor !default; +$euiBorderThin: $euiBorderWidthThin solid $euiBorderColor !default; +$euiBorderEditable: $euiBorderWidthThick dotted $euiBorderColor !default; diff --git a/packages/eui-theme-common/src/global_styling/variables/_buttons.scss b/packages/eui-theme-common/src/global_styling/variables/_buttons.scss new file mode 100644 index 00000000000..4d4e8a5f0b1 --- /dev/null +++ b/packages/eui-theme-common/src/global_styling/variables/_buttons.scss @@ -0,0 +1,18 @@ +$euiButtonHeight: $euiSizeXXL !default; +$euiButtonHeightSmall: $euiSizeXL !default; +$euiButtonHeightXSmall: $euiSizeL !default; + +// Modifier naming and colors. +$euiButtonTypes: ( + primary: $euiColorPrimary, + accent: $euiColorAccent, + success: $euiColorSuccess, + warning: $euiColorWarning, + danger: $euiColorDanger, + ghost: $euiColorGhost, // Ghost is special, and does not care about theming. + text: $euiColorDarkShade, // Reserved for special use cases +) !default; + +// TODO: Remove this once elastic-charts no longer uses this variable +// @see https://github.com/elastic/elastic-charts/pull/2528 +$euiButtonColorDisabledText: $euiColorDisabledText; diff --git a/packages/eui-theme-common/src/global_styling/variables/_colors_vis.scss b/packages/eui-theme-common/src/global_styling/variables/_colors_vis.scss new file mode 100644 index 00000000000..cfffdf5e55d --- /dev/null +++ b/packages/eui-theme-common/src/global_styling/variables/_colors_vis.scss @@ -0,0 +1,72 @@ +// Visualization colors +// stylelint-disable color-no-hex + +// 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 +$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, + ) +) !default; + +$euiPaletteColorBlindKeys: map-keys($euiPaletteColorBlind); + +$euiColorVis0: map-get(map-get($euiPaletteColorBlind, 'euiColorVis0'), 'graphic') !default; +$euiColorVis1: map-get(map-get($euiPaletteColorBlind, 'euiColorVis1'), 'graphic') !default; +$euiColorVis2: map-get(map-get($euiPaletteColorBlind, 'euiColorVis2'), 'graphic') !default; +$euiColorVis3: map-get(map-get($euiPaletteColorBlind, 'euiColorVis3'), 'graphic') !default; +$euiColorVis4: map-get(map-get($euiPaletteColorBlind, 'euiColorVis4'), 'graphic') !default; +$euiColorVis5: map-get(map-get($euiPaletteColorBlind, 'euiColorVis5'), 'graphic') !default; +$euiColorVis6: map-get(map-get($euiPaletteColorBlind, 'euiColorVis6'), 'graphic') !default; +$euiColorVis7: map-get(map-get($euiPaletteColorBlind, 'euiColorVis7'), 'graphic') !default; +$euiColorVis8: map-get(map-get($euiPaletteColorBlind, 'euiColorVis8'), 'graphic') !default; +$euiColorVis9: map-get(map-get($euiPaletteColorBlind, 'euiColorVis9'), 'graphic') !default; + +$euiColorVis0_behindText: map-get(map-get($euiPaletteColorBlind, 'euiColorVis0'), 'behindText') !default; +$euiColorVis1_behindText: map-get(map-get($euiPaletteColorBlind, 'euiColorVis1'), 'behindText') !default; +$euiColorVis2_behindText: map-get(map-get($euiPaletteColorBlind, 'euiColorVis2'), 'behindText') !default; +$euiColorVis3_behindText: map-get(map-get($euiPaletteColorBlind, 'euiColorVis3'), 'behindText') !default; +$euiColorVis4_behindText: map-get(map-get($euiPaletteColorBlind, 'euiColorVis4'), 'behindText') !default; +$euiColorVis5_behindText: map-get(map-get($euiPaletteColorBlind, 'euiColorVis5'), 'behindText') !default; +$euiColorVis6_behindText: map-get(map-get($euiPaletteColorBlind, 'euiColorVis6'), 'behindText') !default; +$euiColorVis7_behindText: map-get(map-get($euiPaletteColorBlind, 'euiColorVis7'), 'behindText') !default; +$euiColorVis8_behindText: map-get(map-get($euiPaletteColorBlind, 'euiColorVis8'), 'behindText') !default; +$euiColorVis9_behindText: map-get(map-get($euiPaletteColorBlind, 'euiColorVis9'), 'behindText') !default; diff --git a/packages/eui-theme-common/src/global_styling/variables/_colors_vis.ts b/packages/eui-theme-common/src/global_styling/variables/_colors_vis.ts new file mode 100644 index 00000000000..4459b04ff8c --- /dev/null +++ b/packages/eui-theme-common/src/global_styling/variables/_colors_vis.ts @@ -0,0 +1,62 @@ +/* + * 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. + */ + +/** + * NOTE: These were quick conversions of their Sass counterparts. + * They have yet to be used/tested. + * TODO: Make the graphic version available from `euiPaletteColorBlind()` + */ + +// 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', + }, + euiColorVis1: { + graphic: '#6092C0', + }, + euiColorVis2: { + graphic: '#D36086', + }, + euiColorVis3: { + graphic: '#9170B8', + }, + euiColorVis4: { + graphic: '#CA8EAE', + }, + euiColorVis5: { + graphic: '#D6BF57', + }, + euiColorVis6: { + graphic: '#B9A888', + }, + euiColorVis7: { + graphic: '#DA8B45', + }, + euiColorVis8: { + graphic: '#AA6556', + }, + euiColorVis9: { + graphic: '#E7664C', + }, +}; + +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, +}; diff --git a/packages/eui-theme-common/src/global_styling/variables/_font_weight.scss b/packages/eui-theme-common/src/global_styling/variables/_font_weight.scss new file mode 100644 index 00000000000..f5dfd287835 --- /dev/null +++ b/packages/eui-theme-common/src/global_styling/variables/_font_weight.scss @@ -0,0 +1,10 @@ +// Separated out to its own file for easy import into docs + +// Font weights +$euiFontWeightLight: 300 !default; +$euiFontWeightRegular: 400 !default; +$euiFontWeightMedium: 500 !default; +$euiFontWeightSemiBold: 600 !default; +$euiFontWeightBold: 700 !default; +$euiCodeFontWeightRegular: 400 !default; +$euiCodeFontWeightBold: 700 !default; diff --git a/packages/eui-theme-common/src/global_styling/variables/_form.scss b/packages/eui-theme-common/src/global_styling/variables/_form.scss new file mode 100644 index 00000000000..41c5dfad04e --- /dev/null +++ b/packages/eui-theme-common/src/global_styling/variables/_form.scss @@ -0,0 +1,21 @@ +// Sizing +$euiFormMaxWidth: $euiSizeXXL * 10 !default; +$euiFormControlHeight: $euiSizeXXL !default; +$euiFormControlCompressedHeight: $euiSizeXL !default; +$euiFormControlPadding: $euiSizeM !default; +$euiFormControlCompressedPadding: $euiSizeS !default; +$euiFormControlBorderRadius: $euiBorderRadius !default; +$euiFormControlCompressedBorderRadius: $euiBorderRadiusSmall !default; + +// Coloring +$euiFormBackgroundColor: tintOrShade($euiColorLightestShade, 60%, 40%) !default; +$euiFormBackgroundDisabledColor: darken($euiColorLightestShade, 2%) !default; +$euiFormBackgroundReadOnlyColor: $euiColorEmptyShade !default; +$euiFormBorderOpaqueColor: shadeOrTint(desaturate(adjust-hue($euiColorPrimary, 22), 22.95), 26%, 100%) !default; +$euiFormBorderColor: transparentize($euiFormBorderOpaqueColor, .9) !default; +$euiFormBorderDisabledColor: transparentize($euiFormBorderOpaqueColor, .9) !default; +$euiFormControlDisabledColor: $euiColorMediumShade !default; +$euiFormControlBoxShadow: 0 0 transparent !default; +$euiFormControlPlaceholderText: makeHighContrastColor($euiTextSubduedColor, $euiFormBackgroundColor) !default; +$euiFormInputGroupLabelBackground: tintOrShade($euiColorLightShade, 50%, 15%) !default; +$euiFormInputGroupBorder: none !default; \ No newline at end of file diff --git a/packages/eui-theme-common/src/global_styling/variables/_index.scss b/packages/eui-theme-common/src/global_styling/variables/_index.scss new file mode 100644 index 00000000000..993979a78a5 --- /dev/null +++ b/packages/eui-theme-common/src/global_styling/variables/_index.scss @@ -0,0 +1,23 @@ +// -------------------------------------------------------------------------------------- +// EUI global variables +// -------------------------------------------------------------------------------------- +// This module contains all global variables available within EUI. Every variable in this +// document should be prefixed with $eui. This lets us know where the variable is +// coming from when looking inside the individual component files. Any component local +// variables should be declared at the top of those documents prefixed with $componentName. + +// Global colors are established and importer per theme, before this manifest +// Import order is important. Size, vis colors, ...etc are used in other variables. +@import 'size'; +@import 'colors_vis'; +@import 'animations'; +@import 'font_weight'; +@import 'typography'; +@import 'borders'; +@import 'responsive'; +@import 'shadows'; +@import 'states'; +@import 'z_index'; + +@import 'buttons'; +@import 'form'; diff --git a/packages/eui-theme-common/src/global_styling/variables/_responsive.scss b/packages/eui-theme-common/src/global_styling/variables/_responsive.scss new file mode 100644 index 00000000000..de6e8ca5b83 --- /dev/null +++ b/packages/eui-theme-common/src/global_styling/variables/_responsive.scss @@ -0,0 +1,9 @@ +$euiBreakpoints: ( + 'xs': 0, + 's': 575px, + 'm': 768px, + 'l': 992px, + 'xl': 1200px +) !default; + +$euiBreakpointKeys: map-keys($euiBreakpoints); diff --git a/packages/eui-theme-common/src/global_styling/variables/_shadows.scss b/packages/eui-theme-common/src/global_styling/variables/_shadows.scss new file mode 100644 index 00000000000..05e445f27a1 --- /dev/null +++ b/packages/eui-theme-common/src/global_styling/variables/_shadows.scss @@ -0,0 +1,2 @@ +// Shadows +$euiShadowColor: $euiColorInk !default; diff --git a/packages/eui-theme-common/src/global_styling/variables/_size.scss b/packages/eui-theme-common/src/global_styling/variables/_size.scss new file mode 100644 index 00000000000..f07645832d1 --- /dev/null +++ b/packages/eui-theme-common/src/global_styling/variables/_size.scss @@ -0,0 +1,15 @@ +$euiSize: 16px !default; + +$euiSizeXS: $euiSize * .25 !default; +$euiSizeS: $euiSize * .5 !default; +$euiSizeM: $euiSize * .75 !default; +$euiSizeL: $euiSize * 1.5 !default; +$euiSizeXL: $euiSize * 2 !default; +$euiSizeXXL: $euiSize * 2.5 !default; + +$euiButtonMinWidth: $euiSize * 7 !default; + +$euiScrollBar: $euiSize !default; +// Corner sizes are used as an inset border and therefore a smaller corner size means a larger thumb +$euiScrollBarCorner: $euiSizeXS !default; +$euiScrollBarCornerThin: $euiSizeS * .75 !default; diff --git a/packages/eui-theme-common/src/global_styling/variables/_states.scss b/packages/eui-theme-common/src/global_styling/variables/_states.scss new file mode 100644 index 00000000000..fba4cc59caa --- /dev/null +++ b/packages/eui-theme-common/src/global_styling/variables/_states.scss @@ -0,0 +1,14 @@ +// Colors +$euiFocusRingColor: rgba($euiColorPrimary, .3) !default; +$euiFocusRingAnimStartColor: rgba($euiColorPrimary, 0) !default; +$euiFocusRingAnimStartSize: 6px !default; +$euiFocusRingAnimStartSizeLarge: 10px !default; + +// Sizing +$euiFocusRingSizeLarge: $euiSizeXS !default; +$euiFocusRingSize: $euiFocusRingSizeLarge * .75 !default; + +// Transparency +$euiFocusTransparency: lightOrDarkTheme(.1, .2) !default; +$euiFocusTransparencyPercent: lightOrDarkTheme(90%, 80%) !default; +$euiFocusBackgroundColor: tintOrShade($euiColorPrimary, $euiFocusTransparencyPercent, $euiFocusTransparencyPercent) !default; diff --git a/packages/eui-theme-common/src/global_styling/variables/_typography.scss b/packages/eui-theme-common/src/global_styling/variables/_typography.scss new file mode 100644 index 00000000000..1ca62f3248c --- /dev/null +++ b/packages/eui-theme-common/src/global_styling/variables/_typography.scss @@ -0,0 +1,75 @@ +// Families +$euiFontFamily: 'Inter', BlinkMacSystemFont, Helvetica, Arial, sans-serif !default; +$euiCodeFontFamily: 'Roboto Mono', Menlo, Courier, monospace !default; + +// Careful using ligatures. Code editors like ACE will often error because of width calculations +$euiFontFeatureSettings: 'calt' 1, 'kern' 1, 'liga' 1 !default; + +// Font sizes -- scale is loosely based on Major Third (1.250) +$euiTextScale: 2.25, 1.75, 1.25, 1.125, 1, .875, .75 !default; + +$euiFontSize: $euiSize !default; // 5th position in scale +$euiFontSizeXS: $euiFontSize * nth($euiTextScale, 7) !default; // 12px +$euiFontSizeS: $euiFontSize * nth($euiTextScale, 6) !default; // 14px +$euiFontSizeM: $euiFontSize * nth($euiTextScale, 4) !default; // 18px +$euiFontSizeL: $euiFontSize * nth($euiTextScale, 3) !default; // 20px +$euiFontSizeXL: $euiFontSize * nth($euiTextScale, 2) !default; // 28px +$euiFontSizeXXL: $euiFontSize * nth($euiTextScale, 1) !default; // 36px + +// Line height +$euiLineHeight: 1.5 !default; +$euiBodyLineHeight: 1 !default; + +// Normally functions are imported before variables in `_index.scss` files +// But because they need to consume some typography variables they need to live here +@function convertToRem($size) { + @return #{$size / $euiFontSize}rem; +} + +// Our base gridline is at 1/2 the font-size, ensure line-heights stay on that gridline. +// EX: A proper line-height for text is 1.5 times the font-size. +// If our base font size (euiFontSize) is 16, our baseline is 8 (16*1.5 / 3). To ensure the +// text stays on the baseline, we pass a multiplier to calculate a line-height in rems. +@function lineHeightFromBaseline($multiplier: 3) { + @return convertToRem(($euiFontSize / 2) * $multiplier); +} + +// Titles map +// Lists all the properties per EuiTitle size that then gets looped through to create the selectors. +// The map allows for tokenization and easier customization per theme, otherwise you'd have to override the selectors themselves +$euiTitles: ( + 'xxxs': ( + 'font-size': $euiFontSizeXS, + 'line-height': lineHeightFromBaseline(3), + 'font-weight': $euiFontWeightBold, + ), + 'xxs': ( + 'font-size': $euiFontSizeS, + 'line-height': lineHeightFromBaseline(3), + 'font-weight': $euiFontWeightBold, + ), + 'xs': ( + 'font-size': $euiFontSize, + 'line-height': lineHeightFromBaseline(3), + 'font-weight': $euiFontWeightSemiBold, + 'letter-spacing': -.02em, + ), + 's': ( + 'font-size': $euiFontSizeL, + 'line-height': lineHeightFromBaseline(4), + 'font-weight': $euiFontWeightMedium, + 'letter-spacing': -.025em, + ), + 'm': ( + 'font-size': $euiFontSizeXL, + 'line-height': lineHeightFromBaseline(5), + 'font-weight': $euiFontWeightLight, + 'letter-spacing': -.04em, + ), + 'l': ( + 'font-size': $euiFontSizeXXL, + 'line-height': lineHeightFromBaseline(6), + 'font-weight': $euiFontWeightLight, + 'letter-spacing': -.03em, + ), +) !default; diff --git a/packages/eui-theme-common/src/global_styling/variables/_z_index.scss b/packages/eui-theme-common/src/global_styling/variables/_z_index.scss new file mode 100644 index 00000000000..2448a34c61a --- /dev/null +++ b/packages/eui-theme-common/src/global_styling/variables/_z_index.scss @@ -0,0 +1,34 @@ +// 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 + +$euiZLevel0: 0; +$euiZLevel1: 1000; +$euiZLevel2: 2000; +$euiZLevel3: 3000; +$euiZLevel4: 4000; +$euiZLevel5: 5000; +$euiZLevel6: 6000; +$euiZLevel7: 7000; +$euiZLevel8: 8000; +$euiZLevel9: 9000; + +$euiZToastList: $euiZLevel9; +$euiZModal: $euiZLevel8; +$euiZMask: $euiZLevel6; +$euiZNavigation: $euiZLevel6; +$euiZContentMenu: $euiZLevel2; +$euiZHeader: $euiZLevel1; +$euiZFlyout: $euiZHeader; +$euiZMaskBelowHeader: $euiZHeader; +$euiZContent: $euiZLevel0; diff --git a/packages/eui-theme-common/src/global_styling/variables/animations.ts b/packages/eui-theme-common/src/global_styling/variables/animations.ts new file mode 100644 index 00000000000..46bce009c92 --- /dev/null +++ b/packages/eui-theme-common/src/global_styling/variables/animations.ts @@ -0,0 +1,66 @@ +/* + * 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'; + +/** + * A constant storing the `prefers-reduced-motion` media query + * so that when it is turned off, animations are not run. + */ +export const euiCanAnimate = + '@media screen and (prefers-reduced-motion: no-preference)'; + +/** + * A constant storing the `prefers-reduced-motion` media query that will + * only apply the content if the setting is off (reduce). + */ +export const euiCantAnimate = + '@media screen and (prefers-reduced-motion: reduce)'; + +/** + * Speeds / Durations / Delays + */ + +export const EuiThemeAnimationSpeeds = [ + 'extraFast', + 'fast', + 'normal', + 'slow', + 'extraSlow', +] as const; + +export type _EuiThemeAnimationSpeed = (typeof EuiThemeAnimationSpeeds)[number]; + +export type _EuiThemeAnimationSpeeds = { + /** - Default value: 90ms */ + extraFast: CSSProperties['animationDuration']; + /** - Default value: 150ms */ + fast: CSSProperties['animationDuration']; + /** - Default value: 250ms */ + normal: CSSProperties['animationDuration']; + /** - Default value: 350ms */ + slow: CSSProperties['animationDuration']; + /** - Default value: 500ms */ + extraSlow: CSSProperties['animationDuration']; +}; + +/** + * Easings / Timing functions + */ + +export const EuiThemeAnimationEasings = ['bounce', 'resistance'] as const; + +export type _EuiThemeAnimationEasing = + (typeof EuiThemeAnimationEasings)[number]; + +export type _EuiThemeAnimationEasings = Record< + _EuiThemeAnimationEasing, + CSSProperties['animationTimingFunction'] +>; + +export type _EuiThemeAnimation = _EuiThemeAnimationEasings & + _EuiThemeAnimationSpeeds; diff --git a/packages/eui-theme-common/src/global_styling/variables/borders.ts b/packages/eui-theme-common/src/global_styling/variables/borders.ts new file mode 100644 index 00000000000..3673010aba2 --- /dev/null +++ b/packages/eui-theme-common/src/global_styling/variables/borders.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 { CSSProperties } from 'react'; +import { ColorModeSwitch } from '../types'; + +export interface _EuiThemeBorderWidthValues { + /** + * Thinnest width for border + * - Default value: 1px + */ + thin: CSSProperties['borderWidth']; + /** + * Thickest width for border + * - Default value: 2px + */ + thick: CSSProperties['borderWidth']; +} + +export interface _EuiThemeBorderRadiusValues { + /** + * Primary corner radius size + * - Default value: 6px + */ + medium: CSSProperties['borderRadius']; + /** + * Small corner radius size + * - Default value: 4px + */ + small: CSSProperties['borderRadius']; +} + +export interface _EuiThemeBorderColorValues { + /** + * Color for all borders; Default is `colors.lightShade` + */ + color: ColorModeSwitch; +} + +export interface _EuiThemeBorderValues extends _EuiThemeBorderColorValues { + /** + * Varied thicknesses for borders + */ + width: _EuiThemeBorderWidthValues; + /** + * Varied border radii + */ + radius: _EuiThemeBorderRadiusValues; +} + +export interface _EuiThemeBorderTypes { + /** + * Full `border` property string computed using `border.width.thin` and `border.color` + * - Default value: 1px solid [colors.lightShade] + */ + thin: CSSProperties['border']; + /** + * Full `border` property string computed using `border.width.thick` and `border.color` + * - Default value: 2px solid [colors.lightShade] + */ + thick: CSSProperties['border']; + /** + * Full editable style `border` property string computed using `border.width.thick` and `border.color` + * - Default value: 2px dotted [colors.lightShade] + */ + editable: CSSProperties['border']; +} + +export type _EuiThemeBorder = _EuiThemeBorderValues & _EuiThemeBorderTypes; diff --git a/packages/eui-theme-common/src/global_styling/variables/breakpoint.ts b/packages/eui-theme-common/src/global_styling/variables/breakpoint.ts new file mode 100644 index 00000000000..68aef1fba38 --- /dev/null +++ b/packages/eui-theme-common/src/global_styling/variables/breakpoint.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. + */ + +export const EuiThemeBreakpoints = ['xs', 's', 'm', 'l', 'xl'] as const; + +// This type cannot be a string enum / must be a generic string +// in case consumers add custom breakpoint sizes, such as xxl or xxs +export type _EuiThemeBreakpoint = string; + +// Explicitly list out each key so we can document default values +// via JSDoc (which is available to devs in IDE via intellisense) +export type _EuiThemeBreakpoints = Record<_EuiThemeBreakpoint, number> & { + /** - Default value: 0 */ + xs: number; + /** - Default value: 575 */ + s: number; + /** - Default value: 768 */ + m: number; + /** - Default value: 992 */ + l: number; + /** - Default value: 1200 */ + xl: number; +}; diff --git a/packages/eui-theme-common/src/global_styling/variables/colors.ts b/packages/eui-theme-common/src/global_styling/variables/colors.ts new file mode 100644 index 00000000000..8604ecafba4 --- /dev/null +++ b/packages/eui-theme-common/src/global_styling/variables/colors.ts @@ -0,0 +1,156 @@ +/* + * 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 { ColorModeSwitch, StrictColorModeSwitch } from '../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 opposite of `emptyShade` + */ + fullShade: ColorModeSwitch; +}; + +export type _EuiThemeTextColors = { + /** + * Computed against `colors.darkestShade` + */ + text: ColorModeSwitch; + /** + * Computed against `colors.text` + */ + title: ColorModeSwitch; + /** + * Computed against `colors.mediumShade` + */ + subduedText: 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; + /** + * The base color for shadows that gets `transparentized` + * at a value based on the `colorMode` and then layered. + */ + shadow: ColorModeSwitch; +}; + +export type _EuiThemeConstantColors = { + /** + * Purest **white** + */ + ghost: string; + /** + * Purest **black** + */ + ink: string; +}; + +export type _EuiThemeColorsMode = _EuiThemeBrandColors & + _EuiThemeBrandTextColors & + _EuiThemeShadeColors & + _EuiThemeSpecialColors & + _EuiThemeTextColors; + +export type _EuiThemeColors = StrictColorModeSwitch<_EuiThemeColorsMode> & + _EuiThemeConstantColors; diff --git a/packages/eui-theme-common/src/global_styling/variables/index.ts b/packages/eui-theme-common/src/global_styling/variables/index.ts new file mode 100644 index 00000000000..48eacef5cbb --- /dev/null +++ b/packages/eui-theme-common/src/global_styling/variables/index.ts @@ -0,0 +1,18 @@ +/* + * 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 * from './animations'; +export * from './borders'; +export * from './breakpoint'; +export * from './colors'; +export * from './_colors_vis'; +export * from './levels'; +export * from './size'; +export * from './shadow'; +export * from './states'; +export * from './typography'; diff --git a/packages/eui-theme-common/src/global_styling/variables/levels.ts b/packages/eui-theme-common/src/global_styling/variables/levels.ts new file mode 100644 index 00000000000..7d38c71791b --- /dev/null +++ b/packages/eui-theme-common/src/global_styling/variables/levels.ts @@ -0,0 +1,60 @@ +/* + * 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'; + +/** + * 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 const EuiThemeLevels = [ + 'toast', + 'modal', + 'mask', + 'navigation', + 'menu', + 'header', + 'flyout', + 'maskBelowHeader', + 'content', +] as const; + +export type _EuiThemeLevel = (typeof EuiThemeLevels)[number]; + +export type _EuiThemeLevels = { + /** - Default value: 9000 */ + toast: NonNullable; + /** - Default value: 8000 */ + modal: NonNullable; + /** - Default value: 6000 */ + mask: NonNullable; + /** - Default value: 6000 */ + navigation: NonNullable; + /** - Default value: 2000 */ + menu: NonNullable; + /** - Default value: 1000 */ + header: NonNullable; + /** - Default value: 1000 */ + flyout: NonNullable; + /** - Default value: 1000 */ + maskBelowHeader: NonNullable; + /** - Default value: 0 */ + content: NonNullable; +}; diff --git a/packages/eui-theme-common/src/global_styling/variables/shadow.ts b/packages/eui-theme-common/src/global_styling/variables/shadow.ts new file mode 100644 index 00000000000..7761fbdb9a0 --- /dev/null +++ b/packages/eui-theme-common/src/global_styling/variables/shadow.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. + */ + +export const EuiThemeShadowSizes = ['xs', 's', 'm', 'l', 'xl'] as const; + +export type _EuiThemeShadowSize = (typeof EuiThemeShadowSizes)[number]; + +/** + * Shadow t-shirt sizes descriptions + */ +export const _EuiShadowSizesDescriptions: Record<_EuiThemeShadowSize, string> = + { + xs: 'Very subtle shadow used on small components.', + s: 'Adds subtle depth, usually used in conjunction with a border.', + m: 'Used on small sized portalled content like popovers.', + l: 'Primary shadow used in most cases to add visible depth.', + xl: 'Very large shadows used for large portalled style containers like modals and flyouts.', + }; + +export interface _EuiThemeShadowCustomColor { + color?: string; + property?: 'box-shadow' | 'filter'; +} diff --git a/packages/eui-theme-common/src/global_styling/variables/size.ts b/packages/eui-theme-common/src/global_styling/variables/size.ts new file mode 100644 index 00000000000..98428346b61 --- /dev/null +++ b/packages/eui-theme-common/src/global_styling/variables/size.ts @@ -0,0 +1,47 @@ +/* + * 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 _EuiThemeBase = number; + +export const EuiThemeSizes = [ + 'xxs', + 'xs', + 's', + 'm', + 'base', + 'l', + 'xl', + 'xxl', + 'xxxl', + 'xxxxl', +] as const; + +export type _EuiThemeSize = (typeof EuiThemeSizes)[number]; + +export type _EuiThemeSizes = { + /** - Default value: 2px */ + xxs: string; + /** - Default value: 4px */ + xs: string; + /** - Default value: 8px */ + s: string; + /** - Default value: 12px */ + m: string; + /** - Default value: 16px */ + base: string; + /** - Default value: 24px */ + l: string; + /** - Default value: 32px */ + xl: string; + /** - Default value: 40px */ + xxl: string; + /** - Default value: 48px */ + xxxl: string; + /** - Default value: 64px */ + xxxxl: string; +}; diff --git a/packages/eui-theme-common/src/global_styling/variables/states.ts b/packages/eui-theme-common/src/global_styling/variables/states.ts new file mode 100644 index 00000000000..2d0283ac949 --- /dev/null +++ b/packages/eui-theme-common/src/global_styling/variables/states.ts @@ -0,0 +1,33 @@ +/* + * 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 '../types'; + +export interface _EuiThemeFocus { + /** + * Default color of the focus ring, some components may override this property + * - Default value: currentColor + */ + color: ColorModeSwitch; + /** + * Thickness of the outline + * - Default value: 2px + */ + width: CSSProperties['borderWidth']; + /** + * Used to transparentize the focus background color + * - Default value: { LIGHT: 0.1, DARK: 0.2 } + */ + transparency: ColorModeSwitch; + /** + * Default focus background color. Not all components set a background color on focus + * - Default value: `colors.primary` computed with `focus.transparency` + */ + backgroundColor: ColorModeSwitch; +} diff --git a/packages/eui-theme-common/src/global_styling/variables/typography.ts b/packages/eui-theme-common/src/global_styling/variables/typography.ts new file mode 100644 index 00000000000..80ebbc8b1cb --- /dev/null +++ b/packages/eui-theme-common/src/global_styling/variables/typography.ts @@ -0,0 +1,146 @@ +/* + * 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'; + +/** + * Font units of measure + */ + +export const EuiThemeFontUnits = ['rem', 'px', 'em'] as const; + +export type _EuiThemeFontUnit = (typeof EuiThemeFontUnits)[number]; + +/* + * Font scale + */ + +export const EuiThemeFontScales = [ + 'xxxs', + 'xxs', + 'xs', + 's', + 'm', + 'l', + 'xl', + 'xxl', +] as const; + +export type _EuiThemeFontScale = (typeof EuiThemeFontScales)[number]; + +export type _EuiThemeFontScales = Record<_EuiThemeFontScale, number>; + +/* + * 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; + /** + * The font family used for serif UI elements like blockquotes within EuiText + */ + familySerif?: string; + /** + * Controls advanced features OpenType fonts. + * https://developer.mozilla.org/en-US/docs/Web/CSS/font-feature-settings + */ + featureSettings?: string; + /** + * Sets the default units used for font size & line height set by UI components + * like EuiText or EuiTitle. Defaults to `rem`. + * + * NOTE: This may overridden by some internal usages, e.g. + * EuiText's `relative` size which must use `em`. + * + * @default 'rem' + */ + defaultUnits: _EuiThemeFontUnit; + /** + * 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; +}; + +/* + * Font weights + */ + +export const EuiThemeFontWeights = [ + 'light', + 'regular', + 'medium', + 'semiBold', + 'bold', +] as const; + +export type _EuiThemeFontWeight = (typeof EuiThemeFontWeights)[number]; + +export type _EuiThemeFontWeights = { + /** - Default value: 300 */ + light: CSSProperties['fontWeight']; + /** - Default value: 400 */ + regular: CSSProperties['fontWeight']; + /** - Default value: 500 */ + medium: CSSProperties['fontWeight']; + /** - Default value: 600 */ + semiBold: CSSProperties['fontWeight']; + /** - Default value: 700 */ + bold: CSSProperties['fontWeight']; +}; + +/** + * Body / Base styles + */ + +export interface _EuiThemeBody { + /** + * A sizing key from one of the font scales to set as the base body font-size + */ + scale: _EuiThemeFontScale; + /** + * A font weight key for setting the base body weight + */ + weight: keyof _EuiThemeFontWeights; +} + +/** + * Title styles + */ + +export interface _EuiThemeTitle { + /** + * A font weight key for setting the base weight for titles and headings + */ + weight: keyof _EuiThemeFontWeights; +} + +/* + * Font + */ + +export type _EuiThemeFont = _EuiThemeFontBase & { + scale: _EuiThemeFontScales; + /** + * @see {@link https://eui.elastic.co/#/theming/typography/values%23font-weight | Reference} for more information + */ + weight: _EuiThemeFontWeights; + body: _EuiThemeBody; + title: _EuiThemeTitle; +}; diff --git a/packages/eui-theme-common/src/index.ts b/packages/eui-theme-common/src/index.ts new file mode 100644 index 00000000000..05e235c669b --- /dev/null +++ b/packages/eui-theme-common/src/index.ts @@ -0,0 +1,12 @@ +/* + * 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 * from './global_styling'; + +export * from './types'; +export * from './utils'; diff --git a/packages/eui-theme-common/src/types.ts b/packages/eui-theme-common/src/types.ts new file mode 100644 index 00000000000..68c72d586df --- /dev/null +++ b/packages/eui-theme-common/src/types.ts @@ -0,0 +1,56 @@ +/* + * 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. + */ + +/* TODO: duplicated types from /eui/src/components/common - extract to shared location */ + +/** + * Like `keyof typeof`, but for getting values instead of keys + * ValueOf + * Results in `'value1' | 'value2'` + */ +export type ValueOf = T[keyof T]; + +/** + * Replaces all properties on any type as optional, includes nested types + * + * @example + * ```ts + * interface Person { + * name: string; + * age?: number; + * spouse: Person; + * children: Person[]; + * } + * type PartialPerson = RecursivePartial; + * // results in + * interface PartialPerson { + * name?: string; + * age?: number; + * spouse?: RecursivePartial; + * children?: RecursivePartial[] + * } + * ``` + */ +export type RecursivePartial = { + [P in keyof T]?: T[P] extends NonAny[] // checks for nested any[] + ? T[P] + : T[P] extends readonly NonAny[] // checks for nested ReadonlyArray + ? T[P] + : T[P] extends Array + ? Array> + : T[P] extends ReadonlyArray + ? ReadonlyArray> + : T[P] extends Set // checks for Sets + ? Set> + : T[P] extends Map // checks for Maps + ? Map> + : T[P] extends NonAny // checks for primitive values + ? T[P] + : RecursivePartial; // recurse for all non-array and non-primitive values +}; +type NonAny = number | boolean | string | symbol | null; diff --git a/packages/eui/src/services/theme/utils.test.ts b/packages/eui-theme-common/src/utils.test.ts similarity index 100% rename from packages/eui/src/services/theme/utils.test.ts rename to packages/eui-theme-common/src/utils.test.ts diff --git a/packages/eui-theme-common/src/utils.ts b/packages/eui-theme-common/src/utils.ts new file mode 100644 index 00000000000..caa529ebd48 --- /dev/null +++ b/packages/eui-theme-common/src/utils.ts @@ -0,0 +1,392 @@ +/* + * 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 { + EuiThemeColorMode, + EuiThemeColorModeInverse, + EuiThemeColorModeStandard, + EuiThemeModifications, + EuiThemeSystem, + EuiThemeShape, + EuiThemeComputed, + COLOR_MODES_STANDARD, + COLOR_MODES_INVERSE, +} from './global_styling'; + +export const DEFAULT_COLOR_MODE = COLOR_MODES_STANDARD.light; + +/** + * Returns whether the parameter is an object + * @param {any} obj - Anything + */ +const isObject = (obj: any) => obj && typeof obj === 'object'; + +/** + * Returns whether the provided color mode is `inverse` + * @param {string} colorMode - `light`, `dark`, or `inverse` + */ +export const isInverseColorMode = ( + colorMode?: string +): colorMode is EuiThemeColorModeInverse => { + return colorMode === COLOR_MODES_INVERSE; +}; + +/** + * Returns the color mode configured in the current EuiThemeProvider. + * Returns the parent color mode if none is explicity set. + * @param {string} colorMode - `light`, `dark`, or `inverse` + * @param {string} parentColorMode - `LIGHT` or `DARK`; used as the fallback + */ +export const getColorMode = ( + colorMode?: EuiThemeColorMode, + parentColorMode?: EuiThemeColorModeStandard +): EuiThemeColorModeStandard => { + if (colorMode == null) { + return parentColorMode || DEFAULT_COLOR_MODE; + } + const mode = colorMode.toUpperCase() as + | EuiThemeColorModeInverse + | EuiThemeColorModeStandard; + if (isInverseColorMode(mode)) { + return parentColorMode === COLOR_MODES_STANDARD.dark || + parentColorMode === undefined + ? COLOR_MODES_STANDARD.light + : COLOR_MODES_STANDARD.dark; + } else { + return mode; + } +}; + +/** + * Returns a value at a given path on an object. + * If `colorMode` is provided, will scope the value to the appropriate color mode key (LIGHT\DARK) + * @param {object} model - Object + * @param {string} _path - Dot-notated string to a path on the object + * @param {string} colorMode - `light` or `dark` + */ +export const getOn = ( + model: { [key: string]: any }, + _path: string, + colorMode?: EuiThemeColorModeStandard +) => { + const path = _path.split('.'); + let node = model; + while (path.length) { + const segment = path.shift()!; + + if (node.hasOwnProperty(segment) === false) { + if ( + colorMode && + node.hasOwnProperty(colorMode) === true && + node[colorMode].hasOwnProperty(segment) === true + ) { + if (node[colorMode][segment] instanceof Computed) { + node = node[colorMode][segment].getValue(null, {}, node, colorMode); + } else { + node = node[colorMode][segment]; + } + } else { + return undefined; + } + } else { + if (node[segment] instanceof Computed) { + node = node[segment].getValue(null, {}, node, colorMode); + } else { + node = node[segment]; + } + } + } + + return node; +}; + +/** + * Sets a value at a given path on an object. + * @param {object} model - Object + * @param {string} _path - Dot-notated string to a path on the object + * @param {any} string - The value to set + */ +export const setOn = ( + model: { [key: string]: any }, + _path: string, + value: any +) => { + const path = _path.split('.'); + const propertyName = path.pop()!; + let node = model; + + while (path.length) { + const segment = path.shift()!; + if (node.hasOwnProperty(segment) === false) { + node[segment] = {}; + } + node = node[segment]; + } + + node[propertyName] = value; + return true; +}; + +/** + * Creates a class to store the `computer` method and its eventual parameters. + * Allows for on-demand computation with up-to-date parameters via `getValue` method. + * @constructor + * @param {function} computer - Function to be computed + * @param {string | array} dependencies - Dependencies passed to the `computer` as parameters + */ +export class Computed { + constructor( + public computer: (...values: any[]) => T, + public dependencies: string | string[] = [] + ) {} + + /** + * Executes the `computer` method with the current state of the theme + * by taking into account previously computed values and modifications. + * @param {Proxy | object} base - Computed or uncomputed theme + * @param {Proxy | object} modifications - Theme value overrides + * @param {object} working - Partially computed theme + * @param {string} colorMode - `light` or `dark` + */ + getValue( + base: EuiThemeSystem | EuiThemeShape | null, + modifications: EuiThemeModifications = {}, + working: Partial, + colorMode?: EuiThemeColorModeStandard + ) { + 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) ?? + (base ? getOn(base, this.dependencies, colorMode) : working) + ); + } + return this.computer( + this.dependencies.map((dependency) => { + return ( + getOn(working, dependency) ?? + getOn(modifications, dependency, colorMode) ?? + (base ? getOn(base, dependency, colorMode) : working) + ); + }) + ); + } +} + +/** + * Returns a Class (`Computed`) that stores the arbitrary computer method + * and references to its optional dependecies. + * @param {function} computer - Arbitrary method to be called at compute time. + * @param {string | array} dependencies - Values that will be provided to `computer` at compute time. + */ +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); +} + +/** + * Type guard to check for a Computed object based on object shape + * without relying on the Computed class directly + */ +const isComputedLike = (key: object): key is Computed => { + if (typeof key !== 'object' || Array.isArray(key)) return false; + + return key.hasOwnProperty('dependencies') && key.hasOwnProperty('computer'); +}; + +/** + * Takes an uncomputed theme, and computes and returns all values taking + * into consideration value overrides and configured color mode. + * Overrides take precedence, and only values in the current color mode + * are computed and returned. + * @param {Proxy} base - Object to transform into Proxy + * @param {Proxy | object} over - Unique identifier or name + * @param {string} colorMode - `light` or `dark` + */ +export const getComputed = ( + base: EuiThemeSystem, + over: Partial>, + colorMode: EuiThemeColorModeStandard +): EuiThemeComputed => { + const output: Partial = { themeName: base.key }; + + function loop( + base: { [key: string]: any }, + over: { [key: string]: any }, + checkExisting: boolean = false, + path?: string + ) { + Object.keys(base).forEach((key) => { + let newPath = path ? `${path}.${key}` : `${key}`; + // @ts-expect-error `key` is not necessarily a colorMode 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)) { + // NOTE: the class type check for Computed is not true for themes created externally; + // we additionally check on the object shape to confirm a Computed value + const baseValue = + base[key] instanceof Computed || isComputedLike(base[key]) + ? base[key].getValue(base.root, over.root, output, colorMode) + : base[key]; + const overValue = + over[key] instanceof Computed || isComputedLike(over[key]) + ? 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); + } + } + }); + } + // Compute standard theme values and apply overrides + loop(base, over); + // Compute and apply extension values only + loop(over, {}, true); + return output as EuiThemeComputed; +}; + +/** + * Builds a Proxy with a custom `handler` designed to self-reference values + * and prevent arbitrary value overrides. + * @param {object} model - Object to transform into Proxy + * @param {string} key - Unique identifier or name + */ +export const buildTheme = (model: T, key: string) => { + const handler: ProxyHandler> = { + getPrototypeOf(target) { + return Reflect.getPrototypeOf(target.model); + }, + + setPrototypeOf(target, prototype) { + return Reflect.setPrototypeOf(target.model, prototype); + }, + + isExtensible(target) { + return Reflect.isExtensible(target); + }, + + preventExtensions(target) { + return Reflect.preventExtensions(target.model); + }, + + getOwnPropertyDescriptor(target, key) { + return Reflect.getOwnPropertyDescriptor(target.model, key); + }, + + defineProperty(target, property, attributes) { + return Reflect.defineProperty(target.model, property, attributes); + }, + + has(target, property) { + return Reflect.has(target.model, property); + }, + + get(_target, property) { + if (property === 'key') { + return _target[property]; + } + + // prevent Safari from locking up when the proxy is used in dev tools + // as it doesn't support getPrototypeOf + if (property === '__proto__') return {}; + + const target = property === 'root' ? _target : _target.model || _target; + // @ts-ignore `string` index signature + const value = target[property]; + if (isObject(value) && !Array.isArray(value)) { + return new Proxy( + { + model: value, + root: _target.root, + key: `_${_target.key}`, + }, + handler + ); + } else { + return value; + } + }, + + set(target: any) { + return target; + }, + + deleteProperty(target: any) { + return target; + }, + + ownKeys(target) { + return Reflect.ownKeys(target.model); + }, + + apply(target: any) { + return target; + }, + + construct(target: any) { + return target; + }, + }; + const themeProxy = new Proxy({ model, root: model, key }, handler); + + return themeProxy; +}; + +/** + * Deeply merges two objects, using `source` values whenever possible. + * @param {object} _target - Object with fallback values + * @param {object} source - Object with desired values + */ +export const mergeDeep = ( + _target: { [key: string]: any }, + source: { [key: string]: any } = {} +) => { + const target = { ..._target }; + + if (!isObject(target) || !isObject(source)) { + return source; + } + + Object.keys(source).forEach((key) => { + const targetValue = target[key]; + const sourceValue = source[key]; + + if (isObject(targetValue) && isObject(sourceValue)) { + target[key] = mergeDeep({ ...targetValue }, { ...sourceValue }); + } else { + target[key] = sourceValue; + } + }); + + return target; +}; diff --git a/packages/eui-theme-common/tsconfig.cjs.json b/packages/eui-theme-common/tsconfig.cjs.json new file mode 100644 index 00000000000..6becea42efa --- /dev/null +++ b/packages/eui-theme-common/tsconfig.cjs.json @@ -0,0 +1,29 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "compilerOptions": { + "rootDir": "src", + "outDir": "lib/cjs", + "target": "es6", + "module": "CommonJS", + "lib": [ + "es6", + "DOM" + ], + "moduleResolution": "Node", + "declaration": true, + "sourceMap": true, + "noEmitHelpers": true, + "incremental": true, + "esModuleInterop": true, + "strict": true, + "skipLibCheck": true, + "tsBuildInfoFile": "lib/cjs/.tsbuildinfo", + "importHelpers": false, + }, + "include": [ + "src" + ], + "exclude": [ + "node_modules" + ], +} \ No newline at end of file diff --git a/packages/eui-theme-common/tsconfig.json b/packages/eui-theme-common/tsconfig.json new file mode 100644 index 00000000000..31450bf12d6 --- /dev/null +++ b/packages/eui-theme-common/tsconfig.json @@ -0,0 +1,28 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "compilerOptions": { + "rootDir": "src", + "outDir": "lib/esm", + "target": "ES2020", + "module": "ESNext", + "lib": [ + "ESNext", + "DOM" + ], + "moduleResolution": "Node", + "declaration": true, + "sourceMap": true, + "noEmitHelpers": true, + "incremental": true, + "esModuleInterop": true, + "strict": true, + "skipLibCheck": true, + "tsBuildInfoFile": "lib/esm/.tsbuildinfo" + }, + "include": [ + "src", + ], + "exclude": [ + "node_modules" + ], +} \ No newline at end of file diff --git a/packages/eui-theme-common/tsconfig.types.json b/packages/eui-theme-common/tsconfig.types.json new file mode 100644 index 00000000000..54e2031daae --- /dev/null +++ b/packages/eui-theme-common/tsconfig.types.json @@ -0,0 +1,13 @@ +{ + "extends": "./tsconfig.cjs.json", + "compilerOptions": { + "outDir": "lib/cjs", + "declaration": true, + "declarationMap": true, + "isolatedModules": false, + "noEmit": false, + "allowJs": false, + "emitDeclarationOnly": true + }, + "exclude": ["node_modules", "**/*.test.ts"] +} \ No newline at end of file diff --git a/packages/eui/.storybook/decorator.tsx b/packages/eui/.storybook/decorator.tsx index 6b78a2e2353..d30f8df311d 100644 --- a/packages/eui/.storybook/decorator.tsx +++ b/packages/eui/.storybook/decorator.tsx @@ -6,12 +6,37 @@ * Side Public License, v 1. */ -import React, { useState, useMemo, FunctionComponent } from 'react'; +import React, { + useState, + useMemo, + FunctionComponent, + useEffect, + useCallback, +} from 'react'; import { css } from '@emotion/react'; import type { Preview } from '@storybook/react'; +import { EuiThemeBorealis } from '@elastic/eui-theme-borealis'; import { EuiThemeColorMode } from '../src/services'; import { EuiProvider, EuiProviderProps } from '../src/components/provider'; +import { EuiThemeAmsterdam } from '../src/themes'; + +const EXPERIMENTAL_THEMES = [ + { + text: 'Borealis', + value: EuiThemeBorealis.key, + provider: EuiThemeBorealis, + }, +]; + +export const AVAILABLE_THEMES = [ + { + text: 'Amsterdam', + value: EuiThemeAmsterdam.key, + provider: EuiThemeAmsterdam, + }, + ...EXPERIMENTAL_THEMES, +]; /** * Primary EuiProvider decorator to wrap around all stories @@ -20,8 +45,9 @@ import { EuiProvider, EuiProviderProps } from '../src/components/provider'; export const EuiProviderDecorator: FunctionComponent< EuiProviderProps<{}> & { writingMode: WritingModes; + themeName: string; } -> = ({ children, writingMode, ...euiProviderProps }) => { +> = ({ children, writingMode, themeName, theme, ...euiProviderProps }) => { // Append portals into Storybook's root div (rather than ) // so that loki correctly captures them for VRT screenshots const [sibling, setPortalSibling] = useState(null); @@ -39,8 +65,28 @@ export const EuiProviderDecorator: FunctionComponent< [writingMode] ); + const getTheme = useCallback(() => { + return AVAILABLE_THEMES.find((t) => themeName?.includes(t.value)); + }, [themeName]); + + const [_theme, setTheme] = useState(getTheme); + + useEffect(() => { + if (!themeName || theme) return; + + setTheme(getTheme); + }, [themeName, theme, getTheme]); + + const euiThemeProp = { + theme: theme ?? _theme?.provider, + }; + return ( - +
{portalInsert && children}
@@ -122,3 +168,18 @@ export const euiProviderDecoratorGlobals: Preview['globalTypes'] = { }, }, }; + +export const euiProviderDecoratorGlobalsExperimental = { + theme: { + description: 'Theme for EuiProvider', + defaultValue: EuiThemeAmsterdam.key, + toolbar: { + title: 'Theme', + items: [ + { value: EuiThemeAmsterdam.key, title: 'Amsterdam', icon: 'box' }, + { value: EuiThemeBorealis.key, title: 'Borealis', icon: 'box' }, + ], + dynamicTitle: true, + }, + }, +}; diff --git a/packages/eui/.storybook/preview.tsx b/packages/eui/.storybook/preview.tsx index c34435f5032..adf24f7f409 100644 --- a/packages/eui/.storybook/preview.tsx +++ b/packages/eui/.storybook/preview.tsx @@ -37,8 +37,13 @@ setEuiDevProviderWarning('error'); /** * Custom global decorators */ +import { isExperimentalThemeEnabled } from '../src/themes'; import { customJsxDecorator } from './addons/code-snippet/decorators/jsx_decorator'; -import { EuiProviderDecorator, euiProviderDecoratorGlobals } from './decorator'; +import { + EuiProviderDecorator, + euiProviderDecoratorGlobals, + euiProviderDecoratorGlobalsExperimental, +} from './decorator'; const preview: Preview = { decorators: [ @@ -48,12 +53,18 @@ const preview: Preview = { colorMode={context.globals.colorMode} {...(context.componentId === 'theming-euiprovider' && context.args)} writingMode={context.globals.writingMode} + themeName={context.globals.theme} > ), ], - globalTypes: { ...euiProviderDecoratorGlobals }, + globalTypes: isExperimentalThemeEnabled() + ? { + ...euiProviderDecoratorGlobals, + ...euiProviderDecoratorGlobalsExperimental, + } + : { ...euiProviderDecoratorGlobals }, parameters: { backgrounds: { disable: true }, // Use colorMode instead options: { diff --git a/packages/eui/package.json b/packages/eui/package.json index 5862770202c..d8a667fc2a3 100644 --- a/packages/eui/package.json +++ b/packages/eui/package.json @@ -16,8 +16,9 @@ ], "scripts": { "start": "cross-env BABEL_MODULES=false webpack serve --config=src-docs/webpack.config.js", + "build:workspaces": "yarn workspaces foreach -Rti --from @elastic/eui-theme-common run build && yarn workspaces foreach -Rti --from @elastic/eui --exclude @elastic/eui --exclude @elastic/eui-theme-common run build", "build-docs": "cross-env BABEL_MODULES=false cross-env NODE_ENV=production NODE_OPTIONS=--max-old-space-size=4096 webpack --config=src-docs/webpack.config.js", - "build": "yarn extract-i18n-strings && node ./scripts/compile-clean.js && node ./scripts/compile-eui.js && yarn compile-scss", + "build": "node ./scripts/compile-i18n-strings.js && node ./scripts/compile-clean.js && node ./scripts/compile-eui.js && yarn compile-scss", "build-pack": "yarn build && npm pack", "compile-icons": "node ./scripts/compile-icons.js && prettier --write --loglevel=warn \"./src/components/icon/assets/**/*.tsx\"", "compile-scss": "node ./scripts/compile-scss.js", @@ -30,7 +31,7 @@ "test": "yarn lint && yarn test-unit", "test-ci": "yarn test && yarn test-cypress", "test-unit": "node ./scripts/test-unit", - "test-staged": "yarn lint && node scripts/test-staged.js", + "test-staged": "yarn build:workspaces && yarn lint && node scripts/test-staged.js", "test-cypress": "node ./scripts/test-cypress", "test-cypress-dev": "yarn test-cypress --dev", "test-cypress-a11y": "yarn test-cypress --a11y", @@ -103,6 +104,8 @@ "@cypress/webpack-dev-server": "^1.7.0", "@elastic/charts": "^64.1.0", "@elastic/datemath": "^5.0.3", + "@elastic/eui-theme-borealis": "workspace:^", + "@elastic/eui-theme-common": "workspace:^", "@emotion/babel-preset-css-prop": "^11.11.0", "@emotion/cache": "^11.11.0", "@emotion/css": "^11.11.0", @@ -253,6 +256,8 @@ }, "peerDependencies": { "@elastic/datemath": "^5.0.2", + "@elastic/eui-theme-borealis": "0.0.1", + "@elastic/eui-theme-common": "0.0.1", "@emotion/css": "11.x", "@emotion/react": "11.x", "@types/react": "^16.9 || ^17.0 || ^18.0", diff --git a/packages/eui/scripts/compile-i18n-strings.js b/packages/eui/scripts/compile-i18n-strings.js new file mode 100644 index 00000000000..f948577d2fd --- /dev/null +++ b/packages/eui/scripts/compile-i18n-strings.js @@ -0,0 +1,26 @@ +/* + * 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. + */ + +const { execSync } = require('child_process'); +const chalk = require('chalk'); +const yargs = require('yargs/yargs'); +const { hideBin } = require('yargs/helpers'); + +const { NODE_ENV } = process.env; + +const info = chalk.white; +const log = chalk.grey; + +if (NODE_ENV !== 'production') return; + +const command = 'yarn extract-i18n-strings'; + +console.log(log(command)); +execSync(command, { + stdio: 'inherit', +}); diff --git a/packages/eui/scripts/deploy/build_docs b/packages/eui/scripts/deploy/build_docs index ab1207b3337..8e700a97b0b 100755 --- a/packages/eui/scripts/deploy/build_docs +++ b/packages/eui/scripts/deploy/build_docs @@ -12,4 +12,4 @@ docker run \ --volume "$PWD":/app \ --workdir /app \ "$DOCKER_BASE_IMAGE" \ - bash -c 'yarn && yarn --cwd packages/eui build && yarn --cwd packages/eui build-docs && yarn --cwd packages/eui build-storybook' + bash -c 'yarn && yarn --cwd packages/eui build:workspaces && yarn --cwd packages/eui build && yarn --cwd packages/eui build-docs && yarn --cwd packages/eui build-storybook' diff --git a/packages/eui/src-docs/src/components/guide_theme_selector/guide_theme_selector.tsx b/packages/eui/src-docs/src/components/guide_theme_selector/guide_theme_selector.tsx index bc9d4449b9d..0f99e738533 100644 --- a/packages/eui/src-docs/src/components/guide_theme_selector/guide_theme_selector.tsx +++ b/packages/eui/src-docs/src/components/guide_theme_selector/guide_theme_selector.tsx @@ -6,7 +6,8 @@ import { useEuiTheme, useIsWithinBreakpoints, } from '../../../../src/services'; -import { EUI_THEME, EUI_THEMES } from '../../../../src/themes'; +import { EUI_THEME } from '../../../../src/themes'; +import { AVAILABLE_THEMES } from '../with_theme/theme_context'; import { ThemeContext } from '../with_theme'; // @ts-ignore Not TS @@ -51,15 +52,15 @@ const GuideThemeSelectorComponent: React.FunctionComponent< const systemColorMode = useEuiTheme().colorMode.toLowerCase(); const currentTheme: EUI_THEME = - EUI_THEMES.find( + AVAILABLE_THEMES.find( (theme) => theme.value === (context.theme ?? systemColorMode) - ) || EUI_THEMES[0]; + ) || AVAILABLE_THEMES[0]; const getIconType = (value: EUI_THEME['value']) => { return value === currentTheme.value ? 'check' : 'empty'; }; - const items = EUI_THEMES.map((theme) => { + const items = AVAILABLE_THEMES.map((theme) => { return ( value); +const THEME_NAMES = AVAILABLE_THEMES.map(({ value }) => value); const THEME_LANGS = theme_languages.map(({ id }) => id); type ThemeContextType = { diff --git a/packages/eui/src-docs/src/index.js b/packages/eui/src-docs/src/index.js index a69a07e3613..d9b072fccf4 100644 --- a/packages/eui/src-docs/src/index.js +++ b/packages/eui/src-docs/src/index.js @@ -4,6 +4,8 @@ import { Provider } from 'react-redux'; import { HashRouter, Switch, Route, Redirect } from 'react-router-dom'; import { Helmet } from 'react-helmet'; +import { isExperimentalThemeEnabled } from '../../src/themes'; + import configureStore from './store/configure_store'; import { AppContext } from './views/app_context'; @@ -13,13 +15,25 @@ import { NotFoundView } from './views/not_found/not_found_view'; import { registerTheme, ExampleContext } from './services'; import Routes from './routes'; +import { + ThemeProvider, + AVAILABLE_THEMES, +} from './components/with_theme/theme_context'; + +// TODO: update SCSS files for new theme once available import themeLight from './theme_light.scss'; import themeDark from './theme_dark.scss'; -import { ThemeProvider } from './components/with_theme/theme_context'; +import themeNewLight from './theme_new_light.scss'; +import themeNewDark from './theme_new_dark.scss'; registerTheme('light', [themeLight]); registerTheme('dark', [themeDark]); +if (isExperimentalThemeEnabled()) { + registerTheme(AVAILABLE_THEMES[2].value, [themeNewLight]); + registerTheme(AVAILABLE_THEMES[3].value, [themeNewDark]); +} + // Set up app // Whether the docs app should be wrapped in diff --git a/packages/eui/src-docs/src/theme_new_dark.scss b/packages/eui/src-docs/src/theme_new_dark.scss new file mode 100644 index 00000000000..7c76c518a04 --- /dev/null +++ b/packages/eui/src-docs/src/theme_new_dark.scss @@ -0,0 +1,7 @@ +@import 'node_modules/@elastic/eui-theme-borealis/src/theme_dark'; +@import './components/index'; +@import './services/playground/index'; +@import './views/index'; + +// Elastic charts +@import '~@elastic/charts/dist/theme'; diff --git a/packages/eui/src-docs/src/theme_new_light.scss b/packages/eui/src-docs/src/theme_new_light.scss new file mode 100644 index 00000000000..1bee3ff7412 --- /dev/null +++ b/packages/eui/src-docs/src/theme_new_light.scss @@ -0,0 +1,7 @@ +@import 'node_modules/@elastic/eui-theme-borealis/src/theme_light'; +@import './components/index'; +@import './services/playground/index'; +@import './views/index'; + +// Elastic charts +@import '~@elastic/charts/dist/theme'; diff --git a/packages/eui/src-docs/src/views/app_context.js b/packages/eui/src-docs/src/views/app_context.js index 729f4c94079..42645f5b2f1 100644 --- a/packages/eui/src-docs/src/views/app_context.js +++ b/packages/eui/src-docs/src/views/app_context.js @@ -11,7 +11,7 @@ import { setEuiDevProviderWarning, euiStylisPrefixer, } from '../../../src/services'; -import { EUI_THEMES } from '../../../src/themes'; +import { AVAILABLE_THEMES } from '../components/with_theme/theme_context'; import favicon16Prod from '../images/favicon/prod/favicon-16x16.png'; import favicon32Prod from '../images/favicon/prod/favicon-32x32.png'; @@ -55,9 +55,13 @@ export const AppContext = ({ children }) => { default: generalEmotionCache, utility: utilityCache, }} - theme={EUI_THEMES.find((t) => t.value === theme)?.provider} + theme={AVAILABLE_THEMES.find((t) => t.value === theme)?.provider} colorMode={ - theme ? (theme.includes('light') ? 'light' : 'dark') : undefined + theme + ? theme.toLowerCase().includes('light') + ? 'light' + : 'dark' + : undefined } > diff --git a/packages/eui/src/components/common.ts b/packages/eui/src/components/common.ts index 2f09a052349..4f88e2324c5 100644 --- a/packages/eui/src/components/common.ts +++ b/packages/eui/src/components/common.ts @@ -17,6 +17,8 @@ import { } from 'react'; import { Interpolation, Theme } from '@emotion/react'; +export type { RecursivePartial, ValueOf } from '@elastic/eui-theme-common'; + export interface CommonProps { className?: string; 'aria-label'?: string; @@ -50,13 +52,6 @@ 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 FunctionComponent ? SFCProps : C extends FunctionComponent @@ -203,43 +198,3 @@ export type PropsForButton = T & { onClick?: MouseEventHandler; } & ButtonHTMLAttributes & P; - -/** - * Replaces all properties on any type as optional, includes nested types - * - * @example - * ```ts - * interface Person { - * name: string; - * age?: number; - * spouse: Person; - * children: Person[]; - * } - * type PartialPerson = RecursivePartial; - * // results in - * interface PartialPerson { - * name?: string; - * age?: number; - * spouse?: RecursivePartial; - * children?: RecursivePartial[] - * } - * ``` - */ -export type RecursivePartial = { - [P in keyof T]?: T[P] extends NonAny[] // checks for nested any[] - ? T[P] - : T[P] extends readonly NonAny[] // checks for nested ReadonlyArray - ? T[P] - : T[P] extends Array - ? Array> - : T[P] extends ReadonlyArray - ? ReadonlyArray> - : T[P] extends Set // checks for Sets - ? Set> - : T[P] extends Map // checks for Maps - ? Map> - : T[P] extends NonAny // checks for primitive values - ? T[P] - : RecursivePartial; // recurse for all non-array and non-primitive values -}; -type NonAny = number | boolean | string | symbol | null; diff --git a/packages/eui/src/global_styling/functions/_index.scss b/packages/eui/src/global_styling/functions/_index.scss index de8260b2bba..7e1a8b1710d 100644 --- a/packages/eui/src/global_styling/functions/_index.scss +++ b/packages/eui/src/global_styling/functions/_index.scss @@ -1,5 +1 @@ -// Math needs to be first in the load order -@import 'math'; - -// Using math, we have functions to manipulate contrast / luminosity for accessibility -@import 'colors'; +@import 'node_modules/@elastic/eui-theme-common/src/global_styling/functions/index'; diff --git a/packages/eui/src/global_styling/functions/size.ts b/packages/eui/src/global_styling/functions/size.ts index 66e6ab507d3..24652cc3ec1 100644 --- a/packages/eui/src/global_styling/functions/size.ts +++ b/packages/eui/src/global_styling/functions/size.ts @@ -6,19 +6,4 @@ * Side Public License, v 1. */ -/** - * Calculates the `px` value based on a scale multiplier - * @param scale - The font scale multiplier - * * - * @param themeOrBase - Theme base value - * * - * @returns string - Rem unit aligned to baseline - */ - -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 { sizeToPixel } from '@elastic/eui-theme-common'; diff --git a/packages/eui/src/global_styling/index.scss b/packages/eui/src/global_styling/index.scss index 5c29e0435a5..ba88bef57dd 100644 --- a/packages/eui/src/global_styling/index.scss +++ b/packages/eui/src/global_styling/index.scss @@ -1,18 +1,3 @@ // Core -// Functions need to be first, since we use them in our variables and mixin definitions -@import 'functions/index'; - -// Variables come next, and are used in some mixins -@import 'variables/index'; - -// Mixins provide generic code expansion through helpers -@import 'mixins/index'; - -// Utility classes provide one-off selectors for common css problems -@import 'utility/index'; - -// The reset file has moved to global_styles.tsx - -// Customization of the React Date Picker -@import 'react_date_picker/index'; +@import 'node_modules/@elastic/eui-theme-common/src/global_styling/index'; \ No newline at end of file diff --git a/packages/eui/src/global_styling/variables/_animations.scss b/packages/eui/src/global_styling/variables/_animations.scss index 0ffe44346dd..b6e40a79f6d 100644 --- a/packages/eui/src/global_styling/variables/_animations.scss +++ b/packages/eui/src/global_styling/variables/_animations.scss @@ -1,13 +1,6 @@ // Animations -$euiAnimSlightBounce: cubic-bezier(.34, 1.61, .7, 1) !default; -$euiAnimSlightResistance: cubic-bezier(.694, .0482, .335, 1) !default; - -$euiAnimSpeedExtraFast: 90ms !default; -$euiAnimSpeedFast: 150ms !default; -$euiAnimSpeedNormal: 250ms !default; -$euiAnimSpeedSlow: 350ms !default; -$euiAnimSpeedExtraSlow: 500ms !default; +@import 'node_modules/@elastic/eui-theme-common/src/global_styling/variables/animations' // Keyframe animation declarations can be found in // utility/animations.scss diff --git a/packages/eui/src/global_styling/variables/_borders.scss b/packages/eui/src/global_styling/variables/_borders.scss index 6fa0216ff3b..818f5807e6f 100644 --- a/packages/eui/src/global_styling/variables/_borders.scss +++ b/packages/eui/src/global_styling/variables/_borders.scss @@ -1,11 +1,3 @@ // Borders -$euiBorderWidthThin: 1px !default; -$euiBorderWidthThick: 2px !default; - -$euiBorderColor: $euiColorLightShade !default; -$euiBorderRadius: $euiSizeS * .75 !default; -$euiBorderRadiusSmall: $euiSizeS * .5 !default; -$euiBorderThick: $euiBorderWidthThick solid $euiBorderColor !default; -$euiBorderThin: $euiBorderWidthThin solid $euiBorderColor !default; -$euiBorderEditable: $euiBorderWidthThick dotted $euiBorderColor !default; +@import 'node_modules/@elastic/eui-theme-common/src/global_styling/variables/borders' diff --git a/packages/eui/src/global_styling/variables/_buttons.scss b/packages/eui/src/global_styling/variables/_buttons.scss index 4d4e8a5f0b1..288dca14d61 100644 --- a/packages/eui/src/global_styling/variables/_buttons.scss +++ b/packages/eui/src/global_styling/variables/_buttons.scss @@ -1,18 +1 @@ -$euiButtonHeight: $euiSizeXXL !default; -$euiButtonHeightSmall: $euiSizeXL !default; -$euiButtonHeightXSmall: $euiSizeL !default; - -// Modifier naming and colors. -$euiButtonTypes: ( - primary: $euiColorPrimary, - accent: $euiColorAccent, - success: $euiColorSuccess, - warning: $euiColorWarning, - danger: $euiColorDanger, - ghost: $euiColorGhost, // Ghost is special, and does not care about theming. - text: $euiColorDarkShade, // Reserved for special use cases -) !default; - -// TODO: Remove this once elastic-charts no longer uses this variable -// @see https://github.com/elastic/elastic-charts/pull/2528 -$euiButtonColorDisabledText: $euiColorDisabledText; +@import 'node_modules/@elastic/eui-theme-common/src/global_styling/variables/buttons' diff --git a/packages/eui/src/global_styling/variables/_colors_vis.scss b/packages/eui/src/global_styling/variables/_colors_vis.scss index cfffdf5e55d..d935ffa7ebe 100644 --- a/packages/eui/src/global_styling/variables/_colors_vis.scss +++ b/packages/eui/src/global_styling/variables/_colors_vis.scss @@ -1,72 +1,3 @@ // Visualization colors -// stylelint-disable color-no-hex -// 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 -$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, - ) -) !default; - -$euiPaletteColorBlindKeys: map-keys($euiPaletteColorBlind); - -$euiColorVis0: map-get(map-get($euiPaletteColorBlind, 'euiColorVis0'), 'graphic') !default; -$euiColorVis1: map-get(map-get($euiPaletteColorBlind, 'euiColorVis1'), 'graphic') !default; -$euiColorVis2: map-get(map-get($euiPaletteColorBlind, 'euiColorVis2'), 'graphic') !default; -$euiColorVis3: map-get(map-get($euiPaletteColorBlind, 'euiColorVis3'), 'graphic') !default; -$euiColorVis4: map-get(map-get($euiPaletteColorBlind, 'euiColorVis4'), 'graphic') !default; -$euiColorVis5: map-get(map-get($euiPaletteColorBlind, 'euiColorVis5'), 'graphic') !default; -$euiColorVis6: map-get(map-get($euiPaletteColorBlind, 'euiColorVis6'), 'graphic') !default; -$euiColorVis7: map-get(map-get($euiPaletteColorBlind, 'euiColorVis7'), 'graphic') !default; -$euiColorVis8: map-get(map-get($euiPaletteColorBlind, 'euiColorVis8'), 'graphic') !default; -$euiColorVis9: map-get(map-get($euiPaletteColorBlind, 'euiColorVis9'), 'graphic') !default; - -$euiColorVis0_behindText: map-get(map-get($euiPaletteColorBlind, 'euiColorVis0'), 'behindText') !default; -$euiColorVis1_behindText: map-get(map-get($euiPaletteColorBlind, 'euiColorVis1'), 'behindText') !default; -$euiColorVis2_behindText: map-get(map-get($euiPaletteColorBlind, 'euiColorVis2'), 'behindText') !default; -$euiColorVis3_behindText: map-get(map-get($euiPaletteColorBlind, 'euiColorVis3'), 'behindText') !default; -$euiColorVis4_behindText: map-get(map-get($euiPaletteColorBlind, 'euiColorVis4'), 'behindText') !default; -$euiColorVis5_behindText: map-get(map-get($euiPaletteColorBlind, 'euiColorVis5'), 'behindText') !default; -$euiColorVis6_behindText: map-get(map-get($euiPaletteColorBlind, 'euiColorVis6'), 'behindText') !default; -$euiColorVis7_behindText: map-get(map-get($euiPaletteColorBlind, 'euiColorVis7'), 'behindText') !default; -$euiColorVis8_behindText: map-get(map-get($euiPaletteColorBlind, 'euiColorVis8'), 'behindText') !default; -$euiColorVis9_behindText: map-get(map-get($euiPaletteColorBlind, 'euiColorVis9'), 'behindText') !default; +@import 'node_modules/@elastic/eui-theme-common/src/global_styling/variables/colors_vis' diff --git a/packages/eui/src/global_styling/variables/_colors_vis.ts b/packages/eui/src/global_styling/variables/_colors_vis.ts index 4459b04ff8c..64dcfd86633 100644 --- a/packages/eui/src/global_styling/variables/_colors_vis.ts +++ b/packages/eui/src/global_styling/variables/_colors_vis.ts @@ -6,57 +6,4 @@ * Side Public License, v 1. */ -/** - * NOTE: These were quick conversions of their Sass counterparts. - * They have yet to be used/tested. - * TODO: Make the graphic version available from `euiPaletteColorBlind()` - */ - -// 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', - }, - euiColorVis1: { - graphic: '#6092C0', - }, - euiColorVis2: { - graphic: '#D36086', - }, - euiColorVis3: { - graphic: '#9170B8', - }, - euiColorVis4: { - graphic: '#CA8EAE', - }, - euiColorVis5: { - graphic: '#D6BF57', - }, - euiColorVis6: { - graphic: '#B9A888', - }, - euiColorVis7: { - graphic: '#DA8B45', - }, - euiColorVis8: { - graphic: '#AA6556', - }, - euiColorVis9: { - graphic: '#E7664C', - }, -}; - -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, -}; +export { colorVis } from '@elastic/eui-theme-common'; diff --git a/packages/eui/src/global_styling/variables/_font_weight.scss b/packages/eui/src/global_styling/variables/_font_weight.scss index f5dfd287835..152c64d8358 100644 --- a/packages/eui/src/global_styling/variables/_font_weight.scss +++ b/packages/eui/src/global_styling/variables/_font_weight.scss @@ -1,10 +1,4 @@ // Separated out to its own file for easy import into docs // Font weights -$euiFontWeightLight: 300 !default; -$euiFontWeightRegular: 400 !default; -$euiFontWeightMedium: 500 !default; -$euiFontWeightSemiBold: 600 !default; -$euiFontWeightBold: 700 !default; -$euiCodeFontWeightRegular: 400 !default; -$euiCodeFontWeightBold: 700 !default; +@import 'node_modules/@elastic/eui-theme-common/src/global_styling/variables/font_weight' diff --git a/packages/eui/src/global_styling/variables/_form.scss b/packages/eui/src/global_styling/variables/_form.scss index 49a4d620b11..cdf08ad78bf 100644 --- a/packages/eui/src/global_styling/variables/_form.scss +++ b/packages/eui/src/global_styling/variables/_form.scss @@ -1,21 +1,2 @@ -// Sizing -$euiFormMaxWidth: $euiSizeXXL * 10 !default; -$euiFormControlHeight: $euiSizeXXL !default; -$euiFormControlCompressedHeight: $euiSizeXL !default; -$euiFormControlPadding: $euiSizeM !default; -$euiFormControlCompressedPadding: $euiSizeS !default; -$euiFormControlBorderRadius: $euiBorderRadius !default; -$euiFormControlCompressedBorderRadius: $euiBorderRadiusSmall !default; - -// Coloring -$euiFormBackgroundColor: tintOrShade($euiColorLightestShade, 60%, 40%) !default; -$euiFormBackgroundDisabledColor: darken($euiColorLightestShade, 2%) !default; -$euiFormBackgroundReadOnlyColor: $euiColorEmptyShade !default; -$euiFormBorderOpaqueColor: shadeOrTint(desaturate(adjust-hue($euiColorPrimary, 22), 22.95), 26%, 100%) !default; -$euiFormBorderColor: transparentize($euiFormBorderOpaqueColor, .9) !default; -$euiFormBorderDisabledColor: transparentize($euiFormBorderOpaqueColor, .9) !default; -$euiFormControlDisabledColor: $euiColorMediumShade !default; -$euiFormControlBoxShadow: 0 0 transparent !default; -$euiFormControlPlaceholderText: makeHighContrastColor($euiTextSubduedColor, $euiFormBackgroundColor) !default; -$euiFormInputGroupLabelBackground: tintOrShade($euiColorLightShade, 50%, 15%) !default; -$euiFormInputGroupBorder: none !default; +// Form +@import 'node_modules/@elastic/eui-theme-common/src/global_styling/variables/form' diff --git a/packages/eui/src/global_styling/variables/_responsive.scss b/packages/eui/src/global_styling/variables/_responsive.scss index de6e8ca5b83..45e7a7c4531 100644 --- a/packages/eui/src/global_styling/variables/_responsive.scss +++ b/packages/eui/src/global_styling/variables/_responsive.scss @@ -1,9 +1 @@ -$euiBreakpoints: ( - 'xs': 0, - 's': 575px, - 'm': 768px, - 'l': 992px, - 'xl': 1200px -) !default; - -$euiBreakpointKeys: map-keys($euiBreakpoints); +@import 'node_modules/@elastic/eui-theme-common/src/global_styling/variables/responsive' diff --git a/packages/eui/src/global_styling/variables/_shadows.scss b/packages/eui/src/global_styling/variables/_shadows.scss index 05e445f27a1..14237af776b 100644 --- a/packages/eui/src/global_styling/variables/_shadows.scss +++ b/packages/eui/src/global_styling/variables/_shadows.scss @@ -1,2 +1,2 @@ // Shadows -$euiShadowColor: $euiColorInk !default; +@import 'node_modules/@elastic/eui-theme-common/src/global_styling/variables/shadows' diff --git a/packages/eui/src/global_styling/variables/_size.scss b/packages/eui/src/global_styling/variables/_size.scss index f07645832d1..987c03b80f6 100644 --- a/packages/eui/src/global_styling/variables/_size.scss +++ b/packages/eui/src/global_styling/variables/_size.scss @@ -1,15 +1 @@ -$euiSize: 16px !default; - -$euiSizeXS: $euiSize * .25 !default; -$euiSizeS: $euiSize * .5 !default; -$euiSizeM: $euiSize * .75 !default; -$euiSizeL: $euiSize * 1.5 !default; -$euiSizeXL: $euiSize * 2 !default; -$euiSizeXXL: $euiSize * 2.5 !default; - -$euiButtonMinWidth: $euiSize * 7 !default; - -$euiScrollBar: $euiSize !default; -// Corner sizes are used as an inset border and therefore a smaller corner size means a larger thumb -$euiScrollBarCorner: $euiSizeXS !default; -$euiScrollBarCornerThin: $euiSizeS * .75 !default; +@import 'node_modules/@elastic/eui-theme-common/src/global_styling/variables/size' diff --git a/packages/eui/src/global_styling/variables/_states.scss b/packages/eui/src/global_styling/variables/_states.scss index fba4cc59caa..581a81bb59b 100644 --- a/packages/eui/src/global_styling/variables/_states.scss +++ b/packages/eui/src/global_styling/variables/_states.scss @@ -1,14 +1 @@ -// Colors -$euiFocusRingColor: rgba($euiColorPrimary, .3) !default; -$euiFocusRingAnimStartColor: rgba($euiColorPrimary, 0) !default; -$euiFocusRingAnimStartSize: 6px !default; -$euiFocusRingAnimStartSizeLarge: 10px !default; - -// Sizing -$euiFocusRingSizeLarge: $euiSizeXS !default; -$euiFocusRingSize: $euiFocusRingSizeLarge * .75 !default; - -// Transparency -$euiFocusTransparency: lightOrDarkTheme(.1, .2) !default; -$euiFocusTransparencyPercent: lightOrDarkTheme(90%, 80%) !default; -$euiFocusBackgroundColor: tintOrShade($euiColorPrimary, $euiFocusTransparencyPercent, $euiFocusTransparencyPercent) !default; +@import 'node_modules/@elastic/eui-theme-common/src/global_styling/variables/states' \ No newline at end of file diff --git a/packages/eui/src/global_styling/variables/_typography.scss b/packages/eui/src/global_styling/variables/_typography.scss index 1ca62f3248c..70a15446af9 100644 --- a/packages/eui/src/global_styling/variables/_typography.scss +++ b/packages/eui/src/global_styling/variables/_typography.scss @@ -1,75 +1 @@ -// Families -$euiFontFamily: 'Inter', BlinkMacSystemFont, Helvetica, Arial, sans-serif !default; -$euiCodeFontFamily: 'Roboto Mono', Menlo, Courier, monospace !default; - -// Careful using ligatures. Code editors like ACE will often error because of width calculations -$euiFontFeatureSettings: 'calt' 1, 'kern' 1, 'liga' 1 !default; - -// Font sizes -- scale is loosely based on Major Third (1.250) -$euiTextScale: 2.25, 1.75, 1.25, 1.125, 1, .875, .75 !default; - -$euiFontSize: $euiSize !default; // 5th position in scale -$euiFontSizeXS: $euiFontSize * nth($euiTextScale, 7) !default; // 12px -$euiFontSizeS: $euiFontSize * nth($euiTextScale, 6) !default; // 14px -$euiFontSizeM: $euiFontSize * nth($euiTextScale, 4) !default; // 18px -$euiFontSizeL: $euiFontSize * nth($euiTextScale, 3) !default; // 20px -$euiFontSizeXL: $euiFontSize * nth($euiTextScale, 2) !default; // 28px -$euiFontSizeXXL: $euiFontSize * nth($euiTextScale, 1) !default; // 36px - -// Line height -$euiLineHeight: 1.5 !default; -$euiBodyLineHeight: 1 !default; - -// Normally functions are imported before variables in `_index.scss` files -// But because they need to consume some typography variables they need to live here -@function convertToRem($size) { - @return #{$size / $euiFontSize}rem; -} - -// Our base gridline is at 1/2 the font-size, ensure line-heights stay on that gridline. -// EX: A proper line-height for text is 1.5 times the font-size. -// If our base font size (euiFontSize) is 16, our baseline is 8 (16*1.5 / 3). To ensure the -// text stays on the baseline, we pass a multiplier to calculate a line-height in rems. -@function lineHeightFromBaseline($multiplier: 3) { - @return convertToRem(($euiFontSize / 2) * $multiplier); -} - -// Titles map -// Lists all the properties per EuiTitle size that then gets looped through to create the selectors. -// The map allows for tokenization and easier customization per theme, otherwise you'd have to override the selectors themselves -$euiTitles: ( - 'xxxs': ( - 'font-size': $euiFontSizeXS, - 'line-height': lineHeightFromBaseline(3), - 'font-weight': $euiFontWeightBold, - ), - 'xxs': ( - 'font-size': $euiFontSizeS, - 'line-height': lineHeightFromBaseline(3), - 'font-weight': $euiFontWeightBold, - ), - 'xs': ( - 'font-size': $euiFontSize, - 'line-height': lineHeightFromBaseline(3), - 'font-weight': $euiFontWeightSemiBold, - 'letter-spacing': -.02em, - ), - 's': ( - 'font-size': $euiFontSizeL, - 'line-height': lineHeightFromBaseline(4), - 'font-weight': $euiFontWeightMedium, - 'letter-spacing': -.025em, - ), - 'm': ( - 'font-size': $euiFontSizeXL, - 'line-height': lineHeightFromBaseline(5), - 'font-weight': $euiFontWeightLight, - 'letter-spacing': -.04em, - ), - 'l': ( - 'font-size': $euiFontSizeXXL, - 'line-height': lineHeightFromBaseline(6), - 'font-weight': $euiFontWeightLight, - 'letter-spacing': -.03em, - ), -) !default; +@import 'node_modules/@elastic/eui-theme-common/src/global_styling/variables/typography' \ No newline at end of file diff --git a/packages/eui/src/global_styling/variables/_z_index.scss b/packages/eui/src/global_styling/variables/_z_index.scss index 2448a34c61a..2c2769dbd85 100644 --- a/packages/eui/src/global_styling/variables/_z_index.scss +++ b/packages/eui/src/global_styling/variables/_z_index.scss @@ -1,34 +1,2 @@ // 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 - -$euiZLevel0: 0; -$euiZLevel1: 1000; -$euiZLevel2: 2000; -$euiZLevel3: 3000; -$euiZLevel4: 4000; -$euiZLevel5: 5000; -$euiZLevel6: 6000; -$euiZLevel7: 7000; -$euiZLevel8: 8000; -$euiZLevel9: 9000; - -$euiZToastList: $euiZLevel9; -$euiZModal: $euiZLevel8; -$euiZMask: $euiZLevel6; -$euiZNavigation: $euiZLevel6; -$euiZContentMenu: $euiZLevel2; -$euiZHeader: $euiZLevel1; -$euiZFlyout: $euiZHeader; -$euiZMaskBelowHeader: $euiZHeader; -$euiZContent: $euiZLevel0; +@import 'node_modules/@elastic/eui-theme-common/src/global_styling/variables/z_index' diff --git a/packages/eui/src/global_styling/variables/animations.ts b/packages/eui/src/global_styling/variables/animations.ts index 46bce009c92..94a1f17c3d5 100644 --- a/packages/eui/src/global_styling/variables/animations.ts +++ b/packages/eui/src/global_styling/variables/animations.ts @@ -5,62 +5,15 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ -import { CSSProperties } from 'react'; -/** - * A constant storing the `prefers-reduced-motion` media query - * so that when it is turned off, animations are not run. - */ -export const euiCanAnimate = - '@media screen and (prefers-reduced-motion: no-preference)'; - -/** - * A constant storing the `prefers-reduced-motion` media query that will - * only apply the content if the setting is off (reduce). - */ -export const euiCantAnimate = - '@media screen and (prefers-reduced-motion: reduce)'; - -/** - * Speeds / Durations / Delays - */ - -export const EuiThemeAnimationSpeeds = [ - 'extraFast', - 'fast', - 'normal', - 'slow', - 'extraSlow', -] as const; - -export type _EuiThemeAnimationSpeed = (typeof EuiThemeAnimationSpeeds)[number]; - -export type _EuiThemeAnimationSpeeds = { - /** - Default value: 90ms */ - extraFast: CSSProperties['animationDuration']; - /** - Default value: 150ms */ - fast: CSSProperties['animationDuration']; - /** - Default value: 250ms */ - normal: CSSProperties['animationDuration']; - /** - Default value: 350ms */ - slow: CSSProperties['animationDuration']; - /** - Default value: 500ms */ - extraSlow: CSSProperties['animationDuration']; -}; - -/** - * Easings / Timing functions - */ - -export const EuiThemeAnimationEasings = ['bounce', 'resistance'] as const; - -export type _EuiThemeAnimationEasing = - (typeof EuiThemeAnimationEasings)[number]; - -export type _EuiThemeAnimationEasings = Record< - _EuiThemeAnimationEasing, - CSSProperties['animationTimingFunction'] ->; - -export type _EuiThemeAnimation = _EuiThemeAnimationEasings & - _EuiThemeAnimationSpeeds; +export { + euiCanAnimate, + euiCantAnimate, + EuiThemeAnimationSpeeds, + EuiThemeAnimationEasings, + type _EuiThemeAnimationSpeed, + type _EuiThemeAnimationSpeeds, + type _EuiThemeAnimationEasing, + type _EuiThemeAnimationEasings, + type _EuiThemeAnimation, +} from '@elastic/eui-theme-common'; diff --git a/packages/eui/src/global_styling/variables/borders.ts b/packages/eui/src/global_styling/variables/borders.ts index 10eda82eb9b..642575add78 100644 --- a/packages/eui/src/global_styling/variables/borders.ts +++ b/packages/eui/src/global_styling/variables/borders.ts @@ -6,69 +6,11 @@ * Side Public License, v 1. */ -import { CSSProperties } from 'react'; -import { ColorModeSwitch } from '../../services/theme/types'; - -export interface _EuiThemeBorderWidthValues { - /** - * Thinnest width for border - * - Default value: 1px - */ - thin: CSSProperties['borderWidth']; - /** - * Thickest width for border - * - Default value: 2px - */ - thick: CSSProperties['borderWidth']; -} - -export interface _EuiThemeBorderRadiusValues { - /** - * Primary corner radius size - * - Default value: 6px - */ - medium: CSSProperties['borderRadius']; - /** - * Small corner radius size - * - Default value: 4px - */ - small: CSSProperties['borderRadius']; -} - -export interface _EuiThemeBorderColorValues { - /** - * Color for all borders; Default is `colors.lightShade` - */ - color: ColorModeSwitch; -} - -export interface _EuiThemeBorderValues extends _EuiThemeBorderColorValues { - /** - * Varied thicknesses for borders - */ - width: _EuiThemeBorderWidthValues; - /** - * Varied border radii - */ - radius: _EuiThemeBorderRadiusValues; -} - -export interface _EuiThemeBorderTypes { - /** - * Full `border` property string computed using `border.width.thin` and `border.color` - * - Default value: 1px solid [colors.lightShade] - */ - thin: CSSProperties['border']; - /** - * Full `border` property string computed using `border.width.thick` and `border.color` - * - Default value: 2px solid [colors.lightShade] - */ - thick: CSSProperties['border']; - /** - * Full editable style `border` property string computed using `border.width.thick` and `border.color` - * - Default value: 2px dotted [colors.lightShade] - */ - editable: CSSProperties['border']; -} - -export type _EuiThemeBorder = _EuiThemeBorderValues & _EuiThemeBorderTypes; +export type { + _EuiThemeBorderWidthValues, + _EuiThemeBorderRadiusValues, + _EuiThemeBorderColorValues, + _EuiThemeBorderValues, + _EuiThemeBorderTypes, + _EuiThemeBorder, +} from '@elastic/eui-theme-common'; diff --git a/packages/eui/src/global_styling/variables/breakpoint.ts b/packages/eui/src/global_styling/variables/breakpoint.ts index 68aef1fba38..85032f57d98 100644 --- a/packages/eui/src/global_styling/variables/breakpoint.ts +++ b/packages/eui/src/global_styling/variables/breakpoint.ts @@ -6,23 +6,8 @@ * Side Public License, v 1. */ -export const EuiThemeBreakpoints = ['xs', 's', 'm', 'l', 'xl'] as const; - -// This type cannot be a string enum / must be a generic string -// in case consumers add custom breakpoint sizes, such as xxl or xxs -export type _EuiThemeBreakpoint = string; - -// Explicitly list out each key so we can document default values -// via JSDoc (which is available to devs in IDE via intellisense) -export type _EuiThemeBreakpoints = Record<_EuiThemeBreakpoint, number> & { - /** - Default value: 0 */ - xs: number; - /** - Default value: 575 */ - s: number; - /** - Default value: 768 */ - m: number; - /** - Default value: 992 */ - l: number; - /** - Default value: 1200 */ - xl: number; -}; +export { + EuiThemeBreakpoints, + type _EuiThemeBreakpoint, + type _EuiThemeBreakpoints, +} from '@elastic/eui-theme-common'; diff --git a/packages/eui/src/global_styling/variables/colors.ts b/packages/eui/src/global_styling/variables/colors.ts index ab15e23d166..48e23b66f82 100644 --- a/packages/eui/src/global_styling/variables/colors.ts +++ b/packages/eui/src/global_styling/variables/colors.ts @@ -6,154 +6,13 @@ * Side Public License, v 1. */ -import { - ColorModeSwitch, - StrictColorModeSwitch, -} from '../../services/theme/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 opposite of `emptyShade` - */ - fullShade: ColorModeSwitch; -}; - -export type _EuiThemeTextColors = { - /** - * Computed against `colors.darkestShade` - */ - text: ColorModeSwitch; - /** - * Computed against `colors.text` - */ - title: ColorModeSwitch; - /** - * Computed against `colors.mediumShade` - */ - subduedText: 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; - /** - * The base color for shadows that gets `transparentized` - * at a value based on the `colorMode` and then layered. - */ - shadow: ColorModeSwitch; -}; - -export type _EuiThemeConstantColors = { - /** - * Purest **white** - */ - ghost: string; - /** - * Purest **black** - */ - ink: string; -}; - -export type _EuiThemeColorsMode = _EuiThemeBrandColors & - _EuiThemeBrandTextColors & - _EuiThemeShadeColors & - _EuiThemeSpecialColors & - _EuiThemeTextColors; - -export type _EuiThemeColors = StrictColorModeSwitch<_EuiThemeColorsMode> & - _EuiThemeConstantColors; +export type { + _EuiThemeBrandColors, + _EuiThemeBrandTextColors, + _EuiThemeShadeColors, + _EuiThemeTextColors, + _EuiThemeSpecialColors, + _EuiThemeConstantColors, + _EuiThemeColorsMode, + _EuiThemeColors, +} from '@elastic/eui-theme-common'; diff --git a/packages/eui/src/global_styling/variables/levels.ts b/packages/eui/src/global_styling/variables/levels.ts index 7d38c71791b..bf808d9abc7 100644 --- a/packages/eui/src/global_styling/variables/levels.ts +++ b/packages/eui/src/global_styling/variables/levels.ts @@ -6,55 +6,8 @@ * Side Public License, v 1. */ -import { CSSProperties } from 'react'; - -/** - * 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 const EuiThemeLevels = [ - 'toast', - 'modal', - 'mask', - 'navigation', - 'menu', - 'header', - 'flyout', - 'maskBelowHeader', - 'content', -] as const; - -export type _EuiThemeLevel = (typeof EuiThemeLevels)[number]; - -export type _EuiThemeLevels = { - /** - Default value: 9000 */ - toast: NonNullable; - /** - Default value: 8000 */ - modal: NonNullable; - /** - Default value: 6000 */ - mask: NonNullable; - /** - Default value: 6000 */ - navigation: NonNullable; - /** - Default value: 2000 */ - menu: NonNullable; - /** - Default value: 1000 */ - header: NonNullable; - /** - Default value: 1000 */ - flyout: NonNullable; - /** - Default value: 1000 */ - maskBelowHeader: NonNullable; - /** - Default value: 0 */ - content: NonNullable; -}; +export { + EuiThemeLevels, + type _EuiThemeLevel, + type _EuiThemeLevels, +} from '@elastic/eui-theme-common'; diff --git a/packages/eui/src/global_styling/variables/shadow.ts b/packages/eui/src/global_styling/variables/shadow.ts index 7761fbdb9a0..69aeb0d5846 100644 --- a/packages/eui/src/global_styling/variables/shadow.ts +++ b/packages/eui/src/global_styling/variables/shadow.ts @@ -6,23 +6,9 @@ * Side Public License, v 1. */ -export const EuiThemeShadowSizes = ['xs', 's', 'm', 'l', 'xl'] as const; - -export type _EuiThemeShadowSize = (typeof EuiThemeShadowSizes)[number]; - -/** - * Shadow t-shirt sizes descriptions - */ -export const _EuiShadowSizesDescriptions: Record<_EuiThemeShadowSize, string> = - { - xs: 'Very subtle shadow used on small components.', - s: 'Adds subtle depth, usually used in conjunction with a border.', - m: 'Used on small sized portalled content like popovers.', - l: 'Primary shadow used in most cases to add visible depth.', - xl: 'Very large shadows used for large portalled style containers like modals and flyouts.', - }; - -export interface _EuiThemeShadowCustomColor { - color?: string; - property?: 'box-shadow' | 'filter'; -} +export { + EuiThemeShadowSizes, + _EuiShadowSizesDescriptions, + type _EuiThemeShadowSize, + type _EuiThemeShadowCustomColor, +} from '@elastic/eui-theme-common'; diff --git a/packages/eui/src/global_styling/variables/size.ts b/packages/eui/src/global_styling/variables/size.ts index 98428346b61..da3c3c1eecb 100644 --- a/packages/eui/src/global_styling/variables/size.ts +++ b/packages/eui/src/global_styling/variables/size.ts @@ -6,42 +6,9 @@ * Side Public License, v 1. */ -export type _EuiThemeBase = number; - -export const EuiThemeSizes = [ - 'xxs', - 'xs', - 's', - 'm', - 'base', - 'l', - 'xl', - 'xxl', - 'xxxl', - 'xxxxl', -] as const; - -export type _EuiThemeSize = (typeof EuiThemeSizes)[number]; - -export type _EuiThemeSizes = { - /** - Default value: 2px */ - xxs: string; - /** - Default value: 4px */ - xs: string; - /** - Default value: 8px */ - s: string; - /** - Default value: 12px */ - m: string; - /** - Default value: 16px */ - base: string; - /** - Default value: 24px */ - l: string; - /** - Default value: 32px */ - xl: string; - /** - Default value: 40px */ - xxl: string; - /** - Default value: 48px */ - xxxl: string; - /** - Default value: 64px */ - xxxxl: string; -}; +export { + EuiThemeSizes, + type _EuiThemeBase, + type _EuiThemeSize, + type _EuiThemeSizes, +} from '@elastic/eui-theme-common'; diff --git a/packages/eui/src/global_styling/variables/states.ts b/packages/eui/src/global_styling/variables/states.ts index 4ec8bf0d87d..205c6e7e7b9 100644 --- a/packages/eui/src/global_styling/variables/states.ts +++ b/packages/eui/src/global_styling/variables/states.ts @@ -6,28 +6,4 @@ * Side Public License, v 1. */ -import { ColorModeSwitch } from '../../services/theme/types'; -import { CSSProperties } from 'react'; - -export interface _EuiThemeFocus { - /** - * Default color of the focus ring, some components may override this property - * - Default value: currentColor - */ - color: ColorModeSwitch; - /** - * Thickness of the outline - * - Default value: 2px - */ - width: CSSProperties['borderWidth']; - /** - * Used to transparentize the focus background color - * - Default value: { LIGHT: 0.1, DARK: 0.2 } - */ - transparency: ColorModeSwitch; - /** - * Default focus background color. Not all components set a background color on focus - * - Default value: `colors.primary` computed with `focus.transparency` - */ - backgroundColor: ColorModeSwitch; -} +export type { _EuiThemeFocus } from '@elastic/eui-theme-common'; diff --git a/packages/eui/src/global_styling/variables/typography.ts b/packages/eui/src/global_styling/variables/typography.ts index 80ebbc8b1cb..dfd57186191 100644 --- a/packages/eui/src/global_styling/variables/typography.ts +++ b/packages/eui/src/global_styling/variables/typography.ts @@ -6,141 +6,17 @@ * Side Public License, v 1. */ -import { CSSProperties } from 'react'; - -/** - * Font units of measure - */ - -export const EuiThemeFontUnits = ['rem', 'px', 'em'] as const; - -export type _EuiThemeFontUnit = (typeof EuiThemeFontUnits)[number]; - -/* - * Font scale - */ - -export const EuiThemeFontScales = [ - 'xxxs', - 'xxs', - 'xs', - 's', - 'm', - 'l', - 'xl', - 'xxl', -] as const; - -export type _EuiThemeFontScale = (typeof EuiThemeFontScales)[number]; - -export type _EuiThemeFontScales = Record<_EuiThemeFontScale, number>; - -/* - * 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; - /** - * The font family used for serif UI elements like blockquotes within EuiText - */ - familySerif?: string; - /** - * Controls advanced features OpenType fonts. - * https://developer.mozilla.org/en-US/docs/Web/CSS/font-feature-settings - */ - featureSettings?: string; - /** - * Sets the default units used for font size & line height set by UI components - * like EuiText or EuiTitle. Defaults to `rem`. - * - * NOTE: This may overridden by some internal usages, e.g. - * EuiText's `relative` size which must use `em`. - * - * @default 'rem' - */ - defaultUnits: _EuiThemeFontUnit; - /** - * 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; -}; - -/* - * Font weights - */ - -export const EuiThemeFontWeights = [ - 'light', - 'regular', - 'medium', - 'semiBold', - 'bold', -] as const; - -export type _EuiThemeFontWeight = (typeof EuiThemeFontWeights)[number]; - -export type _EuiThemeFontWeights = { - /** - Default value: 300 */ - light: CSSProperties['fontWeight']; - /** - Default value: 400 */ - regular: CSSProperties['fontWeight']; - /** - Default value: 500 */ - medium: CSSProperties['fontWeight']; - /** - Default value: 600 */ - semiBold: CSSProperties['fontWeight']; - /** - Default value: 700 */ - bold: CSSProperties['fontWeight']; -}; - -/** - * Body / Base styles - */ - -export interface _EuiThemeBody { - /** - * A sizing key from one of the font scales to set as the base body font-size - */ - scale: _EuiThemeFontScale; - /** - * A font weight key for setting the base body weight - */ - weight: keyof _EuiThemeFontWeights; -} - -/** - * Title styles - */ - -export interface _EuiThemeTitle { - /** - * A font weight key for setting the base weight for titles and headings - */ - weight: keyof _EuiThemeFontWeights; -} - -/* - * Font - */ - -export type _EuiThemeFont = _EuiThemeFontBase & { - scale: _EuiThemeFontScales; - /** - * @see {@link https://eui.elastic.co/#/theming/typography/values%23font-weight | Reference} for more information - */ - weight: _EuiThemeFontWeights; - body: _EuiThemeBody; - title: _EuiThemeTitle; -}; +export { + EuiThemeFontUnits, + EuiThemeFontScales, + EuiThemeFontWeights, + type _EuiThemeFontUnit, + type _EuiThemeFontScale, + type _EuiThemeFontScales, + type _EuiThemeFontBase, + type _EuiThemeFontWeight, + type _EuiThemeFontWeights, + type _EuiThemeBody, + type _EuiThemeTitle, + type _EuiThemeFont, +} from '@elastic/eui-theme-common'; diff --git a/packages/eui/src/services/theme/provider.tsx b/packages/eui/src/services/theme/provider.tsx index c5a6957e8c3..e0678603074 100644 --- a/packages/eui/src/services/theme/provider.tsx +++ b/packages/eui/src/services/theme/provider.tsx @@ -20,6 +20,7 @@ import React, { import { Global, type CSSObject } from '@emotion/react'; import isEqual from 'lodash/isEqual'; +import { EUI_EXPERIMENTAL_THEME_ENABLED_KEY } from '../../themes/themes'; import type { CommonProps } from '../../components/common'; import { cloneElementWithCss } from '../emotion'; import { css, cx } from '../emotion/css'; @@ -117,6 +118,18 @@ export const EuiThemeProvider = ({ ) ); + // TODO: temp. testing code only, remove once obsolete + useEffect(() => { + if (process.env.NODE_ENV === 'development') { + const isEnabled = + localStorage.getItem(EUI_EXPERIMENTAL_THEME_ENABLED_KEY) != null; + + if (!isEnabled) { + localStorage.setItem(EUI_EXPERIMENTAL_THEME_ENABLED_KEY, 'true'); + } + } + }, []); + useEffect(() => { const newSystem = _system || parentSystem; if (prevSystemKey.current !== newSystem.key) { diff --git a/packages/eui/src/services/theme/types.ts b/packages/eui/src/services/theme/types.ts index 04b93b40f20..a8912db19ed 100644 --- a/packages/eui/src/services/theme/types.ts +++ b/packages/eui/src/services/theme/types.ts @@ -5,104 +5,18 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ - -import type { CSSObject } from '@emotion/react'; - -import { RecursivePartial, ValueOf } from '../../components/common'; -import { _EuiThemeAnimation } from '../../global_styling/variables/animations'; -import { _EuiThemeBreakpoints } from '../../global_styling/variables/breakpoint'; -import { _EuiThemeBorder } from '../../global_styling/variables/borders'; -import { _EuiThemeColors } from '../../global_styling/variables/colors'; -import { - _EuiThemeBase, - _EuiThemeSizes, -} from '../../global_styling/variables/size'; -import { _EuiThemeFont } from '../../global_styling/variables/typography'; -import { _EuiThemeFocus } from '../../global_styling/variables/states'; -import { _EuiThemeLevels } from '../../global_styling/variables/levels'; - -export const COLOR_MODES_STANDARD = { - light: 'LIGHT', - dark: 'DARK', -} as const; -export const COLOR_MODES_INVERSE = 'INVERSE' as const; - -export type EuiThemeColorModeInverse = typeof COLOR_MODES_INVERSE; -export type EuiThemeColorModeStandard = ValueOf; -export type EuiThemeColorMode = - | 'light' - | 'dark' - | EuiThemeColorModeStandard - | 'inverse' - | EuiThemeColorModeInverse; - -export type ColorModeSwitch = - | { - [key in EuiThemeColorModeStandard]: T; - } - | T; - -export type StrictColorModeSwitch = { - [key in EuiThemeColorModeStandard]: T; -}; - -export type EuiThemeShape = { - colors: _EuiThemeColors; - /** - Default value: 16 */ - base: _EuiThemeBase; - /** - * @see {@link https://eui.elastic.co/#/theming/sizing | Reference} for more information - */ - size: _EuiThemeSizes; - font: _EuiThemeFont; - border: _EuiThemeBorder; - focus: _EuiThemeFocus; - animation: _EuiThemeAnimation; - breakpoint: _EuiThemeBreakpoints; - levels: _EuiThemeLevels; -}; - -export type EuiThemeSystem = { - root: EuiThemeShape & T; - model: EuiThemeShape & T; - key: string; -}; - -export type EuiThemeModifications = RecursivePartial; - -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; -}; - -export type EuiThemeNested = { - isGlobalTheme: boolean; - hasDifferentColorFromGlobalTheme: boolean; - bodyColor: string; - colorClassName: string; - setGlobalCSSVariables: Function; - globalCSSVariables?: CSSObject; - setNearestThemeCSSVariables: Function; - themeCSSVariables?: CSSObject; -}; +export { + COLOR_MODES_STANDARD, + COLOR_MODES_INVERSE, + type EuiThemeColorModeInverse, + type EuiThemeColorModeStandard, + type EuiThemeColorMode, + type ColorModeSwitch, + type StrictColorModeSwitch, + type EuiThemeShape, + type EuiThemeSystem, + type EuiThemeModifications, + type ComputedThemeShape, + type EuiThemeComputed, + type EuiThemeNested, +} from '@elastic/eui-theme-common'; diff --git a/packages/eui/src/services/theme/utils.ts b/packages/eui/src/services/theme/utils.ts index c7c9c5216d0..e86ad5c659d 100644 --- a/packages/eui/src/services/theme/utils.ts +++ b/packages/eui/src/services/theme/utils.ts @@ -6,375 +6,15 @@ * Side Public License, v 1. */ -import { - EuiThemeColorMode, - EuiThemeColorModeInverse, - EuiThemeColorModeStandard, - EuiThemeModifications, - EuiThemeSystem, - EuiThemeShape, - EuiThemeComputed, - COLOR_MODES_STANDARD, - COLOR_MODES_INVERSE, -} from './types'; - -export const DEFAULT_COLOR_MODE = COLOR_MODES_STANDARD.light; - -/** - * Returns whether the parameter is an object - * @param {any} obj - Anything - */ -const isObject = (obj: any) => obj && typeof obj === 'object'; - -/** - * Returns whether the provided color mode is `inverse` - * @param {string} colorMode - `light`, `dark`, or `inverse` - */ -export const isInverseColorMode = ( - colorMode?: string -): colorMode is EuiThemeColorModeInverse => { - return colorMode === COLOR_MODES_INVERSE; -}; - -/** - * Returns the color mode configured in the current EuiThemeProvider. - * Returns the parent color mode if none is explicity set. - * @param {string} colorMode - `light`, `dark`, or `inverse` - * @param {string} parentColorMode - `LIGHT` or `DARK`; used as the fallback - */ -export const getColorMode = ( - colorMode?: EuiThemeColorMode, - parentColorMode?: EuiThemeColorModeStandard -): EuiThemeColorModeStandard => { - if (colorMode == null) { - return parentColorMode || DEFAULT_COLOR_MODE; - } - const mode = colorMode.toUpperCase() as - | EuiThemeColorModeInverse - | EuiThemeColorModeStandard; - if (isInverseColorMode(mode)) { - return parentColorMode === COLOR_MODES_STANDARD.dark || - parentColorMode === undefined - ? COLOR_MODES_STANDARD.light - : COLOR_MODES_STANDARD.dark; - } else { - return mode; - } -}; - -/** - * Returns a value at a given path on an object. - * If `colorMode` is provided, will scope the value to the appropriate color mode key (LIGHT\DARK) - * @param {object} model - Object - * @param {string} _path - Dot-notated string to a path on the object - * @param {string} colorMode - `light` or `dark` - */ -export const getOn = ( - model: { [key: string]: any }, - _path: string, - colorMode?: EuiThemeColorModeStandard -) => { - const path = _path.split('.'); - let node = model; - while (path.length) { - const segment = path.shift()!; - - if (node.hasOwnProperty(segment) === false) { - 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 { - return undefined; - } - } else { - if (node[segment] instanceof Computed) { - node = node[segment].getValue(null, null, node, colorMode); - } else { - node = node[segment]; - } - } - } - - return node; -}; - -/** - * Sets a value at a given path on an object. - * @param {object} model - Object - * @param {string} _path - Dot-notated string to a path on the object - * @param {any} string - The value to set - */ -export const setOn = ( - model: { [key: string]: any }, - _path: string, - value: any -) => { - const path = _path.split('.'); - const propertyName = path.pop()!; - let node = model; - - while (path.length) { - const segment = path.shift()!; - if (node.hasOwnProperty(segment) === false) { - node[segment] = {}; - } - node = node[segment]; - } - - node[propertyName] = value; - return true; -}; - -/** - * Creates a class to store the `computer` method and its eventual parameters. - * Allows for on-demand computation with up-to-date parameters via `getValue` method. - * @constructor - * @param {function} computer - Function to be computed - * @param {string | array} dependencies - Dependencies passed to the `computer` as parameters - */ -export class Computed { - constructor( - public computer: (...values: any[]) => T, - public dependencies: string | string[] = [] - ) {} - - /** - * Executes the `computer` method with the current state of the theme - * by taking into account previously computed values and modifications. - * @param {Proxy | object} base - Computed or uncomputed theme - * @param {Proxy | object} modifications - Theme value overrides - * @param {object} working - Partially computed theme - * @param {string} colorMode - `light` or `dark` - */ - getValue( - base: EuiThemeSystem | EuiThemeShape, - modifications: EuiThemeModifications = {}, - working: EuiThemeComputed, - colorMode: EuiThemeColorModeStandard - ) { - 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) ?? - getOn(modifications, dependency, colorMode) ?? - getOn(base, dependency, colorMode) - ); - }) - ); - } -} - -/** - * Returns a Class (`Computed`) that stores the arbitrary computer method - * and references to its optional dependecies. - * @param {function} computer - Arbitrary method to be called at compute time. - * @param {string | array} dependencies - Values that will be provided to `computer` at compute time. - */ -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); -} - -/** - * Takes an uncomputed theme, and computes and returns all values taking - * into consideration value overrides and configured color mode. - * Overrides take precedence, and only values in the current color mode - * are computed and returned. - * @param {Proxy} base - Object to transform into Proxy - * @param {Proxy | object} over - Unique identifier or name - * @param {string} colorMode - `light` or `dark` - */ -export const getComputed = ( - base: EuiThemeSystem, - over: Partial>, - colorMode: EuiThemeColorModeStandard -): EuiThemeComputed => { - const output = { themeName: base.key }; - - function loop( - base: { [key: string]: any }, - over: { [key: string]: any }, - checkExisting: boolean = false, - path?: string - ) { - Object.keys(base).forEach((key) => { - let newPath = path ? `${path}.${key}` : `${key}`; - // @ts-expect-error `key` is not necessarily a colorMode 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); - } - } - }); - } - // Compute standard theme values and apply overrides - loop(base, over); - // Compute and apply extension values only - loop(over, {}, true); - return output as EuiThemeComputed; -}; - -/** - * Builds a Proxy with a custom `handler` designed to self-reference values - * and prevent arbitrary value overrides. - * @param {object} model - Object to transform into Proxy - * @param {string} key - Unique identifier or name - */ -export const buildTheme = (model: T, key: string) => { - const handler: ProxyHandler> = { - getPrototypeOf(target) { - return Reflect.getPrototypeOf(target.model); - }, - - setPrototypeOf(target, prototype) { - return Reflect.setPrototypeOf(target.model, prototype); - }, - - isExtensible(target) { - return Reflect.isExtensible(target); - }, - - preventExtensions(target) { - return Reflect.preventExtensions(target.model); - }, - - getOwnPropertyDescriptor(target, key) { - return Reflect.getOwnPropertyDescriptor(target.model, key); - }, - - defineProperty(target, property, attributes) { - return Reflect.defineProperty(target.model, property, attributes); - }, - - has(target, property) { - return Reflect.has(target.model, property); - }, - - get(_target, property) { - if (property === 'key') { - return _target[property]; - } - - // prevent Safari from locking up when the proxy is used in dev tools - // as it doesn't support getPrototypeOf - if (property === '__proto__') return {}; - - const target = property === 'root' ? _target : _target.model || _target; - // @ts-ignore `string` index signature - const value = target[property]; - if (isObject(value) && !Array.isArray(value)) { - return new Proxy( - { - model: value, - root: _target.root, - key: `_${_target.key}`, - }, - handler - ); - } else { - return value; - } - }, - - set(target: any) { - return target; - }, - - deleteProperty(target: any) { - return target; - }, - - ownKeys(target) { - return Reflect.ownKeys(target.model); - }, - - apply(target: any) { - return target; - }, - - construct(target: any) { - return target; - }, - }; - const themeProxy = new Proxy({ model, root: model, key }, handler); - - return themeProxy; -}; - -/** - * Deeply merges two objects, using `source` values whenever possible. - * @param {object} _target - Object with fallback values - * @param {object} source - Object with desired values - */ -export const mergeDeep = ( - _target: { [key: string]: any }, - source: { [key: string]: any } = {} -) => { - const target = { ..._target }; - - if (!isObject(target) || !isObject(source)) { - return source; - } - - Object.keys(source).forEach((key) => { - const targetValue = target[key]; - const sourceValue = source[key]; - - if (isObject(targetValue) && isObject(sourceValue)) { - target[key] = mergeDeep({ ...targetValue }, { ...sourceValue }); - } else { - target[key] = sourceValue; - } - }); - - return target; -}; +export { + DEFAULT_COLOR_MODE, + isInverseColorMode, + getColorMode, + getOn, + setOn, + Computed, + computed, + getComputed, + buildTheme, + mergeDeep, +} from '@elastic/eui-theme-common'; diff --git a/packages/eui/src/themes/index.ts b/packages/eui/src/themes/index.ts index cec3cda26c3..a77ffa51ee1 100644 --- a/packages/eui/src/themes/index.ts +++ b/packages/eui/src/themes/index.ts @@ -7,7 +7,11 @@ */ export type { EUI_THEME } from './themes'; -export { EUI_THEMES, isDefaultTheme } from './themes'; +export { + EUI_THEMES, + isDefaultTheme, + isExperimentalThemeEnabled, +} from './themes'; export { AMSTERDAM_NAME_KEY, EuiThemeAmsterdam } from './amsterdam/theme'; diff --git a/packages/eui/src/themes/themes.ts b/packages/eui/src/themes/themes.ts index d2f714092df..540fac2e5fe 100644 --- a/packages/eui/src/themes/themes.ts +++ b/packages/eui/src/themes/themes.ts @@ -31,3 +31,14 @@ export const EUI_THEMES: EUI_THEME[] = [ export const isDefaultTheme = (name: string) => { return name === AMSTERDAM_NAME_KEY; }; + +export const EUI_EXPERIMENTAL_THEME_ENABLED_KEY = + 'eui-experimental-theme-enabled'; + +export const isExperimentalThemeEnabled = () => { + if (typeof localStorage !== 'undefined') { + return localStorage.getItem(EUI_EXPERIMENTAL_THEME_ENABLED_KEY) === 'true'; + } + + return false; +}; diff --git a/packages/website/package.json b/packages/website/package.json index a3937745371..fde2b3f951d 100644 --- a/packages/website/package.json +++ b/packages/website/package.json @@ -4,6 +4,7 @@ "private": true, "scripts": { "docusaurus": "docusaurus", + "build:workspaces": "yarn workspaces foreach -R --from @elastic/eui-website run build:workspaces && yarn workspaces foreach -Rti --from @elastic/eui-website run build", "start": "cross-env NODE_OPTIONS=--openssl-legacy-provider docusaurus start", "build": "cross-env NODE_OPTIONS=--openssl-legacy-provider docusaurus build", "swizzle": "docusaurus swizzle", @@ -19,7 +20,7 @@ "@docusaurus/preset-classic": "^3.5.2", "@elastic/charts": "^68.0.2", "@elastic/datemath": "^5.0.3", - "@elastic/eui": "97.2.0", + "@elastic/eui": "workspace:^", "@elastic/eui-docgen": "workspace:^", "@elastic/eui-docusaurus-theme": "workspace:^", "@emotion/css": "^11.11.2", diff --git a/packages/website/src/components/homepage/highlights/index.tsx b/packages/website/src/components/homepage/highlights/index.tsx index e9957110530..680a61474f8 100644 --- a/packages/website/src/components/homepage/highlights/index.tsx +++ b/packages/website/src/components/homepage/highlights/index.tsx @@ -3,7 +3,6 @@ import { EuiButtonEmpty, EuiCard, EuiIcon, - EuiImage, EuiText, EuiTextAlign, useEuiMemoizedStyles, @@ -12,8 +11,6 @@ import { } from '@elastic/eui'; import { HomepageContainer, HomepageSection } from '../layout'; import { css } from '@emotion/react'; -import { useContext } from 'react'; -import { AppThemeContext } from '@elastic/eui-docusaurus-theme/lib/components/theme_context/index.js'; import SvgFlex from './svg/flex.svg'; import SvgSpacer from './svg/spacer.svg'; @@ -223,10 +220,7 @@ const getStyles = ({ euiTheme, colorMode }: UseEuiTheme) => ({ }); export const HomepageHighlights = () => { - const { theme } = useContext(AppThemeContext); const styles = useEuiMemoizedStyles(getStyles); - const isDarkMode = theme === 'dark'; - const headingId = useGeneratedHtmlId(); return ( diff --git a/yarn.lock b/yarn.lock index e00d9c2e7df..bdf87cc4186 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5751,8 +5751,9 @@ __metadata: "@docusaurus/types": "npm:^3.5.2" "@docusaurus/utils-validation": "npm:^3.5.2" "@elastic/datemath": "npm:^5.0.3" - "@elastic/eui": "npm:97.2.0" + "@elastic/eui": "workspace:^" "@elastic/eui-docgen": "workspace:^" + "@elastic/eui-theme-borealis": "workspace:^" "@emotion/css": "npm:^11.11.2" "@emotion/react": "npm:^11.11.4" "@types/react": "npm:^18.3.3" @@ -5780,6 +5781,72 @@ __metadata: languageName: unknown linkType: soft +"@elastic/eui-theme-borealis@workspace:^, @elastic/eui-theme-borealis@workspace:packages/eui-theme-borealis": + version: 0.0.0-use.local + resolution: "@elastic/eui-theme-borealis@workspace:packages/eui-theme-borealis" + dependencies: + "@elastic/eui-theme-common": "workspace:^" + "@types/jest": "npm:^29.5.12" + "@types/prettier": "npm:2.7.3" + "@typescript-eslint/eslint-plugin": "npm:^5.59.7" + "@typescript-eslint/parser": "npm:^5.59.7" + eslint: "npm:^8.41.0" + eslint-config-prettier: "npm:^8.8.0" + eslint-plugin-import: "npm:^2.27.5" + eslint-plugin-jest: "npm:^28.5.0" + eslint-plugin-local: "npm:^1.0.0" + eslint-plugin-prettier: "npm:^4.2.1" + jest: "npm:^29.7.0" + prettier: "npm:^2.8.8" + stylelint: "npm:^15.7.0" + stylelint-config-prettier-scss: "npm:^1.0.0" + stylelint-config-standard: "npm:^33.0.0" + stylelint-config-standard-scss: "npm:^9.0.0" + typescript: "npm:^5.6.2" + peerDependencies: + "@elastic/eui-theme-common": 0.0.1 + languageName: unknown + linkType: soft + +"@elastic/eui-theme-common@workspace:^, @elastic/eui-theme-common@workspace:packages/eui-theme-common": + version: 0.0.0-use.local + resolution: "@elastic/eui-theme-common@workspace:packages/eui-theme-common" + dependencies: + "@babel/cli": "npm:^7.21.5" + "@babel/core": "npm:^7.21.8" + "@babel/preset-env": "npm:^7.21.5" + "@babel/preset-react": "npm:^7.18.6" + "@babel/preset-typescript": "npm:^7.21.5" + "@emotion/react": "npm:^11.11.0" + "@types/jest": "npm:^29.5.12" + "@types/prettier": "npm:2.7.3" + "@types/react": "npm:^16.9 || ^17.0 || ^18.0" + "@types/react-dom": "npm:^16.9 || ^17.0 || ^18.0" + "@typescript-eslint/eslint-plugin": "npm:^5.59.7" + "@typescript-eslint/parser": "npm:^5.59.7" + eslint: "npm:^8.41.0" + eslint-config-prettier: "npm:^8.8.0" + eslint-plugin-import: "npm:^2.27.5" + eslint-plugin-jest: "npm:^28.5.0" + eslint-plugin-local: "npm:^1.0.0" + eslint-plugin-prettier: "npm:^4.2.1" + jest: "npm:^29.7.0" + prettier: "npm:^2.8.8" + react: "npm:^18.2.0" + react-dom: "npm:^18.2.0" + rimraf: "npm:^6.0.1" + stylelint: "npm:^15.7.0" + stylelint-config-prettier-scss: "npm:^1.0.0" + stylelint-config-standard: "npm:^33.0.0" + stylelint-config-standard-scss: "npm:^9.0.0" + typescript: "npm:4.5.3" + peerDependencies: + "@emotion/react": 11.x + react: ^16.12 || ^17.0 || ^18.0 + react-dom: ^16.12 || ^17.0 || ^18.0 + languageName: unknown + linkType: soft + "@elastic/eui-website@workspace:packages/website": version: 0.0.0-use.local resolution: "@elastic/eui-website@workspace:packages/website" @@ -5792,7 +5859,7 @@ __metadata: "@docusaurus/types": "npm:^3.5.2" "@elastic/charts": "npm:^68.0.2" "@elastic/datemath": "npm:^5.0.3" - "@elastic/eui": "npm:97.2.0" + "@elastic/eui": "workspace:^" "@elastic/eui-docgen": "workspace:^" "@elastic/eui-docusaurus-theme": "workspace:^" "@emotion/css": "npm:^11.11.2" @@ -5818,58 +5885,7 @@ __metadata: languageName: unknown linkType: soft -"@elastic/eui@npm:97.2.0": - version: 97.2.0 - resolution: "@elastic/eui@npm:97.2.0" - dependencies: - "@hello-pangea/dnd": "npm:^16.6.0" - "@types/lodash": "npm:^4.14.202" - "@types/numeral": "npm:^2.0.5" - "@types/react-window": "npm:^1.8.8" - "@types/refractor": "npm:^3.4.0" - chroma-js: "npm:^2.4.2" - classnames: "npm:^2.5.1" - lodash: "npm:^4.17.21" - mdast-util-to-hast: "npm:^10.2.0" - numeral: "npm:^2.0.6" - prop-types: "npm:^15.8.1" - react-dropzone: "npm:^11.7.1" - react-element-to-jsx-string: "npm:^15.0.0" - react-focus-on: "npm:^3.9.1" - react-is: "npm:^17.0.2" - react-remove-scroll-bar: "npm:^2.3.4" - react-virtualized-auto-sizer: "npm:^1.0.24" - react-window: "npm:^1.8.10" - refractor: "npm:^3.6.0" - rehype-raw: "npm:^5.1.0" - rehype-react: "npm:^6.2.1" - rehype-stringify: "npm:^8.0.0" - remark-breaks: "npm:^2.0.2" - remark-emoji: "npm:^2.1.0" - remark-parse-no-trim: "npm:^8.0.4" - remark-rehype: "npm:^8.1.0" - tabbable: "npm:^5.3.3" - text-diff: "npm:^1.0.1" - unified: "npm:^9.2.2" - unist-util-visit: "npm:^2.0.3" - url-parse: "npm:^1.5.10" - uuid: "npm:^8.3.0" - vfile: "npm:^4.2.1" - peerDependencies: - "@elastic/datemath": ^5.0.2 - "@emotion/css": 11.x - "@emotion/react": 11.x - "@types/react": ^16.9 || ^17.0 || ^18.0 - "@types/react-dom": ^16.9 || ^17.0 || ^18.0 - moment: ^2.13.0 - react: ^16.12 || ^17.0 || ^18.0 - react-dom: ^16.12 || ^17.0 || ^18.0 - typescript: ~4.5.3 || ^5 - checksum: 10c0/c8fed61d39cc6d06f00caf8234b97e9579ca4d11d7bf0d3cf4cb5d09a6782d8866a7db66b02d3d5eb4f2947a213aca03837b38b166020d21c9bda2e7eb20ad11 - languageName: node - linkType: hard - -"@elastic/eui@workspace:packages/eui": +"@elastic/eui@workspace:^, @elastic/eui@workspace:packages/eui": version: 0.0.0-use.local resolution: "@elastic/eui@workspace:packages/eui" dependencies: @@ -5890,6 +5906,8 @@ __metadata: "@cypress/webpack-dev-server": "npm:^1.7.0" "@elastic/charts": "npm:^64.1.0" "@elastic/datemath": "npm:^5.0.3" + "@elastic/eui-theme-borealis": "workspace:^" + "@elastic/eui-theme-common": "workspace:^" "@emotion/babel-preset-css-prop": "npm:^11.11.0" "@emotion/cache": "npm:^11.11.0" "@emotion/css": "npm:^11.11.0" @@ -6072,6 +6090,8 @@ __metadata: yo: "npm:^4.3.1" peerDependencies: "@elastic/datemath": ^5.0.2 + "@elastic/eui-theme-borealis": 0.0.1 + "@elastic/eui-theme-common": 0.0.1 "@emotion/css": 11.x "@emotion/react": 11.x "@types/react": ^16.9 || ^17.0 || ^18.0 @@ -10155,21 +10175,21 @@ __metadata: languageName: node linkType: hard -"@types/react-dom@npm:^18.0.0, @types/react-dom@npm:^18.2.6": - version: 18.2.6 - resolution: "@types/react-dom@npm:18.2.6" +"@types/react-dom@npm:^16.9 || ^17.0 || ^18.0, @types/react-dom@npm:^18.3.0": + version: 18.3.0 + resolution: "@types/react-dom@npm:18.3.0" dependencies: "@types/react": "npm:*" - checksum: 10c0/bd734ca04c52b3c96891a7f9c1139486807dac7a2449fb72e8f8e23018bc6eeeb87a490a105cb39d05ccb7ddf80ed7a441e5bd3e5866c6f6ae8870cd723599e8 + checksum: 10c0/6c90d2ed72c5a0e440d2c75d99287e4b5df3e7b011838cdc03ae5cd518ab52164d86990e73246b9d812eaf02ec351d74e3b4f5bd325bf341e13bf980392fd53b languageName: node linkType: hard -"@types/react-dom@npm:^18.3.0": - version: 18.3.0 - resolution: "@types/react-dom@npm:18.3.0" +"@types/react-dom@npm:^18.0.0, @types/react-dom@npm:^18.2.6": + version: 18.2.6 + resolution: "@types/react-dom@npm:18.2.6" dependencies: "@types/react": "npm:*" - checksum: 10c0/6c90d2ed72c5a0e440d2c75d99287e4b5df3e7b011838cdc03ae5cd518ab52164d86990e73246b9d812eaf02ec351d74e3b4f5bd325bf341e13bf980392fd53b + checksum: 10c0/bd734ca04c52b3c96891a7f9c1139486807dac7a2449fb72e8f8e23018bc6eeeb87a490a105cb39d05ccb7ddf80ed7a441e5bd3e5866c6f6ae8870cd723599e8 languageName: node linkType: hard @@ -36637,6 +36657,16 @@ __metadata: languageName: node linkType: hard +"typescript@npm:^5.6.2": + version: 5.6.2 + resolution: "typescript@npm:5.6.2" + bin: + tsc: bin/tsc + tsserver: bin/tsserver + checksum: 10c0/3ed8297a8c7c56b7fec282532503d1ac795239d06e7c4966b42d4330c6cf433a170b53bcf93a130a7f14ccc5235de5560df4f1045eb7f3550b46ebed16d3c5e5 + languageName: node + linkType: hard + "typescript@npm:~5.5.4": version: 5.5.4 resolution: "typescript@npm:5.5.4" @@ -36657,6 +36687,16 @@ __metadata: languageName: node linkType: hard +"typescript@patch:typescript@npm%3A^5.6.2#optional!builtin": + version: 5.6.2 + resolution: "typescript@patch:typescript@npm%3A5.6.2#optional!builtin::version=5.6.2&hash=b45daf" + bin: + tsc: bin/tsc + tsserver: bin/tsserver + checksum: 10c0/e6c1662e4852e22fe4bbdca471dca3e3edc74f6f1df043135c44a18a7902037023ccb0abdfb754595ca9028df8920f2f8492c00fc3cbb4309079aae8b7de71cd + languageName: node + linkType: hard + "typescript@patch:typescript@npm%3A~5.5.4#optional!builtin": version: 5.5.4 resolution: "typescript@patch:typescript@npm%3A5.5.4#optional!builtin::version=5.5.4&hash=b45daf"