From 3efa3ee616f3913b7d50197287d09ebb5c71e1a2 Mon Sep 17 00:00:00 2001
From: Constance
Date: Wed, 10 Aug 2022 16:08:19 -0700
Subject: [PATCH] Deprecate static breakpoint services (#6119)
* Deprecate BREAKPOINTS and BREAKPOINT_KEYS
* Remove non-public EuiBreakpoints type
- in favor of _EuiThemeBreakpoints type from global_styling
* Move EuiBreakpointSize type export
- Preserve the type export, since the global_styling one has a _ prefix, but move the export out to the main index.ts
* Deprecate `isWithinBreakpoints` in favor of hook version
+ rename hook file to more generic name in prepration for max/min width hooks
+ use snake_case to match rest of repo
* Deprecate `isWithinMaxBreakpoint`, create `useIsWithinMaxBreakpoint`
* Deprecate `isWithinMinBreakpoint`, create `useIsWithinMinBreakpoint`
+ add tests that previously did not exist
* Add documentation examples for new `useIsWithinMin/MaxBreakpoints` hooks
* Update components using `isWithinMinBreakpoint` to use new hook
+ per new hook typing, only accept a named size instead of a number
* Deprecate `getBreakpoint`
- in favor of memoizing its behavior in `CurrentEuiBreakpoint` logic
+ update testenv mock slightly - it's not super DRY, but it's fine
---
.../theme/breakpoints/_breakpoints_js.tsx | 52 ++++--
.../breakpoints/breakpoint_utilities.tsx | 25 +--
.../collapsible_nav.test.tsx.snap | 92 ++++++----
.../collapsible_nav/collapsible_nav.test.tsx | 2 +-
.../collapsible_nav/collapsible_nav.tsx | 45 +----
.../flyout/__snapshots__/flyout.test.tsx.snap | 68 +++----
src/components/flyout/flyout.tsx | 40 +---
src/services/breakpoint/breakpoint.test.ts | 161 ----------------
src/services/breakpoint/breakpoint.ts | 113 ------------
.../breakpoint/current_breakpoint.tsx | 35 +++-
.../current_breakpoint_hook.testenv.tsx | 12 +-
src/services/breakpoint/index.ts | 4 +-
.../breakpoint/is_within_hooks.spec.tsx | 173 ++++++++++++++++++
src/services/breakpoint/is_within_hooks.ts | 74 ++++++++
.../breakpoint/useIsWithinBreakpoints.ts | 31 ----
src/services/index.ts | 8 +-
upcoming_changelogs/6119.md | 11 ++
17 files changed, 457 insertions(+), 489 deletions(-)
delete mode 100644 src/services/breakpoint/breakpoint.test.ts
delete mode 100644 src/services/breakpoint/breakpoint.ts
create mode 100644 src/services/breakpoint/is_within_hooks.spec.tsx
create mode 100644 src/services/breakpoint/is_within_hooks.ts
delete mode 100644 src/services/breakpoint/useIsWithinBreakpoints.ts
create mode 100644 upcoming_changelogs/6119.md
diff --git a/src-docs/src/views/theme/breakpoints/_breakpoints_js.tsx b/src-docs/src/views/theme/breakpoints/_breakpoints_js.tsx
index beb3e48df33..0cbede119e7 100644
--- a/src-docs/src/views/theme/breakpoints/_breakpoints_js.tsx
+++ b/src-docs/src/views/theme/breakpoints/_breakpoints_js.tsx
@@ -3,9 +3,12 @@ import { css } from '@emotion/react';
import {
EuiIcon,
EuiCode,
+ EuiText,
useEuiBreakpoint,
useCurrentEuiBreakpoint,
useIsWithinBreakpoints,
+ useIsWithinMaxBreakpoint,
+ useIsWithinMinBreakpoint,
useEuiTheme,
} from '../../../../../src';
import { sortMapBySmallToLargeValues } from '../../../../../src/services/breakpoint/_sorting';
@@ -57,22 +60,49 @@ export default () => {
be turned on/off from within your component.
- The hook automatically inherits breakpoint sizes from the current
- EUI theme and any theme overrides.
+ You can also use useIsWithinMaxBreakpoint(size){' '}
+ and useIsWithinMinBreakpoint(size). The min/max
+ hooks return true or false if the current browser window width is
+ above or below the passed breakpoint size{' '}
+ respectively.
+
+
+ These hooks automatically inherits breakpoint sizes from the
+ current EUI theme and any theme overrides.
- The sizing options correlate with the keys in the{' '}
- EuiBreakpoints type. The named
+ The sizing options correlate with the keys in{' '}
+ euiTheme.breakpoint. The named
breakpoint starts at the pixel value provided and ends before the
next one.
,
- ,
]
`;
diff --git a/src/components/collapsible_nav/collapsible_nav.test.tsx b/src/components/collapsible_nav/collapsible_nav.test.tsx
index d714aa84b26..8290a998a8b 100644
--- a/src/components/collapsible_nav/collapsible_nav.test.tsx
+++ b/src/components/collapsible_nav/collapsible_nav.test.tsx
@@ -79,7 +79,7 @@ describe('EuiCollapsibleNav', () => {
test('dockedBreakpoint', () => {
const component = mount(
-
+
);
expect(
diff --git a/src/components/collapsible_nav/collapsible_nav.tsx b/src/components/collapsible_nav/collapsible_nav.tsx
index ffac8a407e9..de9e180934c 100644
--- a/src/components/collapsible_nav/collapsible_nav.tsx
+++ b/src/components/collapsible_nav/collapsible_nav.tsx
@@ -11,15 +11,12 @@ import React, {
FunctionComponent,
ReactElement,
ReactNode,
- useEffect,
useRef,
- useState,
} from 'react';
import classNames from 'classnames';
import {
useGeneratedHtmlId,
- isWithinMinBreakpoint,
- throttle,
+ useIsWithinMinBreakpoint,
useCombinedRefs,
} from '../../services';
import { EuiFlyout, EuiFlyoutProps } from '../flyout';
@@ -42,7 +39,7 @@ export type EuiCollapsibleNavProps = Omit<
*/
isDocked?: boolean;
/**
- * Named breakpoint or pixel value for customizing the minimum window width to enable docking
+ * Named breakpoint (`xs` through `xl`) for customizing the minimum window width to enable docking
*/
dockedBreakpoint?: EuiFlyoutProps['pushMinBreakpoint'];
/**
@@ -87,45 +84,9 @@ export const EuiCollapsibleNav: FunctionComponent = ({
shards: [buttonRef, ...(_focusTrapProps.shards || [])],
};
- /**
- * Setting the initial state of pushed based on the `type` prop
- * and if the current window size is large enough (larger than `pushBreakpoint`)
- */
- const [windowIsLargeEnoughToPush, setWindowIsLargeEnoughToPush] = useState(
- isWithinMinBreakpoint(
- typeof window === 'undefined' ? 0 : window.innerWidth,
- dockedBreakpoint
- )
- );
-
+ const windowIsLargeEnoughToPush = useIsWithinMinBreakpoint(dockedBreakpoint);
const navIsDocked = isDocked && windowIsLargeEnoughToPush;
- /**
- * Watcher added to the window to maintain `isPushed` state depending on
- * the window size compared to the `pushBreakpoint`
- */
- const functionToCallOnWindowResize = throttle(() => {
- if (isWithinMinBreakpoint(window.innerWidth, dockedBreakpoint)) {
- setWindowIsLargeEnoughToPush(true);
- } else {
- setWindowIsLargeEnoughToPush(false);
- }
- // reacts every 50ms to resize changes and always gets the final update
- }, 50);
-
- useEffect(() => {
- if (isDocked) {
- // Only add the event listener if we'll need to accommodate with padding
- window.addEventListener('resize', functionToCallOnWindowResize);
- }
-
- return () => {
- if (isDocked) {
- window.removeEventListener('resize', functionToCallOnWindowResize);
- }
- };
- }, [isDocked, functionToCallOnWindowResize]);
-
const classes = classNames('euiCollapsibleNav', className);
// Show a trigger button if one was passed but
diff --git a/src/components/flyout/__snapshots__/flyout.test.tsx.snap b/src/components/flyout/__snapshots__/flyout.test.tsx.snap
index eb4766a1a3b..84e6d418a1e 100644
--- a/src/components/flyout/__snapshots__/flyout.test.tsx.snap
+++ b/src/components/flyout/__snapshots__/flyout.test.tsx.snap
@@ -1026,43 +1026,45 @@ Array [
exports[`EuiFlyout props type=push is rendered 1`] = `
Array [
- ,
- ,
-
+
+
+
-
+
+
+
,
- ,
]
`;
diff --git a/src/components/flyout/flyout.tsx b/src/components/flyout/flyout.tsx
index ecf2612669d..6c301f1d9ea 100644
--- a/src/components/flyout/flyout.tsx
+++ b/src/components/flyout/flyout.tsx
@@ -26,8 +26,7 @@ import {
EuiWindowEvent,
useCombinedRefs,
EuiBreakpointSize,
- isWithinMinBreakpoint,
- throttle,
+ useIsWithinMinBreakpoint,
} from '../../services';
import { CommonProps, keysOf, PropsOfElement } from '../common';
@@ -148,9 +147,9 @@ interface _EuiFlyoutProps {
*/
role?: null | string;
/**
- * Named breakpoint or pixel value for customizing the minimum window width to enable the `push` type
+ * Named breakpoint (`xs` through `xl`) for customizing the minimum window width to enable the `push` type
*/
- pushMinBreakpoint?: EuiBreakpointSize | number;
+ pushMinBreakpoint?: EuiBreakpointSize;
style?: CSSProperties;
/**
* Object of props passed to EuiFocusTrap.
@@ -206,32 +205,12 @@ export const EuiFlyout = forwardRef(
) => {
const Element = as || defaultElement;
const maskRef = useRef(null);
- /**
- * Setting the initial state of pushed based on the `type` prop
- * and if the current window size is large enough (larger than `pushMinBreakpoint`)
- */
- const [windowIsLargeEnoughToPush, setWindowIsLargeEnoughToPush] = useState(
- isWithinMinBreakpoint(
- typeof window === 'undefined' ? 0 : window.innerWidth,
- pushMinBreakpoint
- )
- );
+ const windowIsLargeEnoughToPush = useIsWithinMinBreakpoint(
+ pushMinBreakpoint
+ );
const isPushed = type === 'push' && windowIsLargeEnoughToPush;
- /**
- * Watcher added to the window to maintain `isPushed` state depending on
- * the window size compared to the `pushBreakpoint`
- */
- const functionToCallOnWindowResize = throttle(() => {
- if (isWithinMinBreakpoint(window.innerWidth, pushMinBreakpoint)) {
- setWindowIsLargeEnoughToPush(true);
- } else {
- setWindowIsLargeEnoughToPush(false);
- }
- // reacts every 50ms to resize changes and always gets the final update
- }, 50);
-
/**
* Setting up the refs on the actual flyout element in order to
* accommodate for the `isPushed` state by adding padding to the body equal to the width of the element
@@ -251,9 +230,6 @@ export const EuiFlyout = forwardRef(
* Accomodate for the `isPushed` state by adding padding to the body equal to the width of the element
*/
if (type === 'push') {
- // Only add the event listener if we'll need to accommodate with padding
- window.addEventListener('resize', functionToCallOnWindowResize);
-
if (isPushed) {
if (side === 'right') {
document.body.style.paddingRight = `${dimensions.width}px`;
@@ -267,8 +243,6 @@ export const EuiFlyout = forwardRef(
document.body.classList.remove('euiBody--hasFlyout');
if (type === 'push') {
- window.removeEventListener('resize', functionToCallOnWindowResize);
-
if (side === 'right') {
document.body.style.paddingRight = '';
} else if (side === 'left') {
@@ -276,7 +250,7 @@ export const EuiFlyout = forwardRef(
}
}
};
- }, [type, side, dimensions, isPushed, functionToCallOnWindowResize]);
+ }, [type, side, dimensions, isPushed]);
/**
* ESC key closes flyout (always?)
diff --git a/src/services/breakpoint/breakpoint.test.ts b/src/services/breakpoint/breakpoint.test.ts
deleted file mode 100644
index 14ddf9ba630..00000000000
--- a/src/services/breakpoint/breakpoint.test.ts
+++ /dev/null
@@ -1,161 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0 and the Server Side Public License, v 1; you may not use this file except
- * in compliance with, at your election, the Elastic License 2.0 or the Server
- * Side Public License, v 1.
- */
-
-import {
- getBreakpoint,
- EuiBreakpoints,
- isWithinMaxBreakpoint,
- isWithinBreakpoints,
-} from './breakpoint';
-
-const CUSTOM_BREAKPOINTS: EuiBreakpoints = {
- xl: 1400,
- l: 1078,
- m: 812,
- s: 667,
- xs: 320,
-};
-
-describe('getBreakpoint', () => {
- describe('with default BREAKPOINTS', () => {
- it("should return 'xs' for 320", () => {
- expect(getBreakpoint(320)).toBe('xs');
- });
- it("should return 's' for 667", () => {
- expect(getBreakpoint(667)).toBe('s');
- });
- it("should return 'm' for 812", () => {
- expect(getBreakpoint(812)).toBe('m');
- });
- it("should return 'l' for 1078", () => {
- expect(getBreakpoint(1078)).toBe('l');
- });
- it("should return 'xl' for 1400", () => {
- expect(getBreakpoint(1400)).toBe('xl');
- });
- });
-
- describe('with custom breakpoints', () => {
- it("should return 'undefined' for 240", () => {
- expect(getBreakpoint(240, CUSTOM_BREAKPOINTS)).toBe(undefined);
- });
- it("should return 'xs' for 575", () => {
- expect(getBreakpoint(575, CUSTOM_BREAKPOINTS)).toBe('xs');
- });
- it("should return 's' for 768", () => {
- expect(getBreakpoint(768, CUSTOM_BREAKPOINTS)).toBe('s');
- });
- it("should return 'm' for 992", () => {
- expect(getBreakpoint(992, CUSTOM_BREAKPOINTS)).toBe('m');
- });
- it("should return 'l' for 1200", () => {
- expect(getBreakpoint(1200, CUSTOM_BREAKPOINTS)).toBe('l');
- });
- it("should return 'xl' for 1400", () => {
- expect(getBreakpoint(1400, CUSTOM_BREAKPOINTS)).toBe('xl');
- });
- });
-});
-
-describe('isWithinMaxBreakpoint', () => {
- describe('with default BREAKPOINTS', () => {
- it("should return 'true' for 'xs' and 320", () => {
- expect(isWithinMaxBreakpoint(320, 'xs')).toBe(true);
- });
- it("should return 'true' for 's' and 667", () => {
- expect(isWithinMaxBreakpoint(667, 's')).toBe(true);
- });
- it("should return 'true' for 'm' and 812", () => {
- expect(isWithinMaxBreakpoint(812, 'm')).toBe(true);
- });
- it("should return 'true' for 'l' and 1078", () => {
- expect(isWithinMaxBreakpoint(1078, 'l')).toBe(true);
- });
- it("should return 'true' for 'xl' and 1400", () => {
- expect(isWithinMaxBreakpoint(1400, 'xl')).toBe(true);
- });
- });
-
- describe('with custom breakpoints', () => {
- it("should return 'false' for 'xs' and 240", () => {
- expect(isWithinMaxBreakpoint(240, 'xs', CUSTOM_BREAKPOINTS)).toBe(false);
- });
- it("should return 'true' for 'xs' and 575", () => {
- expect(isWithinMaxBreakpoint(575, 'xs', CUSTOM_BREAKPOINTS)).toBe(true);
- });
- it("should return 'true' for 's' and 768", () => {
- expect(isWithinMaxBreakpoint(768, 's', CUSTOM_BREAKPOINTS)).toBe(true);
- });
- it("should return 'true' for 'm' and 992", () => {
- expect(isWithinMaxBreakpoint(992, 'm', CUSTOM_BREAKPOINTS)).toBe(true);
- });
- it("should return 'true' for 'l' and 1200", () => {
- expect(isWithinMaxBreakpoint(1200, 'l', CUSTOM_BREAKPOINTS)).toBe(true);
- });
- it("should return 'true' for 'xl' and 1400", () => {
- expect(isWithinMaxBreakpoint(1400, 'xl', CUSTOM_BREAKPOINTS)).toBe(true);
- });
- });
-
- describe('with max as a number', () => {
- it("should return 'true' for a 320 width and a '480' max", () => {
- expect(isWithinMaxBreakpoint(320, 480)).toBe(true);
- });
- });
-
- describe('isWithinBreakpoints', () => {
- describe('with default BREAKPOINTS', () => {
- it("should return 'true' for 'xs' and 320", () => {
- expect(isWithinBreakpoints(320, ['xs'])).toBe(true);
- });
- it("should return 'true' for 's' and 667", () => {
- expect(isWithinBreakpoints(667, ['s'])).toBe(true);
- });
- it("should return 'true' for 'm' and 812", () => {
- expect(isWithinBreakpoints(812, ['m'])).toBe(true);
- });
- it("should return 'true' for 'l' and 1078", () => {
- expect(isWithinBreakpoints(1078, ['l'])).toBe(true);
- });
- it("should return 'true' for 'xl' and 1400", () => {
- expect(isWithinBreakpoints(1400, ['xl'])).toBe(true);
- });
- });
-
- describe('with custom breakpoints', () => {
- it("should return 'false' for 'xs' and 240", () => {
- expect(isWithinBreakpoints(240, ['xs'], CUSTOM_BREAKPOINTS)).toBe(
- false
- );
- });
- it("should return 'true' for 'xs' and 575", () => {
- expect(isWithinBreakpoints(575, ['xs'], CUSTOM_BREAKPOINTS)).toBe(true);
- });
- it("should return 'true' for 's' and 768", () => {
- expect(isWithinBreakpoints(768, ['s'], CUSTOM_BREAKPOINTS)).toBe(true);
- });
- it("should return 'true' for 'm' and 992", () => {
- expect(isWithinBreakpoints(992, ['m'], CUSTOM_BREAKPOINTS)).toBe(true);
- });
- it("should return 'true' for 'l' and 1200", () => {
- expect(isWithinBreakpoints(1200, ['l'], CUSTOM_BREAKPOINTS)).toBe(true);
- });
- it("should return 'true' for 'xl' and 1400", () => {
- expect(isWithinBreakpoints(1400, ['xl'], CUSTOM_BREAKPOINTS)).toBe(
- true
- );
- });
- });
-
- describe('with multiple sizes', () => {
- it("should return 'true' for a 667 width and ['xs', 's']", () => {
- expect(isWithinBreakpoints(667, ['xs', 's'])).toBe(true);
- });
- });
- });
-});
diff --git a/src/services/breakpoint/breakpoint.ts b/src/services/breakpoint/breakpoint.ts
deleted file mode 100644
index 90f9f2c4a5d..00000000000
--- a/src/services/breakpoint/breakpoint.ts
+++ /dev/null
@@ -1,113 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0 and the Server Side Public License, v 1; you may not use this file except
- * in compliance with, at your election, the Elastic License 2.0 or the Server
- * Side Public License, v 1.
- */
-
-import { keysOf } from '../../components/common';
-
-import {
- _EuiThemeBreakpoint,
- _EuiThemeBreakpoints,
-} from '../../global_styling/variables/breakpoint';
-import { breakpoint } from '../../themes/amsterdam/global_styling/variables/_breakpoint';
-
-export type EuiBreakpointSize = _EuiThemeBreakpoint;
-export type EuiBreakpoints = _EuiThemeBreakpoints;
-export const BREAKPOINTS = breakpoint;
-export const BREAKPOINT_KEYS = keysOf(BREAKPOINTS);
-
-import { sortMapByLargeToSmallValues } from './_sorting';
-
-/**
- * Given the current `width` and an object of `EuiBreakpoints`,
- * this function returns the string that is the name of the breakpoint key
- * that is less than or equal to the width
- *
- * @param {number} width Can either be the full window width or any width
- * @param {EuiBreakpoints} breakpoints An object with keys for sizing and values for minimum width
- * @returns {string | undefined} Name of the breakpoint key or `undefined` if a key doesn't exist
- */
-export function getBreakpoint(
- width: number,
- breakpoints: EuiBreakpoints = BREAKPOINTS
-): EuiBreakpointSize | undefined {
- // Ensure the breakpoints map is sorted from largest value to smallest
- const sortedBreakpoints: EuiBreakpoints = sortMapByLargeToSmallValues(
- breakpoints
- );
-
- // Find the breakpoint (key) whose value is <= windowWidth starting with largest first
- return keysOf(sortedBreakpoints).find((key) => breakpoints[key] <= width);
-}
-
-/**
- * Given the current `width` and a max breakpoint key,
- * this function returns true or false if the `width` falls within the max
- * breakpoint or any breakpoints below
- *
- * @param {number} width Can either be the full window width or any width
- * @param {EuiBreakpointSize | number} max The named breakpoint or custom number to check against
- * @param {EuiBreakpoints} breakpoints An object with keys for sizing and values for minimum width
- * @returns {boolean} Will return `false` if it can't find a value for the `max` breakpoint
- */
-export function isWithinMaxBreakpoint(
- width: number,
- max: EuiBreakpointSize | number,
- breakpoints: EuiBreakpoints = BREAKPOINTS
-): boolean {
- if (typeof max === 'number') {
- return width <= max;
- } else {
- const currentBreakpoint = getBreakpoint(width, breakpoints);
- return currentBreakpoint
- ? breakpoints[currentBreakpoint] <= breakpoints[max]
- : false;
- }
-}
-
-/**
- * Given the current `width` and a min breakpoint key,
- * this function returns true or false if the `width` falls within the min
- * breakpoint or any breakpoints above
- *
- * @param {number} width Can either be the full window width or any width
- * @param {EuiBreakpointSize | number} min The named breakpoint or custom number to check against
- * @param {EuiBreakpoints} breakpoints An object with keys for sizing and values for minimum width
- * @returns {boolean} Will return `false` if it can't find a value for the `min` breakpoint
- */
-export function isWithinMinBreakpoint(
- width: number,
- min: EuiBreakpointSize | number,
- breakpoints: EuiBreakpoints = BREAKPOINTS
-): boolean {
- if (typeof min === 'number') {
- return width >= min;
- } else {
- const currentBreakpoint = getBreakpoint(width, breakpoints);
- return currentBreakpoint
- ? breakpoints[currentBreakpoint] >= breakpoints[min]
- : false;
- }
-}
-
-/**
- * Given the current `width` and an array of breakpoint keys,
- * this function returns true or false if the `width` falls within
- * any of the named breakpoints
- *
- * @param {number} width Can either be the full window width or any width
- * @param {EuiBreakpointSize[]} sizes An array of named breakpoints
- * @param {EuiBreakpoints} breakpoints An object with keys for sizing and values for minimum width
- * @returns {boolean} Returns `true` if current breakpoint name is included in `sizes`
- */
-export function isWithinBreakpoints(
- width: number,
- sizes: EuiBreakpointSize[],
- breakpoints: EuiBreakpoints = BREAKPOINTS
-): boolean {
- const currentBreakpoint = getBreakpoint(width, breakpoints);
- return currentBreakpoint ? sizes.includes(currentBreakpoint) : false;
-}
diff --git a/src/services/breakpoint/current_breakpoint.tsx b/src/services/breakpoint/current_breakpoint.tsx
index cc213fdfc4e..ab60fbc933f 100644
--- a/src/services/breakpoint/current_breakpoint.tsx
+++ b/src/services/breakpoint/current_breakpoint.tsx
@@ -10,14 +10,19 @@ import React, {
createContext,
useState,
useEffect,
+ useMemo,
+ useCallback,
FunctionComponent,
} from 'react';
-import { _EuiThemeBreakpoint } from '../../global_styling/variables/breakpoint';
+import { keysOf } from '../../components/common';
+import {
+ _EuiThemeBreakpoint,
+ _EuiThemeBreakpoints,
+} from '../../global_styling/variables/breakpoint';
import { useEuiTheme } from '../theme';
import { throttle } from '../throttle';
-
-import { getBreakpoint } from './breakpoint';
+import { sortMapByLargeToSmallValues } from './_sorting';
type CurrentEuiBreakpoint = _EuiThemeBreakpoint | undefined;
@@ -32,27 +37,39 @@ export const CurrentEuiBreakpointContext = createContext(
export const CurrentEuiBreakpointProvider: FunctionComponent = ({
children,
}) => {
+ // Obtain the breakpoints map from the EUI theme
const {
- euiTheme: { breakpoint: breakpoints }, // Obtain the breakpoints map from the EUI theme
+ euiTheme: { breakpoint: breakpoints },
} = useEuiTheme();
+ // Ensure the breakpoints map is sorted from largest value to smallest
+ const sortedBreakpoints: _EuiThemeBreakpoints = useMemo(
+ () => sortMapByLargeToSmallValues(breakpoints),
+ [breakpoints]
+ );
+
+ // Find the breakpoint (key) whose value is <= windowWidth starting with largest first
+ const getBreakpoint = useCallback(
+ (width: number) =>
+ keysOf(sortedBreakpoints).find((key) => sortedBreakpoints[key] <= width),
+ [sortedBreakpoints]
+ );
+
const [currentBreakpoint, setCurrentBreakpoint] = useState<
CurrentEuiBreakpoint
>(
- typeof window !== 'undefined'
- ? getBreakpoint(window.innerWidth, breakpoints)
- : undefined
+ typeof window !== 'undefined' ? getBreakpoint(window.innerWidth) : undefined
);
useEffect(() => {
const onWindowResize = throttle(() => {
- setCurrentBreakpoint(getBreakpoint(window.innerWidth, breakpoints));
+ setCurrentBreakpoint(getBreakpoint(window.innerWidth));
}, 50);
window.addEventListener('resize', onWindowResize);
return () => window.removeEventListener('resize', onWindowResize);
- }, [breakpoints]);
+ }, [getBreakpoint]);
return (
diff --git a/src/services/breakpoint/current_breakpoint_hook.testenv.tsx b/src/services/breakpoint/current_breakpoint_hook.testenv.tsx
index a0ae6fead12..6d291fd8000 100644
--- a/src/services/breakpoint/current_breakpoint_hook.testenv.tsx
+++ b/src/services/breakpoint/current_breakpoint_hook.testenv.tsx
@@ -8,7 +8,8 @@
import { useContext } from 'react';
-import { getBreakpoint } from './breakpoint';
+import { keysOf } from '../../components/common';
+import { breakpoint as breakpoints } from '../../themes/amsterdam/global_styling/variables/_breakpoint';
import { CurrentEuiBreakpointContext } from './current_breakpoint';
/**
@@ -19,7 +20,10 @@ export const useCurrentEuiBreakpoint = () => {
const context = useContext(CurrentEuiBreakpointContext);
if (context !== undefined) return context; // Component has been wrapped, everything is fine.
- return typeof window !== 'undefined'
- ? getBreakpoint(window.innerWidth)
- : undefined;
+ if (typeof window === 'undefined') return undefined; // SSR catch
+
+ // Use the default Amsterdam breakpoints (which are already ordered by largest first)
+ return keysOf(breakpoints).find(
+ (key) => breakpoints[key] <= window.innerWidth
+ );
};
diff --git a/src/services/breakpoint/index.ts b/src/services/breakpoint/index.ts
index ad1594914bc..c512792ee67 100644
--- a/src/services/breakpoint/index.ts
+++ b/src/services/breakpoint/index.ts
@@ -6,7 +6,7 @@
* Side Public License, v 1.
*/
-export * from './breakpoint';
+export type { _EuiThemeBreakpoint as EuiBreakpointSize } from '../../global_styling/variables/breakpoint';
export * from './current_breakpoint';
export * from './current_breakpoint_hook';
-export * from './useIsWithinBreakpoints';
+export * from './is_within_hooks';
diff --git a/src/services/breakpoint/is_within_hooks.spec.tsx b/src/services/breakpoint/is_within_hooks.spec.tsx
new file mode 100644
index 00000000000..abe00264554
--- /dev/null
+++ b/src/services/breakpoint/is_within_hooks.spec.tsx
@@ -0,0 +1,173 @@
+/*
+ * 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 React, { FunctionComponent } from 'react';
+
+import { EuiProvider } from '../../components/provider';
+import { _EuiThemeBreakpoint } from '../../global_styling/variables/breakpoint';
+import {
+ useIsWithinBreakpoints,
+ useIsWithinMaxBreakpoint,
+ useIsWithinMinBreakpoint,
+} from './is_within_hooks';
+
+describe('useIsWithinBreakpoints', () => {
+ const MockComponent: FunctionComponent<{
+ sizes: _EuiThemeBreakpoint[];
+ isResponsive?: boolean;
+ }> = ({ sizes, isResponsive }) => {
+ const isWithinBreakpoints = useIsWithinBreakpoints(sizes, isResponsive);
+ return isWithinBreakpoints ? true : null;
+ };
+
+ it('returns true if the current breakpoint size is in the passed sizes array', () => {
+ cy.viewport(300, 600);
+ cy.mount(
+
+
+
+ );
+ cy.get('[data-test-subj]').should('exist');
+ });
+
+ it('returns false if the current breakpoint size is outside the passed sizes array', () => {
+ cy.viewport(1400, 600);
+ cy.mount(
+
+
+
+ );
+ cy.get('[data-test-subj]').should('not.exist');
+ });
+
+ it('returns false always if isResponsive is passed as false', () => {
+ cy.viewport(300, 600);
+ cy.mount(
+
+
+
+ );
+ cy.get('[data-test-subj]').should('not.exist');
+ });
+
+ it('correctly handles custom breakpoint sizes', () => {
+ cy.viewport(1500, 600);
+ cy.mount(
+
+
+
+ );
+ cy.get('[data-test-subj]').should('exist');
+ });
+});
+
+describe('useIsWithinMaxBreakpoint', () => {
+ const MockComponent: FunctionComponent<{
+ size: _EuiThemeBreakpoint;
+ }> = ({ size }) => {
+ const isWithinMaxBreakpoint = useIsWithinMaxBreakpoint(size);
+ return isWithinMaxBreakpoint ? true : null;
+ };
+
+ it('returns true if the current breakpoint size is smaller than the passed max size', () => {
+ cy.viewport(300, 600);
+ cy.mount(
+
+
+
+ );
+ cy.get('[data-test-subj]').should('exist');
+ });
+
+ it('returns false if the current breakpoint size is larger than the passed max size', () => {
+ cy.viewport(1400, 600);
+ cy.mount(
+
+
+
+ );
+ cy.get('[data-test-subj]').should('not.exist');
+ });
+
+ it('correctly handles custom breakpoint sizes', () => {
+ cy.viewport(1400, 600);
+ cy.mount(
+
+
+
+ );
+ cy.get('[data-test-subj]').should('exist');
+ });
+});
+
+describe('useIsWithinMinBreakpoint', () => {
+ const MockComponent: FunctionComponent<{
+ size: _EuiThemeBreakpoint;
+ }> = ({ size }) => {
+ const isWithinMinBreakpoint = useIsWithinMinBreakpoint(size);
+ return isWithinMinBreakpoint ? true : null;
+ };
+
+ it('returns true if the current breakpoint size is larger than the passed min size', () => {
+ cy.viewport(800, 600);
+ cy.mount(
+
+
+
+ );
+ cy.get('[data-test-subj]').should('exist');
+ });
+
+ it('returns false if the current breakpoint size is smaller than the passed min size', () => {
+ cy.viewport(600, 600);
+ cy.mount(
+
+
+
+ );
+ cy.get('[data-test-subj]').should('not.exist');
+ });
+
+ it('correctly handles custom breakpoint sizes', () => {
+ cy.viewport(600, 600);
+ cy.mount(
+
+
+
+ );
+ cy.get('[data-test-subj]').should('exist');
+ });
+});
diff --git a/src/services/breakpoint/is_within_hooks.ts b/src/services/breakpoint/is_within_hooks.ts
new file mode 100644
index 00000000000..29a5bcef238
--- /dev/null
+++ b/src/services/breakpoint/is_within_hooks.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 { _EuiThemeBreakpoint } from '../../global_styling/variables/breakpoint';
+import { useEuiTheme } from '../theme';
+import { useCurrentEuiBreakpoint } from './current_breakpoint_hook';
+
+/**
+ * Given an array of breakpoint keys, this hook returns true or false
+ * if the breakpoint size of the current window width falls within
+ * any of the named breakpoints.
+ *
+ * @param {EuiThemeBreakpoint[]} sizes An array of named EUI breakpoints
+ * @param {boolean} isResponsive Some components have the option to turn off responsive behavior.
+ * Since hooks can't be called conditionally, it's easier to pass the condition into the hook
+ * @returns {boolean} Returns `true` if current breakpoint name is included in `sizes`
+ */
+export const useIsWithinBreakpoints = (
+ sizes: _EuiThemeBreakpoint[],
+ isResponsive = true
+) => {
+ const currentBreakpoint = useCurrentEuiBreakpoint();
+
+ return currentBreakpoint && isResponsive
+ ? sizes.includes(currentBreakpoint)
+ : false;
+};
+
+/**
+ * Given a max breakpoint key, this hook returns true if the breakpoint size
+ * of the current window width falls within the max breakpoint or any below,
+ * and false otherwise
+ *
+ * @param {EuiThemeBreakpoint} max The named max breakpoint to check against
+ * @returns {boolean} Will return `false` if it can't find a value for the `max` breakpoint
+ */
+export function useIsWithinMaxBreakpoint(max: _EuiThemeBreakpoint): boolean {
+ const {
+ euiTheme: { breakpoint: breakpoints },
+ } = useEuiTheme();
+ const currentBreakpoint = useCurrentEuiBreakpoint();
+
+ if (currentBreakpoint == null || breakpoints[max] == null) {
+ return false;
+ }
+
+ return breakpoints[currentBreakpoint] <= breakpoints[max];
+}
+
+/**
+ * Given a min breakpoint key, this hook returns true if the breakpoint size
+ * of the current window width falls within the min breakpoint or any above,
+ * and false otherwise
+ *
+ * @param {EuiThemeBreakpoint} min The named min breakpoint to check against
+ * @returns {boolean} Will return `false` if it can't find a value for the `min` breakpoint
+ */
+export function useIsWithinMinBreakpoint(min: _EuiThemeBreakpoint): boolean {
+ const {
+ euiTheme: { breakpoint: breakpoints },
+ } = useEuiTheme();
+ const currentBreakpoint = useCurrentEuiBreakpoint();
+
+ if (currentBreakpoint == null || breakpoints[min] == null) {
+ return false;
+ }
+
+ return breakpoints[currentBreakpoint] >= breakpoints[min];
+}
diff --git a/src/services/breakpoint/useIsWithinBreakpoints.ts b/src/services/breakpoint/useIsWithinBreakpoints.ts
deleted file mode 100644
index dcf724bf88a..00000000000
--- a/src/services/breakpoint/useIsWithinBreakpoints.ts
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0 and the Server Side Public License, v 1; you may not use this file except
- * in compliance with, at your election, the Elastic License 2.0 or the Server
- * Side Public License, v 1.
- */
-
-import { _EuiThemeBreakpoint } from '../../global_styling/variables/breakpoint';
-import { useCurrentEuiBreakpoint } from './current_breakpoint_hook';
-
-/**
- * Given the current window.innerWidth and an array of breakpoint keys,
- * this hook stores state and returns true or false if the window.innerWidth
- * falls within any of the named breakpoints.
- *
- * @param {_EuiThemeBreakpoint[]} sizes An array of named EUI breakpoints
- * @param {boolean} isResponsive Some components have the option to turn off responsive behavior.
- * Since hooks can't be called conditionally, it's easier to pass the condition into the hook
- * @returns {boolean} Returns `true` if current breakpoint name is included in `sizes`
- */
-export const useIsWithinBreakpoints = (
- sizes: _EuiThemeBreakpoint[],
- isResponsive = true
-) => {
- const currentBreakpoint = useCurrentEuiBreakpoint();
-
- return currentBreakpoint && isResponsive
- ? sizes.includes(currentBreakpoint)
- : false;
-};
diff --git a/src/services/index.ts b/src/services/index.ts
index 13f0863dded..278dc54fa3a 100644
--- a/src/services/index.ts
+++ b/src/services/index.ts
@@ -23,13 +23,9 @@ export { LEFT_ALIGNMENT, RIGHT_ALIGNMENT, CENTER_ALIGNMENT } from './alignment';
export type { EuiBreakpointSize } from './breakpoint';
export {
- BREAKPOINTS,
- BREAKPOINT_KEYS,
- getBreakpoint,
- isWithinBreakpoints,
useIsWithinBreakpoints,
- isWithinMaxBreakpoint,
- isWithinMinBreakpoint,
+ useIsWithinMaxBreakpoint,
+ useIsWithinMinBreakpoint,
CurrentEuiBreakpointContext,
CurrentEuiBreakpointProvider,
useCurrentEuiBreakpoint,
diff --git a/upcoming_changelogs/6119.md b/upcoming_changelogs/6119.md
new file mode 100644
index 00000000000..40037d8c0b7
--- /dev/null
+++ b/upcoming_changelogs/6119.md
@@ -0,0 +1,11 @@
+- Added new `useIsWithinMaxBreakpoint` and `useIsWithinMinBreakpoint` service hooks
+
+**Breaking changes**
+
+- Removed `getBreakpoint`. Use `useCurrentEuiBreakpoint` instead
+- Removed `BREAKPOINTS` and `BREAKPOINT_KEYS`. Use `euiTheme.breakpoint` instead
+- Removed `isWithinBreakpoints`. Use `useIsWithinBreakpoints` instead
+- Removed `isWithinMaxBreakpoint`. Use `useIsWithinMaxBreakpoint` instead
+- Removed `isWithinMinBreakpoint`. Use `useIsWithinMinBreakpoint` instead
+- `EuiFlyout` now only accepts a named breakpoint size for its `pushMinBreakpoint` prop
+- `EuiCollapsibleNav` now only accepts a named breakpoint size for its `dockedBreakpoint` prop