├── .babelrc ├── examples ├── .babelrc ├── dist │ └── index.html ├── index.js ├── webpack.config.babel.js ├── webpack.config.live.babel.js └── Example.js ├── index.js ├── .travis.yml ├── src ├── Breakpoint │ ├── index.js │ ├── BreakpointProvider.js │ ├── BreakpointProvider.test.js │ ├── breakpoint-util.test.js │ ├── Breakpoint.js │ ├── breakpoint-util.js │ └── Breakpoint.test.js └── index.js ├── .gitignore ├── config ├── jest │ ├── fileTransform.js │ ├── setup.js │ └── cssTransform.js └── polyfills.js ├── .npmignore ├── .eslintrc ├── jest.config.js ├── LICENSE ├── webpack.config.babel.js ├── @types └── index.d.ts ├── CHANGELOG.md ├── package.json └── README.md /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["@babel/preset-env", "@babel/preset-react"] 3 | } -------------------------------------------------------------------------------- /examples/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["@babel/preset-env", "@babel/preset-react"] 3 | } -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | require('./dist/manifest'); 2 | require('./dist/vendor'); 3 | module.exports = require('./dist/index').default; -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '10' 4 | script: 5 | - yarn test 6 | - yarn build 7 | branches: 8 | only: 9 | - master -------------------------------------------------------------------------------- /src/Breakpoint/index.js: -------------------------------------------------------------------------------- 1 | import Breakpoint from './Breakpoint'; 2 | import BreakpointProvider from './BreakpointProvider'; 3 | 4 | export { Breakpoint, BreakpointProvider }; 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist/*.js 3 | examples/dist/*.js 4 | coverage 5 | 6 | .idea 7 | 8 | # lock files 9 | package-lock.json 10 | 11 | # others 12 | .DS_Store 13 | yarn-error.log 14 | 15 | .vscode -------------------------------------------------------------------------------- /config/jest/fileTransform.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const path = require('path'); 3 | 4 | module.exports = { 5 | process(src, filename, config, options) { 6 | return 'module.exports = ' + JSON.stringify(path.basename(filename)) + ';'; 7 | } 8 | }; 9 | -------------------------------------------------------------------------------- /config/jest/setup.js: -------------------------------------------------------------------------------- 1 | import Enzyme from 'enzyme'; 2 | import Adapter from 'enzyme-adapter-react-16'; 3 | // React 16 Enzyme adapter 4 | Enzyme.configure({ adapter: new Adapter() }); 5 | // Make Enzyme functions available in all test files without importing 6 | -------------------------------------------------------------------------------- /examples/dist/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Example 5 | 6 | 7 |
8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /config/jest/cssTransform.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // This is a custom Jest transformer turning style imports into empty objects. 4 | // http://facebook.github.io/jest/docs/tutorial-webpack.html 5 | 6 | module.exports = { 7 | process() { 8 | return 'module.exports = {};'; 9 | }, 10 | getCacheKey() { 11 | // The output is always the same. 12 | return 'cssTransform'; 13 | }, 14 | }; 15 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import { Breakpoint, BreakpointProvider } from './Breakpoint'; 2 | import { setDefaultBreakpoints, setDefaultWidth } from './Breakpoint/breakpoint-util'; 3 | import { useCurrentWidth, useCurrentBreakpointName } from './Breakpoint/BreakpointProvider'; 4 | 5 | export default Breakpoint; 6 | export { Breakpoint, BreakpointProvider, setDefaultBreakpoints, setDefaultWidth, useCurrentWidth, useCurrentBreakpointName }; 7 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | *.log 2 | npm-debug.log* 3 | yarn-error.log 4 | 5 | # Coverage directory used by tools like istanbul 6 | coverage 7 | .nyc_output 8 | 9 | # Dependency directories 10 | node_modules 11 | 12 | # npm package lock 13 | package-lock.json 14 | yarn.lock 15 | 16 | # project files 17 | src 18 | test 19 | examples 20 | CHANGELOG.md 21 | .travis.yml 22 | .editorconfig 23 | .eslintignore 24 | .eslintrc 25 | .babelrc 26 | .gitignore 27 | 28 | 29 | # lock files 30 | package-lock.json 31 | yarn.lock 32 | 33 | # others 34 | .DS_Store 35 | .idea -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "env": { 4 | "browser": true, 5 | "node": true, 6 | "es6": true, 7 | "jest": true 8 | }, 9 | "parserOptions": { 10 | "ecmaVersion": 7, 11 | "sourceType": "module", 12 | "ecmaFeatures": { 13 | "jsx": true 14 | } 15 | }, 16 | "plugins": [ 17 | "react" 18 | ], 19 | "extends": ["eslint:recommended", "plugin:react/recommended"], 20 | "rules": { 21 | "no-multiple-empty-lines": ["error", { "max": 2 }], 22 | "indent": ["error", 2, { "SwitchCase": 1 }], 23 | "no-console": 1 24 | } 25 | } -------------------------------------------------------------------------------- /examples/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | 4 | import Example from './Example'; 5 | import { AppContainer } from 'react-hot-loader'; 6 | // AppContainer is a necessary wrapper component for HMR 7 | 8 | const render = (Component) => { 9 | ReactDOM.render( 10 | 11 | 12 | , 13 | document.getElementById('root') 14 | ); 15 | }; 16 | 17 | render(Example); 18 | 19 | // Hot Module Replacement API 20 | if (module.hot) { 21 | module.hot.accept('./Example', () => { 22 | render(Example) 23 | }); 24 | } 25 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | coverageReporters: [ 3 | 'json', 4 | 'lcov', 5 | 'text-summary' 6 | ], 7 | moduleFileExtensions: [ 8 | 'js', 9 | 'jsx', 10 | 'scss' 11 | ], 12 | modulePaths: [ 13 | './src' 14 | ], 15 | setupFiles: [ 16 | '/config/jest/setup.js' 17 | ], 18 | transform: { 19 | '^.+\\.(js|jsx)$': '/node_modules/babel-jest', 20 | '^.+\\.css$': '/config/jest/cssTransform.js', 21 | '^(?!.*\\.(js|jsx|css|json)$)': '/config/jest/fileTransform.js' 22 | }, 23 | transformIgnorePatterns: [ 24 | '[/\\\\]node_modules[/\\\\].+\\.(js|jsx)$' 25 | ] 26 | } -------------------------------------------------------------------------------- /config/polyfills.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | if (typeof Promise === 'undefined') { 4 | // Rejection tracking prevents a common issue where React gets into an 5 | // inconsistent state due to an error, but it gets swallowed by a Promise, 6 | // and the user has no idea what causes React's erratic future behavior. 7 | require('promise/lib/rejection-tracking').enable(); 8 | window.Promise = require('promise/lib/es6-extensions.js'); 9 | } 10 | 11 | // fetch() polyfill for making API calls. 12 | require('whatwg-fetch'); 13 | 14 | // Object.assign() is commonly used with React. 15 | // It will use the native implementation if it's present and isn't buggy. 16 | Object.assign = require('object-assign'); 17 | -------------------------------------------------------------------------------- /examples/webpack.config.babel.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import webpack from 'webpack'; //eslint-disable-line 3 | import CleanWebpackPlugin from 'clean-webpack-plugin'; 4 | 5 | export default () => ({ 6 | mode: 'production', 7 | entry: { 8 | index: path.join(__dirname, './index.js') 9 | }, 10 | 11 | output: { 12 | filename: 'bundle.js', 13 | path: path.resolve(__dirname, 'dist') 14 | }, 15 | 16 | module: { 17 | rules: [ 18 | { 19 | test: /.jsx?$/, 20 | exclude: /node_modules/, 21 | 22 | use: [ 23 | { 24 | loader: 'babel-loader', 25 | options: { 26 | presets: ['@babel/preset-env', '@babel/preset-react'] 27 | } 28 | } 29 | ] 30 | }, 31 | { 32 | test: /\.(scss)$/, 33 | loader: 'style-loader!css-loader!sass-loader' 34 | } 35 | ] 36 | }, 37 | 38 | resolve: { 39 | extensions: ['.js', '.jsx', '.scss'] 40 | }, 41 | 42 | plugins: [ 43 | // Clean dist folder 44 | new CleanWebpackPlugin(['./dist/build.js']) 45 | ] 46 | }); 47 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018 Dineshkumar Pandiyan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /webpack.config.babel.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import CleanWebpackPlugin from 'clean-webpack-plugin'; 3 | const packageJson = require('./package.json'); 4 | 5 | export default () => ({ 6 | mode: 'production', 7 | entry: { 8 | index: path.join(__dirname, 'src/index.js') 9 | }, 10 | 11 | output: { 12 | path: path.join(__dirname, 'dist'), 13 | filename: '[name].js', 14 | library: packageJson.name, 15 | libraryTarget: 'umd', 16 | globalObject: 'this' 17 | }, 18 | 19 | module: { 20 | rules: [ 21 | { 22 | test: /.jsx?$/, 23 | exclude: /node_modules/, 24 | include: path.join(__dirname, 'src'), 25 | use: [ 26 | { 27 | loader: 'babel-loader', 28 | options: { 29 | presets: ['@babel/preset-env', '@babel/preset-react'] 30 | } 31 | } 32 | ] 33 | } 34 | ] 35 | }, 36 | 37 | resolve: { 38 | extensions: ['.js', '.jsx'] 39 | }, 40 | 41 | externals: { 42 | react: 'react', 43 | reactDOM: 'react-dom' 44 | }, 45 | 46 | plugins: [new CleanWebpackPlugin(['dist/*.*'])], 47 | optimization: { 48 | splitChunks: { 49 | name: 'vendor', 50 | minChunks: 2 51 | } 52 | } 53 | }); 54 | -------------------------------------------------------------------------------- /@types/index.d.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | export = ReactSocks; 4 | export as namespace ReactSocks; 5 | 6 | declare namespace ReactSocks { 7 | interface Props { 8 | [key: string]: React.ReactNode | string | boolean; 9 | children?: React.ReactNode; 10 | up?: boolean; 11 | down?: boolean; 12 | only?: boolean; 13 | tagName?: string; 14 | className?: string; 15 | customQuery?: string; 16 | style?: React.CSSProperties 17 | } 18 | 19 | interface BreakpointProviderProps { 20 | children: React.ReactNode; 21 | } 22 | 23 | interface BreakpointAndModifierProps { 24 | breakpoint?: string; 25 | modifier?: string; 26 | tagName: string; 27 | className: string; 28 | } 29 | 30 | export interface BreakpointContextProps { 31 | currentWidth: number; 32 | currentBreakpointName: string; 33 | } 34 | 35 | export interface BreakpointType { 36 | [breakpoint: string]: number; 37 | } 38 | 39 | export function setDefaultBreakpoints(breakpoints: ReactSocks.BreakpointType[]): void; 40 | export function setDefaultWidth(width: number): number; 41 | export function useCurrentWidth(): number; 42 | export function useCurrentBreakpointName(): string; 43 | 44 | export class Breakpoint extends React.Component { 45 | static extractBreakpointAndModifierFromProps(allProps: ReactSocks.Props): ReactSocks.BreakpointAndModifierProps; 46 | } 47 | 48 | export class BreakpointProvider extends React.Component { 49 | static handleResize(): void; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## To Be Released 4 | 5 | ### 2.2.0 6 | 7 | * Adds a function for setting the default width 8 | 9 | ### 2.1.0 (19 Sep 2019) 10 | 11 | * NEW: Support for custom breakpoints 12 | 13 | ### 2.0.2 (6 Aug 2019) 14 | 15 | * FIX: TypeScript Breakpoint props type issue 16 | 17 | ### 2.0.1 (5 Aug 2019) 18 | 19 | * FIX: TypeScript support for dynamic breakpoints and breakpoint hooks 20 | 21 | ### 2.0.0 (30 Jul 2019) 22 | 23 | * NEW: Add support for React hooks 24 | * NEW: Add types for TypeScript 25 | * IMPROVE: Prevent memory leaks during debounced resize 26 | 27 | ## Released 28 | 29 | ### 1.0.1 (19 Jan 2019) 30 | 31 | * IMPROVE: Debounce resize event as a performance enhancement 32 | * IMPROVE: Add support for custom `tagName` & `className` 33 | * FIX: Can be used with multiple contexts without having to wrap the component with BreakpointProvider in test cases 34 | 35 | ### 1.0.0 (9 Dec 2018) 36 | 37 | * First stable release of **React Socks**. 38 | * Following changes are included in the first stable release based on feedbacks from alpha release. 39 | * `BreakpointProvider` - context wrapper to prevent width calculation in each `Breakpoint` component. 40 | * Support SSR. 41 | * Performance improvements. 42 | 43 | ### 1.0.0-alpha (20 Nov 2018) 44 | 45 | * An alpha version of **React Socks** is released to the world =) 46 | * Following APIs are included in the first alpha 47 | * `setDefaultBreakpoints` to define custom breakpoints 48 | * `Breakpoint` component with `breakpoint` and `modifier` props. 49 | * Three modifiers available 50 | * **only** - will render the component only in the specified breakpoint. 51 | * **up** - will render the component in the specified breakpoint and all the breakpoints above it (greater than the width). 52 | * **down** - will render the component in the specified breakpoint and all the breakpoints below it (less than the width). 53 | -------------------------------------------------------------------------------- /examples/webpack.config.live.babel.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import webpack from 'webpack'; 3 | 4 | export default () => ({ 5 | mode: 'development', 6 | entry: [ 7 | 'react-hot-loader/patch', 8 | // activate HMR for React 9 | 10 | 'webpack-dev-server/client?http://localhost:8080', 11 | // bundle the client for webpack-dev-server 12 | // and connect to the provided endpoint 13 | 14 | 'webpack/hot/only-dev-server', 15 | // bundle the client for hot reloading 16 | // only- means to only hot reload for successful updates 17 | 18 | './examples/index.js' 19 | // the entry point of our app 20 | ], 21 | 22 | output: { 23 | filename: 'bundle.js', 24 | path: path.resolve(__dirname, 'dist'), 25 | publicPath: '/' 26 | // necessary for HMR to know where to load the hot update chunks 27 | }, 28 | 29 | devtool: 'inline-source-map', 30 | 31 | devServer: { 32 | hot: true, 33 | // enable HMR on the server 34 | 35 | contentBase: path.resolve(__dirname, 'dist'), 36 | // match the output path 37 | 38 | publicPath: '/', 39 | // match the output `publicPath` 40 | 41 | stats: 'minimal' 42 | }, 43 | 44 | module: { 45 | rules: [ 46 | { 47 | test: /.jsx?$/, 48 | exclude: /node_modules/, 49 | use: [ 50 | { 51 | loader: 'babel-loader', 52 | options: { 53 | presets: ['@babel/preset-env', '@babel/preset-react'] 54 | } 55 | } 56 | ] 57 | }, 58 | { 59 | test: /\.(scss)$/, 60 | loader: 'style-loader!css-loader!sass-loader' 61 | } 62 | ] 63 | }, 64 | 65 | resolve: { 66 | extensions: ['.js', '.jsx', '.scss'] 67 | }, 68 | 69 | plugins: [new webpack.HotModuleReplacementPlugin()], 70 | optimization: { 71 | namedModules: true 72 | } 73 | }); 74 | -------------------------------------------------------------------------------- /src/Breakpoint/BreakpointProvider.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | import BreakpointUtil from './breakpoint-util'; 5 | import debounce from 'lodash.debounce'; 6 | 7 | const BreakpointContext = React.createContext({ 8 | currentWidth: 9999, 9 | currentBreakpointName: '' 10 | }); 11 | 12 | export default class BreakpointProvider extends React.Component { 13 | constructor(props) { 14 | super(props); 15 | const currentWidth = BreakpointUtil.currentWidth; 16 | 17 | this.state = { 18 | currentWidth: currentWidth, 19 | currentBreakpointName: BreakpointUtil.getBreakpointName(currentWidth) 20 | }; 21 | 22 | this.handleResize = debounce(this.handleResize.bind(this), 100); 23 | } 24 | 25 | componentDidMount() { 26 | window.addEventListener('resize', this.handleResize); 27 | } 28 | 29 | componentWillUnmount() { 30 | window.removeEventListener('resize', this.handleResize); 31 | this.handleResize.cancel(); 32 | } 33 | 34 | handleResize() { 35 | const currentWidth = BreakpointUtil.currentWidth; 36 | 37 | this.setState({ 38 | currentWidth: currentWidth, 39 | currentBreakpointName: BreakpointUtil.getBreakpointName(currentWidth) 40 | }); 41 | } 42 | 43 | render() { 44 | const { children } = this.props; 45 | const { currentWidth, currentBreakpointName } = this.state; 46 | 47 | return ( 48 | 54 | { children } 55 | 56 | ); 57 | } 58 | } 59 | 60 | export const useCurrentWidth = () => { 61 | return React.useContext(BreakpointContext).currentWidth 62 | } 63 | 64 | export const useCurrentBreakpointName = () => { 65 | return React.useContext(BreakpointContext).currentBreakpointName 66 | } 67 | 68 | BreakpointProvider.propTypes = { 69 | children: PropTypes.node, 70 | }; 71 | 72 | export { 73 | BreakpointContext, 74 | }; 75 | -------------------------------------------------------------------------------- /src/Breakpoint/BreakpointProvider.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { shallow } from 'enzyme'; 3 | import { BreakpointProvider } from 'index'; 4 | import { BreakpointUtil } from './breakpoint-util'; 5 | import debounce from 'lodash.debounce'; // eslint-disable-line 6 | import sinon from 'sinon'; 7 | 8 | jest.mock('lodash.debounce', () => jest.fn(fn => fn)); 9 | 10 | describe('Breakpoint Context Provider', () => { 11 | it('render without crashing', () => { 12 | const wrapper = shallow(Hello World); 13 | expect(wrapper).toHaveLength(1); 14 | expect(wrapper.text()).toEqual('Hello World'); 15 | }); 16 | }); 17 | 18 | describe('Breakpoint - large', () => { 19 | let currWidthStub; 20 | let currBPointNameStub; 21 | 22 | beforeEach(() => { 23 | currWidthStub = sinon.stub(BreakpointUtil.prototype, 'currentWidth').get(function getterFn() { 24 | return 1024; 25 | }); 26 | 27 | currBPointNameStub = sinon.stub(BreakpointUtil.prototype, 'getBreakpointName').returns('large'); 28 | }); 29 | 30 | it('should pass the correct value', () => { 31 | const wrapper = shallow(Hello World); 32 | expect(wrapper).toHaveLength(1); 33 | expect(wrapper.prop('value')).toEqual({ 34 | currentWidth: 1024, 35 | currentBreakpointName: 'large', 36 | }); 37 | }); 38 | 39 | it('should set correct state', () => { 40 | let wrapper = shallow( 41 | 42 | 43 | ); 44 | expect(wrapper.state()).toEqual({ 45 | currentWidth: 1024, 46 | currentBreakpointName: 'large', 47 | }); 48 | 49 | currBPointNameStub.restore(); 50 | currBPointNameStub = sinon.stub(BreakpointUtil.prototype, 'getBreakpointName').returns('small'); 51 | 52 | global.dispatchEvent(new Event('resize')); 53 | 54 | expect(wrapper.state()).toEqual({ 55 | currentWidth: 1024, 56 | currentBreakpointName: 'small', 57 | }); 58 | }); 59 | 60 | afterEach(() => { 61 | currWidthStub.restore(); 62 | currBPointNameStub.restore(); 63 | }); 64 | }); 65 | -------------------------------------------------------------------------------- /src/Breakpoint/breakpoint-util.test.js: -------------------------------------------------------------------------------- 1 | import BU, { setDefaultBreakpoints } from './breakpoint-util'; 2 | 3 | describe('Breakpoint Util', () => { 4 | it('set default breakpoints', () => { 5 | setDefaultBreakpoints([ 6 | { xs: 0 }, 7 | { s: 376 }, 8 | { m: 426 }, 9 | { l: 769 }, 10 | { xl: 1025 } 11 | ]); 12 | expect(BU.allBreakpoints).toHaveLength(5); 13 | }); 14 | 15 | it('throw error while setting breakpoints - not an array', () => { 16 | expect(() => setDefaultBreakpoints(null)).toThrowError(/^setDefaultBreakpoints error: Breakpoints should be an array$/); 17 | expect(() => setDefaultBreakpoints("hello")).toThrowError(/^setDefaultBreakpoints error: Breakpoints should be an array$/); 18 | expect(() => setDefaultBreakpoints({})).toThrowError(/^setDefaultBreakpoints error: Breakpoints should be an array$/); 19 | expect(() => setDefaultBreakpoints(undefined)).toThrowError(/^setDefaultBreakpoints error: Breakpoints should be an array$/); 20 | expect(() => setDefaultBreakpoints(1.0)).toThrowError(/^setDefaultBreakpoints error: Breakpoints should be an array$/); 21 | expect(() => setDefaultBreakpoints(true)).toThrowError(/^setDefaultBreakpoints error: Breakpoints should be an array$/); 22 | expect(() => setDefaultBreakpoints(false)).toThrowError(/^setDefaultBreakpoints error: Breakpoints should be an array$/); 23 | }); 24 | 25 | it('throw error while setting breakpoints - not an array of objects', () => { 26 | expect(() => setDefaultBreakpoints([null])).toThrowError(/^setDefaultBreakpoints error: Breakpoints should be an array of objects$/); 27 | expect(() => setDefaultBreakpoints(["hello"])).toThrowError(/^setDefaultBreakpoints error: Breakpoints should be an array of objects$/); 28 | expect(() => setDefaultBreakpoints([undefined])).toThrowError(/^setDefaultBreakpoints error: Breakpoints should be an array of objects$/); 29 | expect(() => setDefaultBreakpoints([1.0])).toThrowError(/^setDefaultBreakpoints error: Breakpoints should be an array of objects$/); 30 | expect(() => setDefaultBreakpoints([true, false])).toThrowError(/^setDefaultBreakpoints error: Breakpoints should be an array of objects$/); 31 | }); 32 | 33 | it('throw error while setting breakpoints - wrong object format', () => { 34 | expect(() => setDefaultBreakpoints([{}])).toThrowError(/^setDefaultBreakpoints error: Each breakpoint object should have only one key$/); 35 | expect(() => setDefaultBreakpoints([{ a: 1010, b: 1010 }])).toThrowError(/^setDefaultBreakpoints error: Each breakpoint object should have only one key$/); 36 | }); 37 | }); 38 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-socks", 3 | "version": "2.2.0", 4 | "description": "React library to render components only on specific viewports", 5 | "types": "@types/index.d.ts", 6 | "main": "./dist/index.js", 7 | "scripts": { 8 | "build": "webpack --config webpack.config.babel.js", 9 | "build-examples": "webpack --config examples/webpack.config.babel.js --progress", 10 | "clean": "rm -rf dist coverage", 11 | "coverage": "jest --coverage", 12 | "lint": "eslint ./src", 13 | "prepublish": "npm run clean && npm run test && npm run build", 14 | "start": "webpack-dev-server --config examples/webpack.config.live.babel.js", 15 | "test": "npm run lint && npm run coverage" 16 | }, 17 | "dependencies": {}, 18 | "devDependencies": { 19 | "@babel/core": "^7.6.0", 20 | "@babel/preset-env": "^7.6.0", 21 | "@babel/preset-react": "^7.0.0", 22 | "@babel/register": "^7.6.0", 23 | "babel-eslint": "^10.0.3", 24 | "babel-jest": "^24.9.0", 25 | "babel-loader": "^8.0.6", 26 | "browser-or-node": "^1.2.1", 27 | "chai": "^4.2.0", 28 | "clean-webpack-plugin": "^0.1.16", 29 | "css-loader": "^2.1.1", 30 | "enzyme": "^3.10.0", 31 | "enzyme-adapter-react-16": "^1.14.0", 32 | "eslint": "^6.4.0", 33 | "eslint-plugin-react": "^7.14.3", 34 | "jest-cli": "^24.9.0", 35 | "lodash.debounce": "^4.0.8", 36 | "node-sass": "^4.12.0", 37 | "prop-types": "^15.7.2", 38 | "react": "^16.6.0", 39 | "react-addons-test-utils": "^15.6.2", 40 | "react-dom": "^16.6.0", 41 | "react-hot-loader": "next", 42 | "react-test-renderer": "^16.9.0", 43 | "regenerator-runtime": "^0.13.3", 44 | "sass-loader": "^8.0.0", 45 | "sinon": "^7.4.2", 46 | "style-loader": "^1.0.0", 47 | "webpack": "^4.40.2", 48 | "webpack-cli": "^3.3.9", 49 | "webpack-dev-server": "^3.8.1" 50 | }, 51 | "peerDependencies": { 52 | "react": ">=16.9.0", 53 | "react-dom": ">=16.9.0" 54 | }, 55 | "keywords": [ 56 | "react", 57 | "render", 58 | "breakpoint", 59 | "responsive", 60 | "media", 61 | "query", 62 | "media-queries", 63 | "react package", 64 | "component", 65 | "library", 66 | "socks" 67 | ], 68 | "author": "Dinesh Pandiyan ", 69 | "license": "MIT", 70 | "bugs": { 71 | "url": "https://github.com/flexdinesh/react-socks/issues" 72 | }, 73 | "homepage": "https://github.com/flexdinesh/react-socks#readme", 74 | "repository": { 75 | "type": "git", 76 | "url": "git+https://github.com/flexdinesh/react-socks.git" 77 | }, 78 | "resolutions": { 79 | "babel-core": "7.0.0-bridge.0" 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /examples/Example.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import Breakpoint, { BreakpointProvider } from '../src/index'; 4 | // import { setDefaultBreakpoints } from '../src/index'; 5 | // setDefaultBreakpoints([ 6 | // { xs: 0 }, 7 | // { s: 376 }, 8 | // { m: 426 }, 9 | // { l: 769 }, 10 | // { xl: 1025 } 11 | // ]); 12 | 13 | class Example extends React.Component { 14 | constructor(props) { 15 | super(props); 16 | } 17 | 18 | componentDidMount() {} 19 | 20 | render() { 21 | return ( 22 | 23 |
24 | 25 |
26 | Hello World! small down 27 |
28 |
29 | 30 |
31 | Hello World! medium down 32 |
33 |
34 | 35 |
36 | Hello World! medium only 37 |
38 |
39 | 40 | 41 |
42 | Hello World! medium up 43 |
44 |
45 | 46 |
47 | Hello World! large down 48 |
49 |
50 | 51 |
52 | Hello World! large up 53 |
54 |
55 | 56 |
57 | Hello World! xlarge only 58 |
59 |
60 | 61 |
62 | Custom breakpoint: (min-width : 500px) 63 |
64 |
65 | 66 |
67 | Custom breakpoint: (max-width : 1000px) 68 |
69 |
70 | 71 |
72 | Custom breakpoint: (min-width : 500px) && (max-width : 700px) 73 |
74 |
75 |
76 |
77 | ); 78 | } 79 | } 80 | 81 | export default Example; 82 | -------------------------------------------------------------------------------- /src/Breakpoint/Breakpoint.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | import BreakpointUtil from './breakpoint-util'; 5 | import { BreakpointContext } from './BreakpointProvider'; 6 | 7 | export default class Breakpoint extends React.Component { 8 | constructor(props) { 9 | super(props); 10 | 11 | this.extractBreakpointAndModifierFromProps = this.extractBreakpointAndModifierFromProps.bind( 12 | this 13 | ); 14 | } 15 | 16 | extractBreakpointAndModifierFromProps(allProps) { 17 | let breakpoint; 18 | let modifier; 19 | let tagName = allProps.tagName || 'div'; 20 | let className = allProps.className || ''; 21 | let style = allProps.style; 22 | let usesCustomQuery = false; 23 | 24 | Object.keys(allProps).forEach((prop) => { 25 | if (prop === 'up' || prop === 'down' || prop === 'only') { 26 | modifier = prop; 27 | } else if (prop === 'customQuery') { 28 | usesCustomQuery = true; 29 | } else if (prop !== 'tagName' && prop !== 'className' && prop !== 'style') { 30 | breakpoint = prop; 31 | } 32 | }); 33 | 34 | if (modifier === 'up' || modifier === 'down' || modifier === 'only') { 35 | usesCustomQuery = false; 36 | } 37 | 38 | if (!modifier && !usesCustomQuery) modifier = 'only'; 39 | 40 | return { 41 | breakpoint, 42 | modifier, 43 | tagName, 44 | className, 45 | style, 46 | customQuery: usesCustomQuery ? allProps.customQuery : null 47 | }; 48 | } 49 | 50 | render() { 51 | const { children, ...rest } = this.props; 52 | const { 53 | breakpoint, 54 | modifier, 55 | className, 56 | tagName, 57 | style, 58 | customQuery 59 | } = this.extractBreakpointAndModifierFromProps(rest); 60 | 61 | const { currentBreakpointName, currentWidth } = this.context; 62 | 63 | const shouldRender = BreakpointUtil.shouldRender({ 64 | breakpointName: breakpoint, 65 | modifier, 66 | currentBreakpointName, 67 | currentWidth, 68 | customQuery 69 | }); 70 | 71 | if (!shouldRender) return null; 72 | 73 | const Tag = tagName 74 | return ( 75 | {children} 76 | ); 77 | } 78 | } 79 | 80 | Breakpoint.contextType = BreakpointContext; 81 | 82 | Breakpoint.propTypes = { 83 | children: PropTypes.node, 84 | up: PropTypes.bool, 85 | down: PropTypes.bool, 86 | only: PropTypes.bool, 87 | tagName: PropTypes.string, 88 | className: PropTypes.string, 89 | style: PropTypes.objectOf(PropTypes.oneOfType([ 90 | PropTypes.string, 91 | PropTypes.number, 92 | ])), 93 | customQuery: PropTypes.string 94 | }; 95 | -------------------------------------------------------------------------------- /src/Breakpoint/breakpoint-util.js: -------------------------------------------------------------------------------- 1 | import { isBrowser } from 'browser-or-node'; 2 | 3 | let DEFAULT_WIDTH = 9999; 4 | 5 | export class BreakpointUtil { 6 | constructor() { 7 | const defaultBreakpoints = [ 8 | { xsmall: 0 }, // all mobile devices 9 | { small: 576 }, // mobile devices (not sure which one's this big) 10 | { medium: 768 }, // ipad, ipad pro, ipad mini, etc 11 | { large: 992 }, // smaller laptops 12 | { xlarge: 1200 } // laptops and desktops 13 | ]; 14 | this.allBreakpoints = defaultBreakpoints; 15 | } 16 | 17 | get currentWidth() { 18 | return this.getWidthSafely(); 19 | } 20 | 21 | getBreakpointName(width) { 22 | let bpName; 23 | 24 | this.allBreakpoints.forEach((obj) => { 25 | let currentKey = Object.keys(obj)[0]; 26 | if (obj[currentKey] <= width) bpName = currentKey; 27 | }); 28 | 29 | return bpName; 30 | } 31 | 32 | getBreakpointWidth(breakpointName) { 33 | let breakpointWidth = 0; 34 | this.allBreakpoints.forEach((obj) => { 35 | let currentKey = Object.keys(obj)[0]; 36 | if (currentKey === breakpointName) breakpointWidth = obj[currentKey]; 37 | }); 38 | 39 | return breakpointWidth; 40 | } 41 | 42 | getNextBreakpointWidth(breakpointName) { 43 | let nextBreakpointName; 44 | let nextBreakpointWidth = 9999; 45 | let currentBreakpointIndex = 0; 46 | 47 | for (let i = 0; i < this.allBreakpoints.length; i++) { 48 | let obj = this.allBreakpoints[i]; 49 | let currentKey = Object.keys(obj)[0]; 50 | if (currentKey === breakpointName) { 51 | currentBreakpointIndex = i; 52 | } 53 | 54 | if (currentBreakpointIndex > 0) { 55 | let nextBreakpointIndex = currentBreakpointIndex + 1; 56 | if (this.allBreakpoints.length > nextBreakpointIndex) { 57 | let nextBreakpointObj = this.allBreakpoints[nextBreakpointIndex]; 58 | nextBreakpointName = Object.keys(nextBreakpointObj)[0]; 59 | nextBreakpointWidth = nextBreakpointObj[nextBreakpointName]; 60 | } 61 | 62 | break; 63 | } 64 | } 65 | 66 | return nextBreakpointWidth; 67 | } 68 | 69 | shouldRender({ breakpointName, modifier, currentBreakpointName, currentWidth, customQuery }) { 70 | if (modifier === 'only') { 71 | if (breakpointName === currentBreakpointName) return true; 72 | } else if (modifier === 'up') { 73 | const breakpointWidth = this.getBreakpointWidth(breakpointName); 74 | if (currentWidth >= breakpointWidth) return true; 75 | } else if (modifier === 'down') { 76 | const nextBreakpointWidth = this.getNextBreakpointWidth(breakpointName); 77 | if (currentWidth < nextBreakpointWidth) return true; 78 | } else if (customQuery) { 79 | return isBrowser && window.matchMedia(customQuery).matches; 80 | } 81 | return false; 82 | } 83 | 84 | set breakpoints(bps) { 85 | this.allBreakpoints = bps; 86 | } 87 | 88 | getWidthSafely() { 89 | return isBrowser && window ? Math.max(document.documentElement.clientWidth, window.innerWidth || 0) : DEFAULT_WIDTH; 90 | } 91 | } 92 | 93 | const B = new BreakpointUtil(); 94 | export default B; 95 | 96 | export const setDefaultBreakpoints = (customBreakpoints) => { 97 | if (!customBreakpoints || typeof customBreakpoints !== 'object' || !(customBreakpoints instanceof Array)) { 98 | throw new Error('setDefaultBreakpoints error: Breakpoints should be an array'); 99 | } 100 | 101 | customBreakpoints.forEach((obj) => { 102 | if (!obj || typeof obj !== 'object') { 103 | throw new Error('setDefaultBreakpoints error: Breakpoints should be an array of objects'); 104 | } 105 | if (Object.keys(obj).length !== 1) { 106 | throw new Error('setDefaultBreakpoints error: Each breakpoint object should have only one key'); 107 | } 108 | }); 109 | 110 | B.breakpoints = customBreakpoints; 111 | }; 112 | 113 | export const setDefaultWidth = (defaultWidth) => { 114 | return DEFAULT_WIDTH = defaultWidth; 115 | }; 116 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # React Socks 2 | 3 | [![Build Status](https://travis-ci.org/flexdinesh/react-socks.svg?branch=master)](https://travis-ci.org/flexdinesh/react-socks) 4 | [![npm version](https://badge.fury.io/js/react-socks.svg)](https://www.npmjs.com/package/react-socks) 5 | [![dependencies Status](https://david-dm.org/flexdinesh/react-socks/status.svg)](https://david-dm.org/flexdinesh/react-socks) 6 | [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT) 7 | 8 | Wrap your components with **React Socks** to prevent unnecessary render in different viewports. 9 | 10 | ```jsx 11 | 12 | 13 | This component will render only in mobile devices 14 | 15 | 16 | ``` 17 | 18 | ## Why? [![start with why](https://img.shields.io/badge/start%20with-why%3F-brightgreen.svg?style=flat)](http://www.ted.com/talks/simon_sinek_how_great_leaders_inspire_action) 19 | 20 | Conventionally we have been writing _css media queries_ for different viewports to hide and show elements that are always present in the DOM. With React taking over the world, everything is about rendering components into the DOM. **React Socks** helps you conditionally render elements based on viewports. 21 | 22 | 1. Render viewport specific components without hassle 23 | 24 | 2. You can define your own breakpoints (Eg. xs, ipad, bigmonitors) and use them 25 | 26 | 3. You can improve your app performance if you lazy load your viewport specific components 27 | 28 | 4. Simpler and **sweeter** syntax for ease of use 29 | 30 | ## Install 31 | 32 | ```sh 33 | $ npm install --save react-socks 34 | ``` 35 | 36 | ## Usage 37 | 38 | Just wrap your top level component with `BreakpointProvider` and use the `Breakpoint` component anywhere you need. 39 | 40 | _Note: `BreakpointProvider` was introduced only in `v1.0.0`. It's not available in previous alpha versions._ 41 | 42 | ```jsx 43 | import { Breakpoint, BreakpointProvider } from 'react-socks'; 44 | 45 | // entry file (usually App.js or index.js) 46 | const App = () => ( 47 | 48 | 49 | 50 | ); 51 | ``` 52 | 53 | ```jsx 54 | // Example.js 55 | const Example = () => { 56 | return ( 57 |
58 | 59 |
I will render only in mobile devices
60 |
61 | 62 | 63 |
I will render only in tablets (iPad, etc...)
64 |
65 | 66 | 67 |
I will render in tablets (iPad, etc...) and everything below (mobile devices)
68 |
69 | 70 | 71 |
I will render in tablets (iPad, etc...) and everything above (laptops, desktops)
72 |
73 | 74 | 75 |
I will render in laptops, desktops and everything above
76 |
77 | 78 | {/* Add breakpoints on the fly using custom queries */} 79 | 80 | 81 |
82 | Custom breakpoint: (min-width : 500px) 83 |
84 |
85 | 86 | 87 |
88 | Custom breakpoint: (max-width : 1000px) 89 |
90 |
91 | 92 | 93 |
94 | Custom breakpoint: (min-width : 500px) && (max-width : 700px) 95 |
96 |
97 |
98 | ); 99 | }; 100 | ``` 101 | 102 | ## API 103 | 104 | - [setDefaultBreakpoints](#set-default-breakpoints) 105 | - [setDefaultWidth](#set-default-width) 106 | - [Breakpoint](#breakpoint) 107 | - [useCurrentWidth](#use-current-width--breakpoint-name) / [useCurrentBreakpointName](#use-current-width--breakpoint-name) 108 | 109 | ### Set Default Breakpoints 110 | 111 | You can define your own breakpoints. 112 | 113 | - Pass an array of objects with the **breakpoint name** and **width** in _px_ to `setDefaultBreakpoints` **once** in your `App.js` or your React entry file. 114 | 115 | **Note: You only need to set default breakpoints once in your app** 116 | 117 | ```jsx 118 | import { setDefaultBreakpoints } from 'react-socks'; 119 | 120 | setDefaultBreakpoints([ 121 | { xs: 0 }, 122 | { s: 376 }, 123 | { m: 426 }, 124 | { l: 769 }, 125 | { xl: 1025 } 126 | ]); 127 | 128 | 129 | I will render only in m devices 130 | 131 | 132 | ``` 133 | 134 | - You can use any breakpoint name (Eg. cats, puppies, dinosaurs, etc) and width. 135 | 136 | ```jsx 137 | setDefaultBreakpoints([ 138 | { cats: 0 }, 139 | { dinosaurs: 900 } 140 | ]); 141 | 142 | 143 | Only cats can render me 144 | 145 | ``` 146 | 147 | - If you don't set a default breakpoint, the library will fallback to **Bootstrap 4 default breakpoints** as described below. 148 | 149 | ```jsx 150 | setDefaultBreakpoints([ 151 | { xsmall: 0 }, // all mobile devices 152 | { small: 576 }, // mobile devices (not sure which one's this big) 153 | { medium: 768 }, // ipad, ipad pro, ipad mini, etc 154 | { large: 992 }, // smaller laptops 155 | { xlarge: 1200 } // laptops and desktops 156 | ]); 157 | ``` 158 | 159 | ### Set Default Width 160 | 161 | You can define your own default width. This will help when you want to render a particular default width from the server. Usually in the server, there are no breakpoints and the lib defaults to 0 and renders mobile views. Use this API to change that. 162 | 163 | - Pass **width** in _px_ to `setDefaultWidth` **once** in your `App.js` or your React entry file. 164 | 165 | **Note: You only need to set default width once in your app** 166 | 167 | ```jsx 168 | import { setDefaultWidth } from 'react-socks'; 169 | 170 | setDefaultWidth(992); // render desktop components in the server 171 | 172 | ``` 173 | 174 | ### Breakpoint 175 | 176 | Import the `Breakpoint` component anywhere in the your code and start using it with your **breakpoint** and **modifier** props. 177 | 178 | ```jsx 179 | // small is breakpoint 180 | // down is modifier 181 | 182 | 183 | This component will render only in mobile devices 184 | 185 | 186 | ``` 187 | 188 | You have **three** modifiers 189 | 190 | - **only** - will render the component **only** in the specified breakpoint. 191 | 192 | - **up** - will render the component **in** the specified breakpoint and all the breakpoints **above** it (greater than the width). 193 | 194 | - **down** - will render the component **in** the specified breakpoint and all the breakpoints **below** it (less than the width). 195 | 196 | 197 | 198 | ### Custom Breakpoints 🆕 199 | 200 | Now, you can add a breakpoint of any width by using this prop: `customQuery`. 201 | Simply write your media query as a _string_ and pass it to `customQuery` 202 | 203 | ```jsx 204 | 205 |
206 | Custom breakpoint: (min-width : 500px) 207 |
208 |
209 | 210 | 211 |
212 | Custom breakpoint: (max-width : 1000px) 213 |
214 |
215 | 216 | 217 |
218 | Custom breakpoint: (min-width : 500px) && (max-width : 700px) 219 |
220 |
221 | ``` 222 | 223 | **Note: `customQuery` will be ignored if you have mentioned any modifiers like `up`, `down` & `only`** 224 | 225 | Use `customQuery` only if you want to make use of arbitary breakpoints. 226 | 227 | 228 | 229 | ### Use Current Width / Breakpoint Name 230 | 231 | If you call `useCurrentWidth` in the render function, you can access the current width directly: 232 | 233 | ```jsx 234 | import { useCurrentWidth } from 'react-socks' 235 | 236 | const CustomComponent = () => { 237 | const width = useCurrentWidth() 238 | if (width < 500) { 239 | return

Hello!

240 | } else { 241 | return

Hello!

242 | } 243 | } 244 | ``` 245 | 246 | You can also use the current breakpoint name with `useCurrentBreakpointName`: 247 | 248 | ```jsx 249 | import { useCurrentBreakpointName } from 'react-socks' 250 | 251 | const CustomComponent = () => { 252 | const breakpoint = useCurrentBreakpointName() 253 | if (breakpoint == 'small') { 254 | return

Hello!

255 | } else { 256 | return

Hello!

257 | } 258 | } 259 | ``` 260 | 261 | ## Contributors 262 | 263 | Thanks goes to these amazing people 🎉 264 | 265 | | [
Dinesh Pandiyan](https://github.com/flexdinesh)
| [
Capelo](https://github.com/antoniocapelo)
| [
Adarsh](https://github.com/sadarshannaiynar)
| [
Patryk](https://github.com/PatrykRudzinski)
| [
WRNGFRNK](https://github.com/wrngfrnk)
| [
Farhad Yasir](https://github.com/nutboltu)
266 | | :---: | :---: | :---: | :---: | :---: | :---: | 267 | | [
Entkenntnis](https://github.com/Entkenntnis)
| [
Douglas Moore](https://github.com/dbryantm)
| [
Abdul rehman](https://github.com/rehman-00001)
| [
Nawaz Khan](https://github.com/nawazkhan)
| [
hems.io](https://github.com/hems)
268 | 269 | ## License 270 | 271 | MIT © Dinesh Pandiyan 272 | -------------------------------------------------------------------------------- /src/Breakpoint/Breakpoint.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { shallow, mount } from 'enzyme'; 3 | import Breakpoint, { BreakpointProvider } from 'index'; 4 | import { BreakpointUtil } from './breakpoint-util'; 5 | import sinon from 'sinon'; 6 | 7 | describe('Breakpoint', () => { 8 | it('render without crashing', () => { 9 | const wrapper = shallow(Hello World); 10 | expect(wrapper).toHaveLength(1); 11 | }); 12 | }); 13 | 14 | describe('Breakpoint - small', () => { 15 | let widthStub; // eslint-disable-line 16 | 17 | beforeEach(() => { 18 | widthStub = sinon 19 | .stub(BreakpointUtil.prototype, 'getWidthSafely') 20 | .returns(577); 21 | }); 22 | 23 | it('should render small only', () => { 24 | let wrapper = mount( 25 | 26 | 27 | render only between 576 and 768 28 | 29 | 30 | ); 31 | expect(wrapper.children().children()).toHaveLength(1); 32 | 33 | wrapper = mount( 34 | 35 | 36 | render only between 576 and 768 37 | 38 | 39 | ); 40 | expect(wrapper.children().children()).toHaveLength(1); 41 | }); 42 | 43 | it('should render small down', () => { 44 | const wrapper = mount( 45 | 46 | 47 | render below 768 48 | 49 | 50 | ); 51 | expect(wrapper.children().children()).toHaveLength(1); 52 | }); 53 | 54 | it('should render small up', () => { 55 | const wrapper = mount( 56 | 57 | 58 | render above 576 59 | 60 | 61 | ); 62 | expect(wrapper.children().children()).toHaveLength(1); 63 | }); 64 | 65 | it('should not render medium only', () => { 66 | let wrapper = mount( 67 | 68 | 69 | should not render between 768 and 992 70 | 71 | 72 | ); 73 | expect(wrapper.children().children()).toHaveLength(0); 74 | }); 75 | 76 | it('should not render medium up', () => { 77 | let wrapper = mount( 78 | 79 | 80 | should not render above 768 81 | 82 | 83 | ); 84 | expect(wrapper.children().children()).toHaveLength(0); 85 | }); 86 | 87 | it('should render medium down', () => { 88 | let wrapper = mount( 89 | 90 | 91 | should render below 992 92 | 93 | 94 | ); 95 | expect(wrapper.children().children()).toHaveLength(1); 96 | }); 97 | 98 | it('should not render large only', () => { 99 | let wrapper = mount( 100 | 101 | 102 | should not render between 992 and 1200 103 | 104 | 105 | ); 106 | expect(wrapper.children().children()).toHaveLength(0); 107 | }); 108 | 109 | it('should not render large up', () => { 110 | let wrapper = mount( 111 | 112 | 113 | should not render above 992 114 | 115 | 116 | ); 117 | expect(wrapper.children().children()).toHaveLength(0); 118 | }); 119 | 120 | it('should render large down', () => { 121 | let wrapper = mount( 122 | 123 | 124 | should render below 1200 125 | 126 | 127 | ); 128 | expect(wrapper.children().children()).toHaveLength(1); 129 | }); 130 | 131 | afterEach(() => { 132 | widthStub.restore(); 133 | }); 134 | }); 135 | 136 | describe('Breakpoint - medium', () => { 137 | let widthStub; // eslint-disable-line 138 | 139 | beforeEach(() => { 140 | widthStub = sinon 141 | .stub(BreakpointUtil.prototype, 'getWidthSafely') 142 | .returns(768); 143 | }); 144 | 145 | it('should not render small only', () => { 146 | let wrapper = mount( 147 | 148 | 149 | should not render between 576 and 768 150 | 151 | 152 | ); 153 | expect(wrapper.children().children()).toHaveLength(0); 154 | }); 155 | 156 | it('should not render small down', () => { 157 | const wrapper = mount( 158 | 159 | 160 | should not render below 768 161 | 162 | 163 | ); 164 | expect(wrapper.children().children()).toHaveLength(0); 165 | }); 166 | 167 | it('should render small up', () => { 168 | const wrapper = mount( 169 | 170 | 171 | should render above 576 172 | 173 | 174 | ); 175 | expect(wrapper.children().children()).toHaveLength(1); 176 | }); 177 | 178 | it('should render medium only', () => { 179 | let wrapper = mount( 180 | 181 | 182 | should render between 768 and 992 183 | 184 | 185 | ); 186 | expect(wrapper.children().children()).toHaveLength(1); 187 | 188 | wrapper = mount( 189 | 190 | 191 | should render between 768 and 992 192 | 193 | 194 | ); 195 | expect(wrapper.children().children()).toHaveLength(1); 196 | }); 197 | 198 | it('should render medium up', () => { 199 | let wrapper = mount( 200 | 201 | 202 | should render above 768 203 | 204 | 205 | ); 206 | expect(wrapper.children().children()).toHaveLength(1); 207 | }); 208 | 209 | it('should render medium down', () => { 210 | let wrapper = mount( 211 | 212 | 213 | should render below 992 214 | 215 | 216 | ); 217 | expect(wrapper.children().children()).toHaveLength(1); 218 | }); 219 | 220 | it('should not render large only', () => { 221 | let wrapper = mount( 222 | 223 | 224 | should not render between 992 and 1200 225 | 226 | 227 | ); 228 | expect(wrapper.children().children()).toHaveLength(0); 229 | }); 230 | 231 | it('should not render large up', () => { 232 | let wrapper = mount( 233 | 234 | 235 | should not render above 992 236 | 237 | 238 | ); 239 | expect(wrapper.children().children()).toHaveLength(0); 240 | }); 241 | 242 | it('should render large down', () => { 243 | let wrapper = mount( 244 | 245 | 246 | should render below 1200 247 | 248 | 249 | ); 250 | expect(wrapper.children().children()).toHaveLength(1); 251 | }); 252 | 253 | afterEach(() => { 254 | widthStub.restore(); 255 | }); 256 | }); 257 | 258 | describe('Breakpoint - large', () => { 259 | let widthStub; // eslint-disable-line 260 | 261 | beforeEach(() => { 262 | widthStub = sinon 263 | .stub(BreakpointUtil.prototype, 'getWidthSafely') 264 | .returns(1024); 265 | }); 266 | 267 | it('should not render small only', () => { 268 | let wrapper = mount( 269 | 270 | 271 | should not render between 576 and 768 272 | 273 | 274 | ); 275 | expect(wrapper.children().children()).toHaveLength(0); 276 | }); 277 | 278 | it('should not render small down', () => { 279 | const wrapper = mount( 280 | 281 | 282 | should not render below 768 283 | 284 | 285 | ); 286 | expect(wrapper.children().children()).toHaveLength(0); 287 | }); 288 | 289 | it('should render small up', () => { 290 | const wrapper = mount( 291 | 292 | 293 | should render above 576 294 | 295 | 296 | ); 297 | expect(wrapper.children().children()).toHaveLength(1); 298 | }); 299 | 300 | it('should not render medium only', () => { 301 | let wrapper = mount( 302 | 303 | 304 | should not render between 768 and 768 305 | 306 | 307 | ); 308 | expect(wrapper.children().children()).toHaveLength(0); 309 | }); 310 | 311 | it('should render medium up', () => { 312 | let wrapper = mount( 313 | 314 | 315 | should render above 768 316 | 317 | 318 | ); 319 | expect(wrapper.children().children()).toHaveLength(1); 320 | }); 321 | 322 | it('should not render medium down', () => { 323 | let wrapper = mount( 324 | 325 | 326 | should not render below 768 327 | 328 | 329 | ); 330 | expect(wrapper.children().children()).toHaveLength(0); 331 | }); 332 | 333 | it('should render large only', () => { 334 | let wrapper = mount( 335 | 336 | 337 | should render between 992 and 1200 338 | 339 | 340 | ); 341 | expect(wrapper.children().children()).toHaveLength(1); 342 | 343 | wrapper = mount( 344 | 345 | 346 | should render between 992 and 1200 347 | 348 | 349 | ); 350 | expect(wrapper.children().children()).toHaveLength(1); 351 | }); 352 | 353 | it('should render large up', () => { 354 | let wrapper = mount( 355 | 356 | 357 | should render above 992 358 | 359 | 360 | ); 361 | expect(wrapper.children().children()).toHaveLength(1); 362 | }); 363 | 364 | it('should render large down', () => { 365 | let wrapper = mount( 366 | 367 | 368 | should render below 1200 369 | 370 | 371 | ); 372 | expect(wrapper.children().children()).toHaveLength(1); 373 | }); 374 | 375 | it('should render as a span', () => { 376 | let wrapper = mount( 377 | 378 | 379 | parent should be a span 380 | 381 | 382 | ); 383 | expect(wrapper.children().children().type()).toEqual('span') 384 | }) 385 | 386 | it('should render as an a', () => { 387 | let wrapper = mount( 388 | 389 | 390 | parent should be an a 391 | 392 | 393 | ); 394 | expect(wrapper.children().children().type()).toEqual('a') 395 | }) 396 | 397 | it('should have className "test"', () => { 398 | let wrapper = mount( 399 | 400 | 401 | parent should have className test 402 | 403 | 404 | ); 405 | expect(wrapper.children().children().hasClass('test')).toEqual(true) 406 | }) 407 | 408 | it('should have stye "display: none"', () => { 409 | let wrapper = mount( 410 | 411 | 412 | parent should have style display: none 413 | 414 | 415 | ); 416 | expect(wrapper.children().children().prop('style')).toEqual({ display: 'none' }) 417 | }) 418 | 419 | afterEach(() => { 420 | widthStub.restore(); 421 | }); 422 | }); 423 | 424 | describe('Breakpoint - customQuery: minWidth', () => { 425 | let widthStub; // eslint-disable-line 426 | 427 | beforeEach(() => { 428 | widthStub = sinon 429 | .stub(BreakpointUtil.prototype, 'getWidthSafely') 430 | .returns(572); 431 | }); 432 | 433 | it('should render as currentWidth greater than customMinWidth', () => { 434 | Object.defineProperty(window, 'matchMedia', { 435 | writable: true, 436 | configurable: true, 437 | value: jest.fn().mockImplementation(query => ({ 438 | matches: true, // assume the query matches 439 | media: query, 440 | onchange: null, 441 | addEventListener: jest.fn(), 442 | removeEventListener: jest.fn(), 443 | dispatchEvent: jest.fn(), 444 | })) 445 | }); 446 | 447 | let wrapper = mount( 448 | 449 | 450 | should render as currentWidth is greater than minWidth 451 | 452 | 453 | ); 454 | expect(wrapper.children().children()).toHaveLength(1); 455 | }); 456 | 457 | it('should not render as currentWidth lesser than customMinWidth', () => { 458 | Object.defineProperty(window, 'matchMedia', { 459 | writable: true, 460 | configurable: true, 461 | value: jest.fn().mockImplementation(query => ({ 462 | matches: false, // assume the query does not match 463 | media: query, 464 | onchange: null, 465 | addEventListener: jest.fn(), 466 | removeEventListener: jest.fn(), 467 | dispatchEvent: jest.fn(), 468 | })) 469 | }); 470 | 471 | let wrapper = mount( 472 | 473 | 474 | should not render as currentWidth is lesser than minWidth 475 | 476 | 477 | ); 478 | expect(wrapper.children().children()).toHaveLength(0); 479 | }); 480 | 481 | 482 | afterEach(() => { 483 | widthStub.restore(); 484 | }); 485 | }); 486 | 487 | describe('Breakpoint - customQuery: maxWidth', () => { 488 | let widthStub; // eslint-disable-line 489 | 490 | beforeEach(() => { 491 | widthStub = sinon 492 | .stub(BreakpointUtil.prototype, 'getWidthSafely') 493 | .returns(720); 494 | }); 495 | 496 | it('should render as currentWidth is less than customMaxWidth', () => { 497 | Object.defineProperty(window, 'matchMedia', { 498 | writable: true, 499 | configurable: true, 500 | value: jest.fn().mockImplementation(query => ({ 501 | matches: true, // assume the query matches 502 | media: query, 503 | onchange: null, 504 | addEventListener: jest.fn(), 505 | removeEventListener: jest.fn(), 506 | dispatchEvent: jest.fn(), 507 | })) 508 | }); 509 | 510 | let wrapper = mount( 511 | 512 | 513 | should render as currentWidth is less than maxWidth 514 | 515 | 516 | ); 517 | expect(wrapper.children().children()).toHaveLength(1); 518 | }); 519 | 520 | it('should not render as currentWidth greater than customMaxWidth', () => { 521 | Object.defineProperty(window, 'matchMedia', { 522 | writable: true, 523 | configurable: true, 524 | value: jest.fn().mockImplementation(query => ({ 525 | matches: false, // assume the query does not match 526 | media: query, 527 | onchange: null, 528 | addEventListener: jest.fn(), 529 | removeEventListener: jest.fn(), 530 | dispatchEvent: jest.fn(), 531 | })) 532 | }); 533 | 534 | let wrapper = mount( 535 | 536 | 537 | should not render as currentWidth is greater than maxWidth 538 | 539 | 540 | ); 541 | expect(wrapper.children().children()).toHaveLength(0); 542 | }); 543 | 544 | 545 | afterEach(() => { 546 | widthStub.restore(); 547 | }); 548 | }); 549 | 550 | describe('Breakpoint - customQuery: minWidth and maxWidth', () => { 551 | let widthStub; // eslint-disable-line 552 | 553 | beforeEach(() => { 554 | widthStub = sinon 555 | .stub(BreakpointUtil.prototype, 'getWidthSafely') 556 | .returns(720); 557 | }); 558 | 559 | it('should render as currentWidth lies between the range', () => { 560 | Object.defineProperty(window, 'matchMedia', { 561 | writable: true, 562 | configurable: true, 563 | value: jest.fn().mockImplementation(query => ({ 564 | matches: true, // assume the query matches 565 | media: query, 566 | onchange: null, 567 | addEventListener: jest.fn(), 568 | removeEventListener: jest.fn(), 569 | dispatchEvent: jest.fn(), 570 | })) 571 | }); 572 | 573 | let wrapper = mount( 574 | 575 | 576 | should render as currentWidth lies between the range 577 | 578 | 579 | ); 580 | expect(wrapper.children().children()).toHaveLength(1); 581 | }); 582 | 583 | it('should not render as currentWidth lies outside the given range', () => { 584 | Object.defineProperty(window, 'matchMedia', { 585 | writable: true, 586 | configurable: true, 587 | value: jest.fn().mockImplementation(query => ({ 588 | matches: false, // assume the query does not match 589 | media: query, 590 | onchange: null, 591 | addEventListener: jest.fn(), 592 | removeEventListener: jest.fn(), 593 | dispatchEvent: jest.fn(), 594 | })) 595 | }); 596 | 597 | let wrapper = mount( 598 | 599 | 600 | should not render as currentWidth lies outside the given range 601 | 602 | 603 | ); 604 | expect(wrapper.children().children()).toHaveLength(0); 605 | }); 606 | 607 | 608 | afterEach(() => { 609 | widthStub.restore(); 610 | }); 611 | }); 612 | 613 | describe('Breakpoint - customQuery', () => { 614 | let widthStub; // eslint-disable-line 615 | 616 | beforeEach(() => { 617 | widthStub = sinon 618 | .stub(BreakpointUtil.prototype, 'getWidthSafely') 619 | .returns(480); 620 | }); 621 | 622 | it('should not render as customQuery is ignored when other modifiers are specified', () => { 623 | Object.defineProperty(window, 'matchMedia', { 624 | writable: true, 625 | configurable: true, 626 | value: jest.fn().mockImplementation(query => ({ 627 | matches: true, // assume the query matches 628 | media: query, 629 | onchange: null, 630 | addEventListener: jest.fn(), 631 | removeEventListener: jest.fn(), 632 | dispatchEvent: jest.fn(), 633 | })) 634 | }); 635 | 636 | let wrapper = mount( 637 | 638 | 639 | Since currentWidth is a small-screen, this will not render! 640 | customQuery is ignored! 641 | 642 | 643 | ); 644 | expect(wrapper.children().children()).toHaveLength(0); 645 | }); 646 | 647 | it('should render though customQuery is ignored because other modifiers are specified', () => { 648 | Object.defineProperty(window, 'matchMedia', { 649 | writable: true, 650 | configurable: true, 651 | value: jest.fn().mockImplementation(query => ({ 652 | matches: true, // assume the query does not match 653 | media: query, 654 | onchange: null, 655 | addEventListener: jest.fn(), 656 | removeEventListener: jest.fn(), 657 | dispatchEvent: jest.fn(), 658 | })) 659 | }); 660 | 661 | let wrapper = mount( 662 | 663 | 664 | should render though customQuery is ignored because other modifiers are specified 665 | 666 | 667 | ); 668 | expect(wrapper.children().children()).toHaveLength(1); 669 | }); 670 | 671 | 672 | afterEach(() => { 673 | widthStub.restore(); 674 | }); 675 | }); 676 | 677 | --------------------------------------------------------------------------------