Skip to content

Commit

Permalink
feat(useMediaQuery): add disable as an option
Browse files Browse the repository at this point in the history
  • Loading branch information
tujoworker committed Sep 21, 2022
1 parent 9775d16 commit 8641e0a
Show file tree
Hide file tree
Showing 6 changed files with 81 additions and 22 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,8 @@ function Component() {
}
```

You can also disable the usage of `window.matchMedia` temporally by providing `disabled: true` as an option.

### Live example

This example uses the `not` property to reverse the behavior.
Expand Down Expand Up @@ -165,7 +167,7 @@ const Playground = () => {
}}
right
>
Change
Switch
</Button>
<MediaQuery when={query}>
<Code>when</Code>
Expand Down Expand Up @@ -252,3 +254,18 @@ You can re-use the SASS mixins from Eufemia:
```

Based of the findings of [this article](https://zellwk.com/blog/media-query-units/) and [this webkit bug](https://bugs.webkit.org/show_bug.cgi?id=156684) Eufemia recommends to use `em` units for media query usage to meet the best overall browser support. Read [more about units](/uilib/usage/best-practices/for-styling#units).

## How to deal with Jest

You can mock `window.matchMedia` with e.g. [jest-matchmedia-mock](https://www.npmjs.com/package/jest-matchmedia-mock).

```js
import MatchMediaMock from 'jest-matchmedia-mock'

const matchMedia = new MatchMediaMock()

it('your test', () => {
matchMedia.useMediaQuery('(min-width: 50em) and (max-width: 60em)')
...
})
```
18 changes: 15 additions & 3 deletions packages/dnb-eufemia/src/shared/MediaQueryUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,11 @@ export type MediaQueryProperties = {
*/
not?: boolean

/**
* If set to true, no MediaQuery will be used.
*/
disabled?: boolean

/**
* For debugging
*/
Expand All @@ -64,6 +69,7 @@ export type MediaQueryProps = {
* If set to true, it will match and return the given children during SSR.
*/
matchOnSSR?: boolean

children?: React.ReactNode
} & MediaQueryProperties

Expand Down Expand Up @@ -127,10 +133,16 @@ export const isMatchMediaSupported = (): boolean =>
* @returns MediaQueryList type
*/
export function makeMediaQueryList(
{ query, when, not = null, log = false }: MediaQueryProperties = {},
{
query,
when,
not = null,
log = false,
disabled = false,
}: MediaQueryProperties = {},
breakpoints: MediaQueryBreakpoints = null
): MediaQueryList {
if (!isMatchMediaSupported()) {
if (disabled || !isMatchMediaSupported()) {
return null
}

Expand All @@ -142,7 +154,7 @@ export function makeMediaQueryList(
const mediaQueryList = window.matchMedia(mediaQueryString)

if (log) {
console.log('mediaQueryString', mediaQueryString)
console.log('MediaQuery:', mediaQueryString)
}

return mediaQueryList
Expand Down
4 changes: 2 additions & 2 deletions packages/dnb-eufemia/src/shared/__tests__/MediaQuery.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,11 @@ describe('MediaQuery', () => {
})

afterEach(() => {
matchMedia.clear()
matchMedia?.clear()
})

afterAll(() => {
matchMedia.destroy()
matchMedia?.destroy()
})

it('should match for query with medium width', () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { isMatchMediaSupported } from '../../MediaQueryUtils'
import { isMatchMediaSupported as _isMatchMediaSupported } from '../../MediaQueryUtils'
import MatchMediaMock from 'jest-matchmedia-mock'

const isMatchMediaSupported = _isMatchMediaSupported as jest.Mock

jest.mock('../../MediaQueryUtils', () => ({
...jest.requireActual('../../MediaQueryUtils'),
isMatchMediaSupported: jest.fn(),
Expand All @@ -14,11 +16,11 @@ export function mockMediaQuery() {
})

afterEach(() => {
matchMedia.clear()
matchMedia?.clear()
})

afterAll(() => {
matchMedia.destroy()
matchMedia?.destroy()
})

return matchMedia
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,20 @@ import { mount } from '../../core/jest/jestSetup'
import MatchMediaMock from 'jest-matchmedia-mock'
import useMediaQuery from '../useMediaQuery'
import Provider from '../Provider'
import { isMatchMediaSupported } from '../MediaQueryUtils'
import {
isMatchMediaSupported as _isMatchMediaSupported,
MediaQueryProps,
} from '../MediaQueryUtils'

const isMatchMediaSupported = _isMatchMediaSupported as jest.Mock

jest.mock('../MediaQueryUtils', () => ({
...jest.requireActual('../MediaQueryUtils'),
isMatchMediaSupported: jest.fn(),
}))

describe('useMediaQuery', () => {
let matchMedia
let matchMedia: MatchMediaMock

beforeAll(() => {
matchMedia = new MatchMediaMock()
Expand All @@ -27,16 +32,16 @@ describe('useMediaQuery', () => {
})

afterEach(() => {
matchMedia.clear()
matchMedia?.clear()
})

afterAll(() => {
matchMedia.destroy()
matchMedia?.destroy()
})

const RenderMediaQueryHook = (props) => {
const RenderMediaQueryHook = (props: MediaQueryProps) => {
const match = useMediaQuery(props)
return match ? props.children : null
return <>{match ? props.children : null}</>
}

it('should have valid strings inside render', () => {
Expand Down Expand Up @@ -149,4 +154,19 @@ describe('useMediaQuery', () => {
expect(match1Handler).toHaveBeenCalledWith(true)
expect(match2Handler).toHaveBeenCalledWith(false)
})

it('can be disabled', () => {
jest.spyOn(window, 'matchMedia').mockImplementationOnce(jest.fn())

matchMedia.useMediaQuery('(min-width: 0) and (max-width: 72em)')

const Comp = mount(
<RenderMediaQueryHook disabled when={{ min: '0', max: 'x-large' }}>
matches
</RenderMediaQueryHook>
)

expect(window.matchMedia).toBeCalledTimes(0)
expect(Comp.text()).toBe('')
})
})
22 changes: 15 additions & 7 deletions packages/dnb-eufemia/src/shared/useMediaQuery.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,15 @@ export type { MediaQueryProps }

export default function useMediaQuery(props: MediaQueryProps) {
const context = React.useContext(Context)
const { query, when, not, matchOnSSR } = props
let [matches] = React.useState(() =>
!isMatchMediaSupported() && isTrue(matchOnSSR) ? true : false
)
const { query, when, not, matchOnSSR, disabled } = props

let matches = React.useMemo(() => {
if (disabled) {
return false // stop here
}

return isTrue(matchOnSSR) && !isMatchMediaSupported()
}, [disabled, matchOnSSR])

const mediaQueryList = React.useRef(
makeMediaQueryList(props, context.breakpoints)
Expand All @@ -30,7 +35,11 @@ export default function useMediaQuery(props: MediaQueryProps) {
const [match, matchUpdate] = React.useState(matches)

const listenerRef = React.useRef<MediaQueryListener>()
React.useEffect(() => {
React.useLayoutEffect(() => {
if (disabled) {
return // stop here
}

if (typeof listenerRef.current === 'function') {
listenerRef.current()

Expand All @@ -47,8 +56,7 @@ export default function useMediaQuery(props: MediaQueryProps) {
)

return listenerRef.current
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [query, when, not])
}, [query, when, not, disabled]) // eslint-disable-line react-hooks/exhaustive-deps

return match
}

0 comments on commit 8641e0a

Please sign in to comment.