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}
);
})}
);
};