useProximityHover
fluid-functionalism/use-proximity-hoverFreeHook
React hook for proximity-based hover detection. Tracks the closest item to the mouse cursor within a container, enabling smooth animated background indicators.
Install it
npx shadcn@latest add https://fluidfunctionalism.com/r/use-proximity-hover.jsonSource
1"use client";23import {4 useRef,5 useState,6 useCallback,7 useEffect,8 type Dispatch,9 type RefObject,10 type SetStateAction,11} from "react";1213export interface ItemRect {14 top: number;15 height: number;16 left: number;17 width: number;18}1920interface UseProximityHoverOptions {21 /**22 * Which direction to resolve the nearest item along.23 * "y" — vertical lists (default): closest by top/height24 * "x" — horizontal strips: closest by left/width25 * "xy" — 2-D grids: closest card across both rows AND columns,26 * measured by Euclidean distance to each item's center27 */28 axis?: "x" | "y" | "xy";29}3031interface UseProximityHoverReturn {32 activeIndex: number | null;33 setActiveIndex: Dispatch<SetStateAction<number | null>>;34 itemRects: ItemRect[];35 /**36 * True once every registered item has been measured and no remeasure is37 * pending, i.e. `itemRects` describes the current item set. Gate absolutely38 * positioned overlays on it: an overlay that mounts against a rect a later39 * pass still corrects animates from the wrong place to the right one, which40 * reads as the highlight sliding in from another row.41 */42 isMeasured: boolean;43 sessionRef: RefObject<number>;44 handlers: {45 onMouseMove: (e: React.MouseEvent) => void;46 onMouseEnter: () => void;47 onMouseLeave: () => void;48 };49 registerItem: (index: number, element: HTMLElement | null) => void;50 /**51 * Invalidates the published rects and runs the hook's coalesced measurement52 * pass again, holding `isMeasured` false until it settles. Reach for it when53 * something other than item registration invalidates layout — a popup that54 * stays mounted between opens keeps its items registered, so nothing else55 * would notice that its rects were taken while it was hidden.56 */57 remeasure: () => void;58 measureItems: () => void;59}6061/**62 * How many frames the coalesced remeasure retries while the registered items63 * still have no layout box. A popup can be in the DOM one frame before it is64 * laid out; retrying beats publishing zeroed rects, and the cap keeps a list65 * that stays hidden for good from spinning frames forever.66 */67const measurementAttempts = 3;6869export function useProximityHover<T extends HTMLElement>(70 containerRef: RefObject<T | null>,71 options: UseProximityHoverOptions = {}72): UseProximityHoverReturn {73 const { axis = "y" } = options;74 const itemsRef = useRef(new Map<number, HTMLElement>());75// … truncated
Files it writes
More from fluid-functionalism
All 53 items| Component | Registry | Kind | Access | Installs | Command |
|---|---|---|---|---|---|
| useMergeSplitBlocksuse-merge-split | fluid-functionalism | Hooks | Free | no deps | |
| useTouchPrimaryuse-touch-primary | fluid-functionalism | Hooks | Free | no deps |
