├── .editorconfig ├── .gitignore ├── .travis.yml ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── assets ├── logo-with-text.png └── logo.svg ├── code-of-conduct.md ├── package-lock.json ├── package.json ├── rollup.config.ts ├── src ├── BreakpointsProvider.tsx ├── HideAt.tsx ├── ShowAt.tsx ├── react-with-breakpoints.ts ├── utils │ ├── airbnbBreakpoints.ts │ ├── breakpoint-types.ts │ ├── context.ts │ ├── debounce.ts │ ├── index.ts │ ├── set-should-render.ts │ └── useBreakpoint.ts └── withBreakpoints.tsx ├── test ├── HideAt.test.tsx ├── ShowAt.test.tsx └── utils │ ├── index.ts │ └── resizeWindow.ts ├── tsconfig.json └── tslint.json /.editorconfig: -------------------------------------------------------------------------------- 1 | #root = true 2 | 3 | [*] 4 | indent_style = space 5 | end_of_line = lf 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | max_line_length = 100 10 | indent_size = 2 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Mac OSX Files 2 | .DS_Store 3 | .Trashes 4 | .Spotlight-V100 5 | .AppleDouble 6 | .LSOverride 7 | .rpt2_cache 8 | 9 | # NPM 10 | node_modules 11 | npm-debug.log 12 | dist 13 | lib 14 | 15 | # Editor 16 | .idea 17 | 18 | # Coveralls 19 | coverage 20 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | cache: 3 | directories: 4 | - ~/.npm 5 | notifications: 6 | email: 7 | on_success: never 8 | node_js: 9 | - '10' 10 | - '11' 11 | - '8' 12 | script: 13 | - npm run test:prod && npm run build 14 | after_success: 15 | - npm run travis-deploy-once "npm run report-coverage" 16 | # - if [ "$TRAVIS_BRANCH" = "master" -a "$TRAVIS_PULL_REQUEST" = "false" ]; then npm run travis-deploy-once "npm run semantic-release"; fi 17 | branches: 18 | except: 19 | - /^v\d+\.\d+\.\d+$/ 20 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | We're really glad you're reading this, because we need volunteer developers to help this project come to fruition. 👏 2 | 3 | ## Instructions 4 | 5 | These steps will guide you through contributing to this project: 6 | 7 | - Fork the repo 8 | - Clone it and install dependencies 9 | 10 | git clone https://github.com/kristof0425/react-with-breakpoints 11 | npm install 12 | 13 | Keep in mind that after running `npm install` the git repo is reset. So a good way to cope with this is to have a copy of the folder to push the changes, and the other to try them. 14 | 15 | Make and commit your changes. Make sure the commands npm run build and npm run test:prod are working. 16 | 17 | Finally send a [GitHub Pull Request](https://github.com/alexjoverm/typescript-library-starter/compare?expand=1) with a clear list of what you've done (read more [about pull requests](https://help.github.com/articles/about-pull-requests/)). Make sure all of your commits are atomic (one feature per commit). 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2017 kristof0425 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 | 5 | # react-with-breakpoints 6 | 7 | [![Build Status](https://travis-ci.org/kristof0425/react-with-breakpoints.svg?branch=master)](https://travis-ci.org/kristof0425/react-with-breakpoints) 8 | [![Coverage Status](https://coveralls.io/repos/github/kristof0425/react-with-breakpoints/badge.svg?branch=master)](https://coveralls.io/github/kristof0425/react-with-breakpoints?branch=master) 9 | 10 | > Build leaner webpages with `react-with-breakpoints` like Airbnb. 👌 11 | 12 | ## 🔧 Install 13 | 14 | ```bash 15 | npm install react-with-breakpoints 16 | 17 | # or use yarn 18 | 19 | yarn add react-with-breakpoints 20 | ``` 21 | 22 | ## 👈 Usage 23 | 24 | **NOTE:** If you'd like to access v3 docs, please check the `v3` branch!
25 | Beware that, `react-with-breakpoints` v4 depends on React Hooks, which were released with `react@16.8.0`. v3 is still maintained. 26 | 27 | ```jsx 28 | // in index.js 29 | import { BreakpointsProvider } from 'react-with-breakpoints'; 30 | 31 | const myApp = () => ( 32 | 33 | 34 | 35 | ); 36 | 37 | // in App.js 38 | import { ShowAt, HideAt } from 'react-with-breakpoints'; 39 | 40 | const App = () => ( 41 | 42 |
Hello World!
43 |
44 | 45 |
Hola Mundo!
46 |
47 | ); 48 | ``` 49 | 50 | ## ⚡️ Component list 51 | - `` 52 | - `` 53 | - `` 54 | 55 | ### `` 56 | 57 | BreakpointsProvider is taking advantage of the new React v16.3 Context API. It is a context provider and therefore it needs to be rendered on the top of your component tree. For more information about React v16.3's Context API please visit [this link](https://reactjs.org/docs/context.html). 58 | 59 | See in an example how you can use it: 60 | 61 | ```jsx 62 | // in index.js 63 | import { BreakpointsProvider } from 'react-with-breakpoints'; 64 | 65 | const breakpoints = { 66 | small: 468, 67 | medium: 768, 68 | large: 1024, 69 | xlarge: Infinity, 70 | } 71 | 72 | const myApp = () => ( 73 | // breakpoints prop is optional 74 | 75 | 76 | ); 77 | ``` 78 | 79 | **NOTE:** 80 | As you can see in the example above, the `breakpoints` prop has been moved from HideAt and ShowAt to the BreakpointsProvider component. There is a disadvantage and an advantage of this. You can finally modify the breakpoints object at one place in your app, it became centralised. The disadvantage is that now you need to refactor your code if you manually set the breakpoints in your project if you used v2. 81 | 82 | | Prop name | Type | Value | Default value | Description | Required | 83 | | --------- | ---- | ----- | ------------- | ----------- | -------- | 84 | | `breakpoints` | `Object` | `{ small: number, medium: number, large: number, xlarge: number }` | See it at [util/airbnbBreakpoints](util/airbnb-breakpoints.js): `{ small: 744, medium: 1128, large: 1440, xlarge: Infinity }` | Here you can override the default Airbnb breakpoints. It needs to be an object with a strict shape, which is shown at the value row. | `false` | 85 | | `onBreakpointChange` | `Function` | `(breakpoint: Breakpoint) => void` | `-` | Callback invoked on breakpoint change. | `false` | 86 | 87 | ### `` 88 | 89 | HideAt is a stateless function, which helps you make your DOM leaner. It hides its children, when the proper criterias are met. 90 | 91 | Let’s see it in action: 92 | 93 | ```jsx 94 | import { HideAt } from 'react-with-breakpoints'; 95 | 96 | const myApp = () => ( 97 | 98 |
Hello World!
99 |
100 | ); 101 | ``` 102 | 103 | Here, the div with the ‘Hello World!’ text is going to appear only if you are viewing your website on a medium or larger sized screen. It’ll be hidden and removed from the DOM on small screen width. HideAt gets the current breakpoint (screen width described as a text eg.: small) from BreakpointsProvider. 104 | 105 | **NOTE:** 106 | As HideAt and ShowAt function the same way (they do the opposite things of each other), they share the same props and prop-types. 107 | 108 | | Prop name | Type | Value | Default value | Description | Required | 109 | | --------- | ---- | ----- | ------------- | ----------- | -------- | 110 | | `breakpoint` | `String` | Either one of these: `'small'`, `'medium'`, `'mediumAndBelow'`, `'mediumAndAbove'`, `'large'`, `'xlarge'` | - | You can set either one of the values to tell the component where to hide or show its children. | `true` | 111 | | `currentBreakpoint` | `String` | Either one of these: `'small'`, `'medium'`, `'large'`, `'xlarge'` | - | It's used by withBreakpoints. Whenever there is a change with the breakpoints, the appropriate value will be passed down to HideAt or ShowAt. | `false` | 112 | 113 | ### `` 114 | 115 | ShowAt functions the opposite way as HideAt does. It reveals its children when the current breakpoint matches its breakpoint. (eg.: small, smallAndBelow) 116 | 117 | As said above, ShowAt and HideAt share the same `props` and `propTypes`, so please look at the prop descriptions at HideAt. 118 | 119 | ```jsx 120 | import { ShowAt } from 'react-with-breakpoints'; 121 | 122 | const myApp = () => ( 123 | 124 |
Hello World!
125 |
126 | ); 127 | ``` 128 | 129 | ## 💪 Contributions 130 | 131 | Although all kinds of contributions are welcome, I wouldn't mind having a system for them. 132 | **Please follow the instructions below, if you’re about to work on this project!** 133 | 134 | 1. If you find something, that bothers you about these modules, or you could improve them, please submit a new issue [here](https://github.com/kristof0425/react-with-breakpoints/issues). 135 | 2. Fork react-with-breakpoints repository and create your changes in your repository. 136 | 3. Create a pull request with the appropriate issue’s number you created (or you found solvable) and put **Review needed** label on it, if you feel like done with your work. 137 | 138 | After this I'll review it personally and hopefully merge it as well. 139 | 140 | Happy coding! ☕️ 141 | 142 | ## 👏 Story 143 | 144 | I wrote a 4 min long story on Medium about what I learned along the way, when I created the first version of these three modules. It got published in [DailyJS](https://medium.com/dailyjs/i-open-sourced-3-modules-from-airbnb-614bc5a2a51d). 🤗 145 | 146 | ## ©️ Licence 147 | MIT 148 | -------------------------------------------------------------------------------- /assets/logo-with-text.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kristofdombi/react-with-breakpoints/fbd471cb88567e50674046953a5c545e36922014/assets/logo-with-text.png -------------------------------------------------------------------------------- /assets/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | rects 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /code-of-conduct.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, gender identity and expression, level of experience, 9 | nationality, personal appearance, race, religion, or sexual identity and 10 | orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at dombi.kristof@gmail.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at [http://contributor-covenant.org/version/1/4][version] 72 | 73 | [homepage]: http://contributor-covenant.org 74 | [version]: http://contributor-covenant.org/version/1/4/ 75 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-with-breakpoints", 3 | "version": "4.0.4", 4 | "description": "Utility React component for altering the visual experience of responsive and lean webpages.", 5 | "main": "dist/react-with-breakpoints.umd.js", 6 | "module": "dist/react-with-breakpoints.es5.js", 7 | "typings": "dist/types/react-with-breakpoints.d.ts", 8 | "files": [ 9 | "dist" 10 | ], 11 | "homepage": "https://github.com/kristof0425/react-with-breakpoints#readme", 12 | "author": "Kristof Dombi ", 13 | "license": "MIT", 14 | "keywords": [ 15 | "react", 16 | "css", 17 | "styles", 18 | "display-none", 19 | "toggle", 20 | "utility-function", 21 | "higher-order-component", 22 | "airbnb", 23 | "breakpoint", 24 | "breakpoints", 25 | "alter", 26 | "ux", 27 | "display", 28 | "react-display", 29 | "hide-at", 30 | "react-hide-at", 31 | "show-at", 32 | "react-show-at" 33 | ], 34 | "engines": { 35 | "node": ">=6.0.0" 36 | }, 37 | "scripts": { 38 | "lint": "tslint --project tsconfig.json -t codeFrame 'src/**/*.ts' 'test/**/*.ts'", 39 | "prebuild": "rimraf dist", 40 | "build": "NODE_ENV=production tsc --module commonjs && rollup -c rollup.config.ts", 41 | "start": "rollup -c rollup.config.ts -w", 42 | "test": "jest --coverage", 43 | "test:watch": "jest --coverage --watch", 44 | "test:prod": "npm run lint && npm run test -- --no-cache", 45 | "report-coverage": "cat ./coverage/lcov.info | coveralls", 46 | "commit": "git-cz", 47 | "semantic-release": "semantic-release", 48 | "semantic-release-prepare": "ts-node tools/semantic-release-prepare", 49 | "precommit": "lint-staged", 50 | "travis-deploy-once": "travis-deploy-once" 51 | }, 52 | "repository": { 53 | "type": "git", 54 | "url": "git+https://github.com/kristof0425/react-with-breakpoint.git" 55 | }, 56 | "bugs": { 57 | "url": "https://github.com/kristof0425/react-with-breakpoints/issues" 58 | }, 59 | "lint-staged": { 60 | "{src,test}/**/*.ts": [ 61 | "prettier --write", 62 | "git add" 63 | ] 64 | }, 65 | "config": { 66 | "commitizen": { 67 | "path": "node_modules/cz-conventional-changelog" 68 | } 69 | }, 70 | "jest": { 71 | "transform": { 72 | ".(ts|tsx)": "ts-jest" 73 | }, 74 | "setupTestFrameworkScriptFile": "jest-enzyme", 75 | "testEnvironment": "jsdom", 76 | "verbose": true, 77 | "testURL": "http://localhost/", 78 | "testRegex": "(/__tests__/.*|\\.(test|spec))\\.(ts|tsx|js)$", 79 | "moduleFileExtensions": [ 80 | "ts", 81 | "tsx", 82 | "js" 83 | ], 84 | "coveragePathIgnorePatterns": [ 85 | "/node_modules/", 86 | "/test/" 87 | ], 88 | "coverageThreshold": { 89 | "global": { 90 | "branches": 90, 91 | "functions": 95, 92 | "lines": 95, 93 | "statements": 95 94 | } 95 | }, 96 | "collectCoverageFrom": [ 97 | "src/*.{js,ts}" 98 | ] 99 | }, 100 | "prettier": { 101 | "semi": false, 102 | "singleQuote": true 103 | }, 104 | "commitlint": { 105 | "extends": [ 106 | "@commitlint/config-conventional" 107 | ] 108 | }, 109 | "devDependencies": { 110 | "@commitlint/cli": "^7.1.2", 111 | "@commitlint/config-conventional": "^7.1.2", 112 | "@types/enzyme": "^3.1.15", 113 | "@types/enzyme-adapter-react-16": "^1.0.3", 114 | "@types/jest": "^23.3.2", 115 | "@types/node": "^10.11.0", 116 | "@types/react": "^16.7.21", 117 | "colors": "^1.3.2", 118 | "commitizen": "^3.0.0", 119 | "coveralls": "^3.0.2", 120 | "cross-env": "^5.2.0", 121 | "cz-conventional-changelog": "^2.1.0", 122 | "enzyme": "^3.8.0", 123 | "enzyme-adapter-react-16": "^1.8.0", 124 | "husky": "^1.0.1", 125 | "jest": "^23.6.0", 126 | "jest-config": "^23.6.0", 127 | "jest-environment-enzyme": "^7.0.1", 128 | "jest-enzyme": "^7.0.1", 129 | "jsdom": "^13.2.0", 130 | "lint-staged": "^8.0.0", 131 | "lodash.camelcase": "^4.3.0", 132 | "prettier": "^1.14.3", 133 | "prompt": "^1.0.0", 134 | "react": "^16.8.2", 135 | "react-dom": "^16.8.2", 136 | "replace-in-file": "^3.4.2", 137 | "rimraf": "^2.6.2", 138 | "rollup": "^0.67.0", 139 | "rollup-plugin-commonjs": "^9.1.8", 140 | "rollup-plugin-json": "^3.1.0", 141 | "rollup-plugin-node-resolve": "^3.4.0", 142 | "rollup-plugin-sourcemaps": "^0.4.2", 143 | "rollup-plugin-terser": "^4.0.3", 144 | "rollup-plugin-typescript2": "^0.18.0", 145 | "semantic-release": "^15.9.16", 146 | "travis-deploy-once": "^5.0.9", 147 | "ts-jest": "^23.10.2", 148 | "ts-node": "^7.0.1", 149 | "tslint": "^5.11.0", 150 | "tslint-config-prettier": "^1.15.0", 151 | "tslint-config-standard": "^8.0.1", 152 | "typedoc": "^0.12.0", 153 | "typescript": "^3.0.3" 154 | }, 155 | "dependencies": {}, 156 | "peerDependencies": { 157 | "react": ">= 16.8.x" 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /rollup.config.ts: -------------------------------------------------------------------------------- 1 | import resolve from 'rollup-plugin-node-resolve' 2 | import commonjs from 'rollup-plugin-commonjs' 3 | import sourceMaps from 'rollup-plugin-sourcemaps' 4 | import camelCase from 'lodash.camelcase' 5 | import typescript from 'rollup-plugin-typescript2' 6 | import json from 'rollup-plugin-json' 7 | import { terser } from 'rollup-plugin-terser' 8 | 9 | const pkg = require('./package.json') 10 | 11 | const libraryName = 'react-with-breakpoints' 12 | 13 | // const isProduction = process.env.NODE_ENV === 'production'; 14 | 15 | export default () => ({ 16 | input: `src/${libraryName}.ts`, 17 | output: [ 18 | { file: pkg.main, name: camelCase(libraryName), format: 'umd', sourcemap: true }, 19 | { file: pkg.module, format: 'es', sourcemap: true }, 20 | ], 21 | // Indicate here external modules you don't wanna include in your bundle (i.e.: 'lodash') 22 | external: ['react'], 23 | watch: { 24 | include: 'src/**', 25 | }, 26 | plugins: [ 27 | // Allow json resolution 28 | json(), 29 | // Compile TypeScript files 30 | typescript({ useTsconfigDeclarationDir: true }), 31 | // Allow bundling cjs modules (unlike webpack, rollup doesn't understand cjs) 32 | commonjs(), 33 | terser(), 34 | // Allow node_modules resolution, so you can use 'external' to control 35 | // which external modules to include in the bundle 36 | // https://github.com/rollup/rollup-plugin-node-resolve#usage 37 | resolve(), 38 | 39 | // Resolve source maps to the original source 40 | sourceMaps(), 41 | ], 42 | }) 43 | -------------------------------------------------------------------------------- /src/BreakpointsProvider.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | 3 | import { airbnbBreakpoints, debounce, Context, Breakpoint } from './utils' 4 | 5 | type Breakpoints = { 6 | small: number 7 | medium: number 8 | large: number 9 | xlarge: number 10 | } 11 | 12 | interface BreakpointsProviderProps { 13 | breakpoints?: Breakpoints 14 | onBreakpointChange?: (breakpoint: Breakpoint) => void 15 | } 16 | 17 | const BreakpointsProvider: React.FC = ({ 18 | breakpoints, 19 | onBreakpointChange, 20 | children 21 | }) => { 22 | const [currentBreakpoint, setCurrentBreakpoint] = React.useState('' as Breakpoint) 23 | 24 | React.useLayoutEffect(() => { 25 | const debouncedResize = debounce(handleResize, 50) 26 | 27 | window.addEventListener('resize', debouncedResize, { passive: true }) 28 | handleResize() 29 | 30 | return () => { 31 | window.removeEventListener('resize', debouncedResize) 32 | } 33 | }, []) 34 | 35 | React.useEffect(() => { 36 | if (onBreakpointChange) { 37 | onBreakpointChange(currentBreakpoint) 38 | } 39 | }, [currentBreakpoint]) 40 | 41 | const handleResize = () => { 42 | const clientWidth = window.innerWidth 43 | if (clientWidth < breakpoints.small) { 44 | setCurrentBreakpoint('small') 45 | } else if (clientWidth < breakpoints.medium) { 46 | setCurrentBreakpoint('medium') 47 | } else if (clientWidth < breakpoints.large) { 48 | setCurrentBreakpoint('large') 49 | } else if (clientWidth <= breakpoints.xlarge || clientWidth > breakpoints.xlarge) { 50 | setCurrentBreakpoint('xlarge') 51 | } 52 | } 53 | 54 | return {children} 55 | } 56 | 57 | BreakpointsProvider.defaultProps = { 58 | breakpoints: airbnbBreakpoints 59 | } 60 | 61 | BreakpointsProvider.displayName = 'BreakpointsProvider' 62 | 63 | export default BreakpointsProvider 64 | -------------------------------------------------------------------------------- /src/HideAt.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | 3 | import { BreakpointEdge, useBreakpoint } from './utils/index' 4 | 5 | interface HideAtProps { 6 | breakpoint: BreakpointEdge 7 | children?: React.ReactNode 8 | } 9 | 10 | // @ts-ignore 11 | export const HideAt: React.FunctionComponent = ({ breakpoint, children }) => { 12 | const shouldRender = useBreakpoint(breakpoint) 13 | 14 | if (shouldRender) { 15 | return children 16 | } 17 | return null 18 | } 19 | 20 | HideAt.displayName = 'HideAt' 21 | 22 | HideAt.defaultProps = { 23 | breakpoint: '' as BreakpointEdge, 24 | children: null 25 | } 26 | 27 | export default HideAt 28 | -------------------------------------------------------------------------------- /src/ShowAt.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | 3 | import { BreakpointEdge, useBreakpoint } from './utils' 4 | 5 | interface ShowAtProps { 6 | breakpoint: BreakpointEdge 7 | children?: React.ReactNode 8 | } 9 | 10 | // @ts-ignore 11 | export const ShowAt: React.FunctionComponent = ({ breakpoint, children }) => { 12 | const shouldRender = useBreakpoint(breakpoint, false) 13 | 14 | if (shouldRender) { 15 | return children 16 | } 17 | return null 18 | } 19 | 20 | ShowAt.displayName = 'ShowAt' 21 | 22 | ShowAt.defaultProps = { 23 | breakpoint: '' as BreakpointEdge, 24 | children: null 25 | } 26 | 27 | export default ShowAt 28 | -------------------------------------------------------------------------------- /src/react-with-breakpoints.ts: -------------------------------------------------------------------------------- 1 | export { default as BreakpointsProvider } from './BreakpointsProvider' 2 | export { default as withBreakpoints } from './withBreakpoints' 3 | export { default as HideAt } from './HideAt' 4 | export { default as ShowAt } from './ShowAt' 5 | export { Context as BreakpointContext } from './utils' 6 | -------------------------------------------------------------------------------- /src/utils/airbnbBreakpoints.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | small: 744, 3 | medium: 1128, 4 | large: 1440, 5 | xlarge: Infinity 6 | } 7 | -------------------------------------------------------------------------------- /src/utils/breakpoint-types.ts: -------------------------------------------------------------------------------- 1 | export default [ 2 | 'small', 3 | 'medium', 4 | 'mediumAndBelow', 5 | 'mediumAndAbove', 6 | 'large', 7 | 'largeAndBelow', 8 | 'largeAndAbove', 9 | 'xlarge' 10 | ] 11 | 12 | export type BreakpointEdge = 13 | | 'small' 14 | | 'medium' 15 | | 'mediumAndBelow' 16 | | 'mediumAndAbove' 17 | | 'large' 18 | | 'largeAndBelow' 19 | | 'largeAndAbove' 20 | | 'xlarge' 21 | 22 | export type Breakpoint = 'small' | 'medium' | 'large' | 'xlarge' 23 | -------------------------------------------------------------------------------- /src/utils/context.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | import { Breakpoint } from './breakpoint-types' 3 | 4 | const Context = React.createContext({ 5 | currentBreakpoint: '' as Breakpoint 6 | }) 7 | 8 | export default Context 9 | -------------------------------------------------------------------------------- /src/utils/debounce.ts: -------------------------------------------------------------------------------- 1 | import Timeout = NodeJS.Timeout 2 | 3 | const debounce = (func: Function, interval: number) => { 4 | let timeout: Timeout | null 5 | return (...args: any) => { 6 | const later = () => { 7 | timeout = null 8 | func.apply(this, args) 9 | } 10 | clearTimeout(timeout) 11 | timeout = setTimeout(later, interval) 12 | } 13 | } 14 | 15 | export default debounce 16 | -------------------------------------------------------------------------------- /src/utils/index.ts: -------------------------------------------------------------------------------- 1 | export { default as airbnbBreakpoints } from './airbnbBreakpoints' 2 | export { default as setShouldRender } from './set-should-render' 3 | export { default as debounce } from './debounce' 4 | export { default as Context } from './context' 5 | export { default as useBreakpoint } from './useBreakpoint' 6 | 7 | // Types 8 | export { BreakpointEdge, Breakpoint } from './breakpoint-types' 9 | -------------------------------------------------------------------------------- /src/utils/set-should-render.ts: -------------------------------------------------------------------------------- 1 | import { BreakpointEdge } from './index' 2 | 3 | export default function( 4 | breakpoints: Array = [], 5 | breakpoint = '' as BreakpointEdge, 6 | isHideAt: Boolean = true 7 | ) { 8 | let shouldRender = true 9 | !isHideAt && (shouldRender = false) 10 | breakpoints.map(b => { 11 | breakpoint === b && (shouldRender = isHideAt ? false : true) 12 | }) 13 | return shouldRender 14 | } 15 | -------------------------------------------------------------------------------- /src/utils/useBreakpoint.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | 3 | import { BreakpointEdge } from './breakpoint-types' 4 | import Context from './context' 5 | import setShouldRender from './set-should-render' 6 | 7 | function useBreakpoint(breakpoint: BreakpointEdge, isHideAt?: Boolean): Boolean { 8 | const { currentBreakpoint } = React.useContext(Context) 9 | 10 | let shouldRender = false 11 | 12 | switch (currentBreakpoint) { 13 | case 'small': 14 | shouldRender = setShouldRender( 15 | ['small', 'mediumAndBelow', 'largeAndBelow'], 16 | breakpoint, 17 | isHideAt 18 | ) 19 | break 20 | case 'medium': 21 | shouldRender = setShouldRender( 22 | ['medium', 'mediumAndAbove', 'mediumAndBelow', 'largeAndBelow'], 23 | breakpoint, 24 | isHideAt 25 | ) 26 | break 27 | case 'large': 28 | shouldRender = setShouldRender( 29 | ['mediumAndAbove', 'large', 'largeAndBelow', 'largeAndAbove'], 30 | breakpoint, 31 | isHideAt 32 | ) 33 | break 34 | case 'xlarge': 35 | shouldRender = setShouldRender( 36 | ['mediumAndAbove', 'largeAndAbove', 'xlarge'], 37 | breakpoint, 38 | isHideAt 39 | ) 40 | break 41 | } 42 | 43 | return shouldRender 44 | } 45 | 46 | export default useBreakpoint 47 | -------------------------------------------------------------------------------- /src/withBreakpoints.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | 3 | import { Context } from './utils/index' 4 | 5 | const withBreakpoints = WrappedComponent => { 6 | const Component = props => ( 7 | 8 | {payload => } 9 | 10 | ) 11 | Component.displayName = `withBreakpoints(${WrappedComponent.displayName || 12 | WrappedComponent.name || 13 | 'Component'})` 14 | return Component 15 | } 16 | 17 | export default withBreakpoints 18 | -------------------------------------------------------------------------------- /test/HideAt.test.tsx: -------------------------------------------------------------------------------- 1 | import * as Enzyme from 'enzyme' 2 | import { mount } from 'enzyme' 3 | import Adapter from 'enzyme-adapter-react-16' 4 | import * as React from 'react' 5 | 6 | import { BreakpointsProvider, HideAt } from '../src/react-with-breakpoints' 7 | import { airbnbBreakpoints as breakpoints } from '../src/utils' 8 | import { resizeWindow } from './utils' 9 | 10 | Enzyme.configure({ adapter: new Adapter() }) 11 | 12 | describe('', () => { 13 | const child =
Hello
14 | 15 | it('allows us to set props', () => { 16 | const $ = mount() 17 | expect($.props().breakpoint).toBe('small') 18 | $.setProps({ breakpoint: 'mediumAndBelow' }) 19 | expect($.props().breakpoint).toBe('mediumAndBelow') 20 | }) 21 | 22 | it('renders child if props are different', () => { 23 | resizeWindow(breakpoints.medium) 24 | const $ = mount( 25 | 26 | 27 | {child} 28 | 29 | 30 | ) 31 | expect($.contains(child)).toBe(true) 32 | }) 33 | 34 | it('renders its children if breakpoint is mediumAndBelow and currentBreakpoint is large', () => { 35 | resizeWindow(breakpoints.large) 36 | const $ = mount( 37 | 38 | 39 | {child} 40 | 41 | 42 | ) 43 | expect($.contains(child)).toBe(true) 44 | }) 45 | 46 | it('renders its children if breakpoint is largeAndBelow and currentBreakpoint is xlarge', () => { 47 | resizeWindow(breakpoints.xlarge) 48 | const $ = mount( 49 | 50 | 51 | {child} 52 | 53 | 54 | ) 55 | expect($.contains(child)).toBe(true) 56 | }) 57 | 58 | it('renders its children if breakpoint is large and currentBreakpoint is medium', () => { 59 | resizeWindow(breakpoints.medium) 60 | const $ = mount( 61 | 62 | 63 | {child} 64 | 65 | 66 | ) 67 | expect($.contains(child)).toBe(true) 68 | }) 69 | 70 | it('renders its children if breakpoint is large and currentBreakpoint is xlarge', () => { 71 | resizeWindow(breakpoints.xlarge) 72 | const $ = mount( 73 | 74 | 75 | {child} 76 | 77 | 78 | ) 79 | expect($.contains(child)).toBe(true) 80 | }) 81 | 82 | it('hides its children if breakpoint is large and currentBreakpoint is large', () => { 83 | resizeWindow(breakpoints.large) 84 | const $ = mount( 85 | 86 | 87 | {child} 88 | 89 | 90 | ) 91 | expect($.contains(child)).toBe(false) 92 | }) 93 | 94 | it('hides its children if breakpoint is xlarge and currentBreakpoint is xlarge', () => { 95 | resizeWindow(breakpoints.xlarge) 96 | const $ = mount( 97 | 98 | 99 | {child} 100 | 101 | 102 | ) 103 | expect($.contains(child)).toBe(false) 104 | }) 105 | 106 | it('hides its children if breakpoint is mediumAndAbove and currentBreakpoint is large', () => { 107 | resizeWindow(breakpoints.large) 108 | const $ = mount( 109 | 110 | 111 | {child} 112 | 113 | 114 | ) 115 | expect($.contains(child)).toBe(false) 116 | }) 117 | 118 | it('hides its children if breakpoint is mediumAndAbove and currentBreakpoint is xlarge', () => { 119 | resizeWindow(breakpoints.xlarge) 120 | const $ = mount( 121 | 122 | 123 | {child} 124 | 125 | 126 | ) 127 | expect($.contains(child)).toBe(false) 128 | }) 129 | 130 | it('hides its children if breakpoint is largeAndAbove and currentBreakpoint is xlarge', () => { 131 | resizeWindow(breakpoints.xlarge) 132 | const $ = mount( 133 | 134 | 135 | {child} 136 | 137 | 138 | ) 139 | expect($.contains(child)).toBe(false) 140 | }) 141 | 142 | it('hides its children if breakpoint is medium and currentBreakpoint is medium', () => { 143 | resizeWindow(breakpoints.medium) 144 | const $ = mount( 145 | 146 | 147 | {child} 148 | 149 | 150 | ) 151 | expect($.contains(child)).toBe(false) 152 | }) 153 | 154 | it('hides its children if props are the same', () => { 155 | resizeWindow(breakpoints.medium) 156 | const $ = mount( 157 | 158 | 159 | {child} 160 | 161 | 162 | ) 163 | expect($.contains(child)).toBe(false) 164 | }) 165 | 166 | it('hides its children breakpoint is largeAndBelow and currentBreakpoint is medium', () => { 167 | resizeWindow(breakpoints.medium) 168 | const $ = mount( 169 | 170 | 171 | { child } 172 | 173 | 174 | ) 175 | expect($.contains(child)).toBe(false) 176 | }) 177 | 178 | it('hides its children breakpoint is largeAndBelow and currentBreakpoint is small', () => { 179 | resizeWindow(breakpoints.small) 180 | const $ = mount( 181 | 182 | 183 | { child } 184 | 185 | 186 | ) 187 | expect($.contains(child)).toBe(false) 188 | }) 189 | }) 190 | -------------------------------------------------------------------------------- /test/ShowAt.test.tsx: -------------------------------------------------------------------------------- 1 | import * as Enzyme from 'enzyme' 2 | import { mount } from 'enzyme' 3 | import Adapter from 'enzyme-adapter-react-16' 4 | import * as React from 'react' 5 | 6 | import { BreakpointsProvider, ShowAt } from '../src/react-with-breakpoints' 7 | import { airbnbBreakpoints as breakpoints } from '../src/utils' 8 | import { resizeWindow } from './utils' 9 | 10 | Enzyme.configure({ adapter: new Adapter() }) 11 | 12 | describe('', () => { 13 | const child =
Hello
14 | 15 | it('allows us to set props', () => { 16 | const $ = mount() 17 | expect($.props().breakpoint).toBe('small') 18 | $.setProps({ breakpoint: 'mediumAndBelow' }) 19 | expect($.props().breakpoint).toBe('mediumAndBelow') 20 | }) 21 | 22 | it("doesn't render its child if props are different", () => { 23 | resizeWindow(breakpoints.medium) 24 | const $ = mount( 25 | 26 | {child} 27 | 28 | ) 29 | expect($.contains(child)).toBe(false) 30 | }) 31 | 32 | it("doesn't render its child if breakpoint is mediumAndBelow and currentBreakpoint is large", () => { 33 | resizeWindow(breakpoints.large) 34 | const $ = mount( 35 | 36 | {child} 37 | 38 | ) 39 | expect($.contains(child)).toBe(false) 40 | }) 41 | 42 | it("doesn't render its child if breakpoint is largeAndBelow and currentBreakpoint is xlarge", () => { 43 | resizeWindow(breakpoints.xlarge) 44 | const $ = mount( 45 | 46 | {child} 47 | 48 | ) 49 | expect($.contains(child)).toBe(false) 50 | }) 51 | 52 | it("doesn't render its child if breakpoint is large and currentBreakpoint is medium", () => { 53 | resizeWindow(breakpoints.medium) 54 | const $ = mount( 55 | 56 | {child} 57 | 58 | ) 59 | expect($.contains(child)).toBe(false) 60 | }) 61 | 62 | it("doesn't render its child if breakpoint is xlarge and currentBreakpoint is large", () => { 63 | resizeWindow(breakpoints.large) 64 | const $ = mount( 65 | 66 | {child} 67 | 68 | ) 69 | expect($.contains(child)).toBe(false) 70 | }) 71 | 72 | it('shows its children if breakpoint is large and currentBreakpoint is large', () => { 73 | resizeWindow(breakpoints.large) 74 | const $ = mount( 75 | 76 | {child} 77 | 78 | ) 79 | expect($.contains(child)).toBe(true) 80 | }) 81 | 82 | it('shows its children if breakpoint is xlarge and currentBreakpoint is xlarge', () => { 83 | resizeWindow(breakpoints.xlarge) 84 | const $ = mount( 85 | 86 | {child} 87 | 88 | ) 89 | expect($.contains(child)).toBe(true) 90 | }) 91 | 92 | it('shows its children if breakpoint is mediumAndAbove and currentBreakpoint is large', () => { 93 | resizeWindow(breakpoints.large) 94 | const $ = mount( 95 | 96 | {child} 97 | 98 | ) 99 | expect($.contains(child)).toBe(true) 100 | }) 101 | 102 | it('shows its children if breakpoint is largeAndAbove and currentBreakpoint is xlarge', () => { 103 | resizeWindow(breakpoints.xlarge) 104 | const $ = mount( 105 | 106 | {child} 107 | 108 | ) 109 | expect($.contains(child)).toBe(true) 110 | }) 111 | 112 | it('shows its children if breakpoint is medium and currentBreakpoint is medium', () => { 113 | resizeWindow(breakpoints.medium) 114 | const $ = mount( 115 | 116 | {child} 117 | 118 | ) 119 | expect($.contains(child)).toBe(true) 120 | }) 121 | 122 | it('shows its children if props are the same', () => { 123 | resizeWindow(breakpoints.medium) 124 | const $ = mount( 125 | 126 | {child} 127 | 128 | ) 129 | expect($.contains(child)).toBe(true) 130 | }) 131 | 132 | it('shows its children if breakpoint is largeAndBelow and currentBreakpoint is medium', () => { 133 | resizeWindow(breakpoints.medium) 134 | const $ = mount( 135 | 136 | {child} 137 | 138 | ) 139 | expect($.contains(child)).toBe(true) 140 | }) 141 | 142 | it('shows its children if breakpoint is largeAndBelow and currentBreakpoint is small', () => { 143 | resizeWindow(breakpoints.small) 144 | const $ = mount( 145 | 146 | {child} 147 | 148 | ) 149 | expect($.contains(child)).toBe(true) 150 | }) 151 | }) 152 | -------------------------------------------------------------------------------- /test/utils/index.ts: -------------------------------------------------------------------------------- 1 | export { default as resizeWindow } from './resizeWindow' 2 | -------------------------------------------------------------------------------- /test/utils/resizeWindow.ts: -------------------------------------------------------------------------------- 1 | export default function(to: number): void { 2 | // @ts-ignore 3 | window.innerWidth = to - 1 4 | window.dispatchEvent(new Event('resize')) 5 | } 6 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "moduleResolution": "node", 4 | "target": "es5", 5 | "module": "es2015", 6 | "lib": ["es2015", "es2016", "es2017", "dom"], 7 | "strict": false, 8 | "sourceMap": true, 9 | "declaration": true, 10 | "allowSyntheticDefaultImports": true, 11 | "esModuleInterop": true, 12 | "experimentalDecorators": true, 13 | "emitDecoratorMetadata": true, 14 | "declarationDir": "dist/types", 15 | "outDir": "dist/lib", 16 | "jsx": "react", 17 | "typeRoots": ["node_modules/@types"] 18 | }, 19 | "include": ["src"] 20 | } 21 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "tslint-config-standard", 4 | "tslint-config-prettier" 5 | ], 6 | "rules": { 7 | "no-duplicate-imports": false 8 | } 9 | } 10 | --------------------------------------------------------------------------------