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 };
};