{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "hover-card",
  "title": "Hover Card",
  "description": "A preview that appears over a link when the pointer rests on it, laid out for free content, a person, a page or a number.",
  "dependencies": [
    "radix-ui"
  ],
  "registryDependencies": [
    "utils"
  ],
  "files": [
    {
      "path": "registry/miseui/ui/hover-card.tsx",
      "content": "import type * as React from \"react\"\nimport { HoverCard as HoverCardPrimitive } from \"radix-ui\"\nimport {\n  LinkPreviewBody,\n  ProfileBody,\n  StatBody,\n  sparklinePoints,\n  type HoverCardFact,\n  type HoverCardLinkPreviewBody,\n  type HoverCardProfileBody,\n  type HoverCardStatBody,\n} from \"@/registry/miseui/ui/hover-card-bodies\"\nimport { cn } from \"@/registry/miseui/lib/utils\"\n\n/** Long enough that a cursor crossing the link on its way elsewhere does not pop a card. */\nconst OPEN_DELAY_MS = 300\n/** Long enough to travel from the trigger into the card. */\nconst CLOSE_DELAY_MS = 150\nconst SIDE_OFFSET_PX = 8\nconst COLLISION_PADDING_PX = 8\n\ntype HoverCardVariant = \"plain\" | \"profile\" | \"link-preview\" | \"stat\"\ntype HoverCardEffect = \"materialize\"\n\nconst PANEL_CLASS: Record<HoverCardVariant, string> = {\n  plain: \"w-64 p-4 text-[13px] leading-5\",\n  profile: \"w-80 p-4\",\n  \"link-preview\": \"w-80 overflow-hidden p-0\",\n  stat: \"w-56 p-4\",\n}\n\nconst PLAIN_MOTION_CLASS = [\n  \"duration-(--duration-fast) ease-(--ease-mise)\",\n  \"data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95\",\n  \"data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95\",\n  \"data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2\",\n  \"data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2\",\n]\n\n/** The enter and exit values stay unqualified so the media overrides below win on source order. */\nconst MATERIALIZE_CLASS = [\n  \"duration-(--duration-base) ease-(--ease-mise-out) will-change-[transform,opacity,filter]\",\n  \"data-[state=open]:animate-in data-[state=closed]:animate-out\",\n  \"fade-in-0 zoom-in-90 blur-in-sm fade-out-0 zoom-out-90 blur-out-sm\",\n  \"motion-reduce:zoom-in-100 motion-reduce:zoom-out-100 motion-reduce:blur-in-0 motion-reduce:blur-out-0\",\n  \"[@media(prefers-reduced-transparency:reduce)]:blur-in-0 [@media(prefers-reduced-transparency:reduce)]:blur-out-0\",\n]\n\nfunction HoverCard({\n  openDelay = OPEN_DELAY_MS,\n  closeDelay = CLOSE_DELAY_MS,\n  ...props\n}: React.ComponentProps<typeof HoverCardPrimitive.Root>) {\n  return <HoverCardPrimitive.Root data-slot=\"hover-card\" openDelay={openDelay} closeDelay={closeDelay} {...props} />\n}\n\nfunction HoverCardTrigger(props: React.ComponentProps<typeof HoverCardPrimitive.Trigger>) {\n  return <HoverCardPrimitive.Trigger data-slot=\"hover-card-trigger\" {...props} />\n}\n\ntype HoverCardPanelProps = Omit<React.ComponentProps<typeof HoverCardPrimitive.Content>, \"title\"> & {\n  /** `materialize` grows the card out of its trigger with scale, blur and opacity together. */\n  effect?: HoverCardEffect\n}\n\ntype HoverCardPlainBody = { variant?: \"plain\"; children?: React.ReactNode }\n\ntype HoverCardContentProps = HoverCardPanelProps &\n  (HoverCardPlainBody | HoverCardProfileBody | HoverCardLinkPreviewBody | HoverCardStatBody)\n\n/** Body fields are not DOM attributes; everything else on the props goes to the Radix panel. */\nconst BODY_KEYS: ReadonlySet<string> = new Set([\n  \"variant\", \"name\", \"handle\", \"avatar\", \"bio\", \"facts\", \"title\", \"description\", \"domain\", \"media\", \"label\", \"value\",\n  \"change\", \"trend\", \"children\", \"effect\", \"className\", \"sideOffset\", \"collisionPadding\",\n])\n\nfunction panelAttributes(props: HoverCardContentProps) {\n  return Object.fromEntries(Object.entries(props).filter(([key]) => !BODY_KEYS.has(key)))\n}\n\nfunction HoverCardBody(props: HoverCardContentProps) {\n  switch (props.variant) {\n    case \"profile\":\n      return <ProfileBody {...props} />\n    case \"link-preview\":\n      return <LinkPreviewBody {...props} />\n    case \"stat\":\n      return <StatBody {...props} />\n    default:\n      return props.children\n  }\n}\n\nfunction HoverCardContent(props: HoverCardContentProps) {\n  const {\n    variant = \"plain\",\n    effect,\n    className,\n    sideOffset = SIDE_OFFSET_PX,\n    collisionPadding = COLLISION_PADDING_PX,\n  } = props\n  return (\n    <HoverCardPrimitive.Portal>\n      <HoverCardPrimitive.Content\n        data-slot=\"hover-card-content\"\n        data-variant={variant}\n        data-effect={effect}\n        sideOffset={sideOffset}\n        collisionPadding={collisionPadding}\n        className={cn(\n          \"z-50 max-w-[calc(100vw-16px)] rounded-lg border bg-popover text-popover-foreground shadow-md outline-hidden\",\n          \"origin-(--radix-hover-card-content-transform-origin)\",\n          PANEL_CLASS[variant],\n          effect === \"materialize\" ? MATERIALIZE_CLASS : PLAIN_MOTION_CLASS,\n          className,\n        )}\n        {...panelAttributes(props)}\n      >\n        <HoverCardBody {...props} />\n      </HoverCardPrimitive.Content>\n    </HoverCardPrimitive.Portal>\n  )\n}\n\nexport {\n  HoverCard,\n  HoverCardTrigger,\n  HoverCardContent,\n  sparklinePoints,\n  type HoverCardContentProps,\n  type HoverCardVariant,\n  type HoverCardEffect,\n  type HoverCardFact,\n}\n",
      "type": "registry:ui"
    },
    {
      "path": "registry/miseui/ui/hover-card-bodies.tsx",
      "content": "import type * as React from \"react\"\n\ninterface HoverCardFact {\n  label: React.ReactNode\n  value: React.ReactNode\n}\n\ninterface HoverCardProfileBody {\n  variant: \"profile\"\n  name: React.ReactNode\n  handle?: React.ReactNode\n  /** Usually an Avatar. */\n  avatar?: React.ReactNode\n  bio?: React.ReactNode\n  /** Short numbers along the bottom, such as followers or repositories. */\n  facts?: readonly HoverCardFact[]\n}\n\ninterface HoverCardLinkPreviewBody {\n  variant: \"link-preview\"\n  title: React.ReactNode\n  description?: React.ReactNode\n  /** Where the link goes, shown last so the title is read first. */\n  domain?: React.ReactNode\n  /** Bleeds to the edges of the card. An image or your own drawing. */\n  media?: React.ReactNode\n}\n\ninterface HoverCardStatBody {\n  variant: \"stat\"\n  label: React.ReactNode\n  value: React.ReactNode\n  /** The comparison that gives the number its meaning, such as \"up 12% on last week\". */\n  change?: React.ReactNode\n  /** Drawn as a sparkline. Needs at least two values. */\n  trend?: readonly number[]\n}\n\nconst TREND_WIDTH = 192\nconst TREND_HEIGHT = 32\n/** Keeps the stroke from being clipped at the top and bottom of the box. */\nconst TREND_INSET = 2\n\n/** Polyline points for a trend. A flat series sits on the middle line instead of dividing by zero. */\nfunction sparklinePoints(values: readonly number[], width: number, height: number, inset = TREND_INSET) {\n  if (values.length < 2) return \"\"\n  const low = Math.min(...values)\n  const range = Math.max(...values) - low\n  const usable = height - inset * 2\n  return values\n    .map((entry, index) => {\n      const x = (index / (values.length - 1)) * width\n      const y = range === 0 ? height / 2 : inset + usable - ((entry - low) / range) * usable\n      return `${Number(x.toFixed(2))},${Number(y.toFixed(2))}`\n    })\n    .join(\" \")\n}\n\nfunction ProfileBody({ name, handle, avatar, bio, facts }: HoverCardProfileBody) {\n  return (\n    <div data-part=\"profile\" className=\"grid gap-3\">\n      <div className=\"flex items-center gap-3\">\n        {avatar}\n        <div className=\"min-w-0\">\n          <p className=\"truncate text-sm font-medium\">{name}</p>\n          {handle ? <p className=\"truncate text-[13px] text-muted-foreground\">{handle}</p> : null}\n        </div>\n      </div>\n      {bio ? <p className=\"text-[13px] leading-5 text-muted-foreground\">{bio}</p> : null}\n      {facts && facts.length > 0 ? (\n        <dl className=\"flex flex-wrap gap-x-4 gap-y-1 text-[13px]\">\n          {facts.map((fact, index) => (\n            <div key={index} className=\"flex flex-row-reverse items-baseline gap-1\">\n              <dt className=\"text-muted-foreground\">{fact.label}</dt>\n              <dd className=\"font-medium tabular-nums\">{fact.value}</dd>\n            </div>\n          ))}\n        </dl>\n      ) : null}\n    </div>\n  )\n}\n\nfunction LinkPreviewBody({ title, description, domain, media }: HoverCardLinkPreviewBody) {\n  return (\n    <div data-part=\"link-preview\">\n      {media ? (\n        <div data-part=\"media\" className=\"aspect-2/1 overflow-hidden border-b bg-muted [&>img]:size-full [&>img]:object-cover\">\n          {media}\n        </div>\n      ) : null}\n      <div className=\"grid gap-1 p-4\">\n        <p className=\"line-clamp-2 text-sm font-medium\">{title}</p>\n        {description ? <p className=\"line-clamp-2 text-[13px] leading-5 text-muted-foreground\">{description}</p> : null}\n        {domain ? <p className=\"pt-1 text-xs text-muted-foreground\">{domain}</p> : null}\n      </div>\n    </div>\n  )\n}\n\nfunction StatBody({ label, value, change, trend }: HoverCardStatBody) {\n  const points = trend ? sparklinePoints(trend, TREND_WIDTH, TREND_HEIGHT) : \"\"\n  return (\n    <div data-part=\"stat\" className=\"grid gap-1\">\n      <p className=\"text-[13px] text-muted-foreground\">{label}</p>\n      <p className=\"text-2xl font-semibold tracking-tight tabular-nums\">{value}</p>\n      {change ? <p className=\"text-[13px] text-muted-foreground\">{change}</p> : null}\n      {points ? (\n        <svg\n          aria-hidden=\"true\"\n          data-part=\"trend\"\n          viewBox={`0 0 ${TREND_WIDTH} ${TREND_HEIGHT}`}\n          preserveAspectRatio=\"none\"\n          className=\"mt-2 h-8 w-full\"\n        >\n          <polyline\n            points={points}\n            fill=\"none\"\n            stroke=\"currentColor\"\n            strokeWidth=\"1.5\"\n            strokeLinecap=\"round\"\n            strokeLinejoin=\"round\"\n            vectorEffect=\"non-scaling-stroke\"\n            className=\"text-mise\"\n          />\n        </svg>\n      ) : null}\n    </div>\n  )\n}\n\nexport {\n  LinkPreviewBody,\n  ProfileBody,\n  StatBody,\n  sparklinePoints,\n  type HoverCardFact,\n  type HoverCardLinkPreviewBody,\n  type HoverCardProfileBody,\n  type HoverCardStatBody,\n}\n",
      "type": "registry:ui"
    }
  ],
  "categories": [
    "overlays"
  ],
  "type": "registry:ui"
}