From 4db8700feb4ed028a46bb0e705e2346d4092b552 Mon Sep 17 00:00:00 2001 From: Kalle <38327916+Sendouc@users.noreply.github.com> Date: Sat, 3 Oct 2026 17:10:19 +0300 Subject: [PATCH] Focus search when going back to weapon landing --- app/components/WeaponLanding.tsx | 27 ++++++++++++++++++- .../build-analyzer/routes/analyzer.tsx | 6 ++++- app/features/builds/routes/builds.$slug.tsx | 2 ++ .../routes/object-damage-calculator.tsx | 6 ++++- 4 files changed, 38 insertions(+), 3 deletions(-) diff --git a/app/components/WeaponLanding.tsx b/app/components/WeaponLanding.tsx index afbd71fc3..c8e5d1fb1 100644 --- a/app/components/WeaponLanding.tsx +++ b/app/components/WeaponLanding.tsx @@ -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; +/** 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({ const { t } = useTranslation(["common", "weapons"]); const [rawCategory] = useSearchParam(weaponLandingSearchParams, "category"); const [searchTerm, setSearchTerm] = React.useState(""); + const searchInputRef = React.useRef(null); + useFocusSearchOnReturn(searchInputRef); const categories: WeaponLandingCategory[] = [ ...weaponCategories.map((weaponCategory) => weaponCategory.name), @@ -198,6 +207,7 @@ export function WeaponLanding({ })} > } value={searchTerm} @@ -274,6 +284,21 @@ export function WeaponLanding({ ); } +/** Returning from a weapon page on desktop lets the user search for the next weapon right away. */ +function useFocusSearchOnReturn(ref: React.RefObject) { + 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, diff --git a/app/features/build-analyzer/routes/analyzer.tsx b/app/features/build-analyzer/routes/analyzer.tsx index 0b5be959f..c91af5847 100644 --- a/app/features/build-analyzer/routes/analyzer.tsx +++ b/app/features/build-analyzer/routes/analyzer.tsx @@ -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} /> } /> diff --git a/app/features/builds/routes/builds.$slug.tsx b/app/features/builds/routes/builds.$slug.tsx index 9abdf2ccd..d95821182 100644 --- a/app/features/builds/routes/builds.$slug.tsx +++ b/app/features/builds/routes/builds.$slug.tsx @@ -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} /> } /> diff --git a/app/features/object-damage-calculator/routes/object-damage-calculator.tsx b/app/features/object-damage-calculator/routes/object-damage-calculator.tsx index 61ec98f2e..52c02dd1f 100644 --- a/app/features/object-damage-calculator/routes/object-damage-calculator.tsx +++ b/app/features/object-damage-calculator/routes/object-damage-calculator.tsx @@ -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} /> } />