PlayerSelect обобщён в PickerSelect (рендер опций задаёт родитель) — им оформлены оба поля создания партии. Фракции, выбранные в других строках, не предлагаются (сервер при рандоме и так их исключал); набор фракций по-прежнему приходит из available_factions группы с учётом дополнений. Выбранное значение ищется в полном списке, а не в отфильтрованном: после «Рандом всем» с повторами фракция строки может быть занята соседней. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
70 lines
2.5 KiB
TypeScript
70 lines
2.5 KiB
TypeScript
import { ChevronDown } from "lucide-react";
|
||
import { type ReactNode, useState } from "react";
|
||
|
||
/**
|
||
* Выпадающий выбор «как select» в стиле подсказок ников (.suggest-*):
|
||
* кнопка-триггер с выбранным значением, под ней список опций. Родитель сам
|
||
* фильтрует options (например, убирает выбранное в других строках) и передаёт
|
||
* selected, найденный в ПОЛНОМ списке: выбранное значение может отсутствовать
|
||
* в options (например, фракция занята другой строкой после «Рандом всем»).
|
||
*/
|
||
export function PickerSelect<T extends { id: number }>({
|
||
selected,
|
||
options,
|
||
placeholder,
|
||
renderOption,
|
||
renderValue,
|
||
onPick,
|
||
}: {
|
||
selected: T | null;
|
||
options: T[];
|
||
placeholder: string;
|
||
renderOption: (option: T) => ReactNode;
|
||
/** Отрисовка выбранного в триггере; по умолчанию — как renderOption. */
|
||
renderValue?: (option: T) => ReactNode;
|
||
onPick: (option: T) => void;
|
||
}) {
|
||
const [open, setOpen] = useState(false);
|
||
return (
|
||
<div className="suggest-wrap">
|
||
<button
|
||
type="button"
|
||
className="picker-trigger"
|
||
onClick={() => setOpen((v) => !v)}
|
||
onBlur={() => setOpen(false)}
|
||
onKeyDown={(e) => {
|
||
if (e.key === "Escape") setOpen(false);
|
||
}}
|
||
>
|
||
{selected ? (
|
||
(renderValue ?? renderOption)(selected)
|
||
) : (
|
||
<span className="picker-placeholder">{placeholder}</span>
|
||
)}
|
||
<ChevronDown size={16} className="picker-chevron" />
|
||
</button>
|
||
{open && options.length > 0 && (
|
||
<div className="suggest-list" role="listbox">
|
||
{options.map((o) => (
|
||
<button
|
||
key={o.id}
|
||
type="button"
|
||
className="suggest-row"
|
||
role="option"
|
||
// mousedown, а не click: mousedown срабатывает ДО blur триггера —
|
||
// иначе onBlur скрыл бы список раньше клика (см. PlayerSuggestions).
|
||
onMouseDown={(e) => {
|
||
e.preventDefault();
|
||
onPick(o);
|
||
setOpen(false);
|
||
}}
|
||
>
|
||
{renderOption(o)}
|
||
</button>
|
||
))}
|
||
</div>
|
||
)}
|
||
</div>
|
||
);
|
||
}
|