{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "drop-overlay",
  "title": "Drop Overlay",
  "author": "Visant Labs <visantsupply@gmail.com>",
  "description": "A camada que aparece quando um arquivo está sendo arrastado sobre a área.",
  "dependencies": [
    "lucide-react"
  ],
  "registryDependencies": [
    "utils"
  ],
  "files": [
    {
      "path": "registry/visant/ui/drop-overlay.tsx",
      "content": "import { ArrowDownToLine, Check } from \"lucide-react\";\n\nimport { cn } from \"@/lib/utils\";\n\n/**\n * Alvo de soltar arquivo — a confirmação visual do drag-and-drop.\n *\n * Pequeno, mas não opcional: sem ele o usuário arrasta um arquivo sobre a área e\n * não tem sinal nenhum de que a página aceita, então solta fora e acha que o\n * upload está quebrado. É o estado que todo mundo esquece de desenhar.\n *\n * ── Os três estados, que são o item inteiro ────────────────────────────────\n *\n * | `state` | quando | o que a pessoa vê |\n * |---|---|---|\n * | `idle` | ninguém arrastando | nada — a zona é o conteúdo |\n * | `over` | arquivo pairando | tracejado + convite |\n * | `drop` | soltou, sistema aceitou | cheio na cor da marca, por um instante |\n *\n * O `drop` existe porque o instante entre soltar e o arquivo aparecer na lista é\n * onde o gesto some: sem uma confirmação de meio segundo, quem soltou não sabe\n * se acertou a zona ou se largou fora e a página ignorou.\n *\n * ── Dois detalhes que fazem funcionar ──────────────────────────────────────\n *\n * `pointer-events-none`: o overlay aparece POR CIMA da zona, e se ele capturasse\n * o ponteiro dispararia o `dragleave` do próprio pai — o overlay piscaria\n * infinitamente e o `drop` nunca chegaria.\n *\n * A camada **fica montada** e troca de opacidade, em vez de desmontar. Overlay\n * que nasce e morre aparece com corte seco no quadro exato em que o cursor cruza\n * a borda, e o corte lê como falha de render; a transição lê como resposta.\n * Montado e invisível não custa nada — é um `div` sem filho pintado.\n *\n * A borda tracejada é a convenção de \"solte aqui\" desde sempre; sólida no estado\n * de espera lê como erro. No `drop` ela fecha, porque aí não é mais convite.\n */\nexport function DropOverlay({\n  visible,\n  state,\n  message = \"Solte o arquivo aqui\",\n  doneMessage = \"Recebido\",\n  className,\n}: {\n  /** Atalho para `state=\"over\"`. Mantido porque é o uso de uma linha. */\n  visible?: boolean;\n  /** Quando presente, manda em `visible`. */\n  state?: \"idle\" | \"over\" | \"drop\";\n  message?: string;\n  doneMessage?: string;\n  className?: string;\n}) {\n  const estado = state ?? (visible ? \"over\" : \"idle\");\n  const solto = estado === \"drop\";\n\n  return (\n    <div\n      aria-hidden={estado === \"idle\" ? \"true\" : undefined}\n      // `role=\"status\"` só nos estados que dizem algo: um live region que existe\n      // o tempo todo com texto fixo faz o leitor de tela repetir sozinho.\n      role={estado === \"idle\" ? undefined : \"status\"}\n      data-state={estado}\n      // A cor do estado aceito sai de `--brand` por `color-mix`, e vai em\n      // `style` porque é valor calculado — classe utilitária com `color-mix`\n      // dentro depende de escape e não sobrevive a um build de terceiro.\n      style={\n        solto\n          ? {\n              borderColor: \"var(--brand)\",\n              background: \"color-mix(in oklab, var(--brand) 16%, var(--background))\",\n            }\n          : undefined\n      }\n      className={cn(\n        \"pointer-events-none absolute inset-0 z-50 flex items-center justify-center\",\n        \"rounded-[var(--r-surface)] border-2\",\n        \"transition-[opacity,border-color,background-color,transform]\",\n        \"[transition-duration:var(--dur-base,220ms)] [transition-timing-function:var(--ease-out)]\",\n        \"motion-reduce:transition-none\",\n        estado === \"idle\" && \"scale-[0.985] opacity-0\",\n        estado === \"over\" &&\n          \"scale-100 border-dashed border-ring/50 bg-background/70 opacity-100 backdrop-blur-sm\",\n        solto && \"scale-100 border-solid opacity-100\",\n        estado === \"idle\" && \"border-dashed border-transparent\",\n        className,\n      )}\n    >\n      {/* O ícone troca junto com a frase: seta pra baixo é o convite, o visto é\n          o recibo. Texto em `foreground` nos dois estados: a marca pinta borda\n          e fundo, nunca a letra (contraste de 1,3:1 no tema claro). */}\n      <span className=\"flex flex-col items-center gap-2 text-center text-foreground\">\n        <span\n          aria-hidden=\"true\"\n          className={cn(\n            \"grid size-9 place-items-center rounded-full border\",\n            solto ? \"border-[var(--brand)] bg-[var(--brand)] text-[var(--brand-foreground)]\" : \"border-border bg-card\",\n          )}\n        >\n          {solto ? <Check className=\"size-4\" /> : <ArrowDownToLine className=\"size-4\" />}\n        </span>\n        <span className=\"text-sm font-medium\">{solto ? doneMessage : message}</span>\n      </span>\n    </div>\n  );\n}\n",
      "type": "registry:ui"
    }
  ],
  "meta": {
    "uso": {
      "quando": "Área de upload que mostra a camada de soltar quando um arquivo é arrastado por cima",
      "evite": "Upload só por botão, sem arrasto; a área vazia antes do primeiro upload, empty-state"
    }
  },
  "categories": [
    "feedback",
    "upload",
    "dnd"
  ],
  "type": "registry:ui"
}