fix(awards): stop the follow-list flicker/lost clicks (inline rows, no nested component)

The Row helper was defined inside AwardsSelectionPanel, so each render minted a
new component type and React remounted the whole list on every click — flicker,
and clicks lost between mousedown and click (hence needing many tries). Inline
the row markup so the keyed buttons keep a stable identity.
This commit is contained in:
2026-08-06 17:32:25 +02:00
parent faf084ddfc
commit f2cc4f11cd
+16 -12
View File
@@ -980,16 +980,6 @@ function AwardsSelectionPanel({ profile }: { profile?: { name?: string; callsign
&& (needle === '' || `${a.code} ${a.name}`.toLowerCase().includes(needle))); && (needle === '' || `${a.code} ${a.name}`.toLowerCase().includes(needle)));
const trackedItems = tracked.map((c) => byCode.get(c)).filter(Boolean) as { code: string; name: string }[]; const trackedItems = tracked.map((c) => byCode.get(c)).filter(Boolean) as { code: string; name: string }[];
const Row = ({ a, arrow, onClick }: { a: { code: string; name: string }; arrow: 'right' | 'left'; onClick: () => void }) => (
<button type="button" onClick={onClick}
className="group w-full flex items-center gap-2 rounded-md px-2 py-1 text-left hover:bg-primary/10">
{arrow === 'left' && <span className="text-muted-foreground opacity-0 group-hover:opacity-100"></span>}
<span className="font-mono text-xs shrink-0">{a.code}</span>
<span className="text-xs text-muted-foreground truncate flex-1">{a.name}</span>
{arrow === 'right' && <span className="text-primary opacity-0 group-hover:opacity-100"></span>}
</button>
);
return ( return (
<div> <div>
<SectionHeader title={t('sec.awards')} hint={t('awards.followHint')} /> <SectionHeader title={t('sec.awards')} hint={t('awards.followHint')} />
@@ -1006,7 +996,14 @@ function AwardsSelectionPanel({ profile }: { profile?: { name?: string; callsign
<Input value={q} onChange={(e) => setQ(e.target.value)} placeholder={t('awards.search')} className="h-8" /> <Input value={q} onChange={(e) => setQ(e.target.value)} placeholder={t('awards.search')} className="h-8" />
</div> </div>
<div className="max-h-[340px] overflow-y-auto p-1.5 space-y-0.5"> <div className="max-h-[340px] overflow-y-auto p-1.5 space-y-0.5">
{available.map((a) => <Row key={a.code} a={a} arrow="right" onClick={() => persist([...tracked, a.code])} />)} {available.map((a) => (
<button key={a.code} type="button" onClick={() => persist([...tracked, a.code])}
className="group w-full flex items-center gap-2 rounded-md px-2 py-1 text-left hover:bg-primary/10">
<span className="font-mono text-xs shrink-0">{a.code}</span>
<span className="text-xs text-muted-foreground truncate flex-1">{a.name}</span>
<span className="text-primary opacity-0 group-hover:opacity-100"></span>
</button>
))}
{available.length === 0 && <div className="p-2 text-xs text-muted-foreground">{t('awards.allTracked')}</div>} {available.length === 0 && <div className="p-2 text-xs text-muted-foreground">{t('awards.allTracked')}</div>}
</div> </div>
</div> </div>
@@ -1017,7 +1014,14 @@ function AwardsSelectionPanel({ profile }: { profile?: { name?: string; callsign
disabled={tracked.length === 0} onClick={() => persist([])}>{t('awards.clear')}</button> disabled={tracked.length === 0} onClick={() => persist([])}>{t('awards.clear')}</button>
</div> </div>
<div className="max-h-[392px] overflow-y-auto p-1.5 space-y-0.5"> <div className="max-h-[392px] overflow-y-auto p-1.5 space-y-0.5">
{trackedItems.map((a) => <Row key={a.code} a={a} arrow="left" onClick={() => persist(tracked.filter((c) => c !== a.code))} />)} {trackedItems.map((a) => (
<button key={a.code} type="button" onClick={() => persist(tracked.filter((c) => c !== a.code))}
className="group w-full flex items-center gap-2 rounded-md px-2 py-1 text-left hover:bg-primary/10">
<span className="text-muted-foreground opacity-0 group-hover:opacity-100"></span>
<span className="font-mono text-xs shrink-0">{a.code}</span>
<span className="text-xs text-muted-foreground truncate flex-1">{a.name}</span>
</button>
))}
{tracked.length === 0 && <div className="p-2 text-xs text-muted-foreground">{t('awards.noneFollowed')}</div>} {tracked.length === 0 && <div className="p-2 text-xs text-muted-foreground">{t('awards.noneFollowed')}</div>}
</div> </div>
</div> </div>