{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "preview-switch-hero",
  "type": "registry:ui",
  "title": "Preview Switch Hero",
  "description": "A split hero with an icon-rail that switches the product preview, a lead-capture column (badge, ratings, email form, CTAs, avatars), and a full-width logo strip. Responsive and theme-aware.",
  "dependencies": [
    "lucide-react"
  ],
  "files": [
    {
      "path": "registry/ruixenui/preview-switch-hero.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\nimport Link from \"next/link\";\nimport { ArrowUpRight, Mail, Star } from \"lucide-react\";\nimport { cn } from \"@/lib/utils\";\n\n/* ── types ───────────────────────────────────────────────────── */\n\nexport interface PreviewTab {\n  id: string;\n  /** Text label shown in the switcher rail. */\n  label: string;\n  /** Panel shown when this tab is active. All panels should share one size. */\n  media: React.ReactNode;\n}\n\nexport interface HeroRating {\n  source: string;\n  score: string;\n  /** Defaults to a filled star. */\n  icon?: React.ReactNode;\n}\n\nexport interface HeroLogo {\n  name: string;\n  /** Optional custom mark; falls back to the name as a text wordmark. */\n  logo?: React.ReactNode;\n}\n\nexport interface HeroAvatar {\n  initials?: string;\n  src?: string;\n}\n\nexport interface Cta {\n  label: string;\n  href?: string;\n}\n\nexport interface PreviewSwitchHeroProps {\n  /** Small pill above the title, e.g. `{ tag: \"NEW\", label: \"…\" }`. */\n  badge?: { tag?: string; label: React.ReactNode };\n  title: React.ReactNode;\n  description?: React.ReactNode;\n  ratings?: HeroRating[];\n  /**\n   * Show the email-capture field above the CTAs. When `false`, the CTAs render\n   * on their own as plain actions (no form/input). Default `true`.\n   */\n  showEmail?: boolean;\n  /** Label above the email field. */\n  emailLabel?: React.ReactNode;\n  emailPlaceholder?: string;\n  /** Called with the email on submit. */\n  onSubmit?: (email: string) => void;\n  /** Submits the form when no `href`; otherwise renders as a link. */\n  primaryCta?: Cta;\n  secondaryCta?: Cta;\n  avatars?: HeroAvatar[];\n  socialProof?: React.ReactNode;\n  /** Text tabs that switch the preview. */\n  tabs: PreviewTab[];\n  /** Logo strip rendered full-width below the split. */\n  logos?: HeroLogo[];\n  className?: string;\n}\n\n/* ── pieces ──────────────────────────────────────────────────── */\n\nfunction TabRail({\n  tabs,\n  active,\n  onSelect,\n}: {\n  tabs: PreviewTab[];\n  active: number;\n  onSelect: (i: number) => void;\n}) {\n  return (\n    <div\n      role=\"tablist\"\n      aria-label=\"Preview switcher\"\n      className=\"flex shrink-0 gap-2 overflow-x-auto [scrollbar-width:none] md:flex-col md:overflow-visible [&::-webkit-scrollbar]:hidden\"\n    >\n      {tabs.map((t, i) => {\n        const isActive = i === active;\n        return (\n          <button\n            key={t.id}\n            type=\"button\"\n            role=\"tab\"\n            aria-selected={isActive}\n            onClick={() => onSelect(i)}\n            className={cn(\n              \"whitespace-nowrap rounded-xl px-4 py-2.5 text-left text-sm transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background\",\n              isActive\n                ? \"bg-muted font-semibold text-foreground shadow-sm ring-1 ring-border\"\n                : \"font-medium text-muted-foreground hover:bg-muted/50 hover:text-foreground\",\n            )}\n          >\n            {t.label}\n          </button>\n        );\n      })}\n    </div>\n  );\n}\n\nfunction PreviewStack({\n  tabs,\n  active,\n}: {\n  tabs: PreviewTab[];\n  active: number;\n}) {\n  return (\n    <div className=\"relative w-full min-w-0 md:flex-1\">\n      {tabs.map((t, i) => {\n        const isActive = i === active;\n        return (\n          <div\n            key={t.id}\n            role=\"tabpanel\"\n            aria-hidden={!isActive}\n            className={cn(\n              \"transition-opacity duration-500\",\n              isActive\n                ? \"relative opacity-100\"\n                : \"pointer-events-none absolute inset-0 opacity-0\",\n            )}\n          >\n            {t.media}\n          </div>\n        );\n      })}\n    </div>\n  );\n}\n\nfunction CtaButton({\n  cta,\n  variant,\n  type,\n}: {\n  cta: Cta;\n  variant: \"primary\" | \"secondary\";\n  type?: \"submit\" | \"button\";\n}) {\n  const className = cn(\n    \"inline-flex h-10 items-center justify-center gap-2 whitespace-nowrap rounded-xl px-3.5 text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background\",\n    variant === \"primary\"\n      ? \"bg-primary text-primary-foreground shadow-sm hover:bg-primary/90\"\n      : \"bg-muted text-muted-foreground hover:bg-background hover:text-foreground hover:shadow-sm hover:ring-1 hover:ring-border\",\n  );\n  const body = (\n    <>\n      {cta.label}\n      {variant === \"primary\" && <ArrowUpRight className=\"size-4 shrink-0\" />}\n    </>\n  );\n  if (cta.href) {\n    return (\n      <Link href={cta.href} className={className}>\n        {body}\n      </Link>\n    );\n  }\n  return (\n    <button type={type ?? \"button\"} className={className}>\n      {body}\n    </button>\n  );\n}\n\n/* ── component ───────────────────────────────────────────────── */\n\nexport function PreviewSwitchHero({\n  badge,\n  title,\n  description,\n  ratings,\n  showEmail = true,\n  emailLabel = \"Enter email address\",\n  emailPlaceholder = \"you@example.com\",\n  onSubmit,\n  primaryCta,\n  secondaryCta,\n  avatars,\n  socialProof,\n  tabs,\n  logos,\n  className,\n}: PreviewSwitchHeroProps) {\n  const emailId = React.useId();\n  const [active, setActive] = React.useState(0);\n\n  // Tabs are click-driven; clicking one swaps the preview in place.\n  const handleSelect = (i: number) => setActive(i);\n\n  const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {\n    e.preventDefault();\n    const data = new FormData(e.currentTarget);\n    onSubmit?.(String(data.get(\"email\") ?? \"\"));\n  };\n\n  return (\n    <section\n      aria-label=\"Hero\"\n      className={cn(\"relative w-full bg-background\", className)}\n    >\n      <div>\n        <div className=\"mx-auto w-full max-w-7xl px-6 py-10 lg:py-14\">\n          <div className=\"flex flex-col-reverse justify-center gap-8 md:flex-row md:items-start md:gap-6 lg:gap-10 xl:gap-[72px]\">\n            {/* ── left: text-tab rail + switchable preview ─────── */}\n            <div\n              className={cn(\n                \"flex min-w-0 flex-col gap-5 md:w-[400px] md:shrink-0 md:flex-row md:gap-4 lg:w-[480px] lg:gap-6\",\n                // When a badge sits above the title, drop the rail + preview by\n                // the badge's footprint (md+) so their tops line up with the\n                // title rather than the badge.\n                badge && \"md:mt-11\",\n              )}\n            >\n              <TabRail tabs={tabs} active={active} onSelect={handleSelect} />\n              <PreviewStack tabs={tabs} active={active} />\n            </div>\n\n            {/* ── right: content (centered on mobile, left on md+) ── */}\n            <div className=\"flex min-w-0 flex-col items-center text-center md:max-w-[496px] md:flex-1 md:items-start md:text-left\">\n              {badge && (\n                <div className=\"mb-4 flex w-fit items-center gap-2 rounded-lg bg-muted py-1 pl-1.5 pr-2.5\">\n                  {badge.tag && (\n                    <span className=\"inline-flex h-4 items-center rounded-[5px] bg-background px-1.5 text-[10px] font-semibold uppercase tracking-wide text-primary shadow-sm\">\n                      {badge.tag}\n                    </span>\n                  )}\n                  <span className=\"text-sm text-muted-foreground\">\n                    {badge.label}\n                  </span>\n                </div>\n              )}\n\n              <h1 className=\"mb-4 text-balance text-3xl font-semibold tracking-tight text-foreground sm:text-4xl lg:mb-5 lg:text-5xl xl:text-[56px] xl:leading-[1.05]\">\n                {title}\n              </h1>\n\n              {description && (\n                <p className=\"text-balance text-base text-muted-foreground lg:text-lg\">\n                  {description}\n                </p>\n              )}\n\n              {ratings && ratings.length > 0 && (\n                <div className=\"mt-6 flex flex-wrap items-center justify-center gap-2 md:justify-start lg:mt-8\">\n                  {ratings.map((r, i) => (\n                    <div\n                      key={`${r.source}-${i}`}\n                      className=\"inline-flex items-center gap-1.5 rounded-full border border-border bg-muted/40 py-1 pl-2 pr-3\"\n                    >\n                      {r.icon ?? (\n                        <Star className=\"size-3.5 fill-amber-400 text-amber-400\" />\n                      )}\n                      <span className=\"text-sm font-semibold text-foreground\">\n                        {r.score}\n                      </span>\n                      <span className=\"text-sm text-muted-foreground\">\n                        {r.source}\n                      </span>\n                    </div>\n                  ))}\n                </div>\n              )}\n\n              {showEmail ? (\n                <form onSubmit={handleSubmit} className=\"mt-6 lg:mt-8\">\n                  <div className=\"mx-auto flex w-full max-w-[420px] flex-col gap-2 md:mx-0\">\n                    <label\n                      htmlFor={emailId}\n                      className=\"text-sm text-muted-foreground\"\n                    >\n                      {emailLabel}\n                    </label>\n                    <div className=\"flex items-center gap-2 rounded-xl border border-border bg-background px-3 shadow-sm transition focus-within:border-foreground focus-within:ring-2 focus-within:ring-ring\">\n                      <Mail className=\"size-5 shrink-0 text-muted-foreground\" />\n                      <input\n                        id={emailId}\n                        name=\"email\"\n                        type=\"email\"\n                        placeholder={emailPlaceholder}\n                        className=\"h-10 w-full bg-transparent text-sm text-foreground outline-none placeholder:text-muted-foreground\"\n                      />\n                    </div>\n\n                    {(primaryCta || secondaryCta) && (\n                      <div className=\"mt-2 flex flex-wrap justify-center gap-3 md:justify-start\">\n                        {primaryCta && (\n                          <CtaButton\n                            cta={primaryCta}\n                            variant=\"primary\"\n                            type=\"submit\"\n                          />\n                        )}\n                        {secondaryCta && (\n                          <CtaButton cta={secondaryCta} variant=\"secondary\" />\n                        )}\n                      </div>\n                    )}\n                  </div>\n                </form>\n              ) : (\n                (primaryCta || secondaryCta) && (\n                  <div className=\"mt-6 flex flex-wrap justify-center gap-3 md:justify-start lg:mt-8\">\n                    {primaryCta && (\n                      <CtaButton\n                        cta={primaryCta}\n                        variant=\"primary\"\n                        type=\"button\"\n                      />\n                    )}\n                    {secondaryCta && (\n                      <CtaButton cta={secondaryCta} variant=\"secondary\" />\n                    )}\n                  </div>\n                )\n              )}\n\n              {(avatars?.length || socialProof) && (\n                <div className=\"mt-6 flex flex-col items-center gap-y-3 md:flex-row\">\n                  {avatars && avatars.length > 0 && (\n                    <div className=\"flex items-center\">\n                      {avatars.map((a, i) => (\n                        <span\n                          key={i}\n                          className={cn(\n                            \"flex size-7 items-center justify-center overflow-hidden rounded-full border-2 border-background bg-muted text-[10px] font-medium text-muted-foreground\",\n                            i > 0 && \"-ml-2\",\n                          )}\n                        >\n                          {a.src ? (\n                            // eslint-disable-next-line @next/next/no-img-element\n                            <img\n                              src={a.src}\n                              alt=\"\"\n                              className=\"size-full object-cover\"\n                            />\n                          ) : (\n                            a.initials\n                          )}\n                        </span>\n                      ))}\n                    </div>\n                  )}\n                  {socialProof && (\n                    <span className=\"text-sm text-muted-foreground md:ml-3\">\n                      {socialProof}\n                    </span>\n                  )}\n                </div>\n              )}\n            </div>\n          </div>\n        </div>\n\n        {/* ── logo strip ─────────────────────────────────────── */}\n        {logos && logos.length > 0 && (\n          <div className=\"border-y border-border\">\n            <div className=\"mx-auto max-w-7xl lg:px-7\">\n              <div className=\"flex items-center overflow-x-auto [scrollbar-width:none] lg:overflow-visible [&::-webkit-scrollbar]:hidden\">\n                {logos.map((l, i) => (\n                  <div\n                    key={`${l.name}-${i}`}\n                    className=\"flex shrink-0 items-center lg:w-full lg:shrink\"\n                  >\n                    <div className=\"flex w-full items-center justify-center px-6 py-5 lg:px-0 lg:py-7\">\n                      {l.logo ?? (\n                        <span className=\"whitespace-nowrap text-base font-semibold tracking-tight text-muted-foreground\">\n                          {l.name}\n                        </span>\n                      )}\n                    </div>\n                    {i < logos.length - 1 && (\n                      <div\n                        aria-hidden\n                        className=\"h-9 w-px shrink-0 bg-border\"\n                      />\n                    )}\n                  </div>\n                ))}\n              </div>\n            </div>\n          </div>\n        )}\n      </div>\n    </section>\n  );\n}\n\nexport default PreviewSwitchHero;\n",
      "type": "registry:ui",
      "target": "components/ruixen/preview-switch-hero.tsx"
    }
  ]
}