From 5648196927e96654a7acdf738bad624fb5c72702 Mon Sep 17 00:00:00 2001 From: mg901 Date: Mon, 16 Sep 2019 00:48:10 +0300 Subject: [PATCH] feat: rewrite to es5 --- src/index.js | 202 ++++++++++++++++++++++++++++++++++++++- src/index.spec.js | 234 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 433 insertions(+), 3 deletions(-) create mode 100644 src/index.spec.js diff --git a/src/index.js b/src/index.js index 36cf0047..0feb520d 100644 --- a/src/index.js +++ b/src/index.js @@ -1,4 +1,200 @@ -// @flow strict +var DEFAULT_BREAKS_MAP = { + tablet: '768px', + desktop: '992px', + lgDesktop: '1200px', +}; -export type { BpProps } from './models'; -export { up, down, only, between } from './utils'; +var invariant = function(condition, message) { + if (!condition) { + throw new Error('[styled-breakpoints]: ' + message); + } +}; + +// withOrientationOrNot :: (String, ?String) -> String | Void +var withOrientationOrNot = function(params, orientation) { + var isValidOrientation = + orientation === 'portrait' || orientation === 'landscape'; + + if (!orientation) return params; + + invariant( + isValidOrientation, + orientation + " is invalid orientation. Use 'landscape' or 'portrait'." + ); + + return params + ' and (orientation: ' + orientation + ')'; +}; + +// withMinMedia :: String -> String +var withMinMedia = function(minWidth) { + return '@media (min-width: ' + minWidth + ')'; +}; + +// withMaxMedia :: String -> String +var withMaxMedia = function(maxWidth) { + return '@media (max-width: ' + maxWidth + ')'; +}; + +// withMinAndMaxMedia :: (String, String) -> String +var withMinAndMaxMedia = function(minWidth, maxWidth) { + return ( + '@media (min-width: ' + minWidth + ') and (max-width: ' + maxWidth + ')' + ); +}; + +// isObject :: a -> Boolean +var isObject = function(value) { + return Object.prototype.toString.call(value).slice(8, -1) === 'Object'; +}; + +// setCustomOrDefaultTheme :: Object -> Object +var setCustomOrDefaultTheme = function(theme) { + switch (true) { + case theme && + theme.breakpoints && + isObject(theme.breakpoints) && + Object.keys(theme.breakpoints).length > 0: + return theme; + default: + theme.breakpoints = DEFAULT_BREAKS_MAP; + return theme; + } +}; + +// toEm :: String -> String +var toEm = function(inPx) { + return parseFloat(inPx) / 16 + 'em'; +}; + +// makeErrorMessage :: (String, Object) -> String +var makeErrorMessage = function(breakName, breaks) { + return ( + "'" + + breakName + + "' is invalid breakpoint name. Use '" + + Object.keys(breaks).join(', ') + + "'." + ); +}; + +// getNextBreakpointName :: String -> Object -> String | Void +var getNextBreakpointName = function(breakName) { + return function(breaks) { + var breakNames = Object.keys(breaks); + var penultimateBreakName = breakNames[breakNames.length - 2]; + var currentPosition = breakNames.indexOf(breakName); + var isInvalidBreakName = currentPosition === -1; + var isLastBreakName = + currentPosition > -1 && currentPosition >= breakNames.length - 1; + + switch (true) { + case isInvalidBreakName: + invariant(!isInvalidBreakName, makeErrorMessage(breakName, breaks)); + break; + case isLastBreakName: + invariant( + !isLastBreakName, + + "Don't use '" + + breakName + + "' because it doesn't have a maximum width. Use '" + + penultimateBreakName + + "'. See https://github.com/mg901/styled-breakpoints/issues/4 ." + ); + break; + default: + return breakNames[currentPosition + 1]; + } + }; +}; + +// 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 + +// getNextBreakpointValue :: (String, Object) -> String | Void +var getNextBreakpointValue = function(breakName, breaks) { + invariant( + breaks[breakName], + "'" + + breakName + + "' is invalid breakpoint name. Use '" + + Object.keys(breaks) + .slice(0, -1) + .join(', ') + + "'." + ); + var getNextProp = getNextBreakpointName(breakName); + + return parseFloat(breaks[getNextProp(breaks)]) - 0.02 + 'px'; +}; + +// getBreakpointValue :: (String, Object) -> String | Void +var getBreakpointValue = function(breakName, breaks) { + invariant(breaks[breakName], makeErrorMessage(breakName, breaks)); + + return breaks[breakName]; +}; + +// calcMinWidth :: (String, Object) -> String +var calcMinWidth = function(breakName, theme) { + var breakpoints = setCustomOrDefaultTheme(theme).breakpoints; + + return toEm(getBreakpointValue(breakName, breakpoints)); +}; + +// calcMaxWidth :: (String, Object) -> String +var calcMaxWidth = function(breakName, theme) { + var breakpoints = setCustomOrDefaultTheme(theme).breakpoints; + + return toEm(getNextBreakpointValue(breakName, breakpoints)); +}; + +// up :: (String, ?String) -> Object -> String +exports.up = function(breakName, orientation) { + return function(props) { + return withOrientationOrNot( + withMinMedia(calcMinWidth(breakName, props.theme)), + orientation + ); + }; +}; + +// down :: (String, ?String) -> Object -> String +exports.down = function(breakName, orientation) { + return function(props) { + return withOrientationOrNot( + withMaxMedia(calcMaxWidth(breakName, props.theme)), + orientation + ); + }; +}; + +// between :: (String, String, ?String) -> Object -> String +exports.between = function(minBreak, maxBreak, orientation) { + return function(props) { + return withOrientationOrNot( + withMinAndMaxMedia( + calcMinWidth(minBreak, props.theme), + calcMaxWidth(maxBreak, props.theme) + ), + orientation + ); + }; +}; + +// only :: (String, ?String) -> Object -> String +exports.only = function(breakName, orientation) { + return function(props) { + return withOrientationOrNot( + withMinAndMaxMedia( + calcMinWidth(breakName, props.theme), + calcMaxWidth(breakName, props.theme) + ), + orientation + ); + }; +}; diff --git a/src/index.spec.js b/src/index.spec.js new file mode 100644 index 00000000..f949a7b5 --- /dev/null +++ b/src/index.spec.js @@ -0,0 +1,234 @@ +var bp = require('.'); + +var CUSTOM_THEME = { + theme: { + breakpoints: { + tablet: '768px', + desktop: '992px', + lgDesktop: '1200px', + }, + }, +}; + +var CUSTOM_THEME_IS_EMPTY = { + theme: {}, +}; + +describe('up', () => { + it('should return min breakpoint value and media query', () => { + expect(bp.up('tablet')(CUSTOM_THEME)).toEqual('@media (min-width: 48em)'); + }); + + it('should return min breakpoint value and media query (from default theme)', () => { + expect(bp.up('tablet')(CUSTOM_THEME_IS_EMPTY)).toEqual( + '@media (min-width: 48em)' + ); + }); + it('should return min breakpoint value and media query with portrait orientation', () => { + expect(bp.up('tablet', 'portrait')(CUSTOM_THEME)).toEqual( + '@media (min-width: 48em) and (orientation: portrait)' + ); + }); + + it('should return min breakpoint value and media query with landscape orientation', () => { + expect(bp.up('tablet', 'landscape')(CUSTOM_THEME)).toEqual( + '@media (min-width: 48em) and (orientation: landscape)' + ); + }); +}); + +describe('down', () => { + it('should return max breakpoint value and media query', () => { + expect(bp.down('tablet')(CUSTOM_THEME)).toEqual( + '@media (max-width: 61.99875em)' + ); + }); + + it('should return max breakpoint value and media query with portrait orientation', () => { + expect(bp.down('tablet', 'portrait')(CUSTOM_THEME)).toEqual( + '@media (max-width: 61.99875em) and (orientation: portrait)' + ); + }); + + it('should return max breakpoint value and media query with landscape orientation', () => { + expect(bp.down('tablet', 'landscape')(CUSTOM_THEME)).toEqual( + '@media (max-width: 61.99875em) and (orientation: landscape)' + ); + }); + + it('should return max breakpoint value and media query (from default theme)', () => { + expect(bp.down('tablet')(CUSTOM_THEME_IS_EMPTY)).toEqual( + '@media (max-width: 61.99875em)' + ); + }); +}); + +describe('between', () => { + it('should returns a string containing the value of the minimum and maximum breakpoints and media query', () => { + expect(bp.between('tablet', 'desktop')(CUSTOM_THEME)).toEqual( + '@media (min-width: 48em) and (max-width: 74.99875em)' + ); + }); + + it('should returns a string containing the value of the minimum and maximum breakpoints and media query and portrait orientation', () => { + expect(bp.between('tablet', 'desktop', 'portrait')(CUSTOM_THEME)).toEqual( + '@media (min-width: 48em) and (max-width: 74.99875em) and (orientation: portrait)' + ); + }); + + it('should returns a string containing the value of the minimum and maximum breakpoints and media query and landscape orientation', () => { + expect(bp.between('tablet', 'desktop', 'landscape')(CUSTOM_THEME)).toEqual( + '@media (min-width: 48em) and (max-width: 74.99875em) and (orientation: landscape)' + ); + }); + + it('should returns a string containing the value of the minimum and maximum breakpoints and media query (from default theme)', () => { + expect(bp.between('tablet', 'desktop')(CUSTOM_THEME_IS_EMPTY)).toEqual( + '@media (min-width: 48em) and (max-width: 74.99875em)' + ); + }); +}); + +describe('only', () => { + it('should returns a string containing the minimum and maximum values of the current breakpoint and media query', () => { + expect(bp.only('tablet')(CUSTOM_THEME)).toEqual( + '@media (min-width: 48em) and (max-width: 61.99875em)' + ); + }); + + it('should returns a string containing the minimum and maximum values of the current breakpoint and media query with portrait orientation', () => { + expect(bp.only('tablet', 'portrait')(CUSTOM_THEME)).toEqual( + '@media (min-width: 48em) and (max-width: 61.99875em) and (orientation: portrait)' + ); + }); + + it('should returns a string containing the minimum and maximum values of the current breakpoint and media query with landscape orientation', () => { + expect(bp.only('tablet', 'landscape')(CUSTOM_THEME)).toEqual( + '@media (min-width: 48em) and (max-width: 61.99875em) and (orientation: landscape)' + ); + }); + + it('should returns a string containing the minimum and maximum values of the current breakpoint and media query (from default theme)', () => { + expect(bp.only('tablet')(CUSTOM_THEME_IS_EMPTY)).toEqual( + '@media (min-width: 48em) and (max-width: 61.99875em)' + ); + }); +}); + +// describe('getBreakpointValue', () => { +// it('return breakpoint value if breakpoint name is valid', () => { +// expect(getBreakpointValue('tablet', DEFAULT_BREAKS_MAP)).toEqual('768px'); +// }); + +// it('show warn if the name of the breakpoint is invalid', () => { +// try { +// getBreakpointValue('!!!', DEFAULT_BREAKS_MAP); +// expect(true).toEqual(false); +// } catch (e) { +// expect(e.message).toEqual( +// `[styled-breakpoints]: '!!!' is invalid breakpoint name. Use 'tablet, desktop, lgDesktop'.` +// ); +// } +// }); +// }); + +// describe('getNextBreakpointName', () => { +// it('return next breakpoint name if breakpoint name is valid', () => { +// expect(getNextBreakpointName('tablet')(DEFAULT_BREAKS_MAP)).toEqual( +// 'desktop' +// ); +// }); + +// it('show warn if the name of the breakpoint is invalid', () => { +// try { +// getNextBreakpointName('!!!')(DEFAULT_BREAKS_MAP); +// expect(true).toEqual(false); +// } catch (e) { +// expect(e.message).toEqual( +// `[styled-breakpoints]: '!!!' is invalid breakpoint name. Use 'tablet, desktop, lgDesktop'.` +// ); +// } +// }); +// }); + +// describe('getNextBreakpointValue', () => { +// it('return next breakpoint value if breakpoint name is valid', () => { +// expect(getNextBreakpointValue('tablet', DEFAULT_BREAKS_MAP)).toEqual( +// '991.98px' +// ); +// }); + +// it('show warn if the name of the breakpoint is invalid', () => { +// try { +// getNextBreakpointValue('!!!', DEFAULT_BREAKS_MAP); +// expect(true).toEqual(false); +// } catch (e) { +// expect(e.message).toEqual( +// `[styled-breakpoints]: '!!!' is invalid breakpoint name. Use 'tablet, desktop'.` +// ); +// } +// }); + +// it('show warn if the breakpoint cannot be used ', () => { +// try { +// getNextBreakpointValue('lgDesktop', DEFAULT_BREAKS_MAP); +// expect(true).toEqual(false); +// } catch (e) { +// expect(e.message).toEqual( +// `[styled-breakpoints]: Don't use 'lgDesktop' because it doesn't have a maximum width. Use 'desktop'. See https://github.com/mg901/styled-breakpoints/issues/4 .` +// ); +// } +// }); +// }); + +// describe('withMinMedia', () => { +// it('should return string containing the breakpoint value with media query', () => { +// expect(hocs.withMinMedia('40em')).toEqual('@media (min-width: 40em)'); +// }); +// }); + +// describe('withMaxMedia', () => { +// it('should return string containing the breakpoint value with media query', () => { +// expect(hocs.withMaxMedia('40em')).toEqual('@media (max-width: 40em)'); +// }); +// }); + +// describe('withMinAndMaxMedia', () => { +// it('should return string containing the breakpoint value with media query', () => { +// expect(hocs.withMinAndMaxMedia('40em', '60em')).toEqual( +// '@media (min-width: 40em) and (max-width: 60em)' +// ); +// }); +// }); + +// describe('calcMinWidth', () => { +// it('calculate min with in pixels from default theme', () => { +// expect(c.calcMinWidth('desktop', DEFAULT_BREAKS)).toEqual('62em'); +// }); +// }); + +// describe('calcMaxWidth', () => { +// it('calculate max with in pixels from default theme', () => { +// expect(c.calcMaxWidth('tablet', DEFAULT_BREAKS)).toEqual('61.99875em'); +// }); +// }); + +// describe('errorReporter', () => { +// it('return object Error with error message', () => { +// expect(invariant).toThrow(); +// }); +// }); + +// describe('toEm', () => { +// it('convert values from pixels to ems', () => { +// expect(toEm('16px')).toEqual('1em'); +// }); +// }); + +// describe('makeErrorMessage', () => { +// it('return string with error message', () => { +// expect(makeErrorMessage('xs', DEFAULT_BREAKS_MAP)).toEqual( +// "'xs' is invalid breakpoint name. Use 'tablet, desktop, lgDesktop'." +// ); +// }); +// });