From a944870ca9bcf0bd069a4b8b0a733943f70e1e13 Mon Sep 17 00:00:00 2001 From: luisdralves Date: Fri, 15 Jul 2022 14:04:54 +0100 Subject: [PATCH] Add `use-detect-outside-click` hook --- .../use-detect-outside-click/index.test.tsx | 75 +++++++++++++++++++ .../src/use-detect-outside-click/index.ts | 48 ++++++++++++ 2 files changed, 123 insertions(+) create mode 100644 packages/hooks/src/use-detect-outside-click/index.test.tsx create mode 100644 packages/hooks/src/use-detect-outside-click/index.ts diff --git a/packages/hooks/src/use-detect-outside-click/index.test.tsx b/packages/hooks/src/use-detect-outside-click/index.test.tsx new file mode 100644 index 0000000..dcc9122 --- /dev/null +++ b/packages/hooks/src/use-detect-outside-click/index.test.tsx @@ -0,0 +1,75 @@ +/** + * Module dependencies. + */ + +import { fireEvent, render, screen } from '@testing-library/react'; +import { useDetectOutsideClick } from './'; +import React, { useRef } from 'react'; + +/** + * `Mock` component. + */ + +const Mock = ({ initiallyActive }: { initiallyActive?: boolean }) => { + const ref = useRef(null); + const [active, setActive] = useDetectOutsideClick(ref, initiallyActive); + + return ( + <> +
setActive(true)} + ref={ref} + > + {active ? 'active' : 'inactive'} +
+ +
{'outside'}
+ + ); +}; + +/** + * Test `useDetectOutsideClick` hook. + */ + +describe(`'useDetectOutsideClick' hook`, () => { + it('should lose focus', () => { + render(); + + expect(screen.queryByText('active')).toBeTruthy(); + + fireEvent.click(screen.getByText('outside')); + + expect(screen.queryByText('inactive')).toBeTruthy(); + }); + + it('should gain focus', () => { + render(); + + expect(screen.queryByText('inactive')).toBeTruthy(); + + fireEvent.click(screen.getByText('inactive')); + + expect(screen.queryByText('active')).toBeTruthy(); + }); + + it('should keep focused', () => { + render(); + + expect(screen.queryByText('active')).toBeTruthy(); + + fireEvent.click(screen.getByText('active')); + + expect(screen.queryByText('active')).toBeTruthy(); + }); + + it('should keep unfocused', () => { + render(); + + expect(screen.queryByText('inactive')).toBeTruthy(); + + fireEvent.click(screen.getByText('outside')); + + expect(screen.queryByText('inactive')).toBeTruthy(); + }); +}); diff --git a/packages/hooks/src/use-detect-outside-click/index.ts b/packages/hooks/src/use-detect-outside-click/index.ts new file mode 100644 index 0000000..dc33dd8 --- /dev/null +++ b/packages/hooks/src/use-detect-outside-click/index.ts @@ -0,0 +1,48 @@ +/** + * Module dependencies. + */ + +import { + Dispatch, + RefObject, + SetStateAction, + useEffect, + useState +} from 'react'; + +/** + * `Result` Props. + */ + +type Result = [boolean, Dispatch>]; + +/** + * Export `useDetectOutsideClick` hook. + */ + +export function useDetectOutsideClick( + ref: RefObject | null | undefined, + initialState = false +): Result { + const [active, setActive] = useState(initialState); + + useEffect(() => { + const handleClickOutside = ({ target }: MouseEvent) => { + if ( + ref?.current && + target instanceof Element && + !ref?.current?.contains(target) + ) { + setActive(false); + } + }; + + document.addEventListener('click', handleClickOutside, true); + + return () => { + document.removeEventListener('click', handleClickOutside, true); + }; + }, [ref]); + + return [active, setActive]; +}