{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "clerk-org-switcher",
  "title": "Clerk Organization Switcher",
  "description": "Organization switcher dropdown with personal account, org list with role badges, and create organization option.",
  "dependencies": [
    "@clerk/nextjs"
  ],
  "registryDependencies": [],
  "files": [
    {
      "path": "registry/default/blocks/clerk/clerk-org-switcher/components/elements/clerk-org-switcher.tsx",
      "content": "\"use client\";\n\nimport { useCallback, useEffect, useRef, useState } from \"react\";\n\nimport { useOrganization, useOrganizationList, useUser } from \"@clerk/nextjs\";\n\nfunction cn(...classes: (string | boolean | undefined)[]) {\n  return classes.filter(Boolean).join(\" \");\n}\n\nfunction ChevronDownIcon({ className }: { className?: string }) {\n  return (\n    <svg\n      aria-hidden=\"true\"\n      className={className}\n      xmlns=\"http://www.w3.org/2000/svg\"\n      width=\"16\"\n      height=\"16\"\n      viewBox=\"0 0 24 24\"\n      fill=\"none\"\n      stroke=\"currentColor\"\n      strokeWidth=\"2\"\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n    >\n      <path d=\"m6 9 6 6 6-6\" />\n    </svg>\n  );\n}\n\nfunction CheckIcon({ className }: { className?: string }) {\n  return (\n    <svg\n      aria-hidden=\"true\"\n      className={className}\n      xmlns=\"http://www.w3.org/2000/svg\"\n      width=\"16\"\n      height=\"16\"\n      viewBox=\"0 0 24 24\"\n      fill=\"none\"\n      stroke=\"currentColor\"\n      strokeWidth=\"2\"\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n    >\n      <polyline points=\"20 6 9 17 4 12\" />\n    </svg>\n  );\n}\n\nfunction PlusIcon({ className }: { className?: string }) {\n  return (\n    <svg\n      aria-hidden=\"true\"\n      className={className}\n      xmlns=\"http://www.w3.org/2000/svg\"\n      width=\"16\"\n      height=\"16\"\n      viewBox=\"0 0 24 24\"\n      fill=\"none\"\n      stroke=\"currentColor\"\n      strokeWidth=\"2\"\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n    >\n      <path d=\"M5 12h14\" />\n      <path d=\"M12 5v14\" />\n    </svg>\n  );\n}\n\nfunction UserIcon({ className }: { className?: string }) {\n  return (\n    <svg\n      aria-hidden=\"true\"\n      className={className}\n      xmlns=\"http://www.w3.org/2000/svg\"\n      width=\"16\"\n      height=\"16\"\n      viewBox=\"0 0 24 24\"\n      fill=\"none\"\n      stroke=\"currentColor\"\n      strokeWidth=\"2\"\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n    >\n      <path d=\"M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2\" />\n      <circle cx=\"12\" cy=\"7\" r=\"4\" />\n    </svg>\n  );\n}\n\nconst AVATAR_COLORS = [\n  \"bg-violet-600\",\n  \"bg-blue-600\",\n  \"bg-emerald-600\",\n  \"bg-amber-600\",\n  \"bg-rose-600\",\n  \"bg-cyan-600\",\n  \"bg-fuchsia-600\",\n  \"bg-lime-600\",\n];\n\nfunction getAvatarColor(id: string) {\n  let hash = 0;\n  for (let i = 0; i < id.length; i++) {\n    hash = ((hash << 5) - hash + id.charCodeAt(i)) | 0;\n  }\n  return AVATAR_COLORS[Math.abs(hash) % AVATAR_COLORS.length];\n}\n\nfunction getInitials(name: string) {\n  return name\n    .split(\" \")\n    .map((w) => w[0])\n    .slice(0, 2)\n    .join(\"\")\n    .toUpperCase();\n}\n\nexport interface ClerkOrgSwitcherProps {\n  className?: string;\n  showPersonalAccount?: boolean;\n}\n\nexport function ClerkOrgSwitcher({\n  className,\n  showPersonalAccount = true,\n}: ClerkOrgSwitcherProps) {\n  const {\n    isLoaded: isOrgListLoaded,\n    userMemberships,\n    setActive,\n  } = useOrganizationList({ userMemberships: true });\n  const { isLoaded: isOrgLoaded, organization } = useOrganization();\n  const { isLoaded: isUserLoaded, user } = useUser();\n\n  const [open, setOpen] = useState(false);\n  const containerRef = useRef<HTMLDivElement>(null);\n\n  useEffect(() => {\n    if (!open) return;\n\n    function handleClickOutside(e: MouseEvent) {\n      if (\n        containerRef.current &&\n        !containerRef.current.contains(e.target as Node)\n      ) {\n        setOpen(false);\n      }\n    }\n\n    document.addEventListener(\"mousedown\", handleClickOutside);\n    return () => document.removeEventListener(\"mousedown\", handleClickOutside);\n  }, [open]);\n\n  const handleSelectOrg = useCallback(\n    (orgId: string) => {\n      setOpen(false);\n      setActive?.({ organization: orgId });\n    },\n    [setActive],\n  );\n\n  const handleSelectPersonal = useCallback(() => {\n    setOpen(false);\n    setActive?.({ organization: null });\n  }, [setActive]);\n\n  if (!isOrgListLoaded || !isOrgLoaded || !isUserLoaded) {\n    return (\n      <div\n        data-slot=\"clerk-org-switcher\"\n        className={cn(\"relative inline-block text-sm\", className)}\n      >\n        <div className=\"flex min-w-[220px] items-center gap-3 rounded-lg border border-border bg-card px-3 py-2\">\n          <span className=\"h-7 w-7 shrink-0 animate-pulse rounded-full bg-muted\" />\n          <span className=\"h-4 flex-1 animate-pulse rounded bg-muted\" />\n          <span className=\"h-4 w-4 shrink-0 animate-pulse rounded bg-muted\" />\n        </div>\n      </div>\n    );\n  }\n\n  const organizations =\n    userMemberships.data?.map((m) => ({\n      id: m.organization.id,\n      name: m.organization.name,\n      imageUrl: m.organization.imageUrl,\n      role: m.role,\n    })) ?? [];\n\n  const isPersonal = organization === null;\n  const activeOrg = organizations.find((o) => o.id === organization?.id);\n\n  return (\n    <div\n      ref={containerRef}\n      data-slot=\"clerk-org-switcher\"\n      className={cn(\"relative inline-block text-sm\", className)}\n    >\n      <button\n        data-slot=\"trigger\"\n        type=\"button\"\n        onClick={() => setOpen((prev) => !prev)}\n        className={cn(\n          \"flex w-full min-w-[220px] items-center gap-3 rounded-lg border border-border bg-card px-3 py-2 text-left transition-colors\",\n          \"hover:bg-accent focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring\",\n          open && \"bg-accent\",\n        )}\n      >\n        {isPersonal ? (\n          <span\n            data-slot=\"personal-avatar\"\n            className=\"flex h-7 w-7 shrink-0 items-center justify-center rounded-full bg-muted\"\n          >\n            <UserIcon className=\"h-3.5 w-3.5 text-muted-foreground\" />\n          </span>\n        ) : activeOrg ? (\n          activeOrg.imageUrl ? (\n            <img\n              src={activeOrg.imageUrl}\n              alt={activeOrg.name}\n              className=\"h-7 w-7 shrink-0 rounded-full object-cover\"\n            />\n          ) : (\n            <span\n              data-slot=\"org-avatar\"\n              className={cn(\n                \"flex h-7 w-7 shrink-0 items-center justify-center rounded-full text-xs font-semibold text-white\",\n                getAvatarColor(activeOrg.id),\n              )}\n            >\n              {getInitials(activeOrg.name)}\n            </span>\n          )\n        ) : null}\n\n        <span\n          data-slot=\"active-label\"\n          className=\"flex-1 truncate font-medium text-foreground\"\n        >\n          {isPersonal ? \"Personal account\" : activeOrg?.name}\n        </span>\n\n        <ChevronDownIcon\n          className={cn(\n            \"h-4 w-4 shrink-0 text-muted-foreground transition-transform duration-200\",\n            open && \"rotate-180\",\n          )}\n        />\n      </button>\n\n      <div\n        data-slot=\"dropdown\"\n        className={cn(\n          \"absolute left-0 z-50 mt-1 w-full origin-top rounded-lg border border-border bg-popover p-1 shadow-lg transition-all duration-150\",\n          open\n            ? \"scale-100 opacity-100\"\n            : \"pointer-events-none scale-95 opacity-0\",\n        )}\n      >\n        {showPersonalAccount && (\n          <>\n            <button\n              data-slot=\"personal-option\"\n              type=\"button\"\n              onClick={handleSelectPersonal}\n              className={cn(\n                \"flex w-full items-center gap-3 rounded-md px-2 py-2 text-left transition-colors\",\n                \"hover:bg-accent\",\n                isPersonal && \"bg-accent\",\n              )}\n            >\n              <span className=\"flex h-7 w-7 shrink-0 items-center justify-center rounded-full bg-muted\">\n                <UserIcon className=\"h-3.5 w-3.5 text-muted-foreground\" />\n              </span>\n\n              <div\n                data-slot=\"personal-info\"\n                className=\"flex flex-1 flex-col truncate\"\n              >\n                <span className=\"truncate text-sm font-medium text-foreground\">\n                  Personal account\n                </span>\n                <span className=\"truncate text-xs text-muted-foreground\">\n                  {user?.primaryEmailAddress?.emailAddress}\n                </span>\n              </div>\n\n              {isPersonal && (\n                <CheckIcon className=\"h-4 w-4 shrink-0 text-primary\" />\n              )}\n            </button>\n\n            <div data-slot=\"divider\" className=\"my-1 h-px bg-border\" />\n          </>\n        )}\n\n        <div data-slot=\"org-list\" className=\"flex flex-col\">\n          {organizations.map((org) => {\n            const isActive = org.id === organization?.id;\n\n            return (\n              <button\n                key={org.id}\n                data-slot=\"org-option\"\n                type=\"button\"\n                onClick={() => handleSelectOrg(org.id)}\n                className={cn(\n                  \"flex w-full items-center gap-3 rounded-md px-2 py-2 text-left transition-colors\",\n                  \"hover:bg-accent\",\n                  isActive && \"bg-accent\",\n                )}\n              >\n                {org.imageUrl ? (\n                  <img\n                    src={org.imageUrl}\n                    alt={org.name}\n                    className=\"h-7 w-7 shrink-0 rounded-full object-cover\"\n                  />\n                ) : (\n                  <span\n                    className={cn(\n                      \"flex h-7 w-7 shrink-0 items-center justify-center rounded-full text-xs font-semibold text-white\",\n                      getAvatarColor(org.id),\n                    )}\n                  >\n                    {getInitials(org.name)}\n                  </span>\n                )}\n\n                <div className=\"flex flex-1 items-center gap-2 truncate\">\n                  <span className=\"truncate text-sm font-medium text-foreground\">\n                    {org.name}\n                  </span>\n                  {org.role && (\n                    <span\n                      data-slot=\"role-badge\"\n                      className=\"shrink-0 rounded bg-secondary px-1.5 py-0.5 text-[10px] font-medium text-muted-foreground\"\n                    >\n                      {org.role}\n                    </span>\n                  )}\n                </div>\n\n                {isActive && (\n                  <CheckIcon className=\"h-4 w-4 shrink-0 text-primary\" />\n                )}\n              </button>\n            );\n          })}\n        </div>\n\n        <div data-slot=\"divider\" className=\"my-1 h-px bg-border\" />\n\n        <button\n          data-slot=\"create-org\"\n          type=\"button\"\n          onClick={() => setOpen(false)}\n          className=\"flex w-full items-center gap-3 rounded-md px-2 py-2 text-left text-muted-foreground transition-colors hover:bg-accent hover:text-foreground\"\n        >\n          <span className=\"flex h-7 w-7 shrink-0 items-center justify-center rounded-full border border-dashed border-border\">\n            <PlusIcon className=\"h-3.5 w-3.5\" />\n          </span>\n          <span className=\"text-sm font-medium\">Create organization</span>\n        </button>\n      </div>\n    </div>\n  );\n}\n",
      "type": "registry:component"
    }
  ],
  "docs": "A complete organization switcher with personal account, org list with role badges (Owner/Admin/Member), active indicator, and create org button. Wire up with Clerk's useOrganizationList() hook.",
  "categories": [
    "clerk"
  ],
  "type": "registry:block"
}