Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[EuiProvider] Detect OS/system light vs darkmode as a default #8026

Merged
merged 10 commits into from
Sep 23, 2024
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';

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yay for being able to reuse things! 👏

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/[email protected]';
import pageNotFoundLight2x from '../../../images/empty-prompt/[email protected]';

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
Loading