diff --git a/src-docs/src/views/theme/breakpoints/_breakpoints_js.tsx b/src-docs/src/views/theme/breakpoints/_breakpoints_js.tsx
index 0cbede119e7..870676528a9 100644
--- a/src-docs/src/views/theme/breakpoints/_breakpoints_js.tsx
+++ b/src-docs/src/views/theme/breakpoints/_breakpoints_js.tsx
@@ -5,6 +5,8 @@ import {
EuiCode,
EuiText,
useEuiBreakpoint,
+ useEuiMaxBreakpoint,
+ useEuiMinBreakpoint,
useCurrentEuiBreakpoint,
useIsWithinBreakpoints,
useIsWithinMaxBreakpoint,
@@ -110,19 +112,10 @@ useIsWithinMinBreakpoint('s')`}
title={useEuiBreakpoint(sizes[])
}
type="hook"
description={
- <>
-
- Given an array of breakpoint keys, this hook generates a CSS media
- query string based on the minimum width and maximum width
- provided.
-
-
- You can also create media queries with a{' '}
- (max-width) only or{' '}
- (min-width) only by utilizing the{' '}
- xs and xl arguments.
-
- >
+
+ Given an array of screen sizes, this hook generates a CSS media
+ query string based on the minimum and maximum screen sizes provided.
+
}
example={
+
+
+ useEuiMaxBreakpoint(size)
+
+ useEuiMinBreakpoint(size)
+ >
+ }
+ type="hook"
+ description={
+
+ Given a single breakpoint key, these hooks generate a min or max CSS
+ media query string based on the single breakpoint dimension
+ returned.
+
+ }
+ example={
+
+ This text is red on screens below the medium breakpoint, and green
+ on screens above.
+
+ }
+ snippet={`\${useEuiMaxBreakpoint('m')} {
+ color: red;
+ }
+ \${useEuiMinBreakpoint('m')} {
+ color: green;
}`}
snippetLanguage="emotion"
/>
diff --git a/src/components/description_list/description_list.styles.ts b/src/components/description_list/description_list.styles.ts
index d8c99a79efe..4e93841489d 100644
--- a/src/components/description_list/description_list.styles.ts
+++ b/src/components/description_list/description_list.styles.ts
@@ -7,7 +7,7 @@
*/
import { css } from '@emotion/react';
-import { logicalTextAlignCSS, euiBreakpoint } from '../../global_styling';
+import { logicalTextAlignCSS, euiMinBreakpoint } from '../../global_styling';
import { UseEuiTheme } from '../../services';
export const euiDescriptionListStyles = (euiThemeContext: UseEuiTheme) => {
@@ -29,7 +29,7 @@ export const euiDescriptionListStyles = (euiThemeContext: UseEuiTheme) => {
`,
// Responsive columns behave as a row on breakpoints xs-s
responsiveColumn: css`
- ${euiBreakpoint(euiThemeContext, ['m', 'xl'])} {
+ ${euiMinBreakpoint(euiThemeContext, 'm')} {
${columnDisplay}
}
`,
diff --git a/src/components/description_list/description_list_description.styles.ts b/src/components/description_list/description_list_description.styles.ts
index c3c081557ca..2d4fd920a5f 100644
--- a/src/components/description_list/description_list_description.styles.ts
+++ b/src/components/description_list/description_list_description.styles.ts
@@ -9,7 +9,8 @@
import { css } from '@emotion/react';
import {
euiFontSize,
- euiBreakpoint,
+ euiMaxBreakpoint,
+ euiMinBreakpoint,
logicalTextAlignCSS,
logicalCSS,
} from '../../global_styling';
@@ -35,11 +36,11 @@ export const euiDescriptionListDescriptionStyles = (
${columnDisplay}
`,
responsiveColumn: css`
- ${euiBreakpoint(euiThemeContext, ['xs', 's'])} {
+ ${euiMaxBreakpoint(euiThemeContext, 'm')} {
${logicalCSS('width', '100%')}
padding: 0;
}
- ${euiBreakpoint(euiThemeContext, ['m', 'xl'])} {
+ ${euiMinBreakpoint(euiThemeContext, 'm')} {
${columnDisplay}
}
`,
diff --git a/src/components/description_list/description_list_description.tsx b/src/components/description_list/description_list_description.tsx
index 515d2fcef1e..2d34d394a8b 100644
--- a/src/components/description_list/description_list_description.tsx
+++ b/src/components/description_list/description_list_description.tsx
@@ -9,7 +9,7 @@
import React, { HTMLAttributes, FunctionComponent, useContext } from 'react';
import classNames from 'classnames';
import { CommonProps } from '../common';
-import { useEuiTheme } from '../../services';
+import { useEuiTheme, useIsWithinMinBreakpoint } from '../../services';
import { euiDescriptionListDescriptionStyles } from './description_list_description.styles';
import { EuiDescriptionListContext } from './description_list_context';
@@ -26,6 +26,7 @@ export const EuiDescriptionListDescription: FunctionComponent {
${columnDisplay}
`,
responsiveColumn: css`
- ${euiBreakpoint(euiThemeContext, ['xs', 's'])} {
+ ${euiMaxBreakpoint(euiThemeContext, 'm')} {
${logicalCSS('width', '100%')}
padding: 0;
}
- ${euiBreakpoint(euiThemeContext, ['m', 'xl'])} {
+ ${euiMinBreakpoint(euiThemeContext, 'm')} {
${columnDisplay}
}
`,
diff --git a/src/components/flex/flex_grid.styles.ts b/src/components/flex/flex_grid.styles.ts
index 9465a743bfc..329921b8902 100644
--- a/src/components/flex/flex_grid.styles.ts
+++ b/src/components/flex/flex_grid.styles.ts
@@ -7,7 +7,7 @@
*/
import { css } from '@emotion/react';
-import { euiBreakpoint } from '../../global_styling';
+import { euiMaxBreakpoint } from '../../global_styling';
import { UseEuiTheme } from '../../services';
// Note: the only way to get column direction working with `display: grid`
@@ -23,7 +23,7 @@ export const euiFlexGridStyles = (
display: grid;
`,
responsive: css`
- ${euiBreakpoint(euiThemeContext, ['xs', 's'])} {
+ ${euiMaxBreakpoint(euiThemeContext, 'm')} {
grid-template-columns: repeat(1, 1fr);
grid-auto-flow: row;
}
diff --git a/src/components/flex/flex_group.styles.ts b/src/components/flex/flex_group.styles.ts
index 419c8670c17..84640987330 100644
--- a/src/components/flex/flex_group.styles.ts
+++ b/src/components/flex/flex_group.styles.ts
@@ -7,7 +7,7 @@
*/
import { css } from '@emotion/react';
-import { euiBreakpoint, logicalCSS } from '../../global_styling';
+import { euiMaxBreakpoint, logicalCSS } from '../../global_styling';
import { UseEuiTheme } from '../../services';
export const euiFlexGroupStyles = (euiThemeContext: UseEuiTheme) => {
@@ -19,7 +19,7 @@ export const euiFlexGroupStyles = (euiThemeContext: UseEuiTheme) => {
flex-grow: 1; // Grow nested flex-groups by default
`,
responsive: css`
- ${euiBreakpoint(euiThemeContext, ['xs', 's'])} {
+ ${euiMaxBreakpoint(euiThemeContext, 'm')} {
flex-wrap: wrap;
& > .euiFlexItem {
diff --git a/src/components/flyout/flyout.styles.ts b/src/components/flyout/flyout.styles.ts
index 6f002161f64..8b323c030e9 100644
--- a/src/components/flyout/flyout.styles.ts
+++ b/src/components/flyout/flyout.styles.ts
@@ -10,7 +10,8 @@ import { css, keyframes } from '@emotion/react';
import { _EuiFlyoutPaddingSize, EuiFlyoutSize } from './flyout';
import {
euiCanAnimate,
- euiBreakpoint,
+ euiMaxBreakpoint,
+ euiMinBreakpoint,
logicalCSS,
mathWithUnits,
} from '../../global_styling';
@@ -66,22 +67,22 @@ export const euiFlyoutCloseButtonStyles = (euiThemeContext: UseEuiTheme) => {
right: css`
${logicalCSS('left', 0)}
- ${euiBreakpoint(euiThemeContext, ['m', 'xl'])} {
- transform: translateX(calc(-100% - ${euiTheme.size.l})) !important;
- }
- ${euiBreakpoint(euiThemeContext, ['xs', 's'])} {
+ ${euiMaxBreakpoint(euiThemeContext, 'm')} {
transform: translateX(calc(-100% - ${euiTheme.size.xs})) !important;
}
+ ${euiMinBreakpoint(euiThemeContext, 'm')} {
+ transform: translateX(calc(-100% - ${euiTheme.size.l})) !important;
+ }
`,
left: css`
${logicalCSS('right', 0)}
- ${euiBreakpoint(euiThemeContext, ['m', 'xl'])} {
- transform: translateX(calc(100% + ${euiTheme.size.l})) !important;
- }
- ${euiBreakpoint(euiThemeContext, ['xs', 's'])} {
+ ${euiMaxBreakpoint(euiThemeContext, 'm')} {
transform: translateX(calc(100% + ${euiTheme.size.xs})) !important;
}
+ ${euiMinBreakpoint(euiThemeContext, 'm')} {
+ transform: translateX(calc(100% + ${euiTheme.size.l})) !important;
+ }
`,
},
};
@@ -107,7 +108,7 @@ export const euiFlyoutStyles = (euiThemeContext: UseEuiTheme) => {
outline: none;
}
- ${euiBreakpoint(euiThemeContext, ['xs', 's'])} {
+ ${euiMaxBreakpoint(euiThemeContext, 'm')} {
// 1. Leave only a small sliver exposed on small screens so users understand that this is not a new page
// 2. If a custom maxWidth is set, we need to override it.
${logicalCSS('max-width', '90vw !important')}
@@ -219,14 +220,14 @@ const composeFlyoutSizing = (
return `
${logicalCSS('max-width', flyoutSizes[size].max)}
- ${euiBreakpoint(euiThemeContext, ['m', 'xl'])} {
- ${logicalCSS('min-width', flyoutSizes[size].min)}
- ${logicalCSS('width', flyoutSizes[size].width)}
- }
- ${euiBreakpoint(euiThemeContext, ['xs', 's'])} {
+ ${euiMaxBreakpoint(euiThemeContext, 'm')} {
${logicalCSS('min-width', 0)}
${logicalCSS('width', flyoutSizes[size].min)}
}
+ ${euiMinBreakpoint(euiThemeContext, 'm')} {
+ ${logicalCSS('min-width', flyoutSizes[size].min)}
+ ${logicalCSS('width', flyoutSizes[size].width)}
+ }
`;
};
diff --git a/src/components/image/image_wrapper.styles.ts b/src/components/image/image_wrapper.styles.ts
index 9adfd83e864..a2c0bbb584b 100644
--- a/src/components/image/image_wrapper.styles.ts
+++ b/src/components/image/image_wrapper.styles.ts
@@ -8,7 +8,7 @@
import { css } from '@emotion/react';
import {
- euiBreakpoint,
+ euiMinBreakpoint,
logicalCSS,
logicalTextAlignCSS,
logicalSide,
@@ -49,7 +49,7 @@ export const euiImageWrapperStyles = (euiThemeContext: UseEuiTheme) => {
// 1: Logical properties/values in `float` is currently not yet supported by all browsers w/o flags
// @see https://caniuse.com/mdn-css_properties_float_flow_relative_values for when we can remove left/right fallbacks
left: css`
- ${euiBreakpoint(euiThemeContext, ['m', 'xl'])} {
+ ${euiMinBreakpoint(euiThemeContext, 'm')} {
float: left; /* 1 */
float: ${logicalSide.left};
${logicalCSS('margin-left', '0')};
@@ -57,7 +57,7 @@ export const euiImageWrapperStyles = (euiThemeContext: UseEuiTheme) => {
}
`,
right: css`
- ${euiBreakpoint(euiThemeContext, ['m', 'xl'])} {
+ ${euiMinBreakpoint(euiThemeContext, 'm')} {
float: right; /* 1 */
float: ${logicalSide.right};
${logicalCSS('margin-right', '0')};
diff --git a/src/components/modal/modal.styles.ts b/src/components/modal/modal.styles.ts
index d7ac17fda5c..a6fcda8195c 100644
--- a/src/components/modal/modal.styles.ts
+++ b/src/components/modal/modal.styles.ts
@@ -10,7 +10,7 @@ import { css } from '@emotion/react';
import { euiShadowXLarge } from '../../themes/amsterdam/global_styling/mixins';
import {
euiCanAnimate,
- euiBreakpoint,
+ euiMaxBreakpoint,
euiAnimSlideInUp,
} from '../../global_styling';
import { UseEuiTheme } from '../../services';
@@ -39,7 +39,7 @@ export const euiModalStyles = (euiThemeContext: UseEuiTheme) => {
${euiTheme.animation.slow} ${euiTheme.animation.bounce};
}
- ${euiBreakpoint(euiThemeContext, ['xs', 's'])} {
+ ${euiMaxBreakpoint(euiThemeContext, 'm')} {
position: fixed;
inset: 0;
border-radius: 0;
@@ -66,7 +66,7 @@ export const euiModalStyles = (euiThemeContext: UseEuiTheme) => {
confirmation: css`
min-inline-size: ${euiFormVariables(euiThemeContext).maxWidth};
- ${euiBreakpoint(euiThemeContext, ['xs', 's'])} {
+ ${euiMaxBreakpoint(euiThemeContext, 'm')} {
${euiShadowXLarge(euiThemeContext, { reverse: true })}
inset-block-start: auto;
}
diff --git a/src/components/modal/modal_body.styles.ts b/src/components/modal/modal_body.styles.ts
index 1330ca9a5c5..6889a5c9fac 100644
--- a/src/components/modal/modal_body.styles.ts
+++ b/src/components/modal/modal_body.styles.ts
@@ -7,7 +7,7 @@
*/
import { css } from '@emotion/react';
-import { euiYScrollWithShadows, euiBreakpoint } from '../../global_styling';
+import { euiYScrollWithShadows, euiMaxBreakpoint } from '../../global_styling';
import { UseEuiTheme } from '../../services';
export const euiModalBodyStyles = (euiThemeContext: UseEuiTheme) => {
@@ -31,7 +31,7 @@ export const euiModalBodyStyles = (euiThemeContext: UseEuiTheme) => {
padding-inline: ${euiTheme.size.l};
padding-block: ${euiTheme.size.s};
- ${euiBreakpoint(euiThemeContext, ['xs', 's'])} {
+ ${euiMaxBreakpoint(euiThemeContext, 'm')} {
padding-block-end: ${euiTheme.size.l};
}
`,
diff --git a/src/components/modal/modal_footer.styles.ts b/src/components/modal/modal_footer.styles.ts
index a0523bd5d7b..9031bd9b1ec 100644
--- a/src/components/modal/modal_footer.styles.ts
+++ b/src/components/modal/modal_footer.styles.ts
@@ -7,7 +7,7 @@
*/
import { css } from '@emotion/react';
-import { euiBreakpoint } from '../../global_styling';
+import { euiMaxBreakpoint } from '../../global_styling';
import { UseEuiTheme } from '../../services';
export const euiModalFooterStyles = (euiThemeContext: UseEuiTheme) => {
@@ -23,7 +23,7 @@ export const euiModalFooterStyles = (euiThemeContext: UseEuiTheme) => {
flex-shrink: 0; // ensure the height of the footer is based off its contents and doesn't squish
gap: ${euiTheme.size.base};
- ${euiBreakpoint(euiThemeContext, ['xs', 's'])} {
+ ${euiMaxBreakpoint(euiThemeContext, 'm')} {
background: ${euiTheme.colors.lightestShade};
padding-block: ${euiTheme.size.m};
padding-inline: ${euiTheme.size.l};
diff --git a/src/components/page/page.styles.ts b/src/components/page/page.styles.ts
index 86e2a789d52..a0eb30e29d8 100644
--- a/src/components/page/page.styles.ts
+++ b/src/components/page/page.styles.ts
@@ -7,7 +7,7 @@
*/
import { css } from '@emotion/react';
-import { euiBreakpoint, logicalCSS } from '../../global_styling';
+import { euiMinBreakpoint, logicalCSS } from '../../global_styling';
import { UseEuiTheme } from '../../services';
export const euiPageStyles = (euiThemeContext: UseEuiTheme) => {
@@ -36,7 +36,7 @@ export const euiPageStyles = (euiThemeContext: UseEuiTheme) => {
row: css`
flex-direction: column;
- ${euiBreakpoint(euiThemeContext, ['m', 'xl'])} {
+ ${euiMinBreakpoint(euiThemeContext, 'm')} {
flex-direction: row;
}
`,
diff --git a/src/components/toast/global_toast_list.styles.ts b/src/components/toast/global_toast_list.styles.ts
index 8429a1d2173..ebcfdcec5ea 100644
--- a/src/components/toast/global_toast_list.styles.ts
+++ b/src/components/toast/global_toast_list.styles.ts
@@ -8,7 +8,8 @@
import { css, keyframes } from '@emotion/react';
import {
- euiBreakpoint,
+ euiMaxBreakpoint,
+ euiMinBreakpoint,
euiScrollBarStyles,
logicalCSS,
logicalCSSWithFallback,
@@ -18,7 +19,7 @@ import { UseEuiTheme } from '../../services';
export const euiGlobalToastListStyles = (euiThemeContext: UseEuiTheme) => {
const { euiTheme } = euiThemeContext;
- const euiToastWidth = euiTheme.base * 20;
+ const euiToastWidth = euiTheme.base * 25;
return {
/**
* 1. Allow list to expand as items are added, but cap it at the screen height.
@@ -33,7 +34,7 @@ export const euiGlobalToastListStyles = (euiThemeContext: UseEuiTheme) => {
position: fixed;
z-index: ${euiTheme.levels.toast};
${logicalCSS('bottom', 0)};
- ${logicalCSS('width', `${euiToastWidth + euiTheme.base * 5}px`)}; /* 2 */
+ ${logicalCSS('width', `${euiToastWidth}px`)}; /* 2 */
${logicalCSS('max-height', '100vh')}; /* 1 */
${logicalCSSWithFallback('overflow-y', 'auto')};
@@ -53,7 +54,7 @@ export const euiGlobalToastListStyles = (euiThemeContext: UseEuiTheme) => {
${logicalCSS('padding-vertical', euiTheme.size.base)};
}
- ${euiBreakpoint(euiThemeContext, ['xs', 's'])} {
+ ${euiMaxBreakpoint(euiThemeContext, 'm')} {
&:not(:empty) {
${logicalCSS('left', 0)};
${logicalCSS('width', '100%')}; /* 1 */
@@ -64,22 +65,18 @@ export const euiGlobalToastListStyles = (euiThemeContext: UseEuiTheme) => {
right: css`
&:not(:empty) {
${logicalCSS('right', 0)};
- ${logicalCSS('padding-left', `${euiTheme.base * 4}px`)}; /* 2 */
- }
- ${euiBreakpoint(euiThemeContext, ['xs', 's'])} {
- &:not(:empty) {
- ${logicalCSS('padding-left', euiTheme.size.base)};
+
+ ${euiMinBreakpoint(euiThemeContext, 'm')} {
+ ${logicalCSS('padding-left', `${euiTheme.base * 4}px`)}; /* 2 */
}
}
`,
left: css`
&:not(:empty) {
${logicalCSS('left', 0)};
- ${logicalCSS('padding-right', `${euiTheme.base * 4}px`)}; /* 2 */
- }
- ${euiBreakpoint(euiThemeContext, ['xs', 's'])} {
- &:not(:empty) {
- ${logicalCSS('padding-right', euiTheme.size.base)};
+
+ ${euiMinBreakpoint(euiThemeContext, 'm')} {
+ ${logicalCSS('padding-right', `${euiTheme.base * 4}px`)}; /* 2 */
}
}
`,
diff --git a/src/global_styling/mixins/__snapshots__/_responsive.test.ts.snap b/src/global_styling/mixins/__snapshots__/_responsive.test.ts.snap
index f8c2f08c825..c21c6c6e12c 100644
--- a/src/global_styling/mixins/__snapshots__/_responsive.test.ts.snap
+++ b/src/global_styling/mixins/__snapshots__/_responsive.test.ts.snap
@@ -1,5 +1,21 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
+exports[`euiMaxBreakpoint generates a max-width only media query (l) 1`] = `"@media only screen and (max-width: 991px)"`;
+
+exports[`euiMaxBreakpoint generates a max-width only media query (m) 1`] = `"@media only screen and (max-width: 767px)"`;
+
+exports[`euiMaxBreakpoint generates a max-width only media query (s) 1`] = `"@media only screen and (max-width: 574px)"`;
+
+exports[`euiMaxBreakpoint generates a max-width only media query (xl) 1`] = `"@media only screen and (max-width: 1199px)"`;
+
+exports[`euiMinBreakpoint generates a min-width only media query (l) 1`] = `"@media only screen and (min-width: 992px)"`;
+
+exports[`euiMinBreakpoint generates a min-width only media query (m) 1`] = `"@media only screen and (min-width: 768px)"`;
+
+exports[`euiMinBreakpoint generates a min-width only media query (s) 1`] = `"@media only screen and (min-width: 575px)"`;
+
+exports[`euiMinBreakpoint generates a min-width only media query (xl) 1`] = `"@media only screen and (min-width: 1200px)"`;
+
exports[`useEuiBreakpoint common breakpoint size arrays returns a media query for two element breakpoint combinations (l and xl) 1`] = `"@media only screen and (min-width: 992px)"`;
exports[`useEuiBreakpoint common breakpoint size arrays returns a media query for two element breakpoint combinations (m and l) 1`] = `"@media only screen and (min-width: 768px) and (max-width: 1199px)"`;
diff --git a/src/global_styling/mixins/_responsive.test.ts b/src/global_styling/mixins/_responsive.test.ts
index 83fe83c4619..6c5da7c013f 100644
--- a/src/global_styling/mixins/_responsive.test.ts
+++ b/src/global_styling/mixins/_responsive.test.ts
@@ -8,7 +8,14 @@
import { testCustomHook } from '../../test/internal';
import { EuiThemeBreakpoints, _EuiThemeBreakpoint } from '../variables';
-import { useEuiBreakpoint, euiBreakpoint } from './_responsive';
+import {
+ useEuiBreakpoint,
+ euiBreakpoint,
+ useEuiMinBreakpoint,
+ euiMinBreakpoint,
+ useEuiMaxBreakpoint,
+ euiMaxBreakpoint,
+} from './_responsive';
describe('useEuiBreakpoint', () => {
describe('common breakpoint size arrays', () => {
@@ -97,7 +104,71 @@ describe('useEuiBreakpoint', () => {
});
});
-describe('euiBreakpoint & custom theme breakpoints', () => {
+describe('euiMinBreakpoint', () => {
+ describe('generates a min-width only media query', () => {
+ EuiThemeBreakpoints.slice(1).forEach((size) => {
+ it(`(${size})`, () => {
+ expect(
+ testCustomHook(() => useEuiMinBreakpoint(size)).return
+ ).toMatchSnapshot();
+ });
+ });
+ });
+
+ describe('fallback behavior', () => {
+ const warnSpy = jest.spyOn(console, 'warn');
+ beforeEach(() => warnSpy.mockReset());
+
+ it('warns if using min-width on a breakpoint that equals 0px', () => {
+ // This functionally does nothing, hence the warning
+ expect(
+ testCustomHook(() => useEuiMinBreakpoint('xs')).return
+ ).toMatchInlineSnapshot('"@media only screen"');
+ expect(warnSpy).toHaveBeenCalledWith('Invalid min breakpoint size: xs');
+ });
+
+ it('warns if an invalid size is passed', () => {
+ expect(
+ testCustomHook(() => useEuiMinBreakpoint('asdf')).return
+ ).toMatchInlineSnapshot('"@media only screen"');
+ expect(warnSpy).toHaveBeenCalledWith('Invalid min breakpoint size: asdf');
+ });
+ });
+});
+
+describe('euiMaxBreakpoint', () => {
+ describe('generates a max-width only media query', () => {
+ EuiThemeBreakpoints.slice(1).forEach((size) => {
+ it(`(${size})`, () => {
+ expect(
+ testCustomHook(() => useEuiMaxBreakpoint(size)).return
+ ).toMatchSnapshot();
+ });
+ });
+ });
+
+ describe('fallback behavior', () => {
+ const warnSpy = jest.spyOn(console, 'warn');
+ beforeEach(() => warnSpy.mockReset());
+
+ it('warns if using max-width on a breakpoint that equals 0px', () => {
+ // This functionally does nothing, hence the warning
+ expect(
+ testCustomHook(() => useEuiMaxBreakpoint('xs')).return
+ ).toMatchInlineSnapshot('"@media only screen"');
+ expect(warnSpy).toHaveBeenCalledWith('Invalid max breakpoint size: xs');
+ });
+
+ it('warns if an invalid size is passed', () => {
+ expect(
+ testCustomHook(() => useEuiMaxBreakpoint('asdf')).return
+ ).toMatchInlineSnapshot('"@media only screen"');
+ expect(warnSpy).toHaveBeenCalledWith('Invalid max breakpoint size: asdf');
+ });
+ });
+});
+
+describe('custom theme breakpoints', () => {
const CUSTOM_BREAKPOINTS = {
xxl: 700,
xl: 600,
@@ -109,21 +180,45 @@ describe('euiBreakpoint & custom theme breakpoints', () => {
};
const mockEuiTheme: any = { euiTheme: { breakpoint: CUSTOM_BREAKPOINTS } };
- it('correctly inherits the breakpoint size override', () => {
- expect(euiBreakpoint(mockEuiTheme, ['s', 'l'])).toMatchInlineSnapshot(
- '"@media only screen and (min-width: 300px) and (max-width: 599px)"'
- );
+ describe('euiBreakpoint', () => {
+ it('correctly inherits the breakpoint size override', () => {
+ expect(euiBreakpoint(mockEuiTheme, ['s', 'l'])).toMatchInlineSnapshot(
+ '"@media only screen and (min-width: 300px) and (max-width: 599px)"'
+ );
+ });
+
+ it('correctly infers the largest breakpoint and does not render a max-width if passed', () => {
+ expect(euiBreakpoint(mockEuiTheme, ['xl', 'xxl'])).toMatchInlineSnapshot(
+ '"@media only screen and (min-width: 600px)"'
+ );
+ });
+
+ it('correctly uses the smallest breakpoint for a min-width if it is not set to 0', () => {
+ expect(euiBreakpoint(mockEuiTheme, ['xxs', 'xs'])).toMatchInlineSnapshot(
+ '"@media only screen and (min-width: 100px) and (max-width: 299px)"'
+ );
+ });
});
- it('correctly infers the largest breakpoint and does not render a max-width if passed', () => {
- expect(euiBreakpoint(mockEuiTheme, ['xl', 'xxl'])).toMatchInlineSnapshot(
- '"@media only screen and (min-width: 600px)"'
- );
+ describe('euiMinBreakpoint', () => {
+ it('correctly inherits the custom breakpoint sizes', () => {
+ expect(euiMinBreakpoint(mockEuiTheme, 'xxs')).toMatchInlineSnapshot(
+ '"@media only screen and (min-width: 100px)"'
+ );
+ expect(euiMinBreakpoint(mockEuiTheme, 'm')).toMatchInlineSnapshot(
+ '"@media only screen and (min-width: 400px)"'
+ );
+ });
});
- it('correctly uses the smallest breakpoint for a min-width if it is not set to 0', () => {
- expect(euiBreakpoint(mockEuiTheme, ['xxs', 'xs'])).toMatchInlineSnapshot(
- '"@media only screen and (min-width: 100px) and (max-width: 299px)"'
- );
+ describe('euiMaxBreakpoint', () => {
+ it('correctly inherits the custom breakpoint sizes', () => {
+ expect(euiMaxBreakpoint(mockEuiTheme, 'l')).toMatchInlineSnapshot(
+ '"@media only screen and (max-width: 499px)"'
+ );
+ expect(euiMaxBreakpoint(mockEuiTheme, 'xxl')).toMatchInlineSnapshot(
+ '"@media only screen and (max-width: 699px)"'
+ );
+ });
});
});
diff --git a/src/global_styling/mixins/_responsive.ts b/src/global_styling/mixins/_responsive.ts
index 54827c4b80f..b125196c11e 100644
--- a/src/global_styling/mixins/_responsive.ts
+++ b/src/global_styling/mixins/_responsive.ts
@@ -11,7 +11,7 @@ import { useEuiTheme, UseEuiTheme } from '../../services/theme/hooks';
import { _EuiThemeBreakpoint } from '../variables';
/**
- * Generates a CSS media query rule string based on the input breakpoint ranges.
+ * Generates a CSS media query rule string based on the input breakpoint *ranges*.
* Examples with default theme breakpoints:
*
* euiBreakpoint(['s']) becomes `@media only screen and (min-width: 575px) and (max-width: 767px)`
@@ -65,3 +65,54 @@ export const useEuiBreakpoint = (
const euiTheme = useEuiTheme();
return euiBreakpoint(euiTheme, sizes);
};
+
+/**
+ * Min/Max width breakpoint utilities that generate only a single min/max query/bound
+ *
+ * *Unlike the above euiBreakpoint utility*, these utilities treat breakpoint
+ * sizes as a one-dimensional point, rather than a two-dimensional *screen range*.
+ * Examples with default theme breakpoints:
+ *
+ * euiMaxBreakpoint('m') becomes `@media only screen and (max-width: 767px)`
+ * euiMinBreakpoint('m') becomes `@media only screen and (min-width: 768px)`
+ *
+ * This is safer and more intentional to use than euiBreakpoint(['xs', 's']) / euiBreakpoint(['m', 'xl'])
+ * in the event that consumers add larger or smaller custom breakpoints (e.g 'xxs' or `xxl`)
+ * and if the intention of the media query is actually "m and below/above" vs. "only screens m/l/xl".
+ */
+
+export const euiMinBreakpoint = (
+ { euiTheme }: UseEuiTheme,
+ size: _EuiThemeBreakpoint
+) => {
+ const minBreakpointSize = euiTheme.breakpoint[size];
+ if (minBreakpointSize) {
+ return `@media only screen and (min-width: ${minBreakpointSize}px)`;
+ } else {
+ console.warn(`Invalid min breakpoint size: ${size}`);
+ return '@media only screen';
+ }
+};
+
+export const useEuiMinBreakpoint = (size: _EuiThemeBreakpoint) => {
+ const euiTheme = useEuiTheme();
+ return euiMinBreakpoint(euiTheme, size);
+};
+
+export const euiMaxBreakpoint = (
+ { euiTheme }: UseEuiTheme,
+ size: _EuiThemeBreakpoint
+) => {
+ const maxBreakpointSize = euiTheme.breakpoint[size];
+ if (maxBreakpointSize) {
+ return `@media only screen and (max-width: ${maxBreakpointSize - 1}px)`;
+ } else {
+ console.warn(`Invalid max breakpoint size: ${size}`);
+ return '@media only screen';
+ }
+};
+
+export const useEuiMaxBreakpoint = (size: _EuiThemeBreakpoint) => {
+ const euiTheme = useEuiTheme();
+ return euiMaxBreakpoint(euiTheme, size);
+};
diff --git a/upcoming_changelogs/6431.md b/upcoming_changelogs/6431.md
new file mode 100644
index 00000000000..c9bd225670c
--- /dev/null
+++ b/upcoming_changelogs/6431.md
@@ -0,0 +1,5 @@
+- Added the `euiMaxBreakpoint` and `euiMinBreakpoint` CSS-in-JS utilities for creating min/max-width media queries
+
+**Bug fixes**
+
+- Fixed multiple component media queries for consumers with custom theme breakpoints