diff --git a/docs/hooks/useWindowSize.ts b/docs/hooks/useWindowSize.ts index f461e2c..a57e417 100644 --- a/docs/hooks/useWindowSize.ts +++ b/docs/hooks/useWindowSize.ts @@ -19,7 +19,7 @@ export function useWindowSize() { outerWidth: window.outerWidth }; } else { - console.error('ReferenceError: typeof window is undefined'); + console.error('ReferenceError: typeof "window" is undefined'); return { innerHeight: 0, diff --git a/packages/media/__mocks__/useMatchMedia.mock.ts b/packages/media/__mocks__/useMatchMedia.mock.ts new file mode 100644 index 0000000..c7fbf76 --- /dev/null +++ b/packages/media/__mocks__/useMatchMedia.mock.ts @@ -0,0 +1,18 @@ +import mediaQuery from 'css-mediaquery'; + +Object.defineProperty(window, 'matchMedia', { + writable: true, + value: jest.fn().mockImplementation(query => ({ + matches: mediaQuery.match(query, { + width: window.innerWidth, + height: window.innerHeight + }), + media: query, + onchange: null, + addListener: jest.fn(), // Deprecated + removeListener: jest.fn(), // Deprecated + addEventListener: jest.fn(), + removeEventListener: jest.fn(), + dispatchEvent: jest.fn() + })) +}); diff --git a/packages/media/__tests__/useMatchMedia.test.ts b/packages/media/__tests__/useMatchMedia.test.ts new file mode 100644 index 0000000..8d0cd52 --- /dev/null +++ b/packages/media/__tests__/useMatchMedia.test.ts @@ -0,0 +1,10 @@ +import { renderHook } from '@testing-library/react-hooks'; +import { useMatchMedia } from '../src/useMatchMedia'; +import '../__mocks__/useMatchMedia.mock'; + +describe('useMatchMedia - checks for media queries via window.matchMedia', () => { + test('should match media query', () => { + const { result } = renderHook(() => useMatchMedia('(min-width: 600px)')); + expect(result.current).toBe(true); + }); +}); diff --git a/packages/media/__tests__/useWindowSize.test.ts b/packages/media/__tests__/useWindowSize.test.ts new file mode 100644 index 0000000..2c14e28 --- /dev/null +++ b/packages/media/__tests__/useWindowSize.test.ts @@ -0,0 +1,14 @@ +import { renderHook } from '@testing-library/react-hooks'; +import { useWindowSize } from '../src/useWindowSize'; + +describe('useWindowSize - gets window dimensions', () => { + test('should return window dimensions', () => { + const { result } = renderHook(() => useWindowSize()); + expect(result.current).toStrictEqual({ + innerHeight: 768, + innerWidth: 1024, + outerHeight: 768, + outerWidth: 1024 + }); + }); +}); diff --git a/packages/media/package.json b/packages/media/package.json index 19b1b77..bd4d384 100644 --- a/packages/media/package.json +++ b/packages/media/package.json @@ -34,7 +34,9 @@ "homepage": "https://github.com/heyjul3s/artifak#readme", "devDependencies": { "@artifak/bundler": "^1.1.3", + "@testing-library/react-hooks": "^3.7.0", "@types/styled-components": "^5.1.3", + "css-mediaquery": "^0.1.2", "styled-components": "^5.2.1" }, "peerDependencies": { diff --git a/packages/media/src/index.ts b/packages/media/src/index.ts index 172f605..2ccb06f 100644 --- a/packages/media/src/index.ts +++ b/packages/media/src/index.ts @@ -6,6 +6,8 @@ export { MediaInputQueries } from './mediaInput'; export { MediaTypes } from './mediaTypes'; export { MediaAccessibilityQueries } from './mediaAccessibilty'; export { MediaDisplayQueries } from './mediaDisplay'; +export { useMatchMedia } from './useMatchMedia'; +export { useWindowSize } from './useWindowSize'; export { all, diff --git a/packages/media/src/useMatchMedia.ts b/packages/media/src/useMatchMedia.ts new file mode 100644 index 0000000..7cf080b --- /dev/null +++ b/packages/media/src/useMatchMedia.ts @@ -0,0 +1,26 @@ +import { useRef, useState, useCallback, useEffect } from 'react'; + +export function useMatchMedia(query: string) { + const matchListRef = useRef(null); + const [isMatch, setIsMatch] = useState(false); + const onMediaQueryListEvent = useCallback((e: MediaQueryListEvent) => { + setIsMatch(e.matches); + }, []); + + useEffect(() => { + matchListRef.current = window.matchMedia(query); + matchListRef.current.addEventListener('change', onMediaQueryListEvent); + setIsMatch(matchListRef.current.matches); + + return function unmountUseMatchMedia() { + if (matchListRef.current) { + matchListRef.current.removeEventListener( + 'change', + onMediaQueryListEvent + ); + } + }; + }, [query]); + + return isMatch; +} diff --git a/packages/media/src/useWindowSize.ts b/packages/media/src/useWindowSize.ts new file mode 100644 index 0000000..212cbcc --- /dev/null +++ b/packages/media/src/useWindowSize.ts @@ -0,0 +1,46 @@ +import { useState, useEffect } from 'react'; + +type WindowSize = { + innerHeight: number; + innerWidth: number; + outerHeight: number; + outerWidth: number; +}; + +export function useWindowSize() { + const [windowSize, setWindowSize] = useState(getSize()); + + function getSize() { + if (typeof window !== 'undefined') { + return { + innerHeight: window.innerHeight, + innerWidth: window.innerWidth, + outerHeight: window.outerHeight, + outerWidth: window.outerWidth + }; + } else { + console.error('ReferenceError: typeof "window" is undefined'); + + return { + innerHeight: 0, + innerWidth: 0, + outerHeight: 0, + outerWidth: 0 + }; + } + } + + function handleResize() { + setWindowSize(getSize()); + } + + useEffect(() => { + window.addEventListener('resize', handleResize); + + return function unmountUseWindowSize() { + window.removeEventListener('resize', handleResize); + }; + }, []); + + return windowSize; +} diff --git a/yarn.lock b/yarn.lock index c101db7..67022d8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3289,6 +3289,14 @@ lodash "^4.17.15" redent "^3.0.0" +"@testing-library/react-hooks@^3.7.0": + version "3.7.0" + resolved "https://registry.yarnpkg.com/@testing-library/react-hooks/-/react-hooks-3.7.0.tgz#6d75c5255ef49bce39b6465bf6b49e2dac84919e" + integrity sha512-TwfbY6BWtWIHitjT05sbllyLIProcysC0dF0q1bbDa7OHLC6A6rJOYJwZ13hzfz3O4RtOuInmprBozJRyyo7/g== + dependencies: + "@babel/runtime" "^7.12.5" + "@types/testing-library__react-hooks" "^3.4.0" + "@testing-library/react@^11.2.2": version "11.2.2" resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-11.2.2.tgz#099c6c195140ff069211143cb31c0f8337bdb7b7" @@ -3586,6 +3594,13 @@ dependencies: "@types/react" "*" +"@types/react-test-renderer@*": + version "17.0.0" + resolved "https://registry.yarnpkg.com/@types/react-test-renderer/-/react-test-renderer-17.0.0.tgz#9be47b375eeb906fced37049e67284a438d56620" + integrity sha512-nvw+F81OmyzpyIE1S0xWpLonLUZCMewslPuA8BtjSKc5XEbn8zEQBXS7KuOLHTNnSOEM2Pum50gHOoZ62tqTRg== + dependencies: + "@types/react" "*" + "@types/react@*": version "17.0.0" resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.0.tgz#5af3eb7fad2807092f0046a1302b7823e27919b8" @@ -3660,6 +3675,13 @@ dependencies: "@types/jest" "*" +"@types/testing-library__react-hooks@^3.4.0": + version "3.4.1" + resolved "https://registry.yarnpkg.com/@types/testing-library__react-hooks/-/testing-library__react-hooks-3.4.1.tgz#b8d7311c6c1f7db3103e94095fe901f8fef6e433" + integrity sha512-G4JdzEcq61fUyV6wVW9ebHWEiLK2iQvaBuCHHn9eMSbZzVh4Z4wHnUGIvQOYCCYeu5DnUtFyNYuAAgbSaO/43Q== + dependencies: + "@types/react-test-renderer" "*" + "@types/uglify-js@*": version "3.11.1" resolved "https://registry.yarnpkg.com/@types/uglify-js/-/uglify-js-3.11.1.tgz#97ff30e61a0aa6876c270b5f538737e2d6ab8ceb" @@ -6097,6 +6119,11 @@ css-loader@^3.5.3: schema-utils "^2.7.0" semver "^6.3.0" +css-mediaquery@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/css-mediaquery/-/css-mediaquery-0.1.2.tgz#6a2c37344928618631c54bd33cedd301da18bea0" + integrity sha1-aiw3NEkoYYYxxUvTPO3TAdoYvqA= + css-select@^1.1.0: version "1.2.0" resolved "https://registry.yarnpkg.com/css-select/-/css-select-1.2.0.tgz#2b3a110539c5355f1cd8d314623e870b121ec858"