Cursor Bubble
sora-ui/cursor-bubbleFreeComponent
A floating label that follows the cursor and elastic-pops into view over interactive targets, powered by GSAP.
Install it
npx shadcn@latest add https://ui.soralabs.io.vn/r/cursor-bubble.jsonSource
1/** biome-ignore-all lint/a11y/noNoninteractiveElementInteractions: The target wraps its own interactive child; the span only needs hover affordances. */2/** biome-ignore-all lint/a11y/noStaticElementInteractions: Same as above — hover-only, no keyboard/click semantics of its own. */34"use client";56import { useGSAP } from "@gsap/react";7import { cn } from "@/lib/utils";8import gsap from "gsap";9import {10 type ComponentPropsWithoutRef,11 createContext,12 type ReactNode,13 useContext,14 useEffect,15 useMemo,16 useRef,17 useState,18} from "react";19import { usePrefersReducedMotion } from "@/hooks/use-prefers-reduced-motion";2021const DEFAULT_LABEL = "click";22const CURSOR_OFFSET_X = 13;23const CURSOR_OFFSET_Y = -43;24const FOLLOW_DURATION = 0.5;25const POP_IN_DURATION = 1.7;26const POP_IN_DELAY = 0.1;27const POP_OUT_DURATION = 0.3;28const RESTING_ROTATION = -30;2930interface CursorBubbleContextValue {31 hide: () => void;32 show: (label: string) => void;33}3435const CursorBubbleContext = createContext<CursorBubbleContextValue | null>(36 null37);3839function useCursorBubbleContext() {40 const context = useContext(CursorBubbleContext);4142 if (!context) {43 throw new Error(44 "CursorBubbleTarget must be used within a CursorBubble provider."45 );46 }4748 return context;49}5051function useCoarsePointer() {52 const [isCoarsePointer, setIsCoarsePointer] = useState(false);5354 useEffect(() => {55 const mediaQuery = window.matchMedia("(pointer: coarse)");5657 const update = () => {58 setIsCoarsePointer(mediaQuery.matches);59 };6061 update();62 mediaQuery.addEventListener("change", update);6364 return () => {65 mediaQuery.removeEventListener("change", update);66 };67 }, []);6869 return isCoarsePointer;70}7172export interface CursorBubbleProps73 extends Omit<ComponentPropsWithoutRef<"div">, "children"> {74 /** Extra classes applied to the floating bubble itself (color, padding, shape, etc). */75 bubbleClassName?: string;76 children?: ReactNode;77}7879function CursorBubble({80 bubbleClassName,81 children,82 className,83 ...props84}: CursorBubbleProps) {85 const bubbleRef = useRef<HTMLDivElement>(null);86 const showRef = useRef<(label: string) => void>(() => undefined);87 const hideRef = useRef<() => void>(() => undefined);88 const prefersReducedMotion = usePrefersReducedMotion();89 const isCoarsePointer = useCoarsePointer();90 const disabled = prefersReducedMotion || isCoarsePointer;9192 useGSAP(93 () => {94 const bubble = bubbleRef.current;95// … truncated
What it pulls in
npm packages
Other registry items
Files it writes
More from Sora UI
All 97 items| Component | Registry | Kind | Access | Installs | Command |
|---|---|---|---|---|---|
| Accordionaccordion | Sora UI | Components | Free | 2 deps | |
| Border Trailborder-trail | Sora UI | Components | Free | 1 dep | |
| Bottom Sheetbottom-sheet | Sora UI | Components | Free | 2 deps | |
| Cursor Trail Revealcursor-trail-reveal | Sora UI | Components | Free | 1 dep | |
| Custom Cursorcustom-cursor | Sora UI | Components | Free | 2 deps | |
| Accordion Demodemo-accordion | Sora UI | Components | Free | no deps | |
| Border Trail Demodemo-border-trail | Sora UI | Components | Free | no deps | |
| Border Trail Loading Card Demodemo-border-trail-loading | Sora UI | Components | Free | no deps |
