├── .editorconfig ├── .eslintrc.js ├── .gitignore ├── .npmignore ├── README.md ├── babel.config.js ├── package-lock.json ├── package.json └── src ├── __tests__ ├── get-screen-size.test.js └── use-screen-size.test.js ├── get-screen-size.js ├── index.js └── use-screen-size.js /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | charset = utf-8 6 | trim_trailing_whitespace = true 7 | insert_final_newline = true 8 | indent_style = space 9 | indent_size = 2 10 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: 'babel-eslint', 3 | env: { 4 | browser: true, 5 | es6: true, 6 | mocha: true, 7 | node: true, 8 | }, 9 | parserOptions: { 10 | sourceType: 'module', 11 | ecmaFeatures: { 12 | experimentalObjectRestSpread: true, 13 | }, 14 | }, 15 | extends: [ 16 | 'eslint:recommended', 17 | 'plugin:react/recommended', 18 | 'plugin:jest/recommended', 19 | ], 20 | settings: { 21 | react: { 22 | version: 'detect', 23 | }, 24 | }, 25 | plugins: [ 'react', 'react-hooks', 'jest' ], 26 | rules: { 27 | // General 28 | 'array-callback-return': [ 'warn' ], 29 | 'eqeqeq': [ 'warn', 'always', { null: 'ignore' } ], 30 | 'new-parens': [ 'warn' ], 31 | 'no-array-constructor': [ 'warn' ], 32 | 'no-caller': [ 'warn' ], 33 | 'no-cond-assign': [ 'warn', 'always' ], 34 | 'no-console': [ 'warn', { allow: [ 'warn', 'error', 'reportException' ] } ], 35 | 'no-eval': [ 'warn' ], 36 | 'no-extend-native': [ 'warn' ], 37 | 'no-extra-bind': [ 'warn' ], 38 | 'no-implied-eval': [ 'warn' ], 39 | 'no-iterator': [ 'warn' ], 40 | 'no-lone-blocks': [ 'warn' ], 41 | 'no-loop-func': [ 'warn' ], 42 | 'no-multi-str': [ 'warn' ], 43 | 'no-native-reassign': [ 'warn' ], 44 | 'no-new-wrappers': [ 'warn' ], 45 | 'no-script-url': [ 'warn' ], 46 | 'no-self-compare': [ 'warn' ], 47 | 'no-shadow-restricted-names': [ 'warn' ], 48 | 'no-template-curly-in-string': [ 'warn' ], 49 | 'no-throw-literal': [ 'warn' ], 50 | 'no-unused-vars': [ 'warn', { 'args': 'none', ignoreRestSiblings: true } ], 51 | 'no-use-before-define': [ 'warn' ], 52 | 'no-useless-computed-key': [ 'warn' ], 53 | 'no-useless-concat': [ 'warn' ], 54 | 'no-useless-constructor': [ 'warn' ], 55 | 'no-useless-rename': [ 'warn' ], 56 | 'no-whitespace-before-property': [ 'warn' ], 57 | 'no-unreachable': [ 'warn' ], 58 | 'no-constant-condition': [ 'warn' ], 59 | 60 | // React 61 | 'react/prop-types': [ 'off' ], 62 | 'react/no-unescaped-entities': [ 'off' ], 63 | 'react/jsx-key': [ 'off' ], 64 | 'react/style-prop-object': [ 'warn' ], 65 | 66 | // React Hooks 67 | 'react-hooks/rules-of-hooks': [ 'error' ], 68 | }, 69 | }; 70 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | lib 3 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | __tests__ 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Get Screen Size Hook 2 | 3 | This hook will use the breakpoints defined in `@drawbotics/drylus-style-vars` to identify screen size where the app is running. 4 | 5 | ## Installation 6 | 7 | ```bash 8 | $ npm install @drawbotics/use-screen-size @drawbotics/drylus-style-vars 9 | ``` 10 | 11 | ## Usage 12 | 13 | ```js 14 | import { useScreenSize } from '@drawbotics/use-screen-size'; 15 | 16 | 17 | const App = ({ children }) => { 18 | const { screenSize, ScreenSizes } = useScreenSize(); 19 | return ( 20 | 21 | {do { 22 | if (screenSize >= ScreenSizes.M) { 23 |
24 | } 25 | }} 26 | {do { 27 | if (screenSize < ScreenSizes.S) { 28 | 29 | } 30 | }} 31 | 32 | {children} 33 | 34 | 35 | ) 36 | }; 37 | 38 | 39 | export default App; 40 | ``` 41 | 42 | ## Api 43 | 44 | The hook returns two properties `screenSize` and `ScreenSizes`: 45 | - `screenSize` is equivalent to the current size of the screen following the current media query matching the size 46 | - `ScreenSizes` is a constant with the screen size value matching its size definition: 47 | ``` 48 | const ScreenSizes = { 49 | XS: 1, 50 | S: 2, 51 | M: 3, 52 | L: 4, 53 | XL: 5, 54 | }; 55 | ``` 56 | 57 | This allows you to mimic the CSS way of writing queries, but in a more verbose way in JavaScript. You can use the comparative operators `<,=,>` to determine what should be rendered on the screen. 58 | 59 | If you want to render something when the screen is smaller or equal to a small size: 60 | ``` 61 | if (screenSize <= ScreenSizes.S) { 62 | // Content for small screens and lower 63 | } 64 | else { 65 | // Content for screens larger than small 66 | } 67 | ``` 68 | 69 | You can also target specific sizes with the `===` operator: 70 | ``` 71 | if (screenSize === ScreenSizes.L) { 72 | // Only render this content when the screen is L (smaller than XL and larger than S) 73 | } 74 | ``` 75 | 76 | For anything larger than XL you should use the following condition: 77 | ``` 78 | if (screnSize > ScreenSizes.XL) { 79 | // Content for big screens 80 | } 81 | ``` 82 | 83 | ### Order of conditions 84 | As with CSS `@media` queries, you have to check the screen size in the same order, i.e. from largest to smallest, otherwise the first one will always apply. This is because a small screen still falls within a large one, but not vice versa. 85 | 86 | --- 87 | 88 | There's also another utility function called `getScreenSize` that returns exactly the contents of the value returned by the hook (properties of `screenSize`) (it's actually used internally in the hook) but that won't update the value based on the resize events. 89 | 90 | Example: 91 | 92 | ```js 93 | import { getScreenSize } from '@drawbotics/use-screen-size'; 94 | 95 | console.log(getScreenSize()); 96 | 97 | // on medium screen prints 98 | 3 99 | ``` -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@babel/preset-env', 4 | '@babel/preset-react', 5 | ], 6 | plugins: [ 7 | '@babel/plugin-proposal-export-default-from', 8 | ], 9 | }; 10 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@drawbotics/use-screen-size", 3 | "version": "2.0.1", 4 | "description": "Hook used to get the current screen size for responsive behaviour", 5 | "main": "lib/index.js", 6 | "author": "Nicolaos Moscholios ", 7 | "license": "UNLICENSED", 8 | "scripts": { 9 | "clean": "rimraf lib/", 10 | "build": "NODE_ENV=production babel src --out-dir lib/", 11 | "test:watch": "BABEL_ENV=test jest --watch", 12 | "prepublishOnly": "npm run clean && npm test && npm run build", 13 | "test": "jest" 14 | }, 15 | "repository": { 16 | "type": "git", 17 | "url": "https://github.com/Drawbotics/use-screen-size" 18 | }, 19 | "publishConfig": { 20 | "access": "public", 21 | "registry": "https://npm.pkg.github.com/" 22 | }, 23 | "husky": { 24 | "hooks": { 25 | "pre-commit": "lint-staged" 26 | } 27 | }, 28 | "lint-staged": { 29 | "*.{js,jsx}": "eslint --format node_modules/eslint-formatter-pretty --max-warnings=0" 30 | }, 31 | "peerDependencies": { 32 | "@drawbotics/drylus-style-vars": "^4", 33 | "react": "^16.8.x" 34 | }, 35 | "devDependencies": { 36 | "@babel/cli": "^7.6.0", 37 | "@babel/core": "^7.6.0", 38 | "@babel/plugin-proposal-export-default-from": "^7.5.2", 39 | "@babel/preset-env": "^7.6.0", 40 | "@babel/preset-react": "^7.0.0", 41 | "@drawbotics/drylus-style-vars": "^4.0.0-alpha.4", 42 | "babel-eslint": "^10.0.3", 43 | "babel-jest": "^24.9.0", 44 | "eslint": "^6.4.0", 45 | "eslint-formatter-pretty": "^2.1.1", 46 | "eslint-plugin-jest": "^22.17.0", 47 | "eslint-plugin-react": "^7.14.3", 48 | "eslint-plugin-react-hooks": "^2.0.1", 49 | "husky": "^3.0.5", 50 | "jest": "^24.9.0", 51 | "lint-staged": "^9.2.5", 52 | "react": "^16.9.0", 53 | "react-dom": "^16.9.0", 54 | "react-test-renderer": "^16.9.0" 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/__tests__/get-screen-size.test.js: -------------------------------------------------------------------------------- 1 | import getScreenSize from '../get-screen-size'; 2 | 3 | 4 | describe('get-screen-size', () => { 5 | let originalWidth; 6 | 7 | const SCREEN_MINISCULE = 100; 8 | const SCREEN_XS = 320; 9 | const SCREEN_S = 375; 10 | const SCREEN_M = 425; 11 | const SCREEN_L = 768; 12 | const SCREEN_XL = 1024; 13 | const SCREEN_HUGE = 1200; 14 | const SCREEN_MASSIVE = 3000; 15 | 16 | beforeAll(() => { 17 | originalWidth = window.innerWidth; 18 | }); 19 | 20 | afterEach(() => { 21 | window.innerWidth = originalWidth; 22 | }); 23 | 24 | it('correctly matches an extra small screen', () => { 25 | window.innerWidth = SCREEN_XS; 26 | 27 | const screenSize = getScreenSize(); 28 | expect(screenSize).toEqual(1); 29 | }); 30 | 31 | it('correctly matches a small screen', () => { 32 | window.innerWidth = SCREEN_S; 33 | 34 | const screenSize = getScreenSize(); 35 | expect(screenSize).toEqual(2); 36 | }); 37 | 38 | it('correctly matches an medium screen', () => { 39 | window.innerWidth = SCREEN_M; 40 | 41 | const screenSize = getScreenSize(); 42 | expect(screenSize).toEqual(3); 43 | }); 44 | 45 | it('correctly matches a large screen', () => { 46 | window.innerWidth = SCREEN_L; 47 | 48 | const screenSize = getScreenSize(); 49 | expect(screenSize).toEqual(4); 50 | }); 51 | 52 | it('correctly matches an extra large screen', () => { 53 | window.innerWidth = SCREEN_XL; 54 | 55 | const screenSize = getScreenSize(); 56 | expect(screenSize).toEqual(5); 57 | }); 58 | 59 | it('correctly matches a huge screen', () => { 60 | window.innerWidth = SCREEN_HUGE; 61 | 62 | const screenSize = getScreenSize(); 63 | expect(screenSize).toBeGreaterThan(5); 64 | }); 65 | 66 | it('it correctly matches a miniscule screen', () => { 67 | window.innerWidth = SCREEN_MINISCULE; 68 | 69 | const screenSize = getScreenSize(); 70 | expect(screenSize).toEqual(1); 71 | }); 72 | 73 | it('it matches a massive screen', () => { 74 | window.innerWidth = SCREEN_MASSIVE; 75 | 76 | const screenSize = getScreenSize(); 77 | expect(screenSize).toBeGreaterThan(5); 78 | }); 79 | }); 80 | -------------------------------------------------------------------------------- /src/__tests__/use-screen-size.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import { act } from 'react-dom/test-utils'; 4 | 5 | import useScreenSize from '../use-screen-size'; 6 | 7 | 8 | const TestComponent = () => { 9 | const { screenSize, ScreenSizes } = useScreenSize(); 10 | 11 | if (screenSize === ScreenSizes.M) { 12 | return
mediumOnlyContent
; 13 | } 14 | else if (screenSize >= ScreenSizes.M && screenSize <= ScreenSizes.XL) { 15 | return
betweenMediumAndXLContent
; 16 | } 17 | else if (screenSize <= ScreenSizes.S) { 18 | return
smallContent
; 19 | } 20 | else if (screenSize > ScreenSizes.XL) { 21 | return
largerThanHugeContent
; 22 | } 23 | else { 24 | return
defaultContent
25 | } 26 | }; 27 | 28 | 29 | describe('use-screen-size', () => { 30 | let container; 31 | let originalWidth; 32 | 33 | const SCREEN_XS = 320; 34 | const SCREEN_S = 375; 35 | const SCREEN_M = 425; 36 | const SCREEN_L = 768; 37 | const SCREEN_HUGE = 1200; 38 | 39 | beforeEach(() => { 40 | container = document.createElement('div'); 41 | document.body.appendChild(container); 42 | originalWidth = window.innerWidth; 43 | }); 44 | 45 | afterEach(() => { 46 | document.body.removeChild(container); 47 | container = null; 48 | window.innerWidth = originalWidth; 49 | }); 50 | 51 | it('renders content for screens smaller and equal to S', () => { 52 | window.innerWidth = SCREEN_XS; 53 | 54 | act(() => { ReactDOM.render(, container) }); 55 | 56 | const rootDiv = container.querySelector('div'); 57 | expect(rootDiv.textContent).toBe('smallContent'); 58 | }); 59 | 60 | it('renders content for screens equal to M size (between S (excl) and M (incl) when size is M', () => { 61 | window.innerWidth = SCREEN_M; 62 | 63 | act(() => { ReactDOM.render(, container) }); 64 | 65 | const rootDiv = container.querySelector('div'); 66 | expect(rootDiv.textContent).toBe('mediumOnlyContent'); 67 | }); 68 | 69 | it('does not render content for screens equal to M size (between S (excl) and M (incl) when size is S', () => { 70 | window.innerWidth = SCREEN_S; 71 | 72 | act(() => { ReactDOM.render(, container) }); 73 | 74 | const rootDiv = container.querySelector('div'); 75 | expect(rootDiv.textContent).not.toBe('mediumOnlyContent'); 76 | }); 77 | 78 | it('renders content between two sizes', () => { 79 | window.innerWidth = SCREEN_L; 80 | 81 | act(() => { ReactDOM.render(, container) }); 82 | 83 | const rootDiv = container.querySelector('div'); 84 | expect(rootDiv.textContent).toBe('betweenMediumAndXLContent'); 85 | }); 86 | 87 | it('renders content for huge screens', () => { 88 | window.innerWidth = SCREEN_HUGE; 89 | 90 | act(() => { ReactDOM.render(, container) }); 91 | 92 | const rootDiv = container.querySelector('div'); 93 | expect(rootDiv.textContent).toBe('largerThanHugeContent'); 94 | }); 95 | }); 96 | -------------------------------------------------------------------------------- /src/get-screen-size.js: -------------------------------------------------------------------------------- 1 | import sv from '@drawbotics/drylus-style-vars'; 2 | 3 | 4 | function _parseMaxWidth(mediaQuery) { 5 | const [ , maxWidth ] = mediaQuery.match(/max-width: (\d+)/); 6 | return parseInt(maxWidth); 7 | } 8 | 9 | 10 | function _parseMinWidth(mediaQuery) { 11 | const [ , minWidth ] = mediaQuery.match(/min-width: (\d+)/); 12 | return parseInt(minWidth); 13 | } 14 | 15 | 16 | export default function getScreenSize() { 17 | if (window.innerWidth <= _parseMaxWidth(sv.screenXs)) { 18 | return 1; 19 | } 20 | else if (window.innerWidth <= _parseMaxWidth(sv.screenS)) { 21 | return 2; 22 | } 23 | else if (window.innerWidth <= _parseMaxWidth(sv.screenM)) { 24 | return 3; 25 | } 26 | else if (window.innerWidth <= _parseMaxWidth(sv.screenL)) { 27 | return 4; 28 | } 29 | else if (window.innerWidth <= _parseMaxWidth(sv.screenXl)) { 30 | return 5; 31 | } 32 | else if (window.innerWidth > _parseMinWidth(sv.screenHuge)) { 33 | return window.innerWidth; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | export useScreenSize from './use-screen-size'; 2 | export getScreenSize from './get-screen-size'; 3 | -------------------------------------------------------------------------------- /src/use-screen-size.js: -------------------------------------------------------------------------------- 1 | import { useState, useEffect } from 'react'; 2 | 3 | import getScreenSize from './get-screen-size'; 4 | 5 | 6 | const ScreenSizes = { 7 | XS: 1, 8 | S: 2, 9 | M: 3, 10 | L: 4, 11 | XL: 5, 12 | }; 13 | 14 | 15 | export default function useScreenSize() { 16 | const [ size, setSize ] = useState(getScreenSize()); 17 | 18 | useEffect(() => { 19 | const handleResize = () => { 20 | setSize(getScreenSize()); 21 | }; 22 | 23 | window.addEventListener('resize', handleResize); 24 | return () => { 25 | window.removeEventListener('resize', handleResize); 26 | }; 27 | }, []); 28 | 29 | return { screenSize: size, ScreenSizes }; 30 | } 31 | --------------------------------------------------------------------------------