{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "p-group-23",
  "description": "Group with filter label, combobox multi-select, and remove button",
  "registryDependencies": [
    "@coss/avatar",
    "@coss/badge",
    "@coss/button",
    "@coss/combobox",
    "@coss/group"
  ],
  "files": [
    {
      "path": "registry/default/particles/p-group-23.tsx",
      "content": "\"use client\";\n\nimport {\n  ChevronsUpDownIcon,\n  FunnelIcon,\n  SearchIcon,\n  XIcon,\n} from \"lucide-react\";\nimport { useState } from \"react\";\nimport { cn } from \"@/registry/default/lib/utils\";\nimport {\n  Avatar,\n  AvatarFallback,\n  AvatarImage,\n} from \"@/registry/default/ui/avatar\";\nimport { Badge } from \"@/registry/default/ui/badge\";\nimport { Button, buttonVariants } from \"@/registry/default/ui/button\";\nimport {\n  Combobox,\n  ComboboxEmpty,\n  ComboboxInput,\n  ComboboxItem,\n  ComboboxList,\n  ComboboxPopup,\n  ComboboxTrigger,\n} from \"@/registry/default/ui/combobox\";\nimport { Group, GroupSeparator, GroupText } from \"@/registry/default/ui/group\";\n\ntype FilterOption = {\n  id: string;\n  label: string;\n  avatar?: string;\n};\n\nconst members: FilterOption[] = [\n  {\n    avatar:\n      \"https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=64&h=64&fit=crop&crop=faces\",\n    id: \"alex-chen\",\n    label: \"Alex Chen\",\n  },\n  {\n    avatar:\n      \"https://images.unsplash.com/photo-1494790108377-be9c29b29330?w=64&h=64&fit=crop&crop=faces\",\n    id: \"sarah-johnson\",\n    label: \"Sarah Johnson\",\n  },\n  {\n    avatar:\n      \"https://images.unsplash.com/photo-1500648767791-00dcc994a43e?w=64&h=64&fit=crop&crop=faces\",\n    id: \"marcus-williams\",\n    label: \"Marcus Williams\",\n  },\n  {\n    avatar:\n      \"https://images.unsplash.com/photo-1438761681033-6461ffad8d80?w=64&h=64&fit=crop&crop=faces\",\n    id: \"emma-davis\",\n    label: \"Emma Davis\",\n  },\n  {\n    avatar:\n      \"https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?w=64&h=64&fit=crop&crop=faces\",\n    id: \"james-miller\",\n    label: \"James Miller\",\n  },\n];\n\nfunction getInitials(name: string): string {\n  const parts = name.trim().split(/\\s+/);\n  if (parts.length === 1) {\n    return parts[0]?.charAt(0).toUpperCase() ?? \"\";\n  }\n  const first = parts[0]?.charAt(0) ?? \"\";\n  const last = parts[parts.length - 1]?.charAt(0) ?? \"\";\n  return (first + last).toUpperCase();\n}\n\nfunction MemberAvatar({\n  name,\n  avatarUrl,\n  className,\n}: {\n  name: string;\n  avatarUrl?: string;\n  className?: string;\n}) {\n  return (\n    <Avatar className={cn(\"size-5\", className)}>\n      {avatarUrl ? <AvatarImage alt={name} src={avatarUrl} /> : null}\n      <AvatarFallback className=\"text-[0.5rem]\">\n        {getInitials(name)}\n      </AvatarFallback>\n    </Avatar>\n  );\n}\n\nexport default function Particle() {\n  const [selectedMembers, setSelectedMembers] = useState<FilterOption[]>(\n    members.slice(0, 2),\n  );\n\n  const renderTriggerContent = () => {\n    if (selectedMembers.length === 0) return \"Select\";\n    const firstMember = selectedMembers[0];\n    const remainingCount = selectedMembers.length - 1;\n\n    return (\n      <div className=\"flex items-center gap-2\">\n        <MemberAvatar\n          avatarUrl={firstMember?.avatar}\n          name={firstMember?.label ?? \"\"}\n        />\n        <span className=\"truncate\">{firstMember?.label}</span>\n        {remainingCount > 0 && (\n          <Badge className=\"tabular-nums\" variant=\"secondary\">\n            +{remainingCount}\n          </Badge>\n        )}\n      </div>\n    );\n  };\n\n  return (\n    <Group>\n      <GroupText\n        className={cn(\n          buttonVariants({\n            size: \"sm\",\n            variant: \"outline\",\n          }),\n          \"pointer-events-none\",\n        )}\n      >\n        <FunnelIcon />\n        Member\n      </GroupText>\n      <GroupSeparator />\n      <Combobox\n        autoHighlight\n        items={members}\n        multiple\n        onValueChange={(value) => {\n          if (Array.isArray(value)) {\n            setSelectedMembers(value);\n          }\n        }}\n        value={selectedMembers}\n      >\n        <ComboboxTrigger\n          render={\n            <Button\n              className={\n                selectedMembers.length === 0 ? \"justify-between\" : undefined\n              }\n              size=\"sm\"\n              variant=\"outline\"\n            />\n          }\n        >\n          {renderTriggerContent()}\n          {selectedMembers.length === 0 && (\n            <ChevronsUpDownIcon className=\"-me-1!\" />\n          )}\n        </ComboboxTrigger>\n        <ComboboxPopup aria-label=\"Select member\">\n          <div className=\"border-b p-2\">\n            <ComboboxInput\n              className=\"rounded-md before:rounded-[calc(var(--radius-md)-1px)]\"\n              placeholder=\"Search members...\"\n              showTrigger={false}\n              startAddon={<SearchIcon />}\n            />\n          </div>\n          <ComboboxEmpty>No members found.</ComboboxEmpty>\n          <ComboboxList>\n            {(option: FilterOption) => (\n              <ComboboxItem key={option.id} value={option}>\n                <div className=\"flex items-center gap-2\">\n                  <MemberAvatar avatarUrl={option.avatar} name={option.label} />\n                  <span>{option.label}</span>\n                </div>\n              </ComboboxItem>\n            )}\n          </ComboboxList>\n        </ComboboxPopup>\n      </Combobox>\n      <GroupSeparator />\n      <Button\n        aria-label=\"Remove filter\"\n        onClick={() => setSelectedMembers([])}\n        size=\"icon-sm\"\n        variant=\"outline\"\n      >\n        <XIcon />\n      </Button>\n    </Group>\n  );\n}\n",
      "type": "registry:block"
    }
  ],
  "categories": [
    "avatar",
    "badge",
    "button",
    "combobox",
    "group"
  ],
  "type": "registry:block"
}