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

[EuiThemeProvider] Allow nested modify.breakpoint overrides #7862

Merged
merged 5 commits into from
Jul 1, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/eui/changelogs/upcoming/7862.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- Updated `EuiThemeProvider`s to allow modifying/setting custom `breakpoint`s in nested usage (as opposed to only at the top `EuiProvider` level)
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,8 @@ export const CustomBreakpointsJS = () => {
<>
<p>
Theme breakpoints can be overriden or added via{' '}
<EuiCode>EuiProvider</EuiCode>&apos;s <EuiCode>modify</EuiCode>{' '}
<EuiCode>EuiProvider</EuiCode>&apos;s or{' '}
<EuiCode>EuiThemeProvider</EuiCode>&apos;s <EuiCode>modify</EuiCode>{' '}
prop.
</p>
<p>
Expand All @@ -232,7 +233,7 @@ export const CustomBreakpointsJS = () => {
Current custom breakpoint: <strong>{currentBreakpoint}</strong>
</p>
}
snippet={`<EuiProvider
snippet={`<EuiThemeProvider
modify={{
breakpoint: {
xxs: 0,
Expand All @@ -246,7 +247,7 @@ export const CustomBreakpointsJS = () => {
}}
>
<App />
</EuiProvider>
</EuiThemeProvider>
`}
snippetLanguage="js"
/>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React, { useContext, useMemo } from 'react';
import { EuiSpacer, EuiText, EuiProvider } from '../../../../../src';
import { EuiSpacer, EuiText, EuiThemeProvider } from '../../../../../src';

import { GuideSection } from '../../../components/guide_section/guide_section';
import { ThemeContext } from '../../../components/with_theme';
Expand Down Expand Up @@ -51,7 +51,7 @@ export default () => {
<GuideSection color="transparent">{valuesContent}</GuideSection>

{currentLanguage.includes('js') && (
<EuiProvider modify={{ breakpoint: CUSTOM_BREAKPOINTS }}>
<EuiThemeProvider modify={{ breakpoint: CUSTOM_BREAKPOINTS }}>
<GuideSection color="subdued">
<EuiText grow={false}>
<h2 id={breakpointSections[1].id}>
Expand All @@ -70,7 +70,7 @@ export default () => {
</GuideSection>

<GuideSection color="transparent">{valuesContent}</GuideSection>
</EuiProvider>
</EuiThemeProvider>
)}
</>
);
Expand Down
8 changes: 5 additions & 3 deletions packages/eui/src/services/breakpoint/current_breakpoint.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import {
_EuiThemeBreakpoint,
_EuiThemeBreakpoints,
} from '../../global_styling/variables/breakpoint';
import { useEuiTheme } from '../theme';
import { useEuiTheme } from '../theme/hooks';
import { throttle } from '../throttle';
import { sortMapByLargeToSmallValues } from './_sorting';

Expand All @@ -31,8 +31,10 @@ export const CurrentEuiBreakpointContext =
createContext<CurrentEuiBreakpoint>(undefined);

/**
* Top level provider (nested within EuiProvider) which provides a single
* resize listener that returns the current breakpoint based on window width
* Returns the current breakpoint based on window width.
* Typically only called by the top-level `EuiProvider` (to reduce the number
* of window resize listeners on the page). Also conditionally called if a
* nested `EuiThemeProvider` defines a `modify.breakpoint` override
*/
export const CurrentEuiBreakpointProvider: FunctionComponent<
PropsWithChildren
Expand Down
28 changes: 28 additions & 0 deletions packages/eui/src/services/theme/provider.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { render } from '@testing-library/react'; // Note - don't use the EUI cus
import { css } from '@emotion/react';

import { EuiProvider } from '../../components/provider';
import { useCurrentEuiBreakpoint } from '../breakpoint';
import { EuiNestedThemeContext } from './context';
import { EuiThemeProvider } from './provider';

Expand Down Expand Up @@ -73,6 +74,33 @@ describe('EuiThemeProvider', () => {

expect(getByText('Modified')).toHaveStyleRule('color', 'hotpink');
});

it('sets a conditional CurrentEuiBreakpointProvider if modify.breakpoint is passed', () => {
window.innerWidth = 2500;
const customBreakpoints = { xxl: 2000 };
const PrintCurrentBreakpoint = () => <>{useCurrentEuiBreakpoint()}</>;

const { container, rerender } = render(
<EuiThemeProvider modify={{ breakpoint: customBreakpoints }}>
<PrintCurrentBreakpoint />
</EuiThemeProvider>
);

expect(container.textContent).toEqual('xxl');

// Does nothing if no modify.breakpoint is passed
const eventListenerSpy = jest.spyOn(window, 'addEventListener');
rerender(
<EuiThemeProvider>
<PrintCurrentBreakpoint />
</EuiThemeProvider>
);
expect(eventListenerSpy).not.toHaveBeenCalledWith('resize');
expect(container.textContent).toEqual('xl');

// Reset window width to jsdom's default
window.innerWidth = 1024;
});
});

describe('nested EuiThemeProviders', () => {
Expand Down
12 changes: 11 additions & 1 deletion packages/eui/src/services/theme/provider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,15 @@ import React, {
useCallback,
PropsWithChildren,
HTMLAttributes,
Fragment,
} from 'react';
import { Global, type CSSObject } from '@emotion/react';
import isEqual from 'lodash/isEqual';

import type { CommonProps } from '../../components/common';
import { cloneElementWithCss } from '../emotion';
import { css, cx } from '../emotion/css';
import { CurrentEuiBreakpointProvider } from '../breakpoint/current_breakpoint';

import {
EuiSystemContext,
Expand Down Expand Up @@ -80,6 +82,12 @@ export const EuiThemeProvider = <T extends {} = {}>({
const [system, setSystem] = useState(_system || parentSystem);
const prevSystemKey = useRef(system.key);

// To reduce the number of window resize listeners, only render a
// CurrentEuiBreakpointProvider if modified breakpoint overrides are passed
const EuiConditionalBreakpointProvider = useMemo(() => {
return _modifications?.breakpoint ? CurrentEuiBreakpointProvider : Fragment;
mgadewoll marked this conversation as resolved.
Show resolved Hide resolved
}, [_modifications]);

const [modifications, setModifications] = useState<EuiThemeModifications>(
mergeDeep(parentModifications, _modifications)
);
Expand Down Expand Up @@ -232,7 +240,9 @@ export const EuiThemeProvider = <T extends {} = {}>({
<EuiNestedThemeContext.Provider value={nestedThemeContext}>
<EuiThemeMemoizedStylesProvider>
<EuiEmotionThemeProvider>
{renderedChildren}
<EuiConditionalBreakpointProvider>
{renderedChildren}
</EuiConditionalBreakpointProvider>
</EuiEmotionThemeProvider>
</EuiThemeMemoizedStylesProvider>
</EuiNestedThemeContext.Provider>
Expand Down
Loading