{
  "aliases": [
    "organization-switcher",
    "workspace-switcher",
    "received-invitations",
    "memberships"
  ],
  "code": "// Research basis: GitHub organization membership + Slack workspace switching/invitations.\n// Each row = recognizable identity (Avatar/logo, NOT an identical decorative icon) + name +\n// a \"現在\" (Current) badge for the active org + role + a permission-aware action menu.\nimport { Card, CardContent, CardHeader, CardTitle, ListRow, Badge, Avatar, AvatarImage, AvatarFallback, EmptyState } from \"@godxjp/ui/data-display\";\nimport { Button } from \"@godxjp/ui/general\";\nimport { Flex } from \"@godxjp/ui/layout\";\nimport { DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem } from \"@godxjp/ui/navigation\";\nimport { MoreHorizontal, Plus } from \"lucide-react\";\n\ntype Role = \"owner\" | \"admin\" | \"member\";\ntype Membership = { id: string; name: string; logoUrl?: string; role: Role; joinedAt: string; isCurrent: boolean; canManage: boolean };\ntype Invite = { id: string; orgName: string; invitedBy: string };\n\nconst ROLE_LABEL: Record<Role, string> = { owner: \"オーナー\", admin: \"管理者\", member: \"メンバー\" };\n\nexport function OrganizationMemberships({\n  memberships, invitations, onOpen, onSwitch, onManage, onLeave, onCreate, onAccept, onDecline,\n}: {\n  memberships: Membership[]; invitations: Invite[];\n  onOpen: (id: string) => void; onSwitch: (id: string) => void; onManage: (id: string) => void;\n  onLeave: (id: string) => void; onCreate: () => void; onAccept: (id: string) => void; onDecline: (id: string) => void;\n}) {\n  return (\n    <Flex direction=\"col\" gap=\"lg\">\n      <Card>\n        <CardHeader>\n          <Flex justify=\"between\" align=\"center\">\n            <CardTitle>組織</CardTitle>\n            {/* Create belongs at page/section level, not per row. */}\n            <Button variant=\"outline\" size=\"sm\" onClick={onCreate}><Plus className=\"size-4\" />組織を作成</Button>\n          </Flex>\n        </CardHeader>\n        {/* flush → ListRows draw their own edge-to-edge dividers. */}\n        <CardContent flush>\n          {memberships.length === 0 ? (\n            <EmptyState variant=\"section\" title=\"所属している組織がありません\" description=\"新しい組織を作成するか、招待を受け取ってください。\" />\n          ) : (\n            memberships.map((m) => (\n              <ListRow\n                key={m.id}\n                leading={\n                  <Avatar>\n                    {m.logoUrl ? <AvatarImage src={m.logoUrl} alt=\"\" /> : null}\n                    <AvatarFallback>{m.name.slice(0, 1)}</AvatarFallback>\n                  </Avatar>\n                }\n                title={\n                  <Flex gap=\"sm\" align=\"center\">\n                    <span className=\"font-medium\">{m.name}</span>\n                    {m.isCurrent && <Badge tone=\"success\" icon={null}>現在</Badge>}\n                  </Flex>\n                }\n                description={ROLE_LABEL[m.role]}\n                trailing={\n                  <Flex gap=\"sm\" align=\"center\">\n                    {m.isCurrent\n                      ? <Button size=\"sm\" onClick={() => onOpen(m.id)}>開く</Button>\n                      : <Button variant=\"outline\" size=\"sm\" onClick={() => onSwitch(m.id)}>切り替え</Button>}\n                    <DropdownMenu>\n                      <DropdownMenuTrigger asChild>\n                        <Button variant=\"ghost\" size=\"icon\" aria-label={`${m.name} の操作`}><MoreHorizontal className=\"size-4\" /></Button>\n                      </DropdownMenuTrigger>\n                      <DropdownMenuContent align=\"end\">\n                        <DropdownMenuItem onSelect={() => onOpen(m.id)}>開く</DropdownMenuItem>\n                        {m.canManage && <DropdownMenuItem onSelect={() => onManage(m.id)}>管理</DropdownMenuItem>}\n                        {m.role !== \"owner\" && <DropdownMenuItem onSelect={() => onLeave(m.id)}>退出</DropdownMenuItem>}\n                      </DropdownMenuContent>\n                    </DropdownMenu>\n                  </Flex>\n                }\n              />\n            ))\n          )}\n        </CardContent>\n      </Card>\n\n      {/* CONDITIONAL invitations — no pending invites ⇒ render NOTHING (no permanent empty Card).\n          Few pending ⇒ a compact actionable list. Many/history ⇒ link to a focused route instead. */}\n      {invitations.length > 0 && (\n        <Card>\n          <CardHeader><CardTitle>保留中の招待</CardTitle></CardHeader>\n          <CardContent flush>\n            {invitations.map((inv) => (\n              <ListRow\n                key={inv.id}\n                title={<span className=\"font-medium\">{inv.orgName}</span>}\n                description={`${inv.invitedBy} からの招待`}\n                trailing={\n                  <Flex gap=\"sm\">\n                    <Button size=\"sm\" onClick={() => onAccept(inv.id)}>参加</Button>\n                    <Button variant=\"ghost\" size=\"sm\" onClick={() => onDecline(inv.id)}>辞退</Button>\n                  </Flex>\n                }\n              />\n            ))}\n          </CardContent>\n        </Card>\n      )}\n    </Flex>\n  );\n}\n// DON'T: passive rows with no action · identical decorative icons · unlabeled timestamps\n// (label them: \"2024年に参加\") · a large empty invitation Card shown permanently.",
  "name": "organization-memberships",
  "tagline": "Workspace membership list — recognizable identity, current/active state, role, permission-aware Open/Switch/Manage/Leave row actions, page-level Create, and CONDITIONAL invitations (omit when none).",
  "tags": [
    "organization",
    "workspace",
    "membership",
    "invitation",
    "account",
    "switcher"
  ]
}
