{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "achievement-unlocked",
  "title": "Achievement Unlocked",
  "description": "Celebratory React dialog for newly unlocked achievements with headline, badge, and share actions. Use for unlock moments, level up animations and more. Built on shadcn/ui + Tailwind. Open source.",
  "dependencies": [
    "lucide-react"
  ],
  "registryDependencies": [
    "achievement-badge",
    "button"
  ],
  "files": [
    {
      "path": "registry/trophy/ui/achievement-unlocked.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { CalendarDays, Share2, X } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\nimport {\n  AchievementBadge,\n  type UserAchievement,\n} from \"@/components/ui/achievement-badge\"\nimport { Button } from \"@/components/ui/button\"\n\n// Types (inlined - only fields used by this component)\ninterface Achievement {\n  id: string\n  name: string\n  trigger?: \"metric\" | \"api\" | \"streak\"\n  description?: string | null\n  unlockedAt?: string\n}\n\n// Props\ninterface AchievementUnlockedProps {\n  /** Achievement that was unlocked */\n  achievement: Achievement\n  /** Control open state */\n  open: boolean\n  /** Callback when open state changes */\n  onOpenChange: (open: boolean) => void\n  /** Label for the secondary action button */\n  secondaryActionLabel?: string\n  /** Callback for the secondary action button (e.g. social share) */\n  onSecondaryActionClick?: () => void\n  /** @deprecated Use onSecondaryActionClick instead */\n  onShare?: () => void\n  /** Custom class for the dialog */\n  className?: string\n}\n\nconst AchievementUnlocked = React.forwardRef<\n  HTMLDivElement,\n  AchievementUnlockedProps\n>(\n  (\n    {\n      achievement,\n      open,\n      onOpenChange,\n      secondaryActionLabel = \"Share\",\n      onSecondaryActionClick,\n      onShare,\n      className,\n    },\n    ref\n  ) => {\n    const handleSecondaryActionClick = onSecondaryActionClick ?? onShare\n    const unlockedDateLabel = React.useMemo(() => {\n      const input = achievement.unlockedAt ?? new Date().toISOString()\n      const date = new Date(input)\n      if (Number.isNaN(date.getTime())) return \"Earned today\"\n      return `Earned ${date.toLocaleDateString(undefined, {\n        day: \"numeric\",\n        month: \"short\",\n        year: \"numeric\",\n      })}`\n    }, [achievement.unlockedAt])\n\n    const badgeAchievement = React.useMemo<UserAchievement>(() => {\n      return {\n        id: achievement.id,\n        name: achievement.name,\n        trigger: achievement.trigger ?? \"streak\",\n        achievedAt: achievement.unlockedAt ?? new Date().toISOString(),\n      }\n    }, [achievement])\n\n    // Handle escape key\n    React.useEffect(() => {\n      if (!open) return\n\n      const handleEscape = (e: KeyboardEvent) => {\n        if (e.key === \"Escape\") {\n          onOpenChange(false)\n        }\n      }\n\n      document.addEventListener(\"keydown\", handleEscape)\n      return () => document.removeEventListener(\"keydown\", handleEscape)\n    }, [open, onOpenChange])\n\n    // Prevent body scroll when open\n    React.useEffect(() => {\n      if (open) {\n        document.body.style.overflow = \"hidden\"\n      } else {\n        document.body.style.overflow = \"\"\n      }\n      return () => {\n        document.body.style.overflow = \"\"\n      }\n    }, [open])\n\n    if (!open) return null\n\n    return (\n      <>\n        {/* Backdrop */}\n        <div\n          className=\"bg-foreground/80 fixed inset-0 z-50\"\n          onClick={() => onOpenChange(false)}\n          aria-hidden=\"true\"\n        />\n\n        {/* Dialog */}\n        <div\n          ref={ref}\n          role=\"dialog\"\n          aria-modal=\"true\"\n          aria-labelledby=\"achievement-title\"\n          className={cn(\n            \"fixed top-1/2 left-1/2 z-50 -translate-x-1/2 -translate-y-1/2\",\n            \"bg-card w-full max-w-md rounded-xl p-6 shadow-2xl\",\n            className\n          )}\n        >\n          {/* Close button */}\n          <Button\n            variant=\"ghost\"\n            size=\"icon\"\n            onClick={() => onOpenChange(false)}\n            aria-label=\"Close\"\n            className=\"absolute top-4 right-4\"\n          >\n            <X className=\"h-4 w-4\" />\n          </Button>\n\n          {/* Content */}\n          <div className=\"flex flex-col items-center text-center\">\n            <AchievementBadge\n              achievement={badgeAchievement}\n              badgeSize=\"xl\"\n              className=\"mt-8 mb-12 border-0 bg-transparent p-0 shadow-none hover:shadow-none [&>span:last-child]:hidden\"\n            />\n\n            <span className=\"text-muted-foreground mb-4 inline-flex items-center gap-1 rounded-full border px-3 py-1 text-sm\">\n              <CalendarDays className=\"h-4 w-4\" />\n              {unlockedDateLabel}\n            </span>\n\n            <h2\n              id=\"achievement-title\"\n              className=\"mb-2 text-4xl font-bold tracking-tight\"\n            >\n              {achievement.name}\n            </h2>\n\n            {achievement.description && (\n              <p className=\"text-muted-foreground mb-3 text-lg\">\n                {achievement.description}\n              </p>\n            )}\n\n            <div className=\"mt-8 flex gap-3\">\n              {handleSecondaryActionClick && (\n                <Button\n                  variant=\"outline\"\n                  size=\"lg\"\n                  onClick={handleSecondaryActionClick}\n                >\n                  <Share2 className=\"h-4 w-4\" />\n                  {secondaryActionLabel}\n                </Button>\n              )}\n\n              <Button onClick={() => onOpenChange(false)}>Awesome!</Button>\n            </div>\n          </div>\n        </div>\n      </>\n    )\n  }\n)\nAchievementUnlocked.displayName = \"AchievementUnlocked\"\n\nexport { AchievementUnlocked }\nexport type { AchievementUnlockedProps, Achievement }\n",
      "type": "registry:ui"
    }
  ],
  "type": "registry:ui"
}