Skip to content

Commit

Permalink
[CSS-in-JS] Convert EuiBreakpoint Mixin to Emotion (#6057)
Browse files Browse the repository at this point in the history
* Converted euiBreakpoint mixin. Added additional parameter options and validation functions

* Retooled euiBreakpoint mixin by adding additional logic and validation conditions to allow consumers the flexibility of obtaining a min-width, max-width, or min and max width media query from a variety of different scenarios

* CHANGELOG

* Clean up a rogue curly brace

* CHANGELOG

* [PR feedback] Simplify branching logic and typing

- Prefer types from theme instead of the breakpoints service (which likely needs to be refactored to use the theme)

- simplify output to use array logic to output/concatenate a string

- Remove undefined type

- improve unit tests:
  - catch console warn conditions
  - don't snapshot invalid/uncommon size combos

* Fix commented out assertion

* Added documentation for euiBreakpoint mixin. Updated changelog

* Update Breakpoint Theming documentation page with an addition entry for the euiBreakpoing function

* Update src-docs/src/views/theme/breakpoints/_breakpoints_js.tsx

Co-authored-by: Constance <[email protected]>

* Update upcoming_changelogs/6057.md

Co-authored-by: Constance <[email protected]>

* Clean up import statements

* Update src-docs/src/views/theme/breakpoints/_breakpoints_js.tsx

Co-authored-by: Greg Thompson <[email protected]>

Co-authored-by: Constance Chen <[email protected]>
Co-authored-by: Constance <[email protected]>
Co-authored-by: Greg Thompson <[email protected]>
  • Loading branch information
4 people authored Jul 20, 2022
1 parent b86ee8c commit 56e7f6f
Show file tree
Hide file tree
Showing 6 changed files with 394 additions and 0 deletions.
48 changes: 48 additions & 0 deletions src-docs/src/views/theme/breakpoints/_breakpoints_js.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,11 @@ import {
EuiBreakpointSize,
EuiCode,
EuiThemeBreakpoints,
useEuiBreakpoint,
} from '../../../../../src';

import { css } from '@emotion/react';

import { EuiThemeBreakpoints as _EuiThemeBreakpoints } from '../_props';
import { getPropsFromComponent } from '../../../services/props/get_props';
import { ThemeExample } from '../_components/_theme_example';
Expand Down Expand Up @@ -132,6 +135,51 @@ export default () => {
snippet="useIsWithinBreakpoints(['l', 'xl'])"
snippetLanguage="js"
/>

<ThemeExample
title={<code>useEuiBreakpoint(sizes[])</code>}
type="hook"
description={
<>
<p>
Given an array of breakpoint keys, this hook generates a CSS media
query string based on the minimum width and maximum width
provided.
</p>
<p>
You can also create media queries with a{' '}
<EuiCode>(min-width)</EuiCode> only or{' '}
<EuiCode>(max-width)</EuiCode> only by utilizing the{' '}
<EuiCode>0</EuiCode> and <EuiCode>Infinity</EuiCode> arguments.
</p>
</>
}
example={
<p
css={css`
${useEuiBreakpoint([0, 'm'])} {
color: ${euiTheme.colors.dangerText};
}
${useEuiBreakpoint(['m', 'xl'])} {
color: ${euiTheme.colors.warningText};
}
${useEuiBreakpoint(['xl', Infinity])} {
color: ${euiTheme.colors.successText};
}
`}
>
This text is red on screens narrower than `m`, yellow between `m` to
`xl` breakpoints, and green on screens wider than `xl`.
</p>
}
snippet={`\${useEuiBreakpoint(['m', 'xl'])} {
color: red;
}
\${useEuiBreakpoint(['xl', Infinity])} {
color: green;
}`}
snippetLanguage="emotion"
/>
</>
);
};
Expand Down
53 changes: 53 additions & 0 deletions src/global_styling/mixins/__snapshots__/_responsive.test.ts.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`useEuiBreakpoint 0 as a first argument should not render a min-width query (0 and l) 1`] = `"@media only screen and (max-width: 991px)"`;

exports[`useEuiBreakpoint 0 as a first argument should not render a min-width query (0 and m) 1`] = `"@media only screen and (max-width: 767px)"`;

exports[`useEuiBreakpoint 0 as a first argument should not render a min-width query (0 and s) 1`] = `"@media only screen and (max-width: 574px)"`;

exports[`useEuiBreakpoint 0 as a first argument should not render a min-width query (0 and xl) 1`] = `"@media only screen and (max-width: 1199px)"`;

exports[`useEuiBreakpoint 0 as a first argument should not render a min-width query (0 and xs) 1`] = `"@media only screen"`;

exports[`useEuiBreakpoint Infinity as a last argument should not render a max-width query (l and Infinity) 1`] = `"@media only screen and (min-width: 992px)"`;

exports[`useEuiBreakpoint Infinity as a last argument should not render a max-width query (m and Infinity) 1`] = `"@media only screen and (min-width: 768px)"`;

exports[`useEuiBreakpoint Infinity as a last argument should not render a max-width query (s and Infinity) 1`] = `"@media only screen and (min-width: 575px)"`;

exports[`useEuiBreakpoint Infinity as a last argument should not render a max-width query (xl and Infinity) 1`] = `"@media only screen and (min-width: 1200px)"`;

exports[`useEuiBreakpoint Infinity as a last argument should not render a max-width query (xs and Infinity) 1`] = `"@media only screen"`;

exports[`useEuiBreakpoint breakpoint size arrays with more than 2 sizes should use the first and last items in the array 1`] = `"@media only screen and (min-width: 575px) and (max-width: 991px)"`;

exports[`useEuiBreakpoint common breakpoint size arrays useEuiBreakpoint returns a media query for two element breakpoint combinations (l and xl) 1`] = `"@media only screen and (min-width: 992px) and (max-width: 1199px)"`;

exports[`useEuiBreakpoint common breakpoint size arrays useEuiBreakpoint returns a media query for two element breakpoint combinations (m and l) 1`] = `"@media only screen and (min-width: 768px) and (max-width: 991px)"`;

exports[`useEuiBreakpoint common breakpoint size arrays useEuiBreakpoint returns a media query for two element breakpoint combinations (m and xl) 1`] = `"@media only screen and (min-width: 768px) and (max-width: 1199px)"`;

exports[`useEuiBreakpoint common breakpoint size arrays useEuiBreakpoint returns a media query for two element breakpoint combinations (s and l) 1`] = `"@media only screen and (min-width: 575px) and (max-width: 991px)"`;

exports[`useEuiBreakpoint common breakpoint size arrays useEuiBreakpoint returns a media query for two element breakpoint combinations (s and m) 1`] = `"@media only screen and (min-width: 575px) and (max-width: 767px)"`;

exports[`useEuiBreakpoint common breakpoint size arrays useEuiBreakpoint returns a media query for two element breakpoint combinations (s and xl) 1`] = `"@media only screen and (min-width: 575px) and (max-width: 1199px)"`;

exports[`useEuiBreakpoint common breakpoint size arrays useEuiBreakpoint returns a media query for two element breakpoint combinations (xs and l) 1`] = `"@media only screen and (max-width: 991px)"`;

exports[`useEuiBreakpoint common breakpoint size arrays useEuiBreakpoint returns a media query for two element breakpoint combinations (xs and m) 1`] = `"@media only screen and (max-width: 767px)"`;

exports[`useEuiBreakpoint common breakpoint size arrays useEuiBreakpoint returns a media query for two element breakpoint combinations (xs and s) 1`] = `"@media only screen and (max-width: 574px)"`;

exports[`useEuiBreakpoint common breakpoint size arrays useEuiBreakpoint returns a media query for two element breakpoint combinations (xs and xl) 1`] = `"@media only screen and (max-width: 1199px)"`;

exports[`useEuiBreakpoint single breakpoint sizes should infer the next breakpoint size as max-width l 1`] = `"@media only screen and (min-width: 992px) and (max-width: 1199px)"`;

exports[`useEuiBreakpoint single breakpoint sizes should infer the next breakpoint size as max-width m 1`] = `"@media only screen and (min-width: 768px) and (max-width: 991px)"`;

exports[`useEuiBreakpoint single breakpoint sizes should infer the next breakpoint size as max-width s 1`] = `"@media only screen and (min-width: 575px) and (max-width: 767px)"`;

exports[`useEuiBreakpoint single breakpoint sizes should infer the next breakpoint size as max-width xl 1`] = `"@media only screen and (min-width: 1200px)"`;

exports[`useEuiBreakpoint single breakpoint sizes should infer the next breakpoint size as max-width xs 1`] = `"@media only screen and (max-width: 574px)"`;
192 changes: 192 additions & 0 deletions src/global_styling/mixins/_responsive.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
/*
* 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 { testCustomHook } from '../../test/internal';
import { EuiThemeBreakpoints, _EuiThemeBreakpoint } from '../variables';
import { useEuiBreakpoint } from './_responsive';

describe('useEuiBreakpoint', () => {
describe('common breakpoint size arrays', () => {
const possibleTwoElementBreakpointCombinations = [];
for (let i = 0; i < EuiThemeBreakpoints.length; i++) {
for (let j = 1; j < EuiThemeBreakpoints.length; j++) {
if (j > i) {
possibleTwoElementBreakpointCombinations.push([
`${EuiThemeBreakpoints[i]}`,
`${EuiThemeBreakpoints[j]}`,
]);
}
}
}

test.each(possibleTwoElementBreakpointCombinations)(
'useEuiBreakpoint returns a media query for two element breakpoint combinations (%s and %s)',
(minSize, maxSize) => {
expect(
testCustomHook(() =>
useEuiBreakpoint([
minSize as _EuiThemeBreakpoint,
maxSize as _EuiThemeBreakpoint,
])
).return
).toMatchSnapshot();
}
);
});

describe('0 as a first argument should not render a min-width query', () => {
EuiThemeBreakpoints.forEach((size) => {
test(`(0 and ${size})`, () => {
expect(
testCustomHook(() => useEuiBreakpoint([0, size])).return
).toMatchSnapshot();
});
});
});

describe('Infinity as a last argument should not render a max-width query', () => {
EuiThemeBreakpoints.forEach((size) => {
test(`(${size} and Infinity)`, () => {
expect(
testCustomHook(() => useEuiBreakpoint([size, Infinity])).return
).toMatchSnapshot();
});
});
});

describe('breakpoint size arrays with more than 2 sizes', () => {
it('should use the first and last items in the array', () => {
expect(
testCustomHook(() => useEuiBreakpoint(['s', 'm', 'l'])).return
).toMatchSnapshot();
});
});

describe('single breakpoint sizes should infer the next breakpoint size as max-width', () => {
EuiThemeBreakpoints.forEach((size) => {
test(`${size}`, () => {
expect(
testCustomHook(() => useEuiBreakpoint([size])).return
).toMatchSnapshot();
});
});
});

describe('invalid arguments', () => {
const fallbackOutput = '@media only screen';

const oldConsoleError = console.warn;
let consoleStub: jest.Mock;
beforeEach(() => {
console.warn = consoleStub = jest.fn();
});
afterEach(() => {
console.warn = oldConsoleError;
});

describe('invalid array sizes', () => {
afterEach(() => {
expect(consoleStub).toHaveBeenCalledTimes(1);
expect(consoleStub).toHaveBeenLastCalledWith(
'Pass more than one breakpoint size'
);
});

it('empty array', () => {
expect(testCustomHook(() => useEuiBreakpoint([])).return).toEqual(
fallbackOutput
);
});

it('invalid single non-breakpoint size', () => {
expect(testCustomHook(() => useEuiBreakpoint([0])).return).toEqual(
fallbackOutput
);
});

it('invalid single non-breakpoint size', () => {
expect(
testCustomHook(() => useEuiBreakpoint([Infinity])).return
).toEqual(fallbackOutput);
});
});

describe('invalid breakpoint keys', () => {
afterEach(() => {
expect(consoleStub).toHaveBeenCalledTimes(2);
expect(consoleStub).toHaveBeenCalledWith(
'Invalid min-width breakpoint size passed'
);
expect(consoleStub).toHaveBeenCalledWith(
'Invalid max-width breakpoint size passed'
);
});

it('warns when invalid size key strings are passed', () => {
expect(
// @ts-expect-error deliberate incorrect type
testCustomHook(() => useEuiBreakpoint(['teeny-tiny', 'SUPERMASSIVE']))
.return
).toEqual(fallbackOutput);
});

it('warns when invalid size numbers are passed', () => {
expect(
testCustomHook(() => useEuiBreakpoint([100, 400])).return // we might support this someday, but today is not that day
).toEqual(fallbackOutput);
});

it('warns when 0 and Infinity are used in the wrong position', () => {
expect(
testCustomHook(() => useEuiBreakpoint([Infinity, 0])).return
).toEqual(fallbackOutput);
});
});

describe('invalid breakpoint size order', () => {
afterEach(() => {
expect(consoleStub).toHaveBeenCalledTimes(1);
expect(consoleStub).toHaveBeenLastCalledWith(
'Invalid breakpoint sizes passed. The first size should be smaller than the last size'
);
});

it('warns if min breakpoint is not smaller than the max breakpoint', () => {
expect(
testCustomHook(() => useEuiBreakpoint(['xl', 'xs'])).return
).toEqual(fallbackOutput);
});

it('warns if the min/max breakpoints are equal', () => {
expect(
testCustomHook(() => useEuiBreakpoint(['s', 's'])).return
).toEqual(fallbackOutput);
});
});

describe('valid but funky breakpoint combos', () => {
afterEach(() => {
expect(consoleStub).not.toHaveBeenCalled();
});

test('0 and xs', () => {
// Since xs is (currently) already 0, this should output nothing
expect(
testCustomHook(() => useEuiBreakpoint([0, 'xs'])).return
).toEqual(fallbackOutput);
});

test('xs and infinity', () => {
// Since xs is (currently) 0, there should be no min or max width
expect(
testCustomHook(() => useEuiBreakpoint(['xs', Infinity])).return
).toEqual(fallbackOutput);
});
});
});
});
Loading

0 comments on commit 56e7f6f

Please sign in to comment.