From 5e5bb8df89dfeb2a0c2f913119245d6a51f22a1b Mon Sep 17 00:00:00 2001 From: Shubh Kumar Date: Sun, 11 Oct 2026 15:25:14 +0545 Subject: [PATCH 1/3] feat(hooks): add useEscapeKey hook for keyboard accessibility (#20) --- src/hooks/useEscapeKey.ts | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 src/hooks/useEscapeKey.ts 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 From cc29de977bbc0570a035363a2b86a7da975b7a76 Mon Sep 17 00:00:00 2001 From: Shubh Kumar Date: Sun, 11 Oct 2026 15:25:15 +0545 Subject: [PATCH 2/3] feat(wallet): wire useEscapeKey into WalletModal to dismiss on Escape (#20) --- src/components/wallet/WalletModal.tsx | 2 ++ 1 file changed, 2 insertions(+) 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 ( From b2e40d4d75001c85f27a9fcc4df1a9f83f3068b7 Mon Sep 17 00:00:00 2001 From: Shubh Kumar Date: Sun, 11 Oct 2026 15:25:16 +0545 Subject: [PATCH 3/3] test(hooks): unit tests for useEscapeKey keyboard listener and cleanup (#20) --- src/__tests__/useEscapeKey.test.tsx | 45 +++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 src/__tests__/useEscapeKey.test.tsx 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