{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "action-hint",
  "type": "registry:component",
  "title": "Action Hint",
  "description": "A particle emitter for showing ephemeral action feedback hints that fade out.",
  "registryDependencies": [
    "@joyco/utils"
  ],
  "files": [
    {
      "path": "registry/components/action-hint.tsx",
      "content": "'use client'\n\nimport * as React from 'react'\nimport { cn } from '@/lib/utils'\n\n/* -------------------------------------------------------------------------------------------------\n * Types\n * -----------------------------------------------------------------------------------------------*/\n\ntype Align = 'start' | 'center' | 'end'\n\ntype Particle = {\n  id: number\n  content: React.ReactNode\n}\n\ntype ActionHintContextValue = {\n  emit: (content: React.ReactNode) => void\n}\n\n/* -------------------------------------------------------------------------------------------------\n * Context\n * -----------------------------------------------------------------------------------------------*/\n\nconst ActionHintContext = React.createContext<ActionHintContextValue | null>(\n  null\n)\n\nexport function useActionHint() {\n  const context = React.useContext(ActionHintContext)\n  if (!context) {\n    throw new Error('useActionHint must be used within an ActionHintEmitter')\n  }\n  return context\n}\n\n/* -------------------------------------------------------------------------------------------------\n * ActionHintEmitter\n * -----------------------------------------------------------------------------------------------*/\n\ninterface ActionHintEmitterProps {\n  children: React.ReactNode\n  className?: string\n  /** Horizontal alignment of the hint relative to the emitter */\n  align?: Align\n  /** Vertical margin (gap) between the hint and the emitter in pixels */\n  margin?: number\n  /** Duration of the fade animation in milliseconds */\n  duration?: number\n}\n\nexport const ActionHintEmitter = React.forwardRef<\n  HTMLDivElement,\n  ActionHintEmitterProps\n>(function ActionHintEmitter(\n  { children, className, align = 'center', margin = 4, duration = 1500 },\n  forwardedRef\n) {\n  const [particles, setParticles] = React.useState<Particle[]>([])\n  const idRef = React.useRef(0)\n\n  const emit = React.useCallback(\n    (content: React.ReactNode) => {\n      const id = idRef.current++\n\n      // Clear all existing particles and add new one\n      setParticles([{ id, content }])\n\n      // Remove after animation completes\n      setTimeout(() => {\n        setParticles((prev) => prev.filter((p) => p.id !== id))\n      }, duration)\n    },\n    [duration]\n  )\n\n  const contextValue = React.useMemo(() => ({ emit }), [emit])\n\n  return (\n    <ActionHintContext.Provider value={contextValue}>\n      <div ref={forwardedRef} className={cn('relative inline-flex', className)}>\n        {children}\n        {particles.map((particle) => (\n          <ActionHintParticle\n            key={particle.id}\n            align={align}\n            margin={margin}\n            duration={duration}\n          >\n            {particle.content}\n          </ActionHintParticle>\n        ))}\n      </div>\n    </ActionHintContext.Provider>\n  )\n})\n\n/* -------------------------------------------------------------------------------------------------\n * ActionHintParticle\n * -----------------------------------------------------------------------------------------------*/\n\nconst alignToPosition: Record<\n  Align,\n  { left?: string; right?: string; transform: string }\n> = {\n  start: { left: '0', transform: 'translateY(-100%)' },\n  center: { left: '50%', transform: 'translateX(-50%) translateY(-100%)' },\n  end: { right: '0', transform: 'translateY(-100%)' },\n}\n\nfunction ActionHintParticle({\n  children,\n  align,\n  margin,\n  duration,\n}: {\n  children: React.ReactNode\n  align: Align\n  margin: number\n  duration: number\n}) {\n  const positionStyles = alignToPosition[align]\n\n  return (\n    <>\n      <style>\n        {`\n          @keyframes action-hint-fade {\n            0% {\n              opacity: 1;\n              margin-top: var(--action-hint-margin);\n            }\n            100% {\n              opacity: 0;\n              margin-top: calc(var(--action-hint-margin) - 1rem);\n            }\n          }\n        `}\n      </style>\n      <div\n        data-slot=\"action-hint-particle\"\n        className={cn(\n          'pointer-events-none absolute top-0 z-50',\n          'rounded-md px-2 py-1 text-xs font-medium whitespace-nowrap shadow-sm',\n          'bg-secondary text-secondary-foreground'\n        )}\n        style={\n          {\n            ...positionStyles,\n            '--action-hint-margin': `${-margin}px`,\n            animation: `action-hint-fade ${duration}ms ease-out forwards`,\n          } as React.CSSProperties\n        }\n      >\n        {children}\n      </div>\n    </>\n  )\n}\n",
      "type": "registry:component"
    }
  ]
}