{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "velocity-highlight",
  "title": "Velocity Highlight",
  "description": "A highlight effect with smooth spring physics that tracks the active element across hover or click interactions.",
  "dependencies": [
    "motion"
  ],
  "registryDependencies": [],
  "files": [
    {
      "path": "registry/primitives/effects/velocity-highlight/index.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\nimport {\n  AnimatePresence,\n  animate,\n  motion,\n  type Transition,\n  useMotionValue,\n} from \"motion/react\";\n\nimport { cn } from \"@/lib/utils\";\n\ntype HighlightMode = \"children\" | \"parent\";\n\ntype Bounds = {\n  top: number;\n  left: number;\n  width: number;\n  height: number;\n};\n\nconst DEFAULT_TRANSITION: Transition = {\n  type: \"spring\",\n  stiffness: 1250,\n  damping: 40,\n  mass: 0.5,\n};\n\nconst MAX_AXIS_SPEED = 1800;\nconst MAX_STRETCH = 0.28;\nconst MAX_SQUASH = 0.2;\nconst STRETCH_TRANSITION = {\n  type: \"spring\" as const,\n  damping: 22,\n  stiffness: 700,\n};\nconst SETTLE_TRANSITION = {\n  type: \"spring\" as const,\n  damping: 24,\n  stiffness: 460,\n};\nconst STRETCH_DURATION = 110;\n\nconst DEFAULT_BOUNDS_OFFSET: Bounds = {\n  top: 0,\n  left: 0,\n  width: 0,\n  height: 0,\n};\n\ntype HighlightContextType<T extends string> = {\n  as?: keyof HTMLElementTagNameMap;\n  mode: HighlightMode;\n  activeValue: T | null;\n  setActiveValue: (value: T | null) => void;\n  setBounds: (bounds: DOMRect) => void;\n  clearBounds: () => void;\n  id: string;\n  hover: boolean;\n  click: boolean;\n  className?: string;\n  style?: React.CSSProperties;\n  activeClassName?: string;\n  setActiveClassName: (className: string) => void;\n  transition?: Transition;\n  disabled?: boolean;\n  enabled?: boolean;\n  exitDelay?: number;\n  forceUpdateBounds?: boolean;\n  scaleX?: unknown;\n  scaleY?: unknown;\n};\n\nconst HighlightContext = React.createContext<\n  // eslint-disable-next-line @typescript-eslint/no-explicit-any\n  HighlightContextType<any> | undefined\n>(undefined);\n\nfunction useHighlight<T extends string>(): HighlightContextType<T> {\n  const context = React.useContext(HighlightContext);\n  if (!context) {\n    throw new Error(\"useHighlight must be used within a HighlightProvider\");\n  }\n  return context as unknown as HighlightContextType<T>;\n}\n\ntype BaseHighlightProps<T extends React.ElementType = \"div\"> = {\n  as?: T;\n  ref?: React.Ref<HTMLDivElement>;\n  mode?: HighlightMode;\n  value?: string | null;\n  defaultValue?: string | null;\n  onValueChange?: (value: string | null) => void;\n  className?: string;\n  style?: React.CSSProperties;\n  transition?: Transition;\n  hover?: boolean;\n  click?: boolean;\n  disabled?: boolean;\n  enabled?: boolean;\n  exitDelay?: number;\n};\n\ntype ParentModeHighlightProps = {\n  boundsOffset?: Partial<Bounds>;\n  containerClassName?: string;\n  forceUpdateBounds?: boolean;\n};\n\ntype ControlledParentModeHighlightProps<T extends React.ElementType = \"div\"> =\n  BaseHighlightProps<T> &\n    ParentModeHighlightProps & {\n      mode: \"parent\";\n      controlledItems: true;\n      children: React.ReactNode;\n    };\n\ntype ControlledChildrenModeHighlightProps<T extends React.ElementType = \"div\"> =\n  BaseHighlightProps<T> & {\n    mode?: \"children\" | undefined;\n    controlledItems: true;\n    children: React.ReactNode;\n  };\n\ntype UncontrolledParentModeHighlightProps<T extends React.ElementType = \"div\"> =\n  BaseHighlightProps<T> &\n    ParentModeHighlightProps & {\n      mode: \"parent\";\n      controlledItems?: false;\n      itemsClassName?: string;\n      children: React.ReactElement | React.ReactElement[];\n    };\n\ntype UncontrolledChildrenModeHighlightProps<\n  T extends React.ElementType = \"div\",\n> = BaseHighlightProps<T> & {\n  mode?: \"children\";\n  controlledItems?: false;\n  itemsClassName?: string;\n  children: React.ReactElement | React.ReactElement[];\n};\n\ntype HighlightProps<T extends React.ElementType = \"div\"> =\n  | ControlledParentModeHighlightProps<T>\n  | ControlledChildrenModeHighlightProps<T>\n  | UncontrolledParentModeHighlightProps<T>\n  | UncontrolledChildrenModeHighlightProps<T>;\n\nfunction Highlight<T extends React.ElementType = \"div\">({\n  ref,\n  ...props\n}: HighlightProps<T>) {\n  const {\n    as: Component = \"div\",\n    children,\n    value,\n    defaultValue,\n    onValueChange,\n    className,\n    style,\n    transition = DEFAULT_TRANSITION,\n    hover = false,\n    click = true,\n    enabled = true,\n    controlledItems,\n    disabled = false,\n    exitDelay = 200,\n    mode = \"children\",\n  } = props;\n\n  const localRef = React.useRef<HTMLDivElement>(null);\n  React.useImperativeHandle(ref, () => localRef.current as HTMLDivElement);\n\n  const propsBoundsOffset = (props as ParentModeHighlightProps)?.boundsOffset;\n  const boundsOffset = propsBoundsOffset ?? DEFAULT_BOUNDS_OFFSET;\n  const boundsOffsetTop = boundsOffset.top ?? 0;\n  const boundsOffsetLeft = boundsOffset.left ?? 0;\n  const boundsOffsetWidth = boundsOffset.width ?? 0;\n  const boundsOffsetHeight = boundsOffset.height ?? 0;\n\n  const boundsOffsetRef = React.useRef({\n    top: boundsOffsetTop,\n    left: boundsOffsetLeft,\n    width: boundsOffsetWidth,\n    height: boundsOffsetHeight,\n  });\n\n  React.useEffect(() => {\n    boundsOffsetRef.current = {\n      top: boundsOffsetTop,\n      left: boundsOffsetLeft,\n      width: boundsOffsetWidth,\n      height: boundsOffsetHeight,\n    };\n  }, [\n    boundsOffsetTop,\n    boundsOffsetLeft,\n    boundsOffsetWidth,\n    boundsOffsetHeight,\n  ]);\n\n  const [activeValue, setActiveValue] = React.useState<string | null>(\n    value ?? defaultValue ?? null,\n  );\n  const [boundsState, setBoundsState] = React.useState<Bounds | null>(null);\n  const [activeClassNameState, setActiveClassNameState] =\n    React.useState<string>(\"\");\n\n  const scaleX = useMotionValue(1);\n  const scaleY = useMotionValue(1);\n  const lastBoundsRef = React.useRef<{\n    bounds: Bounds;\n    timestamp: number;\n  } | null>(null);\n  const stretchRunRef = React.useRef(0);\n\n  const triggerStretch = React.useCallback(\n    (nextBounds: Bounds) => {\n      const timestamp = performance.now();\n      const previous = lastBoundsRef.current;\n      lastBoundsRef.current = { bounds: nextBounds, timestamp };\n\n      if (!previous) return;\n\n      const elapsed = Math.max(timestamp - previous.timestamp, 16);\n      const horizontal = Math.min(\n        (Math.abs(nextBounds.left - previous.bounds.left) /\n          elapsed /\n          MAX_AXIS_SPEED) *\n          1000,\n        1,\n      );\n      const vertical = Math.min(\n        (Math.abs(nextBounds.top - previous.bounds.top) /\n          elapsed /\n          MAX_AXIS_SPEED) *\n          1000,\n        1,\n      );\n\n      if (horizontal === 0 && vertical === 0) return;\n\n      const nextScaleX = Math.max(\n        1 - MAX_SQUASH,\n        Math.min(\n          1 + MAX_STRETCH,\n          1 + horizontal * MAX_STRETCH - vertical * MAX_SQUASH,\n        ),\n      );\n      const nextScaleY = Math.max(\n        1 - MAX_SQUASH,\n        Math.min(\n          1 + MAX_STRETCH,\n          1 + vertical * MAX_STRETCH - horizontal * MAX_SQUASH,\n        ),\n      );\n      const run = ++stretchRunRef.current;\n\n      animate(scaleX, nextScaleX, STRETCH_TRANSITION);\n      animate(scaleY, nextScaleY, STRETCH_TRANSITION);\n\n      window.setTimeout(() => {\n        if (stretchRunRef.current !== run) return;\n        animate(scaleX, 1, SETTLE_TRANSITION);\n        animate(scaleY, 1, SETTLE_TRANSITION);\n      }, STRETCH_DURATION);\n    },\n    [scaleX, scaleY],\n  );\n\n  const safeSetActiveValue = (id: string | null) => {\n    setActiveValue((prev) => {\n      if (prev !== id) {\n        onValueChange?.(id);\n        return id;\n      }\n      return prev;\n    });\n  };\n\n  const safeSetBoundsRef = React.useRef<\n    ((bounds: DOMRect) => void) | undefined\n  >(undefined);\n\n  React.useEffect(() => {\n    safeSetBoundsRef.current = (bounds: DOMRect) => {\n      if (!localRef.current) return;\n\n      const containerRect = localRef.current.getBoundingClientRect();\n      const offset = boundsOffsetRef.current;\n      const newBounds: Bounds = {\n        top: bounds.top - containerRect.top + offset.top,\n        left: bounds.left - containerRect.left + offset.left,\n        width: bounds.width + offset.width,\n        height: bounds.height + offset.height,\n      };\n\n      triggerStretch(newBounds);\n\n      setBoundsState((prev) => {\n        if (\n          prev &&\n          prev.top === newBounds.top &&\n          prev.left === newBounds.left &&\n          prev.width === newBounds.width &&\n          prev.height === newBounds.height\n        ) {\n          return prev;\n        }\n        return newBounds;\n      });\n    };\n  });\n\n  const safeSetBounds = (bounds: DOMRect) => {\n    safeSetBoundsRef.current?.(bounds);\n  };\n\n  const clearBounds = React.useCallback(() => {\n    lastBoundsRef.current = null;\n    stretchRunRef.current += 1;\n    animate(scaleX, 1, SETTLE_TRANSITION);\n    animate(scaleY, 1, SETTLE_TRANSITION);\n    setBoundsState((prev) => (prev === null ? prev : null));\n  }, [scaleX, scaleY]);\n\n  React.useEffect(() => {\n    if (value !== undefined) setActiveValue(value);\n    else if (defaultValue !== undefined) setActiveValue(defaultValue);\n  }, [value, defaultValue]);\n\n  const id = React.useId();\n\n  React.useEffect(() => {\n    if (mode !== \"parent\") return;\n    const container = localRef.current;\n    if (!container) return;\n\n    const onScroll = () => {\n      if (!activeValue) return;\n      const activeEl = container.querySelector<HTMLElement>(\n        `[data-value=\"${activeValue}\"][data-highlight=\"true\"]`,\n      );\n      if (activeEl)\n        safeSetBoundsRef.current?.(activeEl.getBoundingClientRect());\n    };\n\n    container.addEventListener(\"scroll\", onScroll, { passive: true });\n    return () => container.removeEventListener(\"scroll\", onScroll);\n  }, [mode, activeValue]);\n\n  const render = (children: React.ReactNode) => {\n    if (mode === \"parent\") {\n      return React.createElement(\n        Component as React.ElementType,\n        {\n          ref: localRef,\n          \"data-slot\": \"motion-highlight-container\",\n          style: { position: \"relative\", zIndex: 1 },\n          className: (props as ParentModeHighlightProps)?.containerClassName,\n          ...(hover && {\n            onMouseLeave: () => {\n              safeSetActiveValue(null);\n              clearBounds();\n            },\n          }),\n        },\n        <>\n          <AnimatePresence initial={false} mode=\"wait\">\n            {boundsState && (\n              <motion.div\n                data-slot=\"motion-highlight\"\n                animate={{\n                  top: boundsState.top,\n                  left: boundsState.left,\n                  width: boundsState.width,\n                  height: boundsState.height,\n                  opacity: 1,\n                }}\n                initial={{\n                  top: boundsState.top,\n                  left: boundsState.left,\n                  width: boundsState.width,\n                  height: boundsState.height,\n                  opacity: 0,\n                }}\n                exit={{\n                  opacity: 0,\n                  transition: {\n                    ...transition,\n                    delay: (transition?.delay ?? 0) + (exitDelay ?? 0) / 1000,\n                  },\n                }}\n                transition={transition}\n                style={{\n                  position: \"absolute\",\n                  zIndex: 0,\n                  scaleX,\n                  scaleY,\n                  ...style,\n                }}\n                className={cn(className, activeClassNameState)}\n              />\n            )}\n          </AnimatePresence>\n          {children}\n        </>,\n      );\n    }\n\n    return children;\n  };\n\n  return (\n    <HighlightContext.Provider\n      value={{\n        mode,\n        activeValue,\n        setActiveValue: safeSetActiveValue,\n        id,\n        hover,\n        click,\n        className,\n        style,\n        transition,\n        disabled,\n        enabled,\n        exitDelay,\n        setBounds: safeSetBounds,\n        clearBounds,\n        activeClassName: activeClassNameState,\n        setActiveClassName: setActiveClassNameState,\n        forceUpdateBounds: (props as ParentModeHighlightProps)\n          ?.forceUpdateBounds,\n        scaleX,\n        scaleY,\n      }}\n    >\n      {enabled\n        ? controlledItems\n          ? render(children)\n          : render(\n              React.Children.map(children, (child, index) =>\n                React.isValidElement(child) ? (\n                  <HighlightItem key={index} className={props?.itemsClassName}>\n                    {child}\n                  </HighlightItem>\n                ) : (\n                  child\n                ),\n              ),\n            )\n        : children}\n    </HighlightContext.Provider>\n  );\n}\n\nfunction getNonOverridingDataAttributes(\n  element: React.ReactElement,\n  dataAttributes: Record<string, unknown>,\n): Record<string, unknown> {\n  return Object.keys(dataAttributes).reduce<Record<string, unknown>>(\n    (acc, key) => {\n      if ((element.props as Record<string, unknown>)[key] === undefined) {\n        acc[key] = dataAttributes[key];\n      }\n      return acc;\n    },\n    {},\n  );\n}\n\ntype ExtendedChildProps = React.ComponentProps<\"div\"> & {\n  id?: string;\n  ref?: React.Ref<HTMLElement>;\n  \"data-active\"?: string;\n  \"data-value\"?: string;\n  \"data-disabled\"?: boolean;\n  \"data-highlight\"?: boolean;\n  \"data-slot\"?: string;\n};\n\ntype HighlightItemProps<T extends React.ElementType = \"div\"> =\n  React.ComponentProps<T> & {\n    as?: T;\n    children: React.ReactNode;\n    id?: string;\n    value?: string;\n    className?: string;\n    style?: React.CSSProperties;\n    transition?: Transition;\n    activeClassName?: string;\n    disabled?: boolean;\n    exitDelay?: number;\n    asChild?: boolean;\n    forceUpdateBounds?: boolean;\n  };\n\nfunction HighlightItem<T extends React.ElementType>({\n  ref,\n  as,\n  children,\n  id,\n  value,\n  className,\n  style,\n  transition,\n  disabled = false,\n  activeClassName,\n  exitDelay,\n  asChild = false,\n  forceUpdateBounds,\n  ...props\n}: HighlightItemProps<T>) {\n  const itemId = React.useId();\n  const {\n    activeValue,\n    setActiveValue,\n    mode,\n    setBounds,\n    hover,\n    click,\n    enabled,\n    className: contextClassName,\n    style: contextStyle,\n    transition: contextTransition,\n    id: contextId,\n    disabled: contextDisabled,\n    exitDelay: contextExitDelay,\n    forceUpdateBounds: contextForceUpdateBounds,\n    setActiveClassName,\n    scaleX,\n    scaleY,\n  } = useHighlight();\n\n  const Component = (as ?? \"div\") as React.ElementType;\n  const isValidChild = React.isValidElement<ExtendedChildProps>(children);\n  const element = isValidChild ? children : null;\n  const childValue =\n    id ??\n    value ??\n    element?.props?.[\"data-value\"] ??\n    element?.props?.id ??\n    itemId;\n  const isActive = activeValue === childValue;\n  const isDisabled = disabled === undefined ? contextDisabled : disabled;\n  const itemTransition = transition ?? contextTransition;\n\n  const localRef = React.useRef<HTMLDivElement>(null);\n  React.useImperativeHandle(ref, () => localRef.current as HTMLDivElement);\n\n  const refCallback = React.useCallback((node: HTMLElement | null) => {\n    localRef.current = node as HTMLDivElement;\n  }, []);\n\n  React.useEffect(() => {\n    if (mode !== \"parent\") return;\n    let rafId: number;\n    let previousBounds: Bounds | null = null;\n    const shouldUpdateBounds =\n      forceUpdateBounds === true ||\n      (contextForceUpdateBounds && forceUpdateBounds !== false);\n\n    const updateBounds = () => {\n      if (!localRef.current) return;\n\n      const bounds = localRef.current.getBoundingClientRect();\n\n      if (shouldUpdateBounds) {\n        if (\n          previousBounds &&\n          previousBounds.top === bounds.top &&\n          previousBounds.left === bounds.left &&\n          previousBounds.width === bounds.width &&\n          previousBounds.height === bounds.height\n        ) {\n          rafId = requestAnimationFrame(updateBounds);\n          return;\n        }\n        previousBounds = bounds;\n        rafId = requestAnimationFrame(updateBounds);\n      }\n\n      setBounds(bounds);\n    };\n\n    if (isActive) {\n      updateBounds();\n      setActiveClassName(activeClassName ?? \"\");\n    }\n\n    if (shouldUpdateBounds) return () => cancelAnimationFrame(rafId);\n  }, [\n    mode,\n    isActive,\n    activeValue,\n    setBounds,\n    activeClassName,\n    setActiveClassName,\n    forceUpdateBounds,\n    contextForceUpdateBounds,\n  ]);\n\n  if (!isValidChild || !element) return children;\n\n  const dataAttributes = {\n    \"data-active\": isActive ? \"true\" : \"false\",\n    \"aria-selected\": isActive,\n    \"data-disabled\": isDisabled,\n    \"data-value\": childValue,\n    \"data-highlight\": true,\n  };\n\n  const commonHandlers = hover\n    ? {\n        onMouseEnter: (e: React.MouseEvent<HTMLDivElement>) => {\n          setActiveValue(childValue);\n          element.props.onMouseEnter?.(e);\n        },\n        ...(mode !== \"parent\" && {\n          onMouseLeave: (e: React.MouseEvent<HTMLDivElement>) => {\n            setActiveValue(null);\n            element.props.onMouseLeave?.(e);\n          },\n        }),\n      }\n    : click\n      ? {\n          onClick: (e: React.MouseEvent<HTMLDivElement>) => {\n            setActiveValue(childValue);\n            element.props.onClick?.(e);\n          },\n        }\n      : {};\n\n  if (asChild) {\n    if (mode === \"children\") {\n      return React.cloneElement(\n        element,\n        {\n          key: childValue,\n          ref: refCallback,\n          className: cn(\"relative\", element.props.className),\n          ...getNonOverridingDataAttributes(element, {\n            ...dataAttributes,\n            \"data-slot\": \"motion-highlight-item-container\",\n          }),\n          ...commonHandlers,\n          ...props,\n        },\n        <>\n          <AnimatePresence initial={false} mode=\"wait\">\n            {isActive && !isDisabled && (\n              <motion.div\n                layoutId={`transition-background-${contextId}`}\n                data-slot=\"motion-highlight\"\n                style={{\n                  position: \"absolute\",\n                  zIndex: 0,\n                  scaleX,\n                  scaleY,\n                  ...contextStyle,\n                  ...style,\n                }}\n                className={cn(contextClassName, activeClassName)}\n                transition={itemTransition}\n                initial={{ opacity: 0 }}\n                animate={{ opacity: 1 }}\n                exit={{\n                  opacity: 0,\n                  transition: {\n                    ...itemTransition,\n                    delay:\n                      (itemTransition?.delay ?? 0) +\n                      (exitDelay ?? contextExitDelay ?? 0) / 1000,\n                  },\n                }}\n                {...dataAttributes}\n              />\n            )}\n          </AnimatePresence>\n\n          {React.createElement(\n            Component,\n            {\n              \"data-slot\": \"motion-highlight-item\",\n              style: { position: \"relative\", zIndex: 1 },\n              className,\n              ...dataAttributes,\n            },\n            children,\n          )}\n        </>,\n      );\n    }\n\n    return React.cloneElement(element, {\n      ref: refCallback,\n      ...getNonOverridingDataAttributes(element, {\n        ...dataAttributes,\n        \"data-slot\": \"motion-highlight-item\",\n      }),\n      ...commonHandlers,\n    });\n  }\n\n  return enabled\n    ? React.createElement(\n        Component,\n        {\n          key: childValue,\n          ref: localRef,\n          \"data-slot\": \"motion-highlight-item-container\",\n          className: cn(mode === \"children\" && \"relative\", className),\n          ...dataAttributes,\n          ...props,\n          ...commonHandlers,\n        },\n        <>\n          {mode === \"children\" && (\n            <AnimatePresence initial={false} mode=\"wait\">\n              {isActive && !isDisabled && (\n                <motion.div\n                  layoutId={`transition-background-${contextId}`}\n                  data-slot=\"motion-highlight\"\n                  style={{\n                    position: \"absolute\",\n                    zIndex: 0,\n                    scaleX,\n                    scaleY,\n                    ...contextStyle,\n                    ...style,\n                  }}\n                  className={cn(contextClassName, activeClassName)}\n                  transition={itemTransition}\n                  initial={{ opacity: 0 }}\n                  animate={{ opacity: 1 }}\n                  exit={{\n                    opacity: 0,\n                    transition: {\n                      ...itemTransition,\n                      delay:\n                        (itemTransition?.delay ?? 0) +\n                        (exitDelay ?? contextExitDelay ?? 0) / 1000,\n                    },\n                  }}\n                  {...dataAttributes}\n                />\n              )}\n            </AnimatePresence>\n          )}\n\n          {React.cloneElement(element, {\n            style: { position: \"relative\", zIndex: 1 },\n            className: element.props.className,\n            ...getNonOverridingDataAttributes(element, {\n              ...dataAttributes,\n              \"data-slot\": \"motion-highlight-item\",\n            }),\n          })}\n        </>,\n      )\n    : children;\n}\n\nexport {\n  Highlight,\n  HighlightItem,\n  useHighlight,\n  type HighlightProps,\n  type HighlightItemProps,\n};\n",
      "type": "registry:ui",
      "target": "components/unlumen-ui/primitives/effects/velocity-highlight.tsx"
    }
  ],
  "type": "registry:ui"
}