Skip to content

Instantly share code, notes, and snippets.

@kachar
Created June 11, 2024 13:16
Show Gist options
  • Save kachar/fe2711ddf12c0b6947322c2f9a2a6178 to your computer and use it in GitHub Desktop.
Save kachar/fe2711ddf12c0b6947322c2f9a2a6178 to your computer and use it in GitHub Desktop.
ReactRectangleSelection from the https://github.com/remigallego/react-rectangle-selection discontinued code, converted to TypeScript
import React, {
CSSProperties,
MouseEvent,
useCallback,
useEffect,
useMemo,
useRef,
useState,
} from 'react'
import { cn } from '@repo/lib'
type Props = {
disabled?: boolean
onMouseDown?: () => void
onMouseUp?: () => void
onSelect: (
evt: MouseEvent<HTMLDivElement>,
coordinates: { origin: number[]; target: number[] },
) => void
style?: CSSProperties
children?: React.ReactNode
}
export function ReactRectangleSelection({
disabled,
onMouseDown,
onMouseUp,
onSelect,
style,
children,
}: Props) {
const [selectionBox, setSelectionBox] = useState(false)
const [selectionBoxOrigin, setSelectionBoxOrigin] = useState<[number, number]>([0, 0])
const [selectionBoxTarget, setSelectionBoxTarget] = useState<[number, number]>([0, 0])
const [animation, setAnimation] = useState('')
const animationInProgress = useRef<number | null>(null)
const hold = useRef(false)
const containerRef = useRef<HTMLDivElement | null>(null)
useEffect(() => {
return () => {
if (animationInProgress.current) {
clearTimeout(animationInProgress.current)
}
}
}, [])
const closeSelectionBox = useCallback(() => {
if (onMouseUp) onMouseUp()
hold.current = false
setAnimation('react-rectangle-selection--fadeout')
animationInProgress.current = window.setTimeout(() => {
setAnimation('')
setSelectionBox(false)
animationInProgress.current = null
}, 300)
}, [onMouseUp])
const handleMouseDown = useCallback(
(e: MouseEvent<HTMLDivElement>) => {
if (disabled) return
let doubleClick = false
if (animationInProgress.current) {
clearTimeout(animationInProgress.current)
animationInProgress.current = null
}
setSelectionBox(false)
setAnimation('')
if (
animation.length > 0 &&
e.target instanceof HTMLElement &&
e.target.id === 'react-rectangle-selection'
) {
setSelectionBox(false)
setAnimation('')
doubleClick = true
}
hold.current = true
const rect = containerRef.current?.getBoundingClientRect()
const offsetX = rect ? e.clientX - rect.left : e.clientX
const offsetY = rect ? e.clientY - rect.top : e.clientY
setSelectionBoxOrigin([offsetX, offsetY])
setSelectionBoxTarget([offsetX, offsetY])
},
[animation, disabled],
)
const baseStyle: CSSProperties = useMemo(
() => ({
zIndex: 10,
left: Math.min(selectionBoxOrigin[0], selectionBoxTarget[0]),
top: Math.min(selectionBoxOrigin[1], selectionBoxTarget[1]),
height: Math.abs(selectionBoxTarget[1] - selectionBoxOrigin[1]),
width: Math.abs(selectionBoxTarget[0] - selectionBoxOrigin[0]),
userSelect: 'none',
transformOrigin: 'top left',
}),
[selectionBoxOrigin, selectionBoxTarget],
)
return (
<div
ref={containerRef}
className="relative h-full w-full"
onMouseLeave={closeSelectionBox}
onMouseDown={handleMouseDown}
onMouseUp={closeSelectionBox}
onMouseMove={(evt) => {
if (hold.current && !selectionBox) {
if (onMouseDown) onMouseDown()
setSelectionBox(true)
}
if (selectionBox && !animationInProgress.current) {
const rect = containerRef.current?.getBoundingClientRect()
const offsetX = rect ? evt.clientX - rect.left : evt.clientX
const offsetY = rect ? evt.clientY - rect.top : evt.clientY
setSelectionBoxTarget([offsetX, offsetY])
onSelect(evt, {
origin: selectionBoxOrigin,
target: selectionBoxTarget,
})
}
}}>
{selectionBox && (
<div
className={cn('absolute border border-dashed border-white bg-transparent', animation)}
id="react-rectangle-selection"
style={{ ...baseStyle, ...style }}
/>
)}
{children}
</div>
)
}
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
:root {
--tg-electric-rgb: 28, 255, 195;
}
}
@keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes fadeOut {
from {
opacity: 1;
}
to {
opacity: 0;
}
}
/* Selection Rectangle */
.react-rectangle-selection {
@apply animate-fadeIn;
}
.react-rectangle-selection--fadeout {
@apply animate-fadeOut;
}
@layer utilities {
.animate-fadeIn {
animation: fadeIn 0.2s;
}
.animate-fadeOut {
animation: fadeOut 0.3s forwards;
}
}
<ReactRectangleSelection
  onMouseDown={handleMouseDown}
  onMouseUp={handleMouseUp}
  onSelect={handleSelect}
  style={{
    backgroundColor: 'rgba(var(--tg-electric-rgb), 0.2)',
    borderColor: 'rgba(var(--tg-electric-rgb), 0.6',
  }}>
        
some content here

</ReactRectangleSelection>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment