ourshelf/src/utilities/useClickableCard.ts

109 lines
2.6 KiB
TypeScript

'use client'
import type { RefObject } from 'react'
import { useRouter } from 'next/navigation'
import { useCallback, useEffect, useRef } from 'react'
type UseClickableCardType<T extends HTMLElement> = {
card: {
ref: RefObject<T | null>
}
link: {
ref: RefObject<HTMLAnchorElement | null>
}
}
interface Props {
external?: boolean
newTab?: boolean
scroll?: boolean
}
function useClickableCard<T extends HTMLElement>({
external = false,
newTab = false,
scroll = true,
}: Props): UseClickableCardType<T> {
const router = useRouter()
const card = useRef<T>(null)
const link = useRef<HTMLAnchorElement>(null)
const timeDown = useRef<number>(0)
const hasActiveParent = useRef<boolean>(false)
const pressedButton = useRef<number>(0)
const handleMouseDown = useCallback(
(e: MouseEvent) => {
if (e.target) {
const target = e.target as Element
const timeNow = +new Date()
const parent = target?.closest('a')
pressedButton.current = e.button
if (!parent) {
hasActiveParent.current = false
timeDown.current = timeNow
} else {
hasActiveParent.current = true
}
}
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[router, card, link, timeDown],
)
const handleMouseUp = useCallback(
(e: MouseEvent) => {
if (link.current?.href) {
const timeNow = +new Date()
const difference = timeNow - timeDown.current
if (link.current?.href && difference <= 250) {
if (!hasActiveParent.current && pressedButton.current === 0 && !e.ctrlKey) {
if (external) {
const target = newTab ? '_blank' : '_self'
window.open(link.current.href, target)
} else {
router.push(link.current.href, { scroll })
}
}
}
}
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[router, card, link, timeDown],
)
useEffect(() => {
const cardNode = card.current
const abortController = new AbortController()
if (cardNode) {
cardNode.addEventListener('mousedown', handleMouseDown, {
signal: abortController.signal,
})
cardNode.addEventListener('mouseup', handleMouseUp, {
signal: abortController.signal,
})
}
return () => {
abortController.abort()
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [card, link, router])
return {
card: {
ref: card,
},
link: {
ref: link,
},
}
}
export default useClickableCard