{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "newsletter-3",
  "title": "Signup Card With Form States",
  "description": "Newsletter signup card driven by a client state machine, cycling idle, loading spinner, success, and inline email validation error states.",
  "dependencies": [
    "@base-ui/react"
  ],
  "registryDependencies": [
    "button",
    "card",
    "input",
    "label",
    "spinner"
  ],
  "files": [
    {
      "path": "registry/blocks/newsletter/3/newsletter-block.tsx",
      "content": "\"use client\"\n\nimport { useEffect, useRef, useState } from \"react\"\nimport { cn } from \"@/lib/utils\"\nimport { Button } from \"@/components/ui/button\"\nimport {\n  Card,\n  CardContent,\n  CardDescription,\n  CardHeader,\n  CardTitle,\n} from \"@/components/ui/card\"\nimport { Input } from \"@/components/ui/input\"\nimport { Label } from \"@/components/ui/label\"\nimport { Spinner } from \"@/components/ui/spinner\"\nimport { IconPlaceholder } from \"@/components/icons/icon-placeholder\"\n\ntype Status = \"idle\" | \"loading\" | \"success\" | \"error\"\n\nconst EMAIL_PATTERN = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/\n\nexport default function NewsletterBlock() {\n  const [email, setEmail] = useState(\"\")\n  const [status, setStatus] = useState<Status>(\"idle\")\n  const [error, setError] = useState<string | null>(null)\n  const submitTimer = useRef<ReturnType<typeof setTimeout> | null>(null)\n\n  const isLoading = status === \"loading\"\n\n  useEffect(() => {\n    return () => {\n      if (submitTimer.current) clearTimeout(submitTimer.current)\n    }\n  }, [])\n\n  function handleSubmit(event: React.FormEvent<HTMLFormElement>) {\n    event.preventDefault()\n    if (isLoading) return\n\n    const value = email.trim()\n\n    if (value.length === 0) {\n      setStatus(\"error\")\n      setError(\"Please enter your email address.\")\n      return\n    }\n\n    if (!EMAIL_PATTERN.test(value)) {\n      setStatus(\"error\")\n      setError(\"That doesn’t look like a valid email address.\")\n      return\n    }\n\n    setError(null)\n    setStatus(\"loading\")\n\n    submitTimer.current = setTimeout(() => {\n      setStatus(\"success\")\n    }, 1400)\n  }\n\n  function reset() {\n    setEmail(\"\")\n    setError(null)\n    setStatus(\"idle\")\n  }\n\n  return (\n    <section className=\"flex w-full items-center justify-center bg-muted/30 px-6 py-16 text-foreground\">\n      <Card className=\"mx-auto w-full max-w-md\">\n        <CardHeader>\n          <span className=\"flex size-10 items-center justify-center rounded-md bg-primary/10 text-primary\">\n            <IconPlaceholder\n              lucide=\"Send\"\n              tabler=\"IconSend\"\n              hugeicons=\"MailSendIcon\"\n              phosphor=\"PaperPlane\"\n              remixicon=\"RiMailSendLine\"\n              size={20}\n              aria-hidden=\"true\"\n            />\n          </span>\n          <CardTitle className=\"mt-4 text-xl\">\n            Subscribe to the Acme digest\n          </CardTitle>\n          <CardDescription>\n            Product updates, engineering deep-dives, and early access. Once a\n            week, no spam.\n          </CardDescription>\n        </CardHeader>\n\n        <CardContent>\n          {status === \"success\" ? (\n            <div className=\"flex flex-col items-center gap-4 rounded-lg border border-border bg-card px-6 py-8 text-center\">\n              <span\n                className=\"flex size-12 items-center justify-center rounded-full bg-primary/10 text-primary\"\n                aria-hidden=\"true\"\n              >\n                <IconPlaceholder\n                  lucide=\"Check\"\n                  tabler=\"IconCheck\"\n                  hugeicons=\"Tick02Icon\"\n                  phosphor=\"Check\"\n                  remixicon=\"RiCheckLine\"\n                  size={26}\n                />\n              </span>\n              <div className=\"space-y-1\">\n                <p className=\"text-base font-medium\">You’re on the list</p>\n                <p className=\"text-sm text-muted-foreground\">\n                  We sent a confirmation to{\" \"}\n                  <span className=\"font-medium text-foreground\">{email}</span>.\n                  Check your inbox to finish signing up.\n                </p>\n              </div>\n              <Button\n                type=\"button\"\n                variant=\"outline\"\n                onClick={reset}\n                className=\"mt-1\"\n              >\n                <IconPlaceholder\n                  lucide=\"RefreshCw\"\n                  tabler=\"IconRefresh\"\n                  hugeicons=\"RefreshIcon\"\n                  phosphor=\"ArrowsClockwise\"\n                  remixicon=\"RiRefreshLine\"\n                  data-icon=\"inline-start\"\n                  aria-hidden=\"true\"\n                />\n                Use a different email\n              </Button>\n            </div>\n          ) : (\n            <form className=\"flex flex-col gap-3\" onSubmit={handleSubmit}>\n              <div className=\"flex flex-col gap-2\">\n                <Label htmlFor=\"newsletter-email\">Email address</Label>\n                <Input\n                  id=\"newsletter-email\"\n                  type=\"email\"\n                  inputMode=\"email\"\n                  autoComplete=\"email\"\n                  placeholder=\"you@company.com\"\n                  value={email}\n                  disabled={isLoading}\n                  aria-invalid={status === \"error\"}\n                  aria-describedby={\n                    status === \"error\" ? \"newsletter-error\" : undefined\n                  }\n                  onChange={(event) => {\n                    setEmail(event.target.value)\n                    if (status === \"error\") {\n                      setStatus(\"idle\")\n                      setError(null)\n                    }\n                  }}\n                  className={cn(\n                    status === \"error\" &&\n                      \"border-destructive focus-visible:ring-destructive/30\"\n                  )}\n                />\n                {status === \"error\" && error ? (\n                  <p\n                    id=\"newsletter-error\"\n                    role=\"alert\"\n                    className=\"flex items-center gap-1.5 text-sm text-destructive\"\n                  >\n                    <IconPlaceholder\n                      lucide=\"CircleAlert\"\n                      tabler=\"IconAlertCircle\"\n                      hugeicons=\"AlertCircleIcon\"\n                      phosphor=\"WarningCircle\"\n                      remixicon=\"RiErrorWarningLine\"\n                      size={15}\n                      className=\"shrink-0\"\n                      aria-hidden=\"true\"\n                    />\n                    {error}\n                  </p>\n                ) : null}\n              </div>\n\n              <Button type=\"submit\" disabled={isLoading} className=\"w-full\">\n                {isLoading ? (\n                  <>\n                    <Spinner data-icon=\"inline-start\" />\n                    <span className=\"shimmer\">Subscribing…</span>\n                  </>\n                ) : (\n                  <>\n                    <IconPlaceholder\n                      lucide=\"Send\"\n                      tabler=\"IconSend\"\n                      hugeicons=\"MailSendIcon\"\n                      phosphor=\"PaperPlane\"\n                      remixicon=\"RiMailSendLine\"\n                      data-icon=\"inline-start\"\n                      aria-hidden=\"true\"\n                    />\n                    Subscribe\n                  </>\n                )}\n              </Button>\n\n              <p className=\"text-center text-xs text-muted-foreground\">\n                Join 24,000+ developers. Unsubscribe anytime.\n              </p>\n            </form>\n          )}\n        </CardContent>\n      </Card>\n    </section>\n  )\n}\n",
      "type": "registry:component",
      "target": "components/blocks/newsletter-3.tsx"
    }
  ],
  "meta": {
    "height": "439px",
    "tier": "free"
  },
  "categories": [
    "newsletter"
  ],
  "type": "registry:block"
}