From 4eeacc8eaeeecb6d4ff5672183f5faed36cea00f Mon Sep 17 00:00:00 2001 From: debabin Date: Sun, 22 Dec 2024 15:03:07 +0700 Subject: [PATCH] =?UTF-8?q?main=20=F0=9F=A7=8A=20add=20use=20vibrate?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/index.ts | 3 +- src/hooks/useVibrate/useVibrate.demo.tsx | 13 +- src/hooks/useVibrate/useVibrate.test.ts | 84 ------------- src/hooks/useVibrate/useVibrate.ts | 145 ++++++++--------------- 4 files changed, 60 insertions(+), 185 deletions(-) delete mode 100644 src/hooks/useVibrate/useVibrate.test.ts diff --git a/src/hooks/index.ts b/src/hooks/index.ts index 31ea6e64..a98f8586 100644 --- a/src/hooks/index.ts +++ b/src/hooks/index.ts @@ -99,9 +99,10 @@ export * from './useTimeout/useTimeout'; export * from './useTimer/useTimer'; export * from './useToggle/useToggle'; export * from './useUnmount/useUnmount'; +export * from './useVibrate/useVibrate'; export * from './useWebSocket/useWebSocket'; export * from './useWindowEvent/useWindowEvent'; export * from './useWindowFocus/useWindowFocus'; export * from './useWindowScroll/useWindowScroll'; export * from './useWindowSize/useWindowSize'; -export * from './useWizard/useWizard'; \ No newline at end of file +export * from './useWizard/useWizard'; diff --git a/src/hooks/useVibrate/useVibrate.demo.tsx b/src/hooks/useVibrate/useVibrate.demo.tsx index 7ad442ee..f230225c 100644 --- a/src/hooks/useVibrate/useVibrate.demo.tsx +++ b/src/hooks/useVibrate/useVibrate.demo.tsx @@ -1,19 +1,20 @@ import { useVibrate } from './useVibrate'; const Demo = () => { - const { isSupported, isVibrating, vibrate, stop } = useVibrate({ + const { vibrating, vibrate, stop } = useVibrate({ pattern: [300, 100, 200, 100, 1000, 300] }); return ( -
- - -
+ ); }; diff --git a/src/hooks/useVibrate/useVibrate.test.ts b/src/hooks/useVibrate/useVibrate.test.ts deleted file mode 100644 index 796279f6..00000000 --- a/src/hooks/useVibrate/useVibrate.test.ts +++ /dev/null @@ -1,84 +0,0 @@ -import { act, renderHook } from '@testing-library/react'; - -import { useVibrate } from './useVibrate'; - -const vibrateMock = vi.fn(); - -describe('useVibrate', () => { - beforeAll(() => { - Object.defineProperty(navigator, 'vibrate', { - writable: true, - value: vibrateMock - }); - }); - - it('should indicate support for vibration API', () => { - const { result } = renderHook(() => useVibrate(1000)); - - expect(result.current.isSupported).toBe(true); - }); - - it('should start and stop vibration', () => { - const { result } = renderHook(() => useVibrate(1000)); - - act(() => { - result.current.vibrate(); - }); - - expect(vibrateMock).toHaveBeenCalledWith(1000); - expect(result.current.isVibrating).toBe(true); - - act(() => { - result.current.stop(); - }); - - expect(vibrateMock).toHaveBeenCalledWith(0); - expect(result.current.isVibrating).toBe(false); - }); - - it('should handle looped vibration', () => { - vi.useFakeTimers(); - const { result } = renderHook(() => useVibrate({ pattern: [200, 100, 200], loop: true })); - - act(() => { - result.current.vibrate(); - }); - - expect(vibrateMock).toHaveBeenCalledWith([200, 100, 200]); - expect(result.current.isVibrating).toBe(true); - - vi.advanceTimersByTime(500); - - expect(vibrateMock).toHaveBeenCalledTimes(2); - - act(() => { - result.current.stop(); - }); - - expect(vibrateMock).toHaveBeenCalledWith(0); - expect(result.current.isVibrating).toBe(false); - - vi.useRealTimers(); - }); - - it('should respect the enabled parameter', () => { - const { result, rerender } = renderHook( - ({ enabled }) => useVibrate({ pattern: [200, 100, 200], enabled }), - { - initialProps: { enabled: false } - } - ); - - expect(result.current.isVibrating).toBe(false); - - rerender({ enabled: true }); - - expect(result.current.isVibrating).toBe(true); - expect(vibrateMock).toHaveBeenCalledWith([200, 100, 200]); - - rerender({ enabled: false }); - - expect(result.current.isVibrating).toBe(false); - expect(vibrateMock).toHaveBeenCalledWith(0); - }); -}); diff --git a/src/hooks/useVibrate/useVibrate.ts b/src/hooks/useVibrate/useVibrate.ts index 7f4acff6..f09b4ee7 100644 --- a/src/hooks/useVibrate/useVibrate.ts +++ b/src/hooks/useVibrate/useVibrate.ts @@ -1,46 +1,35 @@ -import { useEffect, useState } from 'react'; +import { useEffect, useRef, useState } from 'react'; -import { isClient } from '@/utils/helpers'; +/** The use vibrate pattern type */ +export type UseVibratePattern = number | number[]; -/** The use vibration params */ +/** The use vibrate params type */ export interface UseVibrateParams { + /** Time in milliseconds between vibrations */ + interval?: number; /** Pattern for vibration */ - pattern: number | number[]; - /** Alternate way to enable vibration */ - enabled?: boolean; - /** Indicates thar vibration will be endless */ - loop?: boolean; + pattern: UseVibratePattern; } -/** The use vibration options */ -export interface UseVibrateOptions { - /** Alternate way to enable vibration */ - enabled?: boolean; - /** Indicates thar vibration will be endless */ - loop?: boolean; -} - -/** The use vibration return type */ +/** The use vibrate return type */ export interface UseVibrateReturn { - /** Indicates that the device supports Vibration API */ - isSupported: boolean; - /** Indicates that the device is vibrating */ - isVibrating: boolean; - /** Start vibration function */ - vibrate: (pattern?: number | number[]) => void; - /** Stop vibration function */ + /** The support indicator */ + supported: boolean; + /** The vibrating indicator */ + vibrating: boolean; + /** The pause function */ + pause: () => void; + /** The resume function */ + resume: () => void; + /** The stop function */ stop: () => void; + /** The vibrate function */ + vibrate: (pattern?: UseVibratePattern) => void; } -export type UseVibrate = { - (pattern: number | number[], options?: UseVibrateOptions): UseVibrateReturn; - ({ pattern, loop, enabled }: UseVibrateParams, options?: never): UseVibrateReturn; -}; - -let interval: NodeJS.Timeout; /** * @name useVibrate - * @description - Hook that provides Vibrate API + * @description - Hook that provides vibrate api * @category Browser * * @overload @@ -50,80 +39,48 @@ let interval: NodeJS.Timeout; * @returns {UseVibrateReturn} An object containing support indicator, start vibration and stop vibration functions * * @example - * const { isSupported, isVibrating, vibrate, stop } = useVibrate(1000); - * - * @overload - * @param {(number|number[])} params.pattern Pattern for vibration - * @param {boolean} [params.loop] Indicates thar vibration will be endless - * @param {boolean} [params.enabled] Alternate way to enable vibration - * @returns {UseVibrateReturn} An object containing support indicator, vibrating indicator, start vibration and stop vibration functions - * - * @example - * const { isSupported, isVibrating, vibrate, stop } = useVibrate({pattern: [200, 100, 200], loop: true}); - * */ -export const useVibrate: UseVibrate = (...params) => { - const pattern = - typeof params[0] === 'number' || Array.isArray(params[0]) ? params[0] : params[0]?.pattern; - const { loop, enabled } = - typeof params[0] === 'number' || Array.isArray(params[0]) ? params[1] ?? {} : params[0] ?? {}; - - const [isSupported, setIsSupported] = useState(false); - const [isVibrating, setIsVibrating] = useState(false); - - useEffect(() => { - if (isClient && navigator && 'vibrate' in navigator) { - setIsSupported(true); - } - }, []); - - const vibrate = (curPattern = pattern) => { - if (!isSupported || isVibrating) return; - - const duration = Array.isArray(curPattern) ? curPattern.reduce((a, b) => a + b) : curPattern; - - setIsVibrating(true); - navigator.vibrate(curPattern); - - if (loop) { - interval = setInterval(() => { - navigator.vibrate(curPattern); - }, duration); - } else { - setTimeout(() => { - setIsVibrating(false); - }, duration); - } + * const { supported, vibrating, vibrate, stop } = useVibrate(1000); + */ +export const useVibrate = (params: UseVibrateParams) => { + const supported = typeof navigator !== 'undefined' && 'vibrate' in navigator; + + const interval = params.interval ?? 0; + const intervalIdRef = useRef>(); + const [vibrating, setVibrating] = useState(false); + + const vibrate = (pattern: UseVibratePattern = params.pattern) => { + if (!supported) return; + navigator.vibrate(pattern); + setVibrating(true); }; const stop = () => { - if (!isSupported) return; + if (!supported) return; - setIsVibrating(false); + setVibrating(false); navigator.vibrate(0); - if (loop) { - clearInterval(interval); - } + if (intervalIdRef.current) clearInterval(intervalIdRef.current); }; - useEffect(() => { - if (!isSupported || isVibrating) return; + const pause = () => { + if (!supported) return; + if (intervalIdRef.current) clearInterval(intervalIdRef.current); + }; - if (enabled) { - vibrate(); - } + const resume = () => { + if (!supported) return; + if (intervalIdRef.current) clearInterval(intervalIdRef.current); + intervalIdRef.current = setInterval(vibrate, interval); + }; + useEffect(() => { + if (!supported || typeof params.interval === 'undefined') return; + intervalIdRef.current = setInterval(vibrate, interval); return () => { - if (enabled) { - setIsVibrating(false); - navigator.vibrate(0); - - if (loop) { - clearInterval(interval); - } - } + clearInterval(intervalIdRef.current); }; - }, [enabled]); + }, [params.interval, params.pattern]); - return { isSupported, vibrate, stop, isVibrating } as const; + return { supported, vibrate, stop, vibrating, pause, resume }; };