diff --git a/package.json b/package.json index 0ed56a6..fb113ba 100644 --- a/package.json +++ b/package.json @@ -45,10 +45,12 @@ "@testing-library/jest-dom": "^5.11.10", "@testing-library/react": "^11.2.6", "@testing-library/react-hooks": "^5.1.1", + "@types/css-mediaquery": "^0.1.1", "@types/jest": "^26.0.22", "@types/react": "^17.0.3", "@typescript-eslint/eslint-plugin": "^4.21.0", "@typescript-eslint/parser": "^4.21.0", + "css-mediaquery": "^0.1.2", "eslint": "^7.23.0", "eslint-config-prettier": "^8.1.0", "eslint-plugin-import": "^2.22.1", diff --git a/readme.md b/readme.md index a65f43e..b5efe7c 100644 --- a/readme.md +++ b/readme.md @@ -12,6 +12,7 @@ This package includes different tools that support you with common tasks. - [useInterval](#useinterval) - [Async Data](#async-data) - [LocationProvider](#locationprovider) + - [useMatchMediaQuery](#usematchmediaquery) - [Build & Publish](#build--publish) - [Information](#information) @@ -149,6 +150,20 @@ const MyComponent = () => { } ``` +### useMatchMediaQuery + +This hook is based on the `window.matchQuery` API and can be used to find out if a certain media query matches the current window. + +```tsx +import { useMatchMediaQuery } from '@aboutbits/react-toolbox' + +const TestComponent = () => { + const matches = useMatchMediaQuery('(min-width : 500px)') + if (matches) return
visible
+ return null +} +``` + ## Build & Publish To publish the package commit all changes and push them to main. Then run one of the following commands locally: diff --git a/src/index.ts b/src/index.ts index cfec293..dd89387 100644 --- a/src/index.ts +++ b/src/index.ts @@ -6,6 +6,7 @@ import { LocationContext, LocationContextValue, } from './location-provider/LocationProvider' +import { useMatchMediaQuery } from './useMatchMediaQuery/useMatchMediaQuery' export { useInterval, @@ -15,4 +16,5 @@ export { LocationProvider, LocationContext, LocationContextValue, + useMatchMediaQuery, } diff --git a/src/useMatchMediaQuery/__test__/useMatchMediaQuery.test.tsx b/src/useMatchMediaQuery/__test__/useMatchMediaQuery.test.tsx new file mode 100644 index 0000000..d377555 --- /dev/null +++ b/src/useMatchMediaQuery/__test__/useMatchMediaQuery.test.tsx @@ -0,0 +1,46 @@ +import React from 'react' +import isMatching from 'css-mediaquery' +import { render, RenderResult } from '@testing-library/react' +import { useMatchMediaQuery } from '../useMatchMediaQuery' + +beforeEach(() => { + // mock window.matchMedia (use 'css-mediaquery') + window.matchMedia = jest.fn().mockImplementation((query) => { + return { + addListener: jest.fn(), + media: query, + matches: isMatching.match(query, { + width: 500, + }), + } + }) +}) + +const TestComponent = ({ query }: { query: string }) => { + const isMatched = useMatchMediaQuery(query) + if (isMatched) return
visible
+ return
hidden
+} + +function renderTestComponent(query: string): RenderResult { + return render() +} + +describe('useReactSimpleMatchMedia', () => { + it('should be visible when min width matches', () => { + const { getByText } = renderTestComponent('(min-width : 500px)') + expect(getByText(/visible/i)).toBeInTheDocument() + }) + + it('should not be visible min does not match', () => { + const { getByText } = renderTestComponent('(min-width : 600px)') + expect(getByText(/hidden/i)).toBeInTheDocument() + }) + + it('should be visible if between matches', () => { + const { getByText } = renderTestComponent( + '(min-width : 500px) and (max-width: 600px)' + ) + expect(getByText(/visible/i)).toBeInTheDocument() + }) +}) diff --git a/src/useMatchMediaQuery/useMatchMediaQuery.ts b/src/useMatchMediaQuery/useMatchMediaQuery.ts new file mode 100644 index 0000000..eb2c205 --- /dev/null +++ b/src/useMatchMediaQuery/useMatchMediaQuery.ts @@ -0,0 +1,26 @@ +import { useState, useEffect } from 'react' + +function useMatchMediaQuery(query: string): boolean | undefined { + if (typeof window !== 'object' || !window.matchMedia) return + + const [matches, setMatches] = useState(window.matchMedia(query).matches) + + useEffect(() => { + const media = window.matchMedia(query) + + if (media.matches !== matches) setMatches(media.matches) + + const listener = () => setMatches(media.matches) + + if (media.addEventListener) { + media.addEventListener('change', listener) + } + + return () => + media.removeEventListener && media.removeEventListener('change', listener) + }, [matches, query]) + + return matches +} + +export { useMatchMediaQuery }