diff --git a/src/__tests__/useEscapeKey.test.tsx b/src/__tests__/useEscapeKey.test.tsx new file mode 100644 index 0000000..05da9ec --- /dev/null +++ b/src/__tests__/useEscapeKey.test.tsx @@ -0,0 +1,45 @@ +import React from 'react'; +import { describe, it, expect, vi } from 'vitest'; +import { render, fireEvent } from '@testing-library/react'; +import { useEscapeKey } from '../hooks/useEscapeKey'; + +const TestComponent = ({ onEscape, enabled = true }: { onEscape: () => void; enabled?: boolean }) => { + useEscapeKey(onEscape, enabled); + return
Modal Content
; +}; + +describe('useEscapeKey Hook', () => { + it('triggers onEscape callback when Escape key is pressed', () => { + const handleEscape = vi.fn(); + render(); + + fireEvent.keyDown(window, { key: 'Escape', code: 'Escape' }); + expect(handleEscape).toHaveBeenCalledTimes(1); + }); + + it('does not trigger onEscape when another key is pressed', () => { + const handleEscape = vi.fn(); + render(); + + fireEvent.keyDown(window, { key: 'Enter', code: 'Enter' }); + fireEvent.keyDown(window, { key: 'Tab', code: 'Tab' }); + expect(handleEscape).not.toHaveBeenCalled(); + }); + + it('does not trigger onEscape when disabled', () => { + const handleEscape = vi.fn(); + render(); + + fireEvent.keyDown(window, { key: 'Escape', code: 'Escape' }); + expect(handleEscape).not.toHaveBeenCalled(); + }); + + it('cleans up event listener on unmount', () => { + const handleEscape = vi.fn(); + const { unmount } = render(); + + unmount(); + fireEvent.keyDown(window, { key: 'Escape', code: 'Escape' }); + expect(handleEscape).not.toHaveBeenCalled(); + }); +}); \ No newline at end of file diff --git a/src/components/wallet/WalletModal.tsx b/src/components/wallet/WalletModal.tsx index 820a6ae..165a51d 100644 --- a/src/components/wallet/WalletModal.tsx +++ b/src/components/wallet/WalletModal.tsx @@ -3,6 +3,7 @@ import React from 'react'; import { X, ExternalLink, CheckCircle, ShieldCheck } from 'lucide-react'; import { WalletOption, WalletProviderId } from '../../types/stellar'; +import { useEscapeKey } from '../../hooks/useEscapeKey'; interface WalletModalProps { isOpen: boolean; @@ -21,6 +22,7 @@ export const WalletModal: React.FC = ({ activeWalletId, isConnecting, }) => { + useEscapeKey(onClose, isOpen); if (!isOpen) return null; return ( diff --git a/src/hooks/useEscapeKey.ts b/src/hooks/useEscapeKey.ts new file mode 100644 index 0000000..d50c3f2 --- /dev/null +++ b/src/hooks/useEscapeKey.ts @@ -0,0 +1,25 @@ +'use client'; + +import { useEffect } from 'react'; + +/** + * Custom hook to execute a callback when the Escape key is pressed. + * @param onEscape Callback to invoke upon pressing Escape + * @param enabled Whether the listener is active (default true) + */ +export function useEscapeKey(onEscape: () => void, enabled: boolean = true): void { + useEffect(() => { + if (!enabled) return; + + const handleKeyDown = (event: KeyboardEvent) => { + if (event.key === 'Escape' || event.key === 'Esc') { + onEscape(); + } + }; + + window.addEventListener('keydown', handleKeyDown); + return () => { + window.removeEventListener('keydown', handleKeyDown); + }; + }, [onEscape, enabled]); +} \ No newline at end of file