{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "hover-card-motion",
  "title": "Card-motion hover",
  "description": "Styled hover: the Card-motion set. 5 decorative hover variations (FadeHoverCard, ScaleHoverCard, SlideHoverCard, PopHoverCard, SpringHoverCard) on the ai2 token system, powered by framer-motion, reduced-motion aware and sized sm to xl. Part of the free styled layer.",
  "dependencies": [
    "motion@^12.42.2"
  ],
  "registryDependencies": [
    "@ai2/tokens"
  ],
  "files": [
    {
      "path": "registry/ai2/styled/hover-card-motion.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { AnimatePresence, motion, useReducedMotion } from \"motion/react\"\n\nimport { cn } from \"@/lib/utils\"\n\n/* Motion hover-card family: 5 showy hover cards. The card itself is plain\n   (bg-popover); the DIFFERENCE is the opening entrance animation. As in essentials\n   it is a trigger + a card positioned below it, opened on HOVER (mouseenter/leave)\n   AND focus (focus/blur); NO radix or portal, self-contained. AnimatePresence\n   carries the enter/exit animation. The card is centered horizontally with x:\"-50%\"\n   (a motion value rather than the translate CSS, so motion does not override the\n   transform). Under reduced motion ALL transforms drop away and only an opacity\n   fade remains. Color comes ONLY from tokens; alpha via color-mix. Size = card\n   width. trigger = the trigger, children = the card content. */\n\nexport type StyledSize = \"sm\" | \"md\" | \"lg\" | \"xl\"\n\nconst cardWidth: Record<StyledSize, string> = {\n  sm: \"w-56\",\n  md: \"w-64\",\n  lg: \"w-72\",\n  xl: \"w-80\",\n}\n\n/* Card shell: position plus appearance. Horizontal centring is done with the motion x value, which is why there is NO -translate-x-1/2 here. lucide svg and remixicon i are kept at parity for icon compatibility. */\nconst cardBase =\n  \"absolute left-1/2 top-full z-20 mt-2 block origin-top rounded-xl border border-border bg-popover p-4 text-left text-popover-foreground shadow-lg [&_svg]:size-4 [&_svg]:shrink-0 [&_i]:text-base [&_i]:leading-none\"\n\n/* Trigger link appearance plus focus ring (same as essentials). */\nconst triggerBase =\n  \"inline-flex items-center rounded-md font-medium text-primary underline-offset-4 outline-none hover:underline focus-visible:ring-[3px] focus-visible:ring-ring/50\"\n\ninterface Props {\n  size?: StyledSize\n  trigger?: React.ReactNode\n  children?: React.ReactNode\n  className?: string\n}\n\n/* The enter/exit animation supplied from outside; it differs in every export. Every value carries x:\"-50%\" so the card stays horizontally centred (the motion transform must not override it). */\ntype CardMotion = {\n  initial: Record<string, number | string>\n  animate: Record<string, number | string>\n  exit: Record<string, number | string>\n  transition?: Record<string, unknown>\n}\n\n/* Shared shell: trigger plus an AnimatePresence card. Hover AND focus are tracked separately, and the card is visible while either is active (it stays open on focus even after the mouse leaves). */\nfunction HoverCardShell({\n  props,\n  cardMotion,\n  cardClassName,\n}: {\n  props: Props\n  cardMotion: CardMotion\n  cardClassName?: string\n}) {\n  const { size = \"md\", trigger, children, className } = props\n  const [hovered, setHovered] = React.useState(false)\n  const [focused, setFocused] = React.useState(false)\n  const reduce = useReducedMotion()\n  const isOpen = hovered || focused\n\n  /* reduced motion: no transform, fade only; x is kept for centring. */\n  const fade: CardMotion = {\n    initial: { opacity: 0, x: \"-50%\" },\n    animate: { opacity: 1, x: \"-50%\" },\n    exit: { opacity: 0, x: \"-50%\" },\n    transition: { duration: 0.18, ease: \"easeOut\" },\n  }\n  const m = reduce ? fade : cardMotion\n\n  return (\n    <span\n      data-slot=\"styled-hover-card\"\n      className=\"relative inline-flex\"\n      onMouseEnter={() => setHovered(true)}\n      onMouseLeave={() => setHovered(false)}\n      onFocus={() => setFocused(true)}\n      onBlur={() => setFocused(false)}\n    >\n      {trigger ?? (\n        <button type=\"button\" className={triggerBase}>\n          Hover me\n        </button>\n      )}\n\n      <AnimatePresence>\n        {isOpen ? (\n          <motion.span\n            role=\"dialog\"\n            className={cn(cardBase, cardWidth[size], cardClassName, className)}\n            initial={m.initial}\n            animate={m.animate}\n            exit={m.exit}\n            transition={m.transition ?? { duration: 0.18, ease: \"easeOut\" }}\n          >\n            {children ?? \"A quick preview card that opens on hover or focus.\"}\n          </motion.span>\n        ) : null}\n      </AnimatePresence>\n    </span>\n  )\n}\n\n/* Fade: it appears softly with opacity alone (no transform). */\nexport function FadeHoverCard(props: Props) {\n  return (\n    <HoverCardShell\n      props={props}\n      cardMotion={{\n        initial: { opacity: 0, x: \"-50%\" },\n        animate: { opacity: 1, x: \"-50%\" },\n        exit: { opacity: 0, x: \"-50%\" },\n        transition: { duration: 0.18, ease: \"easeOut\" },\n      }}\n    />\n  )\n}\n\n/* Scale: it opens by growing from the top edge (origin-top, scale 0.92 -> 1). */\nexport function ScaleHoverCard(props: Props) {\n  return (\n    <HoverCardShell\n      props={props}\n      cardMotion={{\n        initial: { opacity: 0, x: \"-50%\", scale: 0.92 },\n        animate: { opacity: 1, x: \"-50%\", scale: 1 },\n        exit: { opacity: 0, x: \"-50%\", scale: 0.96 },\n        transition: { duration: 0.18, ease: \"easeOut\" },\n      }}\n    />\n  )\n}\n\n/* Slide: alttan yukari kayarak girer (y 8 -> 0). */\nexport function SlideHoverCard(props: Props) {\n  return (\n    <HoverCardShell\n      props={props}\n      cardMotion={{\n        initial: { opacity: 0, x: \"-50%\", y: 8 },\n        animate: { opacity: 1, x: \"-50%\", y: 0 },\n        exit: { opacity: 0, x: \"-50%\", y: 6 },\n        transition: { duration: 0.2, ease: \"easeOut\" },\n      }}\n    />\n  )\n}\n\n/* Pop: dusuk damping spring ile overshoot yaparak patlar (scale 0.8 -> 1). */\nexport function PopHoverCard(props: Props) {\n  return (\n    <HoverCardShell\n      props={props}\n      cardMotion={{\n        initial: { opacity: 0, x: \"-50%\", scale: 0.8 },\n        animate: { opacity: 1, x: \"-50%\", scale: 1 },\n        exit: { opacity: 0, x: \"-50%\", scale: 0.9 },\n        transition: { type: \"spring\", stiffness: 500, damping: 12 },\n      }}\n    />\n  )\n}\n\n/* Spring: yumusak spring ile hafif yukselerek ve buyuyerek girer. */\nexport function SpringHoverCard(props: Props) {\n  return (\n    <HoverCardShell\n      props={props}\n      cardMotion={{\n        initial: { opacity: 0, x: \"-50%\", y: 10, scale: 0.96 },\n        animate: { opacity: 1, x: \"-50%\", y: 0, scale: 1 },\n        exit: { opacity: 0, x: \"-50%\", y: 6 },\n        transition: { type: \"spring\", stiffness: 300, damping: 22 },\n      }}\n    />\n  )\n}\n",
      "type": "registry:component",
      "target": "components/ui/hover-card-motion.tsx"
    }
  ],
  "categories": [
    "styled",
    "hover"
  ],
  "type": "registry:component"
}