UI: выбор игрока при создании партии — выпадашка в стиле подсказок
Вместо нативного select — кнопка-триггер (аватар + ник) с выпадающим списком всех игроков группы в стилистике подсказок инвайта; игроки, уже выбранные в других строках, не предлагаются (заодно исключены дубли). Для аватаров в списке MemberRead теперь отдаёт avatar_url. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,63 @@
|
||||
import { ChevronDown } from "lucide-react";
|
||||
import { useState } from "react";
|
||||
|
||||
import { Avatar } from "./Avatar";
|
||||
import { PlayerSuggestions, type PlayerSuggestion } from "./PlayerSuggestions";
|
||||
|
||||
/**
|
||||
* Выбор игрока «как select», но в стиле выпадающих подсказок ников:
|
||||
* кнопка-триггер с аватаром и ником, под ней список options. Родитель сам
|
||||
* фильтрует options (например, убирает выбранных в других строках).
|
||||
*/
|
||||
export function PlayerSelect({
|
||||
value,
|
||||
options,
|
||||
onPick,
|
||||
}: {
|
||||
value: number | "";
|
||||
options: PlayerSuggestion[];
|
||||
onPick: (userId: number | "") => void;
|
||||
}) {
|
||||
const [open, setOpen] = useState(false);
|
||||
const selected = value === "" ? null : options.find((o) => o.user_id === value) ?? null;
|
||||
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 ? (
|
||||
<>
|
||||
<Avatar url={selected.avatar_url} nickname={selected.nickname} size={24} />
|
||||
<span className="nick">{selected.nickname}</span>
|
||||
</>
|
||||
) : (
|
||||
<span className="picker-placeholder">— игрок —</span>
|
||||
)}
|
||||
<ChevronDown size={16} className="picker-chevron" />
|
||||
</button>
|
||||
{open && (
|
||||
<PlayerSuggestions
|
||||
items={options}
|
||||
onPick={(u) => {
|
||||
onPick(u.user_id);
|
||||
setOpen(false);
|
||||
}}
|
||||
onClear={
|
||||
selected
|
||||
? () => {
|
||||
onPick("");
|
||||
setOpen(false);
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -14,13 +14,28 @@ export interface PlayerSuggestion {
|
||||
export function PlayerSuggestions({
|
||||
items,
|
||||
onPick,
|
||||
onClear,
|
||||
}: {
|
||||
items: PlayerSuggestion[];
|
||||
onPick: (item: PlayerSuggestion) => void;
|
||||
// Необязательная строка сброса сверху списка («— игрок —» в PlayerSelect).
|
||||
onClear?: () => void;
|
||||
}) {
|
||||
if (items.length === 0) return null;
|
||||
if (items.length === 0 && !onClear) return null;
|
||||
return (
|
||||
<div className="suggest-list" role="listbox">
|
||||
{onClear && (
|
||||
<button
|
||||
type="button"
|
||||
className="suggest-row suggest-clear"
|
||||
onMouseDown={(e) => {
|
||||
e.preventDefault();
|
||||
onClear();
|
||||
}}
|
||||
>
|
||||
— игрок —
|
||||
</button>
|
||||
)}
|
||||
{items.map((u) => (
|
||||
<button
|
||||
key={u.user_id}
|
||||
|
||||
Reference in New Issue
Block a user