UI: рандом фракции — убрана подпись «случайная», добавлена анимация-барабан названий в строке

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-26 16:56:12 +03:00
co-authored by Claude Opus 4.8
parent 1438a17441
commit 4d365a373b
2 changed files with 61 additions and 18 deletions
+40 -18
View File
@@ -34,6 +34,8 @@ export function CreateMatchPage() {
const [rows, setRows] = useState<Draft[]>([emptyDraft(), emptyDraft()]);
const [error, setError] = useState<string | null>(null);
// Анимация-«барабан» рандома: для крутящейся строки — текущее мелькающее название.
const [spin, setSpin] = useState<Record<number, string | null>>({});
if (lm || lf) return <Spinner />;
if (!groupId) return <div className="muted">Нет активной группы.</div>;
@@ -51,13 +53,25 @@ export function CreateMatchPage() {
const removeRow = (idx: number) => setRows((rs) => rs.filter((_, i) => i !== idx));
const randomizeRow = async (idx: number) => {
const pool = factions ?? [];
if (pool.length === 0) return;
// Запускаем «барабан»: быстро мелькаем названиями фракций.
const ticker = setInterval(() => {
const name = pool[Math.floor(Math.random() * pool.length)].name_ru;
setSpin((s) => ({ ...s, [idx]: name }));
}, 60);
try {
const faction = await randomize.mutateAsync({
group_id: groupId,
exclude_faction_ids: takenFactionIds(idx),
});
await new Promise((r) => setTimeout(r, 700)); // даём барабану покрутиться
clearInterval(ticker);
setSpin((s) => ({ ...s, [idx]: null }));
update(idx, { faction_id: faction.id, was_random: true });
} catch (e) {
clearInterval(ticker);
setSpin((s) => ({ ...s, [idx]: null }));
toast.show(e instanceof ApiError ? e.message : "Ошибка рандома");
}
};
@@ -137,28 +151,36 @@ export function CreateMatchPage() {
</select>
<div className="row">
<select
value={row.faction_id}
onChange={(e) =>
update(idx, {
faction_id: e.target.value ? Number(e.target.value) : "",
was_random: false,
})
}
style={{ flex: 1 }}
{spin[idx] != null ? (
<div className="faction-spin" style={{ flex: 1 }}>{spin[idx]}</div>
) : (
<select
value={row.faction_id}
onChange={(e) =>
update(idx, {
faction_id: e.target.value ? Number(e.target.value) : "",
was_random: false,
})
}
style={{ flex: 1 }}
>
<option value="">— фракция —</option>
{(factions ?? []).map((f) => (
<option key={f.id} value={f.id}>
{f.name_ru}
</option>
))}
</select>
)}
<button
className="btn"
onClick={() => randomizeRow(idx)}
disabled={spin[idx] != null}
title="Случайная фракция"
>
<option value="">— фракция —</option>
{(factions ?? []).map((f) => (
<option key={f.id} value={f.id}>
{f.name_ru}
</option>
))}
</select>
<button className="btn" onClick={() => randomizeRow(idx)} title="Случайная фракция">
<Dices size={18} />
</button>
</div>
{row.was_random && row.faction_id !== "" && <span className="badge">🎲 случайная</span>}
</div>
))}
+21
View File
@@ -92,6 +92,27 @@ input:focus, select:focus, textarea:focus { border-color: var(--accent); }
.error-text { color: var(--danger); font-size: 13px; }
/* «Барабан» рандома фракции — мелькающее название вместо select */
.faction-spin {
display: flex;
align-items: center;
justify-content: center;
padding: 10px 12px;
border: 1px solid var(--accent);
border-radius: var(--radius-sm);
background: var(--surface-2);
color: var(--text);
font-weight: 700;
white-space: nowrap;
overflow: hidden;
animation: faction-roll 0.45s ease-in-out infinite;
}
@keyframes faction-roll {
0% { opacity: 0.5; transform: translateY(-3px); }
50% { opacity: 1; transform: translateY(0); }
100% { opacity: 0.5; transform: translateY(3px); }
}
/* Тумблер-переключатель (вкл/выкл) */
.switch { position: relative; display: inline-block; width: 44px; height: 26px; flex: 0 0 auto; }
.switch input {