├── .circleci
└── config.yml
├── .editorconfig
├── .eslintignore
├── .eslintrc
├── .gitignore
├── .prettierrc
├── .travis.yml
├── .vscode
└── settings.json
├── LICENSE
├── README.md
├── example
├── README.md
├── package.json
├── public
│ ├── favicon.ico
│ ├── index.html
│ └── manifest.json
├── src
│ ├── App.js
│ ├── App.test.js
│ ├── index.css
│ └── index.js
└── yarn.lock
├── package.json
├── src
├── .eslintrc
├── index.tsx
├── lib
│ ├── __test__
│ │ ├── useBreakpoints.mock.js
│ │ └── useBreakpoints.test.ts
│ ├── useBreakpoints.interface.ts
│ └── useBreakpoints.ts
├── react-app-env.d.ts
└── utils
│ ├── __tests__
│ ├── isValidUnit.test.ts
│ └── makeMediaQueryList.ts
│ ├── isBrowser.ts
│ ├── isValidUnit.ts
│ └── makeMediaQueryList.ts
├── tsconfig.json
├── tsconfig.test.json
└── yarn.lock
/.circleci/config.yml:
--------------------------------------------------------------------------------
1 | version: 2.1
2 | orbs:
3 | node: circleci/node@1.1.6
4 | jobs:
5 | build-and-test:
6 | executor:
7 | name: node/default
8 | steps:
9 | - checkout
10 | - node/with-cache:
11 | steps:
12 | - run:
13 | name: 📦 Install packages
14 | command: yarn install
15 | - run:
16 | name: 🧹 Linting
17 | command: yarn test:lint
18 | - run:
19 | name: 🧪 Testing
20 | command: yarn test
21 | publish:
22 | executor:
23 | name: node/default
24 | steps:
25 | - checkout
26 | - run:
27 | name: 📦 Install packages
28 | command: yarn install
29 | - run:
30 | name: 🚧 Building
31 | command: yarn build
32 | - run:
33 | name: 🚀 Publish to Github Package
34 | command: yarn semantic-release || true
35 |
36 | workflows:
37 | build-and-test:
38 | jobs:
39 | - build-and-test
40 | - publish:
41 | requires:
42 | - build-and-test
43 | filters:
44 | branches:
45 | only: master
46 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | charset = utf-8
5 | indent_style = space
6 | indent_size = 2
7 | end_of_line = lf
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | build/
2 | dist/
3 | node_modules/
4 | .snapshots/
5 | *.min.js
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "parser": "@typescript-eslint/parser",
3 | "extends": [
4 | "standard",
5 | "standard-react",
6 | "plugin:prettier/recommended",
7 | "prettier/standard",
8 | "prettier/react",
9 | "plugin:@typescript-eslint/eslint-recommended"
10 | ],
11 | "env": {
12 | "node": true
13 | },
14 | "parserOptions": {
15 | "ecmaVersion": 2020,
16 | "ecmaFeatures": {
17 | "legacyDecorators": true,
18 | "jsx": true
19 | }
20 | },
21 | "settings": {
22 | "react": {
23 | "version": "16"
24 | }
25 | },
26 | "rules": {
27 | "space-before-function-paren": 0,
28 | "react/prop-types": 0,
29 | "react/jsx-handler-names": 0,
30 | "react/jsx-fragments": 0,
31 | "react/no-unused-prop-types": 0,
32 | "import/export": 0
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | # See https://help.github.com/ignore-files/ for more about ignoring files.
3 |
4 | # dependencies
5 | node_modules
6 |
7 | # builds
8 | build
9 | dist
10 | .rpt2_cache
11 |
12 | # misc
13 | .DS_Store
14 | .env
15 | .env.local
16 | .env.development.local
17 | .env.test.local
18 | .env.production.local
19 |
20 | npm-debug.log*
21 | yarn-debug.log*
22 | yarn-error.log*
23 |
24 | # Development env
25 | example
26 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "singleQuote": true,
3 | "jsxSingleQuote": true,
4 | "semi": false,
5 | "tabWidth": 2,
6 | "bracketSpacing": true,
7 | "jsxBracketSameLine": false,
8 | "arrowParens": "always",
9 | "trailingComma": "none"
10 | }
11 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - 12
4 | - 10
5 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | }
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Nabil Benhamou (TheBilTheory)
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 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # useBreakpoints
2 |
3 | [](https://circleci.com/gh/thebiltheory/useBreakpoints/tree/master) [](https://www.npmjs.com/package/@thebiltheory/usebreakpoints)  
4 |
5 | React Hook to map value/breakpoint the styled-component way.
6 |
7 | ## Example/ Demo
8 |
9 | [](https://codesandbox.io/s/amazing-edison-x9g17?fontsize=14&hidenavigation=1&theme=dark)
10 |
11 | ## Install
12 |
13 | ```bash
14 | npm install --save @thebiltheory/usebreakpoints
15 | ```
16 |
17 | or
18 |
19 | ```bash
20 | yarn add @thebiltheory/usebreakpoints
21 | ```
22 |
23 | ## Usage
24 |
25 | ```tsx
26 | const [value, currentBreakpoint] = useBreakpoints(
27 | ['mobile', 'tablet', 'desktop', 'huge screen'],
28 | [576, 768, 992, 1600]
29 | )
30 |
31 | const [value, currentBreakpoint] = useBreakpoints(
32 | [1, 2, 3, 4],
33 | ['576px', '768px', '992px', '1200px']
34 | )
35 |
36 | import theme from 'src/theme'
37 | const [value, currentBreakpoint] = useBreakpoints(
38 | [1, 2, 3, 4],
39 | theme.breakpoints
40 | )
41 | ```
42 |
43 | ## License
44 |
45 | MIT © [TheBilTheory](https://github.com/TheBilTheory)
46 |
--------------------------------------------------------------------------------
/example/README.md:
--------------------------------------------------------------------------------
1 | This example was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
2 |
3 | It is linked to the usebreakpoints package in the parent directory for development purposes.
4 |
5 | You can run `yarn install` and then `yarn start` to test your package.
6 |
--------------------------------------------------------------------------------
/example/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "usebreakpoints-example",
3 | "homepage": "https://TheBilTheory.github.io/usebreakpoints",
4 | "version": "0.0.0",
5 | "private": true,
6 | "dependencies": {
7 | "react": "link:../node_modules/react",
8 | "react-dom": "link:../node_modules/react-dom",
9 | "react-scripts": "link:../node_modules/react-scripts",
10 | "usebreakpoints": "link:.."
11 | },
12 | "scripts": {
13 | "start": "node ../node_modules/react-scripts/bin/react-scripts.js start",
14 | "build": "node ../node_modules/react-scripts/bin/react-scripts.js build",
15 | "test": "node ../node_modules/react-scripts/bin/react-scripts.js test",
16 | "eject": "node ../node_modules/react-scripts/bin/react-scripts.js eject"
17 | },
18 | "eslintConfig": {
19 | "extends": "react-app"
20 | },
21 | "browserslist": [
22 | ">0.2%",
23 | "not dead",
24 | "not ie <= 11",
25 | "not op_mini all"
26 | ]
27 | }
28 |
--------------------------------------------------------------------------------
/example/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thebiltheory/useBreakpoints/b030646d8ae5f1bf0c7bbbb63d05bf8005d8d60d/example/public/favicon.ico
--------------------------------------------------------------------------------
/example/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
10 |
11 |
12 |
16 |
17 |
18 |
27 | usebreakpoints
28 |
29 |
30 |
31 |
34 |
35 |
36 |
37 |
47 |
48 |
49 |
--------------------------------------------------------------------------------
/example/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "usebreakpoints",
3 | "name": "usebreakpoints",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | }
10 | ],
11 | "start_url": ".",
12 | "display": "standalone",
13 | "theme_color": "#000000",
14 | "background_color": "#ffffff"
15 | }
16 |
--------------------------------------------------------------------------------
/example/src/App.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import useBreakpoints from 'usebreakpoints'
3 |
4 | const App = () => {
5 | const breakpoints = [576, 768, 992, 1200]
6 |
7 | const stringBreakpoints = ['576px', '768px', '992px', '1200px']
8 |
9 | const [value, breakpoint] = useBreakpoints([1, 2, 3, 4], breakpoints)
10 |
11 | const [componentId] = useBreakpoints(
12 | ['sad', 'neutral', 'happy', 'rocket'],
13 | breakpoints
14 | )
15 |
16 | const [mood] = useBreakpoints(
17 | ['sad', 'neutral', 'happy', 'rocket'],
18 | stringBreakpoints
19 | )
20 |
21 | const list = {
22 | sad: (
23 |
24 | 😢
25 |
26 | ),
27 | neutral: (
28 |
29 | 😐
30 |
31 | ),
32 | happy: (
33 |
34 | 😆
35 |
36 | ),
37 | rocket: (
38 |
39 | 🚀
40 |
41 | )
42 | }
43 |
44 | const Components = ({ current }) => {
45 | return list[current] ? list[current] : nothing
46 | }
47 |
48 | return (
49 |
57 |
65 |
71 | useBreakpoints
72 |
73 |
{breakpoint}px
74 | I'm in a {mood} mood
75 |
83 |
84 |
85 |
86 |
87 | )
88 | }
89 |
90 | export default App
91 |
--------------------------------------------------------------------------------
/example/src/App.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDOM from 'react-dom'
3 | import App from './App'
4 |
5 | it('renders without crashing', () => {
6 | const div = document.createElement('div')
7 | ReactDOM.render(, div)
8 | ReactDOM.unmountComponentAtNode(div)
9 | })
10 |
--------------------------------------------------------------------------------
/example/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | padding: 0;
4 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
5 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
6 | sans-serif;
7 | -webkit-font-smoothing: antialiased;
8 | -moz-osx-font-smoothing: grayscale;
9 | }
10 |
11 | code {
12 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
13 | monospace;
14 | }
15 |
--------------------------------------------------------------------------------
/example/src/index.js:
--------------------------------------------------------------------------------
1 | import './index.css'
2 |
3 | import React from 'react'
4 | import ReactDOM from 'react-dom'
5 | import App from './App'
6 |
7 | ReactDOM.render(, document.getElementById('root'))
8 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@thebiltheory/usebreakpoints",
3 | "version": "0.0.0-development",
4 | "description": "React Hook to map value/breakpoint the styled-component way.",
5 | "author": "TheBilTheory",
6 | "license": "MIT",
7 | "repository": "TheBilTheory/usebreakpoints",
8 | "main": "dist/index.js",
9 | "module": "dist/index.modern.js",
10 | "source": "src/index.tsx",
11 | "engines": {
12 | "node": ">=10"
13 | },
14 | "scripts": {
15 | "build": "microbundle-crl --no-compress --format modern,cjs",
16 | "start": "microbundle-crl watch --no-compress --format modern,cjs",
17 | "prepublish": "run-s build",
18 | "test": "run-s test:unit test:lint test:build",
19 | "test:build": "run-s build",
20 | "test:lint": "eslint .",
21 | "test:unit": "cross-env CI=1 react-scripts test --env=jsdom",
22 | "test:watch": "react-scripts test --env=jsdom",
23 | "predeploy": "cd example && yarn install && yarn run build",
24 | "deploy": "gh-pages -d example/build",
25 | "semantic-release": "semantic-release"
26 | },
27 | "peerDependencies": {
28 | "react": "^16.0.0"
29 | },
30 | "devDependencies": {
31 | "@semantic-release/github": "^7.0.5",
32 | "@testing-library/react-hooks": "^3.2.1",
33 | "@types/jest": "^25.1.4",
34 | "@types/react": "^16.9.27",
35 | "@typescript-eslint/eslint-plugin": "^2.26.0",
36 | "@typescript-eslint/parser": "^2.26.0",
37 | "babel-eslint": "^10.0.3",
38 | "condition-circle": "^2.0.2",
39 | "cross-env": "^7.0.2",
40 | "eslint": "^6.8.0",
41 | "eslint-config-prettier": "^6.7.0",
42 | "eslint-config-standard": "^14.1.0",
43 | "eslint-config-standard-react": "^9.2.0",
44 | "eslint-plugin-import": "^2.18.2",
45 | "eslint-plugin-node": "^11.0.0",
46 | "eslint-plugin-prettier": "^3.1.1",
47 | "eslint-plugin-promise": "^4.2.1",
48 | "eslint-plugin-react": "^7.17.0",
49 | "eslint-plugin-standard": "^4.0.1",
50 | "gh-pages": "^2.2.0",
51 | "microbundle-crl": "^0.13.8",
52 | "npm-run-all": "^4.1.5",
53 | "prettier": "^2.0.4",
54 | "react": "^16.13.1",
55 | "react-dom": "^16.13.1",
56 | "react-scripts": "^3.4.1",
57 | "react-test-renderer": "^16.13.1",
58 | "semantic-release": "^17.0.7"
59 | },
60 | "files": [
61 | "dist"
62 | ],
63 | "publishConfig": {
64 | "access": "public",
65 | "registry": "https://registry.npmjs.org/",
66 | "tag": "latest"
67 | },
68 | "release": {
69 | "branch": "master",
70 | "verifyConditions": "condition-circle"
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/src/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "jest": true
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/src/index.tsx:
--------------------------------------------------------------------------------
1 | import useBreakpoints from './lib/useBreakpoints'
2 | export default useBreakpoints
3 |
--------------------------------------------------------------------------------
/src/lib/__test__/useBreakpoints.mock.js:
--------------------------------------------------------------------------------
1 | Object.defineProperty(window, 'matchMedia', {
2 | writable: true,
3 | value: jest.fn().mockImplementation((query) => ({
4 | matches: false,
5 | media: query,
6 | onchange: null,
7 | addListener: jest.fn(), // deprecated
8 | removeListener: jest.fn(), // deprecated
9 | addEventListener: jest.fn(),
10 | removeEventListener: jest.fn(),
11 | dispatchEvent: jest.fn()
12 | }))
13 | })
14 |
--------------------------------------------------------------------------------
/src/lib/__test__/useBreakpoints.test.ts:
--------------------------------------------------------------------------------
1 | import './useBreakpoints.mock'
2 | import { renderHook } from '@testing-library/react-hooks'
3 | import useBreakpoints from '../useBreakpoints'
4 |
5 | describe('useBreakpoints', () => {
6 | it('Should return values for desktop breakpoint', () => {
7 | const { result } = renderHook(() =>
8 | useBreakpoints([1, 2, 3, 4], [576, 768, 992, 1200])
9 | )
10 |
11 | expect(result.current[0]).toBe(4)
12 | expect(result.current[1]).toBe(1200)
13 | })
14 |
15 | it('Should return values for mobile breakpoint', () => {
16 | Object.defineProperty(window, 'innerWidth', {
17 | writable: true,
18 | configurable: true,
19 | value: 500
20 | })
21 |
22 | const { result } = renderHook(() =>
23 | useBreakpoints([1, 2, 3, 4], [576, 768, 992, 1200])
24 | )
25 |
26 | expect(result.current[0]).toBe(1)
27 | expect(result.current[1]).toBe(576)
28 | })
29 |
30 | it('Should return values for tablet breakpoint', () => {
31 | Object.defineProperty(window, 'innerWidth', {
32 | writable: true,
33 | configurable: true,
34 | value: 800
35 | })
36 |
37 | const { result } = renderHook(() =>
38 | useBreakpoints([1, 2, 3, 4], [576, 768, 992, 1200])
39 | )
40 |
41 | expect(result.current[0]).toBe(3)
42 | expect(result.current[1]).toBe(992)
43 | })
44 | })
45 |
--------------------------------------------------------------------------------
/src/lib/useBreakpoints.interface.ts:
--------------------------------------------------------------------------------
1 | export type TMediaHandler = (event: MediaQueryListEvent) => void
2 | export type TBreakPointsParam = number[]
3 | export type TValuesParam = string[] | number[]
4 |
--------------------------------------------------------------------------------
/src/lib/useBreakpoints.ts:
--------------------------------------------------------------------------------
1 | import { useEffect, useCallback, useState } from 'react'
2 | import {
3 | TMediaHandler,
4 | TValuesParam,
5 | TBreakPointsParam
6 | } from './useBreakpoints.interface'
7 | import makeMediaQueryList from '../utils/makeMediaQueryList'
8 |
9 | /**
10 | * useBreakpoint
11 | *
12 | * @param {*} values
13 | * @param {*} breakdpoints
14 | *
15 | * Documentation:
16 | * https://developer.mozilla.org/en-US/docs/Web/API/MediaQueryList/addListener
17 | * https://developer.mozilla.org/en-US/docs/Web/API/Window/matchMedia
18 | */
19 | export default function useBreakpoints(
20 | values: TValuesParam,
21 | breakdpoints: TBreakPointsParam
22 | ) {
23 | const [value, setValue] = useState()
24 | const [breakdpoint, setBreakpoint] = useState(0)
25 |
26 | const mediaQueryList = makeMediaQueryList(breakdpoints)
27 |
28 | /**
29 | * Keep hold of the listeners
30 | * to remove them on unmount.
31 | */
32 | const mediaHandlers: TMediaHandler[] = []
33 |
34 | const mediaHandler = useCallback(
35 | (index: number): TMediaHandler => {
36 | return (e: MediaQueryListEvent) => {
37 | if (!e.matches) return
38 | setBreakpoint(breakdpoints[index])
39 | setValue(values[index])
40 | }
41 | },
42 | [breakdpoints, values]
43 | )
44 |
45 | useEffect(() => {
46 | const initial = breakdpoints.findIndex(
47 | (breakpoint: number) => window.innerWidth < breakpoint
48 | )
49 |
50 | if (initial === -1) {
51 | setBreakpoint(breakdpoints[breakdpoints.length - 1])
52 | setValue(values[breakdpoints.length - 1])
53 | } else {
54 | setBreakpoint(breakdpoints[initial])
55 | setValue(values[initial])
56 | }
57 | }, [])
58 |
59 | useEffect(() => {
60 | mediaQueryList.forEach((mediaQuery: any, index: any) => {
61 | mediaQuery.addListener(mediaHandler(index))
62 | mediaHandlers.push(mediaHandler(index))
63 | })
64 |
65 | return () => {
66 | mediaQueryList.forEach((mediaQuery: any, index: any) =>
67 | mediaQuery.removeListener(mediaHandlers[index])
68 | )
69 | }
70 | }, [values, breakdpoints])
71 |
72 | return [value, breakdpoint]
73 | }
74 |
--------------------------------------------------------------------------------
/src/react-app-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/src/utils/__tests__/isValidUnit.test.ts:
--------------------------------------------------------------------------------
1 | import '../../lib/__test__/useBreakpoints.mock'
2 | import isValidUnit from '../isValidUnit'
3 |
4 | describe('Validates breakpoint strings with units', () => {
5 | it('Should throw an error if an invalid CSS unit is provided', () => {
6 | expect(() => isValidUnit('1000pxx')).toThrowError(
7 | `"pxx" is not a valid CSS unit.`
8 | )
9 | })
10 |
11 | it('Should throw an error if an invalid CSS unit is provided', () => {
12 | expect(() => isValidUnit('1000')).toThrowError(`
13 | Breakpoint "1000" does not have a valid length unit.
14 | Did you mean to add cm | mm | in | px | pt | pc | em | ex | ch | rem | vw | vh | vmin | vmax | % ?
15 | `)
16 | })
17 |
18 | it('Should return true if a valid CSS is provided', () => {
19 | expect(() => isValidUnit('1000px')).toBeTruthy()
20 | })
21 | })
22 |
--------------------------------------------------------------------------------
/src/utils/__tests__/makeMediaQueryList.ts:
--------------------------------------------------------------------------------
1 | import '../../lib/__test__/useBreakpoints.mock'
2 | import makeMediaQueryList from '../makeMediaQueryList'
3 |
4 | describe('Generates media query listeners', () => {
5 | const providedBreakPointsAreNumbers = [
6 | window.matchMedia('(min-width: 300px) and (max-width: 500px)'),
7 | window.matchMedia('(min-width: 500px) and (max-width: 800px)'),
8 | window.matchMedia('(min-width: 800px) and (max-width: 1200px)'),
9 | window.matchMedia('(min-width: 1200px) ')
10 | ]
11 |
12 | const providedBreakPointsAreStrings = [
13 | window.matchMedia('(min-width: 300em) and (max-width: 500px)'),
14 | window.matchMedia('(min-width: 500px) and (max-width: 800cm)'),
15 | window.matchMedia('(min-width: 800cm) and (max-width: 1200rem)'),
16 | window.matchMedia('(min-width: 1200rem) ')
17 | ]
18 |
19 | it('Should create an array of mediaQueries with an array of type numbers', () => {
20 | const expected = JSON.stringify(makeMediaQueryList([300, 500, 800, 1200]))
21 | const received = JSON.stringify(providedBreakPointsAreNumbers)
22 |
23 | expect(expected).toEqual(received)
24 | })
25 |
26 | it('Should create an array of mediaQueries with an array of type strings', () => {
27 | const expected = JSON.stringify(
28 | makeMediaQueryList(['300em', '500px', '800cm', '1200rem'])
29 | )
30 | const received = JSON.stringify(providedBreakPointsAreStrings)
31 |
32 | expect(expected).toEqual(received)
33 | })
34 | })
35 |
--------------------------------------------------------------------------------
/src/utils/isBrowser.ts:
--------------------------------------------------------------------------------
1 | export const isBrowser = typeof window !== 'undefined'
2 |
--------------------------------------------------------------------------------
/src/utils/isValidUnit.ts:
--------------------------------------------------------------------------------
1 | const units: string[] = [
2 | 'cm',
3 | 'mm',
4 | 'in',
5 | 'px',
6 | 'pt',
7 | 'pc',
8 | 'em',
9 | 'ex',
10 | 'ch',
11 | 'rem',
12 | 'vw',
13 | 'vh',
14 | 'vmin',
15 | 'vmax',
16 | '%'
17 | ]
18 |
19 | export default function isValidUnit(breakpoint: string) {
20 | const unit: string[] = breakpoint.match(/(\D+)/g) || ['']
21 |
22 | /**
23 | * @todo: Customize the Erro output
24 | * to point to the right file and file number
25 | * in the stack trace
26 | */
27 | if (unit[0] === '') {
28 | throw new Error(`
29 | Breakpoint "${breakpoint}" does not have a valid length unit.
30 | Did you mean to add ${units.join(' | ')} ?
31 | `)
32 | }
33 |
34 | if (!units.includes(unit[0])) {
35 | throw new Error(`"${unit}" is not a valid CSS unit.`)
36 | }
37 |
38 | return true
39 | }
40 |
--------------------------------------------------------------------------------
/src/utils/makeMediaQueryList.ts:
--------------------------------------------------------------------------------
1 | import { isBrowser } from './isBrowser'
2 | import isValidUnit from './isValidUnit'
3 |
4 | type TBreakpoint = number | string
5 |
6 | export default function makeMediaQueryList(breakpoints: TBreakpoint[]) {
7 | return breakpoints.map((breakpoint: TBreakpoint, index: number) => {
8 | const maxWidth = breakpoints[index + 1]
9 |
10 | if (!isBrowser) return
11 |
12 | if (typeof breakpoint === 'string' && isValidUnit(breakpoint)) {
13 | return window.matchMedia(
14 | `(min-width: ${breakpoint}) ${
15 | maxWidth ? `and (max-width: ${maxWidth})` : ''
16 | }`
17 | )
18 | }
19 |
20 | return window.matchMedia(
21 | `(min-width: ${breakpoint}px) ${
22 | maxWidth ? `and (max-width: ${maxWidth}px)` : ''
23 | }`
24 | )
25 | })
26 | }
27 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "outDir": "dist",
4 | "module": "esnext",
5 | "lib": [
6 | "dom",
7 | "esnext"
8 | ],
9 | "moduleResolution": "node",
10 | "jsx": "react",
11 | "sourceMap": true,
12 | "declaration": true,
13 | "esModuleInterop": true,
14 | "noImplicitReturns": true,
15 | "noImplicitThis": true,
16 | "noImplicitAny": true,
17 | "strictNullChecks": true,
18 | "suppressImplicitAnyIndexErrors": true,
19 | "noUnusedLocals": true,
20 | "noUnusedParameters": true,
21 | "allowSyntheticDefaultImports": true,
22 | "target": "es5",
23 | "allowJs": true,
24 | "skipLibCheck": true,
25 | "strict": true,
26 | "forceConsistentCasingInFileNames": true,
27 | "resolveJsonModule": true,
28 | "isolatedModules": true,
29 | "noEmit": true
30 | },
31 | "include": [
32 | "src"
33 | ],
34 | "exclude": [
35 | "node_modules",
36 | "dist",
37 | "example"
38 | ]
39 | }
40 |
--------------------------------------------------------------------------------
/tsconfig.test.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "compilerOptions": {
4 | "module": "commonjs"
5 | }
6 | }
--------------------------------------------------------------------------------