import * as React from 'react'; import { getTeamColorSet, getThemedBadge } from '@renderer/constants/teamColors'; import { useTheme } from '@renderer/hooks/useTheme'; import { cn } from '@renderer/lib/utils'; import { formatAgentRole } from '@renderer/utils/formatAgentRole'; import { agentAvatarUrl, buildMemberAvatarMap, buildMemberColorMap, } from '@renderer/utils/memberHelpers'; import { Command as CommandPrimitive } from 'cmdk'; import { Check, ChevronsUpDown } from 'lucide-react'; import { Popover, PopoverContent, PopoverTrigger } from './popover'; import type { ResolvedTeamMember } from '@shared/types'; interface MemberSelectProps { members: ResolvedTeamMember[]; value: string | null; onChange: (value: string | null) => void; placeholder?: string; /** Show "Unassigned" option at the top of the list */ allowUnassigned?: boolean; /** Size variant */ size?: 'sm' | 'md'; disabled?: boolean; className?: string; } const UNASSIGNED_VALUE = '__unassigned__'; export const MemberSelect = ({ members, value, onChange, placeholder = 'Select member...', allowUnassigned = false, size = 'sm', disabled = false, className, }: MemberSelectProps): React.JSX.Element => { const [open, setOpen] = React.useState(false); const [search, setSearch] = React.useState(''); const listboxId = React.useId(); const { isLight } = useTheme(); const colorMap = React.useMemo(() => buildMemberColorMap(members), [members]); const avatarMap = React.useMemo(() => buildMemberAvatarMap(members), [members]); const selectedMember = React.useMemo( () => (value ? members.find((m) => m.name === value) : null), [members, value] ); const avatarSize = size === 'md' ? 32 : 24; const avatarClass = size === 'md' ? 'size-6' : 'size-5'; const textSize = size === 'md' ? 'text-xs' : 'text-[10px]'; const triggerHeight = size === 'md' ? 'h-9' : 'h-8'; // eslint-disable-next-line sonarjs/function-return-type -- option renderer returns mixed node structure const renderMemberInline = (member: ResolvedTeamMember): React.ReactNode => { const resolvedColor = colorMap.get(member.name); const colors = getTeamColorSet(resolvedColor ?? ''); return ( {member.name === 'team-lead' ? 'lead' : member.name} ); }; return (
e.stopPropagation()} > No members found. {allowUnassigned && !search.trim() ? ( { onChange(null); setOpen(false); setSearch(''); }} className="relative flex w-full cursor-default select-none items-center rounded-sm px-2 py-1.5 text-xs outline-none data-[selected=true]:bg-[var(--color-surface-raised)] data-[selected=true]:text-[var(--color-text)]" > Unassigned {value === null ? ( ) : null} ) : null} {members .filter((m) => { if (!search.trim()) return true; const q = search.toLowerCase(); return ( m.name.toLowerCase().includes(q) || (m.role?.toLowerCase().includes(q) ?? false) || (m.agentType?.toLowerCase().includes(q) ?? false) ); }) .map((m) => { const isSelected = m.name === value; const resolvedColor = colorMap.get(m.name); const colors = getTeamColorSet(resolvedColor ?? ''); const role = formatAgentRole(m.role) ?? formatAgentRole(m.agentType); return ( { onChange(m.name); setOpen(false); setSearch(''); }} className="relative flex w-full cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-xs outline-none data-[selected=true]:bg-[var(--color-surface-raised)] data-[selected=true]:text-[var(--color-text)]" > {m.name === 'team-lead' ? 'lead' : m.name} {role ? ( {role} ) : null} {isSelected ? ( ) : null} ); })}
); };