diff --git a/app/features/settings/components/MatchProfileTab.tsx b/app/features/settings/components/MatchProfileTab.tsx index 594b6a913..4ac054cd2 100644 --- a/app/features/settings/components/MatchProfileTab.tsx +++ b/app/features/settings/components/MatchProfileTab.tsx @@ -1,3 +1,4 @@ +import * as React from "react"; import { useLoaderData } from "react-router"; import { ModeImage } from "~/components/Image"; import type { Preference, UserMapModePreferences } from "~/db/tables"; @@ -98,6 +99,18 @@ function MapModePreferencesField({ onChange({ ...value, pool: filtered }); }; + const pickableModes = modesShort.filter((mode) => { + const mp = value.modes.find((p) => p.mode === mode); + return mp?.preference !== "AVOID"; + }); + + const [selectedMode, setSelectedMode] = React.useState( + modesShort[0], + ); + const activeMode = pickableModes.includes(selectedMode) + ? selectedMode + : pickableModes[0]; + return (
@@ -121,22 +134,16 @@ function MapModePreferencesField({ })}
-
- {modesShort.map((mode) => { - const mp = value.modes.find((p) => p.mode === mode); - if (mp?.preference === "AVOID") return null; - - return ( - p.mode === mode)?.stages ?? []} - onChange={(stages) => handlePoolChange(mode, stages)} - /> - ); - })} -
+ {activeMode ? ( + p.mode === activeMode)?.stages ?? []} + onChange={(stages) => handlePoolChange(activeMode, stages)} + /> + ) : null}
); } diff --git a/app/features/settings/components/ModeMapPoolPicker.module.css b/app/features/settings/components/ModeMapPoolPicker.module.css index a5555722a..435d0db55 100644 --- a/app/features/settings/components/ModeMapPoolPicker.module.css +++ b/app/features/settings/components/ModeMapPoolPicker.module.css @@ -110,3 +110,42 @@ border-bottom: 2px dotted var(--color-bg-higher); } } + +.modeTabs { + display: flex; + gap: var(--s-1); +} + +.modeTab { + display: grid; + place-items: center; + padding: var(--s-1-5); + border: var(--border-style); + border-radius: var(--radius-full); + background-color: transparent; + + & img { + filter: grayscale(100%); + opacity: 0.5; + transition: + filter 0.2s, + opacity 0.2s; + } + + &:hover img { + opacity: 0.8; + } + + &:focus-visible { + outline: var(--focus-ring); + } +} + +.modeTabActive { + background-color: var(--color-bg-higher); + + & img { + filter: none; + opacity: 1; + } +} diff --git a/app/features/settings/components/ModeMapPoolPicker.tsx b/app/features/settings/components/ModeMapPoolPicker.tsx index 99ea5f85e..5c3d8d6ce 100644 --- a/app/features/settings/components/ModeMapPoolPicker.tsx +++ b/app/features/settings/components/ModeMapPoolPicker.tsx @@ -17,12 +17,17 @@ export function ModeMapPoolPicker({ pool, tiebreaker, onChange, + modeTabs, + onModeChange, }: { mode: ModeShort; amountToPick: number; pool: StageId[]; tiebreaker?: StageId; onChange: (stages: StageId[]) => void; + /** When provided, the divider becomes a tab switcher between these modes. */ + modeTabs?: ModeShort[]; + onModeChange?: (mode: ModeShort) => void; }) { const [wigglingStageId, setWigglingStageId] = React.useState( null, @@ -77,7 +82,30 @@ export function ModeMapPoolPicker({ })} - + {modeTabs && onModeChange ? ( +
+ {modeTabs.map((tabMode) => { + const active = tabMode === mode; + + return ( + + ); + })} +
+ ) : ( + + )}
{stageIds.map((stageId) => { diff --git a/e2e/settings.spec.ts b/e2e/settings.spec.ts index fdccc0d0a..78a727ba6 100644 --- a/e2e/settings.spec.ts +++ b/e2e/settings.spec.ts @@ -116,6 +116,9 @@ const setModePreference = ( const mapButton = (page: Page, mode: string, stageId: number) => page.getByTestId(`map-pool-${mode}-${stageId}`); +const selectModeTab = (page: Page, mode: string) => + page.getByTestId(`map-pool-mode-tab-${mode}`).click(); + const SELECTED_MAP_CLASS = /mapButtonGreyedOut/; // The seeded user already has random map pools, so empty the mode's pool to get @@ -141,6 +144,7 @@ test.describe("Match profile map preferences", () => { await navigate({ page, url: SETTINGS_PAGE }); await setModePreference(page, "SZ", "Prefer"); + await selectModeTab(page, "SZ"); await clearMapPool(page, "SZ"); await mapButton(page, "SZ", 1).click(); await expect(mapButton(page, "SZ", 1)).toHaveClass(SELECTED_MAP_CLASS); @@ -150,6 +154,7 @@ test.describe("Match profile map preferences", () => { await isNotVisible(mapButton(page, "SZ", 1)); await setModePreference(page, "SZ", "Prefer"); + await selectModeTab(page, "SZ"); await expect(mapButton(page, "SZ", 1)).toHaveClass(SELECTED_MAP_CLASS); }); @@ -162,9 +167,11 @@ test.describe("Match profile map preferences", () => { // Save a map pool for both SZ and TC (stage 2 is not banned in TC). await setModePreference(page, "SZ", "Prefer"); + await selectModeTab(page, "SZ"); await clearMapPool(page, "SZ"); await mapButton(page, "SZ", 1).click(); await setModePreference(page, "TC", "Prefer"); + await selectModeTab(page, "TC"); await clearMapPool(page, "TC"); await mapButton(page, "TC", 2).click(); await submit(page);