feat: add copy text option to input fields.
This commit is contained in:
@@ -0,0 +1,51 @@
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
|
||||
interface UseCopyToClipboardProps {
|
||||
timeout?: number;
|
||||
}
|
||||
|
||||
export function useCopyToClipboard({ timeout = 2000 }: UseCopyToClipboardProps = {}) {
|
||||
const [isCopied, setIsCopied] = useState<boolean>(false);
|
||||
const timeoutRef = useRef<number | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
if (timeoutRef.current) {
|
||||
globalThis.clearTimeout(timeoutRef.current);
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
const copy = useCallback(
|
||||
async (text: string) => {
|
||||
if (!navigator?.clipboard) {
|
||||
console.warn('Clipboard API not available');
|
||||
setIsCopied(false);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (timeoutRef.current) {
|
||||
globalThis.clearTimeout(timeoutRef.current);
|
||||
}
|
||||
|
||||
try {
|
||||
await navigator.clipboard.writeText(text);
|
||||
setIsCopied(true);
|
||||
|
||||
timeoutRef.current = globalThis.setTimeout(() => {
|
||||
setIsCopied(false);
|
||||
timeoutRef.current = null;
|
||||
}, timeout);
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('Failed to copy text to clipboard:', error);
|
||||
setIsCopied(false);
|
||||
return false;
|
||||
}
|
||||
},
|
||||
[timeout]
|
||||
);
|
||||
|
||||
return { isCopied, copy };
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
import { renderHook, act } from '@testing-library/react';
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { usePasswordVisibilityToggle } from './usePasswordVisibilityToggle.ts';
|
||||
|
||||
describe('usePasswordVisibilityToggle Hook', () => {
|
||||
it('should initialize with visibility set to false by default', () => {
|
||||
const { result } = renderHook(() => usePasswordVisibilityToggle());
|
||||
expect(result.current.isVisible).toBe(false);
|
||||
expect(typeof result.current.toggleVisibility).toBe('function');
|
||||
});
|
||||
|
||||
it('should initialize with visibility set to true if initialVisible is true', () => {
|
||||
const { result } = renderHook(() =>
|
||||
usePasswordVisibilityToggle({ initialVisible: true })
|
||||
);
|
||||
expect(result.current.isVisible).toBe(true);
|
||||
});
|
||||
|
||||
it('should toggle visibility from false to true when toggleVisibility is called', () => {
|
||||
const { result } = renderHook(() => usePasswordVisibilityToggle());
|
||||
expect(result.current.isVisible).toBe(false);
|
||||
act(() => {
|
||||
result.current.toggleVisibility();
|
||||
});
|
||||
expect(result.current.isVisible).toBe(true);
|
||||
});
|
||||
|
||||
it('should toggle visibility from true to false when toggleVisibility is called', () => {
|
||||
const { result } = renderHook(() =>
|
||||
usePasswordVisibilityToggle({ initialVisible: true })
|
||||
);
|
||||
expect(result.current.isVisible).toBe(true);
|
||||
act(() => {
|
||||
result.current.toggleVisibility();
|
||||
});
|
||||
expect(result.current.isVisible).toBe(false);
|
||||
});
|
||||
|
||||
it('should toggle visibility correctly multiple times', () => {
|
||||
const { result } = renderHook(() => usePasswordVisibilityToggle());
|
||||
expect(result.current.isVisible).toBe(false);
|
||||
act(() => {
|
||||
result.current.toggleVisibility();
|
||||
});
|
||||
expect(result.current.isVisible).toBe(true);
|
||||
act(() => {
|
||||
result.current.toggleVisibility();
|
||||
});
|
||||
expect(result.current.isVisible).toBe(false);
|
||||
act(() => {
|
||||
result.current.toggleVisibility();
|
||||
});
|
||||
expect(result.current.isVisible).toBe(true);
|
||||
});
|
||||
|
||||
it('should return a stable toggleVisibility function reference (due to useCallback)', () => {
|
||||
const { result, rerender } = renderHook(() => usePasswordVisibilityToggle());
|
||||
const initialToggleFunc = result.current.toggleVisibility;
|
||||
rerender();
|
||||
expect(result.current.toggleVisibility).toBe(initialToggleFunc);
|
||||
act(() => {
|
||||
result.current.toggleVisibility();
|
||||
});
|
||||
expect(result.current.isVisible).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,20 @@
|
||||
import { useState, useCallback } from 'react';
|
||||
|
||||
interface UsePasswordVisibilityToggleProps {
|
||||
initialVisible?: boolean;
|
||||
}
|
||||
/**
|
||||
* Manages the state for toggling password visibility.
|
||||
*
|
||||
* @param {boolean} [options.initialVisible=false]
|
||||
* @returns {{isVisible: boolean, toggleVisibility: () => void}}
|
||||
*/
|
||||
export function usePasswordVisibilityToggle({ initialVisible = false }: UsePasswordVisibilityToggleProps = {}) {
|
||||
const [isVisible, setIsVisible] = useState<boolean>(initialVisible);
|
||||
|
||||
const toggleVisibility = useCallback(() => {
|
||||
setIsVisible(prev => !prev);
|
||||
}, []);
|
||||
|
||||
return { isVisible, toggleVisibility };
|
||||
}
|
||||
Reference in New Issue
Block a user