Skip to content

Commit 2800656

Browse files
Merge pull request #9 from commitd/sh-useHover
✨ Adding useHover hook
2 parents b4e1ce3 + 1a16e47 commit 2800656

File tree

5 files changed

+138
-0
lines changed

5 files changed

+138
-0
lines changed

src/hooks/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
export * from './useDebounce'
22
export * from './useEventListener'
3+
export * from './useHover'
34
export * from './useInterval'
45
export * from './useLocalState'
56
export * from './usePoll'

src/hooks/useHover/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './useHover'
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import React, { RefObject } from 'react'
2+
import { Story, Meta } from '@storybook/react'
3+
import { Box } from '@committed/components'
4+
import { useHover } from '.'
5+
6+
export interface UseHoverDocsProps<T extends HTMLElement> {
7+
/** element reference to track hover on */
8+
element?: RefObject<T>
9+
}
10+
11+
/**
12+
* useHover tracks the hovered state of the given element.
13+
*
14+
* @param element reference for the element to add the listener too
15+
*/
16+
export const UseHoverDocs: React.FC<UseHoverDocsProps<HTMLElement>> = () => null
17+
18+
export default {
19+
title: 'Hooks/useHover',
20+
component: UseHoverDocs,
21+
excludeStories: ['UseHoverDocs'],
22+
argTypes: {
23+
element: { control: { type: 'none' } },
24+
},
25+
} as Meta
26+
27+
const Template: Story<UseHoverDocsProps<HTMLDivElement>> = () => {
28+
const divRef = React.useRef<HTMLDivElement>(null)
29+
const [isHovered] = useHover(divRef)
30+
return (
31+
<Box
32+
m={3}
33+
p={3}
34+
ref={divRef}
35+
bgcolor={isHovered ? 'primary.main' : 'secondary.main'}
36+
color={isHovered ? 'primary.contrastText' : 'secondary.contrastText'}
37+
>{`This is ${isHovered ? `hovered` : `unhovered`}`}</Box>
38+
)
39+
}
40+
41+
export const Default = Template.bind({})

src/hooks/useHover/useHover.test.ts

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import { renderHook, act } from '@testing-library/react-hooks'
2+
import { useHover } from '.'
3+
import { RefObject } from 'react'
4+
5+
let listeners: Record<string, () => void>
6+
let ref: RefObject<HTMLDivElement>
7+
8+
beforeEach(() => {
9+
listeners = {}
10+
11+
const current = ({
12+
addEventListener: jest.fn((event: string, handler: () => void) => {
13+
listeners[event] = handler
14+
}),
15+
removeEventListener: jest.fn((event: string) => {
16+
delete listeners[event]
17+
}),
18+
} as unknown) as HTMLDivElement
19+
20+
ref = {
21+
current,
22+
} as RefObject<HTMLDivElement>
23+
})
24+
25+
test('Should start with hover false', () => {
26+
const { result } = renderHook(() => useHover(ref))
27+
expect(result.current[0]).toEqual(false)
28+
})
29+
30+
test('Should be true if mouseover', () => {
31+
const { result } = renderHook(() => useHover(ref))
32+
act(() => {
33+
const event = ({} as unknown) as Event
34+
;(listeners.mouseover as EventListener)(event)
35+
})
36+
expect(result.current[0]).toEqual(true)
37+
})
38+
39+
test('Should be false if mouseout', () => {
40+
const { result } = renderHook(() => useHover(ref))
41+
42+
act(() => {
43+
const event = ({} as unknown) as Event
44+
;(listeners.mouseout as EventListener)(event)
45+
})
46+
47+
expect(result.current[0]).toEqual(false)
48+
})
49+
50+
test('Should be toggle if mouseover then mouseout', () => {
51+
const { result } = renderHook(() => useHover(ref))
52+
53+
act(() => {
54+
const event = ({} as unknown) as Event
55+
;(listeners.mouseover as EventListener)(event)
56+
})
57+
expect(result.current[0]).toEqual(true)
58+
59+
act(() => {
60+
const event = ({} as unknown) as Event
61+
;(listeners.mouseout as EventListener)(event)
62+
})
63+
64+
expect(result.current[0]).toEqual(false)
65+
})
66+
67+
test('Should remove listeners', () => {
68+
const { unmount } = renderHook(() => useHover(ref))
69+
70+
unmount()
71+
72+
expect(listeners.mouseover).toBeFalsy()
73+
expect(listeners.mouseout).toBeFalsy()
74+
})

src/hooks/useHover/useHover.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { useState, RefObject } from 'react'
2+
import { useEventListener } from '../useEventListener/useEventListener'
3+
4+
/**
5+
* useHover tracks the hovered state of the given element.
6+
*
7+
* @param element reference to track hover on
8+
*/
9+
export function useHover<T extends HTMLElement = HTMLDivElement>(
10+
element: RefObject<T>
11+
): [boolean] {
12+
const [hovered, setHovered] = useState(false)
13+
14+
const handleMouseOver = () => setHovered(true)
15+
const handleMouseOut = () => setHovered(false)
16+
17+
useEventListener('mouseover', handleMouseOver, element)
18+
useEventListener('mouseout', handleMouseOut, element)
19+
20+
return [hovered]
21+
}

0 commit comments

Comments
 (0)