Focus search when going back to weapon landing

This commit is contained in:
Kalle
2026-10-03 17:10:19 +03:00
parent 4d5d73eae0
commit 4db8700feb
4 changed files with 38 additions and 3 deletions

View File

@@ -2,7 +2,7 @@ import clsx from "clsx";
import { Search as SearchIcon } from "lucide-react";
import * as React from "react";
import { useTranslation } from "react-i18next";
import { Link } from "react-router";
import { Link, useLocation } from "react-router";
import { BackLink } from "~/components/BackLink";
import { CircleBackdrop } from "~/components/CircleBackdrop";
import { EmptyState } from "~/components/EmptyState";
@@ -11,6 +11,7 @@ import { Input } from "~/components/Input";
import { PageHeader } from "~/components/PageHeader";
import { WeaponPoolBanner } from "~/components/WeaponPoolBanner";
import type { AnyWeapon } from "~/features/build-analyzer/analyzer-types";
import { DESKTOP_LAYOUT_QUERY } from "~/hooks/useLayoutSize";
import type {
MainWeaponId,
SpecialWeaponId,
@@ -41,6 +42,12 @@ type WeaponLandingCategory = NonNullable<
type SubOrSpecialWeapon = Extract<AnyWeapon, { type: "SUB" | "SPECIAL" }>;
/** Location state accepted by the weapon landing (e.g. from a weapon page's back link). */
export interface WeaponLandingState {
/** If set, the search is focused on load on desktop. */
focusSearch?: boolean;
}
interface WeaponLandingWeapon {
id: MainWeaponId;
subWeaponId: SubWeaponId;
@@ -82,6 +89,8 @@ export function WeaponLanding<W extends WeaponLandingWeapon>({
const { t } = useTranslation(["common", "weapons"]);
const [rawCategory] = useSearchParam(weaponLandingSearchParams, "category");
const [searchTerm, setSearchTerm] = React.useState("");
const searchInputRef = React.useRef<HTMLInputElement>(null);
useFocusSearchOnReturn(searchInputRef);
const categories: WeaponLandingCategory[] = [
...weaponCategories.map((weaponCategory) => weaponCategory.name),
@@ -198,6 +207,7 @@ export function WeaponLanding<W extends WeaponLandingWeapon>({
})}
>
<Input
ref={searchInputRef}
className={styles.search}
icon={<SearchIcon />}
value={searchTerm}
@@ -274,6 +284,21 @@ export function WeaponLanding<W extends WeaponLandingWeapon>({
);
}
/** Returning from a weapon page on desktop lets the user search for the next weapon right away. */
function useFocusSearchOnReturn(ref: React.RefObject<HTMLInputElement | null>) {
const location = useLocation();
const focusSearch = (location.state as WeaponLandingState | null)
?.focusSearch;
React.useEffect(() => {
if (!focusSearch || !window.matchMedia(DESKTOP_LAYOUT_QUERY).matches) {
return;
}
ref.current?.focus();
}, [focusSearch, ref]);
}
function WeaponRow({
weapon,
href,

View File

@@ -29,7 +29,10 @@ import { Main } from "~/components/Main";
import { PageHeader } from "~/components/PageHeader";
import { Placeholder } from "~/components/Placeholder";
import { Table } from "~/components/Table";
import { WeaponLanding } from "~/components/WeaponLanding";
import {
WeaponLanding,
type WeaponLandingState,
} from "~/components/WeaponLanding";
import { useUser } from "~/features/auth/core/user";
import { objectDamageCalculatorPage } from "~/features/object-damage-calculator/calculator-urls";
import { FULL_GROUP_SIZE } from "~/features/sendouq/q-constants";
@@ -1122,6 +1125,7 @@ function WeaponHeader({ mainWeaponId }: { mainWeaponId: MainWeaponId }) {
weapon: null,
category: weaponIdToCategory(mainWeaponId),
})}
state={{ focusSearch: true } satisfies WeaponLandingState}
/>
}
/>

View File

@@ -19,6 +19,7 @@ import { FilterBar } from "~/components/filter-bar/FilterBar";
import { Image, ModeImage, WeaponImage } from "~/components/Image";
import { Main } from "~/components/Main";
import { PageHeader } from "~/components/PageHeader";
import type { WeaponLandingState } from "~/components/WeaponLanding";
import { weaponLandingSearchParams } from "~/components/weapon-landing-search-params";
import { possibleApValues } from "~/features/build-analyzer/analyzer-constants";
import { useDateTimeFormat } from "~/hooks/intl/useDateTimeFormat";
@@ -145,6 +146,7 @@ export default function WeaponsBuildsPage() {
to={weaponLandingSearchParams.href(BUILDS_PAGE, {
category: weaponIdToCategory(data.weaponId),
})}
state={{ focusSearch: true } satisfies WeaponLandingState}
/>
}
/>

View File

@@ -21,7 +21,10 @@ import {
import { Label } from "~/components/Label";
import { Main } from "~/components/Main";
import { PageHeader } from "~/components/PageHeader";
import { WeaponLanding } from "~/components/WeaponLanding";
import {
WeaponLanding,
type WeaponLandingState,
} from "~/components/WeaponLanding";
import { possibleApValues } from "~/features/build-analyzer/analyzer-constants";
import type { AnyWeapon } from "~/features/build-analyzer/analyzer-types";
import type {
@@ -264,6 +267,7 @@ function WeaponHeader({ weapon }: { weapon: AnyWeapon }) {
weapon: null,
category: weaponLandingCategory(weapon),
})}
state={{ focusSearch: true } satisfies WeaponLandingState}
/>
}
/>