Files
ForbiddenStarsApp/frontend/src/components/PickerSelect.tsx
T
NotBigGhostandClaude Fable 5 93d81010f7 UI: выбор фракции — выпадашка в стиле выбора игрока, без занятых фракций
PlayerSelect обобщён в PickerSelect (рендер опций задаёт родитель) — им
оформлены оба поля создания партии. Фракции, выбранные в других строках,
не предлагаются (сервер при рандоме и так их исключал); набор фракций
по-прежнему приходит из available_factions группы с учётом дополнений.
Выбранное значение ищется в полном списке, а не в отфильтрованном:
после «Рандом всем» с повторами фракция строки может быть занята соседней.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 03:23:14 +03:00

70 lines
2.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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>
);
}