Skip to content

Commit

Permalink
[EuiProvider] Detect OS/system light vs darkmode as a default (#8026)
Browse files Browse the repository at this point in the history
  • Loading branch information
cee-chen authored Sep 23, 2024
1 parent 31c8385 commit 9384ee0
Show file tree
Hide file tree
Showing 19 changed files with 276 additions and 137 deletions.
4 changes: 3 additions & 1 deletion packages/eui/.storybook/decorator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,9 @@ const storybookToolbarWritingModes: Array<
export const euiProviderDecoratorGlobals: Preview['globalTypes'] = {
colorMode: {
description: 'Color mode for EuiProvider theme',
defaultValue: 'light',
defaultValue: window?.matchMedia?.('(prefers-color-scheme: dark)').matches
? 'dark'
: 'light',
toolbar: {
title: 'Color mode',
items: storybookToolbarColorModes,
Expand Down
1 change: 1 addition & 0 deletions packages/eui/changelogs/upcoming/8026.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- Updated `EuiProvider` to inherit from the user's OS/system light/dark mode setting if a `colorMode` prop has not been passed
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import React, { useState } from 'react';

import {
EuiThemeProvider,
useEuiTheme,
useIsWithinBreakpoints,
} from '../../../../src/services';
import { EUI_THEME, EUI_THEMES } from '../../../../src/themes';
Expand Down Expand Up @@ -34,8 +35,6 @@ export const GuideThemeSelector: React.FunctionComponent<
);
};

const STORAGE_KEY = 'legacy_theme_notification';

const GuideThemeSelectorComponent: React.FunctionComponent<
GuideThemeSelectorProps
> = ({ context, onToggleLocale, selectedLocale }) => {
Expand All @@ -44,15 +43,17 @@ const GuideThemeSelectorComponent: React.FunctionComponent<

const onButtonClick = () => {
setPopover(!isPopoverOpen);
localStorage.setItem(STORAGE_KEY, 'dismissed');
};

const closePopover = () => {
setPopover(false);
};

const systemColorMode = useEuiTheme().colorMode.toLowerCase();
const currentTheme: EUI_THEME =
EUI_THEMES.find((theme) => theme.value === context.theme) || EUI_THEMES[0];
EUI_THEMES.find(
(theme) => theme.value === (context.theme ?? systemColorMode)
) || EUI_THEMES[0];

const getIconType = (value: EUI_THEME['value']) => {
return value === currentTheme.value ? 'check' : 'empty';
Expand Down
33 changes: 15 additions & 18 deletions packages/eui/src-docs/src/components/with_theme/theme_context.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,30 +28,27 @@ export const theme_languages: THEME_LANGUAGES[] = [
const THEME_NAMES = EUI_THEMES.map(({ value }) => value);
const THEME_LANGS = theme_languages.map(({ id }) => id);

const defaultState = {
themeLanguage: THEME_LANGS[0],
// eslint-disable-next-line @typescript-eslint/no-unused-vars
changeThemeLanguage: (language: THEME_LANGUAGES['id']) => {},
theme: THEME_NAMES[0],
changeTheme: (themeValue: EUI_THEME['value']) => {
applyTheme(themeValue);
},
};

interface State {
theme: EUI_THEME['value'];
type ThemeContextType = {
theme?: EUI_THEME['value'];
changeTheme: (themeValue: EUI_THEME['value']) => void;
themeLanguage: THEME_LANGUAGES['id'];
}
changeThemeLanguage: (language: THEME_LANGUAGES['id']) => void;
};
export const ThemeContext = React.createContext<ThemeContextType>({
theme: undefined,
changeTheme: () => {},
themeLanguage: THEME_LANGS[0],
changeThemeLanguage: () => {},
});

export const ThemeContext = React.createContext(defaultState);
type State = Pick<ThemeContextType, 'theme' | 'themeLanguage'>;

export class ThemeProvider extends React.Component<PropsWithChildren, State> {
constructor(props: object) {
super(props);

let theme = localStorage.getItem('theme');
if (!theme || !THEME_NAMES.includes(theme)) theme = defaultState.theme;
applyTheme(theme);
const theme = localStorage.getItem('theme') || undefined;
applyTheme(theme && THEME_NAMES.includes(theme) ? theme : THEME_NAMES[0]);

const themeLanguage = this.getThemeLanguage();

Expand Down Expand Up @@ -83,7 +80,7 @@ export class ThemeProvider extends React.Component<PropsWithChildren, State> {

// If not set by either param or storage, or an invalid value, use the default
if (!themeLanguage || !THEME_LANGS.includes(themeLanguage))
themeLanguage = defaultState.themeLanguage;
themeLanguage = THEME_LANGS[0];

return themeLanguage;
};
Expand Down
13 changes: 4 additions & 9 deletions packages/eui/src-docs/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -97,15 +97,10 @@ root.render(
isDeprecated,
}}
>
{({ theme }) => (
<>
{meta}
{createElement(component, {
selectedTheme: theme,
title: name,
})}
</>
)}
{meta}
{createElement(component, {
title: name,
})}
</AppView>
);
}
Expand Down
4 changes: 3 additions & 1 deletion packages/eui/src-docs/src/views/app_context.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,9 @@ export const AppContext = ({ children }) => {
utility: utilityCache,
}}
theme={EUI_THEMES.find((t) => t.value === theme)?.provider}
colorMode={theme.includes('light') ? 'light' : 'dark'}
colorMode={
theme ? (theme.includes('light') ? 'light' : 'dark') : undefined
}
>
<Helmet>
<link
Expand Down
7 changes: 3 additions & 4 deletions packages/eui/src-docs/src/views/app_view.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import React, { useContext } from 'react';
import React from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { useLocation } from 'react-router-dom';
import { toggleLocale as _toggleLocale } from '../actions';
import { GuidePageChrome, ThemeContext, GuidePageHeader } from '../components';
import { GuidePageChrome, GuidePageHeader } from '../components';
import { getLocale, getRoutes } from '../store';
import {
useScrollToHash,
Expand All @@ -21,7 +21,6 @@ export const AppView = ({ children, currentRoute = {} }) => {
const toggleLocale = (locale) => dispatch(_toggleLocale(locale));
const locale = useSelector((state) => getLocale(state));
const routes = useSelector((state) => getRoutes(state));
const { theme } = useContext(ThemeContext);

const portalledHeadingAnchorLinks = useHeadingAnchorLinks();

Expand Down Expand Up @@ -59,7 +58,7 @@ export const AppView = ({ children, currentRoute = {} }) => {
/>
</EuiPageTemplate.Sidebar>

{children({ theme })}
{children}
</EuiPageTemplate>
</LinkWrapper>
);
Expand Down
13 changes: 3 additions & 10 deletions packages/eui/src-docs/src/views/avatar/avatar_icon.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,8 @@
import React, { useContext } from 'react';
import { ThemeContext } from '../../components';

import { EuiAvatar, EuiSpacer, EuiTitle } from '../../../../src/components';
import React from 'react';
import { EuiAvatar, EuiSpacer, EuiTitle, useEuiTheme } from '../../../../src';

export default () => {
const themeContext = useContext(ThemeContext);

/**
* Setup theme based on current light/dark theme
*/
const isDarkTheme = themeContext.theme.includes('dark');
const isDarkTheme = useEuiTheme().colorMode === 'DARK';

return (
<div>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useState, useEffect, useContext } from 'react';
import React, { useState, useEffect } from 'react';
import reactElementToJSXString from 'react-element-to-jsx-string';
import classNames from 'classnames';
import {
Expand All @@ -20,8 +20,8 @@ import {
EuiDescriptionListDescription,
EuiLoadingSpinner,
useIsWithinBreakpoints,
useEuiTheme,
} from '../../../../src';
import { ThemeContext } from '../../components/with_theme';
import { typesOfPanelColors } from './_types_of_panel_colors';
// @ts-ignore Importing from JS file
import { typesOfUseCases } from './_types_of_use_cases';
Expand All @@ -43,12 +43,7 @@ import singleSvg from '../../images/single.svg';
import contentCenterSvg from '../../images/content_center.svg';

export default () => {
const themeContext = useContext(ThemeContext);

/**
* Setup theme based on current light/dark theme
*/
const isDarkTheme = themeContext.theme.includes('dark');
const isDarkTheme = useEuiTheme().colorMode === 'DARK';

const useCasesOptions: EuiRadioGroupOption[] = Object.values(
typesOfUseCases
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,16 @@
import React, { useContext } from 'react';
import React from 'react';
import {
EuiEmptyPrompt,
EuiImage,
EuiButton,
EuiButtonEmpty,
} from '../../../../../src/components';
import { ThemeContext } from '../../../components/with_theme';
useEuiTheme,
} from '../../../../../src';
import pageNotFoundLight from '../../../images/empty-prompt/accessDenied--light.png';
import pageNotFoundDark from '../../../images/empty-prompt/accessDenied--dark.png';

export default () => {
const themeContext = useContext(ThemeContext);
const isDarkTheme = themeContext.theme.includes('dark');
const isDarkTheme = useEuiTheme().colorMode === 'DARK';

const iconImg: string = isDarkTheme ? pageNotFoundDark : pageNotFoundLight;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,19 @@
import React, { useContext } from 'react';
import React from 'react';
import {
EuiEmptyPrompt,
EuiImage,
EuiButton,
EuiButtonEmpty,
} from '../../../../../src/components';
import { ThemeContext } from '../../../components/with_theme';
useEuiTheme,
} from '../../../../../src';

import pageNotFoundDark from '../../../images/empty-prompt/pageNotFound--dark.png';
import pageNotFoundLight from '../../../images/empty-prompt/pageNotFound--light.png';
import pageNotFoundDark2x from '../../../images/empty-prompt/pageNotFound--dark@2x.png';
import pageNotFoundLight2x from '../../../images/empty-prompt/pageNotFound--light@2x.png';

export default () => {
const themeContext = useContext(ThemeContext);
const isDarkTheme = themeContext.theme.includes('dark');
const isDarkTheme = useEuiTheme().colorMode === 'DARK';

const pageNotFound = isDarkTheme ? pageNotFoundDark : pageNotFoundLight;
const pageNotFound2x = isDarkTheme ? pageNotFoundDark2x : pageNotFoundLight2x;
Expand Down
18 changes: 9 additions & 9 deletions packages/eui/src-docs/src/views/home/home_illustration.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
import React, { useContext } from 'react';
import { ThemeContext } from '../../components/with_theme';
import React from 'react';
import illustrationDarkMode from '../../images/illustration-eui-hero-500-darkmode-shadow.svg';
import illustrationLightMode from '../../images/illustration-eui-hero-500-shadow.svg';
import { EuiImage } from '../../../../src/components/image';
import { EuiImage, useEuiTheme } from '../../../../src';

function Icon() {
const themeContext: any = useContext(ThemeContext);
const { colorMode } = useEuiTheme();

const illustration = themeContext.theme.includes('dark') ? (
<EuiImage alt="Elastic UI" url={illustrationDarkMode} />
) : (
<EuiImage alt="Elastic UI" url={illustrationLightMode} />
);
const illustration =
colorMode === 'DARK' ? (
<EuiImage alt="Elastic UI" url={illustrationDarkMode} />
) : (
<EuiImage alt="Elastic UI" url={illustrationLightMode} />
);

return (
<div className="guideHomePage__illustration">
Expand Down
22 changes: 3 additions & 19 deletions packages/eui/src-docs/src/views/text/text_example.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import React, { useContext } from 'react';
import { ThemeContext } from '../../components/with_theme';
import React from 'react';

import { GuideSectionTypes } from '../../components';

Expand Down Expand Up @@ -45,22 +44,6 @@ const textAlignSnippet = [
`,
];

const LineHeightText = () => {
const themeContext = useContext(ThemeContext);
let text;
switch (themeContext.theme) {
default:
text = (
<>
The goal is that the every line-height lands on the{' '}
<EuiCode>4px</EuiCode> baseline grid.
</>
);
}

return text;
};

export const TextExample = {
title: 'Text',
sections: [
Expand Down Expand Up @@ -130,7 +113,8 @@ export const TextExample = {
<p>
Using the <EuiCode>size</EuiCode> prop on <strong>EuiText</strong> you
can get smaller sizes of text than the default. This demo compares the
scaling for all sizes. <LineHeightText />
scaling for all sizes. The goal is that the every line-height lands on
the <EuiCode>4px</EuiCode> baseline grid.
</p>
),
snippet: textScalingSnippet,
Expand Down
14 changes: 3 additions & 11 deletions packages/eui/src-docs/src/views/text/text_scaling.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import React, { useState, useContext } from 'react';
import { ThemeContext } from '../../components/with_theme';
import React, { useState } from 'react';

import {
EuiText,
Expand Down Expand Up @@ -84,7 +83,6 @@ const text = [
];

export default () => {
const themeContext = useContext(ThemeContext);
const textSizeArray = ['xs', 's', 'm'];
const textSizeNamesArray = ['Extra small', 'Small', 'Medium'];

Expand Down Expand Up @@ -132,10 +130,7 @@ export default () => {
options={firstOptions}
/>
<EuiHorizontalRule />
<EuiText
className={`guideDemo__textLines guideDemo__textLines--${themeContext.theme}`}
size={firstSize}
>
<EuiText className="guideDemo__textLines" size={firstSize}>
{text}
</EuiText>
</EuiFlexItem>
Expand All @@ -154,10 +149,7 @@ export default () => {
options={secondOptions}
/>
<EuiHorizontalRule />
<EuiText
className={`guideDemo__textLines guideDemo__textLines--${themeContext.theme}`}
size={secondSize}
>
<EuiText className="guideDemo__textLines" size={secondSize}>
{text}
</EuiText>
</EuiFlexItem>
Expand Down
Loading

0 comments on commit 9384ee0

Please sign in to comment.