Hover Card
optics/hover-cardFreeComponent
For sighted users to preview content available behind a link.
See it running
Open the demo on optics
optics.agusmayol.com.ar/components/hover-cardInstall it
npx shadcn@latest add https://optics.agusmayol.com.ar/r/hover-card.jsonSource
1"use client";23import * as React from "react";4import { PreviewCard as PreviewCardPrimitive } from "@base-ui/react/preview-card";56import { cn } from "@/lib/utils";78// Context local para cada instancia de HoverCard para manejar estados de interacción9const HoverCardInstanceContext = React.createContext({10 shouldKeepOpenRef: { current: false },11 setShouldKeepOpen: () => {},12 isPointerOverTriggerRef: { current: false },13 isPointerOverContentRef: { current: false },14 openHoverCard: () => {},15 delay: 600,16});1718function HoverCard({19 open: controlledOpen,20 onOpenChange,21 delay = 600,22 ...props23} = {}) {24 const [uncontrolledOpen, setUncontrolledOpen] = React.useState(false);2526 const isControlled = controlledOpen !== undefined;27 const isOpen = isControlled ? controlledOpen : uncontrolledOpen;2829 // Refs locales para cada instancia de HoverCard30 const shouldKeepOpenRef = React.useRef(false);31 const isPointerOverTriggerRef = React.useRef(false);32 const isPointerOverContentRef = React.useRef(false);3334 const setShouldKeepOpen = React.useCallback((value) => {35 shouldKeepOpenRef.current = value;36 }, []);3738 const openHoverCard = React.useCallback(() => {39 if (isControlled) {40 onOpenChange?.(true);41 } else {42 setUncontrolledOpen(true);43 }44 }, [isControlled, onOpenChange]);4546 function handleOpenChange(nextOpen, eventDetails) {47 // Lógica para mantener abierto si el mouse está sobre el trigger o el contenido48 if (!nextOpen) {49 if (50 isPointerOverTriggerRef.current ||51 isPointerOverContentRef.current ||52 shouldKeepOpenRef.current53 ) {54 if (isControlled) {55 onOpenChange?.(true);56 } else {57 setUncontrolledOpen(true);58 }59 return;60 }61 }6263 if (!isControlled) {64 setUncontrolledOpen(nextOpen);65 }6667 if (nextOpen) {68 setShouldKeepOpen(true);69 } else {70 setShouldKeepOpen(false);71 }7273 onOpenChange?.(nextOpen, eventDetails);74 }7576 return (77 <HoverCardInstanceContext.Provider78 value={{79 shouldKeepOpenRef,80 setShouldKeepOpen,81 isPointerOverTriggerRef,82 isPointerOverContentRef,83 openHoverCard,84 delay,85 }}86 >87 <PreviewCardPrimitive.Root88 data-slot="hover-card"89 open={isOpen}90 onOpenChange={handleOpenChange}91 delay={delay}92 {...props}93 />94 </HoverCardInstanceContext.Provider>95 );96}9798function HoverCardTrigger({99 onClick,100 onPointerDown,101 onPointerLeave,102 onPointerEnter,103 onTouchStart,104 onTouchEnd,105 onTouchCancel,106 ...props107} = {}) {108 const {109 setShouldKeepOpen,110 isPointerOverTriggerRef,111// … truncated
What it pulls in
npm packages
Other registry items
Files it writes
More from optics
All 79 items| Component | Registry | Kind | Access | Installs | Command |
|---|---|---|---|---|---|
| Accordionaccordion | optics | Components | Free | 5 deps | |
| Alertalert | optics | Components | Free | 4 deps | |
| Alert Dialogalert-dialog | optics | Components | Free | 4 deps | |
| Aspect Ratioaspect-ratio | optics | Components | Free | 2 deps | |
| Auto Heightauto-height | optics | Components | Free | 3 deps | |
| Avataravatar | optics | Components | Free | 3 deps | |
| Badgebadge | optics | Components | Free | 4 deps | |
| Breadcrumbbreadcrumb | optics | Components | Free | 4 deps |
