forked from altalyst-solutions/hookify
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request altalyst-solutions#11 from sleepinzombie/feat/add-…
…use-outside-click Add useOutsideClick hook
- Loading branch information
Showing
11 changed files
with
659 additions
and
3 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
22 changes: 22 additions & 0 deletions
22
examples/basic/src/components/use-outside-click/styles.scss
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
.component { | ||
&__container { | ||
display: flex; | ||
flex-direction: column; | ||
position: relative; | ||
} | ||
|
||
&__button { | ||
padding: 10px 20px; | ||
cursor: pointer; | ||
} | ||
|
||
&__dropdown { | ||
position: absolute; | ||
top: 50px; | ||
left: 0; | ||
padding: 10px; | ||
border: 1px solid #ccc; | ||
border-radius: 4px; | ||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); | ||
} | ||
} |
36 changes: 36 additions & 0 deletions
36
examples/basic/src/components/use-outside-click/use-outside-click.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
import { useOutsideClick } from "@altalyst/hookify"; | ||
import type { MouseEvent } from "react"; | ||
import { useRef, useState } from "react"; | ||
|
||
import "./styles.scss"; | ||
|
||
export const UseOutsideClick = () => { | ||
const [isOpen, setIsOpen] = useState(false); | ||
const dropdownRef = useRef<HTMLDivElement>(null); | ||
|
||
const handleToggleDropdown = (event: MouseEvent) => { | ||
event.stopPropagation(); | ||
setIsOpen((prev) => !prev); | ||
}; | ||
|
||
const handleOutsideClick = () => { | ||
setIsOpen(false); | ||
}; | ||
|
||
useOutsideClick(dropdownRef, handleOutsideClick); | ||
|
||
return ( | ||
<div className="component__container"> | ||
<button className="component__button" onClick={handleToggleDropdown}> | ||
Toggle Dropdown | ||
</button> | ||
|
||
{isOpen && ( | ||
<div ref={dropdownRef} className="component__dropdown"> | ||
<p>Dropdown Content</p> | ||
<p>Click outside to close me!</p> | ||
</div> | ||
)} | ||
</div> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
import { UseOutsideClick } from "../../components/use-outside-click/use-outside-click"; | ||
|
||
export const UseOutsideClickPage = () => { | ||
return ( | ||
<> | ||
<h1>Demo for: useOutsideClick hook</h1> | ||
<UseOutsideClick /> | ||
</> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,14 @@ | ||
import { defineConfig } from 'vite' | ||
import react from '@vitejs/plugin-react' | ||
import react from "@vitejs/plugin-react"; | ||
import { defineConfig } from "vite"; | ||
|
||
// https://vitejs.dev/config/ | ||
export default defineConfig({ | ||
plugins: [react()], | ||
}) | ||
css: { | ||
preprocessorOptions: { | ||
scss: { | ||
api: "modern-compiler", | ||
}, | ||
}, | ||
}, | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,3 @@ | ||
export * from "./use-api"; | ||
export * from "./use-effect-after-mount"; | ||
export * from "./use-outside-click"; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
import type { RefObject } from "react"; | ||
import { useEffect } from "react"; | ||
|
||
/** | ||
* A custom React hook that triggers a callback function when a click or touch event occurs outside the specified element. | ||
* | ||
* This hook is useful for handling scenarios like closing dropdowns, modals, or tooltips when clicking outside the component. | ||
* | ||
* @param {RefObject<HTMLElement>} ref - A React ref object pointing to the element to detect outside clicks for. | ||
* @param {() => void} callback - The callback function to execute when a click or touch event occurs outside the specified element. | ||
* | ||
* @example | ||
* ```typescript | ||
* const ref = useRef<HTMLDivElement>(null); | ||
* useOutsideClick(ref, () => { | ||
* console.log("Clicked outside the component"); | ||
* }); | ||
* | ||
* return <div ref={ref}>Click outside this element</div>; | ||
* ``` | ||
* | ||
* For a full working example, check the implementation in [examples/useOutsideClick.ts](../../examples/basic/src/components/use-outside-click/use-outside-click.tsx). | ||
*/ | ||
export const useOutsideClick = ( | ||
ref: RefObject<HTMLElement>, | ||
callback: () => void | ||
) => { | ||
useEffect(() => { | ||
const handleOutsideClick = (event: MouseEvent | TouchEvent) => { | ||
if (ref.current && !ref.current.contains(event.target as Node)) { | ||
callback(); | ||
} | ||
}; | ||
|
||
document.addEventListener("click", handleOutsideClick); | ||
document.addEventListener("touchstart", handleOutsideClick); | ||
|
||
return () => { | ||
document.removeEventListener("click", handleOutsideClick); | ||
document.removeEventListener("touchstart", handleOutsideClick); | ||
}; | ||
}, [ref, callback]); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
import { useOutsideClick } from "@/hooks"; | ||
import { renderHook } from "@testing-library/react"; | ||
import { describe, expect, it, vi } from "vitest"; | ||
|
||
describe("useOutsideClick", () => { | ||
it("should call the callback when clicking outside the referenced element", () => { | ||
const callback = vi.fn(); | ||
const ref = { current: document.createElement("div") }; | ||
|
||
document.body.appendChild(ref.current); | ||
renderHook(() => useOutsideClick(ref, callback)); | ||
document.body.click(); | ||
|
||
expect(callback).toHaveBeenCalledTimes(1); | ||
|
||
document.body.removeChild(ref.current); | ||
}); | ||
|
||
it("should not call the callback when clicking inside the referenced element", () => { | ||
const callback = vi.fn(); | ||
const ref = { current: document.createElement("div") }; | ||
|
||
document.body.appendChild(ref.current); | ||
renderHook(() => useOutsideClick(ref, callback)); | ||
ref.current.click(); | ||
|
||
expect(callback).not.toHaveBeenCalled(); | ||
|
||
document.body.removeChild(ref.current); | ||
}); | ||
|
||
it("should handle no reference gracefully", () => { | ||
const callback = vi.fn(); | ||
const ref = { current: null }; | ||
|
||
renderHook(() => useOutsideClick(ref, callback)); | ||
document.body.click(); | ||
|
||
expect(callback).not.toHaveBeenCalled(); | ||
}); | ||
}); |