import { useInView } from 'motion/react' import { useEffect, useRef } from 'react' import type React from 'react' import { annotate } from 'rough-notation' import { type RoughAnnotation } from 'rough-notation/lib/model' type AnnotationAction = | 'highlight' | 'underline' | 'box' | 'circle' | 'strike-through' | 'crossed-off' | 'bracket' interface HighlighterProps { children: React.ReactNode action?: AnnotationAction color?: string strokeWidth?: number animationDuration?: number iterations?: number padding?: number multiline?: boolean isView?: boolean } export function Highlighter({ children, action = 'highlight', color = '#ffd1dc', strokeWidth = 1.5, animationDuration = 600, iterations = 2, padding = 2, multiline = true, isView = false, }: HighlighterProps) { const elementRef = useRef(null) const annotationRef = useRef(null) const isInView = useInView(elementRef, { once: true, margin: '-10%', }) // If isView is false, always show. If isView is true, wait for inView const shouldShow = !isView || isInView useEffect(() => { if (!shouldShow) return const element = elementRef.current if (!element) return const annotationConfig = { type: action, color, strokeWidth, animationDuration, iterations, padding, multiline, } const annotation = annotate(element, annotationConfig) annotationRef.current = annotation annotationRef.current.show() const resizeObserver = new ResizeObserver(() => { annotation.hide() annotation.show() }) resizeObserver.observe(element) resizeObserver.observe(document.body) return () => { if (element) { annotate(element, { type: action }).remove() resizeObserver.disconnect() } } }, [shouldShow, action, color, strokeWidth, animationDuration, iterations, padding, multiline]) return ( {children} ) }