Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions packages/use-media/.eslintrc.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
const { join } = require('path')

module.exports = {
rules: {
'import/no-extraneous-dependencies': [
'error',
{ packageDir: [__dirname, join(__dirname, '../../')] },
],
},
}
5 changes: 5 additions & 0 deletions packages/use-media/.npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
**/__tests__/**
examples/
src
.eslintrc.cjs
!.npmignore
51 changes: 51 additions & 0 deletions packages/use-media/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# `@scaleway/use-media`

## A small hook to track CSS media query state

This library has been forked from [use-media](https://github.com/streamich/use-media), many thanks to the original author, [Vadim Dalecky](https://github.com/streamich).

## Install

```bash
$ pnpm add @scaleway/use-media
```

## Usage

### With `useEffect`

```tsx
import { useMedia } from '@scaleway/use-media'

const App = () => {
// Accepts an object of features to test
const isWide = useMedia({ minWidth: '1000px' });
// Or a regular media query string
const reduceMotion = useMedia('(prefers-reduced-motion: reduce)');

return (
<div>
Screen is wide: {isWide ? '😃' : '😢'}
</div>
);
}
```

### With `useLayoutEffect`

```tsx
import { useMediaLayout } from '@scaleway/use-media'

const App = () => {
// Accepts an object of features to test
const isWide = useMediaLayout({ minWidth: '1000px' });
// Or a regular media query string
const reduceMotion = useMediaLayout('(prefers-reduced-motion: reduce)');

return (
<div>
Screen is wide: {isWide ? '😃' : '😢'}
</div>
);
}
```
31 changes: 31 additions & 0 deletions packages/use-media/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{
"name": "@scaleway/use-media",
"version": "1.0.0",
"description": "A small hook to track CSS media query state",
"keywords": [
"react",
"reactjs",
"hooks",
"media",
"media queries"
],
"type": "module",
"main": "dist/index.js",
"module": "dist/index.js",
"types": "dist/index.d.ts",
"browser": {
"dist/index.js": "./dist/index.browser.js"
},
"publishConfig": {
"access": "public"
},
"repository": {
"type": "git",
"url": "https://github.com/scaleway/scaleway-lib",
"directory": "packages/use-media"
},
"license": "MIT",
"peerDependencies": {
"react": "17.x || 18.x"
}
}
69 changes: 69 additions & 0 deletions packages/use-media/src/__tests__/useMedia.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { renderHook } from '@testing-library/react-hooks'
import { useMedia } from '..'

describe('useMedia hook', () => {
it('should return the result of a query with a string', () => {
const { result } = renderHook(() =>
useMedia('screen and (min-width: 1000px)'),
)

expect(result.current).toBe(false)
})

it('should call onChange', () => {
const mockAddEventListener = (_event: string, callback: () => void) =>
callback()

Object.defineProperty(window, 'matchMedia', {
value: jest.fn().mockImplementation((query: string) => ({
addEventListener: mockAddEventListener,
addListener: jest.fn(),
dispatchEvent: jest.fn(),
matches: false,
media: query,
onchange: null,
removeEventListener: jest.fn(),
removeListener: jest.fn(),
})),
writable: true,
})

const { result } = renderHook(() =>
useMedia('screen and (min-width: 1000px)'),
)

expect(result.current).toBe(false)
})

it('should not call onChange when unmounted', () => {
let callback: () => void

const mockAddEventListener = (_event: string, callbackFn: () => void) => {
callback = callbackFn
}

Object.defineProperty(window, 'matchMedia', {
value: jest.fn().mockImplementation((query: string) => ({
addEventListener: mockAddEventListener,
addListener: jest.fn(),
dispatchEvent: jest.fn(),
matches: false,
media: query,
onchange: null,
removeEventListener: jest.fn(),
removeListener: jest.fn(),
})),
writable: true,
})

const { result, unmount } = renderHook(() =>
useMedia('screen and (min-width: 1000px)'),
)

unmount()
// @ts-expect-error variable is assigned inside mockAddEventListener
callback()

expect(result.current).toBe(false)
})
})
1 change: 1 addition & 0 deletions packages/use-media/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { useMedia, useMediaLayout } from './useMedia'
3 changes: 3 additions & 0 deletions packages/use-media/src/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { DependencyList, EffectCallback } from 'react'

export type Effect = (effect: EffectCallback, deps?: DependencyList) => void
51 changes: 51 additions & 0 deletions packages/use-media/src/useMedia.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { useEffect, useLayoutEffect, useState } from 'react'
import { Effect } from './types'

function noop() {}

export const mockMediaQueryList: MediaQueryList = {
addEventListener: noop,
addListener: noop,
dispatchEvent: /* istanbul ignore next */ () => true,
matches: false,
media: '',
onchange: noop,
removeEventListener: noop,
removeListener: noop,
}

const createUseMedia =
(effect: Effect) =>
(query: string, defaultState = false) => {
const [state, setState] = useState(defaultState)

effect(() => {
let mounted = true
const mediaQueryList: MediaQueryList =
typeof window === 'undefined' ||
typeof window.matchMedia === 'undefined'
? mockMediaQueryList
: window.matchMedia(query)

const onChange = () => {
if (!mounted) {
return
}

setState(Boolean(mediaQueryList.matches))
}

mediaQueryList.addEventListener('change', onChange)
setState(mediaQueryList.matches)

return () => {
mounted = false
mediaQueryList.removeEventListener('change', onChange)
}
}, [query])

return state
}

export const useMedia = createUseMedia(useEffect)
export const useMediaLayout = createUseMedia(useLayoutEffect)