Skip to content

Commit

Permalink
feat: useVibration hook implementation and docs (#757)
Browse files Browse the repository at this point in the history
* feat: `useVibration` hook implementation and docs

#fix 683
  • Loading branch information
xobotyi authored May 12, 2022
1 parent 161ac31 commit db40294
Show file tree
Hide file tree
Showing 10 changed files with 181 additions and 0 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,8 @@ Coming from `react-use`? Check out our

- [**`useNetworkState`**](https://react-hookz.github.io/web/?path=/docs/navigator-usenetworkstate--example)
— Tracks the state of browser's network connection.
- [**`useVibrate`**](https://react-hookz.github.io/web/?path=/docs/navigator-usevibrate--example)
— Provides vibration feedback using the Vibration API.
- [**`usePermission`**](https://react-hookz.github.io/web/?path=/docs/navigator-usepermission--example)
— Tracks a permission state.

Expand Down
3 changes: 3 additions & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ module.exports = {
preset: 'ts-jest',
testEnvironment: 'jsdom',
testMatch: ['<rootDir>/src/**/__tests__/dom.[jt]s?(x)'],
setupFiles: ['./src/__tests__/setup.ts'],
},
{
displayName: 'ssr',
Expand All @@ -19,6 +20,7 @@ module.exports = {
preset: 'ts-jest',
testEnvironment: 'jsdom',
testMatch: ['<rootDir>/src/**/__tests__/dom.[jt]s?(x)'],
setupFiles: ['./src/__tests__/setup.ts'],
moduleNameMapper: {
'^../..$': '<rootDir>/cjs',
},
Expand All @@ -28,6 +30,7 @@ module.exports = {
preset: 'ts-jest',
testEnvironment: 'jsdom',
testMatch: ['<rootDir>/src/**/__tests__/dom.[jt]s?(x)'],
setupFiles: ['./src/__tests__/setup.ts'],
moduleNameMapper: {
'^../..$': '<rootDir>',
},
Expand Down
3 changes: 3 additions & 0 deletions src/__tests__/navigator.vibrate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export const NavigatorVibrateSetup = () => {
navigator.vibrate = (_: VibratePattern): boolean => true;
};
7 changes: 7 additions & 0 deletions src/__tests__/setup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/**
* Here goes any setup code that should be performed before actual tests and/or code imports.
*/

import { NavigatorVibrateSetup } from './navigator.vibrate';

NavigatorVibrateSetup();
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ export {
INetworkInformation,
} from './useNetworkState/useNetworkState';
export { usePermission, IUsePermissionState } from './usePermission/usePermission';
export { useVibrate } from './useVibrate/useVibrate';

// Miscellaneous
export { useSyncedRef } from './useSyncedRef/useSyncedRef';
Expand Down
18 changes: 18 additions & 0 deletions src/useVibrate/__docs__/example.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import * as React from 'react';
import { useToggle, useVibrate } from '../..';

export const Example: React.FC = () => {
const [doVibrate, setDoVibrate] = useToggle(false);

useVibrate(
doVibrate,
[100, 30, 100, 30, 100, 30, 200, 30, 200, 30, 200, 30, 100, 30, 100, 30, 100],
true
);

return (
<div>
<button onClick={() => setDoVibrate()}>{doVibrate ? 'Stop' : 'Start'} vibration</button>
</div>
);
};
38 changes: 38 additions & 0 deletions src/useVibrate/__docs__/story.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { Canvas, Meta, Story } from '@storybook/addon-docs/blocks';
import { Example } from './example.stories';
import { ImportPath } from '../../storybookUtil/ImportPath';

<Meta title="Navigator/useVibrate" component={Example} />

# useVibrate

Provides vibration feedback using the [Vibration API](https://developer.mozilla.org/en-US/docs/Web/API/Vibration_API).

> Note: client may disable vibration for websites in browser settings.
- SSR-friendly (do nothing during SSR).
- Supports vibration loops.
- Auto-cancel vibration on component unmount.

#### Example

<Canvas>
<Story story={Example} inline />
</Canvas>

## Reference

```ts
function useVibrate(enabled: boolean, pattern: VibratePattern, loop?: boolean): void;
```

#### Importing

<ImportPath />

#### Arguments

- **enabled** _`boolean`_ - Whether to perform vibration or not.
- **pattern** _`number | number[]`_ - VibrationPattern passed down to `navigator.vibrate`.
- **loop** _`boolean | undefined`_ _(default: `undefined`)_ - If true - vibration will be looped
using `setInterval`.
57 changes: 57 additions & 0 deletions src/useVibrate/__tests__/dom.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { renderHook } from '@testing-library/react-hooks/dom';
import { useVibrate } from '../..';

describe('useVibrate', () => {
const vibrateSpy = jest.spyOn(navigator, 'vibrate');

beforeEach(() => {
vibrateSpy.mockReset();
});

afterAll(() => {
vibrateSpy.mockRestore();
});

it('should be defined', () => {
expect(useVibrate).toBeDefined();
});

it('should render', () => {
const { result } = renderHook(() => useVibrate(true, 100));
expect(result.error).toBeUndefined();
});

it('should call navigator.vibrate', () => {
renderHook(() => useVibrate(true, [100, 200]));
expect(vibrateSpy).toHaveBeenCalledTimes(1);
expect(vibrateSpy.mock.calls[0][0]).toEqual([100, 200]);
});

it('should call navigator.vibrate(0) on unmount', () => {
const { unmount } = renderHook(() => useVibrate(true, [100, 200], true));
unmount();

expect(vibrateSpy.mock.calls[1][0]).toEqual(0);
});

it('should vibrate constantly using interval', () => {
jest.useFakeTimers();
renderHook(() => useVibrate(true, 300, true));

expect(vibrateSpy).toHaveBeenCalledTimes(1);
expect(vibrateSpy.mock.calls[0][0]).toEqual(300);

jest.advanceTimersByTime(299);
expect(vibrateSpy).toHaveBeenCalledTimes(1);

jest.advanceTimersByTime(1);
expect(vibrateSpy).toHaveBeenCalledTimes(2);
expect(vibrateSpy.mock.calls[1][0]).toEqual(300);

jest.advanceTimersByTime(300);
expect(vibrateSpy).toHaveBeenCalledTimes(3);
expect(vibrateSpy.mock.calls[2][0]).toEqual(300);

jest.useRealTimers();
});
});
13 changes: 13 additions & 0 deletions src/useVibrate/__tests__/ssr.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { renderHook } from '@testing-library/react-hooks/server';
import { useVibrate } from '../..';

describe('useVibrate', () => {
it('should be defined', () => {
expect(useVibrate).toBeDefined();
});

it('should render', () => {
const { result } = renderHook(() => useVibrate(true, 100));
expect(result.error).toBeUndefined();
});
});
39 changes: 39 additions & 0 deletions src/useVibrate/useVibrate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { useEffect } from 'react';
import { isBrowser, noop } from '../util/const';

/**
* Provides vibration feedback using the Vibration API.
*
* @param enabled Whether to perform vibration or not.
* @param pattern VibrationPattern passed down to `navigator.vibrate`.
* @param loop If true - vibration will be looped using `setInterval`.
*/
export const useVibrate =
!isBrowser || typeof navigator.vibrate === 'undefined'
? noop
: function useVibrate(enabled: boolean, pattern: VibratePattern, loop?: boolean): void {
useEffect(() => {
let interval: undefined | ReturnType<typeof setInterval>;

if (enabled) {
navigator.vibrate(pattern);

if (loop) {
interval = setInterval(
() => {
navigator.vibrate(pattern);
},
Array.isArray(pattern) ? pattern.reduce((a, n) => a + n, 0) : pattern
);
}

return () => {
navigator.vibrate(0);

if (interval) {
clearInterval(interval);
}
};
}
}, [loop, pattern, enabled]);
};

0 comments on commit db40294

Please sign in to comment.