diff --git a/README.md b/README.md index 5a188ab6..2a87c7dd 100644 --- a/README.md +++ b/README.md @@ -179,7 +179,7 @@ const Component = styled.div` ### Object notation -When using object notation, make sure to explicitly pass `props` to breakpoint +Make sure to explicitly pass `props` to breakpoint methods. Please see the example below using default configuration: ```js diff --git a/core/breakpoints.js b/core/breakpoints.js index 6ebd9692..d4dad17f 100644 --- a/core/breakpoints.js +++ b/core/breakpoints.js @@ -19,62 +19,60 @@ const defaultOptions = { exports.DEFAULT_BREAKPOINTS = DEFAULT_BREAKPOINTS; exports.ERROR_PREFIX = ERROR_PREFIX; -exports.createBreakpoints = memoize( - ({ breakpoints, errorPrefix } = defaultOptions) => { - const names = Object.keys(breakpoints); - const validation = createValidation({ - names, - breakpoints, - errorPrefix, - }); - - const getValueByName = memoize((name) => { - validation.checkIsValidName(name); - validation.checkIsFirstName(name); - - return breakpoints[name]; - }); - - const getNextName = (name) => { - const nextIndex = names.indexOf(name) + 1; - - return names[nextIndex]; - }; - - const getNextValueByName = memoize((name) => { - validation.checkIsValidName(name); - validation.checkIsLastName(name); - - return breakpoints[getNextName(name)]; - }); - - // Maximum breakpoint width. Null for the largest (last) breakpoint. - // The maximum value is calculated as the minimum of the next one less 0.02px - // to work around the limitations of `min-` and `max-` prefixes and viewports with fractional widths. - // See https://www.w3.org/TR/mediaqueries-4/#mq-min-max - // Uses 0.02px rather than 0.01px to work around a current rounding bug in Safari. - // See https://bugs.webkit.org/show_bug.cgi?id=178261 - const calcMaxWidth = memoize((value) => { - return `${parseFloat(value) - 0.02}px`; - }); - - return { - up: getValueByName, - - down: (max) => calcMaxWidth(getValueByName(max)), - - between: (min, max) => ({ - min: getValueByName(min), - max: calcMaxWidth(getValueByName(max)), - }), - - only: (name) => ({ - min: getValueByName(name), - max: calcMaxWidth(getNextValueByName(name)), - }), - }; - } -); +exports.createBreakpoints = ({ breakpoints, errorPrefix } = defaultOptions) => { + const names = Object.keys(breakpoints); + const validation = createValidation({ + names, + breakpoints, + errorPrefix, + }); + + const getValueByName = memoize((name) => { + validation.checkIsValidName(name); + validation.checkIsFirstName(name); + + return breakpoints[name]; + }); + + const getNextName = (name) => { + const nextIndex = names.indexOf(name) + 1; + + return names[nextIndex]; + }; + + const getNextValueByName = memoize((name) => { + validation.checkIsValidName(name); + validation.checkIsLastName(name); + + return breakpoints[getNextName(name)]; + }); + + // Maximum breakpoint width. Null for the largest (last) breakpoint. + // The maximum value is calculated as the minimum of the next one less 0.02px + // to work around the limitations of `min-` and `max-` prefixes and viewports with fractional widths. + // See https://www.w3.org/TR/mediaqueries-4/#mq-min-max + // Uses 0.02px rather than 0.01px to work around a current rounding bug in Safari. + // See https://bugs.webkit.org/show_bug.cgi?id=178261 + const calcMaxWidth = memoize((value) => { + return `${parseFloat(value) - 0.02}px`; + }); + + return { + up: getValueByName, + + down: (max) => calcMaxWidth(getValueByName(max)), + + between: (min, max) => ({ + min: getValueByName(min), + max: calcMaxWidth(getValueByName(max)), + }), + + only: (name) => ({ + min: getValueByName(name), + max: calcMaxWidth(getNextValueByName(name)), + }), + }; +}; function createValidation({ names, breakpoints, errorPrefix }) { const invariant = createInvariantWithPrefix(errorPrefix); diff --git a/styled-breakpoints/styled-breakpoints.js b/styled-breakpoints/styled-breakpoints.js index 556ac585..826a90a1 100644 --- a/styled-breakpoints/styled-breakpoints.js +++ b/styled-breakpoints/styled-breakpoints.js @@ -53,8 +53,9 @@ exports.createStyledBreakpoints = (options = defaultOptions) => { const withBreakpoints = (fn) => (props) => { const mediaQueriesFromTheme = getMediaQueriesFromTheme(props); + const createMemoizedBreakpoints = memoize(createBreakpoints); - const breakpoints = createBreakpoints({ + const breakpoints = createMemoizedBreakpoints({ breakpoints: mediaQueriesFromTheme, errorPrefix: options.errorPrefix, }); diff --git a/styled-breakpoints/styled-breakpoints.spec.js b/styled-breakpoints/styled-breakpoints.spec.js index a276d159..022874c4 100644 --- a/styled-breakpoints/styled-breakpoints.spec.js +++ b/styled-breakpoints/styled-breakpoints.spec.js @@ -50,29 +50,32 @@ describe('styled-breakpoints', () => { }); }); - const { up, down, between, only } = createStyledBreakpoints(); describe('up', () => { + let bp; + beforeEach(() => { + bp = createStyledBreakpoints(); + }); it('should render a media query if the screen width is greater than or equal to 576px', () => { - expect(up('sm')(PROPS_WITH_EMPTY_THEME)).toEqual( + expect(bp.up('sm')(PROPS_WITH_EMPTY_THEME)).toEqual( '@media (min-width: 576px)' ); }); it('should render a media query if the screen width is greater than or equal to 576px for portrait orientation', () => { - expect(up('sm', 'portrait')(PROPS_WITH_EMPTY_THEME)).toEqual( + expect(bp.up('sm', 'portrait')(PROPS_WITH_EMPTY_THEME)).toEqual( '@media (min-width: 576px) and (orientation: portrait)' ); }); it('should render a media query if the screen width is greater than or equal to 576px for landscape orientation', () => { - expect(up('sm', 'landscape')(PROPS_WITH_EMPTY_THEME)).toEqual( + expect(bp.up('sm', 'landscape')(PROPS_WITH_EMPTY_THEME)).toEqual( '@media (min-width: 576px) and (orientation: landscape)' ); }); it('should throw an exception if device orientation is not valid', () => { try { - up('sm', 'wtf')(PROPS_WITH_EMPTY_THEME); + bp.up('sm', 'wtf')(PROPS_WITH_EMPTY_THEME); } catch (error) { expect(error.message).toEqual( `${ERROR_PREFIX}\`wtf\` is invalid orientation. Use \`landscape\` or \`portrait\`.` @@ -82,27 +85,31 @@ describe('styled-breakpoints', () => { }); describe('down', () => { + let bp; + beforeEach(() => { + bp = createStyledBreakpoints(); + }); it('should render a media query if the screen width is less or equal to 575.98px', () => { - expect(down('sm')(PROPS_WITH_EMPTY_THEME)).toEqual( + expect(bp.down('sm')(PROPS_WITH_EMPTY_THEME)).toEqual( '@media (max-width: 575.98px)' ); }); it('should render a media query if the screen width is less or equal to 767.98px for portrait orientation', () => { - expect(down('sm', 'portrait')(PROPS_WITH_EMPTY_THEME)).toEqual( + expect(bp.down('sm', 'portrait')(PROPS_WITH_EMPTY_THEME)).toEqual( '@media (max-width: 575.98px) and (orientation: portrait)' ); }); it('should render a media query if the screen width is less or equal to 767.98px for landscape orientation', () => { - expect(down('sm', 'landscape')(PROPS_WITH_EMPTY_THEME)).toEqual( + expect(bp.down('sm', 'landscape')(PROPS_WITH_EMPTY_THEME)).toEqual( '@media (max-width: 575.98px) and (orientation: landscape)' ); }); it('should throw an exception if device orientation is not valid', () => { try { - down('sm', 'wtf')(PROPS_WITH_EMPTY_THEME); + bp.down('sm', 'wtf')(PROPS_WITH_EMPTY_THEME); } catch (error) { expect(error.message).toEqual( `${ERROR_PREFIX}\`wtf\` is invalid orientation. Use \`landscape\` or \`portrait\`.` @@ -112,28 +119,41 @@ describe('styled-breakpoints', () => { }); describe('between', () => { + let bp; + beforeEach(() => { + bp = createStyledBreakpoints(); + }); it('should render a media query if the screen width greater than equal to 576px and less than or equal to 991.98px', () => { - expect(between('sm', 'md')(PROPS_WITH_EMPTY_THEME)).toEqual( + expect(bp.between('sm', 'md')(PROPS_WITH_EMPTY_THEME)).toEqual( '@media (min-width: 576px) and (max-width: 767.98px)' ); }); it('should render a media query if the screen width greater than equal to 576px and less than or equal to 991.98px and portrait orientation', () => { - expect(between('sm', 'md', 'portrait')(PROPS_WITH_EMPTY_THEME)).toEqual( + expect( + bp.between('sm', 'md', 'portrait')(PROPS_WITH_EMPTY_THEME) + ).toEqual( '@media (min-width: 576px) and (max-width: 767.98px) and (orientation: portrait)' ); }); it('should render a media query if the screen width greater than equal to 576px and less than or equal to 991.98px and landscape orientation', () => { - expect(between('sm', 'md', 'landscape')(PROPS_WITH_EMPTY_THEME)).toEqual( + expect( + bp.between('sm', 'md', 'landscape')(PROPS_WITH_EMPTY_THEME) + ).toEqual( '@media (min-width: 576px) and (max-width: 767.98px) and (orientation: landscape)' ); }); }); describe('only', () => { + let bp; + beforeEach(() => { + bp = createStyledBreakpoints(); + }); + it('should render a media query if the screen width greater than equal to 576px and less than or equal to 767.98px', () => { - expect(only('sm')(PROPS_WITH_EMPTY_THEME)).toEqual( + expect(bp.only('sm')(PROPS_WITH_EMPTY_THEME)).toEqual( '@media (min-width: 576px) and (max-width: 767.98px)' ); });