Comparing version 1.3.1 to 1.4.0
@@ -0,1 +1,8 @@ | ||
# [1.4.0](https://github.com/streamich/use-media/compare/v1.3.1...v1.4.0) (2019-07-27) | ||
### Features | ||
* refactor /src folder ([8f72ba2](https://github.com/streamich/use-media/commit/8f72ba2)) | ||
## [1.3.1](https://github.com/streamich/use-media/compare/v1.3.0...v1.3.1) (2019-07-27) | ||
@@ -2,0 +9,0 @@ |
@@ -1,6 +0,1 @@ | ||
declare type MediaQueryObject = { | ||
[key: string]: string | number | boolean; | ||
}; | ||
export declare const useMedia: (rawQuery: string | MediaQueryObject, defaultState?: boolean) => boolean; | ||
export declare const useMediaLayout: (rawQuery: string | MediaQueryObject, defaultState?: boolean) => boolean; | ||
export default useMedia; | ||
export { default, useMedia, useMediaLayout } from './useMedia'; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
var React = require("react"); | ||
var useState = React.useState, useEffect = React.useEffect, useLayoutEffect = React.useLayoutEffect; | ||
var camelToHyphen = function (str) { | ||
return str.replace(/[A-Z]/g, function (m) { return "-" + m.toLowerCase(); }).toLowerCase(); | ||
}; | ||
var noWindowMatches = { | ||
media: '', | ||
addListener: noop, | ||
removeListener: noop, | ||
matches: false, | ||
onchange: noop, | ||
addEventListener: noop, | ||
removeEventListener: noop, | ||
dispatchEvent: function (_) { return true; } | ||
}; | ||
var objectToString = function (query) { | ||
if (typeof query === 'string') | ||
return query; | ||
return Object.entries(query) | ||
.map(function (_a) { | ||
var feature = _a[0], value = _a[1]; | ||
feature = camelToHyphen(feature); | ||
if (typeof value === 'boolean') { | ||
return value ? feature : "not " + feature; | ||
} | ||
if (typeof value === 'number' && /[height|width]$/.test(feature)) { | ||
value = value + "px"; | ||
} | ||
return "(" + feature + ": " + value + ")"; | ||
}) | ||
.join(' and '); | ||
}; | ||
var createUseMedia = function (effect) { return function (rawQuery, defaultState) { | ||
if (defaultState === void 0) { defaultState = false; } | ||
var _a = useState(defaultState), state = _a[0], setState = _a[1]; | ||
var query = objectToString(rawQuery); | ||
effect(function () { | ||
var mounted = true; | ||
var mql = typeof window === 'undefined' | ||
? noWindowMatches | ||
: window.matchMedia(query); | ||
var onChange = function () { | ||
if (!mounted) | ||
return; | ||
setState(!!mql.matches); | ||
}; | ||
mql.addListener(onChange); | ||
setState(mql.matches); | ||
return function () { | ||
mounted = false; | ||
mql.removeListener(onChange); | ||
}; | ||
}, [query]); | ||
return state; | ||
}; }; | ||
function noop() { } | ||
exports.useMedia = createUseMedia(useEffect); | ||
exports.useMediaLayout = createUseMedia(useLayoutEffect); | ||
exports.default = exports.useMedia; | ||
var useMedia_1 = require("./useMedia"); | ||
exports.default = useMedia_1.default; | ||
exports.useMedia = useMedia_1.useMedia; | ||
exports.useMediaLayout = useMedia_1.useMediaLayout; |
{ | ||
"name": "use-media", | ||
"version": "1.3.1", | ||
"version": "1.4.0", | ||
"description": "useMedia React hook", | ||
@@ -5,0 +5,0 @@ "main": "lib/index.js", |
152
README.md
@@ -5,3 +5,2 @@ # use-media | ||
## Usage | ||
@@ -12,7 +11,9 @@ | ||
```jsx | ||
import {useMedia} from 'use-media'; | ||
import useMedia from 'use-media'; | ||
// Alternatively, you can import as: | ||
// import {useMedia} from 'use-media'; | ||
const Demo = () => { | ||
// Accepts an object of features to test | ||
const isWide = useMedia({ minWidth: 1000 }); | ||
const isWide = useMedia({minWidth: 1000}); | ||
// Or a regular media query string | ||
@@ -36,3 +37,3 @@ const reduceMotion = useMedia('(prefers-reduced-motion: reduce)'); | ||
// Accepts an object of features to test | ||
const isWide = useMediaLayout({ minWidth: 1000 }); | ||
const isWide = useMediaLayout({minWidth: 1000}); | ||
// Or a regular media query string | ||
@@ -48,1 +49,144 @@ const reduceMotion = useMediaLayout('(prefers-reduced-motion: reduce)'); | ||
``` | ||
## Testing | ||
Depending on your testing setup, you may need to mock `window.matchMedia` on components that utilize the `useMedia` hook. Below is an example of doing this in `jest`: | ||
**`/test-utilities/index.ts`** | ||
```jsx | ||
import {mockMediaQueryList} from 'use-media/lib/useMedia'; | ||
// Types are also exported for convienence: | ||
// import {Effect, MediaQueryObject} from 'use-media/lib/types'; | ||
export interface MockMatchMedia { | ||
media: string; | ||
matches?: boolean; | ||
} | ||
function getMockImplementation({media, matches = false}: MockMatchMedia) { | ||
const mql: MediaQueryList = { | ||
...mockMediaQueryList, | ||
media, | ||
matches, | ||
}; | ||
return () => mql; | ||
} | ||
export function jestMockMatchMedia({media, matches = false}: MockMatchMedia) { | ||
const mockedImplementation = getMockImplementation({media, matches}); | ||
window.matchMedia = jest.fn().mockImplementation(mockedImplementation); | ||
} | ||
``` | ||
**`/components/MyComponent/MyComponent.test.tsx`** | ||
```jsx | ||
const mediaQueries = { | ||
mobile: '(max-width: 767px)', | ||
prefersReducedMotion: '(prefers-reduced-motion: reduce)', | ||
}; | ||
describe('<MyComponent />', () => { | ||
const defaultProps: Props = { | ||
duration: 100, | ||
}; | ||
afterEach(() => { | ||
jestMockMatchMedia({ | ||
media: mediaQueries.prefersReducedMotion, | ||
matches: false, | ||
}); | ||
}); | ||
it('sets `duration` to `0` when user-agent `prefers-reduced-motion`', () => { | ||
jestMockMatchMedia({ | ||
media: mediaQueries.prefersReducedMotion, | ||
matches: true, | ||
}); | ||
const wrapper = mount(<MyComponent {...defaultProps} />); | ||
const child = wrapper.find(TransitionComponent); | ||
expect(child.prop('duration')).toBe(0); | ||
}); | ||
}); | ||
``` | ||
## Storing in Context | ||
Depending on your app, you may be using the `useMedia` hook to register many `matchMedia` listeners across multiple components. It may help to elevate these listeners to `Context`. | ||
**`/components/MediaQueryProvider/MediaQueryProvider.tsx`** | ||
```jsx | ||
import React, {createContext, useContext, useMemo} from 'react'; | ||
import useMedia from 'use-media'; | ||
interface Props { | ||
children: React.ReactNode; | ||
} | ||
export const MediaQueryContext = createContext(null); | ||
const mediaQueries = { | ||
mobile: '(max-width: 767px)', | ||
prefersReducedMotion: '(prefers-reduced-motion: reduce)', | ||
}; | ||
export default function MediaQueryProvider({children}: Props) { | ||
const mobileView = useMedia(mediaQueries.mobile); | ||
const prefersReducedMotion = useMedia(mediaQueries.prefersReducedMotion); | ||
const value = useMemo(() => ({mobileView, prefersReducedMotion}), [ | ||
mobileView, | ||
prefersReducedMotion, | ||
]); | ||
return ( | ||
<MediaQueryContext.Provider value={value}> | ||
{children} | ||
</MediaQueryContext.Provider> | ||
); | ||
} | ||
export function useMediaQueryContext() { | ||
return useContext(MediaQueryContext); | ||
} | ||
``` | ||
**`/components/App/App.tsx`** | ||
```jsx | ||
import React from 'react'; | ||
import MediaQueryProvider from '../MediaQueryProvider'; | ||
import MyComponent from '../MyComponent'; | ||
export default function App() { | ||
return ( | ||
<MediaQueryProvider> | ||
<div id="MyApp"> | ||
<MyComponent /> | ||
</div> | ||
</MediaQueryProvider> | ||
); | ||
} | ||
``` | ||
**`/components/MyComponent/MyComponent.tsx`** | ||
```jsx | ||
import React from 'react'; | ||
import {useMediaQueryContext} from '../MediaQueryProvider'; | ||
export default function MyComponent() { | ||
const {mobileView, prefersReducedMotion} = useMediaQueryContext(); | ||
return ( | ||
<div> | ||
<p>mobileView: {Boolean(mobileView).toString()}</p> | ||
<p>prefersReducedMotion: {Boolean(prefersReducedMotion).toString()}</p> | ||
</div> | ||
); | ||
} | ||
``` |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
11656
17
112
189