/ src / components / shared / agent-picker-list.tsx
agent-picker-list.tsx
 1  'use client'
 2  
 3  import { AgentAvatar } from '@/components/agents/agent-avatar'
 4  import { CheckIcon } from '@/components/shared/check-icon'
 5  import type { Agent } from '@/types'
 6  
 7  interface Props {
 8    agents: Agent[]
 9    /** Currently selected agent ID(s). String for single-select, string[] for multi-select. */
10    selected: string | string[]
11    /** Called when an agent is clicked. In multi mode, caller should toggle; in single mode, set. */
12    onSelect: (agentId: string) => void
13    /** Show a "None" option at the top for optional single-select */
14    noneOption?: { label: string; onSelect: () => void }
15    /** Show delegation-capable badge */
16    showDelegationBadge?: boolean
17    /** Max height of the scrollable list */
18    maxHeight?: number
19  }
20  
21  export function AgentPickerList({
22    agents,
23    selected,
24    onSelect,
25    noneOption,
26    showDelegationBadge,
27    maxHeight = 220,
28  }: Props) {
29    const isSelected = (id: string) =>
30      Array.isArray(selected) ? selected.includes(id) : selected === id
31    const noneSelected = Array.isArray(selected) ? selected.length === 0 : !selected
32  
33    if (agents.length === 0 && !noneOption) {
34      return <p className="text-[13px] text-text-3">No agents configured.</p>
35    }
36  
37    return (
38      <div
39        className="flex flex-col gap-1 rounded-[14px] border border-white/[0.06] bg-surface p-1.5 overflow-y-auto"
40        style={{ maxHeight }}
41      >
42        {noneOption && (
43          <button
44            onClick={noneOption.onSelect}
45            className={`relative flex items-center gap-3 px-3 py-2.5 rounded-[10px] cursor-pointer transition-all w-full text-left border-none
46              ${noneSelected ? 'bg-accent-soft' : 'bg-transparent hover:bg-white/[0.03]'}`}
47            style={{ fontFamily: 'inherit' }}
48          >
49            {noneSelected && (
50              <div className="absolute left-0 top-2 bottom-2 w-[2.5px] rounded-full bg-accent-bright" />
51            )}
52            <div className="w-[28px] h-[28px] rounded-full bg-white/[0.06] flex items-center justify-center shrink-0">
53              <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" className={noneSelected ? 'text-accent-bright' : 'text-text-3'}>
54                <circle cx="12" cy="12" r="10" /><line x1="8" y1="12" x2="16" y2="12" />
55              </svg>
56            </div>
57            <span className={`text-[13px] font-600 flex-1 ${noneSelected ? 'text-accent-bright' : 'text-text-2'}`}>
58              {noneOption.label}
59            </span>
60          </button>
61        )}
62        {agents.map((a) => {
63          const active = isSelected(a.id)
64          return (
65            <button
66              key={a.id}
67              onClick={() => onSelect(a.id)}
68              className={`relative flex items-center gap-3 px-3 py-2.5 rounded-[10px] cursor-pointer transition-all w-full text-left border-none
69                ${active ? 'bg-accent-soft' : 'bg-transparent hover:bg-white/[0.03]'}`}
70              style={{ fontFamily: 'inherit' }}
71            >
72              {active && (
73                <div className="absolute left-0 top-2 bottom-2 w-[2.5px] rounded-full bg-accent-bright" />
74              )}
75              <AgentAvatar seed={a.avatarSeed || null} avatarUrl={a.avatarUrl} name={a.name} size={28} />
76              <span className={`text-[13px] font-600 flex-1 truncate ${active ? 'text-accent-bright' : 'text-text-2'}`}>
77                {a.name}
78              </span>
79              {showDelegationBadge && a.delegationEnabled && (
80                <span className="text-[10px] text-text-3/60 flex items-center gap-0.5">
81                  <svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round"><path d="M16 3h5v5"/><path d="M21 3l-7 7"/><path d="M8 21H3v-5"/><path d="M3 21l7-7"/></svg>
82                </span>
83              )}
84              {active && <CheckIcon className="text-accent-bright shrink-0" />}
85            </button>
86          )
87        })}
88      </div>
89    )
90  }