{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "auth-card",
  "title": "Auth Card",
  "description": "Sign-in, sign-up, and verification shells with social providers.",
  "dependencies": [
    "lucide-react@^1.33.0"
  ],
  "registryDependencies": [
    "https://components.exe.xyz/r/button.json",
    "https://components.exe.xyz/r/checkbox.json",
    "https://components.exe.xyz/r/input-otp.json",
    "https://components.exe.xyz/r/input.json",
    "https://components.exe.xyz/r/label.json",
    "https://components.exe.xyz/r/separator.json",
    "https://components.exe.xyz/r/utils.json"
  ],
  "files": [
    {
      "path": "src/components/blocks/auth-card.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { Apple } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Button } from \"@/components/ui/button\"\nimport { Checkbox } from \"@/components/ui/checkbox\"\nimport { Input } from \"@/components/ui/input\"\nimport { InputOtp } from \"@/components/ui/input-otp\"\nimport { Label } from \"@/components/ui/label\"\nimport { Separator } from \"@/components/ui/separator\"\n\nexport type SocialBrand =\n  | \"google\"\n  | \"apple\"\n  | \"github\"\n  | \"gitlab\"\n  | \"microsoft\"\n  | \"x\"\n  | \"facebook\"\n  | \"linkedin\"\n  | \"discord\"\n  | \"slack\"\n  | \"figma\"\n  | \"notion\"\n  | \"dropbox\"\n  | \"spotify\"\n  | \"twitch\"\n  | \"reddit\"\n  | \"tiktok\"\n  | \"instagram\"\n  | \"telegram\"\n  | \"whatsapp\"\n  | \"amazon\"\n  | \"bitbucket\"\n  | \"auth0\"\n  | \"okta\"\n  | \"custom\"\n\nconst brandLabels: Record<Exclude<SocialBrand, \"custom\">, string> = {\n  google: \"Google\",\n  apple: \"Apple\",\n  github: \"GitHub\",\n  gitlab: \"GitLab\",\n  microsoft: \"Microsoft\",\n  x: \"X\",\n  facebook: \"Facebook\",\n  linkedin: \"LinkedIn\",\n  discord: \"Discord\",\n  slack: \"Slack\",\n  figma: \"Figma\",\n  notion: \"Notion\",\n  dropbox: \"Dropbox\",\n  spotify: \"Spotify\",\n  twitch: \"Twitch\",\n  reddit: \"Reddit\",\n  tiktok: \"TikTok\",\n  instagram: \"Instagram\",\n  telegram: \"Telegram\",\n  whatsapp: \"WhatsApp\",\n  amazon: \"Amazon\",\n  bitbucket: \"Bitbucket\",\n  auth0: \"Auth0\",\n  okta: \"Okta\",\n}\n\nconst copy = {\n  signin: {\n    title: \"Welcome back\",\n    description: \"Sign in to continue\",\n    submit: \"Sign in\",\n    switch: \"Create an account\",\n  },\n  signup: {\n    title: \"Create your account\",\n    description: \"Start a new workspace\",\n    submit: \"Create account\",\n    switch: \"Sign in\",\n  },\n  verify: {\n    title: \"Check your inbox\",\n    description: \"Enter the code we sent you\",\n    submit: \"Verify\",\n    switch: \"Back to sign in\",\n  },\n} as const\n\nexport function AuthCard({\n  mode = \"signin\",\n  layout = \"stacked\",\n  providers = [\"google\", \"apple\", \"github\"],\n  title,\n  description,\n  logo,\n  centered = false,\n  confirmPassword = false,\n  footnote,\n  media,\n  email,\n  codeLength = 6,\n  switchHref,\n  onSubmit,\n  onProvider,\n  onComplete,\n  onResend,\n  autoFocus = false,\n  className,\n}: {\n  mode?: \"signin\" | \"signup\" | \"verify\"\n  layout?: \"stacked\" | \"inline\" | \"grid\"\n  providers?: SocialBrand[]\n  title?: React.ReactNode\n  description?: React.ReactNode\n  logo?: React.ReactNode\n  centered?: boolean\n  confirmPassword?: boolean\n  footnote?: React.ReactNode\n  media?: React.ReactNode\n  email?: string\n  codeLength?: number\n  switchHref?: string\n  onSubmit?: (data: FormData) => void\n  onProvider?: (brand: SocialBrand) => void\n  onComplete?: (code: string) => void\n  onResend?: () => void\n  autoFocus?: boolean\n  className?: string\n}) {\n  const defaults = copy[mode]\n  const heading = title ?? defaults.title\n  const sub =\n    description ??\n    (mode === \"verify\" && email\n      ? `Enter the ${codeLength}-digit code we sent to ${email}`\n      : defaults.description)\n  const iconOnly = layout !== \"stacked\"\n  const showProviders = mode !== \"verify\" && providers.length > 0\n\n  return (\n    <div\n      data-slot=\"auth-card\"\n      className={cn(\n        \"w-full max-w-3xl overflow-hidden rounded-[2px] border border-border bg-card\",\n        media && \"md:grid md:grid-cols-2\",\n        className\n      )}\n    >\n      <form\n        className=\"flex flex-col gap-4 p-6\"\n        onSubmit={(event) => {\n          event.preventDefault()\n          onSubmit?.(new FormData(event.currentTarget))\n        }}\n      >\n        {logo ? <div className=\"mb-1\">{logo}</div> : null}\n        <div className={cn(centered && \"text-center\")}>\n          <h2 className=\"font-heading text-xl font-semibold tracking-tight\">\n            {heading}\n          </h2>\n          <p className=\"mt-1 text-sm text-muted-foreground\">{sub}</p>\n        </div>\n\n        {mode === \"verify\" ? (\n          <div className=\"flex flex-col items-center gap-3\">\n            <InputOtp\n              length={codeLength}\n              onComplete={onComplete}\n              aria-label=\"Verification code\"\n            />\n            <Button type=\"button\" variant=\"ghost\" size=\"sm\" onClick={onResend}>\n              Resend code\n            </Button>\n          </div>\n        ) : (\n          <div className=\"flex flex-col gap-3\">\n            {mode === \"signup\" ? (\n              <Field id=\"auth-name\" label=\"Full name\" required>\n                <Input\n                  id=\"auth-name\"\n                  name=\"name\"\n                  autoComplete=\"name\"\n                  required\n                  autoFocus={autoFocus}\n                />\n              </Field>\n            ) : null}\n            <Field id=\"auth-email\" label=\"Email\" required>\n              <Input\n                id=\"auth-email\"\n                name=\"email\"\n                type=\"email\"\n                autoComplete=\"email\"\n                required\n                autoFocus={autoFocus && mode === \"signin\"}\n              />\n            </Field>\n            <Field id=\"auth-password\" label=\"Password\" required>\n              <Input\n                id=\"auth-password\"\n                name=\"password\"\n                type=\"password\"\n                autoComplete={\n                  mode === \"signup\" ? \"new-password\" : \"current-password\"\n                }\n                required\n              />\n            </Field>\n            {mode === \"signup\" && confirmPassword ? (\n              <Field id=\"auth-confirm\" label=\"Confirm password\" required>\n                <Input\n                  id=\"auth-confirm\"\n                  name=\"confirmPassword\"\n                  type=\"password\"\n                  autoComplete=\"new-password\"\n                  required\n                />\n              </Field>\n            ) : null}\n            {mode === \"signin\" ? (\n              <div className=\"flex items-center justify-between gap-2\">\n                <label className=\"flex items-center gap-2 text-sm\">\n                  <Checkbox name=\"remember\" />\n                  Remember me\n                </label>\n                <Button type=\"button\" variant=\"link\" className=\"h-auto px-0\">\n                  Forgot password\n                </Button>\n              </div>\n            ) : (\n              <label className=\"flex items-start gap-2 text-sm\">\n                <Checkbox name=\"terms\" required className=\"mt-0.5\" />\n                <span>I agree to the terms of service and privacy policy</span>\n              </label>\n            )}\n            <Button type=\"submit\">{defaults.submit}</Button>\n          </div>\n        )}\n\n        {showProviders ? (\n          <>\n            <div className=\"flex items-center gap-3\">\n              <Separator className=\"flex-1\" />\n              <span className=\"font-mono text-[11px] text-muted-foreground uppercase\">\n                or continue with\n              </span>\n              <Separator className=\"flex-1\" />\n            </div>\n            <div\n              className={cn(\n                layout === \"stacked\" && \"flex flex-col gap-2\",\n                layout === \"inline\" && \"flex flex-row flex-wrap gap-2\",\n                layout === \"grid\" && \"grid grid-cols-2 gap-2 sm:grid-cols-4\"\n              )}\n            >\n              {providers.map((brand) => (\n                <SocialProviderButton\n                  key={brand}\n                  brand={brand}\n                  iconOnly={iconOnly}\n                  onPress={() => onProvider?.(brand)}\n                />\n              ))}\n            </div>\n          </>\n        ) : null}\n\n        {switchHref ? (\n          <p className=\"text-center text-sm text-muted-foreground\">\n            <a\n              href={switchHref}\n              className=\"text-primary underline-offset-4 hover:underline\"\n            >\n              {defaults.switch}\n            </a>\n          </p>\n        ) : null}\n\n        {footnote ? (\n          <p className=\"text-center text-xs text-muted-foreground\">\n            {footnote}\n          </p>\n        ) : null}\n      </form>\n      {media ? (\n        <div className=\"relative hidden min-h-80 overflow-hidden bg-muted md:block\">\n          {media}\n        </div>\n      ) : null}\n    </div>\n  )\n}\n\nfunction Field({\n  id,\n  label,\n  required,\n  children,\n}: {\n  id: string\n  label: string\n  required?: boolean\n  children: React.ReactNode\n}) {\n  return (\n    <div className=\"flex flex-col gap-1.5\">\n      <Label htmlFor={id}>\n        {label}\n        {required ? <span className=\"text-destructive\"> *</span> : null}\n      </Label>\n      {children}\n    </div>\n  )\n}\n\nfunction SocialProviderButton({\n  brand,\n  iconOnly,\n  onPress,\n}: {\n  brand: SocialBrand\n  iconOnly: boolean\n  onPress: () => void\n}) {\n  const label = brand === \"custom\" ? \"SSO\" : brandLabels[brand]\n  return (\n    <Button\n      type=\"button\"\n      variant=\"outline\"\n      size={iconOnly ? \"icon\" : \"default\"}\n      className={cn(\"bg-card\", !iconOnly && \"w-full justify-start gap-2\")}\n      aria-label={iconOnly ? `Continue with ${label}` : undefined}\n      onClick={onPress}\n    >\n      <BrandMark brand={brand} />\n      {iconOnly ? null : `Continue with ${label}`}\n    </Button>\n  )\n}\n\nfunction BrandMark({ brand }: { brand: SocialBrand }) {\n  if (brand === \"apple\") return <Apple className=\"size-4\" />\n  const letter =\n    brand === \"custom\" ? \"S\" : brandLabels[brand].slice(0, 1).toUpperCase()\n  return (\n    <span\n      aria-hidden\n      className=\"flex size-4 items-center justify-center font-heading text-[11px] leading-none font-semibold\"\n    >\n      {letter}\n    </span>\n  )\n}\n\nexport function AuthMediaCarousel({\n  slides,\n}: {\n  slides: { src: string; alt?: string }[]\n}) {\n  const [index, setIndex] = React.useState(0)\n  const [reduced, setReduced] = React.useState(false)\n\n  React.useEffect(() => {\n    const mq = window.matchMedia(\"(prefers-reduced-motion: reduce)\")\n    setReduced(mq.matches)\n    const onChange = () => setReduced(mq.matches)\n    mq.addEventListener(\"change\", onChange)\n    return () => mq.removeEventListener(\"change\", onChange)\n  }, [])\n\n  React.useEffect(() => {\n    if (reduced || slides.length < 2) return\n    const id = window.setInterval(() => {\n      setIndex((current) => (current + 1) % slides.length)\n    }, 4000)\n    return () => window.clearInterval(id)\n  }, [reduced, slides.length])\n\n  const active = reduced ? 0 : index\n\n  return (\n    <div className=\"relative size-full min-h-80\">\n      {slides.map((slide, slideIndex) => (\n        <img\n          key={slide.src}\n          src={slide.src}\n          alt={slide.alt ?? \"\"}\n          className={cn(\n            \"absolute inset-0 size-full object-cover transition-opacity duration-500\",\n            slideIndex === active ? \"opacity-100\" : \"opacity-0\",\n            reduced && \"duration-0\"\n          )}\n        />\n      ))}\n    </div>\n  )\n}\n",
      "type": "registry:block"
    }
  ],
  "type": "registry:block"
}